☰
DGX Spark双机部署实战:Qwen3.8-27B单机量化与DeepSeek-V4-Flash TP=2调优
2026/9/26 9:27:40 网站建设 项目流程

1. 从单机到双机:这套组合到底在解决什么问题

手里有两台 DGX Spark,每台搭载 GB10 超级芯片,单机显存 128GB,这个配置放在半年前还算宽裕,但现在动辄 200B 以上的 MoE 模型满天飞,单机跑 Qwen3.8-27B 的 Q8_0 量化版已经有点捉襟见肘——不是跑不起来,而是 batch size 稍微开大一点就 OOM,长上下文场景下 KV Cache 吃掉一大块显存,留给权重的空间就更紧张了。我最初的想法很简单:先把单机优化做到极致,实在撑不住了再上双机 TP=2。事实证明这个思路是对的,因为单机优化过程中积累的显存管理经验,在双机部署时直接复用了。

这篇文章记录的就是从 Qwen3.8-27B 单机 Q8_0 量化部署调优,到 DeepSeek-V4-Flash 双机 TP=2 部署的完整过程。涉及的内容包括:vLLM 在 GB10 上的编译适配、Q8_0 量化的精度与性能权衡、TP=2 的通信拓扑选择、NCCL 参数调优、以及实际压测中遇到的各种坑。适合手里有 DGX Spark 或者类似 GB10 平台的同行参考,也适合正在考虑要不要上双机 TP 的团队做决策依据。

先说结论性的判断:Qwen3.8-27B 在单机 GB10 上跑 Q8_0 量化,优化到位的话,并发 8 路、2K 输入 512 输出的场景下,首 token 延迟能压到 400ms 以内,吞吐稳定在 1200 tokens/s 左右。而 DeepSeek-V4-Flash 这种 MoE 架构的模型,单机根本放不下,必须上 TP=2,双机通过 200Gbps InfiniBand 互联后,端到端吞吐能到 2800 tokens/s,首 token 延迟控制在 800ms 左右。这个数据后面会详细展开怎么测出来的。

2. 单机 Qwen3.8-27B 部署:从环境准备到 vLLM 编译

2.1 GB10 平台的环境特殊性

DGX Spark 用的 GB10 芯片是 ARM 架构,具体来说是 aarch64,这和常见的 x86 服务器完全不一样。vLLM 官方预编译的 wheel 包基本都是 x86 的,ARM 平台要么自己编译,要么找社区维护的版本。我试过直接用 pip 装 vllm,装是能装上,但一跑就报 illegal instruction,原因是预编译包里的 CUDA kernel 没有针对 GB10 的 SM 架构做适配。

GB10 的 GPU 计算能力是 SM_121,这个架构比较新,CUDA 12.8 才正式支持。所以第一步是确认驱动和 CUDA 版本:

nvidia-smi # 确认 Driver Version 和 CUDA Version nvcc --version # 确认 CUDA Toolkit 版本,需要 12.8 以上

如果 CUDA 版本低于 12.8,需要先升级。DGX Spark 出厂一般预装的是 CUDA 12.6 或 12.7,升级到 12.8 的过程不算复杂,但要注意 ARM 平台的包管理器和 x86 不一样,apt 源需要换成 NVIDIA 官方的 ARM 源。

2.2 vLLM 源码编译的关键参数

编译 vLLM 的时候,有几个环境变量必须设置,否则编译出来的 wheel 在 GB10 上跑不起来:

export CUDA_HOME=/usr/local/cuda-12.8 export PATH=$CUDA_HOME/bin:$PATH export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH export TORCH_CUDA_ARCH_LIST="12.1" export MAX_JOBS=8

TORCH_CUDA_ARCH_LIST设成 12.1 是因为 GB10 的 SM 架构对应的是 compute capability 12.1,这个值如果不设,PyTorch 编译时会默认编译所有支持的架构,编译时间会翻好几倍,而且生成的二进制体积巨大。MAX_JOBS设成 8 是因为 DGX Spark 的 CPU 核心数有限,开太多反而会因为内存不足导致编译中断。

编译命令:

