文章

Multi-LoRA 生产实战:用适配器缓存与冷热分层稳定多租户推理

从多租户推理场景出发,系统讲解 Multi-LoRA 服务中的适配器缓存、冷热分层、Rank 感知调度、驱逐策略和上线检查方法,帮助团队在 GPU 显存约束下稳定服务海量定制化 LoRA 适配器。

背景:微调模型变多以后,瓶颈从训练转向服务

LoRA(Low-Rank Adaptation)大幅降低了大模型定制成本。一个基础模型可以对应几十、几百甚至上千个业务适配器——客服语气、行业术语、企业知识、代码风格、销售话术、合规回答边界,都可以通过不同的 LoRA 适配器承载。

问题是,训练出很多适配器并不等于可以低成本服务很多适配器。生产系统真正面对的是:

  • 多个租户共享同一个基础模型;
  • 每个租户可能使用不同 LoRA;
  • 请求分布有明显冷热差异;
  • 适配器 Rank、大小、加载时间不一致;
  • GPU 显存同时要容纳基础模型、KV Cache、计算 buffer 和 LoRA 权重;
  • 热点租户不能被冷门适配器的加载阻塞。

因此,Multi-LoRA Serving 的核心不是“能不能加载 LoRA”,而是如何把适配器当作一种可调度、可缓存、可驱逐的在线资源

核心原理:共享基础模型,按请求激活适配器

普通部署方式会把每个定制模型当作一个完整模型实例。如果有 100 个租户,即使它们都基于同一个 Llama、Qwen 或 Mistral 基座,也可能重复加载大量相同权重。这种方式简单,但显存浪费严重,冷启动慢,扩缩容也粗糙。

Multi-LoRA 的思路是:

  1. GPU 常驻一份基础模型权重;
  2. LoRA 适配器以小权重增量形式存储;
  3. 请求进入时携带 adapter_id
  4. 推理引擎在执行对应层时,把适配器增量作用到基础模型计算中;
  5. 调度器允许不同 adapter_id 的请求在同一服务中并发执行。

以 vLLM 为例,它已支持在 OpenAI-compatible server 中通过 --enable-lora--lora-modules 启用 LoRA,请求可以像指定普通 model 一样指定 LoRA 适配器。它还提供 max_lorasmax_lora_rankmax_cpu_loras 等配置,说明 LoRA 在服务端已不是单纯的模型文件,而是运行时资源池的一部分:

vllm serve meta-llama/Llama-3.2-3B-Instruct \
  --enable-lora \
  --lora-modules tenant-a=/models/lora/tenant-a tenant-b=/models/lora/tenant-b \
  --max-lora-rank 64

为什么适配器缓存很关键

当适配器数量较少时,全部放入 GPU 显存看起来最简单。但适配器数量增加后,这种方式会遇到三个核心问题:

第一,显存占用不可控。 LoRA 权重虽然比完整模型小得多,但几百个适配器叠加后仍会挤占 KV Cache 空间,导致并发下降或长上下文能力退化。

第二,加载抖动影响 TTFT。 如果请求命中一个不在 GPU 中的适配器,系统需要从本地磁盘、对象存储、CPU 内存或远端节点加载,首 token 延迟(TTFT)会被明显拉长。

第三,调度器容易出现队头阻塞。 如果批次中混入需要加载冷适配器的请求,热适配器请求也可能被拖慢。

因此,生产系统需要把适配器分为以下几层:

层级位置适合对象目标
HotGPU memory高频租户、低延迟租户、近期持续活跃适配器最小化 TTFT
WarmCPU memory / pinned memory中频适配器、可接受轻微加载延迟的租户降低磁盘和网络加载
ColdSSD / object storage低频租户、归档版本、灰度候选版本降低长期存储成本
Disabled不可服务状态过期、撤回、未审核、风险版本保证治理边界

驱逐策略:不能只用 LRU

很多系统自然想到 LRU(Least Recently Used),但 Multi-LoRA 场景仅靠 LRU 是不够的。原因在于不同适配器的 Rank、大小、加载成本、租户等级、SLO 要求差异巨大。

更合理的驱逐分数可以结合以下因素:

eviction_score = w1 × idle_time
               + w2 × memory_size
               + w3 × load_cost
               - w4 × recent_hit_count
               - w5 × tenant_priority
               - w6 × predicted_near_future_demand

其中各因子含义:

因子含义
idle_time多久没有被请求命中
memory_size适配器显存占用,Rank 越大通常越贵
load_cost从 warm/cold 层恢复的耗时
recent_hit_count近期访问频率
tenant_priority高等级客户不应轻易被驱逐
predicted_near_future_demand基于时间段、任务队列或历史规律预测即将到来的请求

工程上可以先从简单版本做起:

  1. Hot 层设显存水位,例如不超过可用显存的 10%–20%;
  2. adapter_id 维护命中次数、最近访问时间、加载耗时、当前 inflight 请求数;
  3. 当 Hot 层超过水位,优先驱逐低频、低优先级、无 inflight 的适配器;
  4. 对高 Rank 适配器设置更严格的缓存预算;
  5. 对高价值租户设置最小驻留时间,避免频繁抖动。

Rank 感知调度:不同适配器不能完全等价

LoRA 的 Rank 直接影响额外计算和内存占用。vLLM 文档也提醒,--max-lora-rank 不应该设置得远高于实际适配器 Rank,否则会浪费内存并影响性能。

生产系统中至少要为每个适配器记录如下元数据:

adapter_id:         tenant-a-support-v3
base_model:         qwen3-32b
rank:               32
size_mb:            180
status:             active
storage_uri:        s3://llm-adapters/tenant-a/support/v3/
slo_class:          gold
warmup_policy:      preload_on_business_hours
max_concurrent_requests: 32

