Showing Posts From

Npu

模型FP16和INT8量化的计算流程总结

本文对Pytorch等模型训练框架训练出来的模型,进行浮点半精度转换FP16和定点量化INT8的相关概念和计算流程进行完整的总结。 Pytorch 模型的训练与存储格式 在我们常见的 PyTorch、TensorFlow 等主流深度学习框架中,默认使用的张量(Tensor)数据类型是FP32 (32-bit floating point, 单精度浮点数)。因此,我们在 PyTorch 中定义一个卷积层 nn.Conv2d 或线性层 nn.Linear 的时候,框架所提供的随机初始化的权重参数(Weights)、偏置(Biases),以及在前向传播时所产生的激活值(Activations)等,默认全部占用 32 个比特(即4 字节,FP32)的内存空间。 当我们在 Pytorch 中完成模型训练,使用 torch.save() 保存模型文件到本地硬盘。由于训练过程中默认的张量 dtype 类型是 FP32,所以通过这个标准流程保存的模型也就是 FP32 的。 但是为什么我们从 Ultralytics 上下载的官方 YOLO 预训练模型文件,其模型文件大小对应的内部参数是FP16,而不是FP32呢? 这是因为,Ultralytics 官方所提供的 YOLO 预训练模型文件(如 yolov8n.pt 或 yolov5s.pt)在发布前,都会被强制转换成了 FP16 格式,其核心目的是为了大幅节省传输带宽和存储空间。 Ultralytics 在模型训练完成后,会调用他们内部一个极其经典的后处理函数:strip_optimizer()。这个函数会调用模型 .half() 将其权重全部转为 16 位半精度浮点数 FP16 格式。大概的流程如下: FP16半精度转换的计算过程 从严格意义上讲,把模型参数的存储格式由 FP32 转换到 FP16 的过程称为类型转换(Type Casting / Downcasting),而不是严格意义上的量化(Quantization)。 要理解这个转换过程,必须要先理解FP16和FP32这两个数据类型在内存中的真实面貌。这两种浮点类型的数据在内存中的形态符合IEEE 754 浮点数二进制标准。 IEEE 574 规范采用科学计数法(例如 可以写成 )在内存中表征浮点数据。该规范将浮点数的表示分为三个区域:符号位 (Sign)、指数位 (Exponent) 和 尾数位/小数位 (Mantissa/Fraction)。其最终还原的统一数学公式为: 其中: - 符号位 S :永远是最高位(第 1 位),规则为用 0 代表正数,1 代表负数。 - 指数位 E:紧随符号位之后,用于控制数字的大小和动态范围。 - 尾数位 M:用于控制数字的精度和有效数字的位数。在规范的二进制科学记数法中,数字小数点前永远是 1(也就是 )。既然始终是 1,IEEE 754 为了极致榨干内存,直接规定这个 1 不存进内存。这样的话,内存里存的仅仅是小数点后面的部分(即上面公式中的 ),但是在计算的时侯,硬件需要自动把那个 1 加回来。 !ce6be79d-c521-45a1-b6d5-add7e59dbb69.png 那么对于FP32和FP16而言: - FP32:单精度,32bit,其中符号位1bit,指数位8bit,尾数位23bit,可表示的数据范围为 。 - FP16:半精度,16bit,其中符号位1bit,指数位5bit,位数为10bit,可表示的数据范围为 。 那么接下来,模型训练完成后保存的FP32的参数,要转换为FP16格式的话,具体的转换流程是什么呢?主要可以分为以下三个步骤: 第一步是指数位检查与溢出处理 (Overflow/Underflow)。 - 溢出 (Overflow) 检查:如上所述,FP16 能表示的最大绝对值是 65504。如果 FP32 的权重值大于 65504 ,转换器通常会执行 Clamp 操作,将超出部分强行截断为 65504。 - 下溢 (Underflow) 检查:FP16 能表示的最小的正规化数大约是 。如果 FP32 的参数值极小的话,转换器会将其强制为 0。 第二步是尾数位的强制截断。 FP32 有 23 位尾数,而 FP16 只有 10 位。转换器在进行转换的时候直接把后 13 个比特砍掉。在二进制里,这个截断操作相当于抹除了小数点后第 4 位到第 7 位的微小有效数字。当然,这一步也就是FP32到FP16转换中精度损失的主要来源。 最后一步是计算图的重构。也就是更换模型计算图中算子的执行逻辑,把其中FP32的算子(如Conv2D_Float32)操作替换为FP16的算子(如Conv2D_Float16)。 以上三个步骤的完整转换流程如下图所示: !FP32_to_FP16_Conversion_Flowchart_RK3588_%282%29.png 从以上的三个步骤的操作来看,从FP32的模型转换为FP16,模型的推理结果和精度等可能被影响的主要来源于两个方面的因素:指数位的溢出和尾数位的强制截断。但实际上,对于绝大多数 CV 模型和音频模型,FP32 转 FP16 的精度几乎是无损的,这是什么原因呢? 针对指数位溢出的问题,无论是 CNN 还是 Transformer,模型在经过初始化训练后,其权重参数 绝大多数都严格服从均值为 0、方差极小的正态分布。而且当前的 CV 和音频模型中普遍配置了 BatchNorm (BN) 或 LayerNorm (LN) 归一化层,这些归一化层在每次卷积或注意力机制计算完成后,会强制将特征图的数据分布重新拉回均值为 0、方差为 1 的标准状态。这样也就意味着,网络中 99.9% 的权重值都在 这个极小的区间内,FP16 的最大值 65504 ,指数位溢出的概率在权重端几乎为零。 而尾数强制截断所带来的精度损失问题,则通过尾数截断时所采用的阶段策略来缓解。在FP32向FP16转换的过程中,对尾数位的截断采用了一种向偶数舍入 (Round-to-Nearest-Even, RNE)的截断策略(这个策略有点类似于十进制的四舍五入)。如果使用简单的直接截断和舍弃,误差会永远偏向一方,这样经过成千上万次累加后,误差就会如滚雪球般变成灾难。但是,RNE 使得截断误差一个均值为 0 的对称随机噪声。这样的情况下,当模型对成千上万个带有零均值、独立噪声的乘积结果进行求和时,正向的误差和负向的误差在统计上会相互抵消,这样就可以极大程度上缓解强制截断的精度损失和误差问题。 INT8量化的计算过程 与 FP16 在 IEEE 754 标准的基础上做位宽截断的处理过程完全不同,INT8 量化是一场精密的数学空间映射。 它需要将 FP32 的连续的、动态范围极大的浮点数空间,塞进只有 256 个刻度()的离散整数空间中。 对于以上的这个映射,业界标准的做法是使用线性映射变换。这个映射变换由两个关键参数决定: - 缩放因子S:FP32 浮点数,代表了 INT8 空间里一个刻度单位对应于 FP32 空间里的真实范围大小。 - 零点偏移Z:INT8 整数,代表了 FP32 空间里的绝对零点,对应于 INT8 空间里的哪个整数刻度。 基于以上两个参数,假设 FP32 空间内的真值为 ,其对应的 INT8 空间内的量化值为 ,那么 和 之间转换的基本等式就是: 从真值 R 向量化值 Q 计算的公式就是: 其中: - 代表四舍五入到最近整数的操作。 - 则代表防溢出操作,把结果死死限制在 INT8 的合法范围内(也就是在 和 之间,如 )。 而从 FP32 到 INT8 的全流程计算过程也可以分解为以下三个步骤。其实 INT8 的量化计算就是执行以上的量化值的计算公式,只不过在执行这个公式之前首先要算出来其中的参数 和 。 首先是步骤一:要计算缩放因子 S ,在量化值 的范围确定的情况下(对于INT8而言就是-128到127共计256个刻度),首先要确定清楚 FP32 空间对应的真实值 的区间范围 和 。这样才能把 的范围准确的映射到 的范围之内。 这里针对参数权重/偏执值和激活值有两种情况: - 对于参数权重值和偏置值而言:因为这些值都是模型训练好后就确定下来的常量,所以在模型转换器(如瑞芯微的RKNN-Toolkit2)在INT8量化的执行过程中,只需要直接扫描 ONNX 文件里这一层卷积核的矩阵,就可以直接找出 和 。 - 而对于激活值而言:激活值是会随着输入图像的特征数据动态变化的,这就是为什么在进行 INT8 量化的时候,必须要基于训练图片集提供一组量化数据集 dataset(通常包含 20-50 张代表性图片)。INT8 量化的过程中,模型转换器会使用这些图片做一次前向推理,统计每一层输出特征图的概率直方图,然后再使用 KL 散度 (KL-Divergence) 等算法,找到一个最能保留信息量的最优截断阈值 和 。当然这样找到的 和 是由我们提供的量化数据集dataset中的数据计算出来的,所以最好我们提到dataset数据集中包含的图片与量化后实际使用的图片形态相差不大。 步骤二:有了步骤一计算出来的 和 ,接下来就可以计算出来参数 和 。计算这两个参数的流程有两种方法,非对称量化(针对激活值)和对称量化(针对权重值)。 针对权重参数的对称量化: 模型训练出来的权重值的分布(即 和 )通常以 0 为中心,左右基本对称。硬件为了计算极速,强制要求量化零点 ,INT8 量化值的目标范围为 。如 和 为,计算出来的 和 Z 就是: 针对激活值的非对称量化: 非对称量化可以充分的利用 INT8 量化的所有刻度,假设 ,计算 和 的流程就是: > 💡 为什么激活值使用非对称量化,权重值使用对称量化?这是因为,现代 CV 网络中大量使用 ReLU 系列激活函数。经过 ReLU 之后,特征图中的所有负数都被置为 0。此时如果强制对这种单边分布使用对称量化,即强制将映射范围的中心定在 。此时为了能表示 这样的数字,量化刻度就必须覆盖 并映射到 INT8 的 。这样的结果就是 INT8 中 这 127 个状态永远不会被用到(因为根本没有负数激活值),一半量化值被白白浪费掉了,8bit的量化实际上变成了7bit_。_但权重的分布天然就是以 0 为中心的(高斯分布),所以用对称量化基本不会浪费空间,还可以更高效的利用硬件计算加速。 步骤三:物理转换并固化。 第二步计算出来了 和 ,接下来模型转换器就会将所有的 FP32 权重,套用如下公式全部计算成 INT8 数值,并打包到量化模型文件中。 以上描述的INT8量化计算流程的整个流程图如下图所示。 !FP32_to_INT8_Quantization_Flowchart_RK3588.png

