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

181 lines
7.3 KiB
C++

//
// RoPETest.cpp
// MNNTests
//
// Copyright © 2018, Alibaba Group Holding Limited
//
#ifdef MNN_SUPPORT_TRANSFORMER_FUSE
#include <MNN/expr/Expr.hpp>
#include <MNN/expr/ExprCreator.hpp>
#include <algorithm>
#include <cmath>
#include <vector>
#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<float>* qNormGamma = nullptr, const std::vector<float>* kNormGamma = nullptr,
float normEps = 0.0f) {
std::unique_ptr<OpT> 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<float> packC4(const std::vector<float>& input, int seqLen, int channel) {
std::vector<float> 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<float>& input, std::vector<float>& output,
const std::vector<float>& cos, const std::vector<float>& 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<float>& input, std::vector<float>& output,
const std::vector<float>& 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<float> qData(qHead * headDim * seqLen);
std::vector<float> kData(kHead * headDim * seqLen);
std::vector<float> cos(outer * headDim);
std::vector<float> sin(outer * headDim);
std::vector<float> qGamma(headDim);
std::vector<float> 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<float>(), qC4.data(), qC4.size() * sizeof(float));
::memcpy(k->writeMap<float>(), kC4.data(), kC4.size() * sizeof(float));
::memcpy(c->writeMap<float>(), cos.data(), cos.size() * sizeof(float));
::memcpy(s->writeMap<float>(), 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<float> qExpected, kExpected;
if (useNorm) {
std::vector<float> 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<float>(qOut->readMap<float>(), qExpected.data(), qExpected.size(), 0.03f) ||
!checkVector<float>(kOut->readMap<float>(), 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