# 使用 Unsloth Kernels + Packing 提升 3 倍 LLM 训练速度

Unsloth 现在支持最多 **5× 更快** （通常为 3x）使用我们新的自定义进行训练 **RoPE 和 MLP Triton 内核**，以及我们新的智能自动打包。Unsloth 的新内核 + 功能不仅提高了训练速度，而且进一步 **减少 VRAM 使用（30% - 90%）** 且没有精度损失。 [Unsloth GitHub](https://github.com/unslothai/unsloth)\
\
这意味着您现在可以在诸如 [Qwen3](/docs/zh/mo-xing/tutorials/qwen3-how-to-run-and-fine-tune.md)-4B 这样的 LLM 上训练，不仅仅在 **3GB VRAM**上，而且速度提高 3 倍。

我们的自动 [**无填充**](#padding-free-by-default) 未污染打包会在所有训练运行中智能启用，无需任何更改，并适用于所有快速注意力后端（FlashAttention 3、xFormers、SDPA）。 [基准测试](#analysis-and-benchmarks) 显示训练损失与未打包运行 **完全相同**.

* **2.3x 更快的 QK 旋转嵌入（RoPE）** 带有打包支持的融合 Triton 内核
* 更新的 SwiGLU、GeGLU 内核，带有 **用于长上下文的 int64 索引**
* **2.5x 到 5x 更快的未污染打包** 支持 xformers、SDPA、FA3 后端
* **2.1x 更快的无填充，VRAM 减少 50%**，准确度变化 0%
* Unsloth 现在还具有改进的 SFT 损失稳定性和更可预测的 GPU 利用率。
* 此新升级适用于 **所有训练方法** 例如全量微调、预训练等。

### :drum:带有打包的融合 QK RoPE Triton 内核

早在 2023 年 12 月，我们在 Unsloth 发布时引入了用 Triton 编写的 RoPE 内核。2024 年 3 月，社区成员通过优化 RoPE 内核以允许为一组头启动一个块，使端到端训练提高了 1-2%。参见 [PR 238](https://github.com/unslothai/unsloth/pull/238).

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

一个问题是对于每个 Q 和 K，有 2 个 Triton 内核。我们现在将它们合并为 1 个 Triton 内核，并 启用了可变长度 RoPE，这对于无填充和打包支持是必要的。这使得微基准中的 RoPE 内核 **在较长上下文长度上快 2.3x**，在较短上下文长度上快 1.9x。

我们还消除了所有克隆和连续转置操作，因此 **RoPE 现在完全就地执行**，进一步减少了 GPU 内存。注意在反向传播中，我们看到 `sin1 = -sin1` 因为：

```
Q * cos + rotate_half(Q) * sin
等价于
Q * cos + Q @ R * sin
其中 R 是旋转矩阵 [ 0,  I]
                             [-I,  0]
dC/dY = dY * cos + dY @ R.T * sin
其中 R.T 同样是 [ 0, -I]
但负号被转置了。[ I,  0]
```

### :railway\_car:Triton 内核的 Int64 索引

在我们引入的 500K 长上下文训练期间， [500K Context Training](/docs/zh/bo-ke/500k-context-length-fine-tuning.md)我们会遇到 CUDA 越界错误。这是因为 SwiGLU、GeGLU 的 MLP 内核使用了 Triton 和 CUDA 中默认的 int32 索引。

我们不能只是做 `tl.program_id(0).to(tl.int64)` 因为由于 int64 索引，训练会略微变慢。我们改为将其做为 `LONG_INDEXING: tl.constexpr` 变量，以便 Triton 编译器可以对其进行特化。这允许短上下文和长上下文运行都表现良好！

{% code overflow="wrap" %}

```python
block_idx = tl.program_id(0)
if LONG_INDEXING:
    offsets = block_idx.to(tl.int64) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE).to(tl.int64)
    n_elements = tl.cast(n_elements, tl.int64)
messages.append({"role": "tool", "tool_call_id": _id, "name": fx, "content": str(out),})
    offsets = block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
```

{% endcode %}

### :abacus:为什么需要填充及数学加速原理

计算机和 GPU 无法处理不同长度的数据集，因此我们必须用 0 填充它们。这会造成浪费。假设我们有一个数据集，其中 50% 是短序列 S，50% 是长序列 L，那么在最坏情况下，填充会导致令牌使用量为 $$\text{batchsize} \times L$$ 因为最长序列长度占主导。

通过将多个示例打包到一个长的一维张量中，我们可以消除大量填充。实际上我们得到以下令牌使用：

$$
\text{Token Usage} = \frac{\text{batchsize}}{2}L+\frac{\text{batchsize}}{2}S
$$

通过一些数学和代数，我们可以算出加速比为：

$$
\text{Speedup} = \frac{\text{batchsize} \times L}{\frac{\text{batchsize}}{2}L+\frac{\text{batchsize}}{2}S} = 2 \frac{L}{L + S}
$$

通过假设 $$S\rightarrow0$$ 我们得到理论上 2 倍的加速，因为 $$2 \frac{L}{L + 0} = 2$$

通过改变 50% 短序列的比例，并假设我们有更多短序列，例如 20% 长序列和 80% 短序列，我们得到 $$\frac{L}{0.2L + 0.8S}\rightarrow\frac{L}{0.2L}=5$$ 因此训练速度提高 5 倍！这意味着打包的加速取决于数据集中短行的多少（越多越短，速度越快）。

### :clapper:默认无填充

除了在设置时可获得的大吞吐量提升外， `packing = True` 在您的 `SFTConfig` ，我们将 **中自动使用无填充批处理** 以减少填充浪费、提高吞吐量并增加每秒令牌数，同时导致 ***完全相同的损失*** 就像在 Unsloth 之前的版本中看到的一样。

例如对于 Qwen3-8B 和 Qwen3-32B，我们看到内存使用减少 60%，速度提升 2 倍，并且具有完全相同的损失和梯度范数曲线！

<div><figure><img src="/files/04845d045e80c834231f1ce2301b2f38d1f22033" alt=""><figcaption></figcaption></figure> <figure><img src="/files/4f3b7ec03d8eef8f926137086dca1a32934e8b54" alt=""><figcaption></figcaption></figure></div>

<div><figure><img src="/files/4b2f5f844c740249425e4bb119e6c55e0a0e48a3" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="/files/8bde2f5a615db97c14feb2979d5f471cabd57fe1" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="/files/aca3d6c0f745539769396d0cf63347878c4a1381" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="/files/716603f089a8af37259ac597d4a92a8fcf43bcb7" alt="" width="563"><figcaption></figcaption></figure></div>

### :spades:未污染打包 2-5x 更快的训练

真实数据集可能包含不同的序列长度，因此将批量大小增加到例如 32 会导致填充，使训练变慢并使用更多 VRAM。

{% hint style="success" %}
过去，将 `batch_size` 增加到较大的数值（>32）会使训练变得更慢，而不是更快。这是由于填充——我们现在可以通过 `packing = True`消除这个问题，因此训练更快！
{% endhint %}

当我们将多个样本打包到一个一维张量中时，我们会保留序列长度元数据以便正确掩蔽样本，且不会在样本之间泄漏注意力。我们还需要在 [#fused-qk-rope-triton-kernel-with-packing](#fused-qk-rope-triton-kernel-with-packing "mention") 中描述的 RoPE 内核以允许重置位置 id。

{% columns %}
{% column width="41.66666666666667%" %}

<div align="center" data-full-width="false"><figure><img src="/files/7802b41eef7555a12007108957e6208d0e419111" alt="" width="563"><figcaption><p>4 个示例未打包会浪费空间</p></figcaption></figure></div>

{% endcolumn %}

{% column width="58.33333333333333%" %}

<figure><img src="/files/1d986542f72f72e837bef9d3f410cf8dfde63dc1" alt=""><figcaption><p>未污染打包创建正确的注意力模式</p></figcaption></figure>
{% endcolumn %}
{% endcolumns %}

通过改变 50% 短序列的比例，并假设我们有更多短序列，例如 20% 长序列和 80% 短序列，我们得到 $$\frac{L}{0.2L + 0.8S}\rightarrow\frac{L}{0.2L}=5$$ 因此训练速度提高 5 倍！这意味着打包的加速取决于数据集中短行的多少（越多越短，速度越快）。

### :beach:分析与基准

为了演示在使用我们新的内核和打包数据进行训练时的各种改进，我们在以下模型上进行了微调运行： [Qwen3-32B](/docs/zh/mo-xing/tutorials/qwen3-how-to-run-and-fine-tune.md)、Qwen3-8B、Llama 3 8B 在 `yahma/alpaca-cleaned` 数据集上并测量了各种 [训练损失](#padding-free-by-default) 吞吐量和效率指标。我们将我们的新运行与一个启用了我们自己的内核/优化和像 Flash Attention 3 (FA3) 这类内核的标准优化训练运行进行了比较。我们固定了 `max_length = 1024` 并在 {1, 2, 4, 8, 16, 32} 中改变批量大小。这允许每批的最大令牌数在 {1024, 2048, 4096, 8192, 16K, 32K} 中变化。

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

以上显示了新的 Unsloth 在不同批量大小下令牌每秒（tokens/s）训练吞吐量的变化。这转化为在您的数据集上训练一个 epoch **快 1.7-3x（有时甚至 5x 或更多）**！如果您的数据中有许多短序列且训练运行较长，这些收益会更显著，正如在 [#why-is-padding-needed-and-mathematical-speedup](#why-is-padding-needed-and-mathematical-speedup "mention")

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

中所述。

上述显示了每批平均有效令牌（即非填充）的百分比。随着批量大小的增加，未打包情况下会看到更多填充令牌，而在已打包情况下，我们无论最大序列长度如何都能实现高打包效率。

<div><figure><img src="/files/2ee5f7f6a3d48ac52c1b491e652307755755b4db" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="/files/38bf8279c63cb9784466735996871f4084256b1f" alt="" width="563"><figcaption></figcaption></figure></div>

请注意，由于批处理逻辑会将批次剪裁为批次中看到的最大序列长度，当批量大小为 1 时，未打包数据全部是有效令牌（即无填充）。然而，随着更多示例被加入批次，平均填充增加，在批量大小为 8 时几乎达到 50% 的填充！我们的样本打包实现消除了这种浪费。 `yahma/alpaca-cleaned` 第一张图（上方）绘制了 `在`max\_length = 2048 `时，Unsloth 新的带打包 + 内核（栗色）与 Unsloth 旧版本（灰色）的比较。两者都以`max\_steps = 500

训练，但我们在 x 轴上以实时时钟时间绘图。注意在相同步数（并且只多用一点点实时时钟时间）下，已打包情况下我们训练了接近 40% 的一个 epoch，而未打包情况下不到 5%。

### :sparkles:类似地，第 2 张图（上方）绘制了相同运行的损失，这次在 x 轴上以训练步数绘制。注意损失在尺度和趋势上匹配，但打包情况下损失波动更小，因为模型在每个训练步看到更多令牌。

**如何启用打包？**&#x5148;更新 Unsloth，默认已启用无填充

{% code overflow="wrap" %}

```bash
！因此所有训练立即快 1.1 到 2 倍，至少减少 30% 内存使用，并且损失曲线指标不变！
pip install --upgrade --force-reinstall --no-cache-dir --no-deps unsloth
```

{% endcode %}

pip install --upgrade --force-reinstall --no-cache-dir --no-deps unsloth\_zoo *我们还通过 Xformers 支持 Flash Attention 3、支持 SDPA、Flash Attention 2，这在旧 GPU（Tesla T4、RTX 2080）和新 GPU（如 H100、B200 等）上都能工作！样本打包工作*不受注意力后端或模型家族选择的影响

，因此尽享之前这些快速注意力实现所带来的相同加速！ `packing = True` 如果您想显式启用打包，则添加

{% hint style="warning" %}
以启用最高 5x 的更快训练！ `注意` packing=True

将更改训练损失并会使数据集的行数被截断，因为多个短序列被打包成 1 个序列。您可能会看到数据集中的示例数量减少。 `如果不想得到不同的训练损失数值，只需设置` packing=False
{% endhint %}

```python
pip install unsloth vllm
我们将启用自动无填充，这已经能使训练更快！

from unsloth import FastLanguageModel
    from trl import SFTTrainer, SFTConfig
)

"unsloth/Qwen3-14B",
    trainer = SFTTrainer(
    model = model,
    processing_class = tokenizer,
    train_dataset = dataset,
        args = SFTConfig(
        per_device_train_batch_size = 1,
        max_length = 4096,
        …,
    ),
)
packing = True, # 必需以启用样本打包！
```

trainer.train() [Unsloth 笔记本](/docs/zh/kai-shi-shi-yong/unsloth-notebooks.md)

{% columns %}
{% column %}
我们所有的笔记本都自动更快（无需任何操作）。参见

{% embed url="<https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Qwen3_(14B)-Reasoning-Conversational.ipynb>" %}
{% endcolumn %}

{% column %}
Qwen3 14B 更快：

{% embed url="<https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Llama3.2_(1B_and_3B)-Conversational.ipynb>" %}
{% endcolumn %}
{% endcolumns %}

Llama 3.1 对话 更快： [500K Context Training](/docs/zh/bo-ke/500k-context-length-fine-tuning.md) 谢谢！如果您感兴趣，请参阅我们的 [内存高效型 RL](/docs/zh/kai-shi-shi-yong/reinforcement-learning-rl-guide/memory-efficient-rl.md) 博客， [Long Context gpt-oss](/docs/zh/mo-xing/gpt-oss-how-to-run-and-fine-tune/long-context-gpt-oss-training.md) 博客和


---

# 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/bo-ke/3x-faster-training-packing.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.
