# 使用 Unsloth 内核 + Packing 实现 3 倍更快的 LLM 训练

Unsloth 现在支持最多 **5× 更快** （通常为 3x）使用我们新的自定义进行训练 **RoPE 和 MLP Triton 内核**，以及我们新的智能自动打包。Unsloth 的新内核 + 功能不仅提高了训练速度，而且进一步 **减少 VRAM 使用（30% - 90%）** 且没有精度损失。 [Unsloth GitHub](https://github.com/unslothai/unsloth)\
\
这意味着您现在可以在诸如 [Qwen3](https://unsloth.ai/docs/zh/mo-xing/tutorials/qwen3-how-to-run-and-fine-tune)-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="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FewadBu05vK7zAmJRJcj6%2Frope_varlen_qk_rope_kernel_benchmark_v5.png?alt=media&#x26;token=04d277d4-c289-4943-9312-e3d3e2d60bec" 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-length-fine-tuning](https://unsloth.ai/docs/zh/bo-ke/500k-context-length-fine-tuning "mention")我们会遇到 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="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FPATEJoJwIotXNPsYT1hu%2FW%26B%20Chart%2010_12_2025%2C%203_57_51%20am.png?alt=media&#x26;token=e31ee2cd-cd6e-4fd2-9c59-7f2148179815" alt=""><figcaption></figcaption></figure> <figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FjjnXdgPSgUxL9WNzx9wc%2FW%26B%20Chart%2010_12_2025%2C%203_58_19%20am.png?alt=media&#x26;token=54368c73-2ce1-4faa-a1f4-c82341638be3" alt=""><figcaption></figcaption></figure></div>

<div><figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FA61fgCtUj0K9dhrHCt0C%2FW%26B%20Chart%2010_12_2025%2C%203_54_40%20am.png?alt=media&#x26;token=b8472635-4b05-430e-9df1-3820ed381c3f" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FPh0Xfaup0CTz8REL6P9F%2FW%26B%20Chart%2010_12_2025%2C%203_56_38%20am.png?alt=media&#x26;token=86ed33c3-e5ac-4b71-82c3-f8f86ca79862" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2F6FKM6pkRkQX3gzQLdcIP%2FW%26B%20Chart%2010_12_2025%2C%203_55_38%20am.png?alt=media&#x26;token=48d2c1d3-e6f2-420c-8209-70ef247ce63d" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FxbhcPeELu78xh3M01xkf%2FW%26B%20Chart%2010_12_2025%2C%203_56_07%20am.png?alt=media&#x26;token=375c3f27-5af8-43e5-93eb-3818cb401f95" 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="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2F508zS4YN2sYnYjYkt8ej%2Fimage.png?alt=media&#x26;token=a05f917a-f593-4abd-a834-2f3f6652ca5a" alt="" width="563"><figcaption><p>4 个示例未打包会浪费空间</p></figcaption></figure></div>

{% endcolumn %}

{% column width="58.33333333333333%" %}

<figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2F8azEy9wbeF2RWSWbNdma%2Fimage.png?alt=media&#x26;token=a4b96567-244a-45ed-8b18-b16074bac88c" 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](https://unsloth.ai/docs/zh/mo-xing/tutorials/qwen3-how-to-run-and-fine-tune)、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="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FFfmdok7AmeretPSGlZjg%2Fnew%20rope%20kernel%20graph.png?alt=media&#x26;token=d890fd95-c8c0-4817-9ee3-18e3095cde5f" 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="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FReViERxLWBHnT8GOv0ql%2Fpacking_efficiency_by_per_device_train_batch_size.png?alt=media&#x26;token=1c4a78c7-a611-4374-ac03-94aabb1d3184" alt="" width="563"><figcaption></figcaption></figure>

中所述。

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

<div><figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FRfzNZVz9uzDPhEe3frGe%2Funknown.png?alt=media&#x26;token=e9fe893e-6b94-4c0d-b144-ef8315067c1e" alt="" width="563"><figcaption></figcaption></figure> <figure><img src="https://2657992854-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FxhOjnexMCB3dmuQFQ2Zq%2Fuploads%2FTHtcpIdQ0z0mGRYYBfuF%2Funknown.png?alt=media&#x26;token=ec52b2c1-1e60-4ed2-969a-d760af26be2a" 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-notebooks](https://unsloth.ai/docs/zh/kai-shi-shi-yong/unsloth-notebooks "mention")

{% 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-length-fine-tuning](https://unsloth.ai/docs/zh/bo-ke/500k-context-length-fine-tuning "mention") 谢谢！如果您感兴趣，请参阅我们的 [memory-efficient-rl](https://unsloth.ai/docs/zh/kai-shi-shi-yong/reinforcement-learning-rl-guide/memory-efficient-rl "mention") 博客， [long-context-gpt-oss-training](https://unsloth.ai/docs/zh/mo-xing/gpt-oss-how-to-run-and-fine-tune/long-context-gpt-oss-training "mention") 博客和
