7.1. 环境配置

7.1.1. 如何查看当前系统各种软件版本信息

  • cat /etc/version 查看当前系统的SDK版本编译时间

# cat /etc/version
LNX6.1.12_PL5.1_V0.0.8_20240319-1108
  • uname -a 查看内核版本

# uname -a
Linux buildroot 6.1.12-rt7+ #1 SMP PREEMPT Tue Mar 19 11:12:22 CST 2024 aarch64 GNU/Linux
  • strings /dev/block/platform/by-name/uboot | grep "U-Boot 2022.10" 查看uboot版本

# strings /dev/block/platform/by-name/uboot | grep "U-Boot 2022.10"
U-Boot 2022.10-g4432fbb999
U-Boot 2022.10-g4432fbb999 (Mar 19 2024 - 17:22:42 +0800)

7.1.2. 如何设置uboot的bootargs

通过串口连接,在系统启动时,快速持续按下空格键,进入uboot命令行模式,然后通过以下方式设置bootargs:

  • 配置临时启动项, 修改以下命令<items to add>需要参数,配置需要在uboot命令行执行 boot 命令引导内核启动,重启设备配置失效:

setenv bootargs <items to add>
boot
# 例如打开kernel的日志earlycon
setenv bootargs earlycon=uart8250,mmio32,0x32120000
boot
  • 配置长期有效启动项,修改以下命令<items to add>需要参数,执行saveenv 保存配置env分区,即使设备断电重启保持配置有效。

setenv bootargs <itmes to add>
saveenv
reset
  • 通过配置文件修改启动项,在SDK源码目录uboot/tools路径创建一个uboot.env配置文件,按照键值格式写入需要配置参数,通过编译./bd.sh单编uboot./bd.sh uboot在out/product路径下会生成ubootenv.img镜像文件。使用fastboot工具烧写ubootenv.img,即使设备断电重启保持配置有效。

#fastboot烧写ubootenv
fastboot flash ubootenv ubootenv.img

注:以下配置不能通过配置文件方式修改

Hobot>
boot_device=emmc
bootcmd=run ab_select_cmd;run avb_boot;
dev_index=0
dev_name=mmc
ethaddr=46:a1:a3:75:e4:a4
fdtcontroladdr=8beb2680
hb_board_id=0x0202
reset_reason=COLD_BOOT
stderr=serial@32120000
stdin=serial@32120000
stdout=serial@32120000

7.1.3. 为什么程序可以手动执行,添加到/etc/init.d下自启动不行

如果开发多媒体、BPU相关程序,因为部分动态文件存放/usr/hobot/ 目录下,手动执行时候,/usr/hobot/启动时候自动添加到shell环境中,但是自启动时,这部分配置一定配置,所以需要自启动程序配置脚本里面添加环境设置,例如S90cam-service里面配置:

export LD_LIBRARY_PATH="/usr/hobot/lib:${LD_LIBRARY_PATH}"

或者,在程序启动通过 source /etc/profile.d/environment.sh多媒体、BPU、app相关所有动态库、可执行程序添加环境变量中。

7.2. 系统软件

7.2.1. 如何查看系统温度,cpu 和 bpu 运行频率统计信息

hrut_somstatus

7.2.2. 如何读取芯片uid命令

cat /sys/class/socinfo/soc_uid

7.2.3. 如何查看bpu支持频率

cat /sys/class/devfreq/3a000000.bpu/available_frequencies

7.2.4. 如何查看cpu调度模式

cat /sys/devices/system/cpu/cpufreq/policy0/scaling_governor

7.2.5. 如何查看cpu支持频率当前运行频率

cat /sys/devices/system/cpu/cpufreq/policy0/scaling_available_frequencies
cat /sys/devices/system/cpu/cpufreq/policy0/cpuinfo_cur_freq

7.2.6. 如何查看cpu降频温度

cat /sys/devices/virtual/thermal/thermal_zone1/trip_point_1_temp

7.2.7. 关闭cpu核方法

echo 0 > /sys/devices/system/cpu/cpu1/online

# 需要关哪个CPU核,修改以下命令的 <x> 为对应数字即可
echo 0 > /sys/devices/system/cpu/cpu<x>/online

注意:cpu0 无法关闭,x 的取值范围为 1~7。

关闭 cpu 核后可以使用 lscpu 命令查看当前的 cpu 状态:

root@buildroot:~# echo 0 > /sys/devices/system/cpu/cpu1/online
root@buildroot:~# lscpu
Architecture:             aarch64
  CPU op-mode(s):         32-bit, 64-bit
  Byte Order:             Little Endian
CPU(s):                   8
  On-line CPU(s) list:    0,2-7
  Off-line CPU(s) list:   1
......  

上述日志可以看到,cpu1 已经离线了,说明已经成功关闭了 cpu1。

7.2.8. 中断绑定cpu核方法

注意:

下列命令使用中断及 CPU 仅供参考,请用户根据实际需求选取需要绑定的CPU及中断号。

#绑定到CPU3,echo后的8对应CPU3,1/2/4/8/16/64/128/256分别对应CPU0-7,175是中断号
echo 8 > /proc/irq/175/smp_affinity

7.2.9. eMMC烧录器文件

eMMC的空片烧录,可以直接使用disk*.img文件,这个文件二进制文件,可以烧录器直接烧写到eMMC(注意空片烧录需要完整烧录一次disk.img之后,才能单独烧录uboot.img等分区)

如果批量生产,建议烧录贴片:

1)烧录时候首先确认工厂烧录器支持eMMC型号,如果支持,则可以直接烧录。

2)如果烧录器支持,但是可以支持相同封装的,则可以烧录器厂家把eMMC型号添加进去。

3)如果此类封装eMMC的烧录器支持,则可以专门第三方烧录厂。

4)烧录界面配置,可以咨询烧录器厂家,不同烧录器有些差别。

7.2.10. 如何查看修改芯片寄存器

使用 devmem命令可以直接访问物理地址,读写寄存器,常规用法:

devmem ADDRESS [WIDTH [VALUE]]

# 例如读管脚 HSIO_GPIO_00 的复用寄存器的值
devmem 0x3505005C 32

7.2.11. i2c命令使用

# 列举 I2C bus 和上面所有的设备
i2cdetect -l
# 查看 bus 1 上有哪些探测到的外设
i2cdetect -y -r 1
# 写: 0x36为I2C设备的地址, 0x5081为要写的寄存器地址, 0x01为写入的值。
i2ctransfer -f -y 1 w3@0x36 0x50 0x81 0x01
# 读: 0x36为I2C设备的地址, 0x300A为要读的寄存器地址, r3为连续读3Byte, 0x56 0x08 0x41 为读到的寄存器的值。
i2ctransfer -f -y 1 w2@0x36 0x30 0x0A r3

7.2.12. 增强驱动能力(以I2C4举例)

首先更新设备树,相关内容一般kernel/arch/arm64/boot/dts/hobot/pinmux-func.dtsi设备树文件中,以下部分截取内容:

......

pconf_drv_pu_ds7_1v8: pconf-dev-pu-ds7-1v8 {
  bias-pull-up;
  power-source = <HORIZON_IO_PAD_VOLTAGE_1V8>;
  drive-strength = <7>;/*驱动强度*/
};

......

pinctrl_i2c4: i2c4grp {
  horizon,pins = <
    LSIO_I2C4_SCL  LSIO_PINMUX_3 BIT_OFFSET0  MUX_ALT0 &pconf_drv_pu_ds5_1v8
    LSIO_I2C4_SDA  LSIO_PINMUX_3 BIT_OFFSET2  MUX_ALT0 &pconf_drv_pu_ds5_1v8
  >;
};
......

编译烧录之后,在系统可以使用cat /sys/kernel/debug/pinctrl/34180000.lsio_iomuxc/pinconf-pins查询修改是否生效。

root@ubuntu:~# cat /sys/kernel/debug/pinctrl/34180000.lsio_iomuxc/pinconf-pins
Pin config settings per pin
Format: pin (name): configs
......
......
pin 47 (lsio_i2c4_scl): input bias pull down (0 ohms), input bias pull up (0 ohms), output drive strength (10 mA), input enabled, input schmitt enabled, pin output (1 level)
pin 48 (lsio_i2c4_sda): input bias pull down (2097152 ohms), input bias pull up (2097152 ohms), output drive strength (10 mA), input enabled, input schmitt enabled, pin output (1 level)

drive-strength = <2>X5平台一般对应是3mA,选择drive-strength = <7>对应节点之后可以看到驱动电流是10mA。

7.2.13. 内核是否支持 PREEMPT_RT 内核 patch

支持RT Patch (rt7)。

7.2.14. 能否自定义文件系统?

目前支持buildroot文件系统,用户可以重新构建。

7.2.15. 是否支持数字音频接口(Audio PDM)?

支持,配置如下:

引脚端口确认

芯片引脚定义引脚如下,使用排查是否复用。

pdm_pin_mux_check

芯片引脚定义引脚如下,使用排查是否复用。

设备树配置确认

pinctrl 这部分在 BSP 源码的 pinmux-func.dtsi 文件中;

pinctrl_dsp_pdm_cko: pdmckogrp {
  horizon,pins = <
    DSP_PDM_CKO  DSP_PINMUX_0  BIT_OFFSET24  MUX_ALT0 &pconf_drv_pu_ds2_1v8
  >;/* PDM时钟输出的引脚配置,定义了引脚的复用功能和电源配置,使用的时候需要检查是否有复用*/
};

pinctrl_dsp_pdm_in: pdmingrp {
  horizon,pins = <
    DSP_PDM_IN0  DSP_PINMUX_0  BIT_OFFSET26  MUX_ALT0 &pconf_drv_pu_ds2_1v8
    DSP_PDM_IN1  DSP_PINMUX_0  BIT_OFFSET28  MUX_ALT0 &pconf_drv_pu_ds2_1v8
    DSP_PDM_IN2  DSP_PINMUX_0  BIT_OFFSET30  MUX_ALT0 &pconf_drv_pu_ds2_1v8
    DSP_PDM_IN3  DSP_PINMUX_1  BIT_OFFSET0  MUX_ALT0 &pconf_drv_pu_ds2_1v8
  >;/*这是PDM输入的引脚配置,定义了四个输入引脚的复用功能和电源配置,使用的时候需要检查是否有复用*/
};

pdm 绑定 dsp 部分在 BSP 源码的 x5.dtsi 文件中;

archband_pdm: archband_pdm@320d0000 {
      compatible = "archband,pdm-driver";
      reg = <0x320d0000 0x00010000>;
      clocks = <&dspclks X5_DSP_PDM_HMCLKA_CLK>, <&dspclks X5_DSP_PDM_APB_CLK>;
      clock-names = "pdmclk", "pdm_pclk";
      pinctrl-names = "default";
      pinctrl-0 = <&pinctrl_dsp_pdm_cko &pinctrl_dsp_pdm_in>;
      arb-syscon = <&dsp_crm_syscon 0x10>;
      arb,osr = <1>;  
      dmas = <&dsp_axi_dma 20>, <&dsp_axi_dma 19>, <&dsp_axi_dma 18>, <&dsp_axi_dma 17>;
      dma-names = "rx0", "rx1", "rx2", "rx3";
      channel = <2>;
      #sound-dai-cells = <0>;
      status = "disabled";
      resets = <&dsprst DSP_PDM_RESET>;
};

