常见错误与性能调优
RLHF 是"训练框架 + 推理引擎 + 调度系统"的合奏,故障点比纯训练框架多。本章按"出错时间线"组织。
11.1 启动阶段错误
| 现象 | 原因 | 修复 |
|---|---|---|
ImportError: cannot import name '...' from 'tensordict' | tensordict 版本不在锁定区间 | 降到 0.8.x |
ray.exceptions.RaySystemError: ... port already in use | 之前 Ray 实例没关 | ray stop --force 再重启 |
FileNotFoundError: examples/data_preprocess/gsm8k.py | recipe 子仓没拉 | git submodule update --init --recursive recipe |
Hydra 报 could not override 'xxx' | 字段不存在 | 看第 8 章 8.6 节查字段 |
HYDRA_FULL_ERROR not set | 报错信息被截 | HYDRA_FULL_ERROR=1 |
| vLLM init 报 CUDA mismatch | PyTorch / CUDA / vLLM 版本不一致 | 用 docker 镜像 |
| Megatron 加载 HF checkpoint 失败 | 没转格式 | 用 Megatron-Bridge 转 |
11.2 训练阶段错误
| 现象 | 原因 | 修复 |
|---|---|---|
| rollout 阶段 OOM | vLLM gpu_memory_utilization 太大 | 降到 0.5;升 rollout TP |
| 训练阶段 OOM | actor 显存不够 | 开 FSDP offload;降 ppo_micro_batch_size_per_gpu;开 grad ckpt |
| resharding 阶段 OOM | weight gather 占满显存 | 开 streaming sharding;缩 actor TP |
| NCCL hang(多卡) | 某 worker 卡住 | NCCL_DEBUG=INFO;查异常 rank |
| 训练后 logp 与 rollout logp 差异大 | 训推 mismatch | 开 rollout_correction 或换 vexact |
| 第一步训练就 NaN | 数据 / 模板坏 | 查 prompt template;查样本 token id 越界 |
| 训几十步后 NaN | 梯度爆炸 | 开 grad_clip;降 lr |
| Ray actor 自死 | worker 内部异常 | 看 ~/ray_results/<exp>/ 下 stderr |
11.3 算法层面的"不对劲"
| 症状 | 多半原因 | 调哪几个字段 |
|---|---|---|
| reward 不涨 | lr 太小 / KL 太重 | 升 actor.optim.lr;降 kl_loss_coef |
| reward 飙升但生成变蠢 | reward hacking | 补 format / length 惩罚;RM 多样性 |
| KL 失控 | lr 高 + KL 系数低 | 降 lr;升 kl_loss_coef;clip 收紧 |
| response 越训越短 | 短回答 reward 更高 | 加长度奖励;用 SimPO 风格归一 |
| response 越训越长(GRPO 常见) | 长 reward 高 + token-level loss 没开 | 开 DAPO 风格 token-level loss |
| 熵坍塌 | 策略变确定性 | 升 entropy_coef;clip-higher |
| 采样多样性差 | temperature 太低 | 升到 1.0 - 1.2 |
11.4 性能调优清单
训练阶段 throughput 低
- 开
actor.use_remove_padding=true(变长去 pad); - 开
actor.enable_gradient_checkpointing=true(激活重算); - 开
liger-kernel:actor.use_liger=true; - 开
torch.compile:actor.use_torch_compile=true; - 升
actor.ppo_micro_batch_size_per_gpu(显存允许下); - FlashAttention:
actor.use_fused_kernels=true。
rollout 阶段慢
- 升
rollout.max_num_batched_tokens; - 开
rollout.enable_chunked_prefill=true; - 降
rollout.tensor_model_parallel_size(小模型 TP 不要太大); - 升
rollout.gpu_memory_utilization(同时小心 OOM); - 试 SGLang 后端做对比;
- 升
rollout.max_num_seqs。
resharding 慢
- 开 streaming weight transfer;
- 对 MoE 开 router replay;
- 训推后端权重布局要对齐(FSDP + vLLM 默认 OK,Megatron + SGLang 要看 sharding_manager 是否支持)。
11.5 监控指标速查
| 指标 | 正常区间 | 异常预警 |
|---|---|---|
reward/mean | 缓慢上升 | 飙升 → 检查 reward 设计 |
kl/mean | 0.001 - 0.1 | > 1 → 策略漂移严重 |
actor/entropy | 2 - 6 | < 1 → 熵坍塌 |
actor/grad_norm | 0.1 - 1 | > 10 → 梯度爆炸 |
actor/ppo_kl | < clip_ratio | 触顶 → ratio 经常被 clip |
actor/clip_frac | 0.05 - 0.3 | > 0.5 → lr 过高 |
response_length/mean | 稳定或缓升 | 暴涨 → length hacking |
throughput/rollout | — | 跌 → 网络 / vLLM 异常 |
11.6 多机训练专属问题
| 现象 | 修复 |
|---|---|
| Ray dashboard 跨机看不到 worker | 检查 ray_init.address;防火墙开 10001 / 8265 端口 |
| NCCL 跨机带宽极低 | 查 nvidia-smi topo -m;启用 RDMA / IB |
| checkpoint 存到 NFS 慢 | checkpoint 写本地,定期 rsync 到 NFS |
| 多机时 Hydra 找不到 train_files | 共享文件系统挂同路径;或 parquet 提前 copy 到每节点 |
| 跨机 ray worker 启动失败 | 检查 ssh 免密;RAY_BACKEND_LOG_LEVEL=debug |
11.7 提 issue 的最小信息清单
- verl commit hash:
git -C /path/to/verl rev-parse HEAD; - 关键依赖:
pip list | grep -i 'torch\|vllm\|sglang\|ray\|hydra\|tensordict'; - OS + CUDA + driver:
uname -a; nvcc --version; nvidia-smi; - 启动命令 + Hydra
--cfg job输出; - 完整 stderr(含 Ray worker stderr,路径在
~/ray_results/); - 能否在
NaiveWorkerGroup模式(绕过 Ray)复现?
11.9 TransferQueue:rollout → train 的零拷贝管道
main_ppo_sync.py(同步路径)和 fully_async 都用 TransferQueue 取代默认的 ray put/get。verl/experimental/transfer_queue/ 里的实现要点:
- 零 padding:传 ragged batch,不强迫 fixed-length;
- 零拷贝:sender 直接把 GPU tensor 句柄写进 queue,receiver 直接 import;
- flow control:可设上限避免 rollout 堆压 train;
- 多 producer / 多 consumer:fully_async 时多个 rollout actor 同时往 queue 推。
启用:
algorithm:
transfer_queue:
enabled: true
max_queue_length: 256 # 队列长度上限
启用后 main 入口会自动设 TRANSFER_QUEUE_ENABLE=1 环境变量(main_ppo.py:71)。看监控时多了 tq/queue_len 和 tq/wait_time_ms 两个指标——前者长期满 = train 跟不上 rollout,后者长 = rollout 跟不上 train。
11.10 Fully Async 与 One-Step Off-Policy 实战数据
这两个特性详细机制见 ch05.13–5.14,本节给"什么时候选哪个"的决策表。README 公布的 benchmark:
| 配置 | 硬件 / 模型 | 相对 colocate-sync 加速 | 最终精度 |
|---|---|---|---|
| colocate sync(baseline) | 128 卡 Qwen2.5-7B DAPO | 1.00× | baseline |
| one-step off-policy (Megatron) | Qwen2.5-Math-7B DAPO | +23–40% | 65.7% AIME(vs 60.5%) |
| fully_async (FSDP) | 128 卡 Qwen2.5-7B DAPO | 2.35× | 持平 baseline |
| fully_async (Megatron) | 同上 | 2.67× | 持平 baseline |
实战选型经验:
- ≤ 64 GPU 训中等模型:colocate sync 即可,调参省心;
- 64–256 GPU + 中长 CoT:one_step_off_policy,配合 Megatron;
- ≥ 256 GPU + 长 R1 风格:fully_async,rollout / train 资源池必须分开。
11.11 Checkpoint engine:5 种后端
verl/checkpoint_engine/ 提供多后端 checkpoint 写入。读 checkpoint_engine/base.py 能看到统一接口,下面是已经有的实现:
| 后端 | 用途 | 典型选择 |
|---|---|---|
nccl | 纯 NCCL collective 同步 checkpoint,无外部依赖 | fully_async 默认 |
hccl | 华为昇腾 NPU 等价 | NPU 集群 |
kimi | Kimi(月之暗面)私有协议 | 商业部署,需要授权 |
mooncake | Mooncake KV / weight 缓存 | 分离训练-推理集群 |
nixl | NVIDIA NIXL(GPUDirect RDMA) | InfiniBand 集群 |
选项分散在 rollout.checkpoint_engine 子段。一般用户用 NCCL;多机 IB 集群可以试 NIXL;Mooncake / Kimi 是合作客户的扩展点,社区版本默认不启用。
11.12 Router Replay 对 MoE 训练吞吐的影响
ch06.10 已经讲了 R2 / R3 的语义,这里给"开了 router replay 之后实测吞吐变化"参考(这是 README 没强调但很重要的事实):
| router_replay.mode | 训练吞吐相对 disabled | 训练稳定性 |
|---|---|---|
disabled | 1.00× | ★ IS ratio 大方差,DAPO 可能不收敛 |
R2 | 0.95× | ★★★ 训练侧 replay rollout 路由,稳定 |
R3 | 0.85× | ★★★★ 双向 replay,最稳但有跨 step 同步开销 |
"5% 吞吐换收敛性"对于 671B / 235B 这种 MoE 是非常划算的——不开 R2 / R3 可能整个训练发散,等于 -100%。
11.13 compute_throughout_metrics 的指标怎么读
ray_trainer.py:1733 的 compute_throughout_metrics 每步会输出 throughput 相关指标。这套指标是判断"我哪里是瓶颈"的关键,速查表:
| 指标 | 含义 | 判读 |
|---|---|---|
throughput/total_tokens_per_sec | 全局每秒 token | 跌 = 整体瓶颈 |
throughput/gen_tokens_per_sec_per_gpu | 单卡 rollout 速度 | 跌 = vLLM/SGLang 问题;查 KV cache 是否打满 |
throughput/train_tokens_per_sec_per_gpu | 单卡训练速度 | 跌 = engine 问题;查 dynamic_bsz / sequence packing |
timing/gen | rollout wall time | 占总时间一半以上 = 推理瓶颈 |
timing/old_log_prob | old log_prob forward 时间 | 意外 > 0 时 = bypass_mode 没开 |
timing/adv | advantage 计算时间 | GRPO 应当 < 100ms |
timing/update_critic | critic 更新时间 | GRPO 时应为 0 |
timing/update_actor | actor 更新时间 | 看 ppo_epochs × ppo_mini_batch_size 是否合理 |
"为什么 verl 训练这么慢?"绝大多数情况是 timing/gen 大头——往 vLLM / SGLang 这边查,而不是 actor 那边。
11.8 心法
- 能用 docker 不要自己装——把环境问题排除掉一半;
- 小批样本先 dry run——
data.train_batch_size=8 max_response_length=256,验证 reward 与训练循环; - 关注监控指标的变化形态而不是绝对值——一个抖动 / 突变比一个高低值更值得排查。