这可能是最低成本的生产级 GLM-5.3 方案:双 8 卡 W7900D 实测

1 阅读19分钟

16 张显卡,总共 768 GiB 显存,运行 GLM-5.3,最初却只有 4.08 token/s:每生成一个 token,要等约 245 毫秒。

显存足够把模型装下,并不意味着算力就能顺利叠加。我们的两台机器各有 8 张 48 GiB 的 W7900D,节点内走 PCIe 4.0,节点间走 TCP,没有可供这条执行路径使用的 XGMI 高速互联。模型必须分布在多张卡上,但每增加一次跨卡协作,都可能给单个 token 增加一次等待。

这轮 zLLM 的 ROCm 优化,就是从这个约束出发:先减少层内通信,再优化每张卡上的实际工作,最后通过 MTP 推测解码减少完整模型的执行轮数。

直接核对 Amd-1 的结果文件后,单卡 stage、不开 MTP 的完整测试为 11.321 token/s;最新双卡算子并行版本三轮达到 13.842–14.043 token/s,中位数 13.968,约 14 token/s。相比最初约 4.08 token/s,单卡 stage 路线的吞吐提高到约 2.77 倍。

旧双卡版本叠加 MTP 的完整测试还记录了 25.275 token/s。它证明组合路线已经运行过,但不能据此认定最新约 14 token/s 的版本加上 MTP 也已经完成验收。

数据说明:本文主结果来自 Amd-1 上的 JSON 与运行日志。测试语料文件名为 50k-allgpu.txt,实际请求输入为 46,152–46,158 token,输出均为 1,024 token。Decode 速度按首个文本事件之后的 1,023 个生成 token 除以剩余生成时间计算;下文“50K”沿用语料名称,算子 profile 中的 50K 则是其工作量口径。不同提交、nonce 与 MTP 配置的结果不能视为同版本严格 A/B。

先决定数据在哪里,再决定怎么并行

单路自回归生成有一个硬约束:下一个 token 必须等待当前 token 完成采样。

对这次测试的 78 层模型,一个 token 要依次经过所有层、最终输出投影(LM Head)和采样,才能开始下一轮。把这些层放到 16 张卡上,只是把执行链分段,不能自动让同一个 token 同时穿过所有层。

我们的起点是让每张卡负责一段连续、完整的层。权重、KV Cache、稀疏注意力索引状态、专家权重和临时工作区都留在本地,只有层段边界的 hidden activation 需要交接:

点击图片可放大查看。图中数据量与延迟按各自尺度绘制;层内 profile 含嵌套 scope,不应相加为完整 token 的耗时比例。

这里的“单卡 stage”指每段由一张卡执行,整个模型仍然使用 16 张卡。

此前尝试过更细的双卡协作:一层中反复交换 query、selection 和专家计算的 partial,每层有 4–6 次交接。数据量未必大,但每次交换后的 event 等待都会把两张卡的进度差带入关键路径。重复 78 层后,局部计算并行省下的时间,被协作成本抵消。

因此,单请求 decode 的近似账本应该是:

每 token 延迟
  ≈ 所有 stage 的串行执行时间之和
  + stage 交接
  + LM Head、采样和回环

这也决定了如何理解“负载均衡”。多个请求或多个 prefill chunk 可以重叠时,最慢 stage 会限制流水线吞吐;单请求逐 token decode 则首先取决于整条链的总时间。调整层边界只有在减少等待、改善执行效率或缓解资源限制时,才会带来收益。

从 4.08 到 11.32,时间省在了哪里

归档测试使用 GLM-5.3 UD-IQ4_XS GGUF、78 层、Q8G64 KV,使用“50K”语料(实际约 46.15K token),生成 1,024 token,关闭 MTP、DSpark 和旧双卡协作路径。

阶段Decode(token/s)每 token 延迟主要变化
初始单流水线4.084244.8 ms完成拓扑调整,单行算子仍待优化
单卡 stage(非并行)11.321约 88.3 ms专用 IQ kernel、MLA 优化、Q8 shared expert、W8 直读与调度优化

按 11.321 token/s 换算,吞吐约为最初的 2.77 倍,单 token 延迟减少约 63.9%。下面展开其中最有效的几项变化。

Decode 只有一行,不能直接照搬大矩阵乘法

