![]()
今天聊点新东西——Unsloth 和 NVIDIA 官方联手了,三个看起来很"无聊"的优化,组合起来直接把 LLM 训练速度提了大约 25%
而且最骚的是:你不用换硬件、不用改代码、不用换框架,升级一下 Unsloth 就能吃到红利
这次到底动了什么
通常我们说"训练加速",第一反应是更快的 matmul、更好的 attention、fused kernel、grouped GEMM 这些大头算子
但 Unsloth 团队和 NVIDIA 工程师坐下来一起 profile 的时候,发现一个很反直觉的事实——那些大算子早就被卷到极致了,真正在拖后腿的,是算子之间的胶水代码
具体表现是两类:
GPU 在等"元数据"——一些只跟 batch 相关的小信息,每一层都重新构建一遍
拷贝流和计算流被排成了一队——明明可以并行,却轮流执行
针对这两类问题,他们在 Blackwell GPU 上做了三处改动:
Packed-sequence 元数据缓存 :PR unsloth#4243
双缓冲的 checkpoint 激活回传 :PR unsloth-zoo#534
GPT-OSS MoE 路由用 bincount 一次分组 :PR unsloth-zoo#535
三个 PR 看着不起眼,但每一个的视角都挺值得学的
![]()
三个优化各打一处,组合 ~25% 优化一:别把同一份元数据重建 L 遍
先说一个微调里很常见的小技巧——packed sequence
短样本一堆,如果按最长那个 padding 对齐,padding token 上浪费的算力不少。所以大家都改成把多条短样本拼成一条长 sequence,再用一份"边界元数据"告诉模型每条原样本从哪到哪
这份边界元数据 B 包括:
每条样本的长度
累积偏移
cu_seqlens最大序列长度
由上面三项推导出来的 attention mask 结构
关键来了:对一个 packed batch 而言,这份 B 在所有 transformer 层里是一模一样的
但很多实现的写法是:每一层走 attention 的时候,都重新算一遍 B、重新构建一遍 SDPA 的 packed mask、重新生成 xFormers 的 block mask
如果模型有 L 层,本来"build B 一次,用 L 次"的事情,被写成了"build B + build B + ⋯ + build B",重复 L 次
更糟糕的是,这条路径里有些调用会强制 device-to-host 同步,每一层都触发一次 GPU-CPU 同步点
Unsloth 的改法很直接——给当前 packed batch 缓存一份元数据和 mask 结构,按设备分别缓存,后续所有层直接复用
数学上能直接算出来收益:假设原来每层附带的同步/重建开销是 s,缓存之后省下来的时间大概是 (L − 1) · s
实测在 Qwen3-14B QLoRA SFT 上:
前向:+43.3%
反向:+5.8%
整个 batch:+14.3%
前向受益最大,因为元数据和 mask 准备主要发生在前向。反向因为本身计算量更大(尤其叠加 gradient checkpointing),同样的绝对节省时间,相对比例就小一些
为了验证这个数字合理,团队还在 Blackwell 上做了微基准——一次 packed SDPA mask rebuild 大约 13.7 ms。按 (L − 1) · m 估:
Llama-3.2-1B,16 层:每步省 199 ms,端到端约快 11.5%Qwen3-0.6B,28 层:每步省 319 ms,端到端约快 14.8%
理论值和实测值对得上,这种"先优化、再用模型解释清楚为什么有效"的思路,比单纯丢一句"我们快了多少"靠谱得多
优化二:双缓冲,让拷贝藏在计算后面
第二个优化关系到 gradient checkpointing
这个技术微调老手都熟——为了省 VRAM,前向时不保留所有中间激活,反向时再重新算一遍。但 Unsloth 走得更激进,开了 smart checkpointing:把激活直接挪到 pinned CPU memory,反向时再传回 GPU
省显存是省了,但多了一条慢路径:
1. 把激活从 CPU 拷回 GPU
2. 等拷贝完成
3. 在这块激活上跑反向
4. 开始下一份激活的拷贝
如果只用一个 buffer,拷贝流和计算流就会轮流堵车——拷贝的时候计算闲着,计算的时候拷贝闲着
解法老到不能再老:双缓冲
A buffer 在跑反向计算的时候,拷贝流就把下一层的激活预加载到 B buffer,反过来再换。中间层的拷贝就被藏在了反向计算后面
![]()
单缓冲 vs 双缓冲:拷贝藏到计算后面
注意这个改动没有减少任何运算,它只是把"原本被串行的工作"变成了"重叠的工作"
理论上每层的中间耗时从 c + g 降到 max(c, g)。第一次拷贝和最后一次计算还是要曝露出来,所以端到端节省是 (L − 1) · min(c, g)
实测在 NVIDIA B200 Blackwell 上(dense 模型):
模型规模
单步速度变化
提速
多用 VRAM
8B
0.3739 → 0.4053 steps/s
+8.40%
+0.37 GB
14B
0.2245 → 0.2395 steps/s
+6.70%
+0.47 GB
32B
0.1979 → 0.2070 steps/s
+4.61%
+0.23 GB
而且 final loss 基本没变
注意到一个细节:模型越大,绝对节省时间越多,但相对加速反而下降——因为反向计算量本身就大了,能藏的时间相对没那么显眼
这里 Unsloth 留了几个工程细节做兜底,挺值得学:
VRAM 够才开第二个 buffer
显存吃紧自动 fallback 回单 buffer
数值结果保持一致
也就是说:你的卡显存大就吃满红利,显存小也不会因为这个改动 OOM
优化三:MoE 路由的"分组一次性做好"
第三个优化更冷门一点,针对 GPT-OSS 的 MoE 路由
PyTorch 实现里有个常见写法:
for expert_idx in range(num_experts):
token_idx, _ = torch.where(router_indices == expert_idx)
看起来人畜无害,但 torch.where 在这里是 data-dependent——每个 expert 拿到多少 token 是动态的,输出 shape 不确定,会触发 CPU-GPU 同步或者类似的 runtime 开销
更糟的是:这个开销一个 expert 一次,规模随 num_experts 线性增长
Unsloth 的改法是把这事一次性做完:
把所有 expert 分配 flatten
按 expert ID 做 stable sort
用一次
bincount拿到每个 expert 拿到多少 token从 count 推导 offset
直接按 offset 切 token list
逻辑没变,但对 runtime 的"动态查询次数"从 num_experts 直接降到 1
效果:
GPT-OSS 端到端:**~10–15%** 提速
路由热点路径:前向 **+23%**,反向 +13%
注意一个边界条件:这只在 native_torch 后端的 MoE 上生效,用 Megablocks/grouped GEMM 那一套的不归这个改动管
三个优化合在一起:~25%
单看每个 PR:
优化
主要消除的瓶颈
实测收益
Packed-sequence metadata caching
跨层重复构建/同步元数据
Qwen3-14B QLoRA SFT:+14.3% per batch
Double-buffered checkpoint reload
拷贝和反向计算串行
8B +8.4% / 14B +6.7% / 32B +4.6%
GPT-OSS MoE routing with bincount
per-expert 动态索引同步
GPT-OSS 整体 ~10–15%
组合起来,官方给的口径是 "~25% faster"
❝ 这里说一句实话——25% 不是叠加得到的精确数字。三个优化作用面不完全重叠(QLoRA SFT、checkpoint 路径、MoE 路由),具体多少跟你训什么模型、用什么后端、卡多大显存都强相关你能用上吗?怎么开
这是我最想说的一段——对家用 GPU 用户尤其友好
这三个优化的触发条件是你本来就在用的常见配置:
Packed-sequence caching:用了
packing=True的 SFT/QLoRA 都会自动吃到,是 Unsloth 默认推荐的做法双缓冲 checkpoint:用了 Unsloth smart gradient checkpointing 而且 VRAM 还有富余,自动启用
MoE routing:训 GPT-OSS 系且走
native_torch后端,自动启用
启用方式只有一行:
pip install --upgrade unsloth unsloth_zoo
这事的价值在于:以前这种"和厂商一起 profile 出来的内核优化",基本都长在 Megatron-LM、NeMo、TRT-LLM 这种偏数据中心的栈里。家用 4090、5090、48GB 的工作站基本指望不上
现在 Unsloth 把它做进了一个 pip install 就能装的库里,你拿一张 RTX 4090 跑 Qwen3-14B 的 QLoRA,吃到的优化和数据中心 B200 上是同一份代码
❝ 当然 24GB 卡跑不动 14B 全参,但 QLoRA + packing 是吃得到的;如果你有两张 4090 / 一张 5090 / 一张 6000 Ada / DGX Spark 这种 48GB 级别的卡,受益会更明显和 HuggingFace TRL/PEFT 比
很多人会问:HuggingFace 的 TRL + PEFT 不是也能做 QLoRA 吗,区别在哪?
TRL/PEFT 的定位是通用、稳定、覆盖广——支持各种模型、各种任务、各种后端,但不会专门为某个后端做深度 profile
Unsloth 的定位完全不同——只支持一部分主流模型,但每个支持的模型路径都被手撸过 kernel、profile 过同步点。这次和 NVIDIA 的合作,本质上是把数据中心级别的优化思路下放到家用 GPU 这一档
实际选择建议:
想在家用 GPU 上把单卡微调跑到极限,选 Unsloth
想兼顾多卡分布式、奇怪的模型、复杂的 RLHF pipeline,TRL + DeepSpeed/FSDP 更顺手
不冲突,可以 Unsloth 做单卡微调原型,TRL 做大规模训练
其实读完这三个 PR,最大的感受不是"提速 25% 真香",而是这种工程方法学的味道——
"内核之外,让该并行的东西真的并行;让该缓存的信息真的只算一次"
这背后是一句很朴素的话:当主算子被卷到极致后,"快"只剩两条路——少做没必要的事,把没法避免的事并行化
这种思路放到推理优化、Agent 框架、RAG 流程里都成立。下次你看自己的 pipeline 慢的时候,先别急着换模型,profile 一下"重复构建的元数据"和"被串行的拷贝/计算",往往才是真正的大头
制作不易,如果这篇文章觉得对你有用,可否点个关注。给我个三连击:点赞、转发和在看。若可以再给我加个,谢谢你看我的文章,我们下篇再见!
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.