source/backend/opencl/core/OpenCLBackend.cpp (1,417 lines of code) (raw):

// // OpenCLBackend.cpp // MNN // // Created by MNN on 2019/02/28. // Copyright © 2018, Alibaba Group Holding Limited // #include "backend/opencl/core/OpenCLBackend.hpp" #include "MNN_generated.h" #include "core/BufferAllocator.hpp" #include "core/TensorUtils.hpp" #include "shape/SizeComputer.hpp" #include <map> #include <mutex> #include <thread> #include "core/Macro.h" #include "runtime/OpenCLTuneInfo.hpp" #ifdef __ANDROID__ #include <GLES2/gl2.h> #endif //#define OPENCL_FALLBACK_LOG namespace MNN { namespace OpenCL { #ifndef MNN_OPENCL_SEP_BUILD void registerOpenCLOps(); #endif std::mutex CLRuntime::globalRuntimeLock; std::weak_ptr<OpenCLRuntime> CLRuntime::globalRuntime; void CLRuntime::setGlobalCLRuntime(std::shared_ptr<OpenCLRuntime> runtime){ std::lock_guard<std::mutex> _l(globalRuntimeLock); globalRuntime = runtime; } std::shared_ptr<OpenCLRuntime> CLRuntime::getGlobalCLRuntime(){ auto sharedPtr = globalRuntime.lock(); return sharedPtr; } CLRuntime::CLRuntime(const Backend::Info& info){ mInfo = info; int platform_id = 0; int device_id = 0; int platform_size = 0; void *context_ptr = nullptr; auto globalRuntimePtr = getGlobalCLRuntime(); if (nullptr != info.user) { if (info.user->sharedContext != nullptr) { platform_id = ((MNNDeviceContext*)info.user->sharedContext)->platformId; device_id = ((MNNDeviceContext*)info.user->sharedContext)->deviceId; platform_size = ((MNNDeviceContext*)info.user->sharedContext)->platformSize; context_ptr = (((MNNDeviceContext*)info.user->sharedContext)->contextPtr); } } if (nullptr != mInfo.user) { mPrecision = mInfo.user->precision; mMemory = mInfo.user->memory; } if(globalRuntimePtr && globalRuntimePtr.get()->canShareRuntime(platform_size, platform_id, device_id, context_ptr)){ mOpenCLRuntime = globalRuntimePtr; }else{ mOpenCLRuntime.reset(new OpenCLRuntime(platform_size, platform_id, device_id, context_ptr, hint())); setGlobalCLRuntime(mOpenCLRuntime); } //Whether runtimeError mCLRuntimeError = mOpenCLRuntime->isCreateError(); mTunedInfo = new TuneInfo; mImagePool.reset(new ImagePool(mOpenCLRuntime->context())); mBufferPool.reset(new BufferPool(mOpenCLRuntime->context(), CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR)); } CLRuntime::~CLRuntime() { mImagePool = nullptr; mBufferPool = nullptr; mOpenCLRuntime = nullptr; delete mTunedInfo; } static bool _checkTensorInfo(const CLCache::TensorInfoT* dst, const Tensor* src) { if (dst->shape.size() != src->dimensions()) { return false; } for (int j=0; j<dst->shape.size(); ++j) { if (dst->shape[j] != src->length(j)) { return false; } } return true; } bool CLRuntime::onMeasure(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op, Runtime::OpInfo& dstInfo) const { dstInfo.initCostLong = true; if (nullptr == op->name()) { dstInfo.initCostLong = false; return true; } for(auto& info : mTunedInfo->mInfos) { if (info->type != op->type()) { continue; } if (info->name != op->name()->str()) { continue; } if (info->inputs.size() != inputs.size() || info->outputs.size() != outputs.size()) { continue; } bool match = true; for (int i=0; i<inputs.size(); ++i) { auto& dst = info->inputs[i]; auto src = inputs[i]; if (!_checkTensorInfo(dst.get(), src)) { match = false; break; } } if (!match) { continue; } for (int i=0; i<outputs.size(); ++i) { auto& dst = info->outputs[i]; auto src = outputs[i]; if (!_checkTensorInfo(dst.get(), src)) { match = false; break; } } if (match) { // All Info is match dstInfo.initCostLong = false; break; } } return true; } int CLRuntime::onGetRuntimeStatus(RuntimeStatus statusEnum) const { switch (statusEnum) { case STATUS_SUPPORT_FP16: { return mOpenCLRuntime->isSupportedFP16(); break; } case STATUS_SUPPORT_DOT_PRODUCT: { return 0; break; } case STATUS_SUPPORT_POWER_LOW: { return mOpenCLRuntime->isDeviceSupportedLowPower(); break; } default: { MNN_ERROR("unsupported interface"); break; } } return 0; } void CLRuntime::onMaskOpReady(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op) { if (nullptr != op->name()) { auto dstInfo = mTunedInfo; std::unique_ptr<CLCache::OpInfoT> opInfo(new CLCache::OpInfoT);; opInfo->type = op->type(); opInfo->name = op->name()->str(); opInfo->inputs.resize(inputs.size()); for (int v=0; v<opInfo->inputs.size(); ++v) { opInfo->inputs[v].reset(new CLCache::TensorInfoT); opInfo->inputs[v]->shape.resize(inputs[v]->dimensions()); for (int u=0; u<opInfo->inputs[v]->shape.size(); ++u) { opInfo->inputs[v]->shape[u] = inputs[v]->length(u); } } opInfo->outputs.resize(outputs.size()); for (int v=0; v<opInfo->outputs.size(); ++v) { opInfo->outputs[v].reset(new CLCache::TensorInfoT); opInfo->outputs[v]->shape.resize(outputs[v]->dimensions()); for (int u=0; u<opInfo->outputs[v]->shape.size(); ++u) { opInfo->outputs[v]->shape[u] = outputs[v]->length(u); } } dstInfo->mInfos.emplace_back(std::move(opInfo)); } } void CLRuntime::onReset(int numberThread, const BackendConfig* config, bool full) { mInfo.gpuMode = numberThread; } bool CLRuntime::onSetCache(const void* buffer, size_t size) { if (nullptr == buffer) { return false; } auto cacheBuffer = CLCache::GetCache(buffer); flatbuffers::Verifier verify((const uint8_t*)buffer, size); if (false == CLCache::VerifyCacheBuffer(verify)) { return false; } if(nullptr != cacheBuffer->tuned()) { for (int i=0; i<cacheBuffer->tuned()->size(); ++i) { auto srcInfo = cacheBuffer->tuned()->GetAs<CLCache::OpInfo>(i); std::unique_ptr<CLCache::OpInfoT> dst(srcInfo->UnPack()); mTunedInfo->mInfos.emplace_back(std::move(dst)); } } bool res = mOpenCLRuntime->setCache(std::make_pair(buffer, size)); return res; } std::pair<const void*, size_t> CLRuntime::onGetCache() { return mOpenCLRuntime->makeCache(mTunedInfo); } Backend* CLRuntime::onCreate(const BackendConfig* config, Backend* origin) const { auto precision = mPrecision; auto memory = mMemory; if (nullptr != config) { precision = config->precision; memory = config->memory; } return new OpenCLBackend(precision, memory, mInfo.gpuMode, mImagePool, mBufferPool, this); } void CLRuntime::onGabageCollect(int level) { mImagePool->releaseFreeList(); mBufferPool->releaseFreeList(); } float CLRuntime::onGetMemoryInMB() { auto staticMemoryInMB = mBufferPool->totalSize() / 1024.0f / 1024.0f; return staticMemoryInMB; } bool CLRuntime::isCLRuntimeError() { return mCLRuntimeError; } std::map<std::pair<OpType, GpuMemObject>, OpenCLBackend::Creator*>* gCreator() { static std::once_flag once; static std::map<std::pair<OpType, GpuMemObject>, OpenCLBackend::Creator*>* creators = nullptr; std::call_once(once, [&]() { creators = new std::map<std::pair<OpType, GpuMemObject>, OpenCLBackend::Creator*>; }); return creators; }; OpenCLBackend::OpenCLBackend(BackendConfig::PrecisionMode precision, BackendConfig::MemoryMode memory, int gpuMode, std::shared_ptr<ImagePool>imgPool, std::shared_ptr<BufferPool> bufPool, const CLRuntime *runtime) : Backend(MNN_FORWARD_OPENCL) { mGpuMode = gpuMode; mCLRuntime = runtime; mOpenCLRuntime = mCLRuntime->mOpenCLRuntime; if(mOpenCLRuntime->isSupportedFP16()){ mPrecision = precision; } else{ mPrecision = BackendConfig::Precision_High; } mMemory = memory; // set tuneLevel, memtype, record mode setGpuMode(gpuMode); mStaticImagePool = imgPool; mStaticBufferPool = bufPool; if(mOpenCLRuntime.get()){ if(mOpenCLRuntime->isCreateError() == true) { mIsCreateError = true; } mImagePoolFirst.reset(new ImagePool(mOpenCLRuntime->context())); mBufferPoolFirst.reset(new BufferPool(mOpenCLRuntime->context(), CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR)); mExecutionBufferPool.reset(new BufferExecutionPool(mOpenCLRuntime->context(), mOpenCLRuntime->commandQueue(), CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR)); mImagePool = mImagePoolFirst.get(); mBufferPool = mBufferPoolFirst.get(); } mMapMem = std::make_pair(0, nullptr); } OpenCLBackend::~OpenCLBackend() { #ifdef LOG_VERBOSE MNN_PRINT("enter OpenCLBackend::~OpenCLBackend \n"); #endif releaseRecord(); mRecordings.clear(); mImagePool = nullptr; mBufferPool = nullptr; mExecutionBufferPool->clear(); if(mMapMem.second != nullptr) { #ifdef MNN_OPENCL_SVM_ENABLE if(mUseSvm) { clSVMFree(mOpenCLRuntime->context().get(), mMapMem.second); } else #endif { free(mMapMem.second); mMapMem.second = nullptr; } } } OpenCLRuntime* OpenCLBackend::getOpenCLRuntime() { return mOpenCLRuntime.get(); } class CLReleaseExecutionBuffer : public Backend::MemObj { public: CLReleaseExecutionBuffer(std::shared_ptr<OpenCLBufferNode> node, BufferExecutionPool* bufferPool) { mNode = node; mBufferPool = bufferPool; } virtual ~ CLReleaseExecutionBuffer() { mBufferPool->recycle(mNode); } private: std::shared_ptr<OpenCLBufferNode> mNode; BufferExecutionPool* mBufferPool; }; class CLMemReleaseBuffer : public Backend::MemObj { public: CLMemReleaseBuffer(cl::Buffer* bId, BufferPool* bufferPool) { mBuffer = bId; mBufferPool = bufferPool; } virtual ~ CLMemReleaseBuffer() { mBufferPool->recycle(mBuffer); } private: cl::Buffer* mBuffer; BufferPool* mBufferPool; }; class CLMemReleaseImage : public Backend::MemObj { public: CLMemReleaseImage(cl::Image* bId, ImagePool* bufferPool) { mBuffer = bId; mBufferPool = bufferPool; } virtual ~ CLMemReleaseImage() { mBufferPool->recycle(mBuffer); } private: cl::Image* mBuffer; ImagePool* mBufferPool; }; float OpenCLBackend::getBytes(const Tensor* tensor) { float bytes = (float)tensor->getType().bytes(); if (mPrecision != BackendConfig::Precision_High) {// Fp16 if (halide_type_float == tensor->getType().code) { bytes = 2.0; } } auto quant = TensorUtils::getDescribe(tensor)->quantAttr.get(); if (nullptr != quant && TensorUtils::getDescribe(tensor)->type == DataType_DT_INT8) { bytes = 1.0; } if(tensor->getType().bits == 4) { bytes = 0.5; } return bytes; } Backend::MemObj* OpenCLBackend::onAcquire(const Tensor* nativeTensor, StorageType storageType) { #ifdef LOG_VERBOSE MNN_PRINT("Start OpenCLBackend::onAcquireBuffer !\n"); #endif auto tensorShape = OpenCL::tensorShapeFormat(nativeTensor); int N = tensorShape.at(0); int H = tensorShape.at(1); int W = tensorShape.at(2); int C = tensorShape.at(3); #ifdef LOG_VERBOSE MNN_PRINT("OpenCLBackend::onAcquireBuffer: NHWC:[%d, %d, %d, %d]\n", N, H, W, C); #endif #ifndef MNN_OPENCL_BUFFER_CLOSED if(mMemType == BUFFER) { size_t size; float typeSize = getBytes(nativeTensor); if (MNN_DATA_FORMAT_NC4HW4 == TensorUtils::getDescribe(nativeTensor)->dimensionFormat && nativeTensor->dimensions() >= 2) { auto alignC = ROUND_UP(C, 4); // increment of height and width auto hR = ROUND_UP(H + 3, 4) - H; auto wR = ROUND_UP(W + 3, 4) - W; size = N * alignC * W * H; size = size + hR * W * 4 + wR * 4; } else { size = N * H * W * C; size = ROUND_UP(size, 4); } if (mOpenCLRuntime->isSupportedIntelSubgroup()) { int cPack = TensorUtils::getTensorChannelPack(nativeTensor); auto pads = TensorUtils::getDescribe(nativeTensor)->mPads; size_t imageWidth = (size_t) ROUND_UP(UP_DIV(C, cPack), 2) * ROUND_UP(pads.left + W + pads.right, 4);//C-round to 8,W-round to 4, for memory alloc size_t imageHeight = (size_t)N * H; size = imageWidth*imageHeight*cPack; } // Align when int4 memory size = ROUND_UP(size, 2); if (storageType == DYNAMIC_SEPERATE) { auto buffer = mBufferPool->alloc(size*typeSize, true); ((Tensor*)nativeTensor)->buffer().device = (uint64_t)buffer; return new CLMemReleaseBuffer(buffer, mBufferPool); } if (storageType == DYNAMIC) { auto buffer = mBufferPool->alloc(size*typeSize); ((Tensor*)nativeTensor)->buffer().device = (uint64_t)buffer; return new CLMemReleaseBuffer(buffer, mBufferPool); } if (storageType == DYNAMIC_IN_EXECUTION){ auto node = mExecutionBufferPool->alloc(size*typeSize); ((Tensor*)nativeTensor)->buffer().device = reinterpret_cast<uint64_t>(node.get()); return new CLReleaseExecutionBuffer(node, mExecutionBufferPool.get()); } MNN_ASSERT(storageType == STATIC); auto buffer = mStaticBufferPool->alloc(size*typeSize); ((Tensor*)nativeTensor)->buffer().device = (uint64_t)buffer; // fix return new CLMemReleaseBuffer(buffer, mStaticBufferPool.get()); } else #endif /* MNN_OPENCL_BUFFER_CLOSED */ { size_t imageWidth = (size_t) (UP_DIV(C, 4) * W);//image mode only C pack to 4 size_t imageHeight = (size_t)N * H; cl_channel_type dataType = CL_HALF_FLOAT; if(nativeTensor->getType().code == halide_type_int) { if(nativeTensor->getType().bits == 8){ dataType = CL_SIGNED_INT8; } else if(nativeTensor->getType().bits == 32){ dataType = CL_SIGNED_INT32; } } else if(nativeTensor->getType().code == halide_type_uint){ if(nativeTensor->getType().bits == 8){ dataType = CL_UNSIGNED_INT8; } else if(nativeTensor->getType().bits == 32){ dataType = CL_UNSIGNED_INT32; } } else { //when user want high precision, use float datatype if (mPrecision == BackendConfig::Precision_High) { dataType = CL_FLOAT; } } if (storageType == DYNAMIC_SEPERATE) { auto image = mImagePool->alloc(imageWidth, imageHeight, dataType, true); ((Tensor*)nativeTensor)->buffer().device = (uint64_t)image; // fix return new CLMemReleaseImage(image, mImagePool); } if (storageType == DYNAMIC) { auto image = mImagePool->alloc(imageWidth, imageHeight, dataType); ((Tensor*)nativeTensor)->buffer().device = (uint64_t)image; // fix return new CLMemReleaseImage(image, mImagePool); } MNN_ASSERT(storageType == STATIC); auto image = mStaticImagePool->alloc(imageWidth, imageHeight, dataType); ((Tensor*)nativeTensor)->buffer().device = (uint64_t)image; // fix return new CLMemReleaseImage(image, mStaticImagePool.get()); } } bool OpenCLBackend::onSelectDynamicAllocator(int index, int maxIndex) { if (mUseRecordQueue && false == mDevideOpRecord){ return false; } if (maxIndex > 2) { return false; } if (maxIndex > 1 && mImagePoolSecond.get() == nullptr) { mImagePoolSecond.reset(new ImagePool(mOpenCLRuntime->context())); mBufferPoolSecond.reset(new BufferPool(mOpenCLRuntime->context(), CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR)); } if (index == 0) { mImagePool = mImagePoolFirst.get(); mBufferPool = mBufferPoolFirst.get(); } else if (index == 1) { mImagePool = mImagePoolSecond.get(); mBufferPool = mBufferPoolSecond.get(); } return true; } bool OpenCLBackend::onClearBuffer() { mImagePool->clear(); mBufferPool->clear(); if(mMapMem.second != nullptr) { #ifdef MNN_OPENCL_SVM_ENABLE if(mUseSvm) { clSVMFree(mOpenCLRuntime->context().get(), mMapMem.second); } else #endif { free(mMapMem.second); mMapMem.second = nullptr; } } return true; } Execution* OpenCLBackend::onCreate(const std::vector<Tensor*>& inputs, const std::vector<Tensor*>& outputs, const MNN::Op* op) { #ifdef LOG_VERBOSE MNN_PRINT("Start OpenCLBackend::onCreate \n"); #endif auto creators = gCreator(); auto iter = creators->find(std::make_pair(op->type(), mMemType)); if (0 != inputs.size() && (getDataType(inputs[0]) == DataType_DT_INT8 || inputs[0]->getType().bytes() == 1)) { #ifdef OPENCL_FALLBACK_LOG MNN_PRINT("Don't support type %s for int8 input\n", EnumNameOpType(op->type())); #endif for (int i = 0; i < inputs.size(); ++i) { TensorUtils::setTensorSupportPack(inputs[i], false); } for (int i = 0; i < outputs.size(); ++i) { TensorUtils::setTensorSupportPack(outputs[i], false); } return NULL; } if (iter == creators->end()) { mDevideOpRecord = true; #ifdef OPENCL_FALLBACK_LOG if (nullptr != op->name()) { MNN_PRINT("Don't support type %s memObject:%d, %s\n", EnumNameOpType(op->type()), mMemType, op->name()->c_str()); } else { MNN_PRINT("Don't support type %s memObject:%d\n", EnumNameOpType(op->type()), mMemType); } #endif for (int i = 0; i < inputs.size(); ++i) { TensorUtils::setTensorSupportPack(inputs[i], false); } for (int i = 0; i < outputs.size(); ++i) { TensorUtils::setTensorSupportPack(outputs[i], false); } return NULL; } if(mMemType == IMAGE) { auto maxImageSize = mOpenCLRuntime->getMaxImage2DSize(); bool valid = true; for (auto t : inputs) { auto tensorShape = OpenCL::tensorShapeFormat(t); int imageHeight = tensorShape[0] * tensorShape[1]; int imageWidth = tensorShape[2] * UP_DIV(tensorShape[3], 4); if (imageHeight > maxImageSize.at(0) || imageWidth > maxImageSize.at(1)) { valid = false; break; } } for (auto t : outputs) { auto tensorShape = OpenCL::tensorShapeFormat(t); int imageHeight = tensorShape[0] * tensorShape[1]; int imageWidth = tensorShape[2] * UP_DIV(tensorShape[3], 4); if (imageHeight > maxImageSize.at(0) || imageWidth > maxImageSize.at(1)) { valid = false; break; } } if (!valid) { mDevideOpRecord = true; #ifdef OPENCL_FALLBACK_LOG for (auto t : inputs) { auto tensorShape = OpenCL::tensorShapeFormat(t); MNN_PRINT("input n:%d, h:%d, w:%d, c:%d\n", tensorShape[0], tensorShape[1], tensorShape[2], tensorShape[3]); } for (auto t : outputs) { auto tensorShape = OpenCL::tensorShapeFormat(t); MNN_PRINT("output n:%d, h:%d, w:%d, c:%d\n", tensorShape[0], tensorShape[1], tensorShape[2], tensorShape[3]); } MNN_PRINT("beyond cl_image creat size! fallback to cpu backend\n"); #endif for (int i = 0; i < inputs.size(); ++i) { TensorUtils::setTensorSupportPack(inputs[i], false); } for (int i = 0; i < outputs.size(); ++i) { TensorUtils::setTensorSupportPack(outputs[i], false); } return NULL; } } auto exe = iter->second->onCreate(inputs, outputs, op, this); if (NULL == exe) { mDevideOpRecord = true; #ifdef OPENCL_FALLBACK_LOG if (nullptr != op->name()) { MNN_PRINT("The Creator Don't support type %s, memObject:%d, %s\n", MNN::EnumNameOpType(op->type()), mMemType, op->name()->c_str()); } else { MNN_PRINT("The Creator Don't support type %s, memObject:%d,\n", EnumNameOpType(op->type()), mMemType); } #endif for (int i = 0; i < inputs.size(); ++i) { TensorUtils::setTensorSupportPack(inputs[i], false); } for (int i = 0; i < outputs.size(); ++i) { TensorUtils::setTensorSupportPack(outputs[i], false); } return NULL; } #ifdef LOG_VERBOSE MNN_PRINT("End OpenCLBackend::onCreate \n"); #endif return exe; } void OpenCLBackend::onResizeBegin() { #ifndef ENABLE_OPENCL_TIME_PROFILER mOpenCLRuntime->setCommandQueueProfileEnable(); #endif // update mUseRecordableQueueSize if hint has changed mUseRecordableQueueSize = mCLRuntime->hint().encorderNumForCommit <= mUseRecordableQueueSize ? mCLRuntime->hint().encorderNumForCommit : mUseRecordableQueueSize; mUseRecordQueue &= mUseRecordableQueueSize > 0 ? true : false; releaseRecord(); } ErrorCode OpenCLBackend::onResizeEnd() { #ifndef ENABLE_OPENCL_TIME_PROFILER mOpenCLRuntime->setCommandQueueProfileDisable(); #endif if(!mRecordings.empty()){ endRecord(mRecordings.back().record, true); } return NO_ERROR; } void OpenCLBackend::onExecuteBegin() const { mOpenCLRuntime->mQueueCount = 0; clearRecord(); mOpenCLRuntime->clearEvent(); } void OpenCLBackend::onExecuteEnd() const { mOpenCLRuntime->mQueueCount = 0; clearRecord(); enqeueRecord(); mOpenCLRuntime->printEventTime(); } bool OpenCLBackend::isCreateError() const { return mIsCreateError; } bool OpenCLBackend::_allocHostBuffer(int length, const Tensor* srcTensor) const { auto memType = srcTensor->buffer().flags; if (nullptr != mHostBuffer.second && length <= mHostBuffer.first && memType != MNN_MEMORY_AHARDWAREBUFFER) { return true; } cl_int error; #ifdef __ANDROID__ if(MNN_MEMORY_AHARDWAREBUFFER == memType){ if (mOpenCLRuntime->isSupportAHD()){ CLSharedMemReleaseBuffer *sharedMem = (CLSharedMemReleaseBuffer*)TensorUtils::getSharedMem(srcTensor); if(sharedMem == nullptr || (sharedMem != nullptr && srcTensor->buffer().device != sharedMem->getSharedId())){ if(mOpenCLRuntime->getGpuType() == MALI){ const cl_import_properties_arm properties[] = {CL_IMPORT_TYPE_ARM, CL_IMPORT_TYPE_ANDROID_HARDWARE_BUFFER_ARM, 0}; Backend::MemObj* SharedTmp = new CLSharedMemReleaseBuffer(srcTensor->buffer().device, new cl::Buffer(mOpenCLRuntime->context(), (cl_mem_flags)CL_MEM_READ_WRITE, properties, (void*)srcTensor->buffer().device, CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM, &error)); TensorUtils::setSharedMem(srcTensor, SharedTmp); }else if(mOpenCLRuntime->getGpuType() == ADRENO){ cl_mem_ahardwarebuffer_host_ptr myAHBmem = {0}; myAHBmem.ext_host_ptr.allocation_type = CL_MEM_ANDROID_AHARDWAREBUFFER_HOST_PTR_QCOM; myAHBmem.ext_host_ptr.host_cache_policy = CL_MEM_HOST_WRITEBACK_QCOM; myAHBmem.ahb_ptr = (AHardwareBuffer*)srcTensor->buffer().device; Backend::MemObj* SharedTmp = new CLSharedMemReleaseBuffer(srcTensor->buffer().device, new cl::Buffer(mOpenCLRuntime->context(), (cl_mem_flags)(CL_MEM_USE_HOST_PTR | CL_MEM_EXT_HOST_PTR_QCOM), 0, &myAHBmem, &error)); TensorUtils::setSharedMem(srcTensor, SharedTmp); } else{ MNN_ERROR("This device not support AHardWareBuffer\n"); return false; } if (error != CL_SUCCESS) { MNN_ERROR("Alloc mAHardWareBuffer error, code:%d \n", error); return false; } } } else{ MNN_ERROR("This device not support AHardWareBuffer\n"); return false; } } else #endif { MNN_ASSERT(length > 0); mHostBuffer.first = length; mHostBuffer.second.reset(new cl::Buffer(mOpenCLRuntime->context(), (cl_mem_flags)(CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR), (size_t)length, NULL, &error)); if (nullptr == mHostBuffer.second.get() || error != CL_SUCCESS) { MNN_ERROR("Alloc mHostBuffer %d error, code:%d \n", length, error); return false; } } return true; } void OpenCLBackend::copyFromDeviceInt8(const Tensor* srcTensor, const Tensor* dstTensor) const{ std::vector<int> bufferShape = MNN::OpenCL::tensorShapeFormat(dstTensor); auto needSize = dstTensor->size(); auto hostPtr = dstTensor->host<int8_t>(); auto DeviceBuffer = (cl::Buffer*)srcTensor->deviceId(); cl_int error = CL_SUCCESS; #ifndef MNN_OCL_QUANT_DUMP error = mOpenCLRuntime->commandQueue().enqueueReadBuffer(*DeviceBuffer, CL_TRUE, 0, needSize, hostPtr); MNN_ASSERT(error == 0); #else//for dump test int8_t* tmpPtr = (int8_t *)malloc(needSize); error = mOpenCLRuntime->commandQueue().enqueueReadBuffer(*DeviceBuffer, CL_TRUE, 0, needSize, tmpPtr); MNN_ASSERT(error == 0); int C_4 = (bufferShape[3]+3)/4; for(int n=0; n<bufferShape[0]; n++) { for(int c=0; c<bufferShape[3]; c++) { for(int h=0; h<bufferShape[1]; h++) { for(int w=0; w<bufferShape[2]; w++) { hostPtr[n*bufferShape[3]*bufferShape[1]*bufferShape[2] + c*bufferShape[1]*bufferShape[2] + h*bufferShape[2] + w] = tmpPtr[n*C_4*bufferShape[1]*bufferShape[2]*4 + (c/4)*bufferShape[1]*bufferShape[2]*4 + h*bufferShape[2]*4 + w*4 + c%4]; } } } } if(tmpPtr != nullptr) { free(tmpPtr); tmpPtr = nullptr; } #endif #ifdef ENABLE_OPENCL_TIME_PROFILER MNN_PRINT("total kernel time:%d us\n", (int)mOpenCLRuntime->mKernelTime); #endif } void OpenCLBackend::copyToDeviceInt8(const Tensor* srcTensor, const Tensor* dstTensor) const{ auto needSize = srcTensor->size(); auto hostPtr = srcTensor->host<int8_t>(); cl_int error = CL_SUCCESS; auto DeviceBuffer = (cl::Buffer*)dstTensor->deviceId(); mOpenCLRuntime->commandQueue().enqueueWriteBuffer(*DeviceBuffer, CL_TRUE, 0, needSize, hostPtr); } int OpenCLBackend::onSync(Tensor::MapType mtype, bool toCpu, const Tensor* dstTensor) { if (toCpu) { mOpenCLRuntime->commandQueue().finish(); } return 0; } void CLRuntime::convertFromDevice(const Tensor* srcTensor, const Tensor* dstTensor, MNN_DATA_FORMAT data_format, int precision, int backend_memtype, bool svmFlag, int memtype) const { #ifdef __ANDROID__ if(MNN_MEMORY_AHARDWAREBUFFER == memtype){ convertBetweenAHDandCLmem(const_cast<Tensor*>(srcTensor), const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, backend_memtype, false, true); return; } #endif #ifndef MNN_OPENCL_BUFFER_CLOSED if(backend_memtype == BUFFER) { #ifdef MNN_SUPPORT_INTEL_SUBGROUP int cPack = TensorUtils::getTensorChannelPack(srcTensor); if (cPack == 16 && mOpenCLRuntime->isSupportedIntelSubgroup()) { switch (data_format) { case MNN_DATA_FORMAT_NHWC: OpenCL::convertNC4HW4OrNC16HW16BufferToNCHWOrNHWCBuffer(srcTensor, const_cast<Tensor*>(dstTensor), "nc16hw16_buffer_to_nhwc_buffer", mOpenCLRuntime.get(), precision, true, false, svmFlag); break; case MNN_DATA_FORMAT_NCHW: OpenCL::convertNC4HW4OrNC16HW16BufferToNCHWOrNHWCBuffer(srcTensor, const_cast<Tensor*>(dstTensor), "nc16hw16_buffer_to_nchw_buffer", mOpenCLRuntime.get(), precision, true, false, svmFlag); break; case MNN_DATA_FORMAT_NC4HW4: OpenCL::convertNC4HW4BufferBetweenNC16HW16Buffer(srcTensor, const_cast<Tensor*>(dstTensor), "nc16hw16_buffer_to_nc4hw4_buffer", mOpenCLRuntime.get(), precision, OutTrans, false, svmFlag, false, true); break; default: MNN_PRINT("output data format not support for subgroup!\n"); break; } } else #endif OpenCL::convertBufferToBuffer(const_cast<Tensor*>(srcTensor), const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, precision, precision, false, true, true, svmFlag); } else #endif /* MNN_OPENCL_BUFFER_CLOSED */ { switch (data_format) { case MNN_DATA_FORMAT_NHWC: OpenCL::convertImageToNHWCBuffer(srcTensor, const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, false, svmFlag); break; case MNN_DATA_FORMAT_NCHW: OpenCL::convertImageToNCHWBuffer(srcTensor, const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, false, svmFlag); break; case MNN_DATA_FORMAT_NC4HW4: OpenCL::convertImageToNC4HW4Buffer(srcTensor, const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, false, svmFlag); break; default: break; } } } void OpenCLBackend::copyFromDevice(const Tensor* srcTensor, const Tensor* dstTensor) const{ auto needSize = dstTensor->size(); auto shape = tensorShapeFormat(srcTensor); auto srcDimensionFormat = TensorUtils::getDescribe(srcTensor)->dimensionFormat; auto dstDimensionFormat = TensorUtils::getDescribe(dstTensor)->dimensionFormat; auto memType = dstTensor->buffer().flags; bool directCopy = BUFFER == mMemType && (srcDimensionFormat == dstDimensionFormat || srcTensor->dimensions() <= 1) && MNN::MNN_DATA_FORMAT_NC4HW4 != dstDimensionFormat && MNN_DATA_FORMAT_NC4HW4 != srcDimensionFormat && (getDataType(srcTensor) == getDataType(dstTensor)) && memType != MNN_MEMORY_AHARDWAREBUFFER; if (mPrecision != BackendConfig::Precision_High) { // Fp16 if (dstTensor->getType().code == halide_type_float) { directCopy = false; } } if(mOpenCLRuntime->isSupportedIntelSubgroup()){ int cPack = TensorUtils::getTensorChannelPack(srcTensor); if (cPack == 16){ directCopy = false; } } void* hostPtr = dstTensor->host<float>(); if(directCopy){ mOpenCLRuntime->commandQueue().enqueueReadBuffer(openCLBuffer(srcTensor), CL_TRUE, 0, needSize, hostPtr); return; } _allocHostBuffer(needSize, dstTensor); MNN::Tensor interTensor(dstTensor, dstTensor->getDimensionType(), false); interTensor.buffer().device = (uint64_t)mHostBuffer.second.get(); TensorUtils::getDescribe(&interTensor)->dimensionFormat = dstDimensionFormat; //Convert format mCLRuntime->convertFromDevice(srcTensor, (const Tensor*)&interTensor, dstDimensionFormat, mPrecision, mMemType, false); mOpenCLRuntime->printEventTime(); cl_int res; #ifdef ENABLE_OPENCL_TIME_PROFILER mOpenCLRuntime->commandQueue().finish(); { AUTOTIME; res = mOpenCLRuntime->commandQueue().enqueueReadBuffer(*mHostBuffer.second, CL_TRUE, 0, needSize, hostPtr); } #else res = mOpenCLRuntime->commandQueue().enqueueReadBuffer(*mHostBuffer.second, CL_TRUE, 0, needSize, hostPtr); #endif } void CLRuntime::convertToDevice(const Tensor* srcTensor, const Tensor* dstTensor, MNN_DATA_FORMAT data_format, int precision, int backend_memtype, bool svmFlag, int memtype) const { // Format: Host -> OpenCL #ifdef __ANDROID__ if(MNN_MEMORY_AHARDWAREBUFFER == memtype){ convertBetweenAHDandCLmem(const_cast<Tensor*>(srcTensor), const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, backend_memtype, true, false); return; } #endif #ifndef MNN_OPENCL_BUFFER_CLOSED if(backend_memtype == BUFFER) { #ifdef MNN_SUPPORT_INTEL_SUBGROUP int cPack = TensorUtils::getTensorChannelPack(dstTensor); if (cPack == 16 && mOpenCLRuntime->isSupportedIntelSubgroup()) { if (MNN_DATA_FORMAT_NHWC == data_format) { OpenCL::converNCHWOrNHWCBufferToNC4HW4OrNC16HW16Buffer(srcTensor, const_cast<Tensor*>(dstTensor), "nhwc_buffer_to_nc16hw16_buffer", mOpenCLRuntime.get(), precision, true, false, svmFlag); } else if (MNN_DATA_FORMAT_NCHW == data_format) { OpenCL::converNCHWOrNHWCBufferToNC4HW4OrNC16HW16Buffer(srcTensor, const_cast<Tensor*>(dstTensor), "nchw_buffer_to_nc16hw16_buffer", mOpenCLRuntime.get(), precision, true, false, svmFlag); } else if (MNN_DATA_FORMAT_NC4HW4 == data_format) { OpenCL::convertNC4HW4BufferBetweenNC16HW16Buffer(srcTensor, const_cast<Tensor*>(dstTensor), "nc4hw4_buffer_to_nc16hw16_buffer", mOpenCLRuntime.get(), precision, InpTrans, false, svmFlag, true, false); } else { MNN_PRINT("input data format not support or subgroup\n"); MNN_ASSERT(false); } }else #endif OpenCL::convertBufferToBuffer(const_cast<Tensor*>(srcTensor), const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, precision, precision, true, false, false, svmFlag); } else #endif /* MNN_OPENCL_BUFFER_CLOSED */ { if (MNN_DATA_FORMAT_NHWC == data_format) { OpenCL::convertNHWCBufferToImage(srcTensor, const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, false, svmFlag); } else if (MNN_DATA_FORMAT_NCHW == data_format) { OpenCL::convertNCHWBufferToImage(srcTensor, const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, false, svmFlag); } else if (MNN_DATA_FORMAT_NC4HW4 == data_format) { OpenCL::convertNC4HW4BufferToImage(srcTensor, const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), precision, false, svmFlag); } else { MNN_PRINT("data format not support\n"); MNN_ASSERT(false); } } } void OpenCLBackend::copyToDevice(const Tensor* srcTensor, const Tensor* dstTensor) const{ auto needSize = srcTensor->size(); auto shape = tensorShapeFormat(srcTensor); auto srcDimensionFormat = TensorUtils::getDescribe(srcTensor)->dimensionFormat; auto dstDimensionFormat = TensorUtils::getDescribe(dstTensor)->dimensionFormat; auto memType = srcTensor->buffer().flags; void* hostPtr = srcTensor->host<float>(); // 1*1*1*1 don't need convert if(BUFFER == mMemType && srcTensor->getType().code == halide_type_float && mPrecision != BackendConfig::Precision_High && 1 == shape[0] * shape[1] * shape[2] * shape[3]){ needSize /= 2; void *tmpPtr = malloc(needSize); ((half_float::half*)tmpPtr)[0] = (half_float::half)(((float*)hostPtr)[0]); mOpenCLRuntime->commandQueue().enqueueWriteBuffer(openCLBuffer(dstTensor), CL_TRUE, 0, needSize, tmpPtr); free(tmpPtr); return; } bool directCopy = BUFFER == mMemType && (srcDimensionFormat == dstDimensionFormat || srcTensor->dimensions() <= 1) && MNN_DATA_FORMAT_NC4HW4 != dstDimensionFormat && MNN_DATA_FORMAT_NC4HW4 != srcDimensionFormat && (getDataType(srcTensor) == getDataType(dstTensor)) && memType != MNN_MEMORY_AHARDWAREBUFFER; if (mPrecision != BackendConfig::Precision_High) { // Fp16 if (dstTensor->getType().code == halide_type_float) { directCopy = false; } } if(mOpenCLRuntime->isSupportedIntelSubgroup()){ int cPack = TensorUtils::getTensorChannelPack(dstTensor); if (cPack == 16){ directCopy = false; } } if(directCopy){ mOpenCLRuntime->commandQueue().enqueueWriteBuffer(openCLBuffer(dstTensor), CL_TRUE, 0, needSize, hostPtr); return; } _allocHostBuffer(needSize, srcTensor); MNN::Tensor interTensor(srcTensor, srcTensor->getDimensionType(), false); interTensor.buffer().device = (uint64_t)mHostBuffer.second.get(); TensorUtils::getDescribe(&interTensor)->dimensionFormat = srcDimensionFormat; #ifdef ENABLE_OPENCL_TIME_PROFILER mOpenCLRuntime->commandQueue().finish(); { AUTOTIME; mOpenCLRuntime->commandQueue().enqueueWriteBuffer(*mHostBuffer.second, CL_TRUE, 0, needSize, hostPtr); } #else auto res = mOpenCLRuntime->commandQueue().enqueueWriteBuffer(*mHostBuffer.second, CL_TRUE, 0, needSize, hostPtr); if(res != CL_SUCCESS) { MNN_ERROR("OpenCL enqueue write error:%d\n", res); return; } #endif //Covert format mCLRuntime->convertToDevice((const Tensor*)&interTensor, dstTensor, srcDimensionFormat, mPrecision, mMemType, false); } void OpenCLBackend::copyBetweenDevice(const Tensor* srcTensor, const Tensor* dstTensor) const{ int srcMemtype = srcTensor->buffer().flags; int dstMemtype = dstTensor->buffer().flags; if(MNN_FORWARD_CPU == srcMemtype && MNN_FORWARD_CPU == dstMemtype){ mCLRuntime->copyBetweenDevice(srcTensor, dstTensor, mPrecision, mMemType); } else { const Tensor* hostTensor = MNN_FORWARD_CPU != srcMemtype ? srcTensor : dstTensor; const Tensor* deviceTensor = MNN_FORWARD_CPU == srcMemtype ? srcTensor : dstTensor; MNN_DATA_FORMAT data_format = TensorUtils::getDescribe(deviceTensor)->dimensionFormat; bool alloc_error = _allocHostBuffer(0, hostTensor); if(false == alloc_error){ MNN_ERROR("Alloc _allocHostBuffer error\n"); return; } //Covert format if(MNN_FORWARD_CPU != srcMemtype){ mCLRuntime->convertToDevice(hostTensor, deviceTensor, data_format, mPrecision, mMemType, false, srcMemtype); }else{ mCLRuntime->convertFromDevice(deviceTensor, hostTensor, data_format, mPrecision, mMemType, false, dstMemtype); } } } void CLRuntime::copyBetweenDevice(const Tensor* srcTensor, const Tensor* dstTensor, int precision, int backend_memtype) const{ int input_precision = ((OpenCLBackend*)(TensorUtils::getDescribeOrigin(srcTensor)->getBackend()))->getPrecision(); int output_precision = ((OpenCLBackend*)(TensorUtils::getDescribeOrigin(dstTensor)->getBackend()))->getPrecision(); #ifndef MNN_OPENCL_BUFFER_CLOSED if(backend_memtype == BUFFER) { OpenCL::convertBufferToBuffer(const_cast<Tensor*>(srcTensor), const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), input_precision, output_precision, precision, true, true); } else #endif /* MNN_OPENCL_BUFFER_CLOSED */ if(input_precision == output_precision){ std::vector<int> bufferShape = MNN::OpenCL::tensorShapeFormat(srcTensor); mOpenCLRuntime.get()->commandQueue().enqueueCopyImage( openCLImage(srcTensor), openCLImage(dstTensor), {0, 0, 0}, {0, 0, 0}, {(size_t)bufferShape[2]* UP_DIV(bufferShape[3], 4), (size_t)bufferShape[0]*bufferShape[1], 1}); } else{ OpenCL::convertImageToImage(const_cast<Tensor*>(srcTensor), const_cast<Tensor*>(dstTensor), mOpenCLRuntime.get(), input_precision, output_precision, precision); } return; } void OpenCLBackend::onCopyBuffer(const Tensor* srcTensor, const Tensor* dstTensor) const { #ifdef LOG_VERBOSE MNN_PRINT("Start onCopyBuffer !\n"); #endif clearRecord(); if (srcTensor->host<float>() != nullptr) { copyToDevice(srcTensor, dstTensor); }else if(dstTensor->host<void>() != nullptr){ copyFromDevice(srcTensor, dstTensor); }else{ copyBetweenDevice(srcTensor, dstTensor); } #ifdef LOG_VERBOSE MNN_PRINT("end onCopyBuffer !\n"); #endif } void* OpenCLBackend::allocMapTensorMemory(int length, bool svmFlag, cl_device_svm_capabilities svm_cap_) { if(length <= mMapMem.first) { return mMapMem.second; } #ifdef MNN_OPENCL_SVM_ENABLE if(svmFlag) { if(mMapMem.first != 0) { //Release small SVM Memory clSVMFree(mOpenCLRuntime->context().get(), mMapMem.second); } //Alloc proper SVM Memory cl_svm_mem_flags flags = CL_MEM_READ_WRITE; flags |= (svm_cap_ & CL_DEVICE_SVM_FINE_GRAIN_BUFFER) ? CL_MEM_SVM_FINE_GRAIN_BUFFER : 0; flags |= ((svm_cap_ & CL_DEVICE_SVM_FINE_GRAIN_BUFFER) && (svm_cap_ & CL_DEVICE_SVM_ATOMICS)) ? CL_MEM_SVM_ATOMICS : 0; mMapMem.second = clSVMAlloc(mOpenCLRuntime->context().get(), flags, length, 0); if(mMapMem.second == nullptr) { MNN_PRINT("SVM Alloc Failed\n"); } } else #endif { if(mMapMem.first != 0) { free(mMapMem.second); mMapMem.second = nullptr; } mMapMem.second = malloc(length); } mMapMem.first = length; return mMapMem.second; } void* OpenCLBackend::onMapTensor(Tensor::MapType mtype, Tensor::DimensionType dtype, const Tensor* srcTensor) { auto needSize = srcTensor->size(); clearRecord(); #ifdef MNN_OPENCL_SVM_ENABLE auto svm_cap_ = mOpenCLRuntime->getSvmCapabilities(); bool use_svm = (svm_cap_ & CL_DEVICE_SVM_FINE_GRAIN_BUFFER);//support fine grain svm use_svm |= ((svm_cap_ & CL_DEVICE_SVM_COARSE_GRAIN_BUFFER) && mOpenCLRuntime->getGpuType() == ADRENO);//support coarse grain svm and adreno gpu mUseSvm = (mOpenCLRuntime->getCLVersion() > 1.99f && use_svm); if(mUseSvm) {// CL version beyond 2.0 & support svm svmPtr = allocMapTensorMemory(needSize, true, svm_cap_); if(mtype == Tensor::MAP_TENSOR_READ) { //tmpTensor alloc MNN::Tensor tmpTensor(srcTensor, dtype, false); tmpTensor.buffer().device = (uint64_t)svmPtr; //Convert format MNN_DATA_FORMAT format_type = MNN_DATA_FORMAT_NCHW; if(dtype == MNN::Tensor::TENSORFLOW) { format_type = MNN_DATA_FORMAT_NHWC; } else if(dtype == MNN::Tensor::CAFFE_C4) { format_type = MNN_DATA_FORMAT_NC4HW4; } mCLRuntime->convertFromDevice(srcTensor, &tmpTensor, format_type, mPrecision, mMemType, true); } if(svm_cap_ & CL_DEVICE_SVM_FINE_GRAIN_BUFFER) { //Make sure command finished mOpenCLRuntime->commandQueue().finish(); return svmPtr; } auto map_flag = CL_MAP_WRITE; if(mtype == Tensor::MAP_TENSOR_READ) { map_flag = CL_MAP_READ; } cl_int res = clEnqueueSVMMap(mOpenCLRuntime->commandQueue().get(), true, map_flag, svmPtr, needSize, 0, nullptr, nullptr); MNN_CHECK_CL_SUCCESS(res, "svm_map") return svmPtr; } #endif /** Not Support Svm, Use onopyBuffer */ svmPtr = allocMapTensorMemory(needSize, false); if(mtype == Tensor::MAP_TENSOR_READ) { //tmpTensor alloc MNN::Tensor tmpTensor(srcTensor, dtype, false); tmpTensor.buffer().host = (uint8_t *)svmPtr; //use onCopyBuffer onCopyBuffer(srcTensor, &tmpTensor); } return svmPtr; } bool OpenCLBackend::onUnmapTensor(Tensor::MapType mtype, Tensor::DimensionType dtype, const Tensor* dstTensor, void* mapPtr) { #ifdef MNN_OPENCL_SVM_ENABLE auto svm_cap_ = mOpenCLRuntime->getSvmCapabilities(); if(mUseSvm) {// CL version beyond 2.0 & support svm //If COARSE_SVM, Unmap first if(!(svm_cap_ & CL_DEVICE_SVM_FINE_GRAIN_BUFFER)) { cl_int res = clEnqueueSVMUnmap(mOpenCLRuntime->commandQueue().get(), svmPtr, 0, nullptr, nullptr); MNN_CHECK_CL_SUCCESS(res, "svm_unmap") } if(mtype == Tensor::MAP_TENSOR_WRITE) { //interTensor alloc MNN::Tensor interTensor(dstTensor, dtype, false); interTensor.buffer().device = (uint64_t)svmPtr; //Convert format MNN_DATA_FORMAT format_type = MNN_DATA_FORMAT_NCHW; if(dtype == MNN::Tensor::TENSORFLOW) { format_type = MNN_DATA_FORMAT_NHWC; } else if(dtype == MNN::Tensor::CAFFE_C4) { format_type = MNN_DATA_FORMAT_NC4HW4; } mCLRuntime->convertToDevice(&interTensor, dstTensor, format_type, mPrecision, mMemType, true); } mOpenCLRuntime->commandQueue().finish(); return true; } #endif /** Not Support Svm, Use onopyBuffer */ if(mtype == Tensor::MAP_TENSOR_WRITE) { //srcTensor alloc MNN::Tensor srcTensor(dstTensor, dtype, false); srcTensor.buffer().host = (uint8_t *)svmPtr; //use onCopyBuffer onCopyBuffer(&srcTensor, dstTensor); } return true; } bool OpenCLBackend::addCreator(std::pair<OpType, GpuMemObject> t, Creator* c) { auto map = gCreator(); if (map->find(t) != map->end()) { MNN_PRINT("Error: %d type, %d GpuMemObject has be added\n", t.first, t.second); return false; } map->insert(std::make_pair(t, c)); return true; } // ----------------------------------------------------------------------------- // Runtime Register // ----------------------------------------------------------------------------- class CLRuntimeCreator : public RuntimeCreator { virtual Runtime* onCreate(const Backend::Info& info) const { #ifdef MNN_USE_LIB_WRAPPER OpenCLSymbolsOperator::createOpenCLSymbolsOperatorSingleInstance(); if (nullptr == OpenCLSymbolsOperator::getOpenclSymbolsPtr()) { MNN_PRINT("OpenCL init error, fallback ... \n"); return nullptr; } if (true == OpenCLSymbolsOperator::getOpenclSymbolsPtr()->isError()) { MNN_PRINT("Parsing OpenCL symbols error !!! \n"); return nullptr; } #endif auto rt = new CLRuntime(info); if(rt->isCLRuntimeError() == true) { delete rt; return nullptr; } return rt; } virtual bool onValid(Backend::Info& info) const { return true; } }; DataType OpenCLBackend::getDataType(const Tensor* tensor) const{ auto des = TensorUtils::getDescribe(tensor); if (nullptr == des->quantAttr.get()) { return DataType_DT_FLOAT; } return des->type; } cl_channel_type OpenCLBackend::fpType() { if (mPrecision != BackendConfig::Precision_High) { return CL_HALF_FLOAT; } return CL_FLOAT; } int OpenCLBackend::fpBytes() { return (fpType() == CL_FLOAT ? sizeof(float) : sizeof(half_float::half)); } void OpenCLBackend::clearRecord() const{ #if !defined(ENABLE_OPENCL_TIME_PROFILER) && defined(MNN_USE_LIB_WRAPPER) if(mUseRecordQueue && mDevideOpRecord){ for(int i = 0; i < mRecordings.size(); ++i){ std::vector<cl_array_arg_qcom> update_kernel_args; std::vector<cl_workgroup_qcom> update_global_size; std::vector<cl_workgroup_qcom> update_local_size; for (int j = 0; j < mRecordings[i].updateInfo.size(); ++j){ for(int k = 0; k < mRecordings[i].updateInfo[j]->update_kernel_args.size(); ++k){ update_kernel_args.emplace_back(mRecordings[i].updateInfo[j]->update_kernel_args[k]); update_kernel_args.back().dispatch_index = j; } for(int k = 0; k < mRecordings[i].updateInfo[j]->update_global_size.size(); ++k){ update_global_size.emplace_back(mRecordings[i].updateInfo[j]->update_global_size[k]); update_global_size.back().dispatch_index = j; } for(int k = 0; k < mRecordings[i].updateInfo[j]->update_local_size.size(); ++k){ update_local_size.emplace_back(mRecordings[i].updateInfo[j]->update_local_size[k]); update_local_size.back().dispatch_index = j; } } cl_int res = mOpenCLRuntime->commandQueue().EnqueueRecordingQCOM(mRecordings[i].record, update_kernel_args.size(), update_kernel_args.data(), 0, nullptr, update_global_size.size(), update_global_size.data(), update_local_size.size(), update_local_size.data(), 0, nullptr, nullptr); MNN_CHECK_CL_SUCCESS(res, "EnqueueRecordingQCOM"); } mOpenCLRuntime->commandQueue().finish(); mRecordings.clear(); } #endif } void OpenCLBackend::enqeueRecord() const{ #if !defined(ENABLE_OPENCL_TIME_PROFILER) && defined(MNN_USE_LIB_WRAPPER) if(mUseRecordQueue && !mDevideOpRecord){ for(int i = 0; i < mRecordings.size(); ++i){ std::vector<cl_array_arg_qcom> update_kernel_args; std::vector<cl_workgroup_qcom> update_global_size; std::vector<cl_workgroup_qcom> update_local_size; for (int j = 0; j < mRecordings[i].updateInfo.size(); ++j){ for(int k = 0; k < mRecordings[i].updateInfo[j]->update_kernel_args.size(); ++k){ update_kernel_args.emplace_back(mRecordings[i].updateInfo[j]->update_kernel_args[k]); } for(int k = 0; k < mRecordings[i].updateInfo[j]->update_global_size.size(); ++k){ update_global_size.emplace_back(mRecordings[i].updateInfo[j]->update_global_size[k]); } for(int k = 0; k < mRecordings[i].updateInfo[j]->update_local_size.size(); ++k){ update_local_size.emplace_back(mRecordings[i].updateInfo[j]->update_local_size[k]); } } cl_int res = mOpenCLRuntime->commandQueue().EnqueueRecordingQCOM(mRecordings[i].record, update_kernel_args.size(), update_kernel_args.data(), 0, nullptr, update_global_size.size(), update_global_size.data(), update_local_size.size(), update_local_size.data(), 0, nullptr, nullptr); MNN_CHECK_CL_SUCCESS(res, "EnqueueRecordingQCOM"); } mOpenCLRuntime->commandQueue().finish(); } #endif } void OpenCLBackend::releaseRecord(){ #if !defined(ENABLE_OPENCL_TIME_PROFILER) && defined(MNN_USE_LIB_WRAPPER) if(mUseRecordQueue && !mDevideOpRecord){ for(int i = 0; i < mRecordings.size(); ++i){ cl_int res = clReleaseRecordingQCOM(mRecordings[i].record); MNN_CHECK_CL_SUCCESS(res, "clReleaseRecordingQCOM"); } mRecordings.clear(); } #endif } void OpenCLBackend::startRecord(cl_recording_qcom &recording){ #if !defined(ENABLE_OPENCL_TIME_PROFILER) && defined(MNN_USE_LIB_WRAPPER) if(!mUseRecordQueue){ return; } #ifdef LOG_VERBOSE MNN_PRINT("start startRecord !\n"); #endif cl_int res = CL_SUCCESS; if(mDevideOpRecord){ if(recording != NULL){ clReleaseRecordingQCOM(recording); } recording = mOpenCLRuntime->recordableQueue().NewRecordingQCOM(&res); MNN_CHECK_CL_SUCCESS(res, "clNewRecordingQCOM"); } #ifdef LOG_VERBOSE MNN_PRINT("end startRecord !\n"); #endif #endif //ENABLE_OPENCL_TIME_PROFILER } void OpenCLBackend::endRecord(cl_recording_qcom &recording, bool flag){ #if !defined(ENABLE_OPENCL_TIME_PROFILER) && defined(MNN_USE_LIB_WRAPPER) if(!mUseRecordQueue){ return; } #ifdef LOG_VERBOSE MNN_PRINT("start endRecord !\n"); #endif if(mDevideOpRecord){ cl_int res = CL_SUCCESS; res = clEndRecordingQCOM(recording); MNN_CHECK_CL_SUCCESS(res, "clEndRecordingQCOM"); } else if(flag) { // endRecord for last kernel be recorded when record mode is MNN_GPU_RECORD_BATCH if(!mRecordings.empty()){ cl_int res = clEndRecordingQCOM(mRecordings.back().record); mRecordNums = 0; MNN_CHECK_CL_SUCCESS(res, "clEndRecordingQCOM"); } } #ifdef LOG_VERBOSE MNN_PRINT("end endRecord !\n"); #endif #endif //ENABLE_OPENCL_TIME_PROFILER } void OpenCLBackend::addRecord(cl_recording_qcom &record, std::vector<RecordUpdateInfo *>updateInfo){ if(mDevideOpRecord){ RecordInfo info; info.record = record; for(int i = 0; i < updateInfo.size(); ++i) { info.updateInfo.emplace_back(updateInfo[i]); } mRecordings.emplace_back(info); } } void OpenCLBackend::recordKernel2d(const std::shared_ptr<KernelWrap> &kernelW, const std::vector<uint32_t> &gws, const std::vector<uint32_t> &lws, RecordUpdateInfo *updateInfo) { #if !defined(ENABLE_OPENCL_TIME_PROFILER) && defined(MNN_USE_LIB_WRAPPER) if(!mUseRecordQueue){ return; } auto kernel = kernelW->get(); #ifdef LOG_VERBOSE MNN_PRINT("start record2dKernel !\n"); #endif cl_int res = CL_SUCCESS; if(!mDevideOpRecord){ RecordInfo info; int recordNum = mRecordNums == mUseRecordableQueueSize ? 0 : mRecordNums; if(updateInfo != nullptr){ for(int i = 0; i < updateInfo->update_kernel_args.size(); ++i){ updateInfo->update_kernel_args[i].dispatch_index = recordNum; } for(int i = 0; i < updateInfo->update_global_size.size(); ++i){ updateInfo->update_global_size[i].dispatch_index = recordNum; } for(int i = 0; i < updateInfo->update_local_size.size(); ++i){ updateInfo->update_local_size[i].dispatch_index = recordNum; } info.updateInfo.emplace_back(updateInfo); } if(mRecordNums == 0){ cl_recording_qcom recording = mOpenCLRuntime->recordableQueue().NewRecordingQCOM(&res); MNN_CHECK_CL_SUCCESS(res, "clNewRecordingQCOM"); info.record = recording; mRecordings.emplace_back(info); }else if(mRecordNums == mUseRecordableQueueSize){ res = clEndRecordingQCOM(mRecordings.back().record); MNN_CHECK_CL_SUCCESS(res, "clEndRecordingQCOM"); cl_recording_qcom recording = mOpenCLRuntime->recordableQueue().NewRecordingQCOM(&res); MNN_CHECK_CL_SUCCESS(res, "clNewRecordingQCOM"); info.record = recording; mRecordings.emplace_back(info); mRecordNums = 0; } else if(updateInfo != nullptr){ auto &lastInfo = mRecordings.back(); lastInfo.updateInfo.emplace_back(updateInfo); } mRecordNums++; } std::vector<uint32_t> internalGlobalWS = gws; for (size_t i = 0; i < 2; ++i) { internalGlobalWS[i] = ROUND_UP(gws[i], std::max((uint32_t)1, lws[i])); } if(lws[0]==0 || lws[1]==0){ res = mOpenCLRuntime->recordableQueue().enqueueNDRangeKernel( kernel, cl::NullRange, cl::NDRange(internalGlobalWS[0], internalGlobalWS[1]), cl::NullRange, nullptr, nullptr); }else{ res = mOpenCLRuntime->recordableQueue().enqueueNDRangeKernel( kernel, cl::NullRange, cl::NDRange(internalGlobalWS[0], internalGlobalWS[1]), cl::NDRange(lws[0], lws[1]), nullptr, nullptr); } MNN_CHECK_CL_SUCCESS(res, "recordKernel2d"); #ifdef LOG_VERBOSE MNN_PRINT("end record2dKernel !\n"); #endif #endif //ENABLE_OPENCL_TIME_PROFILER } void OpenCLBackend::recordKernel3d(const std::shared_ptr<KernelWrap> &kernelW, const std::vector<uint32_t> &gws, const std::vector<uint32_t> &lws, RecordUpdateInfo *updateInfo) { #if !defined(ENABLE_OPENCL_TIME_PROFILER) && defined(MNN_USE_LIB_WRAPPER) if(!mUseRecordQueue){ return; } auto kernel = kernelW->get(); #ifdef LOG_VERBOSE MNN_PRINT("start record3dKernel !\n"); #endif cl_int res = CL_SUCCESS; std::vector<uint32_t> internalGlobalWS = gws; for (size_t i = 0; i < 3; ++i) { internalGlobalWS[i] = ROUND_UP(gws[i], std::max((uint32_t)1, lws[i])); } if(!mDevideOpRecord){ RecordInfo info; int recordNum = mRecordNums == mUseRecordableQueueSize ? 0 : mRecordNums; if(updateInfo != nullptr){ for(int i = 0; i < updateInfo->update_kernel_args.size(); ++i){ updateInfo->update_kernel_args[i].dispatch_index = recordNum; } for(int i = 0; i < updateInfo->update_global_size.size(); ++i){ updateInfo->update_global_size[i].dispatch_index = recordNum; } for(int i = 0; i < updateInfo->update_local_size.size(); ++i){ updateInfo->update_local_size[i].dispatch_index = recordNum; } info.updateInfo.emplace_back(updateInfo); } if(mRecordNums == 0){ cl_recording_qcom recording = mOpenCLRuntime->recordableQueue().NewRecordingQCOM(&res); MNN_CHECK_CL_SUCCESS(res, "clNewRecordingQCOM"); info.record = recording; mRecordings.emplace_back(info); }else if(mRecordNums == mUseRecordableQueueSize){ res = clEndRecordingQCOM(mRecordings.back().record); MNN_CHECK_CL_SUCCESS(res, "clEndRecordingQCOM"); cl_recording_qcom recording = mOpenCLRuntime->recordableQueue().NewRecordingQCOM(&res); MNN_CHECK_CL_SUCCESS(res, "clNewRecordingQCOM"); info.record = recording; mRecordings.emplace_back(info); mRecordNums = 0; } else if(updateInfo != nullptr){ auto &lastInfo = mRecordings.back(); lastInfo.updateInfo.emplace_back(updateInfo); } mRecordNums++; } if(lws[0]==0 || lws[1]==0 || lws[2]==0){ res = mOpenCLRuntime->recordableQueue().enqueueNDRangeKernel( kernel, cl::NullRange, cl::NDRange(internalGlobalWS[0], internalGlobalWS[1], internalGlobalWS[2]), cl::NullRange, nullptr, nullptr); }else{ res = mOpenCLRuntime->recordableQueue().enqueueNDRangeKernel( kernel, cl::NullRange, cl::NDRange(internalGlobalWS[0], internalGlobalWS[1], internalGlobalWS[2]), cl::NDRange(lws[0], lws[1], lws[2]), nullptr, nullptr); } MNN_CHECK_CL_SUCCESS(res, "recordKernel3d"); #ifdef LOG_VERBOSE MNN_PRINT("end record3dKernel !\n"); #endif #endif //ENABLE_OPENCL_TIME_PROFILER } void OpenCLBackend::setGpuMode(const int cl_mode_num) { int totalSet = 0; bool isSet = (cl_mode_num & MNN_GPU_MEMORY_BUFFER); if(isSet) { mMemType = BUFFER; totalSet++; } isSet = (cl_mode_num & MNN_GPU_MEMORY_IMAGE); if(isSet) { mMemType = IMAGE; totalSet++; } auto gpuType = mOpenCLRuntime->getGpuType(); if(mMemType == AUTO) { if(gpuType == MALI || gpuType == INTEL) { mMemType = BUFFER; } else { mMemType = IMAGE; } } if(totalSet > 1) { MNN_PRINT("set both BUFFER and IMAGE mode is not permitted, please check cl_mode:%x!\n", cl_mode_num); } totalSet = 0; isSet = (cl_mode_num & MNN_GPU_TUNING_NONE); if(isSet) { mTuneLevel = None; totalSet++; } isSet = (cl_mode_num & MNN_GPU_TUNING_FAST); if(isSet) { mTuneLevel = Fast; totalSet++; } isSet = (cl_mode_num & MNN_GPU_TUNING_NORMAL); if(isSet) { mTuneLevel = Normal; totalSet++; } isSet = (cl_mode_num & MNN_GPU_TUNING_HEAVY); if(isSet) { mTuneLevel = Heavy; totalSet++; } isSet = (cl_mode_num & MNN_GPU_TUNING_WIDE); if(isSet) { mTuneLevel = Wide; totalSet++; } if(totalSet != 1) { MNN_PRINT("set multi tuning mode is not permitted, please check cl_mode:%x!\n", cl_mode_num); } totalSet = 0; mUseRecordableQueueSize = mOpenCLRuntime->getUseRecordableQueueSize(); mUseRecordQueue = ((cl_mode_num & MNN_GPU_RECORD_OP) || (cl_mode_num & MNN_GPU_RECORD_BATCH)) && mOpenCLRuntime->isSupportRecordQueue() && (mUseRecordableQueueSize > 0); isSet = (cl_mode_num & MNN_GPU_RECORD_OP); if(isSet) { mDevideOpRecord = true; totalSet++; } isSet = (cl_mode_num & MNN_GPU_RECORD_BATCH); if(isSet) { mDevideOpRecord = false; totalSet++; } if(totalSet > 1) { MNN_PRINT("set multi record kernel mode is not permitted, please check cl_mode:%x!\n", cl_mode_num); } } const Runtime* OpenCLBackend::getRuntime() { return mCLRuntime; } #ifdef MNN_OPENCL_SEP_BUILD bool placeholder = []() { static std::once_flag createOnce; std::call_once(createOnce, []() { MNNInsertExtraRuntimeCreator(MNN_FORWARD_OPENCL, new CLRuntimeCreator, true); }); return true; }(); #else void registerOpenCLRuntimeCreator() { registerOpenCLOps(); MNNInsertExtraRuntimeCreator(MNN_FORWARD_OPENCL, new CLRuntimeCreator, true); } #endif } // namespace OpenCL } // namespace MNN