4. 任务划分与调度:算子级划分、模型级划分、静态调度与动态调度策略

好,咱们今天聊点实在的。任务划分与调度,说白了就是决定「谁干什么活,什么时候干」。

我在做异构平台的时候,见过太多团队把精力全花在算子优化上,结果调度策略一塌糊涂,整体性能反而上不去。你想想看,CPU和NPU就像两个性格迥异的搭档——CPU灵活但慢,NPU快但死板。怎么让它们配合好,这就是调度要解决的问题。

4.1 算子级划分:最细粒度的拆解

算子级划分,就是把一个神经网络里的每个算子,比如卷积、池化、全连接,逐个分配到CPU或NPU上执行。

我个人习惯先看算子的计算密度。什么是计算密度?就是计算量除以访存量。NPU擅长高密度计算,比如大卷积核、大矩阵乘法。CPU擅长低密度、控制流复杂的算子,比如Reshape、Concat、条件判断。

核心原则:

  • 计算密集型算子 → NPU
  • 访存密集型/控制流复杂算子 → CPU
  • 小算子(比如1x1卷积但通道数少)→ 实测为准,有时CPU更快

举个例子。我在做MobileNet部署时,发现Depthwise卷积在NPU上跑得并不快。为什么?因为Depthwise卷积的计算密度太低,NPU的并行能力根本发挥不出来。后来我把Depthwise卷积放到CPU上,反而整体延迟降了15%。

我的经验:算子级划分适合模型结构简单、算子种类少的场景。如果模型有上百个算子,手动划分会累死人。这时候就需要模型级划分了。

4.2 模型级划分:粗粒度的切分

模型级划分,就是把整个模型切成几个子图,每个子图完整地跑在CPU或NPU上。子图内部不做跨设备调度。

这样做的好处很明显——减少了CPU和NPU之间的数据搬运次数。你想想看,每次算子切换都要搬运数据,搬运一次可能几毫秒就没了。模型级划分把多个算子打包成一个子图,子图内部的数据留在设备本地,只有子图边界才需要搬运。

我在项目中遇到过这样一个案例:一个BERT模型,如果按算子级划分,CPU和NPU之间要搬运数据200多次。改成模型级划分后,只搬运了6次。延迟从120ms降到了45ms。

注意:模型级划分不是随便切的。切得太碎,数据搬运次数多;切得太粗,可能某个子图里既有适合CPU的算子又有适合NPU的算子,反而浪费性能。

怎么切?我一般用图优化工具先做一遍算子融合,把能合并的算子合并掉,然后再根据计算密度做子图划分。说白了,就是先「瘦身」,再「分块」。

4.3 静态调度:提前规划,按部就班

静态调度,就是在模型运行之前,把所有任务的执行顺序、设备分配都定死。运行时完全按照这个计划执行,不做任何调整。

静态调度的优点是确定性高、开销小。没有运行时决策,调度器本身几乎不消耗资源。适合那些输入固定、计算图固定的场景,比如云端推理服务。

我早期做芯片验证时,静态调度是首选。因为好调试。出问题了,我可以对着调度表一行一行查,看哪个算子没按计划执行。

但静态调度也有硬伤——它假设所有算子的执行时间都是固定的。实际上,NPU的访存延迟、CPU的缓存命中率都会波动。一旦某个算子跑慢了,整个流水线都得等。

静态调度适用场景:

  • 模型结构固定,输入尺寸固定
  • 对延迟抖动要求高(比如自动驾驶)
  • 硬件资源充足,不需要动态争抢

4.4 动态调度:随机应变,实时决策

动态调度正好相反。它在运行时根据当前系统状态——比如CPU负载、NPU队列长度、数据就绪情况——实时决定下一个任务交给谁。

动态调度的核心是「调度策略」。我见过几种常见的策略:

  • 贪心策略:哪个设备空闲,就把任务扔给谁。简单粗暴,但容易导致负载不均。
  • 负载均衡策略:尽量让CPU和NPU的利用率都保持在70%-80%。高了容易排队,低了浪费资源。
  • 优先级策略:关键算子优先执行,比如检测模型里的目标检测头,比前面的特征提取更重要。

我曾经在一个视频分析项目里用过动态调度。输入视频的分辨率不固定,有时是1080p,有时是4K。静态调度根本没法用——4K帧的处理时间是1080p的4倍,调度表完全失效。后来改成动态调度,根据每帧的实际计算量动态分配,效果好了很多。

避坑指南:动态调度不是万能的。调度器本身也要消耗资源。如果调度决策的耗时比算子执行时间还长,那就得不偿失了。我曾经见过一个团队,动态调度器每100微秒做一次决策,结果调度开销占了总延迟的30%。

4.5 静态 vs 动态:怎么选?

说实话,没有绝对的好坏。我个人的做法是:

  • 如果模型固定、输入固定、硬件固定 → 静态调度,简单可靠
  • 如果输入变化大、硬件资源紧张 → 动态调度,灵活适应
  • 如果两者都有 → 混合调度:大部分用静态,关键路径用动态

混合调度是我现在最常用的方式。比如一个模型90%的算子用静态调度,剩下10%的「不确定算子」用动态调度。这样既保证了整体确定性,又留了灵活调整的空间。

4.6 知识体系总览

下面这张图是我自己总结的任务划分与调度知识体系,你可以对照着看:

任务划分与调度知识体系 任务划分 算子级划分 按算子粒度 逐个分配设备 模型级划分 按子图粒度 打包分配设备 调度策略 静态调度 提前规划 按部就班 动态调度 实时决策 随机应变 关键考量因素 计算密度 决定算子归属 高密度→NPU 低密度→CPU 数据搬运开销 跨设备搬运 减少搬运次数 提升整体性能 负载均衡 CPU/NPU利用率 避免空闲或过载 目标70%-80%

这张图把任务划分和调度策略的关系理清楚了。左边是划分,右边是调度,下面是关键考量因素。你设计系统时,可以按这个框架一步步来:先划分,再选调度策略,最后用关键因素去验证。

好了,这一章的内容就这些。任务划分与调度是异构计算的核心,没有好的调度,再强的NPU也白搭。下一章我们会聊更具体的硬件抽象层设计,到时候再细说。


专注资料整理