技术拆解
把 KV cache 搬上显卡:一次从传输瓶颈到计算瓶颈的转变

一次 decode profile 揭示瓶颈在 host-GPU 间的 KV 往返而非计算本身。KV cache 常驻显存后,每 token 的 KV/IO 从 80.6 降到 1.1 ms/token——数据来自单块 AMD GPU 和单个模型。
本文所有数据来自同一台机器上的一块 AMD RX 9060 XT(RDNA4,gfx1200),跑的是 qwen3-asr-0.6b q8 量化包。这些数字不代表”AMD 平台的表现”,更不代表 OpenASR 的整体性能——它就是一块显卡、一个模型、一组实测记录,作者本人也标注这些数据仍待在正式 release benchmark 中重新验证。限定好这个范围之后,故事本身很干净:一个看起来像是计算密集的 decoder,实际上是传输密集的,而修复方法就是别再把 KV cache 在总线上搬来搬去。
一个诚实的优化故事,往往从”第一个猜测被推翻”开始。这次也一样——最初的不完整 profile 把大约 86% 的时间归到了 encoder 头上。补齐缺失的打点之后,结论完全反转了。
先量后砍
在一段 61 秒的音频上,完整的 decode profile(HIP 后端,已启用 graph reuse)分解如下:
- decode 总耗时:95.4s(94.2 ms/token)
- 真正的 GPU 计算:13.8s(13.6 ms/token)
- KV/IO——host 与 GPU 之间的往返搬运:81.6s(80.6 ms/token)
- 音频 encoder 作为对比:4.0s
Host-GPU 之间的 KV 往返,耗时大约是实际 GPU 计算的 6 倍。而之前一度被怀疑是瓶颈的 encoder,在 95.4s 的 decode 总耗时里只占了 4.0s。Decoder 慢,不是因为矩阵乘法慢,而是因为每一步都要把完整的 key/value 前缀重新上传到显卡,再把投影后的 K/V 读回主机。
Encoder 是个烟雾弹。 早期的不完整 profile 把大约 86% 的时间归到了音频 encoder。把 KV/IO 单独拆出来重新测量后,encoder 只占 95.4s 中的 4.0s。教训很朴素:在重构错误的东西之前,先把打点补全,重新量一遍。
让 KV cache 常驻显存
这个改动(设计笔记里标记为 S5 的步骤)在 GPU 上开辟一块 arena,让 KV cache 常驻其中,新 token 原地追加,而不是每一步在主机侧重建完整历史再上传。前缀的 host-to-device 上传和投影后 K/V 的 device-to-host 回读,都不再发生。
对每 token KV/IO 分量的影响非常直接:从 80.6 降到 1.1 ms/token。需要强调的是,这是一个逐 token 分量的变化,不是端到端加速——两者的度量单位不同,不能画在同一根箭头上。换个角度说,那段 61 秒音频上约 81.6s 的 KV/IO 开销被消除了,计算现在成为 decode 的主导项。
Decoder 不是计算慢,而是搬运慢——只有当 KV 往返在 profile 里占了单独一行的时候,你才看得见这一点。
——x86 GPU 架构笔记
端到端效果
把整个优化序列(S0 到 S5,累计效果,不只是 KV 这一步)汇总来看,这块 GPU 上 HIP 后端的累计结果:
- jfk 片段: RTF 2.50 → 0.717(累计 3.5x,已快于实时)
- 61s 片段: RTF 2.56 → 0.356(累计 7.2x,同样快于实时)
7.2x 是整个优化序列的累计数字,不能全部归功于 KV cache 这一步。单独看 device-resident KV 这一步,它把 61s 片段的 RTF 从 1.64 降到了 0.356——大约贡献了累计收益的 4.6x。两个数字都限定在 RX 9060 XT 和 qwen3-asr-0.6b q8 上,当作实测记录看就好,不是性能承诺。
一套代码,两个 GPU 后端
同一份源码,不含任何后端专属代码,也能跑在 Vulkan 上。用 --features vulkan 编译后,qwen3-asr 在 Vulkan(RADV,gfx1200)上产出了完全正确的结果,jfk 片段 RTF 0.932——同样快于实时。在这台机器上 HIP(jfk 0.717)跑赢了 Vulkan(0.932),符合 HIP 在 AMD 上更成熟的预期。61s 片段没有 Vulkan 的数据可报。
这种后端可移植性和加速本身同等重要:device-resident KV 和 GPU 能力分级机制都是后端无关的,所以一条执行路径就覆盖了 HIP、Vulkan(以及 Apple 上的 Metal),不需要为每个后端 fork 出单独的实现。
需要致谢的是:HIP、Vulkan、Metal 后端——以及底层的 KV 和 flash-attention kernel——都是 ggml 的,这是 llama.cpp 和 whisper.cpp 背后的 MIT 协议 C 库。本文描述的 device-residency 层建立在这个基底之上,不是替代它。
# 检查检测到的 GPU 后端和支持的量化类型openasr doctor最后把限定条件再挂一遍:一块 AMD RX 9060 XT(RDNA4,gfx1200),一个 qwen3-asr-0.6b q8 量化包,Vulkan 数据仅限 jfk 片段,整张表都是实测记录、待正式 benchmark 重新验证。在这个范围内,结论是扎实的——一个读起来像是计算瓶颈的 decoder 其实是传输瓶颈,把 KV cache 搬上显卡就是翻转它的关键。关于配对的 GPU decode 角度——在单块显卡上通过批处理多路并发 decode 来提升吞吐——参见 batched serve-mode decode 的深入分析。