Skip to content

约 10 分钟阅读

19. 为什么没有变快 ​

18. 时延、功耗与内存怎么测 给出了一组口径可靠的数字:编码器在 NPU 上每拍 147 ms,在 CPU 上 69 ms,慢 2.1 倍;但每拍能耗 63 mJ 对 199 mJ,省约 69%。

"更省电"好解释——算得慢但瞬时功率低。真正需要解释的是"更慢"。一颗专门做矩阵运算的加速器,跑一个以矩阵乘为主的模型,比四个通用核心慢一倍,这件事不符合直觉,而不符合直觉的现象如果不查清楚,后面所有优化都会押在错误的方向上。

这一篇讲怎么查清楚的,以及查清楚之后这颗 NPU 该怎么用。

先造几把尺子 ​

直接在真模型上试优化是没有尽头的:图里有 3766 个节点、几十种算子,改哪一处、省多少,全靠猜。有效的做法是先用合成模型在同一颗 NPU 上量出几个基本常数,再拿这些常数去算真模型的时间去向。

合成模型就是手工构造的小图:固定节点数、固定张量形状、计算内容无关紧要,只用来测一个变量。

为什么模型上了 NPU 却没有变快

量出来的六个数:

量实测值怎么测的
每节点地板价0.0024 ms100、400、800、1600 节点的链,耗时严格线性;模型文件体积也严格是 1:4:8:16,说明编译器没有把链条折叠掉
真实形状下每节点0.0035 ms把链条里的张量换成编码器主流的形状
每路张量绑定0.003 ms117 路输入输出对 8 路、总体积相同,只差 0.32 ms
权重读带宽23.9 GB/s单个矩阵乘分别扫 8.39、33.6、81.9 MB 的权重,三点线性
形状敏感度5.6 倍同样是每张量 64 KB、200 个节点,二维 [1,16384] 要 4.956 ms,四维 [1,64,16,16] 只要 0.890 ms
批次收益16 倍数据只花 2.44 倍时间同一条 200 节点的链,批次从 1 提到 16,单位数据成本降 6.6 倍

第一条里那句"模型文件体积严格 1:4:8:16"值得单独说:没有这个交叉校验,就无法排除"编译器把我的测试链优化掉了"这种可能,测出来的线性关系会变成一个假象。造合成模型做基准,一定要有一条独立的证据说明被测对象真的还在。

五个假说,同一批尺子,全部不成立 ​

有了常数,之前那些"听起来合理"的解释就可以逐个算账了。

假说算账结果
时间几乎全花在每节点的固定过路费上,每节点约 0.037 ms实测 0.0024 ms,小 15 倍
把节点数从 3766 砍到 1605 就能追平 CPU折掉 600 个节点只值 1.4 ms,在 144 ms 里可以忽略
把 99 处展开写的激活函数折回成原生算子是必要铺垫数学上等价,同样只值约 1.4 ms,不值得做
234 路张量绑定每路 0.25 ms,合计 58 ms实测合计 2.4 ms,松了 20 倍
每秒只搬 63 MB,远低于内存带宽,所以带宽不是瓶颈结论侥幸是对的,但依据无效:漏算了 157 MB 权重和 121 MB 激活

最后一条是个很典型的坑——结论对、推理错。如果不复核依据,下一次用同样的方法算另一个模型就会出错。

把常数代回真模型,144 ms 的去向是这样的:

项时间占比
节点地板价(3766 × 0.0035)13.2 ms9%
权重读取(157 MB ÷ 23.9 GB/s)6.6 ms5%
234 路张量绑定2.4 ms2%
余下的真实算术约 122 ms83%

可归因的开销合计只有约 22 ms。 也就是说没有"开销"可以削——83% 的时间是这颗 NPU 在老老实实做算术,只是做得不快。

这同时回答了另一个被反复提起的方向:"能不能减少数据搬运"。搬运(权重 6.6 + 绑定 2.4)合计只占 6%,把它清零也只省这么多。方向本身不错,但入口不在这里。

真因:张量填不满计算单元 ​

