RRLforge
实验预计 40 分钟

训推同卡:colocate 的显存怎么分

单卡上训练和推理抢同一块 32GB。这一节把 colocate 模式调稳,并知道它比多卡慢在哪。

学完这节你能做到

  • 说清 colocate 与 server 两种模式的区别,以及单卡为什么只能用前者
  • 调出一组不 OOM 又不浪费的显存分配参数
  • 解释 sleep mode 在省什么,以及为什么 colocate 必然比 server 慢

两种模式

RL 训练需要一个推理引擎持续产出数据。它和训练进程的关系有两种安排:

server 模式(多卡)
┌────────────┐   HTTP   ┌────────────┐
│  训练进程   │ ──────→ │ vLLM 服务   │
│  卡 4-7    │ ←────── │  卡 0-3     │
└────────────┘          └────────────┘
两边同时工作,采样和训练可以重叠

colocate 模式(单卡)
┌──────────────────────────────┐
│      同一张卡,交替执行         │
│  采样 ████    训练 ████        │
│      ████        ████         │
└──────────────────────────────┘
一个时刻只有一件事在跑
servercolocate
卡数要求≥21 张就行
采样与训练可以重叠严格交替
通信HTTP / RPC显存内
权重同步跨进程,要序列化显存拷贝,快
速度明显慢

单卡只能用 colocate。 这不是配置选择,是物理约束——一张卡不可能同时跑训练 kernel 和生成 kernel。

colocate 慢多少

慢在两处:

  1. 不能重叠。server 模式下采样和训练并行,单步墙钟时间约等于 max(采样, 训练);colocate 是 采样 + 训练
  2. 切换开销。每轮要 sleep/wake,显存释放和重新申请要几秒。

按 L2 那次实测:采样 30s + 训练 12s + 切换 3s = 45s。如果能重叠,理想情况是 30s。colocate 的代价大约是 1.5 倍。

单卡上这不是取舍,是唯一选项。接受它,然后优化能优化的部分。

核心参数:显存怎么划

vllm_gpu_memory_utilization(slime 里叫 --sglang-mem-fraction-static)决定给 vLLM 多少。

关键认知:这是预先占用,不是按需申请。 vLLM 启动时就把这个比例的显存吃掉,用来放权重和 KV cache 池。

所以单卡上你必须手工划一条线:

32 GiB
├── vLLM        util × 32     ← 你划的
└── 训练侧      剩下的

怎么定这条线?用 L0 的显存计算器:

  1. 算出训练侧需要多少(权重 + 梯度 + 优化器 + 激活 + logits)
  2. 留 1.5 GiB 给 CUDA context 和碎片
  3. 剩下的除以 32,就是 util 的上限
  4. 从这个上限往下减 5%,作为起始值

以 0.6B 全参为例:

训练侧 ≈ 10.4 GiB   (权重1.1 + 梯度1.1 + AdamW6.7 + 激活0.2 + logits1.2)
context ≈ 1.5 GiB
────────────────────
可给 vLLM ≈ 32 − 10.4 − 1.5 = 20 GiB  →  util 上限 ≈ 0.62
起始值取 0.55
!但实践上建议从 0.3 起步

上面算出 0.55 是理论上限。实际先用 0.3 跑通,确认不 OOM,再往上加。

原因:算得再准也会漏掉一些东西(allocator 碎片、kernel workspace、临时张量)。先跑通再优化,比先优化再 debug OOM 省时间得多。

sleep mode:把显存借来借去

vLLM 的 sleep mode 是 colocate 能舒服工作的关键:

engine = LLM(..., enable_sleep_mode=True)

engine.wake_up()      # 采样前:申请 KV cache
samples = rollout(...)
engine.sleep(level=1) # 采样后:归还 KV cache 显存
# ↓ 这段时间训练侧可以用这块显存
train_step(...)

两个 level:

level释放什么唤醒耗时用在哪
1只释放 KV cache,权重留着快(1~3s)✅ 单卡 colocate 默认
2权重也卸载到 CPU慢(10s+)显存极度紧张时

TRL 里对应 vllm_enable_sleep_mode=True(部分版本),Axolotl 的文档也推荐开启。

sleep mode 让 util 可以设得更高

