单卡搞定大模型并发与降本:vLLM + LiteLLM 生产级流式网关搭建实战

2次阅读
没有评论

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

最近团队内部需要将原本对接 OpenAI 商业接口的业务(代码审查、内部 RAG 检索)平滑迁移到内网私有化模型上。我们手头只有一台配备单张 RTX 4090 24G 的老工控机和一台双路 A10 24G 的开发机。直接用 HuggingFace Transformers 原生 generate 甚至哪怕只套一个 FastAPI 跑推理,并发只要冲到 4 或 5,显存碎片就直接爆满(OOM),首字延迟(TTFT)飙升到 8 秒以上。

经过两周的踩坑与压测调优,我们最终定型了一套“vLLM(推理加速引擎)+ LiteLLM(统一智能网关)”的高并发架构。这套方案不仅完美兼容 OpenAI API 规范,无缝对接现有前端和业务代码,还顺带把显存吞吐拉满了 4 倍以上,并自带 Fallback 熔断降级与精确的 Token 计费日志。

一、为什么是 vLLM + LiteLLM 组合?

在工程选型阶段,很多人纠结过 Ollama、TGI(Text Generation Inference)和 vLLM。简单说说我的实测体会:

  • Ollama:适合极客本地跑着玩,内部依赖 llama.cpp,对量化支持极好,但在多人高并发并发吞吐场景下,请求队列管理非常简单粗暴,容易出现线程饿死。
  • TGI:HuggingFace 的亲儿子,稳定且对 Docker 支持友好,但配置极其繁琐,且对部分国内开源架构(如 Qwen 系列某些魔改注意力机制)的跟进速度有时会慢半拍。
  • vLLM:核心在于 PagedAttention 机制。它像操作系统虚拟内存分页一样管理 KV Cache,彻底解决了显存碎片化问题。实测在处理长上下文(如 8k/16k)的多并发请求时,其 Throughput(吞吐量)是原生推理框架的 5-10 倍。

但是,纯 vLLM 裸奔对外提供服务存在致命缺陷:缺乏细粒度的 API 鉴权、无法动态路由、没有流量平滑截流能力,而且如果模型突然挂掉,前端业务就直接吃 500 报错。LiteLLM 恰好补齐了这块拼图——它作为轻量级 Proxy,能够做请求负载均衡、错误自动 Fallback 到外部备用 API(如智谱或 DeepSeek 官方云接口),并提供完整的 Prometheus 监控指标。

二、底层引擎部署:vLLM 的关键参数避坑

我们以目前单卡表现极其优秀的 Qwen/Qwen2.5-7B-Instruct(或 DeepSeek-R1-Distill-Qwen-8B)为例。不要用裸机 Python 环境装,CUDA 版本和 PyTorch 的编译依赖能折腾掉你半天时间。强烈建议直接拉官方 Docker 镜像。

# 拉取官方最新容器镜像(建议锁定稳定版本)docker pull vllm/vllm-openai:v0.6.3.post1

# 启动 vLLM 推理容器
docker run -d --gpus all \
  --name vllm-qwen7b \
  --restart always \
  --shm-size 8g \
  -p 8000:8000 \
  -v /data/models:/root/.cache/huggingface \
  vllm/vllm-openai:v0.6.3.post1 \
  --model Qwen/Qwen2.5-7B-Instruct \
  --trust-remote-code \
  --dtype bfloat16 \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --max-num-seqs 64 \
  --enforce-eager

踩坑实录与核心参数解析:

  1. --shm-size 8g: 绝对不能省 。PyTorch 底层张量共享依赖 IPC 共享内存,Docker 默认的 64MB 共享内存在并发稍高时会直接导致容器崩溃。
  2. --gpu-memory-utilization 0.90:默认是 0.95。但在 24G 显存的消费级显卡(如 3090/4090)上,显卡本身桌面渲染或系统开销会吃掉几百兆,拉到 0.95 极易触发 CUDA OOM,建议留 10% 显存作为安全缓冲。
  3. --max-num-seqs 64:定义单个 Batch 内处理的最大并发请求数。如果你的业务场景多为短文本对话,可以适当调高到 128;如果是复杂的代码生成或长文档总结,建议锁在 32~64,避免单个请求的计算膨胀把整个显卡卡死。
  4. --enforce-eager:vLLM 默认会尝试使用 CUDA Graph 优化推理延迟。但在部分消费级架构驱动或者特定量化模型下,CUDA Graph 捕获阶段会出现显存泄漏甚至死锁,开启 eager 模式能够牺牲极其微弱的单次推理延迟(约 3%),换来绝对的稳定性。

三、中间层架构:LiteLLM 网关与容灾配置

