共计 3500 个字符,预计需要花费 9 分钟才能阅读完成。
上个月给团队内部搭建一套代码辅助与文档问答 Agent 集群,原以为直接起一个 Ollama 加 WebUI 就能收工,结果内测第一天就翻车了:三个同事同时发起超长 Context 的检索,显存瞬间打满,HTTP 响应直接挂起超时,队列排得遥遥无期。在单卡(RTX 4090 24G 或单张 A100)资源极其有限的场景下,单纯靠“玩具级”运行框架跑多用户并发,本质上是自寻烦恼。
为了把显存利用率榨干到极致,同时提供完全兼容 OpenAI 规范的生产级 API,我花了两天时间重构了整套推理链路:底层采用 vLLM(基于 PagedAttention + Continuous Batching) 负责模型加速推理,前端接入 LiteLLM Proxy 提供负载均衡、密钥鉴权和流式传输缓冲。这篇文章把调参细节、反向代理配置以及我踩过的几个典型“深坑”完整复盘出来。
一、为什么是 vLLM + LiteLLM 架构?
很多极客在本地跑 LLM 时容易陷入一个误区:觉得显卡算力不够就该加卡。但实际排查性能瓶颈会发现,KV Cache 显存碎片化 和静态批处理等待(Static Batching)才是导致吞吐量崩溃的元凶。
- 连续批处理(Continuous Batching):传统的 Hugging Face 接口必须等 Batch 里最长的请求生成完才能释放显存,而 vLLM 可以在 Token 级别动态插入新请求并剔除已完成请求。
- PagedAttention 显存管理:将 KV Cache 切分成物理块(Blocks),像操作系统虚拟内存一样动态映射,几乎将显存浪费率从 60%~80% 降到 4% 以下。
- 网关层解耦:vLLM 官方自带的 API 服务在面对大量网络连接抖动、心跳检测和速率限制时并不够健壮。引入轻量级的 LiteLLM Proxy 作为前置反代,不仅原生兼容所有 OpenAI SDK,还能轻松实现 Token 计数和多模型路由。
二、底层推理服务落地:vLLM 核心参数精细化调优
模型我们以 Qwen2.5-14B-Instruct-GPTQ-Int4 为例。14B 量化模型在单张 24G 显存的 RTX 4090 上能够留出充裕的 KV Cache 空间,性价比极高。
这里直接给出我经过数十次压测验证后的 docker-compose.yml 启动配置:
version: '3.8'
services:
vllm-engine:
image: vllm/vllm-openai:latest
container_name: vllm-engine
runtime: nvidia
environment:
- CUDA_VISIBLE_DEVICES=0
volumes:
- /data/models/Qwen2.5-14B-Instruct-GPTQ-Int4:/model
ports:
- "8000:8000"
ipc: host
command: >
--model /model
--served-model-name qwen-14b
--quantization gptq
--dtype half
--max-model-len 8192
--gpu-memory-utilization 0.92
--max-num-seqs 64
--enforce-eager
restart: unless-stopped
踩坑预警:这几个参数必须重点关注
--gpu-memory-utilization 0.92:默认是 0.9。如果你没有其他后台进程占用显存,建议设到 0.92~0.95。但切记不要设为 1.0,给 CUDA 运行时预留至少 1GB 内存,否则加载复杂算子时会出现不可预知的 OOM。--max-model-len 8192:强烈建议根据实际业务场景显式限制上下文长度。Qwen 原生支持 32k 甚至更长,但上下文越长,每个请求占用的 KV Cache 呈线性暴增。盲目开放 32k 会导致只要有两人发长文,其他所有并发连接全部被挂起等待。ipc: host与--enforce-eager:Docker 容器运行 PyTorch 很容易因共享内存不足直接 SIGBUS 崩溃,ipc: host是必加项;在消费级显卡(如 4090/3090)上,开启--enforce-eager可以绕过部分 Triton 内核在特定驱动下的编译卡顿问题,虽然损失极微小的峰值算力,但显著降低了启动耗时与显存异常。
三、网关层构建:LiteLLM 代理与流式长连接优化
在底层推理跑通后,不要直接把 8000 端口暴露给客户端。通过 LiteLLM Proxy 统一接管鉴权与流式传输(Server-Sent Events)。新建 litellm_config.yaml:
model_list:
- model_name: gpt-4o # 将客户端请求的通用模型名映射到底层模型
litellm_params:
model: openai/qwen-14b
api_base: http://vllm-engine:8000/v1
api_key: "none"
stream_timeout: 60
general_settings:
master_key: "sk-nassky-admin-secret-key"
alerting: ["slack"]
litellm_settings:
drop_params: true
set_verbose: false
telemetry: false
启动 LiteLLM 容器挂载该配置:
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
流式代理(SSE)被 Nginx 截断的排障细节
如果你的架构最外层还挂了一层 Nginx,很多小伙伴会发现流式打字效果消失了,前端必须等大模型整段文字生成完毕才一次性蹦出来。这是因为 Nginx 默认开启了反向代理缓冲区。必须在 Nginx 对应 Location 下追加以下三行指令:
location /v1/ {
proxy_pass http://127.0.0.1:4000/v1/;
# 彻底关闭缓冲,保证流式打字即时吐出
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding on;
# 调大长连接超时,防止推理思考过久被网关切断
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
四、真实压力测试与数据对比
为了验证这套架构的真实表现,我用 Python 编写了一个异步压测脚本,模拟 32 个并发用户同时向服务发送平均 1200 Token 的 Prompt,要求回复至少 500 Token。
| 对比指标 | Ollama (默认配置) | FastAPI + HuggingFace | vLLM + LiteLLM (本文方案) |
|---|---|---|---|
| 并发 32 请求成功率 | 56.2% (出现请求排队超时) | 31.2% (显存直接溢出崩溃) | 100% |
| 首字延迟 (TTFT) | 4.2 秒 | 9.8 秒 | 0.85 秒 |
| 整体系统吞吐量 | 48 tokens/s | 32 tokens/s | 318 tokens/s |
| 显存碎片率 | 未统计 | ~65% | < 3% |
压测实测下来发现:PagedAttention 机制在应对并发波峰时具有压倒性优势。即便 32 个请求同时涌入,vLLM 也能将请求切分进不同的槽位并行处理,平均首字返回时间(TTFT)保持在 1 秒以内,完全感觉不到卡顿。
总结与避坑心得
折腾大模型本地化或私有化部署,核心从来都不是“能跑起来”,而是“跑得稳、扛得住”。总结几条实操中换来的教训:
- 别拿测试工具当生产服务:开发阶段用 Ollama 或 LM Studio 很轻量,但一旦要供多个 Agent 或团队协作使用,立即切换到 vLLM 这种原生支持流水线调度的推理后端。
- 硬限制必须走在前面:在网关层坚决配置好
max_tokens和 Context 上限,绝对不要让终端用户无限制地塞入几十万字的无序文本,否则一张单卡会被单个恶意或低效请求彻底堵死。 - 善用监控看板:vLLM 原生暴露了
/metrics端点,直接用 Prometheus 抓取其中的vllm:num_requests_waiting指标。只要这个数值持续大于 5,就说明到了必须加卡或者部署张量并行(Tensor Parallelism)的时候。
按照这套流水线搭建下来,你的个人服务器或私有 GPU 主机就能以极低的资源开销,提供媲美商业化 API 体验的高并发流式大模型服务。