Skip to content

TraceEntry 到 XEvent/XStat

本页中的所有地址和偏移都适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d)。该二进制包含完整的 C++ 符号;.text/.rodata VMA == 文件偏移。其他版本会有所不同。

摘要

本页记录设备 profiler 的最后一跳:subscriber 栈如何把解码后的 proto2 TraceEntry(每个 16 字节硬件包一个,由 TraceEntriesCoder 生成)转换为设备 XLine 上带有 XStat 注解的 XEventXPlaneBuilder API 页面负责构建器原语GetOrCreateLineGetOrCreate*MetadataAddEventAddStatValue);本页负责从解码 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 XStatdevice_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 的十进制字符串命名 XEventsemantic 路径计算 human name(SyncWait:<n>、通过 symbolizer 得到的 HLO op、固定 counter-line label)。
  • 通用 duration+stat stampTpuXLineBuilder::AddEvent(GtcSpan) 的 GTC→ps 计算,以及始终存在的 device_offset_ps/device_duration_ps XStat 对,再加上调用方在返回的 XEventBuilder 上追加的每个 subscriber 的动态 stats。
  • Begin/end 配对SyncTracker(key = sync_flag_number)和 DmaSubscriber(key = dma_id)是每个 core 的状态机,会把两个 packet 折叠成一个 duration span。
  • Line 分配 — 每条设备 XLine id 都是一个 TpuComponent ordinal,由 149 项 TpuComponentName 表命名;哪个 subscriber 拥有哪些 line 在代码中固定。
Pipeline 入口ConvertTpuTraceToXPlaneV2<TraceEntry> (pxc 0xf1d4360, +5 families)
DispatcherCoreDispatcher<TraceEntry>::Dispatch @ 0xf1ed280
注册CoreDispatcher::RegisterSubscriber @ 0xf1ecee0
通用 stampxprof::TpuXLineBuilder::AddEvent(GtcSpan, XEventMetadata&) @ 0xf1df1e0
Raw 命名TpuXLineBuilder::AddEvent<…ProcessTraceEntry lambda> (pxc 0xf1f26e0, +5)
GTC→ps 转换器GtcSpanConverter::TimespanFromGtcSpan @ 0xf2cb7e0
Line keyTpuXPlaneBuilder::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
通用 statsdevice_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 自己处理。

入口点

text
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_INSTRUCTION id 位于 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+0x38device_offset_ps,StatType 147)和 +0x40device_duration_ps,StatType 148);每条 line 的 GTC clock 对象位于 +0x10

c
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 应用的;在 raw XSpace 中,名称就是数字。raw XSpace 的 consumer 必须把十进制字符串名称通过 TracePoints 主注册表 映射回 enum。

机制 (b) — Semantic

有状态/装饰型 subscriber 会从解码字段计算 human-readable name:

  • SyncGetSyncFlagEventName<TraceEntry> @ 0xf1ef840TraceHeader.trace_point_id+0x18)做 switch,选择固定 prefix string,并追加 sync_flag_numberStrCat + FastIntToBuffer)。prefix 是字节解码的 rodata literal,按 trace_point_id keyed:80"SyncWait:"86"SyncWait:"87"SyncNoWait:"81"Set:"82"Add:"88"Read:"default arm,即 ids 8385,返回空字符串)。在每个 arm 内,entry+0x28 处的 event-oneof discriminant(分别为 0x25/0x26/0x27/0x2b/0x2c/0x2d)只用于选择正确的 typed accessor 来读取 sync_flag_number。一次 blocking wait(id 86 或 DMA-done id 80)→ "SyncWait:<sync_flag_number>"。(注意:"Barrier Wait:"/"Sync Wait:" 确实作为 rodata string 存在,但属于不同代码路径;GetSyncFlagEventName 从不发出它们。)
  • HLOTpuXPlaneBuilder::GetOrCreateXlaEventMetadata(pair<hlo_module, hlo_op>) @ 0xf1e4e40 加上 Symbolizer::{TensorCore,BarnaCore,SparseCore}Symbolize0xf57ce60/da0/ccc0)和 SetNameIfEmpty @ 0xf1e4f40 → HLO op name,在 "XLA Ops" line 上 intern。由 TRACE_INSTRUCTION/SET_TRACEMARK(pxc 85/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)0x211719e0id 的 base-10 文本
