Skip to content

约 8 分钟阅读

18. 时延、功耗与内存怎么测 ​

17. 图转换与算子适配 结束时,模型已经被设备收下了。接下来要回答的是"好不好用",而这一步最容易出事——不是测不出数字,是很容易测出一堆看起来合理、实际上没有意义的数字。

这个项目里作废过三组功耗数据、撤回过两个结论,原因都不是设备问题,是口径没立住。所以这一篇按顺序讲四件事:时延怎么测才可比,功耗在什么条件下根本测不出来,温升为什么没结论,内存怎样拆开归因。

时延:定速和背靠背测的是两件事 ​

最直觉的做法是循环跑 N 次取中位数。这个做法能用,但它测的是峰值能力——背靠背没有间隙,缓存一直是热的,队列一直是满的。

实时语音识别的真实形态不是这样:每 640 ms 来一拍,算完就空着等下一拍。所以需要第二种跑法——按真实节拍定速,每拍的起点按绝对时间边界排(第 i 拍的起点是循环开始时间加上 i 乘以拍长),而不是"算完睡一会"。后者会把每次的抖动累加进去,跑十分钟之后节拍已经漂没了。

定速模式必须同时记录一个数:超时拍数,也就是上一拍还没算完、下一拍就该开始的次数。这是实时能力的判据,比平均耗时更直接。

两种跑法的结果:

加载/编译min中位均值p95
CPU(INT8,4 线程,背靠背)4392 ms61.9464.5266.7690.97
NPU(离线 INT8 模型,背靠背)3671 ms110.38143.58141.21148.68

按 640 ms 定速连续跑 10 分钟(937 拍):

中位均值p95超时占空比
CPU69.0677.67101.300/93711%
NPU146.91146.28152.850/93723%

两张表合起来才说得清楚:NPU 每拍确实更慢,但 937 拍里一次超时都没有,占空比 23%,实时能力完全够。只看第一张表很容易得出"NPU 跑不动"的错误印象——它不是跑不动,是每拍更慢。

还有两条数值上的注意事项。一是样本数要够:NPU 在 5 次采样时中位数是 124.7 ms,加到 20 次变成 143.6 ms,而 CPU 一直稳定在 64.5 ms。前几次偏快,少量采样会系统性高估 NPU。二是两种跑法的数字不能混比:CPU 背靠背 64.5 ms、定速 69.1 ms,差的是缓存热度,拿一边的 CPU 去比另一边的 NPU 会得到错的倍数。

功耗:先过门禁,再谈数字 ​

这些数字是怎么测出来的

功耗这一段作废过两次,教训比数字本身更值得记。

第一条:插着数据线测不出功耗。 插线时电池电流被充电回路钳住,和负载完全脱钩。实测两档读数是 −631 mW 和 −627 mW,几乎一模一样。这不是"差异小",是信号根本没进来——无论跑多久、跑多少次,都不会变。所以采集脚本的第一件事是检查供电状态,不满足就直接退出,不产出数据。

第二条:两档必须处理等量音频。 如果两档都按"尽量快地跑满十分钟",快的那一档在同样墙钟时间里多处理了一倍多的音频,功耗对比毫无意义。所以两档都按 640 ms 定速、都跑 937 拍,吃掉的音频完全一样。

第三条:要有空载基线。 屏幕、系统和采样器本身就在耗电,而且占大头。基线用同一个应用、同一块屏幕、同一个采样器,只把模型换成一个 100 节点的极小图(占空比 0.29%),这样减出来的才是推理本身。

按这套协议跑出来的结果:

整机功耗推理净增95% 置信区间每拍能耗每拍耗时超时
空载1.21 W—————
NPU1.31 W+98 mW±22 mW63 mJ147 ms0/937
CPU1.52 W+311 mW±42 mW199 mJ69 ms0/937

NPU 每拍能耗低约 69%。整机只差 14%,因为屏幕把差异摊薄了——如果是关屏的后台转写场景,整机上的节电比例会明显更大。

为什么只能用均值 ​

这一条单独拿出来说,因为按中位数算会得到相反的结论。

采样频率是 1 Hz,而 CPU 的计算是突发式的:每拍只忙 69 ms,然后空闲 571 ms。绝大多数采样点落在空闲期里,中位数反映的是空闲功率而不是平均功率。按中位数算,CPU 净增只有 +80 mW,看起来比 NPU 还省电——这是纯粹的采样假象。

均值是时间平均功率的无偏估计,三种算法(全段均值、去掉前 60 秒、去掉两头 10%)排序一致。峰值也要记录:p90 是 NPU 1.43 W、CPU 2.10 W,这个差距和"CPU 算得快但瞬时功率高"的解释吻合。

遇到任何突发式负载的功耗测量,都要先问采样频率和占空比的关系。采样周期远大于单次计算耗时时,中位数一定会低估。

