Skip to content

Commit 548dd80

Browse files
authored
Add small-batch quantized matvec kernel (qmv_wide) (#3764)
1 parent 39886de commit 548dd80

6 files changed

Lines changed: 545 additions & 3 deletions

File tree

mlx/backend/metal/kernels/fp_quantized.h

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,125 @@ METAL_FUNC void fp_qmv_impl(
525525
}
526526
}
527527

528+
// Quantized matrix-vector for a small batch of input vectors, the M in
529+
// [2, vector_limit) band between qmv (M==1) and qmm. Each thread owns one
530+
// output row that k_lanes lanes reduce over K; the vecs_per_tg vectors are
531+
// streamed so each weight group is dequantized once and reused across them.
532+
template <typename T, int group_size, int bits, int vecs_per_tg, int k_lanes>
533+
METAL_FUNC void fp_qmv_wide_impl(
534+
const device uint32_t* w,
535+
const device uint8_t* scales,
536+
const device T* x,
537+
device T* y,
538+
const constant int& in_vec_size,
539+
const constant int& out_vec_size,
540+
const constant int& M,
541+
uint3 tid [[threadgroup_position_in_grid]],
542+
uint simd_gid [[simdgroup_index_in_threadgroup]],
543+
uint simd_lid [[thread_index_in_simdgroup]]) {
544+
constexpr int num_simdgroups = 2;
545+
constexpr int results_per_simdgroup = SIMD_SIZE / k_lanes;
546+
constexpr int pack_factor = get_pack_factor<32, bits>();
547+
constexpr int bytes_per_pack = get_bytes_per_pack<32>();
548+
constexpr int nf4 = group_size / 4; // float4 lanes per quant group
549+
550+
typedef float U;
551+
552+
const short k_lane =
553+
simd_lid % k_lanes; // this lane's slot in the K reduction
554+
const short sg_row = simd_lid / k_lanes; // which output row of the simdgroup
555+
556+
const int out_row = tid.y * (results_per_simdgroup * num_simdgroups) +
557+
results_per_simdgroup * simd_gid + sg_row;
558+
const int vec0 = tid.x * vecs_per_tg; // first input vector handled here
559+
560+
const int row = min(out_row, out_vec_size - 1);
561+
562+
const int in_vec_size_w = in_vec_size * bytes_per_pack / pack_factor;
563+
const int in_vec_size_g = in_vec_size / group_size;
564+
const device uint8_t* wrow = (const device uint8_t*)w + row * in_vec_size_w;
565+
const device uint8_t* srow = scales + row * in_vec_size_g;
566+
567+
// One device pointer per streamed vector; the clamp keeps an out-of-range
568+
// tail slot reading a valid row (it is never written below).
569+
const device T* xv[vecs_per_tg];
570+
for (int v = 0; v < vecs_per_tg; v++) {
571+
xv[v] = x + min(vec0 + v, M - 1) * in_vec_size;
572+
}
573+
574+
U result[vecs_per_tg] = {0};
575+
576+
// Each lane reduces a strided subset of the row's quant groups: one group is
577+
// dequantized to float4 lanes and dot()'d against each streamed vector. One
578+
// group per iteration keeps weight-register pressure low for occupancy.
579+
for (int g = k_lane; g < in_vec_size_g; g += k_lanes) {
580+
const int k0 = g * group_size;
581+
U s = dequantize_scale<U, group_size>(srow[g]);
582+
const device uint8_t* wg = wrow + k0 * bytes_per_pack / pack_factor;
583+
584+
float4 w4[nf4];
585+
if constexpr (bits == 4) {
586+
const device uint16_t* wq = (const device uint16_t*)wg;
587+
#pragma unroll
588+
for (int i = 0; i < nf4; i++) {
589+
w4[i] = float4(
590+
Dequantize<4>{}(wq[i]),
591+
Dequantize<4>{}(wq[i] >> 4),
592+
Dequantize<4>{}(wq[i] >> 8),
593+
Dequantize<4>{}(wq[i] >> 12));
594+
}
595+
} else {
596+
#pragma unroll
597+
for (int i = 0; i < nf4; i++) {
598+
w4[i] = float4(
599+
Dequantize<8>{}(wg[4 * i]),
600+
Dequantize<8>{}(wg[4 * i + 1]),
601+
Dequantize<8>{}(wg[4 * i + 2]),
602+
Dequantize<8>{}(wg[4 * i + 3]));
603+
}
604+
}
605+
606+
#pragma unroll
607+
for (int v = 0; v < vecs_per_tg; v++) {
608+
const device vec<T, 4>* xv4 = (const device vec<T, 4>*)(xv[v] + k0);
609+
float acc = 0;
610+
#pragma unroll
611+
for (int j = 0; j < nf4; j++) {
612+
acc += dot(w4[j], float4(xv4[j]));
613+
}
614+
result[v] += s * acc;
615+
}
616+
}
617+
618+
// Reduce each vector's partial over its k_lanes with a shuffle ladder:
619+
// simd_sum would mix the results_per_simdgroup rows a simdgroup spans.
620+
for (int v = 0; v < vecs_per_tg; v++) {
621+
if constexpr (k_lanes >= 32) {
622+
result[v] += simd_shuffle_down(result[v], 16);
623+
}
624+
if constexpr (k_lanes >= 16) {
625+
result[v] += simd_shuffle_down(result[v], 8);
626+
}
627+
if constexpr (k_lanes >= 8) {
628+
result[v] += simd_shuffle_down(result[v], 4);
629+
}
630+
if constexpr (k_lanes >= 4) {
631+
result[v] += simd_shuffle_down(result[v], 2);
632+
}
633+
if constexpr (k_lanes >= 2) {
634+
result[v] += simd_shuffle_down(result[v], 1);
635+
}
636+
}
637+
638+
if (k_lane == 0 && out_row < out_vec_size) {
639+
for (int v = 0; v < vecs_per_tg; v++) {
640+
if (vec0 + v < M) {
641+
y[(vec0 + v) * out_vec_size + out_row] = static_cast<T>(result[v]);
642+
}
643+
}
644+
}
645+
}
646+
528647
template <typename T, const int group_size, int bits>
529648
METAL_FUNC void fp_qvm_impl(
530649
const device uint32_t* w,
@@ -1087,6 +1206,51 @@ template <typename T, const int group_size, int bits, bool batched>
10871206
w, scales, x, y, in_vec_size, out_vec_size, tid, simd_gid, simd_lid);
10881207
}
10891208

