单卡搞定多Agent并发:vLLM + LiteLLM 生产级流式高可用代理实战

3次阅读
没有评论

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

上个月在公司搭建内部多 Agent 自动化评审系统时,我踩了一个差点让整个服务雪崩的巨坑:业务线同时跑几十个 AutoGen 和 Dify 的工作流,每秒涌入上百个长上下文 prompt,原本跑得好好的 Ollama 实例直接由于排队等待导致 HTTP 连接成批超时,首字延迟(TTFT)飙到 15 秒以上,显存更是频繁抛出 CUDA OOM。很多团队在把本地大模型从小规模 Demo 推向多 Agent 生产协作时,最容易忽视的就是 推理引擎的吞吐架构与流式网关的连接池治理。

本文抛弃华而不实的玩具级方案,分享一套我反复调优并在生产环境稳定跑了几个月的极客组合拳:底层基于 vLLM 实现 PagedAttention 显存池化与 Continuous Batching,上层利用 LiteLLM 统一收敛 OpenAI 格式协议并实现高可用 Fallback 与连接限流。无论你手里是一张单卡 RTX 4090 还是企业级 A100/H100,这套方案都能榨干每一点显存与算力。

一、为什么 Ollama/llama.cpp 扛不住多 Agent 并发?

很多开发者习惯用 Ollama 本地起模型,跑单问答确实爽,但 Agent 场景有两个致命特征:

  1. 高频共享前缀(System Prompt 极长):多 Agent 往往带着长达 2k~4k tokens 的角色定义与 Tool Schema,每次请求都要重复计算 KV Cache。
  2. 并发突发性强、请求生存周期短:一个主任务可能分裂出 5 个子 Agent 并行检索,瞬间挤爆单工作线程队列。

传统轻量级方案在并发稍微上来时,显存碎片化严重,显存占用跟并发数几乎成线性硬绑定;而 vLLM 的核心优势在于将虚拟内存分页机制引入了 KV Cache(PagedAttention),结合 自动前缀缓存(Automatic Prefix Caching, APC),能让命中相同 System Prompt 的并发请求直接复用显存物理块,TTFT 瞬间砍掉 70% 以上。

二、底层搭建:vLLM 生产级启动与显存极限压榨

我们以 Qwen2.5-14B-Instruct(AWQ 4-bit 量化版)或 7B 原生 FP16 为例,单张 24G 显存(如 RTX 3090/4090)即可轻松打满高并发。以下是经过生产检验的 Docker 部署参数:

# 拉取最新官方优化镜像
docker run -d --gpus all \
  --name vllm-engine \
  --restart always \
  -p 8000:8000 \
  --ipc=host \
  -v /data/models:/root/.cache/huggingface \
  vllm/vllm-openai:latest \
  --model Qwen/Qwen2.5-14B-Instruct-AWQ \
  --quantization awq \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.92 \
  --max-num-seqs 64 \
  --enable-prefix-caching \
  --disable-log-requests

核心参数排障与避坑点:

  • --enable-prefix-caching:必须开启!这是 Agent 多轮交互与固定 System Prompt 的性能救星,开启后命中的 Token 几乎是零延迟预热。
  • --gpu-memory-utilization 0.92:别留太多,但也不要设为 0.98。PyTorch 动态分配和 CUDA context 仍需少量缓冲,0.90~0.93 是防爆显存的黄金区间。
  • --max-num-seqs 64:控制当前迭代步中最大的并发 Batch 数量。如果显存较小、上下文较长,可以适当降到 32,防止并发请求冲爆 KV Cache 触发 preempt(抢占置换)。
  • --ipc=host:必须开启共享主机 IPC 命名空间,否则在多进程和 NCCL 通信时极容易莫名死锁报 Bus Error。

三、中间件治理:LiteLLM 接入、鉴权与高可用降级

有了底层的推理算力,千万不要让前端业务直接裸调 8000 端口。在多 Agent 场景中,网络抖动、模型超载降级、调用量分发是家常便饭。这里选用轻量且功能强悍的 LiteLLM Proxy 作为模型网关。

我们写一个统一编排的 docker-compose.yml 与路由规则文件 litellm_config.yaml:

# config/litellm_config.yaml
model_list:
  # 本地主用模型(高并发)- model_name: qwen-agent
    litellm_params:
      model: openai/Qwen/Qwen2.5-14B-Instruct-AWQ
      api_base: http://vllm-engine:8000/v1
      api_key: "none"
      tpm: 1000000
      rpm: 1200
  
  # 云端保底兜底模型(防本地 OOM 或宕机)- model_name: qwen-agent-fallback
    litellm_params:
      model: deepseek/deepseek-chat
      api_key: os.environ/DEEPSEEK_API_KEY