打开相应的 config 选项

audio_pdm_deconfig_setting

图形化界面选择。 对应平台如下 config 需要确认:

CONFIG_SND_ARCHBAND_PDM
CONFIG_SND_VIRT_CODEC

CONFIG_SND_SOC_HOBOT_SIMPLE_CARD
CONFIG_SND_DUPLEX_CARD
CONFIG_SND_HOBOT_SOUND_MACHINE
CONFIG_SND_HOBOT_SOUND_DUPLEX_HOST

CONFIG_SND_SIMPLE_CARD_UTILS

验证参考

根据实际使用配置和 codec,参考使用以下命令:

modprobe designware_i2s i2s_ms=1 /*选择i2s的主从模式*/
modprobe es7210
modprobe es8156
modprobe snd-soc-hobot-sound-duplex-host
arecord –l /*查看声卡,确认pdm录制时使用的声卡*/
arecord -Dhw:1,2 -c 2 -r 16000 -f S24_LE -t wav -d 5 /userdata/test.wav	/*录制命令*/

录制完毕之后检查/userdata/test.wav 文件是否正常。

pdm使用限制说明

通道数:8chn(走 alsa 框架测试每次只能验证2通道,也就是一个 HIFI_PIN_INx,原因每个数字 mic 的 data 输出需要占用一个 dma handshake。ALSA 框架支持单根输入多个 dma handshake。) 采样率:16/32/48k 位宽:24bit

7.2.16. 为什么fastboot烧录miniboot后无法启动

错误烧录方式:

fastboot flash addr:0x0 miniboot.img

问题原因: 旧版本miniboot包含miniboot.img,分区表misc分区,而由于版本烧录脚本发生改变,miniboot_all.img = gpt.img + mbr.img + miniboot.img + misc.img ,也就是旧版本的miniboot,而现版本miniboot.img = bl2.img + bl3x.img,所以miniboot的烧录两种方式。

烧录miniboot分区 正确烧录命令:

fastboot flash miniboot miniboot.img

烧录miniboot以及完整分区表和misc 正确烧录命令:

fastboot flash addr:0x0 miniboot_all.img

7.2.17. SDK编译出现 internal compiler error: Illegal instruction

错误日志:

image-20240319224834955

问题原因: gcc arm-gnu-toolchain-11.3.rel1 版本的 CPU 型号存在兼容性问题,无法处理浮点数据。详情可以查阅:Bug 5825 - Illegal instruction: 4 on questionable float conversion

解决: 一般需要更新开发主机的,使用的 CPU 型号。

7.2.18. 为什么单独编译了hbre的模块,在hbre.img中没有包含

问题现象: 使用./bd.sh hbre camsys/libcam 编译后,hbre.img 里面没有包含修改内容

原因解决: 使用./bd.sh hbre 或者./bd.sh app 不带模块名是默认编译所有模块重新生成对应的hbre.img和app.img,但是模块之后,例如./bd.sh hbre camsys/libcam 或者 ./bd.sh hbre liblog则只会编译模块,但是不会打包动作。这样原因可以支持用户单独编译后,把产物下载板端独立调试。如需要编译单独模块重新生成image文件,可以编译命令末尾加上 pack 选项,例如./bd.sh hbre camsys/libcam pack, 这样编译完 camsys/libcam后,会重新打包 hbre.img 分区镜像。或者,可以完成单独模块编译后,执行 ./bd.sh hbre pack 单纯完成hbre分区镜像打包。

7.2.19. 使用buildroot制作文件系统提示 you are leaving 9 commits behind

问题现象: Warning: you are leaving 9 commits behind, not connected to

image-20240319224825834

问题原因: 这重新编译buildroot的时候提示警告。原因第一次编译buildroot时,会把hb_patch_buildroot下的patch打到buildroot的源码下,再次编译时候提示这个警告。

解决办法: 对编译过程没有影响,编译脚本恢复源码初始状态重新打patch。

7.2.20. 使用buildroot制作文件系统提示 LD_LIBRARY_PATH Error

问题现象: 编译buildroot生成文件系统提示以下Error:

You seem to have the current working directory in your LD_LIBRARY_PATH environment variable. This doesn’t work.

image-20240319224814069

问题原因: 这个提示用户环境变量里面已经配置了LD_LIBRARY_PATH,在编译buildroot时,这个配置允许设置的。

解决办法: 查看~/.profile 和 ~/.bashrc 的内容,找到 LD_LIBRARY_PATH 配置,注释掉,然后重新登录终端。echo ${LD_LIBRARY_PATH} 看一下还有没有值,没有可以正常了。

7.2.21. 使用buildroot制作system根文件系统,提示mount not found

问题现象: ../output/build/libglib2-2.72.3/meson.build:2124:2: ERROR: Dependency “mount” not found, tried pkgconfig and cmake

image-20240319224801158

问题原因: 在制作initramfs系统没有执行clean,立即继续制作system的系统情况下,会这个错。

解决办法: 制作initramfs后,把 framework/output 删除,重新编译system。

7.2.22. bd.sh lunch时不到 utils_funcs.sh 文件

问题现象: ./bd.sh lunch

./bd.sh: line 7: /home/work/x5_bjs1/utils_funcs.sh: No such file or directory

问题原因: 在执行 ./bd.sh 时,会通过以下方式获取xbuild.sh的绝对路径:

SCRIPT_DIR=”$( cd “$( dirname “$(readlink -f “${BASH_SOURCE[0]}”)” )” && pwd )”

source “$SCRIPT_DIR/utils_funcs.sh”

bd.sh 是链接到 build/xbuild.sh 的连接,SCRIPT_DIR 是 ./build 目录绝对路径。

但是在sdk打包拷贝过程中有可能连接实体文件, 那获取的SCRIPT_DIR目录可能对,因此提示不到依赖文件。

解决办法

恢复 bd.sh 为连接文件

ln -sf build/xbuild.sh bd.sh

Linux下文件禁止使用zip打包,会改变连接文件, 也可能改变文件权限属性。

执行cp命令带上 -a 选项,可以保持连接状态。

7.2.23. 在ZSH的shell环境使用快捷命令模式编译提示无法找到bd.sh

问题现象: 如题,在zsh 的环境按照文档source build/quickcmd.sh后,使用b命令编译报错如下图:

image-20240319224849099

问题原因: 导入bd.sh位置PATH环境变量时,由于zsh没有BASH_SOURCE这个全局变量,导致路径设置错误。

解决办法:参考 ${BASH_SOURCE[0]} equivalent in zsh?不到BASH_SOURCE这个环境变量时,退化${(%):-%x}}

img

7.2.24. 如果修改内核里面的dts,可以烧录整个镜像

问题: 需要熟悉分区表定义内核编译打包过程。dts和kernel会一起打包成fit格式镜像,文件存储的its定义在device/horizon/x5/board_cfg/soc/x5.its 文件里面,该文件描述board_id 与内核镜像、设备树匹配方式。

解决办法: kernel和dts一起打包在boot.img里面,单独更新boot分区即可。

7.2.25. 在app目录下面增加代码目录,要怎么样参与整体编译

解决办法: app 目录代码由 build/mk_app.sh 脚本负责编译,如果需要添加代码目录,并参与整体编译,需要在 build/mk_app.sh 程序的 components 添加对应目录名。

image-20240319225201040

build/mk_app.sh 会遍历 components , 在对应目录寻找 build.sh(优先匹配) 和 Makefile,如果找到编译。

注意: app目录代码因为独立测试程序,会随时根据需要增加删除,这个list里面目录如果存在,或者不到build.sh 和 Makefile,也不会导致整体编译失败,仅提示模块存在警告。

7.3. 芯片规格

7.3.1. 芯片内存大小上限

最大支持8GB内存。

7.3.2. BPU 的内存访问模式

BPU与CPU共享内存, 在系统会Reserve一块连续物理内存给BPU使用。

7.4. 编解码

7.4.1. 视频码流头部信息正确报错

[ERROR][MM][src/vdi/linux/vdi_osal.c:174] [ERROR][869.56942][3344:3543][VideoDecoder] DecodeHeader:1554 FAILED TO DEC_PIC_HDR: ret(1), SEQERR(00005000) [ERROR][MM][src/vdi/linux/vdi_osal.c:174] [ERROR][869.56980][3344:3543][COMPONENT] Component wave_decoder will be terminated

需要输入解码模块码流第一个帧,给sps,pps,idr,如果给sps,就报如上错误。

7.5. 外设

7.5.1. 如何判断sensor硬件连接是否正常

  • 首先需要使能sensor的供电,一般多路供电(例如 1.8V, 2.8V等),使能供电方式依据开发板情况各有不同

  • 然后需要使能sensor 的 MCLK, 否则sensor的i2c无法正确探测到sensor

echo 1 > /sys/class/vps/mipi_host1/param/snrclk_en
echo 24000000 > /sys/class/vps/mipi_host1/param/snrclk_freq
echo 1 > /sys/class/vps/mipi_host0/param/snrclk_en
echo 24000000 > /sys/class/vps/mipi_host0/param/snrclk_freq
  • 通过 i2cdetect -y -f <i2c_bus> 命令探测sensor, 根据硬件设计情况填写 i2c_bus 编号

7.5.2. ETH phy寄存器读取方法

uboot下可以直接使用mii命令,例如:

mii dump
mii read

7.5.3. Usb软件切换主从模式方法

usb通过软件切换主从模式方法两种,使用debugfs和class/usb_role切换USB控制器工作模式,通过软件切换,可以实现灵活的USB连接方式,方便测试调试。

* 利用debugfs
echo device > /sys/kernel/debug/usb/35100000.usb/mode                     #  usb3.0 port
echo host > /sys/kernel/debug/usb/35100000.usb/mode                       #  usb3.0 port

echo device > /sys/kernel/debug/usb/35300000.usb/mode                     #  usb2.0 port
echo host > /sys/kernel/debug/usb/35300000.usb/mode                       #  usb2.0 port

* 利用class/usb_role
echo device > /sys/class/usb_role/35100000.usb-role-switch/role            # usb3.0 port
echo host > /sys/class/usb_role/35100000.usb-role-switch/role              # usb3.0 port

echo device > /sys/class/usb_role/35300000.usb-role-switch/role            # usb2.0 port
echo host > /sys/class/usb_role/35300000.usb-role-switch/role              # usb2.0 port

7.6. 算法工具链

7.6.1. 常见故障处理

7.6.1.1. hb_mapper checker 常见故障