把节点按形状分类之后,答案就出来了:编码器 81% 的节点(3766 个里的 3063 个)是三维、形状为"时间 8、批次 1、维度 D"。

这类 NPU 的矩阵单元按 16×16×16 的立方块工作,一次吃进一个 16×16 的瓦片。"8 乘 1"的瓦片只填满立方块的三十二分之一,而算一次的代价和填满时相差不大。于是标称上远强于 CPU 的算力,在这张图上只兑现了很小一部分。

这不是算子选得不好,也不是转换做得不对。流式识别的张量本身就是这个样子:一拍 640 ms 音频,经过下采样之后时间维只剩 8,批次是 1 因为只有一路音频在跑。换句话说,慢的原因写在模型的使用形态里,不在模型的实现里。

两个顺带的操作性结论:

  • 不要去手动优化那 250 个转置算子。离线编译器自己会消除冗余转置——往返转置的合成模型编译出来反而更小更快,手动改只会更差。
  • 不要为了"减少节点"去做等价重写。按地板价算,每砍 1000 个节点值 2.4 ms。

对照:同一颗 NPU 上另一个模型赚了 11 倍 ​

如果结论停在"这颗 NPU 不行",那就是错的。同一台设备、同一颗 NPU 上,还跑着一个说话人编码器:90 个节点,每次处理一个 10 秒窗口,从 CPU 的 307.7 ms 降到 27.7 ms,快了 11 倍。

两个模型的差别不在节点数(90 对 3766,少的那个反而赚了),而在每个节点带多少数据。说话人编码器一次吃 10 秒音频,张量塞得很满;流式识别一次只吃 640 ms,还要拆成 8 帧,塞不满。

所以"这颗 NPU 行不行"这个问题问得不对,应该问的是"这张图的形状行不行"。判断一个模型值不值得上这类加速器,先看它的典型张量能不能填满计算单元,而不是先看它有多少参数或多少算子。

顺着真因往下试,又撞上一堵墙 ​

既然填满批次是唯一实测有效的杠杆,下一步就很明确:把同一张图按 16 路并行重转,让 16 条音频流共享一次权重读取和一次计算。这同时解决了搬运问题——157 MB 权重从每拍读一次变成每 16 拍读一次。

这件事做到了一半,记录如下。

第一次判断是错的。 我一开始说"批次维本来就是符号维,不需要改图"。上游 ONNX 确实是符号维,但固定形状那一步做过常量折叠(10891 节点合并到 3766),把批次 1 烧进了 278 个形状重排的常量、38 个膨胀算子和一份注意力掩码常量,而且批次所在的位置并不总是第一维。手工往回改时连续撞上形状冲突,撞了两处就该停下来重新想——正确做法是回到带符号维的上游图,用重写脚本自带的批次参数重跑整条链。

重跑是通的。 批次 16 的图仍然是 3766 个节点、没有残留的不支持算子,117 路输出在宿主上验证通过,最大偏差 9.59e-4。校准集用 16 段错开起点的真实音频并行递推产出。量化和编译都成功,产出 166,697,763 B 的模型。

设备拒收。 兼容性查询返回 1,构建阶段失败,日志指名注意力里的一批矩阵乘不被算子库支持。

隔离实验把原因缩到了一个点上。先排除"批次大于 1 就不行":批次 4 的普通矩阵乘、时间维 32 的矩阵乘,都通过。再排除形状:按报错里的确切形状造合成模型,包括那个被点名的形状,浮点版本全部通过。最后做决定性验证——同一个算子、同一个形状,浮点版本设备收,量化之后设备不收。

结论是:这颗芯片没有量化版本的批量矩阵乘实现。批次为 1 时,这些节点会降解成普通矩阵乘(有量化实现),所以反而能跑;批次大于 1 就变成批量形态,没有量化实现可用。

这一类"只在某个组合下才出现"的限制,光看文档和算子支持列表是查不出来的。能定位到它,靠的是把变量一个一个拆开做合成模型对照——先分离批次与时间维,再分离形状,最后分离精度。

