howtospark
Recipes

BF16256K ctx· model max28.93 tok/s@ 2KvLLMthinking onvanilla

The unquantized BF16 build of NVIDIA's Nemotron 3 Nano 30B-A3B on one DGX Spark. It serves the same full 262,144-token context as the NVFP4 recipe — but it costs 68 GiB instead of 28.3, and decodes at 28.9 tok/s instead of 61.2. This is the page that tells you what NVFP4 is actually buying.

spark .1 · single nodepeer
46 GiB free
Expert planes54.8* GiBBF16
114 usable
68* / 114 GiB
60% of usable
Expert planesDense weightsKV cacheActivations + graphsOS reserve

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

Eval scores

(compare all)

Bench card

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

Overview

This is the reference build of Nemotron 3 Nano 30B-A3B — NVIDIA's own BF16 weights, no quantization step. It exists on this site mostly so the NVFP4 recipe has something honest to be compared against, and the comparison was run on one node, one engine build, both warm, in a single session. The headline is that **you keep the context and you pay for the speed**. The architecture is what makes the long window cheap — 52 layers in the pattern `MEMEM*EMEMEM*…`, of which 23 are Mamba mixers, 23 are 128-expert MoE blocks and only 6 are full attention — and that is a property of the model, not of the numeric format. So BF16 serves the same native 262,144-token window on one Spark with the same 3 GiB pinned KV pool, and a 260,395-token request completes end to end in 88.3 s. What changes is everything on the bandwidth axis: 58.82 GiB of weights resident instead of 18.66, a 68 GiB working set instead of 28.3, and decode at 28.93 tok/s instead of 61.16 at a matched 2,041-token prompt. That is a **2.11x decode penalty** for the unquantized weights. It is worth noting that 2.11x is *less* than the checkpoint-size ratio of 3.15x, and the gap is real rather than measurement slop. Decode time on this model is not purely weight-streaming: the Mamba state updates, the six attention layers, sampling and kernel-launch overhead all cost the same in either build, so shrinking the expert planes by roughly 3.5x moves the total by only 2.11x. If you are budgeting a quantization win on a hybrid Mamba-MoE, expect the byte ratio to overpromise. Decode is nearly flat with fill level, same as the NVFP4 build: 28.97 / 28.93 / 28.80 / 28.37 / 26.55 tok/s at 503 / 2,041 / 8,203 / 32,774 / 131,267-token prompts — 8% decay across a 261x range of prompt length. Prefill is where the long window is paid for: TTFT runs 293 ms at 512 tokens, 456 ms at 2,048, 1.49 s at 8,192, 6.03 s at 32,768 and 32.8 s at 131,072. **Pick this build only if you need it.** The realistic reasons are that you are validating a quantization and need a reference, or you have measured a quality regression on NVFP4 that matters for your workload. Nobody has yet run an eval comparing the two on this model, so "NVFP4 is fine" is currently an assumption on this site, not a measurement. Absent that, the NVFP4 recipe is the better default on a Spark by a wide margin: same context, 2.11x the decode speed, and 40 GiB of the node handed back.

  • Same 262,144-token context as the NVFP4 build — the long window comes from the architecture, not the quantization
  • 28.93 tok/s single-stream decode at a 2,041-token prompt — 2.11x slower than the NVFP4 sibling's 61.16 on the same node and engine build
  • 68 GiB serving working set vs 28.3 GiB for NVFP4; 58.82 GiB of that is weights
  • KV costs 6,390 bytes/token here vs 3,281 on NVFP4: that checkpoint declares kv_cache_quant_algo FP8, this one declares nothing
  • The 3.15x checkpoint-size ratio only buys 2.11x decode — the non-weight half of a hybrid's decode step does not shrink
  • Weight load takes 397 s: at 58.82 GiB the checkpoint exceeds 90% of vLLM's view of available RAM, which disables its prefetch path
  • No speculative decoding — no MTP head in the checkpoint and no published draft

Software requirements

  • vLLM 0.24.0 — with torch 2.11.0+cu130. Matched deliberately to the NVFP4 recipe's build so the two sets of numbers are comparable. Last verified 2026-07-27 on vLLM 0.24.0.
  • NVIDIA driver 580.159.03 — GB10 is sm_121. Nothing FP4-specific is in play on this build — it is a plain BF16 MoE forward pass.
  • Checkpoint nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16 — 13 safetensors shards, ~59 GB. The unsuffixed base repo 401s — the BF16 base is published as -BF16.

