抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

在标准 RL 训练循环中,生成(generation)和训练(training)顺序执行:策略模型先生成 rollout 样本,然后在这些样本上进行训练,往复循环。在生成阶段,训练侧处于空闲状态,反之亦然。

所谓 one-off pipelining 方法将生成阶段和训练阶段拆分为两个并行的协程,使模型能够一边生成新样本,一边对先前生成的数据进行训练,带来更高的 GPU 利用率和更大的训练吞吐量。

然而,这种重叠引入了一个复杂性:推理引擎必须在运行过程中更新权重,而此时可能仍有正在处理的请求。

Pause and Resume API

为了在推理引擎运行时安全地更新权重,vLLM 提供了 pause_generationresume_generation 方法。这让训练侧能够协调出一个干净的窗口来进行权重同步,而不会丢失正在进行的生成任务。

pause_generation

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

mode 参数控制如何处理正在进行的请求:

Mode Behavior
"abort" 立即中止所有正在进行的请求并返回部分结果(默认)
"wait" 等待所有正在进行的请求完成后再暂停
"keep" 冻结队列中的请求;调用resume_generation 后它们会恢复执行

clear_cache 参数控制暂停后是否清空 KV 缓存和前缀缓存等。

resume_generation

1
await engine.resume_generation()

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

HTTP Endpoints

使用 vLLM HTTP server时,可以通过以下 endpoint 调用相同的功能:

  • POST /pause?mode=keep - 暂停生成
  • POST /resume - 恢复生成

注意:数据并行

当使用 vLLM 的内部负载均衡器(即 data_parallel_backend="ray")进行数据并行时,pause 和 resume 会在所有 DP rank 上自动处理——一次调用即可。当使用外部负载均衡器(即代理后的多个独立 vLLM 实例)时,必须在权重更新前后分别向每个引擎实例发送 pause 和 resume 请求。

典型异步RL流程

