专栏算法工具链【地平线 J6工具链入门教程】板端部署UCP使用指南

【地平线 J6工具链入门教程】板端部署UCP使用指南

芯链情报局2025-10-26
268
0

1. 前言

在模型板端部署过程中,开发者主要关心图像如何获取,模型性能如何评测以及如何优化模型等问题。对于图像的获取,地平线提供了Pyramid硬件,其不但可以获取多尺寸图像,且利用内存共享机制可将内存给到BPU直接进行推理。针对耗时,内存占用,DDR带宽占用等指标进行评测和优化,地平线提供了诸如Trace,hrt_ucp_monitor等一系列性能分析工具用于性能监测,使得开发者能够清晰掌握模型运行时的资源占用和硬件效率。最后,地平线提供VP,HPL以及DSP多种模块用于前后处理环节的算法开发。本文将结合实例说明模型如何进行部署,性能分析以及常见的问题解析。

2. UCP简介

J6 工具链在应用部署端新引入了统一计算平台(Unify Compute Platform,以下简称 UCP)。UCP 面向应用层,属于嵌入式应用开发(runtime)范畴,提供视觉处理(Vision Process,以下简称 VP)、模型推理(Neural Network,以下简称 NN)、高性能计算库(High Performance Library,以下简称 HPL)等功能。
UCP还定义了一套统一的异构编程接口,支持对SoC上各后端硬件资源的调用,包括BPU、DSP、ISP、GDC、STITCH、JPU、VPU、PYRAMID 等,以完成 SoC 上任务的统一调度。
UCP 的架构图如下所示:
Description

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 快速上手

以下面的代码为例,说明 DNN 和 UCP 接口的使用方式,整体包含 5 个主要步骤,详细信息可参考用户手册《统一计算平台-模型推理开发》,《模型部署实践指导-模型部署实践指导实例》,《UCP通用API介绍》等相关章节:
Description

⚠️上面的例子仅为demo,实际使用时,需要注意以下几点:

  1. 图像可以直接从Pyramid接口直接获取nv12的输出,无需进行拷贝,可直接传递给BPU进行推理

  2. 输入输出内存的大小和对齐stride,详见第5.3节说明

  3. 接口进行返回值检查,以保证函数的正确执行

3.2 实用技巧

3.2.1 添加desc

有的时候,为了方便自动化作业,需要给不同的模型,输入和输出打上标签以区分他们。

需要注意的是,如果是为输入添加描述信息,由于pyramid和resizer节点会改变bc的输入节点数,因此需要给对应每个节点都添加对应的信息。

比较推荐的做法是在compile之前再添加:

模型部署时,通过下面的接口来获取描述信息:

3.2.2 模型打包
模型打包功能,可以将多个模型打包进一个hbm文件中,对于共享任务可以节省模型的空间,具体api介绍可见《HBDK Tool API Reference》:
在生成hbm文件后,上板运行使用hrt_model_exec查看模型可以看到:
Description
推理测试时,用model_file指定hbm路径,model_name指定具体哪一个模型
Description
3.2.3 小模型批处理

由于BPU为资源独占型硬件,对于那些耗时较短的小模型,其框架调度耗时开销可能大于其模型运行时间,为了缓解这个问题。在J6平台,UCP支持通过复用task_handle方式来一次将多个模型下发,全部执行完成后再一次性返回,从而将N次开销合并为一次:

3.2.4 优先级抢占
在J6计算平台上,BPU硬件本身没有抢占功能,对于一个计算任务其一旦进入BPU后,就无法被打断,其他计算任务只能等待当前计算任务完成退出后才能运行。
此时很容易出现BPU计算资源被一个大模型任务独占,进而影响其他高优先级模型任务的执行,针对这个问题,工具链采用cpu调度的机制来优化BPU资源:
  1. hbm模型在BPU推理表现为一个或多个function-call,function-call为BPU最小的执行单元。当一个模型的所有function-call都执行完成时,这个模型也就执行完成了

  2. 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:
Description
Description
让这两个模型同时运行,且设置max_time_per_fc=2000,两个模型的优先级均为普通优先级时UCP trace耗时如下:
Description
当将模型2的优先级设为high,模型1仍为普通优先级时:
Description
可以看到,在下面的模型一次infer过程中,模型被切分为多个2ms运行的function-call运行,中间插入了很多high优先级模型,导致一次模型前向耗时大大增加。
3.2.5 LRU内存优化
LRU(Least Recently Used)算法是用于优化内存页的调度算法。BPU内存在BPU实际使用前,NN模块内部需要对该块内存进行特殊处理才能够正常使用,如果频繁对模型及其依赖申请释放会导致CPU负载变大,从而可能会引发性能问题。
如果确实有频繁申请释放的需求,推理库提供了内存LRU缓存功能,通过设置环境变量HB_NN_ENABLE_MEM_LRU_CACHE为 true来使用。设置方式如下:

开启了这个功能之后,对模型的输入输出不是实时申请和释放的,会在一开始就申请好并进行循环复用。所以如果用户在模型跑完推理后就立刻执行内存释放操作,实际不会立刻释放,UCP 这一层会等一段时间后才执行(默认至少1s),所以可能会有内存泄漏的风险,建议是模型推理的内存块不要释放,且模型每次输入输出的虚拟地址是复用的。

3.3 输入输出处理

3.3.1 Crop裁剪
Crop主要思想是利用地址偏移,并通过stride将图像多余的部分进行屏蔽从而送入准备好的模型输入。这种Crop方式不引入memory copy,减少IO开销。
限制:
  1. 图像输入大小要大于模型实际输入大小,w_stride要32(E/M)/64(P/H)字节对齐

  2. 模型的validShape为固定值,stride为动态值

  3. 裁剪偏移的输入首地址要32对齐

详细示例可以参考《基础示例包使用说明》中advanced_samples 的crop示例
3.3.2 Resizer
Resizer主要是指具有nv12图像输入和ROI输入的模型,编译器支持通过JIT动态指令的方式,从nv12图像上完成抠图+Resize功能。其不仅仅是图像stride为动态,输入的H,W也为动态,w_stride也同样需要满足32(E/M)/64(P/H)字节对齐,roi不需要进行对齐:
Description
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图像,其格式为:
Description
Description

其中,-1为占位符,表示为动态,Pyramid输入的stride为动态。那么此时我们就需要通过手动计算方式来获取了:

对于非nv12类型的其他输入,以rgb输入input作为例子,1x224x224x3的rgb图像如下所示:

输入申请的大小可以通过aligned byte size来获取:

3.3.4 内存单元对齐
BPU中的内存单元也是遵循向量化对齐的原则,类似于avx/neon等,需要内存对齐。所以对于不满足对齐最小字节的内存要被强制对齐到最小的内存字节上。
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的结果如下图所示:

Description
Description

4. 性能分析

4.1 模型性能分析

如果开发者没有实体板子,只有hbm模型,可以使用hbdk4中的hbm_perf接口获取静态性能评估文件(html,json格式)以及模型耗时:

模型中如果有CPU算子,则会影响perf的结果,建议去除CPU算子之后再进行分析。CPU算子一般可以通过以下两种方式查看到:

  1. convert之后的模型可视化,然后查询是否有hbtl类型算子

  2. 利用statistics接口统计bc模型算子类型

如果有与开发环境直连的板子可以使用下面的方式进行测试,与实测偏差会更小:

或按照用户手册《统一计算平台-模型推理工具介绍》使用hrt_model_exec工具在板端进行性能测试:
4.1.1 带宽占用
静态评测时,带宽信息可以从模型编译过程中生成的xxx.html/xxx.json中文件获取,在ptq中会自动生成这两个文件,在qat中,可以通过生成hbm模型后,使用hbm_perf接口来生成这两个文件。
平均带宽
平均带宽(GB/s) = DDR bytes per second( for n FPS)/n * 设计帧率/2^30,以下面的模型为例,实际需求帧率为30FPS,那么该模型所需的平均带宽为:12293553099/57.12 * 30/2^30 = 6.01GB/s:
Description
Description
峰值带宽
峰值带宽可以通过推理带宽柱状图来进行分析,最高的柱子即最大的load/strore带宽。比如下面这个图,该模型的最大load需求为15515MB/s=15.15GB/s,最大的store需求为13125MB/s=12.82GB/s,最大的load+store需求为11954+11812=23766MB/s=23.21GB/s
Description
4.1.2 带宽优化
在实际应用中,模型的推理耗时可能出现比正常评测要更长的现象,主要原因往往来源于BPU的等待耗时以及带宽资源不足的影响。这里主要针对带宽问题进行说明。
BPU模型的带宽消耗主要集中在模型加载、推理时的featuremap读写,输出写回,优化策略如下:
  1. 使用balance参数来平衡带宽和延时

