预计阅读 7 分钟

LoRA 服务:多适配器动态加载

谁该读这一篇? 业务有多个 fine-tuned adapter 要在线服务的 ML 工程师。 前置阅读: 04-optimizations/03-quantization.md §2-3。 耗时: 30 分钟 学完能: 1. 解释 LoRA 在 attention / MLP 层的注入位置; 2. 让 SGLang 加载多个 adapter 并按请求选; 3. 用 Punica / S-LoRA 思路把多 adapter 同 batch 跑; 4. 评估 LoRA 服务对 throughput 的影响; 5. 选 LoRA vs Full Fine-tune vs Prompt Engineering 的策略。


1. LoRA 复习(30 秒)

原始 Linear:  y = W @ x
LoRA:         y = W @ x + (B @ A) @ x      # B [d, r] · A [r, d]
              其中 r << d,通常 8 / 16 / 32
  • 训练时:只更新 A, B。
  • 推理时:两种选择
  • Merge:加到 W 里,无额外算力。
  • Adapter:保持 A, B 独立,每步加一次。

服务多 adapter 时 merge 不可行(要换 adapter 就换 W),用 adapter 路径。


2. Punica / S-LoRA 思路

朴素 adapter 路径:每请求各自跑一份 A·B·x:

batch 8 个请求各用不同 adapter:
  请求 i: y_i = W @ x_i + B_i @ (A_i @ x_i)

每个 adapter 一个独立 matmul → 不能 batch 一起算。

Punica / S-LoRA 解法:

  • 把所有 adapter 的 A, B 堆成大张量 [N_adapters, r, d]
  • 用 indirection(每请求一个 adapter id)gather 出本请求的 A, B。
  • 写专门的 batched LoRA kernel:一次算 batch 内所有请求的 adapter 贡献。

效果:

  • 单 adapter 时性能接近无 LoRA。
  • 多 adapter 时只损失 5-15%。

源码:srt/lora/


3. SGLang 启用 LoRA

python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-70B-Instruct \
    --lora-paths adapter1=/path/a1 adapter2=/path/a2 \
    --max-loras-per-batch 4 \
    --tp 4

参数:

  • --lora-paths:加载的 adapter 列表(name=path)。
  • --max-loras-per-batch:一个 batch 最多多少不同 adapter。
  • --max-lora-rank:最大 rank(适配最大那个)。

客户端指定:

client.chat.completions.create(
    model="...",
    messages=[...],
    extra_body={"lora_path": "adapter1"}      # 或者用 model 字段
)

4. 动态加载

不想启动时就装所有 adapter?runtime 动态:

curl -X POST localhost:30000/load_lora_adapter \
    -d '{"lora_name": "adapter3", "lora_path": "/path/a3"}'

curl -X POST localhost:30000/unload_lora_adapter \
    -d '{"lora_name": "adapter3"}'

加载需要把 A, B 拷到 GPU;几秒钟。 卸载即时。


5. 性能影响

场景 Throughput 相对基线
无 LoRA 100%
单 adapter 95-98%
4 adapter 同 batch 90-95%
16 adapter 同 batch 80-90%
100 adapter(adapter cache miss 多) 50-70%

max_loras_per_batch 是关键瓶颈。


6. CUDA Graph 与 LoRA

LoRA adapter id 在请求间不同 → batch 形状变化 → CUDA Graph fallback

实测 LoRA 服务 CUDA Graph 利用率显著下降,TPOT 涨。

对策:

  • 限制 adapter 数(hot adapter < 8)。
  • 业务侧把同 adapter 请求路由到同副本(router stick by adapter)。
  • 接受 TPOT 略涨,换 adapter 灵活性。

7. 显存

每个 adapter 占:

2 × num_layers × r × hidden_dim × dtype_bytes

Llama-3-70B, r=16, fp16:

  • 80 × 16 × 8192 × 2 = ~20 MB
  • 100 adapter = ~2 GB

不大,主要是 GPU 上 adapter cache 容量。


8. LoRA 与受约束输出 / 多模态

兼容。 LoRA 只影响 LM weight,不影响:

  • vocab mask(受约束)
  • ViT encoder(多模态)
  • KV cache(除非 LoRA 改了 attention 参数)

不同 adapter 不能共享 KV cache——RadixCache 要用 extra_key=lora_id 隔离。


9. 业务建议

场景 推荐
多业务领域(金融 / 医疗 / 法律) 各自一个 adapter
多语言(中 / 英 / 日) 各一个 adapter,按用户语言路由
多客户(SaaS) 每客户一个 adapter(隔离 + 个性化)
模型轻度优化 单 adapter 全局应用
大幅风格改变 Full fine-tune(LoRA 容量不够)
Prompt 能解决 不用 LoRA

10. 失效模式

现象 排查
adapter 加载失败 path 错 / rank 超 max
adapter 切换慢 adapter cache 满,evict 频繁
同 adapter 不同副本输出不同 副本加载的 adapter 版本不一致
LoRA 输出比 base 还差 adapter 训练问题;先 base 验证
显存涨 adapter cache 容量调小

11. 监控

sglang:lora_active_count                 # 当前加载 adapter 数
sglang:lora_cache_hit_rate
sglang:lora_swap_count                   # adapter swap 频率
sglang:lora_per_request_overhead_ms      # LoRA 给请求加的额外延迟

健康:

  • swap_count < 1 / 分钟(adapter 稳定)。
  • per_request_overhead < 2 ms。

12. 与 Full Fine-tune 的对比

