Skip to content

DMA 端点渲染

本页所有地址均适用于 wheel libtpu-0.0.40 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d,这是无歧义锚点;运行时报告的 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_opcodedst_opcode 以及三个 sync-flag 字段会由 TcParseTable 解析进 proto,但填充 DmaTransfer span 的 producer 从不读取它们,也从不把它们符号化成显示字符串。整单元符号扫描找不到链接进 libtpu.somem_id → namenode_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 描述符上的 DmaTypeSrcMem/DstMem MemIdSrcMem/DstMem CoreIdSrc/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 字节 DmaTransfer span 上的字节来源。
  • 丢弃项 — 哪些描述符/消息字段被解码进 proto,但从未复制进 span。
  • 时间基准和带宽计算ps = round((gtc & ~0xf) · 1e9 / (clk·16)) 以及阈值阶梯。
Lane renderer0xf254bc0xprof::tpu::(anon)::ConvertDmaTransfersToXPlane(absl::Span<const DmaTransfer>, TpuXPlaneBuilder*)
Event adder / timebase0xf1df1e0xprof::TpuXLineBuilder::AddEvent(tpu::GtcSpan, const XEventMetadata&)
Span producer (id-91 store)0xf26c6e0ConvertTpuTraceToXPlane<pxc>::{lambda#1}::{lambda#3}
id-91 描述符 protoOciDescriptorCommonIssuedFromTcs,17 个字段,11 个嵌套枚举;FDP body @ 0xbef9426 (len 3655)
id-50/51 OCI 消息OciMessageGeneratedInIcrEgressDma @ 0xbef7b3b; …IngressDma @ 0xbef7e5f
id-48 ICI packetIciPacketDataPacketQueuedForLocalIngress @ 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 spanoffset_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)顺序如下:

FieldTagC++ offType作用
trace_id_header1+0x18submessage产生 38 位 dma_id 配对键
dma_type2+0x20DmaTypeValuesegress-lane gate (== REMOTEUNICAST)
src_mem_mem_id3+0x24SrcMemMemIdValues源内存类别
src_mem_core_id4+0x28SrcMemCoreIdValues源核心 selector
src_opcode5+0x2cSrcOpcodeValues源侧 opcode
dst_mem_mem_id6+0x30DstMemMemIdValues目标内存类别
dst_mem_core_id7+0x34DstMemCoreIdValues目标核心 selector
dst_opcode8+0x38DstOpcodeValues目标侧 opcode
src_sync_flag_id9+0x3cuint32源 sync-flag 索引
src_sync_flag_core_id10+0x40SrcSyncFlagCoreIdValues源 sync-flag 核心
dst_sync_flag_0_id11+0x44uint32dst sync-flag 0 索引
dst_sync_flag_0_core_id12+0x48DstSyncFlag0CoreIdValuesdst sync-flag 0 核心
dst_sync_flag_1_id13+0x4cuint32dst sync-flag 1 索引
dst_sync_flag_1_core_id14+0x50DstSyncFlag1CoreIdValuesdst sync-flag 1 核心
program_counter15+0x54uint32发出该描述符的 PC
length16+0x58uint32传输长度(granule 单位)
length_granule17+0x5cLengthGranuleValueslength 的 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:

ValueName (pxc)含义
0DMA_TYPE_LOCAL片内 DMA
1DMA_TYPE_CHIP2HOSTchip → host
2DMA_TYPE_REMOTEUNICASTchip → 单个远端 chip(ICI band)
3DMA_TYPE_REMOTEMULTICASTchip → 多个远端 chip

SrcMemMemId / DstMemMemId — 内存类别 (f3 / f6)

2 位类别。每个 value name 都是每个核心类别一个内存空间名的组合,用 _ 连接。配套的 core_id(下一节)选择适用的片段。SrcDst 表除了前缀外完全相同;pxc 值如下:

ValueSRC_MEM_MEM_ID_… / DST_MEM_MEM_ID_… (pxc)NONCORE 段TC 段BC 段
0HBM_TCVMEM_BCBMEMHBMTC VMEMBC BMEM
1RSVD_TCSMEM_BCSMEM(reserved)TC SMEMBC SMEM
2CMEM_TCIMEM_BCBIMEMCMEMTC IMEMBC BIMEM
3RSVD_RSVD_BCVIMEM(reserved)(reserved)BC VIMEM

SrcMemCoreId / DstMemCoreId — 核心 selector (f4 / f7)

3 位 selector,用于区分组合内存类别名称。Src/Dst 表相同;pxc 值如下:

Value…_MEM_CORE_ID_…选择 MemId 片段
0RESERVED
1NONCORE第 1 段(HBM / CMEM / reserved)
2TC0第 2 段(TCVMEM / TCSMEM / TCIMEM)
3TC1第 2 段
4BC0第 3 段(BCBMEM / BCSMEM / BCBIMEM / BCVIMEM)
5BC1第 3 段
6BC2第 3 段
7BC3第 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 值如下:

ValueSRC_OPCODE_…DST_OPCODE_…
0READWRITE
1RESERVEDRESERVED
2INSTRUCTIONMEMSETWRITESPECIAL0
3DATAMEMSETWRITESPECIAL1

Sync-flag 核心枚举 (f10 / f12 / f14)

