TL;DR#

将两台 DGX Spark 从 vLLM TP=2 跨节点部署(RPC 超时、频繁崩溃)迁移到 每台独立运行 vLLM + Bifrost 负载均衡网关后:

  • 稳定性:至少在这轮测试里没有再复现崩溃,P95 延迟大约在 1,400ms(20 tokens)
  • 吞吐:单机 ~15 tok/s,双机并发 ~56 tok/s(Bifrost 开销 <1ms)
  • 可用性:双机独立 + 自动故障转移,不再有单点故障
  • 可扩展:水平加节点即可,不受 TP 上限约束

背景:为什么放弃 TP=2 跨节点#

之前的实践中,我在两台 DGX Spark 上尝试了 vLLM 的 TP=2(跨节点张量并行)部署:

这次测试里最直接的观察是:TP=2 跨节点能跑通但不稳定,日志里反复出现:

TimeoutError: RPC call to sample_tokens timed out.
No available shared memory broadcast block found in 60 seconds.
vllm.v1.engine.exceptions.EngineDeadError: EngineCore encountered an issue.

这和社区里提到的跨节点 TP hang 风险基本一致。如果更看重稳定性,我会更倾向:两台节点各自独立运行 TP=1,前面加负载均衡器。

新架构#

                    ┌──────────────────┐
                    │  Bifrost Gateway │
                    │  100.97.87.120   │
                    │  :8080           │
                    │  负载均衡 + 故障转移  │
                    └────────┬─────────┘
                             │
              ┌──────────────┴──────────────┐
              │   200Gbps 内部网络            │
              │   (enp1s0f0np0, 0.2ms)       │
              │                              │
     ┌────────▼────────┐          ┌──────────▼───────┐
     │  Server 1       │          │  Server 2         │
     │  192.168.200.101│          │192.168.200.102    │
     │  vLLM :8030     │          │ vLLM :8030        │
     │  TP=1 (独立)    │          │ TP=1 (独立)       │
     └─────────────────┘          └───────────────────┘

每台 DGX Spark 独立运行一个完整的 vLLM 实例(TP=1),Bifrost 作为 LLM 网关在两台机器前做负载均衡和故障转移。

为什么选择 Bifrost 而不是 LiteLLM#

特性 LiteLLM Bifrost
语言 Python Go
单请求开销 ~500μs <11μs(快 50x)
部署 依赖多,镜像 ~500MB 单二进制,镜像 ~50MB
负载均衡 基础 round-robin 自适应 + 权重 + 故障链
CPU/内存 较高 极低
配置 YAML JSON + REST API + UI

对于延迟已经 ~100ms(Tailscale VPN)的场景,网关开销能降到多低就多低。

部署过程#

1. 停掉不稳定的 TP=2 容器#

docker rm -f nemotron-tp2-head
docker rm -f nemotron-tp2-worker

2. 每台独立部署 vLLM(TP=1)#

两台服务器使用相同的 Nemotron 模型缓存,各自启动:

docker run -d --name vllm-server --net=host --gpus all --ipc=host \
  -v /home/admin/.cache/huggingface:/root/.cache/huggingface \
  -e VLLM_ATTENTION_BACKEND=FLASHINFER \
  -e VLLM_GPU_MEMORY_UTILIZATION=0.75 \
  -e VLLM_KV_CACHE_DTYPE=fp8 \
  vllm/vllm-openai:gemma4-cu130 \
  /root/.cache/huggingface/hub/models--nvidia--NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4/snapshots/... \
  --served-model-name nemotron-3-super \
  --host 0.0.0.0 --port 8030 \
  --tensor-parallel-size 1 \
  --gpu-memory-utilization 0.75 \
  --kv-cache-dtype fp8 \
  --max-model-len 32768 \
  --moe-backend marlin \
  --quantization fp4

关键参数:

  • --tensor-parallel-size 1:单机完整模型,不切分
  • --gpu-memory-utilization 0.75:DGX Spark 统一内存的保守值
  • --quantization fp4 + --moe-backend marlin:NVFP4 量化 + Marlin 加速

3. 部署 Bifrost 网关#

在 Server 1 上启动 Bifrost:

docker run -d --name bifrost-gateway --net=host \
  -v /home/admin/bifrost-config.json:/app/data/config.json \
  maximhq/bifrost:latest

配置两个 vLLM 后端提供商:

{
  "providers": {
    "vllm-server1": {
      "keys": [{"name": "s1-key", "value": "dummy", "models": ["nemotron-3-super"], "weight": 1}],
      "network_config": {
        "base_url": "http://192.168.200.101:8030",
        "default_request_timeout_in_seconds": 120,
        "max_retries": 2
      },
      "custom_provider_config": {
        "base_provider_type": "openai",
        "allowed_requests": {"chat_completion": true, "text_completion": true}
      }
    },
    "vllm-server2": {
      "keys": [{"name": "s2-key", "value": "dummy", "models": ["nemotron-3-super"], "weight": 1}],
      "network_config": {
        "base_url": "http://192.168.200.102:8030",
        "default_request_timeout_in_seconds": 120,
        "max_retries": 2
      },
      "custom_provider_config": {
        "base_provider_type": "openai",
        "allowed_requests": {"chat_completion": true, "text_completion": true}
      }
    }
  }
}
  • 两台权重相同(1:1),round-robin 负载均衡
  • 超时 120 秒(适应 120B 模型的长推理时间)
  • 重试 2 次(处理网络抖动)

4. 验证#

