共计 4264 个字符,预计需要花费 11 分钟才能阅读完成。
上个月在二手平台收了两块涡轮版 RTX 3090,加上原先主力工作站里的那张大霜塔散热 3090,手里终于凑齐了 72GB 显存。本以为接下来就是愉快的“显存自由”跑 DeepSeek-R1 满血蒸馏版,结果现实给了我当头一棒:主板 PCIe 通道不够用、电源功率不够大、机箱积热严重,三张卡根本塞不进同一个机箱里。
退而求其次,我只能把两块涡轮卡丢在机柜的 2U 工控机上,原先的卡继续留在主力塔式工作站。两台物理机、跨局域网千兆 / 万兆混合环境,如何打通算力池,对外提供一个兼容 OpenAI 接口规范、支持高并发流式输出、且具备故障熔断能力的本地推理网关?折腾了整整一个周末,我踩遍了跨节点通信、SSE 流式缓冲中断和显存碎片化等一系列深坑,这里把整套方案和参数配置完整整理出来。
一、架构设计:拒绝跨机 Ray 集群的“美好陷阱”
刚开始做技术选型时,我最先尝试的是 vLLM 官方推荐的跨节点 Pipeline Parallelism 方案,通过 Ray 跨物理机组建单模型分布式推理。测试拓扑如下:工作站(Card 0)通过局域网连接工控机(Card 1, Card 2)。
但在千兆或普通 2.5G 局域网环境下,这是一个极其糟糕的方案。由于层间张量传输需要频繁同步 KV Cache 与权重激活值,跨机通信瓶颈直接把首字延迟(TTFT)拉高到数秒以上,NCCL 还会偶发性死锁报错:
[Ray] RuntimeError: NCCL error in: /pytorch/torch/csrc/cuda/nccl.cpp:644, unhandled system error, NCCL version 2.18.3
Watchdog caught collective operation timeout: WorkNCCL(OpType=ALLREDUCE)
除非你有 InfiniBand 或者双向 RDMA 万兆网络,否则对于大多数极客而言,跨机模型并行在物理延迟上就是死路一条。最终我选择的架构非常清爽实用:
- 工控机(Node A):双卡 3090 做张量并行(Tensor Parallelism = 2),专门承载大尺寸高精度的
Qwen2.5-32B-Instruct-AWQ或DeepSeek-R1-Distill-Qwen-32B。 - 工作站(Node B):单卡 3090 运行快速模型,比如高吞吐的
Qwen2.5-7B-Instruct或嵌入模型bge-m3。 - 聚合网关层:在 Node A 上通过 Docker 运行 LiteLLM 作为统一接入层,负责 API 鉴权、动态健康检查、并发负载均衡与故障重试。
二、底层引擎部署与显存榨干技巧
在 Node A(双卡 3090)上,由于两块卡通过 NVLink 桥接(淘宝淘的旧款接口),NVLink 提供的 P2P 带宽让单机 TP= 2 表现非常亮眼。以下是我的 Systemd 守护启动配置核心参数:
# /etc/systemd/system/vllm-32b.service
[Unit]
Description=vLLM 32B Server
After=network.target
[Service]
Type=simple
User=root
Environment="CUDA_VISIBLE_DEVICES=0,1"
Environment="NCCL_P2P_DISABLE=0"
Environment="NCCL_IB_DISABLE=1"
ExecStart=/root/miniconda3/envs/vllm/bin/python3 -m vllm.entrypoints.openai.api_server \
--model /data/models/DeepSeek-R1-Distill-Qwen-32B-AWQ \
--served-model-name deepseek-r1-32b \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.92 \
--max-model-len 8192 \
--max-num-seqs 64 \
--enforce-eager \
--port 8001
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
几个血泪调参经验:
--gpu-memory-utilization 0.92:默认的 0.90 虽然保守,但留出 92% 能多放几个并发请求的 KV Cache。切记不要拉到 0.95 以上,否则长上下文时容易触发 CUDA OOM 硬崩溃。--enforce-eager:在 3090 这种安培架构卡上,开启 CUDA Graph 有时会导致冷启动编译显存暴增,甚至报显存不足。加上该参数虽然会损失约 3%~5% 的极限吞吐,但服务启动速度极快,稳定性拔群。--max-model-len 8192:32B 模型吃显存极度恐怖,强行拉满 32k 上下文会让 KV Cache 池严重缩水,并发一上来直接报“No available KV cache blocks”,限制在 8k 是目前最均衡的并发阈值。
三、聚合路由:LiteLLM 高可用与故障降级
底层的两个 vLLM 实例分别占用了 192.168.10.100:8001(Node A)和 192.168.10.101:8001(Node B)。接下来利用 LiteLLM 实现一个统一的 OpenAI API 出口,配置文件如下:
# /home/config/litellm_config.yaml
model_list:
- model_name: coder-main
litellm_params:
model: openai/deepseek-r1-32b
api_base: http://192.168.10.100:8001/v1
api_key: "none"
rpm: 120
timeout: 600
- model_name: coder-fallback
litellm_params:
model: openai/qwen-7b
api_base: http://192.168.10.101:8001/v1
api_key: "none"
rpm: 300
router_settings:
routing_strategy: "latency-based-routing"
num_retries: 2
retry_after: 1
timeout: 60
allowed_fails: 2
cooldown_time: 30
general_settings:
master_key: "sk-nassky-local-token-9988"
通过 Docker 快速把 LiteLLM 拉起:
docker run -d \
--name litellm-gateway \
-v /home/config/litellm_config.yaml:/app/config.yaml \
-p 4000:4000 \
--restart always \
ghcr.io/berriai/litellm:main-latest \
--config /app/config.yaml --port 4000
这套配置最精妙的地方在于 故障自动冷却与降级。当我在工作站上打游戏把单卡显存占满关停 vLLM 实例时,LiteLLM 会在两次超时检测后自动触发熔断,把流量引流到健康节点,不会导致外层的 Cherry Studio 或 Dify 工作流抛出 500 报错崩溃。
四、关键踩坑:SSE 流式截断与反向代理缓冲
在所有服务上线后,我使用 Nginx 暴露公网 / 局域网域名时遇到了最搞心态的一个问题:在网页端聊天界面,打字机效果失效了,模型输出经常先卡住 10 秒,然后突然一次性吐出一大串文字,甚至直接报网络连接重置。
这正是典型的 反向代理缓冲机制(Proxy Buffering)破坏了 Server-Sent Events(SSE)长连接。Nginx 默认会试图缓存上游发来的 TCP 数据包,直到达到一定大小才刷新到下游客户端。必须在 Nginx 主机配置中彻底干掉这些缓冲层:
server {
listen 80;
server_name ai.nassky.internal;
location / {
proxy_pass http://127.0.0.1:4000;
# 必须显式声明 HTTP/1.1,避免 keepalive 失效
proxy_http_version 1.1;
proxy_set_header Connection "";
# 彻底关闭缓冲,关键四连!proxy_buffering off;
proxy_cache off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
# 禁用 Nginx 对 chunked 传输的拆分处理
chunked_transfer_encoding on;
# 传递真实 Host 与 IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
配置重新加载(nginx -s reload)后,立即测试流式接口:
curl -N -X POST http://ai.nassky.internal/v1/chat/completions \
-H "Authorization: Bearer sk-nassky-local-token-9988" \
-H "Content-Type: application/json" \
-d '{"model":"coder-main","messages": [{"role":"user","content":" 写一段快速排序的 Rust 实现 "}],"stream": true
}'
看到终端里字符按字节流以毫秒级的间隔流畅吐出,心里那块石头终于落了地。
五、实测压测与吞吐表现
部署完成后,我对这套双机分布式推理网关做了压测,使用测试工具 llmperf 模拟 8 个并发用户同时发起输入长度为 512 Token、输出长度为 1024 Token 的请求:
| 模型与配置 | 首字延迟 (TTFT) | 生成速度 (TPS/ 流式) | 总吞吐量 (Requests/s) | 显存占用 (每卡) |
|---|---|---|---|---|
| DeepSeek-R1-32B (AWQ, 双卡 TP=2) | 280ms | 38.2 tok/s | 1.82 req/s | 21.8 GB |
| Qwen2.5-7B-FP16 (单卡) | 85ms | 89.6 tok/s | 5.14 req/s | 18.4 GB |
可以看到,双卡 3090 跑 32B 量化版本的流式速度达到 38 tokens/ 秒,完全碾压了普通人日常