榨干单卡24G显存:vLLM + LiteLLM 搭建生产级本地模型流式网关踩坑实录

4次阅读
没有评论

共计 3698 个字符,预计需要花费 10 分钟才能阅读完成。

很多极客在个人工作站或自建 NAS 上折腾大模型,起手式往往是 ollama run qwen2.5。如果只是终端里自己单线程聊天,Ollama 确实开箱即用;但只要你尝试把本地模型挂载到真实的生产环境——比如接入公司的沉浸式翻译、Cursor、几个高频并发的自动化 Agent 脚本,或者丢给小团队十几个人共享,Ollama 的并发吞吐就会迅速雪崩,显存利用率低下、排队队列打满、首字延迟(TTFT)飙到几秒开外。

上周我彻底掀翻了工作站(Ubuntu 22.04 LTS + 单卡 RTX 4090 24G)原有的部署方案,切换到了 vLLM(推理引擎)+ LiteLLM Proxy(API 网关) 的企业级架构。实测下来,在 20+ 并发流式调用下,整体吞吐提升了将近 3 倍,而且把鉴权、限流、日志监控全部统一了。本文把这套架构的完整落地流程、核心参数取舍以及我踩过的三个深坑毫无保留地分享出来。

一、架构设计:为什么这套组合拳是目前的解法最优?

我们先梳理清楚数据流向。纯裸跑 vLLM 虽然吞吐炸裂,但它本质只是个单机推理引擎,缺乏完善的访问控制(API Key 分配)、速率限制(Rate Limit)以及消费额度统计。而 LiteLLM Proxy 是一个极其轻量的 API 网关,不仅完美兼容 OpenAI 接口规范,还能做健康检查、负载均衡与熔断降级。

  [客户端 /Agent/Cursor/Open WebUI]
                 │ (HTTP / SSE 流式请求)
                 ▼
      ┌─────────────────────┐
      │ LiteLLM Proxy (8000)│ ── 负责:API Key 鉴权、限流、统一日志
      └─────────────────────┘
                 │ (内网反代转发)
                 ▼
      ┌─────────────────────┐
      │ vLLM Engine (8001)  │ ── 负责:PagedAttention、连续批处理、GPU 显存调度
      └─────────────────────┘
                 │
                 ▼
      [NVIDIA RTX 4090 24GB] ── 运行:Qwen2.5-14B-Instruct-AWQ

二、Docker Compose 一键编排落地方案

直接通过纯容器化部署,不污染宿主机系统环境。创建目录 /opt/llm-gateway,并在其中创建 docker-compose.yml 和 litellm_config.yaml。

1. docker-compose.yml 配置

这里选用 vllm/vllm-openai:v0.6.3.post1 镜像,模型挂载本地预先下载好的权重目录,避免每次容器重启重复拉取模型。

version: '3.8'

services:
  vllm-engine:
    image: vllm/vllm-openai:v0.6.3.post1
    container_name: vllm-engine
    runtime: nvidia
    restart: always
    environment:
      - CUDA_VISIBLE_DEVICES=0
      - HF_HUB_ENABLE_HF_TRANSFER=0
    volumes:
      - /data/models/Qwen2.5-14B-Instruct-AWQ:/models/Qwen2.5-14B-Instruct-AWQ
    ports:
      - "8001:8000"
    ipc: host
    command: >
      --model /models/Qwen2.5-14B-Instruct-AWQ
      --served-model-name qwen-14b
      --quantization awq
      --dtype half
      --gpu-memory-utilization 0.90
      --max-model-len 8192
      --max-num-seqs 64
      --block-size 16
      --enforce-eager

  litellm-proxy:
    image: ghcr.io/berriai/litellm:main-v1.52.0
    container_name: litellm-proxy
    restart: always
    ports:
      - "8000:4000"
    volumes:
      - ./litellm_config.yaml:/app/config.yaml
    environment:
      - LITELLM_MASTER_KEY=sk-MasterKeyForAdmin9988
    command: ["--config", "/app/config.yaml", "--port", "4000"]
    depends_on:
      - vllm-engine

2. LiteLLM Proxy 规则配置 (litellm_config.yaml)

