Running Muse Glimmer on a DGX Spark: five gotchas

updated August 19, 2026 dgx-spark Muse-Glimmer-30B

Meta released Muse Glimmer on 10 August. NVIDIA’s launch post names the DGX Spark as a recommended platform for it, and publishes exactly one throughput number — measured on Blackwell Ultra, which is not a Spark.

I have a Spark. Between docker run and a server that answered a question correctly, there were five distinct failures. Four of them produce an error you can search for. The fifth produces a running server that returns HTTP 200 at a normal speed and is wrong about everything, which is the one worth reading about.

None of this is a complaint about the model. It is what a day-one release looks like on hardware that is not the hardware the release was tested on, and it is entirely reproducible — every command and error below is real.

Summary: what worked, what didn’t

Everything I tried, in order. Read the middle column before the first one — three of these configurations start a server that answers requests and is wrong about everything, which is a worse outcome than the ones that refuse to start.

# Approach Starts? Output correct? Speed
1 NGC vllm:26.04-py3 (vLLM 0.19.0) ✗ architecture unknown
2 NGC vllm:26.07-py3 (vLLM 0.24.0) ✗ architecture unknown
3 vllm-openai:v0.27.0, transformers fallback, upstream weights ✗ 47 GB shard won’t stage
4 …same, with re-sharded weights ✗ gibberish
5 …same, --enforce-eager ✗ gibberish
6 FP8-block quantized ✗ DeepGEMM assert
7 FP8-block + VLLM_USE_DEEP_GEMM=0 ✗ gibberish
8 muse-glimmer-arm64-cu130, re-sharded BF16 4.3 tok/s
9 …+ DFlash, depth 15, default batch budget ✗ CUDA device-side assert
10 …+ DFlash, depth 4, --max-num-batched-tokens 8192 13.1 tok/s
11 …+ DFlash, depth 15, --max-num-batched-tokens 8192 23.3 tok/s

Updated 2026-08-19: the depth-15 figures below were re-measured. The original run’s serving configuration was never recorded — the container had been started by hand, so the record could not show which drafter depth produced the fastest row. The re-run is the same model, weights, image and flags, and it reproduces the result: the two runs differ by less than noise (chat_short 20.97 ± 1.22 vs 22.39 ± 2.21 tok/s over five repetitions, t = 1.26). The numbers shifted slightly because they are a different draw, not because the earlier ones were wrong.

Row 11 is the answer. Rows 4, 5 and 7 are the reason this post exists — and rows 9 to 11 are why the post carries a correction: for a while I published row 10 as the answer, having blamed the crash on the draft depth rather than the batch budget that actually caused it.

What each working configuration gets you, since rows 8, 10 and 11 are the same model and weights with one flag changed between them:

Config chat_short code_generation
BF16, no speculation 4.30 4.28
+ DFlash, depth 4 13.12 13.76
+ DFlash, depth 15 23.31 25.93

Two scenarios shown because they bracket the range: short chat is the interactive case, code generation is the longest output and gains most from drafting. The other two land between them, and all four are in the benchmark post. Speculative decoding is lossless, so every row is the same output — the slower ones are just slower.

Memory, measured while serving row 10 (--gpu-memory-utilization 0.62 on the GB10’s 121.7 GiB of unified memory):

Weights and non-torch 62.0 GiB
KV cache 10.1 GiB (216,893 tokens, 6.6× concurrency at 32k)
Peak activation 3.3 GiB
CUDA graphs 1.0 GiB
Total for this model ~76 GiB
Whole machine, including the always-on 4B and embedding models 101.8 GiB of 121.7

The BF16 baseline in row 8 is about 5 GiB lighter, that being the size of the DFlash draft model. Draft depth does not change the memory picture — the drafter is the same 5.1 GB model at depth 4 and depth 15; only how many tokens it proposes per pass differs.

Two things worth noting before you plan capacity. Loading the weights alone accounted for 60.7 GiB and took 396 seconds — most of the ten-minute cold start is reading the checkpoint off disk, not initialising the engine. And of the ~76 GiB total, only 10 GiB is KV cache: this model spends its memory on weights, which is the same fact that caps its speed.

That last point has a practical consequence. vLLM notes that the same box could allocate 33.25 GiB of KV cache instead of 10.1 if you asked it to, raising concurrency well beyond the measured 6.6× at 32k context. On a machine this size the constraint on serving more requests is not memory — it is that every one of them is competing for the same 273 GB/s of bandwidth.

1. The version number is lower than the version that fails

The model’s config.json declares MuseGlimmerForConditionalGeneration, and vLLM’s recipe says vLLM 0.27.0+. That is misleading. Native support lives on PR #51655, which is still unmerged, so no released version contains it.

