// // VulkanConvolution.cpp // MNN // // Created by MNN on 2019/01/31. // Copyright © 2018, Alibaba Group Holding Limited // #include "VulkanConvolution.hpp" #include "core/Macro.h" #include "VulkanConvolutionImpl.hpp" #include "core/ConvolutionCommon.hpp" #include "VulkanConv1x1CoopAFP16.hpp" #include "VulkanConv1x1CoopA8.hpp" #include "VulkanConv1x1General.hpp" namespace MNN { int VulkanConvolutionCommon::gImage2ColLocal = 256; std::string VulkanConvolutionCommon::getPostTreatMacro(const Convolution2DCommon* common) { if (common->relu()) { return "RELU_"; } else if (common->relu6()) { return "RELU6_"; } return ""; } static std::shared_ptr _createBufferForConvDepthwise(VulkanBackend* extra, const Convolution2DCommon* mCommon, const float* weightSource, size_t weightSize) { auto outputCount = mCommon->outputCount(); auto totalWeightSize = ALIGN_UP4(mCommon->outputCount()) * (mCommon->kernelY() * mCommon->kernelX()); size_t elementSize = sizeof(float); if (extra->useFP16()) { elementSize = sizeof(int16_t); } auto kernelBuffer = std::make_shared(extra->getMemoryPool(), false, elementSize * totalWeightSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); auto layer = mCommon; auto weight = kernelBuffer->map(); int kw = layer->kernelX(); int kh = layer->kernelY(); int planeStride = kw * kh * 4; int cur = 0; auto packWeight = [&](bool useFP16) { for (int c = 0; c < outputCount; ++c) { int plane = c / 4; int offset = c % 4; for (int y = 0; y < kh; ++y) { for (int x = 0; x < kw; ++x) { int idx = offset + (x + y * kw) * 4 + planeStride * plane; float val = weightSource[cur++]; if (useFP16) { ((half_float::half*)weight)[idx] = (half_float::half)val; } else { ((float*)weight)[idx] = val; } } } } }; packWeight(extra->useFP16()); kernelBuffer->unmap(); return kernelBuffer; } void VulkanConvolutionCommon::writeDeconvolution(VulkanConvolutionCommon::ConvolutionParameter* convCons, const Convolution2DCommon* common, const Tensor* src, const Tensor* dst) { const int icDiv4 = UP_DIV(src->channel(), 4); const int ocDiv4 = UP_DIV(dst->channel(), 4); auto pad = ConvolutionCommon::convolutionTransposePad(src, dst, common); int padX = pad.first; int padY = pad.second; convCons->dilate[0] = common->dilateX(); convCons->dilate[1] = common->dilateY(); convCons->stride[0] = common->strideX(); convCons->stride[1] = common->strideY(); convCons->pad[0] = padX; convCons->pad[1] = padY; convCons->kernelSize[0] = common->kernelX(); convCons->kernelSize[1] = common->kernelY(); convCons->inputSize[0] = src->width(); convCons->inputSize[1] = src->height(); convCons->inputSize[2] = icDiv4; convCons->inputSize[3] = src->batch(); convCons->outputSize[0] = dst->width(); convCons->outputSize[1] = dst->height(); convCons->outputSize[2] = ocDiv4; convCons->outputSize[3] = dst->batch(); } void VulkanConvolutionCommon::writeParameter(ConvolutionParameter* convCons, const Convolution2DCommon* common, const Tensor* input, const Tensor* output) { int icDiv4 = UP_DIV(input->channel(), 4); int ocDiv4 = UP_DIV(output->channel(), 4); auto pad = ConvolutionCommon::convolutionPad(input, output, common); int padX = pad.first; int padY = pad.second; { convCons->dilate[0] = common->dilateX(); convCons->dilate[1] = common->dilateY(); convCons->stride[0] = common->strideX(); convCons->stride[1] = common->strideY(); convCons->pad[0] = padX; convCons->pad[1] = padY; convCons->kernelSize[0] = common->kernelX(); convCons->kernelSize[1] = common->kernelY(); convCons->inputSize[0] = input->width(); convCons->inputSize[1] = input->height(); convCons->inputSize[2] = icDiv4; convCons->inputSize[3] = input->batch(); convCons->outputSize[0] = output->width(); convCons->outputSize[1] = output->height(); convCons->outputSize[2] = ocDiv4; convCons->outputSize[3] = output->batch(); convCons->offset[0] = 0; convCons->offset[1] = 0; convCons->offset[2] = output->height(); } } VulkanConvolutionCommon::VulkanConvolutionCommon(const Convolution2DCommon* common, Backend* bn) : VulkanBasicExecution(bn) { auto extra = static_cast(bn); mCommon = common; mConvCons = std::make_shared(extra->getMemoryPool(), false, sizeof(ConvolutionParameter), nullptr, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); } VulkanConvolutionCommon::~VulkanConvolutionCommon() { } ErrorCode VulkanConvolutionCommon::onEncode(const std::vector& inputs, const std::vector& outputs, const VulkanCommandPool::Buffer* cmdBuffer) { auto input = inputs[0]; auto output = outputs[0]; { auto convCons = (ConvolutionParameter*)mConvCons->map(); writeParameter(convCons, mCommon, input, output); mConvCons->unmap(); } auto code = this->onEncodeConvolution(mCommon, inputs, outputs, cmdBuffer, mConvCons.get()); if (NO_ERROR != code) { return code; } return NO_ERROR; } bool VulkanConvolutionDepthwise::_init(const float* weightData, size_t weightSize, const Op* convOp, Backend* bn, bool initweights) { auto extra = static_cast(bn); auto common = convOp->main_as_Convolution2D()->common(); // Create Pipeline std::vector convTypes{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER}; MNN_ASSERT(OpType_ConvolutionDepthwise == convOp->type()); std::string pKey = "glsl_convolutionDepthwise_"; pKey += getPostTreatMacro(common); if (extra->useFP16()) { pKey += "FP16_"; } pKey += "comp"; mConvPipeline = extra->getPipeline(pKey, convTypes); mLocalX = 16; mLocalY = 16; mConvSet.reset(mConvPipeline->createSet()); if (!initweights) { return true; } auto bytes = sizeof(float); auto c4 = UP_DIV(common->outputCount(), 4); if (nullptr != weightData){ mKernel = _createBufferForConvDepthwise(extra, common, weightData, weightSize); } else { size_t elementSize = sizeof(float); if (extra->useFP16()) { elementSize = sizeof(int16_t); } mKernel.reset(new VulkanBuffer(extra->getMemoryPool(), false, common->kernelX() * common->kernelY() * c4 * 4 * elementSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT)); auto weight = mKernel->map(); ::memset(weight, 0, mKernel->size()); mKernel->unmap(); } auto convReal = convOp->main_as_Convolution2D(); size_t elementSize = sizeof(float); if (extra->useFP16()) { elementSize = sizeof(int16_t); } auto biasBuffer = std::make_shared(extra->getMemoryPool(), false, elementSize * ALIGN_UP4(common->outputCount())); auto bias = biasBuffer->map(); ::memset(bias, 0, ALIGN_UP4(common->outputCount()) * elementSize); if (nullptr != convReal->bias()) { // Create Buffer if (extra->useFP16()) { FLOAT_TO_HALF(convReal->bias()->data(), (int16_t*)bias, common->outputCount()); } else { ::memcpy(bias, convReal->bias()->data(), common->outputCount() * sizeof(float)); } } biasBuffer->unmap(); mBias = biasBuffer; return true; } bool VulkanConvolutionDepthwise::onClone(Backend* bn, const Op* op, VulkanBasicExecution** dst) { if (nullptr == dst) { return true; } auto res = new VulkanConvolutionDepthwise(op, bn); res->mBias = mBias; res->mKernel = mKernel; *dst = res; return true; } VulkanConvolutionDepthwise::VulkanConvolutionDepthwise(const float* weightData, size_t weightSize, const Op* convOp, Backend* bn) : VulkanConvolutionCommon(convOp->main_as_Convolution2D()->common(), bn) { _init(weightData, weightSize, convOp, bn, true); } VulkanConvolutionDepthwise::VulkanConvolutionDepthwise(const Op* op, Backend* bn) : VulkanConvolutionCommon(op->main_as_Convolution2D()->common(), bn) { _init(nullptr, 0, op, bn, false); } VulkanConvolutionDepthwise::~VulkanConvolutionDepthwise() { } ErrorCode VulkanConvolutionDepthwise::onEncodeConvolution(const Convolution2DCommon* common, const std::vector& inputs, const std::vector& outputs, const VulkanCommandPool::Buffer* cmdBuffer, const VulkanBuffer* convCons) { auto input = inputs[0]; auto output = outputs[0]; /*Set Const Parameters*/ int ocDiv4 = UP_DIV(output->channel(), 4); int ow = output->width(); int oh = output->height(); auto extra = static_cast(backend()); if (inputs.size() >= 2) { auto weight = extra->getTensorBuffer(inputs[1]); auto weightSize = extra->getTensorSize(inputs[1]); std::string key = "glsl_dwweightcopy_"; if (extra->useFP16()) { key += "FP16_"; } key += "comp"; auto pipeline = extra->getPipeline(key, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }); std::shared_ptr des(pipeline->createSet()); des->writeBuffer(weight.first->buffer(), 1, weightSize, weight.second); des->writeBuffer(mKernel->buffer(), 0, mKernel->size()); int dim[4] = { common->kernelX(), common->kernelY(), output->channel(), output->channel() * common->kernelX() * common->kernelY() }; std::shared_ptr uniforms(new VulkanBuffer(extra->getMemoryPool(), false, sizeof(dim), &dim, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT)); des->writeBuffer(uniforms->buffer(), 2, uniforms->size()); pipeline->bind(cmdBuffer->get(), des->get()); vkCmdDispatch(cmdBuffer->get(), UP_DIV(dim[3], 256), 1, 1); mExtraBuffers = uniforms; mExtraSets = des; cmdBuffer->barrierSource(mKernel->buffer(), 0, mKernel->size()); } std::pair bias; size_t biasSize; if (inputs.size() >= 3) { bias = extra->getTensorBuffer(inputs[2]); biasSize = extra->getTensorSize(inputs[2]); } else { bias.first = mBias.get(); bias.second = 0; biasSize = mBias->size(); } /*Write Command Buffer*/ auto outputBuffer = extra->getBuffer(outputs[0]); auto inputBuffer = extra->getBuffer(input); mConvSet->writeBuffer(outputBuffer, 0); mConvSet->writeBuffer(inputBuffer, 1); mConvSet->writeBuffer(mKernel->buffer(), 2, mKernel->size()); mConvSet->writeBuffer(bias.first->buffer(), 3, biasSize, bias.second); mConvSet->writeBuffer(convCons->buffer(), 4, convCons->size()); mConvPipeline->bind(cmdBuffer->get(), mConvSet->get()); vkCmdDispatch(cmdBuffer->get(), UP_DIV(ow, mLocalX), UP_DIV(oh, mLocalY), ocDiv4 * input->batch()); return NO_ERROR; } class VulkanConvolutionSlideWindowsInt8 : public VulkanConvolutionCommon { public: struct Resource { const VulkanPipeline* mPipeline; std::shared_ptr mBias; std::shared_ptr mKernel; std::shared_ptr mWeightScale; std::pair mChannels; }; private: std::shared_ptr mResource; std::shared_ptr mConvSet; public: static std::shared_ptr makeResource( std::shared_ptr quanParam, const float* biasPtr, const Convolution2DCommon* convOption, VulkanBackend* vkBn, int srcCount, int outputCount, bool useFP16) { std::shared_ptr resP(new Resource); auto& res = *resP; if (nullptr == quanParam.get() || nullptr == quanParam->weight.get() || srcCount <= 0 || outputCount <= 0) { MNN_ERROR("Invalid quant conv param for Vulkan, srcCount=%d, outputCount=%d\n", srcCount, outputCount); return nullptr; } size_t elementSize = useFP16 ? sizeof(int16_t) : sizeof(float); const int kxky = convOption->kernelX() * convOption->kernelY(); if (kxky <= 0) { MNN_ERROR("Invalid kernel size for Vulkan quant conv, kxky=%d\n", kxky); return nullptr; } const size_t expectedWeightCount = (size_t)srcCount * (size_t)outputCount * (size_t)kxky; const size_t packedWeightCount = (size_t)quanParam->weight.size(); if (quanParam->canUseInt4) { const size_t minimumPackedWeightCount = UP_DIV(expectedWeightCount, (size_t)2); if (packedWeightCount < minimumPackedWeightCount) { MNN_ERROR("Invalid int4 weight size for Vulkan, packed=%zu, required=%zu\n", packedWeightCount, minimumPackedWeightCount); return nullptr; } } else { if (packedWeightCount < expectedWeightCount) { MNN_ERROR("Invalid int8 weight size for Vulkan, count=%zu, required=%zu\n", packedWeightCount, expectedWeightCount); return nullptr; } } // Reorder auto& pool = vkBn->getMemoryPool(); int icC4 = UP_DIV(srcCount, 4); int ocC4 = UP_DIV(outputCount, 4); int unit = 4; int packSize = unit * unit; const size_t reorderWeightCount = (size_t)icC4 * (size_t)ocC4 * (size_t)kxky * (size_t)packSize; if (reorderWeightCount == 0) { MNN_ERROR("Invalid reorder weight size for Vulkan, icC4=%d, ocC4=%d, kxky=%d\n", icC4, ocC4, kxky); return nullptr; } std::vector weightReorder(reorderWeightCount, 0); const auto* rawWeight = reinterpret_cast(quanParam->weight.get()); const auto* rawWeightInt8 = reinterpret_cast(quanParam->weight.get()); int divSize = 1; for (int oz=0; ozcanUseInt4) { const size_t srcByteIndex = srcIndex >> 1; const auto packedValue = rawWeight[srcByteIndex]; const int nibble = (srcIndex & 1) ? (packedValue & 0x0F) : ((packedValue >> 4) & 0x0F); srcValue = (int8_t)(nibble - 8); } else { srcValue = rawWeightInt8[srcIndex]; } dstSz[k * packSize * icC4] = srcValue; } } } if (quanParam->canUseInt4) { divSize = 2; } // Weight const size_t kernelBufferSize = reorderWeightCount / (size_t)divSize; if (kernelBufferSize == 0) { MNN_ERROR("Invalid kernel buffer size for Vulkan quant conv\n"); return nullptr; } res.mKernel.reset(new VulkanBuffer(pool, false, kernelBufferSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); float originOffset = 0.0f; float unpackRate = 127.0f; if (quanParam->canUseInt4) { originOffset = -8.0f; unpackRate = 1.0f; size_t weightLength = kernelBufferSize; std::vector weightNew(weightLength); for (size_t i=0; icopyToGPUBuffer(weightNew.data(), res.mKernel->buffer(), weightNew.size(), 0); } else { vkBn->copyToGPUBuffer(weightReorder.data(), res.mKernel->buffer(), weightReorder.size(), 0); } // Bias { res.mBias.reset(new VulkanBuffer(pool, false, ocC4 * 4 * elementSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); const void * tempPtr = biasPtr; std::vector biasDataFP16; if (useFP16) { biasDataFP16.resize(outputCount * sizeof(int16_t), 0); FLOAT_TO_HALF(biasPtr, (int16_t *) biasDataFP16.data(), outputCount); tempPtr = (const void *) biasDataFP16.data(); } vkBn->copyToGPUBuffer(tempPtr, res.mBias->buffer(), outputCount * elementSize, 0); } // Scale { res.mWeightScale.reset(new VulkanBuffer(pool, false, ocC4 * 4 * 2 * elementSize, nullptr, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_SHARING_MODE_EXCLUSIVE, 0)); auto alphaPtr = quanParam->alpha.get(); auto asym = quanParam->asymmetric; std::vector wscaleData(ocC4 * 4 * 2 * elementSize, 0); half_float::half * tempHalf = (half_float::half *) wscaleData.data(); float * tempFloat = (float *) wscaleData.data(); for (int i=0; icopyToGPUBuffer(wscaleData.data(), res.mWeightScale->buffer(), ocC4 * 4 * 2 * elementSize, 0); } // Build Pipeline // Create Pipeline std::vector convTypes{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER }; std::string pKey = "glsl_convolution"; if (quanParam->canUseInt4) { pKey += "int4_"; } else { pKey += "int8_"; } auto macro = getPostTreatMacro(convOption); pKey += macro; if (useFP16) { pKey += "FP16_"; } pKey += "comp"; res.mPipeline = vkBn->getPipeline(pKey, convTypes); return resP; } VulkanConvolutionSlideWindowsInt8(VulkanBackend* backend, const Convolution2DCommon* convOption, std::shared_ptr resource) : VulkanConvolutionCommon(convOption, backend) { mResource = resource; mConvSet.reset(mResource->mPipeline->createSet()); } ~VulkanConvolutionSlideWindowsInt8() { // Do nothing } virtual bool onClone(Backend* bn, const Op* op, VulkanBasicExecution** dst) override { if (nullptr == dst) { return true; } auto res = new VulkanConvolutionSlideWindowsInt8((VulkanBackend*)bn, op->main_as_Convolution2D()->common(), mResource); *dst = res; return true; } virtual ErrorCode onEncodeConvolution(const Convolution2DCommon* common, const std::vector& inputs, const std::vector& outputs, const VulkanCommandPool::Buffer* cmdBuffer, const VulkanBuffer* constConvBuffer) override { auto src = inputs[0]; auto dst = outputs[0]; const int icDiv4 = UP_DIV(src->channel(), 4); const int ocDiv4 = UP_DIV(dst->channel(), 4); auto vkBn = (VulkanBackend*)backend(); auto extra = static_cast(backend()); /*Write Command Buffer*/ auto outputBuffer = extra->getTensorBuffer(outputs[0]); auto inputBuffer = extra->getTensorBuffer(inputs[0]); mConvSet->writeBuffer(outputBuffer.first->buffer(), 0, extra->getTensorSize(outputs[0]), outputBuffer.second); mConvSet->writeBuffer(inputBuffer.first->buffer(), 1, extra->getTensorSize(inputs[0]), inputBuffer.second); mConvSet->writeBuffer(mResource->mKernel->buffer(), 2, mResource->mKernel->size()); mConvSet->writeBuffer(mResource->mBias->buffer(), 3, mResource->mBias->size()); mConvSet->writeBuffer(mResource->mWeightScale->buffer(), 4, mResource->mWeightScale->size()); mConvSet->writeBuffer(constConvBuffer->buffer(), 5, constConvBuffer->size()); int totalSize = ocDiv4 * outputs[0]->width() * outputs[0]->height() * outputs[0]->batch(); mResource->mPipeline->bind(cmdBuffer->get(), mConvSet->get()); vkCmdDispatch(cmdBuffer->get(), UP_DIV(totalSize, 64), 1, 1); return NO_ERROR; } }; class VulkanConvolutionCreator : public VulkanBackend::Creator { public: virtual VulkanBasicExecution* onCreate(const std::vector& inputs, const std::vector& outputs, const MNN::Op* op, Backend* backend) const override { auto extra = static_cast(backend); auto convReal = op->main_as_Convolution2D(); auto common = convReal->common(); auto outputCount = common->outputCount(); const int fh = common->kernelY(); const int fw = common->kernelX(); int srcCount = 0; const float* source = nullptr; const float* biasPtr = nullptr; int weightSize = 0; std::shared_ptr quanWeight; bool useInt8Conv = false; if (nullptr != op->main_as_Convolution2D()->quanParameter()) { auto quan = op->main_as_Convolution2D()->quanParameter(); if (1 == quan->type() || 2 == quan->type()) { if (quan->has_scaleInt()) { // Don't support IDST-int8 because of error return nullptr; } } const bool hasExternalQuantWeight = (convReal->external() != nullptr && convReal->external()->size() > 1); // For coop/int8 path, external quant weights should also keep int8 payload instead of being forced to float. if ((quan->buffer() || hasExternalQuantWeight) && OpType_Convolution == op->type()) { quanWeight = ConvolutionCommon::load(op, backend, false, true); } else { quanWeight = ConvolutionCommon::load(op, backend, true); } if (quanWeight->weight.get() != nullptr) { useInt8Conv = true; srcCount = inputs[0]->channel(); } else { srcCount = quanWeight->weightFloat.size() / (outputCount * fh * fw); source = quanWeight->weightFloat.get(); weightSize = quanWeight->weightFloat.size(); } } else { if (nullptr != convReal->weight()) { srcCount = convReal->weight()->size() / (outputCount * fh * fw); source = convReal->weight()->data(); weightSize = convReal->weight()->size(); } else { srcCount = convReal->common()->inputCount(); } } if (nullptr != convReal->bias()) { biasPtr = convReal->bias()->data(); } if (op->type() == OpType_Convolution) { auto convCommonParam = op->main_as_Convolution2D()->common(); const int group = convCommonParam->group(); if (1 == group) { auto coopMatInfo = extra->getDevice().getCoopMatInfo(); const auto& subgroup = extra->getDevice().getSubgroupInfo(); const VkSubgroupFeatureFlags requiredSubgroupOps = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT; const bool supportSubgroupArithmetic = subgroup.size > 0 && (subgroup.stages & VK_SHADER_STAGE_COMPUTE_BIT) && ((subgroup.ops & requiredSubgroupOps) == requiredSubgroupOps); bool is1x1 = common->kernelX() == 1 && common->kernelY() == 1 && common->strideX() == 1 && common->strideY() == 1 && inputs[0]->width() == outputs[0]->width() && inputs[0]->height() == outputs[0]->height(); bool singleInput = (inputs.size() == 1); if (useInt8Conv && is1x1 && singleInput) { // CoopMat path only supports int4/int8 weight. For 2/3-bit, go to // VulkanConv1x1General which has the native int2/int3 packed path. const bool isLowBit23 = (quanWeight != nullptr) && (quanWeight->canUseInt2 || quanWeight->canUseInt3); if (!isLowBit23 && coopMatInfo.supportCoopMat && supportSubgroupArithmetic && extra->gpuType() == VulkanRuntime::ADRENO) { // W8A8 path: per-channel asym int8 OR int4 (decode + prefill share // body; INT4 inserts a runtime nibble unpack stage) + S8S8->S32 // cooperative matrix on Adreno. alpha layout for asym is (offset, // scale) per channel-block; per-channel == alpha.size() == // outputCount * 2 (block-quant has size outputCount * blockCount * 2, // which excludes it from this branch). const bool perChannelAsym = (quanWeight != nullptr) && quanWeight->asymmetric && (int)quanWeight->alpha.size() == outputCount * 2 && extra->getDevice().getInt8Support(); if (perChannelAsym && coopMatInfo.supportS8S8S32) { return new VulkanConv1x1CoopA8(extra, convCommonParam, biasPtr, srcCount, outputCount, coopMatInfo, quanWeight); } return new VulkanConv1x1Coop(extra, convCommonParam, nullptr, biasPtr, srcCount, outputCount, coopMatInfo, quanWeight); } return new VulkanConv1x1General(extra, convCommonParam, biasPtr, srcCount, outputCount, quanWeight); } if (coopMatInfo.supportCoopMat && supportSubgroupArithmetic && is1x1 && singleInput && extra->gpuType() == VulkanRuntime::ADRENO) { return new VulkanConv1x1Coop(extra, convCommonParam, source, biasPtr, srcCount, outputCount, coopMatInfo); } if (useInt8Conv) { bool useFP16 = extra->useFP16(); auto res = VulkanConvolutionSlideWindowsInt8::makeResource(quanWeight, biasPtr, convCommonParam, extra, srcCount, outputCount, useFP16); return new VulkanConvolutionSlideWindowsInt8(extra, common, res); } return VulkanConvolutionImpl::create(extra, common, inputs, outputs[0], source, biasPtr, srcCount, outputCount); } else { return nullptr; } } return new VulkanConvolutionDepthwise(source, weightSize, op, backend); } }; static bool gResistor = []() { VulkanBackend::addCreator(OpType_Convolution, new VulkanConvolutionCreator); VulkanBackend::addCreator(OpType_ConvolutionDepthwise, new VulkanConvolutionCreator); return true; }(); } // namespace MNN