Quick start

  1. 1

    Free the node

    This recipe uses one Spark, and unlike the NVFP4 build it needs most of it. Make sure nothing else is holding memory before you start — an idle GB10 should sit around 4 GiB used. ```bash ssh Spark-1 'pkill -f "[v]llm serve"; ray stop --force 2>/dev/null; free -g' ``` The character class in `[v]llm` matters: `pkill -f "vllm serve"` matches the SSH command's own argv and kills your session before it kills the server.

  2. 2

    Download the checkpoint

    ```bash hf download nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B-BF16 \ --local-dir ~/models/hf/Nemotron-3-Nano-30B-A3B-BF16 ``` About 59 GB across 13 shards — plan for that on a node that is probably already near full. Confirm there are no `*.incomplete` files before serving.

  3. 3

    Serve at the full 262,144-token context

    ```bash PATH="$HOME/venvs/vllm/bin:$PATH" \ VLLM_USE_DEEP_GEMM=0 TORCHINDUCTOR_COMPILE_THREADS=2 MAX_JOBS=4 \ vllm serve ~/models/hf/Nemotron-3-Nano-30B-A3B-BF16 \ --served-model-name Nemotron-3-Nano-30B-A3B-BF16 \ --max-model-len 262144 \ --kv-cache-memory-bytes 3221225472 \ --max-num-seqs 4 \ --max-num-batched-tokens 4096 \ --limit-mm-per-prompt '{"image": 0, "video": 0}' \ --port 8000 ``` Startup is about **7.5 minutes**, and almost all of it is one line: `Loading weights took 397.00 seconds`. Then 48 s of profiling, compile and warmup. Two log lines confirm the config landed: ``` Initial free memory 114.5 GiB, reserved 3.0 GiB memory for KV Cache as specified by kv_cache_memory_bytes GPU KV cache size: 504,123 tokens ``` If you see 982,061 tokens instead of 504,123, you are looking at the NVFP4 build's log — the pools differ by exactly 2x. Do **not** add `--gpu-memory-utilization` alongside the pinned pool. Pinning KV makes vLLM skip memory profiling entirely, and the utilization fraction is unreliable on unified memory anyway.

  4. 4

    Warm it before you measure

    Expert planes fault in from NVMe on first touch, and at 59 GB there is a lot to fault in. Send a few hundred tokens of real traffic before you trust any number: ```bash curl -s http://127.0.0.1:8000/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{"model":"Nemotron-3-Nano-30B-A3B-BF16","messages":[{"role":"user","content":"Write 600 words about tidal energy."}],"max_tokens":800}' >/dev/null ``` The tell that a benchmark pass was still cold is that the 512-token scenario measures *slower* than the 2,048-token one. Warm, the curve is monotonic, and it is very stable: two full harness passes back to back agreed within 0.5% at every prompt length.

  5. 5

    Optional — split the reasoning out of the response

    This model emits DeepSeek-R1-style `<think>` reasoning before its answer unless you turn it off per request. The checkpoint ships a matching vLLM parser: ```bash M=~/models/hf/Nemotron-3-Nano-30B-A3B-BF16 # ...add to the serve command above: --reasoning-parser-plugin "$M/nano_v3_reasoning_parser.py" \ --reasoning-parser nano_v3 ``` The reasoning then arrives in `choices[0].message.reasoning` (that field name on vLLM 0.24.0, not `reasoning_content`) and only the answer in `.content`. Measured free on the NVFP4 sibling; not separately re-timed on this build. The published figures below were taken without it.

Key vLLM parameters

