榨干单卡24G显存:vLLM + Qwen2.5-32B 生产级高并发API搭建与压测排坑

5次阅读
没有评论

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

上周团队内部的 Agent 业务跑自动化测试,十几路并发一打进来,原本跑在本地服务器上的 Ollama 服务直接卡死:首字延迟(TTFT)从几百毫秒飙到了 9 秒以上,显卡风扇狂转但算力利用率极低,队列越堆越长最终导致上游网关全面超时。

对于个人尝鲜或单人桌面客户端,Ollama 确实开箱即用;但在真正的多任务 Agent、知识库检索(RAG)或小团队并发场景下,单请求单批处理的设计会迅速成为性能瓶颈。为了榨干手上这台配有单张 RTX 3090(24G 显存)的工作站,我把整套推理后端彻底迁移到了 vLLM,配合 Qwen2.5-32B-Instruct-AWQ 模型,最终在保持显存不爆的前提下,把高并发吞吐提升了近 5 倍。以下是完整的生产级部署、优化与踩坑记录。

一、为什么在并发场景下必须换掉 Ollama?

根本差异在于显存调度和批处理机制:

  • PagedAttention(分页注意力机制): 传统推理框架为每个请求预分配连续的显存块存放 KV Cache,存在大量内存碎片。vLLM 借鉴了操作系统的虚拟内存分页思想,将 KV 缓存切成固定大小的 Block,动态分配,把显存浪费率从 60%+ 压到了 4% 以内。
  • Continuous Batching(连续批处理): 传统框架按整个批次中最长的请求来等待结束,而 vLLM 可以在 Token 级别动态插入新请求、移出已完成请求,GPU Core 几乎时刻保持饱和运转。
  • Automatic Prefix Caching(自动前缀缓存): 在多轮对话或固定 System Prompt 的 Agent 场景下,相同前缀的 KV Cache 无需重复计算,直接内存复用,首字时间直接呈断崖式下降。

二、选型计算:单卡 24G 显存如何塞进 32B 模型?

Qwen2.5-32B 是目前中等尺寸模型中代码与推理能力的第一梯队。但如果使用标准的 FP16 精度,仅模型权重就需要 32 × 2 = 64GB 显存,至少需要两张 A6000 或三张 3090 才能跑。

想要在单卡 24G 运行,必须采用 4-bit 量化。经过对比 GPTQ 与 AWQ,AWQ(Activation-aware Weight Quantization) 在低比特下对激活异常值的保护更好,语言逻辑和代码能力几乎没有感知损失:

  • 模型权重占用: 32B 的 4-bit 权重占用约 18GB 显存。
  • 剩余显存(KV Cache 空间): 24GB - 18GB ≈ 6GB。
  • 这 6GB 显存配合 PagedAttention,在 8K 上下文限制下,足够稳定承载 4~8 个并发请求同时进行流式生成。

三、生产级 Docker Compose 编排与核心参数

不要使用全局 pip install vllm 污染宿主机环境,CUDA 驱动版本和 PyTorch 的绑定极易冲突。官方 Docker 镜像是最稳妥的底座。

在你的工作目录创建 docker-compose.yml:

services:
  vllm-qwen:
    image: vllm/vllm-openai:v0.6.3.post1
    container_name: vllm-qwen2.5-32b
    runtime: nvidia
    restart: unless-stopped
    environment:
      - CUDA_VISIBLE_DEVICES=0
      - HF_HUB_ENABLE_HF_TRANSFER=0
    volumes:
      - /data/models/Qwen2.5-32B-Instruct-AWQ:/model
      - /root/.cache/huggingface:/root/.cache/huggingface
    ports:
      - "8000:8000"
    ipc: host
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    command:
      - "--model"
      - "/model"
      - "--served-model-name"
      - "qwen2.5-32b"
      - "--max-model-len"
      - "8192"
      - "--gpu-memory-utilization"
      - "0.92"
      - "--quantization"
      - "awq"
      - "--enable-prefix-caching"
      - "--disable-log-requests"
      - "--port"
      - "8000"

核心参数排坑解析:

  1. ipc: host: 绝对不能省。 PyTorch 多进程通信依赖共享内存,Docker 默认只有 64MB 的 /dev/shm,高负载下会直接抛出 Bus error (core dumped) 导致容器暴毙。
  2. --gpu-memory-utilization 0.92:告诉 vLLM 最大分配 92% 的显存(约 22GB)。留出 8% 给 CUDA 运行时上下文和驱动开销,设为 1.0 必触发 OOM(Out of Memory)。
  3. --max-model-len 8192:必须显式裁剪。Qwen2.5 原生支持 128k 上下文,如果放任默认,vLLM 会尝试为超长上下文预估显存,留给并发 KV Cache 的空间会被直接压缩归零,导致服务无法启动。
  4. --enable-prefix-caching: 并发提速神器。 开启后,多轮对话和长 System Prompt 请求的 TTFT 将大幅度缩短。

四、关键网关层:Nginx 反向代理流式传输配置

vLLM 暴露的标准 OpenAI 兼容接口走的是 SSE(Server-Sent Events)协议。很多同学在外层套 Nginx 后发现流式输出变成了“整块弹出”,或者请求长文本时中途断开,90% 都是由于缓冲机制配置不当。

在 Nginx 中必须显式关闭代理缓冲并调整超时时间:

server {
    listen 80;
    server_name llm.internal.local;

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

        # 核心:关闭代理缓冲,保证 Token 即时推送到客户端
        proxy_buffering off;
        proxy_cache off;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        chunked_transfer_encoding on;

        # 推理长文本需要较长等待,避免默认 60s 断开
        proxy_read_timeout 600s;
        proxy_send_timeout 600s;
    }
}

五、性能对比与选型实测

在单卡 RTX 3090、Prompt 长度 1200 Token(包含系统提示词)、生成 300 Token 的条件下,使用脚本模拟不同并发量,测试数据如下:

框架 / 模型 并发数 平均 TTFT (首字延迟) 总吞吐量 (Tokens/s) 显存状态
Ollama (Qwen2.5-14B-Q4) 1 420ms 42 tok/s 11GB (动态)
Ollama (Qwen2.5-14B-Q4) 8 7800ms 48 tok/s (严重阻塞) 13GB
vLLM (Qwen2.5-32B-AWQ) 1 380ms 36 tok/s 22GB (预分配锁定)
vLLM (Qwen2.5-32B-AWQ) 正文完
 0
评论(没有评论)
Copyright©2013-2025 Nas的天空 nassky.top 版权所有
 Theme by Puock