Skip to content

Step 25: Row-interleaved weight repacking — contiguous multi-row block layout #52

Closed

Description

@kkokosa

Roadmap Step

Step 25 — Row-interleaved weight repacking

Dependencies

  • Depends on Step 14 (Q4_K_M dequantization — quantized weight format support)

Scope

At model load time, repack quantized weight matrices from row-major into row-interleaved format so SIMD lanes processing multiple output rows access contiguous memory instead of striding across rows.

  • Row-interleaved format — For Q8_0_R8: take 8 consecutive rows' blocks and pack them contiguously. Block b of row r in the original layout at offset r * rowBytes + b * blockBytes maps to interleaved offset (b * 8 + r%8) * blockBytes + (r/8) * 8 * rowBytes. SIMD loads 8 blocks from 8 rows in one contiguous read.
  • Format variants — Q8_0_R4 (4-row interleave, AVX2), Q8_0_R8 (8-row, AVX-512). Similarly for Q4_K, Q5_K, Q6_K, Q5_0 formats.
  • Repacking at load time — After weight tensor metadata is resolved from GGUF, allocate a new NativeMemory.AlignedAlloc(64) buffer and repack. Original mmap'd data still available for fallback. Repacking cost amortized over inference.
  • TransformerWeights integration — Add repacked weight pointer field alongside original weight pointer. Kernels check for repacked pointer first.
  • M dimension alignment — Output dimension (M) must be padded to interleave factor (4 or 8). Handle tail rows with original non-interleaved kernel.
  • Memory overhead — Repacked buffer is same size as original weights (just reordered). Total model memory ~2× during repacking, then original mmap can be released if not shared.

Key Files

  • src/DotLLM.Cpu/Kernels/WeightRepacking.cs (new — repacking logic per quant format)
  • src/DotLLM.Models/Architectures/TransformerWeights.cs (add repacked weight pointers, trigger repacking after load)
  • src/DotLLM.Models/Architectures/TransformerModel.cs (use repacked weights in Gemm/Gemv dispatch)
  • src/DotLLM.Cpu/Kernels/MatMul.cs (add repacked-format GEMV/GEMM entry points)

Tests

  • tests/DotLLM.Tests.Unit/Cpu/Kernels/WeightRepackingTests.cs
  • Test: repack then unpack roundtrip produces identical bytes
  • Test: Q8_0_R4 GEMV produces identical output to standard Q8_0 GEMV
  • Test: Q8_0_R8 GEMV produces identical output to standard Q8_0 GEMV
  • Test: tail rows (M not divisible by interleave factor) handled correctly
  • Test: repacked buffer alignment is 64-byte

References

  • docs/QUANTIZATION.md — block layouts per quant type
  • llamafile/tinyBLAS — row-interleaved weight format for SIMD matmul
  • ik_llama.cpp — repacked weight formats for outer-product kernels

Acceptance Criteria

  • WeightRepacking class implements repack/unpack for Q8_0, Q5_0, Q4_K, Q5_K, Q6_K
  • Repacked weights allocated via NativeMemory.AlignedAlloc with 64-byte alignment
  • TransformerWeights stores repacked pointers alongside originals
  • Repacking triggered at model load time (one-time cost)
  • Tail row handling for M not divisible by interleave factor
  • GEMV/GEMM with repacked weights produces bit-exact output vs. original layout
  • Unit tests for roundtrip correctness and numerical equivalence
  • Memory cleanup: repacked buffers freed on model dispose

Activity

  1. jamesburton commented on Jun 16, 2026

    @jamesburton

    Q4_K MMVQ decode path — implemented, validated, benchmarked

    Done (branch issue/52-kquant-mmvq, commits 00b41ab shader + b427abd kernel/wiring/test):

    • matmul_q4_k_mmvq.comp — per-32-sub-block dp4a (dotPacked4x8AccSatEXT) over the raw 4-bit nibbles × Q8_1 activations, with the asymmetric min term − dmin·mn·s folded in via the Q8_1 block-sum.
    • MatMulQ4KMmvqKernel + RecordMatmul wiring (Q4_K + seqLen==1 → quantize Q8_1 + integer-dot GEMV; gated on HasIntegerDotProduct, DOTLLM_VULKAN_DISABLE_MMVQ opt-out, F32-in fallback). Reuses the Q8_0 MMVQ activation scratch.
    • VulkanMatMulQ4KMmvqKernelTests — argmax + tolerance vs the Q4_K-byte-identical CPU F32-in oracle, 7 shapes.

    Validation: Q4_K MMVQ 7/7, Q8_0 MMVQ + Q4_K GEMV regression 14/14, real-model Q4_K forward 3/3 (now routing through MMVQ).

    Benchmark — Strix Halo (Radeon 8060S, gfx1151), Devstral-Small-2-24B Q4_K_M (dense), Release, clean A/B (same model/hardware):

    Config decode ms/step tok/s
    MMVQ off (F32-in Q4_K GEMV) 453.84 2.20
    MMVQ on (Q4_K dp4a) 349.86 2.86

    +30% decode throughput (1.30×). Prefill unchanged (~1880 ms) — decode-only, as designed (Q4_K prefill still uses the F32 GEMM). The win is the integer-dot path removing the per-element float dequant+madd.

    Follow-ups: Q4_K MMQ prefill (the seqLen>1 analogue); extend MMVQ to Q5_K/Q6_K/Q2_K/Q3_K (same nibble/sub-block structure).

  2. jamesburton commented on Jun 16, 2026

    @jamesburton

    Fresh 7B/8B-class A/B (this build)

    Re-ran on a freshly-downloaded dense model to anchor against the prior ~8.5 tok/s Mistral-7B baseline. Strix Halo (Radeon 8060S, gfx1151), Meta-Llama-3.1-8B-Instruct Q4_K_M (dense), Release, 64 decode steps:

    Config decode ms/step tok/s
    MMVQ off (F32-in Q4_K GEMV) 127.64 7.83
    MMVQ on (Q4_K dp4a) 100.92 9.91

    +26.5% decode (1.27×); non-overlapping spreads (on 94.8–104.3 ms, off 119.8–135.0 ms). The MMVQ-off baseline (7.83) matches the prior pre-MMVQ Mistral-7B figure (~8.5); MMVQ-on clears it at 9.91 on a larger 8B.

    Win is consistent across model sizes: +26.5% @ 8B, +30% @ 24B — the integer-dot path generalizes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestphase-3Phase 3: CPU performance

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions