vulkan : add IQ4_KS and IQ4_KT support (#2332)

* vulkan : use ggml_row_size for types with a per-row scale

Types that declare a row_meta_size store a per-row scale ahead of the row's
blocks, so a row is not ggml_type_size()*ne/ggml_blck_size() bytes. This
under-sized src0 in the four quantized mat-mul paths, and made
ggml_vk_dim01_contiguous() report such a tensor non-contiguous, which in turn
made supports_op reject it. No change for row_meta_size == 0.

* vulkan : add IQ4_KS and IQ4_KT support

A row of these types is one f32 scale followed by the row's blocks, so rows are
not a whole number of blocks apart and the usual block-indexed addressing does
not work. They are read through a uint32_t alias of binding 0 and addressed by
word; types.comp holds the alias, the stride and the decode, so each shader only
expresses its own addressing and no push constant layouts change.

Covers to_fp16, get_rows, mul_mat_vec (incl. MUL_MAT_ID) and scalar + coopmat1
mul_mm. get_rows addresses by row rather than through nb01/02/03, which cannot
express a per-row scale, so supports_op accepts only a contiguous src0 for these
two types. coopmat2 is excluded because coopMatLoadTensorNV addresses through a
uniform grid tensor layout, which cannot describe the row prefix; the two
mat-mat getters return nullptr there and the callers fall back to F16.

* tests : add IQ4_KS/IQ4_KT decode validation

Re-implements in C++ the indexing each of the four shader families uses and
diffs it against ggml's to_float over several row and block counts. CPU only: it
validates the format transcription, not the compiled shaders.
This commit is contained in:
Guy Barel 2026-08-24 17:20:35 +01:00 committed by GitHub
parent ad26e68bee
commit 64109a4d60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1065 additions and 8 deletions

View File

@ -1884,6 +1884,9 @@ static bool ggml_vk_matmul_shmem_support(const vk_device& device, const std::vec
case GGML_TYPE_IQ4_XS:
lut_size = 4*16;
break;
case GGML_TYPE_IQ4_KS:
lut_size = 4*32;
break;
default:
break;
}
@ -2344,6 +2347,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM2(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ3_S], matmul_iq3_s_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_XS], matmul_iq4_xs_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_NL], matmul_iq4_nl_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KS], matmul_iq4_ks_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KT], matmul_iq4_kt_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
} else {
CREATE_MM(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_0].f32acc, matmul_q4_0_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat[GGML_TYPE_Q4_1].f32acc, matmul_q4_1_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
@ -2365,6 +2370,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ3_S].f32acc, matmul_iq3_s_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_XS].f32acc, matmul_iq4_xs_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_NL].f32acc, matmul_iq4_nl_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KS].f32acc, matmul_iq4_ks_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KT].f32acc, matmul_iq4_kt_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
}
CREATE_MM(GGML_TYPE_F32, pipeline_matmul_id_f32, matmul_id_f32_f32, , wg_denoms, warptile, vk_mat_mat_push_constants, 4, _id);
@ -2397,6 +2404,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ3_S].f16acc, matmul_id_iq3_s_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_XS].f16acc, matmul_id_iq4_xs_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_NL].f16acc, matmul_id_iq4_nl_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KS].f16acc, matmul_id_iq4_ks_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KT].f16acc, matmul_id_iq4_kt_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
} else {
CREATE_MM(GGML_TYPE_Q4_0, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_0].f16acc, matmul_id_q4_0_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_Q4_1, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_Q4_1].f16acc, matmul_id_q4_1_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
@ -2418,6 +2427,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ3_S].f16acc, matmul_id_iq3_s_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_XS].f16acc, matmul_id_iq4_xs_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_NL].f16acc, matmul_id_iq4_nl_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KS].f16acc, matmul_id_iq4_ks_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KT].f16acc, matmul_id_iq4_kt_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
}
#undef CREATE_MM2
#undef CREATE_MM
@ -2485,6 +2496,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM2(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ3_S], matmul_iq3_s_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_XS], matmul_iq4_xs_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_NL], matmul_iq4_nl_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KS], matmul_iq4_ks_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM2(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KT], matmul_iq4_kt_f32, mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
#if defined(GGML_VULKAN_INTEGER_DOT_GLSLC_SUPPORT)
if (device->integer_dot_product) {
@ -2522,6 +2535,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ3_S].f16acc, matmul_id_iq3_s_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_XS].f16acc, matmul_id_iq4_xs_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_NL].f16acc, matmul_id_iq4_nl_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KS].f16acc, matmul_id_iq4_ks_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KT].f16acc, matmul_id_iq4_kt_f32, _f16acc, mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
#undef CREATE_MM2
#undef CREATE_MMQ
#undef CREATE_MM
@ -2576,6 +2591,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ3_S].f32acc, matmul_iq3_s_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_XS].f32acc, matmul_iq4_xs_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_NL].f32acc, matmul_iq4_nl_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KS].f32acc, matmul_iq4_ks_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
CREATE_MM(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat[GGML_TYPE_IQ4_KT].f32acc, matmul_iq4_kt_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_push_constants, 3, );
#if defined(GGML_VULKAN_INTEGER_DOT_GLSLC_SUPPORT)
if (device->integer_dot_product) {
@ -2613,6 +2630,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
CREATE_MM(GGML_TYPE_IQ3_S, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ3_S].f32acc, matmul_id_iq3_s_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_XS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_XS].f32acc, matmul_id_iq4_xs_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_NL, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_NL].f32acc, matmul_id_iq4_nl_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KS, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KS].f32acc, matmul_id_iq4_ks_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
CREATE_MM(GGML_TYPE_IQ4_KT, pipeline_dequant_mul_mat_mat_id[GGML_TYPE_IQ4_KT].f32acc, matmul_id_iq4_kt_f32, , mmq_wg_denoms, warptile_mmq, vk_mat_mat_id_push_constants, 4, _id);
}
// reusing CREATE_MM from the fp32 path
if ((device->coopmat2 || device->coopmat_support)
@ -2671,6 +2690,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[GGML_TYPE_IQ3_S][i], "mul_mat_vec_iq3_s_f32_f32_"+std::to_string(i+1), mul_mat_vec_iq3_s_f32_f32_len, mul_mat_vec_iq3_s_f32_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[GGML_TYPE_IQ4_XS][i], "mul_mat_vec_iq4_xs_f32_f32_"+std::to_string(i+1), mul_mat_vec_iq4_xs_f32_f32_len, mul_mat_vec_iq4_xs_f32_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[GGML_TYPE_IQ4_NL][i], "mul_mat_vec_iq4_nl_f32_f32_"+std::to_string(i+1), mul_mat_vec_iq4_nl_f32_f32_len, mul_mat_vec_iq4_nl_f32_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[GGML_TYPE_IQ4_KS][i], "mul_mat_vec_iq4_ks_f32_f32_"+std::to_string(i+1), mul_mat_vec_iq4_ks_f32_f32_len, mul_mat_vec_iq4_ks_f32_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f32_f32[GGML_TYPE_IQ4_KT][i], "mul_mat_vec_iq4_kt_f32_f32_"+std::to_string(i+1), mul_mat_vec_iq4_kt_f32_f32_len, mul_mat_vec_iq4_kt_f32_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[GGML_TYPE_F32 ][i], "mul_mat_vec_f32_f16_f32_"+std::to_string(i+1), mul_mat_vec_f32_f16_f32_len, mul_mat_vec_f32_f16_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {2, 1, 1}, {device->subgroup_size, 2, i+1}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[GGML_TYPE_F16 ][i], "mul_mat_vec_f16_f16_f32_"+std::to_string(i+1), mul_mat_vec_f16_f16_f32_len, mul_mat_vec_f16_f16_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {2, 1, 1}, {device->subgroup_size, 2, i+1}, 1);
@ -2694,6 +2715,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[GGML_TYPE_IQ3_S][i], "mul_mat_vec_iq3_s_f16_f32_"+std::to_string(i+1), mul_mat_vec_iq3_s_f16_f32_len, mul_mat_vec_iq3_s_f16_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[GGML_TYPE_IQ4_XS][i], "mul_mat_vec_iq4_xs_f16_f32_"+std::to_string(i+1), mul_mat_vec_iq4_xs_f16_f32_len, mul_mat_vec_iq4_xs_f16_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[GGML_TYPE_IQ4_NL][i], "mul_mat_vec_iq4_nl_f16_f32_"+std::to_string(i+1), mul_mat_vec_iq4_nl_f16_f32_len, mul_mat_vec_iq4_nl_f16_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[GGML_TYPE_IQ4_KS][i], "mul_mat_vec_iq4_ks_f16_f32_"+std::to_string(i+1), mul_mat_vec_iq4_ks_f16_f32_len, mul_mat_vec_iq4_ks_f16_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_f16_f32[GGML_TYPE_IQ4_KT][i], "mul_mat_vec_iq4_kt_f16_f32_"+std::to_string(i+1), mul_mat_vec_iq4_kt_f16_f32_len, mul_mat_vec_iq4_kt_f16_f32_data, "main", 3, sizeof(vk_mat_vec_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq, i+1}, 1, true);
}
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[GGML_TYPE_F32 ], "mul_mat_vec_id_f32_f32", mul_mat_vec_id_f32_f32_len, mul_mat_vec_id_f32_f32_data, "main", 4, sizeof(vk_mat_vec_id_push_constants), {2, 1, 1}, {device->subgroup_size, 2}, 1);
@ -2718,6 +2741,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[GGML_TYPE_IQ3_S], "mul_mat_vec_id_iq3_s_f32", mul_mat_vec_id_iq3_s_f32_len, mul_mat_vec_id_iq3_s_f32_data, "main", 4, sizeof(vk_mat_vec_id_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[GGML_TYPE_IQ4_XS], "mul_mat_vec_id_iq4_xs_f32", mul_mat_vec_id_iq4_xs_f32_len, mul_mat_vec_id_iq4_xs_f32_data, "main", 4, sizeof(vk_mat_vec_id_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[GGML_TYPE_IQ4_NL], "mul_mat_vec_id_iq4_nl_f32", mul_mat_vec_id_iq4_nl_f32_len, mul_mat_vec_id_iq4_nl_f32_data, "main", 4, sizeof(vk_mat_vec_id_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[GGML_TYPE_IQ4_KS], "mul_mat_vec_id_iq4_ks_f32", mul_mat_vec_id_iq4_ks_f32_len, mul_mat_vec_id_iq4_ks_f32_data, "main", 4, sizeof(vk_mat_vec_id_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq}, 1, true);
ggml_vk_create_pipeline(device, device->pipeline_dequant_mul_mat_vec_id_f32[GGML_TYPE_IQ4_KT], "mul_mat_vec_id_iq4_kt_f32", mul_mat_vec_id_iq4_kt_f32_len, mul_mat_vec_id_iq4_kt_f32_data, "main", 4, sizeof(vk_mat_vec_id_push_constants), {rm_iq, 1, 1}, {subgroup_size_16, rm_iq}, 1, true);
// dequant shaders
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_F32 ], "f32_to_f16", dequant_f32_len, dequant_f32_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
@ -2740,6 +2765,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_IQ3_S], "dequant_iq3_s", dequant_iq3_s_len, dequant_iq3_s_data, "main", 2, 5 * sizeof(uint32_t), {256 * 32, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_IQ4_XS], "dequant_iq4_xs", dequant_iq4_xs_len, dequant_iq4_xs_data, "main", 2, 5 * sizeof(uint32_t), {256 * 32, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_IQ4_NL], "dequant_iq4_nl", dequant_iq4_nl_len, dequant_iq4_nl_data, "main", 2, 5 * sizeof(uint32_t), {256 * 16, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_IQ4_KS], "dequant_iq4_ks", dequant_iq4_ks_len, dequant_iq4_ks_data, "main", 2, 5 * sizeof(uint32_t), {256 * 32, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_dequant[GGML_TYPE_IQ4_KT], "dequant_iq4_kt", dequant_iq4_kt_len, dequant_iq4_kt_data, "main", 2, 5 * sizeof(uint32_t), {256 * 32, 1, 1}, {}, 1);
// get_rows
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_F32 ], "get_rows_f32", get_rows_f32_len, get_rows_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), { 512, 1, 1}, {}, 1);
@ -2759,6 +2786,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_IQ3_S], "get_rows_iq3_s", get_rows_iq3_s_len, get_rows_iq3_s_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_IQ4_XS], "get_rows_iq4_xs", get_rows_iq4_xs_len, get_rows_iq4_xs_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_IQ4_NL], "get_rows_iq4_nl", get_rows_iq4_nl_len, get_rows_iq4_nl_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_IQ4_KS], "get_rows_iq4_ks", get_rows_iq4_ks_len, get_rows_iq4_ks_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows[GGML_TYPE_IQ4_KT], "get_rows_iq4_kt", get_rows_iq4_kt_len, get_rows_iq4_kt_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_F32 ], "get_rows_f32_f32", get_rows_f32_f32_len, get_rows_f32_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), { 512, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_F16 ], "get_rows_f16_f32", get_rows_f16_f32_len, get_rows_f16_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), { 512, 1, 1}, {}, 1);
@ -2777,6 +2806,8 @@ static void ggml_vk_load_shaders(vk_device& device) {
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_IQ3_S], "get_rows_iq3_s_f32", get_rows_iq3_s_f32_len, get_rows_iq3_s_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_IQ4_XS], "get_rows_iq4_xs_f32", get_rows_iq4_xs_f32_len, get_rows_iq4_xs_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_IQ4_NL], "get_rows_iq4_nl_f32", get_rows_iq4_nl_f32_len, get_rows_iq4_nl_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_IQ4_KS], "get_rows_iq4_ks_f32", get_rows_iq4_ks_f32_len, get_rows_iq4_ks_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_get_rows_f32[GGML_TYPE_IQ4_KT], "get_rows_iq4_kt_f32", get_rows_iq4_kt_f32_len, get_rows_iq4_kt_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {1024, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_matmul_split_k_reduce, "split_k_reduce", split_k_reduce_len, split_k_reduce_data, "main", 2, 2 * sizeof(uint32_t), {256 * 4, 1, 1}, {}, 1);
ggml_vk_create_pipeline(device, device->pipeline_flash_attn_split_k_reduce, "fa_split_k_reduce", fa_split_k_reduce_len, fa_split_k_reduce_data, "main", 2, 4 * sizeof(uint32_t), {1, device->subgroup_size, 1}, {device->subgroup_size}, 1, true);
@ -4032,6 +4063,8 @@ static vk_pipeline ggml_vk_get_to_fp16(ggml_backend_vk_context * ctx, ggml_type
case GGML_TYPE_IQ3_S:
case GGML_TYPE_IQ4_XS:
case GGML_TYPE_IQ4_NL:
case GGML_TYPE_IQ4_KS:
case GGML_TYPE_IQ4_KT:
break;
default:
return nullptr;
@ -4040,6 +4073,11 @@ static vk_pipeline ggml_vk_get_to_fp16(ggml_backend_vk_context * ctx, ggml_type
return ctx->device->pipeline_dequant[type];
}
// types that keep a per-row scale ahead of the row's blocks
static bool ggml_vk_is_row_meta_quant(ggml_type type) {
return ggml_internal_get_type_traits(type).row_meta_size != 0;
}
static vk_matmul_pipeline ggml_vk_get_mul_mat_mat_pipeline(ggml_backend_vk_context * ctx, ggml_type src0_type, ggml_type src1_type, ggml_prec prec) {
VK_LOG_DEBUG("ggml_vk_get_mul_mat_mat_pipeline(" << ggml_type_name(src0_type) << ", " << ggml_type_name(src1_type) << ", " << prec << ")");
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_F32) {
@ -4082,6 +4120,12 @@ static vk_matmul_pipeline ggml_vk_get_mul_mat_mat_pipeline(ggml_backend_vk_conte
return nullptr;
}
// the coopmat2 tensor layout cannot describe a per-row scale ahead of the blocks, so
// these types have no tiled matmul there and the caller falls back to dequantizing to F16
if (ctx->device->coopmat2 && ggml_vk_is_row_meta_quant(src0_type)) {
return nullptr;
}
switch (src0_type) {
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
@ -4102,6 +4146,8 @@ static vk_matmul_pipeline ggml_vk_get_mul_mat_mat_pipeline(ggml_backend_vk_conte
case GGML_TYPE_IQ3_S:
case GGML_TYPE_IQ4_XS:
case GGML_TYPE_IQ4_NL:
case GGML_TYPE_IQ4_KS:
case GGML_TYPE_IQ4_KT:
break;
default:
return nullptr;
@ -4145,6 +4191,8 @@ static vk_pipeline ggml_vk_get_dequantize_mul_mat_vec(ggml_backend_vk_context *
case GGML_TYPE_IQ3_S:
case GGML_TYPE_IQ4_XS:
case GGML_TYPE_IQ4_NL:
case GGML_TYPE_IQ4_KS:
case GGML_TYPE_IQ4_KT:
break;
default:
return nullptr;
@ -4184,6 +4232,12 @@ static vk_matmul_pipeline ggml_vk_get_mul_mat_mat_id_pipeline(ggml_backend_vk_co
GGML_ASSERT(src1_type == GGML_TYPE_F32 || (ctx->device->coopmat2 && src1_type == GGML_TYPE_F16));
// the coopmat2 tensor layout cannot describe a per-row scale ahead of the blocks, so
// these types have no tiled matmul there and the caller falls back to dequantizing to F16
if (ctx->device->coopmat2 && ggml_vk_is_row_meta_quant(src0_type)) {
return nullptr;
}
switch (src0_type) {
case GGML_TYPE_Q4_0:
case GGML_TYPE_Q4_1:
@ -4204,6 +4258,8 @@ static vk_matmul_pipeline ggml_vk_get_mul_mat_mat_id_pipeline(ggml_backend_vk_co
case GGML_TYPE_IQ3_S:
case GGML_TYPE_IQ4_XS:
case GGML_TYPE_IQ4_NL:
case GGML_TYPE_IQ4_KS:
case GGML_TYPE_IQ4_KT:
break;
default:
return nullptr;
@ -4239,6 +4295,8 @@ static vk_pipeline ggml_vk_get_dequantize_mul_mat_vec_id(ggml_backend_vk_context
case GGML_TYPE_IQ3_S:
case GGML_TYPE_IQ4_XS:
case GGML_TYPE_IQ4_NL:
case GGML_TYPE_IQ4_KS:
case GGML_TYPE_IQ4_KT:
break;
default:
return nullptr;
@ -4965,9 +5023,14 @@ static void ggml_vk_matmul_id(
}
static bool ggml_vk_dim01_contiguous(const ggml_tensor * tensor) {
// a row that is not a whole number of blocks is not contiguous, and ggml_row_size()
// asserts on it
if (tensor->ne[0] % ggml_blck_size(tensor->type) != 0) {
return false;
}
return
tensor->nb[0] == ggml_type_size(tensor->type) &&
tensor->nb[1] == (tensor->nb[0]*tensor->ne[0])/ggml_blck_size(tensor->type) &&
tensor->nb[1] == ggml_row_size(tensor->type, tensor->ne[0]) &&
tensor->nb[3] == tensor->nb[2]*tensor->ne[2];
}
@ -5202,7 +5265,7 @@ static void ggml_vk_mul_mat_q_f16(ggml_backend_vk_context * ctx, vk_context& sub
const uint32_t split_k = ggml_vk_guess_split_k(ctx, ne01, ne11, ne10, pipeline);
const uint64_t qx_sz = ggml_type_size(src0->type) * x_ne / ggml_blck_size(src0->type);
const uint64_t qx_sz = ggml_row_size(src0->type, ne00) * ne01;
const uint64_t qy_sz = ggml_type_size(src1->type) * y_ne / ggml_blck_size(src1->type);
const uint64_t x_sz = !qx_needs_dequant ? qx_sz : sizeof(ggml_fp16_t) * x_ne;
const uint64_t y_sz = quantize_y ? (y_ne * ggml_type_size(GGML_TYPE_Q8_1) / ggml_blck_size(GGML_TYPE_Q8_1)) : (y_f32_kernel ? sizeof(float) * y_ne : sizeof(ggml_fp16_t) * y_ne);
@ -5405,7 +5468,7 @@ static void ggml_vk_mul_mat_vec_q_f16(ggml_backend_vk_context * ctx, vk_context&
const uint64_t y_ne = ne11 * ne10;
const uint64_t d_ne = ne11 * ne01;
const uint64_t qx_sz = ggml_vk_align_size(ggml_type_size(src0->type) * x_ne / ggml_blck_size(src0->type), ctx->device->properties.limits.minStorageBufferOffsetAlignment);
const uint64_t qx_sz = ggml_vk_align_size(ggml_row_size(src0->type, ne00) * ne01, ctx->device->properties.limits.minStorageBufferOffsetAlignment);
const uint64_t qy_sz = ggml_type_size(src1->type) * y_ne / ggml_blck_size(src1->type);
const uint64_t x_sz = x_non_contig ? ggml_vk_align_size(ggml_type_size(src0->type) * x_ne, ctx->device->properties.limits.minStorageBufferOffsetAlignment) : qx_sz;
const uint64_t y_sz = f16_f32_kernel ? sizeof(float) * y_ne : sizeof(ggml_fp16_t) * y_ne;
@ -5818,7 +5881,7 @@ static void ggml_vk_mul_mat_id_q_f16(ggml_backend_vk_context * ctx, vk_context&
const uint64_t y_ne = padded_n * ne10;
const uint64_t d_ne = ne21 * ne20;
const uint64_t qx_sz = ggml_type_size(src0->type) * x_ne / ggml_blck_size(src0->type);
const uint64_t qx_sz = ggml_row_size(src0->type, ne00) * ne01;
const uint64_t qy_sz = ggml_type_size(src1->type) * y_ne / ggml_blck_size(src1->type);
const uint64_t x_sz = !qx_needs_dequant ? qx_sz : sizeof(ggml_fp16_t) * x_ne;
const uint64_t y_sz = y_f32_kernel ? sizeof(float) * y_ne : sizeof(ggml_fp16_t) * y_ne;
@ -6012,7 +6075,7 @@ static void ggml_vk_mul_mat_vec_id_q_f16(ggml_backend_vk_context * ctx, vk_conte
const uint64_t y_ne = ne11 * ne10;
const uint64_t d_ne = ne21 * ne20;
const uint64_t qx_sz = ggml_vk_align_size(ggml_type_size(src0->type) * x_ne / ggml_blck_size(src0->type), ctx->device->properties.limits.minStorageBufferOffsetAlignment);
const uint64_t qx_sz = ggml_vk_align_size(ggml_row_size(src0->type, ne00) * ne01, ctx->device->properties.limits.minStorageBufferOffsetAlignment);
const uint64_t qy_sz = ggml_type_size(src1->type) * y_ne / ggml_blck_size(src1->type);
const uint64_t x_sz = x_non_contig ? ggml_vk_align_size(ggml_type_size(src0->type) * x_ne, ctx->device->properties.limits.minStorageBufferOffsetAlignment) : qx_sz;
const uint64_t y_sz = f16_f32_kernel ? sizeof(float) * y_ne : sizeof(ggml_fp16_t) * y_ne;
@ -10431,6 +10494,8 @@ static bool ggml_backend_vk_supports_op(ggml_backend_t backend, const ggml_tenso
case GGML_TYPE_IQ3_S:
case GGML_TYPE_IQ4_XS:
case GGML_TYPE_IQ4_NL:
case GGML_TYPE_IQ4_KS:
case GGML_TYPE_IQ4_KT:
break;
default:
return false;
@ -10538,6 +10603,11 @@ static bool ggml_backend_vk_supports_op(ggml_backend_t backend, const ggml_tenso
case GGML_TYPE_IQ2_S:
case GGML_TYPE_IQ3_XXS:
case GGML_TYPE_IQ3_S:
case GGML_TYPE_IQ4_KS:
case GGML_TYPE_IQ4_KT:
// these two are addressed by row rather than through nb01/02/03,
// which cannot express a per-row scale, so only contiguous src0
return ggml_is_contiguous(op->src[0]);
case GGML_TYPE_IQ4_XS:
case GGML_TYPE_IQ4_NL:
return true;

View File

@ -0,0 +1,39 @@
#version 450
#include "dequant_head.comp"
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
layout (binding = 1) writeonly buffer D {D_TYPE data_b[];};
void main() {
// Each thread handles 1 subblock (1 scale and 32 quantized values)
const uint ib = gl_WorkGroupID.x * 32 + gl_LocalInvocationID.x / 8;
init_iq_shmem(gl_WorkGroupSize);
if (ib >= p.nel / 256) {
return;
}
const uint ib32 = gl_LocalInvocationID.x % 8;
const uint blocks_per_row = p.K / QUANT_K;
const uint row_word = (ib / blocks_per_row) * a_row_words(p.K);
const uint blk = a_block_word(row_word, ib % blocks_per_row);
uint vo;
const float dl = iq4_ks_scale(row_word, blk, ib32, vo);
const uint b_idx = 256 * ib + 32 * ib32;
// qs[] starts at byte 8, each subblock owns 16 bytes of it
const uint q_word = blk + 2 + 4 * ib32;
[[unroll]] for (uint l = 0; l < 4; ++l) {
const uint qs = data_a_u32[q_word + l];
[[unroll]] for (uint k = 0; k < 4; ++k) {
const uint q = (qs >> (8 * k)) & 0xFF;
data_b[b_idx + 4 * l + k ] = D_TYPE(dl * kvalues_iq4k[vo + (q & 0xF)]);
data_b[b_idx + 4 * l + k + 16] = D_TYPE(dl * kvalues_iq4k[vo + (q >> 4)]);
}
}
}

View File

@ -0,0 +1,36 @@
#version 450
#include "dequant_head.comp"
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
layout (binding = 1) writeonly buffer D {D_TYPE data_b[];};
void main() {
// Each thread handles 1 subblock (1 scale and 32 quantized values)
const uint ib = gl_WorkGroupID.x * 32 + gl_LocalInvocationID.x / 8;
if (ib >= p.nel / 256) {
return;
}
const uint ib32 = gl_LocalInvocationID.x % 8;
const uint blocks_per_row = p.K / QUANT_K;
const uint row_word = (ib / blocks_per_row) * a_row_words(p.K);
const uint blk = a_block_word(row_word, ib % blocks_per_row);
// shb[] occupies the first 8 words of the block, ql[] the next 16, qh[] the last 8
const uint shb = data_a_u32[blk + ib32];
uint offset;
const float sl = iq4_kt_scale(row_word, shb, offset);
const uint b_idx = 256 * ib + 32 * ib32;
[[unroll]] for (uint ig = 0; ig < 8; ++ig) {
uint x = iq4_kt_index(blk, shb, ib32, ig, offset);
[[unroll]] for (uint k = 0; k < 4; ++k) {
data_b[b_idx + 4 * ig + k] = D_TYPE(sl * iq4_kt_next(x));
}
}
}

View File

@ -0,0 +1,48 @@
#version 450
#extension GL_EXT_control_flow_attributes : enable
#include "types.comp"
#include "generic_binary_head.comp"
layout(local_size_x = 512, local_size_y = 1, local_size_z = 1) in;
// src0 is assumed contiguous. p.nb01/02/03 are byte strides divided by the type size,
// which is not a whole number for a type carrying a per-row scale, so the source row is
// derived from the shape instead. Token embedding tensors are contiguous in practice.
float dequant_elem(const uint row_word, const uint i00) {
const uint blk = a_block_word(row_word, i00 / QUANT_K);
const uint pos = i00 % QUANT_K;
const uint ib32 = pos / 32;
const uint j = pos % 32;
uint vo;
const float dl = iq4_ks_scale(row_word, blk, ib32, vo);
// qs[] starts at byte 8; the low nibbles hold the first 16 weights of the subblock,
// the high nibbles the rest
const uint q = a_byte(blk + 2, 16 * ib32 + (j & 15));
return dl * float(kvalues_iq4k[vo + (j < 16 ? (q & 0xF) : (q >> 4))]);
}
void main() {
const uint i00 = (gl_GlobalInvocationID.x)*2;
const uint i10 = gl_GlobalInvocationID.y;
const uint i11 = (gl_GlobalInvocationID.z)/p.ne12;
const uint i12 = (gl_GlobalInvocationID.z)%p.ne12;
init_iq_shmem(gl_WorkGroupSize);
if (i00 >= p.ne00) {
return;
}
const uint i01 = data_b[i10*p.nb10 + i11*p.nb11 + i12*p.nb12];
const uint row_word = (i01 + (i11 + i12*p.ne02)*p.ne01) * a_row_words(p.ne00);
const uint d_offset = i10*p.nb21 + i11*p.nb22 + i12*p.nb23;
data_d[d_offset + i00 ] = D_TYPE(dequant_elem(row_word, i00 ));
data_d[d_offset + i00 + 1] = D_TYPE(dequant_elem(row_word, i00 + 1));
}

View File

@ -0,0 +1,56 @@
#version 450
#extension GL_EXT_control_flow_attributes : enable
#include "types.comp"
#include "generic_binary_head.comp"
layout(local_size_x = 512, local_size_y = 1, local_size_z = 1) in;
// src0 is assumed contiguous. p.nb01/02/03 are byte strides divided by the type size,
// which is not a whole number for a type carrying a per-row scale, so the source row is
// derived from the shape instead. Token embedding tensors are contiguous in practice.
// The two weights handled by one invocation always fall in the same group of 4, so the
// trellis is only iterated once.
void dequant_pair(const uint row_word, const uint i00, out float v0, out float v1) {
const uint blk = a_block_word(row_word, i00 / QUANT_K);
const uint pos = i00 % QUANT_K;
const uint ib32 = pos / 32;
const uint shb = data_a_u32[blk + ib32];
uint offset;
const float sl = iq4_kt_scale(row_word, shb, offset);
uint x = iq4_kt_index(blk, shb, ib32, (pos % 32) / 4, offset);
const uint k = pos % 4;
v0 = 0.0f;
v1 = 0.0f;
[[unroll]] for (uint l = 0; l < 4; ++l) {
const float w = sl * iq4_kt_next(x);
if (l == k ) v0 = w;
if (l == k + 1) v1 = w;
}
}
void main() {
const uint i00 = (gl_GlobalInvocationID.x)*2;
const uint i10 = gl_GlobalInvocationID.y;
const uint i11 = (gl_GlobalInvocationID.z)/p.ne12;
const uint i12 = (gl_GlobalInvocationID.z)%p.ne12;
if (i00 >= p.ne00) {
return;
}
const uint i01 = data_b[i10*p.nb10 + i11*p.nb11 + i12*p.nb12];
const uint row_word = (i01 + (i11 + i12*p.ne02)*p.ne01) * a_row_words(p.ne00);
const uint d_offset = i10*p.nb21 + i11*p.nb22 + i12*p.nb23;
float v0, v1;
dequant_pair(row_word, i00, v0, v1);
data_d[d_offset + i00 ] = D_TYPE(v0);
data_d[d_offset + i00 + 1] = D_TYPE(v1);
}

View File

@ -0,0 +1,96 @@
#version 450
#extension GL_EXT_shader_explicit_arithmetic_types_int32 : require
#include "mul_mat_vec_base.comp"
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
FLOAT_TYPE temp[NUM_COLS][NUM_ROWS];
void calc_superblock(const uint a_offset, const uint b_offset, const uint ib32, const uint i, const uint num_blocks_per_row, const uint first_row, const uint num_rows) {
const uint y_idx = i * QUANT_K + 32 * ib32;
const uint words_per_row = a_row_words(p.ncols);
// a_offset is an element offset, so it divides exactly by the row length
uint row_word = (a_offset / p.ncols + first_row) * words_per_row;
[[unroll]] for (uint n = 0; n < num_rows; ++n) {
const uint blk = a_block_word(row_word, i);
uint vo;
const float dl = iq4_ks_scale(row_word, blk, ib32, vo);
FLOAT_TYPE sum[NUM_COLS];
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
sum[j] = FLOAT_TYPE(0);
}
[[unroll]] for (uint l = 0; l < 4; ++l) {
const uint qs = data_a_u32[blk + 2 + 4 * ib32 + l];
const vec4 vlo = vec4(kvalues_iq4k[vo + ((qs ) & 0xF)], kvalues_iq4k[vo + ((qs >> 8) & 0xF)],
kvalues_iq4k[vo + ((qs >> 16) & 0xF)], kvalues_iq4k[vo + ((qs >> 24) & 0xF)]);
const vec4 vhi = vec4(kvalues_iq4k[vo + ((qs >> 4) & 0xF)], kvalues_iq4k[vo + ((qs >> 12) & 0xF)],
kvalues_iq4k[vo + ((qs >> 20) & 0xF)], kvalues_iq4k[vo + ((qs >> 28) & 0xF)]);
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
const uint bbase = (j*p.batch_stride_b + b_offset + y_idx) / 4;
const vec4 b0 = vec4(data_b_v4[bbase + l ]);
const vec4 b1 = vec4(data_b_v4[bbase + l + 4]);
sum[j] =
fma(FLOAT_TYPE(b0.x), FLOAT_TYPE(vlo.x),
fma(FLOAT_TYPE(b0.y), FLOAT_TYPE(vlo.y),
fma(FLOAT_TYPE(b0.z), FLOAT_TYPE(vlo.z),
fma(FLOAT_TYPE(b0.w), FLOAT_TYPE(vlo.w),
fma(FLOAT_TYPE(b1.x), FLOAT_TYPE(vhi.x),
fma(FLOAT_TYPE(b1.y), FLOAT_TYPE(vhi.y),
fma(FLOAT_TYPE(b1.z), FLOAT_TYPE(vhi.z),
fma(FLOAT_TYPE(b1.w), FLOAT_TYPE(vhi.w),
sum[j]))))))));
}
}
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
temp[j][n] = fma(FLOAT_TYPE(dl), sum[j], temp[j][n]);
}
row_word += words_per_row;
}
}
void compute_outputs(const uint32_t first_row, const uint32_t num_rows) {
uint a_offset, b_offset, d_offset;
get_offsets(a_offset, b_offset, d_offset);
const uint num_blocks_per_row = p.ncols / QUANT_K;
// 8 threads are used to process each block
const uint blocks_per_wg = gl_WorkGroupSize.x/8;
const uint tid = gl_LocalInvocationID.x;
const uint itid = tid % 8; // 0...7
const uint ix = tid / 8;
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
[[unroll]] for (uint i = 0; i < NUM_ROWS; ++i) {
temp[j][i] = FLOAT_TYPE(0);
}
}
[[unroll]] for (uint i = ix; i < num_blocks_per_row; i += blocks_per_wg)
calc_superblock(a_offset, b_offset, itid, i, num_blocks_per_row, first_row, num_rows);
reduce_result(temp, d_offset, first_row, num_rows, tid);
}
void main() {
const uint first_row = NUM_ROWS * (gl_WorkGroupID.x + gl_NumWorkGroups.x * gl_WorkGroupID.z);
init_iq_shmem(gl_WorkGroupSize);
// do NUM_ROWS at a time, unless there aren't enough remaining rows
if (first_row + NUM_ROWS <= p.stride_d) {
compute_outputs(first_row, NUM_ROWS);
} else {
if (first_row >= p.stride_d) {
return;
}
compute_outputs(first_row, p.stride_d - first_row);
}
}

View File

@ -0,0 +1,90 @@
#version 450
#extension GL_EXT_shader_explicit_arithmetic_types_int32 : require
#include "mul_mat_vec_base.comp"
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
FLOAT_TYPE temp[NUM_COLS][NUM_ROWS];
void calc_superblock(const uint a_offset, const uint b_offset, const uint ib32, const uint i, const uint num_blocks_per_row, const uint first_row, const uint num_rows) {
const uint y_idx = i * QUANT_K + 32 * ib32;
const uint words_per_row = a_row_words(p.ncols);
// a_offset is an element offset, so it divides exactly by the row length
uint row_word = (a_offset / p.ncols + first_row) * words_per_row;
[[unroll]] for (uint n = 0; n < num_rows; ++n) {
const uint blk = a_block_word(row_word, i);
const uint shb = data_a_u32[blk + ib32];
uint offset;
const float sl = iq4_kt_scale(row_word, shb, offset);
FLOAT_TYPE sum[NUM_COLS];
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
sum[j] = FLOAT_TYPE(0);
}
[[unroll]] for (uint ig = 0; ig < 8; ++ig) {
uint x = iq4_kt_index(blk, shb, ib32, ig, offset);
vec4 w;
[[unroll]] for (uint k = 0; k < 4; ++k) {
w[k] = iq4_kt_next(x);
}
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
const vec4 b0 = vec4(data_b_v4[(j*p.batch_stride_b + b_offset + y_idx) / 4 + ig]);
sum[j] =
fma(FLOAT_TYPE(b0.x), FLOAT_TYPE(w.x),
fma(FLOAT_TYPE(b0.y), FLOAT_TYPE(w.y),
fma(FLOAT_TYPE(b0.z), FLOAT_TYPE(w.z),
fma(FLOAT_TYPE(b0.w), FLOAT_TYPE(w.w),
sum[j]))));
}
}
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
temp[j][n] = fma(FLOAT_TYPE(sl), sum[j], temp[j][n]);
}
row_word += words_per_row;
}
}
void compute_outputs(const uint32_t first_row, const uint32_t num_rows) {
uint a_offset, b_offset, d_offset;
get_offsets(a_offset, b_offset, d_offset);
const uint num_blocks_per_row = p.ncols / QUANT_K;
// 8 threads are used to process each block
const uint blocks_per_wg = gl_WorkGroupSize.x/8;
const uint tid = gl_LocalInvocationID.x;
const uint itid = tid % 8; // 0...7
const uint ix = tid / 8;
[[unroll]] for (uint j = 0; j < NUM_COLS; ++j) {
[[unroll]] for (uint i = 0; i < NUM_ROWS; ++i) {
temp[j][i] = FLOAT_TYPE(0);
}
}
[[unroll]] for (uint i = ix; i < num_blocks_per_row; i += blocks_per_wg)
calc_superblock(a_offset, b_offset, itid, i, num_blocks_per_row, first_row, num_rows);
reduce_result(temp, d_offset, first_row, num_rows, tid);
}
void main() {
const uint first_row = NUM_ROWS * (gl_WorkGroupID.x + gl_NumWorkGroups.x * gl_WorkGroupID.z);
// do NUM_ROWS at a time, unless there aren't enough remaining rows
if (first_row + NUM_ROWS <= p.stride_d) {
compute_outputs(first_row, NUM_ROWS);
} else {
if (first_row >= p.stride_d) {
return;
}
compute_outputs(first_row, p.stride_d - first_row);
}
}

View File

@ -747,6 +747,50 @@ void main() {
buf_a[buf_idx + 1 ] = FLOAT_TYPE(kvalues_iq4nl[bitfieldExtract(vui, 8, 4)]) * d;
buf_a[buf_idx + 16] = FLOAT_TYPE(kvalues_iq4nl[bitfieldExtract(vui, 4, 4)]) * d;
buf_a[buf_idx + 17] = FLOAT_TYPE(kvalues_iq4nl[vui >> 12]) * d;
#elif defined(DATA_A_IQ4_KS)
const uint idx = pos_a + (loadc_a + l) * p.stride_a / LOAD_VEC_A + loadr_a;
const uint buf_idx = (loadc_a + l) * SHMEM_STRIDE + loadr_a * LOAD_VEC_A;
// recover the position from the load index; exact because batch_stride_a is ne00*ne01
const uint elem = idx * LOAD_VEC_A;
const uint row_word = (elem / p.stride_a) * a_row_words(p.stride_a);
const uint acol = elem % p.stride_a;
const uint blk = a_block_word(row_word, acol / QUANT_K);
const uint ib32 = (acol % QUANT_K) / 32;
const uint j = acol % 32;
uint vo;
const float dl = iq4_ks_scale(row_word, blk, ib32, vo);
const uint qb = 16 * ib32 + (j & 15);
const uint qw = data_a_u32[blk + 2 + qb / 4] >> (8 * (qb % 4));
const uint qshift = j < 16 ? 0 : 4;
buf_a[buf_idx ] = FLOAT_TYPE(dl * kvalues_iq4k[vo + ((qw >> qshift) & 0xF)]);
buf_a[buf_idx + 1] = FLOAT_TYPE(dl * kvalues_iq4k[vo + ((qw >> (8 + qshift)) & 0xF)]);
#elif defined(DATA_A_IQ4_KT)
const uint idx = pos_a + (loadc_a + l) * p.stride_a / LOAD_VEC_A + loadr_a;
const uint buf_idx = (loadc_a + l) * SHMEM_STRIDE + loadr_a * LOAD_VEC_A;
// LOAD_VEC_A is 4 here, so one load index is one trellis group of 4
const uint elem = idx * LOAD_VEC_A;
const uint row_word = (elem / p.stride_a) * a_row_words(p.stride_a);
const uint acol = elem % p.stride_a;
const uint blk = a_block_word(row_word, acol / QUANT_K);
const uint ib32 = (acol % QUANT_K) / 32;
const uint shb = data_a_u32[blk + ib32];
uint offset;
const float sl = iq4_kt_scale(row_word, shb, offset);
uint x = iq4_kt_index(blk, shb, ib32, (acol % 32) / 4, offset);
buf_a[buf_idx ] = FLOAT_TYPE(sl * iq4_kt_next(x));
buf_a[buf_idx + 1] = FLOAT_TYPE(sl * iq4_kt_next(x));
buf_a[buf_idx + 2] = FLOAT_TYPE(sl * iq4_kt_next(x));
buf_a[buf_idx + 3] = FLOAT_TYPE(sl * iq4_kt_next(x));
#endif
}
[[unroll]] for (uint l = 0; l < BN; l += loadstride_b) {

View File

@ -1356,6 +1356,121 @@ void init_iq_shmem(uvec3 wgsize)
}
#endif
// IQ4_KS and IQ4_KT rows are one f32 scale followed by the row's blocks, so rows are not a
// whole number of blocks apart. These two are addressed by word through a uint32_t alias of
// binding 0; both block sizes and the prefix are multiples of 4 bytes, so strides are exact.
// The prefix is one word for these two types; a type with a different row_meta_size needs
// its own stride.
#define A_ROW_META_WORDS 1
#define QUANT_K_IQ4_KS 256
#define QUANT_R_IQ4_KS 1
struct block_iq4_ks
{
uint8_t scales[QUANT_K_IQ4_KS/32];
uint8_t qs[QUANT_K_IQ4_KS/2];
};
#if defined(DATA_A_IQ4_KS)
#define QUANT_K QUANT_K_IQ4_KS
#define QUANT_R QUANT_R_IQ4_KS
#define A_TYPE block_iq4_ks
#define A_TYPE_ROW_META
#define BLOCK_WORDS 34
#endif
#define QUANT_K_IQ4_KT 256
#define QUANT_R_IQ4_KT 1
struct block_iq4_kt
{
uint32_t qs[QUANT_K_IQ4_KT/8];
};
#if defined(DATA_A_IQ4_KT)
#define QUANT_K QUANT_K_IQ4_KT
#define QUANT_R QUANT_R_IQ4_KT
#define A_TYPE block_iq4_kt
#define A_TYPE_ROW_META
#define BLOCK_WORDS 32
#endif
#if defined(A_TYPE_ROW_META)
layout (binding = 0) readonly buffer A_U32 {uint data_a_u32[];};
uint a_row_words(uint ncols) {
return A_ROW_META_WORDS + (ncols / QUANT_K) * BLOCK_WORDS;
}
uint a_block_word(uint row_word, uint ib) {
return row_word + A_ROW_META_WORDS + ib * BLOCK_WORDS;
}
uint a_byte(uint base_word, uint idx) {
return (data_a_u32[base_word + idx / 4] >> (8 * (idx % 4))) & 0xFF;
}
float a_row_scale(uint row_word) {
return uintBitsToFloat(data_a_u32[row_word]);
}
#endif
#if defined(DATA_A_IQ4_KS)
const int8_t kvalues_iq4k_const[32] = {
int8_t(-127), int8_t(-104), int8_t(-83), int8_t(-65), int8_t(-49), int8_t(-35), int8_t(-22), int8_t(-10),
int8_t(1), int8_t(13), int8_t(25), int8_t(38), int8_t(53), int8_t(69), int8_t(89), int8_t(113),
int8_t(-123), int8_t(-100), int8_t(-79), int8_t(-61), int8_t(-45), int8_t(-31), int8_t(-18), int8_t(-6),
int8_t(5), int8_t(17), int8_t(29), int8_t(42), int8_t(57), int8_t(73), int8_t(93), int8_t(117)
};
shared FLOAT_TYPE kvalues_iq4k[32];
#define NEEDS_INIT_IQ_SHMEM
void init_iq_shmem(uvec3 wgsize)
{
// copy the table into shared memory and sync
for (uint i = gl_LocalInvocationIndex.x; i < kvalues_iq4k.length(); i += wgsize.x) {
kvalues_iq4k[i] = FLOAT_TYPE(kvalues_iq4k_const[i]);
}
barrier();
}
// scales[ib32]: bits 1..7 signed magnitude, bit 0 selects the codebook half (-> vo)
float iq4_ks_scale(uint row_word, uint blk, uint ib32, out uint vo)
{
const uint sc = a_byte(blk, ib32);
vo = (sc & 1) << 4;
return a_row_scale(row_word) * float(int(sc & 254) - 127);
}
#endif
#if defined(DATA_A_IQ4_KT)
// one trellis step; 4 weights per index. See QuantizerIQKT::set_values
float iq4_kt_next(inout uint x)
{
x *= 0xCBAC1FED;
const uint s = x & 0x3F3F3F3F;
return float(int((s & 0xFF) + ((s >> 8) & 0xFF) + ((s >> 16) & 0xFF) + (s >> 24)) - 126);
}
// shb low byte: bits 1..7 signed scale, bit 0 selects the index-range half (-> offset)
float iq4_kt_scale(uint row_word, uint shb, out uint offset)
{
offset = (shb & 1) != 0 ? 4096 + 32768 : 4096;
return a_row_scale(row_word) * float(int((shb & 0xFF) >> 1) - 64);
}
// 15-bit index: 8b ql[jj], one qh[] nibble (low for jj < 32, high above), 3b from shb
uint iq4_kt_index(uint blk, uint shb, uint ib32, uint ig, uint offset)
{
const uint jj = 8 * ib32 + ig;
const uint ql = a_byte(blk + 8, jj);
const uint qh = a_byte(blk + 24, jj % 32);
return offset + (ql | ((qh << (8 - 4 * (jj / 32))) & 0xF00) | (((shb >> (8 + 3 * ig)) & 7) << 12));
}
#endif
// returns the bfloat value in the low 16b.
// See ggml_compute_fp32_to_bf16
uint32_t fp32_to_bf16(float f)

View File

@ -64,9 +64,18 @@ const std::vector<std::string> type_names = {
"iq3_s",
"iq4_xs",
"iq4_nl",
"iq4_ks",
"iq4_kt",
"bf16",
};
// IQ4_KS and IQ4_KT keep a per-row f32 scale ahead of the row's blocks, so they are read
// through a uint32_t alias of the A buffer and need dedicated shaders. The coopmat2 path
// cannot express their addressing and skips them.
static bool is_row_meta_quant(const std::string & tname) {
return tname == "iq4_ks" || tname == "iq4_kt";
}
namespace {
void execute_command(const std::string& command, std::string& stdout_str, std::string& stderr_str) {
#ifdef _WIN32
@ -362,13 +371,19 @@ void matmul_shaders(bool fp16, bool matmul_id, bool coopmat, bool coopmat2, bool
std::string load_vec_quant = "2";
if ((tname == "q4_0") || (tname == "q4_1") || (tname == "iq1_s") || (tname == "iq1_m") || (tname == "iq2_xxs") || (tname == "iq2_xs") || (tname == "iq2_s"))
load_vec_quant = "8";
else if ((tname == "q5_0") || (tname == "q5_1") || (tname == "q8_0") || (tname == "iq3_xxs") || (tname == "iq3_s") || (tname == "iq4_nl"))
// iq4_kt: 4 so that one load index is exactly one trellis group of 4 weights
else if ((tname == "q5_0") || (tname == "q5_1") || (tname == "q8_0") || (tname == "iq3_xxs") || (tname == "iq3_s") || (tname == "iq4_nl") || (tname == "iq4_kt"))
load_vec_quant = "4";
if (tname == "bf16") {
continue;
}
// the coopmat2 tensor layout cannot describe a per-row scale ahead of the blocks
if (coopmat2 && is_row_meta_quant(tname)) {
continue;
}
std::string data_a_key = "DATA_A_" + to_uppercase(tname);
// For unaligned, load one at a time for f32/f16, or two at a time for quants
std::string load_vec_a_unaligned = (coopmat2 || tname == "f32" || tname == "f16" || tname == "bf16") ? "1" : load_vec_quant;
@ -431,6 +446,7 @@ void process_shaders() {
continue;
}
if (tname == "bf16") continue;
if (is_row_meta_quant(tname)) continue;
#if defined(GGML_VULKAN_COOPMAT2_GLSLC_SUPPORT)
if (tname == "f16") {
@ -466,7 +482,7 @@ void process_shaders() {
for (const auto& tname : type_names) {
// mul mat vec
std::string data_a_key = "DATA_A_" + to_uppercase(tname);
std::string shader = (string_ends_with(tname, "_k") || string_starts_with(tname, "iq1_") || string_starts_with(tname, "iq2_") || string_starts_with(tname, "iq3_")) ? "mul_mat_vec_" + tname + ".comp" : "mul_mat_vec.comp";
std::string shader = (string_ends_with(tname, "_k") || string_starts_with(tname, "iq1_") || string_starts_with(tname, "iq2_") || string_starts_with(tname, "iq3_") || is_row_meta_quant(tname)) ? "mul_mat_vec_" + tname + ".comp" : "mul_mat_vec.comp";
string_to_spv("mul_mat_vec_" + tname + "_f32_f32", shader, merge_maps(base_dict, {{data_a_key, "1"}, {"B_TYPE", "float"}, {"B_TYPE_VEC2", "vec2"}, {"B_TYPE_VEC4", "vec4"}, {"D_TYPE", "float"}}));
string_to_spv("mul_mat_vec_" + tname + "_f16_f32", shader, merge_maps(base_dict, {{data_a_key, "1"}, {"B_TYPE", "float16_t"}, {"B_TYPE_VEC2", "f16vec2"}, {"B_TYPE_VEC4", "f16vec4"}, {"D_TYPE", "float"}}));
@ -479,7 +495,8 @@ void process_shaders() {
}
if (!string_ends_with(tname, "_k")) {
shader = (tname == "f32" || tname == "f16" || tname == "bf16") ? "get_rows.comp" : "get_rows_quant.comp";
shader = (tname == "f32" || tname == "f16" || tname == "bf16") ? "get_rows.comp" :
is_row_meta_quant(tname) ? "get_rows_" + tname + ".comp" : "get_rows_quant.comp";
if (tname == "f16") {
string_to_spv("get_rows_" + tname, shader, merge_maps(base_dict, {{data_a_key, "1"}, {"B_TYPE", "int"}, {"D_TYPE", "float16_t"}, {"OPTIMIZATION_ERROR_WORKAROUND", "1"}}));

View File

@ -202,6 +202,9 @@ llama_build_and_test(
)
llama_build_and_test(test-regex-partial.cpp)
llama_build_and_test(test-iq4-ks-kt-decode.cpp)
target_include_directories(test-iq4-ks-kt-decode PRIVATE ${PROJECT_SOURCE_DIR}/ggml/src)
# llama_target_and_test(test-opt.cpp) # SLOW
llama_target_and_test(test-model-load-cancel.cpp LABEL "model")

View File

@ -0,0 +1,443 @@
// Checks the IQ4_KS and IQ4_KT decode arithmetic against ggml's to_float.
//
// This is a CPU test and contains no Vulkan: it re-implements, in C++, the indexing that
// each of the four Vulkan shader families uses for these two types, and diffs the result
// against the CPU reference. It therefore validates the format transcription, not the
// compiled shaders -- it cannot catch a driver or codegen fault, and it has to be kept in
// step with ggml/src/vulkan-shaders/types.comp by hand. The shaders themselves are covered
// on device by test-backend-ops and by GGML_VULKAN_CHECK_RESULTS.
//
// dequant_iq4_k*.comp one thread per 32-weight subblock, global block index
// get_rows_iq4_k*.comp two weights per invocation, row index taken from the shape
// mul_mm.comp A-load LOAD_VEC_A weights per load index
// mul_mat_vec_iq4_k*.comp one thread per subblock, rows walked by the row stride
//
// Buffers are synthesised directly, so every bit pattern is reachable and no quantizer is
// needed, and they are laid out with ggml_row_size() so that a wrong stride in the mirrored
// a_row_words() shows up as a mismatch rather than shifting the reference too.
#include "ggml.h"
#define GGML_COMMON_DECL_C
#define GGML_COMMON_IMPL_C
#include "ggml-common.h"
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
static const uint32_t QK_K_ = 256;
static const uint32_t KS_BLOCK_W = 34; // 8 scale bytes + 128 quant bytes
static const uint32_t KT_BLOCK_W = 32; // 8 shb words + 64 ql bytes + 32 qh bytes
static const uint32_t ROW_META_W = 1; // the row scale
static int g_checks = 0;
static int g_failures = 0;
static int g_reported = 0;
static float bits_to_float(uint32_t u) {
float f;
memcpy(&f, &u, sizeof(f));
return f;
}
static uint32_t a_row_words(uint32_t ncols, uint32_t block_words) {
return ROW_META_W + (ncols / QK_K_) * block_words;
}
static uint32_t rd_byte(const uint32_t * w, uint32_t base_word, uint32_t byte_idx) {
return (w[base_word + byte_idx / 4] >> (8 * (byte_idx % 4))) & 0xFF;
}
// ---------------------------------------------------------------------------------------
// IQ4_KS, once per shader family
static float ks_scale(const uint32_t * w, uint32_t row_word, uint32_t blk, uint32_t ib32, uint32_t & vo) {
const uint32_t sc = rd_byte(w, blk, ib32);
vo = (sc & 1) << 4;
return bits_to_float(w[row_word]) * (float)((int)(sc & 254) - 127);
}
// the 32 weights of one subblock; shared by the dequant and mul_mat_vec mirrors, which
// differ in addressing, not in how a subblock is decoded
static void ks_emit_subblock(const uint32_t * w, uint32_t blk, uint32_t ib32, float dl, uint32_t vo, float * out) {
for (uint32_t l = 0; l < 4; ++l) {
const uint32_t qs = w[blk + 2 + 4 * ib32 + l];
for (uint32_t k = 0; k < 4; ++k) {
const uint32_t q = (qs >> (8 * k)) & 0xFF;
out[4 * l + k ] = dl * (float)iq4k_values[vo + (q & 0xF)];
out[4 * l + k + 16] = dl * (float)iq4k_values[vo + (q >> 4)];
}
}
}
static void ks_dequant_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
const uint32_t blocks_per_row = ncols / QK_K_;
for (uint32_t ib = 0; ib < blocks_per_row * nrows; ++ib) {
const uint32_t row_word = (ib / blocks_per_row) * a_row_words(ncols, KS_BLOCK_W);
const uint32_t blk = row_word + ROW_META_W + (ib % blocks_per_row) * KS_BLOCK_W;
for (uint32_t ib32 = 0; ib32 < 8; ++ib32) {
uint32_t vo;
const float dl = ks_scale(w, row_word, blk, ib32, vo);
ks_emit_subblock(w, blk, ib32, dl, vo, out + 256 * ib + 32 * ib32);
}
}
}
static float ks_get_rows_elem(const uint32_t * w, uint32_t row_word, uint32_t i00) {
const uint32_t blk = row_word + ROW_META_W + (i00 / QK_K_) * KS_BLOCK_W;
const uint32_t pos = i00 % QK_K_;
const uint32_t ib32 = pos / 32;
const uint32_t j = pos % 32;
uint32_t vo;
const float dl = ks_scale(w, row_word, blk, ib32, vo);
const uint32_t qb = 16 * ib32 + (j & 15);
const uint32_t q = rd_byte(w, blk + 2, qb);
return dl * (float)iq4k_values[vo + (j < 16 ? (q & 0xF) : (q >> 4))];
}
static void ks_get_rows_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
for (uint32_t r = 0; r < nrows; ++r) {
const uint32_t row_word = r * a_row_words(ncols, KS_BLOCK_W);
for (uint32_t i00 = 0; i00 < ncols; i00 += 2) {
out[r * ncols + i00 ] = ks_get_rows_elem(w, row_word, i00);
out[r * ncols + i00 + 1] = ks_get_rows_elem(w, row_word, i00 + 1);
}
}
}
static void ks_mul_mm_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
const uint32_t load_vec_a = 2;
for (uint32_t idx = 0; idx < nrows * ncols / load_vec_a; ++idx) {
const uint32_t elem = idx * load_vec_a;
const uint32_t row_word = (elem / ncols) * a_row_words(ncols, KS_BLOCK_W);
const uint32_t acol = elem % ncols;
const uint32_t blk = row_word + ROW_META_W + (acol / QK_K_) * KS_BLOCK_W;
const uint32_t ib32 = (acol % QK_K_) / 32;
const uint32_t j = acol % 32;
uint32_t vo;
const float dl = ks_scale(w, row_word, blk, ib32, vo);
const uint32_t qb = 16 * ib32 + (j & 15);
const uint32_t qw = w[blk + 2 + qb / 4] >> (8 * (qb % 4));
const uint32_t qshift = j < 16 ? 0 : 4;
out[elem ] = dl * (float)iq4k_values[vo + ((qw >> qshift) & 0xF)];
out[elem + 1] = dl * (float)iq4k_values[vo + ((qw >> (8 + qshift)) & 0xF)];
}
}
static void ks_mul_mat_vec_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
const uint32_t words_per_row = a_row_words(ncols, KS_BLOCK_W);
for (uint32_t i = 0; i < ncols / QK_K_; ++i) {
for (uint32_t ib32 = 0; ib32 < 8; ++ib32) {
uint32_t row_word = 0;
for (uint32_t n = 0; n < nrows; ++n) {
const uint32_t blk = row_word + ROW_META_W + i * KS_BLOCK_W;
uint32_t vo;
const float dl = ks_scale(w, row_word, blk, ib32, vo);
ks_emit_subblock(w, blk, ib32, dl, vo, out + n * ncols + i * QK_K_ + 32 * ib32);
row_word += words_per_row;
}
}
}
}
// ---------------------------------------------------------------------------------------
// IQ4_KT, once per shader family
// the single trellis step, as written in types.comp
static float kt_next(uint32_t & x) {
x *= 0xCBAC1FEDu;
const uint32_t s = x & 0x3F3F3F3Fu;
return (float)((int32_t)((s & 0xFF) + ((s >> 8) & 0xFF) + ((s >> 16) & 0xFF) + (s >> 24)) - 126);
}
// index assembly for one group of four weights, shared by the four mirrors below
static uint32_t kt_group_x(const uint32_t * w, uint32_t blk, uint32_t ib32, uint32_t ig, float d, float & sl) {
const uint32_t shb = w[blk + ib32];
sl = d * (float)((int)((shb & 0xFF) >> 1) - 64);
const uint32_t offset = (shb & 1) != 0 ? 4096 + 32768 : 4096;
const uint32_t jj = 8 * ib32 + ig;
const uint32_t ql = rd_byte(w, blk + 8, jj);
const uint32_t jh = jj % 32;
const uint32_t qh = rd_byte(w, blk + 24, jh);
return offset + (ql | ((qh << (8 - 4 * (jj / 32))) & 0xF00) | (((shb >> (8 + 3 * ig)) & 7) << 12));
}
// the 32 weights of one subblock; shared by the dequant and mul_mat_vec mirrors, which
// differ in addressing, not in how a subblock is decoded
static void kt_emit_subblock(const uint32_t * w, uint32_t blk, uint32_t ib32, float d, float * out) {
for (uint32_t ig = 0; ig < 8; ++ig) {
float sl;
uint32_t x = kt_group_x(w, blk, ib32, ig, d, sl);
for (uint32_t k = 0; k < 4; ++k) {
out[4 * ig + k] = sl * kt_next(x);
}
}
}
static void kt_dequant_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
const uint32_t blocks_per_row = ncols / QK_K_;
for (uint32_t ib = 0; ib < blocks_per_row * nrows; ++ib) {
const uint32_t row_word = (ib / blocks_per_row) * a_row_words(ncols, KT_BLOCK_W);
const uint32_t blk = row_word + ROW_META_W + (ib % blocks_per_row) * KT_BLOCK_W;
const float d = bits_to_float(w[row_word]);
for (uint32_t ib32 = 0; ib32 < 8; ++ib32) {
kt_emit_subblock(w, blk, ib32, d, out + 256 * ib + 32 * ib32);
}
}
}
static void kt_get_rows_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
for (uint32_t r = 0; r < nrows; ++r) {
const uint32_t row_word = r * a_row_words(ncols, KT_BLOCK_W);
const float d = bits_to_float(w[row_word]);
for (uint32_t i00 = 0; i00 < ncols; i00 += 2) {
const uint32_t blk = row_word + ROW_META_W + (i00 / QK_K_) * KT_BLOCK_W;
const uint32_t pos = i00 % QK_K_;
float sl;
uint32_t x = kt_group_x(w, blk, pos / 32, (pos % 32) / 4, d, sl);
const uint32_t k0 = pos % 4;
float v0 = 0.0f;
float v1 = 0.0f;
for (uint32_t l = 0; l < 4; ++l) {
const float v = sl * kt_next(x);
if (l == k0 ) v0 = v;
if (l == k0 + 1) v1 = v;
}
out[r * ncols + i00 ] = v0;
out[r * ncols + i00 + 1] = v1;
}
}
}
static void kt_mul_mm_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
const uint32_t load_vec_a = 4;
for (uint32_t idx = 0; idx < nrows * ncols / load_vec_a; ++idx) {
const uint32_t elem = idx * load_vec_a;
const uint32_t row_word = (elem / ncols) * a_row_words(ncols, KT_BLOCK_W);
const uint32_t acol = elem % ncols;
const uint32_t blk = row_word + ROW_META_W + (acol / QK_K_) * KT_BLOCK_W;
float sl;
uint32_t x = kt_group_x(w, blk, (acol % QK_K_) / 32, (acol % 32) / 4, bits_to_float(w[row_word]), sl);
for (uint32_t k = 0; k < 4; ++k) {
out[elem + k] = sl * kt_next(x);
}
}
}
static void kt_mul_mat_vec_shader(const uint32_t * w, uint32_t ncols, uint32_t nrows, float * out) {
const uint32_t words_per_row = a_row_words(ncols, KT_BLOCK_W);
for (uint32_t i = 0; i < ncols / QK_K_; ++i) {
for (uint32_t ib32 = 0; ib32 < 8; ++ib32) {
uint32_t row_word = 0;
for (uint32_t n = 0; n < nrows; ++n) {
const uint32_t blk = row_word + ROW_META_W + i * KT_BLOCK_W;
kt_emit_subblock(w, blk, ib32, bits_to_float(w[row_word]),
out + n * ncols + i * QK_K_ + 32 * ib32);
row_word += words_per_row;
}
}
}
}
// ---------------------------------------------------------------------------------------
// Copied from QuantizerIQKT<32, 4, 15, false, true>::set_values in ggml/src/iqk/iqk_quantize.cpp.
// It reads the accumulator through int8_t, so it carries the same little-endian assumption
// the original has. Used only to cross-check kt_next() over the whole index range.
static void ref_set_values(uint32_t i, float * result, float scale, int offset) {
uint32_t x = i + offset;
const uint32_t ka = 0xCBAC1FED;
uint32_t s;
const int8_t * i8 = (const int8_t *)&s;
for (int k = 0; k < 4; ++k) {
x = ka*x;
s = x & 0x3f3f3f3f;
result[k] = scale*(i8[0] + i8[1] + i8[2] + i8[3] - 126.f);
}
}
static void check(const char * what, const std::vector<float> & got, const std::vector<float> & ref) {
g_checks++;
size_t bad = 0;
size_t first = 0;
for (size_t i = 0; i < ref.size(); ++i) {
if (got[i] != ref[i]) {
if (bad == 0) {
first = i;
}
bad++;
}
}
if (bad == 0) {
return;
}
g_failures++;
if (g_reported < 12) {
g_reported++;
printf(" FAIL %-42s %zu/%zu differ, first at %zu: got %.9g want %.9g\n",
what, bad, ref.size(), first, got[first], ref[first]);
}
}
// Fills a quantized buffer. payload < 0 gives a reproducible pseudo-random pattern,
// otherwise every payload byte is set to that value. Every byte pattern is a valid encoding
// for both types, so this reaches states a quantizer would never emit. The first eight
// bytes of every block are then overwritten with the given scale bytes.
static void fill_pattern(std::vector<uint32_t> & w, uint32_t ncols, uint32_t nrows, uint32_t block_words,
uint32_t words_per_row, uint32_t seed, int payload, const uint8_t * scales) {
const float row_scales[4] = { 0.0125f, -0.5f, 1.0f, 3.75e-3f };
uint32_t rng = seed * 2654435761u + 1u;
for (uint32_t r = 0; r < nrows; ++r) {
uint32_t bits;
const float d = row_scales[r % 4];
memcpy(&bits, &d, sizeof(bits));
w[r * words_per_row] = bits;
for (uint32_t k = ROW_META_W; k < words_per_row; ++k) {
if (payload >= 0) {
w[r * words_per_row + k] = 0x01010101u * (uint32_t)payload;
} else {
rng ^= rng << 13;
rng ^= rng >> 17;
rng ^= rng << 5;
w[r * words_per_row + k] = rng;
}
}
for (uint32_t b = 0; b < ncols / QK_K_; ++b) {
const uint32_t blk = r * words_per_row + ROW_META_W + b * block_words;
for (uint32_t k = 0; k < 8; ++k) {
const uint32_t word = blk + k / 4;
const uint32_t shift = 8 * (k % 4);
w[word] = (w[word] & ~(0xFFu << shift)) | ((uint32_t)scales[k] << shift);
}
}
}
}
static void run_shape(ggml_type type, uint32_t ncols, uint32_t nrows, uint32_t seed,
int payload, const uint8_t * scales, const char * tag) {
const uint32_t block_words = type == GGML_TYPE_IQ4_KS ? KS_BLOCK_W : KT_BLOCK_W;
// laid out with ggml's own row size, so that a wrong stride in a_row_words() shows up as
// a mismatch rather than shifting the reference along with the mirrors
const uint32_t words_per_row = (uint32_t)ggml_row_size(type, ncols) / sizeof(uint32_t);
std::vector<uint32_t> w(words_per_row * nrows);
fill_pattern(w, ncols, nrows, block_words, words_per_row, seed, payload, scales);
// ggml's own dequantizer, one row at a time
std::vector<float> ref((size_t)ncols * nrows);
ggml_type_traits_t tt = ggml_internal_get_type_traits(type);
for (uint32_t r = 0; r < nrows; ++r) {
tt.to_float(&w[r * words_per_row], &ref[(size_t)r * ncols], ncols);
}
struct mirror {
const char * family;
void (*fn)(const uint32_t *, uint32_t, uint32_t, float *);
};
const mirror ks[4] = {
{ "dequant", ks_dequant_shader },
{ "get_rows", ks_get_rows_shader },
{ "mul_mm", ks_mul_mm_shader },
{ "mul_mat_vec", ks_mul_mat_vec_shader },
};
const mirror kt[4] = {
{ "dequant", kt_dequant_shader },
{ "get_rows", kt_get_rows_shader },
{ "mul_mm", kt_mul_mm_shader },
{ "mul_mat_vec", kt_mul_mat_vec_shader },
};
const mirror * mirrors = type == GGML_TYPE_IQ4_KS ? ks : kt;
std::vector<float> got((size_t)ncols * nrows);
for (int m = 0; m < 4; ++m) {
std::fill(got.begin(), got.end(), 0.0f);
mirrors[m].fn(w.data(), ncols, nrows, got.data());
char name[128];
snprintf(name, sizeof(name), "%s %-11s %ux%u %s",
ggml_type_name(type), mirrors[m].family, nrows, ncols, tag);
check(name, got, ref);
}
}
static void run_trellis_sweep(void) {
const int offsets[2] = { 4096, 4096 + 32768 };
const float scales[3] = { 1.0f, -0.25f, 7.5f };
size_t bad = 0;
for (uint32_t idx = 0; idx < 32768; ++idx) {
for (int o = 0; o < 2; ++o) {
for (int s = 0; s < 3; ++s) {
float ref[4];
ref_set_values(idx, ref, scales[s], offsets[o]);
uint32_t x = (uint32_t)offsets[o] + idx;
for (int k = 0; k < 4; ++k) {
if (scales[s] * kt_next(x) != ref[k]) {
bad++;
}
}
}
}
}
g_checks++;
if (bad != 0) {
g_failures++;
printf(" FAIL iq4_kt trellis sweep: %zu of %d values differ\n", bad, 32768 * 2 * 3 * 4);
}
}
int main(void) {
// scale bytes chosen to hit both codebook halves and both ends of the scale field:
// IQ4_KS uses (b & 254) - 127, which reaches -127 and +127; IQ4_KT uses (b >> 1) - 64,
// which reaches -64 and +63. Bit 0 selects the codebook half / the index offset.
const uint8_t extremes[8] = { 0x00, 0x01, 0xFE, 0xFF, 0x80, 0x81, 0x7E, 0x7F };
const uint8_t mid[8] = { 0x40, 0x41, 0x7A, 0x0B, 0xC0, 0xC1, 0x2A, 0x95 };
// several row and block counts, so the row stride is exercised and not just the
// addressing inside a block
const uint32_t shapes[5][2] = { {256, 1}, {256, 3}, {1024, 5}, {1024, 4}, {4096, 2} };
printf("iq4_kt trellis: all 32768 indices x 2 offsets x 3 scales\n");
run_trellis_sweep();
printf("multi-row buffers against ggml to_float\n");
for (int s = 0; s < 5; ++s) {
run_shape(GGML_TYPE_IQ4_KS, shapes[s][0], shapes[s][1], 1u + s, -1, extremes, "rand/extreme-scales");
run_shape(GGML_TYPE_IQ4_KT, shapes[s][0], shapes[s][1], 1u + s, -1, extremes, "rand/extreme-scales");
run_shape(GGML_TYPE_IQ4_KS, shapes[s][0], shapes[s][1], 9u + s, -1, mid, "rand/mid-scales");
run_shape(GGML_TYPE_IQ4_KT, shapes[s][0], shapes[s][1], 9u + s, -1, mid, "rand/mid-scales");
}
printf("saturated payloads\n");
for (int p = 0; p <= 0xFF; p += 0xFF) {
run_shape(GGML_TYPE_IQ4_KS, 512, 2, 0, p, extremes, p == 0 ? "all-zero" : "all-ones");
run_shape(GGML_TYPE_IQ4_KT, 512, 2, 0, p, extremes, p == 0 ? "all-zero" : "all-ones");
run_shape(GGML_TYPE_IQ4_KS, 512, 2, 0, p, mid, p == 0 ? "all-zero" : "all-ones");
run_shape(GGML_TYPE_IQ4_KT, 512, 2, 0, p, mid, p == 0 ? "all-zero" : "all-ones");
}
printf("%d checks, %d failed\n", g_checks, g_failures);
return g_failures == 0 ? 0 : 1;
}