Inference engine · NVIDIA DGX Spark

Rust all the way down.

Ferrite is an LLM inference engine for NVIDIA DGX Spark (GB10), written in Rust from the HTTP server down to the GPU kernels. It serves Qwen3.8-27B on one Spark or across two connected over RoCE, behind an OpenAI-compatible API with streaming, thinking, tool calls, a prompt cache and 128K context.

  • Rust GPU kernels
  • sm_121a
  • EXL3 3.0 bpw
  • NVFP4 prompts
  • DFlash2
  • TP2 over RoCE
  • Apache-2.0

Status: experimental. One model (Qwen3.8-27B), one GPU (GB10, sm_121a). At most 4 requests run at once with DFlash2 drafting; more wait in a queue. It works, but expect rough edges. Issues and measurements from other Sparks are welcome.

Why it exists

How far down can Rust go?

Ferrite started as an experiment by a TensorFold contributor: how far down the stack can Rust go in a real inference engine, all the way into the GPU kernels, and what does it cost? Much of what makes Ferrite work was learned from TensorFold and exllamav3 first.

If you want a mature engine that serves many models, use TensorFold. Ferrite is a focused, single-model look at the Rust question.

What is Rust, and what is not

Rust: every GPU kernel, the CUDA driver bindings, the model and scheduler, speculative decoding, the two-node fabric (RDMA over libibverbs), the tokenizer and the HTTP server.

Not Rust: NVIDIA's ptxas assembler (at build time), the CUDA driver itself, libibverbs, and NCCL for two-node prompt batches. The full ledger, with reasons and replacement plans, is in NON-RUST.md.

Design

From a Rust crate to a GB10 kernel

The kernels are ordinary Rust crates. rustc's nvptx64-nvidia-cuda backend turns them into PTX, NVIDIA's ptxas assembles that for sm_121a, and the cubins are embedded in the server binary and launched through a thin Rust binding to the CUDA driver.

  1. kernels/k-*GPU kernels as Rust crates
  2. rustc nvptx64nightly toolchain, target-cpu sm_121a
  3. PTXone module per kernel crate
  4. ptxas -arch=sm_121aCUDA 13, build time only
  5. fe-servercubins embedded, launched via the CUDA driver API

01EXL3 decode GEMVs

Decode reads Qwen3.8-27B as an EXL3 quant at 3.0 bits per weight (a mixed-width quant with a 5-bit head) at about 90% of GB10's memory bandwidth.

The matrix-vector kernels batch up to 48 rows into one weight decode, and every row stays bit-identical to single-row decoding.

02NVFP4 / FP8 prompt GEMMs

Prompts run through NVIDIA's NVFP4/FP8 checkpoint on tensor-core GEMMs written in Rust (block-scaled mma.sync: NVFP4 with ue4m3 block scales, FP8 e4m3), with the activation quantizers on the GPU too.

Without that checkpoint, Ferrite rebuilds bf16 prompt weights from the EXL3 quant.

03Chunked Gated DeltaNet

Qwen3.8's linear-attention layers carry a recurrent state per request. Prompt rows go through them in chunks of 32 rows, using the chunkwise WY / UT form of the delta rule on TF32 tensor cores (a 3xTF32 split for fp32 accuracy), and the state is written back for decoding.

04DFlash2 verify trees and copy rows

Lossless speculative decoding. The DFlash2 block drafter proposes tokens that are verified as best-first trees in one pass of the big model.

Copy rows propose the continuation of earlier text, which pays off most on code edits. The model's own MTP head is the fallback.

05Two Sparks over RoCE

With two nodes, each Spark holds half of every layer. Small reductions go over RDMA through a Rust proxy kernel, and NCCL takes the large prompt batches. Node A runs the server, node B follows.

06OpenAI-compatible server

Chat completions with streaming, thinking on or off, tool calls, 128K context and up to 4 requests at once (more wait in a queue). Any OpenAI-compatible client works: Open WebUI, Continue, Cline and other coding agents.

A request's output does not depend on what else is running, and speculation never changes the text. Both are checked before every merge.

07Prompt cache for agents

Each conversation is snapshotted at turn boundaries and resumes from the longest match. A conversation that loses its slot is parked in a memory pool, and a system prompt shared by several agents is computed once.

Prompts are cut at the same points with or without a resume, so a resumed reply is the same, token for token, as one computed in full.

Measured

Ferrite's numbers

Two DGX Sparks connected over RoCE, Qwen3.8-27B, greedy decoding, fixed prompts. Medians of 3-5 runs. Generation figures are tokens per second while generating; prompt processing is listed separately. End-to-end figures that include the time to the first token are in docs/benchmarks.md.

473tok/s
Code, 4 requests (aggregate)
273tok/s
Code, 1 request
582tok/s
Code edits, 1 request
~3,550tok/s
Prompt processing, 2K context
0.021
KL divergence vs BF16, code (lower is better)
WorkloadFerrite
Code, 4 requests (aggregate)473
Code, 1 request273
Code, 1 request, 32K context238
Code edits, 1 request (rewrite a file it was shown)582
Code edits, 4 requests (aggregate)481
Prose, 1 request76
Prompt processing, 2K / 32K context~3,550 / ~2,800
KL divergence vs BF16 (code / text / 16K context), lower is better0.021 / 0.040 / 0.024
Ferrite speed by context length on two DGX Sparks: code generation with 1 and 4 requests, prose generation with 1 request, and prompt processing, in tokens per second.

