自定义Backend
所有新增Backend都需继承类,并实现所有纯虚函数。
构造与销毁
Backend构造时,可以额外指定内存环境,在内存受限环境中,应避免非必要的内存使用。可以在构造函数中,完成对计算设备访问的必要初始化,如GPU下预加载shader等。
/** 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);
Backend需要通过onCreate
为op创建出exection实例:
virtual Execution* onCreate(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op) override;
这样,Op Execution中,就可以通过注册追加Op类型:
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通过onAcquireBuffer
为tensor分配内存,通过onReleaseBuffer
为tensor释放内存。内存有三种存储模式:STATIC
内存不复用,一般用于op常量存储;DYNAMIC
内存可复用,一般用于变量存储;DYNAMIC_SEPERATE
内存在pipeline间可复用,一般用于pipeline常量存储。onAcquireBuffer
和onReleaseBuffer
中可以不实际分配/释放内存,只记录内存用量变更,在onAllocateBuffer
调用时,再根据用量计算出优化方案,一次性完成分配/释放。
enum StorageType {
/**
- 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.
*/
virtual bool onAcquireBuffer(const Tensor* tensor, StorageType storageType) = 0;
/**
* @brief release buffer of tensor for given storage type.
* @param tensor buffer provider.
* @param storageType buffer storage type.
* @return success or not.
virtual bool onReleaseBuffer(const Tensor* tensor, StorageType storageType) = 0;
在所有内存都分配完成后,backend会收到onAllocateBuffer
回调:
/**
* @brief clear all dynamic buffers.
* @return success or not.
*/
virtual bool onClearBuffer() = 0;
此外,backend还需要负责tensor数据的拷贝:
/**
* @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
,则一般需要做特殊转换。
Backend在pipeline执行的各个周期都会收到回调,onResizeBegin
和onResizeEnd
在调整内存分配前后调用(op的onResize
会在此间调用);onExecuteBegin
和onExecuteEnd
在op执行前后调用(op的onExecute
会在此间调用);onWaitFinish
相对特殊,由用户主动调用,异步执行的pipeline需要同步等待完成。
注册Backend
class XPUBackendCreator : public BackendCreator {
virtual Backend *onCreate(const Backend::Info &info) const {
return new MetalBackend;
}
};
void registerCPUBackendCreator() {
MNNInsertExtraBackendCreator(MNN_FORWARD_CPU, new CPUBackendCreator);