Prefill 一次处理多行输入,适合用矩阵乘法摊薄权重读取成本。普通 decode 一次只有一行,更接近矩阵乘向量(GEMV):每读入一大批权重,只计算一个 token 的输出。

这时,权重读取、反量化指令、线程映射和 kernel 启动开销都很显眼。我们针对 gfx1100 直接编写 HIP/ROCm 算子,为 IQ3_S、IQ4_XS 和 Q8/W8A16 建立专用路径。

例如,IQ3_S 的 gate/up 使用 8-lane subgroup,再通过 u16 宽读取降低加载开销。在布局和输出 bit pattern 不变的前提下,探针时间从约 207–211 微秒降到 188–190 微秒。

但相似代码不保证相似收益。IQ4_XS down 当时的有效带宽已约为 465 GB/s;移植另一种码本读取写法,时间反而从 102 微秒退化到 144 微秒。这个实验说明,在当前 shape 上,继续围绕码本缓存做文章并没有抓住主要瓶颈。

最值得保留的一次优化:别把量化权重重新撑大

MLA,也就是多头潜在注意力,是每层都会执行的聚合热点。其中 kv_b 的驻留格式曾经浪费了大量带宽。

旧路径把 W8 权重先解成 F16,再以 F32 dense 形式常驻,方便后续 absorb/PV 计算使用。磁盘上的权重虽然量化了,执行时却重新膨胀:相关阶段每层需要读取约 58.6 MB。

新路径让算子直接消费 W8 权重和 scale,流量降到约 14.7 MB,减少到原来的四分之一。完整测试从约 9.76 提升到 10.50/10.28 token/s,两轮平均收益约 6.5%。

这是比“再换一个 tile”更直接的优化:如果热路径最终还是读取 F32,量化就只节省了模型文件大小,没有充分兑现执行时的带宽优势。

其他 MLA 优化也有效。PV 连续加载向量化后,设备 scope 从约 0.484 降到 0.439 ms/layer,完整模型收益约 2.5%–3.5%;selected scan 把 QK 中主要由一个 wave 完成的工作分给 8 个 wave,partial 与 merge 合计从约 90.6 降到 57–59 微秒。

不过,这些局部节省不会自动等额转化成 token 延迟下降。下一部分解释为什么。

Kernel 快了,整条链却可能更慢

DSA 稀疏注意力索引器要从历史位置中选出后续注意力计算使用的集合。在 50K 场景中,score 计算约 91 微秒,已经达到 profile 估算计算上限的约 68%;radix select 的串行尾部更值得处理。

原实现最后两级由一个线程块串行重扫。拆成多级 kernel 后,相关 kernel 时间从约 105 降到 96 微秒,确实更快了。但新增两次各约 12 微秒的 launch 后,完整 select 仍约 139 微秒,没有得到预期的端到端收益。

这些实验使我们的优化单位逐渐变大:先看一个 kernel,再看完整算子,最后看整个 stage 在 token 关键路径上的耗时。

主要限制本轮代表有效方向
权重读取带宽q_bkv_b、MoE 权重流压缩驻留、连续宽读取、共享权重读取
计算与反量化指令DSA score、IQ 解码调整 wave 映射、缩短指令依赖
同步与调度radix 多级选择、launch、双卡 join减少交接、融合依赖链、消除 bridge copy

下图把更新后的原始记录中的带宽数据展开:

图中的有效带宽来自权重字节数除以 kernel 时间,是分析指标;本轮没有取得可用的底层显存流量与 occupancy counter,不能把它当成硬件计数器实测。

Graph 实验:启动更快,完整请求反而慢了 4%–5%

HIP Graph 的思路是把重复执行的 kernel 依赖链预先记录下来,后续直接 replay,减少逐个提交 kernel 的开销。对于单行 decode 中大量短 kernel,这个方向很自然。我们的实验也确实测到了启动收益,但接入生产路径后,收益没有兑现。

探针有效,端到端无效

在 64 个小 kernel 组成的探针中,eager 设备时间线约为 0.76–0.80 ms,预热后的静态 Graph replay 约为 0.23–0.27 ms。折算到每个节点,大约从 12 微秒降到 4 微秒。这说明 Graph 能压缩提交间隙;它并没有让 kernel 内部的计算和权重读取变快。

