GLM 5.2 on 4x GB10s - Switchless Ring - 22 tps Coding

Hi all,

I wanted to share SparkRing, an experimental inference stack I’ve been building for running GLM-5.2 across four directly connected DGX Sparks - without an Ethernet switch in the inference path.

This started as a response to “Your Mikrotik 804 is backordered until September…”

So here we are.

TL;DR

  • 4× DGX Spark

  • Four direct 200GbE ConnectX-7 links arranged as a physical ring.

  • No Ethernet switch. Management backplane is wifi/usb ethernet (future 10Gbe plans)

  • GLM-5.2 MXFP4-Experts-GPTQ, TP4/DCP4/MTP4.

  • Approximately 500K tokens of logical KV capacity.

  • Roughly 19–20 tok/s single-stream decode.

  • Roughly 50–63 tok/s aggregate at C8 in the controlled shared-prefix matrix.

  • Short workload-dependent C8 windows have reached 66.3 aggregate tok/s.

  • I’m seeing consistent 20-30+ C1 mixed use. It’s faster in-use than the benchmarks show

    EDIT: You’ll need to point your LLM agent of choice at the repo to get this stood up.

Measured results

These are 30-second sustained-decode cells. C1 is single-request throughput; C2/C4/C8 are aggregate throughput.

Prefill is standalone uncached C1 prefill. Most context lengths are single-sample scouts.

Context Prefill tok/s C1 C2 aggregate C4 aggregate C8 aggregate
8K 844 20.3 27.1 40.5 49.2
16K 876 19.0 26.4 37.9 53.3
32K 830 20.3 27.6 38.6 51.9
64K 832 20.3 27.0 39.4 50.9
128K 796 19.7 26.3 37.2 47.7

Configuration:

  • Checkpoint: aidendle94/GLM-5.2-MXFP4-Experts-GPTQ
  • TP4 / DCP4 using ag_rs
  • Dynamic MTP2/4
  • nvfp4_ds_mla KV with per-token scaling RoPE fp8
  • 4,000,000,000 KV bytes per rank
  • 500,224-token measured logical KV pool
  • 458,752-token request ceiling
  • 4,096 maximum batched tokens
  • 8 sequence slots
  • FULL_AND_PIECEWISE CUDA graphs

How the switchless part works

Stock NCCL/RoCE on a four-node ring tries to establish queue pairs between non-adjacent ranks. Those ranks are not direct layer-2 neighbors, so the communicator fails before choosing a collective algorithm.

SparkRing avoids requiring those non-adjacent hardware paths.

Its low-level transport layer is called SIRCL: Switchless Inference RDMA Collective Layer. It uses direct-neighbor RDMA RC links, mapped pinned-memory arenas on GB10, explicit sequence/doorbell ordering, and inference-specific collective plans.

The current stack includes:

  • Custom TP4 all-reduce.
  • Custom DCP query and fused output/LSE combine.
  • Custom vocabulary and all-gather paths.
  • CUDA-graph-aware command rings.
  • Explicit software decomposition/relay for communication that would otherwise require a non-adjacent connection.
  • A patched ring-only NCCL fallback for operations not yet moved onto the custom path.

Management, SSH, Gloo, and NCCL bootstrap use a separate management interface. The direct RoCE links carry inference payloads.

SparkCache

The repository also includes SparkCache, an experimental DCP4-sharded persistent context cache. It can snapshot and restore very large KV contexts from local NVMe without affecting unrelated requests.

It works, but its current snapshot/store path can still interfere with unique-context decode slightly. Eliminating that interference is one of the next priorities to remove this regression.

Current limitations

  • Only supports one model?

Next goals

  • Produce a clean-room, end-to-end public runtime build
  • Remove the remaining patched-NCCL fallback operations.
  • Improve unique-context and SparkCache interference behavior.
  • Attack the remaining MoE/expert-weight and launch-latency floor.
  • Move single-stream mixed-workload decode toward a consistent 25–30 tok/s.
  • Compare the direct ring against a proper low-latency switched fabric.
  • Explore whether the transport design generalizes beyond four Sparks, 4+N GB10s
  • Support of more models

Input wanted

I’d especially appreciate help from anyone interested in:

  • Reviewing the RDMA ordering, fences, and CUDA-graph integration
  • Running the cable/link probes on another four-Spark cluster.
  • Reproducing the runtime builder on clean hardware.
  • Comparing against a switched four-node cluster using the same model.
  • MoE expert reuse, persistent-kernel, or launch-reduction ideas
  • Testing failure recovery and long-duration stability
  • Prefill enhancements
  • Correctness testing

This builds on a lot of community work around B12X, vLLM on GB10, Aiden’s GLM checkpoint, and switchless NCCL experiments from these forums and elsewhere. Special credit to all the rtx6k discord community members who work on this stuff constantly to improve it for everyone. Full credits and provenance details are in the repository.