如何把PT格式的模型转换为TFLite/LiteRT模型?

之前在尝试把Ultralytics的YOLOv8模型运行在Android手机上的过程(参考总结Android Flutter项目中YOLO模型的执行流程)中,直接使用了YOLO模型的export接口即可实现把该模型导出为ONNX、TFLite等格式。但是如何我们在开发过程中遇到的模型没有官方所提供的export接口,如何能够把pt格式的模型文件转换为TFLite,从而方便的部署在Android手机上呢? 因此,本文尝试解决这样的问题:如何把一个通用的 PyTorch 模型权重(.pt)文件,通过模型转换处理,变成在 Android 端侧能跑的 TFLite 模型文件(.tflite)? Google的AI Edge Torch/Litert 针对以上所提出的问题,一个比较流行的解决方案是: - PyTorch (.pt) → ONNX: ONNX 是模型交换的通用格式,所以这一步转换相当于是把模型文件脱离了 PyTorch 的框架限制。 - ONNX → TensorFlow (SavedModel): 这是一个转译过程,从ONNX的生态语言转换为TensorFlow生态,确保目标平台(即Google 手机APP)能正确的识别模型文件中的算子。 - TensorFlow → TFLite: 这有点类似于 Linux 下的 strip 和 objcopy,这一步操作会进行常量折叠、算子精简以及最重要的量化操作(Quantization),把默认的 FP32 的权重参数变成 INT8,以适应移动端 CPU/NPU 的算力和存储方面的限制。 但是,以上这个传统的 PT -> ONNX -> TF -> TFLite 路径并非是一个完美的解决方案。这是由于, PyTorch 坚持使用 NCHW (即所谓的Channel First) 而 TensorFlow 则默认使用 NHWC (即所谓的Channel Last),那么转换工具在以上传统的转换路径中为了保住数学上的等价性,会疯狂插入 Transpose 算子。这在手机 CPU/GPU 上运行过程中,会导致频繁的内存重排,严重拖慢推理速度。 因此,Google 在2024年4月推出了一个AI Edge Torch的工具,用于实现从 PT 模型文件向TFLite格式模型文件的直接转换。这个工具利用了 torch.export(PyTorch 2.x 的核心特性)直接捕获计算图,并将其映射为 TFLite 算子,更重要的是,它可以直接在模型转换过程中对齐推理过程中的数据格式,这样就消除了大量不必要的 Transpose 算子,避免后续推理过程要执行的大量内存重排。 到了2025年,Google 对移动端 AI 生态进行了一次品牌重塑:之前的TensorFlow Lite被重新命名为LiteRT。这主要是因为,Google 认为 TensorFlow Lite 这个名字太受限于 TensorFlow 框架了。现在整个AI模型生态的趋势是 PyTorch 统治科研与训练,而 TFLite 统治移动端部署。因此为了吸引 PyTorch 用户,Google 强调 TFLite 这个 Runtime 是通用的端侧AI部署工具,不只是给 TensorFlow 用的。所以现在新的品牌叫 LiteRT (Lite Runtime)。 !image.png 环境安装 要在PC端把一个Pytorch的模型转换为 TFLite/LiteRT 格式,主要是需要依赖于litert-torch和ai-edge-litert 这两个包。 - litert-torch:对应之前的 ai-edge-torch,负责将 PyTorch 算子映射到 LiteRT。 - ai-edge-litert: 对应之前的 tflite-runtime,是推理引擎,负责在 Android 或 PC 上执行生成的 .tflite (LiteRT) 文件。 截至目前(2026年3月份),litert-torch和ai-edge-litert 这两个包的接口变动仍然非常剧烈,导致其对环境的要求非常苛刻。所以一定要创建一个独立的Python环境,并且提前安装好其指定的torch版本,否则后面安装这两个包的过程中很容易出现包冲突的情况。经过仔细检查,目前比较稳定的环境安装步骤为(在Windows的WSL2环境中安装): 注意:这个模型转换和PC端推理的环境必须安装在Linux环境中,我自己在Windows中安装环境总是会以下错误。从报错信息上看,litert-torch依赖于ai-edge-tensorflow,而ai-edge-tensorflow 目前主要发布在 Google 的 Nightly/Experimental 仓库中,对 Windows 平台的原生支持非常滞后。所以这个转换工具最好还是在Linux或者Windows的WSL2环境中来安装。 模型转换 环境安装好以后,就可以开始把Pytor下的预训练pt格式的模型文件转换为TFLite格式了。我这里直接使用Ultralytics/YOLO项目中的yolov8s.pt这个文件做转换测试。 以上模型导出脚本能够成功的导出 FP32 的模型文件,FP32 虽然精度最高,但由于其对内存带宽和存储空间的占用比较大(对于YOLOv8s这个模型而言,转换后的模型文件大小为44MB),在端侧 CPU/NPU 上运行肯定不是性能最优选,因此接下来还需要进行量化(Quantization)处理。 量化 对于YOLO模型自带export接口而言,量化是一个很简单的事情,只需要设置导出时的fp16、int8等导出格式就可以了。但是使用litert_torch 完成模型导出之后,对TensorFlow Lite模型文件进行量化操作的接口发生的变化非常剧烈,导致现在在网上看到的几乎所有量化参考代码在最新的litert-torch 都无法正确、成功的完成量化操作。 通过学习https://github.com/google-ai-edge/litert的ReadME文件: > > > I have a PyTorch model... > > - Goal: Convert a model from PyTorch to run on LiteRT. > - Path1 (classic models): Use the LiteRT Torch Converter to transform your PyTorch model into the .tflite format, and use AI Edge Quantizer to optimize the model for optimal performance under resource constraints. From there, you can deploy it using the standard LiteRT runtime. > - Path2 (LLMs): Use LiteRT Generative Torch API to reauthor and convert your PyTorch LLMs into Apache format, and deploy it using LiteRT LM. > 可以看到对于目前比较新的LiteRT实现而言,量化需要用一个新的工具ai-edge-quantizer(https://github.com/google-ai-edge/ai-edge-quantizer)。这个工具是 Google 专门为了对LiteRT模型进行 INT8/INT4 权重压缩打造的新一代利器。在最新的 Edge AI 理念中,单纯的文件体积 FP16 截断已经不再流行,权重 INT8 + 激活值浮点 (即所谓的Dynamic Quantization) 才是能在手机等端侧设备上上平衡速度、体积和精度的终极王者。 以上的量化流程中选择了wi8-afp32的模式(即权重参数使用INT8量化,激活值仍然使用FP32量化),最终得到的量化后的模型文件只有11MB的样子,是量化前的FP32模型文件的1/4。 除了以上的wi8-afp32量化模式,ai-edge-quantizer还支持其他的量化模式,如下图所示,Google 的工程师用了一种非常直白的命名约定来定义这些量化模式: - w = Weight(权重参数,即模型训练出来的固定参数) - a = Activation(激活值,即模型在推理时流动的中间特征图) - i8 / i4 = 8位 / 4位整数(Integer) - fp32 = 32位浮点数(Float) !8a772873-2af8-4892-8f32-b1998f10d39e.png 推理测试 基于 LiteRT模型文件已经转换并量化完成,下一步就是在PC端的LiteRT runtime中对其进行推理测试。 如上所述,在 LiteRT生态中,ai-edge-litert包对应于之前的 tflite-runtime,也就是负责在 Android 或 PC 上基于LiteRT模型文件执行前向推理的运行时框架。因此,在PC端进行前向推理的流程大致也就是:加载模型以及推理的图片文件→数据预处理→执行推理→对推理结果执行NMS后处理→把NMS过滤的结果叠加在输入图像上。 参考资料 - AI Edge Torch: High Performance Inference of PyTorch Models on Mobile Devices - Google Developers Blog - Convert Models From Pytorch to TFLite With AI Edge Torch | by David Cochard | ailia Tech BLOG (EN) | Medium - https://github.com/google-ai-edge/litert-torch/blob/main/docs/pytorch_converter/README.md - https://github.com/google-ai-edge/ai-edge-quantizer

