Skip to content

ICR Node-Fabric DMA 时间线带

本页所有地址均适用于 wheel libtpu-0.0.40 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d — 明确锚点;运行时报告的 0.103 无法在二进制中静态验证)。其他构建会不同。所有偏移都是 VMA;.text.rodata 映射为 VMA == 文件偏移。

摘要

TPU profiler 在设备 XPlane 中将 inter-chip-router(ICR / node-fabric)DMA 流量渲染为两类 XEvent — "ICI Egress"(kind tag 3)和 "ICI Ingress"(kind tag 2)。渲染器创建了 "From ICI Router"(component 54)和 "To ICI Router"(component 55)两条线,但字节精确的 case→line 绑定是不对称的:egress span 落到 component 54,ingress span 落到 component 64("MemcpyD2H" 线),component 55 被创建但未使用(见 § Lane / Tag Map)。不同于 OCI command 带(trace-point id 22/26/96,它们命名 command,并且只在需要时解析 dma_id),DMA 时间线生产者只以 48, 50, 51, 91 这四个 trace-point id 为键,并把它们拼接成 begin/end DMA span。每个 id 携带一个不同的 node-fabric trace message,贡献 span 的不同部分(begin marker、end marker、byte count 或 end timestamp),并落入两个以 38-bit dma_id 为键的 flat_hash_map<uint64,DmaTransfer> 之一;该 dma_id 从 message 的 TraceIdHeader 提取。

本页负责在共享的 16-byte TraceEntry packet 之上解析这些变体(在这些 deepsea 代上是 2-bit frame + 59-bit TraceHeader + 38-bit TraceIdHeaderchip_id 是 14 bit,不是 12-bit pxc 形式,这正是下文 dma_id 为 38 bit 的原因;见 Trace Entries Coder)。对于这四个 id,生产者从 TraceHeader+0x18 读取线上 trace_point_id,然后解引用 message submessage(TraceEntry+0x10 → 位于 +0x20 的 submessage),并解析按固定 C++ 偏移打包的 proto 字段。这四个 message 清晰地分成两个方向:egress(数据离开并发往 router)使用 id 91(descriptor)+ id 50(egress message);ingress(数据从 router 到达)使用 id 48(ICI data packet)+ id 51(ingress message)。两个独立 map 让 egress 与 ingress span 集合彼此分离,随后各自合并并渲染到自己的 lane。

生产者是 ConvertTpuTraceToXPlane<pxc::profiler::TraceEntry> 中位于 0xf26c6e0 的第三层嵌套 lambda;逐 id 的 dma_id 提取器是位于 0xf699ca0TraceEntryWrapper<pxc>::GetDmaId;lane 渲染器是位于 0xf254bc0ConvertDmaTransfersToXPlane。这个构建中三者都带有完整 C++ 符号,因此 message class 名、字段名和 DmaTransfer 布局都是直接恢复的,不是推断得出。

对于重新实现,契约是:

  • 四个 id 的键集合 {48, 50, 51, 91} 以及生产者如何构造它(4-element GetMerged selector),包括精确的分发顺序。
  • 每个 id 的 message class、proto oneof case 和字段布局 — 生产者读取的每个字段的 C++ 偏移。
  • 每个 id 在 span 中的角色 — 它写入 {begin_gtc, begin_present, end_gtc, end_present, byte_count, kind_tag} 中的哪些项。
  • byte-count 规则 — OCI message 的 msg_data << 9(固定 512 B 粒度)与 descriptor 的 length << {2|9},后者由 length_granule 选择。
  • 38-bit dma_id 提取,以及 begin/end 如何通过两个 egress/ingress map 配对。
