Fix q8_0 gemv: reduction-across-simdgroups convention
Browse filesq8_0's mul_mv splits the K reduction across simdgroups and combines them through
threadgroup memory, where the K-quants give each simdgroup its own rows. It
therefore needs a grid that is not divided by nsg, and it needs smem bound --
upstream branches on exactly this. Without both, q8_0 returned garbage while the
K-quants were correct.
- gguf_metal/ggml_dispatch.mm +16 -5
- result-torch212 +1 -0
- result-torch213 +1 -0
gguf_metal/ggml_dispatch.mm
CHANGED
|
@@ -38,14 +38,21 @@ struct TypeInfo {
|
|
| 38 |
int block_bytes;
|
| 39 |
int mv_nsg; // N_SG_* from ggml-metal-impl.h
|
| 40 |
int mv_nr0; // N_R0_*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
};
|
| 42 |
|
| 43 |
const std::unordered_map<int, TypeInfo> &type_table() {
|
| 44 |
static const std::unordered_map<int, TypeInfo> table = {
|
| 45 |
-
{GGML_Q8_0, {"q8_0", 32, 34, N_SG_Q8_0, N_R0_Q8_0}},
|
| 46 |
-
{GGML_Q4_K, {"q4_K", 256, 144, N_SG_Q4_K, N_R0_Q4_K}},
|
| 47 |
-
{GGML_Q5_K, {"q5_K", 256, 176, N_SG_Q5_K, N_R0_Q5_K}},
|
| 48 |
-
{GGML_Q6_K, {"q6_K", 256, 210, N_SG_Q6_K, N_R0_Q6_K}},
|
| 49 |
};
|
| 50 |
return table;
|
| 51 |
}
|
|
@@ -180,7 +187,11 @@ extern "C" int gguf_metal_mul_mat(void *blocks, size_t blocks_off, void *x, size
|
|
| 180 |
[enc setBuffer:b_blocks offset:blocks_off atIndex:1];
|
| 181 |
[enc setBuffer:b_x offset:x_off atIndex:2];
|
| 182 |
[enc setBuffer:b_out offset:out_off atIndex:3];
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
[enc dispatchThreadgroups:MTLSizeMake(gx, M, 1)
|
| 185 |
threadsPerThreadgroup:MTLSizeMake(32, nsg, 1)];
|
| 186 |
}
|
|
|
|
| 38 |
int block_bytes;
|
| 39 |
int mv_nsg; // N_SG_* from ggml-metal-impl.h
|
| 40 |
int mv_nr0; // N_R0_*
|
| 41 |
+
// Two gemv conventions upstream. The K-quants give each simdgroup its own rows
|
| 42 |
+
// (first_row = (tgpig.x*NSG + sgitg)*nr0), so the grid is divided by nsg and nothing is shared.
|
| 43 |
+
// q8_0 instead splits the K reduction across simdgroups (r0 = tgpig.x*NR0) and combines them
|
| 44 |
+
// through threadgroup memory in helper_mv_reduce_and_write -- so its grid must NOT be divided by
|
| 45 |
+
// nsg, and it needs smem. Getting either wrong silently returns garbage.
|
| 46 |
+
bool mv_reduce_across_sgs;
|
| 47 |
+
size_t mv_smem;
|
| 48 |
};
|
| 49 |
|
| 50 |
const std::unordered_map<int, TypeInfo> &type_table() {
|
| 51 |
static const std::unordered_map<int, TypeInfo> table = {
|
| 52 |
+
{GGML_Q8_0, {"q8_0", 32, 34, N_SG_Q8_0, N_R0_Q8_0, true, 32 * sizeof(float) * N_R0_Q8_0}},
|
| 53 |
+
{GGML_Q4_K, {"q4_K", 256, 144, N_SG_Q4_K, N_R0_Q4_K, false, 0}},
|
| 54 |
+
{GGML_Q5_K, {"q5_K", 256, 176, N_SG_Q5_K, N_R0_Q5_K, false, 0}},
|
| 55 |
+
{GGML_Q6_K, {"q6_K", 256, 210, N_SG_Q6_K, N_R0_Q6_K, false, 0}},
|
| 56 |
};
|
| 57 |
return table;
|
| 58 |
}
|
|
|
|
| 187 |
[enc setBuffer:b_blocks offset:blocks_off atIndex:1];
|
| 188 |
[enc setBuffer:b_x offset:x_off atIndex:2];
|
| 189 |
[enc setBuffer:b_out offset:out_off atIndex:3];
|
| 190 |
+
if (info->mv_smem) {
|
| 191 |
+
[enc setThreadgroupMemoryLength:info->mv_smem atIndex:0];
|
| 192 |
+
}
|
| 193 |
+
const NSUInteger gx = info->mv_reduce_across_sgs ? (N + nr0 - 1) / nr0
|
| 194 |
+
: (N + nr0 * nsg - 1) / (nr0 * nsg);
|
| 195 |
[enc dispatchThreadgroups:MTLSizeMake(gx, M, 1)
|
| 196 |
threadsPerThreadgroup:MTLSizeMake(32, nsg, 1)];
|
| 197 |
}
|
result-torch212
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/nix/store/vsc5ambf7shc0cv3fip7zvfzaxsjk4z2-gguf-kernels-torch-ext
|
result-torch213
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
/nix/store/ipmkgsrz5mzwi56m3264vhb2nm2vpgfy-gguf-kernels-torch-ext
|