// // CPURoPE.cpp // MNN // // Created by MNN on 2018/08/07. // Copyright © 2018, Alibaba Group Holding Limited // #include "CPURoPE.hpp" #include "CPUBackend.hpp" #include "MNN_generated.h" #include "backend/cpu/compute/CommonOptFunction.h" #include "core/Concurrency.h" #include "core/Macro.h" #include "core/TensorUtils.hpp" #include namespace MNN { static std::shared_ptr makeRopeNormResource(const LayerNorm* layerNorm, Backend* backend) { if (nullptr == layerNorm || nullptr == layerNorm->gamma()) { return nullptr; } int gammaSize = layerNorm->gamma()->size(); if (gammaSize <= 0) { return nullptr; } auto res = std::make_shared(); res->mGroup = layerNorm->group(); res->mEpsilon = layerNorm->epsilon(); res->mRMSNorm = layerNorm->useRMSNorm(); res->mAxis = layerNorm->axis() == nullptr ? 1 : layerNorm->axis()->size(); res->mIniGammaBeta = true; res->mGamma.reset(Tensor::createDevice({gammaSize * (int)sizeof(float)})); res->mBeta.reset(Tensor::createDevice({gammaSize * (int)sizeof(float)})); auto status = backend->onAcquireBuffer(res->mGamma.get(), Backend::STATIC) && backend->onAcquireBuffer(res->mBeta.get(), Backend::STATIC); if (!status) { MNN_ERROR("CPURoPE: alloc q/k norm gamma buffer error.\n"); return nullptr; } ::memcpy(res->mGamma->host(), layerNorm->gamma()->data(), gammaSize * sizeof(float)); if (layerNorm->beta() != nullptr && layerNorm->beta()->size() == gammaSize) { ::memcpy(res->mBeta->host(), layerNorm->beta()->data(), gammaSize * sizeof(float)); } else { ::memset(res->mBeta->host(), 0, gammaSize * sizeof(float)); } return res; } static void unpackC4Token(const uint8_t* src, uint8_t* dst, int token, int seqLen, int channel, int bytes, int pack, int channelOffset = 0) { for (int c = 0; c < channel; ++c) { int srcChannel = channelOffset + c; int c4 = srcChannel / pack; int ci = srcChannel % pack; ::memcpy(dst + c * bytes, src + (c4 * seqLen * pack + token * pack + ci) * bytes, bytes); } } static bool validRopeC4Input(const Tensor* q, const Tensor* k, int numHead, int kvNumHead, int headDim) { if (q == nullptr || k == nullptr || numHead <= 0 || kvNumHead <= 0 || headDim <= 0) { return false; } if (TensorUtils::getDescribe(q)->dimensionFormat != MNN_DATA_FORMAT_NC4HW4 || TensorUtils::getDescribe(k)->dimensionFormat != MNN_DATA_FORMAT_NC4HW4) { return false; } if (q->dimensions() < 2 || k->dimensions() < 2) { return false; } return q->length(1) == numHead * headDim && k->length(1) == kvNumHead * headDim; } CPURoPE::CPURoPE(const Op* op, Backend* bn) : MNN::Execution(bn) { auto param = op == nullptr ? nullptr : op->main_as_RoPEParam(); if (param != nullptr) { mRopeCutHeadDim = param->rope_cut_head_dim(); mNumHead = param->num_head(); mKvNumHead = param->kv_num_head(); mHeadDim = param->head_dim(); mQNorm = makeRopeNormResource(param->q_norm(), bn); mKNorm = makeRopeNormResource(param->k_norm(), bn); } } CPURoPE::~CPURoPE() { // Do nothing. } CPURoPE::CPURoPE(Backend* bn) : Execution(bn) { // Do nothing. } ErrorCode CPURoPE::onResize(const std::vector& inputs, const std::vector& outputs) { auto Q = inputs[0]; auto K = inputs[1]; if (!validRopeC4Input(Q, K, mNumHead, mKvNumHead, mHeadDim)) { MNN_ERROR("CPURoPE: invalid C4 head config, numHead=%d, kvNumHead=%d, headDim=%d.\n", mNumHead, mKvNumHead, mHeadDim); return NOT_SUPPORT; } auto bn = static_cast(backend()); auto threadNumber = bn->threadNumber(); auto buf = bn->getBufferAllocator(); auto bytes = bn->functions()->bytes; mTmpQC4 = buf->alloc(threadNumber * mNumHead * mHeadDim * bytes); buf->free(mTmpQC4); mTmpKC4 = buf->alloc(threadNumber * mKvNumHead * mHeadDim * bytes); buf->free(mTmpKC4); if (bytes != 4) { if (mQNorm) { mTmpQFloat = buf->alloc(threadNumber * mNumHead * mHeadDim * sizeof(float)); buf->free(mTmpQFloat); } if (mKNorm) { mTmpKFloat = buf->alloc(threadNumber * mKvNumHead * mHeadDim * sizeof(float)); buf->free(mTmpKFloat); } } return NO_ERROR; } bool CPURoPE::onClone(Backend* bn, const Op* op, Execution** dst) { if (nullptr == dst) { return true; } auto rope = new CPURoPE(bn); rope->mRopeCutHeadDim = mRopeCutHeadDim; rope->mNumHead = mNumHead; rope->mKvNumHead = mKvNumHead; rope->mHeadDim = mHeadDim; rope->mQNorm = mQNorm; rope->mKNorm = mKNorm; *dst = rope; return true; } ErrorCode CPURoPE::onExecute(const std::vector& inputs, const std::vector& outputs) { auto Q = inputs[0]; auto K = inputs[1]; if (!validRopeC4Input(Q, K, mNumHead, mKvNumHead, mHeadDim)) { MNN_ERROR("CPURoPE: invalid C4 input, numHead=%d, kvNumHead=%d, headDim=%d.\n", mNumHead, mKvNumHead, mHeadDim); return NOT_SUPPORT; } auto cos = inputs[2]; auto sin = inputs[3]; auto QOutput = outputs[0]; auto KOutput = outputs[1]; int batch = 1; int seqLen = Q->length(0); int numHead = mNumHead; int headDim = mHeadDim; int kvnumHead = mKvNumHead; auto halfHeadDim = headDim / 2; int threadNum = static_cast(backend())->threadNumber(); int totalWork = batch * seqLen; auto core = static_cast(backend())->functions(); MNN_ASSERT(core->MNNRoPECompute != nullptr); MNN_CONCURRENCY_BEGIN(tId, threadNum) { int start = tId * totalWork / threadNum; int end = (tId + 1) * totalWork / threadNum; for (int i = start; i < end; ++i) { auto cosPtr = static_cast(cos->host()) + i * headDim * core->bytes; auto sinPtr = static_cast(sin->host()) + i * headDim * core->bytes; auto cosEvenPtr = cosPtr; auto cosOddPtr = cosPtr + halfHeadDim * core->bytes; auto sinEvenPtr = sinPtr; auto sinOddPtr = sinPtr + halfHeadDim * core->bytes; auto qPtr = static_cast(Q->host()); auto qPtrOut = static_cast(QOutput->host()) + i * numHead * headDim * core->bytes; auto qTmp = static_cast(mTmpQC4.ptr()) + tId * numHead * headDim * core->bytes; unpackC4Token(qPtr, qTmp, i, seqLen, numHead * headDim, core->bytes, core->pack); qPtr = qTmp; if (mQNorm) { int size = headDim; const float* gamma = mQNorm->mIniGammaBeta ? mQNorm->mGamma->host() : nullptr; const float* beta = mQNorm->mIniGammaBeta ? mQNorm->mBeta->host() : nullptr; if (core->bytes == 4) { for (int h = 0; h < numHead; ++h) { MNNNorm(reinterpret_cast(qPtrOut) + h * headDim, reinterpret_cast(qPtr) + h * headDim, gamma, beta, mQNorm->mEpsilon, size, mQNorm->mRMSNorm); } qPtr = qPtrOut; } else { int totalSize = numHead * headDim; auto tmpQ = reinterpret_cast(mTmpQFloat.ptr() + tId * totalSize * sizeof(float)); core->MNNLowpToFp32(reinterpret_cast(qPtr), tmpQ, totalSize); for (int h = 0; h < numHead; ++h) { MNNNorm(tmpQ + h * headDim, tmpQ + h * headDim, gamma, beta, mQNorm->mEpsilon, size, mQNorm->mRMSNorm); } core->MNNFp32ToLowp(tmpQ, reinterpret_cast(qPtrOut), totalSize); qPtr = qPtrOut; } } core->MNNRoPECompute(qPtrOut, qPtr, cosEvenPtr, cosOddPtr, sinEvenPtr, sinOddPtr, numHead, headDim, mRopeCutHeadDim); qPtr = static_cast(K->host()); qPtrOut = static_cast(KOutput->host()) + i * kvnumHead * headDim * core->bytes; auto kTmp = static_cast(mTmpKC4.ptr()) + tId * kvnumHead * headDim * core->bytes; unpackC4Token(qPtr, kTmp, i, seqLen, kvnumHead * headDim, core->bytes, core->pack); qPtr = kTmp; if (mKNorm) { int size = headDim; const float* gamma = mKNorm->mIniGammaBeta ? mKNorm->mGamma->host() : nullptr; const float* beta = mKNorm->mIniGammaBeta ? mKNorm->mBeta->host() : nullptr; if (core->bytes == 4) { for (int h = 0; h < kvnumHead; ++h) { MNNNorm(reinterpret_cast(qPtrOut) + h * headDim, reinterpret_cast(qPtr) + h * headDim, gamma, beta, mKNorm->mEpsilon, size, mKNorm->mRMSNorm); } qPtr = qPtrOut; } else { int totalSize = kvnumHead * headDim; auto tmpK = reinterpret_cast(mTmpKFloat.ptr() + tId * totalSize * sizeof(float)); core->MNNLowpToFp32(reinterpret_cast(qPtr), tmpK, totalSize); for (int h = 0; h < kvnumHead; ++h) { MNNNorm(tmpK + h * headDim, tmpK + h * headDim, gamma, beta, mKNorm->mEpsilon, size, mKNorm->mRMSNorm); } core->MNNFp32ToLowp(tmpK, reinterpret_cast(qPtrOut), totalSize); qPtr = qPtrOut; } } core->MNNRoPECompute(qPtrOut, qPtr, cosEvenPtr, cosOddPtr, sinEvenPtr, sinOddPtr, kvnumHead, headDim, mRopeCutHeadDim); } } MNN_CONCURRENCY_END(); return NO_ERROR; } class CPURoPECreator : public CPUBackend::Creator { public: virtual Execution* onCreate(const std::vector& inputs, const std::vector& outputs, const MNN::Op* op, Backend* backend) const override { return new CPURoPE(op, backend); } }; REGISTER_CPU_OP_CREATOR(CPURoPECreator, OpType_RoPE); } // namespace MNN