性能调优指导

无论是通过 PTQ链路的性能评估 还是通过 QAT链路的性能评估, 基于前文的性能分析工作,您可能发现性能结果不及预期,本章节内容介绍了地平线对提升模型性能的建议与措施。

性能分析文件指标解读

性能分析工具

您可以通过我们提供的性能分析工具hb_analyzer工具完成各阶段hbir模型及hbm模型的性能分析,具体使用方式及对应的分析文件解读可参考 hb_analyzer工具 章节的介绍。

开发机端仿真性能评估

仿真性能评估指标解读

除了hb_analyzer工具外,在前文的性能评估完成后,我们会得到模型性能评估预估的html和json文件,html文件的可读性更好,其中:

  1. Summary选项卡提供了预估的模型BPU部分性能:

    • FPS (1 core):预估的模型单核运行的FPS。

    • latency:预估的模型运行的latency。

    • BPU conv original OPs per run:模型中所有Conv算子的计算量。

    • compiling options:模型编译参数。

    • compiling HBDK version:模型编译使用的HBDK版本。

    • Model minimum memory requirement: Static memory+Dynamic memory。

    • Static memory:hbm模型文件内存占用。

    • Dynamic memory: Model input memory+Model output memory+Bpu shared temporary memory+Exclusive intermediate memory。

    • Model input memory:模型输入内存占用。

    • Model output memory:模型输出内存占用。

    • Bpu shared temporary memory:BPU共享临时内存,可跨模型复用。

    • Exclusive intermediate memory:独占中间内存,即需为每一帧申请的内存。

  2. Temporal Statistics选项卡提供了模型一帧推理时间内的带宽占用情况。

  3. Layer Details选项卡提供了每一层BPU算子的计算量、计算耗时、数据搬运耗时的信息以及编译后layer活跃时间段(不代表该layer执行时间,通常为多个layer交替/并行执行)。

  4. Timeline选项卡提供了模型一帧推理时间内指令集的预估耗时及对应的计算单元的信息(实际显示指标与配置相关):

    • TAE:Tensor Acceleration Engine,是BPU中负责Tensor计算加速的引擎模块。主要负责各种conv计算,也能支持部分Matrix计算。

    • VAE:Vector Acceleration Engine,是BPU中负责Vector计算加速的引擎模块。主要负责神经网络中各种element-wise的运算操作,例如A+B,A*B,LUT计算。

    • AAE:Auxiliary Acceleration Engine,是BPU中负责辅助计算加速的引擎模块。主要负责张量、向量、标量之外的辅助加速,例如Pooling、Resize、Warp等函数计算。

    • VPU:Vector Processing Unit,是BPU内部负责向量计算的单元,比VAE灵活,算力较VAE低。

    • SPU:Scalar Processing Unit,是BPU内部负责标量计算的单元。

    • TRANS:是BPU中用于处理数据layout变换的计算单元。

    • STORE:表示将数据从内部缓存/寄存器写到内存中(或计算平台外)。

    • LOAD:表示从内存(可能是计算平台外)加载数据到计算平台缓存中。

带宽分析

观察html文件中Temporal Statistics选项卡内的折线情况以及查看json文件中解析出来每类算子的计算带宽情况, 可知模型在哪段结构/哪些算子上卡带宽,computing折线较低的地方,load store都较高,即是因为卡带宽导致计算受限。 在layer details选项卡,也可以找到load/store cost远大于computing的算子。

时序图分析利用率

继续观察html文件中Temporal Statistics选项卡内的折线情况,可以看出,computing折线只统计了TAE器件的计算,因此computing折线下降,也可能是因为其他计算器件的运行时间太长了,且无法与TAE并行。

此时,我们可以通过观察Timeline选项卡中的时序图来进行分析,是否是某个计算器件运行时间太长,与TAE并行度不高。

其他

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

板端实测模型性能评估

板端实测模型性能数据的指标解读您可参考 板端实测模型性能数据 章节的介绍。

性能调优建议

检查影响模型性能的yaml参数

注解

此部分建议仅适用于使用 hb_compile 进行模型转换编译的使用场景。

在模型转换的yaml配置文件中,部分参数会实际影响模型的最终性能,可以先检查下是否已正确按照预期配置,各参数的具体含义和作用请参考 配置文件具体参数信息 小节的内容。

  • debug_mode:该参数用于精度调试分析。如果配置了dump_all_layers_output,则会为每个卷积和矩阵乘算子增加一个反量化输出节点以输出模型的中间结果,它会显著的降低模型上板后的性能。所以在性能评测时,务必要将dump_all_layers_output参数从debug_mode中移除。

  • compile_mode:该参数用于选择模型编译时的优化方向为带宽还是时延,关注性能时请配置为 latency

  • optimize_level:该参数用于选择编译器的优化等级,O0不做任何优化, 编译速度最快,优化程度最低,O1-O2随着优化等级提高,预期编译后的模型的执行速度会更快,但是所需编译时间也会变长。

  • max_time_per_fc:该参数用于控制编译后的模型数据指令的function-call的执行时长,从而实现模型优先级抢占功能。设置此参数更改被抢占模型的function-call执行时长会影响该模型的上板性能。

处理CPU算子