It is still experimental, but four-switchless-Spark GLM-5.2 inference is real and usable. Feedback, criticism, reproduction attempts, and PRs are very welcome. This was clearly coded with the help of LOTs of ai, so code cleanup, coherence, and on and on, will be an ongoing chore for a bit.

Repo needs a bit more added for maintainers / deets, will have that all added soon.

EDIT: I will have some llm benchmark pictures to add when I get back to mi casa.

Just ran, all of these are several tok/s lower than they should be due to how sparkcache is currently working. But, wanted to get some “proof” on the board.

EDIT: speeds closer to those documented in my A/B runs:

We need more supply chain issues to drive great innovation like this.

I will give this a try once I have enough cables next week (supposedly).

Excellent! Yep, as they say, "necessity is the mother of invention.”

Yes, I NEED GLM 5.2 lol.

EDIT: Repo is being updated to improve the setup workflow. Tis a mess right now, so good luck to anyone who tries piecing it together at the moment. Will be cleaned up asap.

You are pushing me towards the edge of buying 2 more sparks to make it 4, lol

The switch was a great excuse for me not to pursue more in getting more sparks, and now you are ruining it 😭

If my DAC idea works out then 4-nodes without a switch will be a reality without model specific workarounds.

I would purchase those split DACs day 1. That would take a whole lot of headache out of this. Assuming the sparks can take advantage of them, how bandwidth, latency, etc are affected. Fun experiments for sure!

Yeah, I went model specific to start, but hope to ultimately shift towards generic model support. We’ll see how it goes.

I’m currently experimenting with sparkcache. Cached context restores from each spark’s local nvme shard with zero cross-node KV traffic; only tiny quorum metadata is exchanged. Pretty neat.

Indeed, the journey is part of the fun.

I hope you manage to get your approach model agnostic, the more options the merrier for the end users.

Got lost in the weeds trying a different model, very little done to clean the mess of a repo, but this model is performing just as well in initial testing. I’m going to A/B some stuff on it and then try to pin down a good baseline config that people’s clankers should be able to get stood up. (I PROMISE THIS TIME!) Smaller size overall so easier for the sparks to throw around.

madeby561/GLM-5.2-MXFP8-NVFP4-NF3-Hybrid

40-50 tok/s range for a 4 concurrent, shared context coding session as well.

I’m realizing I need a 5th dev spark…

@FujitsuPolycom Spent all day trying to set it up today. Really appreciate you taking the time sharing this and I got really excited testing it. I finally managed to get it up and running thanks to Claude. I’m also waiting for my Mikrotik to arrive(got 8 nodes sitting here), and I’m blown away you managed to get this to work.

Either way, since you mentioned you are working on cleaning it up, this might help you or others to get the initial setup right, and hopefully you can help me out with the CUDA related issues. I asked Claude to give me a list with all the things we ran into:

1. --hf-overrides index_topk_pattern missing from launch.example.json
GLM-5.2 ships indexer weights for only 21 of 78 layers (layout is in config.json under indexer_types, which vLLM doesn’t read). Guard in deepseek_v2.py ~1166 never fires, so 57 layers run top-k on uninitialized memory. Silent because the uninitialized-weights check is disabled when quantization is set.
Hidden below index_topk=2048 (top-k picks everything, so garbage is a no-op). Above it: word salad with CJK. acceptance_gate.py can’t catch it, 30-token prompt / max_tokens 64.
Fix: pattern from SETUP.md:881. Also gained ~30% throughput (acceptance 3.14→4.17, step 354→260ms, KV 465,663→575,232).
Check: docker logs <rank> | grep -c "skip sparse MLA indexer computation" should be 57/rank, not 0.

2. Launcher peer ordering doesn’t match transport schedule
sparkring_launcher.py ~164 sorts transport_peers by rank number. tp4_schedule.cpp pairs by XOR (round0 rank^1, round1 rank^3). Correct by luck for ranks 0/1, inverted for 2/3, so both ends pick the same listen/connect role and every rendezvous times out (timed out connecting/accepting control peer, ibv_modify_qp(RTR)).
Fix: [by_rank[id^1], by_rank[id^3]]. After it, every rank has peer0 on cage0 / peer1 on cage1, matching the cable plan. Before it, ranks 2/3 had DEVICE0 on cage1.

3. VLLM_NVFP4_MLA_PER_TOKEN_SCALE=1 missing from example launch config
In SETUP.md 8.4 but not the example. Without it you’re on the legacy static-outer-scale KV record. On MXFP4-Experts-GPTQ, 448 of 2528 latent groups sit under the E4M3 subnormal floor, layers 0-7 entirely.

4. Image bakes VLLM_PREFIX_CACHE_RETENTION_INTERVAL=4096
Pinned vLLM rejects it for GLM-5.2 (no sliding-window/Mamba KV group), engine dies in kv_cache_coordinator right after KV allocation. No value fixes it since envs.py tests membership, needs docker run --env KEY with no =.

5. download-glm52.sh can’t run as shipped
Container runs --user $(id -u) but HF_HOME stays /root/.cache/huggingface (PermissionError on the token file), and the base image has HF_HUB_OFFLINE=1 baked in so snapshot_download refuses to fetch even with working internet.

