跳至内容
Ludovico's Blog
返回

llama.cpp HTP 调优:少用 CPU 为什么反而慢

这次调 Qwen3-0.6B 时,有一个很自然的目标:

CPU 留给调度,计算尽量交给 HTP。

听起来合理。实际跑下来,结果反过来:CPU 用得更少,速度更慢。

这篇只记录这个现象。

两条路径

同一个模型:

Qwen3-0.6B-Q4_0-output-Q4_0.gguf

路径 A:尽量让 HTP 承接算子,不设置 OPFILTER

GGML_HEXAGON_OPPOLL=1

结果:

tg64 = 30.32 ± 1.14 t/s

短 completion profile 里:

eval time = 377.78 ms / 16 runs
23.61 ms/token
42.35 token/s

路径 B:过滤 HTP 上不划算的 attention/KV 算子。

GGML_HEXAGON_OPPOLL=1
GGML_HEXAGON_OPFILTER='FLASH_ATTN_EXT|SET_ROWS'

结果:

tg64 = 67-70 t/s

从名字看,路径 A 更像 NPU 加速;从结果看,路径 B 更快。

no-filter profile 里看到什么

打开 HTP profile 后,no-filter 路径里 SET_ROWSFLASH_ATTN_EXT 都确实在 HTP 上:

SET_ROWS       6-11 us / op
FLASH_ATTN_EXT 245-296 us / layer

SET_ROWS 不是大问题。它是 KV cache 写入,单次十微秒以内。

真正贵的是 FLASH_ATTN_EXT。Qwen3-0.6B 有 28 层,decode 单 token 每层一次 attention。按 250us 算:

250 us * 28 = 7 ms

单 token 要跑到 70 token/s,预算大约是:

1000 ms / 70 = 14.3 ms/token

只 attention 就吃掉一半预算,其他 matmul、fallback、采样还没有算。

为什么 HTP attention 在这里不划算

这里不是说 HTP attention 写错了,也不是说 HTP 不适合 LLM。

问题是 Qwen3-0.6B 太小。

decode 是单 token 场景,算力利用很难拉满。HTP attention 要处理:

Q/K/V view
KV cache
mask
VTCM scratch
DMA prefetch
worker pool
同步

这些固定成本对 4B、7B 可能还能摊掉。对 0.6B,matmul 变小后,固定成本就露出来了。

所以最优策略不是“算子能上 HTP 就上”,而是:

大 matmul 上 HTP
小而碎、调度重的算子 fallback

单独 filter 更差

还试过只过滤一个:

策略tg64
no filter,FLASH_ATTN_EXT + SET_ROWS 都上 HTP30.32 ± 1.14
只 filter FLASH_ATTN_EXT21.54 ± 8.82
只 filter SET_ROWS17.45 ± 5.36
filter `FLASH_ATTN_EXTSET_ROWS`

这说明问题不只是某个 kernel 慢。图切分也很关键。

只把其中一个算子留在 HTP,会产生更差的 HTP/CPU 交替形态。每一段 split 都要同步、搬数据、调度。小模型上,这些开销很容易压过计算收益。

mask 裁剪实验失败了

我们试过改 HTP FLASH_ATTN_EXT,想在 decode 时根据 mask 尾部的无效位置缩短 KV 长度。

尝试了两种判断:

fp16 -inf   = 0xFC00
fp16 -65504 = 0xFBFF

结果没有变化:

FLASH_ATTN_EXT 仍然约 250-290 us / layer
端到端仍然约 41-42 token/s

补丁撤回了。

这个失败也有价值:HTP kernel 里看到的 mask,不是简单“尾部全负无穷”的形态。后续如果要优化,不能在 kernel 里靠猜 mask。更靠谱的方向是从 llama graph / KV cache view 构造处拿到真实有效 KV 长度。

CPU fallback 不是坏事

这次最容易误解的是:

OPFILTER = CPU fallback = 不好

实测不是。

对这个模型,这条路径最快:

GGML_HEXAGON_OPPOLL=1
GGML_HEXAGON_OPFILTER='FLASH_ATTN_EXT|SET_ROWS'

它的含义是:

HTP: Q4_0 matmul、主要 dense 计算
CPU: attention/KV 小算子、lm-head logits

混合执行不是妥协,而是当前硬件和 kernel 形态下的最优点。

什么时候应该少用 CPU

如果目标是系统调度余量、电源、热设计,少用 CPU 是合理目标。

但如果目标是 token/s,要先看 profile。

这次 no-filter 路径确实减少了 CPU 计算,但速度只有:

41-42 token/s

最高速度路径是:

67-70 token/s

所以两个目标要分开说:

目标当前策略
最高 token/sfilter `FLASH_ATTN_EXT
少用 CPUno-filter,但需要继续优化 HTP attention

如果要同时做到少用 CPU 和高 token/s,下一步不是调参数,而是改 HTP FLASH_ATTN_EXT 的 decode 路径。


分享:

上一篇
llama.cpp 里的 lm-head:Q6_K、Q4_0,以及为什么 4B 不一定提速
下一篇
llama.cpp 跑 Qwen3-0.6B:HTP 上从 20 到 70 token/s