1. 前言
在模型板端部署过程中,开发者主要关心图像如何获取,模型性能如何评测以及如何优化模型等问题。对于图像的获取,地平线提供了Pyramid硬件,其不但可以获取多尺寸图像,且利用内存共享机制可将内存给到BPU直接进行推理。针对耗时,内存占用,DDR带宽占用等指标进行评测和优化,地平线提供了诸如Trace,hrt_ucp_monitor等一系列性能分析工具用于性能监测,使得开发者能够清晰掌握模型运行时的资源占用和硬件效率。最后,地平线提供VP,HPL以及DSP多种模块用于前后处理环节的算法开发。本文将结合实例说明模型如何进行部署,性能分析以及常见的问题解析。
2. UCP简介
UCP还定义了一套统一的异构编程接口,支持对SoC上各后端硬件资源的调用,包括BPU、DSP、ISP、GDC、STITCH、JPU、VPU、PYRAMID 等,以完成 SoC 上任务的统一调度。
UCP 的架构图如下所示:

backend | 描述 |
|---|---|
BPU | Brain Process Unit,地平线神经网络计算单元。 |
DSP | Digital Signal Processor,数字信号处理器,是一个可编程的硬件单元。 |
GDC | Geometric Distortion Correction,几何畸变校正模块,可对输入图像进行视角变换、畸变矫正、图像仿射变换等操作。 |
STITCH | stitch,图像拼接模块,可对输入的图像进行裁剪,拼接,拼接模式分别有:alpha融合、alpha beta融合、直接拷贝。 J6B 上没有此器件。 |
JPU | JPEG Processing Unit,主要用以完成JPEG的编解码功能。 |
VPU | Video Processing Unit,是一种专用的视觉处理单元。 |
PYRAMID | Image Pyramid,图像金字塔,可对整幅原始图像进行缩小。 |
ISP | Image Signal Processor,图像信号处理模块,可以将富含图像原始信息的RAW格式转换为易于传输处理的YUV格式。 |
3. 模型推理
3.1 快速上手

⚠️上面的例子仅为demo,实际使用时,需要注意以下几点:
图像可以直接从Pyramid接口直接获取nv12的输出,无需进行拷贝,可直接传递给BPU进行推理
输入输出内存的大小和对齐stride,详见第5.3节说明
接口进行返回值检查,以保证函数的正确执行
3.2 实用技巧
3.2.1 添加desc
有的时候,为了方便自动化作业,需要给不同的模型,输入和输出打上标签以区分他们。
需要注意的是,如果是为输入添加描述信息,由于pyramid和resizer节点会改变bc的输入节点数,因此需要给对应每个节点都添加对应的信息。
比较推荐的做法是在compile之前再添加:
模型部署时,通过下面的接口来获取描述信息:
3.2.2 模型打包

推理测试时,用model_file指定hbm路径,model_name指定具体哪一个模型

3.2.3 小模型批处理
由于BPU为资源独占型硬件,对于那些耗时较短的小模型,其框架调度耗时开销可能大于其模型运行时间,为了缓解这个问题。在J6平台,UCP支持通过复用task_handle方式来一次将多个模型下发,全部执行完成后再一次性返回,从而将N次开销合并为一次:
3.2.4 优先级抢占
此时很容易出现BPU计算资源被一个大模型任务独占,进而影响其他高优先级模型任务的执行,针对这个问题,工具链采用cpu调度的机制来优化BPU资源:
hbm模型在BPU推理表现为一个或多个function-call,function-call为BPU最小的执行单元。当一个模型的所有function-call都执行完成时,这个模型也就执行完成了
BPU模型任务抢占粒度设计为function-all,如果一个模型只有一个function-call那么其无法被抢占,如果一个模型有多个function-call可能出现这个模型完成部分function-call后,BPU挂起当前模型,然后切换执行其他模型
UCP支持任务优先级调度和抢占,可通过hbUCPSchedParam结构体进行配置:
priority:任务优先级,支持[0, 255]之间的数值,对于模型任务而言:
[0, 253]普通优先级,不可抢占其他任务,但在未执行时支持按优先级进行排队
254:为high优先级,支持抢占普通任务
255:为urgent优先级,支持抢占普通任务和high任务
可被中断抢占的任务,需要在模型编译阶段配置max_time_per_fc进行模型拆分
customId:自定义优先级
backend:任务硬件id
- deviceId:设备ID
比如,有下面的两个模型,一个单线程耗时20.9 ms,一个单线程耗时8.3ms:
![]() | ![]() |
|---|

