添加一个后端需要去继承Backend抽象类,并实现所有纯虚函数。
//类函数执行流程:构造 -> onCreate -> onResizeBegin -> onResizeEnd -> onAcquire -> onCopyBuffer -> onExecuteBegin -> onExecuteEnd -> onCopyBuffer -> onClearBuffer
class NPUBackend : public Backend {
public:
//NPUBackend构造函数,NPURuntime用于存放cache参赛以及附加编译选项
NPUBackend(const NPURuntime* runtime);
virtual ~NPUBackend();
//onCreate : 根据传入op的类型,创建对应npu算子
virtual Execution* onCreate(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op) override;
//推理前准备函数
virtual void onExecuteBegin() const override;
//推理后处理函数
virtual void onExecuteEnd() const override;
//内存申请统一函数
virtual Backend::MemObj* onAcquire(const Tensor* tensor, StorageType storageType) override;
//清除缓存
virtual bool onClearBuffer() override;
//内存拷贝函数(通常用于数据格式转换)
virtual void onCopyBuffer(const Tensor* srcTensor, const Tensor* dstTensor) const override;
//维度计算/内存申请前准备
virtual void onResizeBegin() override;
//计算维度/内存申请后处理
virtual void onResizeEnd() override;
}
onCreate函数
Backend需要通过onCreate为为每一个op创建出exection,一个exection通常代表一个算子实例:
它会去后端算子实现中查找算子,如果没有找到对应的算子,则使用CPU的后端
Execution* NPUBackend::onCreate(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op) {
//获取已注册的npu算子map
auto map = getCreatorMap();
auto iter = map->find(op->type());
if (iter == map->end()) {
MNN_ERROR("map not find !!! \n");
if(op != nullptr){
if(op->name() != nullptr){
MNN_PRINT("[NPU] Don't support type %d, %s\n", op->type(), op->name()->c_str());
}
}
return nullptr;
}
//当查找到npu支持该算子,即创建exection
auto exe = iter->second->onCreate(inputs, outputs, op, this);
if (nullptr == exe) {
MNN_ERROR("nullptr == exe !!! \n");
if(op != nullptr){
if(op->name() != nullptr){
MNN_PRINT("[NPU] The Creator Don't support type %d, %s\n", op->type(), op->name()->c_str());
}
}
return nullptr;
}
return exe;
}
onCopyBuffer
拷贝可能在backend内部,也可能在npu backend与CPU backend之间。拷贝需要处理Tensor间的布局转换,相同布局时,可以直接拷贝数据;不同布局,如NHWC
和NC4HW4
,则一般需要做特殊转换。该部分工作需要在onCopyBuffer函数中实现。具体参考:https://github.com/alibaba/MNN/blob/master/source/backend/hiai/backend/NPUBackend.cpp
2.4 onResizeEnd
用于对构图后的模型,进行编译,生产npu可执行模型文件
void NPUBackend::onResizeEnd() {
bulidIRModelAndLoad();
}
2.5 onExecuteEnd
模型推理代码,即npu sdk提供的推理api在此添加
- 新增算子
3.1 实现
每个新增算子都要继承Execution,并重写两个函数onResize,onExecute。onExecute用于mnn到npu参数转换,并重新构图。onExecute在npu没用到,只需返回NO_ERROR;
class NPUCommonExecution : public Execution {
public:
NPUCommonExecution(Backend *backend, const Op *op);
virtual ~NPUCommonExecution() = default;
virtual ErrorCode onResize(const std::vector<Tensor *> &inputs, const std::vector<Tensor *> &outputs) override;
virtual ErrorCode onExecute(const std::vector<Tensor *> &inputs, const std::vector<Tensor *> &outputs) override;
};
ErrorCode NPUActivation::onResize(const std::vector<Tensor *> &inputs, const std::vector<Tensor *> &outputs) {
auto opName = mOp->name()->str();
auto xOp = mNpuBackend->getInputOps(mOp);
shared_ptr<ge::op::Activation> relu(new ge::op::Activation(opName + "_relu"));
(*relu)
.set_input_x(*xOp.get())
.set_attr_coef(.000000)
.set_attr_mode(mType);
mNpuBackend->setOutputOps(mOp, {relu}, outputs);
return NO_ERROR;
}
网友评论