// // GroupNormExecution.cpp // MNN // // Created by MNN on 2023/09/14. // Copyright © 2018, Alibaba Group Holding Limited // #ifdef MNN_SUPPORT_TRANSFORMER_FUSE #include "GroupNormExecution.hpp" #include "core/TensorUtils.hpp" namespace MNN { namespace CUDA { static int32_t findMaxDivisor(int32_t n, int32_t maxAllowedDivisor) { int32_t maxDivisor = -1; for (int32_t i = 1; i <= std::sqrt(n); i++) { if (n % i == 0) { int32_t divisor1 = n / i; int32_t divisor2 = i; if (divisor1 > maxDivisor && divisor1 < maxAllowedDivisor) { maxDivisor = divisor1; } if (divisor2 > maxDivisor && divisor2 < maxAllowedDivisor) { maxDivisor = divisor2; } } } return maxDivisor; } GroupNormExecution::GroupNormExecution(const MNN::Op* op, Backend* backend) : Execution(backend) { auto group_norm_param = op->main_as_GroupNorm(); mEpsilon = group_norm_param->epsilon(); mBSwish = group_norm_param->bSwish(); mGroup = group_norm_param->group(); if (group_norm_param->gamma() && group_norm_param->beta()) { int size = group_norm_param->gamma()->size(); mGammaTensor.reset(Tensor::createDevice({size})); auto status = backend->onAcquireBuffer(mGammaTensor.get(), Backend::STATIC); if (!status) { MNN_ERROR("Out of memory when gamma is acquired in CudaLayerNorm.\n"); } mDeviceGamma = (void *)mGammaTensor.get()->buffer().device; const float* gamma_data = group_norm_param->gamma()->data(); cudaMemcpy(mDeviceGamma, gamma_data, size * sizeof(float), cudaMemcpyHostToDevice); if (group_norm_param->beta()->size() != size) { MNN_ERROR("Size of gamma and beta are not match in CudaLayerNorm.\n"); } mBetaTensor.reset(Tensor::createDevice({size})); status = backend->onAcquireBuffer(mBetaTensor.get(), Backend::STATIC); if (!status) { MNN_ERROR("Out of memory when beta is acquired in CudaLayerNorm.\n"); } mDeviceBeta = (void *)mBetaTensor.get()->buffer().device; const float* beta_data = group_norm_param->beta()->data(); cudaMemcpy(mDeviceBeta, beta_data, size * sizeof(float), cudaMemcpyHostToDevice); } } size_t GroupNormExecution::getWorkspaceSizeInBytes() const { return (sizeof(float) * 2) * mBatch * mGroup; // sizeof(float2) * maxBatchSize * maxNumberOfGroup. float2 // contians two buffers for sum and squared sum } ErrorCode GroupNormExecution::onResize(const std::vector& inputs, const std::vector& outputs) { auto runtime = static_cast(backend())->getCUDARuntime(); auto pool = static_cast(backend())->getStaticBufferPool(); MNN_ASSERT(outputs.size() == 1); auto input = inputs[0]; auto output = outputs[0]; MNN_ASSERT(input->dimensions() == 4); MNN_ASSERT(output->dimensions() == 4); mBatch = input->length(0); if(inputs.size() > 1) { MNN_ASSERT(inputs[1]->dimensions() == 2); MNN_ASSERT(inputs[1]->length(0) == inputs[0]->length(0)); MNN_ASSERT(inputs[1]->length(1) == inputs[0]->length(1)); } auto size = getWorkspaceSizeInBytes(); auto buffer_ws = pool->alloc(size); mWorkSpacePtr = (void*)((uint8_t*)buffer_ws.first + buffer_ws.second); runtime->memset(mWorkSpacePtr, 0, size); return NO_ERROR; } ErrorCode GroupNormExecution::onExecute(const std::vector& inputs, const std::vector& outputs) { #ifdef LOG_VERBOSE MNN_PRINT("start GroupNormExecution onExecute..."); #endif auto runtime = static_cast(backend())->getCUDARuntime(); auto input = inputs[0]; auto output = outputs[0]; runtime->memset(mWorkSpacePtr, 0, getWorkspaceSizeInBytes()); int32_t cPerBlock = 320; int32_t maxBlocksPerHW = 1024; switch (input->length(1)) { case 960: case 1920: cPerBlock = 480; break; case 512: case 256: cPerBlock = 256; break; case 128: cPerBlock = 128; break; default: cPerBlock = 320; } mParams.withSwish = bool(mBSwish); mParams.dst = static_cast((void *)output->deviceId()); if(inputs.size() > 1) { mParams.src = nullptr; mParams.src_0 = static_cast((void *)inputs[0]->deviceId()); mParams.src_1 = static_cast((void *)inputs[1]->deviceId()); } else { mParams.src = static_cast((void *)input->deviceId()); } mParams.gamma = static_cast(mDeviceGamma); mParams.beta = static_cast(mDeviceBeta); mParams.redBuffer = static_cast(mWorkSpacePtr); mParams.n = input->length(0); mParams.h = input->length(2); mParams.w = input->length(3); mParams.c = input->length(1); // Kernel format is NHWC, OP format NC4HW4(NHWC8) MNN_ASSERT(mParams.c % 8 == 0); mParams.groups = mGroup; mParams.hw = mParams.h * mParams.w; const int32_t blocksPerHW = findMaxDivisor(mParams.hw, maxBlocksPerHW); mParams.hwPerBlock = UP_DIV(mParams.hw, blocksPerHW); mParams.cPerBlock = cPerBlock; mParams.cPerGroup = mParams.c / mParams.groups; mParams.hwc = mParams.hw * mParams.c; mParams.invHWC = 1.F / (float) (mParams.hw * mParams.cPerGroup); mParams.groupsPerBlock = cPerBlock / mParams.cPerGroup; groupNormNHWCSum(mParams); checkKernelErrors; groupNormNHWCScale(mParams); checkKernelErrors; #ifdef LOG_VERBOSE MNN_PRINT("end GroupNormExecution onExecute..."); #endif return NO_ERROR; } class GroupNormCreator : public CUDABackend::Creator { public: virtual Execution* onCreate(const std::vector& inputs, const std::vector& outputs, const MNN::Op* op, Backend* backend) const override { if(!static_cast(backend)->useFp16()) { MNN_PRINT("CUDA GroupNorm only support fp16 now!\n"); return nullptr; } return new GroupNormExecution(op, backend); } }; CUDACreatorRegister __GroupNormExecution(OpType_GroupNorm); } // namespace CUDA } // namespace MNN #endif