Skip to content

Commit f9f3365

Browse files
authored
vulkan: Coalesce Q4_K/Q5_K scale loads (ggml-org#21751)
Some SPIR-V compilers (notably mesa) don't handle the current vulkan Q4_K/Q5_K scale load pattern in mul_mat particularly well. While reading three `u8`s from the 12-byte scale array should (at least on some hardware) result in loading the full 12 bytes in a single LOAD followed by whatever extraction is needed, at least the ANV Intel driver really can't practically perform this optimization. `mesa`'s unsigned upper bound logic doesn't handle tracking bounds through ternary, resulting in the `(is < 4) ? ... : is - 4` having an infinite upper bound (as it cannot prove `is - 4` doesn't underflow). While this could still be rectified if mesa looked at the array bounds, it currently doesn't and `glslc` currently emits SPIR-V that doesn't allow for this optimization anyway (though maybe it will at some point, see KhronosGroup/glslang#4206). In mul_mat_vecq we took a different approach to loading the same fields. We read the first two bytes we needed from `scale` then took a branch before deciding whether we needed to read a third byte. In mesa this did, indeed, lead to a top-level branch with conditional loads. As such these loads ended up not being coalesced either (at least in the ANV driver) resulting in additional instructions in our hot loop. Instead, here, we go ahead and force loading the full 12 bytes and extract the bits we need from the packed-u32s instead. In mul_mat there's a few less ternaries and only one extra shift, so even on drivers that did optimize the previous loads properly the only material change should be pulling a few extra bytes into registers (which on most hardware won't cost anything anyway, though ironically on Intel it theoretically could). In mul_mat_vecq this requires a bit of extra math and may read bytes from the u32 that weren't needed, but it seems likely avoiding the branch is a win on most platforms. On Intel Xe2/mesa 26.0.4 with the optimizations from https://gitlab.freedesktop.org/mesa/mesa/-/work_items/15162, for shader matmul_id_subgroup_q4_k_f32_f16acc_aligned_l: * Instruction Count: 2753 -> 2688 * SEND Count: 269 -> 261 * Cycle Count: 273976 -> 266138 * Max live registers: 248 -> 246 * Non SSA regs after NIR: 381 -> 382 for shader matmul_id_subgroup_q5_k_f32_f16acc_aligned_l: * Instruction Count: 2767 -> 2702 * SEND Count: 271 -> 263 * Cycle Count: 274140 -> 268144 * Max live registers: 248 -> 246 * Non SSA regs after NIR: 381 -> 382 for shader mul_mat_vec_id_q4_k_q8_1_f32: * Instruction Count: 1930 -> 1646 * SEND Count: 116 -> 71 * Cycle Count: 1348306 -> 843350 * Max live registers: 78 -> 84 * Non SSA regs after NIR: 300 -> 135 for shader mul_mat_vec_id_q5_k_q8_1_f32: * Instruction Count: 2207 -> 1922 * SEND Count: 131 -> 86 * Cycle Count: 1392012 -> 1037836 * Max live registers: 90 -> 90 * Non SSA regs after NIR: 300 -> 135 for shader mul_mat_vec_q4_k_q8_1_f32: * Instruction Count: 2029 -> 1749 * SEND Count: 111 -> 66 * Cycle Count: 1347278 -> 840118 * Max live registers: 74 -> 80 * Non SSA regs after NIR: 299 -> 134 for shader mul_mat_vec_q5_k_q8_1_f32: * Instruction Count: 2307 -> 2022 * SEND Count: 126 -> 81 * Cycle Count: 1379820 -> 954042 * Max live registers: 86 -> 86 * Non SSA regs after NIR: 299 -> 134 On one Arc Pro B60, unsloth/Qwen3.5-35B-A3B-GGUF:UD-Q4_K_XL: * pp512: 907.34 ± 9.28 -> 941.94 ± 10.53 (+4%) * pp2048: 897.95 ± 1.82 -> 931.55 ± 1.79 (+4%) * tg128: 49.49 ± 0.02 -> 49.86 ± 0.05 (+ <1%) On one Arc Pro B60, unsloth/Qwen3.5-27B-GGUF:Q4_K_S: * pp512: 324.13 ± 10.52 -> 354.33 ± 6.81 (+9%) * pp2048: 329.80 ± 0.25 -> 357.10 ± 0.06 (+8%) * tg128: 17.11 ± 0.01 -> 18.11 ± 0.01 (+6%) On four Arc Pro B60s, unsloth/Qwen3.5-122B-A10B-GGUF:Q5_K_S with -sm layer (note that -sm tensor improvements will naturally be less): * pp512: 264.55 ± 2.81 -> 280.45 ± 3.94 (+6%) * pp2048: 319.32 ± 2.72 -> 335.70 ± 3.48 (+5%) * tg128: 26.39 ± 0.01 -> 26.67 ± 0.01 (+1%)
1 parent 98bb579 commit f9f3365

2 files changed

Lines changed: 44 additions & 33 deletions

File tree

ggml/src/ggml-vulkan/vulkan-shaders/mul_mat_vecq_funcs.glsl

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -296,13 +296,22 @@ vec2 get_dm_scale(uint ib, uint iqs) {
296296
const uint ib_k = ib / 8;
297297
const uint iqs_k = (ib % 8) * 8 + iqs;
298298
const uint is = iqs_k / 8;
299-
u8vec2 scale_dm;
300-
if (is < 4) {
301-
scale_dm = u8vec2(data_a[ib_k].scales[is] & 0x3F, data_a[ib_k].scales[is + 4] & 0x3F);
302-
} else {
303-
scale_dm = u8vec2((data_a[ib_k].scales[is+4] & 0xF) | ((data_a[ib_k].scales[is-4] & 0xC0) >> 2),
304-
(data_a[ib_k].scales[is+4] >> 4) | ((data_a[ib_k].scales[is ] & 0xC0) >> 2));
305-
}
299+
300+
const uvec3 scales = uvec3(data_a_packed32[ib_k].scales[0],
301+
data_a_packed32[ib_k].scales[1],
302+
data_a_packed32[ib_k].scales[2]);
303+
const uint scalesoffs = (is & 3) * 8;
304+
305+
const uint scidx0 = (is < 4) ? 0 : 2;
306+
const uint scidxshift0 = scalesoffs;
307+
const uint scidxshift1 = (is < 4) ? scalesoffs : scalesoffs + 2;
308+
const uint mbidx0 = (is < 4) ? 1 : 2;
309+
const uint mbidxshift0 = (is < 4) ? scalesoffs : scalesoffs + 4;
310+
const uint mbidxshift1 = (is < 4) ? scalesoffs : scalesoffs + 2;
311+
312+
const uint8_t sc = uint8_t(((scales[scidx0] >> scidxshift0) & 0xF) | ((scales[0] >> scidxshift1) & 0x30));
313+
const uint8_t mbyte = uint8_t(((scales[mbidx0] >> mbidxshift0) & 0xF) | ((scales[1] >> mbidxshift1) & 0x30));
314+
u8vec2 scale_dm = u8vec2(sc, mbyte);
306315

307316
return FLOAT_TYPEV2(data_a_packed32[ib_k].dm) * FLOAT_TYPEV2(scale_dm);
308317
}

ggml/src/ggml-vulkan/vulkan-shaders/mul_mm_funcs.glsl

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -201,19 +201,20 @@ void load_a_to_shmem(const uint pos_a, const uint row, const uint col, const uin
201201

202202
const vec2 loadd = vec2(data_a[ib].dm);
203203

204-
const uint scidx0 = (is < 4) ? is : (is + 4);
205-
const uint scidx1 = (is < 4) ? is : (is - 4);
206-
const uint scidxmask1 = (is < 4) ? 0x30 : 0xC0;
207-
const uint scidxshift1 = (is < 4) ? 0 : 2;
208-
const uint mbidx0 = is + 4;
209-
const uint mbidx1 = (is < 4) ? is + 4 : is;
210-
const uint mbidxmask0 = (is < 4) ? 0xF : 0xF0;
211-
const uint mbidxshift0 = (is < 4) ? 0 : 4;
212-
const uint mbidxmask1 = (is < 4) ? 0x30 : 0xC0;
213-
const uint mbidxshift1 = (is < 4) ? 0 : 2;
214-
215-
const uint8_t sc = uint8_t((data_a[ib].scales[scidx0] & 0xF) | ((data_a[ib].scales[scidx1] & scidxmask1) >> scidxshift1));
216-
const uint8_t mbyte = uint8_t((data_a[ib].scales[mbidx0] & mbidxmask0) >> mbidxshift0 | ((data_a[ib].scales[mbidx1] & mbidxmask1) >> mbidxshift1));
204+
const uvec3 scales = uvec3(data_a_packed32[ib].scales[0],
205+
data_a_packed32[ib].scales[1],
206+
data_a_packed32[ib].scales[2]);
207+
const uint scalesoffs = (is & 3) * 8;
208+
209+
const uint scidx0 = (is < 4) ? 0 : 2;
210+
const uint scidxshift0 = scalesoffs;
211+
const uint scidxshift1 = (is < 4) ? scalesoffs : scalesoffs + 2;
212+
const uint mbidx0 = (is < 4) ? 1 : 2;
213+
const uint mbidxshift0 = (is < 4) ? scalesoffs : scalesoffs + 4;
214+
const uint mbidxshift1 = (is < 4) ? scalesoffs : scalesoffs + 2;
215+
216+
const uint8_t sc = uint8_t(((scales[scidx0] >> scidxshift0) & 0xF) | ((scales[0] >> scidxshift1) & 0x30));
217+
const uint8_t mbyte = uint8_t(((scales[mbidx0] >> mbidxshift0) & 0xF) | ((scales[1] >> mbidxshift1) & 0x30));
217218

218219
const float d = loadd.x * sc;
219220
const float m = -loadd.y * mbyte;
@@ -237,19 +238,20 @@ void load_a_to_shmem(const uint pos_a, const uint row, const uint col, const uin
237238

238239
const vec2 loadd = vec2(data_a[ib].dm);
239240

240-
const uint scidx0 = (is < 4) ? is : (is + 4);
241-
const uint scidx1 = (is < 4) ? is : (is - 4);
242-
const uint scidxmask1 = (is < 4) ? 0x30 : 0xC0;
243-
const uint scidxshift1 = (is < 4) ? 0 : 2;
244-
const uint mbidx0 = is + 4;
245-
const uint mbidx1 = (is < 4) ? is + 4 : is;
246-
const uint mbidxmask0 = (is < 4) ? 0xF : 0xF0;
247-
const uint mbidxshift0 = (is < 4) ? 0 : 4;
248-
const uint mbidxmask1 = (is < 4) ? 0x30 : 0xC0;
249-
const uint mbidxshift1 = (is < 4) ? 0 : 2;
250-
251-
const uint8_t sc = uint8_t((data_a[ib].scales[scidx0] & 0xF) | ((data_a[ib].scales[scidx1] & scidxmask1) >> scidxshift1));
252-
const uint8_t mbyte = uint8_t(((data_a[ib].scales[mbidx0] & mbidxmask0) >> mbidxshift0) | ((data_a[ib].scales[mbidx1] & mbidxmask1) >> mbidxshift1));
241+
const uvec3 scales = uvec3(data_a_packed32[ib].scales[0],
242+
data_a_packed32[ib].scales[1],
243+
data_a_packed32[ib].scales[2]);
244+
const uint scalesoffs = (is & 3) * 8;
245+
246+
const uint scidx0 = (is < 4) ? 0 : 2;
247+
const uint scidxshift0 = scalesoffs;
248+
const uint scidxshift1 = (is < 4) ? scalesoffs : scalesoffs + 2;
249+
const uint mbidx0 = (is < 4) ? 1 : 2;
250+
const uint mbidxshift0 = (is < 4) ? scalesoffs : scalesoffs + 4;
251+
const uint mbidxshift1 = (is < 4) ? scalesoffs : scalesoffs + 2;
252+
253+
const uint8_t sc = uint8_t(((scales[scidx0] >> scidxshift0) & 0xF) | ((scales[0] >> scidxshift1) & 0x30));
254+
const uint8_t mbyte = uint8_t(((scales[mbidx0] >> mbidxshift0) & 0xF) | ((scales[1] >> mbidxshift1) & 0x30));
253255

254256
const float d = loadd.x * sc;
255257
const float m = -loadd.y * mbyte;

0 commit comments

Comments
 (0)