DMA 端点渲染
本页所有地址均适用于 wheel
libtpu-0.0.40中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d,这是无歧义锚点;运行时报告的0.103无法在二进制中静态验证)。其他构建会有所不同。所有偏移均为 VMA;.text和.rodata的映射满足 VMA == 文件偏移;.data.rel.ro为 VMA − 0x200000。
摘要
TPU 上的 DMA 描述符携带一整对端点:一个源内存空间、一个目标内存空间、两端各自的 opcode,以及一组 sync-flag 目标。每个端点都是从片上地址映射中取得的 (mem_id, core_id) 元组:HBM、TensorCore VMEM/SMEM/IMEM bank、BarnaCore / SparseCore 本地内存、CMEM,等等。硬件用紧凑方式命名这些端点:2 位 mem_id 选择一个内存类别,配套的 3 位 core_id 区分该类别的哪一个核心实例。profiler 描述符消息 OciDescriptorCommonIssuedFromTcs(node-fabric trace id 91)会把所有这些解码成一个带有完整类型的 proto,其中包含十一个嵌套枚举。
本页关注这些端点枚举,以及它们是否会进入 trace 标签。答案是核心且反直觉的事实:在 pxc (deepsea) 代上不会。 位于 0xf254bc0 的 DMA timeline 渲染 pass ConvertDmaTransfersToXPlane 为每个 span 正好构建八个 XStat,其中没有任何一个是源/目标内存空间或地址。描述符的 src_mem_*、dst_mem_*、src_opcode、dst_opcode 以及三个 sync-flag 字段会由 TcParseTable 解析进 proto,但填充 DmaTransfer span 的 producer 从不读取它们,也从不把它们符号化成显示字符串。整单元符号扫描找不到链接进 libtpu.so 的 mem_id → name、node_type → component 或 DMA 端点 symbolizer。因此,这些端点枚举是二进制遵守的解码契约,不是它执行的渲染契约;renderer 会把每个端点压缩成字节数、带宽字符串和一条 flow 箭头。
所以本页做两件事。第一,建立 id-91 描述符、配套 id-50/51 OCI 消息和 id-48 ICI packet 上每个端点枚举的完整 value→name 表,并以挖出的 FileDescriptorProto 池逐字节验证,包括五代硅片之间多态的内存类别重命名。第二,端到端记录 renderer:每个 span 的八个 XStat、kind-tag → lane/event-name 分派、TpuXLineBuilder::AddEvent (0xf1df1e0) 中 GTC-tick → 皮秒的时间基准,以及五档 %.2f{B,KB,MB,GB,TB}/s 带宽阶梯;这样,哪些内容会被渲染、哪些内容可证明被丢弃,都可以重新实现。原始 band 位布局和四个 id 的配对逻辑在相邻页面 ICR Node-Fabric DMA Timeline Band;本页从合并后的 DmaTransfer span 以及其端点背后的枚举接续。
对于重新实现,契约是:
- 端点枚举表 — id-91 描述符上的
DmaType、SrcMem/DstMem MemId、SrcMem/DstMem CoreId、Src/Dst Opcode、三组SyncFlagCoreId以及LengthGranule;id-50/51 OCI 消息上的MsgType/Opcode/NodeType;id-48 上的RouterLinkPortId,均为 value→name 且有字节锚点。 - 内存类别组合规则 — 每个
mem_id值名都是用_连接的组合名(HBM_TCVMEM_BCBMEM);显示的空间由配套core_id选择的片段决定(NONCORE → 第 1 段,TC → 第 2 段,BC/SC → 第 3 段)。这是从名称结构推断的;libtpu.so中没有执行该 selector。 - 八个渲染出的
XStat以及它们在 0x58 字节DmaTransferspan 上的字节来源。 - 丢弃项 — 哪些描述符/消息字段被解码进 proto,但从未复制进 span。
- 时间基准和带宽计算 —
ps = round((gtc & ~0xf) · 1e9 / (clk·16))以及阈值阶梯。
| Lane renderer | 0xf254bc0 — xprof::tpu::(anon)::ConvertDmaTransfersToXPlane(absl::Span<const DmaTransfer>, TpuXPlaneBuilder*) |
| Event adder / timebase | 0xf1df1e0 — xprof::TpuXLineBuilder::AddEvent(tpu::GtcSpan, const XEventMetadata&) |
| Span producer (id-91 store) | 0xf26c6e0 — ConvertTpuTraceToXPlane<pxc>::{lambda#1}::{lambda#3} |
| id-91 描述符 proto | OciDescriptorCommonIssuedFromTcs,17 个字段,11 个嵌套枚举;FDP body @ 0xbef9426 (len 3655) |
| id-50/51 OCI 消息 | OciMessageGeneratedInIcrEgressDma @ 0xbef7b3b; …IngressDma @ 0xbef7e5f |
| id-48 ICI packet | IciPacketDataPacketQueuedForLocalIngress @ 0xbef6b17 |
| Span 类型 | DmaTransfer,0x58 (88) 字节;push_back @ 0xf2547e0 |
| 端点 renderer | 无 — 单元中没有链接 mem_id/node_type symbolizer |
速览 — 渲染后保留下来的内容
描述符携带十七个字段;渲染后的 XEvent 携带八个 stats。下表完整说明了数据流:左侧每一列都会被解码,只有底部三行会进入标签。
| 描述符 / 消息字段 | 解码进 proto | 到达 XStat |
|---|---|---|
src_mem_mem_id / src_mem_core_id / src_opcode (f3–f5) | 是 | 否 |
dst_mem_mem_id / dst_mem_core_id / dst_opcode (f6–f8) | 是 | 否 |
src_sync_flag_* / dst_sync_flag_0_* / dst_sync_flag_1_* (f9–f14) | 是 | 否 |
program_counter (f15) | 是 | 否 |
id-50/51 addr / node_type / msg_type / opcode | 是 | 否 |
id-48 router_link_port_id / virtual_channel / dst_chip_id | 是 | 否 |
length (f16) × length_granule (f17) → 字节数 | 是 | bytes_transferred (78) |
| 派生:字节 ÷ 时长 | — | bandwidth (string) |
| 派生:GTC span | — | offset_ps / duration_ps + flow (56) + _a (42) |
易错点 — 页面标题描述的是枚举,不是一个已执行的 renderer。期望
ConvertDmaTransfersToXPlane发出类似"HBM → VMEM"的端点标签的重新实现者,会找不到这样的代码。queue(StatType 79) 和details字符串 XStats 确实会附加到每个 span,但它们的源字符串位于DmaTransfer+0x28和+0x40,并且会被 zero-initialized 且从未写入 pxc producer,因此渲染为空字符串。下面的端点枚举描述的是一种 wire format,其 symbolizer 不在这个 translation unit 中(见注意事项)。
id-91 描述符端点枚举
目的
OciDescriptorCommonIssuedFromTcs 是 profiler 为 trace-point id 91 解码的 proto,也就是 TensorCore sequencer 发出以启动 node-fabric DMA 的描述符。它是四种 node-fabric 消息中最丰富的一个:它命名两个端点、两个 opcode、三个 sync-flag 目标、传输长度,以及控制 egress lane 的 dma_type。它的十一个嵌套枚举是权威的源/目标内存空间词汇表。通过遍历位于 0xbef9426 的挖出 FileDescriptorProto body 内的 EnumDescriptorProto block 对它们做了字节验证;value 字符串从 0xbef9a0f 起连续位于 .rodata 中,每个值的 protobuf number 都从 FDP 的 EnumValueDescriptorProto.number 字段解码,而不是从顺序假定。
字段布局
producer 和 parser 用固定 C++ 偏移访问该消息。十七个字段按声明(也是 tag)顺序如下:
| Field | Tag | C++ off | Type | 作用 |
|---|---|---|---|---|
trace_id_header | 1 | +0x18 | submessage | 产生 38 位 dma_id 配对键 |
dma_type | 2 | +0x20 | DmaTypeValues | egress-lane gate (== REMOTEUNICAST) |
src_mem_mem_id | 3 | +0x24 | SrcMemMemIdValues | 源内存类别 |
src_mem_core_id | 4 | +0x28 | SrcMemCoreIdValues | 源核心 selector |
src_opcode | 5 | +0x2c | SrcOpcodeValues | 源侧 opcode |
dst_mem_mem_id | 6 | +0x30 | DstMemMemIdValues | 目标内存类别 |
dst_mem_core_id | 7 | +0x34 | DstMemCoreIdValues | 目标核心 selector |
dst_opcode | 8 | +0x38 | DstOpcodeValues | 目标侧 opcode |
src_sync_flag_id | 9 | +0x3c | uint32 | 源 sync-flag 索引 |
src_sync_flag_core_id | 10 | +0x40 | SrcSyncFlagCoreIdValues | 源 sync-flag 核心 |
dst_sync_flag_0_id | 11 | +0x44 | uint32 | dst sync-flag 0 索引 |
dst_sync_flag_0_core_id | 12 | +0x48 | DstSyncFlag0CoreIdValues | dst sync-flag 0 核心 |
dst_sync_flag_1_id | 13 | +0x4c | uint32 | dst sync-flag 1 索引 |
dst_sync_flag_1_core_id | 14 | +0x50 | DstSyncFlag1CoreIdValues | dst sync-flag 1 核心 |
program_counter | 15 | +0x54 | uint32 | 发出该描述符的 PC |
length | 16 | +0x58 | uint32 | 传输长度(granule 单位) |
length_granule | 17 | +0x5c | LengthGranuleValues | length 的 granule selector |
注意 — 字段 f3–f15(每个端点、opcode、sync-flag 以及 PC)都是 rendering pass 丢弃的字段。producer 只读取 f1(用于
dma_id)、f16 和 f17。C++ 偏移来自0xf26c6e0处 producer store 的恢复结果(length= descriptor dword 22 = +0x58;length_granule= dword 23 = +0x5c)以及消息的TcParseTable字段顺序。
DmaType — egress gate (f2)
传输类别;timeline producer 只有在该值为 REMOTEUNICAST(值 2,即 inter-chip-router 方向)时才保留 span。经字节验证的 value number:
| Value | Name (pxc) | 含义 |
|---|---|---|
| 0 | DMA_TYPE_LOCAL | 片内 DMA |
| 1 | DMA_TYPE_CHIP2HOST | chip → host |
| 2 | DMA_TYPE_REMOTEUNICAST | chip → 单个远端 chip(ICI band) |
| 3 | DMA_TYPE_REMOTEMULTICAST | chip → 多个远端 chip |
SrcMemMemId / DstMemMemId — 内存类别 (f3 / f6)
2 位类别。每个 value name 都是每个核心类别一个内存空间名的组合,用 _ 连接。配套的 core_id(下一节)选择适用的片段。Src 和 Dst 表除了前缀外完全相同;pxc 值如下:
| Value | SRC_MEM_MEM_ID_… / DST_MEM_MEM_ID_… (pxc) | NONCORE 段 | TC 段 | BC 段 |
|---|---|---|---|---|
| 0 | HBM_TCVMEM_BCBMEM | HBM | TC VMEM | BC BMEM |
| 1 | RSVD_TCSMEM_BCSMEM | (reserved) | TC SMEM | BC SMEM |
| 2 | CMEM_TCIMEM_BCBIMEM | CMEM | TC IMEM | BC BIMEM |
| 3 | RSVD_RSVD_BCVIMEM | (reserved) | (reserved) | BC VIMEM |
SrcMemCoreId / DstMemCoreId — 核心 selector (f4 / f7)
3 位 selector,用于区分组合内存类别名称。Src/Dst 表相同;pxc 值如下:
| Value | …_MEM_CORE_ID_… | 选择 MemId 片段 |
|---|---|---|
| 0 | RESERVED | — |
| 1 | NONCORE | 第 1 段(HBM / CMEM / reserved) |
| 2 | TC0 | 第 2 段(TCVMEM / TCSMEM / TCIMEM) |
| 3 | TC1 | 第 2 段 |
| 4 | BC0 | 第 3 段(BCBMEM / BCSMEM / BCBIMEM / BCVIMEM) |
| 5 | BC1 | 第 3 段 |
| 6 | BC2 | 第 3 段 |
| 7 | BC3 | 第 3 段 |
特性 — 完整端点空间是笛卡尔积,而不是任意单表本身。2 位
mem_id× 3 段core_id类别用五位编码整个 HBM / VMEM / SMEM / IMEM / BMEM / VIMEM / CMEM 地址映射。src_mem_mem_id = 0, src_mem_core_id = 2读作 “TC0 VMEM”;同一个mem_id = 0配上core_id = 1则读作 “HBM”。只符号化mem_id、忽略core_id段选择的重新实现,会为每个非 NONCORE 传输生成错误端点。
SrcOpcode / DstOpcode — 每端 opcode (f5 / f8)
源表和目标表不同。pxc 值如下:
| Value | SRC_OPCODE_… | DST_OPCODE_… |
|---|---|---|
| 0 | READ | WRITE |
| 1 | RESERVED | RESERVED |
| 2 | INSTRUCTIONMEMSET | WRITESPECIAL0 |
| 3 | DATAMEMSET | WRITESPECIAL1 |
Sync-flag 核心枚举 (f10 / f12 / f14)
三个 sync-flag core 字段各自携带一个 8 值枚举:SrcSyncFlagCoreIdValues、DstSyncFlag0CoreIdValues、DstSyncFlag1CoreIdValues。三者共享值序列 RESERVED, NONCORE, TC0, TC1, BC0, BC1, BC2, BC3(编号 0–7),形状与 mem-core 枚举相同。
注意 — 这三个 sync-flag 核心枚举是三个不同的
EnumDescriptorProto,并不是与SrcMemCoreId共享的一个类型。每个枚举都有自己的带前缀 value name(SRC_SYNC_FLAG_CORE_ID_TC0、DST_SYNC_FLAG_0_CORE_ID_TC0、DST_SYNC_FLAG_1_CORE_ID_TC0),嵌套在描述符消息下的0xbef9eb5、0xbef9fd2、0xbefa100。所有三者的 value→number 绑定相同;但枚举类型不同。把它们 intern 成同一类型的重新实现,会丢失三个消息内不同的名称。
LengthGranule — 字节数 shift (f17)
把 length 转成字节数的 selector。经字节验证:
| Value | Name | length shift | 字节数 |
|---|---|---|---|
| 0 | LENGTH_GRANULE_512B | << 9 | length × 512 |
| 1 | LENGTH_GRANULE_4B | << 2 | length × 4 |
这是 renderer 消费的唯一描述符枚举:producer 读取 length_granule 选择 shift,并读取 length 计算 bytes_transferred。见 Producer Store。
id-50/51 和 id-48 端点枚举
目的
另外三个 node-fabric trace point 携带各自的端点词汇。id-50/51 是 ICR 中生成的 OCI 消息(OciMessageGeneratedInIcrEgressDma / …IngressDma),二者枚举集逐字节相同。id-48 是 ICI data packet。与描述符一样,producer 只读取 timing/byte-count 字段;这里的端点枚举被解码但被丢弃。
OCI 消息枚举 (id-50 / id-51)
从 egress message FDP body 0xbef7b3b 做字节验证(ingress body 0xbef7e5f 相同):
| Enum (C++ off) | Values |
|---|---|
MsgType (+0x28) | 0 MSG_TYPE_PRIVATE, 1 MSG_TYPE_PUBLIC |
Opcode (+0x2c) | 0 WRITE_NO_DONE, 1 WRITE_WITH_DONE, 2 INC_NO_DONE, 3 INC_WITH_DONE |
NodeType (+0x30) | 0 TCS, 1 BC, 2 CMQ, 3 HBMQ, 4 UHI, 5 ICR, 6 QNM |
注意 —
NodeType是 node-fabric 组件词汇(与 OCI command bands 使用的是同一套)。它是这些消息中最接近路由端点的字段,也是一个假设的node_type → TpuComponentlane symbolizer 会消费的字段。没有这样的 symbolizer 被链接;NodeType在每个 id-50/51 span 上都会被解析后丢弃。id-51 (ingress) producer 只读取msg_data(+0x20,用于<< 9字节数累加);id-50 (egress) 只读取done(+0x24)。
ICI packet 枚举 (id-48)
从 IciPacketDataPacketQueuedForLocalIngress at 0xbef6b17 做字节验证:
| Enum (C++ off) | Values |
|---|---|
RouterLinkPortId (+0x20) | 0 LINK0 … 5 LINK5 (ROUTER_LINK_PORT_ID_LINK0..5) |
producer 只读取 first_packet_in_dma(+0x32)和 last_packet_in_dma(+0x33),也就是 begin/end marker,并丢弃 router_link_port_id、virtual_channel、link-target mask 和 dst_chip_id。
跨代 Memory-ID 重命名
目的
OciDescriptorCommonIssuedFromTcs 存在于该单元发布的所有五代(pxc、vfc、vlc、glc、gfc)。mem-id 组合是多态的:第三段跟随该代的第三核心类别。pxc 有 BarnaCore (BC);vfc/glc/gfc 家族有 SparseCore (SC);vlc 完全没有第三核心,并发布 2 段名称。value number 保持 0–3;只有名称变化。该结果通过枚举各代 FDP 池中的 SRC_MEM_MEM_ID_* / DST_MEM_MEM_ID_* 字符串恢复。
按 generation 的重命名
mem_id | pxc (BarnaCore) | vfc / glc / gfc (SparseCore) | vlc(无 SC) |
|---|---|---|---|
| 0 | HBM_TCVMEM_BCBMEM | HBM_TCVMEM_SCSPMEM | HBM_TCVMEM |
| 1 | RSVD_TCSMEM_BCSMEM | HOST_TCSMEM_SCSMEM | HOST_TCSMEM |
| 2 | CMEM_TCIMEM_BCBIMEM | VMEMALL_TCIMEM_SCSIMEM | NONCORERESERVEDMEM0_TCIMEM |
| 3 | RSVD_RSVD_BCVIMEM | NONCORERESERVEDMEM0_TCRESERVEDMEM_SCTIMEM | NONCORERESERVEDMEM0_TCRESERVEDMEM |
在 SparseCore 代上,core_id 枚举会获得 SC0..SC3(替换 BC0..BC3);SparseCore 字符串 SRC_MEM_CORE_ID_SC0..SC3 存在于该单元中。dma_type 枚举在较新代上也会收缩:pxc 有四值 {LOCAL, CHIP2HOST, REMOTEUNICAST, REMOTEMULTICAST},而 SparseCore 家族的描述符 DmaTypeValues 收缩为二值 {LOCALORHOST = 0, REMOTEUNICAST = 1}(字符串 DMA_TYPE_LOCALORHOST @ 0xbf07208、DMA_TYPE_REMOTEUNICAST @ 0xbf07222 存在于 protodesc_cold 池中)。
特性 — 重命名仅是字符串层面;proto 字段号、C++ 偏移以及 2-bit/3-bit 宽度在五代之间稳定。单个 decoder 可以按 value number 处理每一代;只有显示名称表按 generation 区分。这就是为什么重新实现的 symbolizer 应该以
(gen, mem_id, core_id_segment)为键,而绝不应以 value-name 字符串为键。
段选择规则(推断)
显示的内存空间是由配套 core_id 选择的 mem_id 组合片段:
core_id == NONCORE -> 1st segment (HBM / HOST / CMEM / VMEMALL / reserved)
core_id in {TC0, TC1} -> 2nd segment (TCVMEM / TCSMEM / TCIMEM / TCRESERVEDMEM)
core_id in {BC0..BC3} -> 3rd segment (pxc: BCBMEM / BCSMEM / BCBIMEM / BCVIMEM)
core_id in {SC0..SC3} -> 3rd segment (SC: SCSPMEM / SCSMEM / SCSIMEM / SCTIMEM)易错点 — 这条规则是从名称结构推断的,而不是从 selector 的字节证明而来。
libtpu.so中没有代码按core_id拆分mem_id组合;pxc pass 在任何这样的拆分可能发生前就丢弃了两个字段。该规则在五代上结构一致(段数总是匹配该 generation 的核心类别),但字符串拆分 selector 位于下游 xprof/TensorBoard UI 中,不在此单元内,因此这里无法验证。请把它当成最可能的解释,而不是已确认算法。
Renderer
目的
ConvertDmaTransfersToXPlane (0xf254bc0) 把一段合并后的 DmaTransfer record 转换成 device-XPlane XEvent。它是 deepsea 代唯一的 DMA-transfer XPlane pass(jxc legacy 代在 0xf25c500 处有单独的 ConvertDmaEndsToXPlane)。它先设置四条 timeline lane 和四个 event-metadata 名称,然后遍历 span,并为每条有效 record 在其 kind tag 选择的 lane 上发出一个 XEvent。
设置 — Lanes、Event Names、Stat Names
在 per-span loop 之前,该函数按固定顺序 intern 元数据:
function ConvertDmaTransfersToXPlane(transfers, plane): // 0xf254bc0
// Four timeline lanes, each named via TpuComponentName().
line63 = GetOrCreateLine(plane, 63) // "MemcpyH2D" (host -> device)
line64 = GetOrCreateLine(plane, 64) // "MemcpyD2H" (device -> host)
line54 = GetOrCreateLine(plane, 54) // "From ICI Router" (ingress)
line55 = GetOrCreateLine(plane, 55) // "To ICI Router" (egress)
// Four XEvent names (the title shown on the span).
evtH2D = GetOrCreateEventMetadata(plane, "MemcpyH2D")
evtD2H = GetOrCreateEventMetadata(plane, "MemcpyD2H")
evtIn = GetOrCreateEventMetadata(plane, "ICI Ingress")
evtEg = GetOrCreateEventMetadata(plane, "ICI Egress")
// Six explicit XStat metadata handles (StatType id or literal name).
stBytes = GetOrCreateStatMetadata(plane, GetStatTypeStr(78)) // bytes_transferred
st_a = GetOrCreateStatMetadata(plane, GetStatTypeStr(42)) // group_id ("_a")
stFlow = GetOrCreateStatMetadata(plane, GetStatTypeStr(56)) // flow
stQueue = GetOrCreateStatMetadata(plane, GetStatTypeStr(79)) // queue
stDet = GetOrCreateStatMetadata(plane, "details") // literal
stBw = GetOrCreateStatMetadata(plane, "bandwidth") // literallane 编号(54/55/63/64)及其名称通过 xprof::TpuComponentName 解析;lane-name 目录由 device-plane line reference 拥有。相邻页面 ICR Node-Fabric DMA Timeline Band 详细覆盖 lanes 54/55。
Per-Span Loop 与 Kind 分派
每个 DmaTransfer 为 0x58 字节。loop 通过 record + 0x40 处的 RBX 访问 record,因此结构偏移 k 处的字段读作 [RBX - 0x40 + k]。只有当字节数非零且 begin/end marker 都存在时,span 才会被渲染:
for record in transfers: // 0xf25502e
if byte_count[+0x20] == 0: continue // [RBX-0x20]
if begin_present[+0x08] != 1: continue // [RBX-0x38]
if end_present[+0x18] != 1: continue // [RBX-0x28]
switch (kind_tag[+0x38]): // [RBX-0x08], jt @ 0xab589bc
case 2: event = evtIn, lane = line64; break // "ICI Ingress"
case 3: event = evtEg, lane = line54; break // "ICI Egress"
case 6: event = evtH2D, lane = line63; break // "MemcpyH2D" (default arm)
case 7: event = evtD2H, lane = line63; break // "MemcpyD2H"
default: continue // tags 4,5 skipped
begin = begin_gtc[+0x00] // [RBX-0x40]
end = end_gtc[+0x10] // [RBX-0x30]
if end <= begin: continue // zero/negative span dropped
xevent = AddEvent(lane, GtcSpan{begin, end-begin}, event) // 0xf1df1e0
AttachStats(xevent, record) // the six explicit statskind tag 与相邻页面中的 node-fabric 方向配对:tag 2 = ingress("ICI Ingress"),tag 3 = egress("ICI Egress");tag 6/7 是 host-DMA memcpy 分支。pxc producer 实际只会写 tag 2 和 3;memcpy 分支存在于共享 Convert 中,但不会在捕获的 pxc XSpace 上触发。
注意 —
switchordinal 是kind_tag − 2,通过0xab589bc处的 jump table 索引;tag 0/1 越界,tag 4/5 落入 no-op default。每个分支还会检查其 event-metadata handle 非空,因此 metadata intern 失败的 lane 会被静默跳过,而不是崩溃。
八个 XStats
AddEvent 会盖上两个通用 int64 stats(ps timestamp);loop body 还会再添加六个。完整集合及其在 span 上的字节来源如下:
| XStat | StatType | value case | 来源 | 位置 |
|---|---|---|---|---|
offset_ps | (device offset) | 3 int64 | round(begin_gtc & ~0xf · 1e9 / (clk·16)) | AddEvent |
duration_ps | (device duration) | 3 int64 | round((end−begin masked) · 1e9 / (clk·16)) | AddEvent |
bytes_transferred | 78 | 3 int64 | byte_count[+0x20] | 0xf2550ab |
queue | 79 | 1 string | string @ +0x28 / len +0x30 — pxc 上为空 | 0xf255121 |
details | — | 1 string | std::string @ +0x40(SSO flag +0x57)— pxc 上为空 | 0xf255181 |
_a (group_id) | 42 | 4 uint64 | 常量 1(per-DMA aggregate marker) | 0xf2551f4 |
flow | 56 | 3 int64 | (XFlow::next_flow_id_++ & 0xFF…FF) · 4 + 3 | 0xf255203 |
bandwidth | — | 5 string | StrFormat(unit, bytes / (duration_ps/1e12)) | 0xf255411 |
特性 —
queue(79) 和details在 pxc 上总会附加但总为空。producer 的 id-91 store(0xf26c865)会 zero-fill span(vpxor+ 三个vmovdqu,把 zero ymm 写到+0x08..+0x60),随后只写begin_gtc、present-flags、byte_count和 kind tag;它从不向+0x28或+0x40写入字符串。重新实现仍必须发出这两个 stats(下游 consumer 会依据它们的存在做键控),但值为空。flowid 先被 mask 到 56 位,再<<2 | 3,把它标记为 flow-kind 3(begin↔end DMA 箭头);_a = 1将 span 标记为 per-DMA aggregation。
Producer Store — 它读取什么
span producer(ConvertTpuTraceToXPlane<pxc>,0xf26c6e0 处第三层嵌套 lambda)是端点被丢弃的可证明位置。对于 id-91 描述符,它执行的反编译形式如下:
// id-91 store, @ ~0xf26c865 inside the producer lambda
zero_fill(span + 0x08, 0x58) // vpxor + 3× vmovdqu ymm
span.begin_gtc[+0x00] = begin_present_ts
span.begin_present[+0x08] = 1
shift = (descr.length_granule[+0x5c] == 0) ? 9 : 2 // dword 23
span.byte_count[+0x20] = (u64)descr.length[+0x58] << shift // dword 22
span.kind_tag[+0x38] = 3 // egress
// NO read of descr+0x24..0x50 — every src/dst endpoint, opcode,
// sync-flag, and PC field is skipped.对整个 producer grep src_mem、dst_mem、mem_id、mem_core、opcode 和 sync_flag,返回零引用。这些字段由消息的 TcParseTable 解析进 proto,然后被放弃。这就是 速览中 “否” 列背后的字节级证据。
时间基准与带宽计算
GTC → 皮秒(AddEvent, 0xf1df1e0)
每个 device XEvent timestamp 都是转换成皮秒的 GTC(global time counter)tick。原始 tick 以 clk/16 为单位计数,所以转换会 mask 掉低 4 个小数位,乘以 1e9,并用 round-to-nearest 除以 clk·16。逐字节精确的计算如下:
function AddEvent(line, GtcSpan{begin, dur}, meta): // 0xf1df1e0
clk = *(*(meta.converter[+0x10])) // CycleConverter tick rate
div = clk << 4 // clk · 16 (the /16 fractional convention)
half = div >> 1 // round-to-nearest addend
// offset: mask the begin tick's 4 fractional bits, scale, divide.
offset_ps = (( (begin & ~0xf) * 1e9 ) + half) / div // 128-bit udiv (_udivti3 @0x21213680)
// duration: end = begin + dur; subtract the *masked* begin, re-mask to bits[4:44].
end_masked = ((begin + dur) - (begin & 0x1FFFFFFFFFF0)) & 0x1FFFFFFFFFF0
duration_ps = (( end_masked * 1e9 ) + half) / div // same div / half
// stored both as native XEvent fields and as two int64 XStats (value case 3)
xevent.offset_ps[+0x38] = offset_ps
xevent.duration_ps[+0x30] = duration_ps注意 — begin-offset 和 duration 路径使用两个不同的 mask。begin-offset numerator 在 offset multiply 处使用
~0xf(0xFFFFFFFFFFFFFFF0,只清除低 4 位)做 mask,而 duration 路径用0x1FFFFFFFFFF0(bits 4–44,41 位窗口)mask end。对于范围内 GTC 值二者一致,但字节忠实的重新实现必须使用两个 mask:begin & 0xFFFFFFFFFFFFFFF0、end & 0x1FFFFFFFFFF0。
1e9 以 immediate 0x3B9ACA00 出现;round-half(+ div/2)会在 unsigned 128-bit divide 之前折入 128-bit numerator。两个 stats 通过 converter 预先 intern 的 metadata(meta+0x38 offset,meta+0x40 duration)盖上。clk 的来源,也就是填入 CycleConverter+0x10 的每种硅片标称 GTC 频率,是另一个 decode(TpuVersion → GTC-Hz 表),不在本页范围内。
带宽阶梯(0xf25527b..0xf255411)
AddEvent 返回 XEvent 之后,renderer 计算带宽字符串。字节数和时长都会通过标准 vunpcklps / vsubpd magic-constant 技巧从 u64 → f64 转换,然后相除:
bytes_d = (double) byte_count[+0x20]
dur_s = (double) duration_ps[+0x30] / 1e12 // ps -> seconds
bw_Bps = bytes_d / dur_s // bytes per second
// 5-rung threshold ladder (vucomisd / jae): pick the largest unit <= bw,
// then divide bw by that unit's threshold.
if bw_Bps >= 1e12: s = StrFormat("%.2fTB/s", bw_Bps / 1e12) // @0xa2dfd18
else if bw_Bps >= 1e9 : s = StrFormat("%.2fGB/s", bw_Bps / 1e9 ) // @0xa2de620
else if bw_Bps >= 1e6 : s = StrFormat("%.2fMB/s", bw_Bps / 1e6 ) // @0xa2e0208
else if bw_Bps >= 1e3 : s = StrFormat("%.2fKB/s", bw_Bps / 1e3 ) // @0xa2e0430
else : s = StrFormat("%.2fB/s", bw_Bps )
set_stat(xevent, stBw, s) // value case 5 (string)四个 double 阈值在 0xa2dfd18 (1e12)、0xa2de620 (1e9)、0xa2e0208 (1e6)、0xa2e0430 (1e3) 处验证;五个 format string %.2f{TB,GB,MB,KB,B}/s 在 .rodata 的 0x857a5f4 / 0x857a60f / 0x857a5fd / 0x857a606 / 0x857a5ec 处 intern。这是 DMA span 唯一的人类可读摘要,而且值得注意的是,这是端点带宽显现而端点名称不显现的唯一位置。
注意事项 — 端点去了哪里
端点枚举是一份完整、字节忠实的 wire contract;而在 pxc DMA-timeline pass 上,它们没有 consumer。有三个事实限定了重新实现者可以得出的结论:
pxc pass 会丢弃它们。 这在 producer store(
0xf26c6e0)处得到证明:它读取length、length_granule和dma_id,不读取descr+0x24..0x50中的任何内容。合并后的 span 没有内存空间、opcode 或地址字段;它的布局是{begin, present, end, present, byte_count, queue-ptr/len, kind_tag, details-string},所有 0x58 字节都已解释完毕。没有链接 symbolizer。 整单元符号扫描找不到
mem_id → name、node_type → TpuComponent或 DMA-endpoint stringifier。该单元中仅有的 DMA-XPlane pass 是这个ConvertDmaTransfersToXPlane(pxc,端点被丢弃)以及 jxc 的DeriveHostDmaTransfers/ConvertDmaEndsToXPlane对(0xf25c500),后者从不同的SyncFlagUpdate结构构建 timeline。下游不在范围内。 消费发出
XSpace的 xprof / TensorBoard UI 是否会重新读取原始 proto 字段来构建 routing display,无法从libtpu.so判定;那段代码不在此二进制中。本页的端点枚举表正是这样的下游 consumer 会需要的内容;本页提供它们,并证明libtpu.so本身不使用它们。
坦率总结:渲染出的 ICI DMA span 显示 ps 级 offset/duration、字节数、带宽字符串、flow 箭头和 aggregate marker,绝不显示源或目标内存空间。端点枚举被解码但未被渲染,而可能承载端点标签的 queue/details stats 在 pxc 上为空。
交叉引用
- ICR Node-Fabric DMA Timeline Band — 四个 id
{48,50,51,91}的配对、dma_idextractor(GetDmaId0xf699ca0),以及供给本页渲染的合并DmaTransfer的 band 位布局 - Trace Payload: UHI / OCI / ICI / DMA Bands — 这些端点来源的 OCI / ICI / DMA payload bands 的原始 on-wire 字段 decode
- TraceEntry to XEvent/XStat — 把 decoded trace entry 路由到 producer lambdas 的
CoreDispatcher::Dispatch路径 - XStat Metadata IDs —
GetStatTypeStr(78/42/56/79)背后的 StatType id → name 表 - XEvent Metadata IDs —
GetOrCreateEventMetadata背后的 event-name interning - Profiling and Telemetry — profiler 子系统概览和 device-XPlane lane catalog