ptq时,修改配置文件中的compile_mode:

  1. 对于小模型使用多batch推理模式,可以减少weight的加载次数

  2. 减少模型抢占调用:优先级255的抢占任务会刷新整个SRAM,导致大量带宽开销,建议通过任务编排方式运行模型,而不是优先级抢占

  3. Batch拆分:若模型需要concat多路输入(比如BEV类模型),将batch mode拆分,每一路单独提取特征,牺牲很少的延时来降低峰值带宽

4.1.3 内存占用
模型所需的内存可以通过Summary查看到:

含义

Model input memory

模型输入内存

Model output memory

模型输出内存

Shared temporary memory

运行时内存

Intermediate memory

运行时内存

Dynamic memory

前面的相加

Static memory

hbm文件大小

Shared temporary memory共享临时内存,主要目的是用于相同优先级模型共享内存,优化模型推理内存的使用。对于相同优先级的模型,会共享temporary memory。该功能的约束条件:

  1. 跨BPU Core不可用

  2. 跨优先级不可用,0-253的优先级之间的都可以共享,254只能和其他254共享,255只能与其他255共享

  3. 跨进程不可用

当开发人员对模型运行时所需内存进行评测时,可先通过Summary的内容先进行静态数据评估,模型的内存占用=Static Memory + Dynamic Memory。

4.2 动态性能分析

在模型的部署和运行过程中,我们比较关注模型的推理耗时,bpu/cpu占用,DDR读写带宽以及内存占用。这些信息可以通过以下工具来获取:

4.2.1 hrt_model_exec
hrt_model_exec是一个模型执行工具,可直接用于在开发板上评测模型的推理性能,获取模型信息。工具源码路径在samples/ucp_tutorial/tools/hrt_model_exec。
模型输入输出信息:
Description
模型单线程耗时:
Description
模型多线程耗时:
Description
指定优先级运行:
更多的hrt_model_exec命令可以在《统一计算平台-模型推理工具介绍-hrt_model_exec》中查看。
4.2.1.1 单线程和多线程差异
在单线程下,工具按照单核单线程的串行逻辑运行,统计的性能可以理解为单帧处理的平均时间(包括调度开销,BPU执行时间以及CPU执行时间)。
在多线程下,工具会启动多个线程进行模型推理,统计得到的FPS表示充分使用资源情况下模型的吞吐量,主要用于评测高并发情况下的模型处理能力。
  • 为什么单线程模型运行耗时比多线程耗时短?
    答:由于BPU本身是一种独占硬件,同一时间只能运行一个任务,多个线程同时提交任务时,只能按一定顺序执行,因此多线程模式下,模型的Latency耗时的增大,主要来源于任务下发后的等待时间。
4.2.2 hrt_ucp_monitor
工具hrt_ucp_monitor是一个关于监控硬件IP占用率和内存信息的工具。hrt_ucp_monitor工具位于samples/ucp_tutorial/tools中。
hrt_ucp_moitor支持的内存信息包括DDR读写带宽,ION内存,进程内存,默认为每秒采样 500 次,详细的运行参数请参考《统一计算平台-UCP性能分析工具》。在终端运行命令hrt_ucp_monitor即可看到对应的监控信息:

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

Description
Description
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来测试内存占用:
Description
上图为未加载时,carveout的状态
Description
模型加载后,carveout的状态
4.2.3 hrut_ddr

带宽占用主要使用hrut_ddr来进行分析:

根据hrut_ddr工具的log,获取BPU带宽占用和系统带宽占用,Read+Write的值即为总带宽:
Description

4.3 问题

在实际的运行中,可能会出现与上面带宽评测结果差距较大的情况。这是由于在实际中不仅仅是模型的运行需要带宽,cam和cpu也是需要带宽的。根据过往的经验,可以根据峰值带宽和均值带宽来提前判断是否存在风险,高于理论带宽的75%以上,就需要进行测试验证了。

5. 推理典型问题处理

5.1 timeout问题

5.1.1 模型timeout时间是否设置合理

如果模型是异步推理的,模型本身执行的时间较长,而异步等待接口设置的超时时间不足也可能造成timeout。

timeout的耗时可以设置为模型正常推理时间的一倍即可。

5.1.2 CPU负载是否过高
由于模型的运行调度是由CPU来处理的,如果调度线程一直获取不到时间片,即使任务完成也无法及时同步到用户接口,导致推理延时。
在运行过程中,可以使用top/htop等监视CPU利用率,如果CPU负载超过90%,可能出现系统异常,这个必须得到解决
5.1.3 内存泄漏