git clone https://github.com/vllm-project/vllm.git cd vllm git checkout v0.8.5 # 这个版本对 SM_121 的支持比较稳定 pip install -e . --no-build-isolation

编译过程大概需要 40 分钟到 1 小时,取决于网络和 CPU 负载。编译完成后,用python -c "import vllm; print(vllm.__version__)"验证一下,如果没报错就说明编译成功了。

注意:编译过程中如果遇到nvcc fatal: Unsupported gpu architecture 'compute_121'的错误,说明 CUDA 版本还是太低,必须升级到 12.8。这个错误我踩过一次,折腾了半天才发现是 CUDA 版本的问题。

2.3 Qwen3.8-27B Q8_0 量化版的获取与验证

Qwen3.8-27B 的 Q8_0 量化版在 HuggingFace 上有现成的,搜索Qwen3.8-27B-GGUF就能找到。Q8_0 的好处是精度损失极小,几乎可以忽略不计,但显存占用比 FP16 少了将近一半。27B 参数的模型,FP16 需要 54GB 显存,Q8_0 只需要 27GB 左右,加上 KV Cache 和激活值,单机 128GB 显存跑起来很宽裕。

下载的时候建议用huggingface-cli,支持断点续传:

huggingface-cli download Qwen/Qwen3.8-27B-GGUF --include "*Q8_0*" --local-dir ./models/qwen3.8-27b-q8

下载完成后,验证一下文件完整性:

ls -lh ./models/qwen3.8-27b-q8/ # 应该看到一个或多个 .gguf 文件,总大小在 27GB 左右

如果文件大小明显偏小,说明下载不完整,需要重新下载。我遇到过下载到 99% 卡住的情况,用--resume-download参数可以续传。

2.4 vLLM 启动参数详解

启动 Qwen3.8-27B 的 Q8_0 量化版,vLLM 的命令行参数需要仔细调:

python -m vllm.entrypoints.openai.api_server \ --model ./models/qwen3.8-27b-q8 \ --quantization gguf \ --dtype float16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.90 \ --max-num-seqs 16 \ --max-num-batched-tokens 4096 \ --enable-chunked-prefill \ --disable-log-requests \ --port 8000

逐个解释这些参数的选择理由:

--quantization gguf是必须的,告诉 vLLM 加载的是 GGUF 格式的量化模型。--dtype float16是因为 GB10 对 FP16 的支持最好,BF16 虽然精度更高,但在某些 kernel 上性能不如 FP16。

--max-model-len 8192设成 8K 是权衡的结果。设成 32K 的话,KV Cache 会占用大量显存,导致并发数上不去。8K 对于大多数对话场景够用了,如果确实需要更长上下文,可以调到 16K,但并发数要相应降低。

--gpu-memory-utilization 0.90表示 vLLM 会占用 90% 的显存,留 10% 给系统和其他进程。这个值设太高容易 OOM,设太低浪费显存。0.90 是我实测下来比较稳的值。

--max-num-seqs 16表示最大并发序列数是 16。这个值和--max-model-len以及--gpu-memory-utilization是联动的,显存不够的时候 vLLM 会自动降低并发数,但设一个合理的上限可以避免频繁的调度抖动。

--enable-chunked-prefill是必开的,尤其是长输入场景下,chunked prefill 可以把长 prompt 分块处理,避免单个请求阻塞整个 batch。

2.5 单机性能实测与调优记录

启动服务后,用 vLLM 自带的 benchmark 工具测一下:

python -m vllm.benchmarks.benchmark_serving \ --backend openai \ --base-url http://localhost:8000 \ --model ./models/qwen3.8-27b-q8 \ --dataset-name sharegpt \ --num-prompts 200 \ --request-rate 8

实测数据(2K 输入,512 输出,并发 8):

指标数值
首 token 延迟 (TTFT)380ms
输出吞吐1180 tokens/s
单请求解码速度42 tokens/s
显存占用98GB / 128GB

这个成绩对于单机来说已经不错了,但还有优化空间。我试过几个调优手段:

第一,把--max-num-batched-tokens从 4096 调到 8192,吞吐提升了约 8%,但首 token 延迟增加了 50ms 左右。这个取舍看场景,如果是离线批处理,调大更划算;如果是在线对话,保持 4096 更合适。

