























做过 LLM fine-tune 的同学应该都知道,LoRA 是当前 PEFT 方法里最主流的选择。只引入少量低秩参数,显存省下来了,效果也不差。
但你有没有想过:LoRA 参数只有全参数的 0.3% 不到,为什么加上 LoRA 之后训练速度反而掉了 40%?
这个数字是真实测出来的,不是理论估算。Fine-tune LLaMa-3.1-70B,加上 LoRA(rank=16)之后,训练 throughput 直接下降 40%。本质上是一个让人有些细思极恐的问题:参数只加了 0.3%,计算量也没增加多少,但速度掉了这么多,钱花在哪了?
今天这篇 EuroSys '26 的工作 LoRAFusion 把这个问题剖析得很清楚,顺带还解决了另一个在多任务 fine-tune 场景里被严重忽视的问题:多个 LoRA job 并行时的 pipeline bubble 和 GPU 负载不均衡。
代码已开源:github.com/CentML/lorafusion
先简单回顾一下 LoRA 的公式。对于一个预训练权重矩阵 \(W \in \mathbb{R}^{k \times n}\),LoRA 注入两个低秩矩阵 \(A \in \mathbb{R}^{k \times r}\) 和 \(B \in \mathbb{R}^{r \times n}\)(\(r \ll k, n\)):
\[Y = XW + \alpha \cdot (XA)B = XW + \alpha \cdot SB \]
其中 \(S = XA\) 是中间结果(维度小),\(\alpha\) 是缩放系数。fine-tune 过程中 \(W\) 冻结不更新,只更新 \(A\) 和 \(B\)。
以 LLaMa-3.1-70B 为例:
显存省了将近 8 倍,但训练速度呢?
如下图,LoRA linear 层和普通 frozen linear 层的吞吐量对比:

不管 token 数和 rank 是多少,LoRA linear 比 frozen linear 慢了约 40%(forward)和 36%(backward)。torch.compile 几乎没有帮助。这是为什么?
如下图,一个 LoRA linear 层的运行时细分:

运行时主要分三类:
LoRA 的下投影 \(S = XA\) 的算术强度 \(\mathbb{I}\) 是:
\[\mathbb{I} = \frac{1}{\frac{1}{r} + \frac{1}{n} + \frac{1}{m}} \ll \mathbb{B} \]
由于 \(r\) 很小(比如 16),这个算术强度远低于 H100 的硬件 balance 点(FP16 约 295),确认是 memory-bandwidth-bound。
更直接的数据:用 NVIDIA Nsight Compute 测量,LoRA 模块的 GPU global memory 读写流量比 frozen linear 增加了 2.64 倍。
结论很明确:LoRA 的 runtime overhead 根本原因是大 activation tensor 的重复内存访问,不是 FLOPs 增加。
现实中的 fine-tune 场景经常同时跑多个 LoRA job——超参搜索、多任务适配、多租户服务。这些 job 共用同一个 base model,显存开销很小,完全可以放在同一批 GPU 上跑。
但现有系统(比如 mLoRA)基本是各跑各的,错过了两个重要的优化机会:
如下图,随 global batch size 增大,理想吞吐量的提升幅度:

机会 1:增大 global batch size,降低分布式并行 overhead
把多个 job 的 sample 合并,global batch size 从 4 增到 32,FSDP 的理想吞吐量提升 84%,Pipeline Parallelism 提升 45%。原因是更大的 batch 能更好地 overlap 通信和计算(FSDP),以及填满 pipeline stages 减少 bubble(PP)。
机会 2:跨 job 打包样本,缓解 GPU 负载不均衡
实际数据集的 sequence length 分布差异很大(XSum/CNN/WikiSum 各不相同),导致每个 microbatch 的 token 数波动剧烈。如下图,同一 microbatch_size=4 下,token 数在不同样本间差异悬殊:
这种不均衡在 FSDP 下导致各 rank 等待最慢的那个,在 PP 下形成更多 bubble。如果把多个 job 的样本混合打包,可以有效平衡每个 microbatch 的 token 数,理论上可以带来最高 2.28 倍的吞吐量提升。
如下图,实际测量到的 LoRA fine-tune 相比理想固定长度场景的性能下降(最高约 30%):

这是整个 multi-LoRA fine-tune 领域的核心矛盾:现有系统要么不支持多 job 并行,要么支持了但忽略了 load balance 和 kernel 层面的 redundant memory access。
两个层面的优化,对应两个问题:
如下图,LoRAFusion 整体系统架构:

核心思路:图分裂(Graph Splitting)+ 横向融合
一个直觉上的做法是把整个 LoRA 计算图 fuse 成一个 kernel。但这有两个问题:
LoRAFusion 的解法是在中间张量 \(S = XA\)(维度小,rank 维度)处分裂计算图:
如下图,三种融合策略的对比(recompute / synchronization / graph-split):

\(S\) 的大小是 \(m \times r\)(\(r\) 很小),读写它代价很低。在 \(S\) 处分裂后:
如下图,FusedLoRA 的 kernel 设计(前向和反向):

具体实现:
内存流量实测减少 34%~37%,不需要 recompute,不需要 synchronization。
接下来,扩展到 FusedMultiLoRA,支持同时处理来自不同 job 的 adapter:
如下图,FusedMultiLoRA 的 tile 级 routing 机制:

