# IQK + TurboQuant Merge: Complete Coding Handoff ## Objective Merge ik_llama's IQK CPU GEMM optimization layer into the AtomicBot-ai/atomic-llama-cpp-turboquant fork to get ~5x prefill speedup on Ling 3.0 Flash (bailingmoe3) while retaining TurboQuant's bugfixes (tool calls, token handling, KV cache compression). ## Repository URLs - **TurboQuant (target)**: https://github.com/AtomicBot-ai/atomic-llama-cpp-turboquant - **ik_llama (source)**: https://github.com/ikawrakow/ik_llama.cpp ## Expected Outcome - Prefill: 60 t/s → ~300 t/s (5x improvement) - Decode: unchanged (~9-11 t/s on DDR4) - All TurboQuant features intact (KV cache compression, bailingmoe3 fixes, MTP/NextN) ## Architecture Comparison | Aspect | TurboQuant | ik_llama | |---|---|---| | Code structure | Modular (separate backend libs) | Monolithic (single ggml.c) | | CPU backend file | `ggml/src/ggml-cpu/ggml-cpu.c` (4K lines) | `ggml/src/ggml.c` (32K lines) | | Forward dispatch | `void ggml_compute_forward(params, tensor)` | `int ggml_compute_forward(params, tensor, cgraph, node_n)` | | Params struct | `params->threadpool` (ggml-cpu-impl.h) | `params->shared` (inline in ggml.c) | | Type traits | `type_traits_cpu[]` | `type_traits[]` | | IQK present? | No | Yes | | KV cache compression | Yes (TURBO types) | No | | bailingmoe3 | Yes (bugfixed) | May have issues | ## Why They Don't Conflict The two forks modify different layers: - **TurboQuant**: Higher-level (KV cache quantization types, architecture-specific fixes, MTP speculative decoding) - **IQK**: Lower-level (CPU GEMM primitives in `ggml/src/iqk/`) IQK accelerates `ggml_mul_mat` — the primitive every model calls. TurboQuant's changes are above that layer. The KV cache compression (TURBO types) and Hadamard fast path are in different code regions. ## Step 1: Copy IQK Source Files Copy the entire `ggml/src/iqk/` directory from ik_llama into TurboQuant: ```bash # From the TurboQuant repo root git remote add ik_llama https://github.com/ikawrakow/ik_llama.cpp git fetch ik_llama git checkout ik_llama/master -- ggml/src/iqk/ ``` This brings in 36 files (~2.1 MB total): **Core files (always compiled):** - `iqk/iqk_config.h` (1.7 KB) — build config - `iqk/iqk_common.h` (42 KB) — shared utilities - `iqk/iqk_utils.h` (12 KB) — helper macros - `iqk/iqk_quantize.cpp` (439 KB) — weight quantization - `iqk/iqk_quantize.h` (34 KB) — quantization API - `iqk/iqk_cpu_ops.cpp` (43 KB) — SIMD-accelerated CPU ops (top-k, argsort, etc.) - `iqk/iqk_cpu_ops.h` (2 KB) — ops API **GEMM files (compiled when GGML_IQK_MUL_MAT=ON):** - `iqk/iqk_mul_mat.cpp` (99 KB) — main dispatch + node fusion - `iqk/iqk_mul_mat.h` (4.5 KB) — matmul API - `iqk/iqk_kda.cpp` (17 KB) — KDA (Gated Delta Net) fused path - `iqk/iqk_flash_attn.cpp` (32 KB) — CPU flash attention - `iqk/iqk_flash_impl.h` (2 KB) — FA implementation - `iqk/iqk_gemm_kquants.cpp` (250 KB) — Q2_K through Q6_K kernels - `iqk/iqk_gemm_kquants.h` (0.4 KB) - `iqk/iqk_gemm_ktquants.cpp` (126 KB) — Trellis quant kernels - `iqk/iqk_gemm_ktquants.h` (0.3 KB) - `iqk/iqk_gemm_iquants.cpp` (202 KB) — IQ2_XXS through IQ4_XS kernels - `iqk/iqk_gemm_iquants.h` (0.3 KB) - `iqk/iqk_gemm_iqk_quants.cpp` (288 KB) — IQ2_K through IQ6_K kernels - `iqk/iqk_gemm_iqk_quants.h` (0.3 KB) - `iqk/iqk_gemm_legacy_quants.cpp` (172 KB) — Q4_0, Q4_1, Q5_0, Q5_1, Q8_0 - `iqk/iqk_gemm_legacy_quants.h` (0.5 KB) - `iqk/iqk_gemm_floats.cpp` (48 KB) — FP16/FP32/BF16 kernels - `iqk/iqk_gemm_floats.h` (0.3 KB) - `iqk/iqk_gemm_1bit.cpp` (188 KB) — Bitnet kernels - `iqk/iqk_gemm_1bit.h` (0.3 KB) **Flash attention template files:** - `iqk/fa/iqk_fa_templates.h` — FA template definitions - `iqk/fa/iqk_fa_64_64.cpp` through `iqk_fa_576_512.cpp` (9 files, ~1.6 KB each) — size-specialized FA kernels ## Step 2: CMake Integration ### 2a. Add cmake option In `ggml/src/CMakeLists.txt` (or create a new section), add: ```cmake option(GGML_IQK_MUL_MAT "Enable optimized IQK matrix multiplications" OFF) option(GGML_IQK_FLASH_ATTENTION "Enable IQK Flash Attention kernels" OFF) ``` ### 2b. Add IQK sources to ggml-cpu backend In `ggml/src/ggml-cpu/CMakeLists.txt`, inside the `ggml_add_cpu_backend_variant_impl()` function, before the `target_sources(${GGML_CPU_NAME} PRIVATE ${GGML_CPU_SOURCES})` line, add: ```cmake # IQK sources — unconditional (quantize + CPU ops) set(GGML_CPU_IQK_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_quantize.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_cpu_ops.cpp ) set(GGML_CPU_IQK_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_config.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_common.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_utils.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_quantize.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_cpu_ops.h ) if(GGML_IQK_MUL_MAT) list(APPEND GGML_CPU_IQK_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_mul_mat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_kda.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_flash_attn.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_floats.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_kquants.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_ktquants.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_iquants.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_iqk_quants.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_1bit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_legacy_quants.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_64_64.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_96_96.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_128_128.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_192_128.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_192_192.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_256_256.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_320_256.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_512_512.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_576_512.cpp ) list(APPEND GGML_CPU_IQK_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_mul_mat.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_flash_impl.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/fa/iqk_fa_templates.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_floats.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_kquants.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_ktquants.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_iquants.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_iqk_quants.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_1bit.h ${CMAKE_CURRENT_SOURCE_DIR}/../iqk/iqk_gemm_legacy_quants.h ) endif() target_sources(${GGML_CPU_NAME} PRIVATE ${GGML_CPU_SOURCES} ${GGML_CPU_IQK_SOURCES} ) target_sources(${GGML_CPU_NAME} PRIVATE FILE_SET HEADERS ${GGML_CPU_HEADERS} ${GGML_CPU_IQK_HEADERS} ) target_include_directories(${GGML_CPU_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../iqk ) ``` ### 2c. Add compile definitions In the same `ggml_add_cpu_backend_variant_impl()` function: ```cmake if(GGML_IQK_MUL_MAT) target_compile_definitions(${GGML_CPU_NAME} PRIVATE GGML_USE_IQK_MULMAT) endif() if(GGML_IQK_FLASH_ATTENTION) target_compile_definitions(${GGML_CPU_NAME} PRIVATE GGML_IQK_FLASH_ATTENTION) endif() ``` ### 2d. Top-level CMakeLists.txt In the root `CMakeLists.txt`, add the options and pass them through: ```cmake option(GGML_IQK_MUL_MAT "Enable optimized IQK matrix multiplications" OFF) option(GGML_IQK_FLASH_ATTENTION "Enable IQK Flash Attention kernels" OFF) ``` And ensure they're passed to the ggml subdirectory via `add_subdirectory(ggml)` or `set()`. ## Step 3: Adapt the Forward Dispatch (THE HARD PART) This is where the structural mismatch matters. In ik_llama, IQK hooks into `ggml_compute_forward_mul_mat()` in `ggml.c`. In TurboQuant, that function lives in `ggml-cpu/ggml-cpu.c` with a different signature. ### 3a. Locate the dispatch function In TurboQuant's `ggml/src/ggml-cpu/ggml-cpu.c`, find `ggml_compute_forward_mul_mat()`. This is where MUL_MAT operations are dispatched. ### 3b. Port the IQK hooks From ik_llama's `ggml.c` lines 18016-18111, port these three code blocks into TurboQuant's `ggml_compute_forward_mul_mat()`: **Block 1: Early exit fast path (ik_llama lines 18016-18024)** ```c #ifdef GGML_USE_IQK_MULMAT // When src1 is already in vec_dot_type and dst is F32, use IQK directly if (src1->type == vec_dot_type && dst->type == GGML_TYPE_F32) { iqk_mul_mat_4d(ne01, ne11, ne00, ne02, ne03, ne12, ne13, src0->data, src1->data, dst->data, src0->type, vec_dot_type, params->ith, params->nth); return; } #endif ``` **Block 2: Custom quantization (ik_llama lines 18039-18047)** ```c #ifdef GGML_USE_IQK_MULMAT // Replace standard from_float with IQK's faster quantization if (src1->type != vec_dot_type) { iqk_quantize_any(src1->type, vec_dot_type, ne10, ne11, ne12, ne13, src1->data, wdata, params->ith, params->nth); } #else // Original llama.cpp quantization path ggml_quantize_mat(src1->type, vec_dot_type, ne10, ne11, ne12, ne13, src1->data, wdata, params->ith, params->nth); #endif ``` **Block 3: Main matmul + node fusion (ik_llama lines 18083-18111)** ```c #ifdef GGML_USE_IQK_MULMAT iqk_mul_mat_4d(ne01, ne11, ne00, ne02, ne03, ne12, ne13, src0->data, wdata, dst->data, vec_dot_type, GGML_TYPE_F32, params->ith, params->nth); #else // Original ggml_compute_forward_mul_mat_batched path ... #endif ``` ### 3c. Adapt params->shared to params->threadpool ik_llama's IQK uses `params->shared` for barrier and abort callbacks. TurboQuant uses `params->threadpool`. You need to: 1. Check if `iqk_mul_mat_4d()` and related functions actually use `params->shared` (they likely don't for the basic path — the shared struct is mainly for synchronization in multi-threaded dispatch) 2. If they do, create an adapter that wraps `params->threadpool` into the interface IQK expects 3. If they don't, this is a non-issue ### 3d. Adapt type_traits ik_llama modifies `type_traits[]` entries to use `GGML_TYPE_Q8_2_X4` as `vec_dot_type` for q4_0, q4_1, q5_0, q5_1, q6_0, q8_0 (ik_llama ggml.c lines 714-856). In TurboQuant, the equivalent is `type_traits_cpu[]` in `ggml-cpu/ggml-cpu.c` or `ggml-cpu/traits.cpp`. Add the same modifications: ```c #ifdef GGML_USE_IQK_MULMAT // Override vec_dot_type for legacy quants to use IQK's repacked format type_traits_cpu[GGML_TYPE_Q4_0].vec_dot_type = GGML_TYPE_Q8_2_X4; type_traits_cpu[GGML_TYPE_Q4_1].vec_dot_type = GGML_TYPE_Q8_2_X4; type_traits_cpu[GGML_TYPE_Q5_0].vec_dot_type = GGML_TYPE_Q8_2_X4; type_traits_cpu[GGML_TYPE_Q5_1].vec_dot_type = GGML_TYPE_Q8_2_X4; type_traits_cpu[GGML_TYPE_Q6_0].vec_dot_type = GGML_TYPE_Q8_2_X4; type_traits_cpu[GGML_TYPE_Q8_0].vec_dot_type = GGML_TYPE_Q8_2_X4; #endif ``` ## Step 4: Add IQK Includes In `ggml-cpu/ggml-cpu.c`, add at the top: ```c #ifdef GGML_USE_IQK_MULMAT #include "iqk/iqk_quantize.h" #include "iqk/iqk_cpu_ops.h" #include "iqk/iqk_mul_mat.h" #include "iqk/iqk_config.h" #endif ``` ## Step 5: Node Fusion (Optional, Advanced) ik_llama fuses consecutive MUL_MAT nodes sharing the same src1 (lines 18083-18111). This requires the `int ggml_compute_forward()` signature that returns an updated node index. **Option A (recommended): Skip node fusion initially.** Get the basic IQK fast path working first. The 5x prefill speedup comes primarily from the optimized GEMM kernels, not from node fusion. Node fusion adds maybe 10-20% on top. **Option B (full port):** Modify TurboQuant's forward dispatch signature to match ik_llama's. This requires: 1. Changing `void ggml_compute_forward(...)` to `int ggml_compute_forward(...)` in ggml-cpu.c 2. Updating the dispatch loop in ggml-cpu.c to track node_n 3. Updating all callers of ggml_compute_forward This is invasive and risky. Do it only after the basic path works. **Option C (hybrid):** Use TurboQuant's existing `ggml_cpu_try_fuse_ops()` mechanism (line 3162 of ggml-cpu.c) to implement IQK's MUL_MAT fusion as a separate fusion pass. This avoids changing the forward dispatch signature. ## Step 6: Build and Test ```bash cd atomic-llama-cpp-turboquant cmake -B build -DGGML_CUDA=ON -DGGML_IQK_MUL_MAT=ON -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release -j$(nproc) ``` ### Test 1: Verify IQK is active ```bash ./build/bin/llama-cli -m -p "Hello" -n 1 -lv 3 2>&1 | grep -i iqk ``` Should show IQK initialization messages. ### Test 2: CPU-only prefill benchmark ```bash ./build/bin/llama-bench -m -ngl 0 -t 16 ``` Compare pp512 before and after. Expected: 60 t/s → ~300 t/s. ### Test 3: GPU + CPU hybrid ```bash ./build/bin/llama-bench -m -ngl auto -t 16 ``` ### Test 4: Full server test with Ling 3.0 Flash ```bash ./build/bin/llama-server \ -m Ling-3.0-flash-Q4_K_S.gguf \ --jinja -ngl 99 -c 32768 \ --temp 0.6 --top-p 0.95 --top-k 20 \ --host 127.0.0.1 --port 8080 ``` Verify: - Model loads without errors (especially `blk.0.ssm_f.weight`) - Tool calls work correctly (TurboQuant's bugfix) - KV cache compression works (TurboQuant's turbo3/turbo4 types) - Prefill speed is ~300 t/s - Decode speed is ~9-11 t/s (DDR4 bandwidth limit) ## Known Risks 1. **GATED_DELTA_NET**: Both forks have this op but implementations may differ. ik_llama has `iqk_fused_delta_net()` which is called when IQK is available. Verify this path is correctly guarded by `#ifdef GGML_USE_IQK_MULMAT` and doesn't conflict with TurboQuant's implementation. 2. **SIMD detection**: IQK uses compile-time SIMD detection (`__AVX2__`, `__AVX512__`, `__aarch64__`). TurboQuant's build system may set these differently. Verify the right code paths are selected. 3. **Repacked quantization types**: IQK introduces `Q8_K_R8`, `Q8_K_R16` as first-class GGUF types. These need to be registered in TurboQuant's type system. Check if `ggml-common.h` in TurboQuant already has these or needs them added. 4. **Thread count**: IQK's tiling strategy assumes certain thread counts. The `func16` path (16-row interleaved) is only used on AVX512. On AVX2 (your Zen 3), the `funcs[]` array is used instead. Verify the dispatch selects the right path. ## File Inventory ### Files to CREATE (new): - `ggml/src/iqk/` — entire directory (36 files, ~2.1 MB) - `ggml/src/ggml-cpu/CMakeLists.txt` — modified (add IQK sources) ### Files to MODIFY: - `ggml/src/CMakeLists.txt` — add GGML_IQK_MUL_MAT option - `CMakeLists.txt` (root) — add option passthrough - `ggml/src/ggml-cpu/ggml-cpu.c` — add IQK hooks in ggml_compute_forward_mul_mat() ### Files to NOT TOUCH: - `ggml/src/ggml-turbo-quant.c` — KV cache compression (orthogonal) - `ggml/src/ggml-cpu/ggml-cpu-impl.h` — params struct (no change needed) - Any MTP/NextN files — speculative decoding (orthogonal) - Any bailingmoe3 architecture files — model-specific (orthogonal) ## Time Estimate - Step 1-2 (copy + cmake): 30 minutes - Step 3 (forward dispatch adaptation): 2-4 hours (the hard part) - Step 4-5 (includes + optional fusion): 30 minutes - Step 6 (build + test): 1-2 hours - **Total: 4-7 hours for a competent C/C++ developer** ## Verification Checklist - [ ] `cmake -DGGML_IQK_MUL_MAT=ON` configures without errors - [ ] Build completes without errors - [ ] IQK initialization message appears in verbose logs - [ ] Ling 3.0 Flash loads without `blk.0.ssm_f.weight` errors - [ ] Prefill speed improves from ~60 to ~300 t/s - [ ] Decode speed is unchanged (~9-11 t/s) - [ ] Tool calls work correctly - [ ] KV cache compression (turbo3/turbo4) works - [ ] `/no_think` parameter works - [ ] Multi-slot (-np > 1) works if previously working