当存在内存泄漏时,在系统内存不足的情况下,内存申请缓慢,可能会导致推理超时。可以在编译时添加检测:

或在单元测试时,利用getpid()获取当前进程的pid,再查看/proc/pid/status中的VmRSS。

5.2 推理hang

模型指令原因导致的底层运行错误,错误没有上报,导致hang住。此时,可通过cat/sys/devices/system/bpu/bpu0/task_running对bpu任务情况进行查看,如下图所示:
Description
s_time不为空表示任务已经正常开始,而p_time一直增加没有减少,即可认为BPU任务hang住了, 可以使用watch命令来记录bpu任务情况:

如果发生此类问题,可以提供bpu log给地平线技术支持人员分析,log的地址在:/log/bpux/message 中。

5.3 log获取

在遇到上面的问题的时候,我们可以通过分析日志来获取问题原因,需要的是UCP日志以及系统日志:

5.3.1 UCP日志
在程序运行时可以看到各种log的等级:
Description
在发生上面的问题后,为了获取具体的问题原因,可以修改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日志存储路径

更详细的环境变量和说明可以参考《统一计算平台-UCP通用API介绍-环境变量
5.3.2 系统日志
系统日志获取:
dmesg:在Linux系统中用于显示或控制内核环形缓冲区的内容更,允许查看或操作内核消息。

logcat:可以用于打印设备的系统日志

6. UCP Trace使用

J6算法工具链提供了一套板端实测性能工具UCP Trace,通过在UCP执行的关键路径上嵌入trace记录,进而深入分析UCP应用调度逻辑,具体可以参考《统一计算平台-UCP性能分析工具》一节。
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 运行实例
in_process模式下只能抓取UCP进程内的trace,无需启动prefetto的后台进程
启动步骤:
  • ucp_in_process.json

  • ucp_in_process.cfg

在该目录下会生成trace文件:文件名为output_path中配置的文件名:

Description
1.Perfetto不支持自动覆盖,如果设置路径中有之前的ptrace文件会报错
Description
2.ucp_in_process.json中指定的文件路径是相对路径,需要配置文件和脚本放在同一个路径下
6.1.2 结果解析
生成的ucp.pftrace就是我们要分析的文件,使用Perfetto UI打开:

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

Description
可以看到等待部分耗时大约为80.xms,也可以看到线程和进程的信息(Wait部分)
Description
  • 单线程+多帧

Description
  • 多线程+多帧

Description
如何分析:
  1. 查看UCP内部调度是否正常例如哪块耗时明显高于预期

  2. 观察BPU是否持续在使用:例如两个BPU Opfinish之间的耗时是否符合预期,继而判断任务编排是否合理,任务下发是否及时

  • 多线程+多帧+CPU结果

Description

6.2 system模式

在system模式下,UCP trace只是其中一个数据源,因此需要运行Perfetto的后台进程来完成trace捕获。

  1. 运行Perfetto后台进程

请注意,为了能够获取完整的数据,需要确保hrt_model_exec执行结束前,perfetto进程未退出。可以适当增加ucp_system.cfg中的duration_ms,当前默认为10000ms

  1. 开启一个新终端,设置环境变量和运行程序

  1. 运行程序,比如运行hrt_model_exec命令,并将获取到的ucp.pftrace解析:

Description

7. 视觉处理/高性能算子

UCP提供了视觉处理和高性能算子两大方向的多种接口:

  1. 视觉处理主要针对视频编/解码,光流,AVM拼接等常规视觉算法

  2. 高性能算子依赖于DSP的实现,主要用于fft和ifft的加速

    更多信息可以参考用户手册《统一计算平台》的相关章节。

8. DSP使用

J6的dsp使用了Cadence的Tensilica Vision Q8 DSP IP(J6B为 Vision 130)。支持int8/int16/int32/float32/double的浮点计算。
当前DSP可以用于加速模型前后处理比如点云体素化,模型量化反量化等操作,模型中间的算子加速暂不支持。更详细的说明,请参照《DSP算子开发》章节。
完整的算子开发分为三个步骤:
  1. DSP算子开发

  1. 注册算子,编译镜像

  1. 通过UCP API调用,申请计算资源并执行任务

算法工具链
社区征文征程6官方教程
评论0
0/1000