单机双卡打造高可用AI网关:vLLM与LiteLLM流式代理、Prefix Caching与动态降级实战

9次阅读
没有评论

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

上周给团队内部搭建多智能体(Agent)工作流,在本地一台挂载了两块 RTX 4090 的工作站上跑 Qwen2.5-32B-Instruct。起初以为给模型套个 FastChat 或默认的 vLLM 暴露端口就能直接用,结果三个人同时跑长 Prompt 分析任务时,显存瞬间打满,客户端频频报 502/504 错误,长文本推理下的首字延迟(TTFT)直接飙到了 6 秒以上,用户掐断前端生成后后端还在死命硬算。

暴露出两个致命硬伤: 一是缺乏网关层的并发控制与动态降级机制,二是原生推理引擎缺少针对结构化 System Prompt 的前缀缓存优化 。折腾了两天,最终基于 vLLM (TP=2) + LiteLLM Proxy + Redis 重构了整套架构,实现了多租户 Token 配额、流式 SSE 防抖、双卡并发压榨以及本地故障自动降级公网 API 的全套链路。

一、架构拓扑:本地算力与公网兜底的解耦设计

把裸推理引擎直接暴露给业务端是灾难的根源。一个高可用的 AI 网关至少需要承担身份鉴权、速率限制、请求分流与故障熔断四个职责:

[客户端 / Agent 工作流]
             │ (OpenAI 协议 / SSE 流式传输)
             ▼
      [Nginx 反向代理] ── (关掉缓存缓冲 / 维持长连接心跳)
             │
             ▼
     [LiteLLM Proxy 网关] ── [Redis: Key 配额 / 速率限制 / 统计]
             │
    ┌────────┴────────┐
    │ (权重优先)      │ (熔断 / 排队超时兜底)
    ▼                 ▼
[vLLM 双卡本地实例]   [公网 API: DeepSeek-V3 / Claude 3.5]
(TP=2 / Prefix Cache)

在这种拓扑下,上层所有业务方只需统一对接 LiteLLM 暴露的 /v1/chat/completions 接口。日常请求无缝打在本地私有算力上;一旦本地服务被并发打崩、显存 OOM 或物理重启,LiteLLM 触发健康检查剔除节点,将流量在秒级内无感切至公网兜底模型,确保业务链路零断流。

二、vLLM 引擎深潜:显存榨干与吞吐优化

硬件环境为单机双卡 RTX 4090(24GB x 2),走 PCIe 4.0 x8 + x8 拓扑,没有 NVLink。运行 32B 规模模型,选用 Qwen/Qwen2.5-32B-Instruct-AWQ 进行 4-bit 权重量化部署,在双卡张量并行(Tensor Parallelism, TP=2)下能够空出足够的显存用于 KV Cache。

1. 核心启动参数剖析与防雷

下面是我经过反复压力测试调校出的生产启动脚本:

#!/usr/bin/env bash
# 规避非 NVLink 环境下 PCIe 点对点通信超时问题
export NCCL_P2P_DISABLE=1
export NCCL_IB_DISABLE=1

python3 -m vllm.entrypoints.openai.api_server \
    --model /models/Qwen2.5-32B-Instruct-AWQ \
    --served-model-name qwen2.5-32b-local \
    --tensor-parallel-size 2 \
    --max-model-len 16384 \
    --gpu-memory-utilization 0.92 \
    --enable-prefix-caching \
    --enable-chunked-prefill \
    --max-num-batched-tokens 4096 \
    --max-num-seqs 128 \
    --swap-space 8 \
    --disable-log-requests \
    --port 8000

这里有几个关键参数务必注意:

  • --enable-prefix-caching(核心加速点):Agent 应用通常伴随着极其冗长且重复的 System Prompt 或工具定义(常常占 1k~3k Token)。开启自动前缀缓存后,vLLM 会对匹配历史前缀的 KV Cache 进行复用,实测让 Agent 多轮交互的首字延迟(TTFT)降低了 70% 以上。
  • --enable-chunked-prefill:将大上下文的 Prefill 阶段拆解成切片,避免长输入突然涌入卡死当前正在 Decoding 的其他短流式请求,极大改善多用户并发下的流式打字平滑度。
  • NCCL_P2P_DISABLE=1:在没有 NVLink 桥接的消费级双卡主板上,主板芯片组在 PCIe 寻址处理 P2P 时偶尔会 hang 死或报错 NCCL WARN : Call to connect returned Connection refused,强制关闭 P2P 走系统内存转发虽然损耗微乎其微的吞吐,但能保住极高的稳定性。

三、LiteLLM 网关层:路由分发与动态熔断实装

网关层选用 LiteLLM Proxy,它天然兼容 OpenAI SDK,且支持灵活的 Fallback 规则。编写 litellm_config.yaml 配置文件:

model_list:
  # 1. 优先路由:本地 vLLM
  - model_name: internal-core-model
    litellm_params:
      model: openai/qwen2.5-32b-local
      api_base: http://127.0.0.1:8000/v1
      api_key: "EMPTY"
      timeout: 30
      max_retries: 0

  # 2. 兜底路由:公网高性能模型
  - model_name: internal-fallback-model
    litellm_params:
      model: deepseek/deepseek-chat
      api_key: "os.environ/DEEPSEEK_API_KEY"
      timeout: 60

router_settings:
  routing_strategy: "simple-shuffle"
  fallbacks:
    - internal-core-model: ["internal-fallback-model"]
  context_window_fallbacks:
    - internal-core-model: ["internal-fallback-model"]
  allowed_fails: 2
  cooldown_time: 60

general_settings:
  master_key: "sk-nassky-super-admin-key"
  database_url: "redis://127.0.0.1:6379/0"

使用 Docker Compose 启动网关服务:

services:
  litellm:
    image: ghcr.io/berriai/litellm:main-latest
    container_name: litellm-gateway
    restart: always
    ports:
      - "4000:4000"
    environment:
      - DEEPSEEK_API_KEY=sk-your-deepseek-key
    volumes:
      - ./litellm_config.yaml:/app/config.yaml
    command: ["--config", "/app/config.yaml", "--port", "4000", "--num_workers", "8"]

当本地显存溢出导致 vLLM 抛出 503 Service Unavailable 或请求超过 30 秒无响应时,LiteLLM 会将本地节点打入 60 秒冷却期(cooldown_time: 60),同时立即重试将请求透明降级给公网 API,业务端无感知。

四、排障实录:流式 SSE 截断与内存泄漏攻防

上线第一天,前端同事反馈:长文本输出时经常卡在一半断流,且点击页面上的“停止生成”按钮后,工作站的双卡显存和功耗依旧拉满。

1. Nginx 反向代理层吞掉流式 Chunk

根本原因是 Nginx 默认开启了 Proxy Buffering,会攒够一定字节才下发给客户端,不仅严重劣化 TTFT,还会导致长连接心跳超时断连。必须显式配置 SSE 穿透参数:

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

# 关键:彻底关闭缓冲,启用流式直通
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
chunked_transfer_encoding on;

# 防止 HTTP/1.0 导致的 Keep-Alive 失效
proxy_http_version 1.1;
proxy_set_header Connection "";
}

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