DMA 时间线生产者0xf26c6e0ConvertTpuTraceToXPlane<pxc>::{lambda#1}::{lambda#3}
dma_id 提取器0xf699ca0TraceEntryWrapper<pxc>::GetDmaId(int), single caller @0xf26c8d9
Lane 渲染器0xf254bc0ConvertDmaTransfersToXPlane
Span 合并器0xf26dae0MergeOverlappingTransfers(每个 map 运行一次)
键集合{48, 50, 51, 91} — 由 0x320000005B + 两次 store 构建
Span 值类型DmaTransfer, 0x58 (88) bytes;push_back @0xf2547e0
Egress spanXEvent "ICI Egress"(kind tag 3)→ 渲染在 component 54("From ICI Router")
Ingress spanXEvent "ICI Ingress"(kind tag 2)→ 渲染在 component 64("MemcpyD2H" 线)

一览 — 四个 ICR DMA 带

Band IDMessage class (asic_sw::driver::deepsea::pxc::profiler::)Oneof方向 / map生产者角色携带的 payload
48IciPacketDataPacketQueuedForLocalIngress29 (0x1d)ingress / MAP_Bbegin end markerfirst/last-packet bools
50OciMessageGeneratedInIcrEgressDma31 (0x1f)egress / MAP_A仅 end timestampdone gate, msg_data(此处未使用)
51OciMessageGeneratedInIcrIngressDma32 (0x20)ingress / MAP_Bbyte-count 累加msg_data << 9
91OciDescriptorCommonIssuedFromTcs48 (0x30)egress / MAP_Abegin + byte-countlength << granule, dma_type gate

注意 — 这四个 id 连续,也共享 message class。它们只共享 TraceIdHeader(proto field 1,C++ offset +0x18),后者产生 dma_id。这种分组是行为层面的:它们是 DMA 时间线 pass 作为键使用的四个 trace point。registry 中 GetDmaId 能解析的其他所有 id(OCI command band 22/26/96 等)都不会送入这个 pass — GetDmaId 只有一个 caller(@0xf26c8d9),且只以这四个 id 为键。


构建键集合

目的

在遍历按 core 合并后的 trace entry 之前,生产者构造它关注的 4-element trace-point id 集合,并把它交给 GetMerged;后者只返回 id 在该集合中的 entry。这道门把整个 pass 限制在 id {48, 50, 51, 91}

算法

c
function BuildKeySetAndMerge(entries):           // 0xf26c6e0, top of loop body
    // Pack two ids into one qword on the heap, then grow to a 4-element span.
    p   = operator_new(8)
    *p  = 0x320000005B                            // @0xf26c7bf: { 0x5B=91, 0x32=50 }
    set = operator_new(0x10)
    set[2] = 48                                   // @0xf26c7d9: third element
    set[0..1] = *p                               // copies {91,50} into the span
    set[3] = 51                                   // @0xf26c7ff: fourth element
    count = 4                                     // @ v155
    free(p)
    GetMerged(&merged, entries, &set, count)      // 0xf26ba80, called @0xf26c81d
    free(set)
    return merged                                 // vector of {ChipCoreId, entry*} triples

怪异点 — id 集合先被组装为 little-endian qword 0x320000005B,所以 9150 被按字节打包(0x5B, 0x32)进一次 64-bit store,随后 4851 作为单独 dword 写入。重新实现可以直接使用无序集合 {48, 50, 51, 91};这种打包是代码生成产物,不是语义。

注意事项

GetMerged0xf26ba80)是按 id 集合进行的合并:它把 TpuTraceEntryPerCoreMap 展平成由 {ChipCoreId, TraceEntry*} record 组成的平面 vector(24-byte stride:entry pointer 位于 record+0x10)。DMA 生产者只迭代这个 vector 一次;每条 record 按其 trace_point_id 分类,并路由到下面四个分支之一。


dma_id 提取 — GetDmaId

目的

每个 span 都以一个从 message 的 TraceIdHeader 派生的 38-bit dma_id 为键。GetDmaId 是一个针对线上 trace_point_id 的大 switch;对这四个 DMA id,它走一个 “single-header” 分支:加载 live message,读取其 TraceIdHeader(field 1 at +0x18),然后组合 id。OCI command id(22/23/26/54/55/96)走另一个会调用 CmdDmaIdFromEntry<...> 的分支 — DMA pass 永远不会到达这些分支。

