共计 2915 个字符,预计需要花费 8 分钟才能阅读完成。
上周团队内部的 Agent 业务跑自动化测试,十几路并发一打进来,原本跑在本地服务器上的 Ollama 服务直接卡死:首字延迟(TTFT)从几百毫秒飙到了 9 秒以上,显卡风扇狂转但算力利用率极低,队列越堆越长最终导致上游网关全面超时。
对于个人尝鲜或单人桌面客户端,Ollama 确实开箱即用;但在真正的多任务 Agent、知识库检索(RAG)或小团队并发场景下,单请求单批处理的设计会迅速成为性能瓶颈。为了榨干手上这台配有单张 RTX 3090(24G 显存)的工作站,我把整套推理后端彻底迁移到了 vLLM,配合 Qwen2.5-32B-Instruct-AWQ 模型,最终在保持显存不爆的前提下,把高并发吞吐提升了近 5 倍。以下是完整的生产级部署、优化与踩坑记录。
一、为什么在并发场景下必须换掉 Ollama?
根本差异在于显存调度和批处理机制:
- PagedAttention(分页注意力机制): 传统推理框架为每个请求预分配连续的显存块存放 KV Cache,存在大量内存碎片。vLLM 借鉴了操作系统的虚拟内存分页思想,将 KV 缓存切成固定大小的 Block,动态分配,把显存浪费率从 60%+ 压到了 4% 以内。
- Continuous Batching(连续批处理): 传统框架按整个批次中最长的请求来等待结束,而 vLLM 可以在 Token 级别动态插入新请求、移出已完成请求,GPU Core 几乎时刻保持饱和运转。
- Automatic Prefix Caching(自动前缀缓存): 在多轮对话或固定 System Prompt 的 Agent 场景下,相同前缀的 KV Cache 无需重复计算,直接内存复用,首字时间直接呈断崖式下降。
二、选型计算:单卡 24G 显存如何塞进 32B 模型?
Qwen2.5-32B 是目前中等尺寸模型中代码与推理能力的第一梯队。但如果使用标准的 FP16 精度,仅模型权重就需要 32 × 2 = 64GB 显存,至少需要两张 A6000 或三张 3090 才能跑。
想要在单卡 24G 运行,必须采用 4-bit 量化。经过对比 GPTQ 与 AWQ,AWQ(Activation-aware Weight Quantization) 在低比特下对激活异常值的保护更好,语言逻辑和代码能力几乎没有感知损失:
- 模型权重占用: 32B 的 4-bit 权重占用约 18GB 显存。
- 剩余显存(KV Cache 空间):
24GB - 18GB ≈ 6GB。 - 这 6GB 显存配合 PagedAttention,在 8K 上下文限制下,足够稳定承载 4~8 个并发请求同时进行流式生成。
三、生产级 Docker Compose 编排与核心参数
不要使用全局 pip install vllm 污染宿主机环境,CUDA 驱动版本和 PyTorch 的绑定极易冲突。官方 Docker 镜像是最稳妥的底座。
在你的工作目录创建 docker-compose.yml:
services:
vllm-qwen:
image: vllm/vllm-openai:v0.6.3.post1
container_name: vllm-qwen2.5-32b
runtime: nvidia
restart: unless-stopped
environment:
- CUDA_VISIBLE_DEVICES=0
- HF_HUB_ENABLE_HF_TRANSFER=0
volumes:
- /data/models/Qwen2.5-32B-Instruct-AWQ:/model
- /root/.cache/huggingface:/root/.cache/huggingface
ports:
- "8000:8000"
ipc: host
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command:
- "--model"
- "/model"
- "--served-model-name"
- "qwen2.5-32b"
- "--max-model-len"
- "8192"
- "--gpu-memory-utilization"
- "0.92"
- "--quantization"
- "awq"
- "--enable-prefix-caching"
- "--disable-log-requests"
- "--port"
- "8000"
核心参数排坑解析:
ipc: host: 绝对不能省。 PyTorch 多进程通信依赖共享内存,Docker 默认只有 64MB 的/dev/shm,高负载下会直接抛出Bus error (core dumped)导致容器暴毙。--gpu-memory-utilization 0.92:告诉 vLLM 最大分配 92% 的显存(约 22GB)。留出 8% 给 CUDA 运行时上下文和驱动开销,设为 1.0 必触发 OOM(Out of Memory)。--max-model-len 8192:必须显式裁剪。Qwen2.5 原生支持 128k 上下文,如果放任默认,vLLM 会尝试为超长上下文预估显存,留给并发 KV Cache 的空间会被直接压缩归零,导致服务无法启动。--enable-prefix-caching: 并发提速神器。 开启后,多轮对话和长 System Prompt 请求的 TTFT 将大幅度缩短。
四、关键网关层:Nginx 反向代理流式传输配置
vLLM 暴露的标准 OpenAI 兼容接口走的是 SSE(Server-Sent Events)协议。很多同学在外层套 Nginx 后发现流式输出变成了“整块弹出”,或者请求长文本时中途断开,90% 都是由于缓冲机制配置不当。
在 Nginx 中必须显式关闭代理缓冲并调整超时时间:
server {
listen 80;
server_name llm.internal.local;
location /v1/ {
proxy_pass http://127.0.0.1:8000/v1/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 核心:关闭代理缓冲,保证 Token 即时推送到客户端
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding on;
# 推理长文本需要较长等待,避免默认 60s 断开
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
五、性能对比与选型实测
在单卡 RTX 3090、Prompt 长度 1200 Token(包含系统提示词)、生成 300 Token 的条件下,使用脚本模拟不同并发量,测试数据如下:
| 框架 / 模型 | 并发数 | 平均 TTFT (首字延迟) | 总吞吐量 (Tokens/s) | 显存状态 |
|---|---|---|---|---|
| Ollama (Qwen2.5-14B-Q4) | 1 | 420ms | 42 tok/s | 11GB (动态) |
| Ollama (Qwen2.5-14B-Q4) | 8 | 7800ms | 48 tok/s (严重阻塞) | 13GB |
| vLLM (Qwen2.5-32B-AWQ) | 1 | 380ms | 36 tok/s | 22GB (预分配锁定) |
| vLLM (Qwen2.5-32B-AWQ) | 正文完
评论(没有评论)
|