Recipe · One DGX Spark

Nemotron 3.5 Bolt on one DGX Spark, with TensorFold.

Serve NVIDIA's Nemotron 3.5 Bolt 74B-A9B from a single DGX Spark through an OpenAI-compatible API, with up to 16 streams at once at a 262,144-token window each and image and video input. It runs on TensorFold's Zig engine with NVIDIA's 24GB checkpoint and its LiLiCorr drafter, and loading it takes 51.1 GB, so most of the Spark stays free for other work.

Get the free recipe

Install with an AI agent

Install this model

Paste this into an AI coding agent that can run commands on the machine that will serve the model (the head node, for a Spark cluster), such as Claude Code or Codex. It reads the recipe, adapts it to your setup, starts the server and checks that it works.


        
Nemotron 3.5 Bolt on a DGX Spark: a green wave of points over a dark floor

Performance

Measured, not promised.

One request65.8tok/s, prose
Eight at once176.1tok/s combined
Context262ktokens a stream, up to 16 at once
Prefill1,375tok/s, 32k prompt

One DGX Spark, the recipe's defaults (16 streams, a 262,144-token window each, bf16 KV cache, vision and the LiLiCorr drafter on), measured with sparkDash against the running server. "Aggregate" is the tokens of every request at once; "first token" is the time to the first token.

Decode, prose

Aggregate across the concurrent requests, per request, and time to first token.

RequestsAggregatePer requestFirst token
165.8 tok/s65.8 tok/s62 ms
297.0 tok/s49.6 tok/s117 ms
4131.2 tok/s35.0 tok/s219 ms
8176.1 tok/s24.2 tok/s487 ms
16171.2 tok/s23.2 tok/s6.20 s

Decode, code

The same server, with code prompts: the drafter guesses code more often.

RequestsAggregatePer requestFirst token
1119.5 tok/s119.5 tok/s65 ms
2159.4 tok/s81.5 tok/s107 ms
4210.9 tok/s58.0 tok/s229 ms
8278.4 tok/s38.4 tok/s597 ms
16267.7 tok/s36.0 tok/s8.05 s

Prefill

How fast a cold prompt is read before the reply starts, one request.

PromptPrefillFirst token
4,135 tokens1,575.6 tok/s2.80 s
8,231 tokens1,536.8 tok/s5.53 s
16,424 tokens1,480.8 tok/s11.27 s
32,809 tokens1,375.1 tok/s24.03 s
65,579 tokens1,212.3 tok/s54.27 s
131,115 tokens986.8 tok/s133.04 s

Room to spare

Loading the model with the defaults takes 51.1 GB of the Spark's unified memory: 16 streams, a 262,144-token window each, vision and drafter on. That leaves roughly 70 GB of the 121 GB for whatever else you run beside it.

A chat turn or a repeated system prompt reads only its new tail: the prompt cache keeps each stream's state at its marks, and replies are identical with and without it. A follow-up turn that repeats 50 images and 4 videos (30,500 tokens) answers in 1.7 s.

Speed preview

What 65.8 tok/s feels like.

Sample text played at the measured decode speed: one request at 65.8 tok/s, or four at once at 35.0 tok/s each (131.2 combined), at about four characters a token. Switch between them in the window.

TensorFold · Nemotron-3.5-Bolt · 1× DGX Spark
65.8 tok/sSample text, played at the measured speed

How it is built

A new backend, written for Bolt.

TensorFold's Nemotron family serves Lightning 30B-A3B. Bolt is a different shape: 88 blocks, an expert count that changes by layer, and experts packed at about 2 bits. So the recipe adds a Bolt backend to TensorFold's Zig engine. The HTTP server, chat template, thinking budget, round loop, stop handling and sampler stay TensorFold's own; the forward pass is the recipe's CUDA library, libbolt.so.

The checkpoint

74B parameters, 9B active, 9 of up to 496 experts a token. Of the 88 blocks, 40 are Mamba, 40 are MoE and 8 are attention. NVIDIA's 24GB pack is 22.45 GiB: IQ2_XXS experts, NVFP4 Mamba projections, Q8_0 attention, latent projections and head, and the C-RADIO v4-H vision tower.

Weights stay packed

At one request a token reads about 5 GB, not the 14.6 GB of an fp16 expansion. Each matrix is repacked at load so the dense GEMMs run on tensor cores, and the routed experts run on int8 tensor cores. A block of rows, embedding to head, replays as one CUDA graph.

Drafts, checked

The checkpoint has no MTP head, so the recipe serves it with NVIDIA's LiLiCorr drafter (about 1.3 GB). It proposes seven tokens a stream each round, for every stream in one pass, and TensorFold verifies them, choosing how many to check from costs measured at load. Rollback after a partial accept is a pointer flip.

Memory follows use

Every stream has the full 262,144-token window, but its attention rows are mapped 4,096 tokens at a time and given back when it ends. Only the 0.4 GiB of recurrent state a stream is held from load, so sixteen streams start small and grow with their conversations.

Three bugs had made the old engine's drafter accept 0 of 7 tokens; all three are fixed: the shared expert read the norm of the updated residual instead of the block input, Mamba's dt was clamped at 0.001 (the config's limit is 0), and the drafter applied its grouped convolution before o_proj instead of after. More in the recipe's engine notes and CHANGELOG.

The engine

Powered by TensorFold.

TensorFold, by Ash Hart, is an open-source engine that serves language models on Apple Silicon and NVIDIA GPUs through an OpenAI-compatible API. Each model family brings its own kernels and its own draft verification, tuned for that model instead of one generic path for all.

Lane backends

A model can bring its own forward pass and keep everything else: TensorFold's server, chat template, sampler and speculation loop drive it through verify, keep and draft. That is how Bolt plugs in.

Apple Silicon and NVIDIA

MLX on Macs, and CUDA on NVIDIA GPUs from compute capability 8.9: Ada (RTX 40), Hopper and Blackwell, including the DGX Spark's GB10 and the RTX 50 series.

Many model families

Qwen3.8 Flash Next, Qwen3.8-27B, GLM-5.3-Flash, Nemotron 3.5 Lightning, Gemma 4 and DeepSeek-V4-Flash, among others, each with its own kernels.

In this recipe

TensorFold's Zig engine, tensorfold-native, at a pinned 1.0.12 commit, with its RADIO image tower and video decoder. The recipe's patches register the Bolt family and its video input, and libbolt.so runs the model.

What you need

One Spark, half of it.

A DGX Spark

Or another GB10 system with 128 GB unified memory. Sixteen streams need about 55 GiB free when the server starts: weights, the vision tower, recurrent state, a working cache, the prompt cache and a 10 GiB floor. start.sh stops before launching below that.

Docker

Docker with the NVIDIA container runtime, and your user in the docker group.

Disk

About 23 GiB for the checkpoint under ~/.cache/huggingface, 1.3 GB for the drafter, plus the image under Docker's root; scripts/prepare.sh checks both.

Optional

The hf CLI and a Hugging Face token in ~/.cache/huggingface/token or HF_TOKEN. The checkpoint is public, so neither is required.

Quick start

Three lines, that's all.

1

Clone and start

git clone https://github.com/MiaAI-Lab/Nemotron-3.5-Bolt-TensorFold-DGX-Spark.git
cd Nemotron-3.5-Bolt-TensorFold-DGX-Spark
./start.sh

The first run builds the image (TensorFold at the pinned commit, the recipe's patches and its engine), downloads the checkpoint (23 GiB) and the LiLiCorr drafter (1.3 GB), checks them against the recipe's contract, starts the server and sends a smoke request. Later runs reuse the image and the files, and load in about 45 seconds.

2

Talk to it

Any OpenAI client works with base_url = "http://<spark-address>:8888/v1" and the model Nemotron-3.5-Bolt. The model thinks before it answers, and the reasoning and the answer share max_tokens, so give replies enough; a request without one gets 32,768.

curl -s http://<spark-address>:8888/v1/chat/completions -H 'Content-Type: application/json' -d '{
  "model": "Nemotron-3.5-Bolt",
  "messages": [{"role": "user", "content": "Write a Python fibonacci function."}],
  "max_tokens": 1000
}'
3

Run it day to day

./start.sh restart                            # restart it, e.g. after changing a setting
./stop.sh                                     # stop the server
docker logs -f nemotron35-bolt-tf             # server log
DRY_RUN=1 ./start.sh                          # print the docker command, change nothing

Images and video

Up to 64 images a request.

The checkpoint's own C-RADIO tower (1.7 GB) turns pictures and video frames into tokens, through TensorFold's image tower. Send them as OpenAI-style content parts: an image_url part, or a video_url part for a clip.

ImagesVideos
FormatsPNG and JPEG, up to 10 MB, 8,192 px a side and 16 megapixelsMP4 or WebM (H.264, HEVC, VP8, VP9, MPEG-4), up to 16 MB a clip, an hour long
Per requestUp to 64Up to 4, 20 MB in all
Tokens256 to 3,328 an image (a 512 px square about 260)2 frames a second, at most 256; a 6 s clip is 1,700 tokens, a 2-minute 720p clip 36,500

Tested: 50 images of 2560×1920 (165,600 tokens) in one chat, every one read correctly. Data URLs by default; VISION_URLS=1 also fetches public HTTPS links. VISION=0 serves text only.

Thinking and the API

Thinking on, by default.

The model thinks before it answers. "chat_template_kwargs": {"enable_thinking": false} answers directly, and {"enable_thinking": true, "medium_effort": true} is the model card's medium effort. A request's own temperature, top_p and seed override the defaults (1.0 and 0.95; temperature: 0 is greedy).

  • OpenAI-compatible on port 8888, model id Nemotron-3.5-Bolt.
  • Images on the chat route, the Anthropic route (image blocks) and the Responses route (input_image).
  • A prompt that shares a prefix with an earlier one, including the same pictures, is read from the prompt cache.
  • Not served: logprobs, grammar-constrained decoding, int8 or int4 KV caches, WebP, GIF and BMP pictures.

Memory and settings

Sixteen streams in 51 GB.

Where the memory goes

The weights are 22.45 GiB on disk. Each stream holds 0.4 GiB from load: two copies of its Mamba state, so a draft window can be undone, plus the drafter's context. Its attention rows grow with the conversation, 8 KB a token in bf16, and are given back when it ends. The 8 GiB prompt cache fills with use.

Windowbf16 attention cache
32,768 tokens0.25 GiB
131,072 tokens1.00 GiB
262,144 tokens2.00 GiB (1.03 GiB with KV_DTYPE=fp8)

For a fixed footprint, KV_PIN=1 reserves every stream's whole window at load (16 GiB for 8 × 262,144 in bf16), so a start that cannot have it fails at once. Keep at least 10 GiB free under load: on this machine, running out of unified memory freezes it.

The ones you might change

Every setting lives in scripts/config.sh; override one from the environment (PARALLEL=2 ./start.sh) or a .env file. The full list is in the README.

VariableDefaultMeaning
PARALLEL16Streams decoded together (1 to 16); more requests queue
CONTEXT262144Prompt + reply window of every stream, the model's maximum
KV_DTYPEbf16fp8 halves the cache, at no speed cost
KV_PIN01 reserves every stream's whole window at load
PROMPT_CACHE_GIB8Kept prompt states; 0 turns the cache off
DRAFTS1LiLiCorr drafts; 0 serves one row at a time
VISION1Image and video input; 0 serves text only
THINKING1Think before answering; 0 answers directly
MAX_TOKENS32768Reply length for a request without max_tokens
PORT8888Where the API listens

Licenses

Free, with a few rules.

  • The recipe is MIT.
  • The model weights, downloaded from Hugging Face and not part of the recipe, are under NVIDIA's OpenMDW License Agreement, version 1.1.
  • The image is based on NVIDIA's PyTorch container; by pulling or running it you accept NVIDIA's terms, which the container prints at every start. It also holds FFmpeg (decoders only, LGPL), libjpeg-turbo and ggml's IQ2_XXS tables (MIT).

Credits

Built on the work of others.

The full list is in the recipe's CREDITS.md.

Run it on your Spark.

Free and open, with every patch and every number published.