GLM-4.7-Flash is a 30B-A3B MoE coding model that fits a single 128GB DGX Spark with room to spare. Bringing it up on Blackwell sm_121 took two failures that every published recipe gets wrong: the 'AWQ' build is actually compressed-tensors, and the model speaks MLA, so flash_attn is illegal. Here is the working recipe, the single-stream decode number nobody reports, and what it does to my coding agent.

GLM-4.7-Flash on a Single DGX Spark: the Repo Says AWQ, the Model Says MLA

I added GLM-4.7-Flash to my single DGX Spark for one reason: it is supposed to out-code my daily Qwen3.6, and at 30B total with 3B active it is small enough to leave the box headroom. The bring-up is where it got interesting. Two of the most-copied flags from the model card and the community recipes do not survive contact with Blackwell sm_121, and the failures are loud, fast, and instructive. I measured it the way I measure everything here: single-stream, one GB10, the same harness.

Verdict at a glance

What it is30B total / 3B active MoE, compressed-tensors W4A16, MLA attention, MIT-style open weights
Single-Spark decode53.7 tok/s single-stream (range 49.8-54.9), once patched. Faster than the ~40 I expected; MTP speculative decoding earns its keep. (Qwen on the same box: ~69 tok/s.)
As a coding agentworks, produces correct terse code ("string"[::-1]). A full aider-polyglot pass_rate is impractical single-stream (a reasoning model thinking through 30 exercises at 53 tok/s runs for hours), so decode speed plus spot-correctness is the practical signal here.
Memory~81GB used / ~39GB free at util 0.50; healthy boot ~115s
Spark gotchasAWQ” repo is actually compressed-tensors (do not pass —quantization); MLA forbids flash_attn; util 0.80 OOMs the desktop, use 0.50; and it boots healthy but dies on the first token until you patch MLA
Do I run it?Yes, but only with a source patch. The first-token crash is NOT fixed by upgrading: it persists on the latest vLLM nightly (0.23.1) too, because PR #34695 guards the request-path reads but misses the ones in _compute_prefill_context. The same gap is tracked upstream as vLLM #43888, with a fix in PR #43889. Guard the prefill-context reads and GLM runs at 53.7 tok/s.

Update (2026-06-26): I benched GLM head-to-head against Qwen and dropped it from the coding seat — Qwen-only now. A controlled agent-bench A/B (baseline arm, deterministic typecheck gate) on the ts-rename coding task measured GLM at ~195s/run vs Qwen at ~18s/run — about 10× slower wallclock in the agentic loop. Both produced correct, type-checking renames; the gap is wallclock, not correctness. GLM is a reasoning model, so it thinks through every tool-call round — fine for a one-shot answer, brutal for an interactive agent that loops dozens of times. I stopped the sweep early (thermals) before GLM reached the harder ambiguous-rename gate, so this is a speed verdict, not a final correctness ranking — but ~10× is decisive for an interactive default. Qwen keeps the coding seat; GLM is out of the daily rotation (the launcher and this write-up stay for reproducibility). The patched image still works and everything below reproduces — it just isn’t worth running over Qwen for day-to-day coding on one GB10.

The number nobody reports: single-stream on one GB10

Every GLM-4.7-Flash throughput figure online is a server number: many requests, batched, on datacenter GPUs. That is the wrong metric for an agent sitting on a desk, which experiences one request at a time with nothing batched behind it. On one DGX Spark, serving a single stream, vLLM decodes GLM-4.7-Flash at 53.7 tok/s (range 49.8-54.9), once it is patched to run at all. That is faster than the ~40 the forums led me to expect, and it puts GLM comfortably between my Qwen (69 tok/s) and the dense Gemma reasoner (under 7).

The reason the number is as high as it is comes down to active parameters plus a free lunch. GLM activates ~3B parameters per token against the GB10’s ~273 GB/s memory-bandwidth ceiling, the same ballpark as my Qwen, so the floor is similar. On top of that, MTP speculative decoding earns real throughput here: the model ships a single multi-token-prediction head, and at single-user batch sizes that is exactly where speculative decoding pays off (vLLM’s GLM recipe prescribes num_speculative_tokens 1 for the same reason). The 30B total is mostly idle weight that costs disk and RAM, not decode time.

