专栏算法工具链【地平线 J6工具链进阶教程】J6 模型性能分析及优化

【地平线 J6工具链进阶教程】J6 模型性能分析及优化

芯链情报局2025-12-06
489
0

1.模型性能评价指标

1.1延时

获取模型实测延时latency的方法建议参考用户手册hrt_model_exec工具介绍,本文就不再赘述了。建议的是用latency * 设计帧率去统计每个模型在1s周期内占用的bpu资源,再结合pipeline编排情况去找到需要重点优化的模型。

1.2模型带宽

在perf文件中有一个模型平均带宽的数字,但这个数字与实车实际所需带宽偏差还是比较大的,因为这个带宽需求是基于模型无资源限制,满帧率运行评估出来的。建议按照如下方式计算实车模型平均带宽:

平均带宽(GB/s) = [DDR bytes per second( for n FPS)] / n * 设计帧率 / 2^30

以下面这个模型为例,实车设计帧率为10FPS,则实车时该模型需要的平均带宽为:

68138917200/10.39*10/2^30 = 61.08GB/s

这样更能贴合实车情况汇总得到整个系统所需带宽,并找到真正的主要带宽瓶颈模型。

2.性能分析

2.1工具简介

性能分析主要使用到了静态perf工具。由于很难静态模拟不同器件对片上访存的竞争访问,且访存指令的压缩效果与真实数据的分布强相关,加上J6H/P硬件设计更为复杂,spu,vpu等器件指令运行时长难以预估,所以静态perf的延时与实测的偏差就很难控制在10-20%以内了。为了让用户可以拿到更准确的per layer执行时间用于指导后续模型优化,从OE 3.5.0开始,静态perf工具新增参数remote_ip,只需用户传入一块与开发环境直连(由于各家公司的开发网络管理方式不同,工具难以都支持,因此仅提供了最简单的直连模式)的开发板ip,就可以通过实测回传打点数据以校准per layer耗时。
使用hbm_perf(remote_ip=xx.xx.xx.xx)工具,需要在安装了hbdk4-compiler的基础上,再安装一下hbdk4_runtime_aarch64(docker里默认安装了):

pip3 install OE/package/host/ai_toolchain/hbdk4_runtime_aarch64_unknown_linux_gnu_nash-{version}-py3-none-any.whl

dynamic perf:依据板子实测打点回传校准perf layer的耗时(在没有cpu的情况下,预期可做到与实测偏差不超过1ms)

2.2建议流程

建议按如下流程使用分析工具:

完整流程均集成在了下面这个脚本当中,后文仅简述如何利用各个产物分析模型性能瓶颈:

