共计 2957 个字符,预计需要花费 8 分钟才能阅读完成。
最近我把内部团队的知识库助手和代码补全后端,从传统的 Ollama 方案整体迁移到了 vLLM 架构。Ollama 在个人笔记本上玩单并发确实很爽,但只要并发数一超过 10,首字延迟(TTFT)直接飙到 8 秒以上,显存更是莫名其妙地出现 OOM 崩溃。为了榨干单张 RTX 4090 和 A100 的每一点显存与算力,我花了整整一周时间做参数微调与压测,整理出这套目前运行非常稳定的生产级高并发流式代理方案。
一、为什么原生 Ollama 不适合生产级高并发?
很多开发者刚接触私有化模型时,最喜欢用 Ollama 或原生 HuggingFace Pipelines。但在多用户并发场景下,原生实现有几个致命短板:
- 显存碎片化严重: 缺少动态显存分页管理,长上下文(Context Length)会强行预分配显存,导致 Batch 跑不上去。
- 缺少连续批处理(Continuous Batching): 传统推理服务必须等一个 Batch 内最长的请求跑完才能释放资源,而 vLLM 的迭代级调度(Iteration-level Scheduling)能做到随来随走。
- 反向代理缓冲吃死流式响应: 前端接入 Nginx 后,如果不关闭 Proxy Buffering,流式输出(SSE)会被缓存攒成一大坨再发给前端,用户体验极差。
二、基础设施环境与模型准备
本次实战环境以单卡 NVIDIA RTX 4090 24G 为例,模型选用目前代码与中文能力兼备的 Qwen2.5-14B-Instruct-AWQ(显存吃紧建议 4-bit 量化,若有 A100-80G 请直接上 BF16)。
1. 环境与依赖锁定
# 创建隔离的 Conda 环境
conda create -n vllm-prod python=3.10 -y
conda activate vllm-prod
# 安装匹配 CUDA 12.1+ 的 vLLM
pip install vllm==0.6.3
pip install ray prometheus-client
2. 启动脚本编写(含核心参数压测结论)
直接通过终端拉起服务往往不稳定,我们编写一个生产级启动脚本 start_vllm.sh:
#!/usr/bin/env bash
# 限制可见显卡
export CUDA_VISIBLE_DEVICES=0
# 防止显存碎片分配错误
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
python -m vllm.entrypoints.openai.api_server \
--model /data/models/Qwen2.5-14B-Instruct-AWQ \
--served-model-name qwen-14b \
--host 0.0.0.0 \
--port 8000 \
--dtype float16 \
--quantization awq \
--gpu-memory-utilization 0.92 \
--max-model-len 8192 \
--max-num-seqs 64 \
--enforce-eager \
--trust-remote-code
核心参数避坑要点:
1. --gpu-memory-utilization 0.92:默认是 0.90。对于 24G 显卡,留 8% 显存(约 1.9G)足以应对 CUDA 内核开销和临时缓存,太高(如 0.98)极易在瞬时大并发下抛出 CUDA out of memory。
2. --max-model-len 8192:Qwen2.5 官方宣称支持 128k 上下文,但别盲目开满!单卡跑 14B 模型,如果开到 32k,KV Cache 会瞬间吃光所有剩余显存,并发并发数直接被限制在个位数。业务实际用多少切多少,8k 覆盖 95% 的对话和知识库场景。
3. --enforce-eager:在 RTX 4090 上,启用 CUDA Graph(默认)有时会导致启动时预热编译极慢甚至卡死显卡驱动。加上该参数能跳过 Graph 预分配,虽然牺牲了 3%~5% 的极速吞吐,但服务稳定性大幅提升。
三、生产网关配置:Nginx 流式代理(SSE)与超时治理
让外部前端直接请求 8000 端口极不安全,必须接入 Nginx 进行网关管理。很多同学接入后发现流式输出变成“卡顿 10 秒后一次性吐出所有字”,核心原因就是 Nginx 默认开启了 Response Buffering。
以下是我线上跑的 Nginx 核心片段配置:
upstream vllm_backend {
server 127.0.0.1:8000;
keepalive 32;
}
server {
listen 80;
server_name ai-api.nassky.top;
location /v1/ {
proxy_pass http://vllm_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
#【核心配置】彻底关闭代理缓冲,确保 SSE 首字即时推送
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 转发长连接标准头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Accel-Buffering no; # 针对某些 CDN 穿透流式
}
}
四、高并发压测实测与数据对比
为了验证这套配置的极限承载力,我使用 Python 的 asyncio + httpx 编写了并发压测脚本,模拟 30 个并发客户端同时发起长提示词(约 1200 Tokens)输入,并要求模型生成 300 Tokens 的流式输出。
| 框架方案 | 并发请求数 | 平均首字延迟 (TTFT) | 总吞吐量 (Tokens/s) | 显存占用状态 |
|---|---|---|---|---|
| Ollama (默认配置) | 30 | 7.84 秒 | 46.2 tok/s | 反复 OOM,进程重启 |
| vLLM (未调优基线) | 30 | 1.92 秒 | 284.1 tok/s | 22.8 GB (显存抖动) |
| vLLM (本文调优后) | 30 | 0.48 秒 | 435.6 tok/s | 22.1 GB (平稳运行) |
可以看到,得益于 PagedAttention 消除的显存碎片,调优后的吞吐量提升了近 10 倍,更关键的是首字延迟控制在了半秒内,用户体感从“卡顿”变成了真正的“秒回”。
总结与避坑心得
搭建私有模型推理服务,千万不要相信“开箱即用”能直接丢上生产环境。有几个极客实操经验总结如下:
- 上下文长度切忌虚荣: 显卡显存是有限的,
--max-model-len决定了 KV Cache 的单位大小,业务够用就行,能缩短 1k 就不要多给 100。 - 日志输出不要开 DEBUG: vLLM 在高并发下如果打印海量内部日志,Python GIL 会被日志 I/O 频繁抢占,导致 GPU 实际利用率跌落到 60% 以下。
- 配合 Systemd 做服务保活: 模型服务即使调配再好,依然可能因异常超长请求导致异常退出。务必使用
systemd或supervisor配置自动重启策略,保障 SLA。
这套方案运行在我目前的 NAS 算力节点与物理服务器上,承载内部数十个微服务的数据打通,整体表现非常坚挺。如果你也有私有化模型的并发痛点,不妨立刻按照本文思路动手试试。