共计 3631 个字符,预计需要花费 10 分钟才能阅读完成。
很多朋友在自己的 RTX 4090 或 NAS/ 工作站上跑本地大模型时,都会遇到一个极度尴尬的死结:单个基座模型(比如 Qwen2.5-7B/14B)跑得飞快,但只要挂上两个不同业务场景的微调 LoRA 权重,或者几个 Agent 并发跑长上下文对话,显存立马爆到 CUDA OOM;换用最简单的 Ollama,吞吐量和长文本缓存又卡得让人怀疑人生。
上个月我在给内部一套多智能体客服工作流做架构改造时,实测了 vLLM 和最新的 SGLang。今天这篇文章不讲空洞理论,直接把我在单卡 24G/ 双卡环境下摸爬滚打出来的 高并发长上下文 + 多 LoRA 热挂载 部署架构完整复盘出来。
一、为什么传统推理框架在此场景下撑不住?
单用户单任务本地跑模型,任何推理后端区别都不大。但一旦进入真实 Agent 业务(比如:带系统级几十行前置 Prompt、多轮 Tool Calling、多个细分领域的专有 LoRA),核心性能瓶颈主要集中在两点:
- KV Cache 重复计算:大部分 Agent 共享相同的基础角色 Prompt 与工具定义,传统推理框架每一次请求都要重新计算一遍长 Context 的 Attention,算力全被无意义消耗。
- LoRA 切换开销:如果用原生 HuggingFace 或基础后端,在推理时切换 LoRA 权重通常需要锁住模型甚至重新加载权重文件,请求排队延迟直接飙到十几秒。
解决这个问题的组合拳是:利用 vLLM 的 Dynamic Multi-LoRA Serving 机制,结合 SGLang 的 RadixAttention(前缀树缓存自动复用)。如果你的场景偏向复杂链式调用、Few-shot 模板固定,SGLang 的吞吐甚至能比原生 vLLM 再高出 2 到 3 倍。
二、vLLM 多 LoRA 生产级热挂载配置实战
vLLM 原生支持在单个基座模型上并发挂载数十个 LoRA 权重,而显存只占用一份基座模型加上微小的 LoRA 适配层(低秩矩阵占用极少)。
以下是我目前跑在生产环境中的 Docker Compose 片段,基座使用 Qwen/Qwen2.5-14B-Instruct-AWQ,挂载两个分别负责“代码审查”和“SQL 生成”的 LoRA 权重:
version: "3.8"
services:
vllm-service:
image: vllm/vllm-openai:latest
container_name: vllm-multi-lora
runtime: nvidia
environment:
- CUDA_VISIBLE_DEVICES=0
- NCCL_IGNORE_DISABLED_P2P=1
ports:
- "8000:8000"
volumes:
- /data/models/Qwen2.5-14B-Instruct-AWQ:/models/base
- /data/loras/lora_sql:/models/lora_sql
- /data/loras/lora_code_review:/models/lora_code_review
command: >
--model /models/base
--quantization awq
--enable-lora
--max-loras 4
--max-lora-rank 64
--lora-modules sql-agent=/models/lora_sql code-agent=/models/lora_code_review
--gpu-memory-utilization 0.92
--max-model-len 8192
--enforce-eager
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
restart: unless-stopped
踩坑细节与核心参数解析:
--max-loras 4:指在同一个 Batch 中允许同时并行的不同 LoRA 最大数量。设得过大(比如超过 16)会占用额外的前向计算缓存,导致吞吐下滑;个人工作站建议设为 4 或 8。--max-lora-rank 64:必须覆盖你训练 LoRA 时的最大 rank。如果训练时是 r=32,这里填 32 即可,能进一步节省显存开销。--enforce-eager:AWQ 量化配合 CUDA Graph 有时会在小显存卡上报错爆显存,加此参数可以跳过 CUDA Graph 的捕获,虽然牺牲约 3%~5% 的峰值吞吐,但能保住长上下文下的绝对稳定性。
三、客户端调用:一套接口分发不同专家模型
服务拉起后,vLLM 会模拟标准的 OpenAI 接口,客户端只需在 model 参数中传入配置好的别名(如 sql-agent 或 code-agent),即可完全免等待切换不同的专家模块:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY"
)
# 1. 调用 SQL 生成专用 LoRA
response_sql = client.chat.completions.create(
model="sql-agent",
messages=[{"role": "system", "content": "你是一名精通 ClickHouse 的数据专家。"},
{"role": "user", "content": "把按天分区的日志表按周聚合 PV 和 UV"}
],
temperature=0.1
)
print("SQL LoRA 输出:\n", response_sql.choices[0].message.content)
# 2. 同一个进程直接无缝调用基座模型本身(不挂载任何 LoRA)response_base = client.chat.completions.create(
model="/models/base",
messages=[{"role": "user", "content": "讲个极客笑话"}],
temperature=0.7
)
print("Base 输出:\n", response_base.choices[0].message.content)
四、进阶方案:长上下文 Agent 场景换用 SGLang
如果你的场景不是多 LoRA,而是长文本、固定 Prompt 前缀的多轮 Agent 交互,SGLang 才是目前真正的吞吐之王。它的核心杀手锏是 RadixAttention。
传统 PagedAttention 会根据请求逐次分配内存,而 SGLang 把所有已经计算过的 KV 缓存在内存中以基数树(Radix Tree)的形式管理。只要请求有共同前缀(例如系统 Prompt、RAG 检索注入的文档内容),无论请求相隔多久,KV Cache 命中率能直接达到 90% 以上,首字延迟(TTFT)从秒级缩短到几十毫秒。
# 启动 SGLang 高性能服务端
python3 -m sglang.launch_server \
--model-path /data/models/Qwen2.5-7B-Instruct \
--port 30000 \
--host 0.0.0.0 \
--context-length 16384 \
--mem-fraction-static 0.85 \
--schedule-heuristic lpm
这里 --schedule-heuristic lpm(Longest Prefix Match)至关重要,它会在任务调度队列里优先将共享前缀最长的请求打包在同一个 Batch 内处理,最大化显存命中率。
五、实测基准压测对比
在单张 RTX 4090(24GB VRAM)环境下,输入 2000 tokens(包含 1800 tokens 共享系统 Prompt + RAG 上下文),输出 256 tokens,并发数 16,实测数据如下:
| 部署方案 | 首字延迟 (TTFT P95) | 总吞吐量 (tokens/s) | 显存利用状态 |
|---|---|---|---|
| Ollama (llama.cpp) | 1850 ms | 42 tokens/s | 动态分配,并发排队严重 |
| vLLM (PagedAttention) | 620 ms | 168 tokens/s | 显存占用稳定,并发支持好 |
| SGLang (RadixAttention) | 85 ms | 310 tokens/s | 高复用率,几乎免去预填充计算 |
总结与极客避坑心得
折腾了一圈本地部署后,我的选型建议非常明确:
- 业务多、LoRA 多:毫不犹豫选 vLLM。它的 Multi-LoRA 动态挂载机制极其稳定,配合 AWQ 量化可以在一张 4090 上同时挂载 4~8 个细分领域的模型,运维成本最低。
- 长文档分析、高频前缀 RAG、复杂多步 Agent:选 SGLang。通过 RadixAttention 复用 Prompt KV Cache 带来的性能飞跃,是任何单纯调参都无法比拟的。
- 生产避坑:在单卡跑多 LoRA 时,务必在微调阶段统一
target_modules(例如都只针对q_proj, k_proj, v_proj, o_proj微调),如果几个 LoRA 的挂载层结构不一致,vLLM 的 Batching 效率会受到明显惩罚。