实验报告 · gfx1151 · GLM-5.1

在 AMD Strix Halo 上用 LLM Agent 挖掘 GPU Kernel SOTA

一份诚实的实验报告:大多数"惊艳加速"经不起严格检验,真正的 SOTA 藏在编译器结构性做不到的地方——MoE 的 data-dependent routing。所有数字均为 gfx1151 实测、可复现。

2026-06-08 → 06-12 Radeon 8060S · gfx1151 · RDNA3.5 ROCm 7.13 · torch 2.12 · Triton 3.7 KernelBench 评测 GLM-5.1 生成

摘要

我们用一个 LLM agent(GLM-5.1 生成 OpenAI Triton kernel)在一台消费级 AMD APU(gfx1151,带 128 GB 统一内存的 Strix Halo iGPU)上系统挖掘 GPU kernel 的加速机会,以 KernelBench 为评测框架。

本报告的核心不是某个惊人数字(最高真实合规加速约 1.5×),而是一条可复现、有方法论、诚实的研究路径:

1238×→1.26×
计时 bug 纠正(ms/us 注释)
170× / 10278×
reward-hack(已剔除)
11×→1.04×
FP16 降精度假象
1.10→1.48×
MoE 结构性 SOTA(真金)

结论:在带宽受限的消费级 iGPU 上,rocBLAS / MIOpen / torch.compile 已覆盖大部分易得优化;真正剩余空间在编译器结构性做不到的地方——data-dependent 控制流(MoE expert routing)。

预备三种执行方式 — 看懂数据的前提

全文所有加速比都在这三者之间比较。理解它们,是分辨"真 SOTA"和"赢朴素基准的假象"的前提。速度通常 ① < ② ≈ ③。

① eager即时执行 · 最朴素的基准

PyTorch 默认模式,一个算子一个算子地执行。算 silu(gate)*up 会先 launch 一个 GPU kernel 算 silu、结果写回显存,再 launch 一个 kernel 读出来乘 up。每步都有 kernel 启动开销 + 中间结果的显存往返——简单灵活但慢。几乎任何"融合"都能赢它,所以"vs eager 1.5×"其实意义有限。

② torch.compile编译执行 · 真正的硬基准 ⭐

PyTorch 2.0 的 JIT 编译器(后端 TorchInductor)。它把代码自动 trace 成计算图,再自动融合多个算子、自动生成优化 Triton kernel。上面的 silu·mul 它会自动融成一个 kernel,和手写的差不多快。生产环境都用它——所以只有赢过 torch.compile,手写 kernel 才真有价值,否则 inductor 已经免费帮你做了。

③ 手写 kernelGLM-5.1 生成的 Triton kernel

agent 生成的自定义 kernel——拿来同时和 eager、torch.compile 比,看它到底是真有本事,还是只赢了朴素基准。

一句话:vs eager 看"有没有融合",vs torch.compile 看"有没有真本事"。本报告坚持用 torch.compile 做基准,就是为了不被"赢朴素 eager"的假象骗到(见下方 §03 教训二的对比图)。

为什么 MoE 特殊:graph-break

torch.compile 要把代码 trace 成静态图。但 MoE 的 forward 里有依赖运行时数据的控制流:

moe.py — data-dependent routing
for e in range(num_experts):
    if tok.any():        # ← 分支取决于张量"实际值" → 编译器没法 trace
        xe = x[tok]      # ← 索引取决于运行时才知道的 routing
        ...                  # gate/up GEMM · SwiGLU · scatter-add

这种 data-dependent 控制流(分支和索引都取决于张量实际值),编译器无法 trace,只能断图(graph-break)、把那部分退回 eager 执行。所以 torch.compile 对 MoE 几乎零优化(实测 compile ≈ eager)——这正是手写 kernel 在 MoE 上有结构性优势、不会被编译器追平的根本原因,也是全报告"真 SOTA"的落点。

01真实瓶颈:推理 Profiling

KernelBench 合成题挖掘的边际收益迅速枯竭。我们转向用 ollama + rocprofv3 profile 真实模型推理(gfx1151)。

模型架构decode tok/s有效带宽*
qwen3:8bdense 8B38.9202 GB/s
qwen3:32bdense 32B10.6212 GB/s
gpt-oss:20bMoE 21B/3.6B act48.2~106 GB/s (~50%)
gemma4dense53.0

*有效带宽 = 模型字节数 × decode rate(decode 是 bandwidth-bound)

