Chapter 11
常见错误:Ray 死锁、显存爆、KL 失控
OpenRLHF 的故障范围比纯训练框架大一档,因为多了 Ray 调度 + vLLM 异步 + 4 个角色协同。 本章按"故障发生在哪个阶段"组织,给一份"看到这种现象 → 去哪里查"的对照表。
11.1故障调试金线
遇到任何问题,按这个顺序排查:
- 看 Ray dashboard:
http://<head_ip>:8265,看哪个 actor 是 DEAD; - 看那个 actor 的 stderr:在 dashboard 里点进去,或
ray logs actor <ID>; - 看 Ray placement_group:
ray list placement-groups; - 看 NCCL debug log:
NCCL_DEBUG=INFO; - 用 max_samples=8 dry run:确认配置本身不是问题;
- 对照 examples/scripts/ 里的官方脚本:diff 自己的脚本,看缺啥。
11.2启动阶段错误
| 现象 | 原因 | 修复 |
|---|---|---|
ray.exceptions.RaySystemError |
之前的 Ray 实例没关 | ray stop --force 再 ray start --head |
PlacementGroup waiting forever |
请求的 GPU 资源 > 实际拥有 | ray status 看实际资源;检查 --xxx.num_gpus_per_node |
| vLLM init 极慢(> 10 分钟) | flash-attn 编译 CUDA arch 不匹配 | export TORCH_CUDA_ARCH_LIST="8.0;9.0" |
RuntimeError: CUDA out of memory 启动时 |
colocate 模式但没开 sleep | 加 --vllm.enable_sleep --ds.enable_sleep |
ImportError: cannot import LLM from vllm |
vLLM 版本不兼容 | 对照 requirements.txt 重装;考虑用 docker |
| 模型下载超时 | HF Hub 访问慢 | 国内用 export HF_ENDPOINT=https://hf-mirror.com |
| tokenizer chat template 缺失 | base 模型没有 chat_template | 显式传 --data.tokenizer_chat_template |
11.3训练阶段错误
OOM
| 现象 | 原因 | 修复路径 |
|---|---|---|
| rollout 阶段 OOM | vLLM gpu_memory_utilization 太大 |
降到 0.4-0.5 |
| 训练阶段 OOM | actor 训练显存超 | 开 gradient_checkpointing、降 max_tokens_per_gpu、上 adam_offload |
| resharding 阶段 OOM | actor 权重 gather 后峰值过大 | 用 fully distributed 模式;或加 TP |
| 第 N 步突然 OOM | 遇到一条特别长的样本 | 开 --data.max_len 卡上限;filter overlong samples |
| checkpoint 保存时 OOM | ZeRO-3 gather 完整权重 | 用 ckpt_ds_zero_to_universal.sh 分布式 save |
NCCL hang
| 现象 | 原因 | 修复 |
|---|---|---|
| 训练前几步 hang | actor↔vLLM 的 NCCL group 没建好 | 看 actor 和 vllm 进程 stderr,对齐 master_addr / master_port |
| 多机训练 hang | 跨节点网络不通 | NCCL_DEBUG=INFO + NCCL_IB_DISABLE=0 |
| 训练若干 step 后 hang | 某 actor crash 但其他还在等 RPC | Ray dashboard 找 DEAD actor,看其 stderr |
| NCCL timeout | 默认 30 分钟太短 | export NCCL_TIMEOUT=3600 |
NaN / 数值不稳
| 现象 | 原因 | 修复 |
|---|---|---|
| 第 1 步就 NaN | 数据格式坏 / template 错 | 跑 SFT 模型 baseline 验证数据 |
| 训了几十步 NaN | 梯度爆炸 / fp16 数值范围窄 | 用 bf16;开 --algo.adam.grad_clip 1.0 |
| policy_loss = inf | importance ratio 飘到极端值 | 降 lr;开 --algo.advantage.is_correction_enable |
| value_loss 不下降 | critic lr 太小或没初始化好 | critic lr 升到 1e-5;检查是否从 RM 初始化 |
11.4算法层的"不对劲"
| 症状 | 多半原因 | 调哪几个字段 |
|---|---|---|
| reward 不涨 | lr 太小 / KL 太重 | 升 actor.adam.lr;降 algo.kl.init_coef |
| reward 飙升但生成质量降 | reward hacking | 补 length / format 惩罚;缩 epoch |
| KL 失控(涨到 100+) | lr 太大 / KL coef 太小 | 开 algo.kl.use_adaptive;降 lr |
| 熵坍塌(entropy → 0) | 策略变 deterministic | 升 temperature;clip-higher(DAPO 风格) |
| response 越训越长(GRPO 常见) | 长 response reward 更高 | 开 token-level loss;加 length 惩罚 |
| 训了一会儿模型变蠢 | policy 漂离 reference 过远 | 升 kl.init_coef;停训重启 lr 减半 |
| clipfrac > 0.5 | lr 过高 / clip 过紧 | 降 lr;clip 放宽到 0.3 |
| grad_norm 爆涨 | 梯度异常 | 开 --algo.adam.grad_clip 1.0 |
11.5vLLM 相关问题
| 现象 | 修复 |
|---|---|
| 权重同步慢 | 检查 --vllm.sync_backend nccl 而不是 gloo;NVLink 是否通 |
| rollout 阶段速度抖动 | 关 --vllm.enforce_eager,让 CUDA graph 优化 |
| vLLM logits 和训练 logits 偏差大 | 开 --algo.advantage.is_correction_enable |
| sleep mode 报错 | vLLM 版本 < 0.9 不支持,升级或去掉 enable_sleep |
vLLM init 时报 memory utilization too high |
colocate 时降 gpu_memory_utilization 到 0.4 |
11.6多机训练专属问题
| 现象 | 修复 |
|---|---|
| Ray worker 跨机连不上 head | 防火墙开 10001 / 8265 / 6379 端口 |
| 跨节点 NCCL 带宽极低(< 10 GB/s) | 查 nvidia-smi topo -m;IB 没启用 |
| 跨节点 placement_group 永远 PENDING | STRICT_PACK 凑不齐;改 PACK 或减少需求 |
| checkpoint 路径在某节点找不到 | 用 NFS / S3 / 共享存储 |
| SLURM 调度时间不同步 | 所有节点 NTP 校时 |
11.7监控指标速查(再贴一次)
| 指标 | 正常区间 | 异常预警 |
|---|---|---|
reward/mean | 缓慢上升 | 飞涨 → 检查 reward 设计 |
kl_coef | 初值附近浮动 | 飙升 → policy 漂离过快 |
kl | 0.001 – 0.1 | > 1 → 策略坏了 |
actor/entropy | 2 – 6 | < 1 → 熵坍塌 |
actor/grad_norm | 0.1 – 1 | > 10 → 梯度爆炸 |
actor/clipfrac | 0.05 – 0.3 | > 0.5 → lr 过高 |
response_length/mean | 稳定或缓升 | 暴涨 → length hacking |
throughput/sps | — | 跌 → 网络 / vLLM 异常 |
11.8提 issue 的最小清单
- OpenRLHF commit + version;
pip list | grep -iE 'torch|vllm|deepspeed|ray|flash|transformers';- 启动脚本(隐去模型路径);
- Ray dashboard 截图 + DEAD actor 的 stderr;
NCCL_DEBUG=INFO的 log;- 能否在
max_samples=8上复现? - 能否在 docker 镜像里复现?
11.9异步训练(async_enable)的踩坑
2025-05 引入的 --train.async_enable 让 generate 和 train 并行(ch08.10 提过)。新坑:
| 现象 | 真实原因 | 修复 |
|---|---|---|
| 开了 async 后训练精度反而降 | generate 用的是 N step 前的 policy → off-policy 太严重 | 调小 --train.async_queue_size(默认 4,可降到 2) |
| 开 async + colocate_all 启动报错 | 两个互斥 | async 时不要 colocate_all,可保留 colocate_actor_ref |
| IS ratio 经常 > 2 或 < 0.5 | off-policy 偏离过远 | 开 --algo.advantage.is_correction_enable --algo.is.mode icepop |
| vLLM 卡在 generate 不返回 | async 模式下 vLLM 任务 queue 满 | 降 --train.async_queue_size 或调大 --vllm.num_engines |
11.10Partial rollout 模式的 caveat
OpenRLHF 还有个更激进的 --train.partial_rollout_enable:让 vLLM 在中途 pause / resume,跟训练步交错执行。优点:吞吐再提 20-40%;代价:
- vLLM ≥ 0.8 才支持 pause/resume API;
- 所有 reward function 必须支持"半完成 trajectory"(要么算部分 reward,要么 return 0 等下次);
- 调试比 async 更难——hang 时 vLLM 的内部状态机不容易看;
- 不要和 multi-turn agent 同开(语义冲突)。
11.11Dynamic batch 的设置陷阱
--train.dynamic_batch_enable + --train.max_tokens_per_gpu 让 batch 按 token 数算(而不是按样本数)。看起来很美,但有两个常见坑:
| 坑 | 避坑 |
|---|---|
max_tokens_per_gpu 设太大,OOM 在某个长样本 | 从 8192 起调;ZeRO-3 时翻倍要小心 |
| batch 数不稳定,wandb 上看 step 时间忽长忽短 | 正常现象;按 token / sec 而不是 step / sec 评估吞吐 |
| grad_accum 算不对 | dynamic batch 自动调整 grad_accum_steps 使 token 总数 = global_batch_token_size |
| 跟 packing_samples 重复 | 两者可以同开,packing 在数据侧,dynamic batch 在调度侧 |
11.12Ring Attention 长 CoT 启用 checklist
训长 CoT(> 16K context)时几乎一定要开 ring attention。但开错一个 flag 就 hang。来源 openrlhf/models/ring_attn_utils.py:9。
- 必须开 flash-attn 2,eager / SDPA 与 ring attn 不兼容;
--ds.ring_attn_size必须能整除actor.num_gpus_per_node × actor.num_nodes;- 不要同时开
tensor_parallel_size > 1和ring_attn_size > 1,除非显式构建 3D mesh; - vLLM 端
--vllm.tensor_parallel_size独立配,跟训练 ring_attn 不挂钩; - packing_samples 必开(否则 ring attn 没意义)。
11.13心法
三句话
1. 能用 docker 不要自己装——把环境问题排除掉一半;
2. max_samples=8 dry run——任何修改先 dry run 验证;
3. 看 Ray dashboard 找 DEAD actor——RLHF 多进程 hang 几乎都是某个 actor 死了,其他在等。