# 离线构建自定义算子
## 概述
MindSpore Lite的[转换工具](https://www.mindspore.cn/lite/docs/zh-CN/r2.0.0-alpha/use/converter_tool.html)除了基本的模型转换功能之外,还支持用户对模型进行自定义的优化与构建,生成用户自定义算子的模型。
我们提供了一套注册机制,允许用户基于转换工具进行能力扩展:包括节点解析扩展、模型解析扩展以及图优化扩展,用户可以根据自身的需要对模型实现自定义的解析与融合优化。
节点解析扩展:用户自定义模型中某一节点的解析过程,支持ONNX、CAFFE、TF、TFLITE。接口可参考[NodeParser](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_converter.html#nodeparser)、[NodeParserRegistry](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#nodeparserregistry)。
模型解析扩展:用户自定义模型的整个解析过程,支持ONNX、CAFFE、TF、TFLITE。接口可参考[ModelParser](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_converter.html#modelparser)、[ModelParserRegistry](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#modelparserregistry)。
图优化扩展:模型解析之后,将获得MindSpore定义的图结构,用户可基于此结构自定义图的优化过程。接口可参考[PassBase](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#passbase)、[PassPosition](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#passposition)、[PassRegistry](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#passregistry)。
> 节点解析扩展需要依赖flatbuffers和protobuf及三方框架的序列化文件,并且flatbuffers和protobuf需要与发布件采用的版本一致,序列化文件需保证兼容发布件采用的序列化文件。发布件中不提供flatbuffers、protobuf及序列化文件,用户需自行编译,并生成序列化文件。用户可以从[MindSpore仓](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha)中获取[flatbuffers](https://gitee.com/mindspore/mindspore/blob/r2.0.0-alpha/cmake/external_libs/flatbuffers.cmake)、[probobuf](https://gitee.com/mindspore/mindspore/blob/r2.0.0-alpha/cmake/external_libs/protobuf.cmake)、[ONNX原型文件](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha/third_party/proto/onnx)、[CAFFE原型文件](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha/third_party/proto/caffe)、[TF原型文件](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha/third_party/proto/tensorflow)和[TFLITE原型文件](https://gitee.com/mindspore/mindspore/blob/r2.0.0-alpha/mindspore/lite/tools/converter/parser/tflite/schema.fbs)。
>
> MindSpore Lite还提供了一系列的注册宏,以便于用户侧的扩展接入转换工具。注册宏包括节点解析注册[REG_NODE_PARSER](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#reg-node-parser)、模型解析注册[REG_MODEL_PARSER](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#reg-model-parser)、图优化注册[REG_PASS](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#reg-pass)、图优化调度注册[REG_SCHEDULED_PASS](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#reg-scheduled-pass)。
MindSpore Lite转换工具的扩展能力,目前仅支持Linux系统。
本章节将通过MindSpore Lite转换工具扩展功能的示例程序,涵盖节点扩展案例、优化扩展案例以及编译链接全流程,来使用户能够快速了解转换工具的扩展功能的使用。
> 模型解析扩展,鉴于是模块化的扩展能力,本章不做详细介绍,但会提供一个简化的单元案例,以供用户参考。
本章节以[add.tflite](https://download.mindspore.cn/model_zoo/official/lite/quick_start/add.tflite)模型为例。该模型仅包含一个简单的Add算子,通过自定义的节点解析、图优化,将Add算子转化为[Custom算子](https://www.mindspore.cn/lite/docs/zh-CN/r2.0.0-alpha/use/register_kernel.html#custom算子),最终输出Custom单算子模型。
相关代码放置在[mindspore/lite/examples/converter_extend](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha/mindspore/lite/examples/converter_extend)目录。
## 节点扩展
1. 自定义节点解析:用户需继承[NodeParser](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_converter.html#nodeparser),继而根据不同的框架,选择不同的重载接口。
2. 节点解析注册:用户调用注册接口[REG_NODE_PARSER](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#reg-node-parser),完成自定义的节点解析接入转换工具。
```c++
class AddParserTutorial : public NodeParser { // 继承基类
public:
AddParserTutorial() = default;
~AddParserTutorial() = default;
ops::PrimitiveC *Parse(const std::unique_ptr &tflite_op, // 重载接口
const std::unique_ptr &tflite_subgraph,
const std::unique_ptr &tflite_model) override;
};
REG_NODE_PARSER(kFmkTypeTflite, ADD, std::make_shared()); // 调用注册接口
```
示例代码请参考[node_parser](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha/mindspore/lite/examples/converter_extend/node_parser)。
## 模型扩展
示例代码请参考[MindSpore仓](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha)的模型扩展的单元案例[ModelParserRegistryTest](https://gitee.com/mindspore/mindspore/blob/r2.0.0-alpha/mindspore/lite/test/ut/tools/converter/registry/model_parser_registry_test.cc)。
### 优化扩展
1. 自定义优化:用户需继承[PassBase](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#passbase),重载Execute接口函数[Execute](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#execute)。
2. 优化注册:调用优化的注册接口[REG_PASS](https://www.mindspore.cn/lite/api/zh-CN/r2.0.0-alpha/api_cpp/mindspore_registry.html#reg-pass),完成自定义把用户自己实现的Pass类注册进MindSpore Lite里。
```c++
class PassTutorial : public registry::PassBase { // 继承基类
public:
PassTutorial() : PassBase("PassTutorial") {}
~PassTutorial() = default;
bool Execute(const api::FuncGraphPtr &func_graph) override; // 重载接口
private:
AnfNodePtr CreateCustomOp(const api::FuncGraphPtr func_graph, const CNodePtr &cnode);
};
using mindspore::registry::POSITION_BEGIN; // 选择调度位置
REG_PASS(PassTutorial, opt::PassTutorial) // 注册扩展类
REG_SCHEDULED_PASS(POSITION_BEGIN, {"PassTutorial"}) // 注册调度逻辑
```
示例代码可参考[pass](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha/mindspore/lite/examples/converter_extend/pass)。
> 在离线转换阶段,我们会对模型的每一个节点的输出张量进行推断,包括输出张量的Format、DataType以及Shape,因此,离线转换阶段,用户需提供自己实现的算子的推断过程,这里用户可以参考[算子Infershape扩展](https://www.mindspore.cn/lite/docs/zh-CN/r2.0.0-alpha/use/runtime_cpp.html#扩展使用)说明,示例代码可参考[infer](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha/mindspore/lite/examples/converter_extend/infer)。
## 示例演示
### 编译
- 环境要求
- 系统环境:Linux x86_64,推荐使用Ubuntu 18.04.02LTS
- 编译依赖:
- [CMake](https://cmake.org/download/) >= 3.18.3
- [GCC](https://gcc.gnu.org/releases.html) >= 7.3.0
- 编译准备
MindSpore Lite的发布件不会提供其他框架下的序列化文件,因此,用户需自行编译获得,请参考[概述](https://www.mindspore.cn/lite/docs/zh-CN/r2.0.0-alpha/use/converter_register.html#概述)。
本示例采用的是tflite模型,用户需编译[flatbuffers](https://gitee.com/mindspore/mindspore/blob/r2.0.0-alpha/cmake/external_libs/flatbuffers.cmake),从[MindSpore仓](https://gitee.com/mindspore/mindspore/tree/r2.0.0-alpha)中获取[TFLITE原型文件](https://gitee.com/mindspore/mindspore/blob/r2.0.0-alpha/mindspore/lite/tools/converter/parser/tflite/schema.fbs),最终生成tflite的序列化文件。
在`mindspore/lite/examples/converter_extend`目录下创建`schema`文件目录,继而将生成的序列化文件置于`schema`目录下。
- 编译构建
在`mindspore/lite/examples/converter_extend`目录下执行[build.sh](https://gitee.com/mindspore/mindspore/blob/r2.0.0-alpha/mindspore/lite/examples/converter_extend/build.sh),将自动下载MindSpore Lite发布件并编译Demo。
```bash
bash build.sh
```
> 若使用该build脚本下载MindSpore Lite发布件失败,请手动下载硬件平台为CPU、操作系统为Ubuntu-x64的MindSpore Lite发布件[mindspore-lite-{version}-linux-x64.tar.gz](https://www.mindspore.cn/lite/docs/zh-CN/r2.0.0-alpha/use/downloads.html),将解压后`tools/converter/lib`目录、`tools/converter/include`目录拷贝到`mindspore/lite/examples/converter_extend`目录下。
>
> 通过手动下载并且将文件放到指定位置后,需要再次执行build.sh脚本才能完成编译构建。
- 编译输出
在`mindspore/lite/examples/converter_extend/build`目录下生成了`libconverter_extend_tutorial.so`的动态库。
### 执行程序
1. 拷贝动态库
将生成的`libconverter_extend_tutorial.so`动态库文件拷贝到发布件的`tools/converter/lib`下。
2. 进入发布件的转换目录
```bash
cd ${PACKAGE_ROOT_PATH}/tools/converter/converter
```
3. 创建converter的配置文件(converter.cfg),文件内容如下:
```text
[registry]
plugin_path=libconverter_extend_tutorial.so # 用户请配置动态库的正确路径
```
4. 将转换工具需要的动态链接库加入环境变量`LD_LIBRARY_PATH`
```bash
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/tools/converter/lib
```
5. 执行converter
```bash
./converter_lite --fmk=TFLITE --modelFile=add.tflite --configFile=converter.cfg --outputFile=add_extend
```
执行完后,将生成名为`add_extend.ms`的模型文件,文件路径由参数`outputFile`决定。