入口点

text
0xf26c6e0  ConvertTpuTraceToXPlane<pxc>::{lambda#3}     ── DMA producer
  └─ 0xf699ca0  GetDmaId(int)                           ── 38-bit dma_id, called @0xf26c8d9 with selector 0
       └─ 0xf69a444  single-header compose tail         ── the 38-bit pack

算法

c
function GetDmaId(wrapper, selector):            // 0xf699ca0
    submsg = wrapper[2]                          // TraceEntry at +0x10
    hdr    = submsg[24] ?: TraceHeader_globals_   // TraceHeader, default prototype if null
    id     = hdr[6]                              // trace_point_id (uint32 @ TraceHeader+0x18)
    switch (id):
        // --- the four DMA single-header ids ---
        case 48:  if submsg.oneof == 29 goto live   else def = unk_2237B590  // globals+0x18
        case 50:  if submsg.oneof == 31 goto live   else def = unk_2237AF28
        case 51:  if submsg.oneof == 32 goto live   else def = unk_2237AEF0
        case 91:  if submsg.oneof == 48 goto live   else def = unk_2237B240
            // (oneof checked at submsg+0x28; "globals_" fallback when the
            //  live message is absent — see the per-id globals table below)
        // --- the OCI command ids take CmdDmaIdFromEntry, NOT this tail ---
        case 22/23/26/54/55/96: return CmdDmaIdFromEntry<OciCommon...>(...)
        default: return 0

  live:                                          // LABEL_170
    hdr2 = (*submsg[+0x20]) + 24                  // live message's TraceIdHeader ptr
  compose:                                       // LABEL_172 @0xf69a444
    txn  = hdr2[6]                               // transaction_id  (uint32 @ TraceIdHeader+0x18)
    core = hdr2[7]                               // core_id         (uint32 @ +0x1c)
    chip = hdr2[8]                               // chip_id         (uint32 @ +0x20)
    dma_id = (txn & 0x1FFF00)                     // transaction_id bits 8..21
| (txn & 0xFF)                         // transaction_id bits 0..8
| ((core & 7)      << 21)              // core_id bits 21..24
| ((chip & 0x3FFF) << 24)              // chip_id bits 24..38
    presence = 1                                 // dl = 1 (the "this is a DMA event" flag)
    return dma_id

函数映射

函数地址角色
GetDmaId(int)0xf699ca0逐 id 的 TraceIdHeader → 38-bit dma_id;single caller
single-header compose tail0xf69a44438-bit bitfield pack
CmdDmaIdFromEntry<...>(per-class)OCI command-band 路径,DMA pass 未使用

逐 ID 的 Globals 原型

当 live message 缺失时使用的 "globals_" fallback。每个都是 zero-initialized message prototype;GetDmaId+0x18 读取其 TraceIdHeader slot(下方地址是 globals_ base;分支解引用 base+0x18)。

Band IDglobals_ symbol地址
48IciPacketDataPacketQueuedForLocalIngress_globals_0x2237b578
50OciMessageGeneratedInIcrEgressDma_globals_0x2237af10
51OciMessageGeneratedInIcrIngressDma_globals_0x2237aed8
91OciDescriptorCommonIssuedFromTcs_globals_0x2237b228
— (null default)TraceIdHeader_globals_(全零 → dma_id 0)0x2237a308

易踩坑 — presence bit(dl)由 caller 检查(test dl,1; je drop @0xf26c8de)。全零的 TraceIdHeader_globals_ default 仍然产生 presence == 1 且 dma_id == 0;技术上会构造一个键为 0 的 span,但由于缺少真实 begin/end pair,会在下游被丢弃。不要假设 dma_id == 0 不可能出现。


逐带 Payload 解析

四个分支共享一套骨架:按 trace_point_id 分类(从 TraceHeader+0x18 读取),加载 live message pointer(TraceEntry+0x20,fallback 到 class globals_),验证 submsg+0x28 处的 proto oneof case,应用该带的 gate,找到或插入该带 map 中的 dma_id slot,然后写入该带的贡献。生产者中的分发顺序为:

text
v23 = trace_point_id
if v23 <= 50:                  // → id 48 / id 50
    if v23 != 48: { if v23 == 50: <egress-message arm> }
    else:        <ici-packet arm>
elif v23 == 51:                <ingress-message arm>
elif v23 == 91:                <descriptor arm>

Band 48 — IciPacketDataPacketQueuedForLocalIngress(ingress begin/end markers)

ICI data-packet trace point 通过两个末尾 bool flag 携带一个 ingress DMA 的 beginend。它写入 MAP_B(ingress),kind tag 为 2。

字段Proto #C++ off类型角色
trace_id_headerf1+0x18submessagedma_id source
router_link_port_idf2+0x20enum(已解析,丢弃)
virtual_channelf3+0x24uint32(已解析,丢弃)
link_targetsf4+0x28uint32(已解析,丢弃)
local_ingress_targetf5+0x30bool(已解析,丢弃)
multicastf6+0x31bool(已解析,丢弃)
dst_chip_idf7+0x2cuint32(已解析,丢弃)
first_packet_in_dmaf8+0x32boolbegin marker
last_packet_in_dmaf9+0x33boolend marker
c
function Band48(msg, slot):                      // arm @0xf26ca2f, stores @0xf26ce60..
    if msg[+0x32] == 1:                          // first_packet_in_dma
        slot.begin_gtc[+0x8]  = gtc
        slot.begin_present[+0x10] = 1
        slot.byte_count[+0x28] = 0               // begin zeroes the byte counter
        slot.kind_tag[+0x40]  = 2                // ingress
    else if msg[+0x33] == 1:                     // last_packet_in_dma
        slot.end_gtc[+0x18]   = gtc
        slot.end_present[+0x20] = 1
        slot.kind_tag[+0x40]  = 2

注意 — 单个 id-48 event 只写入 begin marker end marker,绝不会两者都写。store block(0xf26c6e0 中的 @0xf26ce5c..)是 if (first) {...} else { if (last) {...} } 链,因此一个自包含 span 需要两个不同的 id-48 event(一个带 first_packet_in_dma,一个带 last_packet_in_dma),且二者共享同一个 dma_id

Band 50 — OciMessageGeneratedInIcrEgressDma(egress end timestamp)

Egress message 只贡献 egress span 的 end timestamp,并且仅在 done == 1 时贡献。它们携带 msg_data(与 id 51 用于字节数的是同一个字段),但生产者把 id 50 路由到 end-timestamp store,而不是 byte-count store — egress 字节数改由 descriptor(id 91)提供。

字段Proto #C++ off类型角色
trace_id_headerf1+0x18submessagedma_id source
msg_dataf2+0x20uint32(存在,但 id 50 读取)
donef3+0x24boolgatedone == 1
msg_typef4+0x28enum(已解析,丢弃)
opcodef5+0x2cenum(已解析,丢弃)
node_typef7+0x30enumlabel,不是 line key
addrf6+0x34uint32(已解析,丢弃)
c
function Band50(msg, slot):                      // arm @0xf26c919, store @0xf26cf41
    if msg[+0x24] != 1: return                   // done gate
    slot.end_gtc[+0x18]    = gtc
    slot.end_present[+0x20] = 1                   // no bytes, no kind-tag write

注意 — id 50 写 kind tag。它触碰的 slot 的 tag 已由 egress descriptor(id 91)begin 设为 3。因此 span 的 lane 由其 map(MAP_A → tag 3)固定,而不是由 id 50 固定。

Band 51 — OciMessageGeneratedInIcrIngressDma(ingress byte count)

message 布局与 id 50(SHAPE-A)相同,但位于 ingress 侧并用于其字节数。Ingress message 只贡献 byte count;ingress begin/end timestamp 来自 id 48 的 first/last-packet marker。写入 MAP_B(ingress)。

c
function Band51(msg, slot):                       // arm @0xf26ca95, store @0xf26cedb
    slot.byte_count[+0x28] += (uint32)(msg[+0x20] << 9)   // msg_data * 512

shift 固定为 9:OCI message 长度单位是固定 512-byte 粒度,且 OciMessageGeneratedInIcr*Dma 没有 granule 字段。这里是 add(不是 mov),意味着多个具有相同 dma_id 的 ingress message 会把字节数累加到 span 中。

Band 91 — OciDescriptorCommonIssuedFromTcs(egress begin + bytes)

node-fabric descriptor 是四者中最丰富的(17 个字段),但 DMA pass 只读取三个:dma_type(gate)、length(byte source)和 length_granule(shift selector)。它把 egress span 的 begin timestamp byte count 写入 MAP_A(egress),kind tag 为 3。完整的 src/dst endpoint 和 sync-flag 字段会被解析进 proto,但被此 pass 丢弃。

字段Proto #C++ off类型角色
trace_id_headerf1+0x18submessagedma_id source
dma_typef2+0x20enumgate== REMOTEUNICAST
src_mem_mem_idf3+0x24enum(已解析,丢弃)
src_mem_core_idf4+0x28uint32(已解析,丢弃)
src_opcodef5+0x2cenum(已解析,丢弃)
dst_mem_mem_idf6+0x30enum(已解析,丢弃)
dst_mem_core_idf7+0x34uint32(已解析,丢弃)
dst_opcodef8+0x38enum(已解析,丢弃)
src_sync_flag_iddst_sync_flag_1_core_idf9–f14+0x3c+0x50uint32(已解析,丢弃)
program_counterf15+0x54uint32(已解析,不是 byte source)
lengthf16+0x58uint32byte-count source
length_granulef17+0x5cenumshift selector
c
function Band91(descr, slot):                     // arm @0xf26c9b2, store @0xf26c865..88b
    if descr[+0x20] != 2: return                  // dma_type == DMA_TYPE_REMOTEUNICAST
    slot.begin_gtc[+0x8]    = gtc
    slot.begin_present[+0x10] = 1
    shift = (descr[+0x5c] == 0) ? 9 : 2           // length_granule: 512B→<<9, 4B→<<2
    slot.byte_count[+0x28]  = descr[+0x58] << shift   // length, with `mov` (overwrite)
    slot.kind_tag[+0x40]    = 3                    // egress

注意 — id 91 的 byte-count source 是 length(f16, +0x58),不是 program_counter。在 descriptor 的内存布局中,program_counter+0x54)紧挨着 length+0x58)之前,因此两者很容易混淆。生产者的 mov eax,[r14+0x58]; shl rax,cl@0xf26c880)读取 length,shift selector cmp [r14+0x5c],0 读取 length_granuleprogram_counter 字段已解析,但此 pass 从不使用。


