Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions ggml/src/ggml-metal/ggml-metal-device.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "ggml-metal-device.h"

#include "ggml-metal-impl.h"
Expand Down Expand Up @@ -410,6 +411,59 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_sum_rows(ggml_me
return res;
}

ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_q1_0_planes(ggml_metal_library_t lib) {
char base[256];
char name[256];

snprintf(base, 256, "kernel_q1_0_build_planes");
snprintf(name, 256, "%s", base);

ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
if (!res.pipeline) {
res = ggml_metal_library_compile_pipeline(lib, base, name, nullptr);
}

return res;
}

ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv_q1_0_pc(ggml_metal_library_t lib, const ggml_tensor * op, int nr1) {
GGML_TENSOR_LOCALS( int32_t, ne0, op->src[0], ne);
GGML_TENSOR_LOCALS( int32_t, ne1, op->src[1], ne);

char base[256];
char name[256];

const int nsg = N_SG_Q1_0;
const int nr0 = N_R0_Q1_0_PC;

const int16_t r2 = (int16_t) (ne12 / ne02);
const int16_t r3 = (int16_t) (ne13 / ne03);

snprintf(base, 256, "kernel_mul_mv_q1_0_f32_pc_nr1_%d", nr1);
snprintf(name, 256, "%s_nsg=%d_ne12=%d_r2=%d_r3=%d", base, nsg, ne12, r2, r3);

ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
if (!res.pipeline) {
ggml_metal_cv_t cv = ggml_metal_cv_init();

ggml_metal_cv_set_int16(cv, nsg, FC_MUL_MV + 0);
ggml_metal_cv_set_int16(cv, (int16_t) ne12, FC_MUL_MV + 2);
ggml_metal_cv_set_int16(cv, r2, FC_MUL_MV + 3);
ggml_metal_cv_set_int16(cv, r3, FC_MUL_MV + 4);

res = ggml_metal_library_compile_pipeline(lib, base, name, cv);

ggml_metal_cv_free(cv);
}

res.nr0 = nr0;
res.nr1 = nr1;
res.nsg = nsg;
res.smem = 0;

return res;
}

ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_cumsum_blk(ggml_metal_library_t lib, const ggml_tensor * op) {
GGML_ASSERT(op->op == GGML_OP_CUMSUM);

Expand Down Expand Up @@ -860,6 +914,18 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv(ggml_meta
{
nsg = N_SG_Q1_0;
nr0 = N_R0_Q1_0;
// multi-column variants stream each weight block once per nr1 src1
// columns (spec-decode verify batches). GGML_METAL_Q1_0_NR1=1 disables,
// 2..4 forces a variant.
static const int nr1_env = getenv("GGML_METAL_Q1_0_NR1") ? atoi(getenv("GGML_METAL_Q1_0_NR1")) : 0;
if (nr1_env >= 2 && nr1_env <= 4) {
nr1 = nr1_env;
} else if (nr1_env != 1 && ne11 == 3) {
nr1 = 3;
} else if (nr1_env != 1 && ne11 >= 2) {
nr1 = 2;
}
suffix = nr1 == 2 ? "_nr1_2" : nr1 == 3 ? "_nr1_3" : nr1 == 4 ? "_nr1_4" : "";
} break;
case GGML_TYPE_Q2_0:
{
Expand Down
2 changes: 2 additions & 0 deletions ggml/src/ggml-metal/ggml-metal-device.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_glu
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_sum (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_sum_rows (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_cumsum_blk (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_q1_0_planes (ggml_metal_library_t lib);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv_q1_0_pc (ggml_metal_library_t lib, const struct ggml_tensor * op, int nr1);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_cumsum_add (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_tri (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_soft_max (ggml_metal_library_t lib, const struct ggml_tensor * op);
Expand Down
11 changes: 11 additions & 0 deletions ggml/src/ggml-metal/ggml-metal-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
#define N_R0_Q1_0 8
#define N_SG_Q1_0 2

// Q1_0 word-parallel (popcount) verify path: rows per simdgroup, and the uint32
// stride of one activation bit-plane record (8 planes x 4 words + scale + sum + pad).
#define N_R0_Q1_0_PC 4
#define Q1_0_PLANE_STRIDE 36

#define N_R0_Q2_0 8
#define N_SG_Q2_0 2

Expand Down Expand Up @@ -503,6 +508,12 @@ typedef struct {
int16_t r3;
} ggml_metal_kargs_mul_mv;

typedef struct {
int32_t nblk;
int32_t ne11;
uint64_t nb11;
} ggml_metal_kargs_q1_0_planes;

typedef struct {
int32_t ne00;
int32_t ne01;
Expand Down
140 changes: 138 additions & 2 deletions ggml/src/ggml-metal/ggml-metal-ops.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <cstdlib>
#include "ggml-metal-ops.h"

#include "ggml.h"
Expand Down Expand Up @@ -2514,6 +2515,40 @@ int ggml_metal_op_pool_2d(ggml_metal_op_t ctx, int idx) {
return 1;
}

// Q1_0 word-parallel verify path (opt-in): quantize the activation columns into
// int8 bit-planes once, then consume 32 weights per AND+popcount instead of one
// select per weight. See the kernel comment in mul_mv.metal.
//
// The plane scratch is carved out of the padding the allocator adds behind dst, so
// the encoder must take this path exactly when the allocator reserved for it. Both
// call this predicate rather than repeating the shape test.
static bool ggml_metal_op_mul_mat_q1_0_pc_supported(const ggml_tensor * op) {
static const bool q1_0_pc = getenv("GGML_METAL_Q1_0_POPCNT") != nullptr;

if (!q1_0_pc || !op->src[0] || !op->src[1]) {
return false;
}

if (op->src[0]->type != GGML_TYPE_Q1_0 || op->src[1]->type != GGML_TYPE_F32) {
return false;
}

const int64_t ne00 = op->src[0]->ne[0];
const int64_t ne02 = op->src[0]->ne[2];
const int64_t ne03 = op->src[0]->ne[3];

const int64_t ne11 = op->src[1]->ne[1];
const int64_t ne12 = op->src[1]->ne[2];
const int64_t ne13 = op->src[1]->ne[3];

if (ne11 < 2 || ne11 > 16 || ne00 < 128 || ne00 % 128 != 0) {
return false;
}

// the kernel indexes src0/src1 without the broadcast strides
return ne02 == 1 && ne03 == 1 && ne12 == 1 && ne13 == 1;
}

int ggml_metal_op_mul_mat(ggml_metal_op_t ctx, int idx) {
ggml_tensor * op = ctx->node(idx);

Expand Down Expand Up @@ -2551,7 +2586,90 @@ int ggml_metal_op_mul_mat(ggml_metal_op_t ctx, int idx) {

// find the break-even point where the matrix-matrix kernel becomes more efficient compared
// to the matrix-vector kernel
const int ne11_mm_min = 8;
// Q1_0: the generic small-batch mul_mv_ext path is ALU-bound (flat ~2.5 TFLOPS for
// 2..8 columns, 2.5-4x slower per weight pass than the Q1_0 mul_mv kernel), so Q1_0
// stays on the (multi-column) mul_mv kernels up to GGML_METAL_Q1_0_MV_MAX rows.
static const bool q1_0_ext_enable = getenv("GGML_METAL_Q1_0_EXT_ENABLE") != nullptr;
static const int q1_0_mv_max = getenv("GGML_METAL_Q1_0_MV_MAX") ? atoi(getenv("GGML_METAL_Q1_0_MV_MAX")) : 16;

const int ne11_mm_min = op->src[0]->type == GGML_TYPE_Q1_0 ? std::max(8, q1_0_mv_max) : 8;

if (ggml_metal_op_mul_mat_q1_0_pc_supported(op)) {
const int32_t nblk = ne00/128;

ggml_metal_buffer_id bid_src0 = ggml_metal_get_buffer_id(op->src[0]);
ggml_metal_buffer_id bid_src1 = ggml_metal_get_buffer_id(op->src[1]);
ggml_metal_buffer_id bid_dst = ggml_metal_get_buffer_id(op);
ggml_metal_buffer_id bid_planes = bid_dst;

bid_planes.offs += ggml_nbytes(op);

{
auto pipeline = ggml_metal_library_get_pipeline_q1_0_planes(lib);

ggml_metal_kargs_q1_0_planes args = {
/*.nblk =*/ nblk,
/*.ne11 =*/ (int32_t) ne11,
/*.nb11 =*/ nb11,
};

ggml_metal_encoder_set_pipeline(enc, pipeline);
ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0);
ggml_metal_encoder_set_buffer (enc, bid_src1, 1);
ggml_metal_encoder_set_buffer (enc, bid_planes, 2);

ggml_metal_encoder_dispatch_threadgroups(enc, nblk, ne11, 1, 32, 1, 1);
}

// the matmul reads what the plane pass just wrote
ggml_metal_op_concurrency_reset(ctx);

{
static const int nr1_env = getenv("GGML_METAL_Q1_0_PC_NR1") ? atoi(getenv("GGML_METAL_Q1_0_PC_NR1")) : 0;

// nr1=2 measured best at every batch height; nr1=4 spills registers
// (pp4 84.4 vs 62.8 tok/s, pp8 102.7 vs 82.3)
const int nr1 = (nr1_env == 2 || nr1_env == 4) ? nr1_env : 2;

auto pipeline = ggml_metal_library_get_pipeline_mul_mv_q1_0_pc(lib, op, nr1);

ggml_metal_kargs_mul_mv args = {
/*.ne00 =*/ ne00,
/*.ne01 =*/ ne01,
/*.ne02 =*/ ne02,
/*.nb00 =*/ nb00,
/*.nb01 =*/ nb01,
/*.nb02 =*/ nb02,
/*.nb03 =*/ nb03,
/*.ne10 =*/ ne10,
/*.ne11 =*/ ne11,
/*.ne12 =*/ ne12,
/*.nb10 =*/ nb10,
/*.nb11 =*/ nb11,
/*.nb12 =*/ nb12,
/*.nb13 =*/ nb13,
/*.ne0 =*/ ne0,
/*.ne1 =*/ ne1,
/*.nr0 =*/ pipeline.nr0,
/*.r2 =*/ r2,
/*.r3 =*/ r3,
};

const int nr0 = pipeline.nr0;
const int nsg = pipeline.nsg;

ggml_metal_encoder_set_pipeline(enc, pipeline);
ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0);
ggml_metal_encoder_set_buffer (enc, bid_src0, 1);
ggml_metal_encoder_set_buffer (enc, bid_src1, 2);
ggml_metal_encoder_set_buffer (enc, bid_dst, 3);
ggml_metal_encoder_set_buffer (enc, bid_planes, 4);

ggml_metal_encoder_dispatch_threadgroups(enc, (ne01 + nr0*nsg - 1)/(nr0*nsg), (ne11 + nr1 - 1)/nr1, 1, 32, nsg, 1);
}

return 1;
}

// first try to use small-batch mat-mv kernels
// these should be efficient for BS [2, ~8]
Expand All @@ -2562,7 +2680,7 @@ int ggml_metal_op_mul_mat(ggml_metal_op_t ctx, int idx) {
op->src[0]->type == GGML_TYPE_F32 || // TODO: helper function
op->src[0]->type == GGML_TYPE_F16 ||
op->src[0]->type == GGML_TYPE_BF16 ||
op->src[0]->type == GGML_TYPE_Q1_0 ||
(op->src[0]->type == GGML_TYPE_Q1_0 && q1_0_ext_enable) ||
op->src[0]->type == GGML_TYPE_Q2_0 ||
op->src[0]->type == GGML_TYPE_PQ2_0 ||
op->src[0]->type == GGML_TYPE_Q4_0 ||
Expand Down Expand Up @@ -2759,6 +2877,24 @@ int ggml_metal_op_mul_mat(ggml_metal_op_t ctx, int idx) {
return 1;
}

// scratch for the Q1_0 word-parallel verify path: int8 activation bit-planes, one
// record per (column, 128-element block). Only the small-batch shapes that path
// serves are padded, so prefill dst buffers are unaffected.
size_t ggml_metal_op_mul_mat_extra_q1_0_planes(const ggml_tensor * op) {
assert(op->op == GGML_OP_MUL_MAT);

// the encoder gates on this exact predicate, so nothing is reserved for a shape
// that path would not take -- and nothing at all when the path is off
if (!ggml_metal_op_mul_mat_q1_0_pc_supported(op)) {
return 0;
}

const int64_t ne00 = op->src[0]->ne[0];
const int64_t ne11 = op->src[1]->ne[1];

return (size_t) (ne00/128) * ne11 * Q1_0_PLANE_STRIDE * sizeof(uint32_t);
}

size_t ggml_metal_op_mul_mat_id_extra_tpe(const ggml_tensor * op) {
assert(op->op == GGML_OP_MUL_MAT_ID);

Expand Down
3 changes: 3 additions & 0 deletions ggml/src/ggml-metal/ggml-metal-ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ int ggml_metal_op_encode(ggml_metal_op_t ctx, int idx);
// available ops:
//

// activation bit-planes for the Q1_0 word-parallel path
size_t ggml_metal_op_mul_mat_extra_q1_0_planes(const struct ggml_tensor * op);

// tokens per expert
size_t ggml_metal_op_mul_mat_id_extra_tpe(const struct ggml_tensor * op);

Expand Down
4 changes: 4 additions & 0 deletions ggml/src/ggml-metal/ggml-metal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ static size_t ggml_backend_metal_buffer_type_get_alloc_size(ggml_backend_buffer_

// some operations require additional memory for fleeting data:
switch (tensor->op) {
case GGML_OP_MUL_MAT:
{
res += ggml_metal_op_mul_mat_extra_q1_0_planes(tensor);
} break;
case GGML_OP_MUL_MAT_ID:
{
res += ggml_metal_op_mul_mat_id_extra_tpe(tensor);
Expand Down
Loading