维度 LoRA Full Fine-tune
训练显存 < 10% 100%
训练时间 5-10×
推理性能 略低 与 base 一样
多 adapter 服务 ×
容量上限 r 决定 全模型
部署灵活 ✓ 动态加载 切模型要重启

业务变更频繁、多版本并存 → LoRA。 单一模型、追求极致性能 → Full fine-tune(merge 后即原速)。


13. 小结

  • LoRA 用 A·B 低秩矩阵增量;推理时 adapter 路径。
  • SGLang 用 Punica / S-LoRA 思路做 batched multi-LoRA。
  • max_loras_per_batch 是关键参数。
  • LoRA 触发 CUDA Graph fallback,性能损 5-15%。
  • 多业务 / 多语言 / 多租户场景值得开。

14. 自检

  1. Punica / S-LoRA 思路如何让多 adapter 同 batch 跑?
答案 朴素:每请求独立算 `B_i @ A_i @ x_i` matmul,N 个 adapter = N 次独立 matmul,不能合 batch。 Punica / S-LoRA: (1) 所有 adapter 的 A 矩阵堆成 `A_all: [N_adapters, r, d]`,B 同理。 (2) 给每请求一个 `adapter_id: int`,作为 indirection。 (3) 写**带 indirection 的 batched LoRA kernel**:单次 GPU kernel 内部 gather `A_all[adapter_id]` + `B_all[adapter_id]`,一次性算完 batch 内所有请求的 LoRA 贡献。 (4) Base 权重 W 的 matmul 仍是标准 batched GEMM。 效果:N 个不同 adapter 同 batch 算 LoRA 路径只比单 adapter 慢 5-15%(gather 开销 + GPU 计算非满载),远好于 N 次独立 matmul。 代价:实现复杂,需要专门 CUDA kernel。
  1. LoRA 服务为什么让 CUDA Graph 利用率下降?
答案 CUDA Graph 录制时把"哪些 adapter 在 batch 里、它们的 indirection 模式"也烙进去。 每次 batch 内 adapter 组合变化都触发 graph fallback: (a) **请求间 adapter 切换**:batch=[req_A_with_adapter_1, req_B_with_adapter_2, ...],组合可能性爆炸(N adapter 选 K = C(N,K))。 (b) **adapter 加载 / 卸载**:每次 dynamic load 都打破 graph 元数据。 (c) **adapter rank 不同**:不同 adapter rank 不一致时 indirection shape 变。 对策: - **限制并发 adapter 数**:`--max-loras-per-batch 4` 让 graph 可枚举的组合可控。 - **Router stick by adapter**:同 adapter 请求路由同副本,让一个副本的 batch 主要是一种 adapter。 - 接受 TPOT 涨 10-20% 是用 LoRA 的固定成本。
  1. 不同 adapter 能不能共享 RadixCache?为什么?
答案 **不能**。adapter 改变 LM 权重 → 同 token 序列的 attention output 不同 → KV 数值不同。 如果共享:adapter A 算的 KV 被 adapter B 复用 → adapter B 的输出错误,模型胡说。 实现:RadixCache 用 `RadixKey.extra_key = lora_id` 强制不同 adapter 走独立子树([`radix_cache.py:66`](../sglang/python/sglang/srt/mem_cache/radix_cache.py))。 代价:N 个 adapter 各自独立 cache,命中率从 60% × 1 → 60% × 1/N(每 adapter 流量摊薄)。 对策:(a) 业务用 few-shot prompt 而不是 LoRA 实现差异化;(b) LoRA 部署只对真需要差异化推理逻辑的业务;(c) router sticky 把同 adapter 流量集中,让单 adapter cache 沉淀。
  1. 100 个 adapter 服务的瓶颈在哪?
答案 多瓶颈叠加: (1) **adapter cache 容量**:默认 GPU 上只装最热 N 个 adapter(如 8);100 个轮换 → `lora_swap_count` 高。 (2) **CUDA Graph fallback**:100 个 adapter 排列组合无法枚举,几乎所有 decode batch fallback。 (3) **RadixCache 命中率**:100 个独立 cache 子树,每个流量 = 总流量 / 100,sustainability 差。 (4) **HBM 带宽**:100 个 adapter 的 A/B 矩阵交替访问,L2 cache 抖动。 解决: - 业务侧合并:100 个 → 5-10 个核心 adapter + prompt 模板差异化处理长尾。 - Router 严格 sticky by adapter_id,让单副本只装少数 adapter。 - 接受 100 adapter 场景吞吐降 30-50%;预算上算账。
  1. 多语言场景 LoRA vs prompt engineering 怎么选?
答案 **优先 prompt engineering**,迫不得已才 LoRA。 Prompt engineering:在 system 里写 "Answer in Chinese." 或 few-shot 中文例子,base 模型自己适应。优点:零部署成本、零运维负担、即时生效;缺点:弱模型语言切换效果差、长 prompt 占 token。 LoRA:针对每语言训一个 adapter。优点:base 模型小、单语言效果显著提升;缺点:需要训练数据 / 算力、运维 N adapter、CUDA Graph fallback。 规则: - **强模型(Qwen3 / Llama-3.3 / GPT-4 级)+ 主流语言**:纯 prompt engineering。 - **小模型 + 小语种 / 特定领域翻译**:LoRA 价值大。 - **业务要求严格风格 / 术语**:LoRA。 - **流量 < 10 QPS / 语言**:LoRA 性价比差,prompt engineering。 实战:90% 的多语言场景 prompt engineering 足够;LoRA 是兜底。

15. 下一步

上游源码:sglang/python/sglang/srt/lora/