# 内存高效型 RL

我们很高兴在 Unsloth 中推出更高效的强化学习（RL），并带来多项算法改进：

* **上下文长度提升 1.2 到 1.7 倍** 没有任何变慢，也不会额外占用内存！
* **RL 训练运行速度提升 10%** 采用了改进后的内核和异步数据搬运
* **快 2 倍 `torch.compile` 倍** 在模型加载期间

Unsloth **已经** 在 FA2 的所有其他方案中，已能提升 RL 训练速度、上下文窗口，并将 VRAM 占用降低 50–90%，但现在 [**Unsloth 的待机模式**](#unsloth-standby) 进一步提升了这一点。我们的待机功能相较于其他实现，能独特地限制速度下降，有时甚至还能让训练更快！

现在，Qwen3-32B LoRA 16 位可以达到 6,144 的上下文长度，而之前在 1xH100 80GB GPU 上只有 3,600（**更长 1.7 倍**）。Llama-3.1-8B QLoRA 4bit 可以达到 47,500 的长度，而之前是 42,000（更长 1.13 倍）。

我们通过多种内核优化，使 RL 运行速度提升了 10%，并在从训练切换到推理模式时移除了 CPU 与 GPU 之间的 LoRA 通信通道。最后，我们使用了自定义 `torch.compile` 标志，使 vLLM 的 rollout 速度提升了 10%，并将编译时间缩短了 2 倍。

## :sparkles:如何启用优化

要启用 **Unsloth 的待机模式** 功能，设置环境变量 `UNSLOTH_VLLM_STANDBY` 在任何 Unsloth 导入之前。然后设置 `gpu_memory_utilization = 0.95` 就可以了！

```python
import os
os.environ["UNSLOTH_VLLM_STANDBY"] = "1"

from unsloth import FastLanguageModel
import torch
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Qwen3-8B-Base",
    max_seq_length = 2048, # 可为更长的推理轨迹增加
    load_in_4bit = False, # LoRA 16bit 时为 False
    fast_inference = True,
    max_lora_rank = 32, # rank 越大 = 越聪明，但越慢
    gpu_memory_utilization = 0.95,
)
```

## :mortar\_board:不再需要 `gpu_memory_utilization`!

有了 Unsloth 新的 RL 改进，你再也不用担心调节或设置 `gpu_memory_utilization` 了——只需把 GPU 利用率设为 90% 或 95% 即可——遗憾的是 100% 不行，因为小张量需要一些空间。以前需要在 30% 到 95% 之间调试——现在不用了！直接设到最大，剩下的交给 Unsloth！

## :interrobang:为什么 RL 会占用这么多内存？

GRPO（以及许多 RL 变体）高度依赖生成，而生成主要由 vLLM 提供支持。但这带来了很高的代价，因为它需要持续的 **GPU 显存来存放权重、激活值和 KV Cache**.

{% columns %}
{% column %}
推理会消耗大量 VRAM

<figure><img src="/files/81a2f01d7947ed2ab37f21772b23f90c81c968d9" alt=""><figcaption></figcaption></figure>
{% endcolumn %}

{% column %}
而训练同样也会使用 VRAM！

<figure><img src="/files/ab631793dee90c40578ddccedfa275a45d59f702" alt=""><figcaption></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

这意味着 RL 需要同时在 GPU 上保留 2 套 VRAM / 内存：

1. 推理引擎（有模型权重、KV cache）
2. 训练引擎（有模型权重、激活值、梯度、优化器状态）

当前的 RL 框架在 80GB GPU 上必须 50/50 分配，50% 给推理，50% 给训练。而且把权重从训练模式移动到推理模式可能要花相当长时间。

<table><thead><tr><th width="251.51666259765625">80GB GPU</th><th>推理引擎（50%）</th><th>训练引擎（50%）</th></tr></thead><tbody><tr><td>模型权重</td><td>16GB</td><td>16GB</td></tr><tr><td>KV Cache</td><td>24GB</td><td></td></tr><tr><td>激活值、梯度、优化器状态</td><td></td><td>24GB</td></tr></tbody></table>

之前的 Unsloth 版本已经在智能地优化上述内容，因为我们 **直接共享 vLLM 的权重空间，从而消除了模型权重的双重内存占用**。例如这会释放 16GB 空间，可用于增加上下文长度或提升生成速度。另外，我们不需要进行内存搬运，因此训练更快。

| 80GB GPU     | 推理引擎（50%）            | 训练引擎（50%）           |
| ------------ | -------------------- | ------------------- |
| 模型权重         | **共享 16GB**          | **<<< 共享**          |
| KV Cache     | 24GB + 8GB= **32GB** |                     |
| 激活值、梯度、优化器状态 |                      | 24GB + 8GB=**32GB** |

## 🦥Unsloth 待机模式

但我们还能更进一步——首先要注意，RL 的流程是推理，然后训练，再推理，再训练，等等。

<figure><img src="/files/d94da3bcb5b5683f51013162f63da10f6886328f" alt=""><figcaption></figcaption></figure>

这意味着推理和训练的内存空间理论上可以复用，因为推理和训练是分离的模式——这就是 [vLLM 的睡眠模式功能](https://docs.vllm.ai/en/latest/features/sleep_mode.html#rlhf-weight-updates) 的作用，它有 2 个选项：

1. `level = 1` 将权重复制到 CPU 并删除 KV cache
2. `level = 2` 删除权重并删除 KV cache

但要提醒的是，在 Unsloth 中我们共享 vLLM 的权重内存空间——这意味着我们需要一种新的方式来删除 KV cache，并忽略删除权重的操作，我们把这称为 Unsloth 待机模式。

| 80GB GPU                                                                                                                                               | 推理引擎        | 训练引擎         |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------- | ------------ |
| 模型权重                                                                                                                                                   | **共享 16GB** | **<<< 共享**   |
| <p><mark style="background-color:purple;"><strong>多用途</strong></mark></p><p><mark style="background-color:purple;"><strong>64GB 空间</strong></mark></p> | KV Cache    | 激活值、梯度、优化器状态 |

要启用此功能，只需在任何 Unsloth 导入之前，将下面内容添加到所有 RL / GRPO 训练运行中：

```python
import os
os.environ["UNSLOTH_VLLM_STANDBY"] = "1"
```

## 🧪性能实验

在这里你会看到我们如何对 GRPO 的内存使用和上下文长度进行基准测试。请注意，我们会 **每个 prompt 生成 2 次，因为 GRPO 要工作**，至少需要 2 次生成来计算样本均值和方差。 **没有 2 次生成时，单个样本的标准差为 0**。这会导致使用该公式的优势值：（reward - mean）/std **变为未定义**.

$$
Z=\frac{r\_i - \mu}{\sqrt{\frac{1}{n}\sum(r\_i-\mu)^2}} \\
Z\_{n=1}=\frac{r\_1 - \mu}{\sqrt{\frac{1}{1}\sum(r\_1-\mu)^2}}=\frac{0}{0}=\text{undefined}
$$

这意味着就 GRPO 而言，Qwen-3 32B 的最大上下文长度 6,144 实际上是 6,144 乘以 2 次生成，即长度 12,288。

我们下面提供了 Llama-3.1 8B 在 LoRA（16bit）和 QLoRA（4bit）上的实验：

<figure><img src="/files/a58880c3f30abb89fec44e35b05aaf9a6c3a970d" alt="" width="563"><figcaption></figcaption></figure>

**如果你注意到任何训练时间差异，其实并不大**。在我们的同类对比中，我们观察到训练时间下降不到 1%，甚至有时会加速，这可以归因于误差范围。

我们还推测，由于内存压力降低，速度提升也是可能的，因此在 CUDA 内存分配器一侧，可能需要更少的内存清理。

<figure><img src="/files/edf691c3510c53f8993bbb49887bfc992d047e53" alt=""><figcaption></figcaption></figure>

在上图中，你可以看到单张 T4 GPU 上 Qwen 3 4B 的基线模式与待机模式之间的差异。 <mark style="background-color:green;">**我们可以把 vllm 的**</mark><mark style="background-color:green;">**&#x20;**</mark><mark style="background-color:green;">**`gpu_memory_utilisation`**</mark><mark style="background-color:green;">**&#x20;**</mark><mark style="background-color:green;">**提高到 0.95 这么高，而不必担心它会影响训练**</mark>。这意味着你可以容纳更长上下文的序列，并处理更多序列。例如，在第一种情况下，我们有足够内存容纳并处理 32K 长度的序列，前提是训练允许；而之前，任何长度超过 2K 的输入都可能无法装入，最终导致 OOM（显存不足）。

<table data-full-width="true"><thead><tr><th>实验</th><th>配置</th><th>状态</th><th>GPU 内存使用</th><th>备注</th></tr></thead><tbody><tr><td><ol><li><a href="https://colab.research.google.com/drive/18CssBY5C0mStnLvu2Hlt4aFLoPugRG0K?usp=sharing">u0.95gen2ga1s Qwen3_(4B)-GRPO.ipynb</a></li></ol></td><td><p><code>待机 True</code></p><p><code>vllm_gpu_util 0.95</code></p><p><code>num_gen 2</code></p><p><code>grad_acc_steps 2</code></p></td><td>运行 40 步 / 40 分钟</td><td><p>14.5 GiB（由 vllm_gpu_util 设置）</p><p><br></p></td><td>足以容纳 32K KVCache，分块大小为 2-4K，或者说 16K KVCache + 16K 分块</td></tr><tr><td><ol start="2"><li><a href="https://colab.research.google.com/drive/1q0TOUychygfreI2wKpg51sqnRhs5cYnX?usp=sharing">u9ge2ga2s Qwen3_(4B)-GRPO.ipynb</a></li></ol></td><td><p><code>待机 True</code></p><p><code>vllm_gpu_util 0.9</code></p><p><code>num_gen 2</code></p><p><code>grad_acc_steps 2</code></p></td><td>40 分钟内运行 32 步</td><td>13.8 GiB（由…设置）</td><td>大致足以容纳约 28K KVCache，分块大小为 2-4K，或者说 15K KVCache + 15K 分块</td></tr><tr><td><ol start="3"><li><a href="https://colab.research.google.com/drive/12Uw8y5beLzPtx11mCWCYyh9Z_PEHHdId?usp=sharing">u9ge2ga2ns Qwen3_(4B)-GRPO.ipynb</a></li></ol></td><td><p><code>待机 False</code></p><p><code>vllm_gpu_util 0.9</code></p><p><code>num_gen 2</code></p><p><code>grad_acc_steps 2</code></p></td><td>模型能加载，但无法训练，因为连 batch size 为 1 都放不下</td><td>OOM</td><td><br></td></tr><tr><td><ol start="4"><li><a href="https://colab.research.google.com/drive/1GwTlaP5CLsW-BcE1LqZWkz6S8VTWYdJ2?usp=sharing">u8ge2ga2ns Qwen3_(4B)-GRPO.ipynb</a></li></ol></td><td><p><code>待机 False</code></p><p><code>vllm_gpu_util 0.8</code></p><p><code>num_gen 2</code></p><p><code>grad_acc_steps 2</code></p></td><td>模型能加载，但无法训练，因为连 batch size 为 1 都放不下</td><td>OOM</td><td><br></td></tr><tr><td><ol start="5"><li><a href="https://colab.research.google.com/drive/1IuSUNzEBTiURK-vbTQuRDuUl0Ya2pz2t?usp=sharing">u7ge2ga2ns Qwen3_(4B)-GRPO.ipynb</a></li></ol></td><td><p><code>待机 False</code></p><p><code>vllm_gpu_util 0.7</code></p><p><code>num_gen 2</code></p><p><code>grad_acc_steps 2</code></p></td><td><p>训练正常</p><p>28 步需要 39 分钟</p></td><td>约 15.1GiB</td><td>任何稍长一些的输入都会在 colab 上导致 OOM</td></tr><tr><td><ol start="6"><li><a href="https://colab.research.google.com/drive/1RY7HwpZ0luJT70OyLJ6zXKZQ2COdT9QJ?usp=sharing">u7gen2ga2s Qwen3_(4B)-GRPO.ipynb</a></li></ol></td><td><p><code>待机 True</code></p><p><code>vllm_gpu_util 0.7</code></p><p><code>num_gen 2</code></p><p><code>grad_acc_steps 2</code></p></td><td><p>训练正常</p><p>29 步需要 40 分钟</p></td><td>13GiB，但大多数时候在 10-11GB 左右</td><td>在相同配置下，我们这里节省了 2GiB，即 15% 的内存。对于更长的序列，这个数值还可以更高</td></tr></tbody></table>

### H100 实验

| 模型                   | GPU                   | 序列长度   | 生成次数 | 梯度累积步数 |
| -------------------- | --------------------- | ------ | ---- | ------ |
| Qwen2.5-14B-Instruct | NVIDIA H100 80GB PCIe | 32,768 | 8    | 4      |

在下面可折叠的结果中，你可以看到峰值内存使用相差 9GiB（注意，在我们的案例中，90% 的时间里，GPU 内存使用量与峰值内存相同）。 **从直观上看，使用 TRL 和 LoRA，我们最多只能微调一个 80 亿参数模型，且上下文长度最多 1024（少 32 倍）。** 任何更长的序列长度（在相似配置下）都会导致进程因 OOM 而失败。

<details>

<summary>点击查看 Unsloth 待机模式 vs. 无待机基准测试</summary>

```
已启用待机模式：

|===========================================================================|
|                  PyTorch CUDA 内存摘要，设备 ID 0                 |
|---------------------------------------------------------------------------|
|            CUDA OOM 次数: 0            |        cudaMalloc 重试次数: 0         |
|===========================================================================|
|        指标         | 当前使用量  | 峰值使用量 | 总分配量 | 总释放量 |
|---------------------------------------------------------------------------|
| 已分配内存          |  32249 MiB |  43042 MiB | 128336 GiB | 128305 GiB |
|       来自大池       |  31415 MiB |  42165 MiB | 127204 GiB | 127173 GiB |
|       来自小池       |    834 MiB |   1184 MiB |   1132 GiB |   1131 GiB |
|---------------------------------------------------------------------------|
| 活跃内存            |  32249 MiB |  43042 MiB | 128336 GiB | 128305 GiB |
|       来自大池       |  31415 MiB |  42165 MiB | 127204 GiB | 127173 GiB |
|       来自小池       |    834 MiB |   1184 MiB |   1132 GiB |   1131 GiB |
|---------------------------------------------------------------------------|
| 请求内存            |  32199 MiB |  42987 MiB | 128176 GiB | 128145 GiB |
|       来自大池       |  31364 MiB |  42110 MiB | 127047 GiB | 127016 GiB |
|       来自小池       |    834 MiB |   1184 MiB |   1129 GiB |   1128 GiB |
|---------------------------------------------------------------------------|
| GPU 保留内存       |  37644 MiB |  47504 MiB | 705806 MiB | 668162 MiB |
|       来自大池       |  36376 MiB |  46588 MiB | 682818 MiB | 646442 MiB |
|       来自小池       |   1268 MiB |   1284 MiB |  22988 MiB |  21720 MiB |
|---------------------------------------------------------------------------|
| 不可释放内存        | 713142 KiB |   4633 MiB | 103206 GiB | 103205 GiB |
|       来自大池       | 525312 KiB |   4594 MiB | 101923 GiB | 101922 GiB |
|       来自小池       | 187830 KiB |    250 MiB |   1283 GiB |   1283 GiB |
|---------------------------------------------------------------------------|
| 分配次数            |    3460    |    4809    |   15606 K  |   15603 K  |
|       来自大池       |     395    |     563    |    2812 K  |    2811 K  |
|       来自小池       |    3065    |    4270    |   12794 K  |   12791 K  |
|---------------------------------------------------------------------------|
| 活跃分配            |    3460    |    4809    |   15606 K  |   15603 K  |
|       来自大池       |     395    |     563    |    2812 K  |    2811 K  |
|       来自小池       |    3065    |    4270    |   12794 K  |   12791 K  |
|---------------------------------------------------------------------------|
| GPU 保留段         |     913    |     920    |   13260    |   12347    |
|       来自大池       |     279    |     305    |    1766    |    1487    |
|       来自小池       |     634    |     642    |   11494    |   10860    |
|---------------------------------------------------------------------------|
| 不可释放分配        |     422    |     628    |    4766 K  |    4765 K  |
|       来自大池       |      66    |      92    |    1290 K  |    1289 K  |
|       来自小池       |     356    |     555    |    3476 K  |    3475 K  |
|---------------------------------------------------------------------------|
| 超大分配            |       0    |       0    |       0    |       0    |
|---------------------------------------------------------------------------|
| 超大 GPU 段         |       0    |       0    |       0    |       0    |
|===========================================================================|


无待机模式：

|===========================================================================|
|                  PyTorch CUDA 内存摘要，设备 ID 0                 |
|---------------------------------------------------------------------------|
|            CUDA OOM 次数: 0            |        cudaMalloc 重试次数: 0         |
|===========================================================================|
|        指标         | 当前使用量  | 峰值使用量 | 总分配量 | 总释放量 |
|---------------------------------------------------------------------------|
| 已分配内存          |  32711 MiB |  52084 MiB | 142756 GiB | 142724 GiB |
|       来自大池       |  31877 MiB |  51207 MiB | 141499 GiB | 141467 GiB |
|       来自小池       |    834 MiB |   1184 MiB |   1257 GiB |   1256 GiB |
|---------------------------------------------------------------------------|
| 活跃内存            |  32711 MiB |  52084 MiB | 142756 GiB | 142724 GiB |
|       来自大池       |  31877 MiB |  51207 MiB | 141499 GiB | 141467 GiB |
|       来自小池       |    834 MiB |   1184 MiB |   1257 GiB |   1256 GiB |
|---------------------------------------------------------------------------|
| 请求内存            |  32572 MiB |  51658 MiB | 141898 GiB | 141866 GiB |
|       来自大池       |  31738 MiB |  50780 MiB | 140644 GiB | 140613 GiB |
|       来自小池       |    833 MiB |   1184 MiB |   1253 GiB |   1252 GiB |
|---------------------------------------------------------------------------|
| GPU 保留内存       |  49552 MiB |  52188 MiB |  86354 MiB |  36802 MiB |
|       来自大池       |  48320 MiB |  51300 MiB |  84740 MiB |  36420 MiB |
|       来自小池       |   1232 MiB |   1232 MiB |   1614 MiB |    382 MiB |
|---------------------------------------------------------------------------|
| 不可释放内存        |      0 B   |      0 B   |      0 B   |      0 B   |
|       来自大池       |      0 B   |      0 B   |      0 B   |      0 B   |
|       来自小池       |      0 B   |      0 B   |      0 B   |      0 B   |
|---------------------------------------------------------------------------|
| 分配次数            |    3460    |    4809    |   17440 K  |   17437 K  |
|       来自大池       |     395    |     564    |    2742 K  |    2741 K  |
|       来自小池       |    3065    |    4270    |   14698 K  |   14695 K  |
|---------------------------------------------------------------------------|
| 活跃分配            |    3460    |    4809    |   17440 K  |   17437 K  |
|       来自大池       |     395    |     564    |    2742 K  |    2741 K  |
|       来自小池       |    3065    |    4270    |   14698 K  |   14695 K  |
|---------------------------------------------------------------------------|
| GPU 保留段         |       0    |       0    |       0    |       0    |
|       来自大池       |       0    |       0    |       0    |       0    |
|       来自小池       |       0    |       0    |       0    |       0    |
|---------------------------------------------------------------------------|
| 不可释放分配        |       0    |       0    |       0    |       0    |
|       来自大池       |       0    |       0    |       0    |       0    |
|       来自小池       |       0    |       0    |       0    |       0    |
|---------------------------------------------------------------------------|
| 超大分配            |       0    |       0    |       0    |       0    |
|---------------------------------------------------------------------------|
| 超大 GPU 段         |       0    |       0    |       0    |       0    |
|===========================================================================|
```

</details>

下图展示了在 Unsloth 中，待机模式与非待机训练的对比。结果已对 3 次运行取平均，以确保指标没有噪声。事实上，如果你足够放大，会发现启用待机模式也会让它更快，这很可能还是因为前面提到的内存压力更小。

<figure><img src="/files/cde5bdacf20ed87ab04f8a088e1833952ee76477" alt=""><figcaption></figcaption></figure>

### 此前的 A100 40GB 实验

在我们此前对 A100 40GB GPU、使用 Qwen-2.5-3b-instruct 且每个样本 8 次生成的实验中，我们观察到：没有待机模式时，GRPO 训练（模型以 16bit 加载，LoRA，仅权重可训练）最多只能放下 6K 序列长度。使用待机功能后，我们可以放下 10K 甚至更高！ **相比之下，TRL 在保持相同 batch size 的情况下，只能提供最多 1K 的上下文长度。**

<figure><img src="/files/7c92544ec3f2ad7dd560a440f48159e21564e495" alt="" width="563"><figcaption></figcaption></figure>

## :tada:其他优化

我们现在选择了更好的编译标志，并将编译时间缩短了 50% 或更多。我们还成功地动态补丁任意 vLLM 版本以处理 `gc.collect` 更好，以实现向后兼容，灵感来自这个 [vLLM pull request](https://github.com/vllm-project/vllm/pull/21146)。这将编译时间从 2 分钟缩短到 40 秒以内。

我们还优化了 `torch.compile` 标志，并尝试开启一些标志——不幸的是 `combo_kernels` 以及 `multi_kernel` 在 vLLM 0.10 和 Torch 2.8/2.9 nightly 上无法正常工作，并且 `coordinate_descent_tuning` 让所有内核的自动调优速度大幅变慢。它以前的编译时间不到一分钟，但开启后却要 13 分钟以上，而且性能提升很小。

## :books:GRPO 笔记本

我们所有的 GRPO 笔记本默认都启用了 Unsloth 待机模式和所有优化！请查看 <https://docs.unsloth.ai/get-started/unsloth-notebooks> 以获取我们全部的 GRPO 笔记本，或者试试下面这些：

* [**Qwen3（4B）**](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen3_\(4B\)-GRPO.ipynb) **-** 高级 GRPO LoRA
* [**DeepSeek-R1-0528-Qwen3 (8B)**](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/DeepSeek_R1_0528_Qwen3_\(8B\)_GRPO.ipynb) （用于多语言场景）
* [Gemma 3 (1B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Gemma3_\(1B\)-GRPO.ipynb)
* [Llama 3.2（3B）](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Advanced_Llama3_2_\(3B\)_GRPO_LoRA.ipynb) - 高级 GRPO LoRA
* [Llama 3.1 (8B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Llama3.1_\(8B\)-GRPO.ipynb)
* [Phi-4 (14B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Phi_4_\(14B\)-GRPO.ipynb)
* [Mistral v0.3 (7B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Mistral_v0.3_\(7B\)-GRPO.ipynb)
* [Qwen2.5 (3B)](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen2.5_\(3B\)-GRPO.ipynb)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://unsloth.ai/docs/zh/kai-shi-shi-yong/reinforcement-learning-rl-guide/memory-efficient-rl.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