后端推理引擎跑起来后,我们通过 Docker Compose 部署 LiteLLM 网关层。LiteLLM 不仅能抹平所有接口差异,还能在我们本地显卡算力被撑爆或者硬件故障时,丝滑无感地将请求降级分流到商业云端 API。

首先准备网关配置文件 config.yaml:

model_list:
  # 本地私有化主模型
  - model_name: internal-chat-model
    litellm_params:
      model: openai/Qwen/Qwen2.5-7B-Instruct
      api_base: http://vllm-qwen7b:8000/v1
      api_key: "none"
      stream_timeout: 10
      tpm: 100000
      rpm: 300

  # 备用容灾模型:当本地 vLLM 宕机或超载时自动触发
  - model_name: internal-chat-model
    litellm_params:
      model: deepseek/deepseek-chat
      api_key: "sk-xxxxxxxxxxxxxxxxxxxxxxxx"
      rpm: 1000

litellm_settings:
  drop_params: true  # 自动剔除不同模型间不兼容的非标准请求参数
  set_verbose: false
  num_retries: 2     # 本地服务失败时重试次数

general_settings:
  master_key: "sk-MySuperSecretAdminKey" # 管理网关的主 Key

接下来是 docker-compose.yml 协同定义:

version: '3.8'

services:
  litellm-proxy:
    image: ghcr.io/berriai/litellm:main-v1.49.0
    container_name: litellm-proxy
    restart: always
    ports:
      - "4000:4000"
    volumes:
      - ./config.yaml:/app/config.yaml
    environment:
      - DATABASE_URL=file:/app/litellm.db
    command: ["--config", "/app/config.yaml", "--port", "4000", "--num_workers", "4"]

执行 docker compose up -d,至此,网关便对外在 4000 端口暴露了完全符合 OpenAI 标准的接口。

四、业务端接入与流式响应校验

在业务层调用时,前端或微服务客户端完全不需要感知本地 vLLM 的具体实现,代码与使用 OpenAI 官方 SDK 毫无二致:

from openai import OpenAI

client = OpenAI(
    api_key="sk-MySuperSecretAdminKey",
    base_url="http://192.168.1.100:4000" # 替换为你的网关 IP
)

response = client.chat.completions.create(
    model="internal-chat-model",
    messages=[{"role": "system", "content": "你是一个严谨的代码审查专家。"},
        {"role": "user", "content": "用 Python 写一个支持指数避退重试的 HTTP 请求装饰器。"}
    ],
    stream=True,
    temperature=0.2,
)

for chunk in response:
    content = chunk.choices[0].delta.content
    if content:
        print(content, end="", flush=True)

五、单卡并发压力测试与选型建议

我们使用 locust 对单张 RTX 4090 运行 Qwen2.5-7B 进行了流式推理压测(提示词上下文 ~1.2k tokens,期望输出 ~512 tokens):

并发用户数 vLLM 平均 TTFT (首字延迟) FastAPI+Transformers TTFT vLLM 显存分配状态 请求失败率
1 42ms 180ms 显存缓存利用率 12% 0%
16 110ms 2400ms Paged KV Cache 利用率 68% 0%
32 320ms OOM 崩溃 Paged KV Cache 利用率 91% 0%
64 850ms – 队列等待(通过 LiteLLM 限流排队) 0%

可以看到,vLLM 的内存分页管理保证了显存在高负载下不会被不可预期的单次过长响应直接干爆。当并发冲破 64 时,请求只是在队列中发生稍微可感知的等待,而不是直接报 CUDA Out of Memory。

总结与避坑心得

如果你正准备搭建小团队或内网环境的模型推理服务,请记住以下三点硬核结论:

  • 不要把鸡蛋放在一个进程里 :永远不要用 Python 原生 Web 框架直接调用 model.generate 对外提供并发服务。把推理引擎(vLLM)和流量调度(LiteLLM)物理拆开,不仅能提升稳定性,后续横向扩展加卡也极为容易。
  • 注意物理流式代理问题 :如果在外层又套了 Nginx 进行端口映射,务必在 Nginx 的 location 中加上 proxy_buffering off; 和 proxy_cache off;,否则流式输出会被 Nginx 强制攒批缓存,前端页面看到的将不再是一个字一个字蹦出来的效果,而是卡死数秒后突然“喷”出一大坨文字。
  • 消费级显卡的甜点位 :单张 4090 (24G) 跑 7B~8B 模型是最具性价比的生产级搭配,能够把单卡并发稳定承载在 30~50 左右;如果需要上 14B 或 32B 模型,果断采用 AWQ 4-bit 量化或者直接组两张卡做 Tensor Parallelism(--tensor-parallel-size 2),千万不要强行在单卡上开 BF16 硬撑长上下文。

===

正文完
 0
评论(没有评论)