典型的带权重同步的异步 RL 循环如下:

  • 从当前策略开始生成 rollout 样本
  • 当训练器有新权重需要更新时,使用 mode="keep" 暂停生成
  • 将更新后的权重从训练器同步到推理引擎(参见 Weight Transfer
  • 恢复生成——正在进行的请求将使用新权重继续执行
  • 重复此过程

关键在于,使用 mode="keep" 暂停的请求,在暂停前产生的 token 来自权重,而在恢复后产生的 token 则来自权重。clear_cache 参数控制在暂停期间是否使 KV 缓存失效:

  • clear_cache=True 时,之前缓存的 key-value 条目将被丢弃,因此恢复后生成的所有 token 都将完全使用新权重计算。
  • clear_cache=False 时,现有的 KV 缓存条目将被保留,这意味着上下文中的某些 token 可能仍然反映旧权重的状态(即过期的 KV 缓存)。

代码实现

整体架构

1
2
3
4
5
6
HTTP API (api_router.py)
→ AsyncLLM (async_llm.py)
→ EngineCoreClient (core_client.py) [跨进程 ZMQ 通信]
→ EngineCoreProc/DPEngineCoreProc (core.py)
→ Scheduler.set_pause_state() (scheduler.py)
→ Scheduler.schedule() / _process_input_queue() / idle callbacks

核心数据结构

PauseState 状态机 - 调度器维护的一个三态枚举:

状态 含义
UNPAUSED 正常调度
PAUSED_NEW 不调度新请求,已在 running 的请求继续执行(用于abort/wait 模式)
PAUSED_ALL 完全冻结,token_budget 强制为 0(用于keep 模式)

pause_generation

sequenceDiagram
    participant Caller
    participant AsyncLLM
    participant ECP as EngineCoreProc
    participant SC as Scheduler
    Caller->>AsyncLLM: await pause_generation(mode, clear_cache)
    AsyncLLM->>ECP: pause_scheduler(mode, clear_cache)
    alt mode == abort
        ECP->>SC: finish_requests(None, FINISHED_ABORTED)
        Note over SC: running reqs aborted immediately
        ECP->>SC: set_pause_state(PAUSED_NEW)
    else mode == wait
        ECP->>SC: set_pause_state(PAUSED_NEW)
        Note over SC: PAUSED_NEW - waiting queue blocked,
running queue still scheduled else mode == keep ECP->>SC: set_pause_state(PAUSED_ALL) Note over SC: PAUSED_ALL - token_budget=0,
nothing scheduled end rect rgb(245,245,245) Note over SC: Inside schedule() - effect of pause_state alt pause_state == PAUSED_ALL SC->>SC: token_budget = 0, skip running and waiting loops else pause_state == PAUSED_NEW SC->>SC: running loop executes, waiting loop skipped else pause_state == UNPAUSED SC->>SC: both loops execute normally end end ECP->>ECP: _pause_complete() = not has_work() alt complete immediately ECP->>ECP: _reset_caches() if clear_cache ECP-->>AsyncLLM: Future resolved else pending work ECP->>ECP: register idle_state_callback, keep stepping ECP-->>AsyncLLM: Future resolved once idle end AsyncLLM-->>Caller: return

pause_generation(mode=”keep”, clear_cache=True) 从用户 API 到调度器状态机的调用链路:

1
2
3
4
AsyncLLM.pause_generation()
└─> AsyncMPClient.pause_scheduler_async() [ZMQ utility call]
└─> EngineCoreProc.pause_scheduler() [engine process]
└─> Scheduler.set_pause_state()

EngineCoreProc.pause_scheduler() pause mode 对应行为:

Mode Behavior
abort 立即调用finish_requests(None, FINISHED_ABORTED) 中止所有请求,发送 abort 输出,设置 PAUSED_NEW
wait 设置PAUSED_NEW(不中止请求),等待 engine 自然 idle
keep 设置PAUSED_ALL(冻结所有调度),等待 output queue 排空

engine_idle_callback 的注册与触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
run_busy_loop()

├─ _process_input_queue()
│ │
│ ├─ has_work() → PAUSED_ALL 时返回 False
│ │ │
│ │ └─ 进入 while not has_work() 死循环
│ │ │
│ │ ├─ _notify_idle_state_callbacks() ← engine_idle_callback 在这里被调用
│ │ │ [alt _reset_caches] future.set_result(None) ← pause 的 RPC 返回
│ │ │
│ │ └─ input_queue.get(block=True) ← 阻塞等待新消息
│ │ │
│ │ └─ 收到 resume → _handle_client_request()
│ │ → resume_scheduler() → set_pause_state(UNPAUSED)
│ │ → has_work() 重新返回 True,退出内层循环
│ │
│ └─ 退出 _process_input_queue

└─ _process_engine_step() ← 恢复正常的调度+推理

resume_generation

sequenceDiagram
    participant Caller as Trainer/RL Loop
    participant AsyncLLM
    participant Client as EngineCoreClient
    participant Core as EngineCore Proc
    participant Sched as Scheduler
    participant MR as ModelRunner V1/V2

    Caller->>AsyncLLM: resume_generation()
    AsyncLLM->>Client: engine_core.resume_scheduler_async()
    Client->>Core: resume_scheduler()

    alt DP mode DPEngineCoreProc
        Core->>Core: check pending_pause / ignore_start_dp_wave
        Core->>Sched: set_pause_state(UNPAUSED)
        Core->>Core: ignore_start_dp_wave = False
        Note over Core: Barrier - all-reduce across DP ranks
to confirm all have resumed Core->>Sched: has_unfinished_requests() Core->>Core: engines_running = True if global unfinished else single engine Core->>Sched: set_pause_state(UNPAUSED) end Note over Sched: token_budget restored to max_num_scheduled_tokens Core->>Sched: schedule() next step loop alt requests were PAUSED_NEW abort/wait Note over Sched: running reqs never dequeued, continue normally.
Queued adds now flushed in. else requests were PAUSED_ALL mode=keep Note over Sched: frozen running/waiting reqs now eligible,
previously-running reqs treated as resumed Sched->>Sched: _make_cached_request_data(resumed_reqs=[...]) alt use_v2_model_runner == False V1 Sched-->>MR: CachedRequestData - resumed_req_ids + all_token_ids full history MR->>MR: _update_states(scheduler_output) Note over MR: V1 reconstructs full sequence state
from all_token_ids in cached data else use_v2_model_runner == True V2 Sched-->>MR: NewRequestData - prefill_token_ids carries full token ids Note over MR: V2 resumed request emitted as a new request,
all_token_ids skipped in cached_reqs end end Sched-->>Core: SchedulerOutput Core-->>Client: step results / outputs Client-->>AsyncLLM: await completion AsyncLLM-->>Caller: return

resume_generation 调用链路:

1
2
3
4
AsyncLLM.resume_generation()
└─> AsyncMPClient.resume_scheduler_async() [ZMQ utility call]
└─> EngineCoreProc.resume_scheduler() [engine process]
└─> Scheduler.set_pause_state()

只需将调度器状态切回 UNPAUSED。下一次 schedule() 调用时:

  • token_budget 恢复正常计算
  • waiting 队列中的请求重新被调度
  • 之前被 mode="keep" 冻结的请求继续执行

DP 场景的 resume 需要额外的同步:

  1. 确保 pending_pause 已清除、ignore_start_dp_wave 已重置
  2. 调用父类的 resume_scheduler() 将状态设为 UNPAUSED
  3. 通过 all-reduce barrier 等待所有 DP rank 都完成 resume
  4. 如果全局有未完成的请求,启动 step 循环

vllm e2e用例设计

https://github.com/vllm-project/vllm/pull/52144 已合入

1、is_paused状态与幂等性:重复pause/resume,多次循环
2、invalid mode行为 - return status code 状态不变
3、keep/wait/abort对in-flight请求和新请求的行为,及resume之后的行为
4、clear_cache =true/false的行为 - golden 输出比对

1
2
3
4
5
6
7
8
9
 优先级    用例                                                           主要覆盖
━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
P0 test_pause_resume_state_is_idempotent_across_cycles is_paused、重复调用、多次循环
──────── ───────────────────────────────────────────────────────────── ──────────────────────────────────────────────
P0 test_invalid_pause_mode_preserves_state 非法 mode 状态码及状态不变
──────── ───────────────────────────────────────────────────────────── ──────────────────────────────────────────────
P0 test_pause_mode_request_lifecycle[mode] abort/wait/keep 的 in-flight、新请求、resume
──────── ───────────────────────────────────────────────────────────── ──────────────────────────────────────────────
P1 test_clear_cache_preserves_output_and_controls_prefix_cache golden 输出及缓存保留/清除

1. 状态与幂等性 test_pause_resume_state_is_idempotent_across_cycles

1
2
3
4
5
6
7
初始 is_paused == false
resume 两次 -> 都是 200,状态仍为 false

循环两轮:
对 abort / wait / keep:
pause(mode) 两次 -> 都是 200,状态为 true
resume 两次 -> 都是 200,状态为 false

2. 非法 mode test_invalid_pause_mode_preserves_state

1
2
3
4
5
6
7
8
9
10
覆盖两个起始状态:

unpaused -> pause(mode="invalid") -> 422 -> 仍为 unpaused
paused -> pause(mode="invalid") -> 422 -> 仍为 paused

只断言:

* HTTP 状态码。
* 调用前后的 is_paused 相同。
* 可选检查错误位置包含 query 参数 mode,不要完整匹配 Pydantic 错误文本。

3. 三种 mode 的请求生命周期

用一个参数化测试:

1
2
3
@pytest.mark.parametrize("mode", ["abort", "wait", "keep"])
def test_pause_mode_request_lifecycle(server_url, mode):
...

预期矩阵如下:

1
2
3
4
5
6
7
mode    pause 返回时 in-flight 请求                    pause 后提交的新请求     resume 后
━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
abort 已结束,finish_reason == "abort" 已接受但保持 pending 新请求正常完成;旧请求不恢复
─────── ────────────────────────────────────────────── ────────────────────── ──────────────────────────────
wait 已正常完成,通常为 finish_reason == "length" 保持 pending 新请求正常完成
─────── ────────────────────────────────────────────── ────────────────────── ──────────────────────────────
keep 尚未完成,生成进度冻结 保持 pending 新旧请求都正常完成

测试实现仍沿用 threading 风格,但做几点调整:

  • in-flight 请求使用 streaming completion,并用“收到第一个 token”的 Event 确认它确实开始执行,替代固定的 sleep(0.3)。
  • 新请求不使用短 timeout 后断言 not ok。这会混淆“正确排队”和“请求失败”。应保持线程连接,确认 pause 期间线程未结束,resume 后再 join 并断言 ok。
  • 对 keep,可以记录 pause 返回时的 chunk 数,短暂等待后确认 chunk 数未增加,从而验证“冻结”而不仅是“尚未完成”。

4. clear_cache 与 golden 输出 test_clear_cache_preserves_output_and_controls_prefix_cache

服务增加:

1
2
--enable-prefix-caching
--enable-prompt-tokens-details
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
使用一个明显长于 cache block 的固定 prompt,temperature=0:

pause(abort, clear_cache=true) + resume,确保初始缓存为空

R0 = 生成一次,作为运行时 golden
R1 = 相同请求,确认 cached_tokens > 0,证明缓存已预热

pause(abort, clear_cache=false) + resume
R2 = 相同请求
断言 normalized(R2) == normalized(R0)
断言 R2.cached_tokens > 0

pause(abort, clear_cache=true) + resume
R3 = 相同请求
断言 normalized(R3) == normalized(R0)
断言 R3.cached_tokens == 0

不要比较完整响应。golden 只保留:

* choices[0].text
* choices[0].finish_reason
* token 序列,例如 logprobs.tokens
* prompt/completion token 数,但排除 cached_tokens

速度与稳定性

使用 class/module scoped 的 server_url fixture,一次启动服务供所有测试复用,并在每个测试前后调用一次 resume() 恢复状态。cache 测试自行在开始时执行 clear_cache=True,避免共享服务造成缓存污染。

最终成本大致是:

  • 1 次模型服务启动。
  • 3 个 mode 各一个短 in-flight 请求。
  • 4 次短 golden 请求。
  • 状态和非法参数测试几乎没有额外推理成本。

不建议在这一层增加并发 pause/resume race、多请求批量、几十轮循环或 3 modes × 2 clear_cache 全组合;abort/wait/keep 和多请求行为在底层测试中已经有较完整覆盖,HTTP E2E保留上述关键映射即可。

RL e2e用例设计

examples/rl/rlhf_async_new_apis.py 展示了在异步 RL 训练中如何通过 pause/resume + weight transfer 在推理运行期间切换权重。

sequenceDiagram
    participant D as Driver
    participant TM as TrainModel (Ray actor)
    participant LLM as MyLLM (AsyncLLM)

    Note over D,LLM: Setup: init train model + inference engine on separate GPUs
    D->>TM: TrainModel.remote(MODEL_NAME_V2)
    D->>LLM: MyLLM.remote(**llm_kwargs)
    D->>TM: get_master_address_and_port()
    D->>LLM: init_weight_transfer_engine(NCCL init info)
    D->>TM: init_weight_transfer_group(world_size)
    Note over TM,LLM: NCCL process group established

    rect rgb(245,245,245)
    Note over D,LLM: Phase 1: concurrent generation with weight sync
    loop for each prompt
        D->>LLM: do_generate.remote(prompt_ids, sampling_params)
        Note over LLM: streams tokens, sets
_request_pause_flag at threshold end D->>LLM: pause_after_n_tokens() LLM->>LLM: await pause_generation(mode="keep") Note over LLM: sleep(5), _generation_paused = True D->>LLM: start_weight_update(is_checkpoint_format=True) D->>LLM: update_weights(NCCLWeightTransferUpdateRequest) D->>TM: broadcast_weights(packed=True) TM-->>LLM: weights sent over NCCL D->>LLM: finish_weight_update() D->>LLM: resume_generation() LLM-->>D: gen_futures resolve (output, pause_token_index) end Note over D,LLM: Phase 2: validation D->>LLM: shutdown() / ray.kill(llm, train_model) D->>LLM: fresh MyLLM.remote(MODEL_NAME_V2) loop for each result D->>LLM: do_generate(prompt+prefix tokens, max_tokens=n_after) end LLM-->>D: val_results Note over D: compare post-swap tokens vs
fresh V2 output, assert pass_rate

概述:Trainer 占一个 GPU,vLLM 引擎占另一个 GPU,通过 NCCL 通道在 GPU 间直接传输权重。开启 batch_invariant 保证输出确定性,用于后续验证。

Phase 1: 并发生成 + 权重切换。核心流程:触发生成 → 暂停 → 切换权重 → 恢复

Phase 2: 正确性验证。

1
2
3
4
验证逻辑:
旧权重生成的 before tokens + 新权重 = weight-synced 引擎的 after tokens
= 独立 V2 引擎用相同前缀生成的结果
如果两者一致,说明 weight transfer 正确。

model runner v2方案

V1 vs V2 的核心差异:被抢占/恢复请求的处理方式

flowchart TD
    A["resume_scheduler() -> pause_state = UNPAUSED"] --> B["scheduler.schedule() next step"]
    B --> C["RUNNING loop finds frozen/preempted request
status == PREEMPTED or WAITING"] C --> D{"request.status == PREEMPTED?"} D -->|"yes"| E["scheduled_resumed_reqs.append(request)"] D -->|"no (WAITING, i.e. fresh)"| F["scheduled_new_reqs.append(request)"] E --> G{"use_v2_model_runner?"} F --> H["always in scheduled_new_reqs
(NewRequestData)"] G -->|"V1: False"| I["stays in scheduled_resumed_reqs
-> _make_cached_request_data()
-> CachedRequestData
resumed_req_ids += req_id
all_token_ids[req_id] = full history"] G -->|"V2: True"| J["merged into scheduled_new_reqs
-> NewRequestData.from_request(req, blocks, req._all_token_ids)
prefill_token_ids carries full history
all_token_ids NOT populated"] I --> K["GPUModelRunner._update_states:
req_data = scheduled_cached_reqs
resumed_from_preemption = req_id in resumed_req_ids
req_index is None -> reqs_to_add
output_token_ids recovered from all_token_ids"] J --> L["GPUModelRunner treats it like a brand-new request:
uses prefill_token_ids directly,
no need to reconstruct output_token_ids from all_token_ids"]

V1 Model Runner:resumed 请求走 CachedRequestData 路径

  • 被恢复的请求放入 scheduled_cached_reqs.resumed_req_ids
  • 完整的 token 历史(prompt + 已生成 token)通过 all_token_ids 字段传递
  • 调度器需要维护 prev_step_scheduled_req_ids 来判断哪些请求需要传递 all_token_ids
  • Model runner 在 _update_states 中处理 resumed 请求时,需要从 all_token_ids 恢复 output_token_ids

V2 Model Runner:resumed 请求走 NewRequestData 路径

  • scheduled_resumed_reqs合并进 scheduled_new_reqs
  • 完整 token 历史通过 NewRequestData.prefill_token_ids 传递(v2 专用字段)
  • V2 的 GPUModelRunner.add_requests() 统一处理所有请求(包括恢复的请求),使用 prefill_token_ids 作为完整的 token 上下文
  • CachedRequestData.all_token_ids 始终为空 ,不需要 prev_step_scheduled_req_ids 追踪

MRV2 设计原则:“Treat preemption as completion. On resume, re-add request data as fresh state.”

维度 V1 Model Runner V2 Model Runner
resumed 请求的调度输出位置 scheduled_cached_reqs (resumed_req_ids) scheduled_new_reqs (prefill_token_ids)
token 历史传递方式 CachedRequestData.all_token_ids NewRequestData.prefill_token_ids
prev_step_scheduled_req_ids 需要维护 不需要
model runner 处理方式 特殊的 resumed 路径,需恢复 output_token_ids 与新请求完全相同的路径,persistent batch 重新初始化
复杂度 需要区分 running/resumed/new 三种状态 resumed 等同于 new,逻辑更简洁

异步 RL 的 pause/resume 功能直接复用了 vllm 的代码,vllm-ascend 没有自己实现生成层面的 pause/resume 逻辑,而是完全复用了上游 vllm 的基础设施。

具体复用方式:

  1. API 层面:RL 示例直接调用上游 API

    • rlhf_async_new_apis.py 继承 vllm.AsyncLLMEngine,调用 super().pause_generation(mode=”keep”)
    • rlhf_http_hccl.py / rlhf_http_npu_ipc.py 调用 vllm API Server 的 /pause 和 /resume HTTP 端点
  2. 调度器层面:自定义调度器直接 import 上游的类和枚举

    • PauseState 从 vllm.v1.core.sched.interface 导入
    • AsyncScheduler 从 vllm.v1.core.sched.async_scheduler 导入
    • AsyncRecomputeScheduler 继承自上游的 AsyncScheduler
    • 调度循环中通过 self._pause_state == PauseState.PAUSED_ALL 判断是否暂停
  3. vllm-ascend 源码中没有任何 pause_generation、resume_generation、/pause、/resume 或 PauseState 的定义

vllm-ascend 自己实现的部分(与 RL 相关但不属于 pause/resume)

1
2
3
4
5
6
7
8
9
┌──────────────┬───────────────────────────────────────────────────────────────────────────────────────────┐
│ 功能 │ 说明 │
├──────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ Sleep Mode │ NPU 内存 offload/restore,基于 CANN/CaMemAllocator,完全不依赖上游的 CUDA cumem │
├──────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 权重传输后端 │ HCCL 引擎和 NPU IPC 引擎替代 NCCL,用于 RL 场景下 trainer 和 inference 进程间的权重同步 │
├──────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
│ 自定义调度器 │ RecomputeScheduler、AsyncRecomputeScheduler 等扩展上游调度器,添加 recompute/offload 逻辑 │
└──────────────┴───────────────────────────────────────────────────────────────────────────────────────────┘

RL侧接入方案(以vime为例)

1、RL侧暴露的控制平面接口

接口 语义
pause_generation 暂停调度,对应 HTTP/pause,但保留现有 KV cache(mode=keep, clear_cache=false
flush_cache 显式清理 prefix cache,为权重替换腾出/校正状态,与 pause 解耦为独立步骤
start_weight_update / finish_weight_update 显式声明”进入/退出权重更新会话”的窗口,允许引擎内部做互斥锁定、缓冲区切换等准备
continue_generation 恢复调度,对应 HTTP/resume

pause_generation/continue_generation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def pause_generation(self):
if self.node_rank != 0:
return
response = requests.post(
f"http://{self.server_host}:{self.server_port}/pause",
params={"mode": "keep", "clear_cache": "false"},
json={},
)
response.raise_for_status()
return response

def continue_generation(self):
if self.node_rank != 0:
return
response = requests.post(f"http://{self.server_host}:{self.server_port}/resume", json={})
response.raise_for_status()
return response

每个引擎 actor 应仅在”控制节点”(vime 中为 node_rank == 0)真正发出控制请求,其余节点直接 no-op 返回,避免对同一 server 重复请求。

vime 底层 /pause/resume/sleep/wake_upstart_weight_updatefinish_weight_update 等具体是 vLLM server 侧暴露的 HTTP 接口,这部分实现在 vLLM 本身。


2、时序图

sequenceDiagram
    participant M as "MegatronTrainRayActor (rank0)"
    participant V as "VLLMEngine (node_rank0)"
    participant S as "vLLM server (/pause /resume)"

    M->>V: "pause_generation() (Ray remote)"
    alt "node_rank == 0"
        V->>S: "POST /pause (mode=keep, clear_cache=false)"
        S-->>V: "200 OK"
    else "node_rank != 0"
        V-->>M: "no-op, return None"
    end
    M->>V: "flush_cache()"
    Note over M: "int4/fp4 pre-process (可选)"
    Note over M,V: "dist.barrier(Gloo)"

    M->>V: "start_weight_update(is_checkpoint_format=True)"
    Note over M,V: "dist.barrier(Gloo)"

    alt "NCCL, non-colocated (UpdateWeightFromDistributed)"
        loop "For each weight bucket"
            M->>M: "all_gather_params_async / convert_to_hf"
            M->>V: "NCCL broadcast weights"
        end
    else "IPC, colocated (UpdateWeightFromTensor)"
        loop "For each weight chunk (bounded in-flight)"
            M->>M: "build CUDA IPC handles / all_gather_object(Gloo)"
            M->>V: "update_weights_from_tensor (IPC ObjectRef)"
        end
        Note over M: "torch.cuda.ipc_collect()"
    end

    M->>V: "finish_weight_update()"
    Note over M,V: "dist.barrier(Gloo)"
    Note over M: "int4/fp4 post-process (可选)"

    M->>V: "continue_generation() (Ray remote)"
    alt "node_rank == 0"
        V->>S: "POST /resume"
        S-->>V: "200 OK"
    else "node_rank != 0"
        V-->>M: "no-op, return None"
    end
    Note over M,V: "dist.barrier(Gloo)"

pause_generation/pause 端点发送带 mode=keep, clear_cache=false 参数的 POST 请求,暂停生成但保留 KV cache(由 flush_cache 单独控制清理);continue_generation/resume 端点发送无参数 POST 请求以恢复推理。

两个方法都有 if self.node_rank != 0: return 的防护,因为在多节点跨主机部署的引擎中,只有 rank0(持有 vLLM server 控制端点)需要真正发请求,其余 headless worker 直接跳过。


3、model runner v1/v2

vLLM 的 pause/resume 特性,其对 RL 侧暴露的接口(pause_generation / resume_generation)与对应 HTTP 端点(/pause/resume/is_paused)在 model runner v2 引入后保持不变。

model runner v2 相较于 v1 的差异仅体现在调度器(Scheduler)内部向 model runner 传递请求数据的格式上,不影响 RL 侧调用方式,因此 RL 侧无需为适配 v2 做额外修改。

Reference

vLLM文档:https://docs.vllm.ai/en/stable/training/async_rl/

评论