mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-28 08:57:40 -05:00
* Added tiled mul_mat.
For each mul_mat_one_chunk, quants are unpacked into (max) 256x256 tiles of int8,
one routine per quent. Then microkernel computes 16x16 tiles before writing out
256x256 float reults to main memory.
Tests/benches in tests/test-tiled-mulmat.cpp. 3-6x speed improvement
for large matmul, break even at 4096x64 * 64x4096, 80% performance (net
loss) for GEMV. Error rates trivial (order of 1-e04 max, 1-e05 rmse).
* Fixes for ARM/windows builds
* more windows fixes, ggml-cpu.h isn't visible in MSVC for some reason
* unified iqp + tiled on the Q5_K, IQ4_XS set for benchmarking, updated benchmark
* Fixed accidental removal of llama_build_and_test(test-backend-ops.cpp)
* First integration of iqp code
Co-authored-by Bartowski <3266127+bartowski1182@users.noreply.github.com>
* Cleaning up declaration of iq unpacking helpers to align with the bit unpackers
* Removed iqp path
* Fix cross-platform warnings
* Disabling benchmarks unless explicitly enabled
* Fix backend_init for DLL-based builds, add self and bartowski to CODEOWNERS for tiled
* Put benchmarks behind a flag
* kernel fix for AVX2, iq quants
* Fix for asan, leaking memory in test-tiled-mulmat and avoid stack use after return
* guarding env flags with std::call_once
* Simplified repacking for VNNI to a single call per macrotile
* No threadlocals anymore, aligned wdata access
* Doing aligned reads since we ensure alignment with padding in wdata
* Eliminated per-thread gather of Q8_K rows in mul_mat_id, we now gather/repack in a single pass. Repack method now takes pointer array to support both dense/normal and mmid paths. Interface with ggml-cpu.c simplified as a result
* Unified/simplified dispatch and support checks. Put details on wdata needed inside the kernel.h body, simplified interactions with ggml-cpu.c.
* Cleanup includes and whitespace, update src1_repack to return false if we don't need a special repack, so the common case is handled by driver
* Better detection of win32 and additional whitespace fixes
* Gating fuzz tests behind a parameter and some extra prints to try and fix slow CI hosts
* Optimized AVX2 kernel
* Changed interleave format and added ability to interleave in-place after dequant
* Repacks now happen in-place, 16x64 microtiles are independent of each other
* Only repack rows in groups of 16 as they're needed. Save work in low n_rows cases and optimize L1 usage in other cases
* Use long panels for memory-bound regime (M <= 16), reintroduce IQP path for benchmarks
* Fix unused warnings and cleanup. Improved IQ dequantization speed.
* Removed separate process benchmarks
* Revert "Removed separate process benchmarks"
This reverts commit 0688cf43d5.
* AVX2 optimizations and guards for tests on windows
* Removed temp perf harness
* Remove perf-mulmat from build
* Removed IQP path, simplified tests to not use sub processes
* Cleaning up alignment of wdata
* Whitespace fixes and aligning L2 workspace to clean 512kb boundaries
* Update ggml/src/ggml-cpu/tiled/tiled-kernel.cpp
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
* Cleanup merge-duplicated declaration of test-backend-ops target
* Undo accidental line deletion in ggml.c
---------
Co-authored-by: Georgi Gerganov <ggerganov@gmail.com>
853 lines
40 KiB
C++
853 lines
40 KiB
C++
#include "ggml-alloc.h"
|
|
#include "ggml-backend.h"
|
|
#include "ggml.h"
|
|
|
|
#include <time.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#if defined(_WIN32)
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
// failed checks; main exits non-zero on any failures
|
|
static int n_failed = 0;
|
|
|
|
static float * gen_rand_f32(int64_t n) {
|
|
float * data = (float *) malloc(n * sizeof(float));
|
|
for (int64_t i = 0; i < n; ++i) {
|
|
data[i] = (float)rand() / (float)RAND_MAX - 0.5f;
|
|
data[i] *= 5.0f;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
// updates pointers to max_err and rms_err
|
|
static void compare_f32(const float * ref, const float * out, int64_t n, float * max_err, float * rms_err) {
|
|
*max_err = 0.0f;
|
|
double sum_sq_err = 0.0;
|
|
for (int64_t i = 0; i < n; ++i) {
|
|
float err = fabsf(ref[i] - out[i]);
|
|
if (err > *max_err) {
|
|
*max_err = err;
|
|
}
|
|
sum_sq_err += (double)err * err;
|
|
}
|
|
*rms_err = sqrt(sum_sq_err / n);
|
|
}
|
|
|
|
// Fill from a flat row-major f32 source: rows * cols floats.
|
|
static void fill_tensor(struct ggml_tensor * t, const float * src, int64_t rows, int64_t cols, ggml_type qtype) {
|
|
GGML_ASSERT(t->ne[0] == cols);
|
|
GGML_ASSERT(rows * cols == ggml_nelements(t));
|
|
if (qtype == GGML_TYPE_F32) {
|
|
ggml_backend_tensor_set(t, src, 0, ggml_nbytes(t));
|
|
return;
|
|
}
|
|
void * q = malloc(ggml_nbytes(t));
|
|
// some iq types (iq2_xxs, iq2_xs, iq1_s) steer their code choice with an imatrix; the
|
|
// quantized bytes are shared by the reference and tiled paths, so a dummy imatrix is enough
|
|
const float * imatrix = NULL;
|
|
float * im = NULL;
|
|
if (ggml_quantize_requires_imatrix(qtype)) {
|
|
im = (float *) malloc(rows * cols * sizeof(float));
|
|
for (int64_t i = 0; i < rows * cols; i++) im[i] = 1.0f;
|
|
imatrix = im;
|
|
}
|
|
ggml_quantize_chunk(qtype, src, q, 0, rows, cols, imatrix);
|
|
ggml_backend_tensor_set(t, q, 0, ggml_nbytes(t));
|
|
free(q);
|
|
free(im);
|
|
}
|
|
|
|
// The CPU-specific control API is resolved through the backend registry: with
|
|
// GGML_BACKEND_DL the CPU backend is a runtime-loaded module and its symbols
|
|
// are not available at link time
|
|
static void cpu_set_use_ref(ggml_backend_t backend, bool use_ref) {
|
|
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backend));
|
|
void (* set_use_ref)(ggml_backend_t, bool) =
|
|
(void (*)(ggml_backend_t, bool)) ggml_backend_reg_get_proc_address(reg, "ggml_backend_cpu_set_use_ref");
|
|
if (!set_use_ref) {
|
|
fprintf(stderr, "ggml_backend_cpu_set_use_ref not available\n");
|
|
exit(1);
|
|
}
|
|
set_use_ref(backend, use_ref);
|
|
}
|
|
|
|
static void cpu_set_n_threads(ggml_backend_t backend, int n_threads) {
|
|
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backend));
|
|
void (* set_n_threads)(ggml_backend_t, int) =
|
|
(void (*)(ggml_backend_t, int)) ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads");
|
|
if (!set_n_threads) {
|
|
fprintf(stderr, "ggml_backend_set_n_threads not available\n");
|
|
exit(1);
|
|
}
|
|
set_n_threads(backend, n_threads);
|
|
}
|
|
|
|
static void test_matmul(ggml_backend_t backend, int64_t M, int64_t N, int64_t K, ggml_type quant_type) {
|
|
srand(0xBEEF);
|
|
|
|
float * src1_ref = gen_rand_f32(M * N);
|
|
float * src0_ref = gen_rand_f32(N * K);
|
|
float * dst_out = (float *) malloc(M * K * sizeof(float));
|
|
float * dst_tiled = (float *) malloc(M * K * sizeof(float));
|
|
|
|
struct ggml_init_params ip = { 1024*1024*1024, nullptr, true };
|
|
struct ggml_context * ctx = ggml_init(ip);
|
|
|
|
// ggml_mul_mat(t0, t1) computes t1 * t0^T: t0 (src0, quant) is N wide x
|
|
// K high, t1 (src1, f32) is N wide x M high, dst = (K, M).
|
|
struct ggml_tensor * src1 = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, N, M);
|
|
struct ggml_tensor * src0 = ggml_new_tensor_2d(ctx, quant_type, N, K);
|
|
|
|
struct ggml_cgraph * gf = ggml_new_graph(ctx);
|
|
struct ggml_tensor * dst = ggml_mul_mat(ctx, src0, src1);
|
|
ggml_build_forward_expand(gf, dst);
|
|
|
|
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
|
|
|
|
fill_tensor(src1, src1_ref, M, N, GGML_TYPE_F32);
|
|
fill_tensor(src0, src0_ref, K, N, quant_type);
|
|
|
|
cpu_set_use_ref(backend, true);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, dst_out, 0, ggml_nbytes(dst));
|
|
cpu_set_use_ref(backend, false);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, dst_tiled, 0, ggml_nbytes(dst));
|
|
|
|
// std vs tiled: identical quantized inputs, so any large difference here is a bug in the tiled kernel
|
|
float max_err, rms_err;
|
|
compare_f32(dst_out, dst_tiled, M*K, &max_err, &rms_err);
|
|
float tol = 1e-3f;
|
|
|
|
printf("TEST %lldx%lld * %lldx%lld (%s): %s (max_err: %f, rms: %f, tolerance: %f)\n",
|
|
(long long)M, (long long)N, (long long)N, (long long)K,
|
|
ggml_type_name(quant_type), (max_err <= tol) ? "PASS" : "FAIL", max_err, rms_err, tol);
|
|
|
|
// if the tiled kernel deviates from std beyond quantization tolerance,
|
|
// dump a few offenders
|
|
if (max_err > tol) {
|
|
int64_t shown = 0;
|
|
for (int64_t i = 0; i < M*K && shown < 8; ++i) {
|
|
float err = fabsf(dst_out[i] - dst_tiled[i]);
|
|
if (err > tol) {
|
|
printf(" tiled vs std: i=%lld (m=%lld k=%lld) std=%f tiled=%f err=%f\n",
|
|
(long long)i, (long long)(i/K), (long long)(i%K), dst_out[i], dst_tiled[i], err);
|
|
++shown;
|
|
}
|
|
}
|
|
++n_failed;
|
|
}
|
|
|
|
ggml_backend_buffer_free(buf);
|
|
ggml_free(ctx);
|
|
free(src1_ref); free(src0_ref); free(dst_out); free(dst_tiled);
|
|
}
|
|
|
|
// Higher-dim (ne[2], ne[3] > 1) check, std (ggml_mul_mat) is trusted as the reference
|
|
//
|
|
// src1 (F32) : [N, M, src1_2, src1_3] ne0=N (reduction), ne1=M (out0)
|
|
// src0 (quant): [N, K, src0_2, src0_3] ne0=N (reduction), ne1=K (out1)
|
|
// dst = ggml_mul_mat(src0, src1) : [K, M, src1_2, src1_3]
|
|
static void test_matmul_highdim(ggml_backend_t backend, int64_t M, int64_t N, int64_t K,
|
|
int64_t src1_2, int64_t src1_3,
|
|
int64_t src0_2, int64_t src0_3,
|
|
ggml_type quant_type) {
|
|
srand(0xBEEF);
|
|
|
|
const int64_t n_src1 = N*M*src1_2*src1_3;
|
|
const int64_t n_src0 = N*K*src0_2*src0_3;
|
|
const int64_t n_dst = K*M*src1_2*src1_3;
|
|
|
|
float * src1_ref = gen_rand_f32(n_src1);
|
|
float * src0_ref = gen_rand_f32(n_src0);
|
|
float * dst_std = (float *) malloc(n_dst * sizeof(float));
|
|
float * dst_tiled = (float *) malloc(n_dst * sizeof(float));
|
|
|
|
struct ggml_init_params ip = { 1024*1024*1024, nullptr, true };
|
|
struct ggml_context * ctx = ggml_init(ip);
|
|
|
|
int64_t ne_src1[4] = { N, M, src1_2, src1_3 };
|
|
int64_t ne_src0[4] = { N, K, src0_2, src0_3 };
|
|
struct ggml_tensor * src1 = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne_src1);
|
|
struct ggml_tensor * src0 = ggml_new_tensor(ctx, quant_type, 4, ne_src0);
|
|
|
|
struct ggml_cgraph * gf = ggml_new_graph(ctx);
|
|
struct ggml_tensor * dst = ggml_mul_mat(ctx, src0, src1);
|
|
ggml_build_forward_expand(gf, dst);
|
|
|
|
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
|
|
|
|
// the batch dims fold into the row count (rows are contiguous, ne0 fastest)
|
|
fill_tensor(src1, src1_ref, M * src1_2 * src1_3, N, GGML_TYPE_F32);
|
|
fill_tensor(src0, src0_ref, K * src0_2 * src0_3, N, quant_type);
|
|
|
|
// reference = stock path (use_ref keeps the op off the tiled hook), tiled
|
|
// = the gated path; same weights, same op, run twice into separate buffers
|
|
cpu_set_use_ref(backend, true);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, dst_std, 0, ggml_nbytes(dst));
|
|
cpu_set_use_ref(backend, false);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, dst_tiled, 0, ggml_nbytes(dst));
|
|
|
|
// max |dst|: scale for the quantization tolerance
|
|
float scale = 0.0f;
|
|
for (int64_t i = 0; i < n_dst; ++i) {
|
|
scale = fmaxf(scale, fabsf(dst_std[i]));
|
|
}
|
|
|
|
// std vs tiled: identical quantized inputs, so a large difference is a bug
|
|
float max_err, rms_err;
|
|
compare_f32(dst_std, dst_tiled, n_dst, &max_err, &rms_err);
|
|
float tol = (quant_type == GGML_TYPE_F32) ? 1e-4f : fmaxf(1e-3f, 1e-3f*scale);
|
|
|
|
// which path did the tiled op take (mirrors the hard-constraint gate; the
|
|
// harness runs with force on, so the profitability check is bypassed)
|
|
bool tiled_kernel = (N % 256 == 0) && (src0_2 > 0) && (src0_3 > 0)
|
|
&& (src1_2 % src0_2 == 0) && (src1_3 % src0_3 == 0);
|
|
|
|
printf("TEST %lldx%lldx%lldx%lld * %lldx%lldx%lldx%lld (%s, %s): %s (max_err: %f, rms: %f, scale: %f)\n",
|
|
(long long)M, (long long)N, (long long)src1_2, (long long)src1_3,
|
|
(long long)K, (long long)N, (long long)src0_2, (long long)src0_3,
|
|
ggml_type_name(quant_type), tiled_kernel ? "tiled-kernel" : "stock",
|
|
(max_err <= tol) ? "PASS" : "FAIL", max_err, rms_err, scale);
|
|
|
|
if (max_err > tol) {
|
|
int64_t shown = 0;
|
|
for (int64_t i = 0; i < n_dst && shown < 8; ++i) {
|
|
float err = fabsf(dst_std[i] - dst_tiled[i]);
|
|
if (err > tol) {
|
|
printf(" tiled vs std: i=%lld std=%f tiled=%f err=%f\n",
|
|
(long long)i, dst_std[i], dst_tiled[i], err);
|
|
++shown;
|
|
}
|
|
}
|
|
++n_failed;
|
|
}
|
|
|
|
ggml_backend_buffer_free(buf);
|
|
ggml_free(ctx);
|
|
free(src1_ref); free(src0_ref); free(dst_std); free(dst_tiled);
|
|
}
|
|
|
|
// MUL_MAT_ID (MoE) check, std (vec_dot) is trusted as the reference
|
|
//
|
|
// src0 (as, quant): [K, R, n_experts] ne0 = K (reduction), ne1 = R rows per expert, ne2 = experts
|
|
// src1 (b, F32) : [K, b_slots, batch] ne0 = K (reduction), ne1 = b_slots (b rows, broadcast over k columns)
|
|
// ids : [k, batch] expert picked per (slot, batch row); k % b_slots == 0
|
|
// dst = ggml_mul_mat_id(as, b, ids) : [R, k, batch]; column (id, t) = GEMV of src1 row (id % b_slots + t*b_slots)
|
|
// against expert ids[t*k+id]. b_slots = 1 is the common MoE case (all top-k experts see one input row).
|
|
static void test_mul_mat_id(ggml_backend_t backend, int64_t K, int64_t R, int64_t n_experts,
|
|
int64_t k, int64_t b_slots, int64_t batch, ggml_type quant_type,
|
|
bool src1_strided = false) {
|
|
srand(0xBEEF);
|
|
|
|
const int64_t n_as = K * R * n_experts;
|
|
const int64_t n_b = K * b_slots * batch;
|
|
const int64_t n_dst = R * k * batch;
|
|
|
|
float * as_ref = gen_rand_f32(n_as);
|
|
float * b_ref = gen_rand_f32(n_b);
|
|
int32_t * ids = (int32_t *) malloc(k * batch * sizeof(int32_t));
|
|
float * dst_ref = (float *) malloc(n_dst * sizeof(float));
|
|
float * dst_tiled = (float *) malloc(n_dst * sizeof(float));
|
|
|
|
struct ggml_init_params ip = { 1024*1024*1024, nullptr, true };
|
|
struct ggml_context * ctx = ggml_init(ip);
|
|
|
|
int64_t ne_as[4] = { K, R, n_experts, 1 };
|
|
int64_t ne_b[4] = { K, b_slots, batch, 1 };
|
|
int64_t ne_ids[4] = { k, batch, 1, 1 };
|
|
struct ggml_tensor * src0 = ggml_new_tensor(ctx, quant_type, 4, ne_as);
|
|
struct ggml_tensor * ids_t = ggml_new_tensor(ctx, GGML_TYPE_I32, 4, ne_ids);
|
|
struct ggml_tensor * src1;
|
|
struct ggml_tensor * src1_base = NULL;
|
|
if (src1_strided) {
|
|
// strided view: nb[1] = 4 bytes, the tiled path reads src1 through strides into wdata
|
|
src1_base = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, K, b_slots * batch);
|
|
src1 = ggml_view_3d(ctx, src1_base, K, b_slots, batch, 4, (size_t) b_slots * K * 4, 0);
|
|
} else {
|
|
src1 = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne_b);
|
|
}
|
|
|
|
struct ggml_cgraph * gf = ggml_new_graph(ctx);
|
|
struct ggml_tensor * dst = ggml_mul_mat_id(ctx, src0, src1, ids_t);
|
|
ggml_build_forward_expand(gf, dst);
|
|
|
|
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
|
|
|
|
// deterministic balanced routing: each expert gets ~ k*batch/n_experts rows
|
|
for (int64_t t = 0; t < batch; t++) {
|
|
for (int64_t e = 0; e < k; e++) {
|
|
ids[t * k + e] = (int32_t) ((t * k + e) % n_experts);
|
|
}
|
|
}
|
|
|
|
fill_tensor(src1, b_ref, b_slots * batch, K, GGML_TYPE_F32);
|
|
fill_tensor(src0, as_ref, R * n_experts, K, quant_type);
|
|
ggml_backend_tensor_set(ids_t, ids, 0, ggml_nbytes(ids_t));
|
|
|
|
cpu_set_use_ref(backend, true);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, dst_ref, 0, ggml_nbytes(dst));
|
|
cpu_set_use_ref(backend, false);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, dst_tiled, 0, ggml_nbytes(dst));
|
|
|
|
// same quantized inputs, so a large difference is a bug in the tiled path
|
|
float max_err, rms_err;
|
|
compare_f32(dst_ref, dst_tiled, n_dst, &max_err, &rms_err);
|
|
float tol = 1e-3f;
|
|
|
|
printf("TEST mul_mat_id %lldx%lldx%lld * %lldx%lldx%lld ids[%lld,%lld] (%s%s): %s (max_err: %f, rms: %f, tolerance: %f)\n",
|
|
(long long)K, (long long)R, (long long)n_experts,
|
|
(long long)K, (long long)b_slots, (long long)batch,
|
|
(long long)k, (long long)batch,
|
|
ggml_type_name(quant_type), src1_strided ? ", strided-b" : "",
|
|
(max_err <= tol) ? "PASS" : "FAIL", max_err, rms_err, tol);
|
|
|
|
if (max_err > tol) {
|
|
int64_t shown = 0;
|
|
for (int64_t i = 0; i < n_dst && shown < 8; ++i) {
|
|
float err = fabsf(dst_ref[i] - dst_tiled[i]);
|
|
if (err > tol) {
|
|
printf(" tiled vs std: i=%lld (row=%lld slot=%lld batch=%lld) std=%f tiled=%f err=%f\n",
|
|
(long long)i, (long long)(i % R), (long long)((i / R) % k), (long long)(i / (R * k)),
|
|
dst_ref[i], dst_tiled[i], err);
|
|
++shown;
|
|
}
|
|
}
|
|
++n_failed;
|
|
}
|
|
|
|
ggml_backend_buffer_free(buf);
|
|
ggml_free(ctx);
|
|
free(as_ref); free(b_ref); free(ids); free(dst_ref); free(dst_tiled);
|
|
}
|
|
|
|
static double time_graph_compute(ggml_backend_t backend, struct ggml_cgraph * gf) {
|
|
#if defined(_WIN32)
|
|
// high-res timer; clock_gettime is not portable across Windows toolchains
|
|
LARGE_INTEGER freq, t0, t1;
|
|
QueryPerformanceFrequency(&freq);
|
|
QueryPerformanceCounter(&t0);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
QueryPerformanceCounter(&t1);
|
|
return (double) (t1.QuadPart - t0.QuadPart) / (double) freq.QuadPart;
|
|
#else
|
|
struct timespec t0, t1;
|
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
|
return (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;
|
|
#endif
|
|
}
|
|
|
|
// Warm up, then time n runs and return the best
|
|
static double time_graph_compute_best(ggml_backend_t backend, struct ggml_cgraph * gf, int n,
|
|
void * flush_buf = NULL, size_t flush_size = 0) {
|
|
|
|
time_graph_compute(backend, gf); // warmup
|
|
double best = 1e30;
|
|
for (int i = 0; i < n; ++i) {
|
|
// Evict L3 cache first
|
|
if (flush_buf) { memset(flush_buf, 0xAB, flush_size); }
|
|
const double t = time_graph_compute(backend, gf);
|
|
if (t < best) best = t;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// Fetch the repack extra buffer type through the public proc-address API
|
|
static ggml_backend_buffer_type_t get_cpu_repack_buft(void) {
|
|
ggml_backend_dev_t cpu_dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
|
if (!cpu_dev) {
|
|
return NULL;
|
|
}
|
|
ggml_backend_reg_t cpu_reg = ggml_backend_dev_backend_reg(cpu_dev);
|
|
ggml_backend_dev_get_extra_bufts_t get_extra =
|
|
(ggml_backend_dev_get_extra_bufts_t) ggml_backend_reg_get_proc_address(cpu_reg, "ggml_backend_dev_get_extra_bufts");
|
|
if (!get_extra) {
|
|
return NULL;
|
|
}
|
|
ggml_backend_buffer_type_t * bufts = get_extra(cpu_dev);
|
|
// the only extra buffer type the CPU backend exposes is CPU_REPACK
|
|
return (bufts && *bufts) ? *bufts : NULL;
|
|
}
|
|
|
|
struct bench_row {
|
|
const char * name;
|
|
double time_std, time_repack, time_tiled;
|
|
float max_err_repack, rmse_repack;
|
|
float max_err_tiled, rmse_tiled;
|
|
bool have_repack;
|
|
};
|
|
|
|
// MUL_MAT three-way bench: std (default optimized GEMM) vs repack (CPU_REPACK buffer) vs
|
|
// tiled kernel. std is timed in-process with use_ref=true (bypasses the tiled gate); tiled
|
|
// and repack with use_ref=false (repack's tiled gate declines on the buffer's extra). repack
|
|
// is n/a where no repack kernel exists for the type. Errors are vs the vec-only reference (use_ref).
|
|
static bench_row bench_three_way(ggml_backend_t backend, int64_t M, int64_t N, int64_t K, ggml_type quant_type) {
|
|
bench_row row;
|
|
row.name = ggml_type_name(quant_type);
|
|
row.time_std = row.time_repack = row.time_tiled = 0.0;
|
|
row.max_err_repack = row.rmse_repack = row.max_err_tiled = row.rmse_tiled = 0.0f;
|
|
row.have_repack = false;
|
|
|
|
struct ggml_init_params ip = { 1024*1024*1024, nullptr, true };
|
|
struct ggml_context * ctx = ggml_init(ip);
|
|
|
|
struct ggml_tensor * src1 = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, N, M);
|
|
struct ggml_tensor * src0_std = ggml_new_tensor_2d(ctx, quant_type, N, K);
|
|
struct ggml_tensor * src0_rep = ggml_new_tensor_2d(ctx, quant_type, N, K);
|
|
|
|
struct ggml_cgraph * gf = ggml_new_graph(ctx);
|
|
struct ggml_tensor * dst = ggml_mul_mat(ctx, src0_std, src1);
|
|
ggml_build_forward_expand(gf, dst);
|
|
|
|
struct ggml_cgraph * gf_repack = ggml_new_graph(ctx);
|
|
struct ggml_tensor * dst_repack = ggml_mul_mat(ctx, src0_rep, src1);
|
|
ggml_build_forward_expand(gf_repack, dst_repack);
|
|
|
|
ggml_backend_buffer_type_t repack_buft = get_cpu_repack_buft();
|
|
ggml_backend_buffer_t buf_rep = NULL;
|
|
if (repack_buft && N % 8 == 0 && K % 8 == 0) {
|
|
buf_rep = ggml_backend_buft_alloc_buffer(repack_buft, ggml_nbytes(src0_rep));
|
|
src0_rep->buffer = buf_rep;
|
|
src0_rep->data = ggml_backend_buffer_get_base(buf_rep);
|
|
ggml_backend_buffer_init_tensor(buf_rep, src0_rep);
|
|
row.have_repack = (src0_rep->extra != NULL);
|
|
}
|
|
|
|
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
|
|
|
|
srand(0xBEEF);
|
|
float * src1_data = gen_rand_f32(M * N);
|
|
float * src0_data = gen_rand_f32(N * K);
|
|
fill_tensor(src1, src1_data, M, N, GGML_TYPE_F32);
|
|
fill_tensor(src0_std, src0_data, K, N, quant_type);
|
|
if (row.have_repack) {
|
|
fill_tensor(src0_rep, src0_data, K, N, quant_type);
|
|
}
|
|
free(src1_data); free(src0_data);
|
|
|
|
// one warmup + N timings; best (min) wins. The flush buffer is larger than the
|
|
// whole-chip cache, so its memset evicts L3 and each timed iteration streams DRAM.
|
|
const size_t flush_size = 256 * 1024 * 1024;
|
|
void * flush_buf = malloc(flush_size);
|
|
const int n_reps = 5;
|
|
|
|
// std in-process (use_ref bypasses the tiled gate; the vec_dot is the same optimized one)
|
|
cpu_set_use_ref(backend, true);
|
|
row.time_std = time_graph_compute_best(backend, gf, n_reps, flush_buf, flush_size);
|
|
|
|
// tiled in-process (the parent holds TILED_MM=1 forced on)
|
|
cpu_set_use_ref(backend, false);
|
|
row.time_tiled = time_graph_compute_best(backend, gf, n_reps, flush_buf, flush_size);
|
|
|
|
// repack in-process (the tiled gate declines on the buffer's extra)
|
|
if (row.have_repack) {
|
|
row.time_repack = time_graph_compute_best(backend, gf_repack, n_reps, flush_buf, flush_size);
|
|
}
|
|
free(flush_buf);
|
|
|
|
// errors vs the vec-only reference (use_ref)
|
|
float * out_ref = (float *) malloc(M * K * sizeof(float));
|
|
float * out_tiled = (float *) malloc(M * K * sizeof(float));
|
|
float * out_repack = (float *) malloc(M * K * sizeof(float));
|
|
cpu_set_use_ref(backend, true);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, out_ref, 0, ggml_nbytes(dst));
|
|
cpu_set_use_ref(backend, false);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, out_tiled, 0, ggml_nbytes(dst));
|
|
compare_f32(out_ref, out_tiled, M * K, &row.max_err_tiled, &row.rmse_tiled);
|
|
if (row.have_repack) {
|
|
ggml_backend_graph_compute(backend, gf_repack);
|
|
ggml_backend_tensor_get(dst_repack, out_repack, 0, ggml_nbytes(dst_repack));
|
|
compare_f32(out_ref, out_repack, M * K, &row.max_err_repack, &row.rmse_repack);
|
|
}
|
|
free(out_ref); free(out_tiled); free(out_repack);
|
|
|
|
ggml_backend_buffer_free(buf_rep);
|
|
ggml_backend_buffer_free(buf);
|
|
ggml_free(ctx);
|
|
return row;
|
|
}
|
|
|
|
static void print_bench_table(int64_t M, int64_t N, int64_t K, const bench_row * rows, size_t n_types) {
|
|
const double flops = 2.0 * M * N * K;
|
|
|
|
printf("\nBENCH dense %lldx%lld * %lldx%lld (std vs repack vs tiled), min of 5 timings, 8 threads\n",
|
|
(long long)M, (long long)N, (long long)N, (long long)K);
|
|
printf("%-8s %10s %12s %12s %11s %11s %17s %17s %17s %17s\n",
|
|
"type", "std TF", "repack TF", "tiled TF", "repack/std", "tiled/std",
|
|
"max_err(repack)", "rmse(repack)", "max_err(tiled)", "rmse(tiled)");
|
|
for (size_t i = 0; i < n_types; ++i) {
|
|
const bench_row * r = &rows[i];
|
|
if (r->have_repack) {
|
|
printf("%-8s %10.3f %12.3f %12.3f %11.2f %11.2f %17.5e %17.5e %17.5e %17.5e\n",
|
|
r->name,
|
|
flops / (r->time_std * 1e12),
|
|
flops / (r->time_repack * 1e12), flops / (r->time_tiled * 1e12),
|
|
r->time_std / r->time_repack, r->time_std / r->time_tiled,
|
|
r->max_err_repack, r->rmse_repack, r->max_err_tiled, r->rmse_tiled);
|
|
} else {
|
|
printf("%-8s %10.3f %12s %12.3f %11s %11.2f %17s %17s %17.5e %17.5e\n",
|
|
r->name,
|
|
flops / (r->time_std * 1e12),
|
|
"n/a", flops / (r->time_tiled * 1e12), "n/a",
|
|
r->time_std / r->time_tiled,
|
|
"n/a", "n/a", r->max_err_tiled, r->rmse_tiled);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct bench_row_mmid {
|
|
const char * name;
|
|
double time_std, time_repack, time_tiled;
|
|
float max_err_repack, rmse_repack;
|
|
float max_err_tiled, rmse_tiled;
|
|
bool have_repack;
|
|
};
|
|
|
|
// MUL_MAT_ID (MoE) three-way bench: std (default GEMM, use_ref) vs repack (CPU_REPACK buffer)
|
|
// vs tiled kernel (default; TILED_MM_FORCE is set in main so ragged experts take it too).
|
|
// Column (id, t) = GEMV of src1 row (id % b_slots + t * b_slots) against expert ids[t*k+id];
|
|
// cne1 = k * batch / n_experts rows per expert on average. repack is n/a where no repack
|
|
// kernel exists. Errors are vs the vec-only reference (use_ref).
|
|
static bench_row_mmid bench_mul_mat_id(ggml_backend_t backend, int64_t K, int64_t R, int64_t n_experts,
|
|
int64_t k, int64_t b_slots, int64_t batch, ggml_type quant_type) {
|
|
bench_row_mmid row;
|
|
row.name = ggml_type_name(quant_type);
|
|
row.time_std = row.time_repack = row.time_tiled = 0.0;
|
|
row.max_err_repack = row.rmse_repack = row.max_err_tiled = row.rmse_tiled = 0.0f;
|
|
row.have_repack = false;
|
|
|
|
struct ggml_init_params ip = { 1024*1024*1024, nullptr, true };
|
|
struct ggml_context * ctx = ggml_init(ip);
|
|
|
|
int64_t ne_as[4] = { K, R, n_experts, 1 };
|
|
int64_t ne_b[4] = { K, b_slots, batch, 1 };
|
|
int64_t ne_ids[4] = { k, batch, 1, 1 };
|
|
struct ggml_tensor * src1 = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne_b);
|
|
struct ggml_tensor * src0_std = ggml_new_tensor(ctx, quant_type, 4, ne_as);
|
|
struct ggml_tensor * src0_rep = ggml_new_tensor(ctx, quant_type, 4, ne_as);
|
|
struct ggml_tensor * ids_t = ggml_new_tensor(ctx, GGML_TYPE_I32, 4, ne_ids);
|
|
|
|
struct ggml_cgraph * gf = ggml_new_graph(ctx);
|
|
struct ggml_tensor * dst = ggml_mul_mat_id(ctx, src0_std, src1, ids_t);
|
|
ggml_build_forward_expand(gf, dst);
|
|
|
|
struct ggml_cgraph * gf_repack = ggml_new_graph(ctx);
|
|
struct ggml_tensor * dst_repack = ggml_mul_mat_id(ctx, src0_rep, src1, ids_t);
|
|
ggml_build_forward_expand(gf_repack, dst_repack);
|
|
|
|
ggml_backend_buffer_type_t repack_buft = get_cpu_repack_buft();
|
|
ggml_backend_buffer_t buf_rep = NULL;
|
|
if (repack_buft && R % 8 == 0) {
|
|
buf_rep = ggml_backend_buft_alloc_buffer(repack_buft, ggml_nbytes(src0_rep));
|
|
src0_rep->buffer = buf_rep;
|
|
src0_rep->data = ggml_backend_buffer_get_base(buf_rep);
|
|
ggml_backend_buffer_init_tensor(buf_rep, src0_rep);
|
|
row.have_repack = (src0_rep->extra != NULL);
|
|
}
|
|
|
|
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
|
|
|
|
// deterministic balanced routing: each expert gets ~ k*batch/n_experts rows
|
|
int32_t * ids = (int32_t *) malloc(k * batch * sizeof(int32_t));
|
|
for (int64_t t = 0; t < batch; t++) {
|
|
for (int64_t e = 0; e < k; e++) {
|
|
ids[t * k + e] = (int32_t) ((t * k + e) % n_experts);
|
|
}
|
|
}
|
|
srand(0xBEEF);
|
|
float * b_data = gen_rand_f32(K * b_slots * batch);
|
|
float * as_data = gen_rand_f32(K * R * n_experts);
|
|
fill_tensor(src1, b_data, b_slots * batch, K, GGML_TYPE_F32);
|
|
fill_tensor(src0_std, as_data, R * n_experts, K, quant_type);
|
|
if (row.have_repack) {
|
|
fill_tensor(src0_rep, as_data, R * n_experts, K, quant_type);
|
|
}
|
|
ggml_backend_tensor_set(ids_t, ids, 0, ggml_nbytes(ids_t));
|
|
free(b_data); free(as_data);
|
|
|
|
const size_t flush_size = 256 * 1024 * 1024;
|
|
void * flush_buf = malloc(flush_size);
|
|
const int n_reps = 5;
|
|
|
|
// std in-process (use_ref bypasses the tiled gate; the vec_dot is the same optimized one)
|
|
cpu_set_use_ref(backend, true);
|
|
row.time_std = time_graph_compute_best(backend, gf, n_reps, flush_buf, flush_size);
|
|
|
|
// tiled in-process (the parent holds TILED_MM=1 forced on)
|
|
cpu_set_use_ref(backend, false);
|
|
row.time_tiled = time_graph_compute_best(backend, gf, n_reps, flush_buf, flush_size);
|
|
|
|
// repack in-process (the tiled gate declines on the buffer's extra)
|
|
if (row.have_repack) {
|
|
row.time_repack = time_graph_compute_best(backend, gf_repack, n_reps, flush_buf, flush_size);
|
|
}
|
|
free(flush_buf);
|
|
|
|
// errors vs the vec-only reference (use_ref)
|
|
float * out_ref = (float *) malloc(R * k * batch * sizeof(float));
|
|
float * out_tiled = (float *) malloc(R * k * batch * sizeof(float));
|
|
float * out_repack = (float *) malloc(R * k * batch * sizeof(float));
|
|
cpu_set_use_ref(backend, true);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, out_ref, 0, ggml_nbytes(dst));
|
|
cpu_set_use_ref(backend, false);
|
|
ggml_backend_graph_compute(backend, gf);
|
|
ggml_backend_tensor_get(dst, out_tiled, 0, ggml_nbytes(dst));
|
|
compare_f32(out_ref, out_tiled, R * k * batch, &row.max_err_tiled, &row.rmse_tiled);
|
|
if (row.have_repack) {
|
|
ggml_backend_graph_compute(backend, gf_repack);
|
|
ggml_backend_tensor_get(dst_repack, out_repack, 0, ggml_nbytes(dst_repack));
|
|
compare_f32(out_ref, out_repack, R * k * batch, &row.max_err_repack, &row.rmse_repack);
|
|
}
|
|
free(out_ref); free(out_tiled); free(out_repack); free(ids);
|
|
|
|
ggml_backend_buffer_free(buf_rep);
|
|
ggml_backend_buffer_free(buf);
|
|
ggml_free(ctx);
|
|
return row;
|
|
}
|
|
|
|
static void print_mmid_table(int64_t K, int64_t R, int64_t n_experts, int64_t k,
|
|
int64_t b_slots, int64_t batch, const bench_row_mmid * rows, size_t n_types) {
|
|
const double flops = 2.0 * (double) K * R * k * batch;
|
|
|
|
printf("\nBENCH mmid %lldx%lldx%lld k=%lld b_slots=%lld batch=%lld (cne1=%lld, std vs repack vs tiled), min of 5 timings, 8 threads\n",
|
|
(long long)K, (long long)R, (long long)n_experts, (long long)k,
|
|
(long long)b_slots, (long long)batch, (long long)(k * batch / n_experts));
|
|
printf("%-8s %10s %12s %12s %11s %11s %17s %17s %17s %17s\n",
|
|
"type", "std TF", "repack TF", "tiled TF", "repack/std", "tiled/std",
|
|
"max_err(repack)", "rmse(repack)", "max_err(tiled)", "rmse(tiled)");
|
|
for (size_t i = 0; i < n_types; ++i) {
|
|
const bench_row_mmid * r = &rows[i];
|
|
if (r->have_repack) {
|
|
printf("%-8s %10.3f %12.3f %12.3f %11.2f %11.2f %17.5e %17.5e %17.5e %17.5e\n",
|
|
r->name,
|
|
flops / (r->time_std * 1e12),
|
|
flops / (r->time_repack * 1e12), flops / (r->time_tiled * 1e12),
|
|
r->time_std / r->time_repack, r->time_std / r->time_tiled,
|
|
r->max_err_repack, r->rmse_repack, r->max_err_tiled, r->rmse_tiled);
|
|
} else {
|
|
printf("%-8s %10.3f %12s %12.3f %11s %11.2f %17s %17s %17.5e %17.5e\n",
|
|
r->name,
|
|
flops / (r->time_std * 1e12),
|
|
"n/a", flops / (r->time_tiled * 1e12), "n/a",
|
|
r->time_std / r->time_tiled,
|
|
"n/a", "n/a", r->max_err_tiled, r->rmse_tiled);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char ** argv) {
|
|
bool run_bench = false;
|
|
bool run_fuzz = false;
|
|
for (int i = 1; i < argc; ++i) {
|
|
if (strcmp(argv[i], "--bench") == 0) {
|
|
run_bench = true;
|
|
} else if (strcmp(argv[i], "--fuzz") == 0) {
|
|
run_fuzz = true;
|
|
} else {
|
|
fprintf(stderr, "error: unknown argument: %s\n", argv[i]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
// Enable tiled MM, also force tiled MM even when unprofitable for benchmarks
|
|
#if defined(_WIN32)
|
|
_putenv("GGML_CPU_TILED_MM=1");
|
|
_putenv("GGML_CPU_TILED_MM_FORCE=1");
|
|
#else
|
|
setenv("GGML_CPU_TILED_MM", "1", 1);
|
|
setenv("GGML_CPU_TILED_MM_FORCE", "1", 1);
|
|
#endif
|
|
|
|
ggml_backend_load_all();
|
|
ggml_backend_t backend = ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, NULL);
|
|
if (!backend) {
|
|
fprintf(stderr, "failed to initialize the CPU backend\n");
|
|
return 1;
|
|
}
|
|
cpu_set_n_threads(backend, 8);
|
|
// base case, smoke each quant type
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_Q5_K);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_Q4_K);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_Q3_K);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_Q2_K);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ4_XS);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ2_XXS);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ2_XS);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ2_S);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ3_XXS);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ3_S);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ1_S);
|
|
test_matmul(backend, 256, 512, 256, GGML_TYPE_IQ1_M);
|
|
test_mul_mat_id(backend, 256, 256, 2, 8, 1, 64, GGML_TYPE_Q4_K); // single block, cne1 = 256
|
|
test_mul_mat_id(backend, 512, 300, 4, 2, 1, 150, GGML_TYPE_IQ2_XXS); // iq type, imatrix quant
|
|
|
|
if (run_fuzz) {
|
|
printf("Running fuzz tests.\n");
|
|
// ragged edges, both subblock lengths (Q5_K = 32, Q6_K = 16)
|
|
test_matmul(backend, 256, 1024, 8192, GGML_TYPE_Q6_K); // long K, int32 accumulation
|
|
test_matmul(backend, 357, 1024, 137, GGML_TYPE_Q6_K); // ragged M and K
|
|
test_matmul(backend, 16, 1024, 16, GGML_TYPE_Q5_K);
|
|
test_matmul(backend, 18, 1024, 7, GGML_TYPE_Q5_K); // ragged M tail, ragged K in first subblock
|
|
test_matmul(backend, 8, 256, 8, GGML_TYPE_Q5_K);
|
|
test_matmul(backend, 18, 1024, 256, GGML_TYPE_Q5_K);
|
|
test_matmul(backend, 9, 256, 256, GGML_TYPE_Q5_K);
|
|
test_matmul(backend, 8, 256, 8, GGML_TYPE_Q6_K); // tiny M, single QK_K block
|
|
test_matmul(backend, 256, 1024, 8192, GGML_TYPE_Q3_K);
|
|
test_matmul(backend, 357, 1024, 137, GGML_TYPE_Q3_K);
|
|
test_matmul(backend, 8, 256, 8, GGML_TYPE_Q3_K);
|
|
test_matmul(backend, 17, 1024, 257, GGML_TYPE_Q3_K); // K past a full 256 tile
|
|
test_matmul(backend, 257, 1024, 17, GGML_TYPE_Q3_K); // K past a full microtile
|
|
test_matmul(backend, 256, 1024, 8192, GGML_TYPE_Q2_K);
|
|
test_matmul(backend, 357, 1024, 137, GGML_TYPE_Q2_K);
|
|
test_matmul(backend, 8, 256, 8, GGML_TYPE_Q2_K);
|
|
test_matmul(backend, 17, 1024, 257, GGML_TYPE_Q2_K);
|
|
test_matmul(backend, 257, 1024, 17, GGML_TYPE_Q2_K);
|
|
test_matmul(backend, 357, 1024, 137, GGML_TYPE_IQ4_XS); // ragged M and K
|
|
test_matmul(backend, 8, 256, 8, GGML_TYPE_IQ4_XS); // tiny M, single QK_K block
|
|
test_matmul(backend, 17, 1024, 257, GGML_TYPE_IQ4_XS); // K past a full 256 tile
|
|
|
|
// fuzz: M/K around the microtile (16) and tile (256) boundaries
|
|
// Q4_K = subblock 32, Q6_K = subblock 16
|
|
test_matmul(backend, 1, 1024, 1, GGML_TYPE_Q4_K);
|
|
test_matmul(backend, 2, 1024, 3, GGML_TYPE_Q4_K);
|
|
test_matmul(backend, 15, 1024, 15, GGML_TYPE_Q4_K); // M and K in the first microtile
|
|
test_matmul(backend, 17, 1024, 17, GGML_TYPE_Q4_K); // M and K past the first microtile
|
|
test_matmul(backend, 255, 1024, 255, GGML_TYPE_Q4_K); // M and K one short of a tile
|
|
test_matmul(backend, 257, 1024, 257, GGML_TYPE_Q4_K); // M and K one past a tile
|
|
test_matmul(backend, 271, 1024, 271, GGML_TYPE_Q4_K); // 1 tile + 15
|
|
test_matmul(backend, 272, 1024, 272, GGML_TYPE_Q4_K); // 1 tile + 16
|
|
test_matmul(backend, 511, 1024, 511, GGML_TYPE_Q4_K); // 2 tiles - 1
|
|
test_matmul(backend, 513, 1024, 513, GGML_TYPE_Q4_K); // 2 tiles + 1
|
|
test_matmul(backend, 17, 512, 257, GGML_TYPE_Q4_K); // ragged M and K, N = 2 blocks
|
|
|
|
test_matmul(backend, 1, 1024, 1, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 2, 1024, 3, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 15, 1024, 15, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 17, 1024, 17, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 255, 1024, 255, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 257, 1024, 257, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 271, 1024, 271, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 272, 1024, 272, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 511, 1024, 511, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 513, 1024, 513, GGML_TYPE_Q6_K);
|
|
test_matmul(backend, 17, 512, 257, GGML_TYPE_Q6_K);
|
|
|
|
// MUL_MAT_ID (MoE): K = reduction (tiled gate needs K % 256 == 0), R = output rows per expert,
|
|
// k = top-k slots, b_slots = b rows (1 = broadcast MoE, k = i11-diverse gather), cne1 = k*batch/E
|
|
test_mul_mat_id(backend, 512, 300, 4, 2, 1, 150, GGML_TYPE_Q4_K); // ragged R, broadcast, cne1 = 75
|
|
test_mul_mat_id(backend, 512, 256, 4, 2, 2, 150, GGML_TYPE_Q5_K); // i11-diverse gather, HAS_MIN
|
|
test_mul_mat_id(backend, 512, 256, 4, 2, 1, 100, GGML_TYPE_Q6_K); // ragged cne1 = 50
|
|
test_mul_mat_id(backend, 768, 256, 4, 2, 1, 150, GGML_TYPE_Q3_K); // K = 3 slabs
|
|
test_mul_mat_id(backend, 1024, 2048, 128, 8, 1, 2048, GGML_TYPE_Q4_K); // large, cne1 = 128
|
|
test_mul_mat_id(backend, 512, 256, 32, 2, 1, 8, GGML_TYPE_Q4_K); // tiny cne1 (~0.5), forced only
|
|
|
|
// cne1 > 256: the k-outer ring sweeps multiple 256-row windows per expert (the multi-window path)
|
|
test_mul_mat_id(backend, 256, 256, 8, 8, 1, 512, GGML_TYPE_Q4_K); // cne1 = 512, two full windows
|
|
test_mul_mat_id(backend, 512, 256, 8, 8, 1, 300, GGML_TYPE_Q4_K); // cne1 = 300, 2nd window ragged (44 rows)
|
|
test_mul_mat_id(backend, 256, 64, 8, 8, 1, 257, GGML_TYPE_Q4_K); // cne1 = 257, 1-row tail window, R = 1 group
|
|
test_mul_mat_id(backend, 512, 128, 8, 8, 1, 511, GGML_TYPE_Q4_K); // cne1 = 511, 15-row ragged tail
|
|
test_mul_mat_id(backend, 768, 300, 8, 8, 1, 300, GGML_TYPE_Q4_K); // 3 K slabs x 2 windows, ragged R and cne1
|
|
test_mul_mat_id(backend, 768, 300, 8, 2, 2, 300, GGML_TYPE_Q5_K); // cne1 = 600, 3 slabs, 3 windows (last ragged), i11-diverse, HAS_MIN
|
|
|
|
// cne1 = 255: one short of a full ring, last ring row zero-padded by the unpack
|
|
test_mul_mat_id(backend, 512, 256, 4, 4, 1, 255, GGML_TYPE_Q4_K);
|
|
|
|
// long K: 8 slabs per ring row
|
|
test_mul_mat_id(backend, 2048, 300, 8, 2, 1, 400, GGML_TYPE_Q4_K); // cne1 = 100
|
|
|
|
// R group edges: 64 = TILED_MMID_GROUP (ceil div, ragged group tail, single-group early return)
|
|
test_mul_mat_id(backend, 512, 64, 4, 2, 1, 128, GGML_TYPE_Q4_K); // ngroups = 1, most threads idle
|
|
test_mul_mat_id(backend, 512, 65, 4, 2, 1, 128, GGML_TYPE_Q4_K); // ceil-div boundary, 1-row tail group
|
|
test_mul_mat_id(backend, 512, 63, 4, 2, 1, 128, GGML_TYPE_Q4_K); // ragged group tail
|
|
test_mul_mat_id(backend, 512, 1, 4, 1, 1, 256, GGML_TYPE_Q4_K); // R = 1 row, cne1 = 256
|
|
|
|
// strided (non-contiguous) F32 src1: nb[1] = 4 bytes, the tiled path reads through strides
|
|
test_mul_mat_id(backend, 512, 256, 8, 8, 1, 128, GGML_TYPE_Q4_K, true); // cne1 = 128
|
|
|
|
// narrow path (cne1 in [8,16], K a multiple of k_extent): the weight is read as long per-row K streams
|
|
test_mul_mat_id(backend, 1024, 256, 2, 2, 1, 16, GGML_TYPE_Q4_K); // cne1 = 16
|
|
test_mul_mat_id(backend, 1024, 256, 2, 1, 1, 16, GGML_TYPE_Q4_K); // cne1 = 8
|
|
test_mul_mat_id(backend, 512, 128, 2, 3, 1, 8, GGML_TYPE_Q5_K); // cne1 = 12
|
|
|
|
// higher-dim (ne[2], ne[3] > 1)
|
|
// equal-batch and broadcast shapes (src1_2=2*src0_2, src1_3=2*src0_3)
|
|
test_matmul_highdim(backend, 1024, 1024, 1024, 2, 1, 2, 1, GGML_TYPE_Q4_K); // 3D, tiled
|
|
test_matmul_highdim(backend, 1024, 1024, 1024, 1, 2, 1, 2, GGML_TYPE_Q4_K); // 3D, tiled
|
|
test_matmul_highdim(backend, 512, 1024, 512, 2, 2, 2, 2, GGML_TYPE_Q4_K); // 4D, tiled
|
|
test_matmul_highdim(backend, 1024, 1024, 1024, 2, 1, 1, 1, GGML_TYPE_Q4_K); // broadcast r2=2 (src1_2>src0_2)
|
|
test_matmul_highdim(backend, 1024, 1024, 1024, 2, 2, 2, 1, GGML_TYPE_Q4_K); // broadcast r3=2 (src1_3>src0_3)
|
|
test_matmul_highdim(backend, 1024, 1024, 1024, 2, 1, 2, 1, GGML_TYPE_Q6_K); // 3D, tiled, q6_K
|
|
test_matmul_highdim(backend, 1024, 1024, 1024, 2, 2, 2, 2, GGML_TYPE_Q3_K); // 4D, tiled, q3_K
|
|
test_matmul_highdim(backend, 1024, 1024, 1024, 2, 2, 2, 2, GGML_TYPE_Q2_K); // 4D, tiled, q2_K
|
|
}
|
|
|
|
|
|
// benchmark: std (use_ref) vs repack (CPU_REPACK buffer) vs tiled kernel, over all tiled
|
|
// types. std is timed with use_ref=true (bypasses the tiled gate); tiled and repack with
|
|
// use_ref=false. Errors are vs the vec-only reference (use_ref).
|
|
if (run_bench) {
|
|
printf("Starting benchmark runs.\n");
|
|
const ggml_type bench_types[] = { GGML_TYPE_Q2_K, GGML_TYPE_Q3_K, GGML_TYPE_Q4_K, GGML_TYPE_Q5_K, GGML_TYPE_Q6_K,
|
|
GGML_TYPE_IQ4_XS, GGML_TYPE_IQ2_XXS, GGML_TYPE_IQ2_XS, GGML_TYPE_IQ2_S,
|
|
GGML_TYPE_IQ3_XXS, GGML_TYPE_IQ3_S, GGML_TYPE_IQ1_S, GGML_TYPE_IQ1_M };
|
|
const size_t n_types = sizeof(bench_types) / sizeof(bench_types[0]);
|
|
|
|
struct { int64_t M, N, K; } shapes[] = {
|
|
{ 4096, 4096, 4096 },
|
|
{ 4096, 4096, 64 },
|
|
{ 4096, 4096, 32 },
|
|
{ 4096, 4096, 24 },
|
|
{ 4096, 4096, 16 },
|
|
{ 4096, 4096, 8 },
|
|
{ 4096, 4096, 1 },
|
|
};
|
|
for (size_t s = 0; s < sizeof(shapes) / sizeof(shapes[0]); ++s) {
|
|
bench_row rows[n_types];
|
|
for (size_t i = 0; i < n_types; ++i) {
|
|
rows[i] = bench_three_way(backend, shapes[s].M, shapes[s].N, shapes[s].K, bench_types[i]);
|
|
}
|
|
print_bench_table(shapes[s].M, shapes[s].N, shapes[s].K, rows, n_types);
|
|
}
|
|
|
|
// MUL_MAT_ID (MoE): K must be a multiple of 256 (the tiled slab). cne1 = k*batch/E.
|
|
struct { int64_t K, R, E, k, b_slots, batch; } mmid_shapes[] = {
|
|
{ 1024, 1024, 8, 8, 1, 1 }, // cne1 = 1, single-token decode
|
|
{ 1024, 1024, 4, 2, 1, 16 }, // cne1 = 8, narrow path
|
|
{ 1024, 1024, 4, 2, 1, 32 }, // cne1 = 16, narrow path
|
|
{ 512, 512, 8, 2, 1, 128 }, // cne1 = 32
|
|
{ 1024, 1024, 16, 8, 1, 64 }, // cne1 = 32
|
|
{ 1024, 1024, 16, 8, 1, 256 }, // cne1 = 128
|
|
{ 1024, 2048, 32, 8, 1, 512 }, // cne1 = 128, wide experts
|
|
{ 2048, 1024, 16, 8, 1, 1024 }, // cne1 = 512, long dot
|
|
};
|
|
for (size_t s = 0; s < sizeof(mmid_shapes) / sizeof(mmid_shapes[0]); ++s) {
|
|
bench_row_mmid rows[n_types];
|
|
for (size_t i = 0; i < n_types; ++i) {
|
|
rows[i] = bench_mul_mat_id(backend, mmid_shapes[s].K, mmid_shapes[s].R, mmid_shapes[s].E,
|
|
mmid_shapes[s].k, mmid_shapes[s].b_slots, mmid_shapes[s].batch, bench_types[i]);
|
|
}
|
|
print_mmid_table(mmid_shapes[s].K, mmid_shapes[s].R, mmid_shapes[s].E, mmid_shapes[s].k,
|
|
mmid_shapes[s].b_slots, mmid_shapes[s].batch, rows, n_types);
|
|
}
|
|
}
|
|
|
|
ggml_backend_free(backend);
|
|
return n_failed ? 1 : 0;
|
|
}
|