You can check any image in about thirty seconds:

docker run --rm --entrypoint python3 <image> -c "
from vllm.model_executor.models.registry import ModelRegistry
print([a for a in ModelRegistry.get_supported_archs() if 'Glimmer' in a])
"

What that returns:

Image vLLM Muse architecture
nvcr.io/nvidia/vllm:26.04-py3 0.19.0 none
nvcr.io/nvidia/vllm:26.07-py3 0.24.0 none
vllm/vllm-openai:v0.27.0 0.27.0 none
vllm/vllm-openai:muse-glimmer-arm64-cu130 0.26.1rc1.dev608 all four

The image that works self-reports a lower version than two that do not. Searching by version number finds nothing; the support is carried by the tag. There are muse-glimmer-* tags for arm64 and x86_64, cu129 and cu130.

The habit this breaks is a reasonable one: read the requirement, check your version, upgrade until you meet it. That reasoning is sound and it fails completely here, because support for a new architecture arrives on a branch long before it arrives in a release, and a branch build is versioned from wherever it forked. “0.27.0+” was not wrong so much as it was answering a different question than the one I was asking.

The habit worth having instead costs thirty seconds: ask the image what it supports rather than what it is called. The registry check above is the whole test, it works for any model and any image, and it would have saved me several hours.

2. A 47 GB shard will not load on 128 GB of unified memory

With the right image, the first load stalled at 0/2 for eight minutes and the engine was killed:

Checkpoint size: 55.46 GiB. Available RAM: 35.88 GiB.
Loading safetensors checkpoint shards:   0% Completed | 0/2

vLLM streams weights one shard at a time, so peak host memory is roughly the largest single shard, not the whole checkpoint. Meta ships Muse Glimmer as two files, one of them 47 GB. Everything else on this machine arrives in small pieces:

Model Total Largest shard Loads
Qwen3.6-27B-FP8 29 GB 5.6 GB yes
Qwen3.6-35B-A3B 67 GB 3.8 GB yes
gpt-oss-120b 81 GB 4.4 GB yes
Muse-Glimmer-30B 56 GB 47 GB no

The 35B and gpt-oss are larger overall and load without complaint. Total size is not the constraint; shard granularity is. The arithmetic is specific to unified memory: --gpu-memory-utilization 0.62 reserves its share of the same 121 GB pool the loader stages into, leaving 35.88 GB for a 47 GB shard.

The fix is to rewrite the checkpoint into smaller shards. Streaming tensor-by-tensor keeps peak RAM at about one output shard, so the split itself does not need 47 GB:

from safetensors import safe_open
from safetensors.torch import save_file
# plan shard membership from metadata (shapes + dtypes, no tensor loads),
# then materialise one output shard at a time.

Re-sharded 2 → 16 shards, largest 3.98 GB, it loads in about nine minutes alongside the always-on models with no memory pressure.

Large shards are not a mistake on the vendor’s part — fewer, bigger files download faster and are simpler to verify, and on a machine with discrete VRAM and separate system RAM the staging buffer comes out of a different pool, so nothing collides. The 47 GB shard is fine on the hardware it was tested on.

It stops being fine on unified memory, which is exactly the architecture the DGX Spark is sold for. That is the general lesson here and it will outlive this model: on unified memory, the loader and the accelerator are competing for one pool, so the largest shard is a hard constraint independent of how much total memory you have. A 128 GB machine that comfortably serves an 81 GB model can fail to load a 56 GB one.

If you package models, 4–5 GB shards cost you very little and make this class of failure impossible. If you run them, checking the largest shard against your free memory is a one-line test that will keep paying off as unified-memory machines get more common.

3. The fallback backend serves noise at a plausible speed

Before finding the right image, the obvious move is --model-impl transformers, since transformers 5.15 knows the architecture. It loads. It serves HTTP 200. It reports sensible token counts. And it is wrong:

prompt:  "The capital of France is"
output:  '\n_con_c\n\nThe\n'

The cause is visible in the startup log if you look for it:

Fused: input_layernorm (MuseGlimmerTextCenteredRMSNorm) -> GemmaRMSNorm (CustomOp)

A centered RMSNorm subtracts the mean; Gemma’s does not. Substituting one for the other corrupts activations in every layer. The same failure appears in BF16 and FP8, compiled and eager — --enforce-eager does not help, because the substitution happens at model construction, not in the compiler.

This is the dangerous one. Nothing about it looks like a failure: the server is up, latency is normal, throughput is plausible. A benchmark harness measuring tokens per second would have recorded four scenarios of publishable numbers from a model emitting gibberish.

I want to be blunt about how close this came to being published as a result.