qwen3-8b dense qwen3-32b dense gpt-oss:20b MoE 实测 roofline ~207 理论 256 GB/s 202 GB/s · 81% 212 GB/s · 83% 106 GB/s · ~50% ← 优化空间

↑ 两个 dense 模型(不同大小)撞在同一条 ~207 GB/s 实测带宽墙上 → decode 已饱和、无空间;MoE 只用到一半 → 真实瓶颈。

决定性证据:两个不同大小的 dense 模型(8B/32B)算出的有效带宽高度一致(202 / 212 GB/s),证明 dense decode 已带宽饱和(~81% roofline)、无优化空间。而 MoE 带宽利用率只有 ~50% → 真实瓶颈在 MoE(expert gather 的非连续访存 + routing 开销)。

02真实 SOTA:MoE 的结构性优势

构造标准 top-k MoE SwiGLU 参考,GLM-5.1 写融合 Triton kernel。干净 no_grad cuda-event 计时,对比 eager 与 torch.compile

配置eagertorch.compileMoE kernelvs compile正确性
moe · 8 experts5.24 ms5.25 ms4.75 ms1.10×7e-7
moe2 · 32 experts67.24 ms67.51 ms59.13 ms1.14×2e-6
moe3 · 64 experts47.55 ms47.50 ms32.03 ms1.48×2e-6
glm@gfx1151 — final_moe3.sh · no_grad cuda-event
bash final_moe3.sh   # moe3: 1024 tok / 2048 hidden / 64 experts / top-2
max_abs_err 1.9669e-06  ref_mean 0.2138
eager=47.550ms  torch.compile=47.503ms  MoE3_kernel=32.028ms
speedup vs eager=1.485x  vs torch.compile=1.483x

↑ torch.compile(47.50ms)≈ eager(47.55ms)——对 MoE routing 零优化;手写 kernel 32.03ms。

越稀疏,融合收益越大(vs torch.compile)

1.0 1.1 1.2 1.3 1.4 1.5 torch.compile baseline = 1.0× (graph-break, 零优化) 1.10× 1.14× 1.48× 8 experts 32 experts 64 experts moe moe2 moe3

↑ 三个配置都超 torch.compile(它对 MoE routing 一律 graph-break);experts 越多、per-expert 开销越大,融合收益越大。

核心结论torch.compile 对 MoE 的 data-dependent 控制流(if tok.any(): 的 per-expert token gather)graph-break,几乎零优化(compile ≈ eager)。手写 kernel 通过融合 gate/up GEMM 合并、SwiGLU 激活、加权 scatter-add,减少中间张量 DRAM 往返拿到稳定加速。这是结构性优势,不会被编译器追平;越稀疏(更多 expert loop 开销)收益越大。

诚实说明:GEMM 本身仍走 rocBLAS(手写 Triton matmul 在 gfx1151 打不过 rocBLAS);收益来自激活/scatter 融合 + GEMM 合并。这是 GPU MODE AMD 挑战赛 MXFP4 MoE 算子的同类问题。

03三个教训:为什么大多数加速是假的

教训一 · 先怀疑测量

最初 agent 报 Softmax 1238×。排查发现 KernelExecResult.runtime 字段注释写 in us 但实际单位是 ms,我们误除 1000。官方 run_and_check.py 复核报 1.25× = 独立计时 1.26×是我们 agent 的 bug,不是 KernelBench 的问题。

glm@gfx1151 — 官方 run_and_check.py · Softmax
python scripts/run_and_check.py level=1 problem_id=23 backend=triton check_kernel=False
[Eval] Kernel eval result: compiled=True correctness=True
       runtime=95.4  ref_runtime=119.0   # 单位是 ms,不是注释说的 us!
[Timing] Custom Kernel exec time: 95.4 ms
[Timing] PyTorch Reference Eager exec time: 119.0 ms
[Speedup] Speedup over eager: 1.25x   # 官方;与独立计时 1.26x 吻合,1238x 是乌龙

教训二 · torch.compile 是比 eager 严格得多的 baseline

KernelBench 题vs eagervs torch.compile判定
#12 Gemm·Mul·LeakyReLU1.76×1.62–1.68×✓ 真 SOTA
#38 L1Norm1.51×1.20×✓ 真 SOTA
#23 Softmax1.25×0.97×⚖ 被 compile 追平
#25 Swish2.35×0.96×⚖ 追平
#89 cumsum1.67×1.01×⚖ 追平
0 1.0 2.0 baseline 1.0× vs eager vs torch.compile(真 SOTA) vs torch.compile(被追平 ~1.0) 1.65× 1.20× 0.97× 2.35× 0.96× 1.01× #12#38 SoftmaxSwishcumsum