被撤回的那几个数字 ​

写下来是因为它们比正确结果更能说明这件事有多容易做错。

  • 曾经引用过一组"合成模型测得净功耗低 38%"的数据,后来溯源不到可信的采样记录,而且与项目里两份"从未直接测到瓦数"的记录矛盾,已经撤回。
  • 曾经给出过"每次推理温升低 37%",精确到这个程度本身就不该出现在一次性顺序测量里,主动收回。
  • 一处代码缺陷:对毫瓦读数取了绝对值,把充电电流当成了消耗,复测时出现 2675 mW 这种假值。

共同点是都没有先确认测量前提是否成立。功耗这类数据的特点是"永远能给你一个数",所以门禁必须做成自动的,而不是靠记得检查。

温升:这一项没有结论 ​

两档各跑十分钟,机身温度分别升了约 1 °C(26→28、28→29、29→30 °C)。顺序执行时每一段的起点都被上一段抬高,加上电池温度传感器响应慢,这个口径区分不出两档的差别。

所以温升这一项的结论是没有结论,而不是"两档差不多"。要测温升需要换设计:每段之间充分冷却到同一起点、每档重复多次、最好用更快的温度源。在没有做之前,不能用"NPU 更省电所以温升也更低"来替代——这是推理,不是测量。

同样要写清楚的还有几项已知局限:每档只跑了一次且顺序固定(CPU 在最后、机身最热,对 CPU 略不利);CPU 档走的是一个次优的计算后端(见下一节),换成更优的后端预计能降约 24%,但按这个估算仍是 NPU 的约 2.4 倍;只测了编码器,不是端到端链路;整机功耗包含屏幕。

一个长期被记成"正常"的 CPU 后端缺陷 ​

功耗表里的 CPU 档走的是默认计算后端,而不是本该启用的那个优化后端。查下来的链条是这样的:

端侧那个推理运行时的动态库,和上游预编译发布的版本是同一个校验和,里面不含优化后端的任何实现符号。而代码在创建会话时无条件追加这个后端,在这样一个库上会抛异常——另一条链路因此加载即崩。源码本身没错,坏的是构建输入:编译脚本里后端列表是硬编码的字面量、从不校验,而那一步编译从未成功过,进度记录却把它写成"功能正常"。

两个可复用的做法:

  • 产物要带来源信息并在构建时校验,不能靠一个硬编码的列表声明自己编进了什么。一个最低限度的检查是在产物里查关键符号是否存在。
  • 可选后端的降级必须显式告警,不能静默回退。静默回退的代价在这里是性能低 24%,而且谁也不知道。

内存:先确认采集手段,再谈归因 ​

内存排查一开始就建立在一个错误前提上:文档里记着某个场景从 181 MB 涨到 611.8 MB,多出来的 428 MB 被整个归给了说话人分离。实测发现纯转写的活跃识别就已经是 502 MB,而且 611.8 MB 是从外部采的进程常驻内存总量,里面包含约 170 MB 可回收的模型文件页和测试外壳本身的运行时堆。前提错了,后面所有定位都会走偏。

这台设备上还有一个采集手段的问题:进程的内存映射明细对调试 shell 是拒绝访问的(安全策略),常见的那套读法用不了。可用的是系统自带的内存信息工具,它由系统服务侧读取,能按类别汇总,也能逐映射输出,单次约 530 ms,1 秒一次的采样可行;进程状态文件里的常驻内存总量是公开可读的,可以用来交叉校验。

按类别拆开之后,带说话人分离的 NPU 档峰值是这样的:

类别占用说明
应用包文件页169.3 MB文件页,内存紧张时可回收
原生堆134.7 MB
图内存55.3 MBNPU 的计算图占用
应用运行时堆20.2 MB
动态库15.7 MB
合计Pss 518.2 MB / 常驻 580.2 MB

说话人分离这一项的净增约 145 MB,和它 55 MB 的模型体量对得上,没有重复加载,也没有缓存泄漏。"存疑待查"到这里可以撤销。

这组数字还解释了 16. 模型压缩与体量门槛 里为什么否掉了"切成两段用 FP16"的方案:那会让这部分从 162 MB 涨到 293 MB,多出 131 MB,而图内存本来就已经是一项实打实的开销。

总结 ​

这一篇真正交付的不是数字,是口径:时延要分开测峰值和定速,并记录超时拍数;功耗要先过供电门禁、要有空载基线、两档要处理等量音频、突发式负载必须用均值;温升在当前设计下没有结论,就该写成没有结论;内存要先确认采集手段可信、再按类别归因,而不是拿一个总量去推测去向。

把这些立住之后,剩下的问题只有一个:模型确实上了 NPU,确实更省电,但为什么没有变快。

下一篇:19. 为什么没有变快。

别急,先让缓存热一下。