ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RISC-V端侧推理能效治理:调度、量化与空闲态协同优化

RISC-V端侧推理能效治理:调度、量化与空闲态协同优化 去年有一段时间我们团队在一颗RISC-V四核SoC上做端侧推理跑的是YOLOv5检测。板子刚上电时的表现让我印象深刻——推理延迟低温度也低跑几分钟后温度上来系统开始自动降频然后帧率崩了。那阵子大家习惯性把“能效”理解成降频降温遇事不决就把最高频率锁低或者打开thermal限制。折腾了一周功耗是低了但产品根本没法用。后来我把思路整体换掉从被动节流改成主动能效治理核心就落在调度、量化与空闲态优化这三件事上。这篇分享没什么高深理论都是我在RISC-V端侧推理落地过程中反复试出来的方法适合做嵌入式AI部署、实时推理系统和嵌入式Linux优化的朋友参考有基础的人能直接拿去用新手也看得懂。1. 为什么被动节流撑不起端侧推理1.1 降频只是把问题往后推被动节流最典型的实现就是DVFS。芯片有动态电压频率调节频率下降后动态功耗大致按频率线性下降按电压平方下降所以只要把频率降下来温度马上好转。问题在于端侧推理不是恒定负载推理阶段是计算密集型前后处理是内存访问密集型任务队列还有空窗期。固定低频运行会拖慢每个计算阶段导致推理延迟变高帧率下降。更麻烦的是很多平台的DVFS切换不是瞬时完成的频繁调频会让电压调整器一直在跳变功耗不降反升。我拿某款RISC-V开发板实测过温度触发降频后主频从1.5GHz掉到1.0GHz单帧延迟从120毫秒涨到180多毫秒帧率直接腰斩。这就像夏天电不够最后拉闸而不是把电能分配到最需要的地方。被动节流本质上是事后救火没有从任务怎么跑、数据怎么搬、CPU闲着该干嘛这三个角度去解决问题。端侧推理真正需要的是主动治理即在任务调度、数据位宽和空闲低功耗这几个维度上同时下手。1.2 主动能效治理的三个支点主动能效治理的核心思维是把“能效”拆成两条曲线来看一条是性能曲线一条是功耗曲线最终目标是让性能不下降或下降有限的前提下把功耗压下去。我最后收敛到三个抓手。第一个是调度。调度解决的是任务去哪颗核心、什么时候抢占、空闲时是否要主动让核的问题。RISC-V多核平台如果调度器选核不合理任务会在核之间反复迁移缓存不断失效功耗不被算进任务里但实实在在地烧在DDR访问和总线竞争上。第二个是量化。量化解决的是数据量问题FP32换成INT8后同样一个卷积操作需要搬运的字节数变成原来的四分之一计算量也降下来。第三个是空闲态。空闲态解决的是无事可做时的浪费。CPU没有任务时如果还在忙等或者被周期时钟定时唤醒功耗一样很难看。这三个支点不是孤立的量化把任务变轻调度把任务放对位置空闲态把空出来的时间变成低功耗模式三者协同才叫主动治理。2. 调度优化把算力花在刀刃上2.1 用 cpuload 数据先给系统“拍个CT”拿到开发板后我先做的不是改内核而是采集cpuload数据。cpuload并不等于CPU占用率它能反映每个CPU核在单位时间内处于运行态、睡眠态、中断处理的时间比例。我喜欢组合用这几条命令top -d 1看每个线程的CPU占用mpstat -P ALL 1看每个核心的忙碌和空闲比例cat /proc/interrupts看中断有没有集中在某个核上。第一次采集就发现了三个典型问题。一是四个核心负载看起来差不多但单个推理进程的CPU使用率在不断跳变说明它每隔几十毫秒就被调度器迁移一次二是所有网络中断都落在0号核上而推理主线程也在0号核导致中断经常抢占推理三是空闲核并没有真正空闲而是陷入自旋锁忙等cpuload看起来低功耗一样不低。这些问题不看数据是发现不了的。我后来习惯把采集脚本写到后台跑十分钟再分析不要只看一两分钟因为端侧推理负载会有周期性波动。比如摄像头输入、帧率控制、定时同步都会让任务呈现突发性短时间采样很容易漏掉关键相位。分析cpuload时还要结合单核时域曲线而不仅仅是平均值。两个核各自50%负载和同一个核满负载另一核空闲功耗和延迟表现完全不同这一点对推理任务尤其重要。2.2 从绑核、优先级到智能核心调度绑核是最简单也最有效的手段。taskset -c 2 ./infer把推理进程绑定到2号核可以立刻减少进程迁移。迁移听起来只是换个CPU跑实际上是一个连锁反应新核的cache是冷的需要重新从DDR拉数据旧核留下的cache变成无效状态总线流量增加功耗也随之上升。对实时推理来说绑核还能减少调度延迟因为调度器不用再考虑把任务放到哪个核上。如果板子支持实时调度策略还可以配合优先级使用。chrt -f 50 ./infer把推理主线程切成SCHED_FIFO实时优先级让它在唤醒后立刻抢占CPU不被普通线程干扰。这里要小心实时优先级不是越大越好优先级过高会影响中断线程、网络协议栈处理严重时系统输入输出会卡死。我一般把推理线程设为实时优先级40到60之间其他辅助线程用普通优先级。真正智能的核心调度要解决的是异构核心和集群调度。RISC-V平台也有不少SoC采用大小核设计小核能效高、大核吞吐高所以前后处理线程可以绑到小核推理主线程绑到大核。内核如果支持能耗模型调度器它会自动做这种选择但很多RISC-V平台的调度器还比较粗糙更多时候需要手动管理。我常用的方式是用cpuset cgroup把进程限制到指定核上例如把实时推理进程放进只允许大核的cpuset后台服务放到小核的cpuset避免两类任务互相抢核。集群调度也是容易被忽略的维度。同簇的核心共享L2缓存任务跨簇访问时延迟更高、总线竞争更激烈。调度器会尽量让任务留在同一个簇但如果内核不知道簇拓扑就得靠cpuset固定进程能使用的簇范围。我在一个多簇平台上做过对比同样一个检测线程允许它跨簇调度时每帧平均延迟多出十几毫秒绑在单簇后延迟方差明显下降功耗也低了几百毫瓦。2.3 实时性监控与调度延迟排查做实时系统的朋友应该用过QNX Momentics里面能看到时序调度和系统延时的准确分布线程在哪个时刻被哪个对象唤醒、等了多久都一目了然。Linux端也有类似思路用perf sched和ftrace可以取得接近的效果。我通常执行perf sched record -- sleep 10然后再用perf sched latency看结果重点看wakeup latency和scheduling delay这两个指标。有一次我发现推理线程平均唤醒延迟只有几十微秒但最大延迟超过3毫秒查了一圈是内核里的kworker每两毫秒跑一次优先级还比较高把推理线程挤掉了。解决办法是把推理线程改成SCHED_FIFO优先级同时把kworker绑到别的核。还有一个容易忽略的点RISC-V的PLIC中断控制器默认会把外部中断集中投递到一个CPU上导致该CPU被频繁打断。这时需要把网卡、存储等中断的smp_affinity手动改到另一个空闲核例如echo 4 /proc/irq/45/smp_affinity这样推理主线程的时序才稳定下来。排查调度延迟时要有耐心不要只看平均值。平均值再低如果尾部延迟很高推理任务一样会偶发抖动表现为画面卡顿或超时。我建议用直方图方式观察延迟分布只要尾部有超过目标帧间隔的点就要继续优化。3. 量化优化用更少的比特跑同样的推理3.1 为什么端侧推理离不开 INT8 量化RISC-V核的算力增长很快但在端侧推理里真正的瓶颈往往不是MAC阵列而是数据搬运。以卷积层为例每产生一个输出点需要读取输入特征图的窗口和对应的权重值。FP32下一个较大的卷积核参数一次性读进来就是几千字节换成INT8后同样数量参数只占四分之一字节DDR访问量随之降低。对嵌入式板子来说DDR访问能耗远高于计算能耗所以量化的收益不只是速度更是功耗。这里还要说到位宽对计算效率的影响。如果处理器带向量扩展比如RVV一次能装载的数据元素数是按位宽算的INT8能同时处理的元素数量是FP32的四倍。很多RISC-V核跑向量化INT8算子的性价比非常高这也是为什么我只要精度能接受优先上INT8而不是继续在FP32上优化。量化研究方法里那张能量表大家应该也看过访存的成本比整数加法高一两个数量级放到端侧嵌入式里更夸张所以减少内存流量往往比减少计算周期还重要。3.2 从 YOLOv5 到 ONNX 再到 INT8 的实操路径我以YOLOv5s为例说一条能落地的路线。第一步导出ONNXpython export.py --weights yolov5s.pt --include onnx开启simplify。ONNX是中间格式各种量化工具链都能识别。第二步准备校准集。这一步最容易被忽略。校准集不是越多越好而是要贴合真实推理时遇到的输入分布。我从训练集里随机抽300张图做和训练一致的预处理然后用工具链做训练后量化。如果你手头是RK3568这类带NPU的板子可以看到很多yolov5量化rk3568的教程流程基本都是加载ONNX模型、指定量化数据类型、传入校准数据、转换导出。RISC-V平台如果没有NPU可以直接用ONNX Runtime的静态量化接口或者按芯片厂商的PTQ工具来走核心是把每个算子的输入输出范围统计出来然后映射到INT8。量化后必须回到板子上做精度和性能验证不要只看PC模拟结果。我踩过一个典型的坑量化版CLIP模型最后输出的特征维度和原模型对不上一个是5120一个是4096。一开始以为是量化搞坏了后来定位发现是导出时用了一个不同版本的投影层权重结构错位了。量化只会忠实反映模型本身如果模型定义与权重不匹配它不会帮你纠正。所以做任何量化模型之前先用脚本比对各层输入的shape特别是维度、通道数和token长度不要拿起来就量化。3.3 量化后的精度修复与混合量化量化之后最常见的现象是精度下降尤其当你用SiLU或GELU这类非饱和激活函数时输出分布不是简单的均匀分布量化误差会被放大。如果INT8全量化掉点超过一个点我会考虑混合量化把敏感层保持FP16或FP32其他层继续用INT8。很多工具链支持在量化配置里指定算子白名单或黑名单花点时间做敏感层分析是值得的。如果混合量化还不够就要上量化感知训练QAT在训练阶段模拟量化的舍入误差让模型权重去适应这种噪声。QAT成本高但如果目标是长期稳定量产该做还得做。这里放一组我在某RISC-V四核板上的量化前后对照数据硬件不同数值仅供思路参考方案权重格式mAP0.5推理耗时平均功耗原始FP32FP320.582112ms3.2W全INT8 PTQINT80.57473ms2.4W混合量化关键层FP160.58078ms2.5W量化不能和调度割裂它的直接效果是计算量降低、DDR带宽降低这让CPU可以在更低的频率下完成同样的帧率也为调度腾出余量。我在做优化时量化不是最后一步而是和调度同步调整否则又把频率拉上去量化的收益会被抵消掉一部分。4. 空闲态优化让无事可做的时刻真正省电4.1 RISC-V 的 WFI 与 cpuidle 机制RISC-V有WFI指令全称Wait-for-InterruptCPU执行后暂停流水线直到外部中断把核唤醒。操作系统在idle线程里反复执行WFI就是让CPU进入cpuidle状态。问题在于内核要决定CPU睡多深睡深了唤醒慢睡浅了省电少。Linux的cpuidle框架通过governor根据最近的idle间隔预测来选state。我在RISC-V板子上会先确认CPU到底有没有进入idle。看/sys/devices/system/cpu/cpuidle/state*/name有些state名是C1、C2如果只有state0说明firmware或设备树没配置好低功耗状态。还需要检查设备树里idle-states节点的latency-us和min-residency-us参数。这些参数要按SoC手册填填太大会让governor不敢睡深填太小又容易睡进去后立刻被唤醒反而耗电。WiFi数据上我建议把governor从menu换成teo试一下两种算法的预测逻辑不同在某些工作负载下teo对长时间空闲的预测更准。如果使用的是openSBI固件还要确认SBI的电源管理扩展是否完整因为进入更深睡眠最终要通过固件来完成固件不配合内核怎么配都白搭。4.2 动态时钟与唤醒风暴内核如果一直维持周期性tick中断空闲CPU也会每隔一两毫秒被叫醒一次根本睡不踏实。开启CONFIG_NO_HZ_IDLE后空闲CPU会关闭tick这是标准的低功耗配置。更进一步CONFIG_NO_HZ_FULL可以让运行实时任务的CPU也停止tick把时间精度交给应用自己维护适合计算密集型推理任务。唤醒风暴是我最想强调的现象。四个核都在执行周期任务虽然单核负载不高但时钟中断把四个核同时叫醒瞬间电流很高而且它们都没有进入深睡眠。cpuload看着只有百分之十几功耗却和跑满差不多。排查方法很简单看/proc/interrupts里本地时钟中断那一项是否跳得飞快再用trace-cmd把hrtimer和tick事件拉出来看。解决思路是减少不必要的定时器、使用动态tick、把实时任务放到isolcpus隔离核上以及让调度器尽量把负载聚拢到少数核心上。只有整个簇的所有核都空闲下来电源控制器才能进一步关闭CPU簇电源这是空闲态优化和集群调度的真正联动。我在实测中发现只把三个核从负载中释放出来待机功耗就能降低不少比任何模型优化都见效快。5. 几个容易踩的坑和我的避坑习惯5.1 link.ld 与内存布局对能效的影响嵌入式的老朋友应该对risc-v link.ld不陌生它决定代码段、数据段、堆栈放在哪些内存区域。绝大多数教程讲的是怎么把启动代码放到固定地址但很少有人把它当能效治理工具。实际上同一个数据放在DDR和内部SRAM访问功耗差别很大。SRAM在片上不需要DDR刷新带宽低访问一次所需的能量少得多。RISC-V SoC通常有ITCM或紧耦SRAM如果能塞得下模型权重就尽量把热数据放进去。具体做法是在链接脚本里划分一块保留SRAM然后把权重段的位置定义到SRAM大概像这样MEMORY { DDR (rwx) : ORIGIN 0x80000000, LENGTH 512M SRAM (rwx) : ORIGIN 0x10100000, LENGTH 2M } SECTIONS { .rodata : { *(.rodata.model_weight) } SRAM }但要注意改完链接脚本后要确认MMU页表有没有覆盖到SRAM地址否则运行时会触发异常。另一个坑是改了段顺序后向量表被覆盖系统启动不起来。解决办法是用readelf -S和nm检查符号地址确认入口点没有被挤掉。模型量化成INT8后体积小很多放进SRAM的可能性更大所以量化和内存布局是天然搭档。5.2 量化与调度协同的实测复盘我在某颗四核RISC-V板子上做过一组完整对比把数据列出来给各位一个直观参考优化状态推理耗时平均功耗系统温度mAP0.5初始状态125ms3.5W78℃0.582只降频192ms2.4W66℃0.582只做调度和空闲态118ms3.0W72℃0.582调度INT8空闲态72ms1.8W55℃0.574降频换来功耗下降但延迟明显劣化调度和空闲态微调后功耗降得有限加上INT8后数据搬运量下来CPU能在更低频率完成任务空闲态也有机会进入整体效果不是简单叠加而是乘法关系。这也是我强调“主动治理”的原因被动降频只是把系统按下去主动治理则是让系统在更低功耗下自然稳定运行。我个人做能效治理最深的感受是数据闭环比任何技巧都重要。每次改动只动一个变量同时记录cpuload、功耗、帧率、延迟和温度跑二十分钟再对比。一开始我也喜欢堆技巧今天绑核明天量化结果功耗降了精度不行精度恢复又慢了反复横跳。后来把习惯改成先采集数据再做决策效率高了很多。RISC-V生态虽然比ARM稍微折腾一点但好处是系统软件的可控性高你能看到调度器怎么选核、idle怎么选态甚至能改固件。把这些能力用好端侧推理的能效拐点会比你想的来得快希望这篇分享能帮你少走一点弯路。
返回列表