performance_analysis_1.0.3_for_oe3.7.0.py(下载方式见社区帖子附件 【地平线 J6工具链进阶教程】J6 模型性能分析及优化 - 地平线开发者社区
推荐做性能分析时同时传入qat.bc和perf json file(或者hbm),能获取最全的分析信息。分析结果默认保存在当前目录的performance_analysis.log中。

如下为传入qat.bc和hbm的输出示例:

performance_analysis.log (下载方式见社区帖子附件 【地平线 J6工具链进阶教程】J6 模型性能分析及优化 - 地平线开发者社区 ) 

 

2.3关注模型中是否有cpu算子

cpu算子会打断模型,引入额外量化反量化和ddr开销,因此建议模型性能优化的第一步就是优化掉模型中的cpu算子。脚本1使用过程中会打印convert后算子的信息,查看是否有hbtl.call前缀的算子,这些都是cpu算子:

若使用前文的分析脚本,传入qat.bc或者hbm都会帮助检查是否有cpu算子:

若hbm中检查出的cpu算子名字中包含resizer关键字,则可忽略,resizer输入节点需要生成 jit 指令。

 

 

关于常见cpu算子的优化,请查看后文3.1 cpu算子清零

2.4分析高精度conv的配置情况

若是已经进行了量化调优的模型,且高精度引入了较多延时开销,可以关注一下高精度算子的比例,并尝试尽可能减少对计算量大的conv like算子配置输入int16。

使用分析脚本会打印模型中conv like算子的精度配置情况:

再配合perf json file的解析结果,可了解到模型需要重点减少哪部分算子的高精度配置比例:

有一些shape较大的conv like算子配置高精度后延时会激增,可通过脚本中统计耗时排名靠前的算子找到对应信息,并做针对性优化。

若从精度debug分析结果中判断已无法再减少高精度配置了,请参考后文3.2节优化浮点,使其对量化更友好。

 

2.5分析瓶颈结构

hbm perf最终会生成一个html文件和一个json文件,结合html文件和二次处理后的json文件信息里,我们可以分析得到模型的瓶颈结构:

1.大段空白

当前spu和cpu计算是无法获取source layer信息的,如果已经确定模型没有cpu算子,空白的地方大概率就是topk和sort算子(当前J6P只释放了这两个spu算子),如果是模型尾部的topk计算,建议直接删除用c++做,如下模型,topk挪到后处理代码里实现后,模型延时减少了4ms,cpu占用增加非常少;若是模型中间的topk算子,一方面是想办法缩减topk输入shape,第二方面上尝试减少输出k的数量。

2.卡带宽

观察html里Temporal Statistics以及json文件中解析出来每类算子的计算带宽情况,可知模型在哪段结构/哪些算子上卡带宽了:

从这个图可以看到computing折线较低的地方,load store都较高,很明显是因为卡带宽导致计算受限。

结合统计结果发现卡带宽的是这三个算子:add,pad,cat

从layer details表格里查看load/store cost远大于computing的算子

再查看模型结构可知卡带宽是因为多个vision拼接得到bev特征的操作不够高效,不断使用expand算子将尺寸较小的tensor升维与大尺寸tensor相加/concat。通过调整融合顺序,去掉使用expand算子,模型延时减少了3ms。

 

3.conv类计算利用率不高,或者本身conv类计算就太少

computing折线只统计了TAE器件的计算,因此computing折线下降,可能是因为其他计算器件的运行时间太长了,且无法与TAE并行。

如上图所示,有部分结构computing折线较低的地方,load/store开销也不大,观察时序图可发现是vpu器件运行时间太长,与TAE并行度不高,此时要想办法减少vpu上的计算,挪到其他器件上或者直接去掉。

 

4.其他

从Temporal Statistics和Timeline上已经很难找到明显瓶颈的话,就需要直接可视化quantized.bc依据经验找可优化的结构了:

  1. 看看是否有冗余的类型转换,或者没必要的高精度配置;

    1. a.J6E/M使用新qconfig模板配置,前一个算子的输出和下一个算子输入dtype不一致时会引入rescale算子,存在耗时,对于耗时比较大的rescale可以考虑修改量化配置,统一dtype

    2. 依据算法经验判断某个结构物理含义上是否就不会超过int8,可避免使用int16/fp16

  2. 结合硬件对齐规则,看是否有h/w/c维度特别小,远不满足硬件对齐要求的算子;

算子

参数

对齐规则

Conv
(group conv每组内都有对齐要求)

Kernel 1x1

input channel 64 output channel 64

Kernel 3x3 stride 1

input channel 8 output channel 32

Kernel 3x3 stride 2

input channel 8 output channel 64

Kernel 5x5

input channel 8 output channel 16

warp/gather

-

32C

  1. 从数学原理上考虑等价优化:

    1. 模块输出部分通过slice裁剪feature大小的操作可以考虑前移;

    2. 充分利用elementwise的广播特性,减少前端expand

若以上都没有,就需要从算法角度考虑裁剪结构/模型容量了。

 

3.性能优化

3.1cpu算子清零

由于J6P支持较多浮点计算以及不同计算精度之间的相互切换,所以并非所有fp32的算子都会被回退cpu,除了算子约束中明确不支持的算子外,常见的cpu算子主要是下面三类:

1.Scatternd

详情请见社区文章:

如何去除onnx模型里的scatterND算子

Onnx中ScatterND的产生与去除 - 地平线开发者社区

2.int64 index

模型里会输出int64类型的算子一般有topk和argmax,topk输出手动改成int16避免int64 cast的方法请参考:【地平线J6工具链进阶教程】算子优化方案集锦 - 地平线开发者社区,argmax则直接把输出类型手改一下(argmax_output = argmax_output.to(torch.int16))即可。

此外如果对延时没有比较高的要求,也可以不改源码,导出qat.bc之后,用下面代码把模型里int64的数据替换成int32即可,也可避免因为int64计算引入cpu cast:

3.高精度的resize,warp等硬件不支持的算子

如果发现模型里有不符合预期的 int16/fp16输入的cpu resize和warp,大概率是使用新版Qconfig配置时没有打开enable_optimize。其他算子可结合算子约束文档,以及convert时打开advice参数提示的算子回退cpu原因判断。

 

3.2构建量化友好的浮点,减少高精度配置的需要

在实际生产过程中我们发现有一些模型本身就比较不适合量化,导致模型部署过程中投入了较多资源和时间在解决量化精度问题上,建议可参考文档: 搭建量化友好的浮点模型 了解如何对浮点做适当的调整,以使得该模型对量化更加友好。在J6平台上依然有如下优化建议:
  1. 对输入做关于0对称的归一化。雷达点云以及部分有明确物理含义的输入,通常原始数值都较大,建议这类输入都做一下归一化再送入模型,可一定程度上有效降低量化难度。
  2. 尽可能在每个conv like算子后面加bn,避免数值被放大
  3. 部分relu换relu6,进一步控制数值范围
  4. mask不要使用1e10,65536等过大的数字,一般给100(以能起到区分作用,不影响浮点精度为准),或者30000(能让softmax后的值为1就行),不要超过fp16数值范围,否则导出或者qat的时候会出现问题。

 

3.3提高bpu内各个器件的并行度及利用率

1.卡带宽的常见原因

  • 算子的原因
    • 比如warp算子,数据局部性不好(访问的输入数据可能在内存中不是连续的),不好做Tiling;

    • 大量使用concat,pad,slice等对layout要求较高的算子,容易导致数据量膨胀;

  • 模型结构的问题
    • 模型结构较"宽":需要同时alive的tensor较多,L1M放不下;

    • 模型结构太"长":如shortcut跨层太多,数据无法长时间驻留在L1M

2.优化建议

  • 对于warp算子,如果有多个尺寸相同的并行结构,建议沿着batch合并,因为warp只有沿着batch拆分是等价的,h和w都无法拆分,沿着batch维度合并可提高利用率;此外,在满足32C对齐的情况下,减小feature channel大小也能明显减少warp耗时。

  • 对于concat,pad,slice算子,建议尽可能减少和避免使用,比如利用elementwise广播的特性,减少手动使用expand对齐tensor形状等。

  • vpu/vae瓶颈明显:

    • softmax:sima替换self-attention(论文:https://arxiv.org/pdf/2206.08898
    • layernorm:Dyt替换layernorm(论文:https://arxiv.org/abs/2503.10622
    • 尽量避免使用fp32精度,fp32只能运行在vpu上,vpu算力相较于vae和tae小了好几个数量级,且fp32相较于fp16/int16带宽需求膨胀了一倍

其他优化建议阅读:

【地平线J6工具链进阶教程】算子优化方案集锦 - 地平线开发者社区

 

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