Prhub

#6813 [ckpt] fix: use separate magic_recv buffer to prevent weight corruption

原始 PR 作者 KunWuLuan 合并时间 2026-07-07 17:28 文件变更 1 提交数 2 评论 2 代码增减 +51 / -26

执行摘要

修复 Mooncake 权重同步中 magic 信号覆盖数据 buffer 导致权重损坏

Mooncake daisy-chain 权重同步中,接收方在 RDMA 读取数据后,向发送方的数据 buffer 写入 4 字节 magic 完成信号 (0xAB,0xDC,0xEF,0x88)。在多 rank 同节点场景下,该写入可能因 intra-node RDMA 副作用而覆盖数据 buffer 的前 4 字节,导致 embed_tokens.weight[0:2] 被异常值 (-3.85e+17) 损坏,进而所有 attention 计算饱和,推理输出退化为 '!!!!' 重复至 max_response_length。

该 PR 值得精读,尤其对于使用 Mooncake RDMA 多节点训练的团队。修复思路清晰(隔离 side-effect),且第二个 commit 补充的 device synchronize 是对竞态条件的必要防御,显示了作者对 GPU 异步执行模型的理解深度。

讨论亮点

Gemini Code Assist 提出了一个关键竞态条件死锁风险:

  • wait_for_complete 中,buf[:4] = 0 是 GPU 异步操作,函数立即返回后,接收方发送包含 magic slot 指针的 metadata 给下一 rank。
  • 下一 rank 的 RDMA 写入可能在 GPU 清零完成之前到达,使 magic 值被覆盖为 0,导致永久死锁。
  • 结论:需要在 buf[:4] = 0 后添加 get_torch_device().synchronize() 以等待清零完成。该修复已在第二个 commit 中实现。

实现拆解

  1. 新增独立 magic_recv buffer — 在 __init__ 中创建 8 字节 torch.zeros 张量并注册到 batch_register_memory,同时调整 try-import 以兼容 vLLM 和 SGLang 的 StatelessProcessGroup
  2. 修改等待逻辑wait_for_complete 改为检测 magic_slots[idx](magic_recv 的前 4 或后 4 字节)而非数据 buffer,并在检测到 magic 后先同步设备再清零,防止异步清零与下一轮 RDMA 写入的竞态。
  3. 修改发送路径send_weights 为每个 double-buffer 槽位分配 magic_slot(两个 4 字节子切片),在 info 字典中传递 magic_ptr 并在等待完成时检查对应 slot。
  4. 修改接收路径receive_weights 从 info 中提取 magic_ptr,RDMA 读取数据后用 transfer_sync_write 将 magic 写入 magic_ptr 而非数据指针;向后兼容:若 magic_ptr 不存在(旧版)则回退到写入数据 buffer。
文件 模块 状态 重要度
verl/checkpoint_engine/mooncake_checkpoint_engine.py 检查点引擎 modified 7.74

关键符号

MooncakeCheckpointEngine.__init__ MooncakeCheckpointEngine.wait_for_complete MooncakeCheckpointEngine.send_weights MooncakeCheckpointEngine.receive_weights

关键源码片段

verl/checkpoint_engine/mooncake_checkpoint_engine.py core-logic

唯一变更文件:引入 magic_recv buffer 隔离 magic 信号与数据路径,修复权重损坏 bug;包含导入调整。

# 源自 mooncake_checkpoint_engine.py,仅展示核心变更部分的伪代码重构
# 完整的 __init__、send_weights、receive_weights 和 wait_for_complete# __init__ 中新增 magic_recv buffer
self.buf = torch.empty(2 * self.bucket_size, dtype=torch.uint8, device=self.device)
self.magic_buf = torch.empty(4 * 1024, dtype=torch.uint8, device=self.device)
# 分离的 magic 接收 buffer:8 字节 => 每个 double-buffer 槽位 4 字节
self.magic_recv = torch.zeros(8, dtype=torch.uint8, device=self.device)
ret = self.engine.batch_register_memory(
    [self.buf.data_ptr(), self.magic_buf.data_ptr(), self.magic_recv.data_ptr()],
    [2 * self.bucket_size, 4 * 1024, 8],
)# send_weights 中为每个槽分配 magic_slot
magic_slots = [self.magic_recv[:4], self.magic_recv[4:]]
idx = 0
current = bufs[idx]
info = {
    "bucket_meta": bucket_meta,
    "ptr": current.data_ptr(),
    "magic_ptr": magic_slots[idx].data_ptr(), # 新增字段,指向专属 slot
    "len": offset,
    "is_last": False,
}# 等待完成时检测 magic_slot 而非数据 buffer
await self.wait_for_complete(magic_slots[idx])# wait_for_complete:检测 magic 后先同步再清零,防止死锁
async def wait_for_complete(self, buf: torch.Tensor):
    magic = torch.tensor([0xAB, 0xDC, 0xEF, 0x88], dtype=torch.uint8, device=self.device)
    while True:
        if torch.equal(buf[:4], magic):
            buf[:4] = 0 # 重置为 0 以供复用
            get_torch_device().synchronize() # 确保重置完成,防止下一轮 RDMA 写入覆盖
            break
        await asyncio.sleep(0)# receive_weights 中,使用 info.get("magic_ptr", ptr) 向后兼容
magic_ptr = info.get("magic_ptr", ptr) # 旧版对端不发送 magic_ptr 时回退到 ptr
# ... RDMA 读取数据到本地 buffer ...
self.engine.transfer_sync_write(magic_ptr, magic, 4) # 写入专属 slot,而非数据 buffer

评论区精华

wait_for_complete 中 GPU 异步清零导致死锁 正确性

Gemini Code Assist 指出,`buf[:4] = 0` 是 GPU 异步操作,函数立即返回后,下一 rank 的 RDMA 写入可能在清零完成前到达,覆盖 magic 值为 0,导致永久死锁。

结论:在 `buf[:4] = 0` 后添加 `get_torch_device().synchronize()` 等待清零完成。该修复已在第二次提交中实现。 · 已解决

风险与影响

  1. 竞态条件死锁 — 修复前 buf[:4] = 0 是异步 GPU op,重启后 magic slot 可能被下一轮 RDMA 写入覆盖为 0,导致 wait_for_complete 永远等不到 magic 值而死锁;已在 commit 2 通过 get_torch_device().synchronize() 修复。
  2. 兼容性风险 — 新增 magic_ptr 字段,旧版通信对端不发送该字段时,接收方通过 info.get("magic_ptr", ptr) 回退,风险较低。
  3. 仅修改 Mooncake 引擎 — 影响范围局限,但不支持 CI 测试(需要多 GPU Mooncake RDMA 环境)。

用户:修复了 Mooncake daisy-chain 权重同步中严重的权重损坏 bug,恢复 token embedding 正确性,保障 checkpoints 可正确保存与加载。
系统:增加 8 字节额外 GPU 内存及一次 GPU 同步,性能影响可忽略。
团队:无 API 变更,配置兼容,可直接合入。

核心路径变更 缺少测试覆盖(需特殊硬件) 竞态条件死锁(已修复)

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论