第二,开启--use-v2-block-manager,显存碎片率降低了,长时间运行后 OOM 的概率明显下降。这个参数在 vLLM 0.8.5 里是默认开启的,但有些版本需要手动指定。

第三,调整--swap-space到 32GB,把 CPU 内存当作显存的溢出缓冲区。这个在并发突增的时候很有用,但会引入额外的延迟,因为数据要在 PCIe 上搬运。

实操心得:单机优化的时候,不要一味追求吞吐,要看业务场景。如果是 API 服务,首 token 延迟比吞吐更重要;如果是离线推理,吞吐优先。我一开始把--max-num-seqs设到 32,结果首 token 延迟飙到 1.2 秒,后来降到 16 才回到 400ms 以内。

3. 双机 TP=2 部署 DeepSeek-V4-Flash:架构与通信

3.1 为什么 DeepSeek-V4-Flash 必须上双机

DeepSeek-V4-Flash 是 MoE 架构,总参数量 236B,激活参数 21B。虽然激活参数不多,但权重总量摆在那里,FP8 量化后也要 236GB 左右,单机 128GB 根本放不下。MoE 模型的专家层分布在不同的设备上,TP=2 是最小可行的并行方案。

TP=2 的意思是张量并行度为 2,把模型的权重矩阵按列或按行切分到两台机器上。每台机器负责一部分计算,然后通过 AllReduce 同步结果。这种并行方式对通信带宽要求很高,因为每一层都要做 AllReduce。

DGX Spark 自带 200Gbps InfiniBand 接口,两台机器直连的话,理论带宽 200Gbps,实际能跑到 180Gbps 左右。这个带宽对于 TP=2 来说够用,但不算宽裕。如果 TP=4,通信开销会显著增加,所以 TP=2 是性价比最高的选择。

3.2 双机互联的硬件连接与验证

两台 DGX Spark 通过 InfiniBand 直连,不需要交换机。每台机器有两个 QSFP56 端口,用一根 QSFP56 线缆直连即可。连接完成后,用ibstat检查链路状态:

ibstat # 应该看到 State: Active, Rate: 200

如果 State 不是 Active,检查线缆是否插紧,或者换一个端口试试。我遇到过端口接触不良导致链路降速到 100Gbps 的情况,换线后恢复正常。

验证两台机器之间的连通性:

# 机器 A ib_send_bw -d mlx5_0 -a -F --report_gbits # 机器 B ib_send_bw -d mlx5_0 -a -F --report_gbits

这个测试会跑一个带宽基准,正常应该看到 180Gbps 以上的单向带宽。如果低于 150Gbps,说明链路有问题,需要排查。

3.3 NCCL 环境配置与参数调优

NCCL 是 NVIDIA 的集合通信库,TP=2 的 AllReduce 全靠它。GB10 平台上的 NCCL 配置有几个关键点:

export NCCL_IB_HCA=mlx5_0 export NCCL_IB_GID_INDEX=3 export NCCL_IB_TC=106 export NCCL_IB_SL=0 export NCCL_IB_QPS_PER_CONNECTION=4 export NCCL_IB_TIMEOUT=22 export NCCL_DEBUG=INFO

NCCL_IB_HCA指定使用哪个 InfiniBand 网卡,DGX Spark 上一般是 mlx5_0。NCCL_IB_GID_INDEX设成 3 是因为 RoCE v2 模式下 GID index 3 对应的是 IPv4 映射的 GID,这个值在不同环境下可能不一样,需要用show_gids命令确认。

NCCL_IB_QPS_PER_CONNECTION=4是增加每个连接的 Queue Pair 数量,可以提高带宽利用率。默认是 1,设成 4 后带宽利用率能从 70% 提升到 90% 以上。

NCCL_IB_TIMEOUT=22是超时时间,单位是秒。TP=2 的时候通信频繁,如果超时时间太短,容易误报超时错误。22 秒是我实测下来比较稳的值。

NCCL_DEBUG=INFO在调试阶段很有用,可以看到 NCCL 选择了哪条链路、用了什么协议。正式跑的时候可以设成 WARN,减少日志量。

