首页
/ vLLM 在 RLHF 训练中的角色:权重同步、Pause/Resume 与异步强化学习实战指南

vLLM 在 RLHF 训练中的角色:权重同步、Pause/Resume 与异步强化学习实战指南

2026-09-06 09:28:21作者:农烁颖Land

在 RLHF(基于人类反馈的强化学习)等在线强化学习工作流中,推理引擎需要与训练引擎高频协作:策略模型每更新一轮,推理侧的权重就必须同步刷新,同时生成请求不能因此中断。vLLM 为此提供了可插拔的权重传输系统(Weight Transfer)、飞行中安全换权的 Pause/Resume API 以及一批官方 RLHF 示例脚本。读完本文,你将掌握如何在 vLLM 中配置权重传输后端、如何在训练循环中驱动权重同步,以及如何用异步流水线提升 GPU 利用率。

vLLM 在 RLHF 工作流中的定位

Reinforcement Learning from Human Feedback (RLHF) 是一种利用人类生成的偏好数据微调语言模型、使模型输出与期望行为对齐的技术。在 RLHF 及其他在线 RL 方法(如 GRPO)中,策略模型在训练过程中被迭代更新,而 rollout(采样生成)阶段必须由推理引擎高效完成,并且更新后的权重必须实时反映到推理引擎中,否则 rollout 使用的仍是旧策略,训练将失去意义。

vLLM 承担的就是其中的高速 rollout 角色。当前开源生态中,多个主流 RL 库都使用 vLLM 来加速 rollout(按字母序,非穷举):Cosmos-RL、ms-swift、NeMo-RL、Open Instruct、OpenRLHF、PipelineRL、Prime-RL、SkyRL、TRL、Unsloth、verl 等。vLLM 官方文档同时提供了 GRPO 的在线训练实践 notebook(例如基于 TRL 的高效在线训练、基于 Unsloth + vLLM 的 Qwen-3 4B GRPO),可作为入门参考(参见 docs/training/rlhf.md)。

要让 vLLM 真正融入 RL 训练循环,需要解决两个核心问题:

  1. 权重如何从训练进程同步到推理引擎? —— vLLM 的 Weight Transfer 系统 解决这一点,提供 NCCL(多卡分离部署)与 IPC(同卡共置部署)等可插拔后端;
  2. 生成过程中如何安全地换权? —— Async Reinforcement Learning 指南介绍的 pause_generation / resume_generation API 解决这一点,实现生成与训练的流水线化。

权重传输系统:架构与四阶段协议

vLLM 的权重传输系统位于 vllm/distributed/weight_transfer/,其核心设计是两个进程各持一个引擎、且两者对称

训练进程(Trainer process) 推理工作进程(Inference workers)
TrainerWeightTransferEngine WeightTransferEngine
构建者 WeightTransferTrainerFactory.trainer_init(...) vLLM 内部,从 WeightTransferConfig 构建
驱动方式 send_weights() 下方的四阶段协议
持有物 通信器、传输计划、wire 参数 通信器、目标模型

训练侧引擎是有状态的:它持有自己的通信器与 wire 参数,从 WeightSource 中拉取权重,并通过 VLLMWeightSyncClient 驱动推理侧。训练代码无需了解底层传输协议,每个同步轮次只需一次 send_weights() 调用。

每一轮同步在底层都走同一套四阶段协议

  1. 初始化init_weight_transfer_engine):在训练循环开始前调用一次(从 trainer_init 触发),建立训练进程与推理工作进程之间的通信通道;
  2. 开始start_weight_update):让推理引擎为一轮权重更新做好准备;
  3. 权重更新update_weights):实际传输更新后的权重,可调用一次或多次(例如分块传输);
  4. 完成finish_weight_update):收尾(例如对 checkpoint 格式的权重做后处理),所有权重传完后调用一次。

可选的传输后端

后端 传输机制 适用场景
nccl NCCL broadcast 训练与推理分布在不同 GPU 上(可跨节点)
ipc CUDA IPC handles 训练与推理共置在同一 GPU
sparse_nccl NCCL broadcast 基于 checkpoint 坐标的稀疏权重补丁
sharded_rdt NIXL / Ray Direct Transport(拉取式) 超大规模模型,每个 worker 只需自己那片分片(如专家并行的 MoE)