当将模型2的优先级设为high,模型1仍为普通优先级时:

可以看到,在下面的模型一次infer过程中,模型被切分为多个2ms运行的function-call运行,中间插入了很多high优先级模型,导致一次模型前向耗时大大增加。
3.2.5 LRU内存优化
如果确实有频繁申请释放的需求,推理库提供了内存LRU缓存功能,通过设置环境变量HB_NN_ENABLE_MEM_LRU_CACHE为 true来使用。设置方式如下:
开启了这个功能之后,对模型的输入输出不是实时申请和释放的,会在一开始就申请好并进行循环复用。所以如果用户在模型跑完推理后就立刻执行内存释放操作,实际不会立刻释放,UCP 这一层会等一段时间后才执行(默认至少1s),所以可能会有内存泄漏的风险,建议是模型推理的内存块不要释放,且模型每次输入输出的虚拟地址是复用的。
3.3 输入输出处理
3.3.1 Crop裁剪
限制:
图像输入大小要大于模型实际输入大小,w_stride要32(E/M)/64(P/H)字节对齐
模型的validShape为固定值,stride为动态值
裁剪偏移的输入首地址要32对齐
3.3.2 Resizer


3.3.3 图像tensor对齐
在J6芯片,有一块叫Pyramid的金字塔硬件处理模块,可提供Camera输入图像的缩放及ROI抠图能力,其输出为nv12类型的图像数据,并可基于共享内存机制直接给到BPU进行模型推理,因此在J6工具链中:
Pyramid模型是指具有nv12图像输入的模型
- Resizer模型指的是具有nv12图像输入和ROI输入的模型,编译器支持通过JIT动态指令的方式,从nv12图像上完成ROI抠图+Resize功能
J6P/H要求nv12 stride满足64对齐,J6E/M/B是32对齐。
Pyramid的输入stride为动态,比如模型输入为224x224的nv12图像,其格式为:
![]() | ![]() |
|---|
其中,-1为占位符,表示为动态,Pyramid输入的stride为动态。那么此时我们就需要通过手动计算方式来获取了:

输入申请的大小可以通过aligned byte size来获取:
3.3.4 内存单元对齐
J6H/P tensor最小申请内存是256字节,J6E/M是64字节,J6B是128字节,这个差异会体现在模型的aligned byte size和stride属性上。
举个例子:

上面模型的stride=4000,output需要申请的内存为4000Byte,但由于内存需要对齐,所以实际上的需要申请的内存大小为((4000+(256-1))&~(256-1))=4096Byte。
在模型实际部署中,非图像输入/输出节点所需申请的内存大小均可以从模型节点属性的结构体中读取到,因此无需特别关注:
3.3.5 padding
由于内存单元对齐的影响,feature申请的大小和拷贝需要根据stride和alignedByteSize来进行。用户侧需要手动处理这些padding,可能对前处理和后处理的代码有较大的变动。这里地平线提供了一种优化方案:input_no_padding/ouput_no_padding,在开启这两个选项后,可以直接将输入/出实际大小的内存送入接口,接口内部会自行处理对齐,无需用户侧修改代码。但开启这个参数后,可能会对模型延时产生微小影响。
- input_no_padding:对所有非图像的输入去padding
- output_no_padding:对模型所有的输出去padding
若编译时配置了input_no_padding=True,output_no_padding=True,无需关注非图像的对齐问题:
举个例子,比如一个模型的输出shape为1x21x21x255,其output_no_padding=False和output_no_padding=True的结果如下图所示:
![]() | ![]() |
|---|
4. 性能分析
4.1 模型性能分析
如果开发者没有实体板子,只有hbm模型,可以使用hbdk4中的hbm_perf接口获取静态性能评估文件(html,json格式)以及模型耗时:
模型中如果有CPU算子,则会影响perf的结果,建议去除CPU算子之后再进行分析。CPU算子一般可以通过以下两种方式查看到:
convert之后的模型可视化,然后查询是否有hbtl类型算子
利用statistics接口统计bc模型算子类型
如果有与开发环境直连的板子可以使用下面的方式进行测试,与实测偏差会更小:
4.1.1 带宽占用
平均带宽
平均带宽(GB/s) = DDR bytes per second( for n FPS)/n * 设计帧率/2^30,以下面的模型为例,实际需求帧率为30FPS,那么该模型所需的平均带宽为:12293553099/57.12 * 30/2^30 = 6.01GB/s:
![]() | ![]() |
|---|
峰值带宽可以通过推理带宽柱状图来进行分析,最高的柱子即最大的load/strore带宽。比如下面这个图,该模型的最大load需求为15515MB/s=15.15GB/s,最大的store需求为13125MB/s=12.82GB/s,最大的load+store需求为11954+11812=23766MB/s=23.21GB/s