1209+
template <
1210+
typename T,
1211+
int group_size,
1212+
int bits,
1213+
int vecs_per_tg,
1214+
int k_lanes,
1215+
bool batched>
1216+
[[kernel]] void fp_qmv_wide(
1217+
const device uint32_t* w,
1218+
const device uint8_t* scales,
1219+
const device T* x,
1220+
device T* y,
1221+
const constant int& in_vec_size,
1222+
const constant int& out_vec_size,
1223+
const constant int& M,
1224+
const constant int& x_batch_ndims,
1225+
const constant int* x_shape,
1226+
const constant int64_t* x_strides,
1227+
const constant int& w_batch_ndims,
1228+
const constant int* w_shape,
1229+
const constant int64_t* w_strides,
1230+
const constant int64_t* s_strides,
1231+
uint3 tid [[threadgroup_position_in_grid]],
1232+
uint simd_gid [[simdgroup_index_in_threadgroup]],
1233+
uint simd_lid [[thread_index_in_simdgroup]]) {
1234+
if (batched) {
1235+
adjust_matrix_offsets(
1236+
x,
1237+
w,
1238+
scales,
1239+
y,
1240+
out_vec_size * M,
1241+
x_batch_ndims,
1242+
x_shape,
1243+
x_strides,
1244+
w_batch_ndims,
1245+
w_shape,
1246+
w_strides,
1247+
s_strides,
1248+
tid);
1249+
}
1250+
fp_qmv_wide_impl<T, group_size, bits, vecs_per_tg, k_lanes>(
1251+
w, scales, x, y, in_vec_size, out_vec_size, M, tid, simd_gid, simd_lid);
1252+
}
1253+
10901254
template <typename T, const int group_size, int bits, bool batched>
10911255
[[kernel]] void fp_qvm(
10921256
const device uint32_t* w,

mlx/backend/metal/kernels/fp_quantized.metal

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@
5252
D, \
5353
batched)
5454

55+
#define instantiate_quantized_wide(mode, name, type, vecs_per_tg, k_lanes, group_size, bits, batched) \
56+
instantiate_kernel( \
57+
#mode "_" #name "_" #type "_gs_" #group_size "_b_" #bits "_nv_" #vecs_per_tg "_kl_" #k_lanes "_batch_" #batched, \
58+
fp_ ## name, \
59+
type, \
60+
group_size, \
61+
bits, \
62+
vecs_per_tg, \
63+
k_lanes, \
64+
batched)
65+
5566
#define instantiate_quantized_split_k(mode, name, type, split_k, group_size, bits) \
5667
instantiate_kernel( \
5768
#mode "_" #name "_" #type "_gs_" #group_size "_b_" #bits "_spk_" #split_k, \
@@ -105,6 +116,17 @@
105116
instantiate_quantized_quad(mode, qmv_quad, type, 128, 1, group_size, bits) \
106117
instantiate_quantized_quad(mode, qmv_quad, type, 128, 0, group_size, bits)
107118

119+
// vecs_per_tg (input-vector tile) 2..5; the fp path uses k_lanes=16.
120+
#define instantiate_quantized_wide_wrap(mode, name, type, vecs_per_tg, k_lanes, group_size, bits) \
121+
instantiate_quantized_wide(mode, name, type, vecs_per_tg, k_lanes, group_size, bits, 0) \
122+
instantiate_quantized_wide(mode, name, type, vecs_per_tg, k_lanes, group_size, bits, 1)
123+
124+
#define instantiate_quantized_all_wide(type, mode, group_size, bits) \
125+
instantiate_quantized_wide_wrap(mode, qmv_wide, type, 2, 16, group_size, bits) \
126+
instantiate_quantized_wide_wrap(mode, qmv_wide, type, 3, 16, group_size, bits) \
127+
instantiate_quantized_wide_wrap(mode, qmv_wide, type, 4, 16, group_size, bits) \
128+
instantiate_quantized_wide_wrap(mode, qmv_wide, type, 5, 16, group_size, bits)
129+
108130
#define instantiate_quantized_all_splitk(type, mode, group_size, bits) \
109131
instantiate_quantized_split_k(mode, qvm_split_k, type, 8, group_size, bits) \
110132
instantiate_quantized_split_k(mode, qvm_split_k, type, 32, group_size, bits) \
@@ -139,6 +161,7 @@
139161
instantiate_quantized_all_batched(type, mode, group_size, bits) \
140162
instantiate_quantized_all_single(type, mode, group_size, bits) \
141163
instantiate_quantized_all_quad(type, mode, group_size, bits) \
164+
instantiate_quantized_all_wide(type, mode, group_size, bits) \
142165
instantiate_quantized_all_splitk(type, mode, group_size, bits) \
143166
instantiate_quantized_all_aligned(type, mode, group_size, bits) \
144167
instantiate_quantized_all_rhs(type, mode, group_size, bits) \

0 commit comments

Comments
 (0)