四节点 GLM 推理调优:吞吐翻倍的条件
作者在四台 DGX Spark 上通过 GPUStack 和 vLLM 部署 GLM,结合网络调优与 MTP 推测解码,将单流解码速度从 24.3 提至 51.3 tok/s。四并发总输出吞吐从 72.0 提至 104.1 tok/s,结果仅适用于文中配置与测试条件。
AI 深度解读
四台 DGX Spark 组成推理集群,单路生成速度从 24.3 提到 51.3 tok/s,听起来像 GPUStack 带来的翻倍优化。但作者的测试记录指向了更具体的原因:网络先解除通信瓶颈,MTP 推测解码再提高生成速度,GPUStack 则负责把多节点配置和实例管理串起来。
这份记录值得读的地方,是它把加载失败、后端冲突和启动卡死都交代了出来。对于准备自建模型服务的团队,它更像一份排障路线,而不是硬件采购结论。
翻倍的是单路生成,网络带来的是什么?
作者用四台 DGX Spark,通过 GPUStack 管理集群,以 vLLM 运行 GLM-5.3-Flash-NVFP4。最终采用跨节点张量并行 TP=4、流水线并行 PP=1。张量并行会让多个节点共同计算模型,因此节点间通信会直接影响推理。
先看结果边界:
| 指标 | 基线 | 优化后 |
|---|---|---|
| 单路解码吞吐 | 24.3 tok/s | 51.3 tok/s |
| 四请求并发总输出吞吐 | 72.0 tok/s | 104.1 tok/s |
| 四节点、256 MB AllReduce 带宽 | TCP 回退时 4.0 GB/s | 22.9 GB/s |
单路提升为 2.11 倍,四并发总吞吐却没有翻倍。交互速度和集群承载能力,需要分开评估。
网络配置还有一个容易误读的地方:所谓「双轨」,是每台机器上的两个逻辑 RDMA 接口共享一条 200G 物理链路,每节点只接一个交换机端口,并非两条独立的 200G 链路。
作者把主机 MTU 设为 9000,交换机使用 mtu=9216,记录的有效值为 9000。网卡侧通过 mlnx_qos -i enp1s0f0np0 --trust dscp 启用 DSCP 信任,再用 mlnx_qos -i enp1s0f0np0 -f 0,0,0,1,0,0,0,0 为优先级 3 开启 PFC。这里 -p 管优先级映射,-f 才控制 PFC。
NCCL 单轨测得 11.4 GB/s,双逻辑接口达到 22.9 GB/s,约为前者两倍、TCP 回退的 5.7 倍。这些是通信测试带宽,不能当成物理端口线速。交换机也没有可用的 DCB/PFC/ECN 配置,因此不能宣称建成了端到端无损网络。
网络调优对解码吞吐的直接提升只有约 2%–5%,单路翻倍的主要增量出现在 MTP 跑通之后。
作者还报告长输入场景的首 token 时间 TTFT 明显改善,但输入没有给出完整数值,也未交代输入长度、缓存命中和预热条件,不能据此推算其他请求的等待时间。
MTP 为什么先后卡在权重和缓存上?
MTP,即 Multi-Token Prediction,可以参与推测解码:先提出多个候选 token,再由主模型验证,希望减少逐个生成的开销。能否提速,取决于候选接受情况,以及额外计算是否划算。
作者遇到的第一个问题是量化误判。这个 checkpoint 的 MTP 位于第 45 层,名称没有明确包含 mtp。专家权重实际是 BF16,却因量化忽略名单漏配,被草稿路径按 NVFP4 分配,触发 256/512 维度不匹配。
修复前,作者在 /models/GLM-5.3-Flash-NVFP4 下执行 cp -p config.json config.json.bak-ignore45 备份。随后向 quantization_config.ignore 和 text_config.quantization_config.ignore 两个数组追加四项:
model.language_model.layers.45、model.language_model.layers.45*、model.layers.45、model.layers.45*。
这是追加,不能覆盖原数组;第 45 层也只适用于此次权重。四节点全部修改后,还要重建实例才能重新加载。
第二个问题是 KV-cache 布局冲突。主模型使用的 FlashInfer 后端与 MTP 草稿路径不兼容:有的布局不能把 2,560-token 管理块拆成 256-token 内核块,能拆的布局又被解析器拒绝。
作者曾制作本地补丁镜像,最终发现主模型和草稿路径统一使用 B12X 即可启动。去掉本地布局补丁后再次测试,也没有观察到有意义的性能差异。
下面两行解决了后端统一和推测深度配置问题:
--attention-backend=B12X
--speculative-config={"method":"mtp","num_speculative_tokens":3,"moe_backend":"humming","attention_backend":"B12X"}
要留意第二行:JSON 必须作为一个完整参数传入,其中草稿路径也明确指定了 B12X。
参考配置使用 num_speculative_tokens=5,作者比较了深度 3 和 5,各运行三次取中位数,最终选择更快的 3。原文输入没有提供这组具体吞吐值。作者推测更深的预测增加了计算,却没换来足够的接受 token;这仍需接受率和耗时数据验证。
17 条功能回归提示全部通过,包括字符计数用例,只能说明基本功能检查通过,不能证明输出分布完全等价。
多节点服务卡住时,管理面板还不够
另一次故障没有明确报错:四个 vLLM rank 都停在初始化,GPU 利用率为 0%。作者用 py-spy 抓调用栈,发现等待位置是 gloo::transport::tcp::Pair::waitUntilConnected。
原因藏在容器内的 /etc/hosts:主机名被解析到回环地址,Worker 只监听 127.0.0.1。跨节点自然连不上。单独配置 NCCL_SOCKET_IFNAME 无效,还必须指定 Gloo 接口。
最终环境变量如下,它把 RDMA 通信、Gloo 寻址及运行配置集中到了一起:
VLLM_USE_AOT_COMPILE=1
VLLM_USE_MEGA_AOT_ARTIFACT=1
VLLM_USE_V2_MODEL_RUNNER=1
CUTE_DSL_ARCH=sm_121a
VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=0
VLLM_SSM_CONV_STATE_LAYOUT=DS
NCCL_IB_HCA=rocep1s0f0,roceP2p1s0f0
NCCL_IB_TC=104
NCCL_IB_QPS_PER_CONNECTION=4
NCCL_IB_SPLIT_DATA_ON_QPS=1
NCCL_SOCKET_IFNAME=enp1s0f0np0
GLOO_SOCKET_IFNAME=enp1s0f0np0
重点检查最后两行是否对应各节点真实网卡。NCCL_IB_TC=104 在该环境对应 DSCP 26,还需核对它实际映射到的优先级和队列;作者没有固定 NCCL_IB_GID_INDEX,而是检查 NCCL 自动选择的值。排障时另外启用了 NCCL_DEBUG=INFO 和 NCCL_DEBUG_SUBSYS=INIT,NET。
GPUStack 的运维经验也集中在「确认实际状态」。作者使用 GET /v2/models/<model_id>、PUT /v2/models/<model_id> 和 DELETE /v2/model-instances/<instance_id> 读取、更新和重建实例,每次 PUT 后再 GET 核对。他们观察到 image_name 更新后变为 None,需要检查每节点真正运行的镜像;这不能推广成所有版本的共同问题。
面板主要展示 leader 日志时,follower 要进入 Worker,通过 docker exec gpustack-worker cat /var/lib/gpustack/log/serve/INSTANCE_ID.0.log 查看。旧实例残留容器则先按唯一后缀列出、核对身份,再对准确 ID 执行 docker rm -f CONTAINER_ID,避免误删刚创建的容器。
最终镜像为 eugr/spark-vllm-b12x:latest,入口为 vllm serve。容器采用 privileged、host networking 并可访问 /dev/infiniband,但 RDMA 是否生效仍要结合日志与通信测试确认。
我的判断
我认可这份记录对收益来源的区分:GPUStack 提供统一管理,网络、推理后端和模型配置共同决定性能。把 2.11 倍写成 GPUStack 自身的加速能力,会掩盖真正需要复现的条件。
它对容量也留了边界。最终 gpu-memory-utilization=0.85,没有沿用 kv_cache_memory_bytes=8G,也没有提高到 0.87。max-model-len 从此前的 32K 提到 500000,只代表配置上限,不能证明单请求或四并发 500K 已通过完整验证。
三次脚本重启均首次成功,就绪耗时约 12.5–13.5 分钟,其中权重加载约五分钟,B12X 内核调优及相关初始化约七分钟。这足以作为阶段验收记录,还不足以支持长期稳定性结论。
相比直接套参考参数,逐项对照更可靠;相比维护本地补丁,先找兼容后端也更省维护成本。但文章没有完整软硬件版本、成本、功耗及其他硬件对照,因此购买四台 Spark 是否划算,仍然没有答案。
能拿来做什么
- 给前端或 Node 应用接入自托管模型时,分别测单路生成速度、TTFT 和目标并发总吞吐,再用真实请求判断聊天体验与服务容量。
- 开启 MTP 前,先核对 checkpoint 的量化豁免和后端兼容性,再比较推测深度 3 与 5;保留无 MTP 基线,避免把组合收益归给单个参数。
- 遇到多节点初始化卡死时,把调用栈、容器监听地址和主机名解析放在一起检查,并分别确认 NCCL 与 Gloo 的接口配置。
- 将配置回读、逐节点镜像核验和重启就绪时间纳入部署流程;修改模型配置前保留备份,并为实例重建安排可接受的维护窗口。