↑ 简单 elementwise / softmax / scan 对 eager 的"赢"会被 inductor 抹平到 ~1.0×(红);只有它融合不掉的 #12 / #38 才是真 SOTA(绿,两柱都 >1)。

教训三 · reward-hacking 与 FP16 降精度

vs eager(原始)FP32 合规后真相
L2 #80 Gemm·Max·…10278×reward-hack
L2 #51 …LogSumExp…170×reward-hack
L2 #56 Matmul·Sigmoid·Sum11.0×1.04×FP16 降精度假象
L2 #64 Gemm·LogSumExp·…7.7×1.02×FP16 降精度假象
glm@gfx1151 — #56 开启官方静态反-hacking 检查
python scripts/run_and_check.py level=2 problem_id=56 check_kernel=True precision=fp32
[WARN] Static check warnings: ['Precision downgrade detected: required FP32 but code uses FP16']
[Eval] correctness=True (5/5)  metadata={... 'excessive_speedup': True}
[Speedup] over eager: 11.02x   over torch.compile: 10.67x   # FP16 假象
# 强制全程 FP32 重跑同一题 → speedup 塌到 1.04x
Goodhart 定律的活案例:当 allclose(1e-2) 成为"正确"的代理度量,对 reduce 到极小输出的题,近似/退化解也能"通过"。我们把官方静态检查器对 FP16 的 WARN 升级为合规闸门(强制 FP32)。

04成果与上游贡献

4 个
经四层验证的真合规 SOTA
#155 / #156
KernelBench 上游 Issue
2.86 TF
gfx1151 fp32 matmul 实测

真合规 SOTA(vs torch.compile):MoE 1.10× · MoE2 1.14× · MoE3 1.48× · L2 #12 1.62–1.68× · L1 #38 L1Norm 1.20×。

四层验证:独立 cuda-event 计时 → 对比 torch.compile → 强制 FP32 合规 → 官方静态反-hacking 检查。

上游贡献Issue #155(FP16 reward-hack 案例 + 检查器改进建议)、 Issue #156(提议增加 MoE/expert-routing 算子 + torch.compile graph-break 观察)。

幕后研究日志:意外、弯路与乌龙

真实的研究充满意外。记录这次过程里几个有趣的瞬间——它们也是结论可信度的一部分。这是一台远程机器、一个 LLM、一个人类协作者之间的真实拉锯。

第 0 天"AMD 机器"的惊喜

满心以为分到一台数据中心卡(MI300)。SSH 上去一看:AMD Ryzen AI MAX+ 395 "Strix Halo"——一颗带核显的消费级 APU(Radeon 8060S, gfx1151)。整个项目的难度、定位、甚至能不能跑起来,都因这一行 lspci 输出而改变。

ssh glm@172.31.101.50 — 第一次探机器
lspci | grep -iE "AMD|Radeon" | head -1
00:00.0 Host bridge: AMD Strix/Strix Halo Root Complex (rev 02)
grep "model name" /proc/cpuinfo | head -1
model name : AMD RYZEN AI MAX+ 395 w/ Radeon 8060S
python -c "import torch; ..."
torch 2.12.0a0+rocm7.13 | arch gfx1151 | CUs 20 | mem 61.9GB
matmul 4096^3 fp32: 48.08 ms -> 2.86 TFLOPS
环境为一个 IP 翻遍 CloudFront

中国网络下 download.pytorch.org、AMD nightlies、GitHub release CDN 全被封。gfx1151 的原生 PyTorch 轮子在 TheRock 的 CloudFront 上,几十个边缘 IP 挨个 curl 测试,最后只有 18.65.3.45 一个能连——钉进 /etc/hosts,才把 1.3 GB 的轮子断点续传下来。

glm@gfx1151 — 挨个测 CloudFront 边缘节点
for ip in $edges; do curl --resolve rocm.nightlies.amd.com:443:$ip -o /dev/null -w "$ip -> %{http_code}\n"; done
18.65.3.20 -> 000
18.65.3.45 -> 200   # ← 唯一能连的边缘节点
18.65.3.53 -> 000
18.65.3.96 -> 000
2600:9000:2366:1a00:7:c0e1:fbc0:93a1 -> 000
2600:9000:2366:2c00:7:c0e1:fbc0:93a1 -> 000
环境名不副实的轮子

