取决于模型输出数据的用途和后处理的计算模式,应用程序可以使用不同层次的接口来获取模型输出。这些接口应该在 hbrtRiDestroy 之前调用。
最简单的方法是调用以下接口。
这个接口会以 work 参数指定的格式返回模型的第 output_index 个输出。这里的 work 是一个枚举类型,包括以下可能的取值。
例如,如果设置 work 为 WORK_MALLOC_NATIVE_NOPADDING_FLOAT,这个接口会以浮点格式返回native排布、没有 pad 的输出。而如果 work 设置为 WORK_BPU_RAW,这个接口就会直接返回对应输出的 BPU 地址。
也可以使用相应输出特征图的句柄和运行实例的 ID 调用以下接口来获取模型输出。
或者通过以下接口获取输出的 BPU 地址。
为了提升应用的性能,我们推荐从BPU的原始输出数据开始进行后处理。假设后处理算法需要输入浮点数据,此时需要的数据转换步骤是最多的。这些步骤包括 数据layout转换 -> 去除padding -> 反量化 。如果后处理算法能够以跳读的方式获取模型输出,那么去除 padding 的步骤可以被省略。下面简要介绍 BPU 输出数据处理的每一个步骤。
调用以下接口来进行数据的 layout 转换。
注意这些接口只进行 layout 转换,数据的维度和数据类型不会发生改变。接口的 aligned_dim 参数既是输入张量的维度也是输出张量的维度。BPU 上的部分单元可能产生按照大端存储的结果,对于这些结果应该设置接口的 convert_endianness 为 true 来在小端平台上完成大小端转换。可以通过以下接口查询一个特征图是否是按照大端存储。
hbrtConvertLayout 会一次性完成整个特征图的转换,而 hbrtConvertLayoutRoi 只转换整个特征图中的一个 ROI。如果输出数据的通道数很多,我们推荐在一个三重循环中调用 hbrtConvertLayoutToNative111C 来完成 layout 转换并做后处理,这个接口每次只转换一个像素点的所有通道值的数据。这样可以更好地利用 CPU 的缓存局部性。另一种极端是输出数据只有一个通道,此时在一个两重循环中调用 hbrtConvertLayoutToNative1HW1 可能会获得更好的性能。另外一个优化后处理的方向是将数据转换和后处理逻辑融合在一起。例如,对于用来完成实例分割任务的模型,模型输出可能是每个像素块对于不同类别的置信度,后处理逻辑可能只是简单地对于每个像素块找出置信度最高的类别。在这种场景下,可以每次调用 hbrtConvertLayoutToNative111C 转换输出数据一个像素点的所有通道值,然后遍历转换的结果找到数值最大的类别。之后用来存储转换结果的内存就可以被释放或者重新用来存储下一个像素点转换的结果。
以下接口可以用来去除 padding。通常我们不推荐使用这些接口,在后处理中进行跳读总是可以获得更好的性能。
以下接口相反地可以为数据加上 padding。对于从 DDR 输入的模型,如果产生输入数据的模块不支持跳写,可能需要调用以下接口来预处理 BPU 输入数据。
可以使用以下 API 量化模型输入数据,或者反量化模型输出数据。
需要注意的并不是所有的 BPU 输出都需要进行转换。一些操作产生的输出可以被直接解析。可以通过以下接口来查询一个特征图是被什么样的操作产生。
这些操作包括:
如果一个输出特征图是被 CONV 产生的,通常它需要经过以上部分或者全部的步骤来完成数据转换。但是如果输出特征图是由其余已知的操作产生的,则可以使用以下的数据结构来直接解析结果。
对于 OUTPUT_BY_DETECTION_POST_PROCESS, OUTPUT_BY_DETECTION_POST_PROCESS_STABLE_SORT 和 OUTPUT_BY_RCNN_POST_PROCESS, 输出数据的第一个元素(可能是 uint16 或者 float32)表示有效数据有多少个 byte。然后略过相应数据结构的 byte 数,从第二个数据结构开始,每几个 bytes 可以用以下数据结构来读取。