首页
/
工具
/
我进行了一些模型优化技巧,将GH200系统上的GLM5.2从约2.5 tok/s提升至超过50 tok/s。
我进行了一些模型优化技巧,将GH200系统上的GLM5.2从约2.5 tok/s提升至超过50 tok/s。
摘要
一篇详细博客文章,描述了如何通过停止模型跨模块通信,并将FP8 MTP头部嫁接至INT4基础模型上,将双Grace Hopper系统上的GLM-5.2推理速度从2.5 tok/s显著提升到超过50 tok/s。
暂无内容
查看缓存全文
缓存时间:
2026/06/24 14:28
# 双 GH200 上的 LLM 推理,第 3 部分:GLM-5.2、专家卸载和 CPU 问题
来源:https://dnhkng.github.io/posts/gh200-benchmarking-part-3-glm52/
双 GH200 上的 LLM 推理,第 3 部分:GLM-5.2、专家卸载和 CPU 问题
## 引言https://dnhkng.github.io/posts/gh200-benchmarking-part-3-glm52/#introduction
第 1 部分 (https://dnhkng.github.io/posts/gh200-benchmarking/) 以内存系统视角测量了双 GH200 工作站。第 2 部分 (https://dnhkng.github.io/posts/gh200-benchmarking-part-2/) 利用这些测量结果解释了为什么当模型布局适配硬件时,DeepSeek V4 Flash 在 vLLM 中可以很快:将热权重保留在 HBM 中,避免不必要的 Hopper 到 Hopper 流量,并且仅在接受率足以补偿草稿工作的开销时才使用 MTP。
GLM-5.2 在这台机器上**起步为 2.39 输出 tok/s**,经过大量调优后接近 **50 输出 tok/s**。整篇文章一句话概括就是如此。两个改动缩小了差距:阻止模型在两个 GH200 模块之间交叉,然后将一个 FP8 MTP 头嫁接到 INT4 基座模型上。两者结合,使得一个*无法放入 VRAM* 的模型能够以可用的交互速度提供服务。
这个差距的存在是因为 GLM-5.2 ***实在太大了***。它无法放入 HBM,因此 Grace 内存(*幸运的是,我有 960 GB LPDDR5X*)必须成为服务系统的一部分。问题的难度从*如何将模型拆分到两个通过慢速互连连接的 Hopper 上*,升级为更难的:*如何将其拆分到两个 Grace-Hopper 模块上,并协调权重传输到两个独立的 VRAM 集?*
以下是我当前测量结果的简短版本。**TG** 表示 token 生成/解码吞吐量。**PP** 表示提示处理/预填充吞吐量。
模型制品引擎头条 批量-1 TG稳定 批量-4 TG最佳 PP 密集型结果GLM-5.2-FP8vLLM,TP2,专家 UVA 卸载25.66 输出 tok/s(最佳)23.63 聚合输出 tok/s543.66 总 tok/sGLM-5.2-AWQ-INT4vLLM,TP2,专家 UVA 卸载`2048\->512` 下中位数 43.39 输出 tok/s,MTP-3 嫁接54.92 聚合输出 tok/s,MTP-3 嫁接781.00 总 tok/sGLM-5.2 GGUF`UD-IQ2_XXS`llama.cpp / ik_llama.cpp CPU短文本 3.13-3.65 输出 tok/s,长文本 1.72-3.62未测试ik_llama.cpp 下 62.88 pp tok/s
FP8 和 AWQ 批量-1 MTP 头条数字来自 `2048\->512` 运行。FP8 MTP-3 点的热运行平均值为 25.64 输出 tok/s,最佳样本为 25.66。AWQ 批量-1 数字现在是较长冷运行加 10 次热运行重复的中位数,而不是最佳单次热样本。AWQ 批量-4 数字是受控的 MTP-3 并发结果;MTP-4 达到了更高的中位数,但可重复性不足,无法作为头条。
等等,***我为什么还要测试一个慢得多的 CPU 版本?***一个合理的本地智能体架构是:在 CPU 上运行 GLM-5.2 用于较慢的规划、审查或困难决策,同时在 GPU 上配一个快得多的 DeepSeek V4 Flash 实例用于高吞吐路径。用商业模型术语来说,这就是 Opus/Sonnet 风格拆分的本地版本:*一个较慢但更强的模型处理困难调用,一个快速模型处理大部分工作*。不幸的是,虽然它在实践中可行,但实在太慢了。
## 系统回顾https://dnhkng.github.io/posts/gh200-benchmarking-part-3-glm52/#the-system-reminder
机器仍然是相同的双 Grace Hopper 工作站:
组件规格GPU2x Hopper H100,各 96 GB HBM3CPU2x Grace,各 72 核主机内存每个 Grace 480 GB LPDDR5X,总共 960 GBGPU 本地内存总共 192 GB HBM3CUDA13.0驱动580.105.08OSUbuntu 24.04,aarch64
第 1 部分 (https://dnhkng.github.io/posts/gh200-benchmarking/) 中的拓扑数字仍然是有用的心智模型:
路径测量带宽本地 HBM约 3,700 GB/s本地 Grace LPDDR 到本地 Hopper约 377-380 GB/s远程 Grace LPDDR 到 Hopper约 133 GB/sHopper 到 Hopper 分级复制约 57-58 GB/s
模型不能完全放入 HBM,因此解码性能取决于有多少专家流量经过 Grace-to-Hopper C2C 链路,以及每个 Hopper 是否从自己的本地 Grace 内存读取而不是从远程模块读取。
## 带宽估算https://dnhkng.github.io/posts/gh200-benchmarking-part-3-glm52/#a-bandwidth-guestimate
在测量 vLLM 之前,我想做一个简单的估算:如果模型被干净地拆分到两个 GH200 模块上,并且每个 Hopper 只从自己的本地 Grace 内存流式读取活跃专家,那么在没有 MTP 的情况下解码速度应该是多少?
从 FP8 检查点头部来看,路由专家权重在 76 个 MoE 层中大约为 684 GiB。GLM-5.2 每个 MoE 层有 256 个路由专家,每个 token 每个 MoE 层激活 8 个专家,因此每个 token 触及 8 / 256 = 1 / 32 的路由专家池。这使得如果每次生成 token 时这些专家都从 CPU 内存取回,活跃专家流约为 684 GiB / 32 = 21.38 GiB。这仅仅是活跃专家流,不是整个检查点,也不是密集注意力路径。
乐观带宽计算如下:
假设有效专家流带宽路径估算非 MTP 解码一个模块实际序列化流21.38 GiB/令牌377-380 GB/s 本地 Grace 到 Hopper15-18 tok/s两个模块拆分层,无流水线重叠每个模块 10.69 GiB/令牌,两个顺序阶段377-380 GB/s 本地 Grace 到 Hopper15-18 tok/s两个模块拆分层,理想稳态流水线每个模块 10.69 GiB/令牌377-380 GB/s 本地 Grace 到 Hopper30-36 tok/s 聚合卸载的专家交错或远程等效 21.38 GiB/令牌约 133 GB/s 远程 Grace 到 Hopper约 6 tok/s流量落入分级 Hopper 到 Hopper 路径等效 21.38 GiB/令牌约 57-58 GB/s约 2-3 tok/s
专家大小以 GiB 为单位,而测量带宽以十进制 GB/s 为单位。将 GiB 转换为 GB 会在字节流上增加大约 1.074 的因子,因此这个不匹配使得表格稍微保守。范围足够宽,不会改变结论。
这故意是一个带宽上限,忽略了路由开销、注意力、密集层、同步、内核效率、页放置错误以及单个请求不会自动填满两阶段流水线的事实。如果严格的本地 NUMA 运行落在批量-1 的 15-18 tok/s 附近,系统表现就如同活跃专家正在通过 C2C 流式传输。如果落在 2-6 tok/s 附近,那么布局很可能付出了远程内存或跨模块流量代价,并且我们的设置出了问题。
## 我测试了什么https://dnhkng.github.io/posts/gh200-benchmarking-part-3-glm52/#what-i-tested
我测试了三个本地 vLLM 制品,两个来自 HuggingFace,一个是我在这个项目中自己构建的 Frankenstein 版本:
模型位置备注**zai-org/GLM-5.2-FP8**GLM-5.2-FP8 (https://huggingface.co/zai-org/GLM-5.2-FP8)官方 FP8 风格制品,754B 类 MoE,MTP 张量存在**cyankiwi/GLM-5.2-AWQ-INT4**cyankiwi/GLM-5.2-AWQ-INT4 (https://huggingface.co/cyankiwi/GLM-5.2-AWQ-INT4)AWQ INT4 制品,通过 compressed-tensors / Marlin WNA16AWQ + FP8 MTP 嫁接加载cyankiwi/GLM-5.2-AWQ-INT4-MTP-FP8本地实验性嫁接:AWQ 基座模型加上来自官方 FP8 制品的 FP8 层-78 MTP 张量
INT4 检查点大大改变了字节数,但可能不会同样大幅度改变 token 生成速度。一个粗略的半字节每权重的专家流估计会将相同的理想本地内存上限大约设为 FP8 上限的两倍。实际上,INT4 不仅仅是更小的字节流:Marlin/AWQ 内核成本、反量化、图捕获和 vLLM 放置都会累加。
*第一个 FP8 基线非常糟糕*:**2.39 输出 tok/s**。这主要是放置问题,权重传输在两个 GH200 模块之间交叉。
在切换到严格的本地 NUMA 放置并减少专家卸载量直到 HBM/KV 权衡不再改善之后,实际的非 MTP 批量-1 结果是:
配置形状结果TP2,每 rank 卸载 270 GiB,非 MTP1 x 256\->51220.31 输出 tok/sTP2,每 rank 卸载 260 GiB,非 MTP,maxlen 30721 x 256\->51220.53 输出 tok/s
`260 GiB` 点在技术上最快,但它只能通过将最大上下文减少到 3,072 来实现。对于通用启动器,我不会使用它。更安全的 FP8 非 MTP 点是每 rank 卸载 `270 GiB` 专家,具有 4,096 token 的最大上下文。
那个 20 tok/s 的结果是一个提示:它高于简单的序列化 15-18 tok/s 估计。可能的解释是我们得到了部分重叠的效果:不是理想的 30-36 tok/s 稳态流水线,但显然比完全序列化的专家流要好。
对于短提示,MTP 远不如在 DeepSeek V4 Flash 上那样令人兴奋,在那里我们看到了性能的大幅提升:
配置形状结果非 MTP,每 rank 卸载 300 GiB,批量 20481 x 256\->51219.33 输出 tok/sMTP-1,每 rank 卸载 300 GiB,批量 10241 x 256\->51218.43 输出 tok/sMTP-1,每 rank 卸载 300 GiB,批量 20481 x 256\->51221.22 输出 tok/sMTP-1,每 rank 卸载 300 GiB,批量 40961 x 256\->51219.09 输出 tok/sMTP-2,每 rank 卸载 300 GiB,批量 20481 x 256\->5128.87 输出 tok/s
即使是 MTP-1 也只是小胜。它达到了 21.22 输出 tok/s,比匹配的 300 GiB 非 MTP 放置快 9.8%,但仅比最佳实用的 270 GiB 非 MTP 放置快 4.5%。草稿层并非免费,启用它会导致不同的 HBM/卸载权衡。
然而,那个短提示结果并不是故事的全部。使用更现实的 `2048\->512` 批量-1 工作负载和 4096 计划 token 上限,最优点向上移动了:
规格 tokens形状冷输出 tok/s热输出 tok/s热接受率决策MTP-11 x 2048\->51222.6021.94, 22.7286.50-97.30%基线MTP-21 x 2048\->51218.6823.78, 23.0082.22-87.17%优于 MTP-1MTP-31 x 2048\->51224.2325.61, 25.6693.58%最佳测量值MTP-41 x 2048\->51221.6225.48, 16.4847.59-89.06%不稳定,停止
我在那里停止了,没有运行 MTP-5。规则是向上走,当曲线变差时停止。MTP-4 产生了一次良好的热运行,然后在第二次热运行中崩溃,接受率降至 47.59%,输出吞吐量降至 16.48 tok/s。
对于并发 token 生成,MTP 在测量的设置中仍然是一场灾难:
配置形状结果MTP-1,每 rank 卸载 300 GiB4 x 256\->51215.15 聚合输出 tok/s非 MTP,每 rank 卸载 270 GiB4 x 256\->51223.63 聚合输出 tok/s
因此,我不会将 MTP 设为 FP8 的默认并发服务配置。它是一个批量-1 延迟/吞吐量旋钮,最佳推测深度取决于提示长度和输出形状。FP8 头条 PP 密集型结果来自一个独立的非 MTP 运行:
配置形状输出 tok/s总 tok/s提示处理快照非 MTP,每 rank 卸载 270 GiB,PP 密集型4 x 2048\->6416.47543.66624.5 提示 tok/s
## INT4:更快,但有不同的权衡https://dnhkng.github.io/posts/gh200-benchmarking-part-3-glm52/#int4-faster-but-with-a-different-tradeoff
AWQ INT4 模型是这台机器上更好的 vLLM 服务目标。
它作为 `compressed-tensors` 加载,vLLM 选择了 Marlin WNA16 内核用于线性路径和 MoE 路径。在首次服务扫描中,最佳测量的双 GH200 批量-1 解码是:
工作负载输出 tok/s总 tok/sTPOT256\->512,并发 124.7037.0637.39 ms256\->1024,并发 126.1632.7037.67 ms2048\->64,并发 117.61581.2237.94 ms
最佳测量的吞吐量配置是:
工作负载输出 tok/s总 tok/s平均 TPOT4 x 256\->51236.9855.47103.79 ms4 x 2048\->6423.67781.00114.32 ms
这使得 INT4 制品即使在 MTP 之前也是实用的 vLLM 选择。它在每个可比较的服务形状上都比 FP8 快。
最初,权衡在于 MTP。INT4 检查点本身不包含 MTP 层-78 权重,因此 MTP 启动在到达任何接受率问题之前就会失败。
## AWQ + FP8 MTP 嫁接https://dnhkng.github.io/posts/gh200-benchmarking-part-3-glm52/#awq--fp8-mtp-graft
为了测试 GLM-5.2 的 MTP 头是否真的有用,我制作了一个本地实验性嫁接:保留 AWQ INT4 基座模型,添加来自官方 FP8 制品的 FP8 层-78 MTP 张量,合并 safetensors 索引,并修补 vLLM 以便草稿层可以使用 FP8 量化路径,而基座模型保持在 AWQ/Marlin 上。这不是一个干净的官方检查点,但它回答了系统问题。
为了使这个可重现而无需重新分发一个完整的合并模型,我发布了一个小的 delta 仓库:dnhkng/GLM-5.2-AWQ-INT4-FP8-MTP-delta (https://huggingface.co/dnhkng/GLM-5.2-AWQ-INT4-FP8-MTP-delta)。它仅包含从 `zai-org/GLM-5.2-FP8` 提取的 `model.layers.78.*` MTP 张量,加上 `graft_glm52_awq_mtp.sh`。这个 delta 是来自 FP8 MTP 层的 1,569 个张量,不是 AWQ 检查点的替代品。预期的工作流程是:
`1 2 3 4 ./graft_glm52_awq_mtp.sh \ --awq-dir /path/to/GLM-5.2-AWQ-INT4 \ --mtp-delta-dir /path/to/GLM-5.2-AWQ-INT4-FP8-MTP-delta \ --out-dir /path/to/GLM-5.2-AWQ-INT4-MTP-FP8`
该脚本保持 AWQ 权重不变,添加 FP8 MTP 层张量,更新 `model.safetensors.index.json`,并在 `config.json` 中添加 `mtp_quantization_config`,以便 vLLM 可以将草稿层路由到 FP8 量化路径,同时将基座模型保持在 AWQ/Marlin 上。
所需的 vLLM 改动很小但很具体:允许在 DeepSeek/GLM 解码器层中进行仅 MTP 的量化覆盖,从本地 `mtp_quantization_config` 读取该覆盖,并在加载嫁接的 AWQ/FP8 检查点时跳过缺失的混合量化参数名称。如果没有仅 MTP 的 FP8 量化覆盖,嫁接会加载,但接受率实际上为零。
答案是:是的,当正确连接时,MTP 对 AWQ 路径有很大帮助。对于下面的短形状比较,我在与嫁接模型相同的基准测试设置中重新运行了非 MTP AWQ 基线,这就是为什么这些基线值比之前的一般服务扫描略高。使用这些重新测量的非 MTP 行来计算 MTP 改进百分比;之前的 24.70 和 26.16 tok/s 行来自第一次更广泛的 INT4 服务扫描,而不是受控的嫁接比较。
配置形状冷输出 tok/s热输出 tok/s热 TPOT接受率AWQ 非 MTP256\->51225.7726.61-26.63,平均 26.6236.51 ms不适用AWQ + MTP-1256\->51226.9637.29-41.79,平均 38.8224.72 ms98.58%AWQ 非 MTP256\->1024未运行26.94-26.95,平均 26.9536.58 ms不适用AWQ + MTP-1256\->1024未运行37.81-38.08,平均 37.9525.81 ms98.84%
第一个 MTP 请求仍然需要承担首次形状的 JIT 开销。在冷 256\->512 MTP 运行中,TTFT 为 4.17 秒,日志显示 Triton JIT 编译了槽映射、预填充元数据、EAGLE/MTP 输入准备和拒绝采样内核。之后,TTFT 回落到大约 0.59 秒,稳态解码路径停留在约 38-39 输出 tok/s。
这里的非常高的接受率来自这些合成的基准测试提示。真实的智能体提示和结构化续写可能会有较低的接受率,因此短形状 41-46% 的增益应被视为测量得到的基准测试结果,而不是保证的应用级加速。
然后我以更严格的规则重复了推测深度扫描:一次冷运行加十次热运行,不丢弃噪声样本,提示长度从 `256\->512` 到 `8192\->512`。服务器使用了 `MAX_MODEL_LEN=9216`、`MAX_NUM_BATCHED_TOKENS=9216`、`MAX_NUM_SEQS=1`、`TP_SIZE=2`、`CPU_OFFLOAD_GB=170`、专家 UVA 卸载、本地 NUMA 绑定和 FP8 MLA KV 缓存。
实用的比较是 MTP-3 与 MTP-4:
配置形状运行次数中位数输出 tok/s最小最大P10P90CV中位数 TPOT中位数接受率低于 60 接受率的运行次数AWQ 非 MTP256\->5121125.1523.9425.1925.1225.180.01438.62 ms不适用0AWQ 非 MTP2048\->5121124.0324.0124.0524.0224.050.00139.31 ms不适用0AWQ 非
相似文章
Reddit r/LocalLLaMA
一位用户在配备双 RTX 5090 的高端类消费级系统上测试了 unsloth 量化版 GLM-5.2 模型,达到了每秒 12 个 token。
Reddit r/LocalLLaMA
一位用户仅用CPU在本地运行GLM-5.2,演示如何在简陋的配置上运行大型模型。
X AI KOLs Timeline
Community efforts, including a hybrid quantization approach by dnhkng, have enabled vLLM and SGLang to support GLM-5.2 with MTP heads, boosting local inference speed from 2 token/s to over 43 token/s on dual GH200 hardware. The challenge involved managing DSA-based MTP and quantization compatibility.
X AI KOLs Timeline
关于在单台DGX Spark上使用sglang推理和自定义mega-kernel以11 tok/s运行未量化的DeepSeek-v4-Flash模型的更新,正在向GLM-5.2迈进。
Reddit r/LocalLLaMA
LFM2.5 230M 模型使用自定义 WebGPU 内核在浏览器中实现每秒 1,400 个 token,展示了高效的本地推理。