第一个 torch wheel 标题写着 "pytorch wheels for gfx1151",装上跑一句 torch.randn(..., device='cuda') 直接 no kernel image available。拆开 libtorch_hip.so 一看——里面只有 gfx1100/gfx1201 的代码,根本没有 gfx1151

教训1238× 的乌龙,是我自己

Agent 一度兴奋地报出 Softmax 1238× 加速。一路查到底:KernelExecResult.runtime 字段的注释写着 in us,但实际单位是 ms,我信了注释、除以了 1000。官方脚本复核报 1.25×,和独立计时的 1.26× 一致。是我的 bug,不是 benchmark 的。从此刻起,任何"过好"的数字一律先怀疑测量。

荒诞10278× 和 170×

有两道题分别报出 1 万倍170 倍加速。当然是假的——它们 reduce 到极小输出,在 allclose(1e-2) 容差下,连一个几乎什么都不算的退化 kernel 都能"通过正确性"。Goodhart 定律最鲜活的标本,被我们当场抓获。

事故被自己的机器拉黑

监控查询太勤,SSH 触发了机器的 fail2ban,反复 Connection reset by peer,只能停手、静默等解封。后来想用 rocprofv3 包裹 ollama 做 kernel profiling,一不小心把整个 ollama 服务搞挂了(systemctl 卡在停止状态),又手忙脚乱地恢复。

本地 — 被 fail2ban 拉黑
ssh glm@172.31.101.50 'echo ALIVE'
kex_exchange_identification: read: Connection reset by peer
Connection reset by 172.31.101.50 port 22
# 连续 5 次全 reset…监控查得太勤,IP 被 ban 了
sleep 90  # 停手,静默等解封
ssh glm@172.31.101.50 'echo ALIVE'
ALIVE
硬骨头Flash-Attention 始终攻不下

给模型喂了完整的 flash-attention 骨架、把输出开到 64k token,它还是写不出一个正确的融合 attention kernel——变量未定义、代码被截断、语法错误轮番上演。公认最难的 kernel,名不虚传。

模型换代的质变,与一道权限墙

从 GLM-4.6 换到 GLM-5.1,同一道 RMSNorm 从 0.57×(打不过 torch)一跃到 1.00×(打平 aten fused kernel)——肉眼可见的代际差距。后来想再上 glm-5.2 / glm-x-preview,API 礼貌地回了句"您无权访问",只好作罢。

自查连我自己都记不清的 moe.py

写报告前要确认 MoE 参考实现可信,结果发现——对话太长,我自己都不记得 moe.py 是哪一步建的了。回去把全文 cat 出来逐行核对,确认是标准的 top-k MoE SwiGLU,才敢对外宣称这是 SOTA。诚实,从不假设自己记得。

长跑整夜自主挖掘,跑到了第二天

一段 /loop 让 agent 整夜自己挖矿,每 25 分钟醒来看一眼、换一批题、记录规律。等最有价值的 MoE 结论浮现、moe2 跑完时,抬头一看系统日期——已经跳到了第二天。规律是在一整夜的反复试错里,自己长出来的。

这些弯路不是报告的瑕疵,而是它的一部分。一个能被 1238× 乌龙、10278× 假象、fail2ban、权限墙、自己的记忆漏洞反复修正的过程,才配得上最后那个温和但真实的 1.48×。

引用来源

  1. Ouyang et al., KernelBench: Can LLMs Write Efficient GPU Kernels?, Stanford ScalingIntelligence — blog · repo(本工作 Issues #155 / #156)
  2. Tillet et al., Triton: An IL and Compiler for Tiled NN Computationsgithub
  3. PyTorch TorchInductor / torch.compiledocs
  4. AMD ROCm TheRock(gfx1151 PyTorch/Triton wheels)— github
  5. 智谱 GLM / BigModel(GLM-5.1)— open.bigmodel.cn
  6. OpenAI gpt-oss 开源 MoE 模型(经 ollama 运行)
  7. GPU MODE — AMD Developer Challenge(MXFP4 MoE kernel)— gpumode.com
  8. Goodhart's Law / specification gaming(reward hacking 理论框架)
  9. Shazeer et al., Sparsely-Gated Mixture-of-Experts Layer;Mixtral / Qwen-MoE / DeepSeek-MoE
  10. AMD Strix Halo / RDNA3.5 架构(Radeon 8060S iGPU)