// // RoPETest.cpp // MNNTests // // Copyright © 2018, Alibaba Group Holding Limited // #ifdef MNN_SUPPORT_TRANSFORMER_FUSE #include #include #include #include #include #include "MNNTestSuite.h" #include "TestUtils.h" using namespace MNN; using namespace MNN::Express; static EXPRP _RoPEExpr(VARP q, VARP k, VARP cos, VARP sin, int ropeCutHeadDim, int qHead, int kHead, int headDim, const std::vector* qNormGamma = nullptr, const std::vector* kNormGamma = nullptr, float normEps = 0.0f) { std::unique_ptr op(new OpT); op->type = OpType_RoPE; op->main.type = OpParameter_RoPEParam; op->main.value = new RoPEParamT; op->main.AsRoPEParam()->rope_cut_head_dim = ropeCutHeadDim; op->main.AsRoPEParam()->num_head = qHead; op->main.AsRoPEParam()->kv_num_head = kHead; op->main.AsRoPEParam()->head_dim = headDim; if (nullptr != qNormGamma) { op->main.AsRoPEParam()->q_norm.reset(new LayerNormT); op->main.AsRoPEParam()->q_norm->epsilon = normEps; op->main.AsRoPEParam()->q_norm->gamma = *qNormGamma; op->main.AsRoPEParam()->q_norm->useRMSNorm = true; } if (nullptr != kNormGamma) { op->main.AsRoPEParam()->k_norm.reset(new LayerNormT); op->main.AsRoPEParam()->k_norm->epsilon = normEps; op->main.AsRoPEParam()->k_norm->gamma = *kNormGamma; op->main.AsRoPEParam()->k_norm->useRMSNorm = true; } return Expr::create(std::move(op), {q, k, cos, sin}, 2); } static std::vector packC4(const std::vector& input, int seqLen, int channel) { std::vector output(((channel + 3) / 4) * seqLen * 4, 0.0f); for (int t = 0; t < seqLen; ++t) { for (int c = 0; c < channel; ++c) { output[(c / 4) * seqLen * 4 + t * 4 + (c % 4)] = input[t * channel + c]; } } return output; } static void computeRopeExpected(const std::vector& input, std::vector& output, const std::vector& cos, const std::vector& sin, int outer, int head, int headDim, int ropeCutHeadDim) { output = input; int halfDim = headDim / 2; int ropeHalfDim = std::min(ropeCutHeadDim / 2, halfDim); for (int o = 0; o < outer; ++o) { for (int h = 0; h < head; ++h) { for (int i = 0; i < ropeHalfDim; ++i) { int base = (o * head + h) * headDim; int trig = o * headDim + i; float evenVal = input[base + i]; float oddVal = input[base + i + halfDim]; output[base + i] = evenVal * cos[trig] - oddVal * sin[trig]; output[base + i + halfDim] = oddVal * cos[trig + halfDim] + evenVal * sin[trig + halfDim]; } } } } static void computeRmsNormExpected(const std::vector& input, std::vector& output, const std::vector& gamma, int outer, int head, int headDim, float eps) { output.resize(input.size()); for (int o = 0; o < outer; ++o) { for (int h = 0; h < head; ++h) { int base = (o * head + h) * headDim; float sum = 0.0f; for (int i = 0; i < headDim; ++i) { float val = input[base + i]; sum += val * val; } float scale = 1.0f / std::sqrt(sum / headDim + eps); for (int i = 0; i < headDim; ++i) { output[base + i] = input[base + i] * scale * gamma[i]; } } } } class RoPETest : public MNNTestCase { public: virtual ~RoPETest() = default; bool runCase(bool useNorm) { const int batch = 1; const int seqLen = 2; const int qHead = 2; const int kHead = 1; const int headDim = 8; const int halfDim = headDim / 2; const int outer = batch * seqLen; const int ropeCutHeadDim = 6; const float normEps = 1e-6f; std::vector qData(qHead * headDim * seqLen); std::vector kData(kHead * headDim * seqLen); std::vector cos(outer * headDim); std::vector sin(outer * headDim); std::vector qGamma(headDim); std::vector kGamma(headDim); for (int i = 0; i < (int)qData.size(); ++i) { qData[i] = (float)((i % 13) - 6) * 0.17f; } for (int i = 0; i < (int)kData.size(); ++i) { kData[i] = (float)((i % 11) - 5) * -0.13f; } for (int i = 0; i < outer * halfDim; ++i) { int token = i / halfDim; int offset = i % halfDim; cos[token * headDim + offset] = 0.9f - 0.03f * i; cos[token * headDim + offset + halfDim] = 0.91f - 0.02f * i; sin[token * headDim + offset] = 0.1f + 0.04f * i; sin[token * headDim + offset + halfDim] = 0.11f + 0.03f * i; } for (int i = 0; i < headDim; ++i) { qGamma[i] = 0.7f + 0.03f * i; kGamma[i] = 1.2f - 0.04f * i; } auto qC4 = packC4(qData, seqLen, qHead * headDim); auto kC4 = packC4(kData, seqLen, kHead * headDim); auto q = _Input({seqLen, qHead * headDim, 1, 1}, NC4HW4); auto k = _Input({seqLen, kHead * headDim, 1, 1}, NC4HW4); auto c = _Input({batch, seqLen, headDim}, NCHW); auto s = _Input({batch, seqLen, headDim}, NCHW); ::memcpy(q->writeMap(), qC4.data(), qC4.size() * sizeof(float)); ::memcpy(k->writeMap(), kC4.data(), kC4.size() * sizeof(float)); ::memcpy(c->writeMap(), cos.data(), cos.size() * sizeof(float)); ::memcpy(s->writeMap(), sin.data(), sin.size() * sizeof(float)); q->unMap(); k->unMap(); c->unMap(); s->unMap(); auto expr = useNorm ? _RoPEExpr(q, k, c, s, ropeCutHeadDim, qHead, kHead, headDim, &qGamma, &kGamma, normEps) : _RoPEExpr(q, k, c, s, ropeCutHeadDim, qHead, kHead, headDim); auto qOut = Variable::create(expr, 0); auto kOut = Variable::create(expr, 1); std::vector qExpected, kExpected; if (useNorm) { std::vector qNorm, kNorm; computeRmsNormExpected(qData, qNorm, qGamma, outer, qHead, headDim, normEps); computeRmsNormExpected(kData, kNorm, kGamma, outer, kHead, headDim, normEps); computeRopeExpected(qNorm, qExpected, cos, sin, outer, qHead, headDim, ropeCutHeadDim); computeRopeExpected(kNorm, kExpected, cos, sin, outer, kHead, headDim, ropeCutHeadDim); } else { computeRopeExpected(qData, qExpected, cos, sin, outer, qHead, headDim, ropeCutHeadDim); computeRopeExpected(kData, kExpected, cos, sin, outer, kHead, headDim, ropeCutHeadDim); } if (!checkVector(qOut->readMap(), qExpected.data(), qExpected.size(), 0.03f) || !checkVector(kOut->readMap(), kExpected.data(), kExpected.size(), 0.03f)) { MNN_ERROR("RoPETest %s failed!\n", useNorm ? "norm" : "base"); return false; } return true; } virtual bool run(int precision) { return runCase(false) && runCase(true); } }; MNNTestSuiteRegister(RoPETest, "op/rope"); #endif