Blog

Running Laguna S 2.1 on a DGX Spark

How I fit and serve Poolside Laguna S 2.1 on a single NVIDIA DGX Spark using NVFP4, vLLM, DFlash speculative decoding, and a hardened thinking-off…

Jul 30, 20269 min readAILLMLaguna S 2.1DGX SparkNVIDIAvLLMNVFP4DFlashLocal AIBenchmarksMLOps

After months of considering the cost, I bought a DGX Spark.

I wanted a machine that would let me study current AI systems up close. Running the models myself gives me room to test their limits, break their configurations, and understand the machinery behind the API.

I started as an AI sceptic. I changed my mind after working with the models, and now each experiment gives me another problem to solve.

My first serious test after setting up the Spark was Poolside's Laguna S 2.1 NVFP4. Poolside publishes the specifications and serving instructions on the Hugging Face model card.

Laguna S 2.1 is Poolside's open-weight model for agentic coding and long-horizon work. It has 117.6 billion parameters and uses a Mixture-of-Experts (MoE) architecture. For each token, a router chooses from 256 specialised experts and uses one shared expert. The model activates about 8.5B parameters per token, so each step uses a fraction of its full capacity.

The model has 48 layers: 36 use sliding-window attention for efficiency and 12 use global attention to carry information across the full context. This combination lets Laguna support a context window of up to 262,144 tokens while keeping the KV-cache requirements under control.

With those specifications, Laguna was one of the most capable coding models I could fit on one DGX Spark. My first runs produced thinking loops, wasted tokens, and broken tool calls. The serving configuration needed more work than I expected.

After several days of tuning, I found a setup that returns valid tool calls and generates at a useful speed.

I have included the container configuration, deployment scripts, benchmark harnesses, and results in my DGX Spark Field Notes repository.

The working recipe

I started with Blackwellboy's Laguna S 2.1 testing lab. The repository documents a container recipe, tuning experiments, long-context tests, and configurations that failed during testing.

I adapted that work into my own deployment using:

  • the poolside/Laguna-S-2.1-NVFP4 checkpoint
  • the matching DFlash NVFP4 draft model
  • vLLM with the native FlashInfer NVFP4 path
  • seven speculative tokens
  • FP8 KV cache, prefix caching, and chunked prefill
  • the native poolside_v1 reasoning and tool-call parsers
  • a dedicated thinking-off serving lane

Fitting the model in memory

Laguna S 2.1 has 117.6 billion parameters. In BF16, each parameter needs two bytes, so the weights require about 235 GB of memory (219 GiB). The KV cache, CUDA graphs, runtime buffers, and operating system need more.

The DGX Spark has 128 GB of unified memory, far short of the BF16 model's requirements. Laguna activates 8.5B parameters for each token, but the server must load every expert. The router can select a different expert at each layer and for each token.

The NVFP4 checkpoint reduces the model weights to about 72 GB. The matched DFlash draft adds 2.2 GB, bringing the two checkpoints to about 74 GB. The remaining unified memory holds the runtime and my 12 GiB FP8 KV cache.

NVFP4 and DFlash

I serve the model through vLLM, using its OpenAI-compatible API and native FlashInfer NVFP4 path on the GB10. NVFP4 is NVIDIA's Blackwell-native 4-bit floating-point format. Conventional weight-only Q4 formats quantise the weights. NVFP4 runs both weights and activations at four bits on the GB10 Tensor Cores.

Laguna follows the NVFP4_EXPERTS_ONLY_CFG strategy. Its large routed-expert layers use NVFP4, while attention, the shared expert, router, and output head remain in BF16. I used this split to free memory while preserving speed and output quality.

DFlash uses a small draft model to propose future tokens in parallel. Laguna verifies the proposals and accepts the matching tokens. I use seven speculative tokens because the DGX Spark tuning sweep measured the best balance at that setting. Longer drafts reduced acceptance and throughput.

Enforcing thinking-off

Disabling thinking stopped most of the loops, token waste, and tool-calling errors in my tests. A server default did not provide enough protection because clients could re-enable thinking on individual requests. My server now rejects those requests.

I kept Poolside's official poolside_v1 parser. The wrong parser or chat template can make a working model return tool markup as text. With Poolside's parser and the corrected template, vLLM returns structured tool_calls.

My repository contains two profiles:

  • interactive: seven speculative tokens and max-num-seqs=8, which is what I use for interactive coding;
  • production: seven speculative tokens and max-num-seqs=32, which gives better throughput with more concurrent work.

You can find the complete container recipe, deployment guide, and runbook in the repo.

Before starting, make sure you have at least 100 GB of free storage. The two pinned model snapshots use about 74 GB, and you still need space for the runtime image, compiled-kernel cache, and benchmark artifacts.

Run the deployment:

git clone https://github.com/MovieMaker93/dgx-spark-field-notes.git
cd dgx-spark-field-notes/models/lagunalab2.1/deploy

./bootstrap-user-tools.sh
./download-models.sh
./generate-api-key.sh
./service.sh install
./service.sh logs

Then, from another terminal:

./smoke-test.sh

The service starts on 127.0.0.1:8000, requires Bearer authentication, and stays private until it passes the benchmark gate. I generate the token with ./generate-api-key.sh. The script stores it with 0600 permissions at ${XDG_CONFIG_HOME:-$HOME/.config}/laguna-s21/vllm_api_key, outside the repository, and does not print or commit it.

