howtospark
Recipes

1 × Spark256K ctx· model max122.74 tok/s@ 512llama.cppthinking offvanilla

Serve poolside's agentic-coding Laguna-XS 2.1 (33B MoE, ~3B active) on one DGX Spark as a 20.3 GB Q4_K_M GGUF: 90.4 tok/s single-stream with no draft, 122.7 with the DFlash speculator, and the model's full native 262,144-token window open the whole time — which, measured, costs about 1 GiB and zero decode speed. Needs a llama.cpp build carrying the laguna architecture port; stock llama.cpp cannot load this model at all.

spark .1 · single nodehead
82 GiB free
Expert planes18.9 GiBQ4_K_M
114 usable
32* / 114 GiB
28% of usable
Expert planesDraft modelKV cacheActivations + graphsOS reserve

* segment sizes marked with an asterisk are estimates pending a measured run

Eval scores

(compare all)

Across this model's recipes

HumanEval · median score by recipe

Bench card

No measured runs yet — be the first: install the spark-benchmark skill.

Same quantized model

Overview

Laguna-XS 2.1 is poolside's 33B agentic-coding MoE — 256 routed experts plus one shared, 8 firing per token, so about 3B active — with a native 262K context via YaRN. This recipe serves it through llama.cpp as a self-quantized 20.3 GB Q4_K_M GGUF, and it exists as a deliberate counterweight to the NVFP4 vLLM recipe for the same model, because the two engines answer the speed question in completely different places. Start with the thing nobody publishes: on this checkpoint the *quantization scope*, not the engine, is what sets the no-draft ceiling. Every poolside quant (-FP8, -NVFP4, -INT4) carries an ignore list that leaves q/k/v/o/g_proj in BF16, and since only 8 of 256 experts fire per token, that unquantized attention is ~82% of the bytes a token actually reads — all three decode within 2 tok/s of each other at ~42 tok/s. llama.cpp's Q4_K_M has no such scruples: it quantizes attention too, at ~4.5 bits, and measures 90.4 tok/s here with no draft at all. That is a 2.1x no-draft advantage that comes purely from what got quantized. Then the picture inverts. Both stacks can drive the checkpoint's companion DFlash speculator, but they are not equally good at it: vLLM 0.25.1's native draft class reaches ~67% acceptance, while our llama.cpp port of the same draft reaches 28.8–33.5% on the same code workload — re-measured in this session, so the gap is stable, and it is a deficiency in the port rather than a property of the checkpoint. The result is that llama.cpp gets 1.34–1.36x from the draft (90.4 → 122.7 tok/s at a 512-token prompt) where vLLM gets 3.17x, and vLLM's 133.6 tok/s ends up ahead of llama.cpp's 122.7 despite starting from less than half the baseline. So: llama.cpp if you want the best answer without a speculator, a smaller file, and a three-second warm start; vLLM if you are willing to pin a specific version and want the top number. The second thing this recipe settles is context, and the answer is that there is nothing to settle. The July 2026 configs for this model served at -c 10240, on the assumption that a bigger window would cost memory or speed. Measured here: with -c 262144 the server holds 35 GiB steady state and decodes 90.4 / 86.7 / 77.0 tok/s at 512 / 2,048 / 8,192-token prompts, inside run-to-run noise of the 89.4 / 86.3 / 77.1 measured at -c 10240 on 2026-07-12. Open the whole window. The real long-context cost is prefill, not memory: a 131,104-token prompt takes 178 s to first token (690 tok/s prefill) against 2,405 tok/s at 8K. That prompt and a 250,033-token one both served correctly; 270,043 tokens is rejected with a clean HTTP 400, so the published 262,144 is a bracketed number rather than a config value. The load-bearing caveat is that none of this runs on stock llama.cpp — the laguna architecture is a local port (upstreamed as ggml-org/llama.cpp#25595, not merged at time of writing), and the DFlash draft support sits on top of it.

  • 1 x DGX Spark (GB10, sm_121) — 33B MoE in a 20.3 GB Q4_K_M GGUF, ~35 GiB resident
  • 90.4 tok/s single-stream with no draft — 2.1x the same model's vLLM NVFP4 build (41.6), because Q4_K_M quantizes attention and the poolside quants do not
  • 122.7 tok/s with the DFlash speculator at a 512-token code prompt (1.36x); 118.2 at 2K, 102.9 at 8K
  • Draft acceptance 33.5% / 32.7% / 28.8% at 512 / 2K / 8K — well below the ~67% vLLM gets from the same draft; the gap is in our llama.cpp port
  • Full native 262,144-token context open costs ~1 GiB and no measurable decode speed vs the old -c 10240 configs
  • Context bracketed with real requests: 250,033 tokens serves, 270,043 returns a clean 400
  • Long context is prefill-bound, not memory-bound: 178 s to first token at 131K tokens
  • Cold start ~15 s, warm restart ~3 s — no JIT wall, unlike the vLLM path's ~15 min first boot
  • 50–63 W measured during the runs; an 8K-prompt request costs on the order of 200 J
  • Requires a llama.cpp build with the laguna arch port — stock llama.cpp cannot load this model

Software requirements

  • 1 x DGX Spark (GB10, sm_121), NVIDIA driver >= 580, CUDA llama.cpp build for sm_121
  • llama.cpp with the `laguna` architecture port — verified on build b9891-11-g7ad9bd2 (the server reports b25-7ad9bd2), worktree ~/Dev/llama.cpp-laguna, branch laguna-support. Upstream PR ggml-org/llama.cpp#25595 carries the functional arch code; the DFlash draft support (--spec-type draft-dflash) is local on top of it and is not upstream
  • Stock llama.cpp fails to load this GGUF outright — the laguna arch is unknown to it. Serve from the laguna worktree build, NOT ~/Dev/llama.cpp
  • ~21 GB free disk for the quantized GGUF + draft; ~88 GB transiently if you convert and quantize from the BF16 checkpoint yourself
  • Base model: poolside/Laguna-XS-2.1 (license OpenMDW-1.1). Draft: poolside/Laguna-XS-2.1-DFlash
  • No published Q4_K_M GGUF exists for this model — you quantize it yourself (step 2)

Quick start

  1. 1

    Build llama.cpp with the laguna port

    Stock llama.cpp does not know the `laguna` architecture and will refuse the GGUF. Build the fork worktree that carries the arch port plus the DFlash draft implementation. Everything below assumes that build; reaching for the pristine ~/Dev/llama.cpp binary out of muscle memory is the single most common way to fail this recipe.

    # arch port upstreamed as ggml-org/llama.cpp#25595 (not merged at time of writing);
    # the draft-dflash speculator on top of it is local.
    git clone -b laguna-support https://github.com/Sapid-Labs/llama.cpp ~/Dev/llama.cpp-laguna
    cd ~/Dev/llama.cpp-laguna
    cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=121
    cmake --build build --config Release -j
    
    ~/Dev/llama.cpp-laguna/build/bin/llama-server --version   # expect b25-7ad9bd2 or laterbash

    The two deltas that mattered when porting from the near-identical STEP35 arch were not halving n_rot_full (the converter writes the partial rope dim 64 directly) and the attention gate being softplus, not sigmoid. If you port this yourself and generation comes out coherent-but-subtly-wrong, look there first.

  2. 2

    Quantize the checkpoint to Q4_K_M

    No Q4_K_M GGUF is published for Laguna-XS 2.1, so convert the BF16 safetensors and quantize locally. This is the only step that needs real disk: the intermediate BF16 GGUF is 66.9 GB and the Q4_K_M output is 20.3 GB.

    hf download poolside/Laguna-XS-2.1 --local-dir ~/models/hf/Laguna-XS-2.1
    
    cd ~/Dev/llama.cpp-laguna
    python3 convert_hf_to_gguf.py ~/models/hf/Laguna-XS-2.1 \
      --outfile ~/models/gguf/Laguna-XS-2.1-BF16.gguf --outtype bf16
    
    ./build/bin/llama-quantize \
      ~/models/gguf/Laguna-XS-2.1-BF16.gguf \
      ~/models/gguf/Laguna-XS-2.1-Q4_K_M.gguf Q4_K_M
    
    # rm ~/models/gguf/Laguna-XS-2.1-BF16.gguf   # once you're happy, reclaim 66.9 GBbash

    Q8_0 (35.6 GB) and BF16 (66.9 GB) are the other two rungs and both work; measured decode at a 512-token prompt is 62.9 and 33.5 tok/s against Q4_K_M's 90.4. Decode tracks file size almost exactly, which is the expected shape for a ~3B-active MoE on a bandwidth-bound part.

  3. 3

    Serve at the full native context

    Open the whole 262,144-token window. This is the correction the recipe exists to publish: measured back to back against the old -c 10240 configs, it costs about 1 GiB of resident memory and nothing in decode speed. Use --parallel 1 for single-user work — in llama.cpp, -c is the total context split across slots, so --parallel 4 would leave each request with 65,536 tokens.

    ~/Dev/llama.cpp-laguna/build/bin/llama-server \
      -m ~/models/gguf/Laguna-XS-2.1-Q4_K_M.gguf \
      -c 262144 --parallel 1 -ngl 99 -fa on \
      --jinja --reasoning-budget 0 \
      --host 127.0.0.1 --port 8080
    
    # expect, ~15 s cold / ~3 s warm:
    #   srv load_model: initializing, n_slots = 1, n_ctx_slot = 262144
    #   srv llama_server: listening on http://127.0.0.1:8080
    free -g | sed -n '2p'   # measured 35 GiB used of 121bash

    --jinja is mandatory: Laguna's chat template throws 'this custom template is not supported' in llama.cpp's default template engine. --reasoning-budget 0 disables the interleaved thinking; leave it on and a coding request spends its whole output budget on reasoning_content, which reads to a naive client as an empty response.

  4. 4

    Add the DFlash speculator

    The checkpoint has a companion DFlash draft. On this llama.cpp port it buys a consistent ~1.35x on code — smaller than the 3.17x the vLLM path gets from the same draft, because this port's acceptance is ~0.30 against vLLM's ~0.67 — but it is free speed at temperature 0 and costs under 1 GiB.

    hf download poolside/Laguna-XS-2.1-DFlash --local-dir ~/models/hf/Laguna-XS-2.1-DFlash
    python3 ~/Dev/llama.cpp-laguna/convert_hf_to_gguf.py \
      ~/models/hf/Laguna-XS-2.1-DFlash \
      --outfile ~/models/gguf/Laguna-XS-2.1-DFlash-F16.gguf --outtype f16
    
    ~/Dev/llama.cpp-laguna/build/bin/llama-server \
      -m ~/models/gguf/Laguna-XS-2.1-Q4_K_M.gguf \
      -md ~/models/gguf/Laguna-XS-2.1-DFlash-F16.gguf \
      --spec-type draft-dflash --spec-draft-n-max 15 \
      -c 262144 --parallel 1 -ngl 99 -fa on \
      --jinja --reasoning-budget 0 \
      --host 127.0.0.1 --port 8080
    
    # expect on startup:
    #   common_speculative_impl_draft_dflash: - n_max=15, n_min=0, p_min=0.00
    #   common_speculative_impl_draft_dflash: - block_size=16, mask_token_id=12, n_extract=5
    #   common_speculative_impl_draft_dflash: - draft block attention: causalbash

    Keep the drafter's trained sliding window. The -noswa conversion of this draft drops to ~0.001 acceptance at a 7.8K-token prompt (against 0.280 with SWA), so if you have both GGUFs around, the one WITHOUT 'noswa' in the name is the one you want.

  5. 5

    Verify, then measure warm

    Confirm the window is real before you publish a context number, and take decode from a warm server. Both checks below were run in this session and their outputs are the numbers on this page.

    # 1. bracket the context with real requests, not with the config value
    #    250,033-token prompt -> 200 OK, 197 s to first token
    #    270,043-token prompt -> 400 exceed_context_size_error
    
    # 2. warm up, then measure
    curl -s localhost:8080/v1/chat/completions -H 'Content-Type: application/json' \
      -d '{"model":"laguna","messages":[{"role":"user","content":"warmup"}],"max_tokens":64,"temperature":0}' > /dev/null
    
    python3 bench/harness.py \
      bench/configs/laguna-xs-2-1--llama-cpp--q4-k-m--262k--spec-dflash.json \
      --rows-out data/benchmarks/laguna-xs-2-1.json
    
    # 3. read acceptance straight out of the server log
    grep 'draft acceptance' /tmp/lg-df.log | tail -5bash

    llama.cpp prints per-task lines like `draft acceptance = 0.33974 (212 accepted / 624 generated), mean len = 5.93`, which is the cheapest honest way to tell whether the speculator is earning its keep on your workload.

Key vLLM parameters

ParameterValuePurpose
-c262144 (the model's full native window)Total context, split across --parallel slots. The July 2026 configs used 10240 on the assumption that opening the window would cost something. It does not: measured back to back, -c 262144 decodes 90.42 / 86.72 / 76.95 tok/s at 512 / 2,048 / 8,192-token prompts against 89.4 / 86.3 / 77.1 at -c 10240 — inside run-to-run noise — for ~1 GiB more resident. Laguna's sliding-window-512 attention is why. Open it.
--parallel1 for single-userIn llama.cpp, -c is the TOTAL context divided by slots, not per-slot. --parallel 4 with -c 262144 gives each request 65,536 tokens, which is how you accidentally publish a quarter of the context you think you have. Check the `n_ctx_slot` line in the log, not the -c you passed. For batch work the older 4-slot configs measured 45.3 tok/s per stream at concurrency 4 with a 512-token prompt.
--spec-type / -md / --spec-draft-n-maxdraft-dflash / Laguna-XS-2.1-DFlash-F16.gguf / 15Drives the checkpoint's own DFlash speculator. Measured this session, same server, only the draft added: 122.74 / 118.23 / 102.87 tok/s at 512 / 2,048 / 8,192-token code prompts against 90.42 / 86.72 / 76.95 without — 1.36x / 1.36x / 1.34x. Acceptance 0.335 / 0.327 / 0.288, mean accepted run ~5.5–6.2 tokens. That is far below the ~0.67 vLLM 0.25.1 gets from the same draft on the same model, which is a known deficiency in this llama.cpp port.
--jinjarequiredLaguna ships a custom chat template that llama.cpp's built-in template engine rejects with 'this custom template is not supported'. Without --jinja, llama-server and llama-completion will not chat with this model at all.
--reasoning-budget0 for coding endpointsLaguna does interleaved thinking. Left on, a short coding request spends its entire output budget in reasoning_content and a client reading only `content` sees an empty string — which also silently wrecks any benchmark that counts output tokens. All numbers on this page are with thinking off.
-faonFlash attention, kept on for every measurement here. It is part of what makes the 262K window's KV affordable alongside the sliding-window layers.
-ngl99Offload every layer. The Spark's memory is unified, so there is no reason to leave layers on the 'CPU' side — the bytes sit in the same pool either way and the GPU path is faster.

API usage

Chat completion (thinking off)

With --reasoning-budget 0 the answer arrives in choices[0].message.content. Without it, look in reasoning_content instead — that is where the model puts its interleaved thinking, and a client that only reads `content` will think the server returned nothing.

curl -s localhost:8080/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"laguna",
       "messages":[{"role":"user","content":"Write a Python function that parses a CSV into typed dicts."}],
       "max_tokens":512, "temperature":0}'bash

Check the context window is really open

Measured: a 250,033-token prompt serves in 197 s to first token; 270,043 is rejected. Budget for prefill, not for memory — at 131K the prefill rate is 690 tok/s against 2,405 tok/s at 8K.

# tokenize a long document, then send it
curl -s localhost:8080/tokenize -H 'Content-Type: application/json' \
  -d '{"content":"<your long document>"}' \
  | python3 -c 'import json,sys;print(len(json.load(sys.stdin)["tokens"]))'

# past the window you get a clean, specific error rather than a hang:
# {"error":{"code":400,"message":"request (270043 tokens) exceeds the available
#  context size (262144 tokens), try increasing it",
#  "type":"exceed_context_size_error","n_prompt_tokens":270043,"n_ctx":262144}}bash

Read draft acceptance while it serves

Acceptance is content-dependent on this draft: ~0.29–0.34 on code, ~0.11 on reasoning prose. If you are seeing single digits, check you are not using the -noswa conversion of the draft.

grep 'draft acceptance' /path/to/server.log | tail -5
# slot print_timing: id  0 | task 14 | draft acceptance = 0.33974
#   (212 accepted / 624 generated), mean len = 5.93bash

Troubleshooting

llama-server refuses the GGUF with an unknown-architecture error
You are on stock llama.cpp. The `laguna` arch is a port that lives in the fork worktree (~/Dev/llama.cpp-laguna, branch laguna-support; upstreamed as ggml-org/llama.cpp#25595, not merged at time of writing). Serve from that build's binary, not ~/Dev/llama.cpp/build/bin/llama-server. This is the most common failure for this recipe and it is easy to hit by muscle memory.
'this custom template is not supported'
Add --jinja. Laguna's chat template is not expressible in llama.cpp's built-in template engine. This affects llama-server and llama-completion equally.
The model answers with an empty string, or a benchmark reports far fewer tokens than max_tokens
Thinking is on. Laguna does interleaved reasoning and llama.cpp puts it in `reasoning_content`, not `content`, so a client reading only `content` sees nothing while the output budget is consumed. Pass --reasoning-budget 0 for coding/agent endpoints. Every number on this page was measured with thinking off; rows measured with it on are not comparable.
You set -c 262144 but requests fail past ~65K tokens
You also set --parallel 4 (or left it above 1). In llama.cpp, -c is the TOTAL context divided across slots, so four slots at -c 262144 gives each request 65,536 tokens. Use --parallel 1 for single-user serving, and check the log line `n_ctx_slot = 262144` rather than trusting the -c you passed.
Draft acceptance is near zero at long prompts but fine at short ones
You are using a draft GGUF converted without the drafter's trained sliding window. Measured: the -noswa conversion drops to 0.001 acceptance at a 7.8K-token prompt while the SWA-512 one holds 0.280. Keep the trained SWA. The merged vLLM reference implementation strips it, which is likely an unreported upstream bug — do not copy that choice into a llama.cpp conversion.
The DFlash speedup is only ~1.35x, but the vLLM recipe for this model claims 3.17x
Both numbers are real and both were measured on this hardware. The difference is acceptance: vLLM 0.25.1's native laguna_dflash reaches ~0.67 on code, this llama.cpp port reaches 0.288–0.335 (re-confirmed 2026-07-27). The port is numerically deficient somewhere in the draft path; the checkpoint is fine. The compensating fact is that llama.cpp's no-draft baseline is already 2.1x vLLM's (90.4 vs 41.6 tok/s), because Q4_K_M quantizes attention while the poolside NVFP4/FP8/INT4 checkpoints leave it BF16. Net: vLLM+DFlash at 133.6 still beats llama.cpp+DFlash at 122.7, but llama.cpp wins decisively if you are not running a speculator.
A long prompt seems to hang for minutes with no output
Expected — this config is prefill-bound at length, not memory-bound. Measured: a 131,104-token prompt takes 178 s to first token at 690 tok/s prefill; a 250,033-token one took 197 s at a reported 607 tok/s, but that request shared a long prefix with the previous one and so benefited from llama.cpp's slot cache — treat the 250K figure as a floor on the true cost, not a clean measurement. At 8K the prefill rate is 2,405 tok/s. A quarter-million-token prompt is a fine trade for a document you ask several questions about and a bad one for interactive work.
You switch to Q8_0 or BF16 for quality headroom and decode collapses
Working as intended on a bandwidth-bound part: decode tracks file size almost exactly on this ~3B-active MoE. Measured at a 512-token prompt: Q4_K_M 90.4 tok/s (20.3 GB), Q8_0 62.9 (35.6 GB), BF16 33.5 (66.9 GB). If you need the bigger quant, note the DFlash draft pays proportionally MORE there (1.71x on BF16 vs 1.36x on Q4_K_M), because the draft overhead is a smaller fraction of the target passes it saves.
pkill kills your SSH session instead of the server
Over SSH your own command line contains the pattern, so `pkill -f llama-server` matches the shell running it. Put the kill in a script file on the remote host. Note that the usual '[l]lama-server' character-class guard does NOT save you if the same command line also contains an unguarded copy of the path — e.g. the serve command you are about to launch in the same one-liner.

Revision history

What has changed on this page since it was published, and what it measured. Newest first.

  1. re-measured

    Stub promoted to a full recipe: serving steps, memory budget, key flags and troubleshooting written, and both arms re-measured on llama.cpp build b9891-11-g7ad9bd2 with the model's full native 262,144-token context open instead of the -c 10240 the July configs used.

    Opening the window costs nothing measurable: no-draft decode went 89.4 / 86.3 / 77.1 tok/s at -c 10240 (2026-07-12) → 90.42 / 86.72 / 76.95 at -c 262144 for 512 / 2,048 / 8,192-token prompts, at ~1 GiB more resident. The DFlash arm went 126.7 / 117.1 / 110.7 → 122.74 / 118.23 / 102.87 over the same change; the 8K drop is attributed to the benchmark-harness fix landed 2026-07-27 (early-stopping runs were inflating decode), not to the context. Context is now bracketed with real requests — 250,033 tokens serves, 270,043 returns HTTP 400.

Source repoOpenMDW-1.1
Memory budget

The headline memory fact for this recipe is a negative one: opening the model's full 262,144-token context is nearly free, so there is no memory fight to have. Measured on a 121 GiB node with `free -g`, steady state after load: 35 GiB resident with the full 262K window and no draft, 36 GiB with the DFlash draft loaded, against a ~4 GiB idle OS baseline. The harness's own peak-memory sampling agrees and shows how little the workload adds on top: 37.2 / 38.0 / 39.8 GiB at 512 / 2,048 / 8,192-token prompts with no draft, 38.2 / 39.3 / 41.1 GiB with the draft. The reason is Laguna's attention shape — sliding-window 512 on most layers, only a minority full-attention — so a 262,144-token KV allocation lands in the low single-digit GiB rather than the tens. The practical consequence is that the `-c 10240` the July 2026 benchmark configs used was leaving the entire native window on the table for no memory saving at all, which is what this refresh corrected. Segment split below: the GGUF and draft figures are the files on disk; KV and compute buffers are back-computed from the measured 36 GiB steady state minus the OS baseline, so treat that split (not the total) as an estimate.