TFLite如何处理算子版本和兼容性的问题?

在瑞芯微NPU平台上进行模型板端适配的时候,在进行模型板端转换的时候,ONNX 导出时设置的 Opset 版本,与板端模型转换工具(RKNN-Toolkit2)能够支持的 Opset 版本的一致性非常重要,如果板端模型转换工具不支持onnx文件中的某些算子类型的话,这个转换过程就会报错。那么,TFLite是否也存在 Opset 版本的问题,也就是模型导出为 TFLite 格式的时候也应该有个 Opset Version,这个 Opset 的版本需要与 interpreter 能够支持的 Opset 版本相对应,否则也会出现以上瑞芯微平台模型转换的问题?以及在TFLite的工作流程中,究竟是处理Opset版本一致性问题的?本文尝试总结以上问题的答案。 相关的背景信息可参考: - ONNX的Opset版本: 如何理解ONNX模型导出的Opset版本? - TFLite框架的核心概念和工作流程: 如何理解TFLite/LiteRT的核心概念和框架流程? TFLite的Opset版本信息 针对TFLite框架下,是否也有与ONNX相同的OpSet版本的概念和处理这个问题,简单的答案是:TFLite同样有算子版本的概念,但是与算子版本的处理流程与ONNX有较大的差异。 .tflite 文件本质上是一个序列化后的 FlatBuffer 二进制文件。在这个文件头部,有一个专门的 OperatorCode 数组(也就是所谓的算子注册表)。当模型被导出时,TFLite 转换器会把这个模型文件中用到的所有算子登记在这个表里,并且每个算子独立记录其算子版本号,而不是像ONNX那样有一个统一的算子集版本号。 如下图所示的两者的显著差异:TFLite采用的是细粒度的基于各个算子的独立版本号,模型文件本身没有算子集版本的概念;而ONNX采用的这是全局的算子集版本号,通过模型文件来规范和管理整个算子集的版本。 !%E6%97%A0%E6%A0%87%E9%A2%98-2026-03-26-1350.png 为什么TFLite要采用这种单个算子集别的版本管理模式,而不是采用ONNX这样的全局算子集Opset版本的方式? 最主要的原因是,算子级别的版本管理方式可以更好的向前兼容性,这一点对于软硬件碎片化非常严重的Android生态系统非常重要。假设我们的模型中仅包含有最基础的 Conv2D 和 Relu 算子,使用最新版本的模型转换器导出的时候: - 如果是 ONNX的全局Opset模式:模型转换器会将这个ONNX文件标记为比较新的 Opset 18。这样的话,即使 Conv2D 这个算子在 Opset 1 到 18 里没有发生任何变化,那么一个老版本的 ONNX Runtime 在看到 Opset 18 版本的情况下,可能就会直接报错并拒绝加载。 - 而在 TFLite 这种单算子版本的管理模式中:模型转换器在转换过程中,发现该模型没用到 Conv2D 的新特性,就会在TFlite模型文件里将 CONV_2D 这个算子标记为 Version 1。这种情况下,即使用户的Android手机里包含的是很早版本的 TFLite Interpreter,也能完美加载并运行这个最新导出的模型。 算子版本的匹配 与其他嵌入式平台的模型导出、适配以及板端的加载运行相同,在Android系统上运行TFLite格式的模型,在算子的版本匹配上,需要关注三个环节:模型导出和转换工具的算子版本(Converter),Interpreter支持的算子版本,以及delegate支持的算子版本。只有以上三者的算子版本信息相互匹配,才能确保这个TFlite模型在Android系统上成功的跑起来。 - TFLite 模型转换工具、Interpreter以及Delegate等相关的概念及其在Android系统中的结构,可以参考如何理解TFLite/LiteRT的核心概念和框架流程? 这一篇笔记。 Converter TFLite 模型的 Converter 工具内部有一套算子版本试探的算法,来自动检测模型文件中各个算子的版本信息。例如对于 FullyConnected 这个算子,在没有开启 Keep_num_dims 特性的情况下,Converter 在进行模型转换的时候,就会给这个算子打上 Version 1 的标签;而如果模型的推理计算过程需要开启这个新特性,Converter 才会给该算子打上 Version 2。这就相当于是一个“能用低版本绝不用高版本”的算子版本决定策略,这个策略是保证模型和 App 拥有最广设备兼容性的核心所在。 总结起来,对于Converter而言,无论这个Converter工具有多新,它的工作逻辑都是:在进行模型TFLite格式转换的过程中,它会扫描和试探出来每个算子在模型计算图中所要求的最低版本,然后把这个最低版本作为该算子的版本,以此来尽量保证后续这个算子以及模型能够支持到范围最广的设备。 Interpreter Interpreter 也有自己的版本支持情况,那么它在加载TFLite模型文件和构建模型的计算图的过程中,同样会去扫描各个算子的版本信息,并检查该版本与自己支持版本的适配情况。 Interpreter 在加载 TFLite 模型文件的 FlatBuffer 结构的过程中,提取每个算子的版本信息,与自己内部的 OpResolver(算子注册表)进行比对。对于一个算子而言,如果在TFLite文件中该算子需要的版本是 Version 5,而 Interpreter 的代码库里算子注册表只实现了 Version 4,它会立即抛出 Unresolved builtin op 异常,并拒绝执行。 因此,一个模型究竟能否在Android系统中跑起来,Interpreter 对于算子版本的支持情况非常关键,这就是为什么在使用 Flutter 框架开发Android App 时,应该尽量使用最新版本的 tflite_flutter 插件。 > 💡 Interpreter本身就是一个普通的 C++ 动态链接库(比如 libtensorflowlite_c.so),它是被硬打包进 APK 里的,而不是由 Android 系统底层提供的。这样的好处是,Interpreter与APP里面的模型强绑定,只要APP完整测试通过,就可以确保运行在不同硬件平台和Android系统版本上。因此,如果系统中包含有多个跑不同模型的APP,也就有多个各自独立的Interpreter副本。当然,TFLite 的核心解释器被设计得极其精简,去除了所有训练相关的代码。一个基础的 C++ Interpreter 库通常只有 1MB 到 2MB,对现代 App 的体积影响微乎其微。 在 Interpreter 中实际上包含了其所支持的所有算子的CPU实现,这样的话,如果 Delegate 不支持某个算子的话,Interpreter 就会自动调用其内部实现的CPU算子。 Delegate 而在 Delegate方面,同样有自己所支持的各个算子的版本信息,在Delegate与Interpreter握手的过程中,Interpreter会拿到了Delegate所支持的各个算子的版本信息。在这种情况下,Interpreter 在模型前向推理的算子调度过程中,就能够清楚的知道哪些算子 Delegate 能够支持,哪些算子Delegate不支持必须要放在CPU中执行。 当然,不同于 Interpreter 是内置在Android APP层面中,Delegate 对于算子的支持是固定在Android系统中的。这也很好理解,因为 Delegate 之下的 NPU 驱动的实现需要依赖于SOC厂家的实现,是不可能放在APP层面来解决这个问题的。因此,除非升级整个系统,Delegate层面对算子版本的支持情况就是固定的,Delegate没法支持的算子就只能被Interpreter调度到CPU上去执行。 TFLite前向推理与算子版本相关的完整流程 至此,对于TFLite的算子版本管理,TFLite模型文件导出时的算子版本推定,在Android系统中Interpreter和Delegate进行算子版本匹配和调度的完整流程就呼之欲出了: !TFLite_Architecture_Flowchart.png

