Local Claude CLI finally makes sense on a DGX Spark cluster — article mark

Local Claude CLI finally makes sense on a DGX Spark cluster

A 98 GiB mixture-of-experts checkpoint does not fit on a 128 GB unified-memory box alongside its own KV cache. Two boxes and a tensor-parallel split do fit — if the memory is budgeted before launch, not after.

The workload that justifies it is a terminal coding agent. Claude CLI reads whole repositories, carries enormous context and runs for hours; quantise hard and it writes plausible nonsense, cut the context and it forgets the file it just edited. 262,144 tokens of context at 130 tok/s sustained — 242 at peak — across two nodes is where pointing it at your own hardware stops being a demo.

Every figure below is measured on one instrumented build.

Headline figures: hardware, model, context, concurrency and measured throughput 2 × 128 GB unified-memory nodes 98 GiB MoE model, split across both 262,144 token context 8 concurrent seats 242.5 tok/s peak · 129.6 median 14,490 tok/s prompt ingest, peak
Throughput figures are from 255 samples over ~20 minutes of sustained load at 8 seats.

Which Claude does it replace?

Sonnet 5. Not Opus 5. That is the evidence, not the price.

There is exactly one genuinely comparable measurement — a single harness running all three models itself, rather than splicing vendor-reported numbers from different scaffolds. On Artificial Analysis’s Intelligence Index v4.3:

EvaluationThis clusterSonnet 5Opus 5
Intelligence Index v4.3403851
AutomationBench56 %37 %57 %
Terminal-Bench 4.025 %14 %49 %
AA-Briefcase158713551645
GDPval164715011735
Long-context retrieval80 %82 %79 %
SciCode51 %54 %56 %
Humanity’s Last Exam38 %41 %55 %
AA-Omniscience−10+16+37

LiveBench agrees on the ordering: 76.2 / 76.0 / 80.1.

Read the last row before celebrating. AA-Omniscience penalises confident wrongness, and only the open model is negative. It knows less and admits it less. That is the tax you pay, and it is why the frontier model still wins anything where being wrong is expensive.

What it costs to not pay for it

Two DGX Sparks are $9,398 at current MSRP — up from $3,999 a unit, on memory supply. Measured at the wall under load, the pair draws about 300 W, so electricity is roughly €1.90 a day: 1 % of the bill it replaces, and not worth modelling carefully.

The workload actually run through this gateway — 331,125,467 input tokens against 4,903,739 output, at an 89 % cache hit rate — priced at list:

Priced asCost of that one period
Sonnet 5$180.83
Opus 5$452.06
Renting the same open weights$51.97
This clusterelectricity
Days of this workload needed to pay off two DGX Sparks 0 50 100 150 200 days vs Opus 5 20.9 days vs Sonnet 5 52.5 days vs renting the same weights 187.7
At the measured volume and an 89 % cache hit rate, on $9,398 of hardware. The honest competitor is the bottom bar, not the top two.

The strongest cost argument is not the token price — it is the cache. Anthropic’s prompt cache is billed on write, expires after five minutes, and the documentation is explicit that parallel requests on a shared prefix will not all hit it: a swarm fanning out nineteen concurrent calls mostly pays full price. A local prefix cache is free, untimed, and has no such restriction. At a 67.5 : 1 input-to-output ratio, that asymmetry is where the money is.

And the honest counter-case is not Claude at all. Renting these same weights costs about $0.15 / $0.47 per million, and a hosted NVFP4 endpoint is measured at 138.7 tok/s against this cluster’s 59.8 single-seat. Against that, the hardware needs roughly 10.8 % sustained duty to break even — against Claude it needs 1.2–3.1 %.

At a glance

Nodes2 × DGX Spark (GB10)
Memory per node128 GB unified · 121.7 GiB addressable
Interconnectdirect bonded link, 2 × 200 Gb
ModelQwen3.8 Flash-Next MoE · 98.1 GiB · hybrid NVFP4 + bf16 build · MTP drafter
Model shape125B total · 6B active · 512 experts, top-10 · 48 layers
SplitTP=2, every layer across both ranks
Context262,144 tokens
Seats the pinned pool fits6
KV pool28 GB pinned/rank = 1,708,150 tokens
KV cost16,392 B/token/rank
Resident per rank95.29 GiB
Launch requirement103.2 GiB head · 97.7 GiB worker
Cold start499 s on the run below — 762–788 s seen on others
Seats actually run8 (23 % over-subscribed, by choice)
Generated, 8 seatsmedian 129.6 · p95 176.5 · peak 242.5 tok/s
Generated, 1 seat59.8 tok/s
Prompt ingest, 8 seatsmedian 10,653 · peak 14,490 tok/s · 89 % cached
Sustained queue depth25 – 40
Layers48 total · 12 full-attention · 36 linear

