共计 3488 个字符,预计需要花费 9 分钟才能阅读完成。
最近在给团队内部搭建一套私有代码助手和知识库 Agent 网关时,我又遇到了那个老生常谈的硬骨头:本地开源大模型在单个测试请求下跑得飞快,一旦前端并发进来五六个流式对话(SSE),首字延迟(TTFT)直接飙到 8 秒以上,显存瞬间报 OOM(Out of Memory),下游应用连片 504 超时。
很多朋友在用 Ollama 跑个人玩具项目时觉得挺顺手,但只要把它挂到真实的小型业务系统或多人 Agent 场景下,就会发现它的并发调度和队列控制极其脆弱。为了在单张 RTX 4090(24G)或者云端单卡 A10/A100 上榨干每一滴算力,并对外提供兼容 OpenAI 接口的高并发流式代理,我把生产栈重构成了 vLLM + LiteLLM 组合。这篇文章记录了我实测调优后的完整架构、配置文件、核心避坑点以及压测对比数据。
一、痛点拆解:为什么单卡并发总是崩?
在深入配置之前,先理清两个最致命的瓶颈:
- KV Cache 静态分配浪费或碎片化 :原生 HuggingFace 或简易部署框架通常会为每次推理按最大上下文预分配连续显存,导致两三个 4k 上下文请求就把 24G 显存吃光;vLLM 的
PagedAttention虽然解决了内存碎片,但如果显存预留比例(gpu-memory-utilization)没算准,CUDA 上下文或突发长上下文依然会瞬间打崩服务。 - 流式长连接拖垮代理层 :SSE(Server-Sent Events)是长连接。传统的 Python 业务网关如果异步 IO 没处理好,或者反向代理缓冲区(Buffer)没关,会导致 Chunk 积压在代理层,客户端体验到的不是“打字机”流式效果,而是卡顿几秒后突然吐出一大坨文字。
二、底层推理引擎:vLLM 生产级启动配置实战
以部署当前实用性价比极高的 Qwen2.5-7B-Instruct(或 AWQ 量化版 14B)为例。直接裸跑命令往往会踩雷,推荐用 Docker 固化环境参数:
# docker-compose.vllm.yml
services:
vllm-engine:
image: vllm/vllm-openai:latest
container_name: vllm-engine
runtime: nvidia
restart: unless-stopped
environment:
- CUDA_VISIBLE_DEVICES=0
- NCCL_IGNORE_DISABLED_P2P=1
volumes:
- /data/huggingface/hub:/root/.cache/huggingface/hub
ports:
- "8000:8000"
ipc: host
command: >
--model Qwen/Qwen2.5-7B-Instruct
--served-model-name qwen-7b
--host 0.0.0.0
--port 8000
--gpu-memory-utilization 0.90
--max-model-len 8192
--max-num-seqs 64
--block-size 16
--enable-prefix-caching
--disable-log-requests
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
核心参数排坑指北:
--gpu-memory-utilization 0.90:别填 0.98!模型权重加载完后,剩下的显存全会被划分为 KV Cache Block。设得太满,一旦 CUDA 运行时在处理长 Prompt 临时分配激活显存时,就会直接抛出CUDA out of memory。留 10% 是最稳妥的缓冲垫。--max-num-seqs 64:单卡最大并发推理序列数。不要信默认的几百,在消费级单卡上设成 32 到 64 最为健康。并发打满时,vLLM 会优雅地在服务端排队排队(Queue),而不是直接把显存干爆。--enable-prefix-caching: 强烈建议开启! 尤其做 Agent 系统或知识库 RAG 时,System Prompt 和多轮上下文开头很多是重合的。开启此项后,重合前缀直接复用 KV Cache,能把首字延迟(TTFT)降低 60% 以上。ipc: host:必须配置,防止多进程间通信或 PyTorch 共享内存不足导致进程诡异暴毙。
三、流式网关层:LiteLLM 路由与连接池优化
vLLM 负责底层的张量计算与 KV 缓存,但在多客户端接入、密钥鉴权、熔断降级和用量监控层面,裸暴露 vLLM 是不明智的。LiteLLM 提供了极低开销的代理封装层。
编写 litellm-config.yaml:
model_list:
- model_name: gpt-4o-mini # 对外伪装成标准 OpenAI 模型名
litellm_params:
model: openai/qwen-7b
api_base: http://vllm-engine:8000/v1
api_key: "EMPTY"
timeout: 120
stream_timeout: 30
router_settings:
routing_strategy: latency-based-routing
num_retries: 2
timeout: 60
general_settings:
master_key: sk-nassky-master-secret-token
启动 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
网关流式代理深度排错:
如果 LiteLLM 外面还套了一层 Nginx,很多同学会遇到“流式不流,一顿一顿”的现象。务必在反向代理的 location 块中强制关闭缓冲:
location /v1/chat/completions {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Connection "";
# 关键配置:禁用一切反向代理缓冲,保证实时 SSE 吐字
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
四、压测实录:并发与首字延迟(TTFT)实测
我在本地单卡 RTX 4090 机器上,使用 locust 对搭建完成的服务进行并发压力测试。输入 Prompt 长度约为 1200 Tokens(典型 RAG 检索场景),输出限制为 300 Tokens。
| 并发并发数(Concurrency) | 原生 Ollama (默认配置) TTFT | vLLM (无 Prefix Cache) TTFT | vLLM (开启 Prefix Cache) TTFT | 生成吞吐 (Tokens/s) |
|---|---|---|---|---|
| c = 1 | 380 ms | 210 ms | 110 ms | 95 t/s |
| c = 8 | 2150 ms | 480 ms | 230 ms | 420 t/s |
| c = 32 | 超时 / OOM 频繁 | 1280 ms | 650 ms | 1150 t/s |
| c = 64 | 完全服务不可用 | 2890 ms (请求平稳排队) | 1420 ms (无爆显存) | 1320 t/s |
实测数据说明了一切:在开启 prefix-caching 并合理调校了 block-size 与并发序列队列后,单卡 4090 即使在 32 并发的高压突发下,首字吐出依然能控制在 1 秒以内,总吞吐量翻了 10 倍以上。最重要的是, 服务没有因为并发突增而崩溃 OOM,超出的请求全部在队列中安全挂起等待调度。
总结与避坑心得
单卡搞定大模型并发,拼的不是参数量大小,而是对显存分配和 IO 流的精细控制:
- 显存预留必须克制 :永远给 CUDA 上下文保留 10%~15% 的余量,绝不要把
gpu-memory-utilization填到 1.0。 - RAG 场景必开前缀缓存 :
--enable-prefix-caching是单卡性价比最高的神级开关,能吃掉大量重复的检索上下文计算。 - 链路流控不能省 :推理引擎负责“快”,LiteLLM / Nginx 负责“稳”。关闭所有中间层 HTTP 缓冲区,流式 Agent 的响应体验才能丝滑跟手。
按照这套方案跑起来,即便是单卡小机器,也足以稳定支撑一个小团队的生产力工具和本地知识库调用了。极客折腾的意义就在于此:用精打细算的工程调优,把消费级硬件的生产力压榨到极限。