XStat Metadata IDs
本页中的所有地址、偏移和字符串 VA 都适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d)。该二进制携带完整 C++ 符号;.text/.rodataVMA == 文件偏移。其他版本会有所不同。
摘要
XStat 是 profiler XSpace 的注释原语:挂在 XEvent(或 XLine/XPlane)上的类型化 key/value 对。key 是 XStatMetadata.id,即拥有它的 XPlane 的 stat_metadata map 中的索引;value 是六个 protobuf oneof arm 之一(double/uint64/int64/string/bytes/ref)。本页是 stat 目录:libtpu 附加到 TPU 设备和主机 event 上的内建 XStatMetadata name 字符串,按类别分组,并列出每个名称的 value type 以及产生它的 trace-point 或 payload 字段。它是 XEvent Metadata IDs 的同级页面,后者记录 event 字典;stats 注释 events,因此两个字典互不相交,并位于同一个 plane 上的独立 map 中。
这个形状可以映射到 LLVM/MLIR 工程师熟悉的框架。如果 XEvent 是一条 instruction,那么 XStat 就是 operand-attribute:metadata_id 是 attribute kind(像 MLIR attribute name 一样,每个 plane 内建一次),oneof value 是 attribute payload。与 XEvent Metadata IDs 一样,这里没有单一全局 stat-id enum;XStat.metadata_id 是 plane-local 的内建 key,同一个整数在不同 plane 上含义可能不同。稳定的是名称:规范 tsl StatType enum(GetStatTypeStrMap()::stat_type_str_map)提供众所周知的名称,而 XPlaneBuilder::GetOrCreateStatMetadata(name) 会在首次看到每个名称时将其内建,分配下一个空闲的 plane-local id。因此本页按 name + value-type + producer 编目,而不是按固定整数编目,这与 TraceEntry to XEvent 页面记录每个 subscriber stat 发射的方式完全一致。
两个 stat 比较特殊,也是这个二进制中唯一具有事实上的固定整数的 stat:device_offset_ps(StatType 147)和 device_duration_ps(StatType 148)。它们是通用 TpuXLineBuilder::AddEvent(GtcSpan) 标记到每个设备 XEvent 上的绝对设备时间对。它们的 XStatMetadata* 在线 builder 构造时缓存在 TpuXLineBuilder+0x38/+0x40;其他所有 stat 都通过通用 XStatsBuilder::AddStatValue<T> 路径动态添加,oneof arm 由 C++ 值类型 T 选择。
本页是一个参考目录。它覆盖:XStatMetadata 模型和六个 value arm;T 如何在 AddStatValue 调用点选择 arm;通用 device_*_ps 对;按 subscriber 动态发射的 stats(sync wait reason、DMA byte count、counter sample);按类别分组的规范 tsl StatType 名称目录;以及走 bytes arm 的 protobuf-message-valued stats(CoreDetails、PowerComponentMetrics)。
| 归属结构 | XStatMetadata (proto2: id f1, name f2, description f3) |
| Map | XPlane.stat_metadata — map<int64, XStatMetadata> (f5),plane-local |
| Stat record | XStat (proto2): metadata_id f1, oneof value {2..7} |
| 内建(string) | XPlaneBuilder::GetOrCreateStatMetadata(string_view)(也包括 string&&、long) |
| TPU 内建 | xprof::TpuXPlaneBuilder::GetOrCreateStatMetadata<string_view> |
| 添加值 | XStatsBuilder<XEvent>::AddStatValue<T> — 已确认 8 个 T 实例化 |
| 名称表 | GetStatTypeStrMap()::stat_type_str_map(规范 tsl StatType 名称) |
| 固定整数对 | device_offset_ps = 147, device_duration_ps = 148 |
| 始终启用的生产者 | TpuXLineBuilder::AddEvent(GtcSpan) @ 0xf1df1e0(标记 147/148) |
XStatMetadata 模型
目的
XStatMetadata 是给 stat 定型的每 plane 字典条目。XStat 只携带一个整数 metadata_id 和一个值;人类可读名称和描述只在 XStatMetadata 中保存一次,并由 plane 的 stat_metadata map 中的 id 键入。这与 XEventMetadata 一样是 intern-once / reference-by-id 设计,原因也相同:64 字节的 HLO-op 名称或 34 字节的 wait-reason 字符串每个 plane 只内建一次,所有需要它的 event 都引用这个小整数 id。
Schema
经字节确认的 proto2 布局(来自 xplane.proto,主机和设备 plane 结构一致):
message XStatMetadata { int64 id = 1; string name = 2; string description = 3; }
message XStat {
int64 metadata_id = 1; // KEY into XPlane.stat_metadata
oneof value {
double double_value = 2; // oneof case 2
uint64 uint64_value = 3; // oneof case 3 (also used for ref by tsl convention)
int64 int64_value = 4; // oneof case 4
string str_value = 5; // oneof case 5
bytes bytes_value = 6; // oneof case 6
uint64 ref_value = 7; // oneof case 7 (an interned StatMetadata id)
}
}
```text
> **陷阱 —** `XStat` 的 `oneof case` 判别数字(2..7)是**字段号**,并不等同于 `AddStatValue` 反编译写入判别槽的整数。[TraceEntry to XEvent](trace-entry-to-xevent.md#payload-scalar-to-xstat) 页面记录了在调用点经字节确认的运行时判别值:`int64` 写入 case **3**,`uint64` 写入 case **2**,`ref` 写入 case **7**。proto *field* 编号(int64=f4、uint64=f3)和内存中的 *oneof discriminant*(int64→3、uint64→2)是同一 arm 的两种不同编码;重新实现者不能假设它们相等。下方目录列出 proto 字段号;内存判别值请交叉参考 trace-entry 页面。
### `ref_value` 间接层
`ref_value` (f7) 是唯一不太直观的 arm:它的值是同一 plane 上的**另一个 `XStatMetadata.id`**,用于 stat 的值本身也是内建字符串时(这样字符串只在 `stat_metadata` 中存一次,许多 event 通过 id 指向它,而不是每个 event 都携带一份副本)。sync wait-reason stat 使用这个机制,见 [wait-reason 行](#sync-and-control-stats)。`AddStatValue<XStatMetadata&>` 实例化(已确认符号 `AddStatValueIRNS3_13XStatMetadataEEE`)正是这条路径:传入一个内建字符串 `XStatMetadata&`,得到一个指向其 id 的 `ref_value` arm。
---
## 值类型选择 — `AddStatValue<T>`
### 目的
stat 的 value arm **不是**由按名称的表选择,而是由 `XStatsBuilder<XEvent>::AddStatValue<T>` 调用点处参数的 C++ 类型 `T` 选择。该模板会为二进制中用到的每种值类型实例化一次,每个实例化都硬编码它写入的 `oneof` arm。这是重新实现 stat 层最重要的事实:要知道 arm,查看调用点类型,而不是名称。
### 已确认实例化
`XStatsBuilder<tensorflow::profiler::XEvent>`(按 event)和 `XStatsBuilder<XEventMetadata>`(按 event-type)builder 上存在八个 `AddStatValue<T>` 实例化,均以 mangled symbol 形式出现。各自写入的 arm 如下:
| `T`(mangled fragment) | C++ 类型 | `oneof` arm(字段) | 用途 |
|---|---|---|---|
| `Il` | `long` | `int64_value` (f4) | `device_offset_ps`/`device_duration_ps`、step/counter ids |
| `Im` | `unsigned long` | `uint64_value` (f3) | DMA byte count、transaction/correlation ids |
| `IRd` | `double&` | `double_value` (f2) | SPI power(W) samples、bandwidth、occupancy_pct |
| `IRNSt…basic_string…` | `std::string&` | `str_value` (f5) | tensor_shapes、kernel_details、source_stack |
| `IRNSt…basic_string_view…` | `string_view&` | `str_value` (f5) | hlo_op/hlo_module names、fixed labels |
| `IRNS3_13XStatMetadataE` | `XStatMetadata&` | `ref_value` (f7) | sync wait reason(内建字符串 ref) |
| `IRNS3_11CoreDetailsE` | `CoreDetails&` | `bytes_value` (f6) | per-core hardware identity blob |
| `IRNS3_21PowerComponentMetricsE` | `PowerComponentMetrics&` | `bytes_value` (f6) | power/thermal component metrics blob |
> **注意 —** 两个 message 类型实例化(`CoreDetails`、`PowerComponentMetrics`)会把 protobuf 消息序列化进 `bytes` arm。不存在专门的 sub-message arm;结构化 stat 是 `bytes_value` (f6) 中的序列化 blob,与设备 event `TraceEntry` payload 使用的 `XEventMetadata.metadata` arm 相同。重新实现者必须把这些序列化为 bytes,而不是发明新的 arm。
>
> **怪癖 —** 存在一个 `XStatsBuilder<XEvent>::CopyStatValue(XStat&, XPlane&, XStat*)` 符号,plane merge 期间复制 stat 时会把源 stat 的 metadata name 重新内建到目标 plane,并重写 `metadata_id`。这就是为什么 stat *id* 跨 merge 不稳定,而 stat *name* 稳定:merge 按 name 键入,而不是按整数。目录应建立在名称上。
---
## 通用设备时间对
### 目的
两个 stat 会无条件地(只受两个 metadata 指针是否都已内建影响)标记到**每个**携带 timing 的设备 `XEvent` 上。它们是唯一在此二进制中具有固定 StatType 整数的 stat,因为 line builder 在构造时缓存它们的 `XStatMetadata*`,而不是每个 event 按 name 查找。
### 这一对
| Stat name | StatType id | Value arm | Producer |
|---|---|---|---|
| `device_offset_ps` | **147** | `int64_value` (f4) | `TpuXLineBuilder::AddEvent(GtcSpan)` @ `0xf1df1e0`,cached `+0x38` |
| `device_duration_ps` | **148** | `int64_value` (f4) | `TpuXLineBuilder::AddEvent(GtcSpan)` @ `0xf1df1e0`,cached `+0x40` |
两者都携带**绝对**设备 wall time,单位为皮秒,由 GTC→ps 数学计算得到(`round(gtc_ticks × 1e9 / (clock × 16))`,128-bit)。它们复制了 `XEvent.offset_ps`/`duration_ps` 字段,后者是*line-relative* 的。完整机制,包括为什么两者都存在、条件 gate、128-bit divide,记录在 [TraceEntry to XEvent](trace-entry-to-xevent.md#the-universal-stamp--addeventgtcspan)。本页补充的是:这两个是目录的锚点行,是唯一可在二进制中观察到整数 id 的 stat 名称(写入 `XStat.metadata_id` 的 cached `XStatMetadata` 的 `+0x28` 偏移)。
> **陷阱 —** 没有内建两个 StatType 147/148 的 `TpuXLineBuilder`(例如 counter-only line)会发射带 line-relative timing 但**没有** `device_*_ps` stats 的 event。这一对取决于 `this+0x38 && this+0x40` 都非空。在 line-builder 构造时内建两者,否则会丢失每个 event 上的绝对时间基。
---
## Sync 和 Control Stats {#sync-and-control-stats}
### 目的
有状态 sync subscriber 会在 `SyncWait:<n>` duration event 上以动态 stat 形式暴露等待*为什么*发生,以及是*哪个* sync flag([begin/end pairing](trace-entry-to-xevent.md#beginend-pairing--duration-events))。这些是价值最高的 TPU 专用设备 stat。
| Stat name | Value arm | Producer trace-point / field |
|---|---|---|
| sync wait reason — e.g. `"TensorCore waiting for Host Infeed"` (`@0x871cd36`, len `0x22`) | `ref_value` (f7) | id 86/87/80 sync pair,通过 `AddStatValue<XStatMetadata&>` @ `0xf1e1da0` 内建 |
| `sync_flag_id` | `int64_value` (f4) | TCS sync events (80–88) 上的 `SyncFlagValue()` / `sync_flag_number` |
| `overlay_id` | `int64_value` (f4) | overlay subscriber,overlay trace-points → TC Overlay line (7) |
| `step_id` | `int64_value` (f4) | step tracker,`SET_TRACEMARK` (84) step marks |
| `step_num` | `int64_value` (f4) | step tracker,step sequence |
| `group_id` | `int64_value` (f4) | step/event grouping(tsl canonical) |
> **注意 —** 对于 `id==255` dummy/sentinel sync entry,wait-reason `XStatMetadata.name` 是**空字符串**(len 0);该 stat 仍会发射,但它的内建名称为空,因此消费者会看到一个指向无名 stat 的 `ref_value` arm。断言 stat name 非空的重新实现会在这个 sentinel 上失败。
---
## Memory / DMA Stats
### 目的
DMA subscriber 暴露已匹配 DMA span 的传输字节数;OCI/HDE/ICI band 暴露直接从已解码 `TraceEntry` variant 字段读取的 byte count 和 chunk count。这些走 `uint64`/`int64` arm。
| Stat name | Value arm | Producer trace-point / field |
|---|---|---|
| DMA byte count(内建 `bytes_transferred` 或按 line 的 label) | `uint64_value` (f3) | DMA 完成时的 `MemoryDataEnd()`,`AddStatValue<unsigned long>` @ `0xf1df460` |
| `bytes_transferred` | `uint64_value` (f3) | DMA / memcpy events(tsl canonical,string confirmed) |
| `bytes_accessed` | `uint64_value` (f3) | memory-access events(tsl canonical) |
| `bytes` | `uint64_value` (f3) | generic byte-count(tsl canonical) |
| `memory_bandwidth` | `double_value` (f2) | memory events 上的 derived BW(tsl canonical) |
| `queue_id` | `int64_value` (f4) | DMA/queue events(tsl canonical) |
> **注意 —** DMA match key(`dma_id`,来自 `GetDmaId()` @ `0xf698180`)是*配对* key,**不是** stat,它从不会被内建到 `stat_metadata`。字符串 `"dma_id"` 不存在于二进制中(已确认 MISS),这与它作为内存中 `FlatHashMap` key 而非序列化 stat name 的事实一致。不要寻找 `dma_id` stat;暴露出来的 DMA scalar 是 byte count。
---
## Counter / Power / Throttle Stats
### 目的
Counter line 和固件 power/thermal 子系统以 stat 形式暴露 sample。counter sample 使用固定 rodata label;power component 走 message-valued `bytes` arm。
| Stat name | Value arm | Producer |
|---|---|---|
| `"Available Count"` (`@0x84e8ef6`, len `0xf`) | `int64_value` (f4) | TC0 infeed-buffer counter line sample |
| SPI power sample (W) | `double_value` (f2) | `SpiSamplerSubscriber`,ids 168/169 → SPI Meter lines (118/119) |
| `PowerComponentMetrics` blob | `bytes_value` (f6) | firmware power/thermal subscriber,`AddStatValue<PowerComponentMetrics&>` |
| throttle counters (num_thermal/electrical_throttles) | `int64_value` (f4) | id 97 / 200.. throttle events → Power Throttle line (58) |
> **注意 —** counter-sample line 使用常量 rodata stat name(`"Available Count"`),通过 `GetOrCreateStatMetadata(string_view)` 内建;这与上面的 canonical-tsl-named stats 不同。固定 label stat 和 tsl-enum stat 共享同一个 `stat_metadata` map,只是名称字符串来源不同(rodata literal vs `GetStatTypeStrMap` table)。
---
## HLO / XLA-Op Stats
### 目的
HLO subscriber 和事后 symbolizer 会把 compiler-side 身份附加到 `"XLA Ops"` line 上的 XLA-op event。这些都是规范 tsl 名称,除 numeric id 外都为 string-valued。
| Stat name | Value arm | Producer |
|---|---|---|
| `hlo_op` | `str_value` (f5, string_view) | 通过 symbolizer 处理的 `TRACE_INSTRUCTION`/`SET_TRACEMARK` (85/84) |
| `hlo_module` | `str_value` (f5) | 通过 `GetOrCreateXlaEventMetadata(pair)` 得到的 HLO module name |
| `hlo_category` | `str_value` (f5) | symbolizer(tsl canonical) |
| `program_id` | `int64_value` (f4) | per-program identity |
| `tf_op` | `str_value` (f5) | TF-op name(tsl canonical) |
| `tf_function_call` | `str_value` (f5) | TF function call(tsl canonical) |
| `tensor_shapes` | `str_value` (f5) | op tensor-shape string(tsl canonical) |
| `kernel_details` | `str_value` (f5) | kernel detail string(tsl canonical) |
| `source_stack` | `str_value` (f5) | source-location stack(tsl canonical) |
| `flops` | `int64_value` (f4) | op FLOP count(tsl canonical) |
| `long_name` | `str_value` (f5) | full op name(tsl canonical) |
| `level` | `int64_value` (f4) | nesting level(tsl canonical) |
> **注意 —** 这些 stat *name* 已确认以 ASCII 形式存在于二进制中,但每个芯片家族上精确的 subscriber→stat-emission 并未全部逐字节追踪;只有 `hlo_op`/`hlo_module` 路径(通过 `GetOrCreateXlaEventMetadata`)和 symbolizer enrichment 得到确认。应将 canonical-tsl 行的 value-arm assignment 视为从 tsl `StatType` schema 推导而来(该 schema 固定每个名称的 arm):指定了 producer 的地方为 HIGH,仅确认字符串存在的地方为 LOW。
---
## Identity / Host / Provenance Stats
### 目的
Plane 和 host 级身份 stat,以及 build-provenance stat。它们出现在 `/host:<n>` plane 上或作为 plane-level stat;内建方式相同,但大多在主机侧(通过 `tsl::profiler::XPlaneBuilder` 进行动态 name-interning)。
| Stat name | Value arm | Source |
|---|---|---|
| `device_id` | `int64_value` (f4) | plane/event device identity(tsl canonical) |
| `core_id` | `int64_value` (f4) | TPU core ordinal,device events 上的 `CoreId()` |
| `chip_id` | `int64_value` (f4) | TPU chip ordinal(TPU-specific) |
| `device_type` | `str_value` (f5) | device-type label(tsl canonical) |
| `run_id` | `int64_value` (f4) | execution run identity(tsl canonical) |
| `correlation_id` | `uint64_value` (f3) | host↔device correlation(tsl canonical) |
| `context_id` | `int64_value` (f4) | context identity(tsl canonical) |
| `producer_id` | `int64_value` (f4) | flow producer(tsl canonical,flow events) |
| `is_eager` | `int64_value` (f4, bool) | eager-mode flag(tsl canonical) |
| `step_name` | `str_value` (f5) | step label(tsl canonical) |
| `self_duration_ps` | `int64_value` (f4) | self (exclusive) duration(tsl canonical) |
| `min_duration_ps` | `int64_value` (f4) | min instance duration(tsl canonical,event-type stat) |
| `total_profile_duration_ps` | `int64_value` (f4) | profile span(tsl canonical,plane stat) |
| `max_iteration_num` | `int64_value` (f4) | iteration count(tsl canonical) |
| `num_occurrences` | `int64_value` (f4) | aggregated occurrence count(tsl canonical) |
| build provenance — `"<timestamp> cl/<changelist>"` (`absl::StrFormat("%s cl/%s", BuildData::Timestamp(), BuildData::Changelist())`) | `str_value` (f5) | `AddPluginMetadata()` @ `0xf3165c0`,StatType **0xA6 = 166** |
> **怪癖 —** `num_occurrences` *也是*一个 `XEvent` `oneof data` 字段(f5),不只是 stat。聚合后的 `XEvent`(一种 event-type,折叠了多个实例)把计数存进 `XEvent.num_occurrences`;`num_occurrences` *stat* 是按 event-type 的变体。重新实现者不能把同一个计数存到两个地方;聚合 event 应选择 event 字段,type-level aggregation 应选择 stat。
---
## Stat 分类一览
规范 tsl `StatType` enum(由 `GetStatTypeStrMap()::stat_type_str_map` 种下)加上 TPU 专用名称,可以按这些轴分组。这里不倾倒完整的约 150 条 tsl enum,而是给出类别、主导 value arm 和生产子系统:
| Category | Dominant arm | Stat names (representative) | Producer |
|---|---|---|---|
| Device time (fixed-id) | `int64` | `device_offset_ps` (147), `device_duration_ps` (148), `self_duration_ps` | `TpuXLineBuilder::AddEvent(GtcSpan)` |
| Sync / control | `ref` / `int64` | wait-reason (ref), `sync_flag_id`, `overlay_id`, `step_id` | sync/overlay/step subscribers |
| Memory / DMA | `uint64` | byte count, `bytes_transferred`, `bytes_accessed`, `queue_id` | DMA subscriber, OCI/HDE bands |
| Counter / power | `double` / `bytes` | `"Available Count"`, SPI power(W), `PowerComponentMetrics` | counter lines, firmware/SPI subscribers |
| HLO / XLA-op | `string` | `hlo_op`, `hlo_module`, `program_id`, `flops`, `tensor_shapes` | HLO subscriber + symbolizer |
| Identity / host | `int64` / `string` | `device_id`, `core_id`, `chip_id`, `run_id`, `correlation_id` | host TraceMe + device CoreId |
| Provenance | `string` | build-changelist (StatType 166) | `AddPluginMetadata()` |
> **注意 —** 上面的每个名称都已确认以 ASCII 形式存在于 `libtpu.so` 中。每个名称的 value arm 由 tsl `StatType` schema 固定(按约定,一个名称对应一个 arm),因此即使 per-family producer 只有 MEDIUM/LOW 置信度,arm 列仍为 HIGH 置信度。canonical tsl 名称的*整数 id* 无法从字符串恢复;它们由 tsl enum 在构建时分配,并按 name + arm + producer 记录,而不是按固定整数记录,这与 [XEvent Metadata IDs](xevent-metadata-ids.md) 页面完全一致。只有 `device_offset_ps` (147)、`device_duration_ps` (148) 和 provenance stat (166) 有已确认整数。
---
## 相关结构偏移
```text
XStatMetadata (proto2) +0x00 id (field 1) — the plane-local stat id
(name field 2, description field 3 follow)
consumers read id at metadata_ptr + 0x28 when stamping XStat
XStat (proto2) +0x18 metadata_id (= XStatMetadata + 0x28)
+0x20 value (union storage)
+0x28 oneof discriminant (2 uint64 / 3 int64 / 7 ref / double)
(in-memory discriminant; NOT the proto field number)
TpuXLineBuilder +0x38 device_offset_ps XStatMetadata* (StatType 147)
+0x40 device_duration_ps XStatMetadata* (StatType 148)
AddEvent(GtcSpan) gates the pair on both being non-null相关组件
| Component | Relationship |
|---|---|
XStatsBuilder<XEvent>::AddStatValue<T> | 通用添加路径;T 选择 value arm |
XStatsBuilder<XEvent>::CopyStatValue | plane merge 期间重新内建并重写 metadata_id |