单卡跑满4路并发:基于 vLLM + One-API 的高可用私有大模型网关实战

12次阅读
没有评论

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

很多朋友在本地服务器或工作室配了一张 RTX 4090 或 3090 跑开源模型,最开始图省事都会用 Ollama。但在多人共用或者挂接业务 Agent 工作流时,Ollama 很快就会原形毕露:单请求并发阻塞严重、首字生成(TTFT)拖沓、显存释放不及时,一旦并发稍微上去,整个后端立刻排队超时。

本文不扯理论空话,直接针对“单卡 24G 显存跑多并发”和“多 Agent 统一分发鉴权”这两个工程痛点,带你用 vLLM 作为推理推理核心,串联 One-API 搭建一套支持流式推流、动态负载和 Token 配额管控的高可用私有大模型网关。

一、为什么放弃 Ollama,拥抱 vLLM?

Ollama 底层基于 llama.cpp,更适合单用户个人交互,对并行并发处理(Continuous Batching)的支持较弱。而 vLLM 核心引入了 PagedAttention 技术,将显存碎片的浪费率从传统的 60%~80% 压到了 4% 以下。

以经典的 Qwen2.5-7B-Instruct-AWQ 为例:在单张 24G 显存的显卡上,AWQ 4-bit 量化权重只占约 5.5GB 显存,剩下的 18GB 显存全部分配给 KV Cache。这意味着单卡完全可以支撑 4~8 路真实并发流式输出,吞吐量提升至少 300%。

二、底层引擎:vLLM 生产级 Docker 编排与参数调优

很多人启动 vLLM 容易报 OOM(显存溢出),或者请求一多就抛 CUDA out of memory。关键问题出在显存预留比和最大上下文限制上。

1. 编写 docker-compose.vllm.yml

这里以挂载本地 HuggingFace / ModelScope 缓存目录为例,配置如下:

version: '3.8'

services:
  vllm-qwen:
    image: vllm/vllm-openai:latest
    container_name: vllm-qwen2.5-7b
    runtime: nvidia
    environment:
      - CUDA_VISIBLE_DEVICES=0
      - HUGGING_FACE_HUB_TOKEN=your_optional_token
    volumes:
      - /data/models/cache:/root/.cache/huggingface
    ports:
      - "8000:8000"
    ipc: host
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    command: >
      --model Qwen/Qwen2.5-7B-Instruct-AWQ
      --quantization awq
      --port 8000
      --max-model-len 8192
      --gpu-memory-utilization 0.90
      --max-num-seqs 16
      --trust-remote-code
      --served-model-name qwen2.5-7b
    restart: unless-stopped

2. 核心启动参数避坑解析

  • --gpu-memory-utilization 0.90:我在测试中发现千万不要设成 0.95 或更高。PyTorch 自身分配以及某些极端情况下的中间激活值会临时吃显存,设 0.90 是保住容器不崩溃的安全水位线。
  • --max-model-len 8192:虽然模型号称支持 32k 甚至更长上下文,但在并发场景下,直接给满 32k 会瞬间吃空 KV Cache 池。限制为 8192 可以在单卡长文本与多路并发之间取得最佳平衡。
  • --max-num-seqs 16:限制引擎单次处理的最大并发序列数,防止突发流量直接冲垮显存。
  • ipc: host:必须配置,否则多进程通信共享内存受限会导致推理阻塞崩溃。

三、网关层:部署 One-API 实现多租户与请求流控

直接把 vLLM 的 8000 端口暴露给业务层是非常危险的做法:缺乏细粒度的 API Key 权限、无法限制额度、无法无缝热切换模型。One-API 是目前最轻量高效的统一调度网关。

1. 启动 One-API 容器

docker run -d --name one-api \
  --restart always \
  -p 3001:3000 \
  -e TZ=Asia/Shanghai \
  -v /data/one-api/data:/data \
  martinstark/one-api:latest

