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

670 lines
26 KiB
C++

//
// AttentionTest.cpp
// MNNTests
//
// Created by MNN on 2024/07/23.
// Copyright © 2018, Alibaba Group Holding Limited
//
#ifdef MNN_SUPPORT_TRANSFORMER_FUSE
#include <MNN/expr/Expr.hpp>
#include <MNN/expr/ExprCreator.hpp>
#include <MNN/expr/Module.hpp>
#include "core/OpCommonUtils.hpp"
#include "MNNTestSuite.h"
#include "TestUtils.h"
#include <stdlib.h>
#include <vector>
#include <MNN/AutoTime.hpp>
using namespace MNN::Express;
using MNN::KVMeta;
int NumHead = 16;
int KvNumHead = 2;
int HeadDim = 128;
const float diff_threshold = 0.001;
const float diff_percent_threshold = 0.1;
const int pastLength = 101;
#define GENERATE_TOKENS 128
static KVMeta gMeta;
static std::shared_ptr<Module> _makeAttentionModule(int attentionMode = 8, bool outputC4 = false) {
auto Q = _Input();
auto K = _Input();
auto V = _Input();
auto mask = _Input();
std::shared_ptr<MNN::OpT> attention(new MNN::OpT);
attention->type = MNN::OpType_Attention;
attention->main.type = MNN::OpParameter_AttentionParam;
attention->main.value = new MNN::AttentionParamT;
attention->main.AsAttentionParam()->kv_cache = true;
attention->main.AsAttentionParam()->output_c4 = outputC4;
auto o = Variable::create(Expr::create(attention.get(), {Q, K, V, mask}));
auto buffer = Variable::save({o});
MNN::ScheduleConfig config;
auto status = MNNTestSuite::get()->pStaus;
config.type = (MNNForwardType)status.forwardType;
MNN::BackendConfig bnConfig;
bnConfig.memory = (MNN::BackendConfig::MemoryMode)status.memory;
bnConfig.precision = (MNN::BackendConfig::PrecisionMode)status.precision;
bnConfig.power = (MNN::BackendConfig::PowerMode)status.power;
config.backendConfig = &bnConfig;
config.numThread = 1;
std::shared_ptr<Executor::RuntimeManager> rtmgr(Executor::RuntimeManager::createRuntimeManager(config));
rtmgr->setHintPtr(MNN::Interpreter::KVCACHE_INFO, &gMeta);
rtmgr->setHint(MNN::Interpreter::ATTENTION_OPTION, attentionMode);
std::shared_ptr<Module> m(Module::load({}, {}, (uint8_t*)buffer.data(), buffer.size(), rtmgr));
return m;
}
struct KVCache {
VARP pastK;
VARP pastV;
VARP pastMask;
int current = 0;
KVCache() {
pastK = _Input({1, KvNumHead, 1, pastLength, HeadDim}, NCHW);
pastV = _Input({1, KvNumHead, 1, pastLength, HeadDim}, NCHW);
pastMask = _Input({pastLength}, NCHW);
::memset(pastK->writeMap<float>(), 0, pastK->getInfo()->size * sizeof(float));
::memset(pastV->writeMap<float>(), 0, pastK->getInfo()->size * sizeof(float));
for (int v=0; v<pastLength; ++v) {
pastMask->writeMap<float>()[v] = std::numeric_limits<float>::lowest();
}
}
};
static VARP _computeAttentionExpr(VARP Q, VARP K, VARP V, VARP mask, KVCache cache) {
auto qinfo = Q->getInfo();
auto kinfo = K->getInfo();
auto vinfo = V->getInfo();
auto seqLength = qinfo->dim[1];
auto numHead = qinfo->dim[2];
auto headDim = qinfo->dim[3];
auto kvNumHead = kinfo->dim[2];
auto batch = qinfo->dim[0];
auto group = numHead / kvNumHead;
if (mask->getInfo()->type.code == halide_type_int) {
mask = (_Scalar<float>(1.0) - _Cast<float>(mask)) * _Scalar<float>(std::numeric_limits<float>::lowest());
}
Q = _Reshape(Q, {batch, seqLength, kvNumHead,group, headDim});
Q = _Transpose(Q, {0, 2, 3, 1, 4});
K = _Reshape(K, {batch, seqLength, kvNumHead, 1, headDim});
K = _Transpose(K, {0, 2, 3, 1, 4});
auto scale = 1.0f / sqrtf(headDim);
K = K * _Scalar<float>(scale);
K.fix(VARP::CONSTANT);
auto QK = _MatMul(Q, K, false, true); // [batch, kvNumHead, group , seq_len, seq_len]
QK = QK + mask;
auto QKPast = _MatMul(Q, cache.pastK, false, true);
QKPast = QKPast + cache.pastMask;
QK = _Concat({QKPast, QK}, -1);
QK = _Softmax(QK, -1);
V = _Reshape(V, {batch, seqLength, kvNumHead, 1, headDim});
V = _Transpose(V, {0, 2, 3, 1, 4});
V.fix(VARP::CONSTANT);
auto totalV = _Concat({cache.pastV, V}, 3);
auto QKV = _MatMul(QK, totalV, false, false);
auto info = QKV->getInfo();
auto O = _Transpose(QKV, {0, 3, 1, 2, 4});
O = _Reshape(O, {batch, seqLength, -1});
O.fix(VARP::CONSTANT);
// Update KVCache
for (int y=0; y<kvNumHead; ++y) {
::memcpy(cache.pastK->writeMap<float>() + y * pastLength * headDim + cache.current * headDim, K->readMap<float>() + y * seqLength * headDim, seqLength * headDim * sizeof(float));
::memcpy(cache.pastV->writeMap<float>() + y * pastLength * headDim + cache.current * headDim, V->readMap<float>() + y * seqLength * headDim, seqLength * headDim * sizeof(float));
}
for (int i=0; i<seqLength; ++i) {
cache.pastMask->writeMap<float>()[i+cache.current] = 0.0f;
}
cache.current += seqLength;
return O;
}
static std::vector< std::vector< std::vector<float> > > generateRandTensor(int C, int H, int W, int precision) {
std::vector< std::vector< std::vector<float> > > a;
a.resize(C);
for (int i = 0; i < C; i++) {
a[i].resize(H);
for (int j = 0; j < H; j++) {
a[i][j].resize(W);
for (int k = 0; k < W; k++) {
if (precision == 2) {
a[i][j][k] = ((i + j + k) % 10) * 0.002;
} else {
a[i][j][k] = ((i + j + k) % 10) * 0.16 - 5.6;
}
}
}
}
return a;
}
VARP vector_to_var(std::vector< std::vector< std::vector<float> > > & a) {
int C = a.size();
int H = a[0].size();
int W = a[0][0].size();
VARP var = _Input({1, C, H, W}, NCHW, halide_type_of<float>());
float * ptr = var->writeMap<float>();
for (int i = 0; i < C; i++) {
for (int j = 0; j < H; j++) {
for (int k = 0; k < W; k++) {
ptr[i * H * W + j * W + k] = a[i][j][k];
}
}
}
var->unMap();
return var;
}
VARP vector_to_c4_value(std::vector< std::vector< std::vector<float> > > & a) {
int seqLen = a.size();
int kvNumHead = a[0].size();
int headDim = a[0][0].size();
int channel = kvNumHead * headDim;
std::vector<float> packed(((channel + 3) / 4) * seqLen * 4, 0.0f);
for (int s = 0; s < seqLen; ++s) {
for (int h = 0; h < kvNumHead; ++h) {
for (int d = 0; d < headDim; ++d) {
int c = h * headDim + d;
packed[(c / 4) * seqLen * 4 + s * 4 + (c % 4)] = a[s][h][d];
}
}
}
VARP var = _Input({seqLen, channel, 1, 1}, NC4HW4, halide_type_of<float>());
::memcpy(var->writeMap<float>(), packed.data(), packed.size() * sizeof(float));
var->unMap();
return var;
}
VARP vector_to_var(std::vector< std::vector<int> > & a) {
int H = a.size();
int W = a[0].size();
VARP var = _Input({1, 1, H, W}, NCHW, halide_type_of<int>());
int * ptr = var->writeMap<int>();
for (int i = 0; i < H; i++) {
for (int j = 0; j < W; j++) {
ptr[i * W + j] = a[i][j];
}
}
var->unMap();
return var;
}
static std::vector< std::vector< std::vector<float> > >
computeAttention (
std::vector< std::vector< std::vector<float> > > & query,
std::vector< std::vector< std::vector<float> > > & key,
std::vector< std::vector< std::vector<float> > > & value,
std::vector< std::vector<int> > & mask,
int seq_len, int kv_seq_len )
{
int group_size = NumHead / KvNumHead;
std::vector< std::vector< std::vector<float> > > output(seq_len);
for (int i = 0; i < seq_len; i++) {
output[i].resize(NumHead);
for (int j = 0; j < NumHead; j++) {
output[i][j].resize(HeadDim);
}
}
for (int h = 0; h < NumHead; h++) {
int kv_h = h / group_size;
/*---- Q * K ----*/
std::vector< std::vector<float> > qk(seq_len, std::vector<float>(kv_seq_len, 0.0f));
for (int i = 0; i < seq_len; i++) {
for (int j = 0; j < kv_seq_len; j++) {
qk[i][j] = 0.0f;
for (int k = 0; k < HeadDim; k++) {
qk[i][j] += query[i][h][k] * key[j][kv_h][k];
}
}
}
/*---- Mask QK ----*/
if(mask.size() > 0) {
float scale = 1.0 / sqrt(HeadDim);
if (mask[0].size() == seq_len) {
auto diff = kv_seq_len - seq_len;
for (int i = 0; i < seq_len; i++) {
for (int j = 0; j < seq_len; j++) {
qk[i][j+diff] = qk[i][j+diff] * scale + (1.f - mask[i][j]) * std::numeric_limits<float>::lowest();
}
}
} else {
for (int i = 0; i < seq_len; i++) {
for (int j = 0; j < kv_seq_len; j++) {
qk[i][j] = qk[i][j] * scale + (1.f - mask[i][j]) * std::numeric_limits<float>::lowest();
}
}
}
} else {
float scale = 1.0 / sqrt(HeadDim);
for (int i = 0; i < seq_len; i++) {
for (int j = 0; j < kv_seq_len; j++) {
qk[i][j] *= scale;
}
}
}
/*---- Softmax QK ----*/
for (int i = 0; i < seq_len; i++) {
float maxValue = qk[i][0];
for (int j = 1; j < kv_seq_len; j++) {
maxValue = ALIMAX(maxValue, qk[i][j]);
}
for (int j = 0; j < kv_seq_len; j++) {
qk[i][j] -= maxValue;
}
float sum = 0.0f;
for (int j = 0; j < kv_seq_len; j++) {
sum += exp(qk[i][j]);
}
for (int j = 0; j < kv_seq_len; j++) {
qk[i][j] = exp(qk[i][j]) / sum;
}
}
/*---- QK * V ----*/
for (int i = 0; i < seq_len; i++) {
for (int j = 0; j < HeadDim; j++) {
output[i][h][j] = 0.0f;
for (int k = 0; k < kv_seq_len; k++) {
output[i][h][j] += qk[i][k] * value[k][kv_h][j];
}
}
}
}
return output;
}
class NaiveAttention {
private:
std::vector< std::vector< std::vector<float> > > mPastKey, mPastValue;
int mPastLen;
public:
NaiveAttention() : mPastLen(0) {}
~NaiveAttention() = default;
std::vector< std::vector< std::vector<float> > > onExecute (
std::vector< std::vector< std::vector<float> > > & query,
std::vector< std::vector< std::vector<float> > > & key,
std::vector< std::vector< std::vector<float> > > & value,
std::vector< std::vector<int> > & mask,
int seq_len )
{
for (int i = 0; i < seq_len; i++) {
mPastKey.push_back(key[i]);
mPastValue.push_back(value[i]);
}
mPastLen += seq_len;
return computeAttention(query, mPastKey, mPastValue, mask, seq_len, mPastLen);
}
};
class AttentionTest : public MNNTestCase {
protected:
std::vector< std::vector< std::vector<float> > > query;
std::vector< std::vector< std::vector<float> > > key;
std::vector< std::vector< std::vector<float> > > value;
std::vector< std::vector<int> > mask;
std::vector< std::vector< std::vector<float> > > expected_result;
VARP Query, Key, Value, Mask, Output;
VARP Query1, Key1, Value1, Mask1;
public:
AttentionTest() = default;
virtual ~AttentionTest() = default;
void generateInput(int seq_len, int precision, bool genDecodeInput = false) {
query = generateRandTensor(seq_len, NumHead, HeadDim, precision);
key = generateRandTensor(seq_len, KvNumHead, HeadDim, precision);
value = generateRandTensor(seq_len, KvNumHead, HeadDim, precision);
Query = vector_to_var(query);
Key = vector_to_var(key);
Value = vector_to_var(value);
if (genDecodeInput) {
auto vecquery = generateRandTensor(1, NumHead, HeadDim, precision);
auto veckey = generateRandTensor(1, KvNumHead, HeadDim, precision);
auto vecvalue = generateRandTensor(1, KvNumHead, HeadDim, precision);
Query1 = vector_to_var(vecquery);
Key1 = vector_to_var(veckey);
Value1 = vector_to_var(vecvalue);
}
}
void generateChunkMask(int seq_len, int kv_seq_len, int chunk_size, bool genDecodeInput = false) {
// 防止除以0
if (chunk_size <= 0) chunk_size = 1;
mask.resize(seq_len);
// 计算历史长度 (Gap),用于处理 KV 长度大于 Seq 长度的情况 (Right Alignment)
// j < gap 的部分通常被视为 History,默认可见
int gap = kv_seq_len - seq_len;
for (int i = 0; i < seq_len; i++) {
mask[i].resize(kv_seq_len);
// --- 核心逻辑对应 ---
// MNN Expr: auto N = _Divide(i, rankVar) * rankVar + rankVar;
// i 是当前行 (Query),计算当前块的右边界 (不包含)
// 比如 rank=2, i=0, block_end_rel=2; i=2, block_end_rel=4
int block_end_rel = (i / chunk_size) * chunk_size + chunk_size;
for (int j = 0; j < kv_seq_len; j++) {
// 将 j 转换为相对于当前 seq_len 的坐标
int j_rel = j - gap;
if (j_rel < 0) {
// 情况 1: j 在 Gap 区域 (历史 KV Cache)
// 通常历史数据对当前所有 Token 都是可见的
mask[i][j] = 1;
} else {
// 情况 2: j 在当前处理的序列范围内
// 对应 MNN Expr: _Less(j, N)
if (j_rel < block_end_rel) {
mask[i][j] = 1;
} else {
mask[i][j] = 0;
}
}
}
}
// 转为 VARP 并处理成 -inf / 0.0 格式
Mask = vector_to_var(mask);
Mask = (_Scalar<float>(1.0) - _Cast<float>(Mask)) * _Scalar<float>(std::numeric_limits<float>::lowest());
// Decode Input 部分通常保持全 1 (即看清所有历史),或者根据需求修改
if (genDecodeInput) {
std::vector<std::vector<int>> vecmask;
vecmask.resize(1);
vecmask[0].resize(gMeta.previous + 1);
for (int i = 0; i < gMeta.previous + 1; ++i) {
vecmask[0][i] = 1;
}
Mask1 = vector_to_var(vecmask);
Mask1 = (_Scalar<float>(1.0) - _Cast<float>(Mask1)) * _Scalar<float>(std::numeric_limits<float>::lowest());
}
}
void generateMask(int seq_len, int kv_seq_len, bool genDecodeInput = false) {
mask.resize(seq_len);
for (int i = 0; i < seq_len; i++) {
mask[i].resize(kv_seq_len);
for (int j = 0; j < kv_seq_len; j++) {
if (j - i <= kv_seq_len - seq_len) {
mask[i][j] = 1;
} else {
mask[i][j] = 0;
}
}
}
Mask = _Input({}, NCHW, halide_type_of<float>());
Mask1 = _Input({}, NCHW, halide_type_of<float>());
Mask->writeMap<float>()[0] = 0.0f;
Mask1->writeMap<float>()[0] = 0.0f;
}
bool compareResult(int seq_len) {
const float * resultPtr = Output->readMap<float>();
for (int i = 0; i < seq_len; i++) {
for (int j = 0; j < NumHead; j++) {
for (int k = 0; k < HeadDim; k++) {
float diff = fabs(resultPtr[i * NumHead * HeadDim + j * HeadDim + k] - expected_result[i][j][k]);
float diff_percent = fabs(diff / expected_result[i][j][k]);
if (diff > diff_threshold && diff_percent > diff_percent_threshold) {
printf("Result Mismatch: expected %lf but got %lf in CPU Attention Test\n", expected_result[i][j][k], resultPtr[i * NumHead * HeadDim + j * HeadDim + k]);
printf("Error Position: Output[%d][%d][%d]\n", i, j, k);
return false;
}
}
}
}
Output->unMap();
return true;
}
virtual bool run(int precision) {
srand(2024);
// unit test 1
{
std::shared_ptr<NaiveAttention> naiveAttention(new NaiveAttention);
std::shared_ptr<MNN::OpT> attention(new MNN::OpT);
attention->type = MNN::OpType_Attention;
attention->main.type = MNN::OpParameter_AttentionParam;
attention->main.value = new MNN::AttentionParamT;
attention->main.AsAttentionParam()->kv_cache = true;
int seq_len = 10;
generateInput(seq_len, precision);
generateMask(seq_len, seq_len);
expected_result = naiveAttention->onExecute(query, key, value, mask, seq_len);
auto attn = _makeAttentionModule();
gMeta.add = seq_len;
Output = attn->onForward({Query, Key, Value, Mask})[0];
gMeta.sync();
KVCache kvCache;
bool pass = compareResult(seq_len);
if (!pass) {
printf("Error: LowerTriangular Attention with kv_cache unit test failed!\n");
return false;
}
/* generate mask expr */
/* generate mask expr */
auto MaskExpr = vector_to_var(mask);
MaskExpr = (_Scalar<float>(1.0) - _Cast<float>(MaskExpr)) * _Scalar<float>(std::numeric_limits<float>::lowest());
Output = _computeAttentionExpr(Query, Key, Value, MaskExpr, kvCache);
pass = compareResult(seq_len);
if (!pass) {
FUNC_PRINT(1);
return false;
}
// naiveAttention with history is error, use expr to test
Output = _computeAttentionExpr(Query, Key, Value, MaskExpr, kvCache);
gMeta.add = seq_len;
auto output2 = attn->onForward({Query, Key, Value, Mask})[0];
gMeta.sync();
auto diff = _ReduceMax(output2 - Output)->readMap<float>()[0];
if (diff >= 0.01f) { FUNC_PRINT_ALL(diff, f);
return false;
}
}
// test2
{
std::shared_ptr<NaiveAttention> naiveAttention(new NaiveAttention);
std::shared_ptr<MNN::OpT> attention(new MNN::OpT);
attention->type = MNN::OpType_Attention;
attention->main.type = MNN::OpParameter_AttentionParam;
attention->main.value = new MNN::AttentionParamT;
attention->main.AsAttentionParam()->kv_cache = true;
int seq_len = 10;
generateInput(seq_len, precision);
generateChunkMask(seq_len, seq_len, 2);
expected_result = naiveAttention->onExecute(query, key, value, mask, seq_len);
auto attn = _makeAttentionModule();
gMeta.previous = 0;
gMeta.add = seq_len;
Output = attn->onForward({Query, Key, Value, Mask})[0];
gMeta.sync();
KVCache kvCache;
bool pass = compareResult(seq_len);
if (!pass) {
printf("Error: Not LowerTriangular Attention with kv_cache unit test failed!\n");
return false;
}
Output = _computeAttentionExpr(Query, Key, Value, Mask, kvCache);
pass = compareResult(seq_len);
if (!pass) {
FUNC_PRINT(1);
return false;
}
// naiveAttention with history is error, use expr to test
Output = _computeAttentionExpr(Query, Key, Value, Mask, kvCache);
gMeta.add = seq_len;
auto output2 = attn->onForward({Query, Key, Value, Mask})[0];
gMeta.sync();
auto diff = _ReduceMax(output2 - Output)->readMap<float>()[0];
if (diff >= 0.01f) {
FUNC_PRINT_ALL(diff, f);
return false;
}
}
// unit test 3
{
auto rtInfo = ExecutorScope::Current()->getRuntime().first;
bool cpuInfer = true;
for(auto &rt : rtInfo) {
if(rt.first != MNN_FORWARD_CPU) {
cpuInfer = false;
break;
}
}
if(cpuInfer) {
// TODO: CPU support kv_cache == false
return true;
}
// MNN: kv_cache=false also falls back to CPU on OpenCL with
// MNN_GPU_MEMORY_IMAGE (no IMAGE-memtype Attention creator) and
// on Vulkan, so it hits the same CPUAttention "kv_cache == false"
// TODO and crashes. Skip until the CPU fallback is completed.
for(auto &rt : rtInfo) {
if(rt.first == MNN_FORWARD_OPENCL || rt.first == MNN_FORWARD_VULKAN) {
return true;
}
}
std::shared_ptr<NaiveAttention> naiveAttention(new NaiveAttention);
std::shared_ptr<MNN::OpT> attention(new MNN::OpT);
attention->type = MNN::OpType_Attention;
attention->main.type = MNN::OpParameter_AttentionParam;
attention->main.value = new MNN::AttentionParamT;
attention->main.AsAttentionParam()->kv_cache = false;
int seq_len = 128;
generateInput(seq_len, precision);
mask.clear();
expected_result = naiveAttention->onExecute(query, key, value, mask, seq_len);
Output = Variable::create(Expr::create(attention.get(), {Query, Key, Value}));
bool pass = compareResult(seq_len);
if (!pass) {
printf("Error: Attention without kv_cacheunit test failed!\n");
return false;
}
}
return true;
}
};
class SpeedAttentionTest : public AttentionTest {
protected:
std::vector< std::vector< std::vector<float> > > query;
std::vector< std::vector< std::vector<float> > > key;
std::vector< std::vector< std::vector<float> > > value;
std::vector< std::vector<int> > mask;
std::vector< std::vector< std::vector<float> > > expected_result;
public:
SpeedAttentionTest() = default;
virtual ~SpeedAttentionTest() = default;
virtual bool run(int precision) {
std::vector<int> seqs = {4096};
std::shared_ptr<NaiveAttention> naiveAttention(new NaiveAttention);
std::shared_ptr<MNN::OpT> attention(new MNN::OpT);
attention->type = MNN::OpType_Attention;
attention->main.type = MNN::OpParameter_AttentionParam;
attention->main.value = new MNN::AttentionParamT;
attention->main.AsAttentionParam()->kv_cache = true;
/* 3 attention module */
std::vector<int> quantQKV = {8, 9, 10};
std::vector<std::string> testNames = {"float qkv", "quant qk", "quant qkv"};
for (int n = 0; n < seqs.size(); ++n) {
int seq_len = seqs[n];
MNN_PRINT(">>> seq_len=%d, decode_len=%d\n", seq_len, GENERATE_TOKENS);
generateInput(seqs[n], precision, true);
generateMask(seqs[n], seq_len, true);
for (int m = 0; m < testNames.size(); ++m) {
gMeta.previous = 0;
gMeta.add = seq_len;
auto _module = _makeAttentionModule(quantQKV[m]);
MNN::Timer t1;
for (int x = 0; x < 5; ++x) {
Output = _module->onForward({Query, Key, Value, Mask})[0];
}
auto time = (float)t1.durationInUs() / 1000.0f / 5.f;
MNN_PRINT("%s: prefill cost = %.2f\n", testNames[m].c_str(), time);
gMeta.sync();
MNN::Timer t2;
for (int x = 0; x < GENERATE_TOKENS; ++x) {
gMeta.add = 1;
auto output2 = _module->onForward({Query1, Key1, Value1, Mask1})[0];
gMeta.sync();
}
time = (float)t2.durationInUs() / 1000.0f;
MNN_PRINT("%s: decode cost = %f\n", testNames[m].c_str(), time);
}
}
return true;
}
};
MNNTestSuiteRegister(AttentionTest, "op/attention");
class AttentionC4Test : public AttentionTest {
public:
AttentionC4Test() = default;
virtual ~AttentionC4Test() = default;
bool compareC4Result(int seqLen) {
const float* resultPtr = Output->readMap<float>();
const int hidden = NumHead * HeadDim;
std::vector<float> actual(seqLen * hidden);
std::vector<float> expected(seqLen * hidden);
for (int i = 0; i < seqLen; ++i) {
for (int h = 0; h < NumHead; ++h) {
for (int d = 0; d < HeadDim; ++d) {
int c = h * HeadDim + d;
int c4Index = (c % 4) + 4 * i + 4 * seqLen * (c / 4);
int logicalIndex = i * hidden + c;
actual[logicalIndex] = resultPtr[c4Index];
expected[logicalIndex] = expected_result[i][h][d];
}
}
}
if (!checkVectorByRelativeError<float>(actual.data(), expected.data(), actual.size(), 0.02f)) {
MNN_ERROR("AttentionC4Test failed!\n");
return false;
}
return true;
}
bool runOne(int seqLen, int precision) {
std::shared_ptr<NaiveAttention> naiveAttention(new NaiveAttention);
generateInput(seqLen, precision);
generateMask(seqLen, seqLen);
expected_result = naiveAttention->onExecute(query, key, value, mask, seqLen);
gMeta.previous = 0;
gMeta.remove = 0;
gMeta.add = seqLen;
auto attn = _makeAttentionModule(8, true);
Output = attn->onForward({Query, Key, Value, Mask})[0];
gMeta.sync();
if (!compareC4Result(seqLen)) {
return false;
}
auto valueC4 = vector_to_c4_value(value);
gMeta.previous = 0;
gMeta.remove = 0;
gMeta.add = seqLen;
auto attnValueC4 = _makeAttentionModule(8, true);
Output = attnValueC4->onForward({Query, Key, valueC4, Mask})[0];
gMeta.sync();
return compareC4Result(seqLen);
}
virtual bool run(int precision) {
srand(2024);
return runOne(10, precision) && runOne(32, precision);
}
};
MNNTestSuiteRegister(AttentionC4Test, "op/attention_c4");
MNNTestSuiteRegister(SpeedAttentionTest, "speed/attention");
#endif