补救方向和它的状态: 被拒的是 19 个带四维权重的注意力矩阵乘,这些权重合计只有 0.1 MB(而另外 325 个二维权重合计 608.5 MB)。把这 19 个节点排除出量化、保持浮点,体量上几乎不花代价。这一步在记录时点只写好了跳过指定层的量化配置,没有跑完,没有真机结果。 它是一个有依据的方向,不是一个结论。

同样属于推算而非实测的还有一条:按合成模型的缩放关系外推,吞吐档下每拍可能降到 22 ms 量级、能耗降到 10 mJ 量级。这个数字没有在真模型上验证过,而且代价很明确——批次 16 意味着要先缓冲 10.2 秒音频,交互式场景用不了。

两条平台级的边界 ​

有些路不是没调通,是平台没开。这类结论要尽早做实,避免反复投入。

GPU 在这台设备上对通用计算是关闭的。 推理框架在上下文校验阶段就否掉了(1.17 ms 返回,GPU is not supported),系统自带的那份库编译时没有启用 GPU 后端,应用侧没有开关可拨。旁证是系统能力参数里只有图形渲染相关的项,没有通用计算的能力位;GPU 本身在正常工作,但只对渲染开放。两台不同型号的鸿蒙手机结论一致。其余路径也都不通:几种常见的 GPU 执行后端要么不属于这个平台,要么官方已移除;手写计算着色器理论可行,但 3766 个节点的量级工作量按月计,而且低精度支持残缺。

CPU 这一侧还欠着一笔。 对照用的 CPU 档走的是默认计算后端而不是优化后端,原因是产物里压根没编进去(细节在 18. 时延、功耗与内存怎么测)。修好之后 CPU 预计快约 24%,估算仍是 NPU 的约 2.4 倍——所以它不改变结论的方向,但会让差距更大。

采用方案与边界 ​

把所有实测汇到一起,这颗 NPU 在这个模型上的结论是分档的。

场景该不该上 NPU依据
流式实时转写,目标是续航和温升可以上。时延从 69 ms 变成 147 ms,但仍有 77% 的空闲余量、937 拍零超时,而每拍能耗省 69%实测
流式实时转写,目标是首字时延不要上。慢 2.1 倍是结构性的,不是调优问题实测
交互式对话,对时延敏感不要上。同上,而且填批次的方案要先缓冲 10 秒音频实测 + 推算
离线批量转写、多路并发值得试,但目前只有合成模型的外推,真模型上被量化版批量矩阵乘挡住未验证
窗口较长的辅助模型(说话人编码器一类)优先上。同一颗 NPU 上快了 11 倍实测

一个更可迁移的判断方法:先看典型张量能不能填满计算单元,再谈要不要上加速器。参数量和算子数都不是好的指标,形状才是。把一个流式模型的时间维和批次维摊开,如果它们都是个位数,那么这颗加速器的算力优势大概率兑现不了,节能可能仍然成立。

还有一条流程上的结论:这次之所以能把"慢"解释清楚,靠的不是更深的硬件知识,而是先用合成模型量出可复用的常数,再用常数去否定假说。五个假说在被量化之前,每一个听起来都成立,其中两个还指导过实际的优化尝试。拿不到硬件内部指标时,这种"自造标尺"的办法通常比继续猜更快。

总结 ​

模型上了 NPU 却变慢,原因不在算子选择、不在数据搬运、也不在节点数量——可归因的开销合计只占 15%,83% 的时间是真实算术在极低的计算单元利用率下跑出来的,而低利用率来自流式识别"时间维 8、批次 1"的张量形态。同一颗 NPU 上窗口更长的模型反而快了 11 倍,说明问题在图的形状而不在硬件。按这个结论取舍:目标是功耗和温升时,流式档上 NPU 成立且已实测;目标是时延时不成立;吞吐档理论上有优势,但被一个"没有量化实现的批量矩阵乘"挡住,补救方案尚未验证完。

这个子系列到此结束。把视角拉回整条语音链路,识别只是其中一环——状态机、打断、端云协同怎样组织,见下一篇。

下一篇:20. 语音交互链路。

别急,先让缓存热一下。