背景信息:模型检查命令( hb_mapper checker

实际工程中,由于并非所有浮点模型能够转为量化模型,因此转换之前需要进行一次检查,这个check过程,会完成一遍模型转换过程, 但是对于比较耗时步骤,进行简化处理。该命令完成模型检查后,会输出检查结果和OP在设备部署情况。

故障场景:以下使用 hb_mapper checker常见故障场景:

1.故障现象一:

  ERROR The shape of model input:input is [xxx] which has dimensions of 0.
  Please specify input-shape parameter.

故障可能原因:发生故障原因可能模型输入动态shape。

解决建议:针对故障,您使用参数 --input-shape "input_name input_shape"指定输入节点的shape信息。

2.故障现象二:

  ERROR HorizonRT not support these cpu operators: {op_type}

故障可能原因:发生故障原因可能使用的CPU算子地瓜支持的CPU算子。

解决建议:针对故障,您可以根据我们提供算子支持列表内容算子进行替换;若支持的CPU算子模型核心算子,请联系地瓜对此进行开发评估。

3.故障现象三:

  Unsupported op {op_type}

故障可能原因:发生故障原因可能使用的BPU算子地瓜支持的BPU算子。

解决建议:针对故障,若模型整体性能满足需要,您可以忽略日志;若模型整体性能不能达到预期,您可以根据我们提供算子支持列表内容算子进行替换。

4.故障现象四:

  ERROR nodes:['{op_type}'] are specified as domain:xxx, which are not supported by official onnx.
  Please check whether these ops are official onnx ops or defined by yourself

故障可能原因:发生故障原因可能使用自定义算子地瓜支持自定义算子。

解决建议:针对故障,您可以根据我们提供算子支持列表内容算子进行替换参考 自定义算子开发内容完成自定义CPU算子注册。

7.6.1.2. hb_mapper makertbin常见故障

背景信息:模型编译命令( hb_mapper makertbin

命令根据配置文件模型种类,会生成ONNX量化模型以及仿真上板情况的runtime模型。

故障场景:以下使用 hb_mapper makertbin常见故障场景:

1.故障现象一:

  Layer {op_name}
    xxx expect data shape range:[[xxx][xxx]], but the data shape is [xxx]
  Layer {op_name}
    Tensor xxx expects be n dimensions, but m provided

故障可能原因:发生故障原因可能是,{op_name}算子超过支持限制回退到CPU计算。

解决建议:针对故障,若CPU算子带来性能损耗接受,则无需关注信息;若性能不能达到要求,您可以根据我们提供算子支持列表内容该op修改至BPU可支持范围。

2.故障现象二:

  ERROR There is an error in pass: {op_name}. Error message:xxx

故障可能原因:发生故障原因可能是,{op_name}算子优化失败。

解决建议:针对故障,请模型以及.log文件收集好后提供地瓜技术人员进行分析处理。

3.故障现象三:

  Error There is an error in pass:constant_folding.
  Error message: Could not find an implementation for the node {op_name}

故障可能原因:发生故障原因可能算子onnxruntime暂未支持。

解决建议:针对故障,您可以根据我们提供算子支持列表内容算子进行替换,如支持算子核心算子,请联系地瓜对此进行开发评估。

4.故障现象四:

  Start to parse the onnx model
  core dump

故障可能原因:发生故障原因可能模型解析失败(可能导出模型一个output/input节点指定了name)。

解决建议:针对故障,建议您重新导出onnx并确认有效性(导出onnx模型指定output/input name,或者依次每个output/input节点指定名称)。

5.故障现象五:

  Start to calibrate/quantize the model
  core dump

  Start to compile the model
  core dump

故障可能原因:发生故障原因可能模型量化/编译失败。

解决建议:针对故障,请模型以及.log文件收集好后提供地瓜技术人员进行分析处理。

6.故障现象六:

  ERROR model conversion faild: Inferred shape and existing shape differ in dimension x: (n) vs (m)

故障可能原因:发生故障原因可能是onnx模型输入shape非法,或者工具优化pass有误。

解决建议:针对故障,请确保onnx模型有效性,若onnx模型正常推理,请模型提供地瓜技术人员进行分析处理。

7.故障现象七:

  WARNING got unexpected input/output/sumin threshold on conv {op_name}! value: xxx

故障可能原因:发生故障原因可能数据预处理有误,或节点weight值太小/太大。

解决建议:针对故障,请检查数据预处理是否有误;我们建议您使用BN算子优化数据分布。

8.故障现象八:

  ERROR hbdk-cc compile hbir model failed with returncode -n

故障可能原因:发生故障原因可能模型编译失败。

解决建议:针对故障,请模型以及.log文件收集好后提供地瓜技术人员进行分析处理。

9.故障现象九:

  ERROR {op_type}  only support 4 dim input

故障可能原因:发生故障原因可能工具链支持该op输入维度四维。

解决建议:针对故障,我们建议您该op输入维度调整四维输入。

10.故障现象十:

  ERROR {op_type} Not support this attribute/mode=xxx

故障可能原因:发生故障原因可能工具链支持op的属性。

解决建议:针对故障,您可以根据我们提供算子支持列表内容进行替换联系地瓜对此进行开发评估。

11.故障现象十一:

  ERROR There is no node can execute on BPU in this model,
  please make sure the model has at least one conv node which is supported by BPU.

故障可能原因:发生故障原因可能模型没有量化的BPU节点。

解决建议:针对故障,请确保onnx模型有效性,且模型至少使用一个conv;若前述条件满足,请模型以及.log文件收集好后提供地瓜技术人员进行分析处理。

12.故障现象十二:

  ERROR The opset version of the onnx model is n, only model with opset_version 10/11 is supported

故障可能原因:发生故障原因可能模型opset版本超出工具链支持限制。

解决建议:针对故障,请重新导出模型,确保opset_version=10或者11。

13.故障现象十三:

使用run_on_bpu后转换报错。

故障可能原因:发生故障原因可能目前支持算子run_on_bpu。

解决建议:run_on_bpu暂仅支持指定模型中Relu/Softmax/Reshape/pooling(maxpool、avgpool等)算子以及CPU*+Transpose组合(可通过声明Transpose节点名称,将CPU*+Transpose都运行在BPU上,CPU*特指BPU支持的op), 若满足前述条件仍run_on_bpu失败,请联系地瓜技术人员对此进行分析处理;若满足前述条件,可联系地瓜技术人员对此进行开发评估。

14.故障现象十四:

  ERROR unsupported model: BAYES-E not support excute one model on 2core simultaneously now

故障可能原因:发生故障原因是X5目前支持编译双核模型。

解决建议:针对故障,建议您将yaml配置文件的core_num设置为1。

15.故障现象十五:

  ERROR : There is an ERROR during shape inference,···,The error model has been saved as shape_inference_fail.onnx

故障可能原因:发生故障原因可能模型非法工具解析失败。

解决建议:请.log 文件生成shape_inference_fail.onnx 提供地瓜技术人员进行原因分析。

7.6.1.3. hb_model_modifier常见故障

背景信息hb_model_modifier 工具用于指定的runtime模型输入端的Transpose、Quantize节点输出端的Transpose、Dequantize、DequantizeFilter、Cast、Reshape、Softmax节点进行删除操作, 并删除节点信息存放在BIN模型中,可以通过 hb_model_info 进行查看。

故障场景:以下使用 hb_model_modifier常见故障场景:

故障现象:

  ERROR Can not find value info {op_name}

故障可能原因:该问题地瓜已知问题,于OE1.1.14版本进行修复。

解决建议:针对故障,请完整更新OE开发包将horizon-tc-ui升级至1.7.8。

7.6.1.4. hb_verifier常见故障

背景信息hb_verifier 工具用于指定定点模型和runtime模型进行结果验证工具。

使用工具指定图片,则 hb_verifier 工具使用指定图片进行定点模型推理、runtime模型板和X86端模拟器推理,并结果进行比较, 给出是否通过结论(此过程支持自选,您可以根据需要选择进行对比内容)。

使用工具未指定图片,则 hb_verifier 工具默认使用随机生成的tensor数据进行推理。

故障场景:以下使用 hb_verifier常见故障场景:

故障现象:

  ERROR Quanti onnx and Arm result Strict check FAILED

故障可能原因:发生故障原因可能模型一致性失败。

解决建议:针对故障,请模型提供地瓜技术人员进行分析处理。

7.6.1.5. hb_onnxruntime常见故障

背景信息hb_onnxruntime 主要用于onnx模型推理类。

故障场景:以下使用 hb_onnxruntime常见故障场景:

1.故障现象一:

  ERROR [ONNXRuntimeError] : 2:INVALID_ARGUMENT : Unexpected input data type.
  Actual: (N11onnxruntime17PrimitiveDataTypexxx), expected: (N11onnxruntime17PrimitiveDataTypexxx)

故障可能原因:发生故障原因可能输入数据格式模型匹配。

解决建议:针对故障,一般来说浮点onnx模型输入格式为float32,量化模型输入格式为int8。可使用可视化工具查看onnx模型input节点属性。

2.故障现象二:

  [libprotobuf FATAL google/protobuf/stubs/common.cc:83] This program was compiled against version 3.6.1 of the Protocol Buffer runtime library,
  which is not compatible with the installed version on (3.19.4).

故障可能原因:发生故障原因可能是torch使用的protobuf版本和horizon使用的protobuf版本冲突,需要在torch前import。

解决建议:针对故障,您可以将from horizon_tc_ui import HB_ONNXRuntime放在第一行import。import其他API出现相同报错同等适用。

7.6.1.6. libDNN常见故障

背景信息libDNN 主要用于地瓜模型推理库。

故障场景:以下使用 libDNN常见故障场景:

1.故障现象一:

  (common.h:79): HR:ERROR: op_name:xxx invalid attr key xxx

故障可能原因:发生故障原因可能是libDNN暂支持该op的某个属性(后续我们逐步算子约束前移模型转换阶段提醒)。

解决建议:针对故障,您可以根据我们提供算子支持列表内容进行替换联系地瓜对此进行开发评估。

2.故障现象二:

  (hb_dnn_ndarray.cpp:xxx): data type of ndarray do not match specified type. NDArray dtype_: n, given:m

故障可能原因:发生故障原因可能是libDNN暂支持输入类型(后续我们逐步算子约束前移模型转换阶段提醒)。

解决建议:针对故障,您可以根据我们提供算子支持列表内容进行替换联系地瓜对此进行开发评估。

3.故障现象三:

  (validate_util.cpp:xxx):tensor aligned shape size is xxx , but tensor hbSysMem memSize is xxx,
  tensor hbSysMem memSize should >= tensor aligned shape size!

故障可能原因:发生故障原因可能输入数据申请内存不足。

解决建议:针对故障,由于使用hrt_model_exec model_info查看模型input节点aligned shape,是aligned shape*size_of(tensor type)申请内存空间的。 此处我们建议:若libDNN版本高于 1.5.4b,建议直接使用hbDNNTensorProperties.alignedByteSize申请内存空间,若libDNN 版本低于 1.5.4b 版本直接使用aligned shape*size_of(tensor type)申请内存空间。

4.故障现象四:

  (bpu_model_info.cpp:xxx): HR:ERROR: hbm model input feature names must be equal to graph node input names

故障可能原因:该问题地瓜已知问题,属于hb_model_modifer工具已知问题,已于OE1.1.14版本进行修复。

解决建议:针对故障,请完整更新OE开发包将horizon-tc-ui升级至1.7.8版本。

7.6.2. 模型量化上板使用技巧

7.6.2.1. Transformer使用说明

章节各个transformer的概念参数进行说明,并提供参考使用示例,方便进行tranformer操作。

文档内容开始阅读前,以下内容注意:

  • 图片数据三维数据,但地瓜提供的transformer都四维数据方式进行获取处理的,transformer只会输入数据第0张 图片操作。

AddTransformer

说明

输入图片所有像素增加value的操作。该transformer会输出时, 将数据格式转为float32。

参数

  • value: 对每个像素增加数值, 注意value的取值可以负数, 如 -128。

使用举例

  # 对图像数据做减去128的操作
  AddTransformer(-128)

  # 对图像数据做增加127的操作
  AddTransformer(127)

MeanTransformer

说明

输入图片所有像素减去 mean_value 的操作。

参数

  • means: 对每个像素增加数值, 注意value的取值可以负数, 如 -128。

  • data_format: 输入的layout类型,取值范围为[”CHW”,”HWC”], 默认 “CHW”。

使用举例

  # 每个像素减去128.0 输入的类型为CHW
  MeanTransformer(np.array([128.0, 128.0, 128.0]))

  # 每个像素减去不同的数值,103.94, 116.78, 123.68,输入的类型为 HWC
  MeanTransformer(np.array([103.94, 116.78, 123.68]), data_format="HWC")

ScaleTransformer

说明

输入图片所有像素乘以data_scale系数操作。

参数

  • scale_value: 需要乘以系数,如0.0078125 或者1/128。

使用举例

  # 将取值范围-128~127,所有的像素的调整到-1~1之间
  ScaleTransformer(0.0078125)
  # 或者
  ScaleTransformer(1/128)

NormalizeTransformer

说明

用于输入图片进行归一化操作。该transformer会输出时, 将数据格式转为float32。

参数

  • std:输入第一张图片,需要除以数值。

使用举例

  # 将取值范围[-128, 127] 所有的像素的调整到-1~1之间
  NormalizeTransformer(128)

TransposeTransformer

说明

用于做layout转换操作。

参数

  • order: 对输入图片做layout转换顺序(顺序原有的layout顺序有关)。如:HWC的顺序为0,1,2,需要转为CHW时,order为(2,0,1)。

使用举例

  # HWC转到CHW
  TransposeTransformer((2, 0, 1))
  # CHW转到HWC
  TransposeTransformer((1, 2, 0))

HWC2CHWTransformer

说明

用于将NHWC转换为NCHW的操作。

参数:不涉及。

使用举例

  # NHWC转到NCHW
  HWC2CHWTransformer()

CHW2HWCTransformer

说明

用于将NCHW转换为NHWC的操作。

参数:不涉及。

使用举例

  # NCHW转到 NHWC
  CHW2HWCTransformer()

CenterCropTransformer

说明

直接截断取值方式图片中心裁剪一个正方形图片操作。该transformer会输出时, 将数据格式转为float32。当data_type的为uint8时,输出为uint8。

参数

  • crop_size: 中心裁剪正方形边长size。

  • data_type: 输出结果类型,取值范围为[”float”, “uint8”]。

使用举例

  # 以224*224的方式,做中心裁剪,默认输出类型为float32
  CenterCropTransformer(crop_size=224)

  # 以224*224的方式,做中心裁剪,输出类型为uint8
  CenterCropTransformer(crop_size=224, data_type="uint8")

PILCenterCropTransformer

说明

使用PIL的方式图片中心裁剪一个正方形图片操作。该transformer会输出时, 将数据格式转为float32。

参数

  • size: 中心裁剪正方形边长size。

使用举例

  # 以224*224的方式,使用PIL的方式做中心裁剪
  PILCenterCropTransformer(size=224)

LongSideCropTransformer

说明

用于裁剪操作。该 transformer 会输出时, 将数据格式转为float32。

宽度高度数值大时,会裁剪一个中心高度大小为准正方形,如宽100,高70,裁剪之后大小为70*70。

高度宽度数值大时,会裁剪一个中心宽度大小不变,高度差值一半+宽度 的长方形,如宽70,高100,裁剪之后大小70*(100-70)/2+70 ,即70* 85大小长方形。

参数:不涉及。

使用举例

  LongSideCropTransformer()

PadResizeTransformer

说明

使用填充方式图像放大操作。该 transformer 会输出时, 将数据格式转为float32。

参数

  • target_size:目标大小,值为元组,如(240,240)。

  • pad_value:填充数组值,默认值为127。

  • pad_position:填充位置,取值范围为[”boundary”, “bottom_right”],默认值为 “boundary”。

使用举例

  # 裁剪一个大小为512*512,填充到右下角,填充值为0
  PadResizeTransformer((512, 512), pad_position='bottom_right', pad_value=0)

  # 裁剪一个大小为608*608,填充到边框,填充值为 127
  PadResizeTransformer(target_size=(608, 608))

ResizeTransformer

说明

用于调整图像大小操作。

参数

  • target_size:目标大小,值为元组,如(240,240)。

  • mode:图片处理模式,取值范围为(”skimage”,”opencv”),默认值为 “skimage”。

  • method:插值方法,此参数在mode为skimage时生效。取值范围为0-5,默认值为1,其中:

    • 0代表Nearest-neighbor;

    • 1代表Bi-linear(default);

    • 2代表Bi-quadratic;

    • 3代表Bi-cubic;

    • 4代表Bi-quartic;

    • 5代表Bi-quintic。

  • data_type:输出类型,取值范围为(uint8,float),默认为float类型。当设置为uint8时,输出类型为uint8 ,其他情况为float32。

  • interpolation:插值方法,此参数在mode为opencv时生效。默认为空,取值范围为(opencv的插值方式), 目前interpolation仅支持为空或opencv中的INTER_CUBIC两种插值方法,当interpolation为空时,默认使用INTER_LINEAR方式。

    以下为opencv中支持插值方式说明(目前支持插值方式后续迭代逐步支持):

    • INTER_NEAREST,最近插值;

    • INTER_LINEAR,双线性插值,当interpolation为空时,默认使用这种方法。

    • INTER_CUBIC,双三次插值4x4像素邻域立方插值。

    • INTER_AREA,使用像素面积关系采样。它可能图像抽取首选方法,因为可以提供无莫尔条纹结果。但是图像缩放时,它类似于INTER_NEAREST方法。

    • INTER_LANCZOS4,8x8邻域的Lanczos插值。

    • INTER_LINEAR_EXACT,位精确线性插值。

    • INTER_NEAREST_EXACT,位精确最近插值。这产生与PIL、scikit-image或Matlab中最近方法相同结果。

    • INTER_MAX,插值代码掩码。

    • WARP_FILL_OUTLIERS,标志,填充所有目标图像像素。如果其中一些对应于源图像异常值,则它们设置零。

    • WARP_INVERSE_MAP,标志,逆变换。

使用举例

  # 将输入图片大小调整为224*224,采用 opencv 的方式处理图片,插值的方式为双线性,输出为float32
  ResizeTransformer(target_size=(224, 224), mode='opencv', method=1)

  # 将输入图片大小调整为256*256,采用skimage的方式处理图片,插值的方式为双线性,输出为float32
  ResizeTransformer(target_size=(256, 256))

  # 将输入图片大小调整为256*256,采用skimage的方式处理图片,插值的方式为双线性,输出为uint8
  ResizeTransformer(target_size=(256, 256), data_type="uint8")

PILResizeTransformer

说明

使用PIL库调整图像大小操作。

参数

  • size:目标大小,值为元组,如(240,240)。

  • interpolation:指定插值方式,取值范围:(Image.NEAREST,Image.BILINEAR,Image.BICUBIC,Image.LANCZOS), 默认值为Image.BILINEAR。

    • Image.NEAREST:最近采样;

    • Image.BILINEAR:线性插值;

    • Image.BICUBIC:三次样条插值;

    • Image.LANCZOS:高质量采样滤波器。

使用举例

  # 将输入图片大小调整为256*256 插值的方式为线性插值
  PILResizeTransformer(size=256)

  # 将输入图片大小调整为256*256 插值的方式为高质量下采样滤波器
  PILResizeTransformer(size=256, interpolation=Image.LANCZOS)

ShortLongResizeTransformer

说明

按照比例输入图片进行缩放操作,新图片大小设置参数有关。操作方式如下:

  1. 以short_size的大小除以原图片高里最小值,以这个值为缩放比例系数。

  2. 缩放比例系数乘以原图片高中最大值,得到结果大于long_size的数值时,缩放比例系数变更为long_size除以原图片高中最大值。

  3. 使用opencv中的resize方法,根据上方得到缩放比例系数重新裁剪图片。

参数

  • short_size:预期裁剪短边长度。

  • long_size:预期裁剪长度。

  • include_im:默认值为True,设置为True时, 会返回除了返回处理图片, 还会返回原图片。

使用举例

  # 短边长度为20,长边长度为100,返回处理后的图片及原图片
  ShortLongResizeTransformer(short_size=20, long_size=100)

PadTransformer

说明

通过目标大小的size值除以输入图片或者高里最大值系数,然后使用这个系数乘以原有高,resize图片。 然后根据图片大小,除以size_divisor后向上取整后,再乘以size_divisor,为高,生成图片操作。

参数

  • size_divisor:大小除数 ,默认值为128。

  • target_size:目标大小,默认值为512。

使用举例

  # pad大小为1024*1024
  PadTransformer(size_divisor=1024, target_size=1024)

ShortSideResizeTransformer

说明

根据期望短边长度,使用现在长短比例,中心裁剪出新图片大小操作。

参数

  • short_size:预期短边长度。

  • data_type:输出结果类型,取值范围为(”float”,”uint8”),默认取值”float32”, 以 float32 类型输出,设置为uint8时,输出类型为uint8。

  • interpolation:指定插值方式,取值范围为 opencv 中采用插值方式,默认为空。

    目前interpolation仅支持为空或opencv中的INTER_CUBIC两种插值方法,当interpolation为空时,默认使用INTER_LINEAR方式。

    以下为opencv中支持插值方式说明(目前支持插值方式后续迭代逐步支持):

    • INTER_NEAREST,最近插值;

    • INTER_LINEAR,双线性插值,当interpolation为空时,默认使用这种方法。

    • INTER_CUBIC,双三次插值4x4像素邻域立方插值。

    • INTER_AREA,使用像素面积关系采样。它可能图像抽取首选方法,因为可以提供无莫尔条纹结果。但是图像缩放时,它类似于INTER_NEAREST方法。

    • INTER_LANCZOS4,8x8邻域的Lanczos插值。

    • INTER_LINEAR_EXACT,位精确线性插值。

    • INTER_NEAREST_EXACT,位精确最近插值。这产生与PIL、scikit-image或Matlab中最近方法相同结果。

    • INTER_MAX,插值代码掩码。

    • WARP_FILL_OUTLIERS,标志,填充所有目标图像像素。如果其中一些对应于源图像异常值,则它们设置零。

    • WARP_INVERSE_MAP,标志,逆变换。

使用举例

  # 将短边大小调整为256,插值方式为双线性插值
  ShortSideResizeTransformer(short_size=256)

  # 将短边大小调整为256,插值方式为8x8像素邻域内的Lanczos插值
  ShortSideResizeTransformer(short_size=256, interpolation=Image.LANCZOS4)

PaddedCenterCropTransformer

说明

使用填充方式图片中心进行裁剪操作。

.. attention::

适用于EfficientNet-lite相关实例模型。

计算方式为:

  1. 计算系数,int((float( image_size ) / ( image_size + crop_pad ))。

  2. 计算中心size的大小, 系数 * np.minimum( 原始图片高度, 原始图片宽度 ))。

  3. 根据计算出来的size大小,做中心裁剪。

参数

  • image_size:图片大小,默认值为224。

  • crop_pad:中心填充大小,默认值为32。

使用举例

  # 裁剪大小为240*240,填充值为32
  PaddedCenterCropTransformer(image_size=240, crop_pad=32)

  # 裁剪大小为224*224,填充值为32
  PaddedCenterCropTransformer()

BGR2RGBTransformer

说明

输入格式由BGR转成RGB的操作。

参数

  • data_format:数据格式,取值范围为(CHW,HWC),默认值为CHW。

使用举例

  # layout为NCHW时,做BGR转为RGB
  BGR2RGBTransformer()

  # layout为NHWC时,做BGR转为RGB
  BGR2RGBTransformer(data_format="HWC")

RGB2BGRTransformer

说明

输入格式由RGB转成BGR的操作。

参数

  • data_format:数据格式,取值范围为(CHW,HWC),默认值为CHW。

使用举例

  # layout为NCHW时,做RGB转成BGR
  RGB2BGRTransformer()

  # layout为NHWC时,做RGB转成BGR
  RGB2BGRTransformer(data_format="HWC")

RGB2GRAYTransformer

说明

输入格式由RGB转成GRAY的操作。

参数

  • data_format:输入的layout类型,取值范围(”CHW”,”HWC”),默认为”CHW”。

使用举例

  # layout为NCHW时,做RGB转成GRAY
  RGB2GRAYTransformer(data_format='CHW')

  # layout为NHWC时,做RGB转成GRAY
  RGB2GRAYTransformer(data_format='HWC')

BGR2GRAYTransformer

说明

输入格式由 BGR 转成 GRAY 的操作。

参数

  • data_format:输入的layout类型,取值范围 [”CHW”,”HWC”],默认值为”CHW”。

使用举例

  # layout为NCHW时,做BGR转成GRAY
  BGR2GRAYTransformer(data_format='CHW')

  # layout为NHWC时,做BGR转成GRAY
  BGR2GRAYTransformer(data_format='HWC')

RGB2GRAY_128Transformer

说明

输入格式由RGB转成GRAY_128的操作。GRAY_128取值范围为(-128,127)。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”,”HWC”],默认值为”CHW”,此项必填项。

使用举例

  # layout为NCHW时,做RGB转成GRAY_128
  RGB2GRAY_128Transformer(data_format='CHW')

  # layout为NHWC时,做RGB转成GRAY_128
  RGB2GRAY_128Transformer(data_format='HWC')

RGB2YUV444Transformer

说明

输入格式由RGB转成YUV444的操作。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”, “HWC”],默认值为”CHW”,此项必填项。

使用举例

  # layout为NCHW时,做BGR转成YUV444
  BGR2YUV444Transformer(data_format='CHW')

  # layout为NHWC时,做BGR转成YUV444
  BGR2YUV444Transformer(data_format='HWC')

BGR2YUV444Transformer

说明

输入格式由BGR转成YUV444的操作。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”,”HWC”],默认值为 “CHW”,此项必填项。

使用举例

  # layout为NCHW时,做BGR转成YUV444
  BGR2YUV444Transformer(data_format='CHW')

  # layout为NHWC时,做BGR转成YUV444
  BGR2YUV444Transformer(data_format='HWC')

BGR2YUV444_128Transformer

说明

输入格式由BGR转成YUV444_128的操作。YUV444_128取值范围为(-128,127)。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”,”HWC”],默认值为 “CHW”,此项必填项。

使用举例

  # layout为NCHW时,做BGR转成YUV444_128
  BGR2YUV444_128Transformer(data_format='CHW')

  # layout为NHWC时,做BGR转成YUV444_128
  BGR2YUV444_128Transformer(data_format='HWC')

RGB2YUV444_128Transformer

说明

输入格式由RGB转成YUV444_128的操作。YUV444_128取值范围为(-128,127)。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”,”HWC”],默认值为”CHW”,此项必填项。