I had a working endpoint, a benchmark harness, and four scenarios of clean, low-variance timings. Every instinct that normally tells you something is wrong — errors, warnings, latency spikes, missing tokens — said nothing, because from the outside a corrupted model and a working one are indistinguishable. Timing measures how fast tokens arrive, not whether they mean anything. The only reason those numbers did not end up on this site is that I pasted a question into the endpoint by hand before running the benchmark.

So the harness now refuses to record a run until the model has answered a few questions with unambiguous answers. Three prompts, two of three must pass, matched case-insensitively anywhere in the reply so a model that thinks out loud still counts. It costs a couple of seconds per run and it would have caught all three of the broken configurations above in the first one.

If you take one thing from this post: check that the output means something before you trust any number measured alongside it. Not just for a new model — for a new image, a new quantization, a new flag. Every one of those changes the kernels that run, and a wrong kernel is silent.

4. Both quantized paths are dead ends on this chip, for now

FP8 halves the bytes per token, and RedHatAI’s FP8-block build is 34.4 GB with a 28.2 GB largest shard, which would sidestep problem 2 as well. It dies during weight processing:

RuntimeError: Assertion error (deepgemm-src/csrc/apis/layout.hpp:60):
Unknown SF transformation

DeepGEMM has no scale-factor layout for SM121, the GB10’s compute capability. Setting VLLM_USE_DEEP_GEMM=0 gets past it — and produces a server that returns '<|eom|>_cassistant_'. It converts a loud crash into silent corruption, which is worse.

NVFP4 is smaller still, but it is the same FP4 family that already requires VLLM_MXFP4_BACKEND=marlin on this machine to stop gpt-oss corrupting its first token. I did not pursue it.

Quantization is worth revisiting, but not yet, and the reason is narrower than “FP8 is broken on this chip”. The GB10 reports compute capability 12.1, and the kernel libraries dispatch on architecture: DeepGEMM has layouts for the architectures it was built against and no entry for this one. That is a lookup-table gap, not a hardware limitation, and it closes on its own the moment someone adds the case.

What I would want before trying again is simply a build whose FP8 path has been exercised on SM121 — the NVIDIA NGC images are the obvious candidate, since they exist precisely to carry this kind of chip-specific fix, and they already carry one for gpt-oss’s MXFP4 path.

The part I would not repeat is VLLM_USE_DEEP_GEMM=0. Turning off an accelerated path to get past an assertion is a reasonable instinct, and here it traded a crash I could see for corruption I could not. An environment variable that makes an error disappear has not necessarily fixed anything, and on this occasion it made the situation strictly worse.

5. The drafter needs batch space, and without it the error blames the wrong thing

Correction, 14 August 2026. This section originally said the recipe’s num_speculative_tokens: 15 crashes on this hardware and told you to use 4 instead. That was wrong, and the advice cost throughput. The assert is a draft-input buffer overflow, not a depth limit — and my “fix” of dropping to 4 also added –max-num-batched-tokens 8192, which is what actually resolved it. I changed two things at once and credited the wrong one. Re-tested at depth 15 with the same batch setting: it runs clean and is 1.3–2.0× faster than depth 4. Meta’s recipe was right. Corrected figures below; the original run is kept in the repository.

Muse Glimmer ships its own DFlash drafter, and the published recipe says:

--speculative-config '{"method":"dflash","model":"...-assistant","num_speculative_tokens":15}'

Run that as-is and the engine dies during init:

RuntimeError: CUDA error: device-side assert triggered

The cause is in the warning vLLM prints on the way past, which I quoted here for weeks while misreading it:

max_num_scheduled_tokens is set to 2048 based on the speculative decoding settings. [...] Consider increasing max_num_batched_tokens [...] or decrease num_speculative_tokens.

Two remedies, and they are not equivalent. DFlash needs max_num_seqs × (1 + num_speculative_tokens) of batch space to stage its draft inputs — with 256 sequences and 15 draft tokens, 4096. Left to itself the scheduler caps at 2048, the draft input tensor overruns, and the position values become garbage; the assert is downstream of that. Lowering the draft count shrinks the requirement until it fits. Raising the batch budget fixes it properly and keeps the speed:

--max-num-batched-tokens 8192
--speculative-config '{"method":"dflash","model":"...-assistant","num_speculative_tokens":15}'

At depth 15 with that headroom it runs clean, at a mean acceptance length of about 3.4:

Scenario BF16 + DFlash (depth 4) + DFlash (depth 15) vs BF16
chat_short 4.30 13.12 23.31 5.4×
chat_long_context 4.02 9.34 13.54 3.4×
code_generation 4.28 13.76 25.93 6.1×
summarization 3.42 7.21 9.57 2.8×

Speculative decoding is lossless — the full model verifies every drafted token — so all three columns are the same output at different speeds.