如何理解TFLite/LiteRT的核心概念和框架流程?

本文整理了TFLite的核心概念,与ONNX框架的对比,及其内部各个组件的关键工作流程,尝试建立对这个Android系统下的标准AI推理框架的整体概念和工作流程建立完整的理解。 TFLite是什么?以及与ONNX的比较 Google推出的 TensorFlow Lite 是一个专为移动、嵌入式和边缘设备设计的开源深度学习框架。它能够支持开发者在手机(Android/iOS)、微控制器(MCU)及各类物联网设备上,以极低的延迟和较小的二进制体积运行高性能的推理任务。 !image.png 众所周知,在边缘侧的板端设备上运行各种 AI 模型,已经有一个非常流行的 ONNX 框架,那为什么不在 Android 上直接使用 ONNX?原因主要还是 Facebook的 Pytorch/ONNX 与Google的 TensorFlow 之间生态竞争的问题。 虽然 ONNX Runtime 现在也能够支持在 Android上运行,但在 Android APP 中跑AI Model还是应该首选使用 TFLite,原因如下: - 兼容性的问题:在 Android 系统的实现中,Google 提供了一层 NNAPI (Neural Networks API) 的封装。TFLite 是 Google 主推的端侧机器学习框架,它对 NNAPI 的支持是第一优先级的。如果用 ONNX 的话,在调用不同 SOC 厂家的 NPU 时,可能会遇到各种驱动不兼容或性能折损的情况,兼容性的处理会非常头疼。 - 内存与包体积的问题:要在 Linux 或者 Android 系统中使用ONNX来运行模型,就需要把 ONNX Runtime 内置进去,相关的库文件通常很大(几十 MB),这样就会直接增加 App 的安装包大小和内存占用情况。而如果使用TFLite的话就没有这个问题,TFLite 的核心解释器非常小(只有几百 KB),而且 Android 系统中已经内置了相关的运行时环境。 TFLite模型部署的整体工作流程 将一个复杂的深度学习模型部署到手机上,通常需要经历从训练、精简/格式转换/量化再到板端执行的完整的过程。这个流程对于所有的设备端 AI 框架的工作流程而言都是大致相同的。 例如对于瑞芯微平台上的模型适配而言,其整体的适配流程如下: - 首先在PC/AI服务器端完成模型训练,导出为ONNX格式的模型; - 把以上导出的ONNX模型再利用瑞芯微的RKNN-Toolkit工具转换为RKNN格式的板端模型; - 在瑞芯微的处理器中,板端模型与其运行时库librknnrt.so、应用程序相互配合完成板端的推理任务。 以上的这个流程,对于在手机的的 Android 系统中使用 TFLite 框架来运行 AI 推理的流程是完全相同的,只不过整个过程稍微简化了一些而已: - 模型转换部分:首先把原始的训练好的 PyTorch 模型(通常是 .pt 或 .pth)使用 TFLite Converter 之类的工具转换为扁平缓冲区格式的 .tflite 文件。与其他板端模型转换工具的工作流程类似,在这个转换过程中,它会进行算子融合(将多个连续的操作合并为一个,以减少内存访问次数和量化(将原始模型文件中的 32 位浮点数权重转换为 16 位浮点数 FP16 或者 8 位整数 INT8,在微弱损失精度的前提下可换取数倍的速度提升同时降低对模型大小和显存的需求)等操作。 - 板端推理执行部分:在 Android App 运行时,内置在Android 系统中的 TFLite 解释器(Interpreter)会负责加载模型、管理内存缓存并调度计算算子。 > 💡 注:在模型转换部分,Pytorch 框架下训练出来的模型,一般需要先转换为 ONNX 模型,再利用类似 onnx2tensorflow 之类的工具转换为 tflite 格式。不过 Goggle 在2024年也出了一个 AI edge torch的工具可以实现直接的转换。 !image.png TFLite模型在Android框架下是如何运行的? 从以上的框架图中可以看到,模型转换为 TFLite 格式后再Android APP中的运行,需要依赖于两个关键的组件:Interpreter 和 Android Neural Networks API(NNPI)。 其中的Interpreter (解释器)是 TFLite 模型在系统中运行的核心控制中枢。它负责加载模型,解析模型的计算图(Graph),并管理内存缓冲区。在整个模型的工作流程中,Interpreter 并不直接做复杂的浮点和算子运算,而是负责将模型中定义的计算任务分发出去。 - 其实这个Interpreter有些类似于瑞芯微的librknnrt.so,负责辅助应用程序加载模型,解析模型的计算图和管理内存,然后在接收到输入数据的时候,对模型每一层的计算任务进行调度管理,分配到合适的处理器(CPU/GPU/NPU)上,并根据具体的计算调度情况不同的处理器上在处理器之间管理好内存的拷贝。所以其任务就是:加载并解析模型,分发计算任务,管理不同计算任务和处理器上的内存。 而 NNAPI 则是 Android 系统提供的一个 C API。它的存在是为了屏蔽底层 NPU、GPU、DSP 硬件的碎片化。Google 在 Android 系统中所提供的 NNAPI 只是一个与 Interpreter 相互配合的API调用接口的框架,而对于瑞芯微、高通、联发科等 SoC 厂商,要让 Android 手机能流畅地使用 TFLite 执行模型的推理任务,他们的核心工作就在其SOC的设计中进行 NNAPI 接口的具体实现。 具体来讲,这些 SOC 厂商需要根据 Android NNAPI 模块所提供的 NeuralNetworks.h 标准,编写对应的 NPU 驱动指令。这些驱动指令的关键任务就是实现模型的算子映射(将 Android 定义的 ANEURALNETWORKS_CONV_2D 等标准算子,翻译成自己的 NPU 能理解和执行的具体指令)和内存管理(实现不同类型和区域的内存共享控制,尽量避免模型在执行推理过程中数据在 CPU 和 NPU 之间来回拷贝)。 总结起来,就形成了这样的调用关系和流程: !5a379fcc-72eb-471f-9001-bf892b8a6120.png - 首先是APP的应用程序逻辑代码调用 Interpreter 加载和解析模型文件; - 在 Interpreter 对输入数据执行推理流程的过程中,会通过 Delegate 机制调用 Android系统中的 NNAPI 框架中所定义的API。NNAPI中定义了不同的标准算子,Interpreter 在推理中针对具体不同的算子调用 NNAPI 中与之对应的API接口。 - NNAPI之下则是不同 SOC 针对算子的具体实现,这部分由各个 SOC 厂家根据自己SOC中 CPU/GPU/NPU 的能力来进行实现。相当于 NNAPI 定义了一层标准的算子接口,屏蔽了不同 SOC 厂商在其硬件上实现这些算子的差异性,这样的话,对于应用程序和Interpreter而言,看到的就只是 NNAPI 所定义的标准接口。 Interpreter 调用 NNAPI框架的Delegate机制 以上所描述的 Interpreter 和 NNAPI 框架的调用流程只是一个简化的解释,实际上Interpreter并不是直接调用NNAPI所定义的API接口。如果 Interpreter 直接以硬编码的方式去调用 NNAPI的话,会产生两个问题: - 某些算子可能不支持 (Fallback):NNAPI 并不一定能够支持 TFLite 模型文件中的所有算子。如果直接调用的话,遇到不支持的算子程序就崩溃了。 - 跨平台的兼容性问题:TFLite 所谋甚大,不仅要运行在 Android 上,还能够支持在 Windows、Linux、iOS 上跑,而这些系统中根本就没有NNAPI(例如上面架构图中的iOS APP部分根本就没有 NNAPI)。 所以 Interpreter 在 Android 中对于 NNAPI 框架的调用,采用了一种 delegate 的机制。 本质上,Delegate 就是 Interpreter 为了实现跨平台兼容性而设计的一个抽象插件接口。 - 对上(与Interpreter之间),它通过TfLiteDelegate 定义了一个标准的 C API,通过这个C API屏蔽了不同硬件平台、SOC、NPU的差异性。Interpreter始终只需要调用这个C API接口来实现对下层不同算力的调用,实现完整的推理过程。 - 而对下(各种平台不同的NPU实现),它作为客户端,负责调用各个不同系统和硬件平台上特有的深度学习算子运算的加速库(如 Android 的 libneuralnetworks.so 或 iOS 的 Metal.framework)。 至此,TFLite 框架得到完整结构及其调用流程就比较清楚了: !%E6%97%A0%E6%A0%87%E9%A2%98-2026-03-26-1350.png 参考资料 - Google Play 服务 C 和 C++ API 中的 LiteRT  |  Google AI Edge  |  Google AI for Developers - TFLite基础知识 - VitoYeah - 博客

如何理解ONNX模型导出的Opset版本?

在把PC或服务器上基于Pytorch训练好的模型导出为ONNX格式的时候,需要在导出的脚本中设置opset的版本,不同的opset版本设置会对后续的NPU适配过程造成很大的影响,那么这个opset的版本就是是个什么概念?不同的opset版本究竟有哪些差异,以及这些差异对后续的NPU适配过程如何造成影响?本文尝试回答以上问题。 从总的概念上来讲,在嵌入式AI模型的部署流程中,Opset(即Operator Set,算子集) 是 ONNX(Open Neural Network Exchange)所定义的算子集合的版本标准。在将PC上训练好的 PyTorch(.pt/.pth)模型导出为 ONNX 格式时,设置正确的 opset 版本至关重要,因为它直接决定了导出的模型能否在后续的NPU板端模型转换工具成功转换,并高效运行在 NPU 上 。 !image.png 要理解不同的Opset对后续的NPU模型适配和部署流程会造成什么样的影响,首先要深入的理解究竟什么是算子和什么是算子集。 什么是算子(Operator/Op)和算子集(Operator Set)? 在 AI 模型的语境之中,算子(Operator)是模型里的最小执行单元。一个典型的深度学习模型(例如 YOLO 模型),其本质就是一个巨大的有向无环计算图(DAG)。这个图由无数个节点组成,而每个节点就是一个算子。 这就有点类似于我们在写软件程序的时候,如果要实现某项功能,通常会把这个功能封装为一个函数来进行调用。那么_算子就类似于一个可被应用程序调用的标准库函数,对这个算子(函数)的调用,就相当于是在NPU上执行一次最小粒度的标准化操作。_ 常见的算子包括: - Conv (卷积):提取特征,相当于嵌入式里的滤波算法。 - ReLU (激活函数):实际上就是一个简单的 if(x

基于ECS50数据集对PANNs语音分类模型进行的训练流程

本文使用ECS50数据集对语音检测和分类模型PANNs进行自定义训练的完整流程进行了记录。 ECS50数据集简介 在PANNs音频分类模型预研信息总结一文中,详细介绍了在语音分类应用中非常流行的模型PANNs,该模型使用Google的AudioSet数据集上进行了充分的训练,从而在不同种类的语音检测、辨识和分类应用上取得了非常好的效果。本文基于该模型(CNN14结构)的预训练文件Cnn14_mAP=0.431.pth,使用ECS-50语音数据集对预训练模型进行微调,使之适应ECS-50数据集的检测和分类需求。 ESC-50数据集包含有2000个环境录音文件,该数据集适用于环境声音分类的基准方法。数据集中的每个音频文件由5秒长的记录组成,可分为50个语义类,每个类型有40个示例,松散地分为5个主要类别: !image.png ECS-50数据集的下载地址:karolpiczak/ESC-50: ESC-50: Dataset for Environmental Sound Classification > 需要注意的是,ESC-50 数据集中的音频文件原始采样率是 44.1kHz,而 PANNs Cnn14 的预训练权重是基于 32kHz 的。 因此在读取这些音频文件送入模型训练时,必须强制重采样,从44.1Khz转换为32KHz。 ECS-50数据集的训练流程记录 客观的讲,PANNs模型对于自定义数据集的训练支持并不是很友好,所以需要自行设计和处理整个训练过程(数据集准备,模型检测头修改,训练流程)的各个阶段的处理流程。 训练数据集的准备 首先从以上ECS-50数据集的下载地址下载到这个数据集的压缩包,整个数据集有600多MB的样子。 然后基于Pytorch的Dataset类派生专门的ECS50数据集的Dataset类,在其中实现len和getitem接口。此处值得一提的是,因为PANNs模型是基于32KHz的音频文件进行训练的,所以此处在加载训练数据之前,需要把ECS50数据集的44.1KHz的音频文件转换为32KHz。 此外为了进一步规范化训练数据集中的音频数据长度,统一把训练数据集中的音频长度固定为5s,过长的音频文件直接截断,过短的因为文件则在后面补零。 CNN14检测头的修改 PANNs模型是在Google的AudioSet数据集的基础上进行设计和训练的,预训练文件Cnn14_mAP=0.431.pth对应的模型检测头支持527种音频分类。而ECS50数据集支持50种音频分类,因此要在训练之前对检测头部分进行修改,以适应ECS50数据集的要求。 整个过程分为以下步骤: - 首先基于PANNs模型源代码实例化一个Cnn14结构的模型,并加载预训练模型文件。 - 然后修改以上Cnn14模型的检测头部分,最后一层使用一个50个神经元的线性层替代之前默认的527神经元的线性层。 - 最后对新修改的检测头的线性层的权重参数进行初始化。 至此,预训练数据集和待训练的模型结构就准备好了。下一步就开始进行训练。 模型的训练 模型的训练流程的执行与普通的卷积神经网络模型的训练流程没有太大区别,需要注意的是:因为是分类模型,所以训练过程使用标准的交叉熵损失函数,优化器使用Adam。 训练过程中的log信息如下: 可以看到,经过30个epoch的训练以后,在验证集上的识别准确率达到了93.5%。训练完成后会自动把训练好的模型参数保存为cnn14_esc50_best.pth文件。 推理 最后基于以下代码对训练出来的模型进行推理测试: 参考资料 - karolpiczak/ESC-50: ESC-50: Dataset for Environmental Sound Classification

轻量级LLM模型QWen2.5-0.5B在RK3588上的适配流程记录

本文详细学习和总结基于瑞芯微的RKLLM工具及其官方的相关指导文档,把轻量级大语言模型Qwen2.5-0.5B适配到RK3588上的完整过程及其注意事项记录。 总体而言,在瑞芯微NPU上部署 LLM 和部署 YOLO 的核心逻辑与流程是一致的,都是:PC端转换模型 -> 推送模型和库到板端 -> 调用 API 运行。 但是,对于大语言模型的适配而言,不再使用 RKNN-Toolkit2 工具链,而是使用专门为大语言模型优化的 RKLLM-Toolkit 工具链。 - RKNN-Toolkit2: 处理卷积神经网络(CNN)和简单的 Transformer(Vision Transformer)。 - RKLLM-Toolkit: 专门处理大语言模型,支持权重量化(W4A16/W8A8)和 KV Cache 优化。 下图是瑞芯微RKLLM Toolkit及其软硬应用生态的框架图,与RKNN类似: !image.png - RKLLM-Toolkit:用于在PC上进行模型转换和量化。 - RKLLM Runtime:为瑞芯微NPU平台提供C/C++编程接口,帮助用户在NPU上部署RKLLM模型,加速LLM应用的运行。 - RKNPU内核驱动:负责与NPU硬件交互,可以在Rockchip的内核代码中找到。 环境搭建 从rkllm的Github仓库(airockchip/rknn-llm)上下载rkllm代码。 PC端环境 创建一个独立的rkllm环境,并进入rknn-llm仓库代码中的rkllm-toolkit/packages目录下,安装rkllm-toolkit及其依赖包: 板端环境 对于LLM大模型在瑞芯微NPU上的运行,在板端主要需要NPU驱动以及librkllmrt.so链接库(在rkllm-runtime/Linux/librkllm_api/aarch64下)的支持。 NPU驱动要求在v0.9.8版本以上,可以通过以下命令检查自己板端的驱动是否符合要求,否则就需要重新编译内核: 下载模型文件并在PC上推理测试 使用transformers从Huggingface上下载Qwen2.5-0.5B模型的模型文件,并在PC本机上执行推理测试,验证下载文件的正确性: HuggingFace下载速度慢的问题可以通过设置国内镜像来解决,在执行以上下载脚本之前设置环境变量: 下载后的模型文件列表如下图所示: !image.png 模型转换 基于环境搭建部分安装的rkllm-toolkit包,调用以下代码完成模型的转换: > 注意:在量化类型的支持上,RK3588不支持w4a16, 可支持w8a8, w8a8_g128, w8a8_g256, w8a8_g512这四种。 以上模型转换结束后会在当前目录下生成一个qwen2_5_0_5b_w8a8.rkllm文件,大小800M的样子。 板端编译与推理 以上的板端模型转换完成以后,接下来就是准备板端的推理代码: 对于板端推理代码的交叉编译,可以按照常规的开发方法,在PC上编译好以后上传到板子上执行。但是我使用的1.2.3版本的RKLLM-Toolkit,在文档中明确推荐的交叉编译工具版本是GCC10.2,而我PC上的瑞芯微平台的编译工具链是gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu。所幸我用的瑞芯微RK3588板子上运行的Debian系统有满足条件的编译工具: 所以这样就比较简单了,可以直接把板端推理代码的源文件(main.cpp)、头文件(rkllm.h)、链接库(librkllmrt.so)以及前面转换好的板端模型上传到板子上,直接在板子的debian系统中使用g++进行编译: 执行后的打印信息如下: 可以看到,这个模型在RK3588上已经跑起来了。但是0.5B规模的大语言模型所生成的内容还是存在比较大的问题,不仅轻松的解决了西咸一体化这个困扰西安发展30年的痼疾,而且明显把榆林的资料张冠李戴到了咸阳头上。 推理过程中的内存占用上,这个模型只用到了680MB的内存,而实际上光是板端转换后的模型文件就已经800MB了,这就说明瑞芯微RKLLM在执行时并没有把完整的模型文件全部加载到内存中,而是按需加载,这样的话对于嵌入式设备这类内存非常紧张的应用还是非常实用的。 参考资料 - airockchip/rknn-llm - RKLLM SDK User Guide v1.2.3

瑞芯微RK3588 NPU开发环境搭建笔记

本文以RK3588+Debian Linux系统为基础,搭建瑞芯微平台的NPU开发环境与板端推理环境,并以Model Zoo中的Yolov5模型为例进行该模式上板运行流程的总结。 瑞芯微NPU应用开发框架 瑞芯微的NPU应用开发的整体开发流程上,基本上与君正AI应用开发的后处理流程(Post Processing)大致一致,首先是在PC上基于Pytorch或者tensorflow等进行模型的开发和训练,完成后把这个模型(如onnx格式或者pt格式)使用一个 RKNN-Toolkit2 的工具转换为RKNN格式,这个RKNN格式才是能够在瑞芯微芯片上跑起来的端侧模型格式,后续在生产环境中使用C/C++语言调用这个模型进行前向推理。 RKNN的软件框架如下图所示: !image.png RKNN-Toolkit2、RKNN-Toolkit-Lite2、RKNPU2 C++ API之间的关系: - RKNN-Toolkit2用于把PC或者服务器上训练好的模型转换为可以在瑞芯微NPU上运行的RKNN格式,运行在PC端。 - RKNN-Toolkit-Lite2是一个可以在瑞芯微NPU上基于RKNN格式的模型进行前向推理的工具,运行在瑞芯微处理器上。 - RKNN-Toolkit-Lite2本质上是一个在瑞芯微处理器上运行的Python包,所以其执行的前向推理是在瑞芯微硬件上跑的python环境中运行。如果对性能有更高要求,应该使用RKNPU2的C++ API来运行AI而不是RKNN-Toolkit-Lite2。 - 所以:RKNN-Toolkit2用于进行模型转换;RKNN-Toolkit-Lite2用于对转换后的模型在板端进行快速验证和测试;RKNPU2 C++ API才用于最终的项目实际落地。 > 我自己的看法,在板端的推理上,C/C++代码的效率要远远高于Python。所以其实没有太大必要在板端用RKNN-Toolkit-Lite2的python脚本去做推理验证,直接用RKNN-Toolkit2进行模型转换,然后在板端用C/C++进行推理才是一步到位的最成熟的做法。当然,使用RKNN-Toolkit-Lite2的python脚本在板端去做推理验证,优势则在于有大量Python包的支持,而且与PC端环境一致,写推理代码更容易。 RKNN-Toolkit2 能够支持的芯片平台列表: - RK3588 Series - RK3576 Series - RK3566/RK3568 Series - RK3562 Series - RV1103/RV1106 - RV1103B/RV1106B - RV1126B - RK2118 如上所述,瑞芯微NPU应用开发主要依赖于RKNN生态。在瑞芯微芯片的NPU上进行应用开发主要依赖于以下两个在Github上维护的代码仓库: - RKNN-Toolkit2: airockchip/rknn-toolkit2 ,模型转换工具。 - RK Model Zoo: airockchip/rknn_model_zoo ,模型上板运行的支持示例。 RKNN-Toolkit2开发环境的搭建 截至2026年初,最新的RKNN-Toolkit2的版本是2.3.2。 进行瑞芯微RKNN NPU生态的应用开发与测试,首先要进行RKNN-Toolkit2开发环境的搭建。从Github上下载RKNN-Toolkit2和RKNN Model Zoo仓库: 模型转换的Python环境 新建一个专门的rknn环境(Python版本指定为3.12)并安装RKNN-Toolkit2及其所有的依赖包(rknn-toolkit2-master/rknn-toolkit2/packages/x86_64): > 注意,按照以上流程安装的Python3.12环境,在后续在Model Zoo中把Yolov5的onnx文件转换为rkkn时会报错:AttributeError: module 'onnx' has no attribute 'mapping',这是因为Python 环境中安装的 onnx 库版本过高,与当前的 rknn-toolkit2 (v2.3.2) 产生了不兼容。应该在requirements_cp312-2.3.2.txt修改onnx依赖版本为:onnx==1.16.1, 而不是安装onnx的最新版本。 交叉编译环境 板端C/C++代码的交叉编译工具(rk3588为aarch64架构,所以安装64位系统的编译工具):https://releases.linaro.org/components/toolchain/binaries/6.3-2017.05/aarch64-linux-gnu/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu.tar.xz ,后续在编译模型上板推理的C/C++ demo时要用。 板端NPU驱动环境 此外,如以上RKNN的软件框架图所示,在RK3588板端上所执行的AI推理,同时还需要依赖于板端NPU的相关支持环境,包括板端kernel的NPU驱动、瑞芯微RKNN网络的运行时库以及其他的支持库。 首先一点,需要在板端的Kernel中启动NPU驱动支持,并且确保kernel的NPU驱动版本与RKNN-Toolkit2及其运行时库的版本相匹配。可以在Kernel的启动信息或者通过dmesg得到NPU驱动的版本信息: !image.png 除了以上NPU驱动支持以外,板端应用程序在NPU上执行推理还需要运行时库librknnrt.so的支持。librknnrt.so在rknn-toolkit2-master/rknpu2/runtime/Linux/librknn_api/aarch64位置,在进行板端推理的C/C++ demo编译时应该链接到这个动态链接库,并且把这个库提前放到板端系统的LD_LIBRARTY_PATH路径中。 此外,对于模型上板推理的C/C++ demo运行,除了以上的NPU驱动以及NPU运行时支持动态链接库librknnrt.ko,可能还需要其他的多媒体与硬件加速库: - RGA (Rockchip Graphics Architecture):用于硬件加速的图像缩放、裁剪、格式转换(如从摄像头拿到的 YUV 转为 NPU 需要的 RGB)。需要 librga.so 库文件及其头文件。 - MPP (Media Process Platform):用于进行硬件编解码(H.264/H.265),需要 librockchip_mpp.so。 至此,在瑞芯微平台上进行NPU开发的Python、C/C++交叉编译环境以及板端运行环境就已经准备好了。接下来通过Model Zoo中的Yolov5 demo对环境和NPU开发的流程进行总结。 基于Model Zoo中的YOLOv5进行上板测试 在前面已经下载的model zoo的仓库rknn_model_zoo-main的examples中,包含有多个已经在瑞芯微平台上适配好的模型案例,经过简单修改编译后即可上板运行测试。整个流程可以分为两个步骤:模型转换和板端C/C++ demo编译。 模型转换 在examples/yolov5/model目录下,执行download_model.sh,这个脚本的功能就是从指定的网络地址上下载一个onnx的yolov5s模型,所以这一步如果自己有已经训练好的yolov5s模型,完全可以使用自己训练的模型来替代。 接下来在examples/yolov5/python目录下,执行convert.sh脚本来进行模型转换(onnx->rknn)的动作。convert.sh脚本中指定了数据集描述文件路径DATASET_PATH和模型转换输出文件路径DEFAULT_RKNN_PATH,有必要的话可以对这两个值进行修改。然后执行python convert.py ../model/yolov5s_relu.onnx rk3588进行模型转换,转换后的模型会放到DEFAULT_RKNN_PATH所指定的路径中(以rknn为后缀)。 至此上板运行的模型文件就转换好了。 C/C++推理demo 在rknn_model_zoo-main目录下有两个build脚本文件:build-linux.sh和build-android.sh,分别用于针对板端的Linux和Android环境编译C/C++推理demo。此处以RK3588的Debian Linux环境为例,所以首先修改build-linux.sh,在最前面增加交叉编译工具链的路径: !image.png 然后调用build-linux.sh脚本进行yolov5板端推理demo程序的编译:./build-linux.sh -t rk3588 -a aarch64 -d yolov5 -b Release。build-linux.sh实际上会跳转到examples/yolov5/cpp/目录下,找到其中的CMakeLists.txt,调用cmake来执行完整的编译动作。 build-linux.sh的参数说明如下: build-linux.sh执行完成后会在rknn_model_zoo-main目录下生成一个install目录,其中包含了demo在板端运行所需要的所有文件:demo可执行程序,运行时依赖库librknnrt.so,板端rknn模型文件,测试图片文件。所以只需要把这个install目录打包后上传到板端执行其中的demo程序即可: