1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 15:23:05 +04:00

Merge pull request #28524 from nklskyoy:attn-kv-cache

CPU Kernels for fp32 KV Cache #28524

The kernels are:
- pagedAttnQKGemmKernel
- pagedAttnAVGemmKernel

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [ ] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
nklskyoy
2026-03-13 08:23:30 +01:00
committed by GitHub
parent e188ffd190
commit 5c8d3b60f4
4 changed files with 778 additions and 8 deletions
@@ -20,6 +20,84 @@
namespace cv { namespace dnn {
int fastGemmMC(const FastGemmOpt &opt) {
#if CV_TRY_NEON
if (opt.use_neon) {
return opt_NEON::fastGemmMC();
} else
#endif
#if CV_TRY_AVX2
if (opt.use_avx2) {
return opt_AVX2::fastGemmMC();
} else
#endif
#if CV_TRY_AVX
if (opt.use_avx) {
return opt_AVX::fastGemmMC();
} else
#endif
#if CV_TRY_LASX
if (opt.use_lasx) {
return opt_LASX::fastGemmMC();
} else
#endif
{
return cpu_baseline::fastGemmMC();
}
}
int fastGemmNC(const FastGemmOpt &opt) {
#if CV_TRY_NEON
if (opt.use_neon) {
return opt_NEON::fastGemmNC();
} else
#endif
#if CV_TRY_AVX2
if (opt.use_avx2) {
return opt_AVX2::fastGemmNC();
} else
#endif
#if CV_TRY_AVX
if (opt.use_avx) {
return opt_AVX::fastGemmNC();
} else
#endif
#if CV_TRY_LASX
if (opt.use_lasx) {
return opt_LASX::fastGemmNC();
} else
#endif
{
return cpu_baseline::fastGemmNC();
}
}
int fastGemmKC(const FastGemmOpt &opt) {
#if CV_TRY_NEON
if (opt.use_neon) {
return opt_NEON::fastGemmKC();
} else
#endif
#if CV_TRY_AVX2
if (opt.use_avx2) {
return opt_AVX2::fastGemmKC();
} else
#endif
#if CV_TRY_AVX
if (opt.use_avx) {
return opt_AVX::fastGemmKC();
} else
#endif
#if CV_TRY_LASX
if (opt.use_lasx) {
return opt_LASX::fastGemmKC();
} else
#endif
{
return cpu_baseline::fastGemmKC();
}
}
size_t fastGemmPackBSize(size_t N, size_t K, const FastGemmOpt &opt) {
#if CV_TRY_NEON
if (opt.use_neon) {
@@ -399,4 +477,224 @@ void fastGemmBatch(bool trans_a, bool trans_b,
helper.ldb1, beta, c, helper.ldc, opt);
}
void fastGemmBatch(size_t batch,
const std::vector<size_t> &A_offsets, const std::vector<size_t> &B_offsets, const std::vector<size_t> &C_offsets,
int M, int N, int K, float alpha, const Mat&A, int lda0, int lda1,
const Mat&B, int ldb0, int ldb1, float beta, Mat&C, int ldc, FastGemmOpt &opt){
const char *a = (const char *)A.ptr<const float>();
const char *b = (const char *)B.ptr<const float>();
char *c = (char *)C.ptr<float>();
#if CV_TRY_NEON
if (opt.use_neon) {
opt_NEON::fastGemmBatchKernel(batch, A_offsets.data(), B_offsets.data(), C_offsets.data(), M, N, K, alpha, a, lda0, lda1, b, ldb0, ldb1, beta, c, ldc, sizeof(float));
} else
#endif
#if CV_TRY_AVX2
if (opt.use_avx2) {
opt_AVX2::fastGemmBatchKernel(batch, A_offsets.data(), B_offsets.data(), C_offsets.data(), M, N, K, alpha, a, lda0, lda1, b, ldb0, ldb1, beta, c, ldc, sizeof(float));
} else
#endif
#if CV_TRY_AVX
if (opt.use_avx) {
opt_AVX::fastGemmBatchKernel(batch, A_offsets.data(), B_offsets.data(), C_offsets.data(), M, N, K, alpha, a, lda0, lda1, b, ldb0, ldb1, beta, c, ldc, sizeof(float));
} else
#endif
#if CV_TRY_LASX
if (opt.use_lasx) {
opt_LASX::fastGemmBatchKernel(batch, A_offsets.data(), B_offsets.data(), C_offsets.data(), M, N, K, alpha, a, lda0, lda1, b, ldb0, ldb1, beta, c, ldc, sizeof(float));
} else
#endif
{
cpu_baseline::fastGemmBatchKernel(batch, A_offsets.data(), B_offsets.data(), C_offsets.data(), M, N, K, alpha, a, lda0, lda1, b, ldb0, ldb1, beta, c, ldc, sizeof(float));
}
}
void pagedAttnQKGemm(
const Mat& Q,const std::vector<Mat> &K, Mat& A,
int T_q, int Nq, int N_k, int T_s, int D,
const FastGemmOpt &opt
) {
size_t esz = Q.elemSize();
for (size_t s = 0; s < K.size(); s++){
CV_CheckTypeEQ(Q.type(), K[s].type(), "pagedAttnQKGemmKernel: Q and K should have the same type");
CV_CheckTrue(esz == K[s].elemSize(), "pagedAttnQKGemmKernel: Q and K should have the same element size");
}
CV_CheckTypeEQ(Q.type(), A.type(), "pagedAttnQKGemmKernel: Q and A should have the same type");
CV_CheckTrue(
T_s % fastGemmNC(opt) == 0,
"pagedAttnQKGemmKernel: T_s should be divisible by the macro tile size"
);
const auto shape_q = shape(Q);
CV_CheckTrue(
((shape_q.size() == 3) || (shape_q.size() == 4)),
"pagedAttnQKGemmKernel: Q must be 3D or 4D (T_q x Nq x D) or (kq_groups x T_q x Nq x D)"
);
const auto shape_a = shape(A);
CV_CheckTrue( shape_a.size() == 4, "pagedAttnQKGemmKernel: A must be 4D (B x N_q x T_q x D)" );
const int B = shape_a[0];
for (size_t s = 0; s < K.size(); s++) {
const auto shape_k = shape(K[s]);
CV_CheckTrue(
shape_k.size() == 3,
"pagedAttnQKGemmKernel: each K must be 4D (B x N_k x T_s x D)"
);
CV_CheckEQ(shape_k[0], B, "pagedAttnQKGemmKernel: the batch size of K should be the same as A");
CV_CheckEQ(shape_k[1], N_k, "pagedAttnQKGemmKernel: the number of heads in K should match that of Q");
CV_CheckEQ(shape_k[2], D * T_s, "pagedAttnQKGemmKernel: the head dimension of K should match that of Q and A");
}
std::vector<const char*> packed_K;
for (size_t s = 0; s < K.size(); s++){
packed_K.push_back(K[s].ptr<const char>());
}
char*a = A.ptr<char>();
bool isQ3D = shape_q.size() == 3;
#if CV_TRY_NEON
if (opt.use_neon)
opt_NEON::pagedAttnQKGemmKernel(
Q.ptr<const char>(), packed_K, a,
B, T_q, Nq, N_k, T_s, D,
esz, isQ3D
);
else
#endif
#if CV_TRY_AVX2
if (opt.use_avx2)
opt_AVX2::pagedAttnQKGemmKernel(
Q.ptr<const char>(), packed_K, a,
B, T_q, Nq, N_k, T_s, D,
esz, isQ3D
);
else
#endif
#if CV_TRY_AVX
if (opt.use_avx)
opt_AVX::pagedAttnQKGemmKernel(
Q.ptr<const char>(), packed_K, a,
B, T_q, Nq, N_k, T_s, D,
esz, isQ3D
);
else
#endif
#if CV_TRY_LASX
if (opt.use_lasx)
opt_LASX::pagedAttnQKGemmKernel(
Q.ptr<const char>(), packed_K, a,
B, T_q, Nq, N_k, T_s, D,
esz, isQ3D
);
else
#endif
cpu_baseline::pagedAttnQKGemmKernel(
Q.ptr<const char>(), packed_K, a,
B, T_q, Nq, N_k, T_s, D,
esz, isQ3D
);
}
void pagedAttnAVGemm(
const Mat& A,const std::vector<Mat> &V, Mat& Out,
int T_q, int Nq, int N_k, int T_s, int D,
const FastGemmOpt &opt
) {
size_t esz = A.elemSize();
for (size_t s = 0; s < V.size(); s++) {
CV_CheckTypeEQ(A.type(), V[s].type(), "pagedAttnAVGemmKernel: A and V should have the same type");
CV_CheckTrue(esz == V[s].elemSize(), "pagedAttnAVGemmKernel: A and V should have the same element size");
}
CV_CheckTypeEQ(A.type(), Out.type(), "pagedAttnAVGemmKernel: A and Out should have the same type");
CV_CheckTrue(
T_s % fastGemmKC(opt) == 0,
"pagedAttnAVGemmKernel: T_s should be divisible by the macro tile size"
);
const auto shape_a = shape(A);
CV_CheckTrue(
shape_a.size() == 4,
"pagedAttnAVGemmKernel: A must be 4D (B x n_q x T_q x D)"
);
const int B = shape_a[0];
for (size_t s = 0; s < V.size(); s++) {
const auto shape_v = shape(V[s]);
CV_CheckTrue(
shape_v.size() == 3,
"pagedAttnAVGemmKernel: each V must be 3D (B x N_k x (T_s * D))"
);
CV_CheckEQ(shape_v[0], B, "pagedAttnAVGemmKernel: the batch size of V should be the same as A");
CV_CheckEQ(shape_v[1], N_k, "pagedAttnAVGemmKernel: the number of heads in V should match that of A");
CV_CheckEQ(shape_v[2], (int)fastGemmPackBSize(D, T_s, opt),
"pagedAttnAVGemmKernel: the last dimension of V-pages should be be equal to packed D x T_s Mat size");
}
std::vector<const char*> packed_V;
for (size_t s = 0; s < V.size(); s++){
packed_V.push_back(V[s].ptr<const char>());
}
bool canonical_layout = shape(Out).size() == 3;
#if CV_TRY_NEON
if (opt.use_neon)
opt_NEON::pagedAttnAVGemmKernel(
A.ptr<const char>(), packed_V, Out.ptr<char>(),
B, T_q, Nq, N_k, T_s, D,
esz, canonical_layout, fastGemmPackBSize(D, T_s, opt)
);
else
#endif
#if CV_TRY_AVX2
if (opt.use_avx2) {
opt_AVX2::pagedAttnAVGemmKernel(
A.ptr<const char>(), packed_V, Out.ptr<char>(),
B, T_q, Nq, N_k, T_s, D,
esz, canonical_layout, fastGemmPackBSize(D, T_s, opt)
);
}
else
#endif
#if CV_TRY_AVX
if (opt.use_avx){
opt_AVX::pagedAttnAVGemmKernel(
A.ptr<const char>(), packed_V, Out.ptr<char>(),
B, T_q, Nq, N_k, T_s, D,
esz, canonical_layout, fastGemmPackBSize(D, T_s, opt)
);
}
else
#endif
#if CV_TRY_LASX
if (opt.use_lasx){
opt_LASX::pagedAttnAVGemmKernel(
A.ptr<const char>(), packed_V, Out.ptr<char>(),
B, T_q, Nq, N_k, T_s, D,
esz, canonical_layout, fastGemmPackBSize(D, T_s, opt)
);
}
else
#endif
{
cpu_baseline::pagedAttnAVGemmKernel(
A.ptr<const char>(), packed_V, Out.ptr<char>(),
B, T_q, Nq, N_k, T_s, D,
esz, canonical_layout, fastGemmPackBSize(D, T_s, opt)
);
}
}
}} // cv::dnn
@@ -43,6 +43,7 @@ struct FastGemmOpt {
bool all() {
return use_avx || use_avx2 || use_neon || use_lasx;
}
};
struct MatMulHelper {
@@ -154,6 +155,11 @@ struct MatMulHelper {
size_t fastGemmPackBSize(size_t N, size_t K, const FastGemmOpt &opt);
void fastGemmPackB(const Mat &m, std::vector<float> &packed_B, bool trans, FastGemmOpt &opt);
int fastGemmMC(const FastGemmOpt &opt);
int fastGemmNC(const FastGemmOpt &opt);
int fastGemmKC(const FastGemmOpt &opt);
void fastGemmPackB(bool trans, size_t N, size_t K, const float *B, size_t ldb, float *packed_B, const FastGemmOpt &opt);
void fastGemm(bool trans_a, int M, int N, int K,
@@ -175,7 +181,23 @@ void fastGemmBatch(size_t batch, const size_t *A_offsets, const size_t *B_offset
const float *packed_B, float beta, float *C, int ldc, FastGemmOpt &opt);
void fastGemmBatch(bool trans_a, bool trans_b, float alpha, const Mat &A,
const Mat &B, float beta, Mat &C, FastGemmOpt &opt);
void fastGemmBatch(size_t batch,
const std::vector<size_t> &A_offsets, const std::vector<size_t> &B_offsets, const std::vector<size_t> &C_offsets,
int M, int N, int K, float alpha, const Mat&A, int lda0, int lda1,
const Mat&B, int ldb0, int ldb1, float beta, Mat&C, int ldc, FastGemmOpt &opt);
void pagedAttnQKGemm(
const Mat& Q, const std::vector<Mat> &K, Mat& A,
int T_q, int Nq, int N_k, int T_s, int D,
const FastGemmOpt &opts
);
void pagedAttnAVGemm(
const Mat& A,const std::vector<Mat> &V, Mat& Out,
int T_q, int Nq, int N_k, int T_s, int D,
const FastGemmOpt &opt
);
}} // cv::dnn
#endif // OPENCV_DNN_FAST_GEMM_HPP
#endif // OPENCV_DNN_FAST_GEMM_HPP
@@ -78,6 +78,10 @@ namespace cv { namespace dnn { namespace cpu_baseline {
size_t fastGemmPackBSize(int N, int K);
int fastGemmMC();
int fastGemmNC();
int fastGemmKC();
void fastGemmPackBKernel(const char *B, char *packed_B, size_t N, size_t K, size_t ldb0, size_t ldb1, size_t esz);
void fastGemmKernel(size_t M, size_t N, size_t K,
@@ -94,7 +98,16 @@ void fastGemmBatchKernel(size_t batch, const size_t *A_offsets, const size_t *B_
void fastGemmBatchKernel(size_t batch, const size_t *A_offsets, const size_t *B_offsets, const size_t *C_offsets,
size_t M, size_t N, size_t K, float alpha, const char *A, size_t lda0, size_t lda1,
const char *packed_B, float beta, char *C, size_t ldc, size_t esz);
void pagedAttnAVGemmKernel(
const char* A, const std::vector<const char *> &V, char*Out,
size_t B, size_t T_a, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool canonical_layout, size_t packed_stride
);
void pagedAttnQKGemmKernel(
const char *Q, const std::vector<const char *> &K, char *A,
size_t B, size_t T_q, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool isQ3d
);
FAST_GEMM_IMPLEMENT_PACK(8, _f32, float, float)
FAST_GEMM_IMPLEMENT_PACK(12, _f32, float, float)
@@ -105,6 +118,18 @@ size_t fastGemmPackBSize(int N, int K) {
return static_cast<size_t>((N + NC - 1) / NC) * NC * K;
}
int fastGemmMC() {
return FAST_GEMM_F32_MC;
}
int fastGemmNC() {
return FAST_GEMM_F32_NC;
}
int fastGemmKC() {
return FAST_GEMM_F32_PACKED_STRIDE_K;
}
void fastGemmPackBKernel(const char *B, char *packed_B, size_t N, size_t K, size_t ldb0, size_t ldb1, size_t esz) {
size_t GEMM_NC = FAST_GEMM_F32_NC, GEMM_NR = FAST_GEMM_F32_NR;
size_t NC = (((GEMM_NC < N ? GEMM_NC : N) + GEMM_NR - 1) / GEMM_NR) * GEMM_NR;
@@ -462,6 +487,204 @@ void fastGemmBatchKernel(size_t batch, const size_t *A_offsets, const size_t *B_
parallel_for_(Range(0, total), fn, nstripes);
}
// specially for attention
// compute QK, supports KV Cache
// pages in K are always of shape [B, N_kv, T_s, D]
// tot. seq len T = T_s * S
void pagedAttnQKGemmKernel(
const char *Q, const std::vector<const char *> &K, char *A,
size_t B, size_t T_q, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool isQ3d
) {
size_t GEMM_MC = static_cast<size_t>(FAST_GEMM_F32_MC),
GEMM_NC = static_cast<size_t>(FAST_GEMM_F32_NC),
GEMM_MR = static_cast<size_t>(FAST_GEMM_F32_MR),
GEMM_NR = static_cast<size_t>(FAST_GEMM_F32_NR);
size_t MC = (((GEMM_MC < T_q ? GEMM_MC : T_q) + GEMM_MR - 1) / GEMM_MR) * GEMM_MR;
size_t NC = (((GEMM_NC < T_s ? GEMM_NC : T_s) + GEMM_NR - 1) / GEMM_NR) * GEMM_NR;
size_t KC = std::min( static_cast<size_t>(FAST_GEMM_F32_PACKED_STRIDE_K), D);
size_t buff_size = KC * MC * esz;
const size_t m_tiles = (T_q + MC - 1) / MC;
const size_t n_tiles = (T_s + NC - 1) / NC;
const size_t tiles_per_mat = m_tiles * n_tiles;
const size_t S = K.size();
const size_t T = S * T_s;
const int n_kq_groups = Nq / N_k;
size_t batch = Nq * B;
// batch = B x N_q
int total = S * batch * tiles_per_mat;
auto fn = [&](const Range &r) {
cv::AutoBuffer<char, FAST_GEMM_MAX_STACKBUF> packed_q_buff;
packed_q_buff.allocate(buff_size);
char*packed_q = packed_q_buff.data();
size_t start = r.start;
size_t end = r.end;
size_t ldq0 = isQ3d ? Nq * D : D;
for (size_t tile_idx = start; tile_idx < end; tile_idx++) {
size_t idx = tile_idx / tiles_per_mat;
size_t b = idx / (Nq * S);
size_t nq = (idx - b * Nq * S) / S;
size_t n_k = nq / n_kq_groups;
size_t s = (idx - b * Nq * S) % S;
const size_t m_tiles_index = (tile_idx - idx * tiles_per_mat) / n_tiles;
const size_t n_tiles_index = tile_idx % n_tiles;
size_t i0 = m_tiles_index * MC;
size_t j0 = n_tiles_index * NC;
size_t mc = T_q - i0 < MC ? T_q - i0 : MC;
size_t nc = T_s - j0 < NC ? T_s - j0 : NC;
size_t q_offset = b * Nq * T_q * D +
(isQ3d ? nq * D : nq * T_q * D);
const char *q_block = Q + q_offset * esz;
size_t k_offset = b * N_k * T_s * D +
n_k * T_s * D +
j0 * D;
const char *k_block = (const char *)K[s] + k_offset * esz;
// save result to A[b, n_q, : , T_s * s : T_s * (s + 1)]
const int a_offset = b * Nq * T_q * T +
nq * T_q * T +
T_s * s +
i0 * T + j0;
char* a_block = A + a_offset * esz;
int _nc = static_cast<int>((nc + GEMM_NR - 1) / GEMM_NR) * GEMM_NR * esz;
for(int k0 = 0; k0 < D; k0 += KC)
{
int kc = D - k0 < KC ? D - k0 : KC;
// pack q
size_t step_q = (i0 * ldq0 + k0) * esz;
fast_gemm_pack8_f32(mc, kc, q_block + step_q, ldq0, 1, packed_q);
// run kernel
fast_gemm_macro_kernel(mc, nc, kc, packed_q, k_block, 1.f, a_block, T, esz);
k_block += _nc * kc;
}
}
};
int cost_per_thread = static_cast<int>((D / KC) * (MC / GEMM_MR) * (NC / GEMM_NR));
double nstripes = (size_t)total * cost_per_thread * (1 / 1024.0);
parallel_for_(Range(0, total), fn, nstripes);
}
// specially for attention
// compute QK, supports KV Cache
// A is of shape [B, N_q, T_q, T_s * S]
// pages in K are always of shape [B, N_kv, T_s, D]
// tot. seq len T = T_s * S
void pagedAttnAVGemmKernel(
const char* A, const std::vector<const char *> &V, char*Out,
size_t B, size_t T_a, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool canonical_layout, size_t packed_stride
) {
size_t GEMM_MC = static_cast<size_t>(FAST_GEMM_F32_MC),
GEMM_NC = static_cast<size_t>(FAST_GEMM_F32_NC),
GEMM_MR = static_cast<size_t>(FAST_GEMM_F32_MR),
GEMM_NR = static_cast<size_t>(FAST_GEMM_F32_NR);
const size_t S = V.size();
const size_t T = S * T_s;
size_t MC = (((GEMM_MC < T_a ? GEMM_MC : T_a) + GEMM_MR - 1) / GEMM_MR) * GEMM_MR;
size_t NC = (((GEMM_NC < D ? GEMM_NC : D) + GEMM_NR - 1) / GEMM_NR) * GEMM_NR;
size_t KC = std::min( static_cast<size_t>(FAST_GEMM_F32_PACKED_STRIDE_K), T);
size_t buff_size = KC * MC * esz;
const size_t m_tiles = (T_a + MC - 1) / MC;
const size_t n_tiles = (D + NC - 1) / NC;
const size_t tiles_per_mat = m_tiles * n_tiles;
const int n_kq_groups = Nq / N_k;
size_t batch = Nq * B;
int total = batch * tiles_per_mat;
auto fn = [&](const Range &r) {
cv::AutoBuffer<char, FAST_GEMM_MAX_STACKBUF> packed_a_buff;
packed_a_buff.allocate(buff_size);
char* packed_a = packed_a_buff.data();
size_t start = r.start;
size_t end = r.end;
size_t ldc0 = canonical_layout ? D : Nq * D;
for (size_t tile_idx = start; tile_idx < end; tile_idx++) {
size_t idx = tile_idx / tiles_per_mat;
size_t b = idx / Nq;
size_t nq = idx % Nq;
size_t n_k = nq / n_kq_groups;
size_t m_tiles_index = (tile_idx - idx * tiles_per_mat) / n_tiles;
size_t n_tiles_index = tile_idx % n_tiles;
size_t i0 = m_tiles_index * MC;
size_t j0 = n_tiles_index * NC;
size_t mc = T_a - i0 < MC ? T_a - i0 : MC;
size_t nc = D - j0 < NC ? D - j0 : NC;
const size_t a_offset = b * Nq * T_a * T +
nq * T_a * T +
i0 * T;
const char*a_block = A + a_offset * esz;
// start at the 0th row
// column offset is determined according to packed layout
size_t v_offset_base = b * N_k * packed_stride +
n_k * packed_stride +
n_tiles_index * T_s * NC;
size_t o_offset = b * Nq * T_a * D;
if (canonical_layout)
o_offset += nq * T_a * D +
i0 * D + j0;
else
o_offset += i0 * Nq * D +
nq * D + j0;
char* out_block = Out + o_offset * esz;
int _nc = static_cast<int>((nc + GEMM_NR - 1) / GEMM_NR) * GEMM_NR * esz;
for(int k0 = 0; k0 < T; k0 += KC)
{
// T_s should be divisible by KC
// pack a
fast_gemm_pack8_f32(mc, KC, a_block + k0 * esz, T, 1, packed_a);
// get the k-block
size_t sk = k0 / T_s; // which page
size_t k = k0 % T_s;
size_t v_offset = v_offset_base * esz + k * _nc;
const char *v_block = V[sk] + v_offset;
// run kernel
fast_gemm_macro_kernel(mc, nc, KC, packed_a, v_block, 1.f, out_block, ldc0, esz);
}
}
};
int cost_per_thread = static_cast<int>((T / KC) * (MC / GEMM_MR) * (NC / GEMM_NR));
double nstripes = (size_t)total * cost_per_thread * (1 / 1024.0);
parallel_for_(Range(0, total), fn, nstripes);
}
}}} // cv::dnn::cpu_baseline
#undef FAST_GEMM_STORAGE
@@ -117,6 +117,10 @@ CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN
size_t fastGemmPackBSize(int N, int K);
int fastGemmMC();
int fastGemmNC();
int fastGemmKC();
void fastGemmPackBKernel(const char *B, char *packed_B, size_t N, size_t K, size_t ldb0, size_t ldb1, size_t esz);
void fastGemmKernel(size_t M, size_t N, size_t K,
@@ -134,6 +138,18 @@ void fastGemmBatchKernel(size_t batch, const size_t *A_offsets, const size_t *B_
size_t M, size_t N, size_t K, float alpha, const char *A, size_t lda0, size_t lda1,
const char *packed_B, float beta, char *C, size_t ldc, size_t esz);
void pagedAttnQKGemmKernel(
const char *Q, const std::vector<const char *> &K, char *A,
size_t B, size_t T_q, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool isQ3d
);
void pagedAttnAVGemmKernel(
const char* A, const std::vector<const char *> &K, char*Out,
size_t B, size_t T_a, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool canonical_layout, size_t packed_stride
);
#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
/*
@@ -538,6 +554,10 @@ size_t fastGemmPackBSize(int N, int K) {
return static_cast<size_t>((N + NC - 1) / NC) * NC * K;
}
int fastGemmMC() {return FAST_GEMM_F32_MC;}
int fastGemmNC() {return FAST_GEMM_F32_NC;}
int fastGemmKC() {return FAST_GEMM_F32_PACKED_STRIDE_K;}
void fastGemmPackBKernel(const char *B, char *packed_B, size_t N, size_t K, size_t ldb0, size_t ldb1, size_t esz) {
size_t GEMM_NC = FAST_GEMM_F32_NC, GEMM_NR = FAST_GEMM_F32_NR;
size_t NC = (((GEMM_NC < N ? GEMM_NC : N) + GEMM_NR - 1) / GEMM_NR) * GEMM_NR;
@@ -844,13 +864,15 @@ void fastGemmBatchKernel(size_t batch, const size_t *A_offsets, const size_t *B_
size_t KC = std::min( static_cast<size_t>(FAST_GEMM_F32_PACKED_STRIDE_K), K);
size_t buff_size = KC * MC * esz;
bool use_stackbuff = buff_size <= FAST_GEMM_MAX_STACKBUF;
size_t m_tiles = (M + MC - 1) / MC;
size_t n_tiles = (N + NC - 1) / NC;
size_t total_tiles = m_tiles * n_tiles;
auto fn = [&](const Range &r) {
char* packed_a = (char*)(use_stackbuff ? alloca(buff_size) : malloc(buff_size));
cv::AutoBuffer<char, FAST_GEMM_MAX_STACKBUF> packed_a_buff;
packed_a_buff.allocate(buff_size);
char*packed_a = packed_a_buff.data();
const char *packed_b = packed_B;
size_t start = r.start;
size_t end = r.end;
@@ -907,10 +929,6 @@ void fastGemmBatchKernel(size_t batch, const size_t *A_offsets, const size_t *B_
packed_b += _nc * kc;
}
}
if (!use_stackbuff) {
free(packed_a);
}
};
int total = batch * total_tiles;
@@ -919,6 +937,215 @@ void fastGemmBatchKernel(size_t batch, const size_t *A_offsets, const size_t *B_
parallel_for_(Range(0, total), fn, nstripes);
}
// specially for attention
// compute QK, supports KV Cache
// pages in K are always of shape [B, N_kv, T_s, D]
// tot. seq len T = T_s * S
void pagedAttnQKGemmKernel(
const char *Q, const std::vector<const char *> &K, char *A,
size_t B, size_t T_q, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool isQ3d
) {
size_t GEMM_MC = static_cast<size_t>(FAST_GEMM_F32_MC),
GEMM_NC = static_cast<size_t>(FAST_GEMM_F32_NC),
GEMM_MR = static_cast<size_t>(FAST_GEMM_F32_MR),
GEMM_NR = static_cast<size_t>(FAST_GEMM_F32_NR);
size_t MC = (((GEMM_MC < T_q ? GEMM_MC : T_q) + GEMM_MR - 1) / GEMM_MR) * GEMM_MR;
size_t NC = (((GEMM_NC < T_s ? GEMM_NC : T_s) + GEMM_NR - 1) / GEMM_NR) * GEMM_NR;
size_t KC = std::min( static_cast<size_t>(FAST_GEMM_F32_PACKED_STRIDE_K), D);
size_t buff_size = KC * MC * esz;
const size_t m_tiles = (T_q + MC - 1) / MC;
const size_t n_tiles = (T_s + NC - 1) / NC;
const size_t tiles_per_mat = m_tiles * n_tiles;
const size_t S = K.size();
const size_t T = S * T_s;
const int n_kq_groups = Nq / N_k;
size_t batch = Nq * B;
// batch = B x N_q
int total = S * batch * tiles_per_mat;
auto fn = [&](const Range &r) {
cv::AutoBuffer<char, FAST_GEMM_MAX_STACKBUF> packed_q_buff;
packed_q_buff.allocate(buff_size);
char*packed_q = packed_q_buff.data();
size_t start = r.start;
size_t end = r.end;
size_t ldq0 = isQ3d ? Nq * D : D;
for (size_t tile_idx = start; tile_idx < end; tile_idx++) {
size_t idx = tile_idx / tiles_per_mat;
size_t b = idx / (Nq * S);
size_t nq = (idx - b * Nq * S) / S;
size_t n_k = nq / n_kq_groups;
size_t s = (idx - b * Nq * S) % S;
const size_t m_tiles_index = (tile_idx - idx * tiles_per_mat) / n_tiles;
const size_t n_tiles_index = tile_idx % n_tiles;
size_t i0 = m_tiles_index * MC;
size_t j0 = n_tiles_index * NC;
size_t mc = T_q - i0 < MC ? T_q - i0 : MC;
size_t nc = T_s - j0 < NC ? T_s - j0 : NC;
size_t q_offset = b * Nq * T_q * D +
(isQ3d ? nq * D : nq * T_q * D);
const char *q_block = Q + q_offset * esz;
size_t k_offset = b * N_k * T_s * D +
n_k * T_s * D +
j0 * D;
const char *k_block = (const char *)K[s] + k_offset * esz;
// save result to A[b, n_q, : , T_s * s : T_s * (s + 1)]
const int a_offset = b * Nq * T_q * T +
nq * T_q * T +
T_s * s +
i0 * T + j0;
char* a_block = A + a_offset * esz;
int _nc = static_cast<int>((nc + GEMM_NR - 1) / GEMM_NR) * GEMM_NR * esz;
for(int k0 = 0; k0 < D; k0 += KC)
{
int kc = D - k0 < KC ? D - k0 : KC;
// pack q
size_t step_q = (i0 * ldq0 + k0) * esz;
#if CV_NEON && CV_NEON_AARCH64
fast_gemm_pack8_f32(mc, kc, q_block + step_q, ldq0, 1, packed_q);
#elif CV_AVX
fast_gemm_pack12_f32(mc, kc, q_block + step_q, ldq0, 1, packed_q);
#elif CV_LASX
fast_gemm_pack12_f32(mc, kc, q_block + step_q, ldq0, 1, packed_q);
#elif CV_SIMD128
fast_gemm_pack8_f32(mc, kc, q_block + step_q, ldq0, 1, packed_q);
#endif
// run kernel
fast_gemm_macro_kernel(mc, nc, kc, packed_q, k_block, 1.f, a_block, T, esz);
k_block += _nc * kc;
}
}
};
int cost_per_thread = static_cast<int>((D / KC) * (MC / GEMM_MR) * (NC / GEMM_NR));
double nstripes = (size_t)total * cost_per_thread * (1 / 1024.0);
parallel_for_(Range(0, total), fn, nstripes);
}
// specially for attention
// compute QK, supports KV Cache
// A is of shape [B, N_q, T_q, T_s * S]
// pages in K are always of shape [B, N_kv, T_s, D]
// tot. seq len T = T_s * S
void pagedAttnAVGemmKernel(
const char* A, const std::vector<const char *> &V, char*Out,
size_t B, size_t T_a, size_t Nq, size_t N_k, size_t T_s, size_t D,
size_t esz, bool canonical_layout, size_t packed_stride
) {
size_t GEMM_MC = static_cast<size_t>(FAST_GEMM_F32_MC),
GEMM_NC = static_cast<size_t>(FAST_GEMM_F32_NC),
GEMM_MR = static_cast<size_t>(FAST_GEMM_F32_MR),
GEMM_NR = static_cast<size_t>(FAST_GEMM_F32_NR);
const size_t S = V.size();
const size_t T = S * T_s;
size_t MC = (((GEMM_MC < T_a ? GEMM_MC : T_a) + GEMM_MR - 1) / GEMM_MR) * GEMM_MR;
size_t NC = (((GEMM_NC < D ? GEMM_NC : D) + GEMM_NR - 1) / GEMM_NR) * GEMM_NR;
size_t KC = std::min( static_cast<size_t>(FAST_GEMM_F32_PACKED_STRIDE_K), T);
size_t buff_size = KC * MC * esz;
const size_t m_tiles = (T_a + MC - 1) / MC;
const size_t n_tiles = (D + NC - 1) / NC;
const size_t tiles_per_mat = m_tiles * n_tiles;
const int n_kq_groups = Nq / N_k;
size_t batch = Nq * B;
int total = batch * tiles_per_mat;
auto fn = [&](const Range &r) {
cv::AutoBuffer<char, FAST_GEMM_MAX_STACKBUF> packed_a_buff;
packed_a_buff.allocate(buff_size);
char* packed_a = packed_a_buff.data();
size_t start = r.start;
size_t end = r.end;
size_t ldc0 = canonical_layout ? D : Nq * D;
for (size_t tile_idx = start; tile_idx < end; tile_idx++) {
size_t idx = tile_idx / tiles_per_mat;
size_t b = idx / Nq;
size_t nq = idx % Nq;
size_t n_k = nq / n_kq_groups;
size_t m_tiles_index = (tile_idx - idx * tiles_per_mat) / n_tiles;
size_t n_tiles_index = tile_idx % n_tiles;
size_t i0 = m_tiles_index * MC;
size_t j0 = n_tiles_index * NC;
size_t mc = T_a - i0 < MC ? T_a - i0 : MC;
size_t nc = D - j0 < NC ? D - j0 : NC;
const size_t a_offset = b * Nq * T_a * T +
nq * T_a * T +
i0 * T;
const char*a_block = A + a_offset * esz;
// start at the 0th row
// column offset is determined according to packed layout
size_t v_offset_base = b * N_k * packed_stride +
n_k * packed_stride +
n_tiles_index * T_s * NC;
size_t o_offset = b * Nq * T_a * D;
if (canonical_layout)
o_offset += nq * T_a * D +
i0 * D + j0;
else
o_offset += i0 * Nq * D +
nq * D + j0;
char* out_block = Out + o_offset * esz;
int _nc = static_cast<int>((nc + GEMM_NR - 1) / GEMM_NR) * GEMM_NR * esz;
for(int k0 = 0; k0 < T; k0 += KC)
{
// get the k-block
size_t sk = k0 / T_s; // which page
size_t k = k0 % T_s;
size_t v_offset = v_offset_base * esz + k * _nc;
const char *v_block = V[sk] + v_offset;
// T_s should be divisible by KC
// pack
#if CV_NEON && CV_NEON_AARCH64
fast_gemm_pack8_f32(mc, KC, a_block + k0 * esz, T, 1, packed_a);
#elif CV_AVX
fast_gemm_pack12_f32(mc, KC, a_block + k0 * esz, T, 1, packed_a);
#elif CV_LASX
fast_gemm_pack12_f32(mc, KC, a_block + k0 * esz, T, 1, packed_a);
#elif CV_SIMD128
fast_gemm_pack8_f32(mc, KC, a_block + k0 * esz, T, 1, packed_a);
#endif
// run kernel
fast_gemm_macro_kernel(mc, nc, KC, packed_a, v_block, 1.f, out_block, ldc0, esz);
}
}
};
int cost_per_thread = static_cast<int>((T / KC) * (MC / GEMM_MR) * (NC / GEMM_NR));
double nstripes = (size_t)total * cost_per_thread * (1 / 1024.0);
parallel_for_(Range(0, total), fn, nstripes);
}
#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
CV_CPU_OPTIMIZATION_NAMESPACE_END