glm: make GLM 5.2 work on CUDA with SSD streaming, including IQ2_XXS - #717
Open
JonasColmsjo wants to merge 1 commit into
Open
glm: make GLM 5.2 work on CUDA with SSD streaming, including IQ2_XXS#717JonasColmsjo wants to merge 1 commit into
JonasColmsjo wants to merge 1 commit into
Conversation
antirez#617 added GLM 5.2 SSD streaming and distributed inference for ROCm, using GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf. This does the equivalent for CUDA, where the same paths were either unreachable or silently wrong. On a DGX Spark (GB10, 121 GB unified) the 262 GB Q2_K build generated token soup, the 211 GB IQ2_XXS build could not load at all, and loading either without --ssd-streaming wedged the host hard enough to need a power cycle, with nothing logged to say why. 1. Routed MoE never used the streaming expert compaction on CUDA. ds4_gpu_glm_stream_expert_cache_begin_selected_load_tensor exists and works, but its call sites are ROCm, Metal and CPU only, and neither GLM MoE entry read g_stream_selected_cache. So the dispatch asked cuda_resolve_weight_ptr for all n_total_expert experts as one contiguous span; streaming can never satisfy that, and on single-GPU the resolver answers a miss without an error (the multi-GPU path logs selective-cache miss). The kernel then multiplied against memory the model was never written into. Wire it to the same compaction the DeepSeek MXFP4 path uses, and refuse the layer if compaction is unavailable rather than reading unpopulated memory. 2. IQ2_XXS routed experts had no CUDA path. GLM's kernels are Q2_K-only, so the type gate rejected them -- yet the vendored MMQ tier already implements IQ2_XXS MoE and DeepSeek uses it on the same hardware. Its layout contract matches GLM exactly, so gate/up now dispatch through ds4_mmq_iq2_xxs_moe_pair, then SwiGLU and router weights, then down per (token, slot). Down is dispatched on its own type so the RoutedIQ2XXS_blk78Q2K layout works. The fused gate_up_mid_vec entry cannot be used: it is hardcoded to n_expert_used == 6 and GLM uses 8. 3. IQ2_XXS layers were diverted to the generic routed path, which needs batch_routed_* buffers that are not wired up when a shared prefill workspace is in use, so every routed layer failed before reaching a kernel. That predicate existed because the GLM path could not do IQ2_XXS, which (2) changes. Metal and ROCm keep the generic path. 4. The memory guard could not see host memory on Linux. glm_graph_host_memory_bytes() was macOS-only and returned 0 elsewhere, so it fell back to a GPU working-set estimate. On an integrated GPU that is the wrong constraint -- device allocations come from host RAM, so an oversized model wedges the machine instead of failing an allocation. Read the real total via sysconf(_SC_PHYS_PAGES) and print the shortfall and the remedy. 5. Diagnostics, because none of the above was visible. glm_graph_indexed_prefill_trace_enabled() and _trace_all() were hardcoded to return false, making every glm_graph_indexed_prefill_tracef() call site dead code; they now read DS4_GLM_INDEXED_PREFILL_TRACE / _TRACE_ALL, cached since they are consulted per stage per layer. The silent entry guards name the precondition they rejected. Routed-MoE dispatch failures report the expert tensor types, which is what identifies a quant-support problem. DS4_CUDA_VERIFY_WEIGHT_PTR reports weights not resolving to device memory, and an unbounded weight cache on an integrated GPU now says so. Verified on GB10 with SSD streaming: - IQ2_XXS (211 GB): "capital of France" -> Paris; "reverse a string" -> def reverse_string(s): return s[::-1]; 0.46 t/s prefill, 0.49 t/s decode - Q2_K (262 GB): correct, 0.25 t/s -- IQ2_XXS is ~2x faster, streaming 51 GB less - without --ssd-streaming: refuses in seconds, memory untouched, and says "need 249.56 GiB but only 89.63 GiB is available ... retry with --ssd-streaming" - DeepSeek V4 Flash unaffected, resident and streaming (regression-checked) Trace and verify flags are off by default; output is unchanged unless set. Supersedes antirez#683 and antirez#701. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the CUDA counterpart of #617, which added GLM 5.2 SSD streaming and
distributed inference for ROCm using the same
GLM-5.2-UD-IQ2_XXS_RoutedIQ2XXS_blk78Q2K.gguf. On CUDA those paths were eitherunreachable or silently wrong.
Tested on a DGX Spark (GB10, 121 GB unified). Before this: the 262 GB Q2_K build
generated token soup, the 211 GB IQ2_XXS build could not load at all, and loading
either without
--ssd-streamingwedged the host badly enough to need a powercycle — with nothing logged to say why. It cost four power cycles to work out.
1. Routed MoE never used the streaming expert compaction on CUDA
ds4_gpu_glm_stream_expert_cache_begin_selected_load_tensorexists and works, butits call sites are ROCm (
#ifdef DS4_ROCM_BUILD), Metal and CPU only — and neitherds4_gpu_glm_routed_moe_batch_tensornor..._one_tensorreadsg_stream_selected_cache.So the batch dispatch asked
cuda_resolve_weight_ptrfor alln_total_expertexperts as one contiguous span. Streaming can never satisfy that, and on
single-GPU the resolver answers a miss without an error — the multi-GPU path
logs
selective-cache miss, the single-GPU path just returns a pointer. Thekernel then multiplied against memory the model was never written into.
Now wired to the same compaction the DeepSeek MXFP4 path uses at
ds4_cuda.cu:23727, and it refuses the layer if compaction is unavailable ratherthan reading unpopulated memory.
2. IQ2_XXS routed experts had no CUDA path
GLM's routed-MoE kernels are Q2_K-only, so IQ2_XXS was rejected at the type gate —
even though the vendored MMQ tier already implements IQ2_XXS MoE and DeepSeek uses
it on the very same hardware.
The MMQ contract matches GLM's layout exactly:
Wis[n_experts, M, K]withper-expert slabs stacked,
idsis[n_tokens, n_expert_used]row-major, andoutis column-major over (token, slot) — the layout GLM already asserts via
mid_token_stride.K=6144is a multiple of 256 andn_expert_used=8is one ofthe specialised widths.
Gate/up now dispatch through
ds4_mmq_iq2_xxs_moe_pair, then SwiGLU and the routerweights via the existing kernel, then down per (token, slot). Down is dispatched on
its own type, so the
RoutedIQ2XXS_blk78Q2Klayout works. The fusedgate_up_mid_vecentry cannot be used — it is hardcoded ton_expert_used == 6.Decode needs no separate work:
..._one_tensordelegates to the batch entry.3. IQ2_XXS layers were diverted to the generic routed path
glm_graph_layer_uses_generic_routed_moe()sent them tods4_gpu_routed_moe_batch_tensor, which needsg->batch_routed_*buffers that arenot wired up when a shared prefill workspace is in use — so every routed layer
returned 0 before reaching a kernel. That predicate existed because the GLM path
could not do IQ2_XXS, which (2) changes. Metal and ROCm keep the generic path.
4. The memory guard could not see host memory on Linux
glm_graph_host_memory_bytes()was macOS-only and returned0elsewhere, so theguard fell back to a GPU working-set estimate. On an integrated GPU that is the
wrong constraint: device allocations come out of host RAM, so an oversized model
does not fail an allocation — it takes the machine down. Now reads the real total
via
sysconf(_SC_PHYS_PAGES)and, when refusing, prints the shortfall and theremedy.
5. Diagnostics
None of the above was visible.
glm_graph_indexed_prefill_trace_enabled()and_trace_all()were hardcodedreturn false, making everyglm_graph_indexed_prefill_tracef()call site dead code; they now readDS4_GLM_INDEXED_PREFILL_TRACE/_TRACE_ALL, cached since they are consulted atevery stage boundary of every layer. The silent entry guards name the precondition
they rejected instead of returning a bare
falseout of a twelve-conditionif.Routed-MoE dispatch failures report the expert tensor types — which is what
identifies a quant-support problem.
DS4_CUDA_VERIFY_WEIGHT_PTRreports any weightnot resolving to device memory, and an unbounded weight cache on an integrated GPU
now says so.
Verification
On GB10 with SSD streaming:
Paris; reverse a string →def reverse_string(s): return s[::-1]--ssd-streamingneed 249.56 GiB but only 89.63 GiB is available … retry with --ssd-streamingTrace and verify flags are off by default, so output is unchanged unless the env
vars are set.
Supersedes #683 and #701, which covered subsets of this.