共计 3890 个字符,预计需要花费 10 分钟才能阅读完成。
很多同学在本地或内网跑模型时,第一反应往往是装个 Ollama 加上 Open-WebUI。跑单个问答确实爽快,但在真实业务场景或者团队内网中,只要并发稍微上来(比如有 3-5 个人同时调用接口,或者接入自动化 Agent 连续发 prompt),Ollama 的排队延迟就会飙升,甚至直接卡死爆显存。
Ollama 的底层封装虽然傻瓜,但在动态批处理(Continuous Batching)和显存精细化控制上几乎没有给工程师留操作空间。上个月我把部门内部的知识库后端从 Ollama 迁移到了 vLLM + LiteLLM 架构,不仅单卡(RTX 4090 24G)跑 DeepSeek-R1-Distill-Qwen-14B 的并发吞吐提升了将近 4 倍,还顺手搞定了多模型混合路由与云端自动降级。今天把这套经过压测验证的生产级配置与踩坑细节完整记录下来。
一、架构设计:为什么是 vLLM + LiteLLM?
本地 AI 服务的痛点通常集中在两端:底层的显存利用率 与上层的协议管理。
- 推理后端(vLLM):核心优势是 PagedAttention 算法,把注意力机制的 KV Cache 切块分页,几乎彻底消除了内存碎片,配合连续批处理,让 GPU 显存利用率压榨到 95% 以上。
- 网关层(LiteLLM):提供完全兼容 OpenAI 标准格式的轻量网关,自带负载均衡、模型 Fallback(降级)、速率限制与 Token 级消耗统计,可以直接无缝对接现有的各类 Agent、Dify 或 NextChat。
二、底层部署:vLLM 的容器化与显存边界配置
不要在宿主机直接跑 Python 源码部署,CUDA 版本依赖容易把人搞崩溃。推荐使用官方 Docker 镜像配合 Compose 管理。这里以搭载 AWQ 4-bit 量化版本的模型为例,既保住了显存,又保证了推理精度与速度。
# docker-compose.vllm.yml
services:
vllm-deepseek:
image: vllm/vllm-openai:v0.7.2
container_name: vllm-deepseek
runtime: nvidia
environment:
- CUDA_VISIBLE_DEVICES=0
- HUGGING_FACE_HUB_TOKEN=your_hf_token # 如需拉取受限模型
ports:
- "8000:8000"
volumes:
- /data/huggingface_cache:/root/.cache/huggingface
- /data/models:/models
ipc: host
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model /models/DeepSeek-R1-Distill-Qwen-14B-AWQ
--served-model-name deepseek-r1-14b
--quantization awq
--trust-remote-code
--dtype half
--max-model-len 8192
--gpu-memory-utilization 0.90
--swap-space 4
--enforce-eager
restart: unless-stopped
关键参数避坑说明:
--gpu-memory-utilization 0.90:我在测试时踩过坑,千万不要直接拉到 0.98。vLLM 启动时会预先根据这个比例分配 KV Cache。留出 10% 的缓冲显存,可以避免 PyTorch 偶发的短时显存峰值直接导致 OOM 挂掉。--max-model-len 8192:上下文窗口尽量根据实际业务卡死。如果设成模型默认的 32k 或 64k,预留给单连接的 KV Cache 尺寸过大,能并发处理的 Request 数量会断崖式下跌。--enforce-eager:对于中小型模型,开启 eager 模式可以跳过冗长的 CUDA Graph 编译时间,在单卡冷启动时能省下 2-3 分钟,同时也能节省一部分 Graph 占用的固定显存。
三、中间件:配置 LiteLLM 统一网关与高可用容灾
启动了单点推理引擎后,我们在上层架设 LiteLLM 作为流量入口。这套配置实现了:优先调用本地 14B 模型;如果本地并发队列打满报错,或者请求超出长度限制,自动无缝降级走云端 DeepSeek-V3 API。
# litellm_config.yaml
model_list:
# 本地主推理引擎
- model_name: local-deepseek
litellm_params:
model: openai/deepseek-r1-14b
api_base: http://vllm-deepseek:8000/v1
api_key: "none"
rpm: 120
timeout: 60
# 云端备用容灾路由
- model_name: deepseek-cloud
litellm_params:
model: openai/deepseek-chat
api_base: https://api.deepseek.com/v1
api_key: "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
router_settings:
routing_strategy: "latency-based-routing"
redis_host: "redis"
redis_port: 6379
timeout: 45
num_retries: 2
fallbacks:
- local-deepseek: ["deepseek-cloud"]
general_settings:
master_key: "sk-litellm-master-key-xyz"
启动 LiteLLM 代理只需要一条简单的 Docker 命令:
docker run -d \
--name litellm-proxy \
--network host \
-v $(pwd)/litellm_config.yaml:/app/config.yaml \
ghcr.io/berriai/litellm:main-latest \
--config /app/config.yaml --port 4000
四、关键踩坑实录与排障细节
1. 反向代理层吞掉 SSE 流式输出(打字机假死)
部署完成后,许多人发现前端请求接口时,文字不是像打字机一样一个个吐出来,而是卡死 10 几秒后一次性全部喷出来。这是由于网关上层通常有一层 Nginx 做域名解析和 SSL 卸载,Nginx 默认的 buffer 缓存把 SSE(Server-Sent Events)数据包拦截了。
必须在对应的 Nginx location 中加入以下强制不缓存策略:
location /v1/chat/completions {
proxy_pass http://127.0.0.1:4000;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 600s;
}
2. 客户端主动中断导致的孤儿计算
在前端页面中,用户经常在模型输出到一半时点击“停止生成”。如果你的代理层没有正确向后端广播 disconnect 信号,vLLM 依然会在显卡中将后续几千个 Token 算完,浪费大量算力。在 Python 业务层封装流式代理时,务必使用异步生成器并监听断开事件:
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import httpx
app = FastAPI()
@app.post("/proxy/stream")
async def proxy_stream(request: Request):
client = httpx.AsyncClient(timeout=60.0)
body = await request.json()
req = client.build_request("POST", "http://localhost:4000/v1/chat/completions", json=body)
res = await client.send(req, stream=True)
async def event_generator():
try:
async for chunk in res.aiter_raw():
# 检测客户端是否已经挂断
if await request.is_disconnected():
await res.aclose()
break
yield chunk
finally:
await client.aclose()
return StreamingResponse(event_generator(), media_type="text/event-stream")
五、实测压测数据对比
我在同一台测试机上(AMD EPYC 7302 + 64G RAM + RTX 4090 24G),对 Ollama 默认部署 与 vLLM+LiteLLM 进行了 Locust 脚本并发压测,模型均为 Qwen-14B 对应量化版本,Prompt 统一为 512 Tokens,生成长度为 256 Tokens:
| 指标 / 引擎方案 | Ollama (Default) | vLLM + LiteLLM 架构 | 提升幅度 |
|---|---|---|---|
| 首字延迟 (TTFT) 并发 =1 | 280 ms | 210 ms | 快 25% |
| 首字延迟 (TTFT) 并发 =10 | 2,450 ms (严重排队) | 540 ms | 正文完
评论(没有评论)
|