共计 4193 个字符,预计需要花费 11 分钟才能阅读完成。
最近团队的私有化 AI 节点频频告警:业务端既要跑 Qwen2.5-7B 搞内部代码补全,又要跑一个针对客服场景微调的 Llama-3-8B,偶尔还需要调用嵌入模型(BGE-M3)做 RAG 检索。起初为了图省事,大家直接在单台配备 24GB 显存(RTX 4090)的 Linux 宿主机上拉了 Ollama,结果稍微上点并发,平均延迟(TTFT)直接飙升到 8 秒以上,流式断流甚至 OOM 崩溃频发。
排查下来原因很直接:Ollama 适合单人桌面端折腾,但面对多任务、多请求并发场景,其底层模型轮转机制极其粗暴——频繁交换显存中的权重,不仅吞吐低,还无法精细化控制每个任务的显存常驻比例。上周我花了两天时间,将整套本地推理栈重构为 vLLM(推理后端)+ LiteLLM(统一智能网关) 的架构,在单卡 24GB 上完美跑起一主一辅两个 7B/8B 模型的并发推理,并发吞吐提升了 4.2 倍,客户端接入代码一行没改。今天把整套落地配置、踩坑细节和压测数据完整梳理出来。
一、为什么放弃 Ollama,转向 vLLM + LiteLLM?
在企业级服务或多人共享的极客工作流中,本地大模型推理核心要解决三个问题: 显存复用率、KV Cache 调度效率以及统一路由认证 。
- KV Cache 碎片化与吞吐差距 :vLLM 的核心优势在于 PagedAttention,它将显存中的 KV 缓存切成固定大小的物理页,消除了长文本生成时的显存碎片。实测在 10 并发下,同样是 Qwen-7B,vLLM 的 Token 输出吞吐能达到原生 HuggingFace 或 Ollama 的 3~4 倍。
- 细粒度显存切分(显存常驻):通过
--gpu-memory-utilization参数,我们可以精确为每个模型实例划定显存上限。例如为代码模型划定 0.55(约 13.2GB),为小尺寸微调对话模型划定 0.35(约 8.4GB),彻底杜绝由于模型切换引发的冷启动与卡顿。 - 统一接入与鉴权(LiteLLM 担当反向代理):对外暴露标准的
/v1/chat/completions接口,并提供 API Key 鉴权、速率限制(Rate Limiting)、失败重试和自动模型映射,前端应用完全无感。
二、架构设计与显存配额规划
我们以单张 24GB 显存设备为例,规划以下实例分布:
- Port 8001 (vLLM 实例 1):
Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4,负责通用代码与复杂推理,分配 50% 显存(约 12GB)。 - Port 8002 (vLLM 实例 2):
meta-llama/Llama-3.2-3B-Instruct(FP16)或 8B 4-bit 量化版本,负责轻量分类与提取,分配 35% 显存(约 8.4GB)。 - Port 4000 (LiteLLM Proxy):轻量容器,负责负载分发、协议聚合与流量监控,仅占用不到 200MB 宿主机内存。
三、实战部署:Docker Compose 一键启动多模型集群
为了保证环境隔离,不污染宿主机 CUDA 环境,我们全部采用 Docker 容器化运行。以下是经过生产检验的 docker-compose.yml 核心配置文件:
version: '3.8'
services:
# 实例 1:主力代码大模型
vllm-qwen:
image: vllm/vllm-openai:v0.6.3
container_name: vllm-qwen
runtime: nvidia
restart: unless-stopped
environment:
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
volumes:
- /data/models/huggingface:/root/.cache/huggingface
ports:
- "8001:8000"
ipc: host
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4
--served-model-name qwen-coder
--gpu-memory-utilization 0.52
--max-model-len 8192
--trust-remote-code
--disable-log-requests
# 实例 2:轻量级对话 / 提取模型
vllm-llama:
image: vllm/vllm-openai:v0.6.3
container_name: vllm-llama
runtime: nvidia
restart: unless-stopped
environment:
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
volumes:
- /data/models/huggingface:/root/.cache/huggingface
ports:
- "8002:8000"
ipc: host
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model meta-llama/Llama-3.2-3B-Instruct
--served-model-name llama-fast
--gpu-memory-utilization 0.38
--max-model-len 4096
--trust-remote-code
--disable-log-requests
# 统一智能网关
litellm-proxy:
image: ghcr.io/berriai/litellm:main-v1.49.0
container_name: litellm-proxy
restart: unless-stopped
ports:
- "4000:4000"
volumes:
- ./litellm-config.yaml:/app/config.yaml
command: ["--config", "/app/config.yaml", "--port", "4000"]
depends_on:
- vllm-qwen
- vllm-llama
紧接着,在同目录下编写 LiteLLM 的路由配置文件 litellm-config.yaml:
model_list:
- model_name: gpt-3.5-turbo # 兼容旧客户端的默认别名映射
litellm_params:
model: openai/qwen-coder
api_base: http://vllm-qwen:8000/v1
api_key: "none"
- model_name: qwen-coder
litellm_params:
model: openai/qwen-coder
api_base: http://vllm-qwen:8000/v1
api_key: "none"
- model_name: llama-fast
litellm_params:
model: openai/llama-fast
api_base: http://vllm-llama:8000/v1
api_key: "none"
general_settings:
master_key: "sk-nassky-local-prod-key" # 网关统一访问密钥
litellm_settings:
drop_params: true # 过滤掉本地模型不支持的前端奇葩参数
request_timeout: 120 # 避免长提示词生成超时
四、关键踩坑记录与性能调优
在实际跑通这套方案时,我在压测阶段遇到了三个典型的“深坑”,如果不加注意,系统极易崩盘:
1. 两个 vLLM 实例争抢显存导致的 CUDA OOM
很多同学直接把两个实例的 --gpu-memory-utilization 设成 0.5 + 0.5 = 1.0,启动时瞬间报错退出。 切记:CUDA Context 本身以及 PyTorch 激活值需要占用固定的静态显存(约 1GB~1.5GB)。单卡 24GB 部署双实例时,两者设定的比例之和建议严格控制在 0.88 ~ 0.90 之间,留出 2GB 左右的缓冲池给底层上下文调度。
2. ipc: host 绝对不能漏掉
在多进程高负载下,PyTorch 共享内存如果受限,容器会莫名其妙出现 RuntimeError: DataLoader worker (pid xxx) is killed by signal: Bus error 或僵死。在 Docker Compose 中必须加上 ipc: host,让容器直接使用宿主机的共享内存。
3. Context Length(上下文长度)与 KV Cache 的置换权衡
如果不需要处理几十万字的超级文档, 务必使用 --max-model-len 压制上下文最大长度 (例如代码模型限制到 8192,提取模型限制到 4096)。如果不加限制,默认模型会尝试申请 32K 甚至 128K 的 KV 预分配空间,这会迅速吃满预留显存,导致并发处理槽位(Concurrency Slots)暴跌至个位数。
五、性能验证与压测结果
服务启动后,使用 Python 编写并发压测脚本,对网关 http://localhost:4000/v1 进行 20 并发、每请求 500 Token 生成的压力测试,实测对比数据如下:
| 部署方案 | 20 并发平均吞吐 (Token/s) | 首字延迟 TTFT (ms) | 显存占用状态 | 双模型并存稳定性 |
|---|---|---|---|---|
| 原生 Ollama 调度 | 34.2 | 3,240 | 动态抢占(剧烈抖动) | 频繁卸载模型,响应超时 |
| vLLM + LiteLLM 混部 | 148.6 | 410 | 恒定 90%(稳如磐石) | 双模型常驻,零冷启动 |
可以看到,吞吐量不仅暴涨超过 4 倍,最关键的是首字生成延迟(TTFT)从令人难以忍受的 3 秒缩减到 400 毫秒左右,代码补全与流式对话的体验有了质的飞跃。
总结与避坑心得
对于个人极客或中小研发团队,不要盲目去采购昂贵的集群多卡机器。利用单张消费级顶级显卡(3090/4090),通过 vLLM 精细显存切分 + LiteLLM 统一协议路由 的思路,完全能用工程化手段榨干硬件潜能。这种架构既保留了如同调用 OpenAI API 般的开发便利性,又在底层实现了毫秒级的响应与稳定的吞吐。下一阶段如果显存吃紧,还可以进一步引入 AWQ 4-bit 量化甚至投机采样(Speculative Decoding),进一步将推理速度推向极限。
===EOF===