Files
2026-07-13 13:33:03 +08:00

239 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自定义后端
Runtime-Backend是MNN对计算设备的抽象。MNN当前已经支持CPU、Vulkan、OpenCL、Metal、CUDA等Backend**只在计算设备暂未支持时新增Backend**,新增Op,请参阅[新增Op文档](op)。
## 声明
所有新增Backend都需继承`Backend`类,并实现所有纯虚函数。
```cpp
class XPUBackend final : public Backend {
XPUBackend(MNNForwardType type, MemoryMode mode);
virtual ~XPUBackend();
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 void onResizeBegin() override;
virtual ErrorCode onResizeEnd() override;
virtual MemObj* onAcquire(const Tensor* tensor, StorageType storageType) override;
virtual bool onClearBuffer() override;
virtual void onCopyBuffer(const Tensor* srcTensor, const Tensor* dstTensor) const override;
}
```
## 构造与销毁
Backend构造时,可以额外指定内存环境,在内存受限环境中,应避免非必要的内存使用。可以在构造函数中,完成对计算设备访问的必要初始化,如GPU下预加载shader等。
```cpp
/** backend memory mode */
enum MemoryMode {
/** use memory without limit. */
NORMAL = 0,
/** use memory thriftily. */
LIMIT = 1
};
/**
* @brief initializer.
* @param type forward type.
* @param mode memory mode.
*/
Backend(MNNForwardType type, MemoryMode mode = NORMAL);
```
## Execution创建
Backend需要通过`onCreate`为op创建出exection实例:
```cpp
virtual Execution* onCreate(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op) override;
```
可以在方法内根据op类型创建,但更建议提供注册接口:
```cpp
class XPUBackend final : public Backend {
// ...
class Creator {
public:
/**
* @brief create execution for given input, op on metal backend.
* @param inputs given input tensors.
* @param op given op.
* @param backend metal backend.
* @return created execution if supported, NULL otherwise.
*/
virtual Execution *onCreate(const std::vector<Tensor *> &inputs, const MNN::Op *op,
Backend *backend) const = 0;
};
/**
* @brief register creator for given op type.
* @param type given op type.
* @param creator registering creator.
*/
static void addCreator(OpType type, Creator *creator);
// ...
};
template <class T>
class XPUCreatorRegister {
public:
/**
* @brief initializer. register T creator for given op type.
* @param type given op type.
*/
XPUCreatorRegister(OpType type) {
T *test = new T;
XPUBackend::addCreator(type, test);
}
};
```
这样,Op Execution中,就可以通过注册追加Op类型:
```cpp
class XPUPoolingCreator : public XPUBackend::Creator {
public:
virtual Execution *onCreate(const std::vector<Tensor *> &inputs, const MNN::Op *op, Backend *backend) const {
return new XPUPooling(backend, op->main_as_Pool());
}
};
static XPUCreatorRegister<XPUPoolingCreator> __reg(OpType_Pooling);
```
## 内存管理
Backend通过`onAcquire`创建`MemObj`内存对象,定义其析构函数以便为tensor释放内存。内存有三种存储模式:`STATIC`内存不复用,一般用于op常量存储;`DYNAMIC`内存可复用,一般用于变量存储;`DYNAMIC_SEPERATE`内存在pipeline间可复用,一般用于pipeline常量存储。
```cpp
/** backend buffer storage type */
enum StorageType {
/**
use NOT reusable memory.
- allocates memory when `onAcquireBuffer` is called.
- releases memory when `onReleaseBuffer` is called or when the backend is deleted.
- do NOTHING when `onClearBuffer` is called.
*/
STATIC,
/**
use reusable memory.
- allocates or reuses memory when `onAcquireBuffer` is called. prefers reusing.
- collects memory for reuse when `onReleaseBuffer` is called
- releases memory when `onClearBuffer` is called or when the backend is deleted.
*/
DYNAMIC,
/**
use NOT reusable memory.
- allocates memory when `onAcquireBuffer` is called.
- do NOTHING when `onReleaseBuffer` is called.
- releases memory when `onClearBuffer` is called or when the backend is deleted.
*/
DYNAMIC_SEPERATE
};
/**
* @brief allocate buffer of tensor for given storage type.
* @param tensor buffer provider.
* @param storageType buffer storage type.
* @return MemObj for release, if failed, return nullptr.
*/
virtual MemObj* onAcquire(const Tensor* tensor, StorageType storageType) = 0;
```
Backend在调用`onClearBuffer`时,需要释放所有`DYNAMIC``DYNAMIC_SEPERATE`存储模式的内存:
```cpp
/**
* @brief clear all dynamic buffers.
* @return success or not.
*/
virtual bool onClearBuffer() = 0;
```
此外,backend还需要负责tensor数据的拷贝:
```cpp
/**
* @brief copy buffer from tensor to tensor.
* @param srcTensor source buffer provider.
* @param dstTensor dest buffer provider.
*/
virtual void onCopyBuffer(const Tensor* srcTensor, const Tensor* dstTensor) const = 0;
```
**拷贝可能在backend内部,也可能在backend与CPU backend之间。**
**拷贝需要处理Tensor间的布局转换,相同布局时,可以直接拷贝数据;不同布局,如**`**NHWC**`**和**`**NC4HW4**`**,则一般需要做特殊转换。**
## Pipeline回调
Backend在pipeline执行的各个周期都会收到回调,`onResizeBegin``onResizeEnd`在调整内存分配前后调用(op的`onResize`会在此间调用);`onExecuteBegin``onExecuteEnd`在op执行前后调用(op的`onExecute`会在此间调用);`onWaitFinish`相对特殊,由用户主动调用,异步执行的pipeline需要同步等待完成。
```cpp
/**
* @brief callback before resize ops.
*/
virtual void onResizeBegin();
/**
* @brief callback after resize ops.
*/
virtual ErrorCode onResizeEnd();
/**
* @brief callback before executing ops.
*/
virtual void onExecuteBegin() const = 0;
/**
* @brief callback after executing ops.
*/
virtual void onExecuteEnd() const = 0;
```
## Runtime(运行时)
对于使用同一种后端,且存在先后顺序,不会同时运行的模型,MNN提供机制使其共享部分计算资源,比如线程池,内存池等等。
这部分计算资源使用Runtime存储。而Backend则由Runtime创建
### CompileType
Runtime 可以通过指定 CompileType ,决定 MNN 是否跳过几何计算步骤:
```
enum CompilerType {
// 部分执行几何计算,分解形变算子,但不分解 BatchMatMul / Gather 等算子
Compiler_Geometry = 0,
// 完全跳过几何计算步骤,直接使用原始算子
Compiler_Origin = 1,
// 完全执行几何计算,仅此模式下,可以在算子不支持时自动回退到CPU计算
Compiler_Loop = 2,
};
```
### 实现Runtime
Runtime主要实现如下接口:
```
virtual Backend* onCreate(const BackendConfig* config = nullptr, Backend* origin = nullptr) const = 0;
/**
@brief reset runtime
*/
virtual void onReset(int numberThread, const BackendConfig* config, bool full) {
// Do nothing
}
/**
@brief clear unuseful resource
@param level clear level: 0 - 100, bigger mean clear more, smaller mean cache more
*/
virtual void onGabageCollect(int level) = 0;
```
- onCreate :创建 Backend
- onReset :重设默认配置
- onGabageCollect :清理资源以节省内存
### 注册Runtime
注册方法中调用`MNNInsertExtraRuntimeCreator`就可以完成Runtime的注册,这里的注册方法需要在Backend.cpp中声明并调用:
```cpp
class XPURuntimeCreator : public RuntimeCreator {
virtual Runtime* onCreate(const Backend::Info &info) const {
return new XPURuntime;
}
};
void registerXPURuntimeCreator() {
MNNInsertExtraBackendCreator(MNN_FORWARD_XPU, new XPURuntimeCreator);
};
```
使用cmake编译时,完成代码修改后,也需要相应修改CMakeLists.txt。