配置内网路由规则,将上游模型映射为标准接口:

model_list:
  - model_name: gpt-4o-mini # 伪装成公有云常用名字,方便客户端无缝切换
    litellm_params:
      model: openai/qwen-14b
      api_base: http://vllm-engine:8000/v1
      api_key: "EMPTY"
      rpm: 120 # 每分钟请求限制
      tpm: 100000 # 每分钟 Token 限制

litellm_settings:
  drop_params: true # 丢弃下游传递的不可识别非标准参数,防止 vLLM 报 400
  telemetry: false

general_settings:
  master_key: sk-MasterKeyForAdmin9988
  store_model_in_db: false

三、核心显存参数调优:如何避免 OOM 并跑满带宽?

上面 vLLM 的启动命令看似平平无奇,但每一个参数都是用显存 OOM 炸出来的教训:

  • --gpu-memory-utilization 0.90:给 vLLM 预留 90% 的物理显存(24GB 的 90% 约为 21.6GB)。千万不要直接拉到 0.98,因为 Linux 内核的 CUDA Context 自身需要占用几百 MB,加上模型在动态分配 PyTorch Cache 时留一点 Buffer,否则高并发突发时必定硬件级崩溃。
  • --max-model-len 8192:默认模型如果是 32K 或更长,vLLM 会提前按照最大长度预分配 KV Cache。在 24G 显存限制下,如果盲目开 32K,你的并发槽位(KV Cache 剩余块)会所剩无几。锁死在 8K 上下文,是平衡代码阅读、长文摘要与并发吞吐的甜点位。
  • --enforce-eager: 关键避坑点!vLLM 默认会尝试使用 CUDA Graph 优化推理,这在小 Batch 下能提速 5%~10%。但是在单卡装填 14B 模型且并发度较高时,CUDA Graph 在预热捕获阶段会瞬间吃掉接近 2GB 显存,直接抛出 CUDA out of memory。启用 eager 模式虽然损失极微小的极端加速,但极度稳定,显存开销非常线性。

四、实战踩坑与高并发排障实录

1. 踩坑一:Nginx 反代截断 SSE 流式传输,打字机变成“憋大招”

部署完成后,我将网关通过主机的 Nginx 转发给外部访问。测试终端 API 正常,但一旦网页端使用流式输出(Streaming),前端界面就完全没有打字机效果,而是卡住几秒,然后“啪”的一下把几百字全部吐出来。

原因排查: Nginx 默认启用了响应缓冲区(Proxy Buffering)。vLLM 吐出来的 data: {...}\n\n 块被 Nginx 截留在缓冲区中,直到写满 4KB 或响应结束才一次性发给客户端。

解决办法: 在对应的 Nginx 站点配置中彻底关闭代理缓存并关掉 Chunked 限制:

location /v1/ {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;

    # 核心:必须禁用代理缓冲以支持 SSE
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
    chunked_transfer_encoding on;
}

2. 踩坑二:IPC 命名空间共享导致共享内存不足

如果后续尝试做多卡推理(--tensor-parallel-size 2),Docker 默认分配的 /dev/shm(共享内存)只有 64MB,vLLM 内部依赖 PyTorch Distributed 和 NCCL 通信,在多进程拉起通信的一瞬间容器会直接静默退出(Exit 137)。在 compose 里面必须显式加上 ipc: host,让容器直接使用宿主机的共享内存。

五、实测性能压测数据:vLLM vs Ollama

模型统一选用 Qwen2.5-14B-Instruct-AWQ,Prompt 长度统一设为 512 tokens,生成长度 256 tokens,并发逐步提升,使用 locust 模拟流式请求,实测核心指标对比:

并发请求数 Ollama (首字延迟 TTFT) vLLM (首字延迟 TTFT) Ollama 整体吞吐 vLLM 整体吞吐
1 并发 (单请求) 180 ms 165 ms 58 tokens/s 62 tokens/s
8 并发 1420 ms 310 ms 115 tokens/s 248 tokens/s
正文完
 0
评论(没有评论)
Copyright©2013-2025 Nas的天空 nassky.top 版权所有
 Theme by Puock