Byte-Count 规则

byte_count 位于 DmaTransfer+0x28(map-value form)/ +0x20(merge/span form)。存在两套不同的累积规则;适用哪一套由 band 固定决定。

BandSource fieldScaleStore opProducer site
OCI message51msg_data(f2, +0x20<< 9(×512,固定)add0xf26cedb
OCI message50—(仅 end timestamp)
Descriptor91length(f16, +0x58<< (granule==0 ? 9 : 2)mov0xf26c880
ICI packet48—(仅 markers)

Descriptor shift 由 descriptor 自身的 length_granule enum 选择:

length_granuleValueShift含义
LENGTH_GRANULE_512B0<< 9length 计数 512-byte granules
LENGTH_GRANULE_4B1<< 2length 计数 4-byte words

怪异点 — id 91 覆盖mov)byte count,而 id 51 累加add)。这是构造上正确的:id 91 是 descriptor begin — 它把 slot 零初始化并设置一次总大小。id 51 是进入一个由 id-48 marker 设置 begin 的 slot 的 ingress message;同一 dma_id 的多个 ingress message 必须求和。二者永远不会混在一个 span 中,因为 id 91 落入 MAP_A(egress),id 51 落入 MAP_B(ingress)。

egress/ingress 分工

每个方向上,span 字段来自不同 trace point:

