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

181 lines
6.3 KiB
C++

//
// 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<int32_t>({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<int32_t>({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<Tensor*>& inputs, const std::vector<Tensor*>& outputs) {
auto runtime = static_cast<CUDABackend*>(backend())->getCUDARuntime();
auto pool = static_cast<CUDABackend*>(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<Tensor*>& inputs, const std::vector<Tensor*>& outputs) {
#ifdef LOG_VERBOSE
MNN_PRINT("start GroupNormExecution onExecute...");
#endif
auto runtime = static_cast<CUDABackend*>(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<half*>((void *)output->deviceId());
if(inputs.size() > 1) {
mParams.src = nullptr;
mParams.src_0 = static_cast<half const*>((void *)inputs[0]->deviceId());
mParams.src_1 = static_cast<half const*>((void *)inputs[1]->deviceId());
} else {
mParams.src = static_cast<half const*>((void *)input->deviceId());
}
mParams.gamma = static_cast<float const*>(mDeviceGamma);
mParams.beta = static_cast<float const*>(mDeviceBeta);
mParams.redBuffer = static_cast<float*>(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<Tensor*>& inputs, const std::vector<Tensor*>& outputs,
const MNN::Op* op, Backend* backend) const override {
if(!static_cast<CUDABackend*>(backend)->useFp16()) {
MNN_PRINT("CUDA GroupNorm only support fp16 now!\n");
return nullptr;
}
return new GroupNormExecution(op, backend);
}
};
CUDACreatorRegister<GroupNormCreator> __GroupNormExecution(OpType_GroupNorm);
} // namespace CUDA
} // namespace MNN
#endif