Summary
Loading a DeepSeek-V2-family GGUF (MLA + MoE) on Vulkan dies with a bare, unactionable
System.OutOfMemoryException : Insufficient memory to continue the execution of the program.
at DotLLM.Models.Architectures.TransformerWeights.SliceExpertsToF32(...) TransformerWeights.cs:line 2291
at DotLLM.Models.Architectures.TransformerWeights.LoadDeepSeekMoeLayer(...)
at DotLLM.Models.Architectures.TransformerWeights.LoadMlaLayer(...)
at DotLLM.Models.Architectures.TransformerWeights.LoadFromGguf(...)
at DotLLM.Vulkan.VulkanTransformerModel.LoadFromGguf(...) VulkanTransformerModel.cs:line 1156
on a 128 GB box. Nothing in the message says why ~57 GB of host F32 was being allocated, or which tensors caused it. The footprint is knowable before a single byte is allocated — VulkanWeights.CanSkipMoeF32HostDequant already walks exactly the tensor descriptors that decide it.
Evidence — 2026-08-11 parity sweep
| model |
result |
GGUF |
deepseek-coder-v2-lite-q2_k |
FAIL / OOM in 112 s |
bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF Q2_K, 6.43 GB |
deepseek-v2-lite-q4_k_m |
FAIL / OOM in 144 s |
mradermacher/DeepSeek-V2-Lite-GGUF Q4_K_M, 10.36 GB |
Both reach the Vulkan load only after the CPU reference model has already loaded successfully (54.5 s / 105.4 s), i.e. two ~57 GB host F32 expert copies are attempted, since the CPU path (TransformerModel.LoadFromGguf → TransformerWeights.LoadFromGguf(gguf, config)) defaults skipF32MoeDequant: false. TransformerWeights.cs documents the size itself: "64 experts x 2048 hidden x 1408 intermediate x 3 mats x 4 bytes ~ 2.2 GB per layer x 26 MoE layers ~ 57 GB of F32 host RAM".
Root cause (verified against the actual GGUF tensor descriptors)
VulkanWeights.CanSkipMoeF32HostDequant returns false the moment any one routed bank on any MoE layer would fall back to F32, so every bank of every layer gets host-dequantised. MoeRoutedRawDeviceQuantType covers only Q8_0, Q4_K, Q5_K, Q6_K and (with coopmat) F16.
DeepSeek-V2-Lite's moe_intermediate_size is 1408, which is not a multiple of QK_K = 256, so llama.cpp is forced to quantise every ffn_down_exps bank to a non-K format. Census of the real files:
mradermacher/DeepSeek-V2-Lite-GGUF Q4_K_M (parsed with gguf.GGUFReader):
26 ffn_gate_exps.weight Q4_K (2048, 1408, 64) <- supported
26 ffn_up_exps.weight Q4_K (2048, 1408, 64) <- supported
14 ffn_down_exps.weight Q5_0 (1408, 2048, 64) <- NOT supported
12 ffn_down_exps.weight Q8_0 (1408, 2048, 64) <- supported
14 unsupported banks out of 78 force the other 64 to F32 too.
bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUF Q2_K (header range-fetched and parsed):
26 ffn_gate_exps.weight Q2_K (2048, 1408, 64) <- NOT supported
26 ffn_up_exps.weight Q2_K (2048, 1408, 64) <- NOT supported
26 ffn_down_exps.weight IQ4_NL (1408, 2048, 64) <- NOT supported
78/78 unsupported.
This issue is the diagnostics half
The capacity limit itself is real (two 57 GB copies do not fit in 128 GB, and would not fit for full DeepSeek-V2 on any box). Closing the coverage gap so the weights stay quantised on device is tracked separately in #327.
What is a bug here is that we discover it by exhausting the machine's RAM and reporting a stack frame instead of preflighting a number we can compute for free.
Proposed fix
- Extend the preflight to report why, not just whether: which layers/banks resolve to F32, their quant types, and the total F32 host footprint.
- In
VulkanTransformerModel.LoadFromGguf (both overloads), when the F32 fallback is taken and the computed footprint exceeds available memory, throw a typed exception (InsufficientMemoryException) carrying that itemisation — before allocating.
- Gate on the footprint, not merely on the flag, so small MoE models that genuinely fit keep working unchanged.
- Have
RealGgufVulkanParityTests convert that typed exception into Skip.If with the full message, matching its existing VRAM-exhaustion skip. A capacity limit is not a parity failure, and reporting it as FAIL misrepresents sweep results.
Acceptance criteria
Summary
Loading a DeepSeek-V2-family GGUF (MLA + MoE) on Vulkan dies with a bare, unactionable
on a 128 GB box. Nothing in the message says why ~57 GB of host F32 was being allocated, or which tensors caused it. The footprint is knowable before a single byte is allocated —
VulkanWeights.CanSkipMoeF32HostDequantalready walks exactly the tensor descriptors that decide it.Evidence — 2026-08-11 parity sweep
deepseek-coder-v2-lite-q2_kbartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUFQ2_K, 6.43 GBdeepseek-v2-lite-q4_k_mmradermacher/DeepSeek-V2-Lite-GGUFQ4_K_M, 10.36 GBBoth reach the Vulkan load only after the CPU reference model has already loaded successfully (54.5 s / 105.4 s), i.e. two ~57 GB host F32 expert copies are attempted, since the CPU path (
TransformerModel.LoadFromGguf→TransformerWeights.LoadFromGguf(gguf, config)) defaultsskipF32MoeDequant: false.TransformerWeights.csdocuments the size itself: "64 experts x 2048 hidden x 1408 intermediate x 3 mats x 4 bytes ~ 2.2 GB per layer x 26 MoE layers ~ 57 GB of F32 host RAM".Root cause (verified against the actual GGUF tensor descriptors)
VulkanWeights.CanSkipMoeF32HostDequantreturns false the moment any one routed bank on any MoE layer would fall back to F32, so every bank of every layer gets host-dequantised.MoeRoutedRawDeviceQuantTypecovers onlyQ8_0,Q4_K,Q5_K,Q6_Kand (with coopmat)F16.DeepSeek-V2-Lite's
moe_intermediate_sizeis 1408, which is not a multiple ofQK_K = 256, so llama.cpp is forced to quantise everyffn_down_expsbank to a non-K format. Census of the real files:mradermacher/DeepSeek-V2-Lite-GGUFQ4_K_M (parsed withgguf.GGUFReader):14 unsupported banks out of 78 force the other 64 to F32 too.
bartowski/DeepSeek-Coder-V2-Lite-Instruct-GGUFQ2_K (header range-fetched and parsed):78/78 unsupported.
This issue is the diagnostics half
The capacity limit itself is real (two 57 GB copies do not fit in 128 GB, and would not fit for full DeepSeek-V2 on any box). Closing the coverage gap so the weights stay quantised on device is tracked separately in #327.
What is a bug here is that we discover it by exhausting the machine's RAM and reporting a stack frame instead of preflighting a number we can compute for free.
Proposed fix
VulkanTransformerModel.LoadFromGguf(both overloads), when the F32 fallback is taken and the computed footprint exceeds available memory, throw a typed exception (InsufficientMemoryException) carrying that itemisation — before allocating.RealGgufVulkanParityTestsconvert that typed exception intoSkip.Ifwith the full message, matching its existing VRAM-exhaustion skip. A capacity limit is not a parity failure, and reporting it as FAIL misrepresents sweep results.Acceptance criteria
OutOfMemoryException.