One caveat on code_generation: at depth 15 its repetitions ranged from 24.3 to 31.9 tok/s, a standard deviation of 3.9. Deeper drafting makes throughput more sensitive to how predictable the output is, so treat that 25.93 as the softest number in the table.

The 4.3 tok/s baseline is not a tuning failure. 4.3 tok/s × 59.6 GB is about 256 GB/s of weight traffic, against roughly 273 GB/s of memory bandwidth: the model was already running at about 94% of what the machine can move. Decode stayed flat at 4.3 whether the prompt was 128 tokens or 16,384, with KV cache usage at 0.2% throughout. Speculation does not raise that ceiling — it stops spending a full sweep of the weights on every single token.

This is the number that should change how you choose models for a machine like this one, so it is worth being precise about what it says.

The Spark was not running Muse Glimmer badly at 4.3 tok/s. It was running it at essentially the speed physics allows: one token requires reading every weight, 59.6 GB of them, and the machine can move about 273 GB/s. No flag, no image, no amount of tuning moves that. On bandwidth-limited hardware, single-stream decode speed is a property of bytes-per-token, not of how good the implementation is — which is why decode stayed pinned at 4.3 whether the prompt was 128 tokens or 16,384.

That gives you exactly three levers, and it is worth knowing which one you are pulling:

Only the third is free. The first two trade capacity or precision for speed; speculation gives you 2–3× and verifies every token against the full model, so the output is unchanged. If you are choosing a model for a bandwidth-limited box, check whether it ships a drafter before you check its benchmark scores — a model with one will beat a nominally faster model without one, on your hardware, by a margin larger than most quality differences.

The configuration that works

docker run -d --rm --name vllm-muse-glimmer-30b \
  --gpus all --ipc=host --shm-size=16g -p 8008:8008 \
  -v /path/to/Muse-Glimmer-30B-resharded:/model:ro \
  -v /opt/vllm/cache:/root/.cache/huggingface \
  --entrypoint vllm vllm/vllm-openai:muse-glimmer-arm64-cu130 \
  serve /model --served-model-name meta-models/Muse-Glimmer-30B \
  --port 8008 --host 0.0.0.0 --max-model-len 32768 \
  --tensor-parallel-size 1 --gpu-memory-utilization 0.62 \
  --max-num-batched-tokens 8192 \
  --speculative-config '{"method":"dflash","model":"meta-models/Muse-Glimmer-30B-assistant","num_speculative_tokens":4}'

Cold start is about ten minutes: sixteen weight shards plus a 5.1 GB drafter.

One rough edge remains. This build exposes no muse_glimmer reasoning or tool-call parser, so the model’s channel-scoped reasoning arrives raw in content — replies can open with to=self and the model thinking out loud before answering. Strip it, or match on the answer rather than assuming the whole reply is the answer.

Total run time

Arm What ran Machine time
Muse-Glimmer-30B 4 scenarios · 5 repetitions each 40 min
Muse-Glimmer-30B 4 scenarios · 5 repetitions each 14 min
Muse-Glimmer-30B 4 scenarios · 5 repetitions each 8 min
Total 1.0 h
Machine time for the runs cited here, from data/runs/. It counts only configurations that produced measurements; the failures described above wrote no run file.

About an hour of successful measurement, which badly understates what this post cost.

That hour is the configurations that worked. It does not count the images that would not start, the 47 GB shard that would not load, or the backend that served fluent nonsense at a perfectly normal speed — that last one being the expensive kind of failure, because it produces a plausible number and no error.

The measuring is cheap here. Finding a configuration worth measuring is the work, and none of it appears in the figure above.

Should you do this today?

If you want to use the model: wait. PR #51655 will merge, the NGC images will pick it up, and most of this post becomes history — the pre-release tag, the re-sharding, the missing parsers. At that point it is an ordinary vllm serve against an ordinary image, and the only thing left from this list is the draft depth. There is no capability here you cannot have in a few weeks for none of the effort.

If you want to understand the machine: do it now, or do the equivalent with whatever ships next. Nothing in this post is specific to Muse Glimmer. Shard granularity against unified memory, kernel dispatch gaps on a new compute capability, a fallback path that is wrong rather than absent, a vendor default that crashes on your hardware — those recur with every new architecture, and you learn them by hitting them, not by reading release notes.

If you are running a DGX Spark in particular: the bandwidth arithmetic is the durable thing. A 60 GB model at BF16 will give you about 4 tok/s on this machine and no configuration changes that. Decide whether you can quantize it, whether it is sparse, or whether it ships a drafter — before you buy into it, not after.

And whatever you run: check that it can answer a question you know the answer to, before you believe anything else it tells you.

Source data: d994a07defc0d4fa66cd5bd9e6223a2d9555