4.1.2 带宽优化
BPU模型的带宽消耗主要集中在模型加载、推理时的featuremap读写,输出写回,优化策略如下:
使用balance参数来平衡带宽和延时
ptq时,修改配置文件中的compile_mode:
对于小模型使用多batch推理模式,可以减少weight的加载次数
减少模型抢占调用:优先级255的抢占任务会刷新整个SRAM,导致大量带宽开销,建议通过任务编排方式运行模型,而不是优先级抢占
Batch拆分:若模型需要concat多路输入(比如BEV类模型),将batch mode拆分,每一路单独提取特征,牺牲很少的延时来降低峰值带宽
4.1.3 内存占用

含义 | |
|---|---|
Model input memory | 模型输入内存 |
Model output memory | 模型输出内存 |
Shared temporary memory | 运行时内存 |
Intermediate memory | 运行时内存 |
Dynamic memory | 前面的相加 |
Static memory | hbm文件大小 |
Shared temporary memory共享临时内存,主要目的是用于相同优先级模型共享内存,优化模型推理内存的使用。对于相同优先级的模型,会共享temporary memory。该功能的约束条件:
跨BPU Core不可用
跨优先级不可用,0-253的优先级之间的都可以共享,254只能和其他254共享,255只能与其他255共享
跨进程不可用
当开发人员对模型运行时所需内存进行评测时,可先通过Summary的内容先进行静态数据评估,模型的内存占用=Static Memory + Dynamic Memory。
4.2 动态性能分析
在模型的部署和运行过程中,我们比较关注模型的推理耗时,bpu/cpu占用,DDR读写带宽以及内存占用。这些信息可以通过以下工具来获取:
4.2.1 hrt_model_exec
模型输入输出信息:

模型单线程耗时:

模型多线程耗时:

指定优先级运行:
4.2.1.1 单线程和多线程差异
在多线程下,工具会启动多个线程进行模型推理,统计得到的FPS表示充分使用资源情况下模型的吞吐量,主要用于评测高并发情况下的模型处理能力。
- 为什么单线程模型运行耗时比多线程耗时短?
答:由于BPU本身是一种独占硬件,同一时间只能运行一个任务,多个线程同时提交任务时,只能按一定顺序执行,因此多线程模式下,模型的Latency耗时的增大,主要来源于任务下发后的等待时间。
4.2.2 hrt_ucp_monitor
hrt_ucp_moitor支持的内存信息包括DDR读写带宽,ION内存,进程内存,默认为每秒采样 500 次,详细的运行参数请参考《统一计算平台-UCP性能分析工具》。在终端运行命令hrt_ucp_monitor即可看到对应的监控信息:

rss查看可以通过以下命令查看:


HBMEM为应用进程申请的总ION大小:
ION:ION是为了解决内存碎片化而引入的通用内存管理器,一共有三种:ion(上面的ion_cam),reserve(上面的cma_reserved)和carveout(上面的carveout)。ion是主要类型,用于一般的内存分配。reserve本质上也是carveout,区分的主要目的是DDR支持多个bank。对于BPU模型来说,其优先在carveout上分配内存。可以通过观察 /sys/kernel/debug/ion/heaps/carveout来测试内存占用:
上图为未加载时,carveout的状态
模型加载后,carveout的状态
4.2.3 hrut_ddr
带宽占用主要使用hrut_ddr来进行分析:

4.3 问题
在实际的运行中,可能会出现与上面带宽评测结果差距较大的情况。这是由于在实际中不仅仅是模型的运行需要带宽,cam和cpu也是需要带宽的。根据过往的经验,可以根据峰值带宽和均值带宽来提前判断是否存在风险,高于理论带宽的75%以上,就需要进行测试验证了。
5. 推理典型问题处理
5.1 timeout问题
5.1.1 模型timeout时间是否设置合理
如果模型是异步推理的,模型本身执行的时间较长,而异步等待接口设置的超时时间不足也可能造成timeout。
timeout的耗时可以设置为模型正常推理时间的一倍即可。
5.1.2 CPU负载是否过高
在运行过程中,可以使用top/htop等监视CPU利用率,如果CPU负载超过90%,可能出现系统异常,这个必须得到解决
5.1.3 内存泄漏
当存在内存泄漏时,在系统内存不足的情况下,内存申请缓慢,可能会导致推理超时。可以在编译时添加检测:
或在单元测试时,利用getpid()获取当前进程的pid,再查看/proc/pid/status中的VmRSS。
5.2 推理hang