但在修复正确性之后,5 节点 MoE Graph 的完整请求对照结果如下:

同二进制、50K 输入 × 1,024-token decode第一轮第二轮
Graph 关闭9.509 token/s9.607 token/s
Graph 开启9.119 token/s9.124 token/s
吞吐变化−4.1%−5.0%

这是历史 Graph 专项实验,修复提交为 b4433700,结果记档于 51a4f30d。这些数值只用于说明开关 Graph 的同版本对照,本文最新单卡 stage 主口径为已核对的 11.321 token/s,不能把这次历史回退比例直接套到最新版上。

为什么省下 launch,还是亏了

关键在于固定地址带来的桥接成本。这个实现的 Graph 节点记录了设备指针,而上游输出需要搬到 Graph 使用的固定槽位。每层因此多出 3 次设备到设备的 D2D 拷贝,以及这些拷贝对应的 host 提交开销。

实验只包住了 5 个节点,能够摊薄的 launch 间隙有限;新增的桥接工作却每层都要执行。按单节点探针的差额粗算,5 个节点仅对应约 40 微秒的潜在节省,这只是量级估计,并不是生产 Graph 的净收益。真正的账要包含桥接、replay 和后续依赖等待,完整测试已经给出了负收益答案。

同时,小段 MoE Graph 没有覆盖 attention 中较长的串行链,也不会减少专家权重读取、反量化计算或跨卡等待。它只优化了关键路径上的一小部分,却为接入这一小部分增加了固定成本。

正确性问题修好了,性能问题仍然存在

更早的版本还出现过死循环、NaN 和非法访存。根因是 Graph 构建期间使用的中间 buffer 在构建结束后被释放回内存池,但节点仍保留旧设备指针。内存池复用这些地址后,replay 就可能读写其他对象的内存。

独立探针没有复现,是因为探针一直持有相关 buffer;生产运行的资源生命周期不同。修复让 Graph 对象持有所需 buffer,保证 replay 期间地址仍然有效。强加同步曾短暂掩盖症状,但长上下文测试仍失败,因为同步无法修复悬垂指针。

上表的负收益是在修复后测得的。因此,“能稳定 replay”和“能降低端到端延迟”是两个分别需要验收的结果。

什么条件下值得再试

这次实验后,生产路径保持 decode_graph=false。后续值得验证的是覆盖更长依赖链的整层 Graph:上游直接写入固定 workspace,消除额外 bridge copy,同时由 Graph 明确持有其引用的资源,避免工作区扩容或内存池复用使旧指针失效。

覆盖范围扩大后,仍需在同一二进制、相同输入和输出正确性要求下,关闭 profiler 测完整请求。Graph 的验收指标是最终 token 延迟,不是录进去了多少个节点,也不是 replay 探针快了多少。

双卡并行为什么没有接近翻倍

优化单卡路径后,我们重新尝试双卡算子并行。新的 parallel_operator_pairs 在 MLA 中拆 query heads,在 MoE 中拆中间维,并在较大的 attention 和 FFN 边界归并结果。

交换格式也一起调整。KV 由 owner 量化一次,只传新增的 Q8 latent、scale 和 rope 信息,单 token、单层的 KV 交换从 2,304 B 降到 656 B,减少 71.5%。Router 则在两侧重复计算,省去传递路由表的握手。

单卡 stage 与双卡路线的非 MTP 性能量级如下:

执行方式Decode(token/s)每 token 延迟
每个 stage 使用单卡(非并行)11.321约 88.3 ms
双卡 operator pair,三轮中位13.968(约 14)约 71.6 ms

以 11.321 token/s 为参照,双卡三轮中位数约高 23.4%,每 token 的延迟减少约 16.7 毫秒。这是不同版本之间的量级比较,不是同版本只切换并行开关的严格 A/B。双卡拆分的收益远小于理想二分,profile 给出了几个具体原因。

首先,拆 query heads 没有拆历史扫描。两卡仍各自 gather Top-2048、扫描完整逻辑 KV。MLA 的 pair scope 约 0.35–0.45 ms/layer,与单卡的 0.35–0.36 ms/layer 相比,基本没有加速。

其次,大权重计算拆开了,整层依赖链没有一起减半。Norm、router、DSA selection、部分共享专家工作和结果归并仍要付出成本,FFN/MoE 整段只快约 30%–40%。