router_settings:
  routing_strategy: "latency-based-routing" # 按延迟路由
  num_retries: 2
  timeout: 45
  fallbacks:
    - qwen-agent: ["qwen-agent-fallback"] # 本地挂了自动走 API 保底

general_settings:
  master_key: sk-nassky-master-secure-token
  alerting: ["slack"] # 支持钉钉 /Slack 报警

随后编写极简的 docker-compose.yml 启动服务栈:

version: '3.8'

services:
  vllm-engine:
    image: vllm/vllm-openai:latest
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    ipc: host
    restart: unless-stopped
    volumes:
      - /data/models:/root/.cache/huggingface
    environment:
      - HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
    command: >
      --model Qwen/Qwen2.5-14B-Instruct-AWQ
      --quantization awq
      --max-model-len 8192
      --gpu-memory-utilization 0.90
      --max-num-seqs 64
      --enable-prefix-caching
    ports:
      - "8000:8000"

  litellm-proxy:
    image: ghcr.io/berriai/litellm:main-latest
    restart: unless-stopped
    ports:
      - "4000:4000"
    volumes:
      - ./config/litellm_config.yaml:/app/config.yaml
    environment:
      - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
    command: ["--config", "/app/config.yaml", "--port", "4000", "--num_workers", "8"]
    depends_on:
      - vllm-engine

四、实战避坑:解决 SSE 流式代理中的“断流”与超时

在多 Agent(比如 LangChain / CrewAI / AutoGen)联动时,客户端普遍依赖 Server-Sent Events (SSE) 持续接收 Token。网关层常会出现“流传输到一半突然中断”或“客户端提前取消后后端依然在盲目推理(Ghost Inference)”的问题。

1. 反向代理的 Buffer 缓冲踩坑

如果前面挂了 Nginx 作为反向代理,千万要关掉 proxy_buffering,否则 Nginx 会把流式 Token 攒够一块才吐出来,前端不仅感受不到打字机效果,还会因为 TTFT 超时触发重试:

location /v1/ {
    proxy_pass http://127.0.0.1:4000;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Host $host;
    
    # 核心流式配置
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 180s;
    chunked_transfer_encoding on;
}

2. 客户端主动中断的显存释放排查

在 Agent 的 ReAct 循环中,有时由于某个条件已满足,客户端会主动 abort 请求。早期的推理框架会继续把剩下的 max_tokens 生成完,浪费整整几百毫秒的显存。vLLM 内部已经对 HTTP 断开连接做了感知,但前提是 你的代理层不能屏蔽 client_disconnect 信号。在 LiteLLM 中启动参数加上 --num_workers 8 配合异步 FastAPI 事件循环,能确保断连时立即向 vLLM 发送 abort_request,避免显存幽灵占用。

五、真实压测实录:并发 50 场景下的数据表现

为了检验这套架构的可靠性,我使用 locust 模拟 50 个 Agent 进程持续向网关发送携带 2048 token System Prompt 的并发请求,测试不同方案的抗压表现(硬件为单卡 RTX 4090 24G,模型为 Qwen2.5-14B-AWQ):

方案架构 并发数 平均 TTFT (首字延迟) 系统吞吐 (tokens/s) 错误率 (5xx/Timeout)
传统 Ollama (默认配置) 50 并发 12.4s 42.1 tps 28.5% (显存挤爆 / 超时)
vLLM (无 Prefix Cache) 50 并发 3.8s 285.3 tps 0.0%
vLLM + APC + LiteLLM 50 并发 0.68s 410.6 tps 0.0% (极致平稳)

可以看到,开启了 --enable-prefix-caching 的 vLLM 在处理多 Agent 的结构化长 Prompt 时具备降维打击级的优势,首字延迟直接从几秒压到了 680ms,整体系统吞吐暴涨近 10 倍!

总结与极客心得

本地大模型从单人玩具走向团队 Agent 基础设施,核心思路就是 专业的分层治理:让底层的 vLLM 专注于显存分页、算子融合与并行调度,把鉴权、负载均衡、熔断容灾交给轻量灵活的 LiteLLM。对于中小团队或独立开发者而言,不要盲目上昂贵的云端 API 集群,善用好手上的一张消费级显卡加上合理的架构调优,足以撑起每秒数十个并发 Agent 的稳健运转。动手试一下,把你的模型网关真正武装到牙齿吧!

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