text
EGRESS span (MAP_A, kind tag 3 → "To ICI Router"):
    begin_gtc, byte_count   ← id 91  (descriptor: length × granule)
    end_gtc                 ← id 50  (egress message, done==1)

INGRESS span (MAP_B, kind tag 2 → "From ICI Router"):
    begin_gtc, end_gtc      ← id 48  (first/last packet markers)
    byte_count              ← id 51  (ingress message: msg_data × 512)

注意 — 这种不对称 — egress message(id 50)只给 timestamp,ingress message(id 51)只给 bytes — 是 trace point 本身的属性(每侧发出 size 和 completion timestamp 的位置不同),不是 libtpu 的选择。静态解析确认了分工;这里看不到 silicon 层面的理由。


Span 配对与两个 Map

目的

同一 DMA 的 begin 和 end event 作为独立 trace entry 到达;它们通过共享的 38-bit dma_id 重新合并。两个独立的 flat_hash_map<uint64,DmaTransfer> 保存 open span — 一个用于 egress,一个用于 ingress — 因此两个方向的 span 永远不会在 dma_id 上冲突。

算法

c
function PairSpans(merged_entries):              // 0xf26c6e0 main loop
    map_egress  = {}                             // &v147 — id 50 + id 91
    map_ingress = {}                             // &v150 — id 48 + id 51
    vec_egress  = []                             // ptr   — flushed full egress spans
    vec_ingress = []                             // v145  — flushed full ingress spans

    for entry in merged_entries:
        gtc    = entry.TraceHeader.timestamp     // f3, uint64 @ TraceHeader+0x20
        dma_id = GetDmaId(entry, 0)              // 0xf699ca0
        if not dma_id.present: continue          // test dl,1; je drop @0xf26c8de
        id     = entry.trace_point_id            // @ TraceHeader+0x18
        map    = (id == 48 || id == 51) ? map_ingress : map_egress
        slot   = map.find_or_insert(dma_id)      // PrepareInsertSmallNonSoo / PrepareInsertLarge

        // If the slot is already complete (begin+end), flush it and reopen:
        if slot.begin_present && slot.end_present:
            (map == map_ingress ? vec_ingress : vec_egress).push_back(slot.span)  // 0xf2547e0
            slot.begin_present = slot.end_present = 0

        apply_band_contribution(id, entry.message, slot)   // see per-band arms

    for map,vec in {(egress,vec_egress),(ingress,vec_ingress)}:
        flush_all_complete(map, vec)
        MergeOverlappingTransfers(vec)           // 0xf26dae0
        ConvertDmaTransfersToXPlane(vec, plane)  // 0xf254bc0

