ISSUE-001: CBFC Credit Return 建模错误导致大消息带宽崩塌
发现日期: 2026-04-15
状态: 已修复,已验证
影响: G5 仿真大消息场景(>64MB)RMSPE 劣化至 132%~659%,小消息不受影响
问题现象
G5 仿真 H200 8-chip Ring AllReduce,对比实测数据(H200 NVLink4):
| Size | G5/Meas | credit_block 次数 | slot_exhaust 次数 |
|---|---|---|---|
| 1 MB | 0.16x | 0 | 0 |
| 4 MB | 0.32x | 0 | 0 |
| 16 MB | 0.63x | 0 | 0 |
| 64 MB | 0.96x | 0 | 13,042 |
| 128 MB | 1.32x | 7,818 | 31,028 |
| 256 MB | 6.59x | 57,569 | 65,024 |
| 512 MB | 4.24x | 77,354 | 130,560 |
64MB 精度最佳(0.96x),之后急剧恶化,credit_block 从 0 跳至数万次。
小消息误差来自其他原因(CHS 模式尚未实现),大消息劣化是独立问题。
根因分析
直接原因:CreditReturn 与数据包共享 busy_until_ns
event_handlers.rs 中 CreditReturn 事件通过 c2c_net.transmit() 发送,
与数据包使用同一个 busy_until_ns 串行化队列:
// event_handlers.rs ~line 281
Event::CreditReturn { .. } => {
let arrive = c2c_net.transmit(&rx_name, &next_node, ack_wire_bytes, t)
.map(|r| r.arrive_ns as u64)
.unwrap_or(t as u64);
kernel.schedule_at(arrive, evt);
}
c2c_network.rs 中链路模型:
fn transmit(&mut self, wire_bytes: u64, now_ns: f64) -> f64 {
let contention_wait = (self.busy_until_ns - now_ns).max(0.0);
let start_ns = now_ns + contention_wait;
self.busy_until_ns = start_ns + serialization_ns; // 数据包和 credit 共享
return contention_wait + serialization_ns + self.base_latency_ns;
}
级联效应
双向 Ring AllReduce 中,chip0 → chip1 发数据,同时 chip1 → chip0 也发数据。 chip0 收到数据后需要向 chip1 返还 credit,但 chip0 → chip1 链路被正向数据包 饱和,CreditReturn 必须排在所有数据包后面等待:
450 GB/s 链路,一个 8192-byte 数据包串行时间 ≈ 18 ns
如果队列里有 N 个数据包,CreditReturn 等待 ≈ N × 18 ns
大消息时 N 可达数百,credit return 延迟数 us
-> TX 等不到 credit -> credit_block -> slot_exhaust -> 性能崩塌
为什么小消息不受影响
小消息数据量少,链路未饱和,credit return 几乎零排队,及时返还。
Spec 依据
RC Link spec (RCLINK_AFH_SPEC_v2.4) §5.5 CBFC 流量控制:
- "CREDIT 的返还由 MAC 完成"
- CBFC_CTRL 的
FREE_CRD输入来自 MAC 层(硬件信号,非数据包) - "CBFC 的反压与 PFC 共用接口" — 通过
TX_PFC_REQ_O[7:0]端口
PAXI UserGuide (V2R0P5) §2.2:
- "MAC's CBFC/PFC supports 8 Virtual Channels"
- Credit 信息走 MAC 层 PFC 接口,不是 RC Link 数据通道上的包
结论:Credit return 不走任何 VC,是 MAC 层控制信号。
业界对比
主流 C2C/互联协议均不让 credit return 排在数据队列后面:
| 协议 | Credit Return 机制 |
|---|---|
| PCIe | UpdateFC DLLP(8 bytes),独立高优先队列,可在任意帧边界插入 |
| CXL | 嵌入数据 FLIT 头部 2-byte 字段,随数据帧发出 |
| UCIe | 嵌入 FLIT 帧头 1-bit 信号,每帧边界携带 |
| InfiniBand | per-VL 独立管理,不同 VL 互不阻塞 |
所有协议共同点:credit return 不等数据包队列排空,最多等当前帧结束后插入。
解决方案
方案选择
方案 A(采用):CreditReturn 只经历链路传播延迟(base_latency_ns),跳过 busy_until_ns 串行排队。
理由:
- 对应 PCIe/PFC 的帧间插入模型
- Credit 帧极小(64 bytes),在 450 GB/s 下串行时间 0.14 ns,可忽略
- MAC 层处理 credit,不受数据通道排队影响
方案 B(未采用):给 credit return 独立的 busy_until_ns,不与数据竞争。
更精确但实现复杂,在当前精度目标下不必要。
方案 C(未采用):credit return 完全无延迟。
过于简化,忽略了链路传播延迟。
代码修改
文件: perfmodel/evaluation/g5/src/top/event_handlers.rs
修改 CreditReturn 事件处理,绕过 c2c_net.transmit(),直接加传播延迟:
Event::CreditReturn { chip_id: tx_chip, from_chip: rx_chip, count, vc_id } => {
// Credit return 走 MAC 层 PFC 接口,不走数据包通道
// 只需链路传播延迟,跳过 busy_until_ns 串行排队
let prop_delay_ns = c2c_net.propagation_delay(*rx_chip, *tx_chip)
.unwrap_or(0.0);
let arrive = (t + prop_delay_ns).ceil() as u64;
kernel.schedule_at(arrive, evt);
}
需要在 C2CNetwork 上新增 propagation_delay(from, to) 方法,返回链路的 base_latency_ns。
验证方法
- 修复后运行
python docs/validation/validate_g5_allreduce.py - 预期:256MB、512MB 场景 credit_block 次数降至接近 0
- 预期:大消息 RMSPE 从当前 >100% 降至 <30%
- 对比 CHS 模式修复后的整体 RMSPE 目标 <20%
遗留问题
- 小消息(1-16MB)仍有 0.16x~0.63x 误差,根因是 CHS 内存一致性模式未实现(见 plan
floating-gliding-island.md) - CHS + credit return fix 合并后的整体 RMSPE 待验证