共计 3782 个字符,预计需要花费 10 分钟才能阅读完成。
最近给团队内部搭建一套统一的大模型 API 接入层,目标很明确:白天跑本地私有化部署的开源模型(Qwen2.5-14B/32B),用来处理敏感数据和内部 Agent 批量分析;夜间或流量打满时,自动平滑无缝降级到公网商业模型(如 Claude 3.5 Sonnet 或 DeepSeek API)。原本以为用常见的 FastAPI 简单包一层转发就行,结果一上并发测试,流式响应(SSE)卡死、显存碎片引发 OOM、连接数打满 504 Gateway Timeout 连番轰炸。折腾了两天,最终敲定了 vLLM + One-API 的架构方案,这里把完整的部署编排、关键配置和避坑记录整理出来。
一、架构选型与真实痛点拆解
很多同学在本地或私有服务器跑模型,第一反应是直接用 Ollama。Ollama 在单人本地开发测试确实很香,但在多用户、多 Agent 并发发起长文本请求的场景下,它的吞吐表现和显存调度机制很容易成为瓶颈。我们需要满足以下三个硬指标:
- 高吞吐并发推理:必须支持 PagedAttention 和连续批处理(Continuous Batching),最大化榨干显卡算力。
- 协议标准化与统一鉴权:对外必须输出标准的 OpenAI 兼容格式接口,且能精细化控制每个内部业务线 Token 额度。
- 故障隔离与自动 Fallback:本地显存打满或服务挂掉时,客户端零感知自动切换到备用节点或公有云 API。
最终的落地拓扑图很简单:客户端 /Agent → One-API 网关(负责分流、鉴权、限流、Fallback)→ vLLM 推理集群(负责本地高性能推理)/ 商业模型供应商。
二、vLLM 本地高性能推理节点部署
我们采用双卡 RTX 4090(24G × 2)的机器作为承载节点,模型选用性价比极高的 Qwen/Qwen2.5-32B-Instruct-AWQ。AWQ 4-bit 量化后的显存占用大概在 20GB 左右,正好可以双卡张量并行(TP=2),给长上下文(KV Cache)留出充足的显存空间。
1. 启动容器与关键参数陷阱
直接上生产环境经过验证的 docker-compose.yml 片段:
version: '3.8'
services:
vllm-qwen:
image: vllm/vllm-openai:v0.6.3
container_name: vllm-qwen-32b
runtime: nvidia
restart: always
environment:
- CUDA_VISIBLE_DEVICES=0,1
- HUGGINGFACE_HUB_CACHE=/root/.cache/huggingface
volumes:
- /data/models/huggingface:/root/.cache/huggingface
ports:
- "8000:8000"
ipc: host
command:
- "--model=Qwen/Qwen2.5-32B-Instruct-AWQ"
- "--tensor-parallel-size=2"
- "--quantization=awq"
- "--max-model-len=8192"
- "--gpu-memory-utilization=0.92"
- "--enforce-eager"
- "--trust-remote-code"
- "--served-model-name=qwen2.5-32b"
2. 这里有三个极为关键的参数陷阱:
ipc: host必须加:多卡张量并行(Tensor Parallelism)底层依赖 NCCL 进行显卡间通信。如果不共享宿主机的 IPC 内存,在高并发时极易直接触发Watchdog caught collective operation timeout错误。--gpu-memory-utilization=0.92:默认是 0.9。如果你跑 8K 以上的长上下文,千万别直接拉到 0.98,PyTorch 的激活显存波动会直接导致CUDA out of memory。留出 8% 显存作为安全缓冲是非常必要的。--enforce-eager:在某些 Ampere / Ada 架构卡上,vLLM 默认开启 CUDA Graphs 会在捕获阶段预先占用显存。如果遇到莫名其妙的初始化显存爆掉,加上这个参数强制走 Eager 模式,能省下近 2G 的静态图显存,代价是首 Token 延迟有微秒级的极轻微增加,但并发容量显著提升。
三、接入 One-API 构建韧性网关
有了 vLLM 暴露的 8000 端口,千万不要直接暴露给应用层使用。我们使用 One-API(或轻量化的 New-API)作为门面网关。
1. One-API 容器化配置
使用 SQLite 在并发请求超过 30 QPS 时很容易锁库,生产必须直接上 PostgreSQL 或 MySQL:
one-api:
image: calciumion/new-api:latest
container_name: api-gateway
restart: always
ports:
- "3000:3000"
environment:
- SQL_DSN=postgres://api_user:secret_pass@postgres:5432/api_gateway?sslmode=disable
- REDIS_CONN_STRING=redis://:redis_pass@redis:6379/0
- MEMORY_CACHE_ENABLED=true
- GLOBAL_WEB_RATE_LIMIT=1000
depends_on:
- postgres
- redis
2. 渠道与路由策略实操配置
部署完成后进入 Web 控制台,在【渠道】模块做两组关键配置:
- 本地高优先级渠道 :类型选
自定义渠道 (OpenAI 协议),Base URL 填http://vllm-qwen:8000,模型填qwen2.5-32b,重试次数设为 1,渠道优先级设为10。 - 云端降级备用渠道:类型选商业模型服务商(或 SiliconFlow / DeepSeek 官方),同样映射模型重定向别名为
qwen2.5-32b,渠道优先级设为5。
这样设置后,网关在收到请求时,会优先派发到本地 vLLM 集群。一旦本地并发超载返回 503/429,或者显卡死锁断联,One-API 会在秒级内自动触发重试机制,将请求自动降级切到云端 API,上游调用的 Agent 不会出现任何中断异常。
四、反向代理与流式长连接踩坑排障
在 One-API 外层通常还会挂一层 Nginx 处理 HTTPS 和域名映射,这里是导致前端流式打印“吐字卡顿、憋大字”的重灾区。
默认配置下,Nginx 会对上游 HTTP 响应启用 Proxy Buffering,导致大模型生成的 SSE(Server-Sent Events)数据包被 Nginx 暂存,达到 4KB 或缓冲区满才一次性刷给客户端,体验极差。
server {
listen 443 ssl http2;
server_name api.nassky.top;
# SSL 证书配置略...
location /v1/chat/completions {
proxy_pass http://127.0.0.1:3000;
# 关掉缓冲区,保证 SSE 能够逐字流式推送
proxy_buffering off;
proxy_cache off;
# 保持长连接
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 必须调大超时时间!长文本生成可能耗时数分钟
proxy_connect_timeout 300s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
# 禁用分块传输编码的额外开销处理
chunked_transfer_encoding on;
}
}
五、压力测试与实测表现
我们使用 locust 对直接请求 vLLM 与经过 One-API 代理网关进行并发吞吐对比。输入 Prompt 长度约为 1200 Tokens,期望生成 512 Tokens:
| 指标项 | 裸跑 Ollama (单实例) | vLLM (TP=2) | One-API + vLLM 架构 |
|---|---|---|---|
| 并发用户数 (Users) | 10 | 50 | 50 |
| 首字延迟 (TTFT) | 1.42s | 0.38s | 0.41s |
| 生成吞吐 (Tokens/s) | 48.2 | 386.5 | 381.2 |
| 请求失败率 (Failure Rate) | 14.2% (排队超时) | 0.0% | 0.0% |
实测数据表明,在引入 One-API 网关后,首字延迟仅仅增加了不到 30 毫秒,吞吐量损耗在 1.5% 以内,但换来了完整的鉴权 Token 管理、日志审计以及至关重要的宕机自动 Fallback 能力。
总结与避坑心得
在本地大模型与生产业务深度绑定的过程中,不要迷信任何全家桶封装方案。vLLM 负责把硬件吞吐压榨到极限,One-API 负责把接口标准化与路由韧性做好,各司其职才是最稳妥的架构解法。
最后提炼三条排障铁律:
- 双卡以上部署 vLLM 一定要确认共享内存(
shm-size或ipc: host),否则多卡同步必暴毙。 - Nginx 代理层必须显式声明
proxy_buffering off;,否则流式输出体验全毁。 - 给本地大模型配置合理的 Max Context Len,不要盲目拉满到模型理论上限(例如 128K),显存的 KV Cache 空间直接决定了你能支撑的并发请求数。