Skip to content

Vulkan MoE routed-bank device-resident coverage: add Q5_0 / IQ4_NL / Q2_K and make the F32-skip decision per bank instead of per model #327

Description

@jamesburton

Summary

VulkanWeights.MoeRoutedRawDeviceQuantType can keep a routed MoE expert bank device-resident only when it is Q8_0, Q4_K, Q5_K, Q6_K, or (with coopmat) F16. Everything else falls back to a host F32 dequant, and CanSkipMoeF32HostDequant is model-global all-or-nothing: one unsupported bank forces every bank of every MoE layer through F32.

For DeepSeek-V2-Lite that is ~57 GB of host RAM, which OOMs on a 128 GB box (see #326 for the OOM triage and the exact per-file tensor census).

Two independent gaps

1. Missing quant types. Real, shipping llama.cpp GGUFs use formats we do not cover — and not by accident. DeepSeek-V2-Lite's moe_intermediate_size = 1408 is not a multiple of QK_K = 256, so llama.cpp is forced to fall back to a non-K format for every ffn_down_exps:

GGUF gate_exps up_exps down_exps
mradermacher/DeepSeek-V2-Lite-GGUF Q4_K_M Q4_K (26) Q4_K (26) Q5_0 (14), Q8_0 (12)
bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF Q2_K Q2_K (26) Q2_K (26) IQ4_NL (26)

So Q5_0 and IQ4_NL are not exotic — they are what you get whenever the MoE intermediate size is not a multiple of 256, which is every DeepSeek-V2-Lite / Coder-V2-Lite build. Q2_K covers the whole low-bit MoE tier.

2. All-or-nothing granularity. skipF32MoeDequant is a single bool threaded into TransformerWeights.LoadDeepSeekMoeLayer. In the Q4_K_M file, 64 of 78 routed banks are already device-resident-capable; all 64 are host-dequantised anyway because 14 are Q5_0. Per-bank resolution would cut the footprint by ~80% for that file with no new kernels at all.

The all-or-nothing design is deliberate and documented: with skipF32MoeDequant: true, W1/W2/W3 are left as all-NULL pointer arrays, so a bank that then resolves to the F32 upload path reads a null pointer per expert — silent corruption, not a crash. Making this per-bank therefore requires the F32/raw decision to be represented per bank in MoeLayerWeights, not inferred from a model-wide flag.

Suggested direction

Reuse the indexed MoE MMVQ machinery from #137 (already shipped for Qwen3-MoE banks) to add routed-bank device-resident support for Q5_0, IQ4_NL and Q2_K, and make the skip decision per bank rather than per model. Gap 2 is the cheaper and higher-leverage half and can land first.

Acceptance criteria

  • Per-bank F32-vs-raw resolution, with MoeLayerWeights carrying the per-bank decision so a null-pointer F32 read is structurally impossible.
  • Q5_0, IQ4_NL, Q2_K routed banks stay device-resident.
  • deepseek-v2-lite-q4_k_m and deepseek-coder-v2-lite-q2_k load on Vulkan on a 128 GB box and pass RealGgufVulkanParityTests.
  • Kernel-level parity tests for each newly covered quant against the CPU oracle.

Activity

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions