1 前言
在板端部署的应用程序开发中,经常会遇到多个模型同时运行的场景,此时每个模型都需要使用有限的计算资源完成推理,因而不可避免地会出现计算资源的争夺情况。地平线的板端预测库(libDNN)具有高效的调度策略,通常情况下,无需开发者手动干预即可对多模型推理的场景做出合理的调度,以充分利用硬件资源。但有时,我们会希望某些模型的推理任务具有更高的执行优先级,因此为了更加准确地控制多模型的执行情况,地平线提供了优先级调度策略供开发者使用。
本教程首先会梳理优先级调度涉及的关键概念,再介绍板端预测库层面的调度策略,以及更底层的系统软件的抢占功能,最后总结实现优先级调度的具体配置方法。
2 关键概念
2.1 模型
2.2 task
提交推理任务的接口有两个,分别是hbDNNInfer()和hbDNNRoiInfer(),二者根据输入参数执行推理任务,其中后者专用于执行ROI推理任务。hbDNNWaitTaskDone()接口用于等待任务完成或超时。hbDNNReleaseTask()接口用于释放任务。1个task的生命周期从hbDNNInfer()或hbDNNRoiInfer()开始,到hbDNNReleaseTask()结束。
2.3 function-call / FC
function-call是BPU的执行粒度,缩写为FC。runtime模型(bin/hbm)在BPU上执行推理计算时,表现为1个或多个FC的调用,当一个模型所有的FC都执行完成, 那么这个模型也就推理完成了。
2.4 FC最大连续执行时间
这是一个编译参数。在PTQ中,参数名为max_time_per_fc,QAT中,参数名为max-time-per-fc。
该参数用于指定模型每个FC的最大可连续执行时间,取值范围是0和1000-4294967295(2^32-1),单位微秒(μs)。默认值为0,代表无限制。
无论使用PTQ还是QAT流程编译runtime模型,都可以配置该参数。
对于PTQ,max_time_per_fc在yaml文件中的编译参数组(compiler_parameters)中配置。
对于QAT,在使用horizon_plugin_pytorch.quantization.compile_model接口编译模型时,可在额外参数(extra_args)中配置max-time-per-fc,例如extra_args=['--max-time-per-fc',str(1000)]。
如果不配置该参数或者配置为0,则模型的BPU子图只有1段FC,若配置max_time_per_fc=1000,则BPU子图会被分割为多段FC,每段FC的执行时间为1000微秒,最后一个除外,如下图所示。

2.5 优先级
在提交推理任务前,需要先配置推理任务的控制参数,相关参数在结构体hbDNNInferCtrlParam中定义。
其中,priority和customId均能表示task的优先级,priority的优先级高于customId。
此外,bpuCoreId为0代表BPU任意核,1代表BPU核0,2代表BPU核1。考虑优先级调度策略时,需要配置成1或2,如果配置为0则无法控制模型运行在哪个BPU核上。
3 调度策略总览

优先级调度策略存在于板端预测库和系统软件两个模块,如上图所示。板端预测库是地平线针对嵌入式应用开发提供的推理库,用于将runtime模型部署到开发板运行。系统软件比板端预测库更靠近系统底层,会提供BPU驱动,并解析模型指令,模型的BPU部分最终以FC的形式执行。
总体来说,当task被提交后,会进入板端预测库的队列并排序,模型的BPU部分提交到系统软件FC队列前会再做排序,最终以FC的形式执行,完成整个优先级调度过程。
4 板端预测库调度策略
4.1 基础规则
由于在实际的部署场景中,我们希望模型有极致的推理速度,所以通常会将首尾的CPU算子从模型中去除,并将相关计算转移到前后处理中进行,同时会将模型优化成一个完整的BPU子图,因此本小节会针对这种情况进行说明。
入队
当部署代码执行到hbDNNInfer或hbDNNRoiInfer时,task会被提交到板端预测库的队列中,队列会对其中的task做排序。位于队列中的task,均为尚未处理,正在等待被执行的task。每当有新的task进入队列时,都会按照各个task的优先级进行一次排序。排序按照推理控制参数(hbDNNInferCtrlParam)的priority和customId进行。首先比较priority,数值越大优先级越高,在priority相同时比较customId,数值越小优先级越高。
出队
优先级高的task,会优先出队并执行。
假设我们有priority较高的taskA和priority较低的taskB,各绑定了modelA和modelB。原本在队列中只有taskB等待处理,此时taskA被提交入队,由于taskA的优先级高于taskB,因此taskA会优先于taskB出队,且modelA会优先于modelB被处理。
在taskA出队后,modelA的BPU部分会转变为FC,下发到系统软件的FC队列。之后taskB出队,modelB的BPU部分转变为FC,下发到系统软件的FC队列。在系统软件层面,会对这两组FC做进一步的调度。
4.2 多进程调度
4.1节只介绍了单进程内部的调度方式,此处补充介绍多进程调度。J5计算平台支持以Service Mode方式运行程序,优化了多进程场景下的调度策略。通常情况下,也就是Service Mode关闭时,板端预测库只能对单进程内的任务做调度,无法实现跨进程的任务调度。在当前进程正在执行任务的情况下,如果别的进程出现了高优先级的任务,由于这个信息无法跨进程传递,那么高优先级的任务可能无法及时运行。在开启Service Mode后,不同进程的信息会维护在共享内存中,从而实现多进程之间任务的统一调度,能让多个进程中的高优任务优先执行。
注:XJ3计算平台不支持Service Mode。
4.3 模型首尾有CPU算子
如果模型的输入端有CPU算子,则CPU算子会在task出队的时候,由linux系统做调度和计算,CPU计算不会下发到系统软件,当输入端的CPU算子执行结束后,BPU部分才会以FC的形式下发到系统软件。
需要注意一种情况:如果taskA先出队,taskB后出队,但是taskA绑定的modelA在输入端有CPU算子,taskB绑定的modelB是一个纯BPU模型,那么在modelA的CPU算子计算期间,modelB会先转变为FC并率先进入系统软件队列。
对于模型输出端的CPU算子,只有模型BPU部分的全部FC在系统软件中执行完毕后,才会得到执行,同样由linux系统做调度和计算。
4.4 模型中间有CPU算子
假设taskA绑定的modelA结构为BPU-CPU-BPU,taskB绑定的modelB为纯BPU模型,taskA先出队,taskB后出队,首先执行modelA,modelB排队等待。
HB_DNN_BPU_SCHEDULE_THRESHOLD
该环境变量默认值10,单位为百分比(%),表示modelB插入运行后,对modelA的下一个BPU段运行的影响程度,影响程度小于设定值则允许插入运行。该值通过除法计算得出,公式为:
modelA第二段BPU被插队导致延后运行的那部分时间 / modelA第二段BPU运行的总时间 * 100%
此公式也可以改写成:
(modelB的BPU运行时间 - modelA的CPU运行时间)/ modelA第二段BPU运行的总时间 * 100%
举例来说,假设modelA第二段BPU运行时间为100ms,如果modelB中途插入,导致modelA第二段BPU相比原本延后了5ms才开始运行,那么此时受影响程度便为5%,如果该环境变量配置为10,则表示允许的影响程度为10%,5%小于10%,所以在modalA运行到CPU段时,会允许modelB插入运行。
HB_DNN_SCHEDULE_INSERT_FC_MAX_TIME
该环境变量默认值1000,单位毫秒,如果modelB的运行时间小于该值,则允许插入运行。
HB_DNN_SCHEDULE_WAIT_DISPATCH_TIME
该环境变量默认值20,单位毫秒,如果modelA的CPU段运行时间超过该值,则会允许modelB插入运行。
注:模型的BPU段和CPU段运行时间会被板端预测库记录,因此随着推理次数的增加,对耗时的预估会更加准确。
5 系统软件抢占策略
在系统软件层面,不存在模型的概念,模型的BPU部分已经转变为FC,系统软件只会对FC做调度。系统软件接收到的FC均由板端预测库下发。
5.1 high与normal队列
如果没有接收到priority=255的FC,则high队列保持为空,所有priority
对于具有抢占优先级的FC,抢占执行的时机与系统软件的环境变量BPLAT_CORELIMIT有关。
5.2 抢占功能环境变量

上图中normal队列蓝色的三段非抢占优先级FC来自同一个BPU子图,normal队列左侧的FC先出队执行,如果BPLAT_CORELIMIT=1,则抢占优先级FC会在第一个非抢占优先级FC执行结束后立刻抢占执行,如果BPLAT_CORELIMIT=2,则会在第一个或者第二个非抢占优先级FC执行结束后再抢占执行。
此外,如果BPLAT_CORELIMIT=0,则不发生FC抢占,FC执行顺序为系统软件先接收到的先执行。
通常来说,启用系统软件层面的FC抢占功能,配置export BPLAT_CORELIMIT=1即可。
此外,还需要注意以下两点:
priority=255的两个task,模型的FC之间无法实现抢占,即使customId不同也不行。
如果模型拆分出了大于两段的FC,那么这个模型从开始推理到结束,可以被多次抢占。
6 配置方法总结
6.1 模型编译
在编译阶段,低优模型需要配置FC最大连续执行时间,这样才能在系统软件队列中被高优模型的FC抢占。
对于PTQ流程,可在yaml中按如下方式配置:
对于QAT流程,可在调用compile_model接口时按如下方式配置:
6.2 代码编写
在编写部署代码时,需要配置模型推理控制参数的bpuCoreId、priority和customId(可选)。
bpuCoreId为0代表BPU任意核,1代表BPU核0,2代表BPU核1。需要再次强调,在考虑优先级调度策略时,bpuCoreId需要配置为1或2,如果配置成0则无法控制模型运行在哪个BPU核上。
priority必须配置,customId选配,customId只在priority相同时起作用。
6.3 板端部署
执行推理程序前,请在当前进程中运行以下命令启动系统软件抢占功能的环境变量:
若您对优先级调度有困惑,或者希望在J5计算平台开启Service Mode,请联系地平线技术支持。