函数映射

函数地址角色
flat_hash_map policy0x21646fe8FlatHashMapPolicy<unsigned long, DmaTransfer>
PrepareInsertSmallNonSoo0x21118e20small-table insert
PrepareInsertLarge0x2111a600large-table insert
HashKey0xe867c60dma_id 执行的 Hash<unsigned long>
vector<DmaTransfer>::push_back0xf2547e0flush 一个 complete span(88-byte copy)
MergeOverlappingTransfers0xf26dae0按 vector 合并 overlapping span

易踩坑 — “flush-and-reopen” 路径(slot.begin_present && slot.end_presentpush_back,清 flag)意味着重新触碰一个 complete slot 时,它会被发出并复用,而不是原地覆盖。简单覆盖 full slot 的重新实现会在一次 capture 中某个 dma_id 被多个 transfer 复用时丢失 span。


DmaTransfer Span Record

map value type,0x58(88)bytes。map 在 +0x0 存储 dma_id key,并从 +0x8 开始存储 span body;merge/span form(push_back 复制的内容,也是渲染器读取的内容)只有 body。

字段Map-value offSpan/merge off类型含义
dma_id+0x0(key)uint64pairing key(38-bit)
begin_gtc+0x8+0x0uint64begin GTC tick(sort key)
begin_present+0x10+0x8boolbegin written
end_gtc+0x18+0x10uint64end GTC tick
end_present+0x20+0x18boolend written
byte_count+0x28+0x20uint64accumulated bytes
kind_tag+0x40+0x38uint322 = ingress, 3 = egress

