采样后端:vLLM / SGLang / TensorRT-LLM
7.1 RLHF 里"采样"为什么单独一个后端
RLHF 训练循环每 step 都要做一次大规模生成(rollout):
- 对当前 actor 模型,针对 $B$ 条 prompt 生成 $n$ 个 response;
- 每个 response 长度可能上万 token;
- 占整个 step 时间的 30%-70%。
训练框架(FSDP / Megatron)的 forward + generate 比专门的推理引擎慢 5-10×。所以 RLHF 系统几乎都把 rollout 分给专门的推理引擎。
7.2 verl 支持的采样后端
| 后端 | Hydra 选项 | 主战场 |
|---|---|---|
| vLLM | rollout.name=vllm | 默认;最广泛 |
| SGLang | rollout.name=sglang | 新一代,长上下文 / 多轮工具调用强 |
| TensorRT-LLM | rollout.name=trtllm | NVIDIA 极致吞吐 |
| HF Transformers | rollout.name=hf | 调试 / 小模型 / 验证数值等价 |
| vexact | 2026/05 新出 | "零失配"HF rollout,权重布局与训练相同 |
7.3 vLLM 后端关键配置
| 字段 | 含义 |
|---|---|
rollout.tensor_model_parallel_size | vLLM 自家 TP 度 |
rollout.gpu_memory_utilization | vLLM 抢用显存的上限,0.5-0.7 推荐 |
rollout.dtype | bf16 / fp16 / fp8 |
rollout.max_num_batched_tokens | vLLM 一次 batch 的最大 token 数 |
rollout.max_num_seqs | 同时 in-flight 的 sequence 数 |
rollout.n | 每个 prompt 采样几条(GRPO 必须 ≥ 4) |
rollout.temperature | 采样温度 |
rollout.top_p | top-p 截断 |
rollout.enforce_eager | 禁用 CUDA graph(调 bug 时打开) |
rollout.enable_chunked_prefill | chunk prefill 提升长 prompt 效率 |
rollout.disable_log_stats | 关 vLLM 自己的 stats 输出 |
7.4 SGLang 后端的差异
| 能力 | vLLM | SGLang |
|---|---|---|
| continuous batching | ✅ | ✅ |
| PagedAttention | ✅ | ✅ |
| 多轮工具调用 (function call) | 支持但需自拼 | 原生 RadixAttention 共享前缀 |
| 长上下文(>128K) | 支持但较慢 | 较快 |
| 权重热更新 | 需要 cold restart | 支持 weight update API |
| verl 集成成熟度 | 最稳定 | 稳定 |
切换:
actor_rollout_ref.rollout.name=sglang
actor_rollout_ref.rollout.tensor_model_parallel_size=2
actor_rollout_ref.rollout.gpu_memory_utilization=0.6
7.5 rollout TP 度怎么选
核心矛盾:
- TP 越大 → 单次生成显存压力越低;
- TP 越大 → 通信占比越高;
- TP 必须能整除 GPU 数(actor 共置时 = 节点 GPU 数)。
常见经验值(H100/A100 80G):
| 模型 | 推荐 rollout TP |
|---|---|
| ≤ 4B | 1 |
| 7B-13B | 2 |
| 30B | 2 或 4 |
| 70B | 4 或 8 |
| 235B+ MoE | 8 |
7.6 rollout.n:GRPO 组大小
GRPO 需要对同一 prompt 采样 $n$ 个 response 做组内归一化:
rollout.n | 说明 |
|---|---|
| 1 | 非 GRPO(PPO / REINFORCE) |
| 4 | GRPO 最小可行 |
| 5-8 | 常见推荐 |
| 16+ | R1 / DAPO 风格"高 sample efficiency",开销大 |
7.7 权重 resharding:vLLM ↔ FSDP 的桥
训练 actor 用 FSDP 切,rollout 用 vLLM 自家 TP 切。切法不同,无法直接对接,verl 在 verl/workers/sharding_manager/fsdp_vllm.py 等文件里写了双向转换:
- FSDP 把分片参数 all-gather 到 rank 0;
- 按 vLLM 期望的布局把参数 dispatch 给每个 vLLM TP rank;
- rollout 完成,KV cache 清掉;
- FSDP 重新建 shard。
这一步是 hybrid engine 的命脉。新模型架构如果 sharding manager 不支持,rollout 阶段会报形状不匹配,需要按现有模板加适配。
7.8 rollout correction:把"模型实际生成"调成"真实数据"
问题:训练用的 model.forward 算的 logp,和 rollout 引擎实际采样时的 logp,可能因实现差异(kernel、数值精度、attention 实现)不完全一致。这种 mismatch 会让 PPO 的 importance ratio 偏离真实值。
verl 提供 rollout correction:
algorithm.rollout_correction.enable=true
algorithm.rollout_correction.method=is # importance sampling
详细机制见 verl/trainer/config/algorithm/rollout_correction.yaml。新出的 vexact 就是为了彻底消除这个 mismatch 设计的。
7.9 router replay:MoE rollout 的特殊处理
MoE 模型的 expert 路由有随机性,训练和推理可能选不同 expert。verl 在 2026/04 PyTorch Conference Europe 上展示了 Router Replay:让 rollout 时记录每 token 的 expert 选择,训练 forward 重放相同选择,保证数值一致。
配置:
actor_rollout_ref.rollout.router_replay=true
7.10 调优清单
| 现象 | 修复 |
|---|---|
| rollout 阶段 OOM | 降 gpu_memory_utilization;升 TP |
| rollout 阶段慢 | 升 max_num_batched_tokens;用 chunked prefill |
| 权重 resharding 慢 | 开 streaming sharding;用 vexact |
| 训练后 logp 与 rollout logp 差距大 | 开 rollout_correction 或换 vexact |
| MoE 训练数值不稳 | 开 router_replay |
| SGLang init 慢 | 关 enforce_eager,重用 CUDA graph 缓存 |
7.12 ServerAdapter:rollout 端真正在做什么
verl 的 rollout 都是"客户端 + 独立 server 进程"架构。Server 是 Ray actor(vLLM AsyncLLMEngine / SGLang http_server),客户端是 worker 里的 ServerAdapter。两类后端的对照:
| 维度 | vLLM | SGLang |
|---|---|---|
| 客户端文件 | workers/rollout/vllm_rollout/vllm_rollout.py:61 ServerAdapter | workers/rollout/sglang_rollout/sglang_rollout.py:95 ServerAdapter |
| weight 传输通道 | CUDA IPC / 共享内存 / ZMQ | HTTP(POST /update_weights) |
| 调用方式 | server_handle.collective_rpc() 走 Ray | AsyncHttpServerAdapter 走 HTTP |
| sleep / wake | Ray RPC wake_up(tags=...) / sleep(level=...) | HTTP /resume_memory_occupation / /release_memory_occupation |
| LoRA 增量加载 | 原生支持,需要 VLLM_ALLOW_RUNTIME_LORA_UPDATING=true | 原生支持,单独 endpoint 上传 adapter |
| FP8 量化推理 | 需手动 quant 模型 | 客户端内联 wrap(sglang_rollout.py:329–339) |
| Prefill-Decode 分离 | 不支持 | 原生支持(见下节) |
两者都做的事:
- 客户端只在 rank 0 of rollout group(确切说是 TP 内的 rank 0)真正发请求,其他 rank 在 collective 里参与即可;
- 同时跑多副本(
data_parallel_size>1)时,每个副本对应一个 server actor,名字形如vllm_server_{replica_rank}。
7.13 BucketedWeightSender:vLLM 的 IPC 优化
vLLM 这条路最快的原因之一是不走 HTTP,走 CUDA IPC。vllm_rollout.py:178–182 调用的 BucketedWeightSender 做的事:
- 把训练侧 yield 的
(name, tensor)累积成 ≈ 100 MB 的 bucket; - 桶满了,通过 CUDA IPC handle(同节点)或 ZMQ(跨节点)发给 vLLM server;
- server 端
update_weights_from_ipc在 GPU 上直接rebuild_cuda_tensor()——零拷贝。
同节点情况下整个 update_weights 的实际数据移动量是极小的——只传 IPC handle 而不传张量本身。这是 colocate 模式的性能基础。
7.14 SGLang Prefill-Decode 分离(PD disaggregation)
SGLang 在长 CoT 训练场景下有个独门能力:把 prefill 和 decode 分到不同的 server actor,各自调整资源。代码在 sglang_rollout.py:160–181:
| 字段 | 含义 |
|---|---|
_pd_role | "prefill" 或 "decode" |
_pd_server_index | 这一角色下第几个 server actor |
| server actor 名规则 | sglang_server_{replica}_{node_rank}(colocated)sglang_server_decode_{replica}_{idx}(disaggregated) |
用途:long CoT 训练里 prefill / decode 计算特性差异巨大——prefill 算力受限,decode 带宽受限。把它们分到不同 GPU 池可以分别调参、各自达到最大吞吐。配置入口:actor_rollout_ref.rollout.disaggregation: { mode: pd, ... }。
7.15 vexact:金标 rollout 后端
2026-05 引入的 verl-project/vexact 是个独立 Python 包,但 verl 提供了内置对接。它的存在理由:排除"训推 logits 不一致"作为 PPO 不收敛的可能。
| 特性 | vexact | vLLM / SGLang |
|---|---|---|
| 训练侧 logits | 与 rollout 完全一致 | 有 small numerical drift |
| 底层实现 | 共享 HF transformer 定义,batch-invariant kernel | 各自的 fused kernel |
| 吞吐 | 低(HF 级别) | 高 |
| 典型用法 | "调试一组 step 看 IS ratio 是不是 ≈ 1" | 生产训练 |
启用方式:YAML 加 actor_rollout_ref.rollout.name: vexact。一切 rollout 接口和 vLLM/SGLang 兼容,可以直接对照实验。
7.16 选型决策树(最终版)
7.17 这章你需要带走什么
- 知道 verl 给你 vLLM / SGLang / TRT-LLM / HF / vexact 五条采样后端;
- 新手默认 vLLM 即可,遇到长上下文 / 多轮工具调用考虑 SGLang;
- 会调
tensor_model_parallel_size/gpu_memory_utilization/n三个最关键字段; - 理解"训推 mismatch"问题,知道 rollout correction / vexact 是干什么的;
- 看到
ServerAdapter知道它是 worker 内的客户端,真正跑模型的是同名 Ray actor; - SGLang 的 PD disaggregation 是 long CoT 训练的杀手锏。