ISSUE-002: Overcredit 静默丢包导致 128MB NAK 级联
发现日期: 2026-04-15
状态: 已分析,待修复
影响: G5 仿真 128MB AllReduce 产生 161 次 Go-Back-N,sent 膨胀至预期的 2.3 倍,G5/Meas = 1.31x
问题现象
在 ISSUE-001 credit return 修复后(RMSPE 降至 44.7%),128MB 仍异常:
| Size | G5/Meas | sent | nak | no_sendable | slot_exhaust |
|---|---|---|---|---|---|
| 64MB | 0.96x | 16,384 | 0 | 26 | 13,042 |
| 128MB | 1.31x | 33,069 | 161 | 6,254 | 31,028 |
| 256MB | 1.10x | 13,084 | 0 | 63 | 8,316 |
| 512MB | 1.14x | 36,402 | 0 | 61 | 16,508 |
128MB 出现 161 次 NAK,sent = 33,069 ≈ 预期 14,336 的 2.3 倍。其他尺寸 NAK = 0。
根因分析
直接原因:pending_packets 溢出时静默丢包
rc_link_tx.rs 中 submit_packet() 逻辑:
pub fn submit_packet(...) -> bool {
if let Some(slot_idx) = self.alloc_slot(...) {
// slot 可用 → 正常分配
true
} else if self.pending_packets.len() < self.config.overcredit_limit as usize {
// slot 耗尽但 pending 有空间 → 入 pending 队列
self.pending_packets.push_back(PendingPacket { ... });
false
} else {
false // ← 静默丢包!overcredit buffer 已满
}
}
当 512 个 slot 全满 且 pending_packets(上限 512)也满时,第 1025+ 个包被静默丢弃。
丢包 → 接收方见 PSN gap → 生成 NAK → Go-Back-N 重传所有在途 slot → 更多拥塞 → 更多丢包 → 级联。
为什么 128MB 触发而其他尺寸不触发
每步每链路的包数:
| Size | 每步包数(估算) | slot(512) + pending(512) = 1024 | 是否溢出 |
|---|---|---|---|
| 64MB | ~512 | 512 ≤ 1024 | 不溢出 |
| 128MB | ~1024 | 1024 = 1024,边界触发 | 溢出 |
| 256MB | ~2048 | > 1024,但步骤节奏不同,credit 阻塞更早 | 待分析 |
128MB 恰好在临界点:每步包数刚好将 slot + pending 同时填满,触发溢出。
Spec 依据
RCLINK_AFH_SPEC_v2.4 §5.5 CBFC 流量控制
"若剩余的 Credit 不足,即表示接收方没有足够的缓冲空间,此时数据包调度程序禁止该 VC 从 lossless 队列调度数据包进行传输。"
Credit 耗尽 = 禁止发送(阻塞),不是丢包。
RCLINK_AFH_SPEC_v2.4 §5.10 TYPE1 报文发送仲裁
Slot 状态机有四个状态:
EMPTY → WAIT_GRANT → WAIT_ACK → EMPTY
↕ ↕
WAIT_CRD WAIT_GRANT(retry)
WAIT_CRD:Credit 不足时 Slot 的正确状态,保留在队列中等 credit 返还,不丢包WAIT_CRD → WAIT_GRANT:credit 返还后自动恢复
硬件接口 §7.3.1 TX_S_CRD_O
"TX_S_CRD_O[3:0]:令牌反馈信号,为高时标识有一个包缓冲区释放,上层逻辑可以发送一个报文"
上游(PAXI/CDMA)必须持有令牌才能提交包,没有令牌就等待。 硬件通过令牌机制保证上游不会超出 TYPE1_OST_N(512)。不存在 overflow 后静默丢弃的机制。
业界对比
主流协议在 buffer 满时均采用阻塞而非丢包:
| 协议 | Credit 耗尽行为 |
|---|---|
| PCIe | 发送方停止,等 UpdateFC DLLP 返还 credit |
| InfiniBand | per-VL 流量停止,等 credit 返还 |
| RoCEv2 | CBFC/PFC 反压,上游暂停 |
| RC Link | CBFC Status=0,调度程序禁止该 VC 发包 |
所有协议共同点:buffer 满 = 阻塞上游,不丢包。
解决方案
方案(采用):去掉 pending_packets,改为上游阻塞
核心原则:submit_packet() slot 满时直接返回 false,包留在 paxi_core.segment_queue,等 ACK 释放 slot 后再由 feed_rc_link() 补充。
改动清单
文件 1: perfmodel/evaluation/g5/src/tier6/rc_link_tx.rs
- 删除
pending_packets: VecDeque<PendingPacket>字段 - 删除
PendingPacket结构体 - 删除
overcredit_limit配置字段(RcLinkTxConfig) submit_packet()简化为:slot 可用返回 true,否则返回 false(不入 pending)- 删除
drain_pending()方法及其在process_ack()中的调用 - 删除
diag_slot_exhaust计数器(改为记录 slot 满时的阻塞次数diag_slot_full) - 删除
overcredit_remaining()方法
文件 2: perfmodel/evaluation/g5/src/tier6/paxi.rs
rc_link_available_capacity()改为只返回self.tx.free_slot_count()(去掉 overcredit 部分)
文件 3: configs/chips/SG2262.yaml
- 删除
overcredit_limit配置项
数据流对比
修复前:
paxi_core.segment_queue → feed_rc_link(capacity = free_slots + overcredit_remaining)
→ submit_packet → slot 或 pending_packets [满了丢包!]
修复后:
paxi_core.segment_queue → feed_rc_link(capacity = free_slots)
→ submit_packet → slot [slot 满时不取, 等 ACK]
ACK 触发链路:
ACK → on_ack() → process_ack() [释放 slot] → feed_rc_link() [从 segment_queue 补包] → try_arbitrate()
验证方法
- 修复后运行
python docs/validation/validate_g5_allreduce.py - 预期:128MB
nak=0,sent接近预期 ~14,336 - 预期:128MB G5/Meas 从 1.31x 降至接近 1.0x
- 确认其他尺寸(64MB、256MB、512MB)不受影响
遗留问题
- 小消息(1-16MB)仍有 0.16x~0.63x 误差,根因是 CHS 内存一致性模式未实现(见 plan
floating-gliding-island.md) - 修复后整体 RMSPE 目标 <20%