Topology

ArrangementFitsContextDecodeFails by
TP=2, 2 nodesyes262,144one collective per layerlatency, if the link is not direct
Single node + offloadnominallysmallcliff once experts pagethrashing under concurrency
Pipeline parallel, 2 nodesyes262,144fewer, larger transfersbubbles at low batch

TP=2 for interactive work. It sets one hard requirement: a direct link, not a switch path — a per-layer collective runs thousands of times a second. Pin the collective library’s interface explicitly or it selects the management NIC.

How it fits together

Client, gateway and the two-rank cluster, with the fleet scheduler holding one reservation per node Claude CLI terminal agent Gateway one address, forever routes only to a held engine counts every token TP = 2 cluster rank 0 head · serves the API rank 1 worker · headless 2 × 200 Gb every layer split across both ranks 98 GiB · 262,144 ctx · 8 seats Fleet scheduler one reservation per node — it grants the whole box
The client knows one address. The gateway owns which engine is serving; the scheduler owns which machines are available.

Memory budget

Per-rank memory budget against a 121.7 GiB ceiling 0 25 50 75 100 121.7 GiB per node ceiling 121.7 rank 0 head 18.5 free 103.2 GiB rank 1 worker 24.0 free 97.7 GiB
Weights · expert tables · drafter · KV · workspace and graphs · role overhead · free.
ComponentGiB / rank
Weights36.89
Expert / PLE tables26.82
MTP drafter0.75
KV pool (pinned)26.08
Workspace + CUDA graphs4.75
Resident95.29
Role overhead — head+7.91 → 103.2
Role overhead — worker+2.41 → 97.7
Headroom — head / worker18.5 / 24.0

Those rows sum to 64.46 GiB of model bytes per rank against a 98.1 GiB checkpoint, which only reconciles one way: TP=2 splits the expert and attention weights across both ranks, and replicates the embedding tables and the drafter on each. Splitting is not the same as halving.

Three consequences:

KV and concurrency

16,392 B/token/rank × 262,144 tokens = 4.30 GB per seat. Pool 28 GB → 1,708,150 tokens → 6.52 seats → floor 6.

ContextSeatsKV per seat
262,14464.30 GB
131,072132.15 GB
65,536261.07 GB

Only 12 of 48 layers are full attention; the hybrid’s recurrent state pads the page. Take the pool size from the engine’s own startup report, not from a formula you derived.

262,144 is not an arbitrary window — it is the last one that works. On this model’s own long-context evaluation, multi-needle retrieval scores 95.98 at 128K and 93.00 at 256K, then collapses to 40.53 at 512K and 26.44 at 1M. Simple single-needle retrieval survives to 1M; reasoning across several needles does not. Running at 262,144 sits on the last measured good point, and the “1M context” claim on models of this class should be read as retrieval-only.

Raising concurrency costs no KV. The pool is fixed; seats only decide occupancy, plus CUDA graphs. Past the pool the engine queues — it does not OOM and does not refuse to start. So the ceiling is advice: warn, then store what the operator asked for.

Measured over-subscription. This cluster runs 8 seats at the full window: 8 × 262,144 = 2,097,152 tokens against a 1,708,150-token pool — 23 % beyond what fits. Nothing fails. vLLM admits what the pool holds and queues the rest, sustaining a depth of 25–37, and aggregate throughput rises: median 129.6 tok/s and peaks of 242.5, against 106.3 at six seats. A gate that had clamped 8 down to 6 would have cost that throughput to protect against a failure that does not occur.

Sizing the pool: context × concurrency

KV per seat is almost exactly 1 GiB per 65,536 tokens at 16,392 B/token/rank. Non-KV cost per rank is fixed — 77.12 GiB on the head, 71.62 on the worker — so on a 121.7 GiB box the head can hold 44.58 GiB of KV. That, not the model, is what bounds concurrency.