三个 sync-flag core 字段各自携带一个 8 值枚举:SrcSyncFlagCoreIdValuesDstSyncFlag0CoreIdValuesDstSyncFlag1CoreIdValues。三者共享值序列 RESERVED, NONCORE, TC0, TC1, BC0, BC1, BC2, BC3(编号 0–7),形状与 mem-core 枚举相同。

注意 — 这三个 sync-flag 核心枚举是三个不同的 EnumDescriptorProto,并不是与 SrcMemCoreId 共享的一个类型。每个枚举都有自己的带前缀 value name(SRC_SYNC_FLAG_CORE_ID_TC0DST_SYNC_FLAG_0_CORE_ID_TC0DST_SYNC_FLAG_1_CORE_ID_TC0),嵌套在描述符消息下的 0xbef9eb50xbef9fd20xbefa100。所有三者的 value→number 绑定相同;但枚举类型不同。把它们 intern 成同一类型的重新实现,会丢失三个消息内不同的名称。

LengthGranule — 字节数 shift (f17)

length 转成字节数的 selector。经字节验证:

ValueNamelength shift字节数
0LENGTH_GRANULE_512B<< 9length × 512
1LENGTH_GRANULE_4B<< 2length × 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 → TpuComponent lane 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_idvirtual_channel、link-target mask 和 dst_chip_id


跨代 Memory-ID 重命名

目的

OciDescriptorCommonIssuedFromTcs 存在于该单元发布的所有五代(pxcvfcvlcglcgfc)。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_idpxc (BarnaCore)vfc / glc / gfc (SparseCore)vlc(无 SC)
0HBM_TCVMEM_BCBMEMHBM_TCVMEM_SCSPMEMHBM_TCVMEM
1RSVD_TCSMEM_BCSMEMHOST_TCSMEM_SCSMEMHOST_TCSMEM
2CMEM_TCIMEM_BCBIMEMVMEMALL_TCIMEM_SCSIMEMNONCORERESERVEDMEM0_TCIMEM
3RSVD_RSVD_BCVIMEMNONCORERESERVEDMEM0_TCRESERVEDMEM_SCTIMEMNONCORERESERVEDMEM0_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 @ 0xbf07208DMA_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 组合片段:

text
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 元数据:

c
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")          // literal

lane 编号(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 才会被渲染:

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

kind 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 上触发。

注意 — switch ordinal 是 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 上的字节来源如下:

XStatStatTypevalue case来源位置
offset_ps(device offset)3 int64round(begin_gtc & ~0xf · 1e9 / (clk·16))AddEvent
duration_ps(device duration)3 int64round((end−begin masked) · 1e9 / (clk·16))AddEvent
bytes_transferred783 int64byte_count[+0x20]0xf2550ab
queue791 stringstring @ +0x28 / len +0x30pxc 上为空0xf255121
details1 stringstd::string @ +0x40(SSO flag +0x57)— pxc 上为空0xf255181
_a (group_id)424 uint64常量 1(per-DMA aggregate marker)0xf2551f4
flow563 int64(XFlow::next_flow_id_++ & 0xFF…FF) · 4 + 30xf255203
bandwidth5 stringStrFormat(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 会依据它们的存在做键控),但值为空。flow id 先被 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 描述符,它执行的反编译形式如下:

c
// 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_memdst_memmem_idmem_coreopcodesync_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。逐字节精确的计算如下:

c
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 处使用 ~0xf0xFFFFFFFFFFFFFFF0,只清除低 4 位)做 mask,而 duration 路径用 0x1FFFFFFFFFF0(bits 4–44,41 位窗口)mask end。对于范围内 GTC 值二者一致,但字节忠实的重新实现必须使用两个 mask:begin & 0xFFFFFFFFFFFFFFF0end & 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 转换,然后相除:

c
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.rodata0x857a5f4 / 0x857a60f / 0x857a5fd / 0x857a606 / 0x857a5ec 处 intern。这是 DMA span 唯一的人类可读摘要,而且值得注意的是,这是端点带宽显现而端点名称不显现的唯一位置。


注意事项 — 端点去了哪里

端点枚举是一份完整、字节忠实的 wire contract;而在 pxc DMA-timeline pass 上,它们没有 consumer。有三个事实限定了重新实现者可以得出的结论:

  1. pxc pass 会丢弃它们。 这在 producer store(0xf26c6e0)处得到证明:它读取 lengthlength_granuledma_id,不读取 descr+0x24..0x50 中的任何内容。合并后的 span 没有内存空间、opcode 或地址字段;它的布局是 {begin, present, end, present, byte_count, queue-ptr/len, kind_tag, details-string},所有 0x58 字节都已解释完毕。

  2. 没有链接 symbolizer。 整单元符号扫描找不到 mem_id → namenode_type → TpuComponent 或 DMA-endpoint stringifier。该单元中仅有的 DMA-XPlane pass 是这个 ConvertDmaTransfersToXPlane(pxc,端点被丢弃)以及 jxc 的 DeriveHostDmaTransfers / ConvertDmaEndsToXPlane 对(0xf25c500),后者从不同的 SyncFlagUpdate 结构构建 timeline。

  3. 下游不在范围内。 消费发出 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 上为空。


交叉引用