每个 input tile 带一个 adapter ID,kernel 执行时动态查表,加载对应的 \(A\) 和 \(B\) 矩阵并应用正确的 dropout 和 scaling。Base model 的计算在所有 token 间共享,adapter-specific 逻辑按 tile 独立处理。反向传播时,梯度同样按 adapter ID 路由回各自的 adapter,互不干扰。
这是 LoRAFusion 的另一个核心贡献,解决的是如何把多个 job 的样本打包成均衡的 microbatch。
如下图,Multi-LoRA 调度器的三步工作流:

整个调度分三步:
Step 1:Adapter 分组(Bubble Lemma)
Pipeline 并行下有一个依赖约束:adapter \(i\) 的 global batch \(j\) 的 sample,在它的 backward 完成之前,同一 adapter 的 global batch \(j+1\) 的 sample 不能开始 backward。简单说就是:同一个 adapter 的相邻两个 global batch,在时间线上必须间隔至少 \(S-1\) 个 microbatch(\(S\) 是 pipeline stages 数)。
LoRAFusion 通过把 adapter 按 sequence length 分布分组,并在相邻组之间保持这个间隔,来满足 Bubble Lemma。组内采用"头尾配对"——长 sequence 和短 sequence 的 adapter 配对,提升负载均衡。
Step 2:两阶段 MILP bin-packing
组内的样本需要被打包到 microbatch 里(每个 microbatch 有 token 容量上限)。目标是:①最小化 microbatch 数;②让最空的 microbatch 尽量空(为后续合并留空间)。
这是一个 bin-packing 问题,用两阶段 MILP 求解:
MILP 设置超时,超时则 fallback 到贪心算法。多个 global batch 的打包并行用 multiprocessing 执行。
Step 3:跨 batch 合并
每个 global batch 的最后一个 microbatch 往往是欠填充的(last microbatch 问题)。LoRAFusion 在满足 Bubble Lemma 的前提下,把下一个 global batch 的开头 token 挪进来填充,减少 pipeline bubble。
系统异步性保障:调度时间线性增长(每个样本约 4ms CPU 时间),但调度本身和 GPU 训练并行执行,overhead 被隐藏在 GPU 计算时间内。
如下图,在 H100 上训练 4 个 LoRA adapter 的端到端 throughput 对比:

核心数字:
不同模型的收益规律:
如下图,在 L40S 上的对比结果:

L40S 的绝对提升略小(单卡显存有限,限制了 batch size),但 LoRAFusion 依然保持领先。
如下图,在 4、8、16 卡 H100 上的扩展性对比(DP scaling vs Job scaling):

关键发现:Job-level scaling(用更多 GPU 跑更多并行 job)优于 DP scaling(用更多 GPU 扩展同一 job),在 8 和 16 卡时分别高出 1.18× 和 1.25×。
原因很直接:更多的 job 提供了更大的 global batch,调度灵活性更高,pipeline bubble 更少。LoRAFusion 同时兼容 DP scaling,在 DP scaling 场景下也比 Megatron-LM 快 1.78×,比 mLoRA 快 1.50×。
如下图,FusedLoRA 和 FusedMultiLoRA kernel 的吞吐量对比:

FusedMultiLoRA 在反向传播时需要额外累加多个 adapter 的梯度,有轻微 overhead,但整体依然显著优于 baseline。
如下图,内存流量实测(Nsight Compute):

DRAM 总流量减少 34%~37%,和理论分析完全对应。
如下图,不同 adapter 数量下 LoRAFusion 的 pipeline bubble 比率:

bubble 从将近一半降到一成,调度的效果非常明显。
如下图,LoRAFusion 在 LLaMa-3.1-70B(4 H100)上的加速分解:

从 Megatron-LM baseline 开始,各组件的贡献:
| 累加组件 | 相对 Megatron-LM 加速 |
|---|---|
| + FusedLoRA | 1.13× |
| + Multi-LoRA Zero-Bubble PP | 1.50× |
| + FusedMultiLoRA | 1.72× |
| + Adaptive Scheduling | 2.05× |
kernel fusion + pipeline 调度 + 负载均衡,三个维度缺一不可,组合起来才是最终的 2× 提升。
为什么 torch.compile 解决不了这个问题?
torch.compile 有 compiler-based fusion,但测试结果是对 forward pass 几乎零收益,backward 只有可以忽略的微小改善。原因是 LoRA 的计算图结构对 torch.compile 的 fusion 规则来说不够"友好"——大 activation tensor 在多个分支之间共享,编译器难以自动找到减少内存 traffic 的最优分裂点。LoRAFusion 的 graph-splitting 策略本质上是一个领域特定的 kernel 设计,需要对 LoRA 结构有深入理解。
Kernel 可以直接插入现有系统?
是的,FusedLoRA kernel 可以作为 plug-and-play 替换,直接接到 PEFT Library、LLaMa-Factory 等现有系统上。论文中验证了数值精度等价,不影响模型质量。这个"即插即用"的设计思路值得借鉴——优化和算法分离,降低落地门槛。
LoRA 变体怎么办(DoRA/VeRA)?
论文讨论了可扩展性:这些变体的核心 LoRA 计算结构不变,只是在外面套了 prologue/epilogue 函数,LoRAFusion 的 kernel 可以通过添加这些函数来扩展。更通用的方案是集成进 torch.compile 的 compiler 框架,未来工作。
LoRAFusion 找到了两个被现有系统忽视的问题,并给出了系统性的解法:
两者结合,端到端吞吐量最高提升 1.96×(平均 1.47×),而且代码已经开源,kernel 部分可以直接插到现有系统里。
对做 LLM fine-tune 的工程师来说,这篇工作的实用价值很直接。感兴趣的可以去试一下:github.com/CentML/lorafusion
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。