不开 sleep mode:vLLM 全程占着 util × 32,训练侧只能用剩下的。 开了 sleep mode:训练阶段那部分 KV cache 显存能借给训练侧用。

等价于让你把 util 设得比理论上限更高,因为两边不再同时峰值。这是单卡 colocate 最实用的一个开关。

OOM 排查顺序

colocate 下 OOM 有两种,先分清是哪一种:

报错发生在 采样阶段  →  vLLM 侧不够   →  降 max_model_len / 降并发 / 提 util
报错发生在 训练阶段  →  训练侧不够     →  降 micro batch / 降 util / 开检查点

看 traceback 里出现的是 vLLM 的栈还是 transformers 的栈就能区分。

然后按这个顺序动,一次只动一个

顺序动作影响效果吗
1micro batch 降到 1 + 梯度累积补回❌ 不影响,数学等价
2gradient_checkpointing❌ 不影响,只是慢 30%
3enable_sleep_mode❌ 不影响
4max_model_len 到实际需要❌ 只要不低于真实最大长度
5调整 util(哪边不够给哪边)❌ 只影响速度
6降 rollout 并发⚠️ 只影响速度,但会明显变慢
7max_completion_length⚠️ 可能影响效果
8num_generations⚠️ 影响优势估计质量
9关参考模型(beta=0)⚠️ 失去 KL 约束和监控
10换 LoRA⚠️ 表达能力受限
×不要先动 num_generations

num_generations 是新手最容易先降的,因为它看起来直接减少了工作量。

但 G 是 GRPO 的基线来源。G 从 8 降到 4,组内基线的估计方差显著增大,退化组(std=0)的比例也会上升。一般不要低于 4。

前六项都不影响效果,先把它们用完。

时间去哪了

记一次实测的分解(0.6B、8 prompt × 8 条、平均 152 token):

采样            28.4 s   62%   ████████████████████████
前向 + 反向     12.1 s   26%   ██████████
权重同步         2.2 s    5%   ██
sleep/wake      3.1 s    7%   ███
─────────────────────────────
单步            45.8 s

结论很清楚:优化的重点在采样。

按性价比排序的四个动作:

  1. 提高 util(如果显存允许)—— KV cache 池更大,能容纳更多并发,吞吐直接上去
  2. max_completion_length —— 采样时间对长度基本是线性的。但要先确认截断率不高
  3. 确认 CUDA graph 开着 —— 关掉能损失几倍吞吐
  4. 减小固定开销的占比 —— 每步 batch 调大,让同步和切换的 5 秒摊薄
i第 4 点值得多说一句

同步 + 切换固定 5.3 秒,占单步 12%。如果把每步的 prompt 数从 8 加到 16(采样时间翻倍到 57s),固定开销的占比就从 12% 降到 7%。

代价是每步显存峰值更高,而且更新频率降低。这是「大 batch 少步数」和「小 batch 多步数」的经典权衡,在 colocate 下前者更划算,因为固定开销更大。

rl@forge
目标 0/3
  1. 1.看 util 给太高时 OOM 发生在哪一侧
  2. 2.拿到单步时间分解,确认瓶颈在采样
  3. 3.提高 util 并开 sleep mode,看单步时间下降多少
colocate 调优演练。目标是在不 OOM 的前提下把单步时间压下来。
goals 看目标,hint 要提示。
[rl@forge ~/rlforge]$
help 查看用法 · goals 看目标 · hint 要提示 · ↑↓ 翻历史
检查点单选

为什么单卡只能用 colocate 模式?

检查点单选

colocate 下 OOM,traceback 出现在 transformers 的 modeling 文件里。该先动什么?

检查点单选

sleep mode(level=1)的作用是什么?

这节课的落点

  • 单卡只能 colocate,代价约 1.5 倍(不能重叠 + 切换开销),这是物理约束
  • vllm_gpu_memory_utilization预占不是按需,必须手工划线
  • 理论上限用显存计算器算,但实践从 0.3 起步,跑通再往上加
  • sleep mode level=1 归还 KV cache 显存,等价于让 util 能设得更高
  • OOM 先看 traceback 在哪一侧,再按「不影响效果」的顺序动刀
  • 不要先降 num_generations,它是 GRPO 的基线来源,一般不低于 4
  • 采样占 60%+,优化重点:提 util、控长度、保住 CUDA graph、摊薄固定开销

延伸资料