# 通过 Bifrost 请求 Server 1
curl http://100.97.87.120:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"vllm-server1/nemotron-3-super","messages":[{"role":"user","content":"Hello"}],"max_tokens":30}'

# 通过 Bifrost 请求 Server 2
curl http://100.97.87.120:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"vllm-server2/nemotron-3-super","messages":[{"role":"user","content":"Hello"}],"max_tokens":30}'

Benchmark 结果#

测试环境#

项目
模型 NVIDIA Nemotron-3-Super-120B-A12B-NVFP4
量化 FP4 (NVFP4), Marlin 后端
KV Cache FP8
GPU 2x GB10 Blackwell(128GB 统一内存)
GPU 内存利用率 75%
最大上下文 32,768 tokens
网关 Bifrost(Go 原生,<11μs 开销)

单请求性能#

测试 服务器 平均延迟 吞吐 P95 延迟
20 tokens Server 1 1,401 ms 14.3 tok/s 1,408 ms
20 tokens Server 2 1,388 ms 14.4 tok/s 1,394 ms
100 tokens Server 1 6,638 ms 15.1 tok/s 6,640 ms
100 tokens Server 2 6,546 ms 15.3 tok/s 6,546 ms
300 tokens Server 1 19,692 ms 15.2 tok/s 19,698 ms

并发性能(5 个并行请求)#

服务器 总时间 有效吞吐 成功率
Server 1 3,754 ms 26.6 tok/s 5/5 ✅
Server 2 3,427 ms 29.2 tok/s 5/5 ✅

关键发现#

  1. 两台服务器在这轮测试里的性能非常接近:~15 tok/s 单请求,~28 tok/s 并发
  2. Bifrost 开销可忽略:<1ms,远低于网络延迟(~100ms Tailscale)
  3. 极度稳定:所有测试 100% 成功,零崩溃,零超时
  4. P95 延迟一致:没有长尾延迟问题

与 TP=2 跨节点的对比#

维度 TP=2 跨节点 单节点 + Bifrost
稳定性 ❌ RPC 超时,频繁崩溃 ✅ 100% 稳定
可用性 单点(Head 节点挂则全挂) 双机 + 自动故障转移
吞吐 N/A(崩溃无法测) ~15 tok/s(单),~56 tok/s(双机并发)
延迟 高(跨节点 NCCL 同步) 低(独立推理,无跨节点同步)
扩展性 受限(TP 上限通常 ≤8) 水平扩展(加节点 + 改权重)
运维复杂度 高(NCCL 调优、hang 排查) 低(独立容器 + JSON 配置)

使用方式#

Python SDK#

from openai import OpenAI

client = OpenAI(
    base_url="http://100.97.87.120:8080/v1",
    api_key="dummy"  # 本地部署无需认证
)

# 请求 Server 1
response = client.chat.completions.create(
    model="vllm-server1/nemotron-3-super",
    messages=[{"role": "user", "content": "解释 FP4 量化"}],
    max_tokens=500
)
print(response.choices[0].message.content)

# 请求 Server 2
response = client.chat.completions.create(
    model="vllm-server2/nemotron-3-super",
    messages=[{"role": "user", "content": "解释 MoE 架构"}],
    max_tokens=500
)

cURL#

# 通过 Bifrost 负载均衡
curl http://100.97.87.120:8080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "vllm-server1/nemotron-3-super",
    "messages": [{"role": "user", "content": "Hello"}],
    "max_tokens": 50
  }'

中国大陆网络优化要点#

由于两台服务器在中国大陆,需要特别注意网络问题:

Docker 镜像#

nvcr.iodocker.iomaximhq/bifrost 在中国大陆可能被墙或极慢。

我当时的处理方法

  1. 国内镜像代理:配置 Docker registry mirrors
  2. 离线传输(我这边更稳一些):
    # 在有网络的地方下载
    docker save maximhq/bifrost:latest | gzip > bifrost.tar.gz
    # 传输到服务器
    scp bifrost.tar.gz admin@192.168.200.101:~/
    # 加载
    docker load < bifrost.tar.gz
    

HuggingFace 模型#

huggingface.co 在中国大陆被墙。当前服务器已有模型缓存,如需新模型:

export HF_ENDPOINT=https://hf-mirror.com
# 或使用 ModelScope
modelscope download --model NVIDIA/Nemotron-3-Super-120B-A12B-NVFP4

网络延迟#

当前通过 Tailscale VPN 访问服务器,延迟 ~100ms。优化方案:

  • 在国内云服务器设置跳板机
  • Bifrost 配置更长的超时和重试(已配置 120s + 2 次重试)

Makefile 集成#

项目 Makefile 已添加完整支持:

# 部署单节点 vLLM
make vllm-single-deploy

# 部署 Bifrost 网关
make bifrost-deploy

# 测试 Bifrost
make bifrost-test

# 检查状态
make bifrost-status

# 停止服务
make bifrost-stop
make vllm-single-stop

总结#

项目 结果
单机稳定吞吐 ~15 tok/s(Nemotron 120B, FP4)
双机并发吞吐 ~56 tok/s(两台并行)
Bifrost 开销 <1ms
稳定性 这轮测试未再出现崩溃
这次更稳的方案 ✅ 单节点 TP=1 + Bifrost 负载均衡

结论:对于这两台 DGX Spark 的场景,我目前更倾向放弃跨节点 TP=2,改用独立运行 + Bifrost 负载均衡。至少在这轮实践里,这条路更稳定,也更容易维护。