The first cold start takes 10 to 15 minutes after the image build. The server loads the model shards, compiles the FlashInfer kernels, and captures the CUDA graphs. Use a chat completion from smoke-test.sh as the readiness check; /v1/models can respond before the model can generate.

Secure remote access with Tailscale and Pi

After the checks pass, I expose vLLM to my tailnet and keep it off the LAN. I followed NVIDIA's DGX Spark Tailscale playbook, checked the Spark with tailscale status and tailscale ip -4, then ran ./service.sh expose-tailnet. The script binds vLLM to the first Tailscale IPv4 address and keeps Bearer authentication enabled. Tailscale encrypts traffic between clients.

On the machine running the Pi coding agent, I copy the key through a secure channel to ~/.config/laguna-s21/vllm_api_key, run chmod 600 on it, and add this provider to ~/.pi/agent/models.json:

{
  "providers": {
    "laguna-spark": {
      "baseUrl": "http://<spark-tailnet-name>:8000/v1",
      "api": "openai-completions",
      "apiKey": "!cat ~/.config/laguna-s21/vllm_api_key",
      "authHeader": true,
      "compat": {
        "supportsStore": false,
        "supportsDeveloperRole": false,
        "supportsReasoningEffort": false,
        "maxTokensField": "max_tokens"
      },
      "models": [{
        "id": "poolside/Laguna-S-2.1-NVFP4",
        "name": "Laguna S 2.1 (DGX Spark)",
        "reasoning": false,
        "input": ["text"],
        "contextWindow": 262144,
        "maxTokens": 8192,
        "cost": {
          "input": 0,
          "output": 0,
          "cacheRead": 0,
          "cacheWrite": 0
        }
      }]
    }
  }
}

Pi reads the token from the protected file at request time and sends it as an Authorization: Bearer header, so the JSON file contains no secret. If Pi runs on the Spark, I use http://127.0.0.1:8000/v1. In my first end-to-end test, Laguna selected Pi's read tool, Pi read the requested file, and Laguna used the result in its next turn.

Benchmarks

An HTTP 200 response says little about model quality. Tool parsing, long-context recall, concurrency, and code generation can still fail.

I ran these tests:

  • cold-prefill and generation speed from 512 to 128K prompt tokens
  • native tool selection, exact arguments, and tool-result replay
  • concurrent request isolation
  • semantic recall at long context
  • agent-shaped tool, code, JSON, and prose prompts
  • all 164 HumanEval and HumanEval+ tasks

I measured these results on one DGX Spark with the interactive vLLM profile, seven DFlash speculative tokens, and thinking off:

CheckResult
Peak cold-prefill throughput3,129.6 tok/s at 16K
Cold-prefill throughput at 128K1,920.5 tok/s
Generation at 128K23.2 tok/s
Time to first token at 128K68.96 seconds
Semantic recall12/12 facts at 239,935 prompt tokens
Native tool tests6/6 passed
Concurrent isolation8/8 requests passed
Agent-shaped serving median23.21 tok/s
Code-generation median45.91 tok/s
HumanEval154/164 (93.90% pass@1)
HumanEval+148/164 (90.24% pass@1)
Laguna S 2.1 serving and qualification dashboard
Laguna S 2.1 context benchmark

I report separate scores because each test covers a different failure mode. One aggregate number would hide a broken tool parser behind fast generation or strong code results.

The repository includes scripts for each test. Run the required suite with:

cd models/lagunalab2.1/deploy
./bootstrap-benchmark-tools.sh
./benchmarks/run-all-required.sh

The results snapshot includes the dashboard, context chart, CSV data, and interpretation notes. I measured these results on my machine with this serving profile. They do not establish Laguna as the best model for other hardware or workloads.

One final health check

Run the doctor from Blackwellboy's Model Serving Minefield, even if the endpoint appears healthy.

My repo now includes a wrapper that downloads a pinned, checksum-verified version of the doctor and runs it against the local endpoint:

cd models/lagunalab2.1/deploy
./run-minefield-doctor.sh

I thought the endpoint was ready. The doctor found three problems:

  • the server accepted invented request fields
  • clients could override the thinking-off default
  • the chat template inserted empty <think></think> blocks into multi-turn history

Normal conversations did not expose these bugs. I added strict request validation and enforced the thinking-off policy. I also corrected the chat template to omit empty reasoning blocks. After rebuilding, the doctor made 15 requests and reported zero problems.

Since applying those fixes, I have run the model for a full day and generated millions of tokens without the tool-calling issues I was seeing before.

Next steps

I can now use Laguna for coding instead of tuning the server.

I get better results in English, so I write both prompts and agent instructions in English. I have not run a multilingual benchmark.

My next experiment uses Qwen3.6 27B NVFP4 with a DFlash draft. I have pinned the recipe and prepared the same-host qualification tests. I will publish numbers after I measure them on my Spark, then test Qwen3.6 35B and compare both models with Laguna.

I use the repository as a lab notebook for model deployments, failed attempts, and benchmarks on the Spark.

I am also studying how LLMs work and building one from scratch. Running Laguna taught me how to serve a model; building one should show me what happens inside the transformer.

If you try this recipe, let me know how it goes. I want to hear whether thinking-off and the native Poolside parser solve the same tool-calling problems for you.

Follow and support my experiments

I share updates from my DGX Spark experiments and model tests on X.

If these field notes saved you time, you can support my experiments by buying me a coffee.

Follow @devopsfortunato on X ☕ Buy me a coffee

References