KV pool required by concurrency, at four context lengths, against the pinned pool and the hardware ceiling 0 8 16 24 32 40 48 concurrent seats 0 10 20 30 40 50 GiB hardware ceiling 44.6 GiB pinned pool today 26.1 GiB 32k 64k 128k 256k running here: 8 seats at 256k = 32.0 GiB 6 GiB past the pinned pool, so the rest queues
Each line is KV demand for one context length. Below the grey rule it fits the pool; below the amber rule the hardware could hold it if the pool were pinned larger.
ContextKV / seatSeats at the 28 GB pinPool for 8 seatsPool for 16 seatsMax seats on this hardware
32,7680.50 GiB48 (engine cap)4.0 GiB8.0 GiB48 (cap, not memory)
65,5361.00 GiB268.0 GiB16.0 GiB44
131,0722.00 GiB1316.0 GiB32.0 GiB22
262,1444.00 GiB632.0 GiB64.0 GiB — does not fit11

Read it as a budget, not a limit. Two consequences worth acting on:

Cold start: 499 s on this run

Cold start phases, zero to first token in 499 seconds 0s 100 200 300 400 499 base weights 209s · 41.9 % drafter weights 67s · 13.4 % encoder profiling 34s compile 14s warmup pass 54s kernel autotune 24s graph capture 25s API server up 14s first token 58s
One instrumented start. Weights are 276 s of 499; the other 223 s — 44.7 % — is everything after them. Other starts of the same pair have taken 762–788 s.
Phases%
Base weights (28 shards)20941.9
Drafter weights (28 shards again)6713.4
Encoder-cache profiling346.8
Compile142.8
Warmup pass5410.8
Kernel autotune244.8
Graph capture255.0
API server up142.8
To first token5811.6
Total499100

That is one start, instrumented end to end. Other starts of the same pair have taken 762–788 s: the phases are the same, their durations are not. Treat the shape as the finding and the total as a sample.

The shard set is walked twice — base weights, then the drafter’s. A single counter reaches 28/28 and restarts at 0, which reads as a crash loop. Two bars, not one.

Speculative decoding

95.0-second sample, both ranks, 6 seats busy:

CounterΔper second
Drafts3,28734.6
Draft tokens13,148138.4
Accepted tokens6,83672.0
Generated tokens10,100106.3
Acceptance rate6,836 / 13,14852 %
Accepted per draft2.08

Draft tokens run 1.30× above generated. Summing the wrong series inflates a dashboard by that factor; only generation_tokens is what a client receives.

Throughput

Generated tokens per second at one seat and at eight, with window spread 0 50 100 150 200 250 tok/s 1 seat 59.8 8 seats, median 129.6 8 seats, p95 176.5 8 seats, peak 242.5 8 seats, 6s spread 56.3 – 242.5
255 samples over ~20 minutes of sustained load at 8 seats. The capped bar is the spread of the same metric, not a fifth measurement.

Generated, from 255 samples across ~20 minutes of sustained load at 8 seats:

Measurementtok/s
8 seats, peak sample242.5
8 seats, p95176.5
8 seats, median129.6
8 seats, slowest sample56.3
6 seats, sustained over 95 s106.3
1 seat59.8
Per stream at 6 seats24 – 36
Prompt side, same windowValue
Peak ingest14,490 tok/s
p95 / median ingest12,484 / 10,653
Cached share89 %
Queue depth, sustained25 – 40

The spread is the point. The same cluster, same load, same six minutes: 56.3 to 242.5 tok/s, a 4.3× range across six-second windows. Any figure quoted from one window is noise. Sample over a minute, and label cumulative totals with their window — a counter that restarts with the gateway must say so.

What to use it for

The honest boundary: when you need logic, not judgement. This is not the model to reach for when the output is design — a UI, a layout, copy that has to land — or when a single answer has to be exactly right the first time. Frontier hosted models are still better at taste and at one-shot precision, and pretending otherwise wastes everyone’s afternoon.

What it is very good at is volume over time.