ParameterValuePurpose
--max-model-len262144The model's native window, and it is as affordable in BF16 as it is in NVFP4 because only 6 of 52 layers hold a KV cache. Verified with real requests rather than inherited from the model card: a 260,395-token prompt served successfully in 88.3 s, and a prompt totalling 262,145 tokens was refused with an HTTP 400 in 0.4 s rather than crashing the engine.
--kv-cache-memory-bytes3221225472Pin the KV pool at 3 GiB rather than steering it with --gpu-memory-utilization; on unified memory the utilization fraction counts system-wide usage and is not a reliable lever. 3 GiB yields 504,123 tokens = 6,390 bytes/token = 1.92x concurrency at the full window. Keep this identical to the NVFP4 recipe if you are comparing the two — it is what exposes that the same pin buys half as many tokens here.
--max-num-seqs4Nothing derives from this value on this config: there is no speculative decoding feeding a CUDA-graph capture size, and the KV pool is pinned rather than profiled. Left at 4 to match the NVFP4 recipe. Note that concurrency is a worse deal on this build — at 4 concurrent 512-token streams each one decodes at 17.16 tok/s for 66.2 tok/s aggregate, against 28.97 single-stream.
--max-num-batched-tokens4096Bounds the chunked-prefill chunk. On a recipe whose selling point is a 262K window this is a context lever, not just a latency one — an unbounded chunk is what turns a long prefill into an engine death on this hardware. 4,096 sustained ~5,500 tok/s prefill at 8K and served a 260K-token prompt without incident.
no quantization flagsNOT set (deliberate)Deliberately none, and that is the point of the recipe: plain BF16 weights, no hf_quant_config in the checkpoint, so vLLM also gives you a BF16 KV cache. If you want the FP8 KV without quantizing the weights you have to ask for it explicitly with --kv-cache-dtype fp8, which is a different experiment and is not what these numbers measure.
VLLM_USE_DEEP_GEMM0DeepGEMM has no sm_121 path that helps this checkpoint; leaving it enabled costs startup time for nothing — and startup already costs 7.5 minutes here.
TORCHINDUCTOR_COMPILE_THREADS / MAX_JOBS2 / 4Bounds the compiler, not the model. Kernel JIT on GB10 runs one nvcc per core by default at several GiB each, which can burst more memory than the model itself and get the box OOM-killed during the profiling run. That risk is higher on this build than on NVFP4, because the weights alone already hold 58.82 GiB.
no speculative configNOT set (deliberate)There is nothing to enable. The checkpoint has no MTP head — no num_nextn_predict_layers, no mtp_num_hidden_layers, and no extra layer beyond the 52 — and NVIDIA publishes no EAGLE/DFlash draft. An external draft is not a quick win either: spec-decode against a Mamba/linear-attention hybrid needs the recurrent state partitioned into speculative and non-speculative slots, which is engine work rather than a flag.

API usage

Chat

curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "Nemotron-3-Nano-30B-A3B-BF16",
    "messages": [{"role": "user", "content": "What is 17*23?"}],
    "max_tokens": 300
  }'bash

Turn the reasoning off

# The model thinks by default. Suppress it per request via the chat template:
curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "Nemotron-3-Nano-30B-A3B-BF16",
    "messages": [{"role": "user", "content": "What is 17*23?"}],
    "chat_template_kwargs": {"enable_thinking": false},
    "max_tokens": 300
  }'bash

Check what your KV cache actually costs

# The one line that distinguishes this build from the NVFP4 one at runtime.
# Divide the pinned pool by the reported token count:
grep 'GPU KV cache size' /tmp/bf16-serve.log
#   GPU KV cache size: 504,123 tokens
#   3221225472 / 504123 = 6,390 bytes/token   (BF16 KV)
#
# The NVFP4 sibling, same pin, same 6 attention layers:
#   GPU KV cache size: 982,061 tokens
#   3221225472 / 982061 = 3,281 bytes/token   (FP8 KV, from the checkpoint's
#                                              hf_quant_config kv_cache_quant_algo)bash

Troubleshooting