注意 — gtc 是 event 的 TraceHeader.timestamp(proto field 3,uint64 at TraceHeader+0x20),每条 entry 加载一次(@0xf26c8cd)。它是原始 GTC tick;GTC→picosecond 转换稍后发生在渲染器的 AddEvent(GtcSpan) 路径中。


渲染到 Lane — ConvertDmaTransfersToXPlane

目的

每个合并后的 DmaTransfer span vector 会被转换为对应 XPlane line 上的 XEvent。渲染器预先创建四条线(63 MemcpyH2D、64 MemcpyD2H、54 From-ICI-Router、55 To-ICI-Router)和四个 event-metadata name,然后按每个 span 的 kind_tag switch 选择 line 和 event name。对 ICR band 只有 tag 2 和 3 会被产生;tag 6/7 是 host-DMA 分支(此 trace family 未使用),其他任何 tag 都会被跳过。

算法

c
function ConvertDmaTransfersToXPlane(spans, plane):  // 0xf254bc0
    line_h2d  = plane.GetOrCreateLine(63)        // "MemcpyH2D"
    line_d2h  = plane.GetOrCreateLine(64)        // "MemcpyD2H"
    line_in   = plane.GetOrCreateLine(54)        // "From ICI Router"
    line_out  = plane.GetOrCreateLine(55)        // "To ICI Router"
    meta_in   = plane.GetOrCreateEventMetadata("ICI Ingress", 11)  // @0xf254eb8
    meta_out  = plane.GetOrCreateEventMetadata("ICI Egress",  10)  // @0xf254edf
    stat_bytes   = StatMetadata(GetStatTypeStr(78))   // byte-count stat
    stat_bw      = StatMetadata("bandwidth")
    // (also: details, queue=StatType 79 — not populated on this trace family)

    for span in spans:
        if not (span.byte_count && span.begin_present && span.end_present): continue
        switch span.kind_tag:                    // @0xf254... switch on [rbx-8], jt @0xab589bc
            case 2: line = line_d2h; meta = meta_in;  break    // ingress "ICI Ingress" → line 64 (byte-confirmed)
            case 3: line = line_in;  meta = meta_out; break    // egress  "ICI Egress"  → line 54 (byte-confirmed)
            case 6: line = line_h2d; meta = "MemcpyH2D"; break // host (unused here)
            case 7: line = line_h2d; meta = "MemcpyD2H"; break // host (unused here)
            default: continue                    // tags 0/4/5 dropped
        if span.end_gtc <= span.begin_gtc: continue   // non-positive duration
        ev = line.AddEvent(GtcSpan{begin_gtc, end_gtc})   // 0xf1df1e0
        ev.AddStat(stat_bytes, span.byte_count)
        if span.byte_count: ev.AddStat(stat_bw, bandwidth(span))

Lane / Tag Map

Kind tagSource bandsXEvent name渲染所在的 Line(component)
248(markers)+ 51(bytes)"ICI Ingress"64("MemcpyD2H" 线,component 64)
391(begin+bytes)+ 50(end)"ICI Egress"54("From ICI Router", component 54)
6(host H2D,未使用)"MemcpyH2D"63 "MemcpyH2D"
7(host D2H,未使用)"MemcpyD2H"63 "MemcpyH2D"
0 / 4 / 5dropped(无)

