文件最后提交记录最后更新时间
Update Mini-RL Co-authored-by: Rose_is_Rosie<2362225813@qq.com> # message auto-generated for no-merge-commit merge: !7572 merge minirl into master Update Mini-RL Created-by: jin-yongxu Commit-by: Rose_is_Rosie Merged-by: ascend-robot Description: ## Motivation Add Mini-RL, a minimal example for RL training and rollout weight synchronization. ## Checklist **Before PR**: - [ ] The new code needs to comply with the Clean Code specification. - [ ] The PR content is self-checked, and the expression can be clear and the writing standardized **After PR**: - [x] CLA has been signed and all committers have signed the CLA in this PR. - [ ] The ci-pipeline is passed, Code Check is passed. See merge request: Ascend/ModelZoo-PyTorch!757213 天前
update minirl readme Co-authored-by: Rose_is_Rosie<2362225813@qq.com> # message auto-generated for no-merge-commit merge: !7580 merge minirl into master update minirl readme Created-by: jin-yongxu Commit-by: Rose_is_Rosie Merged-by: ascend-robot Description: ## Motivation update minirl readme ## Checklist **Before PR**: - [ ] The new code needs to comply with the Clean Code specification. - [ ] The PR content is self-checked, and the expression can be clear and the writing standardized **After PR**: - [ ] CLA has been signed and all committers have signed the CLA in this PR. - [ ] The ci-pipeline is passed, Code Check is passed. See merge request: Ascend/ModelZoo-PyTorch!75806 天前
README.md

MiniRL:用最小代码看懂 RL 训推同步

MiniRL 是一个参考 verl 实现思路、面向大模型强化学习流程的最小化示例。它不试图一次性实现完整 PPO/GRPO 算法,而是把 RL 系统里最容易让新人困惑的一段链路单独拎出来:

训练进程更新了模型参数之后,推理服务如何拿到最新权重,并继续生成下一批样本?

在真实 RLHF/RLAIF/Agent RL 系统里,训练和推理通常不是一个普通 Python 函数调用。训练侧可能由 FSDP 管理,推理侧可能由 vLLM 或 SGLang 提供服务,它们的显存管理方式、进程模型、并行方式都不一样。MiniRL 的价值就是用一套尽量短的代码,把这些组件如何启动、如何共用或分离 GPU、如何同步权重串起来。

当前 MiniRL 默认执行一轮验证流程:

  1. 启动 Ray。
  2. 创建 actor 训练 worker。
  3. 启动 rollout 推理服务。
  4. 先把 actor 初始权重同步到 rollout。
  5. 执行一次 mock rollout generation。
  6. 执行一次 mock actor training。
  7. 再把训练后的 actor 权重同步到 rollout。
  8. 清理 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 持有训练模型,执行反向传播和参数更新 TrainingWorkerActorWorkerFusedActorRolloutWorker
Rollout / Inference 持有推理模型,负责高吞吐生成样本 RolloutServerManagerRolloutReplica、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 推理后端,支持 vllmsglang
rollout.free_cache_engine 权重同步和训练/推理切换时是否释放推理侧 cache/weights
rollout.tensor_model_parallel_size 推理服务的 tensor parallel 大小
checkpoint_engine.backend 权重同步后端,支持 hccldiskmooncake
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

这里最重要的是两次同步:

  1. 训练前同步:确保 rollout 服务启动后拿到 actor 的初始权重。
  2. 训练后同步:模拟 actor 训练完成后,把新权重推给 rollout,下一轮生成时使用新策略。

在真实 RL 中,mock_generate_sequences 会替换成批量采样,mock_train_step 会替换成 PPO/GRPO 等算法更新。

共卡模式和分离模式

MiniRL 支持两种资源布局:fusedseparate

共卡模式: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
显存管理 推理和训练交替占用显存,需要 releaseresumefree_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=vllmrollout.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。对于 hcclmooncake,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.jsonrank_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() 会:

  1. 从 actor engine 拿到 (name, tensor) 权重流。
  2. 把 tensor 放入固定大小的 send_buf
  3. 每个 bucket 附带一份 metadata,记录 tensor 名字、shape、dtype、offset。
  4. 用 HCCL broadcast 把 buffer 发到 rollout ranks。
  5. 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]

可以把它理解成更底层的高性能搬运方式:

  1. 初始化时注册两块 bucket buffer 和一块 magic buffer。
  2. 训练侧把权重写入当前 bucket。
  3. 训练侧把 bucket metadata 发给 rollout rank。
  4. rollout rank 用 transfer_sync_read 读取远端 buffer。
  5. 读取完成后写 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() 做了几件事:

  1. 通过 Ray 找到对应的 vllm_server_{replica_rank}_{node_rank}
  2. 调用 server 的 collective_rpc("update_weights_from_ipc"),让 vLLM worker extension 准备接收权重。
  3. 使用 BucketedWeightSender 把权重按 bucket 发出去。
  4. 如果当前设备不支持 IPC,就自动回退到 shared memory。
  5. vLLM worker extension 收到 bucket 后调用 self.model_runner.model.load_weights(weights)
  6. 更新完成后清理 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() 的核心是:

  1. checkpoint_engine.update_weights_bucket_megabytes 把权重分桶。
  2. 对每个 bucket 调用 SGLang 的 sgl_update_weights(...)
  3. 通过 device_mesh["infer_tp"] 确定 tensor parallel 组内如何分发权重。
  4. 更新完成后调用 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

但需要保证环境里已经安装对应推理后端。

应该从哪里开始读代码?

建议按这个顺序:

  1. minirl/main.py:看程序怎么启动。
  2. minirl/trainer.py:看总流程怎么串起来。
  3. minirl/engine/engine_workers.py:看 actor worker 和 fused worker 的区别。
  4. minirl/rollout/rollout_server_manager.py:看 rollout replica 怎么创建。
  5. minirl/checkpoint_engine/base.py:看分离模式下权重同步怎么协调。
  6. minirl/rollout/vllm_rollout/minirl/rollout/sglang_rollout/:看具体推理后端如何接入。