最后,attention 和 MoE 每层都要双向交换 partial。仅 peer-copy kernel 在 78 层累计就约 3.8 ms/token,此外还有 event、join 和两侧进度差。

以上瓶颈来自较早双卡版本的 profile,用来解释优化方向,不能直接当作最新 da1e40be 版本的耗时分解。在这组 profile 中,跨节点 TCP 的裸传输只有几十微秒/token,主要损失反而发生在节点内的逐层协作。真正需要减少的是重复扫描和同步等待,单纯缩小消息字节数还不够。

按照 Amdahl 定律,未拆分的串行工作会限制整体加速;新增的通信和同步还会进一步抵消收益。两张卡都在忙,并不意味着每个 token 的关键路径缩短了一半。

继续优化的方向是让 owner 保存唯一的权威 hidden 状态,把 partial 改为向 owner 单向归约,减少反向交换和重复 residual;再探索按历史维度拆分 DSA/MLA,让两张卡真正各扫一部分历史。跨越下一档性能需要改变数据分工。

MTP 的收益,来自少跑几次完整模型

当普通 decode 的单 token 延迟已经优化到约 88.3 毫秒,继续抠掉几微秒的边际收益会越来越小。MTP 提供了另一个方向:先提出多个候选 token,再由目标模型验证,让一轮昂贵的验证提交多个有效 token。

它没有消除自回归语义,而是利用候选前缀,把部分逐 token 工作变成多行验证。是否更快,取决于每轮最终接受多少 token,以及 draft、verify、回退和状态追赶的总成本。

Amd-1 的 paired-mtp5-g2-50k-d1024-r1.json 记录了真实的双卡加 MTP 测试:输入 46,152 token、输出 1,024 token,decode 用时 40.475 秒,吞吐 25.275 token/s。配置为 parallel_operator_pairs=true、draft depth 5、verify group rows 2;日志确认 MTP L78 也使用 operator pair。

对应完整请求日志记录:target rounds 为 315,验证候选 1,567 个,接受 709 个,候选接受率 45.25%。按日志口径,每个 target round 平均提交约 3.25 个 token(1,024 / 315)。每层 draft depth 的接受数依次为 241、179、130、92、67,说明加深候选深度后,后续位置的有效产出逐步减少。

同一路线其他配置的完整测试为:depth 3 / group 2 达到 23.793 token/s,depth 5 / group 1 为 20.647 token/s。它们说明 verify 组织方式会影响端到端收益;由于请求 nonce 与配置不同,这里不把差值归因于单一因素。

25.275 是旧双卡配置的一次完整实测,不是最新 da1e40be 双卡版本加 MTP 的成绩,也不是多轮稳定基线。 日志中的 1,024-token 请求先完成,随后才出现 rocprof 附加的另一轮诊断;本文不把后续 profiler 结果混进这一吞吐数字。最新双卡版本仍需单独验证 MTP 组合的速度与正确性。

对这条路线,下一步最值得关注的也不是单独增加 draft depth,而是让多行 verify 真正共享投影、MLA 和 MoE 的权重读取。多验证一行的成本越低,接受更多候选才越有价值。

正确性同样要覆盖状态:候选不匹配后,target KV、DSA selection 和 MTP cache 必须回到正确位置;接受前缀后,相关缓存还要完成追赶。最终吞吐必须建立在这些状态一致的基础上。

为什么选 4 bit:可以升到 5/6 bit,FP8 超出了当前硬件预算

我们当前选择 4-bit 量化,是为了先给长上下文、MTP 和多卡执行留出足够的显存余量。4 bit 是当前部署的起点,后续可以上升到 5 bit、6 bit。按照我们对这条执行路径的判断,升位基本不会带来性能损失,但模型质量会有明显提升。 如果更看重回答质量,这比继续压低权重 bit 数更值得做。

位宽增加不等于端到端耗时同比增加。当前单路 decode 的时间还包含 DSA、attention、kernel 提交、跨卡同步和采样回环;低 bit 格式本身也要支付码本、scale、符号处理和反量化指令的成本。因此,从 4 bit 升到 5/6 bit 后,权重读取量增加,并不会直接变成同等比例的 token 延迟增加。这里的判断针对本项目的模型、硬件和执行路径;本文没有列出 5/6 bit 的独立速度或质量测评数值。