s_time不为空表示任务已经正常开始,而p_time一直增加没有减少,即可认为BPU任务hang住了, 可以使用watch命令来记录bpu任务情况:
如果发生此类问题,可以提供bpu log给地平线技术支持人员分析,log的地址在:/log/bpux/message 中。
5.3 log获取
在遇到上面的问题的时候,我们可以通过分析日志来获取问题原因,需要的是UCP日志以及系统日志:
5.3.1 UCP日志

在发生上面的问题后,为了获取具体的问题原因,可以修改log等级来抓取不同等级的日志,配置方式如下:
UCP log设置主要通过以下环境变量:
HB_UCP_LOG_LEVEL:ucp模块log等级(等级从0到6,分别为trace, debug, info, warn, error, critical, never, 默认为warn)
HB_NN_LOG_LEVEL:nn模块log等级
HB_UCP_LOG_PATH: ucp日志存储路径
5.3.2 系统日志
dmesg:在Linux系统中用于显示或控制内核环形缓冲区的内容更,允许查看或操作内核消息。
logcat:可以用于打印设备的系统日志
6. UCP Trace使用
UCP Tracer记录点:UCP记录点包括任务trace记录点和算子trace记录点
trace类型 | 名称 | 说明 |
|---|---|---|
算子 | SubmitOp | 算子提交 |
OpInfer | 算子开始执行 | |
OpFinish | 算子执行结束 | |
任务 | hbDNNInfer | 创建模型推理任务 |
hbDNNRoiInfer | 创建ROI模型推理任务 | |
hbVPxxx | 创建视觉处理任务 | |
hbHPLxxx | 创建高性能计算任务 | |
hbUCPSubmitTask | 任务提交 | |
${TaskType}:Wait | 等待任务执行结束 | |
TaskSetDone | 通知任务执行完成 | |
hbUCPReleaseTask | 任务释放 |
6.1 in_process模式
6.1.1 运行实例
启动步骤:
ucp_in_process.json
ucp_in_process.cfg
在该目录下会生成trace文件:文件名为output_path中配置的文件名:

1.Perfetto不支持自动覆盖,如果设置路径中有之前的ptrace文件会报错
2.ucp_in_process.json中指定的文件路径是相对路径,需要配置文件和脚本放在同一个路径下
6.1.2 结果解析

选择生成的ucp.pftrace文件,选中一个带有forward::Wait字样的一块,如下图所示:


单线程+多帧

多线程+多帧

如何分析:
查看UCP内部调度是否正常例如哪块耗时明显高于预期
观察BPU是否持续在使用:例如两个BPU Opfinish之间的耗时是否符合预期,继而判断任务编排是否合理,任务下发是否及时
多线程+多帧+CPU结果

6.2 system模式
在system模式下,UCP trace只是其中一个数据源,因此需要运行Perfetto的后台进程来完成trace捕获。
运行Perfetto后台进程
请注意,为了能够获取完整的数据,需要确保hrt_model_exec执行结束前,perfetto进程未退出。可以适当增加ucp_system.cfg中的duration_ms,当前默认为10000ms
开启一个新终端,设置环境变量和运行程序
运行程序,比如运行hrt_model_exec命令,并将获取到的ucp.pftrace解析:

7. 视觉处理/高性能算子
UCP提供了视觉处理和高性能算子两大方向的多种接口:
视觉处理主要针对视频编/解码,光流,AVM拼接等常规视觉算法
高性能算子依赖于DSP的实现,主要用于fft和ifft的加速
更多信息可以参考用户手册《统一计算平台》的相关章节。
8. DSP使用
当前DSP可以用于加速模型前后处理比如点云体素化,模型量化反量化等操作,模型中间的算子加速暂不支持。更详细的说明,请参照《DSP算子开发》章节。
完整的算子开发分为三个步骤:
DSP算子开发
注册算子,编译镜像
通过UCP API调用,申请计算资源并执行任务