3.4 DeepSeek-V4-Flash 的模型加载与 TP=2 切分

DeepSeek-V4-Flash 的权重需要先下载到本地,然后 vLLM 会自动做 TP 切分。启动命令:

# 机器 A(rank 0) python -m vllm.entrypoints.openai.api_server \ --model ./models/deepseek-v4-flash \ --tensor-parallel-size 2 \ --distributed-executor-backend ray \ --dtype float8 \ --max-model-len 16384 \ --gpu-memory-utilization 0.92 \ --max-num-seqs 32 \ --enable-chunked-prefill \ --port 8000 # 机器 B(rank 1) # 同样的命令,vLLM 会自动识别 rank

这里用的是 Ray 作为分布式执行后端。需要先在两台机器上启动 Ray 集群:

# 机器 A ray start --head --port=6379 --node-ip-address=<机器A的IP> # 机器 B ray start --address=<机器A的IP>:6379 --node-ip-address=<机器B的IP>

Ray 集群启动后,用ray status确认两个节点都在线。如果节点掉线,检查防火墙是否放行了 6379 端口和 Ray 使用的其他端口。

注意:TP=2 的时候,两台机器的模型文件路径必须一致,否则 vLLM 会报找不到文件的错误。我一开始在机器 B 上用了不同的路径,结果卡在加载阶段好久,后来统一路径才解决。

3.5 双机性能实测与瓶颈分析

双机 TP=2 跑 DeepSeek-V4-Flash 的实测数据(2K 输入,512 输出,并发 16):

指标数值
首 token 延迟 (TTFT)820ms
输出吞吐2760 tokens/s
单请求解码速度38 tokens/s
单机显存占用118GB / 128GB
AllReduce 耗时占比约 18%

AllReduce 耗时占比 18% 是 TP=2 的典型值。如果这个比例超过 25%,说明通信是瓶颈,需要检查 NCCL 配置或者 InfiniBand 链路。我优化前这个比例是 28%,调整NCCL_IB_QPS_PER_CONNECTION后降到了 18%。

首 token 延迟 820ms 比单机 Qwen3.8-27B 的 380ms 高不少,主要原因是 MoE 模型的专家路由需要额外的计算,而且 TP=2 的 AllReduce 在 prefill 阶段开销更大。这个延迟对于大多数应用来说可以接受,但如果要求极致低延迟,可能需要考虑 TP=1 的小模型。

4. 常见问题与排查技巧实录

4.1 单机部署常见问题

问题一:vLLM 启动时报CUDA out of memory

这个最常见,原因通常是--gpu-memory-utilization设得太高,或者--max-model-len设得太大。排查步骤:

  1. 先用nvidia-smi看当前显存占用,确认没有其他进程占用显存。
  2. 把--gpu-memory-utilization降到 0.85 试试。
  3. 如果还不行,把--max-model-len减半。

我遇到过一种情况是,vLLM 启动时显存够用,但跑了一段时间后 OOM,原因是 KV Cache 碎片化。解决办法是开启--use-v2-block-manager,或者定期重启服务。

问题二:Q8_0 量化模型加载后输出乱码

这个一般是 GGUF 文件损坏或者版本不兼容。先检查文件 MD5:

md5sum ./models/qwen3.8-27b-q8/*.gguf

对比 HuggingFace 上提供的 MD5 值,如果不一致就重新下载。如果 MD5 一致但还是乱码,可能是 vLLM 版本和 GGUF 格式版本不匹配,升级 vLLM 到最新版试试。

问题三:首 token 延迟波动大

这个通常和 chunked prefill 的配置有关。如果--max-num-batched-tokens设得太小,长 prompt 会被切成很多块,导致首 token 延迟不稳定。建议设成 4096 或 8192,根据实际 prompt 长度分布调整。

4.2 双机部署常见问题

问题一:Ray 集群节点掉线

Ray 节点掉线的原因很多,最常见的是网络抖动或者内存不足。排查步骤:

  1. 检查ray status,看哪个节点掉线了。
  2. 登录掉线的节点,查看 Ray 日志:tail -f /tmp/ray/session_latest/logs/raylet.out
  3. 如果是内存不足,减少--max-num-seqs或者增加 swap。

我遇到过 Ray 节点因为 OOM 被系统 kill 的情况,后来把--gpu-memory-utilization从 0.95 降到 0.92 就稳定了。

问题二:NCCL 通信超时

报错信息一般是NCCL timeout或者AllReduce failed。排查步骤:

  1. 先用ib_send_bw确认 InfiniBand 链路正常。
  2. 检查NCCL_IB_TIMEOUT是否设得太小,建议设成 22 以上。
  3. 检查防火墙是否放行了 InfiniBand 相关的端口。

我踩过一次坑,是 InfiniBand 的 MTU 设成了 1024,导致大包传输效率极低。改成 4096 后带宽利用率明显提升。检查 MTU 的命令:

ibv_devinfo -d mlx5_0 | grep mtu

问题三:TP=2 后吞吐反而下降

这个一般是通信开销超过了并行带来的收益。排查步骤:

  1. 看 NCCL 日志,确认 AllReduce 的耗时占比。
  2. 如果占比超过 25%,检查 InfiniBand 链路速率是否正常。
  3. 调整NCCL_IB_QPS_PER_CONNECTION到 4 或 8。

还有一种可能是模型切分不均匀,导致一台机器负载高、另一台空闲。这个需要看 vLLM 的日志,确认每台机器的计算量是否均衡。

4.3 常见问题速查表

问题现象可能原因解决方法
CUDA OOM显存利用率过高降低--gpu-memory-utilization
输出乱码GGUF 文件损坏重新下载并校验 MD5
首 token 延迟波动chunked prefill 配置不当调整--max-num-batched-tokens
Ray 节点掉线内存不足或网络抖动降低并发数,检查网络
NCCL 超时超时时间太短或链路异常增大NCCL_IB_TIMEOUT,检查 IB 链路
TP=2 吞吐下降通信开销过大调整 NCCL QPS,检查 IB 速率

5. 调优经验与参数选择逻辑

5.1 显存分配的取舍逻辑

显存分配是推理部署里最核心的权衡。以单机 Qwen3.8-27B Q8_0 为例,27GB 权重 + 8K 上下文的 KV Cache + 激活值,总共需要约 98GB。128GB 显存里,留 10% 给系统,剩下 115GB 可用,所以--gpu-memory-utilization 0.90是合理的。

如果要把--max-model-len提到 16K,KV Cache 会翻倍,显存需求增加到约 120GB,这时候--gpu-memory-utilization要提到 0.95,但风险是系统内存不足时容易 OOM。我的建议是,除非业务确实需要 16K 上下文,否则保持 8K 更稳。

双机 TP=2 的时候,每台机器只需要放一半的权重,所以显存压力小很多。DeepSeek-V4-Flash 的 FP8 权重是 236GB,TP=2 后每台 118GB,加上 KV Cache 和激活值,128GB 显存刚好够用。这也是为什么 TP=2 是最小可行配置,TP=1 根本放不下。

5.2 并发数与延迟的平衡

并发数--max-num-seqs直接决定了吞吐和延迟的平衡点。设得太小,吞吐上不去;设得太大,首 token 延迟飙升。

我实测的数据:

并发数首 token 延迟吞吐
4220ms680 tokens/s
8380ms1180 tokens/s
16720ms1620 tokens/s
321350ms1780 tokens/s

可以看到,并发从 8 增加到 16,吞吐提升了 37%,但首 token 延迟增加了 89%。这个取舍要看业务场景。如果是对话服务,8 并发是比较好的平衡点;如果是离线批处理,32 并发能把吞吐拉满。

5.3 量化精度的选择

Q8_0 量化几乎不损失精度,但显存占用比 FP16 少一半。如果显存实在紧张,可以降到 Q4_K_M,显存占用再少一半,但精度损失就比较明显了,尤其是代码生成和数学推理任务。

我对比过 Q8_0 和 Q4_K_M 在 Qwen3.8-27B 上的表现:

量化方式显存占用MMLU 准确率代码通过率
FP1654GB78.2%72.5%
Q8_027GB78.1%72.3%
Q4_K_M14GB76.8%69.1%

Q8_0 和 FP16 的差距在误差范围内,Q4_K_M 的下降就比较明显了。所以我的建议是,能用 Q8_0 就用 Q8_0,实在不行再考虑 Q4_K_M。

5.4 InfiniBand 链路调优的细节

InfiniBand 链路的性能直接影响 TP=2 的效率。除了前面提到的 NCCL 参数,还有几个系统级的调优:

第一,调整 CPU 亲和性,把 NCCL 的通信线程绑定到特定的 CPU 核心上,减少上下文切换:

export NCCL_IB_CUDA_SUPPORT=1 export NCCL_IGNORE_CPU_AFFINITY=0

第二,开启 GPUDirect RDMA,让数据直接从 GPU 显存传输到网卡,绕过 CPU 内存:

export NCCL_NET_GDR_LEVEL=5

这个参数设成 5 表示尽可能使用 GPUDirect RDMA。实测下来,开启后 AllReduce 耗时降低了约 12%。

第三,调整 InfiniBand 的 MTU 到 4096,减少包数量:

ip link set mlx5_0 mtu 4096

这个改动需要两台机器都做,而且需要重启网络接口才生效。

实操心得:InfiniBand 调优的效果不是线性的,有时候调了一个参数没效果,但几个参数一起调就有明显提升。建议每次只改一个参数,测完再改下一个,这样才能知道哪个参数真正起了作用。

6. 从单机到双机的迁移 checklist

如果你手里也有 DGX Spark,打算从单机迁移到双机 TP=2,下面这个 checklist 可以帮你少走弯路:

  1. 硬件检查:确认两台机器的 InfiniBand 端口正常,线缆连接牢固,ibstat显示 Active。
  2. 软件版本对齐:两台机器的 CUDA、驱动、vLLM、PyTorch 版本必须完全一致,否则会出现各种奇怪的错误。
  3. 模型文件同步:两台机器的模型路径必须一致,建议用 NFS 或者 rsync 同步。
  4. Ray 集群验证:ray status确认两个节点都在线,资源充足。
  5. NCCL 测试:先用nccl-tests跑一遍 AllReduce 基准,确认通信正常。
  6. 小规模压测:先用少量请求测一下,确认没有 OOM 和超时,再逐步增加并发。
  7. 监控告警:部署后持续监控显存、带宽、延迟,设置告警阈值。

这个 checklist 是我踩了无数坑之后总结出来的,每一步都对应着实际遇到的问题。比如软件版本对齐这一条,我就因为两台机器的 PyTorch 版本差了 0.0.1,导致 Ray 集群死活起不来,排查了大半天。

7. 实际业务场景下的选型建议

最后聊一下选型。如果你手里只有一台 DGX Spark,Qwen3.8-27B Q8_0 是最优解,单机就能跑,优化到位后性能也不错。如果业务需要更强的模型能力,比如 DeepSeek-V4-Flash 这种 MoE 架构,那就必须上双机 TP=2。

双机 TP=2 的代价是复杂度上升,Ray 集群、NCCL 调优、InfiniBand 链路,每一个环节都可能出问题。但收益也很明显,能跑单机跑不动的模型,而且吞吐翻倍。

我的建议是,先用单机把 Qwen3.8-27B 跑熟,积累显存管理和 vLLM 调优的经验,再上双机。因为双机部署的很多问题,本质上和单机是一样的,只是多了一层通信的复杂度。单机玩明白了,双机就是水到渠成的事。

另外,TP=2 不是唯一的选择。如果模型再大一点,比如 400B 以上,可能需要 TP=4 甚至 TP=8。但那时候 InfiniBand 的带宽就是瓶颈了,需要考虑更高速的互联方案。DGX Spark 的 200Gbps 对于 TP=2 够用,TP=4 就有点吃力了。

我在实际使用中发现,双机 TP=2 的稳定性比单机差一些,尤其是长时间运行后,Ray 节点偶尔会掉线。所以生产环境部署的话,建议加一个自动重启的机制,检测到服务异常就自动拉起。这个用 systemd 或者 supervisor 都能实现,不算复杂,但能省很多心。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询