FP8 则越过了另一条边界:在当前这套硬件上,无法满足本文完整部署的显存预算。 这套机器的高质量 4-bit checkpoint 已约 362 GiB,按权重翻倍粗估,8-bit 接近 724 GiB,而 16 张卡的物理显存合计只有 768 GiB。剩余约 44 GiB 还要容纳长上下文 KV、DSA 索引状态、MTP、peer 副本、workspace、HIP runtime 和显存碎片,无法支撑当前部署。这个判断首先来自容量与运行余量,不是说 API 无法表达 FP8。

权重方案本项目的选择与判断
4 bit当前主路径,优先保留上下文与执行余量
5/6 bit可升级方向;在当前路径上基本不损失性能,质量可明显提升,部署时需重新核算显存余量
FP8当前硬件无法满足完整部署的显存预算

实际执行也不要求所有张量使用同一位宽。当前 routed expert 使用 IQ3_S/IQ4_XS,适合直接消费的投影保留 W8A16,KV 使用 Q8G64,LM Head 使用 Q8G128。升级权重位宽时,仍应按算子选择格式,并用目标任务检查质量收益。

无论选择 4、5 还是 6 bit,都应让热路径直接消费压缩权重,避免重新膨胀成 F32 常驻。这样,增加的显存预算才能用于改善模型质量,而不是消耗在不必要的中间表示上。

下一步,继续沿完整请求计时

Prefill 已有历史 cooperative 路径在 50K 口径下达到 1331.62 token/s 的归档结果。它属于另一执行配置,不能与最新 decode 数字拼成同一次运行的成绩。下一阶段希望补齐多行算子、改善 chunk 与 stage 调度,向 1500 token/s 推进,并用完整 50K 的首 token 延迟验收。

Decode 的下一步是在最新双卡版本上重新验证 MTP:先检查输出与缓存状态,再测多轮接受率、多行验证成本和完整请求吞吐。旧配置的 25.275 token/s 提供了实验依据,但 30+ 仍只是目标,不能把不同版本的收益直接相乘。

最新记录还提到,目标 GPU 在执行本地层段的活跃窗口里,有效占用可达到 70% 以上。它不代表 16 张卡在整段单路 decode 中同时达到 70% 利用率,也不能与硬件 occupancy 混用。局部工作已经更紧凑,是否改善用户等待时间,仍要回到端到端计时。

这轮优化中,我们保留了能减少权重读取的 W8 直读,也否决了微基准更快、整条链却没有收益的尝试。后续仍沿用同一标准:用 reference 检查数值和状态,用 profiler 找到等待发生在哪里,最后关闭 profiler,在相同输入与配置下测完整请求。

每个 token 都要走完的那条路,才是最终需要优化的对象。


资料口径:算法与历史实验依据 2026-09-05 更新的项目优化记录;当前速度由 Amd-1 的原始 JSON、配置与日志交叉核对。以下为本次引用的完整请求,未重新运行 benchmark。

路线 / 结果文件输入 token输出 tokenDecode(token/s)
单卡 stage:c343909e-current-routepair-no-mtp-strict1024-r1.json46,1581,02411.321
双卡:paired-directjoin-da1e40be-formal-no-mtp.json46,1531,02414.043
双卡复测:paired-directjoin-da1e40be-formal-recheck-no-mtp.json46,1531,02413.842
双卡复测:paired-directjoin-da1e40be-formal-recheck-no-mtp-r2.json46,1531,02413.968
旧双卡 + MTP:paired-mtp5-g2-50k-d1024-r1.json46,1521,02425.275

结果文件分别位于 Amd-1 的 /workspace/zllm-kv-eval-run/runs/results/,基础语料 SHA-256 一致,nonce 不完全相同。最新三轮双卡结果的输出 SHA-256 一致;这验证了这三轮输出一致性,不等同于独立模型质量评测。部分 runner 的 topology 字段沿用了“16 serial stages”模板,运行日志实际确认双卡路线每节点为 4 个 owner-peer pair,两节点共 8 个逻辑 stage。


中文原文:zhuai.tech/blog/rocm-g…

English:zhuai.tech/en/blog/roc…

zLLM GitHub:github.com/zllm-lab/zl…