MiniRL:用最小代码看懂 RL 训推同步
MiniRL 是一个参考 verl 实现思路、面向大模型强化学习流程的最小化示例。它不试图一次性实现完整 PPO/GRPO 算法,而是把 RL 系统里最容易让新人困惑的一段链路单独拎出来:
训练进程更新了模型参数之后,推理服务如何拿到最新权重,并继续生成下一批样本?
在真实 RLHF/RLAIF/Agent RL 系统里,训练和推理通常不是一个普通 Python 函数调用。训练侧可能由 FSDP 管理,推理侧可能由 vLLM 或 SGLang 提供服务,它们的显存管理方式、进程模型、并行方式都不一样。MiniRL 的价值就是用一套尽量短的代码,把这些组件如何启动、如何共用或分离 GPU、如何同步权重串起来。
当前 MiniRL 默认执行一轮验证流程:
- 启动 Ray。
- 创建 actor 训练 worker。
- 启动 rollout 推理服务。
- 先把 actor 初始权重同步到 rollout。
- 执行一次 mock rollout generation。
- 执行一次 mock actor training。
- 再把训练后的 actor 权重同步到 rollout。
- 清理 Ray actors。
其中 rollout generation 和 actor training 是 mock 流程,用 sleep 模拟耗时;但模型初始化、rollout 服务启动、显存 sleep/resume、权重同步链路是真实走通的。
为什么 RL 里会有“训推同步”
普通监督训练大多是这样的:
flowchart LR
Data[训练数据] --> Train[训练模型]
Train --> Save[保存 checkpoint]
强化学习训练更像一个循环:
flowchart LR
Policy[当前策略模型] --> Rollout[推理生成回答]
Rollout --> Reward[打分 / 奖励]
Reward --> Train[根据奖励训练策略]
Train --> Sync[同步新权重给推理服务]
Sync --> Rollout
这里的“策略模型”就是正在被训练的大模型。每一轮训练都会让模型参数发生变化,而下一轮生成样本时,推理服务应该使用最新模型。否则就会出现一种错位:训练侧已经学到了新策略,推理侧还在用旧策略采样。
所以 RL 系统里通常会拆出两个角色:
| 角色 | 作用 | MiniRL 中对应模块 |
|---|---|---|
| Actor / Trainer | 持有训练模型,执行反向传播和参数更新 | TrainingWorker、ActorWorker、FusedActorRolloutWorker |
| Rollout / Inference | 持有推理模型,负责高吞吐生成样本 | RolloutServerManager、RolloutReplica、vLLM/SGLang server |
| Checkpoint Engine | 在训练侧和推理侧之间搬运权重 | checkpoint_engine/* |
环境准备
可以通过拉取 verl 镜像来运行 minirl。
1. 拉取镜像
# vllm 推理后端镜像
docker pull quay.io/ascend/verl:verl-8.5.0-a3-ubuntu22.04-py3.11-latest
# sglang 推理后端镜像
docker pull quay.io/ascend/verl:verl-sglang-8.5.0-a3-ubuntu22.04-py3.11-latest
2. 启动容器
请根据您要使用的推理后端修改以下脚本中的 DOCKER_IMAGE 参数,并自定义 CONTAINER_NAME。
DOCKER_IMAGE=quay.io/ascend/verl:verl-xx
CONTAINER_NAME=xx
eth_name=`ifconfig |grep "$(hostname -I |awk '{print $1}'|awk -F '.' '{print $0}')" -B 1|awk -F ':' '{print$1}' | head -1 | tail -1`
docker run -it \
--net host \
--shm-size 720g \
--privileged \
--device=/dev/davinci0 \
--device=/dev/davinci1 \
--device=/dev/davinci2 \
--device=/dev/davinci3 \
--device=/dev/davinci4 \
--device=/dev/davinci5 \
--device=/dev/davinci6 \
--device=/dev/davinci7 \
--device=/dev/davinci8 \
--device=/dev/davinci9 \
--device=/dev/davinci10 \
--device=/dev/davinci11 \
--device=/dev/davinci12 \
--device=/dev/davinci13 \
--device=/dev/davinci14 \
--device=/dev/davinci15 \
--device=/dev/davinci_manager \
--device=/dev/devmm_svm \
--device=/dev/hisi_hdc \
-e LD_LIBRARY_PATH=/usr/local/Ascend/driver/lib64/driver:/usr/local/Ascend/driver/lib64/common:/usr/local/lib \
-e HCCL_SOCKET_IFNAME=$eth_name \
-e GLOO_SOCKET_IFNAME=$eth_name \
-e PYTHONHASHSEED=0 \
-e HCCL_NPU_SOCKET_PORT_RANGE="auto" \
-e HCCL_HOST_SOCKET_PORT_RANGE="auto" \
-v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/bin/msnpureport:/usr/bin/msnpureport \
-v /root/.cache:/root/.cache \
-v /home:/home \
-v /etc/hccn.conf:/etc/hccn.conf \
--name "$CONTAINER_NAME" \
"$DOCKER_IMAGE" \
/bin/bash
3. 进入容器
将以下命令中的 CONTAINER_NAME 替换成启动容器时自定义的 CONTAINER_NAME 即可。
docker exec -it CONTAINER_NAME bash
4. 安装 Mooncake
git clone -b v0.3.9 --depth 1 https://github.com/kvcache-ai/Mooncake.git
cd Mooncake
sed -i `/USE_ASCEND_DIRECT/s/OFF/ON/g` mooncake-common/common.cmake
sed -i 's|sh scripts/build_wheel.sh|bash scripts/build_wheel.sh|g' scripts/ascend/dependencies_ascend.sh
bash scripts/ascend/dependencies_ascend.sh
export HCCL_INTRA_ROCE_ENABLE=1
export ASCEND_USE_SHORT_CONNECTION=1
5. 准备 Mini-RL
git clone https://gitcode.com/Ascend/ModelZoo-PyTorch.git
cd ModelZoo-PyTorch/PyTorch/built-in/others/Mini-RL
怎么运行
在仓库根目录,直接用模块方式启动,启动前请确认镜像推理后端是否与配置文件中的推理后端名称一致。
python -m minirl.main
Note
实际运行时会夹杂 Ray、torch、vLLM、SGLang、HCCL/Gloo 等依赖日志。MiniRL 自己的业务日志都有 [MiniRL] 前缀,如果只想看训练、推理、权重同步这些关键阶段,可以这样过滤:
python -m minirl.main 2>&1 | grep --line-buffered '\[MiniRL\]'
如果只希望使用部分设备,可以在启动前设置可见卡。例如 Ascend 环境:
export ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7,8
python -m minirl.main
Warning
可见设备数量要与配置中的资源数量一致。
可以直接修改 minirl/configs/xx.yaml 来调整运行方式。也可以用命令行覆盖 YAML 配置。MiniRL 使用 OmegaConf,因此可以这样改配置:
python -m minirl.main rollout.name=sglang rollout.tensor_model_parallel_size=4
如果要切换到 minirl/configs 下的其它 YAML,可以用 config= 指定文件名:
python -m minirl.main config=separate
python -m minirl.main config=separate rollout.tensor_model_parallel_size=4
默认配置在 minirl/configs/fused.yaml:
cluster:
mode: fused
ray_address: null
actor:
n_gpus_per_node: 8
nnodes: 1
rollout:
n_gpus_per_node: 8
nnodes: 1
actor:
model_config:
local_path: /home/model/Qwen3-30B-A3B-Instruct-2507
engine_config:
backend: fsdp2
fsdp_size: 8
param_offload: true
optimizer_offload: true
optimizer_config:
lr: 1e-5
rollout:
name: sglang
local_path: ${actor.model_config.local_path}
dtype: bfloat16
gpu_memory_utilization: 0.6
free_cache_engine: true
tensor_model_parallel_size: 4
checkpoint_engine:
update_weights_bucket_megabytes: 2048
backend: hccl
root_dir: /tmp/minirl_checkpoint
debug:
verify_light_transfer: false
verify_live_weights: false
perturb_before_sync: false
perturb_delta: 0.001
最需要关注的是这几项:
| 配置 | 含义 |
|---|---|
cluster.mode |
fused 表示训练和推理共卡;separate 表示训练和推理分开用卡 |
cluster.actor.* |
actor 训练 worker 使用多少节点、每节点多少卡 |
cluster.rollout.* |
分离模式下 rollout 推理 worker 使用多少节点、每节点多少卡 |
actor.model_config.local_path |
HuggingFace 模型本地路径 |
actor.engine_config.backend |
训练后端,目前只支持 fsdp2 |
actor.engine_config.fsdp_size |
FSDP 并行组大小 |
actor.engine_config.param_offload |
是否把训练侧参数 offload 到 CPU |
actor.engine_config.optimizer_offload |
是否把优化器状态 offload 到 CPU |
rollout.name |
推理后端,支持 vllm 和 sglang |
rollout.free_cache_engine |
权重同步和训练/推理切换时是否释放推理侧 cache/weights |
rollout.tensor_model_parallel_size |
推理服务的 tensor parallel 大小 |
checkpoint_engine.backend |
权重同步后端,支持 hccl、disk、mooncake |
checkpoint_engine.debug.verify_light_transfer |
轻量传输校验开关 |
checkpoint_engine.debug.verify_live_weights |
严格 runtime 权重校验开关 |
checkpoint_engine.debug.perturb_before_sync |
调试用:从第二次同步开始给训练侧权重加扰动 |
运行成功后,过滤后的业务日志大致如下。不同配置下 backend、replica 数、端口和 digest 目录会不同,但整体阶段顺序一致:
[MiniRL] Ray initialized.
[MiniRL] Trainer initialized. Initializing workers and rollout servers...
[MiniRL] [Init] Creating Ray resource pools.
[MiniRL] [Init] Creating actor workers.
[MiniRL] [Init] Initializing actor models.
[MiniRL] Qwen3MoeForCausalLM contains 30.53B parameters
[MiniRL] Before FSDP, memory allocated (GB): 0.00, memory reserved (GB): 0.00, device memory used/total (GB): 0.38/61.27
[MiniRL] Applied FSDP wrapping.
[MiniRL] After FSDP, memory allocated (GB): 7.11, memory reserved (GB): 9.18, device memory used/total (GB): 10.03/61.27
[MiniRL] [Init] Starting rollout servers.
[MiniRL] SGLang http server: rollout_mode=<RolloutMode.HYBRID: 'hybrid'>, replica_rank=0, node_rank=0, nnodes=1, visible_devices='0,1,2,3'
[MiniRL] SGLang http server: rollout_mode=<RolloutMode.HYBRID: 'hybrid'>, replica_rank=1, node_rank=0, nnodes=1, visible_devices='4,5,6,7'
[MiniRL] Rollout servers ready: backend=sglang, replicas=2, tp_size=4, addresses=['worker-0:41417', 'worker-1:33403']
[MiniRL] [Init] Initializing checkpoint engine manager.
[MiniRL] Workers and rollout servers are ready. Starting mock RL loop...
[MiniRL] [Step 1/4] Sync initial actor weights to rollout before training.
[MiniRL] Weight sync started: sync_index=1, phase=initial, mode=fused, rollout_backend=sglang, checkpoint_engine=fused/in-process, verify_light_transfer=false, verify_live_weights=true
[MiniRL] Fused weight sync started: sync_index=1, rank=0/8, rollout_backend=sglang, free_cache_engine=true, verify_light_transfer=false, verify_live_weights=true
[MiniRL] Fused weight sync: release rollout memory before sync_index=1.
[MiniRL] Fused weight sync: resume rollout weights for sync_index=1.
[MiniRL] SGLang tensor weight update started: replica=0, tp_size=4, bucket_mb=2048, verify_light_transfer=false, verify_live_weights=true
[MiniRL] SGLang strict runtime weight verification started: replica=0, source=tensor
[MiniRL] SGLang rollout strict runtime weight verification passed: tensors=531, bytes=61064245248, live_digest_dir=/tmp/minirl_checkpoint/live_weight_digests/sglang_tensor_replica0_rollout0_..., live_digest_summary={'matched': 2124, 'mismatched': 0, 'missing': 0, 'unexpected': 0, ...}
[MiniRL] SGLang tensor weight update started: replica=1, tp_size=4, bucket_mb=2048, verify_light_transfer=false, verify_live_weights=false, configured_verify_live_weights=true
[MiniRL] Fused weight sync finished: sync_index=1, rollout_backend=sglang
[MiniRL] Weight sync finished: sync_index=1, phase=initial.
[MiniRL] [Step 2/4] Run mock rollout generation.
[MiniRL] [Rollout] mock generate backend=sglang, replicas=2,
[MiniRL] [Step 3/4] Run mock actor training.
[MiniRL] [Actor] mock train step started: backend=fsdp2, param_offload=true, optimizer_offload=true
[MiniRL] [Actor] mock train step finished.
[MiniRL] [Step 4/4] Sync trained actor weights to rollout.
[MiniRL] Weight sync started: sync_index=2, phase=trained, mode=fused, rollout_backend=sglang, checkpoint_engine=fused/in-process, verify_light_transfer=false, verify_live_weights=true
[MiniRL] Fused weight sync started: sync_index=2, rank=0/8, rollout_backend=sglang, free_cache_engine=true, verify_light_transfer=false, verify_live_weights=true
[MiniRL] Fused weight sync: resume rollout weights for sync_index=2.
[MiniRL] SGLang tensor weight update started: replica=0, tp_size=4, bucket_mb=2048, verify_light_transfer=false, verify_live_weights=true
[MiniRL] SGLang strict runtime weight verification started: replica=0, source=tensor
[MiniRL] SGLang rollout strict runtime weight verification passed: tensors=531, bytes=61064245248, live_digest_dir=/tmp/minirl_checkpoint/live_weight_digests/sglang_tensor_replica0_rollout0_..., live_digest_summary={'matched': 2124, 'mismatched': 0, 'missing': 0, 'unexpected': 0, ...}
[MiniRL] SGLang tensor weight update started: replica=1, tp_size=4, bucket_mb=2048, verify_light_transfer=false, verify_live_weights=false, configured_verify_live_weights=true
[MiniRL] Fused weight sync finished: sync_index=2, rollout_backend=sglang
[MiniRL] Weight sync finished: sync_index=2, phase=trained.
[MiniRL] Mock RL loop finished.
[MiniRL] Mini RL initialization, mock train/infer, and weight sync verified. Shutting down actors.
这说明完整的启动、推理服务初始化、权重同步和一轮 mock RL loop 已经走通。
整体流程图
flowchart TD
A[python -m minirl.main] --> B[Ray init]
B --> C[load_config]
C --> D[TaskRunner Ray Actor]
D --> E[Trainer.init_workers]
E --> F[创建 Ray resource pool]
F --> G{cluster.mode}
G -->|fused| H[FusedActorRolloutWorker]
G -->|separate| I[ActorWorker]
H --> J[初始化 FSDP actor 模型]
H --> K[在同一组 worker 上启动 rollout server]
I --> L[初始化 FSDP actor 模型]
I --> M[单独启动 rollout replica 和 checkpoint workers]
J --> N[初始权重同步]
K --> N
L --> N
M --> N
N --> O[mock rollout generation]
O --> P[mock actor training]
P --> Q[同步训练后权重到 rollout]
Q --> R[shutdown]
可以把 MiniRL 理解成一个“系统联调脚手架”:它先把训练侧和推理侧都启动起来,再验证二者之间的权重同步路径是否可用。
一轮 RL loop 里发生了什么
Trainer.fit() 里固定跑一轮:
sequenceDiagram
participant T as Trainer
participant A as Actor Workers
participant R as Rollout Servers
participant C as Checkpoint Engine
T->>A: init actor model
T->>R: init rollout server
T->>A: get actor weights
A->>R: sync initial weights
T->>R: mock_generate_sequences()
T->>A: mock_train_step()
T->>A: get updated actor weights
A->>C: send_weights()
C->>R: receive / apply weights
T->>R: shutdown
这里最重要的是两次同步:
- 训练前同步:确保 rollout 服务启动后拿到 actor 的初始权重。
- 训练后同步:模拟 actor 训练完成后,把新权重推给 rollout,下一轮生成时使用新策略。
在真实 RL 中,mock_generate_sequences 会替换成批量采样,mock_train_step 会替换成 PPO/GRPO 等算法更新。
共卡模式和分离模式
MiniRL 支持两种资源布局:fused 和 separate。
共卡模式:cluster.mode=fused
共卡模式下,训练 worker 和推理服务共用同一批 GPU。MiniRL 会创建 FusedActorRolloutWorker,每个 worker 里先初始化训练模型,再通过 rollout adapter 启动推理服务。
flowchart LR
subgraph GPU0[GPU / NPU 0]
A0[Actor shard 0]
R0[Rollout shard 0]
end
subgraph GPU1[GPU / NPU 1]
A1[Actor shard 1]
R1[Rollout shard 1]
end
A0 <--> R0
A1 <--> R1
它的特点是:
| 维度 | 说明 |
|---|---|
| 资源利用 | 适合资源紧张时使用,同一批卡既训练又推理 |
| 同步路径 | actor worker 直接把内存中的权重交给 rollout adapter |
| 显存管理 | 推理和训练交替占用显存,需要 release、resume、free_cache_engine |
| 代码入口 | FusedActorRolloutWorker.update_weights() |
| 适用场景 | 小规模验证、单机调试、资源有限的训练任务 |
共卡模式里最关键的动作是“让出显存”。在同步或训练前,rollout 侧可以释放 KV cache 或权重缓存,训练侧再恢复模型进行训练。训练完成后,再恢复 rollout 的权重和 KV cache。
分离模式:cluster.mode=separate
分离模式下,训练和推理使用不同的 GPU 资源池。actor worker 只负责训练,rollout replica 单独启动推理服务和 checkpoint workers。
flowchart LR
subgraph TrainPool[训练资源池]
A0[Actor worker 0]
A1[Actor worker 1]
end
subgraph RolloutPool[推理资源池]
R0[Rollout server 0]
R1[Rollout server 1]
end
A0 --> C[Checkpoint Engine]
A1 --> C
C --> R0
C --> R1
它的特点是:
| 维度 | 说明 |
|---|---|
| 资源利用 | 训练和推理各占一批卡,互不抢显存 |
| 同步路径 | 通过 checkpoint engine 在 actor workers 和 rollout workers 之间传权重 |
| 显存管理 | 推理服务可以常驻,更接近生产服务形态 |
| 代码入口 | CheckpointEngineManager.update_weights() |
| 适用场景 | 大规模训练、高吞吐采样、训练和推理并行调度 |
分离模式更像真实生产系统。训练池持续做参数更新,推理池持续做样本生成,中间通过权重同步机制把新策略发布到推理侧。
代码架构
MiniRL 的代码可以按“入口、训练、推理、同步、配置”来理解:
minirl/
main.py # 程序入口,初始化 Ray,启动 TaskRunner
trainer.py # 总控流程:建 worker、启动 rollout、同步权重、跑一轮 loop
configs/
fused.yaml # fused 模式配置
config.py # dataclass 配置定义和 load_config
engine/
engine_workers.py # ActorWorker / FusedActorRolloutWorker / TrainingWorker
base.py # 训练后端注册表
fsdp/ # FSDP 训练引擎实现
rollout/
rollout_server_manager.py # 管理 rollout replica
replica.py # RolloutReplica 抽象类,处理 hybrid / standalone 模式
adapter.py # rollout adapter 抽象接口
registry.py # vLLM / SGLang 后端注册
vllm_rollout/ # vLLM server、adapter、replica、启动参数
sglang_rollout/ # SGLang server、adapter、replica
checkpoint_engine/
base.py # CheckpointEngine 抽象和 manager
hccl_checkpoint_engine.py # HCCL 权重同步
disk_checkpoint_engine.py # 磁盘 checkpoint 同步
mooncake_checkpoint_engine.py # Mooncake 同步
主调用链如下:
flowchart TD
Main[main.py] --> TaskRunner[TaskRunner Ray actor]
TaskRunner --> Trainer[trainer.py]
Trainer --> ResourcePool[ResourcePoolManager]
Trainer --> ActorWorkers[ActorWorker / FusedActorRolloutWorker]
Trainer --> RolloutManager[RolloutServerManager]
ActorWorkers --> TrainingWorker[TrainingWorker]
TrainingWorker --> EngineRegistry[EngineRegistry]
EngineRegistry --> FSDP[FSDP engine]
RolloutManager --> Replica[RolloutReplica]
Replica --> VLLM[vLLMReplica / vLLMHttpServer]
Replica --> SGLang[SGLangReplica / SGLangHttpServer]
Trainer --> CheckpointManager[CheckpointEngineManager]
CheckpointManager --> HCCL[HCCL]
CheckpointManager --> Disk[Disk]
CheckpointManager --> Mooncake[Mooncake]
核心结构
1. Ray 负责资源和进程编排
MiniRL 不手动 fork 训练和推理进程,而是用 Ray actor 表达不同角色。这样可以把“哪个 worker 放在哪张卡上”交给 Ray placement group 管理。
Trainer._init_resource_pools() 会根据:
cluster:
actor:
n_gpus_per_node: 2
nnodes: 1
创建 actor resource pool。分离模式下,rollout replica 还会根据 cluster.rollout 创建自己的资源池。
2. 训练侧和推理侧都通过 registry 解耦
训练后端通过 EngineRegistry 创建:
EngineRegistry.new(
backend=self.engine_config.backend,
model_config=self.model_config,
engine_config=self.engine_config,
optimizer_config=self.optimizer_config,
)
rollout 后端通过 rollout.registry 创建:
get_rollout_adapter_class(self.rollout_config.name)
get_rollout_replica_class(self.rollout_config.name)
因此 rollout.name=vllm 和 rollout.name=sglang 对上层 Trainer 来说是同一套接口,只是底层启动方式不同。
3. 权重同步是统一接口,不绑定某一种后端
Checkpoint engine 负责回答一个问题:actor 训练侧已经拿到了新权重,rollout 推理侧怎样才能拿到同一份权重?
MiniRL 把这个过程抽象成四个关键动作:
| 方法 | 作用 |
|---|---|
prepare() |
每个 worker 准备自己的同步元信息,例如地址、端口、buffer 信息 |
build_topology() |
根据 trainer/rollout worker 数量建立通信拓扑,并给每个 worker 分配同步 rank |
send_weights() |
训练侧从 actor engine 取出参数并发送 |
receive_weights() |
推理侧接收参数,交给 rollout adapter 更新推理模型 |
在共卡模式中,FusedActorRolloutWorker 可以直接把 actor.engine.get_per_tensor_param() 交给 rollout adapter。
在分离模式中,CheckpointEngineManager 会协调 actor workers 和 rollout checkpoint workers:
sequenceDiagram
participant T as Trainer
participant A as ActorWorker
participant M as CheckpointEngineManager
participant W as Rollout CheckpointWorker
participant R as Rollout Server
T->>M: update_weights()
M->>A: prepare()
M->>W: prepare()
M->>M: build_process_group()
M->>A: send_weights()
M->>W: receive_weights()
W->>R: adapter.update_weights()
M->>A: finalize()
M->>W: finalize()
这样训练侧不用知道 rollout 后端是 vLLM 还是 SGLang,推理侧也不用知道 actor 后端是不是 FSDP。
MiniRL 当前实现了三种 checkpoint engine:
| backend | 主要方式 | 适合场景 | 特点 |
|---|---|---|---|
hccl |
使用 HCCL process group,把权重按 bucket 广播到 rollout 侧 | Ascend/NPU 上的高速同步 | 传输在设备侧完成,适合训练和推理都在 NPU 上的场景 |
disk |
训练侧 rank0 保存 HuggingFace checkpoint 到共享目录,推理侧从目录 reload | 调试、跨进程/跨服务兼容、无法直接设备通信时 | 简单直观,但依赖共享文件系统,速度通常低于设备直传 |
mooncake |
使用 Mooncake TransferEngine 做 P2P buffer 传输 | 需要更高性能的点对点传输场景 | 通过注册内存和 P2P read/write 搬运 bucket |
4. 权重同步校验
MiniRL 现在提供两类校验开关,二者不做兼容开关映射,需要分别独立控制,可以按需单独或同时打开:
| 开关 | 类型 | 是否落盘 | 主要校验什么 |
|---|---|---|---|
checkpoint_engine.debug.verify_light_transfer |
轻校验 | 不落盘 | 训练侧发出的 tensor digest 与推理侧加载前收到的 tensor digest 是否一致 |
checkpoint_engine.debug.verify_live_weights |
重校验 | 会落盘 | 推理引擎热加载完成后的 live runtime 权重是否与训练侧期望一致 |
轻校验只做在线 digest。对于 hccl 和 mooncake,digest 会从训练侧随 tensor 流带到最终推理侧加载点。disk 后端的轻校验没有实际意义,因为推理侧本来就是直接读取训练侧保存下来的 checkpoint,所以当前不做 disk 轻校验。
重校验会在权重已经写入推理引擎之后执行。它会把训练侧权重转换成推理后端 runtime layout 下的期望 digest,再让 vLLM/SGLang dump 当前 live 权重 digest,最后比较两边是否严格一致。为了降低开销,多 rollout replica 场景下只会对第一个 replica 执行重校验并 dump 一份 live 权重 digest。dump 文件会保留在:
${checkpoint_engine.root_dir}/live_weight_digests/
如果没有配置 checkpoint_engine.root_dir,默认使用:
/tmp/minirl_checkpoint/live_weight_digests/
一次重校验会生成类似这样的目录,其中目录名里的 replica0 表示只校验第一个 rollout replica:
/tmp/minirl_checkpoint/live_weight_digests/sglang_tensor_replica0_rollout0_1779346455747/
目录里的 rank_0_live_weight_digests.json、rank_1_live_weight_digests.json 等文件对应推理后端的 TP rank。
重校验通过时会看到类似日志:
SGLang rollout strict runtime weight verification passed:
matched=2124, mismatched=0, missing=0, unexpected=0
命令行里可以这样临时打开,不必改 YAML:
python -m minirl.main \
checkpoint_engine.debug.verify_live_weights=true
调试时还可以打开:
checkpoint_engine:
debug:
perturb_before_sync: true
perturb_delta: 0.001
扰动只会从第二次权重同步开始生效,因为第一次同步只是把初始 actor 权重同步到 rollout,尚未经过训练更新。
HCCL:设备侧广播权重 bucket
hccl 后端的流程可以理解成“训练侧把权重切成多个桶,然后用 HCCL 广播给 rollout 侧”。
flowchart LR
A[Actor rank0] -->|bucket metadata via ZMQ| R1[Rollout rank1]
A -->|HCCL broadcast bucket| R1
A -->|HCCL broadcast bucket| R2[Rollout rank2]
A -->|HCCL broadcast bucket| R3[Rollout rank3]
实现上,HCCLCheckpointEngine.send_weights() 会:
- 从 actor engine 拿到
(name, tensor)权重流。 - 把 tensor 放入固定大小的
send_buf。 - 每个 bucket 附带一份 metadata,记录 tensor 名字、shape、dtype、offset。
- 用 HCCL broadcast 把 buffer 发到 rollout ranks。
- rollout 侧
receive_weights()按 metadata 从 buffer 中还原出每个 tensor。
如果开启 checkpoint_engine.debug.verify_light_transfer,metadata 里还会带上 digest,用于最终在推理侧加载前校验收到的 tensor 是否与训练侧发出的 tensor 一致。
Disk:保存完整 HuggingFace checkpoint 后 reload
disk 后端不走 tensor streaming。它的方式更像一次“保存并重新加载模型”:
sequenceDiagram
participant A as Actor rank0
participant D as Shared Disk
participant W as Rollout CheckpointWorker
participant R as Rollout Server
A->>D: save_pretrained(sync_dir)
A->>D: write READY marker
W->>D: wait READY
W->>R: adapter.update_weights_from_disk(sync_dir)
R->>D: reload checkpoint
DiskCheckpointEngine.send_weights() 只在训练侧 rank0 真正保存 checkpoint,其他 actor rank 会消费权重流但不落盘。保存完成后,它会写一个 READY 文件。rollout 侧的 checkpoint worker 等到 READY 出现后,不调用 receive_weights(),而是调用 get_checkpoint_path() 拿到目录,再让推理后端从磁盘 reload。
这个后端的优势是最容易理解,也最容易排查问题;缺点是依赖共享存储,速度和文件系统性能强相关。
Mooncake:P2P buffer 传输
mooncake 后端使用 Mooncake TransferEngine。它会注册设备内存 buffer,然后 rollout 侧通过 P2P read 从上一个 rank 的 buffer 里读取数据。
flowchart LR
A[Actor rank0 buffer] -->|TransferEngine read| R1[Rollout rank1 buffer]
R1 -->|forward metadata / buffer| R2[Rollout rank2 buffer]
R2 -->|forward metadata / buffer| R3[Rollout rank3 buffer]
可以把它理解成更底层的高性能搬运方式:
- 初始化时注册两块 bucket buffer 和一块 magic buffer。
- 训练侧把权重写入当前 bucket。
- 训练侧把 bucket metadata 发给 rollout rank。
- rollout rank 用
transfer_sync_read读取远端 buffer。 - 读取完成后写 magic 标记,通知发送侧这个 bucket 可以复用。
这类后端更偏性能工程,适合对权重同步耗时比较敏感的场景。
5. Rollout adapter 怎样把权重更新到推理后端
Checkpoint engine 只负责“把权重送到 rollout 侧”。权重到了 rollout 侧以后,还需要真正写进 vLLM 或 SGLang 的推理模型里,这一步由 rollout adapter 负责。
MiniRL 用 BaseRolloutAdapter 把上层需要的能力收敛成几个动作:
| 方法 | 作用 |
|---|---|
update_weights(weights, ...) |
把 checkpoint engine 收到的新权重更新进推理模型 |
update_weights_from_disk(path, ...) |
对 disk 后端,从 checkpoint 目录直接 reload |
Trainer 只关心“让 rollout 准备接收权重”和“把权重发过去”,不关心 vLLM/SGLang 的内部细节。
vLLM adapter:IPC / 共享内存传 bucket,再调用 model.load_weights
vLLM 的更新路径如下:
sequenceDiagram
participant W as Rollout CheckpointWorker
participant A as vLLM ServerAdapter
participant S as vLLMHttpServer
participant E as vLLM worker extension
participant M as vLLM model
W->>A: update_weights(weights)
A->>S: collective_rpc(update_weights_from_ipc)
A->>E: BucketedWeightSender sends buckets
E->>E: BucketedWeightReceiver receives buckets
E->>M: model.load_weights(weights)
A->>S: clear_kv_cache()
vLLM ServerAdapter.update_weights() 做了几件事:
- 通过 Ray 找到对应的
vllm_server_{replica_rank}_{node_rank}。 - 调用 server 的
collective_rpc("update_weights_from_ipc"),让 vLLM worker extension 准备接收权重。 - 使用
BucketedWeightSender把权重按 bucket 发出去。 - 如果当前设备不支持 IPC,就自动回退到 shared memory。
- vLLM worker extension 收到 bucket 后调用
self.model_runner.model.load_weights(weights)。 - 更新完成后清理 prefix/KV cache。
如果 checkpoint backend 是 disk,则不走 bucket 传输,而是:
flowchart LR
D[checkpoint directory] --> S[vLLM update_weights_from_disk]
S --> L[vLLM model loader]
L --> M[reload model weights]
M --> C[clear KV cache]
也就是 adapter 调用 update_weights_from_disk(model_path),vLLM extension 使用 vLLM 的 model loader 从磁盘重新加载权重。
SGLang adapter:用 SGLang weight sync 工具更新 tensor
SGLang 的 adapter 会先懒加载 HTTP client,连接到对应的 sglang_server_{replica_rank}_{node_rank}。tensor 更新路径是:
sequenceDiagram
participant W as Rollout CheckpointWorker
participant A as SGLang ServerAdapter
participant H as AsyncHttpServerAdapter
participant S as SGLang engine
W->>A: update_weights(weights)
A->>A: group tensors into buckets
A->>H: sgl_update_weights(params_batch)
H->>S: update_weights_from_tensor
A->>H: flush_cache()
SGLang ServerAdapter.update_weights() 的核心是:
- 按
checkpoint_engine.update_weights_bucket_megabytes把权重分桶。 - 对每个 bucket 调用 SGLang 的
sgl_update_weights(...)。 - 通过
device_mesh["infer_tp"]确定 tensor parallel 组内如何分发权重。 - 更新完成后调用
flush_cache(),避免旧 cache 继续影响新权重。
vLLM 和 SGLang 在 MiniRL 中的角色
MiniRL 支持两种 rollout 后端:
| 后端 | 配置 | 作用 |
|---|---|---|
| vLLM | rollout.name: vllm |
启动 OpenAI API 风格的 vLLM server,支持 sleep mode 和权重更新 |
| SGLang | rollout.name: sglang |
启动 SGLang HTTP server,通过 adapter 管理权重和缓存 |
对使用者来说,切换后端主要改配置:
python -m minirl.main rollout.name=sglang
但需要保证环境里已经安装对应推理后端。
应该从哪里开始读代码?
建议按这个顺序:
minirl/main.py:看程序怎么启动。minirl/trainer.py:看总流程怎么串起来。minirl/engine/engine_workers.py:看 actor worker 和 fused worker 的区别。minirl/rollout/rollout_server_manager.py:看 rollout replica 怎么创建。minirl/checkpoint_engine/base.py:看分离模式下权重同步怎么协调。minirl/rollout/vllm_rollout/或minirl/rollout/sglang_rollout/:看具体推理后端如何接入。