Prhub

#5801 [vllm, fsdp] fix: apply FSDP buffer updates during rollout weight sync

原始 PR 作者 chenshui223 合并时间 2026-06-09 11:25 文件变更 3 提交数 1 评论 14 代码增减 +338 / -7

执行摘要

修复 FSDP 训练中 rollout 缓冲区不同步

Fix FSDP-to-rollout weight sync for models whose registered buffers are updated during training. The existing rollout update path applies parameter updates through load_weights(...), but registered buffers from the FSDP state dict can be missed. This may cause rollout/training mismatch for models that rely on mutable buffers such as e_score_correction_bias.

对于从事 FSDP 与 vLLM 集成开发的工程师,此 PR 值得精读,特别是其通过拆分权重更新来安全同步缓冲区的设计模式。测试代码中使用的桩模块技巧也可作为在无硬件依赖下进行单元测试的参考。

讨论亮点

Review 中 gemini-code-assist 建议 split_buffer_updates 返回 named_buffers 字典以避免重复遍历模型缓冲区,作者采纳。Copilot 指出在 FP8 分支中缓冲区更新未同步到 MTP drafter 模型,最终实现通过 _apply_buffer_updates_all_models 循环所有模型解决。Copilot 建议测试应恢复 sys.modules 的修改,最终代码使用 try/finally 保证恢复。关于 non_blocking 参数的建议未采纳。

实现拆解

  1. 新增 verl/workers/rollout/vllm_rollout/weight_update_utils.py,提供 split_buffer_updatesapply_buffer_updates 函数。
  2. verl/workers/rollout/vllm_rollout/utils.py 中导入上述函数,在 _update_weights 中拆分权重,参数继续通过 load_weights 加载,缓冲区通过 apply_buffer_updates 更新。新增 _apply_buffer_updates_all_models 确保主模型和 MTP drafter 同步。
  3. 调整 FP8 和标准分支的权重加载逻辑,仅传递 param_updates
  4. 新增 CPU 测试文件,通过桩模块模拟 vLLM 依赖,验证缓冲区路由和集成行为。
文件 模块 状态 重要度
verl/workers/rollout/vllm_rollout/weight_update_utils.py 采样 added 7.97
verl/workers/rollout/vllm_rollout/utils.py 采样 modified 7.01
tests/workers/rollout/test_vllm_weight_update_utils_on_cpu.py 采样 added 7.47

关键符号

split_buffer_updates apply_buffer_updates _apply_buffer_updates_all_models

关键源码片段

verl/workers/rollout/vllm_rollout/utils.py core-logic

修改了权重同步的核心路径,整合缓冲区更新。

def _apply_buffer_updates_all_models(self, buffer_updates, main_named_buffers):
    """将缓冲区更新应用到主模型和同步的 MTP drafter 模型。    The main model (yielded first) reuses the prebuilt ``main_named_buffers`` map;
    the drafter builds its own from its own model.  Returns the number of buffers
    applied to the main model.
    """
    models = list(self._iter_all_models())
    loaded = apply_buffer_updates(models[0], buffer_updates, named_buffers=main_named_buffers)
    for model in models[1:]:
        apply_buffer_updates(model, buffer_updates)
    return loaded# 在 _update_weights 中(摘录核心分支):
else:
    param_updates, buffer_updates, named_buffers = split_buffer_updates(
        self.model_runner.model, weights
    )
    # 处理参数更新(FP8 / 标准)...
    # 应用缓冲区更新到所有模型
    loaded_buffers = self._apply_buffer_updates_all_models(buffer_updates, named_buffers)

评论区精华

性能:重复遍历模型缓冲区 性能

Both split_buffer_updates and apply_buffer_updates iterate over all model buffers... This is inefficient.

结论:作者通过 split_buffer_updates 返回 named_buffers,apply_buffer_updates 接受可选的 named_buffers 参数,避免了重复遍历。 · 已解决

FP8 分支中 drafter 模型缓冲区未同步 正确性

In the FP8 branch, drafter sync is applied for parameter updates, but buffer updates are only applied to self.model_runner.model. ... the draft model can become inconsistent.

结论:最终实现中 _apply_buffer_updates_all_models 循环所有模型,包括 drafter,因此已修复。 · 已解决

测试中 sys.modules 泄露 测试

mutates sys.modules ... never restores the prior state. That can leak into later tests.

结论:最终代码使用 try/finally 恢复 sys.modules 的原始状态。 · 已解决

使用 non_blocking=True 提高性能 性能

Hard-coding non_blocking=False prevents non-blocking host→device copies ... can reduce throughput.

结论:作者未采纳,可能出于安全考虑或因为当前非阻塞不需要。 · not resolved

风险与影响

核心路径变更:_update_weights 是 rollout 权重同步的关键函数,拆分逻辑可能引入回归。测试依赖桩模块:测试使用桩模块模拟 vLLM 依赖,可能不完全反映真实行为。兼容性:若模型缓冲区未通过 register_buffer 注册,将不会被同步。性能:缓冲区数量通常较少,性能影响可忽略。

用户影响:修复了依赖可变缓冲区(如 e_score_correction_bias)的模型在 FSDP 训练中的不一致。对于无此类缓冲区的模型无影响。系统影响:新增一个工具模块,仅 rollout 路径使用。团队影响:为 FSDP 训练提供了正确的缓冲区同步机制。

核心路径变更 测试依赖桩模块 FP8 分支兼容性 MTP drafter 同步

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论