Speed by context length. Medians, with the minimum and maximum as bars.

Reading these numbers

  • Speculative decoding speed depends on the text. Code and edits are faster than prose.
  • The prose figure counts only replies that don't fall into echoing the prompt, which the standard benchmark prose prompt tends to cause.
  • Sampling (temperature above 0) accepts fewer drafted tokens than greedy decoding.
  • On a single Spark, generation runs at roughly half to two-thirds of the two-node speed: about 208 tok/s for 4-request code with prompt processing counted in (313 on two Sparks, counted the same way).

How they were measured

One request's generation is timed from its first token to its last (500 tokens); four requests give their combined rate after the last one's first token. Prompt processing is timed to the first token with a cold prompt cache, and accuracy is the KL divergence of the next-token distribution against the BF16 model over fixed code, text and long-context sequences. The method, the clients and how to reproduce them are in docs/benchmarks.md.

Agents

The prompt cache at work

Four coding agents at once on two Sparks, sharing a ~13K-token system prompt and running 2-3 turns each (tools/bench/agents.py), with the prompt cache on instead of off. The cache is on by default; FERRITE_CACHE=0 turns it off. Its park pool is 12 GB on one Spark and 24 GB on each of two (FERRITE_PARK_GB overrides), and it shrinks if needed to keep 20 GB of memory available.

-74%
Prompt tokens computed (thinking on: -86%)
5.1s
First turn, time to first token (was 16.4 s)
0.4-0.6s
Later turns, time to first token
92tok/s
Combined output (was 46.5; thinking on: 36.9 to 108)

How it works, what it guarantees and its limits are in docs/prompt-cache.md.

Quickstart

Serve Qwen3.8-27B on a Spark

Needs a DGX Spark (GB10) with DGX OS, NVIDIA driver 580+, at least 95 GB of available memory and 38 GB of disk. One command takes a fresh Spark to a running server: it checks the machine, installs Ferrite and the models, starts the server and tests it. No sudo, and it can be run again.

curl -fsSL https://raw.githubusercontent.com/BHCC2025/ferrite/main/scripts/install.sh | bash

Install with your AI agent. Paste this into your coding agent on the Spark:
Install Ferrite on this DGX Spark by following https://github.com/BHCC2025/ferrite/blob/main/AGENTS.md exactly; stop and ask me if any check fails.

Or step by step, from the release tarball (no Rust or CUDA toolchain needed):

# 1. unpack the release and check the machine
tar xzf ferrite-<version>-dgx-spark-aarch64.tar.gz
cd ferrite-<version>-dgx-spark-aarch64
scripts/check-deps.sh

# 2. download the models (about 38 GB) into $FERRITE_MODELS
export FERRITE_MODELS=$HOME/models
scripts/fetch-models.sh

# 3. serve an OpenAI-compatible API on :8000 (model id "spark")
scripts/serve.sh
curl -s localhost:8000/v1/models

Two Sparks

Same Ferrite folder and model folders on both nodes, passwordless ssh from node A to node B, NCCL on both. Then on node A:

FERRITE_PEER=<node-b> tools/serve-tp2.sh

The two-node scripts detect the RoCE devices, interface and GID the nodes share; tools/site.sh prints every resolved setting.

Build from source

Stable Rust for the host crates, the kernels' pinned nightly (nightly-2026-11-04) with rust-src, llvm-tools and llvm-bitcode-linker, and CUDA 13's ptxas. The check script installs the Rust toolchains.

scripts/check-deps.sh --build --install
cargo build --release -p fe-server --features ferrite -p fe-cli

Point any OpenAI-compatible client at http://<spark>:8000/v1. The full guide, with every setting, client examples and troubleshooting, is in docs/quickstart.md.

Models

RoleHugging Face repository
Decode weights: EXL3 mixed 3.0 bpw (5-bit head) BHCC2025/Qwen3.8-27B-EXL3-3.0bpw-mix
Prompt weights: NVFP4/FP8 (optional) nvidia/Qwen3.8-27B-NVFP4
Draft model: DFlash2 z-lab/Qwen3.8-27B-DFlash2

All three are Apache-2.0 and derive from Qwen/Qwen3.8-27B (Apache-2.0). Without the NVIDIA checkpoint, Ferrite processes prompts with bf16 weights rebuilt from the EXL3 quant, at about half the prompt speed.

Credits

Built on other people's work

Ferrite stands on a lot of other people's work. CREDITS.md lists every project and paper it drew on and exactly what it took from each. The largest debts:

  • TensorFold Ferrite grew out of work on TensorFold. Its kernel designs, the hybrid of NVFP4 prompt processing with EXL3 decode, the DFlash2 verify tree and copy rows all came from there, as did much of the two-Spark approach. Thank you.
  • exllamav3 The EXL3 format, its kernels and the quantization tools, plus the QTIP trellis quantization it builds on.
  • DFlash2 By z-lab and inco.ai: the block draft model behind the speculative decoding.
  • NVIDIA NVFP4 checkpoint The NVFP4/FP8 prompt weights, made with NVIDIA Model Optimizer.
  • Qwen3.8 The model itself, by the Qwen team.

License

Ferrite is licensed under Apache-2.0. See LICENSE and NOTICE, which carries the notices of the projects Ferrite builds on.