Task Proto
本页中的所有地址、字段号和偏移都适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d)。该二进制文件未剥离符号 —— 完整 C++ 符号存在,且.text/.rodata/.lrodataVMA 等于文件偏移。其他版本会有所不同。
摘要
tensorflow.profiler.Task proto 是 xprof profiler 的逐 worker 设备元数据记录:每个 host/worker 一个消息,用于确定谁捕获了 profile(build provenance、host identity、启动命令行)、何时捕获(profile 窗口),以及 —— 对 device-trace timeline 最重要的部分 —— silicon 以什么时钟频率运行。它是一个扁平 proto3 消息,包含 18 个单一标量字段,逐字节从 descriptor pool FileDescriptorProto @ 0xbe999a0(路径 third_party/xprof/plugin/tensorboard_plugin_profile/protobuf/task.proto,package tensorflow.profiler)中切出。运行时它是 Map<uint32 task_id, Task> 的值 —— 每条记录以 worker ordinal 为键 —— 由 cheap-op gate xprof::XlaJfProfileCheapOps(Task const&) @ 0xf2ca280 读取。
18 个字段中有三个是时钟频率:tensor_core_freq_hz(f11)、sparse_core_freq_hz(f12)和 gtc_freq_hz(f13),全部为 uint64。它们是 riegeli Trace Container 上记录的 clock-domain conversion 的运行时配套常量:每个 device-plane XEvent 偏移都是 round(gtc · 1e9 / (clk << 4)),除数 clk 是 GTC 频率。本页提供这些常量;除法数学和 resync-aware 分段转换器位于 riegeli 页面。重新实现者必须先内化一个细节(详见内置与运行时时钟权威):libtpu 内的转换器不会读取 Task.gtc_freq_hz —— 它加载 .lrodata 0x1c60480 处的内置逐 DeviceType kHz 表。Task proto 的 *_freq_hz 字段是同一常量的 captured-profile 记录,暴露给事后工具。
Task proto 是身份与时钟,不是设备遥测。可与 tpu-telemetry proto 对比,后者是周期性设备状态快照(功耗、throttle、ECC、温度):telemetry 回答“芯片现在在做什么”;Task 回答“哪个 build、哪个 host、哪个 clock domain 产生了这个 profile”。profiler 通过 17 项 TaskEnvStatType 字典,将 Task 字段作为 XStat 暴露到 kTaskEnvPlaneName host plane;XPlane/XStat/TraceMe 页面负责 plane 组装机制,本页负责 Task→XStat 字段映射。值得注意的是,芯片的 PCI identity 和拓扑坐标不是标量 Task 字段 —— 芯片身份位于 device-trace container 中的 DeviceIdentifiers PCI 元组,拓扑则是自由形式 system_topology XStat 字符串。
对于重新实现,契约是:
- 18 字段 Task proto schema —— 字段号 → 名称 → proto 类型 → 语义组,全部逐字节来自 descriptor pool,包括三个时钟频率字段 f11/f12/f13。
- 时钟常量角色 —— 哪个字段馈入 device timebase(
gtc_freq_hz)、哪个是实际转换器输入(内置 kHz 表,而非 proto 字段),以及由此得到的 trace wrap period。 - 17 项
TaskEnvStatTypeXStat 字典 —— 从 str→enum init list @0x21c20f00恢复的 Task→host-plane surface 映射。 - 身份边界 —— 此 proto 中的 identity 是什么(host/build/command line),以及哪些位于别处(PCI chip identity、topology)。
| Proto | tensorflow.profiler.Task —— proto3,18 个单一标量字段 |
| Descriptor | FileDescriptorProto @ 0xbe999a0(路径 …/protobuf/task.proto,417 字节 DescriptorProto) |
| 运行时表 | Task::_table_ @ 0x2164fed0;Task_globals_ @ 0x22266028 |
| 容器 | Map<uint32 task_id, Task>(每个 worker 一条记录;TryEmplaceInternal @ 0xf2fa900) |
| Consumer | xprof::XlaJfProfileCheapOps(Task const&) @ 0xf2ca280(读取 clocks/version) |
| 时钟字段 | f11 tensor_core_freq_hz,f12 sparse_core_freq_hz,f13 gtc_freq_hz(全部 uint64) |
| 时钟字段字符串 | tensor_core_freq_hz @ 0xbe99b16,sparse_core_freq_hz @ 0xbe99b33,gtc_freq_hz @ 0xbe99b50 |
| 实际转换器输入 | 内置逐 DeviceType kHz 表 @ .lrodata 0x1c60480(步长 0x448,int32 @+4) |
| Host XStat 字典 | 17 项 TaskEnvStatType,str→enum init list @ 0x21c20f00;plane kTaskEnvPlaneName @ 0x21c9e0e8 |
Task Proto Schema
目的
Task 消息是 profiler session 元数据:一个 Map<uint32 task_id, Task> 在每次 profiling session 中填充一次,每个 host/worker 一个 entry。uint32 键是 worker ordinal;值携带产生二进制的 build、运行它的 host、profiler 捕获的时间窗口、逐引擎时钟频率以及 host resource caps。map 类型由 MapEntryFuncs<uint32, Task, FieldType 13, FieldType 11> / Map<uint32, Task>::TryEmplaceInternal @ 0xf2fa900 符号逐字节确认 —— FieldType 13 是 uint32 键,FieldType 11 是嵌入消息值。
字段映射
全部 18 个字段都是单一(label 1)proto3 标量,从 417 字节 DescriptorProto(FileDescriptorProto @ 0xbe999a0 内的 message_type[0],field-4 位于 +0x5e)中切出,并由 protoc --decode_raw 交叉检查。Proto 类型码:1=double、3=int64、4=uint64、8=bool、9=string、13=uint32。每个字段名都已与 .rodata 逐字确认(见字段字符串锚点)。
| f# | 名称 | 类型 | 分组 | 含义 |
|---|---|---|---|---|
| 1 | changelist | int64 | build | 构建该二进制所用的 source revision |
| 2 | clean_build | bool | build | 构建时 workspace 是 clean 还是 dirty |
| 3 | build_time | int64 | build | unix build timestamp |
| 4 | build_target | string | build | build-target label(作为 version string) |
| 5 | command_line | string | host | 启动命令行参数 |
| 6 | start_time | int64 | host | 进程启动时间 |
| 7 | task_address | string | host | worker BNS / network address |
| 8 | profile_time_ns | uint64 | window | profile start wall-clock,ns |
| 9 | profile_duration_ms | uint32 | window | profile 长度,ms |
| 10 | host_trace_level | uint32 | window | host TraceMe verbosity level |
| 11 | tensor_core_freq_hz | uint64 | clock | TensorCore cycle clock (Hz) |
| 12 | sparse_core_freq_hz | uint64 | clock | SparseCore cycle clock (Hz) |
| 13 | gtc_freq_hz | uint64 | clock | Global Time Counter clock (Hz) —— 时间基准除数 |
| 14 | peak_memory_usage | uint64 | resource | host RSS 峰值 |
| 15 | cpu_limit | double | resource | Borg CPU limit |
| 16 | cpu_usage | double | resource | Borg CPU usage |
| 17 | workspace_id | string | build | workspace identifier |
| 18 | snapshot | int64 | build | workspace snapshot id |
五个语义组并未在 proto 中声明 —— 它们是字段集的自然分区,也与 host XStat 字典遵循的分组一致:build provenance(1、2、3、4、17、18)、host/task identity(5、6、7)、profile window(8、9、10)、clock rates(11、12、13)、resource caps(14、15、16)。
注意 —— 编号并不按组连续:
workspace_id(17)和snapshot(18)是追加在 resource caps 后面的 build-provenance 字段,通常说明这些字段是在后续 proto revision 中添加的。重新实现必须以字段号为键,而不是以组内位置为键,因为 wire tag 是编号。
这里的 Identity 是什么,又不是什么
Task proto 的 identity 字段是 host/build identity,不是 chip identity:
- Host identity 是
task_address(f7,worker BNS/address)、command_line(f5)和start_time(f6)。uint32 task_idmap 键在一个 session 内区分 worker。 - Build identity 是
changelist/build_time/build_target/clean_build/workspace_id/snapshot—— libtpu 二进制自身的 version provenance。
重新实现者可能会期待的芯片级 identity —— PCI vendor/device 元组、芯片坐标、device ordinal —— 不在此 proto 中:
陷阱 —— 芯片的 PCI identity 是device-trace container 中携带的 12 字节
asic_sw.proto.DeviceIdentifiers元组(JfTrace.device_identifiers,字段 19),不是 Task 字段。fleet/topology 坐标暴露为自由形式system_topologyXStat 字符串(TaskEnvStatType15),不是结构化 Task 字段。去 Task proto 中寻找芯片坐标或 device ordinal 的重新实现将一无所获 —— 它们分别属于 device-trace container 的DeviceIdentifierskeying 和 host-plane topology XStat。
字段字符串锚点 {#field-string-anchors}
字段名连续位于序列化 descriptor blob(protodesc_cold section)中,紧跟在消息头之后。三个时钟频率字段是时间基准的主锚点,并已逐字节确认:
| 字段字符串 | descriptor addr |
|---|---|
tensor_core_freq_hz | 0xbe99b16 |
sparse_core_freq_hz | 0xbe99b33 |
gtc_freq_hz | 0xbe99b50 |
完整名称串跨越 0xbe99a03–0xbe99b50(Task、changelist、…、gtc_freq_hz);路径字符串 …tensorboard_plugin_profile/protobuf/task.proto 和 package tensorflow.profiler 均逐字存在,将 FileDescriptorProto 锚定在 0xbe999a0。
时钟频率字段与时间基准
目的
三个 uint64 时钟频率字段(f11/f12/f13)是 device-trace 流水线将原始硬件 tick 转换为墙钟皮秒所需的运行时时钟常量。每个引擎 —— TensorCore、SparseCore —— 都有自己的 cycle clock,用于将逐引擎 duration-cycle count 转换为时间;Global Time Counter 有第三个芯片范围时钟。消耗这些常量的转换数学由 riegeli Trace Container 负责;本节精确说明哪个常量扮演哪个角色,以及实际运行时值来自哪里。
角色
| 字段 | f# | 域 | 在时间基准中的角色 |
|---|---|---|---|
gtc_freq_hz | 13 | 芯片范围 Global Time Counter | device-timeline 除数:每个 device-plane XEvent 偏移都是 round(gtc · 1e9 / (gtc_freq_hz << 4)) |
tensor_core_freq_hz | 11 | TensorCore cycle clock | 将 TensorCore 逐事件 duration-cycle count 转换为时间 |
sparse_core_freq_hz | 12 | SparseCore cycle clock | 将 SparseCore 逐事件 duration-cycle count 转换为时间 |
gtc_freq_hz 的角色是核心:每个 16 字节 trace 包上的 TraceHeader.timestamp 是 ×16 定点的 GTC tick(低 4 位为小数),TpuXLineBuilder::AddEvent(GtcSpan) @ 0xf1df1e0 将其除以 gtc_freq · 16 以落到皮秒。两个 ×16 因子 —— tick 中的一个,以及除数 << 4 中的一个 —— 正好抵消。逐引擎 tensor_core_freq_hz/sparse_core_freq_hz 是从字段名推断出来,用于缩放编解码器解码出的逐 band duration-cycle counts;精确的逐子系统 subscriber 是哪个进行了除法,未端到端跟踪(精确逐 payload 绑定置信度 LOW;字段名称与类型为 CERTAIN)。
注意 —— GTC clock 使设备 timeline 在全局上保持一致。由于 GTC 是芯片范围且跨芯片同步(由
SetGtcConfiguration编程),一个频率即可把每个核心和每个芯片的事件转换到单一墙钟轴。相对地,逐引擎 TensorCore/SparseCore clocks 只需要扩展一个已经由 GTC 定位的事件上的相对 duration。这就是为什么 GTC clock 是 offset placement 中的除数,而 engine clocks 只缩放 durations。
内置与运行时时钟权威 {#the-baked-vs-runtime-clock-authority}
二进制中有两个 GTC 频率来源,重新实现者必须理解 libtpu 内转换器实际使用哪一个:
- 运行时
Task.gtc_freq_hz(f13,uint64Hz) —— 在 session time 捕获到 profile 中的 proto 字段。 - 内置逐 DeviceType 表,位于
.lrodata 0x1c60480(每个 DeviceType 步长0x448,int32 @+4= GTC freq,单位 kHz;int32 @+8= timestamp bit-width 48/45/64)。值包括700000kHz(pxc/jellyfish-class,48-bit ts)、800000/833000kHz(vxc/gxc 45-bit families)以及1333000kHz(一个 64-bit-ts jellyfish-class family)。
GtcSpanConverter 的 clock divisor 从内置逐 DeviceType kHz 表加载,而不是从 Task.gtc_freq_hz 加载。converter ctor @ 0xf2cb6e0 无条件执行 clk = table_base[DeviceType*0x448 + 4];AddEvent 中的除法是 gtc · 1e9 / (clk_khz << 4)。kHz 单位来自字节算术(gtc_x16=16 → 700000 kHz = 1428.571 ps,800000 = 1250.000 ps,833000 = 1200.480 ps,1333000 = 750.188 ps,全部匹配 ticks/(clk_khz·1000)·1e12)。Task proto 的 gtc_freq_hz 是同一常量的 captured-profile companion —— 时钟的线上记录,暴露给下游工具 —— 不是 in-binary converter 用来相除的值。
这对重新实现者有两方面影响:
- 如果重新实现 in-libtpu device→XPlane conversion,请完全按照二进制做法,以 DeviceType ordinal 为键,从逐 DeviceType kHz 表驱动除法。读取
Task.gtc_freq_hz并除以它(单位 Hz,而非 kHz)会因小数缩放偏离 16×,并且使用错误来源。 - 如果重新实现只有 serialized profile 的事后 trace reader,
Task.gtc_freq_hz就是你的权威 —— 内置表是 libtpu 内部数据,外部 reader 不可用。两者在数值上应一致(gtc_freq_hzHz ==clk_khz · 1000)。
捕获 profile 的运行时 Task.gtc_freq_hz 是否曾在 converter ctor 中覆盖内置 clk,未观察到 —— ctor 加载内置表,视野中没有 runtime-Hz override 路径(INFERRED;*_freq_hz 字段可能仅为事后 reader 存在)。override 问题标记为 LOW confidence;CERTAIN 的是 in-libtpu converter 使用内置 kHz 表。
Trace-Wrap Period
同一时钟常量决定 GTC 的固定宽度计数器多久 wrap。给定内置表中的 timestamp bit-width w(+8:48 / 45 / 64)和 GTC 频率:
wrap_period_s = 2^w / gtc_freq_hz
= 2^48 / 700e6 ≈ 4.0e5 s (pxc/vlc, 48-bit ts, 700 MHz)
= 2^45 / 800e6 ≈ 4.4e4 s (vfc/glc/gfc, 45-bit ts, 800 MHz)解析长 trace 的重新实现必须考虑此 wrap;resync-aware 分段转换器(GtcSpanConverter::TimespanFromGtcSpan @ 0xf2cb7e0,由 riegeli 页面负责)通过在 {walltime_ns, gtc} 校准锚点上二分搜索来处理中途 GTC reset,正因为单一扁平除法无法承受 counter wrap 或 resync。
TaskEnvStatType XStat 字典 {#the-taskenvstattype-xstat-dictionary}
目的
profiler 不会在 timeline 上将 Task proto 作为 proto 暴露 —— 它将选定的 Task 字段(加上一些派生值)渲染为 kTaskEnvPlaneName host plane 上的 XStat(plane name @ 0x21c9e0e8,形式为 /host:<n>)。从 stat name 到 enum id 的映射是 17 项 TaskEnvStatType enum,从 str→enum init list @ 0x21c20f00 恢复。XPlane/XStat 页面负责 plane 及其 XStatMetadata 的构建方式;本字典是 Task 专属 surface。
字典
17 条记录,步长 0x18(+0x10 处 enum = 值 1..17,字符串 {ptr@+0, len@+8},ptr 在加载时通过 R_X86_64_RELATIVE 重定位)。每个名称都已在 .rodata 中逐字确认。“← Task field”列是每个 stat 的 provenance:
| Enum | XStat name | .rodata str | ← Task field / derivation |
|---|---|---|---|
| 1 | build_changelist | 0x84d97b6 | changelist (f1) |
| 2 | build_snapshot | 0x84e35fb | snapshot (f18) |
| 3 | build_workspace_id | 0x86fd57b | workspace_id (f17) |
| 4 | clean_build | 0x86f91d7 | clean_build (f2) |
| 5 | build_time | 0x86bf23b | build_time (f3) |
| 6 | build_target | 0x8503333 | build_target (f4) |
| 7 | command_line_args | 0x854efee | command_line (f5) |
| 8 | process_start_time | 0x86be930 | start_time (f6) |
| 9 | task_bns | 0x8544af9 | task_address (f7) |
| 10 | profile_start_time | 0x86beaa0 | profile_time_ns (f8) |
| 11 | profile_stop_time | 0x86bec17 | profile_time_ns + profile_duration_ms (f8+f9) |
| 12 | peak_memory_usage | 0x86d7111 | peak_memory_usage (f14) |
| 13 | borg_cpu_limit | 0x84f9429 | cpu_limit (f15) |
| 14 | borg_cpu_usage | 0x86d7123 | cpu_usage (f16) |
| 15 | system_topology | 0x84b9c7f | topology coords(自由形式字符串,不是 Task 标量) |
| 16 | compilation_task_info | 0x85db5e3 | compiler/version metadata |
| 17 | profile_options | 0x85363f2 | 请求的 profile options |
该映射大多与 Task 字段 1:1,对三个派生/外部 entry 例外:profile_stop_time(11)是计算值(start + duration),system_topology(15)/ compilation_task_info(16)/ profile_options(17)来自 profiler session 中的其他位置,而不是标量 Task 字段。名称 1–14 是 Task 的逐字节精确投影;15–17 对绑定到某个来源为 HIGH-confidence(字符串逐字节精确,三个非 Task entry 的精确 producer 未跟踪)。
怪癖 —— 时钟频率字段 f11/f12/f13 不在这个 host-plane 字典中。clocks 馈入的是 device-plane timebase(GTC→ps 除法),不是 host Task-environment plane。期望在 host plane 上找到
gtc_freq_hzXStat 的重新实现会找不到 —— clocks 被 converter 以数值方式消耗,从不作为 host-plane attribute 暴露。host plane 只携带 identity/version/window/resource 事实。
查找机制
两个方向(name→enum 和 enum→name)都是构建一次并缓存的 absl::flat_hash_map:
// GetTaskEnvStatTypeStr @0x1c8eb8c0 / FindTaskEnvStatType @0x1c8eba20
// forward map (str->enum) @0x22579b30, reverse (enum->str) @0x22579b20, guard @0x22579b38
function BuildTaskEnvStatTypeMaps(): // StrMap lambda @0x1c8ec5e0
once (guard @0x22579b38): // call_once
m = flat_hash_map(/*capacity*/ 17); // mov $0x11,%edx @0x1c8ec770
for (name, enum) in init_list @0x21c20f00: // 17 records, stride 0x18
m.insert(name -> enum); // ptr field relocated R_X86_64_RELATIVE
forward = m; reverse = invert(m);init list 的 string-pointer 字段在文件镜像中为 0;实际 .rodata 地址是加载时应用的 R_X86_64_RELATIVE addend。map capacity 17(0x11)硬编码在 0x1c8ec770,与 enum 基数匹配。
Task 元数据流水线
profiler session start
│
▼
Map<uint32 task_id, Task> (one Task per host/worker; TryEmplaceInternal @0xf2fa900)
│ Task { changelist/build_*/snapshot/workspace_id [build provenance]
│ command_line/start_time/task_address [host identity]
│ profile_time_ns/profile_duration_ms/host_trace_level [window]
│ tensor_core_freq_hz/sparse_core_freq_hz/gtc_freq_hz [clock rates]
│ peak_memory_usage/cpu_limit/cpu_usage [resource caps] }
│
├──────────────► host plane (kTaskEnvPlaneName)
│ surfaced as XStats via TaskEnvStatType (17 entries):
│ build_changelist … task_bns … profile_start/stop_time …
│ system_topology · compilation_task_info · profile_options
│ (NB: the *_freq_hz clocks are NOT surfaced here)
│
└──────────────► device timebase [riegeli-trace-container.md]
gtc_freq_hz (f13) == the captured record of the GTC clock
│ in-libtpu converter divides by the BAKED per-DeviceType kHz table @0x1c60480,
│ not by this field — Task.gtc_freq_hz is the post-hoc-reader companion
▼
TpuXLineBuilder::AddEvent(GtcSpan) @0xf1df1e0
device_offset_ps = round(gtc · 1e9 / (clk_khz << 4))相关符号和偏移
| 符号 | 地址 | 作用 |
|---|---|---|
FileDescriptorProto (task.proto) | 0xbe999a0 | 切出 18 字段映射的 417 字节 descriptor |
| Field-name run | 0xbe99a03–0xbe99b50 | Task、changelist、…、gtc_freq_hz |
Task::_table_ | 0x2164fed0 | runtime reflection table |
Task_globals_ | 0x22266028 | proto default-instance globals |
Map<uint32,Task>::TryEmplaceInternal | 0xf2fa900 | 逐 worker map insert |
MapEntryFuncs<uint32,Task,…> | 0xf2f8060 | map-entry serialization funcs(key=u32,value=Task) |
XlaJfProfileCheapOps(Task const&) | 0xf2ca280 | 读取 clocks/version 的 consumer |
TaskEnvStatType init list | 0x21c20f00 | 17 条 {str,enum} 记录,步长 0x18 |
kTaskEnvPlaneName | 0x21c9e0e8 | host Task-env plane name |
GetTaskEnvStatTypeStr | 0x1c8eb8c0 | enum→string accessor |
FindTaskEnvStatType | 0x1c8eba20 | string→enum accessor |
TaskEnvStatType map guard / fwd / rev | 0x22579b38 / 0x22579b30 / 0x22579b20 | 构建一次的 flat_hash_maps |
| Baked per-DeviceType GTC clk table | 0x1c60480 (.lrodata) | 步长 0x448;+4 clk(kHz),+8 ts-width |
GtcSpanConverter::ctor(DeviceType) | 0xf2cb6e0 | 从内置表加载 clk —— 实际除数来源 |
TpuXLineBuilder::AddEvent(GtcSpan) | 0xf1df1e0 | 消耗 clock 的 GTC→ps 除法 |
注意 —— Task proto 的填充点 —— profile 时写入
tensor_core_freq_hz/sparse_core_freq_hz/gtc_freq_hz、build/version 字段以及 topology 到Task的 host-side 函数 —— 未定位为单个 setter。consumer(XlaJfProfileCheapOps@0xf2ca280)会读取它;producer 可能是 host-side xprof session-info collector,不在这里跟踪的 device-side decode 路径中(gap,不是猜测)。
相关组件
| 组件 | 关系 |
|---|---|
| riegeli Trace Container | 消耗此 proto 记录的 GTC clock 常量;负责 round(gtc · 1e9 / (clk << 4)) 除法和 resync-aware 分段转换器 |
| tpu-telemetry proto | 正交的设备状态快照(power/throttle/ECC/temps);Task 是 profiler identity+clocks,telemetry 是 live device metrics —— 不同栈 |
| XPlane / XStat / TraceMe | 负责 17 项 TaskEnvStatType 字典将 Task 字段暴露到其上的 plane/XStatMetadata 组装 |
| Profiling and Telemetry Overview | capture→encode→decode→xplane 流水线;Task proto 是框定 captured profile 的逐 worker 元数据记录 |
交叉引用
- riegeli Trace Container —— 除以 GTC clock 的时间基准;本页提供
gtc_freq_hz常量以及 converter 实际相除的内置逐 DeviceType kHz 表 - tpu-telemetry proto —— 对比:设备状态指标 vs 此 proto 的 profiler identity/clocks
- XPlane / XStat / TraceMe ——
TaskEnvStatType字典馈入的 host-plane XStat 组装 - Profiling and Telemetry Overview —— 此元数据记录框定的五阶段 device-trace 流水线