ConvertDmaTransfersToXPlane @ 0xf254bc0 中的 tag → metadata 配对已按字节确认:id 91 把 tag 3 写入 MAP_A(egress),id 48 把 tag 2 写入 MAP_B(ingress),每个 map 都 flush 到自己的 vector。渲染器的 case 2 配对 "ICI Ingress" metadata(v120),case 3 配对 "ICI Egress" metadata(v121)。但是,每个 span 落到的 line 不是人们可能预期的对称 54/55 组合:字节精确的 switch 把 case 2(ingress)路由到 line-64 builder(v131, component 64),把 case 3(egress)路由到 line-54 builder(v137, component 54)。TpuComponentName(54) = "From ICI Router",TpuComponentName(55) = "To ICI Router",TpuComponentName(64) = "MemcpyD2H" — 因此渲染出的 line name 会读作干净的 "From/To ICI Router" 对;egress span 落在 "From ICI Router",ingress span 落在 "MemcpyD2H" 线。Component 55("To ICI Router")在线设置中被创建,但没有被任何 switch arm 选中。重新实现必须遵循字节精确的 case→builder 绑定(2→line64, 3→line54),而不是假设 direction↔component 对称。(见 DMA Endpoint Rendering,其 renderer table 携带相同绑定。)

怪异点 — 渲染器还 intern 了 queue(StatType 79)和 details stat metadata,但此 trace family 上的 DmaTransfer span 只携带 {begin, end, bytes, tag}。id-48 的 router_link_port_id/virtual_channel/link_targets 以及 id-91 的 src/dst endpoint 和 sync-flag 字段会被解析进 proto 然后丢弃 — 它们在这里永远不会进入 XStat。只有 dma_id 派生的配对、GTC span 和 byte count 能保留到渲染出的 event 中。


Band Decode 引用的枚举

EnumValuesUsed by
DmaTypeValues0=LOCAL, 1=CHIP2HOST, 2=REMOTEUNICAST, 3=REMOTEMULTICASTid 91 gate
LengthGranuleValues0=LENGTH_GRANULE_512B, 1=LENGTH_GRANULE_4Bid 91 shift
NodeTypeValues0..6 = TCS/BC/CMQ/HBMQ/UHI/ICR/QNMid 50/51 label(丢弃)
RouterLinkPortIdValues0..5 = LINK0..LINK5id 48 label(丢弃)

注意 — 只有 REMOTEUNICAST descriptor 会进入 egress timeline:id 91 由 dma_type == 2 gate。Local、host 和 multicast DMA 在 span 构建前就被过滤掉。渲染所有 dma_type 值的重新实现会让 "To ICI Router" lane 过度填充。


尚未解析的内容

  • 真实 capture 中的 span 节奏。 静态解析证明了配对逻辑,但无法证明一个 egress DMA 是否总是发出恰好一个 id-91 begin 和一个具有相同 dma_id 的 id-50 end(而非拆分/合并)。flush-and-reopen 路径会处理重新触碰的 slot,但时间顺序(id-91 begin 是否总是先于 id-50 end?)来自 trace-point 语义假设,而非字节证明。
  • 被丢弃的 descriptor/packet 字段。 14 个未使用的 id-91 endpoint/sync-flag 字段以及 id-48 link/channel 字段已解析,但未由此 pass 渲染。是否有其他 XPlane pass 或下游 symbolizer 读取它们,未追踪。
  • GTC→picosecond 和 bandwidth 字符串转换。 AddEvent(GtcSpan)0xf1df1e0)以及 "%.2fGB/s" 风格的 bandwidth StrFormat 此处未解析;begin/end GTC tick 和 byte count 是精确的,但渲染出的 duration 和 bandwidth 取决于 GTC-tick divide,本页没有钉住它。
  • 逐代变体。 GetDmaId(int) 只针对 pxc template。vfc/vlc/glc/gfc 各代把 DMA band 折叠进一个(很可能 inline 的)逐代 producer,处理加宽后的 id;它们是否复用相同的四 id 键集合和 byte-count 规则,尚未定位。

交叉引用