使用举例

  # layout为NCHW时,做RGB转成 YUV444_128
  RGB2YUV444_128Transformer(data_format='CHW')

  # layout为NHWC时,做RGB转成 YUV444_128
  RGB2YUV444_128Transformer(data_format='HWC')

BGR2YUVBT601VIDEOTransformer

说明

输入格式由BGR转成YUV_BT601_Video_Range的操作。

YUV_BT601_Video_Range,某些摄像头输入数据是YUV BT601(Video Range)格式的,取值范围为16~235,该transformer就是适配这种格式数据产生的。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”,”HWC”],默认值为”CHW”,此项必填项。

使用举例

  # layout为 NCHW时,做BGR转成YUV_BT601_Video_Range
  BGR2YUVBT601VIDEOTransformer(data_format='CHW')

  # layout为NHWC时,做BGR转成YUV_BT601_Video_Range
  BGR2YUVBT601VIDEOTransformer(data_format='HWC')

RGB2YUVBT601VIDEOTransformer

说明

输入格式由RGB转成YUV_BT601_Video_Range的操作。

YUV_BT601_Video_Range,某些摄像头输入数据是YUV BT601(Video Range)格式的,取值范围为16~235,该transformer就是适配这种格式数据产生的。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”,”HWC”],默认值为”CHW”,此项必填项。

使用举例

  # layout为NCHW时,做RGB转成YUV_BT601_Video_Range
  RGB2YUVBT601VIDEOTransformer(data_format='CHW')

  # layout为NHWC时,做RGB转成YUV_BT601_Video_Range
  RGB2YUVBT601VIDEOTransformer(data_format='HWC')

YUVTransformer

说明

输入格式成YUV444的操作。

参数

  • color_sequence:颜色序列,此项必填项。

使用举例

  # 将BGR读入的图片转为YUV444
  YUVTransformer(color_sequence="BGR")

  # 将RGB读入的图片转为YUV444
  YUVTransformer(color_sequence="RGB")

ReduceChannelTransformer

说明

将C通道缩减单通道操作。该transformer主要针对于C通道,如shape为1*3*224*224 改为1*1*224*224。 使用时layout一定和data_format值对齐,避免造成删错通道。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”, “HWC”],默认值为”CHW”。

使用举例

  # 删除layout为NCHW的C通道
  ReduceChannelTransformer()
  # 或者
  ReduceChannelTransformer(data_format="CHW")

  # 删除layout为NHWC的C通道
  ReduceChannelTransformer(data_format="HWC")

BGR2NV12Transformer

说明

输入格式由BGR转成NV12的操作。

参数

  • data_format:输入的layout类型,取值范围为[”CHW”,”HWC”],默认值为”CHW”。

  • cvt_mode:cvt模式,取值范围为(rgb_calc,opencv),默认值为rgb_calc。

    • rgb_calc,采用mergeUV的方式处理图片;

    • opencv,采用opencv的方式处理图片。

使用举例

  # layout为NCHW时,由BGR转为NV12,采用rgb_calc模式处理图片
  BGR2NV12Transformer()
  # 或者
  BGR2NV12Transformer(data_format="CHW")

  # layout为NHWC时,由BGR转为NV12,采用opencv模式处理图片
  BGR2NV12Transformer(data_format="HWC", cvt_mode="opencv")

RGB2NV12Transformer

说明

输入格式由RGB转成NV12的操作。

参数

  • data_format:输入的 layout 类型,取值范围 [”CHW”, “HWC”], 默认值为”CHW”。

  • cvt_mode:cvt模式,取值范围为(rgb_calc,opencv),默认值为rgb_calc。

    • rgb_calc,采用mergeUV的方式处理图片;

    • opencv,采用opencv的方式处理图片。

使用举例

  # layout为NCHW时,有RGB转为NV12,采用rgb_calc模式处理图片
  RGB2NV12Transformer()
  # 或者
  RGB2NV12Transformer(data_format="CHW")

  # layout为NHWC时,有RGB转为NV12,采用opencv模式处理图片
  RGB2NV12Transformer(data_format="HWC", cvt_mode="opencv")

NV12ToYUV444Transformer

说明

输入格式由NV12转成YUV444的操作。

参数

  • target_size:目标大小,值为元组,如(240,240)。

  • yuv444_output_layout:yuv444输出的layout,取值范围为(HWC,CHW),默认值为”HWC”。

使用举例

  # layout为NCHW ,大小为768*768, nv12转yuv444
  NV12ToYUV444Transformer(target_size=(768, 768))

  # layout为NHWC ,大小为224*224, nv12转yuv444
  NV12ToYUV444Transformer((224, 224), yuv444_output_layout="HWC")

WarpAffineTransformer

说明

用于图像仿射变换操作。

参数

  • input_shape:输入的shape值。

  • scale:乘以系数。

使用举例

  # 大小为512*512,长边长度为1.0
  WarpAffineTransformer((512, 512), 1.0)

F32ToS8Transformer

说明

用于输入格式从float32转换为int8的操作。

参数:不涉及。

使用举例

  # 输入格式从 float32转为 int8
  F32ToS8Transformer()

F32ToU8Transformer

说明

用于输入格式从float32转换为uint8的操作。

参数:不涉及。

使用举例

  # 输入格式从 float32 转为 uint8
  F32ToU8Transformer()

7.6.2.2. 示例YOLOv5x模型使用说明

  1. YOLOv5x模型:

  • 可以从URL:yolov5-2.0下载相应的pt文件。

    在clone代码时,请确认使用的Tags是 v2.0 ,否则导致转换失败。

  • md5sum码:

md5sum File
2e296b5e31bf1e1b6b8ea4bf36153ea5 yolov5l.pt
16150e35f707a2f07e7528b89c032308 yolov5m.pt
42c681cf466c549ff5ecfe86bcc491a0 yolov5s.pt
069a6baa2a741dec8a2d44a9083b6d6e yolov5x.pt
  • 为了更好适配后处理代码,我们在ONNX模型导出对Github代码如下修改 (代码参见:https://github.com/ultralytics/yolov5/blob/v2.0/models/yolo.py):


    def forward(self, x):
        # x = x.copy()  # for profiling
        z = []  # inference output
        self.training |= self.export
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            #  x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
            x[i] = x[i].permute(0, 2, 3, 1).contiguous()
  • 说明:除了每个输出分支尾部从4维到5维的reshape(即将channel从255拆分成3x85),然后将layout从NHWC转换成NCHW再输出。

    以下左图修改模型某一输出节点可视化图,右图修改对应输出节点可视化图。

    yolov5

  • 下载完成通过脚本 https://github.com/ultralytics/yolov5/blob/v2.0/models/export.py 进行pt文件到ONNX文件转换。

  • 注意事项

    使用export.py脚本时,请注意:

    1. 由于地瓜AI工具链支持的ONNX opset版本1011,请torch.onnx.exportopset_version 参数根据使用版本进行修改。

    2. torch.onnx.export 部分默认输入名称参数'images' 改为 'data',与模型转换示例的YOLOv5x示例脚本保持一致。

    3. parser.add_argument 部分默认数据输入尺寸640x640改为模型转换示例包YOLOv5x示例的672x672。

7.6.2.3. 模型精度调优checklist

严格按照下图步骤1-5来进行模型精度验证保留每个步骤代码结果:

model_accuracy_check

进行排查前,请确认当前模型转换所用的Docker镜像转换环境版本,并保留版本信息

1. 验证浮点onnx模型推理结果

进入模型转换环境,来测试浮点onnx模型(特指从DL框架导出的onnx模型)的单张结果,此步骤结果训练模型推理结果完全一致(nv12格式除外,可能引入少许差异)

参考如下示例代码步骤,来确认浮点onnx模型推理步骤、数据预处理、后处理代码是否正确!


  from horizon_tc_ui import HB_ONNXRuntime
  import numpy as np
  import cv2

  def preprocess(input_name):
      # BGR->RGB、Resize、CenterCrop···
      # HWC->CHW
      # normalization
      return data

  def main():
      # 加载模型文件
      sess = HB_ONNXRuntime(model_file=MODEL_PATH)
      # 获取输入&输出节点名称
      input_names = [input.name for input in sess.get_inputs()]
      output_names = [output.name for output in sess.get_outputs()]
      # 准备模型输入数据
      feed_dict = dict()
      for input_name in input_names:
          feed_dict[input_name] = preprocess(input_name)

      # 原始浮点onnx,数据dtype=float32, 开始模型推理,推理的返回值是一个list,依次与output_names指定名称一一对应
      outputs = sess.run(output_names, feed_dict)

      # 后处理
      postprocess(outputs)

  if __name__ == '__main__':
      main()

2. 验证yaml配置文件以及前、后处理代码正确性

测试 original_float.onnx 模型单张结果,应浮点onnx模型推理结果完全一致(nv12格式除外,由于nv12数据本身有损,可能引入少许差异)

使用开源工具 Netron 打开 original_float.onnx 模型,并查看预处理节点 HzPreprocess 算子详细属性,获取我们 数据预处理 需要参数:data_formatinput_type

由于HzPreprocess节点存在,会使得转换模型预处理操作可能原始模型有所不同,该算子进行模型转换时,根据yaml配置文件配置参数(input_type_rt、input_type_train以及norm_type、mean_value、scale_value)来决定是否模型加入HzPreprocess节点,预处理节点生成细节,请参考PTQ原理步骤详解章节norm_type 配置参数说明 内容,另外预处理节点出现转换过程产生所有产物中。

理想状态下,这个HzPreprocess节点应该完成 input_type_rt 到 input_type_train 的完整转换, 但实际情况整个type转换过程需要使用地瓜AI芯片硬件完成,但ONNX模型里面没有包含硬件转换部分,因此ONNX的真实输入类型使用一种中间类型,这种中间类型就是硬件对 input_type_rt 的处理结果类型, 故针对图像输入数据类型为:RGB/BGR/NV12/YUV444/GRAY,并且数据dtype= uint8的模型时,在预处理代码需要-128操作,featuremap 数据类型因为使用是float32,因此预处理代码不需要-128操作; original_float.onnx的数据layout(NCHW/NHWC)会保持原始浮点模型输入layout一致。

参考如下示例代码步骤,来确认 original_float.onnx 模型推理步骤、数据预处理、后处理代码是否正确!

数据预处理部分建议参考使用地瓜模型转换 horizon_model_convert_sample 示例包中的caffe、onnx等示例模型预处理步骤方法


  from horizon_tc_ui import HB_ONNXRuntime
  import numpy as np
  import cv2

  def preprocess(input_name):
      # BGR->RGB、Resize、CenterCrop···
      # HWC->CHW(通过onnx模型输入节点的具体shape来判断是否需要做layout转换)
      # normalization(若已通过yaml文件将norm操作放入了模型中,则不要在预处理中做重复操作)
      #-128(除featuremap输入外,其他类型输入均需做-128操作,即由unit8转为int8)
      return data

  def main():
      # 加载模型文件
      sess = HB_ONNXRuntime(model_file=MODEL_PATH)
      # 获取输入&输出节点名称
      input_names = [input.name for input in sess.get_inputs()]
      output_names = [output.name for output in sess.get_outputs()]
      # 准备模型输入数据
      feed_dict = dict()
      for input_name in input_names:
          feed_dict[input_name] = preprocess(input_name)
      #图像输入的模型(RGB/BGR/NV12/YUV444/GRAY),数据dtype= uint8, featuremap模型,数据dtype=float32,
      outputs = sess.run(output_names, feed_dict)

      # 后处理
      postprocess(outputs)

  if __name__ == '__main__':
      main()

3. 验证模型优化阶段引入精度误差

测试 optimize_float.onnx 模型单张结果,应与original_float.onnx推理结果完全一致

使用开源工具 Netron 打开 optimize_float.onnx 模型,并查看预处理节点 HzPreprocess 算子详细属性,获取我们数据预处理需要参数:data_formatinput_type;

optimize_float.onnx模型推理参考如下示例代码步骤,来确认 optimize_float.onnx 模型推理步骤、数据预处理、后处理代码是否正确!

数据预处理部分建议参考使用地瓜模型转换 horizon_model_convert_sample 示例包中的caffe、onnx等示例模型预处理步骤方法


  from horizon_tc_ui import HB_ONNXRuntime
  import numpy as np
  import cv2

  def preprocess(input_name):
      # BGR->RGB、Resize、CenterCrop···
      # HWC->CHW(通过onnx模型输入节点的具体shape来判断是否需要做layout转换)
      # normalization(若已通过yaml文件将norm操作放入了模型中,则不要在预处理中做重复操作)
      #-128(除featuremap输入外,其他类型输入均需做-128操作,即由unit8转为int8)
      return data

  def main():
      # 加载模型文件
      sess = HB_ONNXRuntime(model_file=MODEL_PATH)
      # 获取输入&输出节点名称
      input_names = [input.name for input in sess.get_inputs()]
      output_names = [output.name for output in sess.get_outputs()]
      # 准备模型输入数据
      feed_dict = dict()
      for input_name in input_names:
          feed_dict[input_name] = preprocess(input_name)
      #图像输入的模型(RGB/BGR/NV12/YUV444/GRAY),数据dtype= uint8, featuremap模型,数据dtype=float32
      outputs = sess.run(output_names, feed_dict)

      # 后处理
      postprocess(outputs)

  if __name__ == '__main__':
      main()

4. 验证量化精度是否满足预期

测试quantized.onnx的精度指标。

使用开源工具 Netron 打开 quantized.onnx 模型,并查看预处理节点 HzPreprocess 算子详细属性,获取我们数据预处理需要参数:data_formatinput_type;

quantized.onnx模型推理参考如下示例代码步骤,来确认 quantized.onnx 模型推理步骤、数据预处理、后处理代码是否正确!

数据预处理部分建议参考使用地瓜模型转换 horizon_model_convert_sample 示例包中的caffe、onnx等示例模型预处理步骤方法


  from horizon_tc_ui import HB_ONNXRuntime
  import numpy as np
  import cv2

  def preprocess(input_name):
      # BGR->RGB、Resize、CenterCrop···
      # HWC->CHW(通过onnx模型输入节点的具体shape来判断是否需要做layout转换)
      # normalization(若已通过yaml文件将norm操作放入了模型中,则不要在预处理中做重复操作)
      #-128(除featuremap输入外,其他类型输入均需做-128操作,即由unit8转为int8)
      return data

  def main():
      # 加载模型文件
      sess = HB_ONNXRuntime(model_file=MODEL_PATH)
      # 获取输入&输出节点名称
      input_names = [input.name for input in sess.get_inputs()]
      output_names = [output.name for output in sess.get_outputs()]
      # 准备模型输入数据
      feed_dict = dict()
      for input_name in input_names:
          feed_dict[input_name] = preprocess(input_name)
      #图像输入的模型(RGB/BGR/NV12/YUV444/GRAY),数据dtype= uint8,featuremap模型,数据dtype=float32
      outputs = sess.run(output_names, feed_dict)

      # 后处理
      postprocess(outputs)

  if __name__ == '__main__':
      main()

5. 确保模型编译过程无误板端推理代码正确

使用 hb_verifier 工具验证quantized.onnx和.bin的一致性,模型输出至少满足小数点后2-3位对齐

hb_verifier 工具(详细介绍参考)的使用方法,请参考PTQ原理步骤详解章节hb_verifier 工具 内容。

模型一致性校验通过,则仔细检查开发板端的前、后处理代码!

若quantized.onnx与.bin模型一致性校验失败,请联系地瓜技术人员

7.6.2.4. 模型量化yaml配置文件模板

Caffe模型量化yaml文件模板

新建 caffe_config.yaml 文件,并直接拷贝以下内容,然后填写标记必选参数参数即可进行模型转换,若需了解参数使用说明,可参考 yaml配置文件详解 章节内容。


# Copyright (c) 2020 Horizon Robotics.All Rights Reserved.

# 模型转化相关的参数
model_parameters:

  # 必选参数
  # Caffe浮点网络数据模型文件, 例如:caffe_model: './horizon_ultra_caffe.caffemodel'
  caffe_model: ''

  # 必选参数
  # Caffe网络描述文件, 例如:prototxt: './horizon_ultra_caffe.prototxt'
  prototxt: ''

  march: "bayes-e"
  layer_out_dump: False
  working_dir: 'model_output'
  output_model_file_prefix: 'horizon_x5'

# 模型输入相关参数
input_parameters:

  input_name: ""
  input_shape: ''
  input_type_rt: 'nv12'
  input_layout_rt: ''

  # 必选参数
  # 原始浮点模型训练框架中所使用训练的数据类型,可选的值为rgb/bgr/gray/featuremap/yuv444, 例如:input_type_train: 'bgr'
  input_type_train: ''

  # 必选参数
  # 原始浮点模型训练框架中所使用训练的数据排布, 可选值为 NHWC/NCHW, 例如:input_layout_train: 'NHWC'
  input_layout_train: ''

  #input_batch: 1

  # 必选参数
  # 原始浮点模型训练框架中所使用数据预处理方法,可配置:no_preprocess/data_mean/data_scale/data_mean_and_scale
  # no_preprocess 不做任何操作,对应的 mean_value  或者 scale_value 均无需配置
  # data_mean 减去通道均值mean_value,对应的 mean_value 需要配置,并注释掉scale_value
  # data_scale 对图像像素乘以data_scale系数,对应的 scale_value需要配置,并注释掉mean_value
  # data_mean_and_scale 减去通道均值后再乘以scale系数,标识下方对应的 mean_value  和 scale_value 均需配置
  norm_type: ''

  # 必选参数
  # 图像减去的均值, 如果是通道均值,value之间必须用空格分隔
  # 例如:mean_value: 128.0 或者 mean_value: 111.0 109.0 118.0
  mean_value:

  # 必选参数
  # 图像预处理缩放比例,如果是通道缩放比例,value之间必须用空格分隔,计算公式:scale = 1/std
  # 例如:scale_value: 0.0078125 或者 scale_value: 0.0078125 0.001215 0.003680
  scale_value:

# 模型量化相关参数
calibration_parameters:

  # 必选参数
  # 模型量化的参考图像的存放目录,图片格式支持Jpeg、Bmp等格式,图片来源一般是从测试集中选择100张图片,并要覆盖典型场景,不要是偏僻场景,如过曝光、饱和、模糊、纯黑、纯白等图片
  # 请根据 02_preprocess.sh 脚本中的文件夹路径来配置,例如:cal_data_dir: './calibration_data_yuv_f32'
  cal_data_dir: ''

  cal_data_type: 'float32'
  calibration_type: 'default'

# 编译器相关参数
compiler_parameters:

  compile_mode: 'latency'
  debug: False
  optimize_level: 'O3'

ONNX模型量化yaml文件模板

新建 onnx_config.yaml 文件,并直接拷贝以下内容,然后填写标记必选参数参数即可进行模型转换,若需了解参数使用说明,可参考 yaml配置文件详解 章节内容。


# Copyright (c) 2020 Horizon Robotics.All Rights Reserved.

# 模型转化相关的参数
model_parameters:

  # 必选参数
  # Onnx浮点网络数据模型文件, 例如:onnx_model: './horizon_ultra_onnx.onnx'
  onnx_model: ''

  march: "bayes-e"
  layer_out_dump: False
  working_dir: 'model_output'
  output_model_file_prefix: 'horizon_ultra'

# 模型输入相关参数
input_parameters:

  input_name: ""
  input_shape: ''
  input_type_rt: 'nv12'
  input_layout_rt: ''

  # 必选参数
  # 原始浮点模型训练框架中所使用训练的数据类型,可选的值为rgb/bgr/gray/featuremap/yuv444, 例如:input_type_train: 'bgr'
  input_type_train: ''

  # 必选参数
  # 原始浮点模型训练框架中所使用训练的数据排布, 可选值为 NHWC/NCHW, 例如:input_layout_train: 'NHWC'
  input_layout_train: ''

  #input_batch: 1

  # 必选参数
  # 原始浮点模型训练框架中所使用数据预处理方法,可配置:no_preprocess/data_mean/data_scale/data_mean_and_scale
  # no_preprocess 不做任何操作,对应的 mean_value  或者 scale_value 均无需配置
  # data_mean 减去通道均值mean_value,对应的 mean_value 需要配置,并注释掉scale_value
  # data_scale 对图像像素乘以data_scale系数,对应的 scale_value需要配置,并注释掉mean_value
  # data_mean_and_scale 减去通道均值后再乘以scale系数,标识下方对应的 mean_value  和 scale_value 均需配置
  norm_type: ''

  # 必选参数
  # 图像减去的均值, 如果是通道均值,value之间必须用空格分隔
  # 例如:mean_value: 128.0 或者 mean_value: 111.0 109.0 118.0
  mean_value:

  # 必选参数
  # 图像预处理缩放比例,如果是通道缩放比例,value之间必须用空格分隔,计算公式:scale = 1/std
  # 例如:scale_value: 0.0078125 或者 scale_value: 0.0078125 0.001215 0.003680
  scale_value:

# 模型量化相关参数
calibration_parameters:

  # 必选参数
  # 模型量化的参考图像的存放目录,图片格式支持Jpeg、Bmp等格式,图片来源一般是从测试集中选择100张图片,并要覆盖典型场景,不要是偏僻场景,如过曝光、饱和、模糊、纯黑、纯白等图片
  # 请根据 02_preprocess.sh 脚本中的文件夹路径来配置,例如:cal_data_dir: './calibration_data_yuv_f32'
  cal_data_dir: ''

  cal_data_type: 'float32'
  calibration_type: 'default'

# 编译器相关参数
compiler_parameters:

  compile_mode: 'latency'
  debug: False
  optimize_level: 'O3'

7.6.2.5. 定点.bin模型上板多batch使用说明

  • 1.模型转换时,在yaml配置文件通过input_batch配置batch_size;

  • 2.上板bin模型输入时,以原始模型维度1×3×224×224,修改input_batch为10,也就是10×3×224×224这个维度举例:

  • 准备数据:

    Image图像数据:设置 aligned_shape = valid_shape ,然后单张数据准备方式,把10张图片依次顺序写入申请内存空间;

    FeatureMap数据:按aligned_shape把数据padding好,然后按单batch数据准备方式,把10份数据依次顺序写入申请内存空间,模型推理流程单batch模型推理流程一致;

7.6.2.6. 自定义算子开发说明

简介

地瓜工具链已经支持丰富算子,在大多数情况下,您模型应该可以通过所述模型转换顺利部署地瓜计算平台上。 已支持算子情况可以参考 算子支持约束列表 章节。 少部分算子支持情况下,我们强烈建议尝试替换算子可能性,这样有利于地瓜计算平台能力充分发挥出来,且开发成本低。

自定义算子提供CPU上算子开发能力,一个完整自定义算子应用过程包括创建模板、算子实现、算子编译、含自定义算子模型转换运行自定义op模型几个阶段。具体流程如下所示:

custom_op_development

如图所示,定义自定义OP需要部分任务:在模型转换阶段,需要提供自定义OP的python代码;在模拟器/上板运行推理阶段,需要提供自定义OP的C++代码。 要确保部分代码运算一致性。

自定义算子模型转换

模型文件修改

准备自定义算子实现后,为了算子应用起来,您需要原始模型文件模型转换配置两个方面做出相应调整 (下面分别Caffe 模型ONNX 模型为例)。

Caffe 模型

原始模型文件中,将自定义算子对应算子类型标记Custom,并提供一组 custom_param,示例如下。


  layer {
    name: "hr_op"
    type: "Custom"
    bottom: "res3d_in"
    top: "res3d"
    custom_param {
      kind: "CustomIdentity"
      shape {
        dim: 1
        dim: 512
        dim: 28
        dim: 28
      }
      params: "'kernel_size': 10 \n'threshold': 0.5"
    }
  }

以上完整 custom_param 示例中:

  • kind自定义算子内部实现名称,该自定义OP为恒等OP,因此命名CustomIdentity,该名称后续Python及C++代码中均体现。

  • shape算子输出尺寸,需要完整指定。

  • params算子传入参数指定形式'param_name': param_value,多个参数之间使用 \n 分隔。

模型转换配置中,使用自定义算子需要配置文件加入一个自定义op参数如下:


  #...

  custom_op:
    # 自定义op的校准方式
    custom_op_method: register

    # 自定义OP的实现文件
    op_register_files: sample_custom.py

对于 Caffe 模型,以上参数组中两个参数必须配置的。 custom_op_method 固定使用 registerop_register_files自定义算子计算实现文件,请使用相对路径。

完成这些配置后,模型转换后续步骤其他一般模型转换过程一致。

ONNX 模型

1.含有自定义算子的Onnx模型获取:

  • 从pytorch等其他框架转换


import torch
from horizon_nn.horizon_onnx.onnx_pb import TensorProto
from torch.onnx.symbolic_helper import parse_args
from torch.onnx.utils import register_custom_op_symbolic
from torch import Tensor

model = torch.hub.load('pytorch/vision:v0.10.0', 'googlenet', pretrained=True)

def _transform_input(x: Tensor) -> Tensor:
    return x

model._transform_input = _transform_input

@parse_args("v", "v")
def horizon_pool(g, input, output_size):
    return g.op(
        'horizon.custom::PyOp', #required, ! must be 'horizon.custom' domain !
        input,
        class_name_s="GlobalAveragePool",  #required ! must match the class def name in sample_custom python file !
        compute_s="compute",  #optional, 'compute' by default
        module_s="sample_custom",  #required ! must match the file name of the "op_register_files" !
        input_types_i=[TensorProto.FLOAT],  #required
        output_types_i=[TensorProto.FLOAT],  #required
        output_shape_s=["1, 1024, 1, 1"]) #required

d_input = torch.rand(1, 3, 224, 224)
register_custom_op_symbolic('::adaptive_avg_pool2d',
                            horizon_pool,
                            opset_version=11)
torch.onnx.export(model, d_input, "googlenet_cop.onnx", opset_version=11)
  • 直接生成onnx模型

参考代码:


import onnx
import numpy as np
from onnx import helper, checker, shape_inference, numpy_helper, TensorProto


def make_normal_data(shape):
    return np.random.normal(loc=0.0, scale=1.0, size=shape).astype(np.float32)


# conv
def make_simple_model():

    # create nodes
    conv_input_shape = (1, 3, 224, 224)
    conv_output_shape = (1, 3, 224, 224)

    add_param_shape = (1, 3, 224, 224)
    add_1_param_data = np.zeros(add_param_shape).astype(np.float32)
    add_2_param_data = np.ones(add_param_shape).astype(np.float32)

    conv_weight_shape = (3, 3, 3, 3)
    conv_output_shape = (1, 3, 224, 224)
    conv_weight_data = make_normal_data(conv_weight_shape)

    add_1_node = helper.make_node(
        "PyOp",  # required, 类型必须是'PyOp'
        name="add_1",  # required, 不同的op名称不能相同
        inputs=["input0", "add_1_param"],  # required, 需要是一个list, 且需要与实现文件中的输入数量保持一致
        outputs=["add_1_out"],  # required, 需要是一个list, 且需要与实现文件中的输出数量保持一致
        domain="horizon.cop1",  # required, 不同实现逻辑的自定义算子实现需要通过不同的domain名称来实现
        class_name="Cop1",  # required, 需要与自定义算子的实现文件中的class名称一致
        module="custom_op.horizon_ops",  # required, 需要与包含自定义算子的实现文件的路径一致
        compute="compute",  # required, 需要与自定义算子实现class中的计算逻辑函数一致
        input_types=[
            TensorProto.FLOAT,
            TensorProto.FLOAT,
        ],  # required, 需要是一个list, 其长度需要与该算子的inputs属性数量一致, 且与实现文件中的输入数量保持一致
        output_types=[
            TensorProto.FLOAT
        ],  # required, 需要是一个list, 其长度需要与该算子的outputs属性数量一致, 且与实现文件中的输出数量保持一致
        output_shape=["1, 3, 224, 224"],  # optional, 在模型中未添加pyop的输出 value_info时, 必须填写
    )

    add_2_node = helper.make_node(
        "PyOp",
        name="add_2",
        inputs=["input1", "add_1_out", "add_2_param"],
        outputs=["add_2_out", "output0"],
        domain="horizon.cop2",
        class_name="Cop2",
        module="custom_op.horizon_ops",
        compute='compute',
        input_types=[TensorProto.FLOAT, TensorProto.FLOAT,
                      TensorProto.FLOAT],  #required
        output_types=[TensorProto.FLOAT, TensorProto.FLOAT],  #required
        output_shape=["1, 3, 224, 224", "1, 3, 224, 224"])

    conv_1_node = helper.make_node("Conv",
                                    inputs=["add_2_out", "W0"],
                                    outputs=["output1"],
                                    dilations=(1, 1),
                                    group=1,
                                    kernel_shape=(3, 3),
                                    pads=(1, 1, 1, 1),
                                    name="conv_1")
    # nodes
    nodes = [add_1_node, add_2_node, conv_1_node]

    # inputs
    model_input_1 = helper.make_tensor_value_info("input0", TensorProto.FLOAT,
                                                  conv_input_shape)
    model_input_2 = helper.make_tensor_value_info("input1", TensorProto.FLOAT,
                                                  conv_input_shape)

    # Outputs
    model_output_1 = helper.make_tensor_value_info("output0",
                                                    TensorProto.FLOAT,
                                                    conv_output_shape)
    model_output_2 = helper.make_tensor_value_info("output1",
                                                    TensorProto.FLOAT,
                                                    conv_output_shape)

    # Intermediate tensors
    add_1_out = helper.make_tensor_value_info("add_1_out", TensorProto.FLOAT,
                                              conv_output_shape)
    add_2_out = helper.make_tensor_value_info("add_2_out", TensorProto.FLOAT,
                                              conv_output_shape)

    # create constant tensor
    W0_tensor = helper.make_tensor("W0", TensorProto.FLOAT, conv_weight_shape,
                                    conv_weight_data.flatten())

    add_1_param = helper.make_tensor("add_1_param",
                                      TensorProto.FLOAT, add_param_shape,
                                      add_1_param_data.flatten())
    add_2_param = helper.make_tensor("add_2_param",
                                      TensorProto.FLOAT, add_param_shape,
                                      add_2_param_data.flatten())

    # make graph
    graph = helper.make_graph(
        nodes,
        "simple_conv_model",
        inputs=[model_input_1, model_input_2],  # input
        outputs=[model_output_1, model_output_2],  # output
        initializer=[W0_tensor, add_1_param, add_2_param],  # initializer
        value_info=[add_1_out, add_2_out],  # value_info
    )

    # make model
    onnx_model = helper.make_model(graph,
                                    opset_imports=[
                                        helper.make_opsetid("", 11),
                                        helper.make_opsetid("horizon.cop1", 1),
                                        helper.make_opsetid("horizon.cop2", 1)
                                    ],
                                    producer_name="onnx-test")

    # shape inference
    onnx_model = shape_inference.infer_shapes(onnx_model)

    # # model check
    checker.check_model(onnx_model)

    # save model
    onnx.save(onnx_model, "custom_op.onnx")

注意:

Onnx模型中PyOp属性注意点:

  • domain属性一定设置,不然的话默认成onnx标准domain从而报错。不同实现自定义算子需要设置不同的domain下。

  • module需要注册使用注册文件同名。若注册文件当前目录文件夹中,则需要修改module内容。例如: 若 sample_custom.py当前路径的custom_op 文件夹中,则该module应设置custom_op.sample_custom

  • 目前仅onnx模型支持多种类型自定义算子,如需要其他框架支持多种类型自定义算子联系地瓜技术支持人员。

2.与 Caffe 模型一致,需要模型转换配置中,使用自定义算子需要配置文件加入一个自定义op参数如下:


#...

custom_op:
  # 自定义op的校准方式
  custom_op_method: register

  # 自定义OP的实现文件
  op_register_files: sample_custom.py

对于 ONNX 模型,以上参数组中两个参数必须配置的。 custom_op_method 固定使用 registerop_register_files自定义算子计算实现文件,请使用相对路径。

完成这些配置后,模型转换后续步骤其他一般模型转换过程一致。

算子实现

模型转换阶段,需要提供自定义算子的Python实现,工具利用实现函数完成模型校准必需推理阶段。

注意:

  • 由于工具在PTQ转换过程working_dir工作目录,我们强烈建议算子实现涉及工作目录配置时,配置绝对路径,

  • 需要配置相对路径,请以 working_dir工作目录进行相对路径指定。

Python模板文件(sample_custom.py)如下:


from horizon_nn.custom.op_registration import op_implement_register, op_shape_infer_register

@op_implement_register("CustomIdentity")
class CustomIdentity(object):
    def __init__(self, kernel_size, threshold):
        self._kernel_size = kernel_size
        self._default_threshold = threshold

    def compute(self, X):
        return X

@op_shape_infer_register("CustomIdentity")
def infer_shape(inputs_shape):
    outputs_shape = inputs_shape
    return outputs_shape

custom_op示例配置文件(horizon_ops.py)如下:


from horizon_nn.custom.op_registration import op_implement_register

@op_implement_register("Cop1")
class Cop1(object):
    def __init__(self, ):
        pass

    def compute(self, x1, x2):
        out = x1 + x2 + 1
        return out


@op_implement_register("Cop2")
class Cop2(object):
    def __init__(self, ):
        pass

    def compute(self, x1, x2, x3):
        out = x1 + x2 + x3 + 1
        return out, out

文件名字(sample_custom.py)需要填入模型转换的yaml配置文件op_register_files ,否则工具无法正常import自定义算子定义, 并且修饰op_implement_register 注册的custom op类名称 CustomIdentity 需要与Caffe自定义OP的属性 kind 或者Onnx自定义OP的属性 class_name 一致。

对于 Caffe 模型, init 函数参数(kernel_size, threshold)都通过prototxt文件params 传入的, 用于自定义op模块初始化。 op_shape_infer_register 用于Caffe模型算子shape注册。

对于 Onnx 模型,自定义op的shape解析两种方式,可以通过创建onnx模型时,将pyop输出的value_info添加到onnx模型中, 或者对应的pyop中创建output_shape属性。 还需要注意自定义算子module 必须存放自定义算子实现文件保持一致, 如果属性设置custom_op.horizon_ops , 则自定义算子实现文件名称为 horizon_ops ,且放在 custom_op文件夹中, 保持与onnx模型层级关系。 由于同一个domain中同名算子实现必须相同, 因此不同自定义算子domain 属性需要不同。

上述操作完成即可进行浮点定点操作,得到相应的bin文件。

自定义算子上板运行

拿到bin文件后,还不能直接开发板运行。在运行之前需要提供自定义算子的C++代码实现。 您可以使用下文提供模板进行修改添加示例代码进行使用。

如果只是希望测试自定义算子功能,也可以直接使用我们提供模板文件,模板文件中将输入直接赋值输出使用, 所以这个自定义算子不会结果造成任何影响。

自定义算子C++模板

Runtime模板文件如下:

  // custom_identity_add1.h
  #ifndef ADVANCED_SAMPLES_CUSTOM_IDENTITY_ADD1_H_
  #define ADVANCED_SAMPLES_CUSTOM_IDENTITY_ADD1_H_

  #include <string>
  #include <vector>

  #include "dnn/hb_dnn.h"
  #include "dnn/plugin/hb_dnn_layer.h"
  #include "dnn/plugin/hb_dnn_ndarray.h"

  namespace hobot {
  namespace dnn {

  Layer *Cop1_layer_creator();

  class Cop1 : public Layer {
  public:
    Cop1() = default;
    ~Cop1() override = default;

  public:
    int32_t Init(const Attribute &attributes) override;

    int32_t Forward(const std::vector<NDArray *> &bottomBlobs,
                    std::vector<NDArray *> &topBlobs,
                    const hbDNNInferCtrlParam *inferCtrlParam) override;

    std::string GetType() const override { return "Cop1"; }

    uint32_t GetInputCount() const override { return num_args_; }

  private:
    std::string custom_op_name_;
    int32_t num_args_;
  };

  }  // namespace dnn
  }  // namespace hobot

  #endif
 // custom_identity_add1.cpp
  #include "custom_identity_add1.h"

  namespace hobot {
  namespace dnn {

  Layer *Cop1_layer_creator() { return new Cop1; }

  int32_t Cop1::Init(const Attribute &attributes) {
    // unused attribute, just demonstrating
    attributes.GetAttributeValue(&custom_op_name_, "custom_op_name");
    // node's input count
    attributes.GetAttributeValue(&num_args_, "num_args");
    return 0;
  }

  int32_t Cop1::Forward(const std::vector<NDArray *> &bottomBlobs,
                        std::vector<NDArray *> &topBlobs,
                        const hbDNNInferCtrlParam *inferCtrlParam) {
    const NDArray *input0 = bottomBlobs[0];
    const NDArray *input1 = bottomBlobs[1];
    NDArray *out = topBlobs[0];

    const auto *input0_data = input0->Dptr<float>();
    const auto *input1_data = input1->Dptr<float>();

    auto *out_data = out->Dptr<float>();
    uint32_t size = out->Size();

    for (uint32_t i = 0U; i < size; i++) {
      out_data[i] = input0_data[i] + input1_data[i] + 1;
    }
    return 0;
  }
  }  // namespace dnn
  }  // namespace hobot
  // custom_identity_add2.h
  #ifndef ADVANCED_SAMPLES_CUSTOM_IDENTITY_ADD2_H_
  #define ADVANCED_SAMPLES_CUSTOM_IDENTITY_ADD2_H_

  #include <string>
  #include <vector>

  #include "dnn/hb_dnn.h"
  #include "dnn/plugin/hb_dnn_layer.h"
  #include "dnn/plugin/hb_dnn_ndarray.h"

  namespace hobot {
  namespace dnn {

  Layer *Cop2_layer_creator();

  class Cop2 : public Layer {
  public:
    Cop2() = default;
    ~Cop2() override = default;

  public:
    int32_t Init(const Attribute &attributes) override;

    int32_t Forward(const std::vector<NDArray *> &bottomBlobs,
                    std::vector<NDArray *> &topBlobs,
                    const hbDNNInferCtrlParam *inferCtrlParam) override;

    std::string GetType() const override { return "Cop2"; }

    uint32_t GetInputCount() const override { return num_args_; }

    uint32_t GetOutputCount() const override { return 2U; }

  private:
    std::string custom_op_name_;
    int32_t num_args_;
  };

  }  // namespace dnn
  }  // namespace hobot

  #endif
// custom_identity_add2.cpp
  #include "custom_identity_add2.h"

  namespace hobot {
  namespace dnn {

  Layer *Cop2_layer_creator() { return new Cop2; }

  int32_t Cop2::Init(const Attribute &attributes) {
    // unused attribute, just demonstrating
    attributes.GetAttributeValue(&custom_op_name_, "custom_op_name");
    // node's input count
    attributes.GetAttributeValue(&num_args_, "num_args");
    return 0;
  }

  int32_t Cop2::Forward(const std::vector<NDArray *> &bottomBlobs,
                        std::vector<NDArray *> &topBlobs,
                        const hbDNNInferCtrlParam *inferCtrlParam) {
    const NDArray *input0 = bottomBlobs[0];
    const NDArray *input1 = bottomBlobs[1];
    const NDArray *input2 = bottomBlobs[2];
    NDArray *out0 = topBlobs[0];
    NDArray *out1 = topBlobs[1];

    const auto *input0_data = input0->Dptr<float>();
    const auto *input1_data = input1->Dptr<float>();
    const auto *input2_data = input2->Dptr<float>();

    auto *out0_data = out0->Dptr<float>();
    auto *out1_data = out1->Dptr<float>();

    uint32_t size = out0->Size();

    for (uint32_t i = 0U; i < size; i++) {
      out0_data[i] = input0_data[i] + input1_data[i] + input2_data[i] + 1;
      out1_data[i] = out0_data[i];
    }
    return 0;
  }
  }  // namespace dnn
  }  // namespace hobot

备注:函数名称前缀(即 Cop1Cop2) 需要自定义OP的类型( Kind )一致, 其传入参数为:

  • bottom_blobs :自定义OP节点输入数据。

  • top_blobs :自定义OP节点输出数据。

  • inferCtrlParam :自定义算子初始化阶段输入参数。

注意: 模板运算规则输出等于所有输入数据累加加上数值1,因此后续需要定义其他行为,则相应更改运算规则即可。

自定义算子注册

完成C++版本模板修改后,仅需要示例的CMakeLists.txt中添加模板文件包含, 并应用程序增加算子注册即可,注册参考以下代码:

  #include "custom_identity_add1.h"
  #include "custom_identity_add2.h"

  hbDNNRegisterLayerCreator("Cop1", hobot::dnn::Cop1_layer_creator);
  hbDNNRegisterLayerCreator("Cop2", hobot::dnn::Cop2_layer_creator);
  ....

完成模板文件依赖算子注册后,即可含有自定义算子模型进行执行操作。

  在使用前,请您确认模型的自定义算子名称与注册的算子名称是相同的。

参考文档,请参阅Runtime示例advanced_samples