vLLM TP=2 跨节点部署实践:两台 DGX Spark 跑 Qwen3.5-35B-A3B
概述#
本文记录在两台 DGX Spark 上首次以 vLLM TP=2(跨节点张量并行)方式部署 Qwen3.5-35B-A3B 的完整过程。这次实验先得到几条比较初步的观察:
- TP=2 跨节点推理可以跑通,短请求成功完成
- 成功样本吞吐约 21–31 tok/s,与单机 TP=1(~29 tok/s)基本持平
- 稳定性不足:代码生成 case 在约 300 秒后返回 HTTP 500
- 按这轮实验结果看,如果是追求更稳妥的生产方案,我目前更倾向于两台节点各自独立运行 TP=1,再通过 LiteLLM / Nginx 做负载均衡
技术背景#
DGX Spark 与统一内存#
DGX Spark 搭载 NVIDIA GB10 Grace Blackwell Superchip,其关键特征是 128GB LPDDR5X 统一内存(CPU+GPU 共享),带宽 273 GB/s。
与独立显存的传统 GPU 不同,统一内存没有独立的 VRAM 分区。这意味着:
--gpu-memory-utilization不能像常规设置那样给到 0.9,需要保守设为 0.7,为内存碎片留出余量- 按我这轮实验里的环境约束,最好关闭 swap,否则系统在内存压力下很容易出现明显卡顿甚至失去响应
- 模型权重、KV cache、推理中间态都在同一块 128GB 池子里分配
vLLM 与 Tensor Parallel#
vLLM 是一个高性能 LLM 推理引擎,提供 OpenAI 兼容 API、PagedAttention 显存管理、持续批处理等特性。它原生支持 Tensor Parallel(张量并行):将模型的计算图按张量维度切分到多个 GPU 上执行,各 GPU 之间通过 NCCL(NVIDIA GPU 间通信库)进行 all-reduce 通信同步中间结果。
- TP=1:单 GPU 承载完整模型(单机默认)
- TP=2:模型张量切分到 2 个 GPU 上,每个 GPU 只持有模型的一半参数
- TP 粒度越细,单卡内存压力越低,但跨卡通信开销越大
通常情况下,TP 用于同一台机器内的多卡并行。这次实验的关键不同在于:两台物理独立的 DGX Spark 各出 1 张 GPU,组成一个 TP=2 推理实例。
跨节点 TP 的挑战#
跨节点 Tensor Parallel 并非 vLLM 的主要设计场景。主要风险包括:
- NCCL 通信延迟:节点间通过以太网(非 InfiniBand),all-reduce 操作延迟显著高于机内 NVLink/PCIe
- Hang 风险:社区已知跨节点 TP 在特定情况下可能出现进程挂死
- 稳定性不确定:短请求可能正常返回,但长文本生成或高并发场景可能触发超时或 OOM(Out of Memory)
这也是本次实践要验证的核心问题。
实验环境#
硬件与网络#
| 项目 | 值 |
|---|---|
| 节点 | 2 台 DGX Spark (GB10) |
| Coordinator (rank 0) | 100.97.87.120 |
| Worker (rank 1) | 100.67.164.92 |
| GPU 驱动 | 580.126.09 |
| CUDA | 13.0 |
| Docker 镜像 | vllm/vllm-openai:gemma4-cu130 (v0.18.2) |
| NCCL 网卡 | enp1s0f0np0(以太网,无 InfiniBand) |
模型#
Qwen3.5-35B-A3B-Claude-4.6-Opus-Reasoning-Distilled:
- MoE(Mixture of Experts,混合专家)架构,总参数 35B,每 token 激活约 3B
- FP8(8-bit 浮点)量化
- 多模态模型(本次仅使用文本能力)
选择它的原因:
- 模型足够大,单机虽能装载但已接近内存边界,适合验证跨节点分摊
- MoE 架构每 token 激活参数少,理论上比 dense 大模型更适合分布式推理
- 这是当前 repo 里已成功跑通 TP=2 的实际对象
vLLM 配置#
| 参数 | 值 |
|---|---|
--tensor-parallel-size |
2 |
--pipeline-parallel-size |
1 |
--nnodes |
2 |
--distributed-executor-backend |
mp(multiprocessing) |
--gpu-memory-utilization |
0.7 |
--kv-cache-dtype |
fp8 |
--max-model-len |
8192 |
--max-num-seqs |
64 |
VLLM_ATTENTION_BACKEND |
FLASHINFER |
VLLM_USE_FLASHINFER_MOE_FP8 |
1 |
NCCL_IB_DISABLE |
1 |
部署架构#
- Coordinator(rank 0):对外暴露 OpenAI 兼容 API(port 8000)
- Worker(rank 1):headless(无对外 API)方式作为分布式参与者加入
两台机器共同组成一个 TP=2 实例,而非各自独立运行服务。
部署过程#
通过 Ansible 编排两台节点同时启动,分为四步:
- 预检查:确认 Docker 可用、GPU 可见、NCCL 网卡存在、模型缓存已就位
- 解析模型路径:找到 HuggingFace 缓存中的模型目录并映射到容器挂载路径
- 启动 vLLM:两台节点各自执行
run-vllm-tp2.sh,通过TP2_NODE_RANK区分 coordinator/worker - 验证:确认 coordinator API 端口可达,TP=2 分布式启动无报错
核心启动命令(两节点分别以 rank 0/1 执行):
CUDA_VISIBLE_DEVICES=0 \
VLLM_ATTENTION_BACKEND=FLASHINFER \
VLLM_USE_FLASHINFER_MOE_FP8=1 \
vllm serve <model-path> \
--distributed-executor-backend mp \
--nnodes 2 \
--node-rank <0|1> \
--tensor-parallel-size 2 \
--pipeline-parallel-size 1 \
--master-addr <coordinator-ip> \
--master-port 29500 \
--gpu-memory-utilization 0.7 \
--kv-cache-dtype fp8 \
--max-model-len 8192 \
--max-num-seqs 64
Benchmark#
方法#
目标不是做大盘压测,而是验证 TP=2 路径在真实请求下的基本可用性。
- 通过 coordinator 的
/v1/completions接口发请求 temperature=0,stream=false- 3 个 case:
short-greeting(短文本)、memory-summary(摘要生成)、code-snippet(代码生成) - 记录 HTTP 状态码、completion tokens、总延迟、tokens/s
- benchmark 前后各采集一次节点内存/GPU 快照
结果#
| Case | HTTP | Completion Tokens | Latency | Tokens/s | 说明 |
|---|---|---|---|---|---|
| short-greeting | 200 | 32 | 1534 ms | 20.86 | 成功 |
| memory-summary | 200 | 96 | 3088 ms | 31.09 | 成功 |
| code-snippet | 500 | 0 | 300791 ms | 0.0 | 约 300 秒后返回 EngineCore 错误 |
成功完成的两个 case 吞吐落在 20.86–31.09 tok/s 区间。
内存快照#
| 节点 | 基准 | benchmark 后 | GPU 利用率变化 |
|---|---|---|---|
| Coordinator | 98 Gi | 97 Gi | 0% → 96% |
| Worker | 90 Gi | 90 Gi | 0% → 0% |
两台节点统一内存占用均处于高位,符合预期(模型已加载)。但 worker 节点 GPU 利用率在 benchmark 后仍为 0%,提示可能存在 NCCL 通信不均衡或负载分配问题。
关键观察#
- TP=2 跨节点推理在这轮测试里可以跑通:短文本请求成功返回,说明双节点 TP 的分布式推理闭环至少已经建立
- 稳定性不足:
code-snippetcase 在约 300 秒后返回 HTTP 500(EngineCore 错误),说明更长、更复杂的生成场景仍有问题 - Worker GPU 利用率为 0%:这与 NCCL 跨以太网 all-reduce 的延迟和通信瓶颈相符,是跨节点 TP 的典型表现
与单机结果对照#
作为参考,同一模型在单台 DGX Spark(TP=1)上的 benchmark 结果为:
| 场景 | Tokens | 延迟 | Tokens/s |
|---|---|---|---|
| 短回答 | 50 | 14.22s | 3.5(冷启动) |
| 中等解释 | 150 | 5.20s | 28.9 |
| 代码生成 | 200 | 6.83s | 29.3 |
| 长解释 | 400 | 13.34s | 30.0 |
单机稳定吞吐约 29 tok/s,TP=2 成功样本的吞吐范围(21–31 tok/s)与之接近甚至略高,但 TP=2 的致命弱点是 稳定性(出现了 500 错误),而单机没有出现此类问题。
结论#
- 两台 DGX Spark 以 vLLM TP=2 方式部署 Qwen3.5-35B-A3B 可以跑通,短请求已成功完成
- 成功样本吞吐约 21–31 tok/s,与单机 TP=1 结果(~29 tok/s)基本持平
- 但跨节点 TP=2 稳定性尚未得到验证:代码生成 case 在约 300 秒后返回 HTTP 500,worker GPU 利用率异常
- 对于生产场景,按目前这组结果,我更倾向的做法是:两台节点各自独立运行 TP=1 实例,通过 LiteLLM 或 Nginx 做负载均衡,而不是直接上跨节点 TP
本次实验是一份实践记录而非结论性报告,重点在于沉淀跨节点 TP=2 的部署流程与基线数据,为后续进一步优化或排查提供参考。
后续实验方向#
结合 2026 年社区已有的实验结果(NVIDIA Developer Forums、GitHub 社区 Docker 项目等),以下是从本次 TP=2 实践出发可以继续验证的方向:
1. 切换 Ray 后端替代 MP 后端#
本次实验使用的是 --distributed-executor-backend mp(multiprocessing)。按我目前查到的社区案例,Ray 后端(--distributed-executor-backend ray)在多节点编排上看起来更成熟,也是一些社区 Docker 镜像(eugr/spark-vllm-docker、bjk110/spark_vllm_docker)的默认选择。
后续可对比 MP vs Ray 在以下维度的差异:
- 长文本生成的稳定性(是否仍出现 HTTP 500)
- Worker GPU 利用率是否恢复正常
- NCCL 通信延迟与吞吐量
2. 排查 PyTorch 版本与 CUDA Graph 兼容性#
社区已确认一个典型的 TP=2 挂死根因:PyTorch 版本不匹配(编译用 2.10.0 vs 要求的 2.9.1)会导致 CUDA Graph(GPU 执行图优化技术)异常,进而引发一个节点掉线、另一个节点 GPU 100% 空转。
可验证的措施:
- 确认当前 vLLM 镜像内的 PyTorch 版本
- 尝试
--enforce-eager跳过 CUDA Graph,观察是否改善稳定性 - 如有条件,切换到社区验证过的 Docker 镜像
3. NCCL 网络层优化#
本次实验 NCCL 走的是普通以太网(enp1s0f0np0),NCCL_IB_DISABLE=1。DGX Spark 实际配备 ConnectX-7 网卡,支持 200Gbps RoCE(RDMA over Converged Ethernet,在以太网上实现远程直接内存访问)。
后续可验证:
- 启用 RDMA/RoCE 模式(
NCCL_IB_DISABLE=0,配置NCCL_IB_HCA包含两个 NIC 半区) - 通过
NCCL_DEBUG=INFO确认 vLLM 启动时使用的是Using network IB而非 TCP - 用
all_gather_perf独立验证 RoCE 带宽是否饱和(社区实测约 24.38 GB/s / ~195 Gbps @ MTU 9000) - 对比 TCP vs RoCE 模式下 TP=2 的吞吐与稳定性差异
4. 量化方案对比:AWQ vs NVFP4 vs FP8#
按我目前看到的社区实验,NVFP4 在 DGX Spark 上暂时没体现出明显优势,甚至可能需要模型补丁;而 AWQ(Activation-aware Weight Quantization,激活感知权重量化,压缩到 4-bit INT4)在当前成熟度看起来更高,性能和推理质量也更稳定一些。
可对比测试:
| 量化方案 | 预期 tok/s | 稳定性 | 备注 |
|---|---|---|---|
| FP8(当前) | ~29 | 基准 | 本次实验使用 |
| AWQ 4bit | 待测 | 社区中更常见 | MiniMax-M2.1-AWQ、GLM-4.7-AWQ 已在社区验证 |
| NVFP4 | 待测 | 需补丁 | 单节点 decode 可达 61-65 tok/s(Nemotron-30B),但 TP=2 兼容性待确认 |
5. 更大模型的 TP=2 验证#
社区确认两台 DGX Spark 可以承载 130B 参数级模型(含 KV cache 和 speculative decoding)。结合 TP=2 的内存分摊优势,可尝试:
Qwen3.5-122B-FP8(社区 Docker 已有 preset,TP=2)Qwen3.5-397B-INT4(INT4 量化后 TP=2 可跑)
这可能会是 TP=2 更有说服力的使用场景:单机较难装载的模型,通过跨节点张量切分实现推理。
6. 四节点集群#
NVIDIA 已在 2026 年 3 月官方支持 四节点 DGX Spark 组网(512GB 共享内存)。如果有条件扩展到 4 台,可以验证:
- TP=4 下的模型承载能力(397B 甚至更大)
- 是否需要 RoCE 200GbE 交换机
- 三节点环形拓扑 vs 四节点全互联的通信差异
7. 内存调优#
统一内存是 DGX Spark 的核心特征也是瓶颈。可进一步验证:
--gpu-memory-utilization从 0.7 逐步调高到 0.88(社区里较常见的取值),观察吞吐与 OOM 风险vm.swappiness=10的系统级调优效果- KV cache 大小与最大上下文长度的权衡(更长的 context 需要更低的 GPU memory utilization)
优先级建议#
如果只能选一两个方向继续,按当前信息我会优先看:
- 切换 Ray 后端 + 排查 PyTorch 版本 — 最可能直接解决本次出现的 HTTP 500 稳定性问题
- 启用 RoCE + 验证 NCCL IB 模式 — 跨节点 TP 的网络层是最大瓶颈,RoCE 相比 TCP 的延迟差异可能决定 TP=2 是否可用
其他实验记录#
本文仅聚焦 TP=2 跨节点部署这一条主线。完整的实验过程中还包含大量单机测试、模型筛选、失败尝试,本文不再赘述。所有实验的原始数据、脚本和报告均开源在 GitHub:
单机 Benchmark#
| 模型 | 架构 | 后端 | tok/s | 状态 | 报告 |
|---|---|---|---|---|---|
| Gemma-4-31B-IT-NVFP4 | Dense (31B 全激活) | TRITON_ATTN | 7.3 | ✅ 完成 | benchmark report |
| Qwen3.5-35B-A3B-Claude-Distilled | MoE (35B/3B 活跃) | FLASHINFER MoE FP8 | 29.3 | ✅ 完成 | MoE 对比报告 |
Gemma-4 的 7.3 tok/s 远低于社区预期(25-40 tok/s),我当时的判断是其异构 head size(256/512)导致 FlashInfer 不可用(ValueError: head_size not supported),回退到 Triton 后性能下降 3-5x。按这次排查结果看,这更像是架构层面的限制,至少不是单靠几项常规配置就能直接修好的问题。
失败的模型测试#
| 模型 | 失败原因 | 详情 |
|---|---|---|
| MiniMax-M2.5-NVFP4 (~100B MoE) | OOM,内存占用 126GB | 超过 128GB 统一内存安全上限 |
| Qwen3-235B-A22B-NVFP4 (235B/22B MoE) | OOM,内存占用 125GB | 需 112GB+ GPU 内存,超出统一内存安全阈值 |
| Qwen3.5-397B-A17B (397B/17B MoE) | 模型体积 >128GB | 明确不适合单机部署 |
详见 MoE 模型对比报告。
其他问题记录#
- 缓存模型缺配置文件:本地缓存的
Qwen3.5-35B-A3B-Claude-Distilled缺少preprocessor_config.json,需从 HuggingFace 官方Qwen/Qwen3.5-35B-A3B补全后才能运行 - TP=2 Worker GPU 利用率 0%:benchmark 前后快照均显示 worker 节点 GPU 利用率为 0%,提示 NCCL 通信或负载分配可能存在异常
基础设施代码#
Repo 中除实验报告外,还包含完整的部署与管理工具:
| 类型 | 文件 | 用途 |
|---|---|---|
| 部署脚本 | scripts/run-vllm-tp2.sh |
TP=2 单节点容器启动 |
| 部署脚本 | scripts/vllm-benchmark.sh |
单机多模型自动化 benchmark |
| 部署脚本 | scripts/vllm-tp2-benchmark.sh |
TP=2 benchmark 采集与报告生成 |
| 部署脚本 | scripts/monitor-unified-memory.sh |
统一内存实时监控(含 OOM 预警) |
| Ansible Playbook | playbooks/vllm-deploy.yml |
单机 vLLM 部署 + 系统加固 |
| Ansible Playbook | playbooks/vllm-tp2-deploy.yml |
TP=2 双节点部署 |
| Ansible Playbook | playbooks/vllm-tp2-validate.yml |
TP=2 验证(健康检查 + 冒烟测试) |
| Ansible Playbook | playbooks/vllm-tp2-stop.yml |
TP=2 停止 |
| 配置 | config/vllm.env |
vLLM 环境变量(FlashInfer、NCCL、内存限制等) |
| 配置 | config/vllm.service |
systemd service 模板(含 OOM 保护) |
| 入口 | Makefile |
所有操作的统一入口 |
设计文档#
Repo 的 docs/superpowers/ 目录下还保留了 TP=2 benchmark 功能的完整设计文档与实施计划:
- TP=2 Benchmark 设计文档 — 目标、非目标、部署拓扑、验证流程、风险与缓解
- TP=2 Benchmark 实施计划 — 5 个任务的具体实施步骤
附录:术语速查#
| 术语 | 解释 |
|---|---|
| DGX Spark | NVIDIA 的个人 AI 工作站,搭载 GB10 Superchip,128GB 统一内存,面向推理场景 |
| GB10 / Grace Blackwell Superchip | NVIDIA SoC,集成 20 核 ARM CPU + Blackwell GPU,共享 128GB LPDDR5X 内存,带宽 273 GB/s |
| 统一内存 (Unified Memory) | CPU 和 GPU 共享同一块内存池,无独立显存。模型权重、KV cache、中间态都在这块池子里分配 |
| MoE (Mixture of Experts) | 混合专家模型。模型包含多个"专家"子网络,推理时只激活部分专家,因此总参数大但实际计算量小 |
| KV Cache | Key-Value Cache,推理过程中缓存的注意力中间状态。避免每个 token 生成时重复计算前文,但占用大量显存 |
| NCCL | NVIDIA Collective Communications Library,GPU 间通信库。TP 场景下 GPU 通过 NCCL 同步数据 |
| all-reduce | 分布式通信操作:多个 GPU 各自有数据,求和/平均后广播回所有 GPU |
| FlashInfer | NVIDIA 的推理加速内核库,针对 Blackwell 等新一代 GPU 架构优化,vLLM 的 attention 和 MoE 计算后端 |
| InfiniBand | 高性能网络互连技术,数据中心 GPU 间通信常用,延迟远低于以太网。DGX Spark 未配备 |
| RoCE | RDMA over Converged Ethernet,在以太网上实现远程直接内存访问,绕过 CPU 直接读写远端内存 |
| ConnectX-7 | NVIDIA 的 SmartNIC 网卡芯片,DGX Spark 内置,支持 200Gbps RoCE |
| CUDA Graph | NVIDIA 的 GPU 执行图优化技术,预录制 kernel 序列减少 CPU 启动开销。某些 PyTorch 版本下会引发兼容性 hang |
| AWQ | 激活感知的权重量化方法,压缩到 4-bit INT4,体积缩小约 4 倍 |
| NVFP4 | NVIDIA 的 4-bit 浮点量化格式,专为 Blackwell 设计,理论性能优于 AWQ,但当前成熟度不如 AWQ |