net_router 发射器流水线
本页所有地址均适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(构建libtpu_lts_20260413_b_RC00,build-id89edbbe81c5b328a958fe628a9f2207d)。该镜像未剥离符号;反混淆后的符号名按原样引用。.textVMA 等于文件偏移(.text基址0xe63c000);.data.rel.ro带有0x200000的 VMA→文件偏移差。其他版本会有所不同。
摘要
net_router 发射器流水线是 limited-ICI collective 路由的降低侧:它是一套分阶段机制,把 collective 的 replica-group / device-assignment 关系转换为扁平的 net_router::Transfer 记录列表,将该列表交给 hop-assignment 求解器,最后把得到的调度按每步重放为 inter-chip-interconnect (ICI) DMA 程序。它是该构建中的两个路由产物之一,即每个 collective 的显式调度,不同于具备韧性的逐链路自动路由表,并且完全位于 xla::jellyfish::net_router 命名空间中。
本页涵盖流水线中的三个阶段以及在它们之间流动的记录:
- 每个 collective 的
Transfer集合构建器 —CreateAllToAllTransfers、CreateAllGatherTransfers,以及(交叉引用的)CreateCollectivePermuteTransfers。每个构建器都会把扁平的 device/replica id 解码为 torus 坐标,把坐标映射到物理 core id,并发射一个或多个 16 字节的{src_core, src_index, dst_core, dst_index}Transfer记录。三者唯一的区别在于它们如何枚举 source→target 关系。 - 分阶段流水线回调 —
CreateRoutingSchedule构造并由离散事件模拟器耗尽的三个延迟std::function闭包($_4defer-at-step、$_1buffer-release、$_2commit-placement)。本页记录发射器如何构造并延迟它们;求解器内部结构(堆、比较器、候选方向生成)见 Create Routing Schedule。 - 发射驱动器 —
net_router::EmitRoutingCode,它消费调度(或直接运行CreateRoutingSchedule)并按 core、按 step 重放:分配 sync flag,读取 4 个方向列,在kPipelineFactor=3窗口周围发起逐步 DMA + wait + prefetch。
构建器送入的调度,即堆遍历、Schedule 记录、PointerType,见 Create Routing Schedule。逐链路自动路由表以及 Direction[] → PerLinksRoutingTable 的降低见 Unicast Route Emission 和 Route-Table Generation。本页只链接它们,不重新推导。
对于重新实现,契约如下:
Transfer记录 — 每个构建器写入、并由CreateRoutingSchedule/EmitRoutingCode消费的 16 字节{src_core@0, src_index@4, dst_core@8, dst_index@0xc}四元组。- 三个构建器 — 共享的 id→coordinate→core 机制,以及各 collective 的枚举方式:A2A 基于有序 replica-group 位置的双向配对集合,AG 的
i→j广播加 ordinals 去重表,CP 的逐source_target_pair集合。 - 流水线回调 —
$_4作为延迟原语,$_1作为 buffer-release / in-flight 跟踪器,$_2作为仅提交一次的 commit;发射器如何构造闭包并把它们延迟到正确的 step。 EmitRoutingCode— 每个 core 的基准索引、4 方向读取、逐步 DMA / wait / prefetch 序列,以及强制流水线窗口的AllocateScopedSflag同步屏障。
| 命名空间 | xla::jellyfish::net_router |
| A2A 构建器 | AllToAllEmitterBase::CreateAllToAllTransfers @ 0x10f05580 |
| AG 构建器 | (anon)::CreateAllGatherTransfers @ 0x1380ea20 |
| CP 构建器 | (anon)::CreateCollectivePermuteTransfers @ 0x13470fe0(见 create-routing-schedule) |
| 流水线回调 | $_4 @ 0x13825b60 · $_1 @ 0x13826dc0 · $_2 @ 0x13827760 |
| 求解器 | CreateRoutingSchedule @ 0x1381c6a0 → CreateRoutingScheduleLiteral @ 0x13822400 |
| 发射驱动器 | net_router::EmitRoutingCode @ 0x13819ca0 |
Transfer 记录 | 16 字节 {src_core@0, src_index@4, dst_core@8, dst_index@0xc} |
| 流水线因子 | kPipelineFactor = 3(3 阶段 DMA 延迟窗口) |
| 证据等级 | 可重新实现级 / 经 IDA 反编译 + 反混淆符号逐字节确认 |
1. Transfer 记录
目的
net_router::Transfer 是流水线每个阶段共享的原子单位。它表示一个 buffer slot 从源 core 到目标 core 的单次逻辑移动,采用物理 core id 表示,而不是 chip 坐标或 device id。每个 collective 的构建器生成一个 std::vector<Transfer>;CreateRoutingSchedule 消费一个 absl::Span<Transfer const>(span 类型可在 EmitRoutingCode 的 mangled signature 中看到:absl::Span<Transfer const>);调度 literal 则为每个 (core, step, direction) slot 序列化一条记录。
布局
net_router::Transfer (16 bytes)
+0x00 int src_core physical core id of the source
+0x04 int src_index buffer / slot ordinal within the source
+0x08 int dst_core physical core id of the destination
+0x0c int dst_index buffer / slot ordinal within the destination
```text
16 字节大小通过两种方式逐字节确认:构建器用元素步长 `0x10` 索引 `vector<Transfer>`(A2A 写入一个 `0x20` 字节的*配对* slot,即两个相邻 `Transfer`),并且 `CreateAllToAllRoutingScheduleTable` 在把 span 交给 `CreateRoutingScheduleLiteral` 前用 `shl $0x4`(×16)重新缩放计数。反编译的构建器返回 `absl::StatusOr<std::vector<net_router::Transfer>>`(反混淆后的返回类型在 `MakeErrorStreamWithOutput<...vector<net_router::Transfer>...>` 错误流实例化中按原样出现)。
> **注意 —** `src_index` / `dst_index` 是 **buffer 所在地址空间内的 slot / rank 序号**,不是 core id。core id 位于 `+0x00` / `+0x08`。index 字段的语义因 collective 而异(§2):A2A 使用组内位置;AG 固定 `src_index=0` 并把 `dst_index` 设为源 rank;CP 从 read/write buffer 序号派生它。下游 `Pointer` 会把该 index 和 `PointerType` 标记一起携带(见 [create-routing-schedule § PointerType](create-routing-schedule.md#the-pointertype-enum))。
### 函数映射
| 函数 | VMA | 作用 |
|---|---|---|
| `Transfer::ToString` | —(用于 debug log) | 为 `VLOG` 格式化 `{src_core, src_index, dst_core, dst_index}` |
| `CreateAllToAllTransfers` | `0x10f05580` | A2A `vector<Transfer>` 构建器 |
| `CreateAllGatherTransfers` | `0x1380ea20` | AG `vector<Transfer>` 构建器 |
| `CreateCollectivePermuteTransfers` | `0x13470fe0` | CP `vector<Transfer>` 构建器 |
---
## 2. 每个 Collective 的 `Transfer` 集合构建器
三个 limited-ICI collective 都构建**同一个** 16 字节 `Transfer` 列表并送入同一个求解器;它们唯一的区别是 source→target 关系的枚举方式。三者的公共机制相同。
### 2.1 共享机制 — id → coordinate → core
每个构建器都会对每个参与者执行相同的三步解码:
1. **组成员关系。** CP 读取 HLO `source_target_pairs`(用户排列)。A2A 读取 `device_list()` 和 `has_replica_groups()`;AG 在 `GetCollectiveOpGroupMode` 之后读取 `GetParticipatingDevicesGroups(DeviceAssignment, replica_groups, mode)`。结果是一组 device group,每个都是 device id 的小型 `InlinedVector`。
2. **id → 逻辑坐标。** 扁平 device/replica id 通过对 `LogicalTopologyInfo` 维度做整数除法(`idiv`/`div`)解码,再经过有步长的多维线性化(对每维 stride 做 8-wide `imul`/`add` 点积)。
3. **coordinate → 物理 core id。** 坐标通过 `LogicalTopologyInfo+0x18` 处的 coord→core-id 表映射(CP 在自己的 info struct 上使用 `+0x10`)。得到的 core id 会落入 `Transfer.src_core` / `Transfer.dst_core`。
`channel_id()` 和 `has_replica_groups()` 选择 group mode;`Target::ReplicaCount` @ `0x1d6141e0` 约束迭代边界。该解码链已按访问模式逐字节确认,但每维 stride 的*值*(`LogicalTopologyInfo` 内 stride vector 的来源)只读到了访问级别,因此精确 stride 布局标为 **HIGH**。
```c
// shared decode (schematic; per-builder offsets noted in §2.2–2.3)
for each device_group in groups:
for each member id in device_group:
coord = decode_coordinate(id, LogicalTopologyInfo.dims) // idiv + strided linearization
core = coord_to_core_table[coord] // LogicalTopologyInfo+0x18
// ... emit Transfer(s) using core ids ...2.2 AllToAll — 基于有序位置的双向配对
AllToAllEmitterBase::CreateAllToAllTransfers @ 0x10f05580(TU all_to_all_emitter_base.cc,path str @ 0x878b4af)计算组结构,然后对每个 replica group 内的每个有序位置对发射一个对称配对。
function CreateAllToAllTransfers(hlo, target, topo_info): // 0x10f05580
devices = hlo.device_list() // 0x1e5a95c0 (vtable +0x28)
cid = hlo.channel_id() // 0x1e59ff80
mesh_dim0 = topo_info+0x00; mesh_dim1 = topo_info+0x04
if hlo.has_replica_groups(): // 0x1e5a95e0
group_size = *(group_vtable+0x18)
else:
group_size = (cid & 1) ? mesh_dim0 : mesh_dim1 // cmovne @0x10f055f3
total_chips = ChipBounds.X(TpuTopology+0x58) * ChipBounds.Y(+0x5c)
RET_CHECK total_chips % group_size == 0 // str @0x9fc9afc (line ~158)
num_groups = total_chips / group_size
// allocate num_groups × 24-byte group records
for each group in groups:
RET_CHECK group.size() == group_size // str (line ~650)
for src_pos in [0, group_size):
for dst_pos in [0, group_size): // ordered pairs
core_src = coord_to_core(group[src_pos])
core_dst = coord_to_core(group[dst_pos])
// emit a 0x20-byte PAIR slot = two adjacent Transfers
Transfer A = { src_core=core_src, src_index=dst_pos, dst_core=core_dst, dst_index=src_pos }
Transfer B = { src_core=core_dst, src_index=dst_pos, dst_core=core_src, dst_index=src_pos }
```text
每个有序位置对贡献一个**正向和一个反向 hop**(一个 `0x20` 字节 slot,保存两个相邻 `Transfer`),因此完整的 all-to-all 排列,即每个 rank 把 slot-*j* 发送给 rank-*j* 并从 rank-*i* 接收 slot-*i*,被表达为一个对称的 `core_i ↔ core_j` 集合。`src_index` / `dst_index` 是组内 rank 序号(循环位置计数器),不是 core id。
字节锚点:`device_list` @ call site `0x10f055a5`;`channel_id` @ `0x10f055cf`;`has_replica_groups` @ `0x10f055d9`;`group_size` `cmovne` @ `0x10f055f3`;`total_chips = X·Y` `imul` @ `0x10f05610`;`num_groups` `idiv` @ `0x10f05626`;`total_chips % group_size == 0` RET_CHECK 字符串 @ `0x9fc9afc`(line `0x9e`);coord→core 表基址位于 `LogicalTopologyInfo+0x18`;Transfer A 写入 @ `0x10f05d84`;Transfer B 写入 @ `0x10f05d30` / `0x10f05e00` / `0x10f05ee7`;`0x20` pair-stride 前进 @ `0x10f05d4a`。
> **陷阱 —** A2A 循环遍历的是**有序**位置对(`src_pos`, `dst_pos`),并为每个 slot 发射两个方向。只迭代 `src_pos < dst_pos` 并只发射一个方向的重新实现,会正好丢掉一半 all-to-all 流量,即反向 hop,调度会静默不完整。
### 2.3 AllGather — 带 ordinals 去重的 `i→j` 广播
`(anon)::CreateAllGatherTransfers` @ `0x1380ea20`(TU `all_gather_emitter.cc`,path str @ `0x8761218`)把 HLO 转换为 `HloAllGatherInstruction`,解析 group mode,取得参与的 device group,然后为每个 `(source rank i, dest rank j)` 发射一个 `Transfer`。
```c
function CreateAllGatherTransfers(hlo, target, topo_info): // 0x1380ea20
ag = cast<HloAllGatherInstruction>(hlo) // 0xf313920
mode = GetCollectiveOpGroupMode(ag.use_global_device_ids, // 0x1e52b8a0
ag.channel_id.has_value) // (HLO+0xf0 bit, HLO+0xc8)
groups = GetParticipatingDevicesGroups(device_assignment, // 0x1e46bc20
replica_groups, mode)
ordinals[core] = -1 for all core // sentinel dedup table (r13)
for each group in groups:
for i in [0, group.size()): // source rank
core_i = coord_to_core(group[i])
RET_CHECK group[i] >= 0 // str @0x9fc6648
RET_CHECK ordinals[core_i] < 0 // str @0x9fd0a94 — not yet assigned
for j in [0, group.size()): // dest rank
core_j = coord_to_core(group[j])
RET_CHECK group[j] >= 0 // str @0x9fc65a3
Transfer = { src_core=core_i, src_index=0, dst_core=core_j, dst_index=i }src_index 始终为 0,因为每个 rank 贡献自己的单个输入 shard。dst_index = i 是源 rank 数据在 gather 输出中占据的 slot。ordinals 哨兵表(按 core,-1 = 未见过)用于去重 self/重复 source-core 分配:如果同一个源 core 被枚举两次,RET_CHECK ordinals[group[i]] < 0 会失败。构建器还会发出两条调试 VLOG 行,即 "transfers: "(str @ 0xa232d61)和 "ordinals: "(str @ 0xa23572f),二者都通过 Transfer::ToString 格式化 Transfer。
字节锚点:HloAllGatherInstruction cast @ 0x1380ea44;GetCollectiveOpGroupMode @ 0x1380ea81;GetParticipatingDevicesGroups @ 0x1380eabf;成员列表 (%r8)[idx] 4 字节读取 @ 0x1380efe8;Transfer 写入 {src@0=r10d, idx@4=$0, dst@8=r12d, idx@0xc=r9d} @ 0x1380f000,第二站点 @ 0x1380f0e3;ordinals 去重 r13[core] @ 0x1380f042/0x1380f04e;三个 RET_CHECK 字符串 @ 0x9fc65a3 / 0x9fc6648 / 0x9fd0a94。
注意 — AllGather 有第二条降低路径(ND-ring
MeshNDInfo表),它不经过CreateAllGatherTransfers。AllGatherEmitter::GenerateConstants@0x13801be0由ShouldUseExplicitRouting@0x13803aa0门控:为 true 时运行CreateAllGatherTransfers → CreateRoutingScheduleLiteral(本页);为 false 时通过CreateStaticNDRingReplicaInfoTable@0x1c69e900/CreateNDRingReplicaInfoTable@0x1c69e7e0构建 ND-ring replica-info 表。ND-ring 路径不在本页范围内。
2.4 CollectivePermute — 逐 source_target_pair
(anon)::CreateCollectivePermuteTransfers @ 0x13470fe0 是第三个构建器。它枚举 HLO source_target_pairs(用户指定的排列),并为每个 (pair × buffer × read/write) 发射一个 Transfer,其中 src/dst 通过同样的 id→coord→core 机制解析。它的完整推导,包括 src_index = read_write_idx · NumReadWritesPerBuffer + buffer 索引约定,见 Create Routing Schedule;这里只点名它以补全三元组。
2.5 三个构建器对比
| Collective | 构建器 (VMA) | pair 来源 | 每个元素的 Transfer 形状 |
|---|---|---|---|
| CollectivePermute | CreateCollectivePermuteTransfers 0x13470fe0 | HLO source_target_pairs | 每个 (pair × buffer × r/w) 一个 Transfer |
| AllToAll | CreateAllToAllTransfers 0x10f05580 | replica-group 有序位置对 | 双向 PAIR(i→j 和 j→i);src/dst_index = 组内位置 |
| AllGather | CreateAllGatherTransfers 0x1380ea20 | replica-group 成员 i, j | i→j 广播;src_index=0,dst_index=i |
陷阱 — AllReduce 不在此表中。对
CreateRoutingScheduleLiteral的全文 caller xref 只找到 AllGather、AllToAll 和 CollectivePermute。AllReduce 通过EmitRoutingCode的直接CreateRoutingSchedule调用(§4,运行时非 literal 路径)到达逐步程序,因此完全不使用这些每个 collective 的Transfer构建器。置信度:HIGH(与 overview 一致)。
3. 分阶段流水线回调
CreateRoutingSchedule 把它的 kPipelineFactor=3 软件流水线作为基于逐步回调向量的离散事件模拟器驱动。流水线由三个延迟的 std::function<Status(map<XY, IterationInfo>&)> 闭包实现。本页记录发射器如何构造并延迟它们,以及每个闭包在降低侧做什么;触发它们的堆遍历和它们填充的 Schedule 记录见 Create Routing Schedule § The Per-Hop Buffer Handoff。
三者共享同一个签名,因此可以放在同一个向量中。map<XY, IterationInfo>& 参数是逐步 destination-XY scoreboard;$_1 和 $_2 会忽略它(它们作用于捕获状态),该参数存在只是为了让 deferred-callback 向量类型一致。
3.1 $_4 — 延迟原语
function defer_at_step(extra_actions, index, cb): // 0x13825b60
// extra_actions = vector<optional<vector<function<Status(map<XY,IterationInfo>&)>>>>
// outer element stride 0x20: vector{ptr@0,size@8,cap@0x10} + optional has_value byte @0x18
if index < extra_actions.size:
RET_CHECK extra_actions[index].has_value() // str @0xa171d66 (line 0x691)
extra_actions[index].value.emplace_back(cb) // 32-byte ymm payload move @0x13825bba
else:
grow extra_actions to index+1, engaging empty optional<vector<function>> slots
// vmovups xmm0 + movq 0,+0x10 + movb 1,+0x18 @0x13825c10..0x13825c27
```text
`$_4` 是延迟原语:把回调 `cb` 追加到 `extra_actions[index]` 中,如果 `index` 超出末尾,则增长外层向量并启用空的 `optional<vector<function>>`。当模拟推进到第 *k* 步时,会按顺序耗尽 `extra_actions[k]` 的回调。元素类型由 `__throw_length_error` 符号固定:`vector<optional<vector<function<Status(map<XY,IterationInfo>&)>>>>` @ `0x13825f30` 和内层 `vector<function<...>>` @ `0x13825f35`。两个 `$_4` call site(`0x13820ae8` 用于 `$_1`,`0x13820fd1` 用于 `$_2`)是它的**仅有**调用者。
### 3.2 `$_1` — buffer-release / in-flight 跟踪
当某个 hop 的 DMA 落入 `kAlloc` scratch buffer 时,`$_1`(延迟闭包的 `__call_func`,@ `0x13826dc0`)会把该 buffer 标记为可用并记录 in-flight DMA。它的捕获是一个扁平的 `0x28` 字节 POD(在 `0x13820aa1` 以 `new $0x28` 构建;`__large_clone` @ `0x13827700`,`__large_destroy` @ `0x13827740`;relro 重定位 @ `0x21924d58`):
```c
function buffer_release(capture): // 0x13826dc0
// capture (0x28 POD): {Allocator-set-ptr@0, XY-key@8, deque-ctx@0x18, int available_at@0x20}
entry = FlatHashMap<XY, Allocator>.find_or_prepare_insert(set, &capture.XY) // 0x13826de1
available_at = capture.available_at + 1 // inc @0x13826e14
RET_CHECK available.empty() || available.back().second <= available_at // sorted by step; str @0x8509fa3 (line 0x185)
RET_CHECK ptr.type == PointerType::kAlloc // str @0x873065f (line 0x186)
RET_CHECK ptr.index.has_value() // str @0xa16fa09 (line 0x187)
RET_CHECK *ptr.index < size // str @0x8672033 (line 0x18c)
RET_CHECK c_none_of(available, e -> e.first == *ptr.index) // NO double-release; str @0xa0f3a0c (line 0x18b)
available.push_back((*ptr.index, available_at))
latest_dma_out.push_back(*ptr.index | (available_at << 32)) // deque<pair<int,int>>; __add_back_capacity @0x13826f4d两个不变量构成 buffer handoff:可用性(buffer index 进入一个按 release step 排序、以 destination-XY 为键的列表;下一个 hop 只有在它出现之后才读取它,结合流水线因子,这就是下一个 hop 在 kPipelineFactor 步之后运行的原因)和 in-flight 串行化((index, step) pair 被推入 latest_dma_out deque,而 LogAndValidatePaths 中检查的冲突不变量 !latest_dma_out.contains({src, block}) 会禁止同一个源 block 在有 DMA in flight 时发起第二个 DMA)。
特性 — 这里跟踪的 relay buffer 始终是
kAlloc(RET_CHECK ptr.type == PointerType::kAlloc,字符串"ptr.type == PointerType::kAlloc"@0x873065f)。collective 的真实kInput/kOutput端点从不进入 in-flight 跟踪器;只有中间 scratch hop 会进入。
3.3 $_2 — commit-placement
当某个 hop 的端点固定后,$_2(__call_func @ 0x13827760)会为在此 step 到达的每个 transfer,把 16 字节 Action 写入调度的 placement 数组。在三者中独特的是,它的捕获是一个 0x30 字节对象,并且拥有一个 absl::InlinedVector<int,1>(transfer-id 集合),在 0x13820f6f 以 new $0x30 构建;__large_clone @ 0x13827840 通过 Storage<int,1>::InitFrom @ 0x13826580 深拷贝 InlinedVector;__large_destroy @ 0x138278c0;relro 重定位 @ 0x21924d88:
function commit_placement(capture): // 0x13827760
// capture (0x30): {placement-ctx@0, InlinedVector<int,1> transfer_ids@8, Action16 payload@0x20}
list = (capture[8] & 1) ? &capture[0x10] (inline) : capture[0x10] (heap)
for t in transfer_ids:
RET_CHECK t < placement.size() // cmp @0x138277a7, ud2 @0x138277e0
RET_CHECK !placement[t].has_value() // place ONCE; str @0xa171d87/d88 (line 0x6d9)
placement[t][0..0x10) = capture.Action16 // vmovups @0x138277c0
placement[t].has_value = 1 // movb $1,0x10 @0x138277c9
// placement record stride 0x14 (5×4): {int@0,int@4,int@8,int step@0xc,byte has_value@0x10}
```text
捕获的 int-list 是此 step 提交的 transfer id 集合;`0x10` 字节 payload 是 `Action` 端点四元组(两个 `Pointer`)。已提交的 `Action` 之后会落入 Type-5 literal 序列化的每个 `{core, step, direction}` slot。
### 3.4 主循环中的闭包构造
发射器在 `CreateRoutingSchedule` 主循环中构造并延迟两个闭包:
| closure | capture alloc | capture build sites | deferred via `$_4` at |
|---|---|---|---|
| `$_1` (buffer-release) | `new $0x28` @ `0x13820aa1` | payload @ `0x13820ab8`, ctx `r12=0x20(*(-0x30))` @ `0x13820ac5`, `int -0xa8` step @ `0x13820ad0` | `0x13820ae8` (then free @ `0x13820af5`) |
| `$_2` (commit-placement) | `new $0x30` @ `0x13820f6f` | IV head @ `0x13820f90`, IV body @ `0x13820f9a`, `Action16` @ `0x13820fb0` | `0x13820fd1` (cleanup @ `0x13820fe0`) |
二者都是堆分配(`"large"`)的 `std::function` policy。两个位置都把 defer step 读作 `-0xa8(rbp)`;`$_1` 内部会加 `+1`。这些数值与弹出的 step 和 `kPipelineFactor` 的精确算术关系只读到了立即数级别,即 `available_at = step+3` 是否精确成立,还是 `step+1` 加上只在 `LogAndValidatePaths` 中强制的 `+3`,没有隔离到单个常量。对精确 defer-step 计算的置信度:HIGH。
| callback | VMA | role | capture (bytes) | key CHECK (str / line) |
|---|---|---|---|---|
| `$_4` | `0x13825b60` | 把 cb 延迟到 step *k* | (operator args) | `extra_actions[index].has_value()` `0xa171d66` / `0x691` |
| `$_1` | `0x13826dc0` | buffer-release / in-flight | `0x28` 扁平 POD | `available.back().second<=available_at` `0x8509fa3`/`0x185`;`kAlloc` `0x873065f`/`0x186` |
| `$_2` | `0x13827760` | commit-placement | `0x30`(拥有 `IV<int,1>`) | `!placement[transfer].has_value()` `0xa171d87`/`0x6d9` |
---
## 4. 发射驱动器 — `EmitRoutingCode`
`net_router::EmitRoutingCode` @ `0x13819ca0` 是流水线的终端阶段:它把调度转换为实际的 `Llo` IR,即 core 在运行时重放的逐步 ICI DMA 程序。它的 mangled signature(已在 `*_functions.json` 中确认)接受一个 `LloRegionBuilder`、一个 `MemUnit`、一个 `absl::Span<Transfer const>`、一个可选 `MemorySpace` span、一个 `ProgramSharedRegistry*`、一个地址/barrier 回调的 `variant`(其中包括 `function<LloMemoryAddress(PointerType, PointerType, LloValue*)>` resolver)、一个可选 `BarrierConfig`,以及一个 `LogRecorder*`。
### 4.1 驱动器结构
```c
function EmitRoutingCode(builder, …, transfers, …, callbacks, …): // 0x13819ca0
schedule = CreateRoutingSchedule(topology, transfers) // 0x13820… direct call @ line 427
// allocate the per-step sync flags
sflag0 = builder.AllocateScopedSflag(0, 0) // @0x… (line 662)
sflag4 = builder.AllocateScopedSflags(4, 0, 0) // 4-wide (line 663)
sflag8 = builder.AllocateScopedSflags(8, 0, 0) // 8-wide (line 664)
for each core this program emits for:
base = GetLimitedIciRoutingTableIndex(core, …, "net-router", …) // net_util:: (line 1056)
for each step:
RoutingTableStartDma(emitter, …) // issue the step's DMA (line 1105)
RoutingTableWaitForDmaInFlight(emitter, …) // wait on the pipeline window (line 1106)
e0 = GetRoutingTableElement(emitter, …, base, 0) // N column
e1 = GetRoutingTableElement(emitter, …, base, 1) // W column
e2 = GetRoutingTableElement(emitter, …, base, 2) // S column
e3 = GetRoutingTableElement(emitter, …, base, 3) // E column (lines 1212–1215)
RoutingTableStartPrefetchIfNeeded(emitter, …, base) // prefetch next (line 1108/1222)字节锚点:直接 CreateRoutingSchedule(&v343, …) 调用 @ decompile line 427(这是 AllReduce 也会使用的运行时非 literal 路径);AllocateScopedSflag(0,0) @ line 662,以及 AllocateScopedSflags(4,…) / AllocateScopedSflags(8,…) @ lines 663–664;带 "net-router" / "net-router-send" 标签的 GetLimitedIciRoutingTableIndex @ lines 1056 / 1391;RoutingCodeEmitter::RoutingTableStartDma / RoutingTableWaitForDmaInFlight / RoutingTableStartPrefetchIfNeeded 三联调用 @ lines 1105–1108 和 1204–1222;四个 GetRoutingTableElement(…, 0/1/2/3) 读取 @ lines 1212–1215。
4.2 4 方向读取
每个 step 读取四个 GetRoutingTableElement 列,即每个 ICI compass port {N=0, W=1, S=2, E=3} 一个(Direction enum 见 create-routing-schedule § Direction)。四列是 $_2 为该 (core, step) cell 提交的四个 Action slot;每个非零元素表示要在该 port 上发起的 DMA,每个零元素表示“此 core/step/port 上没有 DMA”。对于 literal 路径,GetRoutingTableElement 解码 packed s32 元素(SerializeAction 布局见 create-routing-schedule § Schedule literal 和 route-table-generation);对于直接路径,会在内存中读取调度的 placement。
4.3 sync-flag 流水线窗口
AllocateScopedSflag / AllocateScopedSflags(4) / AllocateScopedSflags(8) 分配是运行时 barrier,用于强制 $_1 / latest_dma_out 在编译期建模的 kPipelineFactor=3 窗口。RoutingTableWaitForDmaInFlight 会阻塞到某一步的 in-flight DMA 已经退役到足以读取 scratch buffer;这是 available 列表和 !latest_dma_out.contains(...) 不变量的运行时实现。RoutingTableStartPrefetchIfNeeded 将下一步的源 fetch 与当前 DMA 重叠,以维持 3 阶段流水线深度。从编译期 placement / latest_dma_out 输出到确切运行时 sflag 索引的映射未在此逐字节追踪(sflag 分配已确认;其逐步索引分配为 LOW)。
注意 — 调度 literal 只回答某个 step、某个 port 上 core 在哪些 buffer pointer 之间执行 DMA;它不说明字节经过哪些物理 chip。该多跳链路路径来自具备韧性的逐链路路由表,在 descriptor 携带 destination chip id 时由片上路由引擎解析。见 Unicast Route Emission、Intra-Chip Descriptor 和 overview § 1.1。
5. 流水线概览
| stage | function / site (VMA) | output |
|---|---|---|
CP Transfer set | CreateCollectivePermuteTransfers @ 0x13470fe0 | vector<Transfer> (source_target_pairs) |
A2A Transfer set | CreateAllToAllTransfers @ 0x10f05580 | vector<Transfer>(bidir replica pairs) |
AG Transfer set | CreateAllGatherTransfers @ 0x1380ea20 | vector<Transfer>(i→j broadcast) |
| build schedule (solver) | CreateRoutingSchedule @ 0x1381c6a0 | Schedule{Step[]·{XY→Action[4]}} |
| ↳ defer cb to step | $_4 @ 0x13825b60 | extra_actions[step] += function |
| ↳ buffer-release / in-flight | $_1 @ 0x13826dc0 | available 列表 + latest_dma_out deque |
| ↳ commit placement | $_2 @ 0x13827760 | placement[transfer] Action + has_value |
| validate (pipeline factor 3) | LogAndValidatePaths @ 0x13823dc0 | Schedule metrics |
| Type-5 route literal | CreateRoutingScheduleLiteral @ 0x13822400 | s32[X·Y·steps·4+4] |
| A2A schedule table | CreateAllToAllRoutingScheduleTable @ 0x10f061c0 | literal(RET_CHECK device_assignment) |
| AG constants (explicit gate) | AllGatherEmitter::GenerateConstants @ 0x13801be0 | literal 或 ND-ring table |
| runtime replay | EmitRoutingCode @ 0x13819ca0 | 逐步 ICI DMA 程序 |
交叉引用
- 路由概览 — route-table-vs-route-schedule 划分,以及该发射器所在的端到端流水线
- Create Routing Schedule — hop-assignment 求解器:堆遍历、
SchedulingQueueKey比较器、Schedule记录、PointerTypeenum,以及 CP 的CreateCollectivePermuteTransfers索引约定,也就是本页构建的Transfer列表的消费者 - Unicast Route Emission —
Direction[] → PerLinksRoutingTable行,以及解析 descriptor 多跳链路路径的 routing-table 索引 - Route-Table Generation — Type-5 route literal 列 → ICI-port 映射,以及具备韧性的表生成
- Intra-Chip Descriptor — 每步 DMA descriptor,其 routing 字段由调度的
Action端点提供 - 返回索引