判断模型内是否存在CPU算子您可参考 模型中是否有CPU算子 章节的介绍,如果根据评估,确认了突出的性能瓶颈是由于当前算子在CPU上运行导致的。 那么此种情况下,我们建议您先查看 工具链算子支持约束列表 章节,确认当前运行在CPU上的算子是否具备BPU支持的能力。

对于PTQ链路,如果所使用的算子参数超出了BPU支持的约束范围,建议先将原始浮点模型计算参数调整到BPU约束范围内。 为了方便您快速知晓超出约束的具体参数,建议您再使用 验证模型 部分介绍的方法做一遍检查,工具将会直接给出超出BPU支持范围的参数提示。

注解

修改原始浮点模型参数对模型计算精度的影响需要您自己把控,例如Convolution的 input_channeloutput_channel 超出范围就是一种较典型的情况,减少channel快速使得该算子被BPU支持,单单只做这一处修改也预计会对模型精度产生影响。

如果算子并不具备BPU支持能力,就需要您根据以下情况做出对应优化操作:

  • CPU算子处于模型中部

    对于CPU算子处于模型中部的情况,建议您优先尝试参数调整、算子替换或修改模型。

  • CPU算子处于模型首尾部

    对于CPU算子处于模型首尾部的情况,如果节点与模型输入输出相连。

    • 如果您是通过PTQ链路,使用 hb_compile 工具进行的模型量化编译,可以在yaml文件model_parameters配置组(模型参数组)中增加 remove_node_type 参数, 并重新编译模型,详细使用方式可参考 模型参数组 章节的介绍。

    • 如果您是通过PTQ链路API方式或者通过QAT链路进行的模型量化编译,可以通过调用 remove_io_op 接口的方式进行算子的删除, 并重新编译模型,详细使用方式可参考 算子删除 章节的介绍。

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

  • ScatterND

    目前工具链BPU在J6P、J6H及J6B平台上支持fp16和fp32输入的ScatterND算子,除前述情况外,此算子在CPU上会严重影响模型耗时,移除Scatternd算子的方法您可参考 如何去除onnx模型里的scatterND算子Onnx中ScatterND的产生与去除

  • int64 index

    模型里会输出int64类型的算子一般有topk和argmax,topk和argmax输出避免int64 cast的方法您可参考 【地平线J6工具链进阶教程】算子优化方案集锦,此外如果对延时没有比较高的要求,也可以不改源码,导出qat.bc之后,用下面代码把模型里int64的数据替换成int32即可,也可避免因为int64计算引入cpu cast:

    model = load("qat.bc")
    model.replace_index_tensor_type()
    quantized_model = convert(model, march="nash-p")
  • 高精度的resize,warp等硬件不支持的算子

    如果发现模型里有不符合预期的int16/fp16输入的cpu resize和warp,大概率是使用新版Qconfig配置时没有打开 enable_optimize,或是其他不符合预期的qconfig误配。 其他算子可结合 工具链算子支持约束列表 章节的算子约束信息,以及convert时打开 advice 参数提示的算子回退CPU的原因进行判断。

构建量化友好的浮点模型

在实际生产过程中我们会发现有一些模型本身就比较不适合量化,导致模型部署过程中投入了较多资源和时间在解决量化精度问题上, 您可以参考构建量化友好模型 章节了解如何对浮点模型做适当的调整,以使得该模型对量化更加友好。

除此之外在J6平台上,我们有如下优化建议:

  1. 对输入做关于0对称的归一化。雷达点云以及部分有明确物理含义的输入,通常原始数值都较大,建议这类输入都做一下归一化再送入模型,可一定程度上有效降低量化难度。

  2. 尽可能在每个conv like算子后面加bn,避免数值被放大。

  3. 部分relu换relu6,进一步控制数值范围。

  4. mask不要使用1e10,65536等过大的数字,一般给100(以能起到区分作用,不影响浮点精度为准),或者30000(能让softmax后的值为1即可),不要超过int16/fp16数值范围,否则导出或者qat的时候会出现问题。

提高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,参考材料:相关论文

      • layernorm:Dyt替换layernorm,参考材料:相关论文

      • 尽量避免使用fp32精度,fp32只能运行在VPU上,VPU算力相较于VAE和TAE小了好几个数量级,且fp32相较于fp16/int16带宽需求膨胀了一倍。

BPU面向高效率模型优化

学术界在持续优化算法模型的计算效率(同样算法精度下所需的理论计算量越小越高效)、参数效率(同样算法精度下所用参数量越小越高效)。 这方面的代表工作有EfficientNet和ResNeXt,二者分别使用了Depthwise Convolution和Group Convolution。 面对这样的高效率模型,GPU/TPU支持效率很低,不能充分发挥算法效果,学术界被迫针对GPU/TPU分别优化了EfficientNet V2/X和NFNet,优化过程主要是通过减少Depthwise Convolution的使用以及大幅扩大Group Convolution中的Group大小,这些调整都降低了原本模型的计算效率和参数效率。

更多高效模型设计指导建议可参考 高效模型设计指导 章节内容。 同时,我们也提供了丰富的模型给您作为直接的参考,在OpenExplorer包的samples/ai_toolchain/horizon_model_train_sample路径下为您提供了参考算法模型的源码,算法包使用说明可参考 参考算法 章节说明。

其他优化建议,您可参考 【地平线J6工具链进阶教程】算子优化方案集锦 的相关介绍。