GetOrCreateEventMetadata(string&&)0x1cf4d380按名称 intern → metadata_id
GetSyncFlagEventName<TraceEntry> (pxc)0xf1ef840"SyncWait:<n>"
GetOrCreateXlaEventMetadata(pair)0xf1e4e40"XLA Ops" 上的 HLO op name
Symbolizer::TensorCoreSymbolize0xf57ce60HLO 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>

算法

c
// 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 @ 0xf2c47e0ProcessSyncUnblockForVwait @ 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。

c
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 line

component → 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 bandLinesOwning subsystem
0–6Host Interface, Steps*, XLA Modules*, XLA Ops*(3), Async XLA Ops*, Barna Ch12, XLA TraceMetop-level HLO / step / TraceMe lanes
7–20TC 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–45Barna Core + its sync/fabric/concat/channels (28–43)/IMEM/BMEMBarna Core engine
46–67Sparse 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–116TAC 0–15 (68–83), TEC 0–15 (84–99), SC TraceMe (100), TEC TraceMe 0–15 (101–116)per-tile SparseCore lanes
117–148SC 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 @ 0x21c9e0b80x8534785 "XLA Ops";kStepLineName → "Steps";kSparseCoreStepLineName → "Sparse Core Steps")。

Subscriber → line 所有权

固定的 event→lane 路由,已在每个 subscriber body 中字节确认:

SubscriberTrace-points (pxc)XLine(s) (TpuComponent)
TensorCoreHloSubscriber84/85 (SET_TRACEMARK / TRACE_INSTRUCTION)3 "XLA Ops"
SyncSubscriber{80,81,82,86,87,88} (mask 0x1c7)17 "Tensor Core Sync Flag" (+ counter line)
TensorCoreOverlaySubscriberoverlay trace-points7 "TC Overlay"
TensorCoreStepSubscriber / StepTrackerSET_TRACEMARK (step marks)1 / 117 "Steps" / "SC Steps"
ScalarFenceSubscriber89/90 (SCALAR_FENCE_START/END)9 "Scalar Unit" (fence span)
DmaSubscriberDMA req / data-endmem / Memcpy line by dma_id
FirmwareSubscribermanager/power FW events120–130, 134–139, 141, 143 (kComponents, byte-read)
PowerThrottleSubscriber97 / 200.. (THROTTLE_*)58 "Power Throttle"
SpiSamplerSubscriber168/169 (SPI_SAMPLER_*)118/119 "SPI Sampler Power Meter(W)"
SparseCore{Hlo,Task,Overlay,Syncs,Step}SubscriberSC_* (108..135)46/47/48, 65/66/67/142
…OnDeviceTraceMeSubscriberTraceMark TraceMe records6, 100, 101–116 "TraceMe" lanes
LloOpEventSubscriberTRACE_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)SubscriberXLineXEvent name sourcePairing keyPer-event XStats
86 UNSUCCESSFUL_SYNC_ATTEMPT (begin)Sync17 Sync FlagSyncWait:<sfn>sync_flag_number (block)device_*_ps + "…waiting for Host Infeed" (ref)
80 EXT_SYNC_FLAG_UPDATE_DMA_DONE (end)Sync17SyncWait:<sfn>sync_flag_number (unblock)(closes the span begun by id 86)
87 SUCCESSFUL_SYNC_ATTEMPTSync17SyncNoWait:<sfn>(instantaneous, no span)device_*_ps only
81/82/88 SET/ADD/READ_SYNC_FLAGSync17 / counterSet:/Add:/Read:<sfn>(instantaneous)"Available Count" (counter)
84/85 SET_TRACEMARK / TRACE_INSTRUCTIONHLO3 XLA OpsHLO op (symbolizer)(none)hlo_op/hlo_module/program_id
CMQ/HDE DMA req + data-endDmamem / Memcpydecimal id / op namedma_id (First→Last)uint64 byte count
97 / 200.. THROTTLE_*PowerThrottle58 Throttledecimal idrun-length / pointthrottle counters
168/169 SPI_SAMPLER_*SpiSampler118/119 Meterdecimal idper-framepower(W) double
MGR / power FWFirmware120–130 / 134–139 / 141 / 143decimal id / labelrun-lengthpower/util counters
(any unhandled banded id)raw TraceEventSubscriberthe owning linedecimal string of id(none)device_*_ps only

相关 Struct 偏移

text
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