Z.ai dropped GLM-5.3-Flash on August 26 — a 320B-total / 18B-active multimodal MoE with 1M-token training context, MIT licensed, and a stealth "ox-alpha" pedigree. It beats our current local default (DeepSeek-V4-Flash-0731) on every overlapping public agent benchmark, including the cleanest same-harness comparison we could find: Toolathlon 78.4 vs 70.3. James's directive was simple: intelligence over speed. So we spent the release night getting it running on the dual DGX Spark (GB10) pair.
The pair had been serving DeepSeek-V4-Flash-0731 as our production route since July. On most hardware, "free the box" is one compose-down. On GB10 unified memory it's a checklist, because everything — weights, KV cache, page cache, display server, shared-memory segments — competes for the same 119 GiB pool, and the model bind is budgeted to within half a GiB.
# Spark1 (head): compose down the DSF checkout with its exact project name
cd /home/milo/ds4-f-mia-anemll-0731-mia-aug26
COMPOSE_PROJECT_NAME=ds4-f-mia-anemll-0731-mia-aug26 \
docker compose --env-file .env.dspark -f docker-compose.dspark.yml down
# Spark2 (worker): no compose file owns the containers — remove them directly
docker ps -aq | xargs -r docker rm -f
A half-torn-down pair is worse than an untouched one: a fresh rank that rendezvouses with a dying rank hangs in Gloo for ~30 minutes before failing with nothing useful in the logs. Both ranks down, always, before either comes back up.
sudo rm -rf /dev/shm/psm_* /dev/shm/sem.* /dev/shm/torch_* /dev/shm/mp-* /dev/shm/vllm*
NCCL, PyTorch multiprocessing, and vLLM all leave segments in /dev/shm after an unclean exit. They're invisible in docker ps and they count against the same unified pool the next bind needs.
sudo sync; echo 3 | sudo tee /proc/sys/vm/drop_caches # BOTH nodes, EVERY launch
This is the single most important line on this hardware. After ~180 GiB of weights stream through the filesystem, the page cache is warm with exactly the data you no longer need — and on unified memory the GPU allocator can't reclaim it fast enough mid-load. The documented failure shape (Tony hit it too) is a bind that dies ~20 minutes in, when the allocator finally starves. free -g under-reports the problem on GB10, so you run the ritual unconditionally rather than checking first.
Our first GLM bind died mysteriously — and the old DSF container came back on its own. The teardown had worked; something was undoing it. The culprit was a root cron job on Spark1:
*/5 * * * * /usr/local/bin/spark1-inference-ready.sh # every 5 min: "is inference up? no? relaunch it"
It was written as a self-healing guard for the production stack — exactly what you want in production, exactly what you don't want on a test night, and invisible unless you go looking (crontab -l, systemctl list-timers, both nodes). We commented it out with a dated tag (#MILO-TESTING-20260826) rather than deleting it, so promote-or-rollback can restore the guard deliberately. A second surprise of the same class: Spark2's worker container had been restarted by a stale compose restart policy minutes after the first teardown. Lesson: after tearing down, watch the pair for a few minutes and confirm nothing respawns.
Both Sparks ship booting into a full GNOME session — display manager, gnome-remote-desktop, CUPS print spooler, Bluetooth, Avahi, ModemManager, colord. Headless boxes, no monitor attached:
sudo systemctl set-default multi-user.target # console-only from next boot
sudo systemctl stop gdm3 gnome-remote-desktop cups cups-browsed bluetooth avahi-daemon ModemManager colord
sudo systemctl disable gnome-remote-desktop cups cups-browsed bluetooth avahi-daemon ModemManager
Honest accounting: measured process RSS for the GUI stack was ~0 — with no session running, GNOME never spins up, so this freed almost nothing on paper. We did it anyway, for two reasons. First, on UMA a display manager's GPU/display reservations don't appear as process RSS but still come out of the model's pool. Second, one of our binds failed by exactly 0.51 GiB — at that margin, removing every nondeterministic tenant is cheap insurance. Reverting is one line (set-default graphical.target + re-enable).
docker system prune. The DSF images and checkout are the rollback path; disk was not the constraint.End state, verified on both nodes before every launch attempt: 0 containers, 115 GiB available of 119, RoCE fabric pinging at ~1.2 ms, and nothing on a timer waiting to resurrect the old stack.
Our first lane was a vLLM + Ray TP2 recipe using the LibertAIDAI NVFP4 quant (~181 GiB — routed-expert FFN quantized, everything else BF16 including the vision tower). Four attempts, three distinct failures:
| Attempt | Config | Failure |
|---|---|---|
| 1 | defaults | Crash at init: processor_config.json missing — the quant repo force-pushed a fix mid-download, leaving a split snapshot. Fixed by reconciling and serving by snapshot path. |
| 2 | gpu_mem_util 0.84, 131K ctx, MTP-4 | Clean fail at the KV check: 2.44 GiB available vs 2.95 GiB needed. Short 0.51 GiB. |
| 3 | gpu_mem_util 0.88 | Passed the KV check, then died in warmup kernel compiles with NV_ERR_NO_MEMORY in dmesg — a GPU-driver-level unified-memory OOM. |
| 4 | gpu_mem_util 0.86, MTP-3 | Aborted mid-load (we stopped the lane). |
The root cause was already documented — we just found it late. Tony's deploy report describes the identical ladder: below ~0.85 utilization the KV cache starves at 131K+; at ~0.85 and above, the MTP draft head (+~5 GB) tips GB10 unified memory into a driver-level OOM. The fix is to pin --kv-cache-memory to the safe number vLLM prints in its startup log instead of riding the utilization edge. His repo documents seven distinct day-0 bugs across vLLM, FlashInfer, and their dependency chain, with probes and receipts — the best single reference on GB10 unified-memory serving behavior we've seen.
Before continuing we did a fresh research round and had two frontier models (GPT-5.6 Sol and Grok 4.6) review the deployment plan. Both converged on the same pick: 0xSero's SGLang SM121 bundle — a pinned, published image digest, no local build, CUDA graphs on (no enforce-eager), FP8 KV cache, and NEXTN MTP-5 speculative decoding, with acceptance evidence including vision and tool calls dated the same morning.
Image: ghcr.io/0xsero/glm-5.3-flash-sglang-sm121@sha256:f9ac60ba...
(derives from lmsysorg/sglang:glm-5.3-flash + six GLM/SGLang patches)
Model: LibertAIDAI/GLM-5.3-Flash-NVFP4, 120 shards, hash-locked manifest
Serve: TP2/EP2 · 131,072 ctx · DSA attention (flashinfer_sparse_mla)
KDA via Triton · fp8_e4m3 KV · MoE flashinfer_cutlass
NEXTN MTP-5 adaptive · --tool-call-parser glm47 · --reasoning-parser glm45
Two adaptations for our fabric: the compose file's NCCL env assumed single CX7 interfaces (we run a bonded pair — bond0, rocep1s0f0/f1, 10.0.0.0/24), and the repo's validate-checkpoint.sh counts real files, so HF-cache symlinks had to be materialized first. Validation then passed on both nodes: 120 shards plus four locked metadata hashes. One wrinkle: our chat_template.jinja hash didn't match the manifest — upstream had force-pushed again — so we fetched the exact pinned revision of that one file and re-validated to hash-exact.
Worker rank launched first, head 25 seconds later. The bind completed in about 13 minutes including CUDA graph capture, and allocated an 865,088-token fp8 KV pool (6.6× the configured context) with 9.06 GiB still free — the allocation that four vLLM attempts couldn't make, achieved mostly by the fp8 KV cache halving the footprint.
All numbers below are from probes run against the live endpoint this session (single stream, warm server, wall-clock including TTFT). Operational probes, not a formal harness.
| Gate | Result |
|---|---|
| Health + model list | ✅ glm-5.3-flash, stable 7+ hours at first gate run |
| Math sanity (17×23) | ✅ 391 |
| Single tool call | ✅ clean OpenAI tool_calls[], correct JSON args, finish_reason: tool_calls |
| Vision (generated test image) | ✅ "Red." — correct, one word as asked |
| Reasoning/content separation | ✅ reasoning_content and content both preserved (thinking mode needs max_tokens headroom) |
| 3-hop tool chain (list → read → count) | ✅ correct order, correct final answer |
| Tool-error recovery (injected EACCES) | ✅ graceful — acknowledged the error and retried sensibly |
| Parallel tool calls | ✅ two calls in one turn |
| 20-hop sequential tool gauntlet | ✅ 20/20 hops, correct running total, 30s wall (~1.5s/hop) |
| Decode, prose (300 tok) | 24.7 tok/s |
| Decode, structured JSON (600 tok) | 35.7 tok/s — MTP speculative decoding runs hot on structured output |
| Long-context ladder (32K→131K needle) | ✅ 5/5 HIT at every depth — see table below |
The structured-output number is the one that matters for agent work: Hermes traffic is mostly tool calls and JSON, exactly where MTP acceptance is highest. That matches what Tony measured independently on his TP4 deployment ("MTP runs hot" on structured output). For context, DSF-0731 decodes faster in prose — the trade is deliberate: this model is smarter on agentic benchmarks and adds native vision, which DSF never had.
Decode throughput against the other live lanes in the fleet, each measured on its own stack with its own regime — these are operational probes, not same-harness benchmarks, so treat the table as orientation rather than a podium:
| Stack | Decode, prose | Decode, code/structured | Fixture decode | Notes |
|---|---|---|---|---|
| GLM-5.3-Flash (2× GB10, SGLang TP2, MTP-5) | 24.7 tok/s | 35.7 tok/s (structured JSON) | — | structured output is where MTP acceptance runs hottest; adds native vision DSF never had |
| DS4-F keys-ablit (2× GB10, vLLM, 4-bit) | 33–35 tok/s | 63–69 tok/s (code) | 87.77 tok/s (count 1→300) | the route we're replacing — faster prose, weaker on agentic benchmarks (Toolathlon 70.3 vs 78.4) |
| M5 Max oMLX 27B (dense, MLX, MTP) | 43–46 tok/s | 65–69 tok/s (code) | — | different model class entirely (27B dense); benchmark for what a small local lane gives you |
The trade is deliberate: GLM-5.3-Flash gives up roughly a third of DS4-F's prose decode to gain the benchmark wins and native vision. For agent traffic — tool calls and JSON, where the 35.7 tok/s figure lives — the gap is narrower than the prose number suggests.
Needle-in-haystack at five depths, run against the live endpoint (cold first rung, warm after; effective prefill = prompt tokens / wall clock including generation):
| Rung | Prompt tokens | Wall | Effective prefill | Needle |
|---|---|---|---|---|
| 32K | 24,025 | 67s | 356 tok/s (cold) | ✅ HIT |
| 50K | 37,524 | 32s | 1,189 tok/s | ✅ HIT |
| 80K | 60,024 | 54s | 1,107 tok/s | ✅ HIT |
| 100K | 75,023 | 63s | 1,193 tok/s | ✅ HIT |
| 131K | 98,275 | 79s | 1,246 tok/s | ✅ HIT |
Sustained ~1.2K tok/s prefill once warm was the pleasant surprise of the night — DSA sparse attention doing its job. A full-context agentic prompt costs about 80 seconds of prefill, which is workable for the long-document jobs this context size exists for.
With every gate green, we pointed the Hermes local route at the new server: provider default model → glm-5.3-flash, context pinned to 131,072 (the route previously advertised 200K for DSF — leaving that in place would have silently truncated long prompts, a reviewer catch), thinking disabled for agentic traffic, and a live ROUTE_OK verified through the Hermes CLI against the actual endpoint. DSF-0731's checkout, images, and weights remain untouched on both nodes — rollback is a ~7–15 minute relaunch plus gates, and the production watchdog stays disarmed until this promote is final.
This was a day-0 community effort; almost none of the hard work was ours.
Provenance: all measurements in this post were made against our own hardware (2× DGX Spark GB10, ConnectX-7 RoCE bonded fabric) on August 27, 2026, using the recipes credited above. Community figures cited (Toolathlon scores, Tony's TP4 numbers) are theirs, labeled as such.