Skip to content

net_router 发射器流水线

本页所有地址均适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(构建 libtpu_lts_20260413_b_RC00,build-id 89edbbe81c5b328a958fe628a9f2207d)。该镜像剥离符号;反混淆后的符号名按原样引用。.text VMA 等于文件偏移(.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 命名空间中。

本页涵盖流水线中的三个阶段以及在它们之间流动的记录:

  1. 每个 collective 的 Transfer 集合构建器CreateAllToAllTransfersCreateAllGatherTransfers,以及(交叉引用的)CreateCollectivePermuteTransfers。每个构建器都会把扁平的 device/replica id 解码为 torus 坐标,把坐标映射到物理 core id,并发射一个或多个 16 字节的 {src_core, src_index, dst_core, dst_index} Transfer 记录。三者唯一的区别在于它们如何枚举 source→target 关系。
  2. 分阶段流水线回调CreateRoutingSchedule 构造并由离散事件模拟器耗尽的三个延迟 std::function 闭包($_4 defer-at-step、$_1 buffer-release、$_2 commit-placement)。本页记录发射器如何构造并延迟它们;求解器内部结构(堆、比较器、候选方向生成)见 Create Routing Schedule
  3. 发射驱动器net_router::EmitRoutingCode,它消费调度(或直接运行 CreateRoutingSchedule)并按 core、按 step 重放:分配 sync flag,读取 4 个方向列,在 kPipelineFactor=3 窗口周围发起逐步 DMA + wait + prefetch。

构建器送入的调度,即堆遍历、Schedule 记录、PointerType,见 Create Routing Schedule。逐链路自动路由表以及 Direction[] → PerLinksRoutingTable 的降低见 Unicast Route EmissionRoute-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 @ 0x1381c6a0CreateRoutingScheduleLiteral @ 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 序列化一条记录。

布局

text
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 内的每个有序位置对发射一个对称配对

c
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 @ 0x1380ea44GetCollectiveOpGroupMode @ 0x1380ea81GetParticipatingDevicesGroups @ 0x1380eabf;成员列表 (%r8)[idx] 4 字节读取 @ 0x1380efe8Transfer 写入 {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 表),它经过 CreateAllGatherTransfersAllGatherEmitter::GenerateConstants @ 0x13801be0ShouldUseExplicitRouting @ 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 形状
CollectivePermuteCreateCollectivePermuteTransfers 0x13470fe0HLO source_target_pairs每个 (pair × buffer × r/w) 一个 Transfer
AllToAllCreateAllToAllTransfers 0x10f05580replica-group 有序位置对双向 PAIRi→jj→i);src/dst_index = 组内位置
AllGatherCreateAllGatherTransfers 0x1380ea20replica-group 成员 i, ji→j 广播;src_index=0dst_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 — 延迟原语

c
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 始终是 kAllocRET_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 集合),在 0x13820f6fnew $0x30 构建;__large_clone @ 0x13827840 通过 Storage<int,1>::InitFrom @ 0x13826580 深拷贝 InlinedVector;__large_destroy @ 0x138278c0;relro 重定位 @ 0x21924d88

c
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 literalroute-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 EmissionIntra-Chip Descriptoroverview § 1.1


5. 流水线概览

stagefunction / site (VMA)output
CP Transfer setCreateCollectivePermuteTransfers @ 0x13470fe0vector<Transfer> (source_target_pairs)
A2A Transfer setCreateAllToAllTransfers @ 0x10f05580vector<Transfer>(bidir replica pairs)
AG Transfer setCreateAllGatherTransfers @ 0x1380ea20vector<Transfer>i→j broadcast)
build schedule (solver)CreateRoutingSchedule @ 0x1381c6a0Schedule{Step[]·{XY→Action[4]}}
↳ defer cb to step$_4 @ 0x13825b60extra_actions[step] += function
↳ buffer-release / in-flight$_1 @ 0x13826dc0available 列表 + latest_dma_out deque
↳ commit placement$_2 @ 0x13827760placement[transfer] Action + has_value
validate (pipeline factor 3)LogAndValidatePaths @ 0x13823dc0Schedule metrics
Type-5 route literalCreateRoutingScheduleLiteral @ 0x13822400s32[X·Y·steps·4+4]
A2A schedule tableCreateAllToAllRoutingScheduleTable @ 0x10f061c0literal(RET_CHECK device_assignment)
AG constants (explicit gate)AllGatherEmitter::GenerateConstants @ 0x13801be0literal ND-ring table
runtime replayEmitRoutingCode @ 0x13819ca0逐步 ICI DMA 程序

交叉引用

  • 路由概览 — route-table-vs-route-schedule 划分,以及该发射器所在的端到端流水线
  • Create Routing Schedule — hop-assignment 求解器:堆遍历、SchedulingQueueKey 比较器、Schedule 记录、PointerType enum,以及 CP 的 CreateCollectivePermuteTransfers 索引约定,也就是本页构建的 Transfer 列表的消费者
  • Unicast Route EmissionDirection[] → PerLinksRoutingTable 行,以及解析 descriptor 多跳链路路径的 routing-table 索引
  • Route-Table Generation — Type-5 route literal 列 → ICI-port 映射,以及具备韧性的表生成
  • Intra-Chip Descriptor — 每步 DMA descriptor,其 routing 字段由调度的 Action 端点提供
  • 返回索引