6. No tool/reasoning parser in example launch config
Tool calls come back as raw text with tool_calls: null, so agent loops silently stop. glm47 works for both on this image.

7. CUDA graphs fail wrong, not slow
Full 8.4 env, MAX_QUERY_ROWS=40, CPU pins, graph ports. Boots and serves, then answers a 13k-token prompt with the single token lock. DCP1 graph arm produced identical lock. Suspect the workspace issue from TESTING_HISTORY.

Result after 1-6: 18.3 tok/s median on 500-token code completions, eager, TP4/DCP4 on MXFP4-Experts-GPTQ, clean at 13k context. ~92% of the published eager number.

Thanks again for sharing it!

Amazing! Thank you for the detailed report and putting in the effort to slog through that mess. I know that wasn’t easy. The numbers and reports from Claude are super useful!

The good/bad news? Most of those issues I have worked out now or are simply not present in the latest quant I’ve switched to. I’m trying the MXFP8/NVFP4/NF3 hybrid that I run on my 6k stack, on the sparks. It’s smaller overall and looks promising.

madeby561/GLM-5.2-MXFP8-NVFP4-NF3-Hybrid

Your feedback was used to (hopefully) provide an easier to follow recipe for getting this stood up (still tweaking). The repo is going to be on fire the rest of the evening through tomorrow while I test said layouts and update the standup scripts and model performance info. Then I’m headed out of town for 9 days so dev work may be limited :(

Currently testing FP8 MLA KV versus NVFP4 + FP8 RoPE (and eventually BF16 RoPE).

Check out that KV space.

Model: madeby561/GLM-5.2-MXFP8-NVFP4-NF3-Hybrid
TP4 DCP4 AMTP2-4 (adaptive MTP 2-4 tokens)
KV format: NVFP4 MLA + FP8 RoPE + per-token scaling
Serving: max 8 sequences, 4096 batched tokens
Weight mix: 64 NVFP4 + 192 NF3 experts
Total KV: 875,520
Max Mod length: 458,752
Quick test on above config:

Concurrently, I have my other GLM stack working out the SIRCL protocol for the generic collective layer.

EDIT: Wanted to add that sparkcache is not incorporated into this test lane. Too many balls to juggle, but will be high priority when I return. Can’t live without cache KV in real usage…

Ok, the repo should have an easier path to getting this model up, with these specs:

Model: madeby561/GLM-5.2-MXFP8-NVFP4-NF3-Hybrid
TP4 DCP4 AMTP2-4 (adaptive MTP 2-4 tokens)
KV format: NVFP4 MLA + FP8 RoPE + per-token scaling
Serving: max 8 sequences, 4096 batched tokens
Weight mix: 64 NVFP4 + 192 NF3 experts
Total KV: 875,520
Max Mod length: 458,752

Work on the generic SIRCL transport continues and I’m going to try adding a 3rd model tonight, just out of my own curiosity, even smaller, even better KLD: willfalco/GLM-5.2-EXL3-TR3-3.25bpw

Guys on the 6k discord are rocking with their quant work.

EDIT: Setup is designed to be agent-friendly. Give your preferred coding agent shell access, point it at the repo, and say:

“Set up SparkRing’s nvfp4-rope8 GLM-5.2 profile on my four directly cabled DGX Sparks. Start by reading AGENTS.md, docs/PREREQUISITES.md, and docs/QUICKSTART.md. My head node is ssh USER@HEAD_NODE_IP. Discover and validate the remaining nodes, NICs, RDMA devices, GIDs, cabling, storage, and existing model files using the included tooling. Show me the dry-run plan before making changes or stopping any running services. Then run the bootstrap and verify all four ranks plus the API.”

Repository: FujitsuPolycom/sparkring

Awesome! I will spin it up and report back. Thanks again.

//EDIT

Worked like a charm and the good news is I got similiar results this time!

Thanks again for sharing this. It’s incredible what this community is capable of! Let me know if you need any further testing. I got another 4 nodes sitting around. I don’t fully understand the technical aspects of this, but how feasible would it be to scale this up, or is 4 nodes the “real” limit to this? Regardless, I’m amazing you got this working, and I’m pretty sure this community will take it even further! Cheers!

Really appreciate you giving this a go and good to hear it was relatively straightforward!

I’ve got that 3.25bpw quant working now as well. I’ll get it added as a second profile since it’s even smaller than NF3 and has as good deal better KLD across the board. Only tradeoff currently seems to be prefill, I’m stuck in the 500s with this quant, hoping it’s just some config I’ve missed because decode is just as good or better than the NF3 quant. Could be calculation lose.

More info on EXL3 quant soon! It’s got room for 1M KV.

Also, again, im building on top of TONS of work from the RTX6000 discord community. The sparkinfer/b12x, the fork of vllm, etc. The optimizations in those codebases are just as, if not more important to the performance here than my little harness that lets everything ‘speak’