Files
wehub-resource-sync ec436095dd
Book-CI / test (macos-latest) (push) Has been cancelled
Book-CI / test (ubuntu-latest) (push) Has been cancelled
Book-CI / test (windows-latest) (push) Has been cancelled
Release Fake Tag / publish (push) Has been cancelled
Deploy / deploy (macos-latest) (push) Has been cancelled
Deploy / deploy (ubuntu-latest) (push) Has been cancelled
Deploy / deploy (windows-latest) (push) Has been cancelled
Release to PyPI / Build & publish sglang-kt (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.11) (push) Has been cancelled
Release to PyPI / Build kt-kernel (Python 3.12) (push) Has been cancelled
Release to PyPI / Publish kt-kernel to PyPI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:30:03 +08:00

3423 lines
132 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef AMX_KERNELS_HPP
#define AMX_KERNELS_HPP
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <memory>
#include "amx_buffers.hpp"
#include "amx_config.hpp"
#include "amx_quantization.hpp"
#include "amx_utils.hpp"
#include "llama.cpp/ggml-impl.h"
#include "llama.cpp/ggml-quants.h"
#include "llamafile/sgemm.h"
#include "utils.hpp"
namespace amx {
// Compile-time detection: true when AMX intrinsics are available
#if defined(__AMX__) || defined(__AMXINT8__) || defined(__AMXBF16__) || defined(__AMX_TILE__) || defined(HAVE_AMX)
inline constexpr bool AMX_AVAILABLE = true;
#ifndef HAVE_AMX
#define HAVE_AMX
#endif
#else
inline constexpr bool AMX_AVAILABLE = false;
#endif
/*
We use 1-3-3
C = A x B
A is a row major matrix of size M x K, usually an Linear Layer weight matrix
B is a col major vector of size K x N, usually an input vector, N is usually
quite small
B
A C
A C
A C
TMM 0-2: A
TMM 3: B
TMM 4-6: C
3
0 4
1 5
2 6
*/
template <class, class>
struct dpb133 {
static void run();
};
template <>
inline void dpb133<int8_t, int8_t>::run() {
_tile_dpbssd(4, 0, 3);
_tile_dpbssd(5, 1, 3);
_tile_dpbssd(6, 2, 3);
}
template <>
inline void dpb133<int8_t, uint8_t>::run() {
_tile_dpbsud(4, 0, 3);
_tile_dpbsud(5, 1, 3);
_tile_dpbsud(6, 2, 3);
}
template <>
inline void dpb133<uint8_t, int8_t>::run() {
_tile_dpbusd(4, 0, 3);
_tile_dpbusd(5, 1, 3);
_tile_dpbusd(6, 2, 3);
}
template <>
inline void dpb133<uint8_t, uint8_t>::run() {
_tile_dpbuud(4, 0, 3);
_tile_dpbuud(5, 1, 3);
_tile_dpbuud(6, 2, 3);
}
template <int TILE_K = 32>
struct GemmKernel133 {
static constexpr int TILE_M = 16;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 4;
static constexpr int OUTPUT_T_SIZE = 4;
static constexpr int M_STEP = TILE_M * 3;
static constexpr int N_STEP = TILE_N;
static constexpr int K_STEP = TILE_K;
static int recommended_nth(int m) { return (m + M_STEP - 1) / M_STEP; }
static void config() {
#ifdef HAVE_AMX
TileConfig tile_config;
for (int i = 0; i < 3; i++) tile_config.set_row_col(i, TILE_M, TILE_K);
tile_config.set_row_col(3, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK);
for (int i = 4; i < 7; i++) tile_config.set_row_col(i, TILE_M, TILE_N * OUTPUT_T_SIZE);
tile_config.set_config();
#endif
}
template <typename TA, typename TB, typename TC>
static void run_full_tile(const TA* a, size_t lda, const TB* b, size_t ldb, TC* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
_tile_loadd(2, offset_pointer(a, lda * TILE_M * 2), lda);
_tile_loadd(3, b, ldb);
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, ldc * TILE_N), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_N * 2), ldc);
dpb133<TA, TB>::run();
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, ldc * TILE_N), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_N * 2), ldc);
#endif
}
template <typename TA, typename TB, typename TC>
static void run_full_tile_zero(const TA* a, size_t lda, const TB* b, size_t ldb, TC* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
_tile_loadd(2, offset_pointer(a, lda * TILE_M * 2), lda);
_tile_loadd(3, b, ldb);
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
dpb133<TA, TB>::run();
// debug_tiles(7);
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, ldc * TILE_N), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_N * 2), ldc);
#endif
}
static void convert_full_tile_b_to_vnni_inplace(void* b) { transpose_16x8_32bit((__m256i*)b); }
template <typename TA>
struct ATile {
TA v[3 * TILE_M * TILE_K];
void partial_load(TA* a, int m, int k, size_t lda) {
// memset(v, 0, sizeof(TA) * 3 * TILE_M * TILE_K);
for (int i = 0; i < m; i++) {
for (int j = 0; j < k; j++) {
v[i * TILE_K + j] = a[i * lda + j];
}
}
}
void partial_load_quant(block_q4_0* a, int m, int k, size_t lda) {
assert(k == 32);
// memset(v, 0, sizeof(TA) * 3 * TILE_M * TILE_K);
__m256i* vv = (__m256i*)v;
for (int i = 0; i < m; i++) {
vv[i] = dequant4x32(offset_pointer(a, lda * i)->qs);
vv[i] = _mm256_sub_epi8(vv[i], _mm256_set1_epi8(8));
}
}
void partial_load_quant(block_q8_0* a, int m, int k, size_t lda) {
assert(k == 32);
// memset(v, 0, sizeof(TA) * 3 * TILE_M * TILE_K);
__m256i* vv = (__m256i*)v;
for (int i = 0; i < m; i++) {
vv[i] = unaligned_copy8x32(offset_pointer(a, lda * i)->qs);
}
}
template <typename QA>
void partial_load_quant(TA* a, int m, size_t lda) {
// memset(v, 0, sizeof(TA) * 3 * TILE_M * TILE_K);
if constexpr (std::is_same_v<QA, blocks_aligned_q8_0_ref>) {
__m512i* vv = (__m512i*)v;
for (int i = 0; i < m; i++) {
vv[i] = copy8x64(offset_pointer(a, lda * i));
}
} else if constexpr (std::is_same_v<QA, blocks_aligned_q4_0_ref>) {
assert(0);
} else {
assert(0);
}
}
void partial_load_quant(block_q4_K* a, int m, int inner_block_idx, size_t lda) {
// memset(v, 0, sizeof(TA) * 3 * TILE_M * TILE_K);
__m256i* vv = (__m256i*)v;
size_t qs_offset = inner_block_idx / 2 * 32;
for (int i = 0; i < m; i++) {
block_q4_K* spa = offset_pointer_row_major(a, i, 0, lda);
if (inner_block_idx % 2 == 0) {
vv[i] = lo4bit(spa->qs + qs_offset);
} else {
vv[i] = hi4bit(spa->qs + qs_offset);
}
}
}
void partial_load_quant(blocks_aligned_q8_0_ref a, int m, int k, int blck_stride) {
// memset(v, 0, sizeof(TA) * 3 * TILE_M * TILE_K);
__m512i* vv = (__m512i*)v;
for (int i = 0; i < m; i++) {
vv[i] = copy8x64(a.offset(blck_stride * i).qs);
}
}
};
template <typename TB>
struct alignas(64) BTile {
TB v[TILE_N * TILE_K];
__m512 scale = {};
void partial_load(TB* b, int n, int k, size_t ldb) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < k; j++) {
v[i * TILE_K + j] = b[i * ldb + j];
}
}
transpose_16x8_32bit((__m256i*)v);
}
void partial_load_quant(block_q8_0* b, int n, int k, size_t ldb) {
assert(k == 32);
memset(v, 0, sizeof(TB) * TILE_K * TILE_N);
__m256i* vv = (__m256i*)v;
float* bss = reinterpret_cast<float*>(&scale);
for (int i = 0; i < n; i++) {
vv[i] = unaligned_copy8x32(offset_pointer(b, ldb * i)->qs);
float sb = GGML_FP16_TO_FP32(offset_pointer_col_major(b, 0, i, ldb)->d);
bss[i] = sb;
}
transpose_16x8_32bit(vv);
}
void partial_load_quant(blocks_aligned_q8_0_ref b, int n, int k, int blck_stride) {
assert(k == 64);
memset(v, 0, sizeof(TB) * TILE_K * TILE_N);
__m512i* vv = (__m512i*)v;
float* vs = reinterpret_cast<float*>(&scale);
for (int i = 0; i < n; i++) {
auto ref = b.offset(blck_stride * i);
vv[i] = copy8x64(ref.qs);
float sb = GGML_FP16_TO_FP32(*ref.d);
vs[i] = sb;
}
transpose_16x16_32bit(vv);
}
void load_from(TB* b, size_t ldb) {
__m256i* vb = (__m256i*)b;
__m256i* vo = (__m256i*)v;
for (int i = 0; i < 16; i++) {
vo[i] = *offset_pointer(vb, ldb * i);
}
transpose_16x8_32bit(vo);
}
template <typename TA, typename TC>
void run_full_ac(TA* a, size_t lda, TC* c, size_t ldc) {
run_full_tile(a, lda, v, TILE_N * VNNI_BLK, c, ldc);
}
};
template <typename TB>
struct alignas(64) BTileSum {
TB v[TILE_N * TILE_K];
__m512 scale = {};
__m512 sum = {};
void partial_load_quant(block_q8_K* b, int n, int inner_block_idx, size_t ldb) {
memset(v, 0, TILE_K * TILE_N);
__m256i* vv = (__m256i*)v;
float* scale_s = reinterpret_cast<float*>(&scale);
float* sum_s = reinterpret_cast<float*>(&sum);
for (int i = 0; i < n; i++) {
block_q8_K* spb = offset_pointer_col_major(b, 0, i, ldb);
vv[i] = unaligned_copy8x32(spb->qs + inner_block_idx * 32);
scale_s[i] = spb->d;
sum_s[i] =
spb->bsums[inner_block_idx * 2] + spb->bsums[inner_block_idx * 2 + 1]; // TODO: may this will be slow
// printf("scale[%d] = %f, sum_s[%d] = %f\n", i, scale_s[i], i,
// sum_s[i]);
}
transpose_16x8_32bit(vv);
}
};
template <typename TC>
struct alignas(64) CTile {
static_assert(sizeof(TC) == 4);
TC v[3 * TILE_M * TILE_N] = {};
void partial_load(TC* c, int m, int n, size_t ldc) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
v[i * TILE_N + j] = offset_pointer(c, ldc * i)[j];
}
}
}
void partial_store(TC* c, int m, int n, size_t ldc) {
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
offset_pointer(c, ldc * i)[j] = v[i * TILE_N + j];
}
}
}
void to_fp32() {
__m512i* vv = (__m512i*)v;
__m512* vf = (__m512*)v;
for (int i = 0; i < 3 * TILE_M; i++) {
vf[i] = _mm512_cvtepi32_ps(vv[i]);
}
}
};
template <typename TA, typename TB, typename TC>
struct PartialTiles {
ATile<TA> ta;
BTile<TB> tb;
CTile<TC> tc;
void partial_run(int m, int n, int k, TA* a, size_t lda, TB* b, size_t ldb, TC* c, size_t ldc) {
ta.partial_load(a, m, k, lda);
tb.partial_load(b, n, k, ldb);
tc.partial_load(c, m, n, ldc);
run_full_tile(ta.v, TILE_K, tb.v, TILE_N * VNNI_BLK, tc.v, TILE_N * OUTPUT_T_SIZE);
tc.partial_store(c, m, n, ldc);
}
template <typename QA>
void partial_run_quant(int m, int n, int k, QA* a, size_t lda, block_q8_0* b, size_t ldb, float* c, size_t ldc) {
assert(QK4_0 == 32);
assert(QK8_0 == 32);
ta.partial_load_quant(a, m, k, lda);
tb.partial_load_quant(b, n, k, ldb);
run_full_tile_zero(ta.v, TILE_K, tb.v, TILE_N * VNNI_BLK, tc.v, TILE_N * OUTPUT_T_SIZE);
__m512i* cs = (__m512i*)tc.v;
for (int i = 0; i < m; i++) {
__m512 as = _mm512_set1_ps(GGML_FP16_TO_FP32(offset_pointer_row_major(a, i, 0, lda)->d));
__m512* now = reinterpret_cast<__m512*>(offset_pointer_row_major(c, i, 0, ldc));
*now = _mm512_fmadd_ps(_mm512_mul_ps(as, tb.scale), _mm512_cvtepi32_ps(cs[i]), *now);
}
}
template <typename QA>
void partial_run_quant_ac(int m, int n, int k, QA* a, size_t lda, float* c, size_t ldc) {
assert(QK4_0 == 32);
assert(QK8_0 == 32);
ta.partial_load_quant(a, m, k, lda);
run_full_tile_zero(ta.v, TILE_K, tb.v, TILE_N * VNNI_BLK, tc.v, TILE_N * OUTPUT_T_SIZE);
__m512i* cs = (__m512i*)tc.v;
for (int i = 0; i < m; i++) {
__m512 as = _mm512_set1_ps(GGML_FP16_TO_FP32(offset_pointer_row_major(a, i, 0, lda)->d));
__m512* now = reinterpret_cast<__m512*>(offset_pointer_row_major(c, i, 0, ldc));
*now = _mm512_fmadd_ps(_mm512_mul_ps(as, tb.scale), _mm512_cvtepi32_ps(cs[i]), *now);
}
}
template <typename AQA>
void partial_run_quant_ac(int m, int n, int k, AQA a, int a_blck_stride, float* c, size_t ldc) {
assert(AQA::block_size == 64);
ta.partial_load_quant(a, m, k, a_blck_stride);
run_full_tile_zero(ta.v, TILE_K, tb.v, TILE_N * VNNI_BLK, tc.v, TILE_N * OUTPUT_T_SIZE);
__m512i* cs = (__m512i*)tc.v;
for (int i = 0; i < m; i++) {
__m512 as = _mm512_set1_ps(GGML_FP16_TO_FP32(*a.offset(i * a_blck_stride).d));
// printf("%f\n", GGML_FP16_TO_FP32(*a.offset(i * a_blck_stride).d));
__m512* now = reinterpret_cast<__m512*>(offset_pointer_row_major(c, i, 0, ldc));
*now = _mm512_fmadd_ps(_mm512_mul_ps(as, tb.scale), _mm512_cvtepi32_ps(cs[i]), *now);
}
}
};
template <typename TA, typename TB, typename TC>
struct PartialTilesSum {
ATile<TA> ta;
BTileSum<TB> tb;
CTile<TC> tc;
void partial_run_quant_ac(int m, int n, int inner_block_idx, block_q4_K* a, size_t lda, float* c, size_t ldc,
float a_scale, float a_min) {
ta.partial_load_quant(a, m, inner_block_idx, lda);
run_full_tile_zero(ta.v, TILE_K, tb.v, TILE_N * VNNI_BLK, tc.v, TILE_N * OUTPUT_T_SIZE);
__m512i* cs = (__m512i*)tc.v;
for (int i = 0; i < m; i++) {
__m512* now = reinterpret_cast<__m512*>(offset_pointer_row_major(c, i, 0, ldc));
*now = _mm512_fmadd_ps(_mm512_sub_ps(_mm512_mul_ps(_mm512_cvtepi32_ps(cs[i]), _mm512_set1_ps(a_scale)),
_mm512_mul_ps(tb.sum, _mm512_set1_ps(a_min))),
tb.scale, *now);
// C += Bscale * (Ascale * dp - Amin * Bsum)
}
}
};
};
struct GemmKernel133BF {
using dt = ggml_bf16_t;
using output_t = float;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 32;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 2;
static constexpr int M_STEP = TILE_M * 3;
static constexpr int N_STEP = TILE_N;
static constexpr int K_STEP = TILE_K;
static int recommended_nth(int m) { return (m + M_STEP - 1) / M_STEP; }
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 32
for (int i = 0; i < 3; i++) tile_config.set_row_col(i, TILE_M, TILE_K * sizeof(dt));
// size is 8 x 64
tile_config.set_row_col(3, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK * sizeof(dt));
// size is 16 x 64
for (int i = 4; i < 7; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
static void run_full_tile(const dt* a, size_t lda, const dt* b, size_t ldb, output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
_tile_loadd(2, offset_pointer(a, lda * TILE_M * 2), lda);
_tile_loadd(3, b, ldb);
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, ldc * TILE_N), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_N * 2), ldc);
_tile_dpbf16ps(4, 0, 3);
_tile_dpbf16ps(5, 1, 3);
_tile_dpbf16ps(6, 2, 3);
// debug_tiles(7);
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, ldc * TILE_N), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_N * 2), ldc);
#endif
}
struct ATile {
dt v[3 * TILE_M * TILE_K];
void partial_load(dt* a, int m, int k, size_t lda) {
assert(k == TILE_K);
__m512* vv = (__m512*)v;
__m512* va = (__m512*)a;
for (int i = 0; i < m; i++) {
vv[i] = *offset_pointer_row_major(va, i, 0, lda);
}
}
};
struct alignas(64) BTile {
dt v[TILE_N * TILE_K];
void full_load(dt* b, size_t ldb) { partial_load(b, TILE_N, TILE_K, ldb); }
void partial_load(dt* b, int n, int k, size_t ldb) {
__m512* vv = (__m512*)v;
__m512* vb = (__m512*)b;
for (int i = 0; i < n; i++) {
vv[i] = *offset_pointer_col_major(vb, 0, i, ldb);
}
transpose_16x16_32bit((__m512i*)v);
}
template <typename TA, typename TC>
void run_full_ac(TA* a, size_t lda, TC* c, size_t ldc) {
run_full_tile(a, lda, v, TILE_N * VNNI_BLK * sizeof(dt), c, ldc);
}
};
struct alignas(64) CTile {
output_t v[3 * TILE_M * TILE_N];
// c must be 64 aligned, ldc must be 64 aligned
void partial_load(float* c, int m, int n, size_t ldc) {
assert(n <= TILE_N);
__m512* vv = (__m512*)v;
__m512* vc = (__m512*)c;
for (int i = 0; i < m; i++) {
vv[i] = *offset_pointer_row_major(vc, i, 0, ldc);
}
}
void partial_store(float* c, int m, int n, size_t ldc) {
assert(n <= TILE_N);
__m512* vv = (__m512*)v;
__m512* vc = (__m512*)c;
for (int i = 0; i < m; i++) {
*offset_pointer_row_major(vc, i, 0, ldc) = vv[i];
}
}
};
struct PartialTiles {
ATile ta;
BTile tb;
CTile tc;
void partial_run(int m, int n, int k, dt* a, size_t lda, dt* b, size_t ldb, output_t* c, size_t ldc) {
ta.partial_load(a, m, k, lda);
tb.partial_load(b, n, k, ldb);
tc.partial_load(c, m, n, ldc);
run_full_tile(ta.v, TILE_K * sizeof(dt), tb.v, TILE_N * VNNI_BLK * sizeof(dt), tc.v, TILE_N * sizeof(output_t));
tc.partial_store(c, m, n, ldc);
}
};
};
template <typename T1, typename T2>
constexpr T2 convert_to(const T1& value) {
if constexpr (std::is_same<T1, T2>::value) {
return value;
} else if constexpr (std::is_same<T1, ggml_bf16_t>::value && std::is_same<T2, float>::value) {
return GGML_BF16_TO_FP32(value);
} else if constexpr (std::is_same<T1, float>::value && std::is_same<T2, ggml_bf16_t>::value) {
return GGML_FP32_TO_BF16(value);
}
}
struct GemmKernel224BF {
using dt = ggml_bf16_t;
using output_t = float;
static constexpr double ELEMENT_SIZE = 2;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 32;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 2;
static constexpr int M_STEP = TILE_M * 2;
static constexpr int N_STEP = TILE_N * 2;
static constexpr int K_STEP = TILE_K;
static inline const int N_BLOCK = 256;
static inline const int K_BLOCK = 1792;
static std::string name() { return "BF16"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 32
for (int i = 0; i < 2; i++) tile_config.set_row_col(i, TILE_M, TILE_K * sizeof(dt));
// size is 16 x 32
for (int i = 2; i < 4; i++) tile_config.set_row_col(i, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK * sizeof(dt));
// size is 16 x 16
for (int i = 4; i < 8; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
static void load_a(dt* a, size_t lda) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
#else
(void)a;
(void)lda;
#endif
}
static void load_b(dt* b, size_t ldb) {
#ifdef HAVE_AMX
_tile_loadd(2, b, ldb);
_tile_loadd(3, offset_pointer(b, ldb * TILE_N), ldb);
#else
(void)b;
(void)ldb;
#endif
}
static void clean_c() {
#ifdef HAVE_AMX
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
_tile_zero(7);
#endif
}
static void load_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_loadd(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void store_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_stored(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void run_tile() {
#ifdef HAVE_AMX
_tile_dpbf16ps(4, 0, 2);
_tile_dpbf16ps(5, 0, 3);
_tile_dpbf16ps(6, 1, 2);
_tile_dpbf16ps(7, 1, 3);
#endif
}
struct BufferA {
ggml_bf16_t* a;
int max_m, k;
static size_t required_size(int max_m, int k) { return sizeof(ggml_bf16_t) * max_m * k; }
BufferA(int max_m, int k, void* ptr) : max_m(max_m), k(k) {
assert(reinterpret_cast<intptr_t>(ptr) % 64 == 0);
assert(max_m % M_STEP == 0);
assert(k % K_STEP == 0);
a = reinterpret_cast<ggml_bf16_t*>(ptr);
}
void set_data(void* new_ptr) { a = reinterpret_cast<ggml_bf16_t*>(new_ptr); }
void from_mat(int m, ggml_bf16_t* src, int ith, int nth) {
assert(m <= max_m);
assert(ith == 0 && nth == 1);
int m_block_size = (m + M_STEP - 1) / M_STEP * M_STEP;
for (int m_begin = 0; m_begin < m; m_begin += M_STEP) {
for (int k_block_begin = 0; k_block_begin < k; k_block_begin += K_BLOCK) {
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
for (int k_begin = 0; k_begin < k_block_size; k_begin += K_STEP) {
for (int i = 0; i < M_STEP && m_begin + i < m; i++) {
__m512i* s = (__m512i*)(src + (m_begin + i) * k + k_block_begin + k_begin);
__m512i* d =
(__m512i*)(a + k_block_begin * m_block_size + m_begin * k_block_size + k_begin * M_STEP + i * K_STEP);
avx512_copy_32xbf16(s, d);
}
}
}
}
}
ggml_bf16_t* get_submat(int m, int k, int m_begin, int k_begin) {
int m_block_size = (m + M_STEP - 1) / M_STEP * M_STEP;
int k_block_begin = k_begin / K_BLOCK * K_BLOCK;
k_begin -= k_block_begin;
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
return a + k_block_begin * m_block_size + m_begin * k_block_size + k_begin * M_STEP;
}
};
struct BufferB {
ggml_bf16_t* b;
int n, k;
static constexpr bool SCALE = false;
static size_t required_size(int n, int k) { return sizeof(ggml_bf16_t) * n * k; }
BufferB(int n, int k, void* ptr) : n(n), k(k) {
assert(reinterpret_cast<intptr_t>(ptr) % 64 == 0);
assert(n % N_STEP == 0);
assert(k % K_STEP == 0);
b = reinterpret_cast<ggml_bf16_t*>(ptr);
}
void set_data(void* new_ptr) { b = reinterpret_cast<ggml_bf16_t*>(new_ptr); }
void _pack_block(ggml_bf16_t* src, int src_stride, int n_block_begin, int n_block_size) {
for (int n_begin = 0; n_begin < n_block_size; n_begin += N_STEP) {
for (int k_block_begin = 0; k_block_begin < k; k_block_begin += K_BLOCK) {
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
for (int k_begin = 0; k_begin < k_block_size; k_begin += K_STEP) {
for (int i = 0; i < N_STEP; i++) {
__m512i* s = (__m512i*)(src + (n_begin + i) * src_stride + k_block_begin + k_begin);
__m512i* d = (__m512i*)(b + n_block_begin * k + k_block_begin * n_block_size + n_begin * k_block_size +
k_begin * N_STEP + i * K_STEP);
avx512_copy_32xbf16(s, d);
}
transpose_16x16_32bit((__m512i*)(b + n_block_begin * k + k_block_begin * n_block_size +
n_begin * k_block_size + k_begin * N_STEP));
transpose_16x16_32bit((__m512i*)(b + n_block_begin * k + k_block_begin * n_block_size +
n_begin * k_block_size + k_begin * N_STEP + TILE_N * K_STEP));
}
}
}
}
void from_mat(ggml_bf16_t* src, int ith, int nth) {
auto [n_start, n_end] = split_range_n(n, ith, nth);
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
_pack_block(src + n_block_begin * k, k, n_block_begin, n_block_size);
}
/**
* @brief Pack a transposed matrix into BufferB format.
*
* src is a row-major (src_n, src_k) matrix. The target BufferB has shape (n=src_k, k=src_n),
* i.e., the logical transpose. Each call processes one N_BLOCK of the target (selected by ith/nth).
*
* Uses a thread-local strip buffer for tiled transpose, then reuses the same packing logic as from_mat.
*/
void from_mat_transposed(ggml_bf16_t* src, int src_n, int src_k, int ith, int nth) {
auto [n_start, n_end] = split_range_n(n, ith, nth);
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
if (n_block_size <= 0) return;
// Thread-local strip buffer: n_block_size × k BF16 values
thread_local std::vector<ggml_bf16_t> strip;
strip.resize(n_block_size * k);
// Tiled transpose from source into strip
// Target row r (in N_BLOCK) corresponds to source column (n_block_begin + r)
// Target col c corresponds to source row c
// strip[r * k + c] = src[c * src_k + (n_block_begin + r)]
constexpr int TILE = 32;
for (int c_tile = 0; c_tile < k; c_tile += TILE) {
int c_end = std::min(c_tile + TILE, k);
for (int r_tile = 0; r_tile < n_block_size; r_tile += TILE) {
int r_end = std::min(r_tile + TILE, n_block_size);
for (int c = c_tile; c < c_end; c++) {
for (int r = r_tile; r < r_end; r++) {
strip[r * k + c] = src[c * src_k + (n_block_begin + r)];
}
}
}
}
// Reuse existing packing logic on the transposed strip buffer
_pack_block(strip.data(), k, n_block_begin, n_block_size);
}
/**
* @brief Unpack BF16 BufferB back to row-major BF16 matrix (lossless).
*
* Reverses _pack_block(): un-VNNI-transpose each tile, then copy BF16
* values back to row-major dst[n, k].
*/
void to_mat(ggml_bf16_t* dst, int ith, int nth) {
auto [n_start, n_end] = split_range_n(n, ith, nth);
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
if (n_block_size <= 0) return;
// Thread-local tile buffer for un-VNNI (N_STEP * K_STEP * sizeof(bf16) = 32*32*2 = 2048 bytes)
alignas(64) ggml_bf16_t tile_copy[N_STEP * K_STEP];
for (int n_begin = 0; n_begin < n_block_size; n_begin += N_STEP) {
for (int k_block_begin = 0; k_block_begin < k; k_block_begin += K_BLOCK) {
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
for (int k_begin = 0; k_begin < k_block_size; k_begin += K_STEP) {
ggml_bf16_t* tile_src = b + n_block_begin * k + k_block_begin * n_block_size +
n_begin * k_block_size + k_begin * N_STEP;
// Copy tile and reverse VNNI transpose (self-inverse)
memcpy(tile_copy, tile_src, N_STEP * K_STEP * sizeof(ggml_bf16_t));
transpose_16x16_32bit((__m512i*)tile_copy);
transpose_16x16_32bit((__m512i*)(tile_copy + TILE_N * K_STEP));
// Copy rows back to row-major dst
for (int i = 0; i < N_STEP; i++) {
__m512i* s = (__m512i*)(tile_copy + i * K_STEP);
__m512i* d = (__m512i*)(dst + (n_block_begin + n_begin + i) * k + k_block_begin + k_begin);
avx512_copy_32xbf16(s, d);
}
}
}
}
}
/**
* @brief Direct BufferB → transposed BufferB repack (no BF16 workspace).
*
* src has shape (src.n, src.k), this (dest) has shape (n=src.k, k=src.n).
* For each dest tile: un-VNNI source tile → transpose 32×32 BF16 → re-VNNI → store.
* BF16 is lossless, so this produces bit-identical results to to_mat + from_mat_transposed.
*/
void from_bb_transposed(const BufferB& src, int ith, int nth) {
assert(n == src.k && k == src.n);
auto [n_start, n_end] = split_range_n(n, ith, nth);
int dst_nb_begin = n_start;
int dst_nb_size = n_end - dst_nb_begin;
if (dst_nb_size <= 0) return;
// Helper: compute tile pointer in a packed BF16 BB
auto tile_ptr = [](ggml_bf16_t* base, int total_n, int total_k,
int abs_n, int abs_k) -> ggml_bf16_t* {
int nb_begin = abs_n / N_BLOCK * N_BLOCK;
int n_within = abs_n - nb_begin;
int nb_size = std::min(N_BLOCK, total_n - nb_begin);
int kb_begin = abs_k / K_BLOCK * K_BLOCK;
int k_within = abs_k - kb_begin;
return base + nb_begin * total_k + kb_begin * nb_size +
n_within * std::min(K_BLOCK, total_k - kb_begin) + k_within * N_STEP;
};
alignas(64) ggml_bf16_t src_tile[N_STEP * K_STEP];
alignas(64) ggml_bf16_t dst_tile[N_STEP * K_STEP];
for (int dn = 0; dn < dst_nb_size; dn += N_STEP) {
for (int dk_block = 0; dk_block < k; dk_block += K_BLOCK) {
int dk_block_size = std::min(K_BLOCK, k - dk_block);
for (int dk = 0; dk < dk_block_size; dk += K_STEP) {
int abs_dn = dst_nb_begin + dn;
int abs_dk = dk_block + dk;
// Source tile at (abs_dk, abs_dn): src rows [abs_dk..+32), cols [abs_dn..+32)
ggml_bf16_t* sp = tile_ptr(src.b, src.n, src.k, abs_dk, abs_dn);
memcpy(src_tile, sp, N_STEP * K_STEP * sizeof(ggml_bf16_t));
transpose_16x16_32bit((__m512i*)src_tile);
transpose_16x16_32bit((__m512i*)(src_tile + TILE_N * K_STEP));
// Transpose 32×32 BF16: dst_tile[j][i] = src_tile[i][j]
for (int i = 0; i < N_STEP; i++) {
for (int j = 0; j < K_STEP; j++) {
dst_tile[j * K_STEP + i] = src_tile[i * K_STEP + j];
}
}
// Re-VNNI and store to dest tile at (abs_dn, abs_dk)
transpose_16x16_32bit((__m512i*)dst_tile);
transpose_16x16_32bit((__m512i*)(dst_tile + TILE_N * K_STEP));
ggml_bf16_t* dp = tile_ptr(b, n, k, abs_dn, abs_dk);
memcpy(dp, dst_tile, N_STEP * K_STEP * sizeof(ggml_bf16_t));
}
}
}
}
ggml_bf16_t* get_submat(int n, int k, int n_begin, int k_begin) {
int n_block_begin = n_begin / N_BLOCK * N_BLOCK;
n_begin -= n_block_begin;
int n_block_size = std::min(N_BLOCK, n - n_block_begin);
int k_block_begin = k_begin / K_BLOCK * K_BLOCK;
k_begin -= k_block_begin;
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
return b + n_block_begin * k + k_block_begin * n_block_size + n_begin * k_block_size + k_begin * N_STEP;
}
};
struct BufferC {
float* c;
int max_m, n;
// 物理布局(按 float 元素数)
// 逻辑矩阵 C 为 (max_m, n) 行主序,max_m 为 M_STEP 的倍数,
// n 按 N_BLOCK 分块。
// 存储顺序:
// n_block(N_BLOCK 列) → m_block(M_STEP 行) → n_step(N_STEP 列) → (M_STEP×N_STEP) 行主序 tile。
// 因此可视为 5D
// c[n_blocks][m_blocks][n_steps][M_STEP][N_STEP]
// n_blocks = ceil(n / N_BLOCK)m_blocks = max_m / M_STEP
// n_steps = N_BLOCK / N_STEP(尾块可能更小)。
// get_submat(m_begin, n_begin) 返回连续的 (M_STEP×N_STEP) tile 起始地址。
static size_t required_size(int max_m, int n) { return sizeof(float) * max_m * n; }
BufferC(int max_m, int n, void* ptr) : max_m(max_m), n(n) {
assert(reinterpret_cast<intptr_t>(ptr) % 64 == 0);
assert(max_m % M_STEP == 0);
assert(n % N_STEP == 0);
c = reinterpret_cast<float*>(ptr);
}
void set_data(void* new_ptr) { c = reinterpret_cast<float*>(new_ptr); }
void to_mat(int m, ggml_bf16_t* dst, int ith, int nth) {
assert(m <= max_m);
auto [n_start, n_end] = split_range_n(n, ith, nth);
int m_block_size = (m + M_STEP - 1) / M_STEP * M_STEP;
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
for (int m_begin = 0; m_begin < m; m_begin += M_STEP) {
for (int n_begin = 0; n_begin < n_block_size; n_begin += N_STEP) {
for (int i = 0; i < M_STEP && m_begin + i < m; i++) {
__m512* x0 =
(__m512*)(c + m_block_size * n_block_begin + m_begin * n_block_size + n_begin * M_STEP + i * N_STEP);
__m512* x1 = (__m512*)(c + m_block_size * n_block_begin + m_begin * n_block_size + n_begin * M_STEP +
i * N_STEP + 16);
avx512_32xfp32_to_32xbf16(x0, x1, (__m512i*)(dst + (m_begin + i) * n + n_block_begin + n_begin));
}
}
}
}
float* get_submat(int m, int n, int m_begin, int n_begin) {
int m_block_size = (m + M_STEP - 1) / M_STEP * M_STEP;
int n_block_begin = n_begin / N_BLOCK * N_BLOCK;
int n_block_size = std::min(N_BLOCK, n - n_block_begin);
n_begin -= n_block_begin;
return c + m_block_size * n_block_begin + m_begin * n_block_size + n_begin * M_STEP;
}
};
};
struct GemmKernel224Int8 {
using dt = int8_t;
using output_t = int32_t;
static constexpr double ELEMENT_SIZE = 1;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 64;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 4;
static constexpr int M_STEP = TILE_M * 2;
static constexpr int N_STEP = TILE_N * 2;
static constexpr int K_STEP = TILE_K;
// static inline const int N_BLOCK = 256;
static inline const int N_BLOCK = 64;
// static inline const int N_BLOCK = 32;
static inline const int K_BLOCK = 3584;
static std::string name() { return "INT8"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 64
for (int i = 0; i < 2; i++) tile_config.set_row_col(i, TILE_M, TILE_K * sizeof(dt));
// size is 16 x 64
for (int i = 2; i < 4; i++) tile_config.set_row_col(i, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK * sizeof(dt));
// size is 16 x 16
for (int i = 4; i < 8; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
static void load_a(dt* a, size_t lda) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
#else
(void)a;
(void)lda;
#endif
}
static void load_b(dt* b, size_t ldb) {
#ifdef HAVE_AMX
_tile_loadd(2, b, ldb);
_tile_loadd(3, offset_pointer(b, ldb * TILE_N), ldb);
#else
(void)b;
(void)ldb;
#endif
}
static void clean_c() {
#ifdef HAVE_AMX
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
_tile_zero(7);
#endif
}
static void load_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_loadd(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void store_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_stored(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void run_tile() {
#ifdef HAVE_AMX
_tile_dpbssd(4, 0, 2);
_tile_dpbssd(5, 0, 3);
_tile_dpbssd(6, 1, 2);
_tile_dpbssd(7, 1, 3);
#endif
}
using BufferA = BufferAImpl<GemmKernel224Int8>;
using BufferC = BufferCImpl<GemmKernel224Int8>;
struct BufferB {
int8_t* b;
float* d;
int n, k;
static constexpr bool SCALE = true;
static size_t required_size(int n, int k) { return sizeof(int8_t) * n * k + sizeof(float) * n; }
BufferB(int n, int k, void* ptr) : n(n), k(k) {
assert(reinterpret_cast<intptr_t>(ptr) % 64 == 0);
assert(n % N_STEP == 0);
assert(k % K_STEP == 0);
if (n % N_STEP || k % K_STEP) {
printf("n: %d, k: %d, N_STEP: %d, K_STEP: %d\n", n, k, N_STEP, K_STEP);
throw std::runtime_error("BufferB: n and k must be multiples of N_STEP and K_STEP");
}
b = reinterpret_cast<int8_t*>(ptr);
d = reinterpret_cast<float*>(b + n * k);
}
void _pack_block(ggml_bf16_t* src_data, int src_stride, int n_block_begin, int n_block_size) {
// Phase 1: compute per-row scales
for (int n_begin = 0; n_begin < n_block_size; n_begin += N_STEP) {
for (int i = 0; i < N_STEP; i++) {
float amax = 0.0f;
for (int j = 0; j < k; j += 32) {
__m512 f0, f1;
avx512_32xbf16_to_32xfp32((__m512i*)(src_data + (n_begin + i) * src_stride + j), &f0, &f1);
amax = MAX(amax, _mm512_reduce_max_ps(_mm512_abs_ps(f0)));
amax = MAX(amax, _mm512_reduce_max_ps(_mm512_abs_ps(f1)));
}
d[n_block_begin + n_begin + i] = amax / ((1 << 7) - 1);
}
}
// Phase 2: quantize and pack
for (int n_begin = 0; n_begin < n_block_size; n_begin += N_STEP) {
for (int k_block_begin = 0; k_block_begin < k; k_block_begin += K_BLOCK) {
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
for (int k_begin = 0; k_begin < k_block_size; k_begin += K_STEP) {
for (int i = 0; i < N_STEP; i++) {
__m512 id = _mm512_set1_ps(d[n_block_begin + n_begin + i] ? 1.0f / d[n_block_begin + n_begin + i] : 0.0f);
int8_t* dst = b + n_block_begin * k + k_block_begin * n_block_size + n_begin * k_block_size +
k_begin * N_STEP + i * K_STEP;
__m512 f0, f1, f2, f3;
avx512_32xbf16_to_32xfp32((__m512i*)(src_data + (n_begin + i) * src_stride + k_block_begin + k_begin),
&f0, &f1);
avx512_32xbf16_to_32xfp32((__m512i*)(src_data + (n_begin + i) * src_stride + k_block_begin + k_begin) + 1,
&f2, &f3);
__m512i i0 = _mm512_cvtps_epi32(_mm512_mul_ps(f0, id));
__m512i i1 = _mm512_cvtps_epi32(_mm512_mul_ps(f1, id));
__m512i i2 = _mm512_cvtps_epi32(_mm512_mul_ps(f2, id));
__m512i i3 = _mm512_cvtps_epi32(_mm512_mul_ps(f3, id));
__m128i s0 = _mm512_cvtsepi32_epi8(i0);
__m128i s1 = _mm512_cvtsepi32_epi8(i1);
__m128i s2 = _mm512_cvtsepi32_epi8(i2);
__m128i s3 = _mm512_cvtsepi32_epi8(i3);
_mm_store_si128((__m128i*)dst, s0);
_mm_store_si128((__m128i*)(dst + 16), s1);
_mm_store_si128((__m128i*)(dst + 32), s2);
_mm_store_si128((__m128i*)(dst + 48), s3);
}
transpose_16x16_32bit((__m512i*)(b + n_block_begin * k + k_block_begin * n_block_size +
n_begin * k_block_size + k_begin * N_STEP));
transpose_16x16_32bit((__m512i*)(b + n_block_begin * k + k_block_begin * n_block_size +
n_begin * k_block_size + k_begin * N_STEP + TILE_N * K_STEP));
}
}
}
}
void from_mat(ggml_bf16_t* src, int ith, int nth) {
auto [n_start, n_end] = split_range_n(n, ith, nth);
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
_pack_block(src + (size_t)n_block_begin * k, k, n_block_begin, n_block_size);
}
/**
* @brief Pack a transposed matrix into INT8 BufferB format.
*
* src is a row-major (src_n, src_k) BF16 matrix. The target BufferB has shape (n=src_k, k=src_n).
* Each call processes one N_BLOCK of the target (selected by ith/nth).
*/
void from_mat_transposed(ggml_bf16_t* src, int src_n, int src_k, int ith, int nth) {
auto [n_start, n_end] = split_range_n(n, ith, nth);
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
if (n_block_size <= 0) return;
// Thread-local strip buffer: n_block_size × k BF16 values
thread_local std::vector<ggml_bf16_t> strip;
strip.resize(n_block_size * k);
// Tiled transpose from source into strip
constexpr int TILE = 32;
for (int c_tile = 0; c_tile < k; c_tile += TILE) {
int c_end = std::min(c_tile + TILE, k);
for (int r_tile = 0; r_tile < n_block_size; r_tile += TILE) {
int r_end = std::min(r_tile + TILE, n_block_size);
for (int c = c_tile; c < c_end; c++) {
for (int r = r_tile; r < r_end; r++) {
strip[r * k + c] = src[c * src_k + (n_block_begin + r)];
}
}
}
}
// Reuse existing packing logic (scale computation + quantization) on the transposed strip buffer
_pack_block(strip.data(), k, n_block_begin, n_block_size);
}
/**
* @brief Dequantize INT8 BufferB back to BF16 row-major matrix.
*
* Reverses _pack_block(): un-VNNI-transpose each tile, then dequantize
* int8 * per-row-scale -> float -> BF16.
*
* dst is a row-major (n, k) BF16 matrix. Each call processes one N_BLOCK
* partition (selected by ith/nth).
*/
void to_mat(ggml_bf16_t* dst, int ith, int nth) {
auto [n_start, n_end] = split_range_n(n, ith, nth);
int n_block_begin = n_start;
int n_block_size = n_end - n_block_begin;
if (n_block_size <= 0) return;
// Thread-local tile buffer for un-VNNI (N_STEP * K_STEP = 32 * 64 = 2048 bytes)
alignas(64) int8_t tile_copy[N_STEP * K_STEP];
for (int n_begin = 0; n_begin < n_block_size; n_begin += N_STEP) {
for (int k_block_begin = 0; k_block_begin < k; k_block_begin += K_BLOCK) {
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
for (int k_begin = 0; k_begin < k_block_size; k_begin += K_STEP) {
int8_t* tile_src = b + n_block_begin * k + k_block_begin * n_block_size +
n_begin * k_block_size + k_begin * N_STEP;
// Copy tile and reverse VNNI transpose (transpose_16x16_32bit is self-inverse)
memcpy(tile_copy, tile_src, N_STEP * K_STEP);
transpose_16x16_32bit((__m512i*)tile_copy);
transpose_16x16_32bit((__m512i*)(tile_copy + TILE_N * K_STEP));
// tile_copy is now in original row-major int8 order:
// tile_copy[i * K_STEP + j] = quantized value at logical row (n_begin+i), col (k_begin+j)
// SIMD dequant: 16 int8 -> 16 fp32 (* scale) -> 16 bf16, 4 iterations per row (K_STEP=64)
for (int i = 0; i < N_STEP; i++) {
__m512 vs = _mm512_set1_ps(d[n_block_begin + n_begin + i]);
ggml_bf16_t* dst_ptr = dst + (n_block_begin + n_begin + i) * k + k_block_begin + k_begin;
int8_t* src_ptr = tile_copy + i * K_STEP;
for (int j = 0; j < K_STEP; j += 32) {
// Convert 16 int8 -> 16 int32 -> 16 fp32, multiply scale, convert to bf16
__m128i i8_0 = _mm_load_si128((__m128i*)(src_ptr + j));
__m128i i8_1 = _mm_load_si128((__m128i*)(src_ptr + j + 16));
__m512i i32_0 = _mm512_cvtepi8_epi32(i8_0);
__m512i i32_1 = _mm512_cvtepi8_epi32(i8_1);
__m512 f0 = _mm512_mul_ps(_mm512_cvtepi32_ps(i32_0), vs);
__m512 f1 = _mm512_mul_ps(_mm512_cvtepi32_ps(i32_1), vs);
avx512_32xfp32_to_32xbf16(&f0, &f1, (__m512i*)(dst_ptr + j));
}
}
}
}
}
}
/**
* @brief Direct INT8 BufferB → transposed INT8 BufferB (no BF16 workspace).
*
* src has shape (src.n, src.k), this (dest) has shape (n=src.k, k=src.n).
* Two-pass algorithm with register-based 16×16 sub-block transposes:
* Pass 1: SIMD absmax scan → per-dest-row scales d[j]
* Pass 2: 8 sub-blocks of 16×16: dequant → register transpose → quantize → VNNI-pack
*/
void from_bb_transposed(const BufferB& src, int ith, int nth) {
assert(n == src.k && k == src.n);
auto [n_start, n_end] = split_range_n(n, ith, nth);
int dst_nb_begin = n_start;
int dst_nb_size = n_end - dst_nb_begin;
if (dst_nb_size <= 0) return;
auto tile_ptr = [](int8_t* base, int total_n, int total_k,
int abs_n, int abs_k) -> int8_t* {
int nb_begin = abs_n / N_BLOCK * N_BLOCK;
int n_within = abs_n - nb_begin;
int nb_size = std::min(N_BLOCK, total_n - nb_begin);
int kb_begin = abs_k / K_BLOCK * K_BLOCK;
int k_within = abs_k - kb_begin;
return base + nb_begin * total_k + kb_begin * nb_size +
n_within * std::min(K_BLOCK, total_k - kb_begin) + k_within * N_STEP;
};
alignas(64) int8_t tile_copy[N_STEP * K_STEP]; // 2KB un-VNNI workspace
// === Pass 1: SIMD per-dest-row absmax ===
alignas(64) float absmax_arr[N_BLOCK];
memset(absmax_arr, 0, dst_nb_size * sizeof(float));
int c_start = (dst_nb_begin / K_STEP) * K_STEP;
int c_end_limit = dst_nb_begin + dst_nb_size;
for (int src_c = c_start; src_c < c_end_limit; src_c += K_STEP) {
int col_lo = std::max(dst_nb_begin, src_c);
int local_lo = col_lo - src_c;
int buf_offset = col_lo - dst_nb_begin;
int ncols = std::min(c_end_limit, src_c + K_STEP) - col_lo;
int nchunks = ncols / 16;
__m512 amax[4];
for (int c = 0; c < nchunks; c++)
amax[c] = _mm512_setzero_ps();
for (int src_r = 0; src_r < src.n; src_r += N_STEP) {
int8_t* sp = tile_ptr(src.b, src.n, src.k, src_r, src_c);
memcpy(tile_copy, sp, N_STEP * K_STEP);
transpose_16x16_32bit((__m512i*)tile_copy);
transpose_16x16_32bit((__m512i*)(tile_copy + TILE_N * K_STEP));
for (int i = 0; i < N_STEP; i++) {
float abs_scale = src.d[src_r + i];
abs_scale = abs_scale >= 0 ? abs_scale : -abs_scale;
__m512 vs = _mm512_set1_ps(abs_scale);
int8_t* row = tile_copy + i * K_STEP + local_lo;
for (int c = 0; c < nchunks; c++) {
__m128i i8_16 = _mm_load_si128((__m128i*)(row + c * 16));
__m512i abs_i32 = _mm512_abs_epi32(_mm512_cvtepi8_epi32(i8_16));
amax[c] = _mm512_max_ps(amax[c],
_mm512_mul_ps(_mm512_cvtepi32_ps(abs_i32), vs));
}
}
}
for (int c = 0; c < nchunks; c++)
_mm512_store_ps(absmax_arr + buf_offset + c * 16, amax[c]);
}
for (int j = 0; j < dst_nb_size; j++)
d[dst_nb_begin + j] = absmax_arr[j] / 127.0f;
// === Pass 2: register-based 16×16 sub-block transpose ===
alignas(64) int8_t quant_tile[N_STEP * K_STEP]; // 2KB
for (int dn = 0; dn < dst_nb_size; dn += N_STEP) {
for (int dk_block = 0; dk_block < k; dk_block += K_BLOCK) {
int dk_block_size = std::min(K_BLOCK, k - dk_block);
for (int dk = 0; dk < dk_block_size; dk += K_STEP) {
int abs_dn = dst_nb_begin + dn;
int abs_dk = dk_block + dk;
int c_align = (abs_dn / K_STEP) * K_STEP;
int c_offset = abs_dn - c_align;
for (int half = 0; half < 2; half++) {
int src_r = abs_dk + half * N_STEP;
int8_t* sp = tile_ptr(src.b, src.n, src.k, src_r, c_align);
memcpy(tile_copy, sp, N_STEP * K_STEP);
transpose_16x16_32bit((__m512i*)tile_copy);
transpose_16x16_32bit((__m512i*)(tile_copy + TILE_N * K_STEP));
for (int src_rb = 0; src_rb < N_STEP; src_rb += 16) {
for (int src_cb = 0; src_cb < N_STEP; src_cb += 16) {
// Load 16×16 int8 sub-block, dequant to float in registers
__m512i regs[16];
for (int i = 0; i < 16; i++) {
int8_t* addr = tile_copy + (src_rb + i) * K_STEP + c_offset + src_cb;
float scale = src.d[src_r + src_rb + i];
__m512i i32 = _mm512_cvtepi8_epi32(_mm_load_si128((__m128i*)addr));
regs[i] = _mm512_castps_si512(
_mm512_mul_ps(_mm512_cvtepi32_ps(i32), _mm512_set1_ps(scale)));
}
// Transpose 16×16 in registers (32-bit element shuffle)
transpose_16x16_32bit(regs);
// Quantize transposed floats and store to quant_tile
int dest_rb = src_cb; // 0 or 16
int dest_cb = half * 32 + src_rb; // 0, 16, 32, or 48
for (int i = 0; i < 16; i++) {
float sv = d[abs_dn + dest_rb + i];
float id = sv ? 1.0f / sv : 0.0f;
__m512i q = _mm512_cvtps_epi32(
_mm512_mul_ps(_mm512_castsi512_ps(regs[i]), _mm512_set1_ps(id)));
_mm_store_si128(
(__m128i*)(quant_tile + (dest_rb + i) * K_STEP + dest_cb),
_mm512_cvtsepi32_epi8(q));
}
}
}
}
// VNNI pack
transpose_16x16_32bit((__m512i*)quant_tile);
transpose_16x16_32bit((__m512i*)(quant_tile + TILE_N * K_STEP));
// Write to dest BB
int8_t* dp = b + dst_nb_begin * k + dk_block * dst_nb_size +
dn * dk_block_size + dk * N_STEP;
memcpy(dp, quant_tile, N_STEP * K_STEP);
}
}
}
}
int8_t* get_submat(int n, int k, int n_begin, int k_begin) {
int n_block_begin = n_begin / N_BLOCK * N_BLOCK;
n_begin -= n_block_begin;
int n_block_size = std::min(N_BLOCK, n - n_block_begin);
int k_block_begin = k_begin / K_BLOCK * K_BLOCK;
k_begin -= k_block_begin;
int k_block_size = std::min(K_BLOCK, k - k_block_begin);
return b + n_block_begin * k + k_block_begin * n_block_size + n_begin * k_block_size + k_begin * N_STEP;
}
float* get_scale(int n, int n_begin) { return d + n_begin; }
};
static void amx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, float* c, BufferA* ba,
BufferB* bb) {
using K = GemmKernel224Int8;
if (k_block_begin == 0) {
K::clean_c();
} else {
K::load_c((int32_t*)c, K::N_STEP * sizeof(int32_t));
}
for (int k_begin = 0; k_begin < K::K_BLOCK && k_block_begin + k_begin < k; k_begin += K::K_STEP) {
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin + k_begin), K::K_STEP * sizeof(int8_t));
K::load_b(bb->get_submat(n, k, n_begin, k_block_begin + k_begin), K::K_STEP * sizeof(int8_t));
K::run_tile();
}
K::store_c((int32_t*)c, K::N_STEP * sizeof(int32_t));
}
static void avx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, float* c, BufferA* ba,
BufferB* bb) {
__m512i* c512 = (__m512i*)c;
int m_block_end = std::min(m - m_begin, M_STEP);
if (k_block_begin == 0) {
for (int m_i = 0; m_i < m_block_end; m_i++) {
c512[m_i * 2] = _mm512_setzero_si512();
c512[m_i * 2 + 1] = _mm512_setzero_si512();
}
}
for (int k_begin = 0; k_begin < K_BLOCK && k_block_begin + k_begin < k; k_begin += K_STEP) {
static_assert(K_STEP * sizeof(int8_t) == sizeof(__m512i));
static_assert(N_STEP / TILE_N == 2, "Must be lke this");
int32_t* a32 = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin + k_begin);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin + k_begin);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma = _mm512_set1_epi32(a32[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
c512[m_i * 2 + n_i] = _mm512_dpbssd_epi32(c512[m_i * 2 + n_i], ma, b512[n_i * 16 + k_i]);
}
}
}
}
}
static void apply_scale(int m, int n, int m_begin, int n_begin, float* c, BufferA* ba, BufferB* bb) {
using K = GemmKernel224Int8;
int to = m - m_begin;
if (m - m_begin > K::M_STEP) {
to = K::M_STEP;
}
for (int i = 0; i < to; i++) {
__m512 as = _mm512_set1_ps(*ba->get_scale(m, m_begin + i));
__m512 bs = _mm512_load_ps(bb->get_scale(n, n_begin));
__m512i now = _mm512_load_si512((__m512i*)(c + i * K::N_STEP));
__m512 result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
_mm512_store_ps((__m512*)(c + i * K::N_STEP), result);
bs = _mm512_load_ps(bb->get_scale(n, n_begin) + K::TILE_N);
now = _mm512_load_si512((__m512i*)(c + i * K::N_STEP + K::TILE_N));
result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
_mm512_store_ps((__m512*)(c + i * K::N_STEP + K::TILE_N), result);
}
}
};
struct GemmKernel224Int4 {
using dt = void;
using output_t = int32_t;
static constexpr double ELEMENT_SIZE = 0.5;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 64;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 4;
static constexpr int M_STEP = TILE_M * 2;
static constexpr int N_STEP = TILE_N * 2;
static constexpr int K_STEP = TILE_K;
// static inline const int N_BLOCK = 256;
static inline const int N_BLOCK = 128;
// static inline const int N_BLOCK = 64;
// static inline const int K_BLOCK = 7168;
static inline const int K_BLOCK = 3584;
// static inline const int K_BLOCK = 2560;
static std::string name() { return "INT4"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 64
for (int i = 0; i < 2; i++) tile_config.set_row_col(i, TILE_M, TILE_K);
// size is 16 x 64
for (int i = 2; i < 4; i++) tile_config.set_row_col(i, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK);
// size is 16 x 16
for (int i = 4; i < 8; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
alignas(64) static constexpr uint8_t hi_mask_arr[64] = {
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
alignas(64) static constexpr uint8_t lo_mask_arr[64] = {
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F};
alignas(64) static constexpr uint8_t sign_mask_arr[64] = {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
};
static __m512i hi_mask() { return *((__m512i*)(&hi_mask_arr[0])); }
static __m128i hi_mask_128() { return *((__m128i*)(&hi_mask_arr[0])); }
static __m512i lo_mask() { return *((__m512i*)(&lo_mask_arr[0])); }
static __m128i lo_mask_128() { return *((__m128i*)(&lo_mask_arr[0])); }
static __m128i si_mask_128() { return *((__m128i*)(&sign_mask_arr[0])); }
static void load_b_hi(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i)));
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N))));
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_b_lo(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_slli_epi32(_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_slli_epi32(
_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N)))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_a(dt* a, size_t lda) {
#ifdef HAVE_AMX
_tile_stream_loadd(0, a, lda);
_tile_stream_loadd(1, offset_pointer(a, lda * TILE_M), lda);
#else
(void)a;
(void)lda;
#endif
}
static void clean_c() {
#ifdef HAVE_AMX
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
_tile_zero(7);
#endif
}
static void load_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_loadd(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void store_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_stored(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void run_tile() {
#ifdef HAVE_AMX
_tile_dpbssd(4, 0, 2);
_tile_dpbssd(5, 0, 3);
_tile_dpbssd(6, 1, 2);
_tile_dpbssd(7, 1, 3);
#endif
}
using BufferA = BufferAImpl<GemmKernel224Int4>;
using BufferB = BufferBInt4Impl<GemmKernel224Int4>;
using BufferC = BufferCImpl<GemmKernel224Int4>;
static void avx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, float* c, BufferA* ba,
BufferB* bb) {
using K = GemmKernel224Int4;
__m512i* c512 = (__m512i*)c;
int m_block_end = std::min(m - m_begin, M_STEP);
if (k_block_begin == 0) {
for (int m_i = 0; m_i < m_block_end; m_i++) {
c512[m_i * 2] = _mm512_setzero_si512();
c512[m_i * 2 + 1] = _mm512_setzero_si512();
}
}
for (int k_begin = 0; k_begin < K::K_BLOCK && k_block_begin + k_begin < k; k_begin += K::BufferB::B_K_STEP) {
int32_t* a32_lo = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin + k_begin);
int32_t* a32_hi = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin + k_begin + K::K_STEP);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin + k_begin);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_lo = _mm512_set1_epi32(a32_lo[m_i * 16 + k_i]);
__m512i ma_hi = _mm512_set1_epi32(a32_hi[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_lo = _mm512_slli_epi32(_mm512_and_si512(K::lo_mask(), b512[n_i * 16 + k_i]), 4);
c512[m_i * 2 + n_i] = _mm512_dpbssd_epi32(c512[m_i * 2 + n_i], ma_lo, b512_lo);
__m512i b512_hi = _mm512_and_si512(K::hi_mask(), b512[n_i * 16 + k_i]);
c512[m_i * 2 + n_i] = _mm512_dpbssd_epi32(c512[m_i * 2 + n_i], ma_hi, b512_hi);
}
}
}
}
}
static void amx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, float* c, BufferA* ba,
BufferB* bb) {
using K = GemmKernel224Int4;
if (k_block_begin == 0) {
K::clean_c();
} else {
// printf("load from c int4\n");
K::load_c((int32_t*)c, K::N_STEP * sizeof(int32_t));
}
for (int k_begin = 0; k_begin < K::K_BLOCK && k_block_begin + k_begin < k; k_begin += K::BufferB::B_K_STEP) {
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin + k_begin), K::K_STEP * sizeof(int8_t));
K::load_b_lo(bb->get_submat(n, k, n_begin, k_block_begin + k_begin), K::BufferB::B_K_STEP / 2);
K::run_tile();
// DEBUG
// if(m_begin == 0 && n_begin == 0 && k_begin==0){
// int8_t *ba_ptr = ba->get_submat(m, k, m_begin, k_block_begin + k_begin);
// int8_t *bb_ptr = (int8_t *)bb->get_submat(n, k, n_begin, k_block_begin + k_begin);
// printf("k_begin:%d,k_block_begin:%d\n",k_begin,k_block_begin);
// for(int j=0;j<4096;j++){
// printf("a[%d]: %d ", j, ba_ptr[j]);
// }
// printf("\n");
// for(int j=0;j<4096;j++){
// printf("b[%d]: %d ", j, bb_ptr[j]);
// }
// printf("\n");
// }
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin + k_begin + K::K_STEP), K::K_STEP * sizeof(int8_t));
K::load_b_hi(bb->get_submat(n, k, n_begin, k_block_begin + k_begin), K::BufferB::B_K_STEP / 2);
K::run_tile();
}
// debug_tiles_224();
K::store_c((int32_t*)c, K::N_STEP * sizeof(int32_t));
// DEBUG c 的值,第一行的前 30 列
// printf("\nint4, m_begin:%d,n_begin:%d,k_block_begin:%d\n",m_begin,n_begin,k_block_begin);
// for(int j=0;j<30;j++){
// printf("c[%d]: %d ", j, ((int32_t *)c)[j]);
// }
// printf("\n");
}
static void apply_scale(int m, int n, int m_begin, int n_begin, float* c, BufferA* ba, BufferB* bb) {
using K = GemmKernel224Int4;
int to = m - m_begin;
if (m - m_begin > K::M_STEP) {
to = K::M_STEP;
}
for (int i = 0; i < to; i++) {
__m512 as = _mm512_set1_ps(*ba->get_scale(m, m_begin + i));
__m512 bs = _mm512_load_ps(bb->get_scale(n, n_begin));
__m512i now = _mm512_load_epi32((__m512i*)(c + i * K::N_STEP));
__m512 result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
// if(i==0){
// printf("\nnormal\n");
// printf("m_begin:%d,n_begin:%d\n", m_begin, n_begin);
// // 打印 result 结果,16 个 float 数值
// for(int j = 0; j < 16; j++) {
// float val = *((float *) &result + j);
// int32_t now_val = *((int32_t *) &now + j);
// printf("result[%d]: %f,now:%d ", j, val, now_val);
// }
// printf("\n");
// }
_mm512_store_ps((__m512*)(c + i * K::N_STEP), result);
bs = _mm512_load_ps(bb->get_scale(n, n_begin) + K::TILE_N);
now = _mm512_load_si512((__m512i*)(c + i * K::N_STEP + K::TILE_N));
result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
// if(i==0){
// printf("\nnormal\n");
// printf("m_begin:%d,n_begin:%d\n", m_begin, n_begin);
// // 打印 result 结果,16 个 float 数值
// for(int j = 0; j < 16; j++) {
// float val = *((float *) &result + j);
// int32_t now_val = *((int32_t *) &now + j);
// printf("result[%d]: %f,now:%d ", j+16, val, now_val);
// }
// printf("\n");
// }
_mm512_store_ps((__m512*)(c + i * K::N_STEP + K::TILE_N), result);
}
}
};
struct GemmKernel224Int4_1 {
using dt = void;
using output_t = int32_t;
static constexpr double ELEMENT_SIZE = 0.5;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 64;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 4;
static constexpr int M_STEP = TILE_M * 2;
static constexpr int N_STEP = TILE_N * 2;
static constexpr int K_STEP = TILE_K;
static inline const int N_BLOCK = 256;
// static inline const int K_BLOCK = 7168;
static inline const int K_BLOCK = 3584;
// static inline const int K_BLOCK = 2560;
static std::string name() { return "INT4_1"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 64
for (int i = 0; i < 2; i++) tile_config.set_row_col(i, TILE_M, TILE_K);
// size is 16 x 64
for (int i = 2; i < 4; i++) tile_config.set_row_col(i, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK);
// size is 16 x 16
for (int i = 4; i < 8; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
alignas(64) static constexpr uint8_t hi_mask_arr[64] = {
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
alignas(64) static constexpr uint8_t lo_mask_arr[64] = {
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F};
alignas(64) static constexpr uint8_t sign_mask_arr[64] = {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
};
static __m512i hi_mask() { return *((__m512i*)(&hi_mask_arr[0])); }
static __m128i hi_mask_128() { return *((__m128i*)(&hi_mask_arr[0])); }
static __m512i lo_mask() { return *((__m512i*)(&lo_mask_arr[0])); }
static __m128i lo_mask_128() { return *((__m128i*)(&lo_mask_arr[0])); }
static __m128i si_mask_128() { return *((__m128i*)(&sign_mask_arr[0])); }
static void load_b_hi(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i)));
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N))));
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_b_lo(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_slli_epi32(_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_slli_epi32(
_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N)))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_a(dt* a, size_t lda) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
#else
(void)a;
(void)lda;
#endif
}
// static void load_b(dt* b, size_t ldb) {
// _tile_loadd(2, b, ldb);
// _tile_loadd(3, offset_pointer(b, ldb * TILE_N), ldb);
// }
static void clean_c() {
#ifdef HAVE_AMX
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
_tile_zero(7);
#endif
}
static void load_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_loadd(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void store_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_stored(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void run_tile() {
#ifdef HAVE_AMX
_tile_dpbsud(4, 0, 2);
_tile_dpbsud(5, 0, 3);
_tile_dpbsud(6, 1, 2);
_tile_dpbsud(7, 1, 3);
#endif
}
using BufferA = BufferAWithSumImpl<GemmKernel224Int4_1>;
using BufferB = BufferBInt4WithZeroImpl<GemmKernel224Int4_1>;
using BufferC = BufferCImpl<GemmKernel224Int4_1>;
static void avx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, float* c, BufferA* ba,
BufferB* bb) {
using K = GemmKernel224Int4_1;
__m512i* c512 = (__m512i*)c;
int m_block_end = std::min(m - m_begin, M_STEP);
if (k_block_begin == 0) {
for (int m_i = 0; m_i < m_block_end; m_i++) {
c512[m_i * 2] = _mm512_setzero_si512();
c512[m_i * 2 + 1] = _mm512_setzero_si512();
}
}
for (int k_begin = 0; k_begin < K::K_BLOCK && k_block_begin + k_begin < k; k_begin += K::BufferB::B_K_STEP) {
int32_t* a32_lo = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin + k_begin);
int32_t* a32_hi = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin + k_begin + K::K_STEP);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin + k_begin);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_lo = _mm512_set1_epi32(a32_lo[m_i * 16 + k_i]);
__m512i ma_hi = _mm512_set1_epi32(a32_hi[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_lo = _mm512_slli_epi32(_mm512_and_si512(K::lo_mask(), b512[n_i * 16 + k_i]), 4);
c512[m_i * 2 + n_i] = _mm512_dpbusd_epi32_compat(c512[m_i * 2 + n_i], b512_lo, ma_lo);
__m512i b512_hi = _mm512_and_si512(K::hi_mask(), b512[n_i * 16 + k_i]);
c512[m_i * 2 + n_i] = _mm512_dpbusd_epi32_compat(c512[m_i * 2 + n_i], b512_hi, ma_hi);
}
}
}
}
}
static void amx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, float* c, BufferA* ba,
BufferB* bb) {
using K = GemmKernel224Int4_1;
if (k_block_begin == 0) {
K::clean_c();
} else {
K::load_c((int32_t*)c, K::N_STEP * sizeof(int32_t));
}
for (int k_begin = 0; k_begin < K::K_BLOCK && k_block_begin + k_begin < k; k_begin += K::BufferB::B_K_STEP) {
// printf("offset a %ld\n", pointer_offset(ba->get_submat(m, k, m_begin, k_block_begin + k_begin),
// ba->a)); printf("offset b %ld\n", pointer_offset(bb->get_submat(n, k, n_begin, k_block_begin +
// k_begin), bb->b));
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin + k_begin), K::K_STEP * sizeof(int8_t));
K::load_b_lo(bb->get_submat(n, k, n_begin, k_block_begin + k_begin), K::BufferB::B_K_STEP / 2);
K::run_tile();
// DEBUG
// if(m_begin == 0 && n_begin == 0 && k_begin==0){
// int8_t *ba_ptr = ba->get_submat(m, k, m_begin, k_block_begin + k_begin);
// int8_t *bb_ptr = (int8_t *)bb->get_submat(n, k, n_begin, k_block_begin + k_begin);
// printf("k_begin:%d,k_block_begin:%d\n",k_begin,k_block_begin);
// for(int j=0;j<2048;j++){
// printf("a[%d]: %d ", j, ba_ptr[j]);
// }
// printf("\n");
// for(int j=0;j<2048;j++){
// printf("b[%d]: %d ", j, bb_ptr[j]);
// }
// printf("\n");
// }
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin + k_begin + K::K_STEP), K::K_STEP * sizeof(int8_t));
K::load_b_hi(bb->get_submat(n, k, n_begin, k_block_begin + k_begin), K::BufferB::B_K_STEP / 2);
K::run_tile();
}
// debug_tiles_224();
K::store_c((int32_t*)c, K::N_STEP * sizeof(int32_t));
// DEBUG c 的值,第一行的前 30 列
// printf("\nint4_1, m_begin:%d,n_begin:%d,k_block_begin:%d\n",m_begin,n_begin,k_block_begin);
// for(int j=0;j<30;j++){
// printf("c[%d]: %d ", j, ((int32_t *)c)[j]);
// }
// printf("\n");
}
static void apply_scale(int m, int n, int m_begin, int n_begin, float* c, BufferA* ba, BufferB* bb) {
using K = GemmKernel224Int4_1;
int to = m - m_begin;
if (m - m_begin > K::M_STEP) {
to = K::M_STEP;
}
for (int i = 0; i < to; i++) {
__m512 as = _mm512_set1_ps(*ba->get_scale(m, m_begin + i));
__m512 asum = _mm512_set1_ps(*ba->get_sum(m, m_begin + i));
__m512 bs = _mm512_load_ps(bb->get_scale(n, n_begin));
__m512 b_mins = _mm512_load_ps(bb->get_min(n, n_begin));
__m512i now = _mm512_load_epi32((__m512i*)(c + i * K::N_STEP));
__m512 result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
result = _mm512_add_ps(result, _mm512_mul_ps(asum, b_mins));
_mm512_store_ps((__m512*)(c + i * K::N_STEP), result);
bs = _mm512_load_ps(bb->get_scale(n, n_begin) + K::TILE_N);
b_mins = _mm512_load_ps(bb->get_min(n, n_begin) + K::TILE_N);
now = _mm512_load_si512((__m512i*)(c + i * K::N_STEP + K::TILE_N));
result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
result = _mm512_add_ps(result, _mm512_mul_ps(asum, b_mins));
_mm512_store_ps((__m512*)(c + i * K::N_STEP + K::TILE_N), result);
}
}
};
template <typename TA, typename TB, typename TC>
void mat_mul_single(int m, int n, int k, TA* a, size_t lda, TB* b, size_t ldb, TC* c, size_t ldc);
template <>
inline void mat_mul_single(int m, int n, int k, int8_t* a, size_t lda, int8_t* b, size_t ldb, int32_t* c, size_t ldc) {
using Kernel = GemmKernel133<32>;
for (int m_begin = 0; m_begin < m; m_begin += GemmKernel133<32>::M_STEP) {
int m_end = std::min(m_begin + GemmKernel133<32>::M_STEP, m);
for (int n_begin = 0; n_begin < n; n_begin += GemmKernel133<32>::N_STEP) {
int n_end = std::min(n_begin + GemmKernel133<32>::N_STEP, n);
for (int k_begin = 0; k_begin < k; k_begin += GemmKernel133<32>::K_STEP) {
int k_end = std::min(k_begin + GemmKernel133<32>::K_STEP, k);
int8_t* as = offset_pointer_row_major(a, m_begin, k_begin, lda);
int8_t* bs = offset_pointer_col_major(b, k_begin, n_begin, ldb);
int32_t* cs = offset_pointer_row_major(c, m_begin, n_begin, ldc);
GemmKernel133<32>::BTile<int8_t> tb;
if (n_end - n_begin == GemmKernel133<32>::N_STEP && k_end - k_begin == GemmKernel133<32>::K_STEP) {
tb.load_from(bs, ldb);
} else {
tb.partial_load(bs, n_end - n_begin, k_end - k_begin, ldb);
}
if (m_end - m_begin == GemmKernel133<32>::M_STEP && k_end - k_begin == GemmKernel133<32>::K_STEP) {
// printf("sub mat mul, full tile: (%d,%d),(%d,%d),(%d,%d)\n",
// m_begin, m_end, n_begin, n_end, k_begin, k_end);
tb.run_full_ac(as, lda, cs, ldc);
} else {
// printf("sub mat mul, partial tile: (%d,%d),(%d,%d),(%d,%d)\n",
// m_begin, m_end, n_begin, n_end, k_begin, k_end);
GemmKernel133<32>::PartialTiles<int8_t, int8_t, int32_t> p;
p.partial_run(m_end - m_begin, n_end - n_begin, k_end - k_begin, as, lda, bs, ldb, cs, ldc);
}
}
}
}
}
template <>
inline void mat_mul_single(int m, int n, int k, ggml_bf16_t* a, size_t lda, ggml_bf16_t* b, size_t ldb, float* c,
size_t ldc) {
// // GemmKernel133BF::config();
// for (int m_begin = 0; m_begin < m; m_begin += GemmKernel133BF::M_STEP) {
// int m_end = std::min(m_begin + GemmKernel133BF::M_STEP, m);
// for (int n_begin = 0; n_begin < n; n_begin += GemmKernel133BF::N_STEP) {
// int n_end = std::min(n_begin + GemmKernel133BF::N_STEP, n);
// for (int k_begin = 0; k_begin < k; k_begin += GemmKernel133BF::K_STEP)
// {
// int k_end = std::min(k_begin + GemmKernel133BF::K_STEP, k);
// ggml_bf16_t* as = offset_pointer_row_major(a, m_begin, k_begin, lda);
// ggml_bf16_t* bs = offset_pointer_col_major(b, k_begin, n_begin, ldb);
// GemmKernel133BF::BTile tb;
// if (n_end - n_begin == GemmKernel133BF::N_STEP && k_end - k_begin ==
// GemmKernel133BF::K_STEP) {
// tb.full_load(bs, ldb);
// } else {
// tb.partial_load(bs, n_end - n_begin, k_end - k_begin, ldb);
// }
// float* cs = offset_pointer_row_major(c, m_begin, n_begin, ldc);
// if (m_end - m_begin == GemmKernel133<32>::M_STEP && k_end - k_begin
// == GemmKernel133<32>::K_STEP) {
// // printf("sub mat mul, full tile: (%d,%d),(%d,%d),(%d,%d)\n",
// m_begin, m_end, n_begin, n_end, k_begin,
// // k_end);
// tb.run_full_ac(as, lda, cs, ldc);
// } else {
// // printf("sub mat mul, partial tile: (%d,%d),(%d,%d),(%d,%d)\n",
// m_begin, m_end, n_begin, n_end, k_begin,
// // k_end);
// GemmKernel133BF::PartialTiles p;
// p.partial_run(m_end - m_begin, n_end - n_begin, k_end - k_begin,
// as, lda, bs, ldb, cs, ldc);
// }
// }
// }
// }
}
template <typename QA>
void mat_mul_single(int m, int n, int k, QA* a, size_t lda, block_q8_0* b, size_t ldb, float* c, size_t ldc) {
// amx::init();
assert(QK8_0 == 32);
assert(QK4_0 == 32);
assert(GemmKernel133<32>::K_STEP == 32);
// assert(reinterpret_cast<intptr_t>(c) % 64 == 0);
assert(ldc % 64 == 0);
// GemmKernal133::config();
for (int n_begin = 0; n_begin < n; n_begin += GemmKernel133<32>::N_STEP) {
int n_end = std::min(n_begin + GemmKernel133<32>::N_STEP, n);
for (int k_begin = 0; k_begin < k; k_begin += GemmKernel133<32>::K_STEP) {
int k_end = std::min(k_begin + GemmKernel133<32>::K_STEP, k);
int kb = k_begin / GemmKernel133<32>::K_STEP;
block_q8_0* bs = offset_pointer_col_major(b, kb, n_begin, ldb);
GemmKernel133<32>::PartialTiles<int8_t, int8_t, int32_t> p;
p.tb.partial_load_quant(bs, n_end - n_begin, k_end - k_begin, ldb);
for (int m_begin = 0; m_begin < m; m_begin += GemmKernel133<32>::M_STEP) {
int m_end = std::min(m_begin + GemmKernel133<32>::M_STEP, m);
QA* as = offset_pointer_row_major(a, m_begin, kb, lda);
float* cs = offset_pointer_row_major(c, m_begin, n_begin, ldc);
// printf("sub mat mul: (%d,%d),(%d,%d),(%d,%d) %ld %ld\n", m_begin,
// m_end, n_begin, n_end, k_begin, k_end,as-a,bs-b);
// p.partial_run_quant(m_end - m_begin, n_end - n_begin, k_end -
// k_begin, as, lda, bs, ldb, cs, ldc);
p.partial_run_quant_ac(m_end - m_begin, n_end - n_begin, k_end - k_begin, as, lda, cs, ldc);
}
}
}
}
inline void mat_mul_single(int m, int n, int k, block_q4_K* a, size_t lda, block_q8_K* b, size_t ldb, float* c,
size_t ldc) {
assert(QK_K == 256);
assert(k % QK_K == 0);
assert(QK_K % GemmKernel133<32>::K_STEP == 0);
assert(GemmKernel133<32>::K_STEP == 32);
assert(ldc % 64 == 0);
for (int m_begin = 0; m_begin < m; m_begin += GemmKernel133<32>::M_STEP) {
int m_end = std::min(m_begin + GemmKernel133<32>::M_STEP, m);
for (int n_begin = 0; n_begin < n; n_begin += GemmKernel133<32>::N_STEP) {
int n_end = std::min(n_begin + GemmKernel133<32>::N_STEP, n);
float* cs = offset_pointer_row_major(c, m_begin, n_begin, ldc);
for (int k_bigstart = 0; k_bigstart < k; k_bigstart += QK_K) {
int k_bigend = k_bigstart + QK_K;
int super_block_index = k_bigstart / QK_K;
block_q8_K* super_bs = offset_pointer_col_major(b, super_block_index, n_begin, ldb);
block_q4_K* super_as = offset_pointer_row_major(a, m_begin, super_block_index, lda);
float super_scale = GGML_FP16_TO_FP32(super_as->d);
float super_min = GGML_FP16_TO_FP32(super_as->dmin);
__m512 a_sm = _mm512_mul_ps(
_mm512_cvtepi32_ps(_mm512_cvtepu8_epi32(make_q4K_scale_and_min(super_as->scales))),
_mm512_insertf32x8(_mm512_castps256_ps512(_mm256_set1_ps(super_scale)), _mm256_set1_ps(super_min), 1));
float* a_scale = reinterpret_cast<float*>(&a_sm);
float* a_min = a_scale + 8;
for (int inner_idx = 0; inner_idx < 256 / 32; inner_idx++) {
amx::GemmKernel133<32>::PartialTilesSum<uint8_t, int8_t, float> t;
// printf("sub mat mul: (%d,%d),(%d,%d),(%d,%d) %d\n", m_begin, m_end,
// n_begin, n_end, k_bigstart,
// k_bigend,inner_idx);
t.tb.partial_load_quant(super_bs, n_end - n_begin, inner_idx, ldb);
t.partial_run_quant_ac(m_end - m_begin, n_end - n_begin, inner_idx, super_as, lda, cs, ldc,
a_scale[inner_idx], a_min[inner_idx]);
}
}
}
}
}
inline void mat_mul_single(int m, int n, int k, blocks_aligned_q8_0_ref a, int a_blck_stride, blocks_aligned_q8_0_ref b,
int b_blck_stride, float* c, size_t ldc) {
using Kernel = GemmKernel133<64>;
using TA = uint8_t;
using TB = int8_t;
for (int m_begin = 0; m_begin < m; m_begin += Kernel::M_STEP) {
int m_end = std::min(m_begin + Kernel::M_STEP, m);
for (int n_begin = 0; n_begin < n; n_begin += Kernel::N_STEP) {
int n_end = std::min(n_begin + Kernel::N_STEP, n);
for (int k_begin = 0; k_begin < k; k_begin += Kernel::K_STEP) {
int k_end = std::min(k_begin + Kernel::K_STEP, k);
int k_block = k_begin / Kernel::K_STEP;
auto as = a.offset(m_begin * a_blck_stride + k_block);
auto bs = b.offset(n_begin * b_blck_stride + k_block);
auto cs = offset_pointer_row_major(c, m_begin, n_begin, ldc);
// printf("sub mat mul: (%d,%d),(%d,%d),(%d,%d) %ld %ld\n", m_begin,
// m_end, n_begin, n_end, k_begin, k_end,as.d-a.d,bs.d-b.d);
Kernel::PartialTiles<TA, TB, int32_t> t;
t.tb.partial_load_quant(bs, n_end - n_begin, k_end - k_begin, b_blck_stride);
t.partial_run_quant_ac(m_end - m_begin, n_end - n_begin, k_end - k_begin, as, a_blck_stride, cs, ldc);
}
}
}
}
inline void merge_mat(int d0, int d1, float* a, float* b, size_t ld) {
__m512* va = (__m512*)a;
__m512* vb = (__m512*)b;
size_t d1v = (d1 + 15) / 16;
for (int i = 0; i < d0; i++) {
auto ta = offset_pointer_row_major(va, i, 0, ld);
auto tb = offset_pointer_row_major(vb, i, 0, ld);
for (int j = 0; j < d1v; j++) {
ta[j] = _mm512_add_ps(ta[j], tb[j]);
}
}
}
inline void merge_mats(int d0, int d1, int cnt, float** data, size_t ld) {
for (int i = 0; i < cnt; i++) {
assert((intptr_t)data[i] % 64 == 0);
assert(ld % 64 == 0);
}
while (cnt > 1) {
int new_cnt = (cnt + 1) / 2;
for (int i = 0; i < new_cnt; i++) {
int j = new_cnt + i;
if (j < cnt) {
// printf("merge %d %d\n", i, j);
merge_mat(d0, d1, data[i], data[j], ld);
}
}
cnt = new_cnt;
}
}
template <typename TA, typename TB, typename TC>
struct GemmKernel {
static_assert(sizeof(TA) == -1, "No associated type defined for this type.");
using type = GemmKernel224BF;
};
template <typename TB>
struct GemmKernel<uint8_t, TB, float> {
using type = GemmKernel133<32>;
};
template <typename TB>
struct GemmKernel<int8_t, TB, float> {
using type = GemmKernel133<32>;
};
template <>
struct GemmKernel<block_q4_0, block_q8_0, float> {
using type = GemmKernel133<32>;
};
template <>
struct GemmKernel<block_q8_0, block_q8_0, float> {
using type = GemmKernel133<32>;
};
template <>
struct GemmKernel<block_q4_K, block_q8_K, float> {
using type = GemmKernel133<32>;
};
template <>
struct GemmKernel<ggml_bf16_t, ggml_bf16_t, float> {
// using type = GemmKernel133BF;
using type = GemmKernel224BF;
};
// template <typename TA, typename TB, typename TC>
// void mat_mul(int m, int n, int k, TA* a, size_t lda, TB* b, size_t ldb, TC*
// c, size_t ldc, int ith, int nth) {
// using K = typename GemmKernel<TA, TB, TC>::type;
// int m_partition_count = (m + K::M_STEP - 1) / K::M_STEP;
// int partition_count_per_thread = (m_partition_count + nth - 1) / nth;
// int partition_start = ith * partition_count_per_thread;
// int partition_end = std::min(partition_start + partition_count_per_thread,
// m_partition_count); int m_start = partition_start * K::M_STEP; int m_end =
// std::min(m, partition_end * K::M_STEP);
// mat_mul_single(m_end - m_start, n, k, offset_pointer(a, m_start * lda),
// lda, b, ldb, offset_pointer(c, m_start * ldc),
// ldc);
// }
template <typename TA, typename TB, typename TC>
void mat_mul(int m, int n, int k, TA* a, size_t lda, TB* b, size_t ldb, TC* c, size_t ldc, int ith, int nth) {
using K = typename GemmKernel<TA, TB, TC>::type;
int n_partition_count = (n + K::N_STEP - 1) / K::N_STEP;
int partition_count_per_thread = (n_partition_count + nth - 1) / nth;
int partition_start = ith * partition_count_per_thread;
int partition_end = std::min(partition_start + partition_count_per_thread, n_partition_count);
int n_start = partition_start * K::N_STEP;
int n_end = std::min(n, partition_end * K::N_STEP);
mat_mul_single(m, n_end - n_start, k, a, lda, offset_pointer_col_major(b, 0, n_start, ldb), ldb,
offset_pointer_row_major(c, 0, n_start, ldc), ldc);
}
inline void mat_mul(int m, int n, int k, std::shared_ptr<GemmKernel224BF::BufferA> ba,
std::shared_ptr<GemmKernel224BF::BufferB> bb, std::shared_ptr<GemmKernel224BF::BufferC> bc, int ith,
int nth) {
using K = GemmKernel224BF;
assert(n % K::N_STEP == 0);
assert(k % K::K_STEP == 0);
auto [n_start, n_end] = K::split_range_n(n, ith, nth);
// printf("n_start %d n_end %d\n", n_start, n_end);
for (int k_block_begin = 0; k_block_begin < k; k_block_begin += K::K_BLOCK) {
for (int m_begin = 0; m_begin < m; m_begin += K::M_STEP) {
for (int n_begin = n_start; n_begin < n_end; n_begin += K::N_STEP) {
float* c = bc->get_submat(m, n, m_begin, n_begin);
// if (m - m_begin == 1) {
if (false) {
// if(k_block_begin==0&&m_begin==0&&n_begin==n_start)
// printf("AVX");
__m512* c512 = (__m512*)c;
if (k_block_begin == 0) {
for (int m_i = 0; m_i < m; m_i++) {
c512[m_i * 2] = _mm512_setzero_ps();
c512[m_i * 2 + 1] = _mm512_setzero_ps();
}
}
for (int k_begin = 0; k_begin < K::K_BLOCK && k_block_begin + k_begin < k; k_begin += K::K_STEP) {
int32_t* a32 = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin + k_begin);
__m512bh* b512 = (__m512bh*)bb->get_submat(n, k, n_begin, k_block_begin + k_begin);
for (int m_i = 0; m_i < m; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512bh ma = (__m512bh)_mm512_set1_epi32(a32[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
c512[m_i * 2 + n_i] = _mm512_dpbf16_ps(c512[m_i * 2 + n_i], ma, b512[n_i * 16 + k_i]);
}
}
}
}
} else {
if (k_block_begin == 0) {
K::clean_c();
} else {
K::load_c(c, K::N_STEP * sizeof(float));
}
for (int k_begin = 0; k_begin < K::K_BLOCK && k_block_begin + k_begin < k; k_begin += K::K_STEP) {
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin + k_begin), K::K_STEP * sizeof(ggml_bf16_t));
K::load_b(bb->get_submat(n, k, n_begin, k_block_begin + k_begin), K::K_STEP * sizeof(ggml_bf16_t));
K::run_tile();
}
K::store_c(c, K::N_STEP * sizeof(float));
}
}
}
}
}
inline void vec_mul(int m, int n, int k, std::shared_ptr<GemmKernel224BF::BufferA> ba,
std::shared_ptr<GemmKernel224BF::BufferB> bb, std::shared_ptr<GemmKernel224BF::BufferC> bc, int ith,
int nth) {
mat_mul(m, n, k, ba, bb, bc, ith, nth);
}
template <typename K, bool amx_or_avx = true>
void integer_mat_mul(int m, int n, int k, typename K::BufferA* ba, typename K::BufferB* bb, typename K::BufferC* bc,
int ith, int nth) {
assert(n % K::N_STEP == 0);
assert(k % K::K_STEP == 0);
auto [n_start, n_end] = K::split_range_n(n, ith, nth);
for (int k_block_begin = 0; k_block_begin < k; k_block_begin += K::K_BLOCK) {
for (int m_begin = 0; m_begin < m; m_begin += K::M_STEP) {
for (int n_begin = n_start; n_begin < n_end; n_begin += K::N_STEP) {
float* c = bc->get_submat(m, n, m_begin, n_begin);
if constexpr (amx_or_avx && AMX_AVAILABLE) {
K::amx_kernel(m, n, k, m_begin, n_begin, k_block_begin, c, ba, bb);
} else {
K::avx_kernel(m, n, k, m_begin, n_begin, k_block_begin, c, ba, bb);
}
if (k_block_begin + K::K_BLOCK >= k) {
K::apply_scale(m, n, m_begin, n_begin, c, ba, bb);
}
}
}
}
}
inline void vec_mul(int m, int n, int k, std::shared_ptr<GemmKernel224Int8::BufferA> ba,
std::shared_ptr<GemmKernel224Int8::BufferB> bb, std::shared_ptr<GemmKernel224Int8::BufferC> bc,
int ith, int nth) {
integer_mat_mul<GemmKernel224Int8, false>(m, n, k, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void mat_mul(int m, int n, int k, std::shared_ptr<GemmKernel224Int8::BufferA> ba,
std::shared_ptr<GemmKernel224Int8::BufferB> bb, std::shared_ptr<GemmKernel224Int8::BufferC> bc,
int ith, int nth) {
integer_mat_mul<GemmKernel224Int8, true>(m, n, k, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void vec_mul(int m, int n, int k, std::shared_ptr<GemmKernel224Int4::BufferA> ba,
std::shared_ptr<GemmKernel224Int4::BufferB> bb, std::shared_ptr<GemmKernel224Int4::BufferC> bc,
int ith, int nth) {
integer_mat_mul<GemmKernel224Int4, false>(m, n, k, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void mat_mul(int m, int n, int k, std::shared_ptr<GemmKernel224Int4::BufferA> ba,
std::shared_ptr<GemmKernel224Int4::BufferB> bb, std::shared_ptr<GemmKernel224Int4::BufferC> bc,
int ith, int nth) {
integer_mat_mul<GemmKernel224Int4, true>(m, n, k, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void vec_mul(int m, int n, int k, std::shared_ptr<GemmKernel224Int4_1::BufferA> ba,
std::shared_ptr<GemmKernel224Int4_1::BufferB> bb, std::shared_ptr<GemmKernel224Int4_1::BufferC> bc,
int ith, int nth) {
integer_mat_mul<GemmKernel224Int4_1, false>(m, n, k, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void mat_mul(int m, int n, int k, std::shared_ptr<GemmKernel224Int4_1::BufferA> ba,
std::shared_ptr<GemmKernel224Int4_1::BufferB> bb, std::shared_ptr<GemmKernel224Int4_1::BufferC> bc,
int ith, int nth) {
integer_mat_mul<GemmKernel224Int4_1, true>(m, n, k, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void mat_mul(int m, int n, int k, blocks_aligned_q8_0_ref aref, int a_blck_stride, blocks_aligned_q8_0_ref bref,
int b_blck_stride, float* c, size_t ldc, int ith, int nth) {
using K = GemmKernel133<64>;
int m_partition_count = (m + K::M_STEP - 1) / K::M_STEP;
int partition_count_per_thread = (m_partition_count + nth - 1) / nth;
int partition_start = ith * partition_count_per_thread;
int partition_end = std::min(partition_start + partition_count_per_thread, m_partition_count);
int m_start = partition_start * K::M_STEP;
int m_end = std::min(m, partition_end * K::M_STEP);
mat_mul_single(m_end - m_start, n, k, aref.offset(m_start * a_blck_stride), a_blck_stride, bref, b_blck_stride,
offset_pointer(c, m_start * ldc), ldc);
}
// K-group quantization kernel with intermediate int32 accumulation
struct GemmKernel224Int4KGroup {
using dt = void;
using output_t = int32_t;
static constexpr double ELEMENT_SIZE = 0.5;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 64;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 4;
static constexpr int M_STEP = TILE_M * 2;
static constexpr int N_STEP = TILE_N * 2;
static constexpr int K_STEP = TILE_K;
static inline const int N_BLOCK = 256;
// K_BLOCK should match k_group_size for proper scaling
static inline const int K_BLOCK = 7168; // Will be overridden by k_group_size
static std::string name() { return "INT4_KGROUP"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 64
for (int i = 0; i < 2; i++) tile_config.set_row_col(i, TILE_M, TILE_K);
// size is 16 x 64
for (int i = 2; i < 4; i++) tile_config.set_row_col(i, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK);
// size is 16 x 16
for (int i = 4; i < 8; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
alignas(64) static constexpr uint8_t hi_mask_arr[64] = {
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
alignas(64) static constexpr uint8_t lo_mask_arr[64] = {
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F};
static __m512i hi_mask() { return *((__m512i*)(&hi_mask_arr[0])); }
static __m512i lo_mask() { return *((__m512i*)(&lo_mask_arr[0])); }
static void clean_c() {
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
_tile_zero(7);
}
static void load_c(output_t* c, size_t ldc) {
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_loadd(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
}
static void store_c(output_t* c, size_t ldc) {
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_stored(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
}
static void load_a(dt* a, size_t lda) {
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
}
static void load_b_lo(dt* b, size_t ldb) {
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
// __m512i temp = _mm512_and_si512(lo_mask(), *static_cast<__m512i *>(offset_pointer(b, ldb * i)));
// db[i] = _mm512_slli_epi32(temp, 4);
db[i] = _mm512_slli_epi32(_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
// __m512i temp = _mm512_and_si512(lo_mask(), *static_cast<__m512i *>(offset_pointer(b, ldb * (i + TILE_N))));
// db[i] = _mm512_slli_epi32(temp, 4);
db[i] = _mm512_slli_epi32(
_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N)))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
}
static void load_b_hi(dt* b, size_t ldb) {
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i)));
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N))));
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
}
static void run_tile() {
#ifdef HAVE_AMX
_tile_dpbssd(4, 0, 2);
_tile_dpbssd(5, 0, 3);
_tile_dpbssd(6, 1, 2);
_tile_dpbssd(7, 1, 3);
#endif
}
using BufferA = BufferAKGroupImpl<GemmKernel224Int4KGroup>;
using BufferB = BufferBKGroupImpl<GemmKernel224Int4KGroup>;
using BufferC = BufferCReduceImpl<GemmKernel224Int4KGroup>;
// K-group aware AVX kernel - processes a single B_K_STEP chunk
static void avx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, int32_t* int_c, BufferA* ba,
BufferB* bb, int k_group_size) {
using K = GemmKernel224Int4KGroup;
__m512i* c512 = (__m512i*)int_c;
int m_block_end = std::min(m - m_begin, M_STEP);
// Initialize int_c to zero at the start of k_group
if (k_block_begin % k_group_size == 0) {
for (int m_i = 0; m_i < m_block_end; m_i++) {
c512[m_i * 2] = _mm512_setzero_si512();
c512[m_i * 2 + 1] = _mm512_setzero_si512();
}
}
int k_offset = k_block_begin % K::BufferB::B_K_STEP;
if (k_offset == 0) {
int32_t* a32_lo = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_lo = _mm512_set1_epi32(a32_lo[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_lo = _mm512_slli_epi32(_mm512_and_si512(K::lo_mask(), b512[n_i * 16 + k_i]), 4);
c512[m_i * 2 + n_i] = _mm512_dpbssd_epi32(c512[m_i * 2 + n_i], ma_lo, b512_lo);
}
}
}
} else {
int32_t* a32_hi = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin - K::K_STEP);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_hi = _mm512_set1_epi32(a32_hi[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_hi = _mm512_and_si512(K::hi_mask(), b512[n_i * 16 + k_i]);
c512[m_i * 2 + n_i] = _mm512_dpbssd_epi32(c512[m_i * 2 + n_i], ma_hi, b512_hi);
}
}
}
}
}
// K-group aware AMX kernel - processes a single K_STEP chunk (lo or hi nibble)
static void amx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, int32_t* int_c, BufferA* ba,
BufferB* bb, int k_group_size) {
using K = GemmKernel224Int4KGroup;
// Initialize or load int_c at start of k_group
if (k_block_begin % k_group_size == 0) {
K::clean_c();
} else {
K::load_c(int_c, K::N_STEP * sizeof(int32_t));
}
// Determine if we're processing lo or hi nibble based on position within B_K_STEP
int k_offset = k_block_begin % K::BufferB::B_K_STEP;
if (k_offset == 0) {
// Process lo nibble
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin), K::K_STEP * sizeof(int8_t));
K::load_b_lo(bb->get_submat(n, k, n_begin, k_block_begin), K::BufferB::B_K_STEP / 2);
K::run_tile();
} else {
// Process hi nibble (k_offset == K_STEP)
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin), K::K_STEP * sizeof(int8_t));
K::load_b_hi(bb->get_submat(n, k, n_begin, k_block_begin - K::K_STEP), K::BufferB::B_K_STEP / 2);
K::run_tile();
}
K::store_c(int_c, K::N_STEP * sizeof(int32_t));
}
// K-group aware scale application
static void apply_scale_kgroup(int m, int n, int m_begin, int n_begin, int k_begin, float* c, int32_t* int_c,
BufferA* ba, BufferB* bb, int k, int k_group_size) {
using K = GemmKernel224Int4KGroup;
int to = m - m_begin;
if (m - m_begin > K::M_STEP) {
to = K::M_STEP;
}
for (int i = 0; i < to; i++) {
// Get scale for this k_group
__m512 as = _mm512_set1_ps(*ba->get_scale(m, m_begin + i, k, k_begin));
__m512 bs = _mm512_load_ps(bb->get_scale(n, n_begin, k, k_begin));
__m512i now = _mm512_load_epi32((__m512i*)(int_c + i * K::N_STEP));
__m512 result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
// Load existing float value from c and add
__m512 existing = _mm512_load_ps((__m512*)(c + i * K::N_STEP));
result = _mm512_add_ps(existing, result);
_mm512_store_ps((__m512*)(c + i * K::N_STEP), result);
// Second half
bs = _mm512_load_ps(bb->get_scale(n, n_begin, k, k_begin) + K::TILE_N);
now = _mm512_load_si512((__m512i*)(int_c + i * K::N_STEP + K::TILE_N));
result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
existing = _mm512_load_ps((__m512*)(c + i * K::N_STEP + K::TILE_N));
result = _mm512_add_ps(existing, result);
_mm512_store_ps((__m512*)(c + i * K::N_STEP + K::TILE_N), result);
}
}
};
struct GemmKernel224Int4_1KGroup {
using dt = void;
using output_t = int32_t;
static constexpr double ELEMENT_SIZE = 0.5;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 64;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 4;
static constexpr int M_STEP = TILE_M * 2;
static constexpr int N_STEP = TILE_N * 2;
static constexpr int K_STEP = TILE_K;
static inline const int N_BLOCK = 256;
// static inline const int K_BLOCK = 7168;
static inline const int K_BLOCK = 3584;
// static inline const int K_BLOCK = 2560;
static std::string name() { return "INT4_1K"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 64
for (int i = 0; i < 2; i++) tile_config.set_row_col(i, TILE_M, TILE_K);
// size is 16 x 64
for (int i = 2; i < 4; i++) tile_config.set_row_col(i, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK);
// size is 16 x 16
for (int i = 4; i < 8; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
alignas(64) static constexpr uint8_t hi_mask_arr[64] = {
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
alignas(64) static constexpr uint8_t lo_mask_arr[64] = {
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F};
alignas(64) static constexpr uint8_t sign_mask_arr[64] = {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
};
static __m512i hi_mask() { return *((__m512i*)(&hi_mask_arr[0])); }
static __m128i hi_mask_128() { return *((__m128i*)(&hi_mask_arr[0])); }
static __m512i lo_mask() { return *((__m512i*)(&lo_mask_arr[0])); }
static __m128i lo_mask_128() { return *((__m128i*)(&lo_mask_arr[0])); }
static __m128i si_mask_128() { return *((__m128i*)(&sign_mask_arr[0])); }
static void load_b_hi(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i)));
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N))));
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_b_lo(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_slli_epi32(_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_slli_epi32(
_mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N)))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_a(dt* a, size_t lda) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
#else
(void)a;
(void)lda;
#endif
}
// static void load_b(dt* b, size_t ldb) {
// _tile_loadd(2, b, ldb);
// _tile_loadd(3, offset_pointer(b, ldb * TILE_N), ldb);
// }
static void clean_c() {
#ifdef HAVE_AMX
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
_tile_zero(7);
#endif
}
static void load_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_loadd(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void store_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_stored(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void run_tile() {
#ifdef HAVE_AMX
_tile_dpbsud(4, 0, 2);
_tile_dpbsud(5, 0, 3);
_tile_dpbsud(6, 1, 2);
_tile_dpbsud(7, 1, 3);
#endif
}
using BufferA = BufferAWithSumKGroupImpl<GemmKernel224Int4_1KGroup>;
using BufferB = BufferBInt4WithZeroKGroupImpl<GemmKernel224Int4_1KGroup>;
using BufferC = BufferCReduceImpl<GemmKernel224Int4_1KGroup>;
static void avx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, int32_t* int_c, BufferA* ba,
BufferB* bb, int k_group_size) {
using K = GemmKernel224Int4_1KGroup;
__m512i* c512 = (__m512i*)int_c;
int m_block_end = std::min(m - m_begin, M_STEP);
if (k_block_begin % k_group_size == 0) {
for (int m_i = 0; m_i < m_block_end; m_i++) {
c512[m_i * 2] = _mm512_setzero_si512();
c512[m_i * 2 + 1] = _mm512_setzero_si512();
}
}
int k_offset = k_block_begin % K::BufferB::B_K_STEP;
if (k_offset == 0) {
int32_t* a32_lo = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_lo = _mm512_set1_epi32(a32_lo[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_lo = _mm512_slli_epi32(_mm512_and_si512(K::lo_mask(), b512[n_i * 16 + k_i]), 4);
c512[m_i * 2 + n_i] = _mm512_dpbusd_epi32_compat(c512[m_i * 2 + n_i], b512_lo, ma_lo);
}
}
}
} else {
int32_t* a32_hi = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin - K::K_STEP);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_hi = _mm512_set1_epi32(a32_hi[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_hi = _mm512_and_si512(K::hi_mask(), b512[n_i * 16 + k_i]);
c512[m_i * 2 + n_i] = _mm512_dpbusd_epi32_compat(c512[m_i * 2 + n_i], b512_hi, ma_hi);
}
}
}
}
}
static void amx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, int32_t* int_c, BufferA* ba,
BufferB* bb, int k_group_size) {
using K = GemmKernel224Int4_1KGroup;
if (k_block_begin % k_group_size == 0) {
K::clean_c();
} else {
K::load_c(int_c, K::N_STEP * sizeof(int32_t));
}
// Determine if we're processing lo or hi nibble based on position within B_K_STEP
int k_offset = k_block_begin % K::BufferB::B_K_STEP;
if (k_offset == 0) {
// Process lo nibble
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin), K::K_STEP * sizeof(int8_t));
K::load_b_lo(bb->get_submat(n, k, n_begin, k_block_begin), K::BufferB::B_K_STEP / 2);
K::run_tile();
} else {
// Process hi nibble (k_offset == K_STEP)
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin), K::K_STEP * sizeof(int8_t));
K::load_b_hi(bb->get_submat(n, k, n_begin, k_block_begin - K::K_STEP), K::BufferB::B_K_STEP / 2);
K::run_tile();
}
K::store_c(int_c, K::N_STEP * sizeof(int32_t));
}
static void apply_scale_kgroup(int m, int n, int m_begin, int n_begin, int k_begin, float* c, int32_t* int_c,
BufferA* ba, BufferB* bb, int k, int k_group_size) {
using K = GemmKernel224Int4_1KGroup;
int to = m - m_begin;
if (m - m_begin > K::M_STEP) {
to = K::M_STEP;
}
for (int i = 0; i < to; i++) {
__m512 as = _mm512_set1_ps(*ba->get_scale(m, m_begin + i, k, k_begin));
__m512 asum = _mm512_set1_ps(*ba->get_sum(m, m_begin + i, k, k_begin));
__m512 bs = _mm512_load_ps(bb->get_scale(n, n_begin, k, k_begin));
__m512 b_mins = _mm512_load_ps(bb->get_min(n, n_begin, k, k_begin));
__m512i now = _mm512_load_epi32((__m512i*)(int_c + i * K::N_STEP));
__m512 result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
result = _mm512_add_ps(result, _mm512_mul_ps(asum, b_mins));
__m512 existing = _mm512_load_ps((__m512*)(c + i * K::N_STEP));
result = _mm512_add_ps(result, existing);
_mm512_store_ps((__m512*)(c + i * K::N_STEP), result);
bs = _mm512_load_ps(bb->get_scale(n, n_begin, k, k_begin) + K::TILE_N);
b_mins = _mm512_load_ps(bb->get_min(n, n_begin, k, k_begin) + K::TILE_N);
now = _mm512_load_si512((__m512i*)(int_c + i * K::N_STEP + K::TILE_N));
result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
result = _mm512_add_ps(result, _mm512_mul_ps(asum, b_mins));
existing = _mm512_load_ps((__m512*)(c + i * K::N_STEP + K::TILE_N));
result = _mm512_add_ps(result, existing);
_mm512_store_ps((__m512*)(c + i * K::N_STEP + K::TILE_N), result);
}
}
};
struct GemmKernel224Int4_1_LowKGroup {
using dt = void;
using output_t = int32_t;
static constexpr double ELEMENT_SIZE = 0.5;
static constexpr int TILE_M = 16;
static constexpr int TILE_K = 64;
static constexpr int TILE_N = 16;
static constexpr int VNNI_BLK = 4;
static constexpr int M_STEP = TILE_M * 2;
static constexpr int N_STEP = TILE_N * 2;
static constexpr int K_STEP = TILE_K;
static inline const int N_BLOCK = 256;
// static inline const int K_BLOCK = 7168;
static inline const int K_BLOCK = 3584;
// static inline const int K_BLOCK = 2560;
static std::string name() { return "INT4_1K"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {
#ifdef HAVE_AMX
enable_amx();
TileConfig tile_config;
// size is 16 x 64
for (int i = 0; i < 2; i++) tile_config.set_row_col(i, TILE_M, TILE_K);
// size is 16 x 64
for (int i = 2; i < 4; i++) tile_config.set_row_col(i, TILE_K / VNNI_BLK, TILE_N * VNNI_BLK);
// size is 16 x 16
for (int i = 4; i < 8; i++) tile_config.set_row_col(i, TILE_M, TILE_N * sizeof(output_t));
tile_config.set_config();
#endif
}
alignas(64) static constexpr uint8_t hi_mask_arr[64] = {
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
alignas(64) static constexpr uint8_t lo_mask_arr[64] = {
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F};
alignas(64) static constexpr uint8_t sign_mask_arr[64] = {
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
};
static __m512i hi_mask() { return *((__m512i*)(&hi_mask_arr[0])); }
static __m128i hi_mask_128() { return *((__m128i*)(&hi_mask_arr[0])); }
static __m512i lo_mask() { return *((__m512i*)(&lo_mask_arr[0])); }
static __m128i lo_mask_128() { return *((__m128i*)(&lo_mask_arr[0])); }
static __m128i si_mask_128() { return *((__m128i*)(&sign_mask_arr[0])); }
static void load_b_hi(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_srli_epi32(_mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_srli_epi32(
_mm512_and_si512(hi_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N)))), 4);
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_b_lo(dt* b, size_t ldb) {
#ifdef HAVE_AMX
// 在函数内部分配一个局部(栈上)对齐缓冲区
alignas(64) int8_t local_buffer[TILE_N * TILE_K];
__m512i* db = reinterpret_cast<__m512i*>(local_buffer);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * i)));
}
asm volatile("" ::: "memory");
_tile_loadd(2, db, TILE_K);
for (size_t i = 0; i < TILE_N; i++) {
db[i] = _mm512_and_si512(lo_mask(), *static_cast<__m512i*>(offset_pointer(b, ldb * (i + TILE_N))));
}
asm volatile("" ::: "memory");
_tile_loadd(3, db, TILE_K);
#else
(void)b;
(void)ldb;
#endif
}
static void load_a(dt* a, size_t lda) {
#ifdef HAVE_AMX
_tile_loadd(0, a, lda);
_tile_loadd(1, offset_pointer(a, lda * TILE_M), lda);
#else
(void)a;
(void)lda;
#endif
}
// static void load_b(dt* b, size_t ldb) {
// _tile_loadd(2, b, ldb);
// _tile_loadd(3, offset_pointer(b, ldb * TILE_N), ldb);
// }
static void clean_c() {
#ifdef HAVE_AMX
_tile_zero(4);
_tile_zero(5);
_tile_zero(6);
_tile_zero(7);
#endif
}
static void load_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_loadd(4, c, ldc);
_tile_loadd(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_loadd(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_loadd(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void store_c(output_t* c, size_t ldc) {
#ifdef HAVE_AMX
_tile_stored(4, c, ldc);
_tile_stored(5, offset_pointer(c, TILE_N * sizeof(output_t)), ldc);
_tile_stored(6, offset_pointer(c, ldc * TILE_M), ldc);
_tile_stored(7, offset_pointer(c, ldc * TILE_M + TILE_N * sizeof(output_t)), ldc);
#else
(void)c;
(void)ldc;
#endif
}
static void run_tile() {
#ifdef HAVE_AMX
_tile_dpbsud(4, 0, 2);
_tile_dpbsud(5, 0, 3);
_tile_dpbsud(6, 1, 2);
_tile_dpbsud(7, 1, 3);
#endif
}
using BufferA = BufferAWithSumKGroupImpl<GemmKernel224Int4_1_LowKGroup>;
using BufferB = BufferBInt4WithZeroLowKGroupImpl<GemmKernel224Int4_1_LowKGroup>;
using BufferC = BufferCReduceImpl<GemmKernel224Int4_1_LowKGroup>;
static void avx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, int32_t* int_c, BufferA* ba,
BufferB* bb, int k_group_size) {
using K = GemmKernel224Int4_1_LowKGroup;
__m512i* c512 = (__m512i*)int_c;
int m_block_end = std::min(m - m_begin, M_STEP);
if (k_block_begin % k_group_size == 0) {
for (int m_i = 0; m_i < m_block_end; m_i++) {
c512[m_i * 2] = _mm512_setzero_si512();
c512[m_i * 2 + 1] = _mm512_setzero_si512();
}
}
int k_offset = k_block_begin % K::BufferB::B_K_STEP;
if (k_offset == 0) {
int32_t* a32_lo = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_lo = _mm512_set1_epi32(a32_lo[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_lo = _mm512_and_si512(K::lo_mask(), b512[n_i * 16 + k_i]);
c512[m_i * 2 + n_i] = _mm512_dpbusd_epi32_compat(c512[m_i * 2 + n_i], b512_lo, ma_lo);
}
}
}
} else {
int32_t* a32_hi = (int32_t*)ba->get_submat(m, k, m_begin, k_block_begin);
__m512i* b512 = (__m512i*)bb->get_submat(n, k, n_begin, k_block_begin - K::K_STEP);
for (int m_i = 0; m_i < m_block_end; m_i++) {
for (int k_i = 0; k_i < 16; k_i++) {
__m512i ma_hi = _mm512_set1_epi32(a32_hi[m_i * 16 + k_i]);
for (int n_i = 0; n_i < 2; n_i++) {
__m512i b512_hi = _mm512_srli_epi32(_mm512_and_si512(K::hi_mask(), b512[n_i * 16 + k_i]), 4);
c512[m_i * 2 + n_i] = _mm512_dpbusd_epi32_compat(c512[m_i * 2 + n_i], b512_hi, ma_hi);
}
}
}
}
}
static void amx_kernel(int m, int n, int k, int m_begin, int n_begin, int k_block_begin, int32_t* int_c, BufferA* ba,
BufferB* bb, int k_group_size) {
using K = GemmKernel224Int4_1_LowKGroup;
if (k_block_begin % k_group_size == 0) {
K::clean_c();
} else {
K::load_c(int_c, K::N_STEP * sizeof(int32_t));
}
// Determine if we're processing lo or hi nibble based on position within B_K_STEP
int k_offset = k_block_begin % K::BufferB::B_K_STEP;
if (k_offset == 0) {
// Process lo nibble
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin), K::K_STEP * sizeof(int8_t));
K::load_b_lo(bb->get_submat(n, k, n_begin, k_block_begin), K::BufferB::B_K_STEP / 2);
K::run_tile();
} else {
// Process hi nibble (k_offset == K_STEP)
K::load_a(ba->get_submat(m, k, m_begin, k_block_begin), K::K_STEP * sizeof(int8_t));
K::load_b_hi(bb->get_submat(n, k, n_begin, k_block_begin - K::K_STEP), K::BufferB::B_K_STEP / 2);
K::run_tile();
}
K::store_c(int_c, K::N_STEP * sizeof(int32_t));
}
static void apply_scale_kgroup(int m, int n, int m_begin, int n_begin, int k_begin, float* c, int32_t* int_c,
BufferA* ba, BufferB* bb, int k, int k_group_size) {
using K = GemmKernel224Int4_1_LowKGroup;
int to = m - m_begin;
if (m - m_begin > K::M_STEP) {
to = K::M_STEP;
}
for (int i = 0; i < to; i++) {
__m512 as = _mm512_set1_ps(*ba->get_scale(m, m_begin + i, k, k_begin));
__m512 asum = _mm512_set1_ps(*ba->get_sum(m, m_begin + i, k, k_begin));
__m512 bs = _mm512_load_ps(bb->get_scale(n, n_begin, k, k_begin));
__m512 b_mins = _mm512_load_ps(bb->get_min(n, n_begin, k, k_begin));
__m512i now = _mm512_load_epi32((__m512i*)(int_c + i * K::N_STEP));
__m512 result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
result = _mm512_add_ps(result, _mm512_mul_ps(asum, b_mins));
__m512 existing = _mm512_load_ps((__m512*)(c + i * K::N_STEP));
result = _mm512_add_ps(result, existing);
_mm512_store_ps((__m512*)(c + i * K::N_STEP), result);
bs = _mm512_load_ps(bb->get_scale(n, n_begin, k, k_begin) + K::TILE_N);
b_mins = _mm512_load_ps(bb->get_min(n, n_begin, k, k_begin) + K::TILE_N);
now = _mm512_load_si512((__m512i*)(int_c + i * K::N_STEP + K::TILE_N));
result = _mm512_mul_ps(_mm512_mul_ps(as, bs), _mm512_cvtepi32_ps(now));
result = _mm512_add_ps(result, _mm512_mul_ps(asum, b_mins));
existing = _mm512_load_ps((__m512*)(c + i * K::N_STEP + K::TILE_N));
result = _mm512_add_ps(result, existing);
_mm512_store_ps((__m512*)(c + i * K::N_STEP + K::TILE_N), result);
}
}
};
// K2 Signed Int4 K-group quantization kernel (AVX only, no AMX)
// For K2 MoE - signed int4 range: [-8, 7]
struct GemmKernel224Int4SmallKGroup {
using dt = uint8_t; // packed int4 type
using output_t = int32_t;
static constexpr double ELEMENT_SIZE = 0.5;
static constexpr int VNNI_BLK = 4;
static constexpr int M_STEP = 1;
static constexpr int N_STEP = 32;
static constexpr int K_STEP = 32;
static inline const int N_BLOCK = 256;
// K_BLOCK should match k_group_size for proper scaling
static inline const int K_BLOCK = 7168; // Will be overridden by k_group_size
static std::string name() { return "K2_INT4_KGROUP"; }
static int recommended_nth(int n) { return (n + N_BLOCK - 1) / N_BLOCK; }
static std::pair<int, int> split_range_n(int n, int ith, int nth) {
int n_start = N_BLOCK * ith;
int n_end = std::min(n, N_BLOCK * (ith + 1));
return {n_start, n_end};
}
static void config() {}
alignas(64) static constexpr uint8_t hi_mask_arr[32] = {
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0};
alignas(64) static constexpr uint8_t lo_mask_arr[32] = {
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F};
alignas(64) static constexpr uint8_t sign_xor_arr[32] = {
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88};
static __m256i hi_mask() { return *((__m256i*)(&hi_mask_arr[0])); }
static __m256i lo_mask() { return *((__m256i*)(&lo_mask_arr[0])); }
static __m256i sign_xor_mask() { return *((__m256i*)(&sign_xor_arr[0])); }
using BufferA = BufferASmallKGroupImpl<GemmKernel224Int4SmallKGroup>;
using BufferB = BufferBInt4KGroupImpl<GemmKernel224Int4SmallKGroup>; // Use new signed int4 buffer
using BufferC = BufferCReduceImpl<GemmKernel224Int4SmallKGroup>;
// K-group aware AVX kernel for signed int4
static inline __m512i compressed_int4_to_int8_avx512(__m256i b256) {
b256 = _mm256_xor_si256(b256, sign_xor_mask());
__m256i b_hi = _mm256_and_si256(b256, hi_mask());
__m256i b_lo = _mm256_slli_epi16(_mm256_andnot_si256(hi_mask(), b256), 4);
__m256i unpack_lo = _mm256_unpacklo_epi8(b_lo, b_hi);
__m256i unpack_hi = _mm256_unpackhi_epi8(b_lo, b_hi);
__m512i result = _mm512_inserti64x4(_mm512_castsi256_si512(unpack_lo), unpack_hi, 1);
const __m512i lane_shuffle = _mm512_set_epi64(7, 6, 3, 2, 5, 4, 1, 0);
return _mm512_permutexvar_epi64(lane_shuffle, result);
}
static inline void integer_mat_vec_kgroup(int m, int n, int k, int k_group_size, BufferA* ba, BufferB* bb,
BufferC* bc, int ith, int nth) {
auto [n_start, n_end] = split_range_n(n, ith, nth);
for (int m_begin = 0; m_begin < m; m_begin++) {
float* c = bc->get_submat(m, n, m_begin, n_start);
__m512i* a512 = (__m512i*)ba->get_submat(m, k, m_begin, 0);
for (int n_block_begin = n_start; n_block_begin < n_end; n_block_begin++) {
__m256i* b256 = (__m256i*)bb->get_submat(n, k, n_block_begin, 0);
float* as = (float*)ba->get_scale(m, m_begin, k, 0);
float* bs = (float*)bb->get_scale(n, n_block_begin, k, 0);
__m512 sum = _mm512_setzero_ps();
#define WORK_K_BLOCK(k_block) \
{ \
__m256 abscale0 = _mm256_set1_ps(as[(k_block) * 2] * bs[(k_block) * 2]); \
__m256 abscale1 = _mm256_set1_ps(as[(k_block) * 2 + 1] * bs[(k_block) * 2 + 1]); \
__m512 abscale = _mm512_insertf32x8(_mm512_castps256_ps512(abscale0), abscale1, 1); \
__m512i mul = _mm512_setzero_si512(); \
mul = _mm512_dpbssd_epi32(mul, a512[k_block], compressed_int4_to_int8_avx512(b256[k_block])); \
sum = _mm512_add_ps(sum, _mm512_mul_ps(abscale, _mm512_cvtepi32_ps(mul))); \
}
for (int k_block = 0; k_block < k / 64; k_block += 2) {
WORK_K_BLOCK(k_block);
WORK_K_BLOCK(k_block + 1);
}
c[n_block_begin - n_start] = _mm512_reduce_add_ps(sum) / 16;
}
}
}
};
inline void vec_mul_kgroup(int m, int n, int k, int k_group_size,
std::shared_ptr<GemmKernel224Int4SmallKGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4SmallKGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4SmallKGroup::BufferC> bc, int ith, int nth) {
GemmKernel224Int4SmallKGroup::integer_mat_vec_kgroup(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void mat_mul_kgroup(int m, int n, int k, int k_group_size,
std::shared_ptr<GemmKernel224Int4SmallKGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4SmallKGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4SmallKGroup::BufferC> bc, int ith, int nth) {
GemmKernel224Int4SmallKGroup::integer_mat_vec_kgroup(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith, nth);
}
// New k-group aware matrix multiplication function
template <typename K, bool amx_or_avx = true>
void integer_mat_mul_kgroup(int m, int n, int k, int k_group_size, typename K::BufferA* ba, typename K::BufferB* bb,
typename K::BufferC* bc, int ith, int nth) {
assert(n % K::N_STEP == 0);
assert(k % K::K_STEP == 0);
assert(k % k_group_size == 0);
auto [n_start, n_end] = K::split_range_n(n, ith, nth);
// Process by k_groups
for (int k_group_begin = 0; k_group_begin < k; k_group_begin += k_group_size) {
for (int m_begin = 0; m_begin < m; m_begin += K::M_STEP) {
for (int n_begin = n_start; n_begin < n_end; n_begin += K::N_STEP) {
float* c = bc->get_submat(m, n, m_begin, n_begin);
int32_t* int_c = bc->get_int_submat(m, n, m_begin, n_begin);
// Initialize float c to zero at the very beginning
if (k_group_begin == 0) {
for (int i = 0; i < K::M_STEP && m_begin + i < m; i++) {
for (int j = 0; j < K::N_STEP; j++) {
c[i * K::N_STEP + j] = 0.0f;
}
}
}
for (int k_begin = k_group_begin; k_begin < std::min(k, k_group_begin + k_group_size); k_begin += K::K_STEP) {
if constexpr (amx_or_avx && AMX_AVAILABLE) {
K::amx_kernel(m, n, k, m_begin, n_begin, k_begin, int_c, ba, bb, k_group_size);
} else {
K::avx_kernel(m, n, k, m_begin, n_begin, k_begin, int_c, ba, bb, k_group_size);
}
}
// }
// Apply scale and accumulate to float buffer at end of k_group
K::apply_scale_kgroup(m, n, m_begin, n_begin, k_group_begin, c, int_c, ba, bb, k, k_group_size);
}
}
}
}
// Convenience functions for k-group kernels
inline void vec_mul_kgroup(int m, int n, int k, int k_group_size, std::shared_ptr<GemmKernel224Int4KGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4KGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4KGroup::BufferC> bc, int ith, int nth) {
integer_mat_mul_kgroup<GemmKernel224Int4KGroup, false>(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith, nth);
}
inline void mat_mul_kgroup(int m, int n, int k, int k_group_size, std::shared_ptr<GemmKernel224Int4KGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4KGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4KGroup::BufferC> bc, int ith, int nth) {
integer_mat_mul_kgroup<GemmKernel224Int4KGroup, true>(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith, nth);
}
// Convenience functions for k-group kernels
inline void vec_mul_kgroup(int m, int n, int k, int k_group_size,
std::shared_ptr<GemmKernel224Int4_1KGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4_1KGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4_1KGroup::BufferC> bc, int ith, int nth) {
integer_mat_mul_kgroup<GemmKernel224Int4_1KGroup, false>(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith,
nth);
}
inline void mat_mul_kgroup(int m, int n, int k, int k_group_size,
std::shared_ptr<GemmKernel224Int4_1KGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4_1KGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4_1KGroup::BufferC> bc, int ith, int nth) {
integer_mat_mul_kgroup<GemmKernel224Int4_1KGroup, true>(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith,
nth);
}
// Convenience functions for k-group kernels
inline void vec_mul_kgroup(int m, int n, int k, int k_group_size,
std::shared_ptr<GemmKernel224Int4_1_LowKGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4_1_LowKGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4_1_LowKGroup::BufferC> bc, int ith, int nth) {
integer_mat_mul_kgroup<GemmKernel224Int4_1_LowKGroup, false>(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith,
nth);
}
inline void mat_mul_kgroup(int m, int n, int k, int k_group_size,
std::shared_ptr<GemmKernel224Int4_1_LowKGroup::BufferA> ba,
std::shared_ptr<GemmKernel224Int4_1_LowKGroup::BufferB> bb,
std::shared_ptr<GemmKernel224Int4_1_LowKGroup::BufferC> bc, int ith, int nth) {
integer_mat_mul_kgroup<GemmKernel224Int4_1_LowKGroup, true>(m, n, k, k_group_size, ba.get(), bb.get(), bc.get(), ith,
nth);
}
} // namespace amx
#endif // AMX_KERNELS_HPP