一、引言
在 Qwen3.8-27B 的 decode 链路上,GDN(Gated DeltaNet)占到了 89% 的时间。这个数字意味着,无论推理框架、调度策略和量化方案如何调整,只要 GDN 自身的耗时不变,整体吞吐就始终被压在同一个天花板之下。当优化目标被明确设定为 10K tok/s 时,绕开 GDN 已经不再是一个可选项。
国产 XPU 上的推理优化,长期面临一个现实:官方算子库的成熟度决定了相当一部分性能上限。对于大模型推理,框架层的调度与批处理只是把请求送到 kernel 面前,kernel 能不能把显存带宽用满,才是吞吐的最终决定因素。GDN 的 89% 占比,正是这种决定因素的一个具体体现。
这篇文章记录的不是一个已经完成的目标,而是一次把边界摸清楚的过程。它的价值在于,把「还能不能继续优化」这个模糊问题,转化为一张可以逐项打勾的清单。我们沿着官方栈的三个方向逐一探索:官方提供的两个 recurrent 实现、隐藏在环境变量里的 FP16 加速开关,以及 low-rank 路线。每一条路都走到了尽头,最终得到的是一个清晰的性能边界,以及边界之外还剩下的两条路。
结论可以先放在这里:官方栈内所有可选项——两个实现、全部开关、low-rank——全部探索完毕之后,距离 10K tok/s 仍需要 recurrent kernel 的 batch 并行度再提升约 3 倍以上。官方栈已经没有更多可挖掘的空间,「一滴都没有了」是客观结论,不是抱怨。
二、背景
Qwen3.8-27B 共 64 层,其中 48 层是 linear_attn。GDN 之所以在 decode 阶段占据主导,是因为每一层 decode 都要执行一段完整且无法摊薄的递推链路:
输入投影 GEMM → short conv(kernel size 4)→ gating
→ recurrent 递推(state 128×128 per head × 48 heads)→ 输出投影recurrent state 的总量为 96MB。对于 decode T=1 的场景,输入投影 GEMM 和输出投影尚可依赖大 batch 摊薄,而 recurrent 递推必须逐 token 更新 state,其访存模式天然靠近带宽敏感区间。short conv 与 gating 的计算量相对有限,真正的压力集中在 state 的更新与读取上。GDN 的 recurrent 更新在数学上可以理解为对 state 做逐步的 rank-1 更新,同时伴随一个衰减项。这也是后文 low-rank 路线能够被提出的前提:如果 state 的更新带有明显的低秩结构,就存在用更小 state 近似全秩更新的空间。但「近似」的具体语义,必须回到算子的定义中去确认,而不是直接套用矩阵分解的直觉。
这里需要先明确两组度量口径。单流(batch=1 或低并发)描述的是单条请求的端到端延迟体验;高并发 batch 描述的是系统的吞吐能力。decode T=1 的 micro-benchmark 去掉了一切调度和排队因素,只保留单层算子的纯计算耗时,因此它是定位瓶颈最干净的观察窗口。后续所有「每层耗时」的数字,均来自这一窗口。
在 decode T=1 的 micro-benchmark 下,关键数字如下:
| 实现 | 配置 | recurrent 单层耗时 |
|---|---|---|
| kunlun_ops 全秩 recurrent kernel | 单流 | 0.048ms/层 |
| torch einsum 参考实现 | batch=1 | 0.39ms/层 |
| torch einsum 参考实现 | batch=64 | 0.9ms/层 |
| 官方融合 kernel | 相对 einsum | 快 2–3 倍 |
官方融合 kernel 相对纯 torch einsum 参考实现有 2–3 倍的收益,说明融合本身已经在发挥作用。enforce-eager 下的逐层计时可作为另一组参考:conv 0.45ms、recurrent 0.23ms、输入投影约 1.4ms。eager 路径并不代表生产配置,但它清晰显示了各算子之间的相对量级,也印证了 GDN 是整条链路里绕不开的一环。
batch 扩展性是理解这条链路的关键。batch=256 时,recurrent 约 1.3ms/层,48 层合计 62ms,占一次 iteration 70ms 的 89%。而理论上限按 state 96MB 读写除以 2.4TB/s 带宽计算,约为 0.08ms/层:
理论单层耗时 ≈ 96MB 读写 / 2.4TB/s ≈ 0.08ms官方 kernel 距离带宽上限仍有约 16 倍的差距。这个 16 倍,就是后续所有讨论的坐标系。它同时说明,任何在配置层面、调度层面做的优化,最多只能回收那 16 倍中的一小部分;真正的大头必须回到 kernel 本身。
三、技术过程
整个排查遵循一个简单原则:先保证数值对齐,再谈性能。每次切换实现或打开开关之后,先复测输出结果与参考实现的一致性,确认没有引入数值误差,再做吞吐对比。这样做的目的,是把「换了一个实现」和「换坏了一个实现」区分开,避免把误差误判成收益。
1. 官方两个 recurrent 实现
第一轮比较的是两个官方实现:
kunlun_ops的 xfagated_delta_net(31 参数)xspeedgate_ops的fused_recurrent_gated_delta_rule_fwd
实测 Dense 256 并发下,后者 1592 tok/s,略低于前者的 1619 tok/s。差距不大,但方向明确:在相同并发条件下,后者的实现并没有带来额外收益。于是回退到 gated_delta_net,保留 xfa 路径作为 Dense 的默认实现。需要说明的是,回退并非因为后者有正确性问题。两个实现都能产出正确结果,差异只在吞吐。既然官方已经提供了不止一个 recurrent kernel,比较它们本身就是官方栈探索的第一步。
这一轮的价值在于排除了「换一个官方 recurrent 实现就能明显提速」的可能性。两个实现的差距属于噪声量级,真正的瓶颈不在这里。
2. 官方隐藏开关
在官方栈中发现了一个环境变量级别的开关:
XPU_SET_RECURRENT_GATED_DELTA_RULE_FWDV2_FP16_FAST_OPT=3取值含义为:1 负载优先、2 理论性能优先、3 自动。设置为 3 即启用全 FP16 加速。该开关并非默认开启,需要显式设置才会生效。实测结果为 Dense 增加 2–4%,MTP 256 并发增加 9%(1482 → 1609–1622 tok/s)。
这个开关带来的收益真实但有限。它属于「能回收的最后几个百分点」之一,证明官方栈在 FP16 加速路径上还有一层默认没有打开的优化,但打开之后,收益并未改变整体格局。
3. low-rank 路线
low-rank 使用了官方的 fused_recurrent_gated_delta_rule_lowrank_fwd,r=16/32。micro-bench 单流为 0.014ms/层,相比全秩的 0.048ms/层快了 3.4 倍,是本次探索中最具吸引力的单点数据。
但需要特别说明的是,这里的 UV state 语义是「最近 r 步 rank-1 更新的 sliding-window 缓存」,不是 SVD 低秩分解。这一点直接决定了初始化路线的可行性:随机投影初始化会输出错误结果(inf),SVD 在 XPU 上一次约 5 秒,完全不可用。也就是说,直接复用低秩分解的直觉会踩进两个坑,而这两个坑在工程上都没有便宜的解法。这也再次提醒,low-rank 这个词在推理侧的含义和它在训练侧或矩阵近似中的含义并不完全一致。能不能复用,取决于对 state 语义的理解是否准确。
正确的路径是 chunk low-rank 全链路:chunk_gated_delta_rule_lowrank_fwd_h。数学验证已经通过,o diff 为 0.0002;在没有可用 SVD 的前提下,chunk low-rank 是唯一能够保留数学正确性的折中。但 256 并发 prefill 会触发 kernel 崩溃,目前仍在定位中。low-rank 的潜力已经被看见,但距离可用还有最后一段没有走完。
四、结果与边界
把上述路径叠加后,最终成绩如下:
| 配置 | 吞吐 |
|---|---|
| MTP + FP16_FAST_OPT=3,单流 | 55–59 tok/s |
| MTP + FP16_FAST_OPT=3,256 并发 | 1609–1622 tok/s |
| Dense,256 并发 | 1579–1619 tok/s |
| 权威口径 | 3201 output tok/s |
需要强调的是,这里的单流 55–59 tok/s 与 256 并发 1609–1622 tok/s 是两个完全不同的度量维度,不能直接对比。前者描述单条请求的端到端体验,后者描述高并发下的系统吞吐。权威口径 3201 output tok/s 是最终对外使用的口径。从单流到 256 并发再到权威口径,三层数字对应三种观察角度:单流看最坏延迟,并发看可扩展性,权威口径看对外交付。三层之间不存在线性换算,但它们共同指向同一个瓶颈——recurrent kernel 在 batch 放大后的并行度不足。
以 3201 output tok/s 为基线,10K tok/s 需要约 3.1 倍的提升。这个倍数与 batch 扩展性观察到的 recurrent 瓶颈相互印证:当 recurrent 在 batch=256 下占 89% 时,任何非 recurrent 路径的优化都无法填满这个缺口。
距离 10K tok/s,需要 recurrent kernel 的 batch 并行度再提升约 3 倍以上。官方栈内的可选项——两个实现、全部开关、low-rank——已经全部探索完毕。剩余路径只有两条:
- 独立于官方、使用汇编编写的算子库和运行库:自研 kernel,做 batch 并行与多级融合。理论上限是当前官方 kernel 的约 16 倍。
- 等待官方 kernel 更新。
两条路径的性质不同,取舍也清晰:
| 路径 | 性质 | 预期上限 | 主要成本 |
|---|---|---|---|
| 独立算子库与运行库 | 自研 kernel,batch 并行 + 多级融合 | 约为当前官方 kernel 的 16 倍 | 工程投入高,需自行验证正确性 |
| 等待官方 kernel 更新 | 复用官方栈 | 未知 | 时间不受控,无法排期 |
五、结论
这次调优最核心的收获不是某个单点参数,而是把「官方栈」这个模糊边界真正摸清楚了。只有把官方栈的选项用尽,才能看清性能上限在哪里:当前瓶颈不在配置,而在 recurrent kernel 本身的 batch 并行度与融合程度。配置层面的改动只能带来几个百分点的波动,真正决定上限的是 kernel 离带宽理论值还有多远的距离。
官方栈真的已经一滴都没有了。下一步要么走向独立于官方、使用汇编编写的算子库和运行库,要么等待官方 kernel 的更新。需要指出的是,自研 kernel 的意义不在于替换单个算子,而在于把 recurrent 的 batch 并行与多级融合重新组织起来,向那 16 倍的理论上限靠近。这轮工作的意义,是把「官方栈已经尽力」从一句猜测变成一组可复现的数据。这不是对官方栈的否定,而是对边界的确认。边界一旦写实,下一步的取舍就不再有歧义。