TraceEntry 到 XEvent/XStat
本页中的所有地址和偏移都适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d)。该二进制包含完整的 C++ 符号;.text/.rodataVMA == 文件偏移。其他版本会有所不同。
摘要
本页记录设备 profiler 的最后一跳:subscriber 栈如何把解码后的 proto2 TraceEntry(每个 16 字节硬件包一个,由 TraceEntriesCoder 生成)转换为设备 XLine 上带有 XStat 注解的 XEvent。XPlaneBuilder API 页面负责构建器原语(GetOrCreateLine、GetOrCreate*Metadata、AddEvent、AddStatValue);本页负责从解码 entry 驱动这些原语的一切:dispatcher 扇出、每个 subscriber 的 ProcessTraceEntry visitor、两套 XEvent 命名机制、payload scalar → XStat 映射、duration event 的 begin/end 配对,以及按 band 分配 XLine。
这个形状可以干净地映射到 LLVM/MLIR 工程师已经熟悉的框架。可以把解码后的 RepeatedPtrField<TraceEntry> 看作指令流,把 CoreDispatcher 看作 pass-manager,它按 opcode(trace_point_id)把每条“指令”路由到每个注册了兴趣的 pass(subscriber),并把每个 subscriber 的 ProcessTraceEntry 看作一次性 lowering,用来发射设备平面的 XEvent。有两个事实让这一步并不平凡,也是它需要单独成页的原因。第一,wire trace_point_id 从不存入 XEvent,它会被转换成名称字符串(通过两套机制之一),并在每个 plane 内 intern 成 XEventMetadata,所以 XEvent.metadata_id 是 plane-local intern slot,是一个完全不同的整数。第二,若干 event kind 是有状态的:一次 wait 或 DMA 是两个 wire packet(begin + end),由每个 core 的 Tracker 通过匹配 key 配对成一个单独的 duration XEvent,这个 XEvent 直到 closing packet 到达才存在。
该转换有一个通用主干和一个每个 subscriber 可变的部分。主干是 xprof::TpuXLineBuilder::AddEvent(GtcSpan, XEventMetadata&) @ 0xf1df1e0:它总是把 GTC start/duration 转换为皮秒,写入 XEvent.offset_ps/duration_ps,并且追加两个冗余的 int64 XStat:device_offset_ps(StatType 147)和 device_duration_ps(StatType 148),其 metadata 指针由 line builder 在构造时缓存。可变部分则是每个 subscriber 选择一条 line(一个 TpuComponent ordinal)、一个名称(原始十进制字符串或 human/HLO 名称),以及零个或多个 domain XStat(sync wait reason、DMA byte count、counter value)。本页重建这两部分。
对于重新实现,本页重建的契约是:
- Dispatch 扇出 —
CoreDispatcher::Dispatch读取TraceHeader.trace_point_id,并通过FlatHashMap<id, vector<subscriber*>>路由到每个已注册 subscriber 的虚函数ProcessTraceEntry;一个 wire id 可以扇出到多个 subscriber 并产生多个XEvent。 - 两套命名机制 — raw 路径用
trace_point_id的十进制字符串命名XEvent;semantic 路径计算 human name(SyncWait:<n>、通过 symbolizer 得到的 HLO op、固定 counter-line label)。 - 通用 duration+stat stamp —
TpuXLineBuilder::AddEvent(GtcSpan)的 GTC→ps 计算,以及始终存在的device_offset_ps/device_duration_psXStat对,再加上调用方在返回的XEventBuilder上追加的每个 subscriber 的动态 stats。 - Begin/end 配对 —
SyncTracker(key =sync_flag_number)和DmaSubscriber(key =dma_id)是每个 core 的状态机,会把两个 packet 折叠成一个 duration span。 - Line 分配 — 每条设备
XLineid 都是一个TpuComponentordinal,由 149 项TpuComponentName表命名;哪个 subscriber 拥有哪些 line 在代码中固定。
| Pipeline 入口 | ConvertTpuTraceToXPlaneV2<TraceEntry> (pxc 0xf1d4360, +5 families) |
| Dispatcher | CoreDispatcher<TraceEntry>::Dispatch @ 0xf1ed280 |
| 注册 | CoreDispatcher::RegisterSubscriber @ 0xf1ecee0 |
| 通用 stamp | xprof::TpuXLineBuilder::AddEvent(GtcSpan, XEventMetadata&) @ 0xf1df1e0 |
| Raw 命名 | TpuXLineBuilder::AddEvent<…ProcessTraceEntry lambda> (pxc 0xf1f26e0, +5) |
| GTC→ps 转换器 | GtcSpanConverter::TimespanFromGtcSpan @ 0xf2cb7e0 |
| Line key | TpuXPlaneBuilder::GetOrCreateLine(TpuComponent) @ 0xf1df120 (line id == ordinal) |
| Line-name 表 | TpuComponentName @ 0x1c8ebb60 (149-arm rel32 jump table @ 0xb43d200) |
| Sync 配对 | SyncTracker::ProcessTraceEntry @ 0xf1ef3c0, key = sync_flag_number |
| DMA 配对 | DmaSubscriber::ProcessTraceEntry @ 0xf1dfee0, key = dma_id |
| 通用 stats | device_offset_ps (147) @ +0x38, device_duration_ps (148) @ +0x40 of TpuXLineBuilder |
Pipeline 概览 — 从 Decode 到 XSpace
目的
说明 subscriber translation 在更宽的设备路径中的位置。codec(TraceEntriesCoder)向上交出一个 RepeatedPtrField<TraceEntry>,每个 16 字节硬件包一个 entry。ConvertTpuTraceToXPlaneV2<TraceEntry> 为每个 ChipCoreId 构建一个 /device:TPU:<core> XPlane,向 CoreDispatcher 注册一组 subscriber,并把每个解码 entry 通过它重放。dispatcher 是唯一的路由点;它下游的一切都是每个 subscriber 自己处理。
入口点
RepeatedPtrField<TraceEntry> ── one entry per 16-byte packet (TraceEntriesCoder)
└─ ConvertTpuTraceToXPlaneV2<TraceEntry> ── pxc 0xf1d4360 (+vlc/vfc/glc/gfc/jxc)
│ GetDeviceIdentifiers ; GetTraceCodec ── per-ChipCoreId setup
│ build CoreContext: TpuXPlaneBuilder (one XPlane) + subscriber set
└─ CoreDispatcher<TraceEntry>::Dispatch ── 0xf1ed280
│ read TraceHeader.trace_point_id (+0x18)
│ FlatHashMap<id, vector<subscriber*>> lookup (built by RegisterSubscriber 0xf1ecee0)
└─ for each matching subscriber:
subscriber::ProcessTraceEntry ── virtual, call *(vtable+0x10)
├─ GetOrCreateLine(TpuComponent) ── 0xf1df120, line id == ordinal
├─ name = [raw] decimal | [semantic] SyncWait:/HLO/label
├─ GetOrCreateEventMetadata(name) ── per-plane intern → metadata_id
└─ TpuXLineBuilder::AddEvent(GtcSpan, *meta) ── 0xf1df1e0
→ XEvent + {device_offset_ps, device_duration_ps} + per-event XStats
```text
### 算法
```c
function ConvertTpuTraceToXPlaneV2(decoded_entries): // pxc 0xf1d4360
ids = GetDeviceIdentifiers() // transport identity
codec = GetTraceCodec(ids) // 0xf5a2900
for each ChipCoreId core in ids:
ctx = CoreContext(core) // owns one /device:TPU:<core> XPlane
ctx.xplane = TpuXPlaneBuilder(core)
ctx.dispatcher = CoreDispatcher<TraceEntry>()
RegisterSubscribers(ctx) // bind trace_point_id sets → subscribers
for each TraceEntry e in decoded_entries:
ctx.dispatcher.Dispatch(wrap(e)) // 0xf1ed280
Flush(all dispatchers) // drain stateful trackers' open spans
function CoreDispatcher::Dispatch(entry): // 0xf1ed280
id = entry.header.trace_point_id // TraceHeader+0x18
subs = subscriber_map.find(id) // FlatHashMap<id, vector<subscriber*>>
if subs == end: return // unregistered id → dropped
for sub in *subs:
sub->vtable[0x10](sub, entry) // virtual ProcessTraceEntry怪癖 — 单个
trace_point_id可以由多个 subscriber 注册(例如SET_TRACEMARK会喂给 step tracker、HLO subscriber 和 on-device TraceMe subscriber)。Dispatch会把一个 wire entry 扇出到所有这些 subscriber,因此一个 packet 可以在多条 line 上变成多个XEvent。假设一个 packet → 一个 event 的重新实现会让 timeline 填充不足。注意 — 每个 family 的
TracePoints<TraceEntry>注册集合,也就是每个 subscriber 在每个 chip family 上注册哪些 id,是在构造时从运行时 descriptor(HLO subscriber 的SET_TRACEMARK/TRACE_INSTRUCTIONid 位于subscriber+0x8c/+0x10c)读取的,而不是烘焙成字面量。它们在比较点确认了 pxc(84/85),但没有对 vfc/vlc/glc/gfc/jxc 做穷尽 dump。请把下表中的每个 family id 集合视为 pxc 为高置信度,其他 family 为中等置信度。主 id 目录见 TracePoints 主注册表;metadata-id 分配见 XEvent Metadata IDs。
通用 Stamp — AddEvent(GtcSpan)
目的
每个带 timing 的设备 XEvent 都通过 xprof::TpuXLineBuilder::AddEvent(GtcSpan, const XEventMetadata&) @ 0xf1df1e0 创建。这是唯一 chokepoint,负责 (a) 通过 generic builder 在线上创建 XEvent,(b) 把 GTC span 转换为皮秒,(c) 写入 line-relative 的 XEvent.offset_ps/duration_ps,以及 (d) 追加两个冗余的绝对 device_offset_ps/device_duration_ps XStat。Subscriber 从不直接写 timing 字段,而是把一个 GtcSpan 和一个 interned XEventMetadata 交给这个函数,由它完成 stamp。
算法
已按 decompiled body 做字节确认。两个缓存的 XStatMetadata* 位于 TpuXLineBuilder+0x38(device_offset_ps,StatType 147)和 +0x40(device_duration_ps,StatType 148);每条 line 的 GTC clock 对象位于 +0x10。
function TpuXLineBuilder::AddEvent(this, gtc_start, gtc_dur): // 0xf1df1e0
XLineBuilder::AddEvent(this, meta) // 0x1cf4dc40 — create XEvent, copy metadata_id
// (A) the universal device_*_ps XStat pair — only if BOTH metadata ptrs cached
if *(this+0x38) != null && *(this+0x40) != null: // line 30: gate
clk16 = 16 * (**(this+0x10)) // per-line clock << 4
// offset: round(gtc_start * 1e9 / clk16)
off_ps = udivti3(0x3B9ACA00 * (gtc_start & ~0xF) + clk16/2, clk16) // 128-bit divide
// duration: round((end - start_masked) * 1e9 / clk16)
dur_ps = udivti3(0x3B9ACA00 * ((gtc_start+gtc_dur - (gtc_start & 0x1FFFFFFFFFF0)) & 0x1FFFFFFFFFF0)
+ clk16/2, clk16)
s1 = RepeatedPtrFieldBase::Add<XStat>(xevent.stats) // line 46
s1.metadata_id = (*(this+0x38)) + 0x28 // device_offset_ps XStatMetadata.id
s1.oneof_case = 3 // int64_value (field 4)
s1.int64_value = off_ps
s2 = RepeatedPtrFieldBase::Add<XStat>(xevent.stats) // line 61
s2.metadata_id = (*(this+0x40)) + 0x28 // device_duration_ps XStatMetadata.id
s2.oneof_case = 3
s2.int64_value = dur_ps
// (B) the XEvent's own line-relative timing — always
span = GtcSpanConverter::TimespanFromGtcSpan(*(this+0x10), gtc_start, gtc_dur) // 0xf2cb7e0
xevent.offset_ps = span.offset // +0x38, oneof "data" case 2
xevent.duration_ps = span.duration // +0x30, presence orb |= 4
return XEventBuilder{ line, xevent } // caller adds per-event XStats
```text
> **怪癖 —** 每个设备 `XEvent` 最终都会把 timing 记录**两次**:`XEvent.offset_ps`/`duration_ps`(line-relative,ps)和 `device_offset_ps`/`device_duration_ps` `XStat`(absolute,ps)。这是有意设计,不是意外冗余。`XEvent.offset_ps` 相对于 `XLine` origin,若 line 在后续 merge 中被 rebase,它会被重写;`device_*_ps` `XStat` 携带绝对设备时间并在 rebase 后保留,这是 consumer 在 converter 归一化 line origin 后恢复真实 device wall time 的方式。
>
> **陷阱 —** `device_*_ps` stamp **取决于**两个 `XStatMetadata` 指针(`this+0x38`、`this+0x40`)都非 null。未在构造时 intern 这两个 metadata object 的 `TpuXLineBuilder`(例如 counter-only line)会静默发出带有 line-relative timing、但*没有*绝对 `device_*_ps` stats 的 `XEvent`。重新实现必须在 line-builder 构造时 intern StatTypes 147/148,否则会在每个 event 上丢掉绝对 timebase。
### GTC→ps 数学
转换公式是 `picoseconds = round(gtc_ticks × 1e9 / (clock × 16))`,使用 128-bit integer arithmetic,因为 `gtc_ticks × 1e9` 会溢出 64 位。常量已字节确认:`0x3B9ACA00` 是 `1e9`;乘数 `16 × clock`(`clk16`)是按比例缩放的每代 GTC period;`+clk16/2` 是 round-to-nearest;mask `0x1FFFFFFFFFF0`(41-bit,清低 nibble)和 `0xFFFFFFFFFFFFFFF0` 在乘法前把 tick value clamp 到硬件 counter 的有效范围。`_udivti3` 是编译器的 128÷64 unsigned divide。这与 transport layer 上的 timebase math 一致;codec 侧 GTC 处理见 [TraceEntriesCoder](trace-entries-coder.md)。
### 函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
| `TpuXLineBuilder::AddEvent(GtcSpan, XEventMetadata&)` | `0xf1df1e0` | 通用 stamp:XEvent + device_*_ps stats |
| `tsl::profiler::XLineBuilder::AddEvent(XEventMetadata&)` | `0x1cf4dc40` | base:追加 XEvent,复制 metadata_id |
| `GtcSpanConverter::TimespanFromGtcSpan` | `0xf2cb7e0` | GTC span → {offset_ps, duration_ps} |
| `_udivti3` | (libgcc) | ps 乘法所需的 128÷64 unsigned divide |
---
## XEvent 命名 — 两套机制
### 目的
wire `trace_point_id` 是一个带 band 的硬件 enum(有空洞,且因 family 而异),并且**从不**存储在 `XEvent` 中。每个 subscriber 计算一个*名称字符串*,通过 `GetOrCreateEventMetadata` 在每个 plane 内 intern,得到的 plane-local `metadata_id` 作为 `XEvent` 的 key。名称只有两种产生方式。
### 机制 (a) — Raw / Generic
generic subscriber 路径(`TensorCoreHloSubscriber`/`SparseCore…` raw `AddEvent<…lambda>`,pxc @ `0xf1f26e0`,+5 families)用 **`trace_point_id` 的十进制字符串**命名 `XEvent`。这是任何没有 semantic handler 的 banded id 的 codec-level fallback。
```c
function TpuXLineBuilder::AddEvent<lambda>(subscriber, gtc, trace_point_id): // pxc 0xf1f26e0
// per-subscriber cache: FlatHashMap<int, XEventMetadata*> at subscriber+0x18
slot = per_id_cache[trace_point_id] // 0xf1e6820 operator[]
if slot != null:
meta = slot // hit: reuse interned metadata
else:
buf = FastIntToBuffer(trace_point_id) // 0x211719e0 — e.g. 81 → "81"
meta = XPlaneBuilder::GetOrCreateEventMetadata(move(buf)) // 0x1cf4d380, string&&
per_id_cache[trace_point_id] = meta // cache the ptr
AddEvent(gtc, *meta) // 0xf1df1e0 universal stamp注意 — raw name 是 id 的十进制文本,不是 human enum name。id
81会成为XEventMetadata.name"81"。human enum name(SET_SYNC_FLAG,等等)是在下游 profile UI 中有 symbol table 时由 symbolizer 应用的;在 rawXSpace中,名称就是数字。rawXSpace的 consumer 必须把十进制字符串名称通过 TracePoints 主注册表 映射回 enum。
机制 (b) — Semantic
有状态/装饰型 subscriber 会从解码字段计算 human-readable name:
- Sync —
GetSyncFlagEventName<TraceEntry>@0xf1ef840对TraceHeader.trace_point_id(+0x18)做 switch,选择固定 prefix string,并追加sync_flag_number(StrCat+FastIntToBuffer)。prefix 是字节解码的 rodata literal,按trace_point_idkeyed:80→"SyncWait:",86→"SyncWait:",87→"SyncNoWait:",81→"Set:",82→"Add:",88→"Read:"(defaultarm,即 ids83–85,返回空字符串)。在每个 arm 内,entry+0x28处的 event-oneofdiscriminant(分别为0x25/0x26/0x27/0x2b/0x2c/0x2d)只用于选择正确的 typed accessor 来读取sync_flag_number。一次 blocking wait(id86或 DMA-done id80)→"SyncWait:<sync_flag_number>"。(注意:"Barrier Wait:"/"Sync Wait:"确实作为 rodata string 存在,但属于不同代码路径;GetSyncFlagEventName从不发出它们。) - HLO —
TpuXPlaneBuilder::GetOrCreateXlaEventMetadata(pair<hlo_module, hlo_op>)@0xf1e4e40加上Symbolizer::{TensorCore,BarnaCore,SparseCore}Symbolize(0xf57ce60/da0/ccc0)和SetNameIfEmpty@0xf1e4f40→ HLO op name,在"XLA Ops"line 上 intern。由TRACE_INSTRUCTION/SET_TRACEMARK(pxc85/84)驱动。 - 固定 counter label — 少数 counter-line event 使用常量 rodata name,例如
"TensorCore Infeed Buffer"(@0x85b4e21),通过GetOrCreateEventMetadata(string_view)@0x1cf4d0c0在 counter line 上 intern。
命名函数映射
| 函数 | 地址 | 产物 |
|---|---|---|
TpuXLineBuilder::AddEvent<lambda> (pxc) | 0xf1f26e0 | 十进制字符串 raw name |
FastIntToBuffer(int) | 0x211719e0 | id 的 base-10 文本 |
GetOrCreateEventMetadata(string&&) | 0x1cf4d380 | 按名称 intern → metadata_id |
GetSyncFlagEventName<TraceEntry> (pxc) | 0xf1ef840 | "SyncWait:<n>" 等 |
GetOrCreateXlaEventMetadata(pair) | 0xf1e4e40 | "XLA Ops" 上的 HLO op name |
Symbolizer::TensorCoreSymbolize | 0xf57ce60 | HLO symbol resolution |
Payload Scalar 到 XStat
目的
除通用 device_*_ps 对之外,每个 subscriber 会把它关心的一个或少数几个解码 payload scalar 作为动态 XStat 放到刚构建的 XEvent 上。该映射是每个 subscriber 自己的,不是 table-driven:每个 ProcessTraceEntry 读取其字段的 TraceEntryWrapper accessor(SyncFlagValue()、GetDmaId()、MemoryDataEnd(),等等),intern 匹配的 stat name,并在返回的 XEventBuilder 上调用 AddStatValue<V>。
算法
// after AddEvent(GtcSpan) returns the XEventBuilder:
function add_dynamic_stat(event_builder, name, value):
md = XPlaneBuilder::GetOrCreateStatMetadata(name) // intern by name → per-plane stat id
XStatsBuilder<XEvent>::AddStatValue<V>(event_builder, md, value) // append XStat, set oneof
```text
`AddStatValue<V>` 写入的 `oneof` case 取决于 value type,已在 call site 确认:
| Value kind | XStat oneof case | Field | Used by |
|---|---|---|---|
| `int64` | 3 (`int64_value`) | 4 | 通用 `device_*_ps` 对 |
| `uint64` | 2 (`uint64_value`) | 3 | DMA byte count(`AddStatValue<unsigned long>` @ `0xf1df460`) |
| `double` | (per schema) | — | SPI power(W) samples |
| `XStatMetadata&` (ref) | 7 (`ref_value`) | — | sync wait reason(interned string ref,`0xf1e1da0`) |
### 已观察到的动态 stats
已确认的 `name` → `value` 映射,有字节锚点:
- **Sync wait reason** — `"TensorCore waiting for Host Infeed"`(`@0x871cd36`,len `0x22`)作为 `SyncWait` duration event 上的 `ref_value`(case 7)。对于 `id==255` sentinel,stat name 为空(len 0)。
- **Counter sample** — `"Available Count"`(`@0x84e8ef6`,len `0xf`)位于 TC0 infeed-buffer counter line。
- **DMA byte count** — transferred byte count(来自 `MemoryDataEnd()`)作为 DMA duration event 上的 `uint64_value`(case 2)。
> **注意 —** *完整的*每个 subscriber field→stat-name 映射(每个 family 上哪个 decoded scalar 会变成 `core_id`/`chip_id`/`sync_flag_id`/`overlay_id`/`step_id`/`marker_payload`)**没有完全列举**;机制和高价值名称已确认,但穷尽 dump 每个 family 的每个 `ProcessTraceEntry`/`ProcessScTraceEntry` lambda 超出了范围。动态 stat-name 目录见 [XStat Metadata IDs](xstat-metadata-ids.md);除本处列出的名称外,请把每个 family 的 scalar 暴露视为**中等置信度**。
---
## Begin/End 配对 — Duration Events
### 目的
有些 event kind 是 *span*,不是 point:一次 sync wait 和一次 DMA 都作为两个 wire packet(begin + end)到达。subscriber 持有每个 core 的 *Tracker*,记录 begin,按 key 匹配 end,只有到那时才用匹配的 `{start, end}` span 调用 `AddEvent(GtcSpan)`,从而产生**一个** duration `XEvent`。`XEvent` 在 closing packet 到达前不存在;未匹配的 begin 不会产生 event(或在 trace 结束时 flush)。
### Sync-flag 配对 — `SyncTracker`
`SyncTracker` 是每个 core、每个 sync-flag 的状态机。`SyncSubscriber::ProcessTraceEntry`(pxc `0xf1eeee0`)会在 `{80,81,82,86,87,88}` 中的每个 sync trace-point 上 dispatch(由 `(id-80 < 9) & (0x1C7 >> (id-80))` 选择,已字节解码)。其中 tracker 本身只消费两个形成 span 的 id:`86`(block)和 `80`(unblock);其余会渲染为 point/counter event。匹配 key 是 `sync_flag_number`。
```c
function SyncTracker::ProcessTraceEntry(entry): // pxc 0xf1ef3c0
if entry.wrapper_case != 2: return // *a2 == 2 gate
switch entry.header.trace_point_id: // +0x18
case 86: // UNSUCCESSFUL_SYNC_ATTEMPT — the WAIT BLOCKS
ProcessSyncBlock(sync_flag_value, sync_flag_number) // 0xf2c46a0
case 80: // EXTERNAL_SYNC_FLAG_UPDATE_DMA_DONE ── DMA-side unblock
return ProcessSyncUnblock(value, sync_flag_number) // 0xf2c4700
// NOTE: this tracker only handles 86 (block) and 80 (unblock).
// id 87 (SUCCESSFUL_SYNC_ATTEMPT) is NOT processed here — it names
// a "SyncNoWait:" instantaneous event, not the end of a wait span.
function ProcessSyncBlock(value, sfn): // 0xf2c46a0
state.value = value // +0x08
state.sync_flag_num = sfn // +0x18 — THE MATCH KEY (line 18)
state.waiting = 1 // +0x28 (line 22-23)
// stash first-block start gtc at +0x58/+0x60
function ProcessSyncUnblock(value, sfn) -> optional<SyncWaitInfo>: // 0xf2c4700
if state.waiting != 1: return none // +0x28 == 1 (line 13)
if state.sync_flag_num != sfn: return none // +0x18 == sfn (line 15) — key match
result = state.SyncWaitInfo{ start_gtc, end_gtc=value } // copy the 32-byte pending block
state.waiting = 0
return result // SyncSubscriber renders it匹配时,SyncSubscriber::AddSyncWaitEvent @ 0xf1ef520 会用返回的 SyncWaitInfo 构建 GtcSpan{start, end},并在 TpuComponent 17("Tensor Core Sync Flag")上发出一个 duration XEvent,名称为 "SyncWait:<sfn>",带 ref_value wait-reason stat。Out-of-order 和 vector-wait completion(ProcessSyncBlockOOO @ 0xf2c47e0,ProcessSyncUnblockForVwait @ 0xf2c4740)会扩展匹配,同时比较 state+0x20 处的第二个 transaction/value。
陷阱 — begin packet 是
UNSUCCESSFUL_SYNC_ATTEMPT(id 86),不是一个叫“begin”的 opcode;硬件在 wait 阻塞时会发出一次失败的 sync attempt。tracker 中的 closing packet 是外部EXTERNAL_SYNC_FLAG_UPDATE_DMA_DONE(id 80),不是SUCCESSFUL_SYNC_ATTEMPT(id 87):SyncTracker::ProcessTraceEntry只路由 id 86 →ProcessSyncBlock,id 80 →ProcessSyncUnblock。按名称字面理解的重新实现会颠倒 begin/end,或错误地在 id 87 上关闭 span。配对语义是:block = wait 开始(id 86,failed attempt);unblock = wait 所阻塞的 DMA 完成(id 80)。Id 87(SUCCESSFUL_SYNC_ATTEMPT)是单独的瞬时"SyncNoWait:"event,表示 sync flag 已经满足,因此没有 span。瞬时 sync-flag op(Set/Add/Read,ids 81/82/88)同样不配对,它们会变成 point event 或 counter sample。
DMA 配对 — DmaSubscriber
DmaSubscriber::ProcessTraceEntry @ 0xf1dfee0 通过 GetDmaId() @ 0xf698180,在 FlatHashMap<uint64 dma_id, vector<pending-start>> 中按 key 记录 pending start。
function DmaSubscriber::ProcessTraceEntry(entry): // jxc 0xf1dfee0
if entry.CoreId() != this_core: return // filter
dma_id = entry.GetDmaId() // 0xf698180 — THE MATCH KEY
if entry.MemoryCommand() && entry.First(): // DMA STARTED (descriptor / first packet)
pending[dma_id].push(entry) // record start gtc
else if entry.MemoryDataEnd() || entry.Last(): // DMA COMPLETED
start = pending[dma_id].pop() // pop matching start
span = GtcSpan{ start.gtc, entry.gtc }
line = GetOrCreateLine(component_for(dma_id)) // a mem / Memcpy line
eb = TpuXLineBuilder::AddEvent(span, *meta) // 0xf1df1e0
add_dynamic_stat(eb, "<byte count>", entry.MemoryDataEnd()) // uint64_value
```text
> **注意 —** *其他*有状态 tracker 的内部 match-key offset(`StepTracker` 按 TraceMark sequence,`TaskTracker` 按 task id,`OverlayTracker` 按 overlay id)已通过符号定位,但**未在此做字节解码**;只有 `SyncTracker`(`sync_flag_number`)和 `DmaSubscriber`(`dma_id`)被确认到精确 key。请把 Step/Task/Overlay 配对 key 视为**低置信度**。
### 配对函数映射
| 函数 | 地址 | Match key |
|---|---|---|
| `SyncTracker::ProcessTraceEntry<pxc>` | `0xf1ef3c0` | `sync_flag_number` (`state+0x18`) |
| `SyncTracker::ProcessSyncBlock` | `0xf2c46a0` | stores key `+0x18`, waiting `+0x28` |
| `SyncTracker::ProcessSyncUnblock` | `0xf2c4700` | gates `+0x28==1`, cmp `+0x18==sfn` |
| `SyncSubscriber::AddSyncWaitEvent` | `0xf1ef520` | emits span on TpuComponent 17 |
| `DmaSubscriber::ProcessTraceEntry<jxc>` | `0xf1dfee0` | `dma_id` (FlatHashMap) |
| `StepTracker::ProcessTraceEntry` | `0xf2c4480` | TraceMark sequence (not decoded) |
| `TaskTracker::ProcessTraceEntry` (gfc) | `0xf2394e0` | task id (not decoded) |
| `OverlayTracker::ProcessTraceOperand` | `0xf2c3e40` | overlay id (not decoded) |
---
## XLine 分配 — TpuComponent Lanes
### 目的
每条 device-plane `XLine` 都是一个 `xprof::TpuComponent` enum ordinal。`TpuXPlaneBuilder::GetOrCreateLine(TpuComponent c)` @ `0xf1df120` 用 ordinal 作为 line id 调用 generic `XPlaneBuilder::GetOrCreateLine((long)c)`,并在 line 是新建时把它命名为 `TpuComponentName(c)`。**line id 是 component ordinal,名称固定。**哪个 subscriber 写入哪条或哪些 line,在每个 `ProcessTraceEntry` body 中硬编码。
### 算法
```c
function TpuXPlaneBuilder::GetOrCreateLine(this, plane_builder, component): // 0xf1df120
line = XPlaneBuilder::GetOrCreateLine(plane_builder, component) // 0x1cf4d9a0, id == ordinal
name = TpuComponentName(component) // 0x1c8ebb60, 149-arm table
if line.name is empty:
XLineBuilder::SetName(line, name) // 0xf1ad5a0
return linecomponent → line-name 目录
TpuComponentName @ 0x1c8ebb60 是一个位于 0xb43d200 的 149-arm rel32 jump table(bound check cmp $0x94),已字节解码。七个 entry 被 relocation 到 kXxxLineName string global(R_X86_64_RELATIVE addend = string VA)。这里不 dump 全部 149 行,而是按 band 组织;命名特定 subscriber target 的行会单独点出。
| Ordinal band | Lines | Owning subsystem |
|---|---|---|
| 0–6 | Host Interface, Steps*, XLA Modules*, XLA Ops*(3), Async XLA Ops*, Barna Ch12, XLA TraceMe | top-level HLO / step / TraceMe lanes |
| 7–20 | TC Overlay (7), Tensor Core (8), Scalar Unit (9), VPU (10), MXU vpush/vmatmul/vpop (11–13), XLU/XU/RPU (14–16), Tensor Core Sync Flag (17), TC IMEM/VMEM/SMEM (18–20) | TensorCore functional units + sync |
| 21–45 | Barna Core + its sync/fabric/concat/channels (28–43)/IMEM/BMEM | Barna Core engine |
| 46–67 | Sparse Core (46), SC TEC (47), SC TAC (48), Node Fabric, Host/ICI router lanes, HBM, Power Throttle (58), SC Modules*/Ops*/Syncs (65–67) | SparseCore + fabric + I/O |
| 68–116 | TAC 0–15 (68–83), TEC 0–15 (84–99), SC TraceMe (100), TEC TraceMe 0–15 (101–116) | per-tile SparseCore lanes |
| 117–148 | SC Steps* (117), power/thermal meters (118–130), GBMC sensors, PCIe util (134–139), cost model, stats lines (140–148) | firmware power/thermal/counter lanes |
* = relocated kXxxLineName global(例如 kXlaOpLineName @ 0x21c9e0b8 → 0x8534785 "XLA Ops";kStepLineName → "Steps";kSparseCoreStepLineName → "Sparse Core Steps")。
Subscriber → line 所有权
固定的 event→lane 路由,已在每个 subscriber body 中字节确认:
| Subscriber | Trace-points (pxc) | XLine(s) (TpuComponent) |
|---|---|---|
TensorCoreHloSubscriber | 84/85 (SET_TRACEMARK / TRACE_INSTRUCTION) | 3 "XLA Ops" |
SyncSubscriber | {80,81,82,86,87,88} (mask 0x1c7) | 17 "Tensor Core Sync Flag" (+ counter line) |
TensorCoreOverlaySubscriber | overlay trace-points | 7 "TC Overlay" |
TensorCoreStepSubscriber / StepTracker | SET_TRACEMARK (step marks) | 1 / 117 "Steps" / "SC Steps" |
ScalarFenceSubscriber | 89/90 (SCALAR_FENCE_START/END) | 9 "Scalar Unit" (fence span) |
DmaSubscriber | DMA req / data-end | mem / Memcpy line by dma_id |
FirmwareSubscriber | manager/power FW events | 120–130, 134–139, 141, 143 (kComponents, byte-read) |
PowerThrottleSubscriber | 97 / 200.. (THROTTLE_*) | 58 "Power Throttle" |
SpiSamplerSubscriber | 168/169 (SPI_SAMPLER_*) | 118/119 "SPI Sampler Power Meter(W)" |
SparseCore{Hlo,Task,Overlay,Syncs,Step}Subscriber | SC_* (108..135) | 46/47/48, 65/66/67/142 |
…OnDeviceTraceMeSubscriber | TraceMark TraceMe records | 6, 100, 101–116 "TraceMe" lanes |
LloOpEventSubscriber | TRACE_INSTRUCTION (LLO ops) | 8 "Tensor Core" + units 9–16 |
FirmwareSubscriber::GetTrackedComponents()::kComponents 已字节确认(19 项 int32 array,size 0x4c,对 vfc/vlc/glc/gfc 相同)= {120,121,122,123,124,125,126,127,128,129,130, 134,135,136,137,138,139, 141, 143}(注意 131–133、140、142 的空洞)。
Event-Type 行摘要
四项交付物(命名、stats、配对、line)按代表性 event 各占一行:
| Wire trace-point (pxc) | Subscriber | XLine | XEvent name source | Pairing key | Per-event XStats |
|---|---|---|---|---|---|
| 86 UNSUCCESSFUL_SYNC_ATTEMPT (begin) | Sync | 17 Sync Flag | SyncWait:<sfn> | sync_flag_number (block) | device_*_ps + "…waiting for Host Infeed" (ref) |
| 80 EXT_SYNC_FLAG_UPDATE_DMA_DONE (end) | Sync | 17 | SyncWait:<sfn> | sync_flag_number (unblock) | (closes the span begun by id 86) |
| 87 SUCCESSFUL_SYNC_ATTEMPT | Sync | 17 | SyncNoWait:<sfn> | (instantaneous, no span) | device_*_ps only |
| 81/82/88 SET/ADD/READ_SYNC_FLAG | Sync | 17 / counter | Set:/Add:/Read:<sfn> | (instantaneous) | "Available Count" (counter) |
| 84/85 SET_TRACEMARK / TRACE_INSTRUCTION | HLO | 3 XLA Ops | HLO op (symbolizer) | (none) | hlo_op/hlo_module/program_id |
| CMQ/HDE DMA req + data-end | Dma | mem / Memcpy | decimal id / op name | dma_id (First→Last) | uint64 byte count |
| 97 / 200.. THROTTLE_* | PowerThrottle | 58 Throttle | decimal id | run-length / point | throttle counters |
| 168/169 SPI_SAMPLER_* | SpiSampler | 118/119 Meter | decimal id | per-frame | power(W) double |
| MGR / power FW | Firmware | 120–130 / 134–139 / 141 / 143 | decimal id / label | run-length | power/util counters |
| (any unhandled banded id) | raw TraceEventSubscriber | the owning line | decimal string of id | (none) | device_*_ps only |
相关 Struct 偏移
TpuXLineBuilder +0x00/+0x08 base XLineBuilder {XPlane*, XLine*}
+0x10 per-line GTC clock object (clk for *1e9/(clk<<4))
+0x18 TpuXPlaneBuilder*
+0x38 device_offset_ps XStatMetadata* (StatType 147)
+0x40 device_duration_ps XStatMetadata* (StatType 148)
(AddEvent gates on +0x38 && +0x40 non-null)
XEvent (proto2) +0x18 stats RepeatedPtrField
+0x30 duration_ps (presence orb |= 4)
+0x38 offset_ps (oneof "data" case 2)
+0x40 oneof "data" case discriminant
XStat (proto2) +0x18 metadata_id (= XStatMetadata + 0x28)
+0x20 value
+0x28 oneof case (2 uint64 / 3 int64 / 7 ref / double)
SyncTracker +0x08 value
+0x18 sync_flag_number ── THE MATCH KEY
+0x20 2nd key (OOO / vwait)
+0x28 waiting bit
+0x58/+0x60 first-block start-gtc stash
SyncWaitInfo (returned) +0x00 start_gtc +0x08 end_gtc +0x20 present-byte
Subscriber base +0x08/+0x0c {core_id, chip_id} filter (from CoreId())
+0x10 TpuXPlaneBuilder*
+0x18 per-id XEventMetadata* cache (FlatHashMap, raw path)
CoreDispatcher FlatHashMap<trace_point_id, vector<shared_ptr<subscriber>>>
(Dispatch keys on TraceHeader+0x18)
```text
---
## 相关组件
| Component | Relationship |
|---|---|
| `ConvertTpuTraceToXPlaneV2<TraceEntry>` | 构建 XPlane 并注册 subscriber 集合的每个 family orchestrator |
| `tsl::profiler::XLineBuilder` / `XPlaneBuilder` | `TpuXLineBuilder` 包装的 generic builder primitives |
| `Symbolizer` / `TpuXPlaneSymbolizer` | 对 semantic-named events 的事后 HLO/PC symbol enrich |
| `TpuProfilerImpl::CollectData(XSpace*)` @ `0xef34860` | 调用转换的 drain entry |
---
## 交叉引用
- [Profiling and Telemetry 概览](overview.md) — 本页的 translation 所关闭的五阶段 capture pipeline
- [TraceEntriesCoder](trace-entries-coder.md) — 把 16 字节 packet 解码为本页消费的 `TraceEntry` proto 的设备 codec
- [XPlaneBuilder / XStat / TraceMe](xplane-xstat-traceme.md) — 本 translation 驱动的 builder API(`AddEvent`、`GetOrCreate*Metadata`、`AddStatValue`)
- [XEvent Metadata IDs](xevent-metadata-ids.md) — 两套命名机制填充的每个 plane interned event-name dictionary
- [XStat Metadata IDs](xstat-metadata-ids.md) — stat-id 目录:`device_offset_ps`(147)、`device_duration_ps`(148)和每个 subscriber 的动态名称
- [TracePoints 主注册表](tracepoints-master-registry.md) — dispatcher 路由所依据的 `trace_point_id` enum,以及 raw 十进制名称映射回的目标
- [Riegeli Trace Container](riegeli-trace-container.md) — 解码后的 `TraceEntry` stream 从中 unpack 的 on-wire container