RISC-V 端侧推理的能效治理实践笔记调度、量化与空闲态优化嵌入式AI圈子这两年有个很明显的风向变化大家不再只盯着端侧推理的“能不能跑”而是开始认真算“跑一次要多少毫焦耳”。尤其在RISC-V平台上算力底座本来就不如旗舰ARM那么充沛又能耗极其敏感所以同样是部署一个模型能耗账算得细不细直接决定了产品是“能落地”还是“只能停留在Demo阶段”。我最近在某个四核RISC-V SoC上做端侧推理优化核心任务就是把一个视觉检测模型的端侧推理功耗压下来同时保证帧率和延时达标。这个项目踩了很多坑也拿到了一组比较满意的数据。我把整体思路和实操细节整理成这篇笔记重点围绕三个方向调度、量化、空闲态优化。文章比较长但每一节都是实测过的东西希望对正在做RISC-V端侧推理或者嵌入式AI部署的朋友有参考价值。1. 项目整体设计与思路拆解从被动节流到主动能效治理1.1 被动节流的局限在哪里先说背景。项目最初的目标很简单粗暴模型推理在RISC-V核上太慢、太热所以第一版优化方案就是“降频省电”——CPU频率调低电压跟着降芯片温度下来了功耗看起来也小了然后呢帧率一落千丈任务在实时性上根本不达标。这就是典型的“被动节流”思路等发现问题温度过高、功耗超标、电量掉太快之后才去做限制性操作。DVFS降频、任务限速、强制睡眠这些都属于被动手段。它们的问题不在于没有效果而在于反应太慢、粒度太粗——降频是把所有任务一刀切不管当前这个计算是不是关键路径强制睡眠不看外设状态醒来之后发现数据没准备好白白浪费了唤醒延迟。另一个被动节流的典型坑是只盯着“计算时的功耗”完全忽略了“不计算时的功耗”。实际用功耗仪抓曲线的时候会发现模型推理完成之后CPU还在高频空转、DSP和NPU外设仍然上电整板功耗几乎没有掉下来。计算只占整个工作周期的三分之一剩余时间都在空转漏电。这就是典型的能效治理没做到位。1.2 主动能效治理的整体框架后面我们换了个思路不把功耗当“事后惩罚”而是把它当成和性能平级的调度目标在任务开始之前就做规划。整个框架可以总结成“感知—决策—执行—反馈”四个环节感知收集当前负载特征比如推理任务的帧间隔、模型在哪几个核上跑、外设工作状态、电池电量或供电余量。决策根据负载特征预测未来一段时间通常是下一个帧周期的毫秒级窗口需要的算力然后决定CPU频率档位、任务绑核策略、量化精度的切换时机、空闲时进入多深的睡眠态。执行通过Linux内核的cpufreq、cpuidle、调度器接口把这些决策落地成实际的频率、任务迁移、WFI指令、外设上下电动作。反馈通过能耗计数值或功耗采样对比实际功耗和预期值动态修正决策参数。这套框架里调度、量化、空闲态优化正好对应了“决策”和“执行”环节中最核心的三个抓手调度决定算力用在哪量化决定算力效率多高空闲态决定不需要时算力有没有真正被关掉。三者是整个能效治理链路里不可拆分的整体。1.3 为什么选择这三个优化方向先说结论这三个方向都是“投入产出比”极高的切入点而且彼此之间有强耦合。单独只看一个方向很容易遇到瓶颈。调度优化的是“时间维度的能效”把任务放到合适的核上让CPU不要无意义地高频空转。很多RISC-V多核SoC大小核之间不只是频率差异还有缓存和指令集差异。调度做得好同样的模型推理大核和小核的能效比能差出好几倍。量化优化的是“空间维度的能效”用更少的bit表达同样的信息带宽、内存、算力都成倍降低。尤其对RISC-V平台来说没有NV或者CUDA那样夸张的并行单元int8相比fp32的收益是全方位的。空闲态优化优化的是“时间维度的漏电”计算任务之间的间隙CPU能不能真正低功耗待机。这部分收益在大多数文档里都被低估了。实际做完之后看数据三者叠加带来的收益不是简单的相加而是相乘——量化降低了单帧计算量调度让计算尽量密集地跑在高能效核上空闲态把计算间隙的漏电压到最低。这比只做其中任何一个方向都要有效得多。2. 调度层优化让每次计算都花在刀刃上2.1 RISC-V多核调度模型的现状和难点首先要承认一个现状Linux内核主线的调度器CFS在设计时是以通用性能为目标的并没有对端侧推理做特殊优化。它在RISC-V上跑起来没问题但如果你想让它主动做能效管理就得在调度策略上做动作。我们用的这颗SoC是4核RISC-V实测下来有两个特点和调度关系特别大一是核间差异。其中两个核频率高但功耗高适合跑突发性的重要计算另外两个核频率低但能效比好适合跑后台任务和网络协议栈。如果让Linux默认调度器自由负载均衡推理任务经常被调度到低频核上帧率立刻崩掉。反而是强制把推理任务绑定在高频核上把系统服务、日志、协议栈这类任务丢到低频核整体的功耗和帧率都能达到一个更好的均衡点。二是中断和调度延迟。RISC-V平台不像有些ARM SoC那样有成熟的电源管理固件和调度硬件辅助中断affinity、唤醒延迟、以及CPUidle和调度器之间的联动很多时候需要自己在内核配置和用户态代码里手动控制。2.2 实操任务分类、核间绑定与优先级策略我们的做法分三步走第一步任务分类。把系统里所有活跃的任务按照“实时性要求”“计算密度”“是否可以延迟”三个维度分类。比如推理任务实时性高、计算密度高、不可延迟属于性能敏感型图像采集/前处理任务和采样硬件强相关、周期性明确属于周期约束型日志/网络/OTA后台任务可延迟、计算密度低属于后台型。第二步核间绑定。把性能敏感型任务绑定到高频核后台型任务绑定到低频核周期约束型任务则根据当前帧率动态选择核组。这一步用的是sched_setaffinity在应用层做其实就够了不用改内核。第三步优先级和节流策略。用chrt给周期约束型任务设置合理的实时优先级同时给后台任务加CPU亲和性和带宽限制cgroup cpu.max防止它们打扰推理任务的Cache。这里有一点很关键任务绑核之后Cache局部性也变好了。推理模型如果在同一个核上反复执行模型权重和激活值在L1/L2里是有命中率的一旦任务在不同核间迁移Cache全部重载瞬时功耗和延时都会明显提高。所以绑核不只是为了频率更是为了Cache亲和性。2.3 调度与DVFS的联动配置调度做好了频率调节也得跟上。我们用的是schedutil这个governor——它是Linux默认的频控策略能根据调度器的负载变化快速调频。但schedutil不是拿来就能用的有几个参数需要根据RISC-V平台的特性调整schedutil的上调阈值up_threshold调高一些避免轻微负载波动就触发升频RISC-V核的调频响应不像ARM那么平滑频繁升频降频反而会增加延时还有可能引发震荡下调延迟down_delay调大一些确保负载下降后不要立刻降频给推理任务的下一帧留出余量避免在帧间间隙频繁跳频最低频率不要压得太低实测发现低频档位的能效不一定好因为执行同样计算量的时间变长了反而可能更费电。还要注意一个很容易踩的坑cpufreq调频和cpuidle睡眠之间的冲突。如果空闲策略允许CPU进入深度睡眠唤醒延迟可能高达数毫秒而调频需要CPU处于活跃状态才能快速响应。两者配置不当就会出现“任务到了CPU还没醒”的尴尬局面。后面第四节会细说这个问题。2.4 实测调度的收益调度优化做完后我们测了一组对比数据同样的模型、同样的输入分辨率、同样的量化精度配置平均帧延时整机推理功耗CPU利用率波动默认调度 无绑核98ms2.4W±20%绑核 手动优先级61ms2.0W±8%绑核 schedutil调参58ms1.8W±5%帧延时降了40%功耗降了25%。这个收益几乎是“白捡”的因为没有改变任何计算逻辑只是让算力更合理地分布在合适的位置。3. 量化模块实战INT8精度层次的落地细节3.1 量化方案选型PTQ还是QAT调度解决的是“算力怎么分配”量化解决的是“算力本身的效率”。这部分的起点是模型在RISC-V端侧的INT8量化落地。为什么先说INT8而不是更激进的INT4或者FP16原因很现实RISC-V平台的向量扩展RVV对8bit整型的支持最成熟工具链、Kernel库、算子优化的覆盖度都最好。INT4的模型压缩率更高但很多RISC-V核上根本没有对应的向量指令优化跑起来反而更慢。FP16则需要硬件支持半精度指令在中低端RISC-V核上往往被拆成多条指令事倍功半。量化方案上我们评估了PTQ训练后量化和QAT量化感知训练两条路PTQ比如onnx转INT8时的动态/静态量化速度快、不需要重新训练但精度损失明显尤其对检测模型的小目标、边界框回归这类对数值敏感的层来说很容易出问题。QAT需要重新微调模型训练成本高但可以针对RISC-V平台的实际推理数值范围做适配精度损失可以压到很小。项目实际情况是模型是拿现成的预训练权重做迁移没有完整的训练数据管线所以QAT的可行性很低。最后我们选了混合精度方案——对大部分卷积层用PTQ量化为INT8对几个敏感层比如检测头、最后的分类层、以及一些数值范围特别宽的add层保持FP16或FP32。这个思路既保住了推理速度又把精度损失控制在可接受范围内。3.2 算子层面的量化验收标准量化做得好不好不能只看整体精度指标还要看每个算子的精度退化是否符合预期。我们的验收流程是这样的先用一组固定的校准图片大概500张覆盖面要广做PTQ量化校准统计每层的激活值分布对每一层在量化前后跑同一批数据记录激活值的最小/最大/均值/标准差差异标出所有差异超过阈值的算子人工判断这些算子是否在模型的关键路径上对关键路径上的敏感算子逐个尝试混合精度保留方案。这个环节特别容易被忽略。很多团队的量化验收只看最终mAP或者准确率一旦整体精度勉强达到要求就忽略了中间层数值分布已经严重畸变的事实。这种模型短期跑没问题但换了一批输入数据、或者换了推理平台数值误差会迅速放大问题暴露出来的时候已经很难查了。实操中我们遇到过两类典型敏感算子一类是BatchNorm层在量化前没有做折叠fold导致激活范围的校准完全不准确另一类是残差相加层Add两个分支的数值范围差别很大量化后在相加时出现截断误差。BatchNorm折叠这个点值得多说几句。很多RISC-V端侧推理框架要求模型在导出时已经完成BatchNorm融合但不少训练脚本导出的模型里BatchNorm还明晃晃地挂在卷积后面。如果不做折叠直接量化激活值的范围统计出来是虚高的int8的有效bit就被浪费掉了。我在第一次量化的时候就是没注意这个导致精度指标比预期跌了4%排查了半天才发现是BatchNorm没折叠。3.3 量化和调度的联动动态切换精度档量化还有一层更深的应用把量化精度当成一个可以动态切换的档位配合调度决策使用。这个思路是这样的端侧推理通常不是一个恒定的负载而是周期性变化比如视频流每帧一次检测。在负载高、温度高、电量低的时候我们可以把模型切换到更激进的量化档比如部分层从INT8切到INT4虽然精度略降但计算量更小、功耗更低在负载低、电量充足的时候切到高精度档保证识别效果。实现上现在的推理框架给我们留了口子把同一模型导出成多个不同精度的副本运行时按需加载。切换时机由前述的“感知决策”模块统一控制——当调度器检测到连续几帧的帧间隔都在缩小、CPU频率已经顶到上限时就触发降精度档当帧间隔越来越大、CPU正在降频时就恢复高精度档。这套机制做了之后我们在一段长时间运行的测试里对比纯INT8和动态精度两档方案的总体能效方案平均帧延时平均整机功耗精度指标mAP0.5固定INT845ms1.9W0.82动态INT8/INT441ms1.4W0.80精度只掉了0.02功耗却降了26%。对于端侧场景来说这个交换非常划得来。3.4 量化与RISC-V向量指令的配合最后说一个容易被忽略的工程细节内存布局要和向量指令的位宽对齐。RISC-V的RVV向量扩展在访问INT8数据时如果内存地址没有对齐到向量位宽的整数倍会额外产生非对齐访问惩罚性能直接掉一截。我们实测过同样的INT8卷积算子在数据按RV_VLEN/8字节对齐和不对齐两种情况下算子耗时能差出2~4倍。所以量化模型在导出时feature map的宽度、通道数、以及tensor stride都要手动检查对齐情况。这个点在网上很少被提到但实际对RISC-V端侧推理性能影响非常大。具体的对齐做法是在模型导出时对padding和stride做约束让每行tensor的起始地址至少满足64字节对齐对应常见的RVV VLEN512bit场景然后在推理框架的内存池分配时给每个tensor多预留一些对齐填充位。注意torch导出时packed layout和普通NCHW/NHWC的进出顺序也要一起考虑否则对齐做好了算子内部访存顺序变了Cache miss反而变多得不偿失。4. 空闲态优化把“闲着”的时间变成节能4.1 空闲态层级与功耗/唤醒延迟的平衡如果说调度和量化是在“做事的时候”省电那空闲态优化就是在“不做事的时候”省电。这一块我们在项目里的收益非常直观把模型推理的空闲间隙利用好之后整板功耗在待机场景下直接降了一个数量级。RISC-V核常见的空闲态可以粗略分成几档WFI等待中断CPU暂停执行等待中断唤醒唤醒延迟很短微秒级功耗降低有限浅睡眠light sleep关闭大部分时钟树保留L1缓存内容唤醒延迟在几十微秒左右深睡眠deep sleep关闭核心电源域丢失Cache内容唤醒延迟达到毫秒级但漏电降到极低。Linux内核的cpuidle框架就是围绕这几档空闲态做选择的。默认的menugovernor会根据CPU当前的idle时间预测来选择进入多深的睡眠。但对端侧推理场景来说默认策略有个很大的问题它不会考虑下一帧任务的到达时间。以视频检测为例每帧到达的间隔是固定的33ms30fps。推理在10ms内完成剩下23ms都是空闲。如果在这23ms里CPU进入深睡眠唤醒延迟5ms那么下一帧到达时任务根本没法及时响应帧率就会掉。但如果只在idle的23ms里进入浅睡眠省电效果又有限。所以空闲态的选择必须由应用层把下一帧的时间点告诉内核而不是靠内核去猜。实操方法在推理线程完成一帧推理后将自己SCHED_FIFO挂起并设置一个高精度定时器timerfd或clock_nanosleep指向下一帧的预期到达点内核cpuidle在感知到这一情况后会倾向于选择“恰好能在下一次定时器到期前唤醒”的最深空闲态。这个策略在支持LPI低功耗空闲的RISC-V平台上效果尤其明显。4.2 空闲态和DMA、外设的协同坑这部分是我踩坑最多的地方专门写出来。第一个坑DMA传输期间CPU不能进入深睡眠。我们的图像采集用的是DMA直接把传感器数据搬到内存DMA传输期间CPU其实不需要参与计算看起来非常适合睡眠省电。但实测发现DMA虽然不占用CPU但它的描述符读取和中断上报依赖某些始终开启的时钟域。CPU一旦进入深睡眠DMA传输节奏也会受牵连轻则传输延时增加重则数据覆盖。最终解法是把DMA和CPU睡眠做握手DMA开始传输前先把CPU提升到浅睡眠挡位传输完成中断到来后才允许CPU按正常idle策略进入深睡眠。第二个坑唤醒中断的affinity要绑在正确的核上。如果唤醒中断默认路由到低频核而推理任务绑定在高频核上那么高频核不会被唤醒任务被迫在低频核上重启性能断崖式下跌。这个问题的排查极其隐蔽因为从日志看CPU是正常“睡眠—唤醒”的任务也没有报错但帧率就是上不去最后还是通过/proc/interrupts看出中断落到了错误的核上。第三个坑日志打印和调试串口会破坏空闲态。开着串口终端跑功耗测试系统会周期性被调试输出唤醒功耗数据根本测不准。这个看似废话实际工作里经常有人拿着带调试串口的数据就汇报了。我后面做功耗采集时都是拔掉串口用功耗仪直接看整板电流。4.3 tickless idle别让定时器打扰睡眠Linux内核的tickless idle无时钟空闲特性对空闲态优化至关重要。默认情况下内核的周期tick会周期性唤醒CPU每次唤醒都是有功耗的。开启CONFIG_NO_HZ_IDLE之后CPU进入idle时tick会被挂起直到有定时器事件或中断才醒过来。但RISC-V平台上这个特性经常因为驱动里的频繁timer而被破坏。比如某个驱动每10ms注册一个定时器轮询状态这个定时器会强制CPU频繁醒来处理tick导致深睡眠根本进不去。排查方法是看/proc/timer_list里各个timer的到期频率把所有非关键驱动的轮询定时器改成事件触发式或者至少放宽到秒级。4.4 空闲态优化的实测数据空闲态配置调好之后我们在一段“推理10ms、空闲20ms”的模拟负载下测功耗配置平均整机功耗含外设唤醒延迟影响不禁idle 默认menu governor1.6W无浅睡眠L1 retention0.9W无感知深睡眠 应用层定时唤醒0.45W帧率下降约2%深睡眠 DMA握手后0.5W无从1.6W到0.5W直接省掉近70%。这个数据对其他项目可能不完全适用但足以说明空闲态在端侧能效治理里回报往往是最大的只是做起来比调频、量化更隐蔽、更需要系统级配合。5. 常见问题与排查技巧实录5.1 问题速查表下面按“现象—原因—解法”整理我在这个项目里遇到的最典型问题看到类似症状可以直接照表排查现象可能原因排查手段解法帧率波动大绑核后反而更卡中断没有随任务绑定到同一核查看/proc/interrupts用perf sched看调度情况用irq_set_affinity_hint或设备树interrupts-extended配置中断亲和性推理延时偶尔出现几十毫秒尖峰空闲态过深、唤醒延迟过大打开/sys/kernel/debug/pm_genpd/pm_genpd_summary观察各域状态限制最深idle等级或用应用层定时器提前唤醒CPUINT8量化后精度明显崩掉BatchNorm未折叠导出模型检查图中是否有独立BN节点在训练/导出阶段做BN折叠再走PTQ流程同一算子不同核上性能差异大对齐方式与RVV VLEN不匹配检查tensor的内存对齐和stride导出的模型统一按64字节对齐重排CPU频率震荡、功耗反而升高schedutil上调/下调阈值不合理抓取/sys/kernel/debug/cpufreq的target_index切换记录调高上调阈值、调长下调延迟降低跳频频率整机功耗测不出空闲态收益调试串口频繁唤醒内核拔掉调试串口/关闭console输出用功耗仪直接采集电流确保日志彻底关闭5.2 排障的几个通用技巧实战里有一个特别好用的原则叫单一变量法。做能效优化的系统级问题排查时最容易犯的错误是一次改了很多东西绑核、量化、睡眠策略、外设配置然后看结果——好或者坏根本不知道是哪个变量起的作用。我的操作方式是每次只动一个变量验证一次功耗和性能变化如果一次要动多个变量必须用脚本把所有配置版本记录下来并支持快速回滚。实测下来这个方法能减少至少一半的排障时间。另外一个技巧是建立能效基线。在项目一开始跑一个最朴素的版本默认内核、默认调度、FP32模型、不禁idle记录下整板功耗和帧延时作为基线。之后每做一步优化都在这个基线上做对比。没有基线任何功耗数据都缺乏参照系你根本不知道自己的优化是真是假。5.3 针对RISC-V平台的特别提示最后说几句针对RISC-V平台的独特经验。RISC-V的工具链生态还在快速成熟期跟ARM平台相比很多优化手段没有现成的“一键方案”。比如ARM的DSU动态共享单元自带比较成熟的电源管理协同逻辑但很多RISC-V SoC的电源域划分、睡眠等级都要开发者自己通过设备树和内核驱动去配置。所以做RISC-V端侧优化一个重要的前置工作就是把SoC的电源管理手册吃透——哪些外设挂在哪个电源域、每个电源域的唤醒源是什么、睡眠/唤醒的时序要求是什么这些信息在芯片手册里都有但很少有人认真整理成自己的工程文档。另一个经验是充分利用开源工具链。RISC-V的GCC工具链、Linux主线的RISC-V支持已经非常可用很多优化点其实不需要自己从零造轮子。比如RVV指令的高效利用可以借助开源的算子库如NCNN、TFLite Micro针对RISC-V的优化分支站在别人的肩膀上比自己重新调指令快得多。6. 数据测试方法与结果分析6.1 测试环境与测量方法这个项目的测试环境如下SoC四核RISC-V支持RVV 0.7.1向量扩展、支持DVFS和多级空闲态内存LPDDR4800MHz模型轻量级目标检测模型输入分辨率640x640约2.1M参数推理框架基于开源端侧推理框架自定义算子适配RISC-V向量指令操作系统Linux 5.10开启CONFIG_NO_HZ_IDLE和EAS相关配置测量工具高精度功耗仪采样率1kHz直接在电源输入端测整板电流电压。这里有一个测量方法上的关键点功耗数据必须在稳定负载下采集至少60秒取平稳段的平均值。不能开机就跑也不能只测10秒——RISC-V核的动态调频和温度反馈回路的响应时间较长短窗口测出来的数据不仅波动大而且没有统计意义。6.2 三种优化叠加的最终数据完整优化做完后我们记录了从“初始版本”到“最终版本”全过程的累计数据版本平均帧延时整机平均功耗单帧能效mJ/帧精度mAP0.5初始版本FP32、默认调度、不禁idle105ms3.2W336mJ0.84 调度优化绑核、schedutil调参63ms2.3W145mJ0.84 INT8量化含混合精度42ms1.8W75.6mJ0.82 空闲态优化深睡眠DMA握手41ms0.8W32.8mJ0.82从初始版本到最终版本单帧能效提升了10倍以上。这个数据让我更坚信一个判断端侧推理的功耗问题不能靠单一手段解决必须把调度、量化、空闲态放到一个系统里统筹考虑。6.3 后续可以扩展的方向这套优化做完其实还有几个方向可以继续推进我把它们记在项目收尾笔记里模型本身的轻量化剪枝量化解决的是“表达的bit数”剪枝解决的是“计算的冗余度”。两者结合理论上能再压掉30%-50%的计算量。NPU/DSP卸载RISC-V CPU核上去不动的算子可以评估是否卸载到平台自带的NPU或DSP上。这会引入新的数据拷贝和任务同步开销需要重新做一轮能耗评估。自适应量化策略的强化目前的动态精度切换还是基于简单的帧间隔判断后续可以尝试用历史功耗数据做简单的预测模型让精度切换更平滑。这个项目做完后我个人最大的体会是能效治理不是一个优化动作而是一套系统方法论。它需要把模型、运行时、内核、硬件手册、功耗测量工具全部打通。任何一个环节的信息缺失都会让你对数据产生误判最终做出错误的优化决策。如果你也在做RISC-V端侧推理优化我的建议是第一步先做功耗基线测量和任务画像搞清楚算力到底用在了哪里、时间到底花在了哪里第二步再针对调度和空闲态做系统级适配第三步才是量化等模型层面的优化。顺序反了很多功夫会白费。希望这篇笔记能帮你少踩几个坑。