委托调试¶
委托后端 是设备端模型的重要组成部分,因为它们在定义行为方面具有灵活性。这种灵活性的一个副作用是它作为一种不透明的转换操作。这会模糊在后处理中很有价值的丰富关联和变异。
例如,如果在委托中发生两种不同的算子融合,后处理将无法区分这两种转换。
具体来说,这使得通过委托图关联运行时信息(如性能分析结果)变得困难。委托调试标识符提供了一个框架,委托作者可以通过它传播此信息并将其用于运行后分析。
准备工作分为三个阶段
提前 (AOT):委托作者生成一个 **调试句柄映射**。
运行时:委托作者使用在 **调试句柄映射** 中提前注册的 **委托调试标识符** 进行日志记录。
反序列化:委托作者为委托事件中的自定义元数据提供解析器。
提前集成¶
委托作者通过从后端实现返回 **调试句柄映射** 来传播在 Lowered 后端中发生的转换。
生成调试句柄映射¶
**调试句柄映射** 通过将 **委托调试标识符** 映射到调试句柄来传达后端中发生的转换。
**委托调试标识符** 是生成或用户提供的标识符,用于表示运行时感兴趣的点。回想一下,调试句柄是模型图中算子实例的唯一标识符。
例如
{ 0: (10, 11), 1: (11, 12) }: 运行时中的标识符 0 和 1 分别对应于具有调试句柄 (10, 11) 和 (11, 12) 的算子。
{ “fused_op_1_2_3”: (11, 12, 15) }: 运行时中的标识符 “fused_op_1_2_3” 对应于具有调试句柄 (11, 12, 15) 的算子,其中 11, 12, 15 分别对应于算子 1、算子 2 和算子 3。
注意
标识符是将运行时结果连接到模型图的一种方式;标识符的解释由委托作者定义。
**调试句柄映射** 通过使用 **DelegateMappingBuilder** 构建,并作为 PreprocessResult
的一部分返回。
class PreprocessResult:
processed_bytes: bytes = bytes()
debug_handle_map: Optional[
Union[Dict[int, Tuple[int]], Dict[str, Tuple[int]]]
] = None
PreprocessResult
在 此处 定义。
DelegateMappingBuilder¶
DelegateMappingBuilder
是一个用于管理和构建调试句柄映射的辅助类。构建器的结果应在构建 PreprocessResult 时传入。
DelegateMappingBuilder
在 此处 定义。
一个 DelegateMappingBuilder
实例可以按以下两种模式之一构建:手动标识符或生成标识符。
# Manual Identifiers, Default
builder = DelegateMappingBuilder(generated_identifiers=False)
# Generated Identifiers
builder = DelegateMappingBuilder(generated_identifiers=True)
对于**手动标识符**,用户在创建条目时传入一个**委托调试标识符**。对于**生成标识符**,构建器将自动分配一个**委托调试标识符**。
要将条目添加到**调试句柄映射**,请使用 insert_delegate_mapping_entry
。它将一个或多个 fx.Node(s)
或调试句柄(源自 node.meta["debug_handle"])关联到一个可选的**委托调试标识符**(用于手动标识符)。调用会返回记录的标识符。
def insert_delegate_mapping_entry(
self,
nodes: Optional[Union[Node, List[Node]]] = None,
handles: Optional[Union[int, List[int]]] = None,
identifier: Optional[Union[int, str]] = None,
) -> Union[int, str]:
要检索**调试句柄映射**,请使用 get_delegate_mapping
。
def get_delegate_mapping(
self,
) -> Union[Dict[int, Tuple[int]], Dict[str, Tuple[int]]]
AOT 映射的演示可以在 此处 找到。
运行时日志记录¶
对应于 AOT 映射,运行时定义了记录这些事件的功能。
实时日志记录¶
ExecuTorch 允许您进行实时日志记录。**实时日志记录**在执行过程中 timestamp 可用时非常有用。它开销最小,且作者调用起来很直观。
要实时记录事件(例如,明确表示性能分析的开始和停止),使用 event_tracer_start_profiling_delegate
创建一个 EventEntry
,并使用 event_tracer_end_profiling_delegate
结束为提供的 EventTracer
创建的 EventEntry
。
要使用 event_tracer_start_profiling_delegate
启动一个 EventTracerEntry
,根据**委托调试标识符**的类型(分别为 str 和 int),将**委托调试标识符**(提前提供给 debug_handle_map
)作为 name 或 delegate_debug_id
参数传入。
EventTracerEntry event_tracer_start_profiling_delegate(
EventTracer* event_tracer,
const char* name,
DebugHandle delegate_debug_id)
要结束一个 EventTracerEntry
,只需向 event_tracer_end_profiling_delegate
提供原始的 EventTracerEntry
。
可选地,此时还可以记录额外的运行时 metadata
。
void event_tracer_end_profiling_delegate(
EventTracer* event_tracer,
EventTracerEntry event_tracer_entry,
const void* metadata = nullptr,
size_t metadata_len = 0)
事后日志记录¶
ExecuTorch 也允许您进行事后日志记录。有些运行时设置在执行时无法访问 timestamp。**事后日志记录**使作者仍然能够记录这些事件。
要事后记录事件(例如,同时记录开始时间和结束时间),调用 event_tracer_log_profiling_delegate
,并结合实时日志记录 API 中使用的参数和 timestamp。
void event_tracer_log_profiling_delegate(
EventTracer* event_tracer,
const char* name,
DebugHandle delegate_debug_id,
et_timestamp_t start_time,
et_timestamp_t end_time,
const void* metadata = nullptr,
size_t metadata_len = 0)
运行时代码的演示可以在 此处 找到。
从委托事件中暴露自定义元数据¶
如上所述的运行时日志记录 API 所示,用户可以记录一个字节数组以及其委托性能分析事件。我们通过 Inspector API 在后处理中向用户提供这些数据。
用户在创建 Inspector 实例时可以传递一个元数据解析器。解析器是一个可调用对象,用于反序列化数据并返回字符串列表或包含键值对的字典。反序列化后的数据随后会添加到事件块中的相应事件中,供用户使用。以下是如何编写此解析器的示例:
注意:反序列化器的输入是一个列表,其中每个条目是一个字节序列(本质上每个条目都是一个不可变的字节数组)。用户应遍历此列表,反序列化每个条目,然后以预期的格式(字符串列表或字典)返回。
Inspector(
etdump_path=etdump_path,
# Optional
etrecord=etrecord_path,
# Optional, only needed if debugging was enabled.
buffer_path=buffer_path,
delegate_metadata_parser=parse_delegate_metadata
)
def parse_delegate_metadata(delegate_metadatas: List[bytes]) -> Union[List[str], Dict[str, Any]]:
metadata_str = []
for metadata_bytes in delegate_metadatas:
metadata_str += str(metadata_bytes)
return metadata_str