Bring-up: two failures the recipes cause

This is the part worth the price of admission, because the public recipes actively mislead.

Failure 1: the “AWQ” build is compressed-tensors. The repo is named GLM-4.7-Flash-AWQ-4bit, so the obvious flag is --quantization awq_marlin. That crashes at config validation: “Quantization method specified in the model config (compressed-tensors) does not match the quantization method specified in the quantization argument (awq_marlin).” The weights are packaged as compressed-tensors W4A16, not classic AWQ. The fix is to pass no quantization flag at all and let vLLM auto-detect: it loads CompressedTensorsWNA16MarlinMoEMethod and picks the Marlin MoE kernels itself. The name lies; the config tells the truth.

Failure 2: the model speaks MLA, so flash_attn is illegal. The next instinct is --attention-backend flash_attn, which every fast-LLM guide recommends. It crashes: “Selected backend FLASH_ATTN is not valid for this configuration. Reason: [‘head_size not supported’, ‘kv_cache_dtype not supported’, ‘MLA not supported’].” GLM-4.x uses Multi-head Latent Attention, the same compressed-KV trick as DeepSeek, and flash_attn cannot do MLA. The fix is again to specify nothing: vLLM auto-selects TritonMLABackend, which on sm_121 is the only MLA backend with working kernels. FlashMLA (Hopper/SM100), FlashInfer-MLA and CUTLASS-MLA (CC 10.x) are all gated out on Blackwell consumer silicon (vLLM attention-backend docs). There is no faster MLA path to switch to today.

The unified-memory tax. Both failures are recoverable in a minute. The one that bites harder is gpu-memory-utilization. On a discrete GPU you set it to 0.90 and move on. On the Spark, that fraction is a fraction of the 128GB unified memory the OS also lives in. At 0.80 vLLM reserved ~97GB and left the desktop with ~5GB, which is an out-of-memory wall, not a slowdown. At 0.50 (a 60GB budget) GLM is comfortable and the system keeps ~60GB. MLA helps here too: its KV cache is roughly a tenth of standard attention, so 0.50 is generous, not tight.

Optimization: what moved the needle, what did not

I tuned for the mutex workflow (only one model resident at a time, swapped on demand) and for single-user latency.

What did not help and is worth not trying: forcing a different attention backend (none exist for MLA on sm_121), --enforce-eager (kills CUDA graphs for a real speed loss), and any non-Marlin quant kernel.

The wall: a healthy endpoint that cannot generate a token

This is where the bring-up ended, and it is the most important part. After the two fixes above, vLLM loads the model, reports Application startup complete, and answers /health with a 200. By every check a dashboard would run, GLM is up. Then you send it a single chat completion and the engine dies:

mla_attention.py, forward_mha -> _compute_prefill_context:
    kv_c_normed = kv_c_normed.to(self.kv_b_proj.weight.dtype)
AttributeError: 'ColumnParallelLinear' object has no attribute 'weight'
-> EngineDeadError

The cause is a clean incompatibility, not a tuning problem. MLA’s prefill step absorbs the kv_b_proj projection and reads its .weight tensor directly. In the cyankiwi build, kv_b_proj is packaged as compressed-tensors W4A16, so there is no plain .weight (the data lives in weight_packed and friends). vLLM’s MLA path in this nightly does not dequantize it, it just reaches for an attribute that is not there. I confirmed it is not a flag: the crash is identical with prefix-caching and chunked-prefill both off, with and without ignore_eos. It is the same stack every time.

A green /health told me nothing. The lesson generalizes: on Blackwell, with a quantized MLA model, a successful boot is necessary and nowhere near sufficient. Smoke-test one real generation before you believe a model works, let alone benchmark it.

”Just upgrade vLLM” does not fix it