推理侧的配置只暴露一个后端选择器。WeightTransferConfig 的定义见 vllm/config/weight_transfer.py,其中 backend 字段为字符串选择器(默认 "nccl"),在引擎创建时对照 WeightTransferEngineFactory 注册表做校验;其余所有传输细节( rendezvous 参数、打包参数等)都由训练侧决定并在初始化握手中下发,推理侧不需要也不能与之“不一致”。

快速上手:两侧各写多少代码?

推理侧:只声明后端

from vllm import LLM
from vllm.config import WeightTransferConfig

llm = LLM(
    model="my-model",
    weight_transfer_config=WeightTransferConfig(backend="nccl"),  # 或 "ipc"、"sparse_nccl"、"sharded_rdt"
)

在线服务场景则直接用命令行:

vllm serve my-model \
    --weight-transfer-config '{"backend": "nccl"}'

训练侧:建一次引擎,每轮调一次 send_weights()

from vllm.distributed.weight_transfer import (
    ModuleSource,
    HTTPVLLMWeightSyncClient,
    WeightTransferTrainerFactory,
)
from vllm.distributed.weight_transfer.nccl_engine import NCCLTrainerInitInfo

# 在训练循环开始前,只构建一次。
engine = WeightTransferTrainerFactory.trainer_init(
    init_info=NCCLTrainerInitInfo(
        master_address=master_address,
        master_port=master_port,
        world_size=world_size,   # 训练进程 + 全部推理 worker 的总数
        rank=0,                  # 本训练进程的 rank;rank 0 是发送方
        packed=True,
    ),
    client=HTTPVLLMWeightSyncClient("http://localhost:8000"),
    source=ModuleSource(model),
)

# 每个权重同步轮次调用一次。
for step in range(num_steps):
    train_one_step(model)
    engine.send_weights()

send_weights() 会同时在推理侧驱动 start → update → finish,并完成数据面传输,包括后端所需的并发编排(例如 NCCL 后端要求 worker 的 update_weights 与训练侧的 broadcast 同时执行,因为两侧在同一个 NCCL 集合调用内汇合/rendezvous)。训练侧没有 backend= 参数:每个 TrainerInitInfo 子类自带一个 ClassVar 形式的 backend,工厂按它分发。

NCCL 后端:分离部署与打包广播

NCCL 引擎(vllm/distributed/weight_transfer/nccl_engine.py)适用于训练与推理分卡(甚至跨节点)的场景。详细文档见 docs/training/weight_transfer/nccl.md

工作原理:

  1. 训练进程与所有推理 worker 通过 StatelessProcessGroup(vLLM 提供的、不依赖 torch.distributed 全局组的抽象)加入同一个 NCCL 进程组。训练侧是 rank 0,worker 从 rank_offset(1)开始编号;
  2. 训练侧向所有 worker 同时广播权重,每个 worker 接收并加载;
  3. 可选的 packed tensor 广播:把多个小张量打包进大缓冲,配合双/三重缓冲与 CUDA stream 重叠,减少 NCCL 调用次数、提升吞吐。

关键参数在 NCCLTrainerInitInfo 上:

字段 默认值 说明
master_address rendezvous 主机地址
master_port rendezvous 端口
world_size 训练侧 + 全部 worker 的 NCCL 组大小
rank 本训练进程的 rank(关键字参数);0 是发送方
packed True 是否使用打包广播
packed_buffer_size_bytes 1 GiB 打包缓冲大小
packed_num_buffers 2 轮换缓冲数量(双/三重缓冲)

从源码结构看,这里有一条重要的设计不变量:wire 参数(如 packed故意不放在 WeightTransferConfig 或每轮的 update_weights 载荷里——训练侧在 trainer_init 时下发,worker 只读取被告知值。这样两侧 packed 标志不一致的状态是“不可表达的”,而不是“被不鼓励的”。NCCL 是少数同时读取 WeightSourcemetadata() 与迭代字节流两条通道的后端:引擎按 metadata() 构造每轮 update info 先发给 worker 用于确定接收缓冲尺寸与分块边界,字节本身来自迭代 source;发送端会逐参数比对迭代结果与声明元数据,一旦发散立即抛出异常(指明首个不一致的参数),而不是让坏数据上网络。ModuleSource 天然满足该不变量;自写 WeightSource(如 Megatron 导出、MoE 重融合)时应把它作为首要测试项。

内存方面:轮换缓冲在整个传输期间都存活,两侧各占用 packed_buffer_size_bytes * packed_num_buffers(默认配置下约 2 GiB),显存紧张时调小 packed_buffer_size_bytes

Sparse NCCL:只传变化的元素

sparse_nccl 后端(vllm/distributed/weight_transfer/sparse_nccl_engine.py)用于按 checkpoint/Hugging Face 坐标表达稀疏、扁平索引的权重补丁:每个推理 rank 收到相同的 checkpoint 全局补丁,再由模型原生 load_weights() 映射到本 rank 的 TP/EP 与打包运行时参数。它是 delta 型后端——每次调用携带的是替换补丁而非模型参数的稳定流,因此不接受 WeightSource,补丁直接传给 send_weights(patches),空补丁列表是 no-op,单次调用即完整走一遍 start / update / finish。传输量为 O(nnz)(只发索引与值),但 checkpoint 应用本身仍走原生 loader 的 O(N) staging。支持张量并行且无需与训练侧布局一致。示例见 examples/rl/rlhf_sparse_nccl.py(Qwen3 MoE 逐专家 checkpoint 更新,1 个训练 GPU + 2 个 TP2/EP2 推理 GPU)。

IPC 后端:同卡共置、零拷贝

IPC 引擎(vllm/distributed/weight_transfer/ipc_engine.py)通过 CUDA IPC 句柄让训练与推理进程在同一 GPU 上直接共享显存,避免任何数据拷贝,是共置部署下最高效的选择;多 GPU 场景(如 FSDP)也支持,每个 GPU 会先 all-gather 出完整权重,再由正确的共置进程提取。详细文档见 docs/training/weight_transfer/ipc.md

流程概述:

  1. 训练侧为每个权重创建 CUDA 张量并用 torch.multiprocessing.reductions.reduce_tensor 生成 IPC 句柄;多 rank 时(FSDP)每个 rank 先用 ModuleSource 在自己的 GPU 上物化完整张量;
  2. 所有训练 rank 的句柄经默认进程组 all-gather 合并(按 GPU UUID 映射),由发送方通过 client 下发,每个 worker 只读取自己 GPU 对应的句柄;
  3. 推理 worker 用 rebuild_cuda_tensor 从句柄重建张量,直接读训练侧显存。

训练侧代码与 NCCL 几乎同构,只是换成 IPCTrainerInitInfo

from vllm.distributed.weight_transfer.ipc_engine import IPCTrainerInitInfo

engine = WeightTransferTrainerFactory.trainer_init(
    init_info=IPCTrainerInitInfo(rank=0, packed=False),   # rank 0 是发送方
    client=HTTPVLLMWeightSyncClient("http://localhost:8000"),
    source=ModuleSource(model),
)
engine.send_weights()   # 每个同步轮次调用一次

IPCTrainerInitInfo 的字段:

字段 默认值 说明
rank 本训练进程的 rank(关键字参数);0 是发送方
packed False 启用分块、内存有界的传输
packed_buffer_size_bytes 1 GiB packed=True 时的分块大小

两个实战要点:

  • 分块传输:默认所有权重在一次 update_weights 中发完,要求两侧 GPU 同时装得下完整模型。packed=True 后权重被拼接进固定大小缓冲、按块分多次 update_weights 发送(仍在一对 start / finish 括号内,层式重载路径只初始化一次、收尾一次),每块被消费后即可释放显存:

    engine = WeightTransferTrainerFactory.trainer_init(
        init_info=IPCTrainerInitInfo(
            rank=0,
            packed=True,
            packed_buffer_size_bytes=256 * 1024 * 1024,  # 256 MB 分块
        ),
        client=client,
        source=ModuleSource(model),
    )
    

    多 rank 训练时,生产侧各块之间带跨 rank 屏障,防止某 rank 覆盖缓冲时共置 worker 仍在读取当前块,该逻辑内建在 send_weights() 中。

  • HTTP 传输警告:IPC 句柄是序列化 Python 对象,走 HTTP 时必须在服务端与客户端都设置 VLLM_ALLOW_INSECURE_SERIALIZATION=1(句柄会被 pickle 并 base64 编码传输)。

HTTP 服务端的权重同步端点

以 HTTP server 方式运行 vLLM 时,权重传输暴露以下端点(HTTPVLLMWeightSyncClient 会替你调用前四个):

端点 方法 说明
/init_weight_transfer_engine POST 用后端特定信息初始化权重传输引擎
/start_weight_update POST 开始一轮权重更新
/update_weights POST 传输一批带后端特定元数据的权重
/finish_weight_update POST 完成更新,可选择提交 weight_version
/update_weight_version POST 不改变模型权重、只更新 weight_version
/weight_info GET 查询最新已提交的权重版本
/pause POST 权重同步前暂停生成,处理在途请求
/resume POST 权重同步后恢复生成
/get_world_size GET 获取推理 worker 数量(便于计算 NCCL world size)

注意:HTTP 权重同步端点要求设置 VLLM_SERVER_DEV_MODE=1。此外,Rust 前端的可选 gRPC Control 服务为可信 sidecar 暴露同样的 pause、sleep、权重传输与权重版本生命周期;ServerInfo.rl_capabilities 响应会报告是否配置了权重传输与 sleep mode。无论走 HTTP 还是 gRPC,后端特定的 init_info / update_info 都是 JSON 元数据,张量本身始终走配置的 NCCL、IPC、sparse-NCCL 或 sharded-RDT 传输。

多 rank 训练器:FSDP / TP / PP / EP 下如何同步

对于分片式训练器(FSDP、TP/PP/EP),每一个训练 rank 都要构建引擎并调用 send_weights()

  • Rank 0 是发送方:只有它持有通信器、与 client 对话、真正把字节放上网络;
  • 非发送 rank 也必须迭代 WeightSource——因为物化一个参数本身通常就是集合通信(如 FSDP 的 full_tensor() all-gather、Megatron 导出),若某些 rank 跳过就会死锁。

每个进程在 init info 中传入自己的 rank。它是显式参数而非从全局进程组读取,因为同时存在 FSDP / TP / PP / EP 多个进程组时,全局组语义是模糊的:

engine = WeightTransferTrainerFactory.trainer_init(
    init_info=NCCLTrainerInitInfo(..., rank=torch.distributed.get_rank()),
    client=client,
    source=ModuleSource(model),
)
engine.send_weights()  # 在所有 rank 上调用

多 rank 场景的完整可运行示例:examples/rl/rlhf_nccl_fsdp_ep.py(NCCL + FSDP2 + 专家并行,每个 FSDP rank 都构建引擎并参与 full_tensor() gather,仅 rank 0 触网)与 examples/rl/rlhf_ipc_fsdp_ep.py(IPC + FSDP2 共置于 4 张 GPU,带 packed 分块与传输前后的 sleep/wake)。

异步 RL:Pause/Resume API 与典型循环

在标准 RL 循环中,生成与训练顺序执行:策略生成 rollout → 训练 → 循环。生成期间训练卡闲置,反之亦然。**一次性流水线(one-off pipelining)**方案把生成与训练拆成两个并行协程——一边用旧数据训练,一边用当前策略生成新样本,从而提升 GPU 利用率与训练吞吐。

但重叠引入了一个难题:必须在推理引擎仍在处理请求时、"飞行中"(mid-flight)更新权重。vLLM 的解法是 pause_generationresume_generation(见 docs/training/async_rl.md)。

pause_generation

await engine.pause_generation(mode="keep", clear_cache=True)

mode 决定在途请求如何处置:

Mode 行为
"abort" 立即中止所有在途请求并返回部分结果(默认)
"wait" 等待所有在途请求完成后再暂停
"keep" 将请求冻结在队列中,调用 resume_generation 时继续

clear_cache 决定暂停后是否清空 KV cache 与前缀缓存。

resume_generation

await engine.resume_generation()

暂停后恢复调度器。以 mode="keep" 冻结的请求将继续生成。

HTTP 端点

设置 VLLM_SERVER_DEV_MODE=1 后,vLLM HTTP server 通过以下方式暴露相同能力:

  • POST /pause?mode=keep — 暂停生成
  • POST /resume — 恢复生成
  • POST /abort_requests — 中止在途请求但不暂停调度器(发 {} 中止全部,或 {"request_ids": [...]} 指定)
  • GET /weight_info — 返回最新已提交的 weight_version

关于数据并行的一个重要区分:使用 vLLM 内建负载均衡器data_parallel_backend="ray")时,pause/resume 会自动在所有 DP rank 上生效,调用一次即可;使用外部负载均衡器(多个独立 vLLM 实例置于代理之后)时,必须在权重更新前后向每一个引擎实例单独发送 pause 与 resume 请求。

典型异步 RL 循环

  1. 用当前策略开始生成 rollout;
  2. 训练器产生新权重后,以 mode="keep" 暂停生成;
  3. 将训练器侧的新权重同步到推理引擎(Weight Transfer 系统);
  4. 恢复生成——在途请求继续,且带上新权重;
  5. 重复。

这里的关键洞察是:以 mode="keep" 暂停的请求,在暂停产出的 token 来自权重,恢复产出的 token 来自权重。clear_cache 决定 KV cache 是否被失效:clear_cache=True 时之前缓存的键值对全部丢弃,恢复后生成的每个 token 都完全由新权重计算;clear_cache=False 时保留既有 KV cache 条目,上下文中部分 token 仍反映旧权重(陈旧 KV cache)。是否可接受取决于算法对一致性的要求——GRPO 类方法通常按 rollout 批次边界对齐权重版本,此时 clear_cache 的取舍直接影响同一请求内新旧 token 的混合程度。

官方 RLHF 示例:从哪份代码入手

examples/rl/ 目录下有一组端到端可运行的 RLHF 示例,覆盖各后端与部署形态,是最直接的实战入口:

示例 说明
rlhf_http_nccl.py 分离部署的起点。训练器占 1 张 GPU,2× 张量并行 fp8 服务占另外 2 张;HTTP 控制面 + NCCL 数据面,脚本自行拉起并销毁 server
rlhf_http_ipc.py 共置部署的起点。server 与训练模型共享单张 GPU;HTTP 控制面 + CUDA IPC 数据面,同样自管理 server 生命周期
rlhf_async_new_apis.py 异步权重同步的完整演示:vllm.AsyncLLMEngine + NCCL 传输 + 飞行中暂停/换权/恢复,并开启 batch invariance 做确定性校验——把换权后的输出与一个直接加载训练模型的新 vLLM 实例逐 token 比对(NVIDIA 要求 100% 精确匹配,ROCm 放宽到 90%)
rlhf_nccl_fsdp_ep.py NCCL + FSDP2 + 专家并行的多 rank 训练器示例
rlhf_ipc_fsdp_ep.py IPC + FSDP2,4 张 GPU 上与 --data-parallel-size 4 server 共置
rlhf_sparse_nccl.py sparse NCCL 的逐专家 checkpoint 补丁(Qwen3 MoE)

rlhf_async_new_apis.py 为例,其核心编排清晰展示了异步 RL 的骨架:训练模型(Qwen3-1.7B)与推理引擎(Qwen3-1.7B-Base)分别放在不同 GPU 的 Ray actor 中,训练侧用 RayVLLMWeightSyncClient 直连引擎对象;生成一批请求后,任一请求达到 token 阈值即 pause_generation(mode="keep"),调用 broadcast_weights()(内部即 engine.send_weights()),再 resume_generation();脚本随后打印每个请求换权前后分别产出的 token 段,并用新模型实例验证换权后的 token 与直接加载新权重的输出完全一致。

延伸阅读与扩展点

整个系统的所有部件都可替换:要发送的权重(WeightSource)、控制面传输(VLLMWeightSyncClient,内置 HTTP 与 Ray 两种 client,也可自行适配)、以及传输本身(两个引擎 ABC 各自带工厂注册表)。对于自建 RL 基础设施的团队,这意味着可以把 vLLM 的权重同步层无缝嵌入自有的训练框架——只需实现自己的 WeightSourceTrainerInitInfo 子类,即可复用四阶段协议、多 rank 语义与全部 HTTP/gRPC 端点。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388