调度器可以根据 Rank 和租户等级做分组:

  • 小 Rank 高频适配器:适合放入热队列,保持高并发;
  • 大 Rank 高频适配器:需要单独预算,避免挤占所有显存;
  • 冷门适配器:允许排队加载,不进入核心低延迟队列;
  • 灰度适配器:限制并发,观察错误率和质量指标后再放量。

这类策略与 S-LoRA、Punica、Chameleon、CaraServe 等研究方向一致:它们都不是简单地“加载 LoRA”,而是在适配器存放、批处理、GPU/CPU 协同、Rank 差异和调度策略上做系统优化。

动态加载的安全边界

动态加载 LoRA 很方便,但不能把 /v1/load_lora_adapter 这类能力直接暴露给不可信调用方。vLLM 文档明确提示,动态 LoRA 配置存在安全风险,不应在非隔离、非完全可信的生产环境中使用。

生产环境至少需要以下边界:

  • adapter_id 必须来自内部注册表,不能由用户直接传任意路径;
  • storage_uri 必须在白名单存储桶和路径前缀下;
  • 适配器文件需要签名、哈希校验和版本记录;
  • 加载、卸载、替换必须有审计日志;
  • 适配器状态要经过 uploaded → validated → staged → active → disabled 流程;
  • 推理请求只能选择当前租户有权限访问的 adapter_id
  • 适配器替换需要灰度发布和回滚机制。

工程落地架构

一个可维护的 Multi-LoRA 服务可以拆成五个模块:

1. Adapter Registry(适配器注册中心)

负责保存适配器元数据:租户、基础模型、Rank、版本、状态、存储地址、校验哈希、发布时间、回滚版本。

2. Adapter Cache Manager(适配器缓存管理器)

负责 Hot / Warm / Cold 分层、命中统计、加载、卸载、驱逐和预热。它需要暴露以下关键指标:

指标含义
adapter_cache_hit_ratio适配器缓存命中率
adapter_gpu_resident_countGPU 常驻适配器数量
adapter_load_latency_ms适配器加载延迟
adapter_eviction_count适配器驱逐次数
adapter_memory_bytes适配器显存占用
cold_adapter_request_count冷适配器请求数量

3. Request Router(请求路由器)

根据 tenant_idtask_typemodel_alias 解析出 adapter_id,并检查权限、状态和灰度比例。

4. Scheduler(调度器)

做 Rank 感知、租户公平性、队列隔离、加载中请求合并。多个请求同时命中同一个冷适配器时,不应重复加载,而应合并等待同一次加载结果。

5. Release Controller(发布控制器)

负责适配器版本发布、灰度、回滚、禁用和审计。适配器本质上是线上模型能力的一部分,必须纳入模型发布治理。

常见误区

误区一:LoRA 很小,所以不用管理

LoRA 单个文件小,不代表几百个适配器同时在线也小。显存不是只给 LoRA 用,还要给 KV Cache、batch buffer、通信 buffer 和框架 runtime 留空间。

误区二:只监控 GPU 利用率就够了

Multi-LoRA 的问题经常表现为 TTFT 抖动、adapter load latency、cache miss、队头阻塞,而不是 GPU 利用率下降。只看 GPU 利用率很容易漏掉真实瓶颈。

误区三:动态加载等于生产弹性

动态加载只是能力,不是治理。没有注册表、权限、签名、灰度和回滚,动态加载会变成线上风险入口。

误区四:所有租户使用同一套缓存策略

不同租户的 SLO、成本预算和调用规律不同。高价值低延迟租户应有更强缓存保障,低频租户可以接受 cold start。

上线检查清单

  • 是否定义 adapter_id 命名规范和版本规范;
  • 是否记录 base_model 与 adapter 的兼容关系;
  • 是否限制最大 Rank、最大适配器大小和最大并发;
  • 是否有 Hot / Warm / Cold 分层策略;
  • 是否有缓存命中率、加载延迟、驱逐次数、显存占用指标;
  • 是否支持请求级 cost attribution(成本归因);
  • 是否支持 adapter 级灰度发布和快速回滚;
  • 是否禁止用户传入任意 LoRA 路径;
  • 是否对适配器文件做哈希校验和签名校验;
  • 是否在压测中覆盖冷启动、热点突增、批量发布、回滚和租户隔离场景。

参考资料

常见问题

Multi-LoRA 服务为什么不能只靠加载多个模型实例解决?
因为完整模型实例会重复占用基础模型权重,显存成本和冷启动成本迅速放大。多租户场景中真正变化的是 LoRA 增量权重,共享基础模型、按请求激活适配器更节省显存,也利于集中调度。
适配器缓存应该优先缓存哪些 LoRA?
优先缓存高频、近期活跃、Rank 较高且加载成本大的适配器,同时保留显存水位,避免挤占 KV Cache 和连续批处理空间。驱逐时不止看最近访问时间,还要看租户等级、适配器大小和未来访问概率。
动态加载 LoRA 是否适合直接暴露到生产环境?
不适合。动态加载必须放在内部控制面后面,需要鉴权、租户隔离、路径白名单、版本签名和审计日志保护,不能直接让外部请求指定任意适配器路径,否则会引入供应链和越权风险。
Multi-LoRA 生产环境最容易被忽略的指标是什么?
GPU 利用率之外,适配器缓存命中率、加载延迟(adapter_load_latency_ms)、驱逐次数、冷适配器请求占比和 TTFT 抖动才是 Multi-LoRA 场景的真实瓶颈,只看 GPU 利用率很容易漏掉问题。