vLLM 异步强化学习:基于 pause/resume 的生成—训练并行与权重热更新指南
本技术指南讲解 vLLM 仓库 docs/training/async_rl.md 中定义的**异步强化学习(Async RL)**支持体系:如何在训练与推理并行运行的同时,通过 pause_generation / resume_generation 安全地把训练侧新权重同步进正在出词的推理引擎。读完本文,你将掌握三种暂停模式的取舍、KV 缓存一致性语义、HTTP 层等价端点,以及一套可直接照搬的权重同步循环,并能在仓库源码与示例中找到对应的实现证据。
一、为什么需要 Async RL:one-off pipelining
标准的 RL 训练循环里,生成(rollout)与训练(training)是串行交替的:策略模型先跑出一批样本,训练器基于这批样本更新一轮权重,然后继续下一轮生成。这个过程中,生成阶段训练加速卡闲置、训练阶段推理加速卡闲置,GPU 利用率因此受限于单侧负载。
文档提出的一对一流水线(one-off pipelining)思路是:把生成与训练拆成两条并行协程,让推理引擎在旧样本上训练的同时,持续产出新样本,由此获得更高的 GPU 利用率与训练吞吐。
但并行重叠立刻引入一个核心难点:权重必须在请求还在飞行(in-flight)的半途被换入推理引擎。如果只是简单粗暴地覆盖权重,正在解码的请求可能读到"前半段用旧权重、后半段用新权重"以外的混乱状态(例如 KV cache 与权重不一致)。这正是 vLLM 引入 pause/resume API 的原因。
二、核心 API:pause_generation 与 resume_generation
为了在引擎运行期间安全更新权重,vLLM 在推理引擎上暴露了 pause_generation 与 resume_generation 两个异步方法,让训练器协调出一个干净的权重同步窗口,且不丢失进行中的工作。它们定义在 vllm/v1/engine/async_llm.py 的 AsyncLLM 类中,对应协议见 vllm/engine/protocol.py。
pause_generation
Python 侧调用方式如下(AsyncLLM 的签名位于 async_llm.py#L874-L917):
await engine.pause_generation(mode="keep", clear_cache=True)
实际签名中还包含一个已废弃的历史参数 wait_for_inflight_requests: bool | None = None(传入后内部会触发 DeprecationWarning 并把 mode 置为 "wait"),新代码应直接使用 mode。
mode 参数决定如何处理进行中的请求,其取值类型为 Literal["abort", "wait", "keep"](见 vllm/v1/engine/init.py#L31):
| Mode | 行为 |
|---|---|
"abort" |
立即中止所有 in-flight 请求并返回部分结果(默认) |
"wait" |
等待所有 in-flight 请求结束再暂停 |
"keep" |
冻结队列中的请求,调用 resume_generation 后继续生成 |
clear_cache 参数控制暂停结束后是否清空 KV cache 与 prefix cache:
await engine.pause_generation(mode="keep", clear_cache=True)
在引擎内核 vllm/v1/engine/core.py 的 pause_scheduler 实现中,三种模式对应两条调度状态路径:
"abort":先调用scheduler.finish_requests(None, RequestStatus.FINISHED_ABORTED)立刻终止全部请求,再把调度器置为PAUSED_NEW(新请求只排队、不进入step()),可选清空缓存后完成。"wait":置为PAUSED_NEW(新请求排队但调度器继续 step),直到 in-flight 请求排空,可选清空缓存。"keep":直接置为PAUSED_ALL,返回一个在输出队列清空时完成的 Future——请求既不被中止也不被推进,而是冻结在调度队列里等待恢复。
无论哪种模式,暂停期间新到达的生成/编码请求都不会被调度,直到调用 resume_generation。暂停完成后内核还会通过 collective_rpc("synchronize_device") 做一次设备同步,确保设备真正空闲(对应 core.py#L853-L886)。另外需要注意:"wait" 模式在 in-process engine 模式下不可用(pause_scheduler 会直接抛出 ValueError)。
resume_generation
await engine.resume_generation()
resume_generation 会把调度器状态从暂停态改回 UNPAUSED(见 async_llm.py#L919-L921 与 core.py#L890-L892),随后用 mode="keep" 冻结的请求会继续解码。引擎还额外提供了只读状态查询 is_paused(),可判断当前是否处于任一暂停态。
三、HTTP 端点:dev-mode 下的等价格令
设置环境变量 VLLM_SERVER_DEV_MODE=1 后,vLLM HTTP 服务器会挂载一批与上述 API 等价的端点。路由实现在 vllm/entrypoints/serve/dev/rlhf/api_router.py:
| 端点 | 方法 | 说明 |
|---|---|---|
/pause?mode=keep |
POST | 暂停生成;mode 支持 abort / wait / keep,另有 clear_cache 查询参数 |
/resume |
POST | 恢复生成 |
/abort_requests |
POST | 不暂停调度器,直接中止 in-flight 请求:body 为 {} 中止全部,或 {"request_ids": [...]} 中止指定请求 |
/weight_info |
GET | 返回最新已提交的 weight_version |
以 curl 为例:
# 冻结进行中的请求(keep 模式)
curl -X POST "http://localhost:8000/pause?mode=keep"
# 训练器完成权重同步后恢复生成
curl -X POST "http://localhost:8000/resume"
# 中止全部 in-flight 请求(不暂停调度器)
curl -X POST http://localhost:8000/abort_requests -d '{}'
# 查询当前已提交的权重版本
curl http://localhost:8000/weight_info
路由层还额外暴露了 /is_paused(GET,返回 {"is_paused": bool})。注意 /pause 中传入非法的 mode 值会被拒绝并返回 400。/abort_requests 在未提供 request_ids 时会遍历 AsyncLLM.output_processor 中跟踪的全部请求(含并行采样父请求),按内部 ID 中止。
补充说明:带
VLLM_SERVER_DEV_MODE=1的同一套 dev 路由同时承载了完整的权重转移控制面(/init_weight_transfer_engine、/start_weight_update、/update_weights、/finish_weight_update、/update_weight_version、/get_world_size等)。pause/resume 只是其中与"调度生命周期"相关的两个端点。完整端点表可查阅 docs/training/weight_transfer/README.md。
数据并行(DP)下的注意点
文档给出了一条重要的数据并行使用约束:
- 当使用 vLLM 内部负载均衡(即
data_parallel_backend="ray")时,pause/resume 会由系统自动在所有 DP rank 上统一处理,单次调用即可; - 当使用外部负载均衡(即多个相互独立的 vLLM 实例位于代理之后)时,你必须在权重更新前后,逐个实例地发送 pause 与 resume 请求。
仓库中 examples/features/pause_resume/data_parallel_pause_resume.py 提供了面向 HTTP 端点的封装示例,其中 pause_generation(base_url, mode="keep") 与 resume_generation(base_url) 演示了逐实例调用的写法。
四、典型异步 RL 循环:权重同步五步走
把上述 API 组装起来,一个典型的异步 RL 权重同步循环如下:
- 用当前策略启动 rollout 生成;
- 一旦训练器有新权重待同步,以
mode="keep"暂停生成; - 把更新后的权重从训练器同步到推理引擎(见 Weight Transfer 文档);
- 恢复生成——被冻结的 in-flight 请求用新权重继续;
- 循环往复。
其中最关键的技术洞察是权重边界与 token 的对应关系:
- 以
mode="keep"冻结的请求,暂停前产出的 token 来自旧权重,恢复后产出的 token 来自新权重; - 单个请求的产物因此被明确切分为"旧权重段 + 新权重段",这是后续校验阶段能精确对账的前提。
clear_cache 则决定 KV 缓存在暂停窗口内的去留:
clear_cache |
语义 |
|---|---|
True |
暂停后丢弃此前缓存的 key-value 条目。恢复后产出的全部 token 均由新权重从头计算,上下文与权重严格一致 |
False |
保留已有 KV cache 条目。恢复后上下文中的部分 token 仍反映旧权重(即存在 stale KV cache),换取更快的恢复速度 |
实现上,暂停完成的收尾逻辑 _finish_pause(clear_cache)(见 core.py#L853-L858)会在 clear_cache=True 时重置 KV cache、prefix cache、多模态 cache 与 encoder cache,并同步设备。异步入口 async_llm.py#L908-L910 还会在暂停前主动清空多模态 cache(renderer.clear_mm_cache_async()),并在暂停完成后小睡 20ms,保证 in-flight 请求的最终输出先于 pause_generation 返回,便于调用方按直觉顺序处理事件。
五、完整示例拆解:rlhf_async_new_apis.py
文档末尾指出的参考示例是 examples/rl/rlhf_async_new_apis.py。它以真实可运行的形态把本文所有概念串了起来:vllm.AsyncLLMEngine + Ray 进程管理 + NCCL 权重传输 + 中途中止/恢复 + 结果校验。
脚本整体划分为两条独立 GPU 流水线:
- 训练侧:
TrainModel是一个@ray.remote(num_gpus=1)actor,用 Hugging FaceAutoModelForCausalLM加载Qwen/Qwen3-1.7B训练模型; - 推理侧:自定义子类
MyLLM(vllm.AsyncLLMEngine)以 Ray 作为distributed_executor_backend,加载基础模型Qwen/Qwen3-1.7B-Base(权重传输配置为WeightTransferConfig(backend="nccl"))。
关键流程分两个阶段:
Phase 1:并发请求 + 中途换权重
- 定义
PAUSE_TOKEN_THRESHOLD = 10,采样参数为贪心解码(temperature=0),max_tokens = PAUSE_TOKEN_THRESHOLD + N_NEW_TOKENS(N_NEW_TOKENS = 100); - 通过
do_generate为一批 13 条 prompt 各发起一个远端生成任务(gen_futures),同时在pause_after_n_tokens协程中轮询"是否有请求越过 10 token 阈值"; - 一旦越过阈值就调用
super().pause_generation(mode="keep")冻结全部 in-flight 请求,随后小睡 5 秒,并把_generation_paused置位——后续生成循环据此记录pause_token_index(即"换权重前已产出的 token 数"); - 调用训练 actor 的
broadcast_weights(),由 trainer 侧引擎通过send_weights()驱动"初始化 → 开始 → 传输 → 结束"四阶段协议,经 NCCL broadcast 把训练模型权重覆盖到推理 worker(该机制的架构细节见 docs/training/weight_transfer/README.md,NCCL 后端说明见 nccl.md); - 调用
resume_generation()恢复,收集结果后按pause_idx切分:all_token_ids[:pause_idx]为旧权重段、all_token_ids[pause_idx:]为新权重段,分别解码打印。
Phase 2:用全新 vLLM 实例做正确性校验
验证阶段的思路是:用 prompt + 旧权重段 token 作为输入,在一个直接加载 V2 训练模型的全新 MyLLM 实例上重新贪心解码,将其输出与 Phase 1 中"新权重段"做逐 token 比对。这种比对依赖批次不变性(batch-invariant)生成——即输出与请求被如何批处理无关。示例通过 Ray runtime env 设置 VLLM_BATCH_INVARIANT=1 开启该特性;批次不变性目前要求 NVIDIA compute capability 9.0 及以上的 GPU(H100/H200、B100/B200)。ROCm 平台上由于存在残余非确定性,示例放宽到 90% 的通过率(MIN_PASS_RATE = 0.9),同时注入固定 seed、关闭 prefix caching、max_num_seqs=1 等确定性设置;而真正的权重同步故障会造成约 0% 通过率而非 90%+,因此该阈值足以区分"实现缺陷"与"平台抖动"。
最终脚本断言 pass_rate >= MIN_PASS_RATE,否则以详细的首个发散 token 信息宣告失败。这套"换权重后输出 ≈ 全新加载同权重实例的输出"的验证模式,是判断权重同步是否真正生效的黄金标准。
六、把机制放进更大的 RL 生态
pause/resume 只是 vLLM RL 训练体系里"调度生命周期"的一环,与之配套的仓库资源还有:
- 权重传输四阶段协议:初始化(
init_weight_transfer_engine)→ 开始(start_weight_update)→ 传输(update_weights,可多次调用以支持分块)→ 结束(finish_weight_update,可提交weight_version)。协议由 trainer 侧TrainerWeightTransferEngine驱动,推理侧 worker 被动响应,详见 docs/training/weight_transfer/README.md。 - 多种传输后端:NCCL(训练/推理分 GPU)、IPC(CUDA IPC handle,同卡共置)、sparse_nccl(checkpoint 坐标稀疏补丁)、sharded_rdt(NIXL/Ray Direct Transport,MoE 专家并行等超大模型场景)。传输后端的选择只由推理侧
WeightTransferConfig(backend=...)声明,训练侧则由各TrainerInitInfo子类自带的backendClassVar 决定。 - HTTP 训练流程示例:
examples/rl/目录下还提供了rlhf_http_ipc.py、rlhf_http_nccl.py(通过 HTTP 端点 + IPC/NCCL 权重通道驱动、并在其中使用/pause、/resume的完整示例),以及面向 FSDP/EP 的rlhf_ipc_fsdp_ep.py、rlhf_nccl_fsdp_ep.py、rlhf_sharded_rdt_small_ep.py、rlhf_sparse_nccl.py等进阶变体,可作为理解数据并行与分片权重同步的补充素材。 - 分层(layerwise)与采样掩码:若训练按层推进或需要控制 token 级别的训练目标,可进一步参考 docs/training/layerwise.md 与 docs/training/sampling_mask.md。
结语
异步 RL 的价值在于让训练与生成始终并行运转;而要把"并行"变成"安全",关键就在于 vLLM 提供的 pause/resume 调度原语。本文覆盖的 abort / wait / keep 三种模式、clear_cache 的一致性语义、dev-mode HTTP 端点与数据并行注意事项,共同构成了构建 RLHF / GRPO 等在线 RL 训练闭环的工程基础。动手实践时,建议先运行 examples/rl/rlhf_async_new_apis.py 观察"同一请求旧/新权重分界"的实际输出,再对照 async_llm.py 与 core.py 中的调度状态机,即可完整建立起从 API 到内核的认知链路。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00