You want to know what NVFP4 actually costs you in quality, and whether this build is worth its 2.11x decode penalty.
Nobody has measured it yet, on this site or anywhere we could find, and we are not going to pretend otherwise. What is measured is the price: 28.93 tok/s against 61.16 at a matched 2,041-token prompt, and 68 GiB of working set against 28.3 — same node, same vLLM 0.24.0, both warm, both measured on 2026-07-27. Running `evals/run_eval.py` against the two endpoints in one session is what would settle it. Until someone does, treat this recipe as the reference build you validate against, not as the one you serve.
Startup takes over seven minutes and the log says auto-prefetch is disabled.
Expected, and it is a unified-memory artifact rather than a fault. vLLM prints `Auto-prefetch is disabled because the filesystem (EXT4) is not a recognized network FS and the checkpoint size (58.82 GiB) exceeds 90% of available RAM (54.33 GiB)`. On a Spark, weights and system memory come out of the same 128 GB pool, so a 59 GB checkpoint is genuinely close to all the RAM vLLM believes it has, and the prefetch path switches itself off. Weight load then takes 397 s. Budget seven and a half minutes for every restart and get the serve command right the first time — the NVFP4 build boots in about three.
A 3 GiB KV pin gives you half the tokens you expected from reading the NVFP4 recipe.
It does, and the geometry is not the reason — both builds have the same 6 full-attention layers with 2 KV heads at head_dim 128. The NVFP4 checkpoint ships an `hf_quant_config` declaring `kv_cache_quant_algo: FP8`, which vLLM honours (`kv_cache_dtype=fp8_e4m3` in the startup config); the BF16 repo ships no such file and gets a BF16 cache. So 504,123 tokens here vs 982,061 there, 6,390 bytes/token vs 3,281. The general lesson: when you compare quant siblings, do not assume KV bytes/token is held constant — quantizing the weights may have quantized the KV too. You can ask for the cheaper cache explicitly with `--kv-cache-dtype fp8` if you want it without the quantized weights.
The benchmark rows report `peakMemoryGb: 15.7`, which is less than the weights.
That figure is wrong for this model and should be ignored — the weight residency alone is 58.82 GiB, so 15.7 GB cannot be a working set. Use `free -g` on the node while serving and subtract the idle baseline instead; that is where the 68 GiB in the memory budget above comes from (72 GiB serving, 4 GiB idle measured on the same node after teardown). Peak-memory reporting is not reliable on GB10 unified memory, where `nvidia-smi` returns `[N/A]` for `memory.used` entirely.
Decode at a 512-token prompt benchmarks slower than at 2,048 tokens.
The run is cold, and at 59 GB of weights there is more to fault in from NVMe than on any quantized build. Warm the server with a few hundred tokens of traffic, then run the harness twice and keep the second pass. Warm, this model is exceptionally reproducible: two consecutive full passes agreed within 0.5% at every prompt length, so if your numbers move more than that, something else is running on the node.
A very long prompt looks like it has hung.
It is prefilling, and on this model prefill is the slow half. Measured TTFT: 293 ms at 512 tokens, 456 ms at 2,048, 1.49 s at 8,192, 6.03 s at 32,768, 32.8 s at 131,072 — and a single 260,395-token request took 88.3 s end to end. Prefill throughput peaks near 5,500 tok/s around an 8K prompt and falls to ~4,000 tok/s at 128K. Nothing is wrong; the 262K window is real, it just is not free.
You batch four streams and the aggregate throughput disappoints.
At 4 concurrent 512-token streams this build gives 17.16 tok/s per stream and 66.2 tok/s aggregate, against 28.97 tok/s single-stream — so batching buys 2.3x total throughput while more than halving each user's speed. That is a normal bandwidth-bound trade, but it is a worse one here than on the NVFP4 build, because there is more weight traffic per token to begin with. If you need concurrency on a Spark, the quantized checkpoint is the lever to pull first, not `--max-num-seqs`.
`pkill -f "vllm serve"` over SSH drops your connection instead of stopping the server.
The remote command's own argv contains the pattern, so pkill matches the shell running it. Use a character class: `pkill -f "[v]llm serve"`. The same trap fires if the *body* of a heredoc you are writing over SSH contains the string `vllm serve` — write the script locally and scp it across instead.

Revision history

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

  1. performanceaction needed

    Promoted from a benchmark-only stub to a full recipe: memory budget, serve command, flags and troubleshooting, all measured on one Spark against vLLM 0.24.0 — and matched deliberately to the NVFP4 recipe's engine build and KV pin so the two pages can be read against each other.

    Context 10,240 → 262,144 tokens (bracketed with a real 260,395-token request that served in 88.3 s, and a 262,145-token request that was cleanly refused). Decode at a matched ~2,042-token prompt is 28.73 → 28.93 tok/s — i.e. unchanged, reproducing the 2026-07-07 rows on the same node, so this recipe was never rotting, only unfinished. The KV pool is now pinned at 3 GiB (504,123 tokens) rather than sized by --gpu-memory-utilization 0.62, and --max-num-seqs drops 8 → 4 to match the sibling recipe.

Memory budget

The serving working set is 68 GiB of the node's 114 GiB usable — measured as `free -g` while serving (72 GiB) minus the 4 GiB idle baseline read on the same node after teardown. That splits into 58.82 GiB of weights (vLLM prints the checkpoint size in its startup log and this build loads it verbatim), the 3 GiB pinned KV pool, and ~6.2 GiB of activations, Mamba conv/SSM state, CUDA-graph pools and allocator slack taken as the balance. The split of the 58.82 GiB between routed experts and everything else is apportioned from parameter counts, not measured per-tensor: 29.4B of the 31.6B parameters are routed experts, which at 2 bytes each is 54.8 GiB, leaving ~4.1 GiB for the Mamba mixers, the 6 attention layers, the shared expert, the norms and the 131,072 x 2,688 embeddings plus untied lm_head. One number here is not what the NVFP4 sibling reports, and the difference is the point: KV costs **6,390 bytes/token** on this build against 3,281 on NVFP4. Both models have the same 6 full-attention layers with 2 KV heads at head_dim 128, so the geometry is identical — but the NVFP4 checkpoint's `hf_quant_config` declares `kv_cache_quant_algo: FP8` and vLLM honours it, while the BF16 repo has no such file and gets a BF16 cache. Quantizing the weights quantized the KV too. A 3 GiB pin still holds 504,123 tokens here, which is 1.92x concurrency at the full window, so it does not change what you can serve — only what it costs. Ignore the `peakMemoryGb` figure on the benchmark rows for this model: the harness reports 15.7 GB, which is below the weight residency and is not a usable memory number on GB10 unified memory.