ICR Node-Fabric DMA 时间线带
本页所有地址均适用于 wheel
libtpu-0.0.40中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d— 明确锚点;运行时报告的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 TraceIdHeader — chip_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 提取器是位于 0xf699ca0 的 TraceEntryWrapper<pxc>::GetDmaId;lane 渲染器是位于 0xf254bc0 的 ConvertDmaTransfersToXPlane。这个构建中三者都带有完整 C++ 符号,因此 message class 名、字段名和 DmaTransfer 布局都是直接恢复的,不是推断得出。
对于重新实现,契约是:
- 四个 id 的键集合
{48, 50, 51, 91}以及生产者如何构造它(4-elementGetMergedselector),包括精确的分发顺序。 - 每个 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 时间线生产者 | 0xf26c6e0 — ConvertTpuTraceToXPlane<pxc>::{lambda#1}::{lambda#3} |
dma_id 提取器 | 0xf699ca0 — TraceEntryWrapper<pxc>::GetDmaId(int), single caller @0xf26c8d9 |
| Lane 渲染器 | 0xf254bc0 — ConvertDmaTransfersToXPlane |
| Span 合并器 | 0xf26dae0 — MergeOverlappingTransfers(每个 map 运行一次) |
| 键集合 | {48, 50, 51, 91} — 由 0x320000005B + 两次 store 构建 |
| Span 值类型 | DmaTransfer, 0x58 (88) bytes;push_back @0xf2547e0 |
| Egress span | XEvent "ICI Egress"(kind tag 3)→ 渲染在 component 54("From ICI Router") |
| Ingress span | XEvent "ICI Ingress"(kind tag 2)→ 渲染在 component 64("MemcpyD2H" 线) |
一览 — 四个 ICR DMA 带
| Band ID | Message class (asic_sw::driver::deepsea::pxc::profiler::) | Oneof | 方向 / map | 生产者角色 | 携带的 payload |
|---|---|---|---|---|---|
| 48 | IciPacketDataPacketQueuedForLocalIngress | 29 (0x1d) | ingress / MAP_B | begin 或 end marker | first/last-packet bools |
| 50 | OciMessageGeneratedInIcrEgressDma | 31 (0x1f) | egress / MAP_A | 仅 end timestamp | done gate, msg_data(此处未使用) |
| 51 | OciMessageGeneratedInIcrIngressDma | 32 (0x20) | ingress / MAP_B | byte-count 累加 | msg_data << 9 |
| 91 | OciDescriptorCommonIssuedFromTcs | 48 (0x30) | egress / MAP_A | begin + byte-count | length << 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}。
算法
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,所以91和50被按字节打包(0x5B,0x32)进一次 64-bit store,随后48和51作为单独 dword 写入。重新实现可以直接使用无序集合{48, 50, 51, 91};这种打包是代码生成产物,不是语义。
注意事项
GetMerged(0xf26ba80)是按 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 永远不会到达这些分支。
入口点
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算法
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 tail | 0xf69a444 | 38-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 ID | globals_ symbol | 地址 |
|---|---|---|
| 48 | IciPacketDataPacketQueuedForLocalIngress_globals_ | 0x2237b578 |
| 50 | OciMessageGeneratedInIcrEgressDma_globals_ | 0x2237af10 |
| 51 | OciMessageGeneratedInIcrIngressDma_globals_ | 0x2237aed8 |
| 91 | OciDescriptorCommonIssuedFromTcs_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,然后写入该带的贡献。生产者中的分发顺序为:
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 的 begin 和 end。它写入 MAP_B(ingress),kind tag 为 2。
| 字段 | Proto # | C++ off | 类型 | 角色 |
|---|---|---|---|---|
trace_id_header | f1 | +0x18 | submessage | dma_id source |
router_link_port_id | f2 | +0x20 | enum | (已解析,丢弃) |
virtual_channel | f3 | +0x24 | uint32 | (已解析,丢弃) |
link_targets | f4 | +0x28 | uint32 | (已解析,丢弃) |
local_ingress_target | f5 | +0x30 | bool | (已解析,丢弃) |
multicast | f6 | +0x31 | bool | (已解析,丢弃) |
dst_chip_id | f7 | +0x2c | uint32 | (已解析,丢弃) |
first_packet_in_dma | f8 | +0x32 | bool | begin marker |
last_packet_in_dma | f9 | +0x33 | bool | end marker |
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_header | f1 | +0x18 | submessage | dma_id source |
msg_data | f2 | +0x20 | uint32 | (存在,但 id 50 不读取) |
done | f3 | +0x24 | bool | gate(done == 1) |
msg_type | f4 | +0x28 | enum | (已解析,丢弃) |
opcode | f5 | +0x2c | enum | (已解析,丢弃) |
node_type | f7 | +0x30 | enum | label,不是 line key |
addr | f6 | +0x34 | uint32 | (已解析,丢弃) |
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)。
function Band51(msg, slot): // arm @0xf26ca95, store @0xf26cedb
slot.byte_count[+0x28] += (uint32)(msg[+0x20] << 9) // msg_data * 512shift 固定为 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_header | f1 | +0x18 | submessage | dma_id source |
dma_type | f2 | +0x20 | enum | gate(== REMOTEUNICAST) |
src_mem_mem_id | f3 | +0x24 | enum | (已解析,丢弃) |
src_mem_core_id | f4 | +0x28 | uint32 | (已解析,丢弃) |
src_opcode | f5 | +0x2c | enum | (已解析,丢弃) |
dst_mem_mem_id | f6 | +0x30 | enum | (已解析,丢弃) |
dst_mem_core_id | f7 | +0x34 | uint32 | (已解析,丢弃) |
dst_opcode | f8 | +0x38 | enum | (已解析,丢弃) |
src_sync_flag_id … dst_sync_flag_1_core_id | f9–f14 | +0x3c–+0x50 | uint32 | (已解析,丢弃) |
program_counter | f15 | +0x54 | uint32 | (已解析,不是 byte source) |
length | f16 | +0x58 | uint32 | byte-count source |
length_granule | f17 | +0x5c | enum | shift selector |
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 selectorcmp [r14+0x5c],0读取length_granule。program_counter字段已解析,但此 pass 从不使用。
Byte-Count 规则
byte_count 位于 DmaTransfer+0x28(map-value form)/ +0x20(merge/span form)。存在两套不同的累积规则;适用哪一套由 band 固定决定。
| 侧 | Band | Source field | Scale | Store op | Producer site |
|---|---|---|---|---|---|
| OCI message | 51 | msg_data(f2, +0x20) | << 9(×512,固定) | add | 0xf26cedb |
| OCI message | 50 | —(仅 end timestamp) | — | — | — |
| Descriptor | 91 | length(f16, +0x58) | << (granule==0 ? 9 : 2) | mov | 0xf26c880 |
| ICI packet | 48 | —(仅 markers) | — | — | — |
Descriptor shift 由 descriptor 自身的 length_granule enum 选择:
length_granule | Value | Shift | 含义 |
|---|---|---|---|
LENGTH_GRANULE_512B | 0 | << 9 | length 计数 512-byte granules |
LENGTH_GRANULE_4B | 1 | << 2 | length 计数 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:
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 上冲突。
算法
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 policy | 0x21646fe8 | FlatHashMapPolicy<unsigned long, DmaTransfer> |
PrepareInsertSmallNonSoo | 0x21118e20 | small-table insert |
PrepareInsertLarge | 0x2111a600 | large-table insert |
HashKey | 0xe867c60 | 对 dma_id 执行的 Hash<unsigned long> |
vector<DmaTransfer>::push_back | 0xf2547e0 | flush 一个 complete span(88-byte copy) |
MergeOverlappingTransfers | 0xf26dae0 | 按 vector 合并 overlapping span |
易踩坑 — “flush-and-reopen” 路径(
slot.begin_present && slot.end_present→push_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 off | Span/merge off | 类型 | 含义 |
|---|---|---|---|---|
dma_id | +0x0 | (key) | uint64 | pairing key(38-bit) |
begin_gtc | +0x8 | +0x0 | uint64 | begin GTC tick(sort key) |
begin_present | +0x10 | +0x8 | bool | begin written |
end_gtc | +0x18 | +0x10 | uint64 | end GTC tick |
end_present | +0x20 | +0x18 | bool | end written |
byte_count | +0x28 | +0x20 | uint64 | accumulated bytes |
kind_tag | +0x40 | +0x38 | uint32 | 2 = ingress, 3 = egress |
注意 —
gtc是 event 的TraceHeader.timestamp(proto field 3,uint64 atTraceHeader+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 都会被跳过。
算法
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 tag | Source bands | XEvent name | 渲染所在的 Line(component) |
|---|---|---|---|
| 2 | 48(markers)+ 51(bytes) | "ICI Ingress" | 64("MemcpyD2H" 线,component 64) |
| 3 | 91(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 / 5 | — | dropped | (无) |
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)和detailsstat metadata,但此 trace family 上的DmaTransferspan 只携带{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 引用的枚举
| Enum | Values | Used by |
|---|---|---|
DmaTypeValues | 0=LOCAL, 1=CHIP2HOST, 2=REMOTEUNICAST, 3=REMOTEMULTICAST | id 91 gate |
LengthGranuleValues | 0=LENGTH_GRANULE_512B, 1=LENGTH_GRANULE_4B | id 91 shift |
NodeTypeValues | 0..6 = TCS/BC/CMQ/HBMQ/UHI/ICR/QNM | id 50/51 label(丢弃) |
RouterLinkPortIdValues | 0..5 = LINK0..LINK5 | id 48 label(丢弃) |
注意 — 只有
REMOTEUNICASTdescriptor 会进入 egress timeline:id 91 由dma_type == 2gate。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"风格的 bandwidthStrFormat此处未解析;begin/end GTC tick 和 byte count 是精确的,但渲染出的 duration 和 bandwidth 取决于 GTC-tick divide,本页没有钉住它。 - 逐代变体。
GetDmaId(int)只针对pxctemplate。vfc/vlc/glc/gfc各代把 DMA band 折叠进一个(很可能 inline 的)逐代 producer,处理加宽后的 id;它们是否复用相同的四 id 键集合和 byte-count 规则,尚未定位。
交叉引用
- UHI / OCI / ICI / General-DMA Payloads — 兄弟 DMA 和 ICI band 解析;本页让 ICR node-fabric band(48/50/51/91)与它们保持区分。
- TracePoints Master Registry — master band-ID registry;包含这四个 id 在内的所有 trace-point id 的归属页。
- Trace Entries Coder — 这些 payload 在其上解析的 16-byte packet、
TraceHeader和TraceIdHeader布局。 - DMA Endpoint Rendering — 平行的 DMA-rendering 路径;用于交叉链接此 band 与之共享渲染器的 host-DMA(MemcpyH2D/D2H, tags 6/7)分支。
- Trace Entry to XEvent — 此 DMA pass 所特化的一般 entry→XEvent 转换框架。