The obvious move is to blame an old vLLM and pull a fresh nightly. I did: the image jumped from v0.20.2rc1 to v0.23.1rc1.dev309 (which is supposed to contain PR #34695, the fix for exactly this kv_b_proj.weight crash). It crashed in the same place. PR #34695 is incomplete: it guards the request-path reads, but _compute_prefill_context still reads kv_b_proj.weight.dtype unguarded, so on the latest nightly the model still dies, now even earlier (during the KV-cache profiling run at init rather than on the first request). The same gap is tracked upstream as vLLM #43888, with a fix in PR #43889. Upgrading was also a net loss for the rest of my stack: 0.23 broke my Gemma reasoner with a separate modelopt tie_weights NotImplementedError. I rolled back to 0.20.

The fix, and the numbers

The seam is MLA-meets-quantized-kv_b_proj, and the fix is small: guard the three self.kv_b_proj.weight.dtype reads (two of them in _compute_prefill_context) with hasattr(self.kv_b_proj, "weight") and fall back to the layer’s params_dtype when the weight is packed. That is the same shape as PR #34695, just applied to the reads it missed. I baked it into a patched image (a one-line sed on mla_attention.py that rewrites all three reads), pointed the launcher at it, and GLM came up clean: healthy in 115s, survives generation, decodes at 53.7 tok/s, and produces correct output (asked for a one-line string reversal it returns "string"[::-1] and nothing else).

Credit where due: this is the same guard the eugr/spark-vllm-docker DGX-Spark recipe ships as its fix-glm-4.7-flash-AWQ mod (a local copy of #34695, applied at build time), which is what pointed me at the real cause. That mod also bundles a separate triton_mla num_kv_splits speed patch I have not applied yet, it reportedly lifts short-context throughput further, so my 53.7 tok/s is likely a floor, not a ceiling. The crash fix is already tracked upstream as #43888 with a fix in PR #43889; my guard is the same shape, and I confirmed the same crash and fix on sm_121. Until that lands in a release, the patched image is what makes GLM runnable next to Qwen at all (though I later dropped it from the coding seat — see the Update at the top).

Reproduce

The working launcher (switch-llm.sh glm), abbreviated:

docker run -d --name vllm-glm47-flash --gpus all --network host --ipc host \
  -v /ai/models:/ai/models -v /ai/vllm-cache:/ai/vllm-cache \
  -e TORCH_CUDA_ARCH_LIST=12.1a -e VLLM_FLASHINFER_MOE_BACKEND=latency \
  -e VLLM_CACHE_ROOT=/ai/vllm-cache/glm \
  ghcr.io/spark-arena/dgx-vllm-eugr-nightly:latest \
  vllm serve cyankiwi/GLM-4.7-Flash-AWQ-4bit --served-model-name glm-4.7-flash \
    --max-model-len 65536 --max-num-seqs 4 --gpu-memory-utilization 0.50 \
    --compilation-config '{"cudagraph_capture_sizes":[1,2,4]}' \
    --speculative-config '{"method":"mtp","num_speculative_tokens":1}' \
    --enable-prefix-caching --enable-chunked-prefill --trust-remote-code \
    --enable-auto-tool-choice --tool-call-parser glm47 --reasoning-parser glm45

No --quantization (auto compressed-tensors), no --attention-backend (auto TritonMLA), no fp8 KV (unsupported with the MLA backend).

Caveats

Single-stream, one GB10, vLLM v0.20.2rc1 nightly, June 2026. The decode number is single-user and will differ under concurrency. GLM’s reasoning parser can leak <think> into content on some multi-turn tool-call paths; it is version-sensitive. I ran this as the coding engine in the mutex rotation for a while, but the head-to-head in the Update at the top retired it — Qwen now holds the coding seat; the GLM and Gemma launchers stay for reproducibility, not daily use.

Was this worth it? Zap the article.

Value for value, no signup. Sats go straight to the writer.

sats zapped
zaps
Today 7d 30d All-time
Unique readers
Page views
All Article Insights →