XPlane / XStat / TraceMe 发射
本页中的所有地址和偏移都适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d)。其他版本会有所不同。
摘要
libtpu 携带了一份完整的、位于二进制内部的 TensorFlow/TSL profiler 发射栈副本,不只是 XSpace/XPlane/XLine/XEvent/XStat proto2 消息类,还包括库中每个生产者用来填充它们的 builder 层(tsl::profiler::XPlaneBuilder、XLineBuilder、XEventBuilder、XStatBuilder)。从硬件环形缓冲区解码出的设备 trace-entry,以及在运行时线程上打开的主机 TraceMe 作用域,最终都会落到写入同一个 XSpace 的同一组 builder 调用。本页负责说明这个对象模型和 builder API:四层事件树、每个 plane 的两个内建 metadata 字典、GetOrCreateLine/AddEvent/AddStat 构造界面,以及生成 /host:* line 的主机侧 TraceMe/TraceMeEncode 插桩。
这个模型可以映射到任何 LLVM/XLA 工程师已经熟悉的框架。可以把 XSpace 看作 profiling module,每个 XPlane 看作一个带有私有符号表的 function(一个设备核心或一个主机线程组),每个 XLine 看作一个 basic-block timeline,每个 XEvent 看作一条带持续时间的 instruction,每个 XStat 看作一个 operand annotation。两个符号表 XEventMetadata 和 XStatMetadata 都是内建化的:event 只存储一个 64 位 metadata_id 外键,而人类可读名称、类别和源位置在每个 plane 中只保存一次。这是最重要的结构事实:生产者和消费者必须就 id↔name 字典达成一致,因此 id 枚举被拆到各自页面(XEvent Metadata IDs、XStat Metadata IDs),并且不会在这里重复。
两种 builder 方言并存。通用的 tsl::profiler builder(XPlaneBuilder::GetOrCreateLine @ 0x1cf4d9a0、XLineBuilder::AddEvent @ 0x1cf4dc40)是与上游一致的核心。libtpu 用 TPU 方言(xprof::TpuXPlaneBuilder、xprof::TpuXLineBuilder::AddEvent @ 0xf1df1e0)包装它们,加入设备语义 line key(TpuComponent、IciPort、GtcSpan)和 HLO 感知 metadata。设备路径(trace-entry → TpuXLineBuilder::AddEvent)由 TraceEntry → XEvent/XStat 负责;本页记录该方言所依托的通用 builder,以及折叠进同一个 XSpace 的、完全独立的主机 TraceMe 路径。
对于重新实现,本页重建的契约是:
- 四层对象模型 —
XSpace⊃XPlane⊃XLine⊃XEvent⊃XStat,每个XPlane有两个内建 metadata map,以及每一层所需的 proto2 字段布局。 - builder API —
GetOrCreateLine、GetOrCreate{Event,Stat}Metadata(按 id 和按 name)、XLineBuilder::AddEvent,以及XEventBuilder/XStatBuildermutator,包括它们写入的精确 proto 偏移。 - metadata 内建协议 —
proto2::Map<int64, XEventMetadata>/Map<int64, XStatMetadata>、带 SOO 缓存的int64→XLine*line map,以及为什么 id 是按 plane 分配的。 - 主机
TraceMe路径 — thread-local 无锁事件捕获(TraceMeRecorder)、name#k1=v1,k2=v2#TraceMeEncode线格式、(thread<<32)|counteractivity-id 方案,以及ConvertCompleteEventsToXPlanehost-line 桥。
| 对象模型 | tensorflow.profiler.XSpace ⊃ XPlane ⊃ XLine ⊃ XEvent ⊃ XStat (proto2) |
| 通用 builder | tsl::profiler::{XPlaneBuilder, XLineBuilder, XEventBuilder, XStatBuilder} |
GetOrCreateLine(int64) | XPlaneBuilder::GetOrCreateLine @ 0x1cf4d9a0 |
AddEvent(XEventMetadata&) | XLineBuilder::AddEvent @ 0x1cf4dc40 |
| Event-metadata 内建(按 id) | XPlaneBuilder::GetOrCreateEventMetadata(int64) @ 0x1cf4cfe0 |
| Stat-metadata 内建(按 id) | XPlaneBuilder::GetOrCreateStatMetadata(int64) @ 0x1cf4d500 |
| Event-metadata 内建(按 name) | GetOrCreateEventMetadata(string_view) @ 0x1cf4d0c0 |
| TPU 设备方言 | xprof::TpuXLineBuilder::AddEvent(GtcSpan, XEventMetadata&) @ 0xf1df1e0 |
| 主机捕获 | tsl::profiler::TraceMeRecorder::Record @ 0x207ff580 |
| 主机编码 | tensorflow::profiler::TraceMeEncode @ 0x10885f20 |
| 主机 → XPlane 桥 | ConvertCompleteEventsToXPlane @ 0xf32ff00 |
XSpace 对象模型
目的
XSpace 是一次 profiling session 的根容器,也是通过 PJRT Profiler extension 离开 libtpu 的唯一对象,即序列化后的 XSpace blob。它下面的一切都是生产者在序列化前构建出的结构。该模型是与上游一致的 proto2;libtpu 的贡献是填充它的 TPU 专用 event/stat id,而不是 schema。
层级布局
XSpace
├─ repeated XPlane planes // one per device core + one per host thread-group
├─ repeated string errors
├─ repeated string warnings
└─ repeated string hostnames
XPlane
├─ int64 id // plane id (e.g. device:TPU:0, host:0)
├─ string name // "/device:TPU:0", "/host:0", …
├─ repeated XLine lines
├─ map<int64, XEventMetadata> event_metadata // interned event dictionary
├─ map<int64, XStatMetadata> stat_metadata // interned stat dictionary
└─ map<int64, string> (string interning, optional)
XLine
├─ int64 id // timeline id within the plane
├─ int64 display_id
├─ string name / display_name
├─ int64 timestamp_ns // line origin (absolute wall clock)
├─ int64 duration_ps // line extent
└─ repeated XEvent events
XEvent
├─ int64 metadata_id // FK → XPlane.event_metadata
├─ int64 offset_ps // start, relative to XLine.timestamp_ns
├─ int64 duration_ps
├─ int64 num_occurrences
└─ repeated XStat stats
XStat
├─ int64 metadata_id // FK → XPlane.stat_metadata
└─ oneof value { int64 | uint64 | double | bytes str_value | int64 ref_value }
```text
> **注意 —** 时间存在两种尺度。`XLine.timestamp_ns` 是**纳秒**级的绝对 wall-clock 起点;`XEvent.offset_ps`/`duration_ps` 是相对该起点的**皮秒**。设备 cycle counter 两者都不是,它在到达 `XEvent` 之前,会由 `TpuXLineBuilder` 使用每代硬件的 clock period 转换成 ps offset。混淆这些尺度是典型的计时错误;见 [profiling overview](overview.md) 中的 timestamp 说明。
### 为什么要内建
每个 `XPlane` 上的两个 `map<int64, *Metadata>` 字段,是 `XEvent`/`XStat` 查入的字典。重复一万次的 `TpuExecute` event 会存储一万个 8 字节 `metadata_id` 值,以及*一个* `XEventMetadata{name:"TpuExecute", …}`。内建是按 plane 进行的,而不是全局的:`/device:TPU:0` 和 `/device:TPU:1` 上的同一个逻辑 event,会在各自 plane 的 map 中获得新分配的 id。因此消费者必须读取 `XPlane.event_metadata` 才能解析任何 `XEvent.metadata_id`,不存在跨 plane 的 id 命名空间。生产者和消费者共享的 id 目录见 [XEvent Metadata IDs](xevent-metadata-ids.md) 和 [XStat Metadata IDs](xstat-metadata-ids.md)。
---
## XPlaneBuilder API
### 目的
`XPlaneBuilder` 是单个 `XPlane` 上的 mutation handle。它有三项职责:按 id 交出(或创建)`XLine`,按 id 或 name 内建 `XEventMetadata`/`XStatMetadata`,并缓存查找结果,让热解码循环中的重复调用不用重新扫描 proto map。二进制内部实例由所列地址处的 `tsl::profiler::XPlaneBuilder` 符号确认,即该模型在 libtpu 中被*实例化*,而不只是声明。
### 对象布局
从反编译出的 builder 看,`XPlaneBuilder` 持有后端 `XPlane*` 和三个侧向缓存:
| 字段 | 偏移(qword index) | 类型 | 含义 |
|---|---|---|---|
| plane backing | `+0x10` (`*((q*)this+2)`) | `XPlane*` | 正在构建的 proto;所有 `RepeatedPtrFieldBase::Add` 目标都从这里解析 |
| line map | `+0x68` (`this+104`) | `absl flat_hash_map<int64, XLine*>` (SOO) | id → line 缓存;≤1 个条目时启用小对象优化 |
| SOO single-slot key | `+0x70`/`+0x78` (`this+14/15`) | `int64` | map 增长越过 SOO 前的内联 last-line id/ptr |
| event-metadata cache | `+0x80` (`this+16`) | hash-set storage | metadata 查找的后端 |
> **怪癖 —** `GetOrCreateLine` (@ `0x1cf4d9a0`) 不会直接进入 hash map。第一条 line 会把 id 内联存进两个 builder qword(`this+14`、`this+15`),由 thread-local fast-path counter(`__tls_get_addr(&qword_22048D78)`)保护;只有第二个不同 line id 出现时才会调用 `GrowSooTableToNextCapacityAndPrepareInsert`。预先为每个 plane 分配完整 `flat_hash_map` 的重新实现能匹配*语义*,但不匹配布局;如果它试图与二进制偏移交叉检查,就会读错偏移。
### `GetOrCreateLine(int64 id)` — @ `0x1cf4d9a0`
```c
function GetOrCreateLine(builder, id): // 0x1cf4d9a0
// Fast path: SOO single-slot or grown flat_hash_map keyed by id
if line_map_size > 1:
slot = find_or_prepare_insert_large(line_map, id) // absl raw_hash_set
else:
// inline single-entry cache in builder+14/+15, gated by a
// thread-local fast-path counter to avoid hashing the common case
slot = soo_single_slot(builder, id)
if slot.value != null: // already created
return slot.value
// Miss: append a fresh XLine to XPlane.lines and back-fill id
plane = builder->plane // *(builder+2)
xline = RepeatedPtrFieldBase::Add<XLine>(plane+24) // proto repeated field
plane->_has_bits |= 1 // +0x10 |= 1
slot.value = xline
*(xline + 56) = id // XLine.id @ +0x38
xline->_has_bits |= 8 // mark id present
return xline契约:GetOrCreateLine 在一个 plane 内对 id 是幂等的,返回稳定的 XLine*,并且只在首次请求时才惰性追加到 XPlane.lines。TPU 方言中的 TpuXPlaneBuilder::GetOrCreateLine(TpuComponent) @ 0xf1df120 和 GetOrCreateLine(IciPort) @ 0xf25af20 是轻量包装:把设备语义 key 映射到 int64 line id,然后调用这里。
Metadata 内建 — 按 id
GetOrCreateEventMetadata(int64) @ 0x1cf4cfe0 和 GetOrCreateStatMetadata(int64) @ 0x1cf4d500 是内建原语。两者形状相同,只有目标 map 和回填字段偏移不同:
function GetOrCreateEventMetadata(builder, id): // 0x1cf4cfe0
plane = builder->plane // *(builder+2)
plane->_has_bits |= 0x10 // mark event_metadata present
SyncMapWithRepeatedField(plane->event_metadata) // proto2 map<->repeated sync
md = Map<int64, XEventMetadata>::TryEmplace( // insert-or-find by id
plane->event_metadata_map, id)
md->id = id // XEventMetadata.id @ +0x68
md->_has_bits |= 0x20
return md // caller fills name/category
function GetOrCreateStatMetadata(builder, id): // 0x1cf4d500
plane->_has_bits |= 0x20 // mark stat_metadata present
SyncMapWithRepeatedField(plane->stat_metadata)
md = Map<int64, XStatMetadata>::TryEmplace(plane->stat_metadata_map, id)
md->id = id // XStatMetadata.id @ +0x38
md->_has_bits |= 4
return md
```text
> **注意 —** 回填偏移不同,因为两个 metadata 消息布局不同:`XEventMetadata.id` 落在 proto offset `+0x68`(该消息还带有 `name`、`display_name`、`metadata` bytes 和 child stats),而 `XStatMetadata.id` 落在 `+0x38`(只带有 `id`、`name`、`description`)。两个函数都返回*指向 proto map node 内部的指针*,因此调用方会原地修改内建消息,不存在 copy-back 步骤。
### Metadata 内建 — 按 name
按 name 的重载(`GetOrCreateEventMetadata(string_view)` @ `0x1cf4d0c0`,以及位于 `0x1cf4d380`/`0x1cf4d0c0` 的 `string`/`&&` 变体;stat 对应项在 `0x1cf4d5e0`/`0x1cf4d8a0`)维护*第二个*索引:name→id hash,使两个使用同一 event 名称的生产者得到同一个内建条目。名称是新的时,它们分配新 id,通过上面的 id 路径创建 metadata,并设置其 `name` 字段。只读同伴 `GetEventMetadata(string_view)` @ `0x1cf4d480` 和 `GetStatMetadata(*)` @ `0x1cf4d4c0`/`0x1cf4d560` 只查找不创建,供必须找到已有 id 的消费者使用。
| Builder method | Key | Address | Creates? |
|---|---|---|---|
| `GetOrCreateEventMetadata(int64)` | id | `0x1cf4cfe0` | yes |
| `GetOrCreateEventMetadata(string_view)` | name | `0x1cf4d0c0` | yes |
| `GetOrCreateEventMetadata(string&&)` | name | `0x1cf4d380` | yes |
| `GetEventMetadata(string_view)` | name | `0x1cf4d480` | no (lookup) |
| `GetOrCreateStatMetadata(int64)` | id | `0x1cf4d500` | yes |
| `GetOrCreateStatMetadata(string_view)` | name | `0x1cf4d5e0` | yes |
| `GetOrCreateStatMetadata(string&&)` | name | `0x1cf4d8a0` | yes |
| `GetStatMetadata(int64)` | id | `0x1cf4d560` | no (lookup) |
| `GetStatMetadata(string_view)` | name | `0x1cf4d4c0` | no (lookup) |
| `CreateEventMetadata()` | — | `0x1cf4d040` | yes (no key) |
| `GetOrCreateCounterLine()` | — | `0x1cf4db20` | yes |
---
## XLineBuilder / XEventBuilder API
### 目的
`XLineBuilder` 是一个 `XLine` 上的 mutation handle;它唯一的热方法 `AddEvent` 会追加一个由已内建 `XEventMetadata` 标识的 `XEvent`,并返回用于填充 event timing 和 stats 的 `XEventBuilder`。`XStatBuilder` 类似地填充一个 `XStat` 的值。
### `XLineBuilder::AddEvent(const XEventMetadata&)` — @ `0x1cf4dc40`
```c
function XLineBuilder::AddEvent(line_builder, md): // 0x1cf4dc40
xline = line_builder->line // *(md) → line backing via builder
arena = xline->arena // *(line+8), low-bit tagged
xevent = RepeatedPtrFieldBase::Add<XEvent>(xline+24) // append to XLine.events
xline->_has_bits |= 1
xevent->metadata_id = md->id // *(xevent+40) = *(md+88) → XEvent.metadata_id @ +0x28
xevent->_has_bits |= 2
// returns an XEventBuilder { line_builder copy, xevent* } in `this`
return XEventBuilder{ line=line_builder.line, event=xevent }该函数密集但明确:它在线的 repeated field 上分配一个 XEvent,从内建的 XEventMetadata 复制 metadata id(*(md+88) → +0x28 处的 XEvent.metadata_id),并返回一个 XEventBuilder 值(尾部的 vpermilps/vmovups 把 line backing 和新 event 指针打包进返回的 builder)。随后调用方在返回的 builder 上设置 timing 和 stats。
陷阱 —
AddEvent接收的是XEventMetadata&,不是 id 整数。metadata 在AddEvent前必须已经在这个 plane 中内建过(通过父XPlaneBuilder上的GetOrCreate*Metadata调用);该函数会读取*(md+88)来复制 id,自身不做任何内建。用属于另一个 plane 的 metadata 对象调用AddEvent会复制外来 id,并悄悄把 event 错误地键入。
XEventBuilder mutator
在 AddEvent 之后,XEventBuilder 携带 {line*, event*},并暴露直接写入 XEvent proto 的内联 setter:
| Setter(逻辑) | 写入 | XEvent 偏移 |
|---|---|---|
SetOffsetPs / SetTimestampNs(line_ns) | offset_ps(相对 line 起点) | +0x18 |
SetDurationPs / SetEndTimestampPs | duration_ps | +0x20 |
SetNumOccurrences | num_occurrences | +0x30 |
AddStatValue(XStatMetadata&, value) | 向 XEvent.stats 追加 XStat | repeated +0x38 |
AddStatValue 是 XEvent 层面的 AddEvent 类比物:它向 event 的 repeated stats 字段追加一个 XStat,从传入的(已内建的)XStatMetadata 设置 XStat.metadata_id,并把值写入 oneof。proto schema 中观察到的值变体是 int64/uint64/double/bytes (str_value)/int64 (ref_value);ref_value 引用 plane 字符串表中的内建字符串,用于大型或重复字符串 stat。
TPU 设备方言
设备路径不会直接调用通用 AddEvent;它调用 xprof::TpuXLineBuilder::AddEvent(GtcSpan, const XEventMetadata&) @ 0xf1df1e0,该函数在 GtcSpan(全局时间计数器 start/end 对)中携带设备 timing,并委托给通用 builder。模板实例化 TpuXLineBuilder::AddEvent<…TraceEntry…>(每个芯片家族一个:pxc @ 0xf1f26e0、vfc、vlc、glc、gfc、jxc)是已解码 TraceEntry 变成 XEvent 的精确汇合点,每一个都是下方主机桥的设备对应物。这些转换(cycle→ps 转换、哪些 scalar 变成哪些 XStat)由 TraceEntry → XEvent/XStat 负责;为它们供给数据的 codec 见 TraceEntriesCoder。
设备 Trace-Entry 如何变成 XEvent
五阶段捕获流水线(概览)最终落到这个对象模型。把两者缝合起来,一个已解码的 TraceEntry 会按如下方式落到设备 XLine 上:
TraceEntry (decoded proto, oneof variant = the event)
│
├─ trace_point_id ──▶ map to enum name string
│ │
│ └─▶ XPlaneBuilder::GetOrCreateEventMetadata(name) // intern → metadata_id
│
├─ TpuXPlaneBuilder::GetOrCreateLine(TpuComponent/IciPort) // 0xf1df120 / 0xf25af20
│ └─▶ XPlaneBuilder::GetOrCreateLine(int64) // 0x1cf4d9a0
│
├─ TpuXLineBuilder::AddEvent(GtcSpan, XEventMetadata&) // 0xf1df1e0
│ └─▶ XLineBuilder::AddEvent(XEventMetadata&) // 0x1cf4dc40 → XEvent
│
├─ timestamp (device cycle counter) ──▶ ÷ clock period ──▶ XEvent.offset_ps / duration_ps
│
└─ variant scalars (transaction_id, core_id, byte counts, …)
└─▶ GetOrCreateStatMetadata(name) + AddStatValue(...) ──▶ XStat[]
```text
重新实现者必须内化的关键观察是:设备 `trace_point_id`(一个分段的硬件 enum,有空洞且特定于家族)**从不**存进 `XEvent`。它在第 5 阶段被翻译成*名称字符串*,该名称通过 `GetOrCreateEventMetadata` 内建,而 `XEvent.metadata_id` 是*plane-local* 的内建索引,是完全不同的数字。完整翻译表见 [TraceEntry → XEvent/XStat](trace-entry-to-xevent.md)。
---
## 主机 TraceMe 路径
### 目的
主机 event,包括 `TpuCompile`、`TpuExecute`、队列提交、megascale transport、allocator 活动,不来自硬件环形缓冲区。它们来自在 TPU-runtime 线程上打开的 `tsl::profiler::TraceMe` RAII 作用域。`TraceMe` 子系统按线程无锁捕获这些作用域,然后转换器把捕获到的 scope 折叠进同一个 `XSpace` 的 `/host:*` `XLine`,设备路径也写入这个 `XSpace`。这是 [profiling overview](overview.md) 中所称“两种来源,一个 `XSpace`”的另一半。
### 捕获 — `TraceMeRecorder`
`TraceMe` 作用域在构造时记录 start event,在析构时记录 end event,二者都通过 `TraceMeRecorder::Record` @ `0x207ff580`。`Record` 被刻意设计为无锁:它写入**thread-local** 的分块 event queue(`PerThread<ThreadLocalRecorder>`),因此生产主机 event 永远不会争用全局锁。
```c
function TraceMeRecorder::Record(event): // 0x207ff580
tls = PerThread<ThreadLocalRecorder>::Get() // lazily inited per thread
queue = tls->active_chunk // *(tls+48)
n = tls->count // *(tls+56)
slot = queue + 16 + 40 * (n - queue->base) // 40-byte Event record
*slot = move(event) // {activity_id, name, ts, …}
if (n + 1) - queue->base == 1638: // chunk full (1638 events)
next = new chunk(0x10000) // 64 KiB, zeroed
next->base = n + 1
queue->next = next
tls->active_chunk = next // append-only linked chunks
tls->count = n + 1怪癖 — chunk 在链接新的 64 KiB chunk 前恰好容纳 1638 个 event。这是
(0x10000 − 16) / 40 ≈ 1638:chunk 是 65536 字节块,16 字节 header(baseindex +nextpointer),其余是 40 字节Eventrecord 的扁平数组。按 event 数量而不是固定 64 KiB block 来确定 chunk 大小的重新实现,会在边界处偏离,并错误拼接Consume依赖的 start/end 配对。
Activity id — NewActivityId
Start 和 end event 通过 TraceMeRecorder::NewActivityId @ 0x207ff7a0 生成的 64 位 activity id 配对:
function NewActivityId(): // 0x207ff7a0
if !tls.thread_id_set: // first call this thread
tls.thread_id = atomic_fetch_add(&thread_counter, 1) // global thread index
tls.thread_id_set = true
id = (uint64(tls.thread_id) << 32) | tls.local_counter // pack
tls.local_counter += 1
return id
```text
该 id 将全局唯一的 32 位 thread index 打包到高半部,并将每线程单调 counter 放在低半部:`(thread_index << 32) | per_thread_seq`。这保证了全局唯一性,同时热路径上没有任何跨线程同步;唯一的 atomic 是*每线程一次*的 `thread_counter` 递增。消费者使用这个 id 将 `TraceMeProducer` 作用域与它的 `TraceMeConsumer` 配对(可在许多 `TraceMe::AppendMetadata` 实例化中看到 `ContextType`/flow-event 关联)。
### 编码 — `TraceMeEncode`
名称携带 key/value metadata 的 `TraceMe` 使用 `tensorflow::profiler::TraceMeEncode` @ `0x10885f20` 将参数序列化进*event name string 本身*,采用规范 TraceMe metadata 线格式:
```text
name#key1=value1,key2=value2,…#TraceMeEncode(name, {TraceMeArg…}) 构建一个字符串:基础 name,然后是 #,再是逗号分隔的 key=value 对,最后是闭合 #。反编译显示它会对 name 执行 strlen,把输出缓冲区大小设为 name_len + Σ(key_len + value_len) + (2·argc + 1)(#、,、=、# 标点),若超过 22 字节 SSO buffer 则堆分配,并追加每个 TraceMeArg。数值参数会内联转成字符串。消费者侧把 #…# 后缀解析回 XStat,因此 TraceMe 参数会变成主机 XEvent 上的 XStat,这正好镜像了设备 variant scalar 变成 XStat 的方式。
注意 —
TraceMeEncode是主机 event 的唯一 metadata 通道:捕获时没有独立的 stat-id 字段。key/value 对在转换前都携带在 name string 内;转换时 parser 将每个 key 内建为XStatMetadata,并将每个 value 作为XStat值。这就是为什么主机 stat name 是动态字符串(按 name 内建),而设备 stat id 是固定 enum(按 id 内建):两半通过同一个GetOrCreateStatMetadata的不同重载进行内建。
主机 → XPlane 桥 — ConvertCompleteEventsToXPlane
采集时,主机 sub-profiler(HostTracer)调用 TraceMeRecorder::Consume @ 0x207fe700,把每个线程的 chunk queue drain 到 ThreadEvents vector 中;随后 tsl::profiler::ConvertCompleteEventsToXPlane @ 0xf32ff00 将它们折叠进一个 XPlane(/host:0)。这个转换器是设备 TpuXLineBuilder::AddEvent 路径的主机类比物:反编译显示它遍历捕获到的 event,对每个 event name 调用 GetOrCreateEventMetadata,调用 RepeatedPtrFieldBase::Add<XEvent> 追加,并对每个解码出的 metadata pair 调用 GetOrCreateStatMetadata + 追加 XStat,也就是上文记录的同一组 builder 原语,只是由主机捕获而不是设备 codec 驱动。
TraceMeRecorder (per-thread lock-free chunks)
│ Stop() → Consume() @ 0x207fe700 → vector<ThreadEvents>
▼
ConvertCompleteEventsToXPlane @ 0xf32ff00
│ per event:
│ name ──▶ GetOrCreateEventMetadata(name) → XEvent.metadata_id
│ start/end ──▶ XEvent.offset_ps / duration_ps (line origin = capture start)
│ "#k=v#" args──▶ GetOrCreateStatMetadata(k) + AddStatValue(v) → XStat[]
│ one XLine per producing thread (line id = thread id)
▼
/host:0 XPlane ── merged into the same XSpace as the device planes
```text
结果是:主机 `TraceMe` 作用域和设备硬件 trace-entry 会到达同一个 `XSpace` 的不同 plane(`/host:N` vs `/device:TPU:N`),由相同的 `XPlaneBuilder`/`XLineBuilder` API 构建,仅通过各自的 metadata 字典和 line-key 约定区分。
---
## Builder 函数映射
| Function | Address | Role |
|---|---|---|
| `XPlaneBuilder::GetOrCreateLine(int64)` | `0x1cf4d9a0` | id → `XLine`,SOO 缓存,惰性追加 |
| `XPlaneBuilder::GetOrCreateEventMetadata(int64)` | `0x1cf4cfe0` | 按 id 内建 event metadata |
| `XPlaneBuilder::GetOrCreateEventMetadata(string_view)` | `0x1cf4d0c0` | 按 name 内建 event metadata |
| `XPlaneBuilder::GetOrCreateStatMetadata(int64)` | `0x1cf4d500` | 按 id 内建 stat metadata |
| `XPlaneBuilder::GetOrCreateStatMetadata(string_view)` | `0x1cf4d5e0` | 按 name 内建 stat metadata |
| `XPlaneBuilder::CreateEventMetadata()` | `0x1cf4d040` | 分配无 key metadata |
| `XPlaneBuilder::GetOrCreateCounterLine()` | `0x1cf4db20` | 专用 counter timeline |
| `XLineBuilder::AddEvent(const XEventMetadata&)` | `0x1cf4dc40` | 追加 `XEvent`,复制 metadata id |
| `TpuXLineBuilder::AddEvent(GtcSpan, XEventMetadata&)` | `0xf1df1e0` | 设备方言包装 |
| `TpuXPlaneBuilder::GetOrCreateLine(TpuComponent)` | `0xf1df120` | 设备语义 line key |
| `TpuXPlaneBuilder::GetOrCreateLine(IciPort)` | `0xf25af20` | ICI-port line key |
| `TraceMeRecorder::Record(Event&&)` | `0x207ff580` | 无锁按线程捕获 |
| `TraceMeRecorder::NewActivityId()` | `0x207ff7a0` | `(thread<<32)\|counter` id |
| `TraceMeRecorder::Consume()` | `0x207fe700` | drain 按线程 chunk |
| `TraceMeEncode(name, args)` | `0x10885f20` | `name#k=v,…#` 线格式 |
| `ConvertCompleteEventsToXPlane(...)` | `0xf32ff00` | host events → `/host` XPlane |
---
## 相关组件
| Component | Relationship |
|---|---|
| `tsl::profiler::ProfilerCollection` | 拥有驱动这些 builder 的主机(`HostTracer`)和设备(`TpuProfilerImpl`)sub-profiler |
| `xprof::TpuXLineBuilder` (templated) | 设备侧 `AddEvent<TraceEntry>` 汇合点;`ConvertCompleteEventsToXPlane` 的设备对应物 |
| PJRT Profiler extension | 将完成的 `XSpace` 序列化出库 |
---
## 交叉引用
- [Profiling and Telemetry Overview](overview.md) — 五阶段流水线,以及本页 builder 构造的双来源(`/host` + `/device`)`XSpace`
- [TraceEntry → XEvent/XStat](trace-entry-to-xevent.md) — 调用 `TpuXLineBuilder::AddEvent` 的设备侧翻译,以及 cycle→ps 转换
- [TraceEntriesCoder](trace-entries-coder.md) — 生成本模型消费的 `TraceEntry` proto 的设备 codec
- [XEvent Metadata IDs](xevent-metadata-ids.md) — `GetOrCreateEventMetadata` 查入的内建 event-id 字典
- [XStat Metadata IDs](xstat-metadata-ids.md) — `GetOrCreateStatMetadata` 查入的内建 stat-id 字典
- [Task Proto](task-proto.md) — 框定一次 profiling run 的外围 task/session descriptor
- [PJRT Profiler Extension](../pjrt/ext-profiler.md) — 将组装好的 `XSpace` 序列化给 client 的 C-ABI