Fits wellWhy the numbers say so
Deep research: agent swarms reading and summarising for hours8 concurrent seats, depth 25–37 sustained, no per-token cost
Long-context reading — whole repositories, large corpora262,144 tokens, 14,490 tok/s prompt ingest at peak
Bulk transformation: extraction, classification, migration passesoutput-light, input-heavy work is where the cost curve bites hardest
Overnight and unattended runsno rate limit, no quota, no surprise bill
Anything that must not leave the buildingweights and traffic never leave the two boxes
Deterministic capacity for a fixed teamthe ceiling is yours, and it is the same at 3 a.m.
Reach for a hosted frontier modelWhy
Anything where being confidently wrong is expensiveAA-Omniscience −10 against Sonnet’s +16 and Opus’s +37 — it knows less and admits it less
Long-horizon agentic runsthe gap widens with steps, and 4-bit quantization damages the same axis: per-step tool-name hallucination roughly doubles
One-shot precision on a hard problemfewer retries beats cheaper retries
Latency-sensitive single-user turnsper stream is 24–36 tok/s under load, not 240
Rarely-run, high-stakes reasoningthe volume is too low for the economics to matter

One assumption worth dropping: that front-end work belongs on the hosted side. It is the intuitive call and the measurements do not support it — on WebDev Arena this model scores 1635, ahead of every Claude Opus 4.x (best 1559). The real divide is not logic-versus-design; it is single-shot versus sustained. Open MoE models hold their own on anything resolved in a few steps and degrade disproportionately across long horizons.

The workload that makes the case. This gateway’s cumulative counters since it started: 331,125,467 prompt tokens against 4,903,739 generated — a 67.5 : 1 input-to-output ratio, at an 89 % prompt cache hit rate. That is the signature of swarmed research agents: read enormous amounts, emit comparatively little. It is also the shape of workload that is most punishing to meter, because the bulk of it is input and the bulk of it is repetitive.

On a subscription, that pattern runs into quota long before it runs into capability. Here it runs into nothing — the marginal cost of the 331 millionth token is the electricity to produce it.

The gateway

Clients know one address. Everything else is the gateway’s problem.

ResponsibilityMissing it costs
One stable port for every enginetwo addresses in every tool
Route only to a held enginerequests stranded in a loading engine
Adopt a running pair across its own restartevery gateway deploy costs 499 s
End every request it startsleaked depth; a healthy cluster looks full
Report tokens from engine countersdashboards that disagree with the model
Answer everything that is not inferencecontrol-plane requests proxied to the model

Point Claude CLI’s base URL at the gateway and give it a model name the gateway recognises. That is the whole integration: an Anthropic-shaped API, streamed with read1 and never buffered, and nothing in the agent aware that two machines are behind it.

Handover, when a scheduler owns the boxes

EventMeasured
Reservation claim latency0.07 s
Lane restart → re-claim, node A / B30 s / 34 s
Abort bound allowed120 s
Frozen-lease sweep~35 s
Reservation lease, before → after600 s → 60 s
Drain ceiling30 s
Acquire wait before spilling120 s
Stalled handover, before the fix4 min 30 s
Co-tenant model holding the card17.6 GiB

Five failures

SymptomCauseFix
Loaded, idle cluster bypassed for the fallbackin-flight counters leaked on a timeout path that never decremented; 7 stranded requests = 7 permanent depthone helper, called from completion and every early return
Healthy serving pair torn down on deployranks predating a model label answered nothing; “no answer” read as “different model”adopt at the right generation; let the engine’s own model report condemn it
Operator picks 8 seats, gets 6a recommendation enforced as a limitstore what was asked; warn when it exceeds the pool
Every web search returns nothing, successfully3 usable providers, all rate-limited; 0 results reported as success12 providers → 166 results for the query that returned 0
Dead rank’s log gone before the post-mortemteardown removes the container firstcapture the tail on the way down

Exposure

Relay inference. Answer everything else.

ClassPreflightedAnswerProxying it costs
GET / HEAD on an API pathnogateway, 405backend CORS relayed; any site reads the reply
POST, simple content typenogateway decidessame, on the generating endpoint
OPTIONS preflightyesgateway, granting nothinga grant to the inference API
Non-API path, any method—gatewaytraversal becomes a proxied request
POST inference—relay—

Closing the preflight while leaving simple GET open achieves nothing: no preflight is sent for a simple request. Fix the rule, not the reported instance.

Checklist

Things that will bite you

A guard script you forgot. A per-minute cron doing docker start undoes a manual stop within 60 s; you will blame your own unload path.

The lucky sample. Any gate reading a 17.6 GiB-swinging number once eventually reads it at the wrong moment, and the failure lands minutes later somewhere else.

A green test that has never failed. Run each new check against the build immediately before its own fix. Two of mine passed against unfixed trees and would have certified fixes that were never written.

Prose is not evidence. A commit message describing a change is not the change. Read the diff first.

Questions? Let's Chat :)

distributed systems