docs/develop_guides/nnadapter.md
摘要: 近年来,深度学习框架对多硬件的支持加快了 AI 硬件在各领域的落地,为了让更多硬件加入到飞桨硬件生态大家庭,本文介绍了一种新的硬件适配方案————NNAdapter: 飞桨推理 AI 硬件统一适配框架,旨在进一步降低硬件厂商适配门槛、开发和沟通成本。
注意: 为了更好的理解以下内容,可以先从这篇 飞桨推理硬件适配方案 文档对 NNAdapter 有一个快速的认识。
随着深度学习技术在各领域的广泛应用,涌现了很多比 CPU, GPU 传统架构更高效的 AI 专用芯片,例如华为昇腾 310 NPU、百度昆仑 XPU、寒武纪 MLU 和谷歌 TPU 等。
但良好的软件生态是 AI 硬件获得成功的关键,它不仅取决于硬件厂商自身软件栈的成熟度,更依赖于是否能够获得深度学习框架的广泛支持,因为后者能够帮助用户简化业务部署过程,降低因硬件差异带来的迁移成本,快速获得更高的性能和能效收益,但如何让厂商以较低成本快速完成硬件适配,又是对深度学习框架提出的一个考验。
目前,飞桨推理框架根据硬件厂商提供的接口层级,将硬件适配分为算子和子图两种方式:前者一般适用于 CPU 、GPU 这类提供低级接口的如通用编程语言/指令集、数学库和算子库的硬件;后者则适用于提供图级别如模型组网、生成接口的硬件,例如:英伟达的 TensorRT、华为昇腾的 CANN 的 GE graph 和 Intel OpenVINO 等,它的优点是屏蔽了硬件细节,模型的优化、生成和执行均由厂商的 SDK 完成,对负责硬件适配的研发人员的能力要求较低,让推理框架更多关注通用优化方法的研究和框架的开发。
近两年来,飞桨轻量推理框架 Paddle Lite 基于子图方式完成了华为昇腾 NPU、华为麒麟 NPU 、芯原 NPU 、联发科 APU 、颖脉 NNA 、寒武纪 MLU 等硬件的适配,但在与硬件厂商合作过程中,逐渐发现了该方案的一些不足之处,主要涉及以下两个方面:
由一系列 C 接口组成的、支撑各种深度学习框架在各种硬件(特别是 AI ASIC 芯片)完成高效推理的通用接口,它是建立深度学习推理框架和硬件的桥梁,实现了推理框架和硬件适配解耦,包含 API 、标准算子定义、 Runtime 和 HAL 标准接口定义四个重要组成部分。
类似于 Google 的 Android NNAPI 、NVIDIA 的 TensorRT 、 Intel 的 OpenVINO ,为了实现与推理框架的完全解耦,方便适配不同的推理框架,需要提供包含设备管理、多设备统一上下文管理、模型组网、编译和生成、执行等在内的、完备的、稳定的 API (参考 NNAPI 命名规则),实现从设备初始化、多设备统一上下文的创建、模型中间表达的建立、设备代码的生成和执行、结果的获取等一系列完整的模型推理链条的打通。具体的,包含以下几类 API (详细说明见『附录』的『 NNAdapter API 』章节):
设备管理
查询设备基本信息,包括设备名称、厂商名称、加速卡类型和 HAL 库版本,以及设备的获取和初始化等。
NNAdapterDevice_acquire, NNAdapterDevice_release, NNAdapterDevice_getName, NNAdapterDevice_getVendor, NNAdapterDevice_getType, NNAdapterDevice_getVersion
多设备统一上下文管理
创建多种设备统一的设备上下文,通过 Key-value 字串的方式为每种设备配置设备运行、模型编译和执行等参数。
NNAdapterContext_create, NNAdapterContext_destroy
模型组网
为了实现与推理框架中模型表达方式的解耦,建立与设备无关的、统一的 NNAdapter 模型 Model 的中间表达,需要基于如下 API 将推理框架的模型中的算子、张量对象转化为 NNAdapter 的操作符 Operation 和操作数 Operand。
NNAdapterModel_create, NNAdapterModel_destroy, NNAdapterModel_finish, NNAdapterModel_addOperand, NNAdapterModel_setOperandValue, NNAdapterModel_getOperandType, NNAdapterModel_addOperation, NNAdapterModel_identifyInputsAndOutputs
模型编译和生成
基于创建的模型编译实例,通过在 HAL 层库中调用厂商 SDK 实现 NNAdapter 模型的中间表达向目标设备代码的转换。
NNAdapterCompilation_create, NNAdapterCompilation_destroy, NNAdapterCompilation_finish, NNAdapterCompilation_queryInputsAndOutputs
模型执行
创建执行计划实例,设置输入、输出,执行目标设备代码后将结果返回给推理框架。
NNAdapterExecution_create, NNAdapterExecution_destroy, NNAdapterExecution_setInput, NNAdapterExecution_setOutput, NNAdapterExecution_compute
为了建立独立于推理框架的、与设备无关的、Runtime 层与 HAL 层统一的模型中间表达,除了需要定义模型和它包含的操作数和操作符的数据结构,还要对已支持的操作符的类型及参数列表进行标准化。
目前 NNAdapter 参考 ONNX 、PaddlePaddle 、Pytorch 和 TensorFlow 的算子定义完成了 65 个(后续会陆续增加)操作符的定义,形式如下所示(每个标准算子的详细定义见『附录』的『 NNAdapter 标准算子』章节):
typedef enum {
...
/**
* Performs element-wise binary addition(with Numpy-style broadcasting
* https://numpy.org/doc/stable/user/basics.broadcasting.html).
*
* Inputs:
* * 0: input0, a NNADAPTER_FLOAT32,
* NNADAPTER_QUANT_INT8_SYMM_PER_LAYER tensor.
* * 1: input1, a tensor with the same type as input0.
* * 2: fuse_code, a NNADAPTER_INT32 scalar, specifies the activation to the
* result, must be one of NNAdapterFuseCode values.
*
* Outputs:
* * 0: output, the result with the same type as two inputs.
*
* Available since version 1.
*/
NNADAPTER_ADD,
...
} NNAdapterOperationCode;
上述代码节选自 nnadapter.h ,它描述了 逐元素相加操作符 ADD 的基本功能、输入操作数列表、输出操作数列表和所适用的版本。需要注意的是,在模型组网创建一个操作符时,输入、输出操作数列表中的每一个操作数需要严格按照定义的顺序给定。
Runtime 作为 API 和 HAL 层的桥梁,其作用不仅是将 API 的调用翻译成模型、操作数、操作符的中间表达以及设备 HAL 层接口的调用,还包括设备 HAL 层库的注册、模型缓存的序列化和反序列化。
设备 HAL 层库的注册
用户进程的模型在某个设备上执行第一次推理时, Runtime 的 DeviceManager 发现该设备的 HAL 层库没有被加载,则会根据设备名找到并加载 HAL 库,再依据约定的设备接口描述符号命名规则解析并获得该设备的设备接口描述实例的首地址,进而获得设备的基本信息和各功能函数地址,最后将它注册到 DeviceManager 由其统一管理。
多种设备间的异构
目前已支持多种设备间的异构,即同一个硬件的不同运算单元,例如联发科芯片的 DSP 和 APU,它将根据每一种设备支持的操作符列表进行子图划分, 按照拓扑顺序在不同的设备中执行模型片段。
模型缓存的序列化和反序列化
Runtime 通过设备 HAL 层库调用厂商 SDK 将模型的中间表示转为设备代码的过程通常耗时较长,一般与模型规模成正比,与芯片 CPU 的处理能力成反比,例如 MobileNetV1 全量化模型在的 RK1808 芯片上的编译耗时大约在15秒左右,而 ResNet50 全量化模型的耗时更是达到分钟级别。因此,模型的在线编译和生成大大增加了用户进程启动后的第一次推理耗时,这在一些应用中是不可接受的,为了避免这个问题,Runtime 支持将已编译的设备代码缓存到文件系统中,而在下一次模型编译时直接加载该缓存文件,这就涉及到缓存文件的序列化和反序列化过程。
为了屏蔽硬件细节,向 Runtime 提供统一的设备访问接口,我们在 Runtime 和 厂商 SDK 之间建立了 HAL 硬件抽象层,它是由 C 结构体实现的统一设备接口描述、模型、操作数和操作符的中间表达等数据结构组成,代码如下所示(访问 types.h 和 device.h 获得最新代码):
typedef struct Operand {
NNAdapterOperandType type;
void* buffer;
uint32_t length;
} Operand;
typedef struct Argument {
int index;
void* memory;
void* (*access)(void* memory, NNAdapterOperandType* type);
} Argument;
typedef struct Operation {
NNAdapterOperationType type;
std::vector<Operand*> input_operands;
std::vector<Operand*> output_operands;
} Operation;
typedef struct Cache {
const char* token;
const char* dir;
std::vector<NNAdapterOperandType> input_types;
std::vector<NNAdapterOperandType> output_types;
std::vector<uint8_t> buffer;
} Cache;
typedef struct Model {
std::list<Operand> operands;
std::list<Operation> operations;
std::vector<Operand*> input_operands;
std::vector<Operand*> output_operands;
} Model;
typedef struct Device {
// Properties
const char* name;
const char* vendor;
NNAdapterDeviceType type;
int32_t version;
// Interfaces
int (*open_device)(void** device);
void (*close_device)(void* device);
int (*create_context)(void* device, const char* properties, int (*callback)(int event_id, void* user_data), void** context);
void (*destroy_context)(void* context);
int (*create_program)(void* context, Model* model, Cache* cache, void** program);
void (*destroy_program)(void* program);
int (*execute_program)(void* program, uint32_t input_count, Argument* input_arguments, uint32_t output_count, Argument* output_arguments);
} Device;
模型、操作数和操作符的中间表达
为了便于 Runtime 和 HAL 层之间的沟通,还需要建立模型的统一表达,目前采用了较为简单的 C 结构体的表示方法定义了模型 Model 、操作数 Operand 和操作符 Operation ,其中:
1)一个模型由若干个操作数、操作符组成模型的输入、输出操作数会被额外按照顺序依次存储,但操作符不一定是按照拓扑顺序存储的,您可以借助 SortOperationsInTopologicalOrder 实现操作符的拓扑排序。例如在华为昇腾 HAL 层的 对多输出的算子插入 dummy 的 ADD 算子的优化器 的实现中,需要首先调用 SortOperationsInTopologicalOrder 才能获得经过拓扑排序后的操作符列表。而为了方便调试,您还可以通过 Visualize 将模型数据结构输出为 DOT 格式字符串,将其复制到 webgraphviz 即可绘制模型拓扑结构。例如在华为昇腾 HAL 层的 打印优化前后的模型拓扑结构 代码;
2)一个操作符由操作符类型、输入操作数列表和输出操作数列表组成,需要特别注意的是,操作数列表中的元素顺序需要严格按照操作符的定义的顺序依次存放。
设备接口描述
为 Runtime 在不同硬件提供统一的访问接口,需要对硬件的功能进行抽象和封装,涉及设备基本信息和标准功能接口,以下是昇腾 310 HAL 层设备接口描述结构体的实现(访问 driver.cc 获得最新代码):
...
export "C" nnadapter::hal::Device __nnadapter_device__huawei_ascend_npu = {
.name = "huawei_ascend_npu",
.vendor = "Huawei",
.type = NNADAPTER_ACCELERATOR,
.version = 1,
.open_device = nnadapter::huawei_ascend_npu::OpenDevice,
.close_device = nnadapter::huawei_ascend_npu::CloseDevice,
.create_context = nnadapter::huawei_ascend_npu::CreateContext,
.destroy_context = nnadapter::huawei_ascend_npu::DestroyContext,
.create_program = nnadapter::huawei_ascend_npu::CreateProgram,
.destroy_program = nnadapter::huawei_ascend_npu::DestroyProgram,
.execute_program = nnadapter::huawei_ascend_npu::ExecuteProgram,
};
在注册一个新的设备时,要求对 Device 结构的所有成员进行赋值,涉及设备基本信息和从 open_device 到 execute_program 的设备标准功能接口的设置,特别是后者,它们被 Runtime 调用的时机如下图所示(详细过程可参考下一章节的『应用程序、 Paddle Lite 、NNAdapter 和硬件 SDK 之间的详细调用过程』)。
如下图所示,目前 NNAdapter 作为一个后端以子图方式接入到 Paddle Lite 中,如下步骤简单描述了 Paddle Lite 从模型的加载和解析、图优化、子图算子的执行,再到 NNAdapter HAL 层库调用硬件 SDK 执行的整个过程:
模型文件的加载和解析
Paddle 模型由程序 Program 、块 Block 、算子 Operator 和变量 Variable 组成,程序由若干块组成,块由若干算子和变量组成,变量包括中间变量和持久化变量,如卷积的权值,经序列化保存后形成 Combined 和 Non-combined 两种形式的模型文件, Non-combined 形式的模型由一个网络拓扑结构文件 model 和一系列以变量名命名的参数文件组成, Combined 形式的模型由一个网络拓扑结构文件 model 和一个合并后的参数文件 params 组成,其中网络拓扑结构文件是基于 Protocol Buffers 格式以 Paddle proto 文件描述的规则序列化后的文件。
计算图的转化
将每个块按照如下规则生成对应的计算图的过程:每个算子或变量都对应计算图的一个节点,节点间的有向边由算子的输入、输出决定(依赖关系确定边的方向),算子节点与变量节点相邻。
图分析和优化
将一系列 pass (优化器,用于描述一个计算图变换得到另一个计算图的处理过程)按照一定的顺序依次应用到每个块对应的计算图的过程,包括量化信息处理、算子融合、 Kernel 选择、类型转化、上下文创建、内存复用优化和子图检测等,实现不同设备的适配、高效的计算和更少的内存占用。其中,子图检测作为 NNAdapter 的关键模块,承担着硬件子图划分的工作,具体地,基于设备已支持的算子列表,将连续支持的算子融合形成一个子图,并在子图算子执行时将其转为 NNAdapter 模型下发给设备 HAL 层库实现子图向设备代码的转换。
运行时程序的生成和执行
按照拓扑顺序遍历优化后的计算图,生成算子和 Kernel 列表的过程。
下图描述了用户视角下的 Paddle Lite 推理框架、 NNAdapter Runtime 和 NNAdapter 硬件 HAL 层库之间的调用关系。
用户 APP 首先调用 Paddle Lite 动态库 libpaddle_full_api_shared.so 和 libpaddle_light_api_shared.so 并设置 NNAdapter 设备名称,在其首次推理时会加载 NNAdapter Runtime 动态库 libnnadapter.so ,然后根据用户设置的设备名称加载 NNAdapter 硬件 HAL 层动态库,例如华为昇腾 310 NPU 的 HAL 层库 libhuawei_ascend_npu.so ,最后调用硬件厂商的软件栈完成推理,例如华为昇腾 310 NPU 的 CANN 框架的 libascendcl.so 。
设备查询和设置
check_nnadapter_device_name
bool check_nnadapter_device_name(const std::string& device_name)
通过设备名称查询设备是否可用,设备名称包括 huawei_ascend_npu , huawei_kirin_npu , amlogic_npu , rockchip_npu , mediatek_apu , imagination_nna 等,已支持设备的最新列表可在 NNAdapter HAL 中查询。
set_nnadapter_device_names
void set_nnadapter_device_names(const std::vector<std::string>& device_names)
设置模型在哪些设备中运行。
设备上下文的参数设置
void set_nnadapter_context_properties(const std::string& context_properties)
模型缓存
set_nnadapter_model_cache_dir
void set_nnadapter_model_cache_dir(const std::string& model_cache_dir)
启用模型编译缓存功能,设置编译后的设备程序的缓存文件(以 .nnc 为扩展名)的存储目录,它能够跳过每次进程启动且模型首次推理时的编译步骤,减少首次推理耗时。
set_nnadapter_model_cache_buffers
void set_nnadapter_model_cache_buffers(const std::string& model_cache_token, const std::vector<char>& model_cache_buffer)
设置模型缓存的标识和数据,子图在编译生成设备程序时,如果成功匹配到 model_cache_token ,则跳过模型编译步骤,直接使用缓存数据恢复设备程序(需要设备 HAL 层库的支持),该接口通常用于从内存中设置解密后的模型缓存数据。
model_cache_token 对应子图和设备的模型缓存数据。自定义子图分割
set_nnadapter_subgraph_partition_config_path
void set_nnadapter_subgraph_partition_config_path(const std::string& subgraph_partition_config_path)
设置自定义子图分割配置文件路径,用于将某些算子强制异构到 CPU ,防止因切分成过多子图而导致的性能下降,内存增加。该配置文件的规则如下:
1)每行记录用于唯一标识某一个或某一类需要被强制异构到 CPU 的算子。
2)每行记录由『算子类型:输入张量名列表:输出张量名列表』组成,即以冒号分隔算子类型、输入和输出张量名列表,以逗号分隔输入、输出张量名列表中的每个张量名。
3)可省略输入、输出张量名列表中的部分张量名,如果不设置任何输入、输出张量列表,则代表计算图中该类型的所有算子节点均被强制异构到CPU。
用法举例:
op_type0:var_name0,var_name1:var_name2 表示将类型为 op_type0 、输入张量为 var_name0 和 var_name1 、输出张量为 var_name2 的算子强制异构到 CPU 上
op_type1::var_name3 表示将类型为 op_type1 、任意输入张量、输出张量为 var_name3 的算子强制异构到 CPU 上
op_type2:var_name4 表示将类型为 op_type2 、输入张量为 var_name4 、任意输出张量的算子强制异构到 CPU 上
op_type3 表示任意类型为 op_type3 的算子均被强制异构到CPU上
为了方便唯一标识模型中的某一个算子,可以在使用 cxxconfig 加载Paddle模型进行 nb 模型转换或直接推理时,设置 GLOG_v=5 打印完整调试信息,然后以 subgraph operators 为关键字搜索,例如: ssd_mobilenet_v1_relu_voc_fp32_300 模型运行在华为麒麟 NPU 时,将得到如下调试信息:
subgraph clusters: 1
digraph G {
node_1150[label="batch_norm_0.tmp_3"]
node_1154[label="batch_norm_1.tmp_3"]
node_1190[label="batch_norm_10.tmp_3"]
node_1194[label="batch_norm_11.tmp_3"]
...
node_1426->node_1427
node_1427->node_1428
node_1428->node_1429
} // end G
subgraph operators:
feed:feed:image
conv2d:image,conv1_weights,conv1_bn_offset:batch_norm_0.tmp_3
depthwise_conv2d:batch_norm_0.tmp_3,conv2_1_dw_weights,conv2_1_dw_bn_offset:batch_norm_1.tmp_3
conv2d:batch_norm_1.tmp_3,conv2_1_sep_weights,conv2_1_sep_bn_offset:batch_norm_2.tmp_3
...
box_coder:concat_0.tmp_0,concat_1.tmp_0,reshape2_0.tmp_0:box_coder_0.tmp_0
multiclass_nms:box_coder_0.tmp_0,transpose_12.tmp_0:save_infer_model/scale_0.tmp_0
fetch:save_infer_model/scale_0.tmp_0:fetch
其中:
1) subgraph operators 一行的后面是模型经过 Paddle Lite 各种优化 Pass 后的全部算子集合,可以非常方便的作为自定义子图分割配置文件的内容,这也将成为我们在硬件适配时快速调通目标模型的好帮手(即先将所有算子强制异构到 CPU 上,然后一行一行的删掉,让它们跑在目标设备上,这种方法可以快速定位问题算子,完成整个模型的调通)。
2) subgraph clusters 一行的后面是经过子图检测后的子图个数,它下面从 digraph G { 开始到 } // end G 结束的部分则是用于可视化子图检测后的模型拓扑结构的 DOT 格式字符串,可将其复制到 webgraphviz 进行可视化,其中不同颜色的算子代表所属不同的子图。
同样的,以 ssd_mobilenet_v1_relu_voc_fp32_300 为例,下面两张图展示了使用自定义子图分割配置前后的子图融合结果的对比:
1)未使用自定义子图分割配置:
2)使用如下自定义子图配置:
transpose2:conv2d_22.tmp_1:transpose_0.tmp_0,transpose_0.tmp_1
transpose2:conv2d_23.tmp_1:transpose_1.tmp_0,transpose_1.tmp_1
注意:该接口仅用于 cxxconfig 加载 Paddle 模型生成 nb 模型或直接推理时使用。
set_nnadapter_subgraph_partition_config_buffer
void set_nnadapter_subgraph_partition_config_buffer(const std::string& subgraph_partition_config_buffer)
设置自定义子图分割配置内容,该接口通常用于加、解密场景。
set_nnadapter_subgraph_partition_config_path 中阐述的一致。提示:如果图片太小看不清,可以在图片上方点击右键并选择『在新标签页中打开该图片』。
查询设备是否可用,将设置的设备名称列表、设备上下文参数、模型缓存数据存储在 Paddle Lite 的 Scope 中( Scope 与 Predictor 绑定。通常存储模型的张量数据)。
从 Scope 中获取设备名称列表、设备上下文参数,创建设备实例、设备统一上下文实例和与设备无关的模型实例。
将 Paddle Lite 子图中的张量和算子全部转换为 NNAdapter 的操作数和操作符后加入到模型实例中。
创建编译实例,从模型缓存中直接恢复设备程序,或通过目标设备的 HAL 层库调用硬件 SDK ,将模型实例编译生成设备程序。
创建执行计划实例,设置输入、输出内存和访问函数,在设备程序执行完毕后,将结果返回给应用程序。
从 driver 目录中的复制一份 HAL 作为参考(AI 加速卡类硬件可以参考华为昇腾 NPU huawei_ascend_npu , SoC 类硬件可以参考华为麒麟 NPU huawei_kirin_npu )。
基于参考硬件的 HAL 代码开发目标硬件的 HAL ,主要涉及 cmake 脚本的修改、 设备接口的实现(设备初始化、模型转换、编译和执行)。
Model 转成厂商 SDK 中的模型的表示,其工作主要在于实现 Operation 到厂商 SDK 中的算子的表示的转换器,例如:华为昇腾 NPU HAL 中的 NNADAPTER_ADD 操作符到 CANN SDK 的 ge::op::Add 的转换,代码涉及以下三个部分:
Model 到厂商 SDK 模型转换步骤的 Operation 转换过程中,用于保证正确调用指定的转换器生成并添加厂商 SDK 的算子表示,进而基于厂商 SDK 完成模型转换。基于 PaddleLite-generic-demo 跑通第一个分类模型:当目标硬件的 HAL 层代码开发完成后(前期仅需开发一个 NNADAPTER_SOFTMAX 的转换器即可),需要验证 HAL 层到厂商 SDK 的链路是否打通,为方便厂商和用户测试,我们提供了包含图像分类和目标检测模型的 Demo 的压缩包,它支持 NNAdapter 目前已支持的所有硬件,覆盖 x86 Linux 、ARM Linux 和 Android 系统,可以本地执行或基于 ssh 或 adb 方式推送到远端设备上执行,各硬件的文档均涉及 Demo 的使用方法,具体可以访问:华为昇腾 NPU 、华为麒麟 NPU 、联发科 APU 和颖脉 NNA 等。
core dump ,也可能在模型跑通后发现结果无法与 CPU 结果对齐,这些问题尝尝源于部分 NNAdapter 操作符到厂商 SDK 算子的转换器的 BUG 导致的,有效的解决办法是:先将模型中所有 Paddle 算子强制跑在 CPU 上,然后根据模型拓扑顺序,逐步将 Paddle 算子放在目标硬件上执行,通过二分法、排除法最终定位到有问题的算子转换器上,具体可以参考上一章节中『自定义子图分割』。添加算子、模型的单元测试
test_mobilenet_v1_int8_per_channel_nnadapter 和 test_mobilenet_v1_int8_per_layer_nnadapter )。添加用户说明文档,示例:华为昇腾 NPU 的文档源码。
提交代码和文档:当代码和文档都已经准备好了后,就可以向 Paddle Lite 的 github 代码仓库 发起 Pull request 了,但只有飞桨研发同学完成 code reivew 后方可合入主线,具体方法如下:
# git clone https://github.com/UserName/Paddle-Lite
# cd Paddle-Lite
$ git checkout -b UserName/FeatureName
$ pip install pre-commit
$ pre-commit install
$ git status
On branch hongming/print_ssa_graph
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
(commit or discard the untracked or modified content in submodules)
modified: lite/core/optimizer/optimizer.h
$ git diff
diff --git a/lite/core/optimizer/optimizer.h b/lite/core/optimizer/optimizer.h
index 00e9e07..1b273af 100644
--- a/lite/core/optimizer/optimizer.h
+++ b/lite/core/optimizer/optimizer.h
@@ -55,7 +55,8 @@ class Optimizer {
if (passes.empty()) {
std::vector<std::string> passes_local{
- {"lite_quant_dequant_fuse_pass", //
+ {"graph_visualze",
+ "lite_quant_dequant_fuse_pass", //
"lite_conv_elementwise_fuse_pass", // conv-elemwise-bn
$ git add lite/core/optimizer/optimizer.h
$ git status
On branch hongming/print_ssa_graph
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: lite/core/optimizer/optimizer.h
$ git commit -m "Add graph_visualze pass to output ssa graph
> test=develop"
CRLF end-lines remover...................................................Passed
Check for added large files..............................................Passed
Check for merge conflicts................................................Passed
Check for broken symlinks................................................Passed
Detect Private Key.......................................................Passed
Fix End of Files.........................................................Passed
clang-format.............................................................Passed
cpplint..................................................................Passed
copyright_checker........................................................Passed
[hongming/print_ssa_graph 75ecdce] Add graph_visualze pass to output ssa graph test=develop
1 file changed, 2 insertions(+), 1 deletion(-)
$ git remote -v
origin https://github.com/UserName/Paddle-Lite.git (fetch)
origin https://github.com/UserName/Paddle-Lite.git (push)
$ git remote add upstream https://github.com/PaddlePaddle/Paddle-Lite
$ git remote
origin
upstream
$ git fetch upstream
remote: Enumerating objects: 105, done.
remote: Counting objects: 100% (105/105), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 142 (delta 99), reused 100 (delta 99), pack-reused 37
Receiving objects: 100% (142/142), 52.47 KiB | 2.00 KiB/s, done.
Resolving deltas: 100% (103/103), completed with 45 local objects.
From https://github.com/PaddlePaddle/Paddle-Lite
a1527e8..d6cdb1e develop -> upstream/develop
2136df9..17a58b6 gh-pages -> upstream/gh-pages
1091ab8..55be873 image-sr-v2 -> upstream/image-sr-v2
* [new branch] release/v2.2.0 -> upstream/release/v2.2.0
* [new tag] v2.2.0 -> v2.2.0
$ git branch
develop
* hongming/print_ssa_graph
$ git pull upstream develop
From https://github.com/PaddlePaddle/Paddle-Lite
* branch develop -> FETCH_HEAD
Removing lite/kernels/npu/bridges/transpose_op_test.cc
Removing lite/kernels/npu/bridges/batch_norm_op_test.cc
Merge made by the 'recursive' strategy.
lite/kernels/npu/bridges/batch_norm_op_test.cc | 168 ------------------------------------------------------------------------------------------------
lite/kernels/npu/bridges/transpose_op.cc | 2 +-
lite/kernels/npu/bridges/transpose_op_test.cc | 153 ---------------------------------------------------------------------------------------
lite/tests/kernels/CMakeLists.txt | 4 +--
lite/tests/kernels/batch_norm_compute_test.cc | 2 ++
lite/tests/kernels/transpose_compute_test.cc | 44 ++++++++++++-------------
mobile/test/CMakeLists.txt | 6 ++++
mobile/test/net/test_mobilenet_male2fe.cpp | 66 ++++++++++++++++++++++++++++++++++++++
8 files changed, 99 insertions(+), 346 deletions(-)
delete mode 100644 lite/kernels/npu/bridges/batch_norm_op_test.cc
delete mode 100644 lite/kernels/npu/bridges/transpose_op_test.cc
create mode 100644 mobile/test/net/test_mobilenet_male2fe.cpp
$ git branch
develop
* hongming/print_ssa_graph
$ git push origin hongming/print_ssa_graph
Counting objects: 8, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 868 bytes | 0 bytes/s, done.
Total 8 (delta 6), reused 0 (delta 0)
remote: Resolving deltas: 100% (6/6), completed with 6 local objects.
remote:
remote: Create a pull request for 'hongming/print_ssa_graph' on GitHub by visiting:
remote: https://github.com/UserName/Paddle-Lite/pull/new/hongming/print_ssa_graph
remote:
To https://github.com/UserName/Paddle-Lite.git
* [new branch] hongming/print_ssa_graph -> hongming/print_ssa_graph
NNAdapter_getVersion
int NNAdapter_getVersion(uint32_t* version)
获取 NNAdapter 版本值。
NNAdapterDevice_acquire
NNAdapterDevice_acquire(const char* name, NNAdapterDevice** device)
通过名称获取设备实例。
NNAdapterDevice_release
NNAdapterDevice_release(NNAdapterDevice* device)
释放设备实例(注意:只有进程退出时,才会释放设备 HAL 层库)。
NNAdapterDevice_getName
int NNAdapterDevice_getName(const NNAdapterDevice* device, const char** name)
获得设备名称。
NNAdapterDevice_getVendor
int NNAdapterDevice_getVendor(const NNAdapterDevice* device, const char** vendor)
获得设备厂商名称。
NNAdapterDevice_getType
int NNAdapterDevice_getType(const NNAdapterDevice* device, NNAdapterDeviceType* type)
获得设备类型。
NNAdapterDeviceCode 定义, NNADAPTER_CPU 代表 CPU , NNADAPTER_GPU 代表 GPU , NNADAPTER_ACCELERATOR 代表神经网络加速器。NNAdapterDevice_getVersion
int NNAdapterDevice_getVersion(const NNAdapterDevice* device, int32_t* version)
获取设备HAL动态链接库的版本值。
NNAdapterContext_create
int NNAdapterContext_create(NNAdapterDevice** devices, uint32_t num_devices, const char* properties, NNAdapterContext** context)
为多种设备创建一个统一设备上下文,并通过 Key-value 字符串的形式将设备的参数信息传递给每一个设备 HAL 层库。
devices中设备实例的个数。NNAdapterContext_destroy
void NNAdapterContext_destroy(NNAdapterContext* context)
销毁统一设备上下文实例。
NNAdapterModel_create
int NNAdapterModel_create(NNAdapterModel** model)
创建一个空的、与设备无关的模型实例。
NNAdapterModel_destroy
void NNAdapterModel_destroy(NNAdapterModel* model)
销毁模型实例及相关资源。
NNAdapterModel_finish
int NNAdapterModel_finish(NNAdapterModel* model)
结束模型组网。
NNAdapterModel_addOperand
int NNAdapterModel_addOperand(NNAdapterModel* model, const NNAdapterOperandType* type, NNAdapterOperand** operand)
向模型中增加一个操作数,即神经网络模型中的张量。
NNAdapterOperandType 定义,包含精度类型、数据布局、生命周期、维度信息和量化信息。NNAdapterModel_setOperandValue
int NNAdapterModel_setOperandValue(NNAdapterOperand* operand, void* buffer, uint32_t length, bool copy)
设置常量操作数的值。
buffer 。后者要求在模型编译前都不允许修改 buffer 指向的内容。NNAdapterModel_getOperandType
int NNAdapterModel_getOperandType(NNAdapterOperand* operand, NNAdapterOperandType** type)
查询操作数的类型。
NNAdapterModel_addOperation
int NNAdapterModel_addOperation(NNAdapterModel* model, NNAdapterOperationType type, uint32_t input_count, NNAdapterOperand** input_operands, uint32_t output_count, NNAdapterOperand** output_operands, NNAdapterOperation** operation)
向模型中增加一个操作符,并设置它的输入、输出操作数,即神经网络模型中的算子。
NNAdapterOperationCode 定义,包含二维卷积 NNADAPTER_CONV_2D ,最大值池化 NNADAPTER_AVERAGE_POOL_2D ,均值池化 NNADAPTER_MAX_POOL_2D 等操作符。NNAdapterModel_identifyInputsAndOutputs
int NNAdapterModel_identifyInputsAndOutputs(NNAdapterModel* model, uint32_t input_count, NNAdapterOperand** input_operands, uint32_t output_count, NNAdapterOperand** output_operands)
标识模型的输入、输出操作数,其生命周期将被标记为 NNADAPTER_MODEL_INPUT 和 NNADAPTER_MODEL_OUTPUT 类型。
NNAdapterCompilation_create
int NNAdapterCompilation_create(NNAdapterModel* model, const char* cache_token, void* cache_buffer, uint32_t cache_length, const char* cache_dir, NNAdapterContext* context, NNAdapterCompilation** compilation)
创建一个编译实例,基于指定的统一设备上下文,为多种设备(当前版本仅支持一种设备)编译模型实例或直接加载模型缓存。如果同时设置模型实例和模型缓存参数,则优先加载模型缓存,因此存在以下三种情况:
1)当设置 cache_token , cache_buffer 和 cache_length 时,则直接从内存中加载模型缓存,此时将忽略 model 参数。
2)当设置 cache_token 和 cache_dir 时,将从 <cache_dir> 指定的目录中查找并尝试加载 <cache_token>.nnc 模型缓存文件,成功加载后将忽略 model 参数,否则在调用 NNAdapterCompilation_finish 完成模型实例 model 的在线编译后,在 <cache_dir> 目录中生成 <cache_token>.nnc 文件。
3)当 cache_token , cache_buffer , cache_length 和 cache_dir 均未被设置时,则在调用 NNAdapterCompilation_finish 后完成模型实例 model 的在线编译。需要注意的是,由于未设置 cache_token 和 cache_dir ,在编译完成后将不会生成模型缓存文件,将使得在模型首次推理时都会进行模型的在线编译,导致首次推理耗时过长。
cache_buffer 成对使用。NNAdapterCompilation_destroy
void NNAdapterCompilation_destroy(NNAdapterCompilation* compilation)
销毁编译实例。
NNAdapterCompilation_finish
int NNAdapterCompilation_finish(NNAdapterCompilation* compilation)
结束编译配置的设置,调用设备 HAL 层库对 NNAdapterCompilation_create 中的模型实例 model 进行在线编译并生成设备程序。
NNAdapterCompilation_queryInputsAndOutputs
int NNAdapterCompilation_queryInputsAndOutputs(NNAdapterCompilation* compilation, uint32_t* input_count, NNAdapterOperandType** input_types, uint32_t* output_count, NNAdapterOperandType** output_types)
查询编译后的模型的输入、输出操作数的数量和类型,必须在 NNAdapterCompilation_finish 执行后才能调用,可以通过以下两次调用获得输入、输出操作数数量和类型信息。
1)当 input_types 和 output_types 为 NULL 时,则仅查询输入、输出操作数的数量并将值存储在 input_count 和 output_count 。
2)当 input_types 和 output_types 不为 NULL 时,则将输入、输出操作数的类型依次存储在 input_types 和 output_types (要求调用方根据 input_count 和 output_count 分配它们的内存)。
NNAdapterExecution_create
int NNAdapterExecution_create(NNAdapterCompilation* compilation, NNAdapterExecution** execution)
基于编译实例创建一个执行计划实例。
为了方便理解 NNAdapterCompilation 和 NNAdapterExecution 的区别,可以将 NNAdapterCompilation 简单理解为已经编译好的设备代码,而 NNAdapterExecution 代表如何执行它,可以是顺序依次执行,也可以并行执行,可以是同步执行,也可以是异步执行,但目前 NNAdapter 仅支持同步顺序执行。
NNAdapterExecution_destroy
void NNAdapterExecution_destroy(NNAdapterExecution* execution)
销毁执行计划实例。
NNAdapterExecution_setInput
int NNAdapterExecution_setInput(NNAdapterExecution* execution, int32_t index, void* memory, void* (*access)(void* memory, NNAdapterOperandType* type))
设置执行计划输入操作数的内存实例和访问函数。
为了能够让HAL层库更加灵活的访问推理框架的张量对象,在设置执行计划的输入时,要求设置内存实例 memory 和内存实例访问函数 access ,例如:
typedef struct {
NNAdapterOperandPrecisionCode precision;
uint32_t dimensions_count;
int32_t dimensions_data[NNADAPTER_MAX_SIZE_OF_DIMENSIONS];
void* buffer;
size_t length;
} Memory;
void* access_input_memory(void* memory, NNAdapterOperandType* type) {
Memory* handle = reinterpret_cast<Memory*>(memory);
// Return the dimensions and the host buffer to HAL
memcpy(type->dimensions.data, handle->dimensions_data, handle->dimensions_count);
return handle->buffer;
}
Memory input;
NNAdapterExecution_setInput(execution, index, reinterpret_cast<void*>(&input), access_input_memory);
access 函数访问 memory 获得 host 端缓存实际地址。NNAdapterExecution_setOutput
int NNAdapterExecution_setOutput(NNAdapterExecution* execution, int32_t index, void* memory, void* (*access)(void* memory, NNAdapterOperandType* type))
设置执行计划输出操作数的内存实例和访问函数。
基于 NNAdapterExecution_setInput 示例中的 memory 的定义实现输出内存实例的访问函数 access :
void* access_output_memory(void* memory, NNAdapterOperandType* type) {
Memory* handle = reinterpret_cast<Memory*>(memory);
// Get the buffer length according to the type->precision and type->dimensions
size_t request_length = GetBufferLength(type);
if (request_length > handle->length) {
free(handle->buffer);
handle->buffer = malloc(request_length);
assert(handle->buffer);
handle->length = request_length;
}
// Tell the inference framework the output dimensions and return the host buffer to HAL
memcpy(handle->dimensions_data, type->dimensions.data, type->dimensions.count);
handle->dimensions_count = type->dimensions.count;
return handle->buffer;
}
Memory output;
NNAdapterExecution_setOutput(execution, index, reinterpret_cast<void*>(&output), access_output_memory);
access 函数访问 memory 获得 host 端缓存实际地址。NNAdapterExecution_compute
int NNAdapterExecution_compute(NNAdapterExecution* execution)
同步调度执行计划实例。
NNADAPTER_ABS
逐元素取绝对值: output = abs(input) 。
input 的形状和类型相同。NNADAPTER_ADAPTIVE_AVERAGE_POOL_2D
二维自适应平均池化。
input 相同。NNADAPTER_ADAPTIVE_MAX_POOL_2D
二维自适应最大池化。
input 相同。return_indices 决定,形状与输出操作数 output 相同,类型:NNADAPTER_INT32 、 NNADAPTER_INT64 ,由输入操作数 return_indices_dtype 决定。NNADAPTER_ADD
逐元素相加: output = input0 + input1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_AND
逐元素逻辑与: output = input0 && input1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_ARG_MAX
沿给定 axis 轴计算输入操作数 input 的最大元素的索引值。
axis 轴上计算最大元素的索引值, 形状: [1] ,类型: NNADAPTER_INT32 ,取值: axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致。axis 轴,如果保留,则输出操作数在该轴上的尺寸是 1 ,形状: [1] ,类型: NNADAPTER_BOOL8 ,取值: true 、 false 。input 和 keepdim 决定,类型: NNADAPTER_INT32 、 NNADAPTER_INT64 ,由输入操作数 dtype 决定。NNADAPTER_ARG_MIN
沿给定 axis 轴计算输入操作数 input 的最小元素的索引值。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致。input 和 keepdim 决定,类型: NNADAPTER_INT32 、 NNADAPTER_INT64 ,由输入操作数 dtype 决定.NNADAPTER_ASSIGN
将输入操作数的数据拷贝至输出操作数。
input 的形状和类型相同。NNADAPTER_AVERAGE_POOL_2D
二维平均池化。
pads 显式指定填充大小, NNADAPTER_AUTO_PAD_SAME 表示自动计算填充大小保证输出与输入的形状相同,NNADAPTER_AUTO_PAD_VALID 表示不填充。input 相同 。
当 ceil_mode 为 false 时,
H_out = floor((H_in + padding_height_top + padding_height_bottom - filter_height) / stride_height + 1)
W_out = floor((W_in + padding_width_left + padding_width_right - filter_width) / stride_width + 1)
当 ceil_mode 为 true 时,
H_out = ceil((H_in + padding_height_top + padding_height_bottom - filter_height) / stride_height + 1)
W_out = ceil((W_in + padding_width_left + padding_width_right - filter_width) / stride_width + 1)
NNADAPTER_BATCH_NORMALIZATION
按批次正则化,根据均值和方差对批数据的每个通道进行正则化,具体实现方式请参考论文 Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift https://arxiv.org/pdf/1502.03167.pdf 。
input 的形状和类型相同。NNADAPTER_CAST
数据类型转换。
dtype 相同,形状和 input 相同。NNADAPTER_CHANNEL_SHUFFLE
通道混洗重排,它将输入通道分成 group 个子组,并通过逐一从每个子组中选择元素来获得新的顺序: C_out[k * group + g] = C_in[g * size + k] ,其中 size = C_in / group ,具体实现请参考论文 https://arxiv.org/pdf/1707.01083.pdf 。
input 的通道数,形状: [1] ,类型: NNADAPTER_FLOAT32。input 的形状和类型相同。NNADAPTER_CLIP
对所有元素进行剪裁,使其限制在 [min, max] 内: output = min(max(input, min), max) 。
input 相同。input 相同。input 的形状和类型相同。NNADAPTER_CONCAT
沿 axis 轴将多个输入进行拼接。
axis 轴的维度不同,所有输入的其它维度数必须相同,类型:NNADAPTER_FLOAT32 、 NNADAPTER_QUANT_INT8_SYMM_PER_LAYER 。axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致。input0 ~ inputn-1 的类型相同。NNADAPTER_CONV_2D
二维卷积。
pads 显式指定填充大小, NNADAPTER_AUTO_PAD_SAME 表示自动计算填充大小保证输出与输入的形状相同,NNADAPTER_AUTO_PAD_VALID 表示不填充。group 必须为 1 。group = C_out = C_in 。0 : output ,输出操作数,类型与输入 input 相同,形状: [N, C_out, H_out, W_out],计算公式如下:
H_out = (H_in + padding_height_top + padding_height_bottom - (dilation_height * (filter_height - 1) + 1)) / stride_height + 1
W_out = (W_in + padding_width_left + padding_width_right - (dilation_width * (filter_width - 1) + 1)) / stride_width + 1
NNADAPTER_CONV_2D_TRANSPOSE
二维转置(反)卷积。
pads 显式指定填充大小, NNADAPTER_AUTO_PAD_SAME 表示自动计算填充大小保证输出与输入的形状相同,NNADAPTER_AUTO_PAD_VALID 表示不填充。group 必须为 1 。group = C_out = C_in 。0 : output ,输出操作数,类型与输入 input 相同,形状: [N, C_out, H_out, W_out],计算公式如下:
H_out = (H_in - 1) * stride_height - padding_height_top - padding_height_bottom + (dilation_height * (filter_height - 1)) + 1 + output_padding_height
W_out = (W_in - 1) * stride_width - padding_width_left - padding_width_right + (dilation_width * (filter_width - 1) + 1)) + 1 + output_padding_width
NNADAPTER_COS
逐元素取余弦值: output = cos(input) 。
input 的形状和类型相同。NNADAPTER_CUM_SUM
沿给定 axis 轴计算累加和。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,默认是 -1 。input 的形状和类型相同。NNADAPTER_DEFORMABLE_CONV_2D
二维可变形卷积。
input 相同。input 相同。group 必须为 1 。group = C_out = C_in 。0 : output ,输出操作数,类型与输入 input 相同,形状: [N, C_out, H_out, W_out],计算公式如下:
H_out = (H_in + padding_height_top + padding_height_bottom - (dilation_height * (filter_height - 1) + 1)) / stride_height + 1
W_out = (W_in + padding_width_left + padding_width_right - (dilation_width * (filter_width - 1) + 1)) / stride_width + 1
NNADAPTER_DEQUANTIZE
反量化:output = (input - zero_point) * scale , 其中 zero_point 和 scale 来自输入操作数 input 的类型参数,如果采用对称量化,则有:zero_point = 0 。
input 相同。NNADAPTER_DIV
逐元素除: output = input0 / input1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_EQUAL
逐元素关系等于: output = input0 == input1 ,与 Numpy 的广播规则 https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型: NNADAPTER_BOOL8 。NNADAPTER_EXP
逐元素计算 e 的次幂: output = e^input 。
input 的形状和类型相同。NNADAPTER_EXPAND
根据给定的形状对输入进行扩展,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
shape 的值相同,类型和 input 相同。NNADAPTER_FILL
创建指定形状和类型的操作数,将其所有元素值全部填充为同一个值。
shape 的值相同,类型和值与 value 相同。NNADAPTER_FILL_LIKE
根据给定操作数的形状创建一个新的操作数,将其所有元素值全部填充为同一个值。
input 相同,类型和值与 value 相同。NNADAPTER_FLATTEN
根据给定的 start_axis 和 stop_axis 起、止轴将连续的维度进行展开。
input 相同.NNADAPTER_FLOOR
逐元素向下取整: output = floor(input) 。
input 的形状和类型相同。NNADAPTER_FLOOR_DIV
逐元素相除并向下取整: output = floor (input0 / input1) ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_FULLY_CONNECTED
全链接层: output = activation(input * weight' + bias) 。
weight[1] , batch_size = num_elements / input_size , num_elements 是 input 的元素个数, 类型: NNADAPTER_FLOAT32 、 NNADAPTER_QUANT_INT8_SYMM_PER_LAYER 。input 相同。NNADAPTER_GATHER
沿着给定的轴根据索引获取指定的单个或多个条目。
input 在 axis 维度的长度。axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致。input 相同,维度是 Q + (R - 1) 。NNADAPTER_GELU
逐元素计算高斯误差线性单元激活值,具体实现请参考论文 https://arxiv.org/abs/1606.08415 。
input 的形状和类型相同。NNADAPTER_GREATER
逐元素关系大于: output = input0 > input1 ,与 Numpy 的广播规则 https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型: NNADAPTER_BOOL8 。NNADAPTER_GREATER
逐元素关系大于等于: output = input0 >= input1 ,与 Numpy 的广播规则 https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型: NNADAPTER_BOOL8 。NNADAPTER_GRID_SAMPLE
基于 flow field 网格的对输入进行双线性插值采样,网格通常由 affine_grid 生成, 形状为 [N, H, W, 2] ,它是 [N, H, W] 的采样点的 (x, y) 坐标。 其中,x 坐标是输入数据的 W 维度的索引,y 坐标是 H 维度的索引,最终输出采样值为采样点的四个最接近的角点的双线性插值结果,输出形状为 [N, C, H, W] 。
input 的形状和类型相同。NNADAPTER_GROUP_NORMALIZATION
按组正则化,根据均值和方差对通道进行分组正则化,具体实现方式请参考论文 Group Normalization https://arxiv.org/abs/1803.08494 。
input 的形状和类型相同。NNADAPTER_HARD_SIGMOID
逐元素计算分段线性逼近激活值: output = max(0, min(1, alpha * input + beta)) 。
input 的形状和类型相同。NNADAPTER_HARD_SWISH
逐元素计算 hardswish 激活值: output = input * max(0, min(1, alpha * input + beta)) 。
input 的形状和类型相同。NNADAPTER_INSTANCE_NORMALIZATION
按实例正则化,根据每个样本的每个通道的均值和方差信息进行正则化, 具体实现请参考论文 Instance Normalization: The Missing Ingredient for Fast Stylization https://arxiv.org/abs/1607.08022 。
input 的形状和类型相同。NNADAPTER_LAYER_NORMALIZATION
按层正则化,具体实现请参考论文 Layer Normalization https://arxiv.org/pdf/1607.06450v1.pdf 。
begin_norm_axis 轴到 rank(input) 的全部维度 ,类型: NNADAPTER_FLOAT32 。begin_norm_axis 轴到 rank(input) 的全部维度 ,类型: NNADAPTER_FLOAT32 。begin_norm_axis 轴到 rank(input) 的维度执行,形状: [1] ,类型: NNADAPTER_INT32 。input 的形状和类型相同。NNADAPTER_LEAKY_RELU
逐元素计算修正线性单元激活值: 当 input >= 0 时, output = input ; 当 input < 0 时, output = alpha * input 。
input 的形状和类型相同。NNADAPTER_LESS
逐元素关系小于: output = input0 < input1 ,与 Numpy 的广播规则 https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型: NNADAPTER_BOOL8 。NNADAPTER_LESS_EQUAL
逐元素关系小于等于: output = input0 <= input1 ,与 Numpy 的广播规则 https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型: NNADAPTER_BOOL8 。NNADAPTER_LOG
逐元素计算自然对数: output = ln(input) 。
input 的形状和类型相同。NNADAPTER_LOG_SOFTMAX
沿着给定的轴逐元素计算 log softmax 激活值: output = log(exp(input) / reduce_sum(exp(input), axis=axis, keepdims=true)) 。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,默认是 1 。input 的形状和类型相同。NNADAPTER_LP_NORMALIZATION
沿给定轴进行 Lp 正则化: 当 p = 1 时, output = input / (sum(abs(input)) + epsilon) ; 当 p = 2 时, output = input / (sqrt(sum(input^2)) + epsilon) 。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,默认是 1 。input 的形状和类型相同。NNADAPTER_LRN
局部响应正则化层,用于对局部输入区域正则化,执行一种侧向抑制,具体实现参考论文:https://papers.nips.cc/paper/4824-imagenet-classification-with-deep-convolutional-neural-networks.pdf
input 的形状和类型相同。NNADAPTER_MAT_MUL
计算两个操作数的乘积,计算方法与 numpy.matmul https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.matmul.html 相同。
x 相同。x 的最后两维转置,形状: [1] , 类型:NNADAPTER_BOOL8 ,取值: true 、 false ,默认是 false 。y 的最后两维转置,形状: [1] , 类型:NNADAPTER_BOOL8 ,取值: true 、 false ,默认是 false 。x 和 y 广播后的形状决定,类型与输入操作数 x 和 y 相同。NNADAPTER_MAX
逐元素取最大值: output = max(input0 , input1) ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_MAX_POOL_2D
二维最大池化。
pads 显式指定填充大小, NNADAPTER_AUTO_PAD_SAME 表示自动计算填充大小保证输出与输入的形状相同,NNADAPTER_AUTO_PAD_VALID 表示不填充。input 相同 。
当 ceil_mode 为 false 时,
H_out = floor((H_in + padding_height_top + padding_height_bottom - filter_height) / stride_height + 1)
W_out = floor((W_in + padding_width_left + padding_width_right - filter_width) / stride_width + 1)
当 ceil_mode 为 true 时,
H_out = ceil((H_in + padding_height_top + padding_height_bottom - filter_height) / stride_height + 1)
W_out = ceil((W_in + padding_width_left + padding_width_right - filter_width) / stride_width + 1)
return_indices 决定,形状与输出操作数 output 相同,类型:NNADAPTER_INT32 、 NNADAPTER_INT64 ,由输入操作数 return_indices_dtype 决定。NNADAPTER_MESHGRID
根据给定的多个向量创建多个网格。
input0 ~ inputn-1 相同。NNADAPTER_MIN
逐元素取最小值: output = min(input0 , input1) ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_MUL
逐元素相乘: output = input0 * input1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_NOT
逐元素逻辑非: output = !input 。
input 的形状和类型相同。NNADAPTER_NOT_EQUAL
逐元素关系不等于: output = input0 != input1 ,与 Numpy 的广播规则 https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型: NNADAPTER_BOOL8 。NNADAPTER_OR
逐元素逻辑或: output = input0 || input1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_PAD
多维填充。
input)] ,类型: NNADAPTER_INT32 ,取值: 根据每个维度设置填充大小,每个元素代表的含义为: x0_begin, x0_end, x1_begin, x1_end, ... ,其中 x0_begin 和 x0_end 分别代表第 0 维的左、右边界的填充大小。input 相同。input 的形状和 pads 的值决定,类型与输入操作数 input 相同。NNADAPTER_POW
逐元素计算指数: output = input0 ^ input1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_PRIOR_BOX
在 SSD ( Single Shot MultiBox Detector )模型中用于生成候选框,输入的每个位产生 N 个候选框, N 由 min_sizes , max_sizes 和 aspect_ratios 的数目决定,候选框的尺寸在 ( min_size , max_size ) 之间,该尺寸根据 aspect_ratios 在序列中生成。
NNADAPTER_PRELU
逐元素计算参数化修正线性单元激活值:当 input >= 0 时, output = input ;当 input < 0 时, output = slope * input 。
input 的形状和类型相同。NNADAPTER_QUANTIZE
量化: output = input / scale + zero_point 。
scale 的形状决定,形状: [1] ,类型: NNADAPTER_INT32 ,取值: axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,默认是 1 。scale 相同,类型: NNADAPTER_FLOAT32。scale 和 zero_point 决定,形状和 input 相同。NNADAPTER_RANGE
生成一个由以步长 step 均匀分隔给定数值区间 [ start , end ) 的连续数值组成的操作数。
start 的形状和类型相同。start 的形状和类型相同。start 、 end 、 step 共同决定, 类型与输入操作数 start 相同。NNADAPTER_REDUCE_MAX
沿着给定的单个或多个轴计算最大值。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,如果是空,则对所有维度计算并返回单个元素。input 、 axes 、 keepdim 共同决定, 类型与输入操作数 input 相同。NNADAPTER_REDUCE_MEAN
沿着给定的单个或多个轴计算平均值。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,如果是空,则对所有维度计算并返回单个元素。input 、 axes 、 keepdim 共同决定, 类型与输入操作数 input 相同。NNADAPTER_REDUCE_SUM
沿着给定的单个或多个轴计算和。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,如果是空,则对所有维度计算并返回单个元素。input 、 axes 、 keepdim 共同决定, 类型与输入操作数 input 相同。NNADAPTER_RELU
逐元素计算线性整流单元激活值: output = max(0, input) 。
input 的形状和类型相同。NNADAPTER_RELU6
逐元素计算线性整流单元激活值: output = min(6, max(0, input)) 。
input 的形状和类型相同。NNADAPTER_RESHAPE
改变形状,维持所包含的元素的数量和数值不变。
shape 和 input 的形状计算获得,类型与输入操作数 input 相同。NNADAPTER_RESIZE_NEAREST
基于最临近插值法调整图像大小,输出的高和宽按照 shape 、 scales 顺序确定优先级。
shape 和 scales 必须至少设置一个。shape 、 scales 、 input 的维度计算获得,类型与输入操作数 input 相同。NNADAPTER_RESIZE_LINEAR
基于双向性插值法调整图像大小,输出的高和宽按照 shape 、 scales 顺序确定优先级。
shape 和 scales 必须至少设置一个。shape 、 scales 、 input 的维度计算获得,类型与输入操作数 input 相同。NNADAPTER_ROI_ALIGN
在指定输入的感兴趣区域上基于双线性插值以获得固定大小的特征图,具体实现请参考论文 Mask R-CNN https://arxiv.org/abs/1703.06870 。
rois 中的坐标从其输入尺寸按比例映射到输入特征图的尺寸,形状: [1] , 类型: NNADAPTER_FLOAT32 。input 相同。NNADAPTER_ROLL
沿给定维度滚动张量输入。超出最后位置的元素将在第一个位置重新引入,如果不设置 axes,则张量将在滚动之前展开变平,然后恢复为原始形状。。
input 的形状和类型相同。NNADAPTER_RSQRT
逐元素计算平方根的倒数: output = 1 / (sqrt(input)) 。
input 的形状和类型相同。NNADAPTER_SHAPE
获得输入的形状。
NNADAPTER_SIGMOID
逐元素计算 sigmoid 激活值: output = 1 / (1 + exp(-input)) 。
input 的形状和类型相同。NNADAPTER_SIN
逐元素取正弦值: output = sin(input) 。
input 的形状和类型相同。NNADAPTER_SLICE
沿着多个轴生成 input 的片段。类似 numpy : https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html ,沿着 axes 的每个轴以 starts 、 ends 、 step 为起始、终止、步长获取 input 的片段。如果 starts[i] 、 ends[i] 为负数,则需要加上输入 input 对应轴 axes[i] 的维度 dims[axes[i]] 。如果 starts[i] 或 ends[i] 的值大于 dims[axes[i]] ,将被截断到 dims[axes[i]] - 1 。如果 dims[axes[i]] 维度未知,建议将 ends[i] 设置为 INT_MAX ,反向则设置为 INT_MIN 。
starts) - 1] 。axes 相同,类型: NNADAPTER_INT32 。axes 相同,类型: NNADAPTER_INT32 。axes 相同,类型: NNADAPTER_INT32 ,取值: 默认值是 1。axes 、 starts 、 ends 、steps 和 input 的维度计算获得,类型与输入操作数 input 相同。NNADAPTER_SOFTMAX
沿着给定的轴逐元素计算 softmax 激活值: output = exp(input) / reduce_sum(exp(input), axis=axis, keepdims=true) 。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,默认是 1 。input 的形状和类型相同。NNADAPTER_SOFTPLUS
逐元素计算 softplus 激活值: output = log(1 + exp^(beta * input)) / beta,考虑数值稳定性,当 beta * input > threshold ,公式转变为线性函数 output = input。
input 的形状和类型相同。NNADAPTER_SPLIT
沿着给定的轴将输入分割成多个子部分。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致。input 在 axis 轴的维度。axis 、 split 和 input 的维度计算获得,类型与输入操作数 input 相同。NNADAPTER_SQUARE
逐元素计算平方: output = input^2 。
input 的形状和类型相同。NNADAPTER_SQUEEZE
沿给定 axes 轴删除 input 的形状中长度为 1 的维度。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,如果是空,则删除所有维度中长度为 1 的维度。input 的类型相同。NNADAPTER_STACK
沿给定 axis 轴对输入进行堆叠操作,要求所有输入的形状相同。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致。input0 ~ inputn-1 的类型相同。NNADAPTER_SUB
逐元素相减: output = input0 - input1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 相同。input0 和 input1 广播后的形状决定,类型与输入操作数 input0 和 input1 相同。NNADAPTER_SUM
多个输入逐元素求和: output = input0 + input1 + ... + inputn-1 ,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同。
input0 ~ inputn-1 广播后的形状决定,类型与输入操作数 input0 ~ inputn-1 相同。NNADAPTER_SWISH
逐元素计算 swish 激活值: output = input / (1 + e ^ (-input)) 。
input 的形状和类型相同。NNADAPTER_TANH
逐元素计算 tanh 激活值: output = tanh(input) 。
input 的形状和类型相同。NNADAPTER_TILE
沿着输入的每个维度 i 复制 repeats[i] 次。
input)] ,类型: NNADAPTER_INT32 。input 相同,且 output_dims[i] = input_dims[i] * repeats[i] ,类型:与输入操作数 input 相同。NNADAPTER_TOP_K
沿给定的轴 axis 在 input 中查找最大或最小的前 k 个值和索引。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致。k 个值,类型: NNADAPTER_BOOL8 ,取值: true 、 false , false 代表返回最小的 k 个值。k 个值,类型:与输入操作数 input 相同。k 个值的索引,类型:NNADAPTER_INT32 或 NNADAPTER_INT64 。NNADAPTER_TRANSPOSE
根据 perm 对输入进行数据重排,类似于 numpy.transpose https://numpy.org/doc/stable/reference/generated/numpy.transpose.html 。例如:输入的形状为 (1, 2, 4) , perm 为 (1, 0, 2) ,输出形状为 (2, 1, 3) 。
input)] ,类型: NNADAPTER_INT32 。input 相同,且 output_dims[i] = input_dims[perm[i]] ,类型:与输入操作数 input 相同。NNADAPTER_UNSQUEEZE
沿给定 axes 轴在 input 的形状中插入长度为 1 的维度。
axis 的有效范围是 [-R, R) , R 是输入操作数 input 的维度,当 axis 为负数时,效果与 axis + R 一致,如果是空,则删除所有维度中长度为 1 的维度。input 的类型相同。NNADAPTER_WHERE
根据条件 condition 从 input0 或 input1 中选择元素作为输出,广播规则与 Numpy https://numpy.org/doc/stable/user/basics.broadcasting.html 相同, 行为与 numpy.where https://numpy.org/doc/stable/reference/generated/numpy.where.html 相同。
input0 或 input1 的条件,类型: NNADAPTER_BOOL8 ,取值:对应位置的值为 true,则输出的相应位置返回 input0 的元素,否则返回 input1 的元素。input0 相同。input 的类型相同。NNADAPTER_YOLO_BOX
基于YOLOv3网络的输出结果,生成YOLO检测框, 具体细节可以参考 https://www.paddlepaddle.org.cn/documentation/docs/zh/2.1/api/paddle/vision/ops/yolo_box_cn.html#yolo-box 。
imgsize 范围内,形状: [1] ,类型: NNADAPTER_BOOL8,取值: true 、false ,默认是 true 。class_num] ,类型: NNADAPTER_FLOAT32 。