跳到主要内容

ISSUE-026: cmd_queue 4-bank 按 cmd_id % 4 分流导致 Ring AR dep 链跨 bank 死锁

发现日期:2026-05-15 状态已修复 (2026-05-15) 类型项目 spec 误读原始 spec + 实现 bug (实施跟随误读) 影响范围:所有 Ring/HD/RD/RH 类 collective 算法 (cmd 间存在强 cmd_id_dep 链). 影响表现:

  • 仿真层 部分 chip 的 cmd 全部死锁 (sendq Ready 但无 cmd 仲裁出去),
  • kernel 事件队列耗尽时仿真"提前结束",返回偏低的 sim_time,无 panic 无报错,
  • 历史微测试 test_2port_allreduce_faster_than_1port / test_2port_latency_varies_with_n#[ignore] 状态下被掩盖,
  • 大规模生产 (64 chip + multi-channel C=8) 时表现为 Math 对比"数值上对得上但 G5 wire 量明显偏低",因为部分 chip cmd 死锁未跑。

问题现象

直接现象 (微基准 reproducer)

test_2port_allreduce_faster_than_1port (multi_chip/tests.rs:268,当前 #[ignore]):

N=4 chips Ring AllReduce, 1MB, ports=2
expand-time cmd 数 per chip: [(0, 22), (1, 22), (2, 22), (3, 22)] ← expand 对称
load_commands per chip: chip 0..3 各 22 cmd 全部 load 进 unit (0 dropped)

实测 cmd_count (arbitrate 后):
chip 0: unit0=3, unit1=2
chip 1: 无 stats ← 完全死锁
chip 2: unit0=2, unit1=8
chip 3: unit0=5, unit1=5

[DBG] 显示 chip 1 unit 0 txt1[Ready s=12 r=5 sync=0 pend=0] — thread 1 状态 Ready, sendq 12 cmd 待执行,但 22 个 cmd 全部 0 个仲裁出去.

[DIAG] chip=1 lg=0..3 RC_TX: arb_calls=0 — chip 1 PAXI/RC Link 完全没收到 frame.

仿真层后果

  • sim_time 仍返回数值 (1832 ns / 2748 ns),因为 kernel 在 chip 0/2/3 推进的事件耗尽后认为仿真结束,
  • 没有 panic 没有 warning — 死锁是隐性的,
  • 1-port vs 2-port 数值看似"接近"实则两者都部分死锁,加速比无意义。

调查过程

  • [查 spec] docs/specs/G5仿真建模/G5-CDMA建模设计规格.md §指令队列深度 (L412-429): spec 列出 "send_cmd_queue 64 × 4-bank" 物理结构,注释 "4-bank 设计允许同 bank 内的指令独立调度,不同 bank 可并发推进". 未规定 cmd 到 bank 的分配规则.
  • [查 spec] 原始 2262 CDMA spec v2.2.md L466-L491:列出 cmd_queue / send_cmd_queue / rcv_cmd_queue 物理深度 + bank 数,未规定 cmd 到 bank 的分配规则. WRR 仲裁 (L489) 只描述线程级仲裁,不涉及 bank 级。
  • [查代码] tier6/cdma/thread.rs:10-11:
    /// cmd 按 cmd_id % BANK_COUNT 分流到 bank, 仲裁时跨 bank RR 推进允许并发调度.
    pub(super) const CMD_QUEUE_BANK_COUNT: usize = 4;
    G5 实施层选 cmd_id % 4 作为 bank 分配规则。这是 P3-T3 任务的实施决策,spec 未强制。
  • [查代码] tier6/cdma/unit.rs:247:
    let bank = (cmd.cmd_id as usize) % CMD_QUEUE_BANK_COUNT;
    load_commands 按 cmd_id mod 4 分流。
  • [查代码] tier6/cdma/thread.rs:118-127 peek_sendq:
    pub(super) fn peek_sendq(&self) -> Option<&CDMACommand> {
    let start = self.sendq_bank_rr;
    for offset in 0..CMD_QUEUE_BANK_COUNT {
    let bank = (start + offset) % CMD_QUEUE_BANK_COUNT;
    if let Some(cmd) = self.sendq_banks[bank].front() {
    return Some(cmd); // 返回第一个非空 bank 的头, 不检查 dep
    }
    }
    None
    }
    peek 找"第一个非空 bank 的头",不跨 bank 找"first executable".
  • [查代码] tier6/cdma/unit.rs:200-205 sendq_dep_satisfied:
    pub(super) fn sendq_dep_satisfied(&self, thread: &CDMAThread) -> bool {
    match thread.peek_sendq() {
    Some(cmd) => self.cmd_dep_satisfied(thread, cmd),
    None => false,
    }
    }
    dep 检查只看 peek 返回的那一条 cmd. 如果该 cmd dep 不满足,整个 thread 被 select_ready_sendq_thread 跳过。
  • [假设 1] 4-bank 分流 + 强 dep 链导致死锁 — 验证:
    • chip 1 cmd[0] (cmd_id=2, Transfer, dep=NO_DEP) → bank 2
    • chip 1 cmd[3] (cmd_id=12, Transfer, dep=11) → bank 0 ← peek 起点
    • peek_sendq 起点 bank 0,头部 cmd_id=12 dep=11, thread.sync_id=0 → 不满足 → thread 跳过
    • bank 2 的 cmd_id=2 (NO_DEP,可立即执行) 永远不会被 peek 看到,永久死锁
    • 已验证为根因
  • [假设 2] RR pointer 没正确推进 — 排除:pointer 起点 0,但 peek 内部循环已扫 4 bank,推进无关
  • [假设 3] sync_id 没正确更新 — 排除:sync_id 永远 0 是 结果 不是原因 (cmd 没仲裁出去自然 sync_id 不推进,这是循环锁死)
  • [假设 4] thread state 没切 Ready — 排除:G5_DEBUG=1 输出显示 txt1[Ready ...]
  • [假设 5] CdmaWake 没到 chip 1 — 排除:临时 [CDBG] log 显示 chip 1 CdmaWake 被触发多次,每次 try_arbitrate 返回 0 refs

为什么 273 测试都通过

  • collective 单元测试只验证 cmd 数量 / 结构 (test_2port_cmd_count_4chip 等),不验证仿真 sim_time 是否合理
  • multi_chip 集成测试 (incast / clos 等) 用大消息 + Switch, kernel 事件队列在死锁前已经推进很多,sim_time 返回数值,测试只断言 sim_time > 0
  • chip 0 因为 cmd_id 从 0 起步,bank 0 头部恰好是 NO_DEP 的 cmd,单 chip 测试不暴露
  • 历史微测试 test_2port_allreduce_faster_than_1port 在加 #[ignore] 时附注 "thread_id 修复后暴露",说明开发者早知道问题但未深究

为什么生产看上去没问题

  • 64 chip + multi-channel C=8 场景下,replicate_channels 把数据拆 8 份,多 channel 并行让 sim_time 看似合理
  • 实际上每个 channel 内仍可能部分 chip 死锁,只是 chip 0/2/3 等 "幸运 chip" 跑完后 sim_time 已经接近 Math 理论值
  • Math 对比"通过" 是数值上凑巧,不是仿真行为正确

根因分析

两层根因

根因 1:项目 spec 误读原始 SG2262 spec 的 SRAM bank 语义

原始 SG2262 spec L467 send_cmd_queue: depth 64 × data_width 128 × bank 4Partition Overview 微架构子模块清单 中的 SRAM 物理参数描述。业界 multi-bank SRAM FIFO 的标准设计:单逻辑 FIFO + N 个物理 SRAM bank 切片,bank 用于解决单端口 SRAM 一周期只能 1 读或 1 写的端口冲突 (W 端 + R 端同周期访问需要 ≥ 2 bank 交错). 4 bank 是更宽裕配置,对上层透明。

项目 G5-CDMA spec v1.3.3 之前的描述 "4-bank 设计允许同 bank 内的指令独立调度,不同 bank 可并发推进" 把 SRAM 物理 bank 误读为 "4 个独立可并发的逻辑指令队列". 实际上原始 spec L489 描述的 WRR 仲裁切的是 线程,不是 bank — 如果 bank 是逻辑并发单位,spec 必然要描述 bank 间仲裁规则,但 spec 完全没有此描述。

根因 2:实施跟随项目 spec 的误读

G5 P3-T3 任务实施 cmd_queue 4-bank 时,按项目 spec 的误读描述把 sendq/rcvq 拆成 4 个 VecDeque,并选择 bank = cmd_id % 4 作为分流规则。

触发条件:软件层生成的 cmd 间存在强 cmd_id_dep 链 (Ring/HD/RD/RH 都是),且 dep 链跨 4 个 bank:

  • Ring AR per chip 1 chunk 序列:MsgWait → Transfer → RemoteMsgSend → MsgWait → ...
  • 相邻 cmd 的 cmd_id 连续,mod 4 后散布到 4 个 bank
  • 每个 bank 的头部都 dep 前一个 bank 的 cmd

仲裁逻辑缺陷peek_sendq (thread.rs:118-127) 找"第一个非空 bank 的头",没有跨 bank 找"first executable" 的能力。sendq_dep_satisfied (unit.rs:200-205) 只检查 peek 返回的一条 cmd,该 cmd dep 不满足时整个 thread 被仲裁器跳过,bank 2/3 中可执行的 cmd 永远不会被发现.

死锁循环

  1. CdmaWake 触发 try_arbitrate
  2. select_ready_sendq_thread 调 sendq_dep_satisfied
  3. peek_sendq 返回 bank 0 头 (dep 不满足)
  4. thread 跳过,try_arbitrate 返回 0 refs
  5. 没有任何 cmd dispatch → 没有 PAXI submit → 没有 DataArrived → sync_id 永远 0 → dep 永远不满足
  6. → 永久死锁,kernel 事件队列耗尽后仿真静默退出

Spec / 文档依据

原始 Spec (硬件文档)

"send_cmd_queue: CDMA send cmd queue, depth 64 × data_width 128 × bank 4" — 2262 CDMA spec v2.2.md L467

"从sendq输出的仲裁方式采用WRR(基于权重的轮询仲裁)... 利用sendq的输出作为仲裁信号" — 2262 CDMA spec v2.2.md L489

关键:原始 spec 描述 4-bank 物理结构 + 线程级 WRR 仲裁,未规定 cmd → bank 的分配规则,也未规定 bank 间仲裁规则。

项目 Spec

"send_cmd_queue: 64 × 4-bank 分流后的 send 类指令 (sendq)" "4-bank 设计允许同 bank 内的指令独立调度,不同 bank 可并发推进." — docs/specs/G5仿真建模/G5-CDMA建模设计规格.md L420, L423

关键:项目 spec 引用 "4-bank 并发",但未规定分配规则。

推断

硬件层 4-bank 物理设计的合理用途

  • 服务多个并发 thread 的 cmd (但 send_cmd_queue 已经是 per-thread, bank 不应再按 thread 切)
  • 服务不同 op_type 子类 (但 sendq 已经按 op_type 分流为 sendq / rcvq)
  • 承载来自不同 QP / DMA channel 的并发流,同一 dep 链应进同一 bank 保留 FIFO 语义

参考类似硬件 (PCIe TLP queue / NoC VC / NVMe SQ) 的 4-bank 通用做法:按 flow_id / qp_id / tag 分配,让同一 flow 的有序 cmd 始终进同一 bank (保 FIFO),跨 flow 才并发。

未解决

  • 原始 spec 没说"硬件分配规则",不能 100% 确定硬件 RTL 是按什么 hash
  • 需要看 SG2262 RTL 实现的 cmd_queue 输入侧 dispatch 逻辑 (用户/硬件团队确认)
  • 项目 spec 没强制 cmd_id % 4 — 这是 P3-T3 实施时的简化决策,可独立修订

业界对比

通过 web search 确认 multi-bank SRAM FIFO 的业界标准设计:

"To implement a multi-port FIFO, the number of banks must increase, with generally W + R banks required for a FIFO with W write ports and R read ports." "Single-port SRAMs can only support either one read or one write access in the same cycle, and read and write conflicts can occur..." "Accesses can be interleaved, simultaneously utilizing one bank of single-port memory for writing and the other for reading"

Design a sync FIFO using single-port SRAMs (Chipress), Multi-banked interleaved single-port RAM FIFO (ResearchGate), Building Multiport Memories with Block RAMs (Tom Verbeure)

业界结论:

  • depth × width × bank Nmulti-bank SRAM 物理参数 标准格式
  • N 个 bank 是 物理切片,对应 N 个独立 single-port SRAM 实例,交错访问避免读写端口冲突
  • 逻辑上是单 FIFO,bank 间交错由 SRAM controller 处理,上层不感知
  • 同样语义见 PCIe TLP banking, NoC router VOQ banking 等

解决方案

备选方案

方案描述优点缺点
A. 改 bank 分配规则让同 thread / dep 链的 cmd 进同一 bank (例 bank = 0 固定,或按 qp_id 散布)保留 spec "4-bank 并发" 语义,单 thread 内 FIFO 保 dep 顺序4-bank 在 G5 微基准里几乎退化成 1-bank,失去并发收益 (但 Ring AR 不需要 cmd-level 并发,它已有 sendq/rcvq 双队列并发)
B. 修 peek/consume 逻辑跨 bank 找 first-executablepeek_sendq 扫所有 bank 头,选第一个 dep 满足的 cmd保留 cmd_id % 4 分配规则,仲裁层兜底违反"FIFO bank" 语义 (peek 不再返回 bank 头),性能上每 arbitrate 多扫 4 头
C. 取消 4-bank,单队列回滚 P3-T3,用单 VecDeque简单,与 P3 前一致失去 spec 要求的 4-bank 物理结构
D. 改软件 expand 层,让相邻 dep 链 cmd 共 bankexpand 时给 cmd_id 加 4 倍偏移 (相邻 dep cmd 用 cmd_id+4 而非 +1)不改仿真层侵入式改 expand,影响所有 collective. cmd_id 数值跳跃也违反"全局单调"语义

采用方案

方案:项目 spec 修正语义 + 实施回滚为单 FIFO.

理由:

  1. 业界证据:multi-bank SRAM FIFO 标准设计就是单逻辑 FIFO + 物理多 bank,没有"逻辑并发 bank"语义
  2. 原始 spec 一致性:SG2262 spec L484-485 描述线程级 cmd_buff, L489 WRR 切线程 — 都不涉及 bank,只有 L467 SRAM 物理参数提及 bank
  3. simulation 抽象层级:G5 不需要复现 SRAM 端口冲突缓解机制 (硬件 SRAM controller 内部处理)
  4. 保留 cmd_queue depth 64 软上限:这是 spec 显式契约,用于建模 burst 提交场景的 back-pressure

实际修复

文件改动
docs/specs/G5仿真建模/G5-CDMA建模设计规格.md v1.3.4§指令队列深度 修正:删除"4-bank 并发推进"描述,改为"bank 是 SRAM 物理切片,逻辑上是单 FIFO" + 引用业界 multi-bank SRAM FIFO 标准
tier6/cdma/thread.rssendq_banks: [VecDeque; 4] / rcvq_banks: [VecDeque; 4] → 单 sendq: VecDeque / rcvq: VecDeque;移除 CMD_QUEUE_BANK_COUNT 常量 + sendq_bank_rr / rcvq_bank_rr 字段
tier6/cdma/unit.rsload_commands 移除 bank = cmd.cmd_id % BANK_COUNT 分流,直接 push_back 到单 FIFO
tier6/cdma/tests.rstest_cmd_queue_4bank_dispatchtest_sendq_fifo_order_no_dep (单 FIFO 顺序);test_cmd_queue_same_bank_fifo_ordertest_sendq_fifo_preserves_load_order_with_gap_cmd_ids;新增 test_sendq_dep_chain_no_deadlock_across_simulated_banks (此 ISSUE regression)
multi_chip/tests.rsrun_allreduce_sim 顺手修两个测试 fixture bug: algorithm: "ring" → 根据 ports 选 "ring_2port"; num_link_groups: 1 → 4 (满足 2-port CW/CCW frame 跨 LG 并发的物理前提);解除两个 #[ignore] 注释

验证

Reproducer (修复前 fail,修复后 pass)

$env:PATH = "$env:USERPROFILE\.rustup\toolchains\stable-x86_64-pc-windows-msvc\bin;$env:PATH"
cargo test --release test_2port_allreduce_faster_than_1port test_2port_latency_varies_with_n
# 修复前: FAILED — 2-port (2748ns) > 1-port (1832ns), 1-port 1832 也是部分 chip 死锁的伪结果
# 修复后: PASS — 2-port 显著快于 1-port, 1-port 数值上升 (因为不再有 chip 死锁掩盖真实时间)

Regression Test

tier6/cdma/tests.rs::test_sendq_dep_chain_no_deadlock_across_simulated_banks:

  • 单 thread, 4 个强 dep 链 cmd (cmd[k+1].dep = cmd[k].cmd_id)
  • cmd_id 故意散布到原 4 bank (mod 4 全覆盖):2 / 11 / 12 / 13
  • 多轮 try_arbitrate 并模拟 cmd 完成回调 (sync_id 推进)
  • 断言:全部 4 cmd 仲裁出来,sendq 清空

全量回归

  • cargo test --release 全量 281 → 284 passed (281 原 - 2 旧 4-bank 测试改名为 sendq FIFO 测试 + 3 新 sendq 测试 (含 regression) + 2 解除 ignored 测试)
  • 0 failed
  • ignored 从 4 → 2 (两个 num_chunks > 1 功能占位测试保留)

遗留问题

  1. 影响范围深度未量化:此 bug 自 P3-T3 引入后,所有生产仿真结果可能都偏低。修复后建议:
    • 重跑 docs/validation/通信原语评估-TPS186/ 下所有 sweep,对比修复前后数据,
    • 评估是否需要重新生成历史 validation 结论。
  2. PAXI Bridge 多 unit 共享 LG 的并行模型 未在本 issue 调查范围内 — 即使修复 4-bank 死锁后,2-port vs 1-port 的实际加速比仍需独立验证 (可能存在第二个 bug,涉及多 unit 共享 PAXI bridge 的 LG 选择策略).
  3. 集成测试缺 sim_time 合理性断言:现有 multi_chip 集成测试只断言 sim_time > 0. 应该补 sim_time 与 Math 理论值对比 (允许 ±20% 偏差),作为长期 regression guard.