2. 渠道配置关键操作

容器启动后,浏览器访问 http://< 服务器 IP>:3001,默认账号密码 root / 123456(首次登录务必修改)。

  1. 进入 渠道管理 -> 添加新渠道。
  2. 类型 :选择 自定义渠道 或 OpenAI。
  3. 名称:填写 local-vllm-qwen。
  4. 代理(Base URL):填写 http://172.17.0.1:8000(宿主机内网 IP 端口,禁止直接填 127.0.0.1)。
  5. 模型列表:手动填入 qwen2.5-7b(必须与 vLLM 启动参数 --served-model-name 保持一致)。
  6. 密钥:由于 vLLM 默认无鉴权,这里任意填写一段字符串占位即可(例如 vllm-local-token)。

配置完成后,点击“测试”,看到延迟为几毫秒或几十毫秒的绿色响应即代表打通。

四、实战验证:并发压测与流式响应实测

我们写一个简单的并发异步 Python 脚本,模拟 4 个 Agent 同时调用该接口,验证流式输出与并发耗时:

import asyncio
import time
from openai import AsyncOpenAI

# 指向 One-API 网关地址
client = AsyncOpenAI(
    base_url="http://127.0.0.1:3001/v1",
    api_key="sk-your-one-api-key"
)

async def stream_query(user_id: int, prompt: str):
    start = time.perf_counter()
    first_token_time = None
    total_tokens = 0

    response = await client.chat.completions.create(
        model="qwen2.5-7b",
        messages=[{"role": "user", "content": prompt}],
        stream=True,
        max_tokens=512
    )

    async for chunk in response:
        delta = chunk.choices[0].delta.content
        if delta:
            if first_token_time is None:
                first_token_time = time.perf_counter()
            total_tokens += 1

    total_cost = time.perf_counter() - start
    ttft = (first_token_time - start) * 1000 if first_token_time else 0
    tps = total_tokens / total_cost if total_cost > 0 else 0
    print(f"[Worker {user_id}] 耗时: {total_cost:.2f}s | 首字延迟(TTFT): {ttft:.1f}ms | 吞吐: {tps:.1f} tokens/s")

async def main():
    prompt = "请用 Python 编写一个支持重试机制的异步 HTTP 请求客户端,并解释其关键实现细节。"
    tasks = [stream_query(i, prompt) for i in range(4)]
    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())

实测结果对比(单张 RTX 4090)

运行方案 4 并发首字延迟 (TTFT) 整体平均吞吐 (Token/s) 显存使用情况 长连接稳定性
Ollama (默认配置) ~1850 ms ~32 tokens/s 动态波动,释放滞后 后进请求严重阻塞排队
vLLM + One-API ~240 ms ~145 tokens/s 池化常驻 90% 流式平滑,无卡顿感

总结与避坑心得

搭建这套私有化模型体系,我踩过几个比较深的地雷,供大家排雷:

  1. 内网穿透与跨容器通信:Docker 容器内的 One-API 无法通过 localhost:8000 访问 vLLM 容器。要么组建同一个 Docker 网络,要么直接走宿主机 Docker Bridge IP(Linux 默认 172.17.0.1,Mac 使用 host.docker.internal)。
  2. 模型权重的格式选择:尽量直接使用官方提供的 AWQ 或 GPTQ 分支。相比于自己把 FP16 动态量化,预量化的权重在载入速度和推理吞吐上有显著优势。
  3. 流式响应截断问题:使用 Nginx 等前端反代 One-API 时,务必在反代配置中加上 proxy_buffering off; 和 proxy_read_timeout 600s;,否则 SSE(Server-Sent Events)长连接会被反向代理强行缓冲导致完全丧失打字机效果。

通过这套组合拳,单卡设备的性能潜力才真正被压榨到了极致,无论是给个人做知识库 Agent 还是支撑中小型团队的日常开发辅助,都足够稳健且丝滑。

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