Fucina¶
Fucina (Italian for forge) is a CPU-first tensor/autograd library written in pure Zig 0.16. Axes have names checked at compile time, and computation is eager: every op executes the moment your code calls it, on real buffers — no graph to build, plan, or compile first, so what you read is what runs, in inference and in training alike. There is no C/C++ build system and no Python runtime dependency: Zig vector kernels, with CBLAS providers and a GPU offload (Metal or CUDA) as opt-in accelerators for matrix multiplication.
To prove the library on real workloads, this repository also ships complete applications built on it (see what the library enables today): LLM chat and serving (Qwen3, DeepSeek, GLM, Gemma, ...), speech, vision, and audio models — each validated against its reference implementation and benchmarked against llama.cpp, which it matches or beats on most measured CPU shapes (docs/BENCHMARK.md, losses included). The applications use the library; they are not the library, and they will graduate to their own repositories.
The manual is the rendered documentation: the machine-verified API reference, the guides, and Forging Deep Learning in Zig, a book-length course that rebuilds this library from a dtype enum to a live guitar amp and chatting language models.
What it looks like¶
Axis tags and rank are comptime facts: Tensor(.{ .batch, .in }) is a
different type from Tensor(.{ .in, .batch }), contraction is by axis
name, the result's tag set is computed at compile time, and a misaligned
contraction is a compile error — not a runtime shape crash three layers
deep. The pattern, condensed (see examples/spirals/main.zig and the production
trainer in src/llm/qwen3/train.zig):
const Model = struct {
w1: Tensor(.{ .h1, .in }),
b1: Tensor(.{.h1}),
w2: Tensor(.{ .class, .h1 }),
b2: Tensor(.{.class}),
};
fn forward(ctx: *ExecContext, m: *const Model, x: *const Tensor(.{ .batch, .in })) !Tensor(.{ .batch, .class }) {
var z1 = try x.dot(ctx, &m.w1, .in); // contract .in -> .{ .batch, .h1 }
defer z1.deinit();
var s1 = try z1.add(ctx, &m.b1);
defer s1.deinit();
var a1 = try s1.tanh(ctx);
defer a1.deinit();
var z2 = try a1.dot(ctx, &m.w2, .h1); // contract .h1 -> .{ .batch, .class }
defer z2.deinit();
return try z2.add(ctx, &m.b2);
}
// Inference: the defers free each intermediate as soon as it is consumed.
// Training: open an exec scope and the SAME forward trains as-is — the
// scope adopts every intermediate (value + autograd node), each deinit
// becomes a no-op borrow-release, and the step's whole graph stays alive
// until backward(), then is released at once when the scope closes.
const scope = ctx.openExecScope();
defer ctx.closeExecScope(scope);
const logits = try forward(ctx, &model, &x);
const loss = try logits.crossEntropy(ctx, .class, labels);
try loss.backward(ctx);
try opt.step(ctx);
The shape discipline lives in the program text, the way it did in Fortran —
real A(n,m) told you the rank before you read a single loop — and Zig's
comptime makes it free: the tags exist only in the type system and compile
away entirely.
Getting started¶
Requires Zig 0.16.0 — the toolchain is
pinned (build.zig.zon enforces the minimum); other versions will not
build. Fucina is an ordinary Zig package:
zig fetch --save git+https://github.com/matteo-grella/fucina#v0.1.0
// build.zig
const fucina_dep = b.dependency("fucina", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("fucina", fucina_dep.module("fucina"));
exe.root_module.addImport("fucina_llm", fucina_dep.module("fucina_llm")); // LLM stack; optional
A first program, verbatim from the reference's §1.4, where CI compiles and runs this exact block on every push:
const std = @import("std");
const fucina = @import("fucina");
test "first program" {
const alloc = std.testing.allocator;
var ctx: fucina.ExecContext = undefined;
ctx.init(alloc);
defer ctx.deinit();
// x: [batch=1, in=2], w: [in=2, out=1]
var x = try fucina.Tensor(.{ .batch, .in }).variable(&ctx, try ctx.fromSlice(&.{ 1, 2 }, &.{ 2, 3 }));
defer x.deinit();
var w = try fucina.Tensor(.{ .in, .out }).variable(&ctx, try ctx.fromSlice(&.{ 2, 1 }, &.{ 4, 5 }));
defer w.deinit();
var y = try x.dot(&ctx, &w, .in); // contract .in => [batch, out]
defer y.deinit();
var loss = try y.sumAll(&ctx);
defer loss.deinit();
try loss.backward(&ctx);
var gx = (try x.grad(&ctx)).?; // dloss/dx = w^T = [4, 5]
defer gx.deinit();
try std.testing.expectApproxEqAbs(@as(f32, 23.0), try loss.item(), 1e-6);
try std.testing.expectApproxEqAbs(@as(f32, 4.0), (try gx.dataConst())[0], 1e-6);
}
BLAS/GPU configuration passes through b.dependency options
(.blas = .none, .backend = .scalar, ...) and the modules carry their
own link inputs, so the defaults work with no extra build steps. The API is
pre-1.0 and will change: pin the tag (or a commit) you fetch. Details,
option reference, and the vendoring fallback:
REFERENCE §2.5.
Build with -Doptimize=ReleaseFast whenever speed matters (Debug is 10–50x
slower) — that applies to your application exactly as to the in-tree
binaries. Builds are tuned to the machine that compiles them. Without
-Dtarget, Zig targets the host CPU with its full feature set — as if
-march=native were always on — and Fucina's kernels specialize at compile
time (NEON/dotprod arms on Apple Silicon, AVX2/AVX-VNNI on modern x86;
unused arms are not in the binary). Two rules follow: run the binary on the
machine you built it on, and if you must cross-compile, pass -Dcpu as
well (e.g. -Dtarget=x86_64-linux -Dcpu=x86_64_v3) — a bare -Dtarget
gets that architecture's baseline features and silently loses the fast
kernels.
What the library enables today¶
With the tensor core in place, Fucina grows and gets tested through real
applications, so the runtime and the things built on it develop side by
side. Every family below is ordinary consumer code of the same public
fucina/fucina_llm surface wired in Getting started, so examples/
doubles as a corpus of real usage: open any main.zig and read how the
library is actually driven. And every family is validated against its
reference implementation, a discipline that is the core of the project:
token-ID-exact tokenizers vs llama-tokenize, logit-parity oracles vs
llama.cpp, byte-exact quantization encoders vs ggml, byte-identical GGUF
re-emit. Each family's folder under examples/ carries its own README
with copy-paste commands; docs/RUNNING-MODELS.md
is the index — verified weight downloads and licenses, plus the machinery
shared across runners (expert streaming, GPU offload, global knobs).
Run one:
git clone https://github.com/matteo-grella/fucina
cd fucina
# Grab a small model. `hf` is the Hugging Face CLI
# (pip install -U huggingface_hub; formerly `huggingface-cli`); or just
# download the GGUF from your browser into models/.
mkdir -p models
hf download Qwen/Qwen3-0.6B-GGUF Qwen3-0.6B-Q8_0.gguf --local-dir models
# Talk to it
zig build qwen3 -Doptimize=ReleaseFast -- models/Qwen3-0.6B-Q8_0.gguf \
--chat "What is the capital of France?" --no-think
# Or serve it to any OpenAI client (chat completions + responses, SSE
# streaming, JSON-schema constrained output with -Dllguidance=true)
zig build lmserve -Doptimize=ReleaseFast -- models/Qwen3-0.6B-Q8_0.gguf --port 8080
# No model files needed to verify the toolchain:
zig build test
Build options (-Dbackend, -Dblas, -Dmax-threads, -Dgpu=metal, …)
are documented in
the manual's toolchain chapter.
Language models¶
| Family | What it is |
|---|---|
| Qwen3 dense (0.6B–8B) + MoE (30B-A3B, 235B-A22B) | chat / REPL / raw generation, lossless speculative decoding, batch-N multi-conversation decode, JSON-schema/grammar constrained output |
| DeepSeek V2/V3 (MLA) | multi-head latent attention with the compressed KV cache as the default; covers V2-Lite, Moonlight-16B-A3B, and GLM-5.2 (glm-dsa) checkpoints |
| GLM-4.5 family (MoE) | V3-style trunk plus the model's own nextn layer for lossless multi-token-prediction drafting |
| DeepSeek V4 Flash 284B-A13B | hyper-connections trunk with native MTP speculative decoding; the 164.6 GB Q4K release decodes on a 64 GB machine |
| Gemma 4 26B-A4B (MoE) | chat / REPL / speculative decoding, JSON-schema/grammar constrained output |
| Qwen3.5 0.8B | hybrid Gated-DeltaNet architecture (conv + delta scan + gated attention) |
| DiffusionGemma 26B-A4B | block text-diffusion decoding on the Gemma backbone |
| Inkling 975B-A41B (MoE) | hybrid local/global-attention multimodal decoder: text chat plus image and audio input towers |
| nanochat | karpathy/nanochat ported whole: BPE tokenizer training, GPT pretraining, SFT, bits-per-byte eval, and chat — trained from scratch on CPU |
MoE models bigger than RAM are first-class: --moe-stream keeps only the
dense weights resident and pages routed experts from disk through a
pinned-set + LRU tier, bit-identical to the resident path — that is how the
142 GB Qwen3-235B and the 164.6 GB V4 Flash decode on a 64 GB machine.
Speech, vision, and audio¶
| Family | What it is |
|---|---|
| Parakeet (NVIDIA NeMo FastConformer) | speech-to-text: offline, streaming, and live microphone |
| OmniVoice | MaskGIT text-to-speech with voice cloning (Higgs Audio v2 codec included) |
| LocateAnything-3B | NVIDIA's open-vocabulary detection VLM: text-prompted labeled boxes, byte-compatible with the reference CLI |
| facedetect (insightface buffalo_l) | face detection, recognition, gender/age, anti-spoofing, and dense landmarks |
| Neural Amp Modeler | .nam guitar-amp profiles: run, train, export, live amp simulation |
These applications will eventually graduate into their own repositories.
The known debt of the in-tree phase is that generic operations accumulate
inside the examples — resamplers, spectrograms, reference-parity image
resizing — and graduation starts with an audit of which of those are
really tensor ops that belong in the core. Research experiments that lack
a reference oracle live on research/* branches rather than main —
currently research/nla, a natural-language autoencoder study
(text→vector→text on a Qwen3 GGUF) built on the trainer's hidden-state
seams.
Performance¶
Measured, not asserted: the protocol is paired same-machine runs against a
reference implementation — same GGUF, same thread count, CPU-only both
sides — with losses recorded as plainly as wins, each number carrying its
hardware, protocol, and caveats. The short version of the dated record
(snapshot 2026-07-04): on Apple M1 Max, of 236 paired sweep cells across
Qwen3 dense (0.6B/1.7B), Qwen3.5, the 30B MoE, and Gemma-26B, Fucina is
faster in 221 and at parity in 13 — dense prefill geomeans 1.18–1.81x per
format, large MoE prefill up to ~2x. On an x86 Raptor Lake box
(AVX2+VNNI), Fucina wins all dense quantized formats (medians 1.32–1.95x)
while llama.cpp decisively wins MoE small-batch prefill. The full record —
the losing cells, the caveats, and the reproduction commands
(tools/fetch_refs.sh, tools/bench_gate.py) — is
docs/BENCHMARK.md.
On top of raw speed there is lossless, draft-model-free speculative
decoding (up to 2.3x on retrieval-structured tasks, never-a-loss cost
gate; docs/SPECULATIVE.md) and batch-N multi-stream
decode (3.2x aggregate throughput at 8 streams). Structured output is
built in: a pluggable logit-processor seam on the shared sampler, with
JSON-schema/regex/Lark constrained decoding through the vendored
llguidance engine (opt-in
-Dllguidance=true; composes with speculative decoding —
docs/CONSTRAINED-DECODING.md).
Training¶
The runtime trains as well as it infers: an eager autograd engine with exec scopes, activation checkpointing, deterministic dropout (counter-based RNG), and SGD/AdamW/Muon/APOLLO optimizers golden-parity-tested against their reference implementations. LoRA fine-tuning of a quantized Qwen3 GGUF runs end-to-end on CPU (~932 ms/step, 38.2 tok/s supervised throughput for Qwen3-0.6B-Q4_K_S on an M1 Max), and the loop closes: fine-tune → merge → quantize → the exported GGUF loads and answers in llama.cpp.
zig build finetune -Doptimize=ReleaseFast -- --steps 30
Gradient-free training is a first-class alternative: fucina.es implements
evolution strategies at scale (arXiv:2509.24372) — seed-regenerated noise,
forward passes only, algebra cross-checked bitwise against the reference —
and zig build es-finetune fine-tunes the same GGUF with it, LoRA-only or
full-parameter, under rule-based (R1-style) or loss-based rewards. Because
autograd, KV-cache plumbing, and serving live in one runtime, the same
trainer also implements Cartridges (arXiv:2506.06266): a corpus is
compressed into a trained KV prefix by in-process self-study distillation
(zig build cartridge; docs/CARTRIDGES.md).
docs/TRAINING.md is the full guide, including how the gradients were verified (PyTorch goldens, finite differences, and a real-model audit) and its open issues.
Design¶
Fucina is deliberately eager and local: no global graph object, no fusion pass, no compiler layer. The execution context validates shapes once, then dispatches to small unchecked, allocation-free backend kernels selected at build time. Transient memory goes through a thread-safe reusable buffer pool with bucket-rounded buffer allocation for small temporaries — the rationale (and why it beats an arena here) is in docs/MEMORY-MODEL.md. The intended growth path is model-specific sessions with semantic weight binding and preallocated buffers, not a generic ggml-like graph. docs/ARCHITECTURE.md maps the whole tree.
That choice has a known price, paid deliberately. Whole-graph machinery — fusion passes, static device memory planning, captured launch sequences — is what mainstream GPU inference stacks are built on, so an eager runtime can use a GPU only at the op seam (which is what the Metal and CUDA offloads do) and will never chase TensorRT-class GPU throughput. On CPU the ledger reads the other way: per-op dispatch costs next to nothing, kernels specialize at compile time instead of fusing at runtime, and the paired benchmarks are run against a graph executor. CPU-first is the design point, not a stage on the way to somewhere else.
Documentation¶
The manual renders all of it — reference, guides, course, and per-example pages — with search. The sources:
| Doc | Contents |
|---|---|
| docs/ARCHITECTURE.md | the actual source layout, layer by layer — start here |
| docs/REFERENCE.md | the detailed API reference: the full public surface, exact semantics, machine-verified snippets |
| docs/course/ | Forging Deep Learning in Zig — a book-length course that teaches Zig and deep learning together by rebuilding this library's journey, from a dtype enum to a live guitar amp and chatting language models; per-chapter video scripts included |
| docs/RUNNING-MODELS.md | the model index: verified weight downloads + licenses, the example-to-README map, shared runner machinery |
| docs/LMSERVER.md | the lmserve example: OpenAI API mapping tables, streaming contracts, server architecture |
| docs/BENCHMARK.md | the measurement protocol and dated Fucina-vs-llama.cpp records, wins and losses |
| docs/TRAINING.md | the training guide: autograd, optimizers, LoRA, evolution strategies, checkpoints, gradient verification |
| docs/MEMORY-MODEL.md | ownership rules and the buffer-pool-not-arena adjudication |
| docs/PORTING.md | the porting method — how every model family here earned its parity claims, written for the next port |
| docs/SPECULATIVE.md | design record: lossless draft-model-free speculative decoding |
| docs/PTQTP-RECIPE.md | walkthrough: PTQTP-quantize any GGUF (MoE experts included) and run it, resident or streamed |
| docs/CONSTRAINED-DECODING.md | design record: grammar/JSON-schema constrained decoding and its speculation composition |
| docs/CARTRIDGES.md | design record: trained KV-prefix corpus compression (Cartridges) — self-study distillation, serving, gates |
| docs/ENGRAM.md | design record: conditional n-gram memory (Engram) — hashed lookup tables, graft mode, reference parity |
AGENTS.md |
build/test/bench commands, build options, repo map, house rules |
| docs/THIRD-PARTY-NOTICES.md | full provenance and license inventory of third-party material |
Status and scope¶
Honest expectations:
- CPU-first, two ISAs. Tuned on Apple Silicon (aarch64 NEON/sdot) and
x86-64 (AVX2/AVX-VNNI); a scalar reference backend covers everything else.
The Metal offload accelerates specific GEMM shapes on macOS; it is not a
general GPU runtime. The CUDA sibling (
-Dgpu=cuda, Linux/NVIDIA) plugs into the same seam with zero build-time SDK dependency: f32/f16 GEMM via dlopen'd cuBLAS, quantized dense + MoE prefill and fused prefill attention via vendored PTX kernels, and an opt-in decode GEMV. - The API is not stable. This is a young codebase published in the open,
not a 1.0 library. The 0.x tags are pins for
zig fetch, not a semver promise. Expect churn. - Model weights are not included. Each model family carries its own
license (Qwen: Apache-2.0; Gemma: Google's Gemma Terms of Use; Parakeet:
CC-BY-4.0; OmniVoice weights: CC-BY-NC).
docs/RUNNING-MODELS.mdnotes the terms next to each download. - Benchmarks age. llama.cpp moves fast; the dated records in
docs/BENCHMARK.mdare snapshots, not eternal claims.
Origins¶
Fucina grew out of autograd concepts I first explored in Go with
spaGO — above all the idea that the
graph should be implicit in the values themselves: no graph object, no
tape, no persistent engine. Each result carries a pointer to the operation
that produced it, and backward() discovers the topology by walking those
pointers. spaGO executed that idea the Go way: one goroutine per node,
each blocking until its gradient contributions arrived, the runtime
scheduler absorbing the wait. Zig has no goroutines, so Fucina keeps the
idea and rethinks the execution: every node carries an atomic dependency
counter, and its gradient fires only when the counter drains — concurrent,
on a bounded worker pool, no blocked workers (src/ag/). (AFAIK) Mainstream
stacks route backward through a central engine over an explicit node graph
or a trace; here the live tensors are the graph.
As for the language: I wanted to stay as close to the metal as possible, and — honestly — I was also looking for a good excuse to finally learn Zig. This project is it.
Development — code and documentation alike — leans on strong assistance from agentic coding systems, with humans leading the ideas, the testing, and the debugging, and writing first-hand as well. That stance now has a motto, thanks to Salvatore Sanfilippo: "Control the ideas, not the code". I also wanted to gain practical experience coding with AI, and so far I have found that the best results come from a review loop where humans and multiple frontier models critique one another’s specifications and implementations, iterating toward a shared consensus.
As for what gets built: right now the core — the tensor/autograd runtime — evolves in lockstep with the examples, growing exactly the ops each new port demands, and the examples follow a personal criterion rather than a roadmap: they are the deep-learning architectures I want to understand deeply, in the spirit of Feynman's "What I cannot create, I do not understand." The Neural Amp Modeler is the one that outgrew the criterion — I genuinely enjoy using it, which is its own small achievement: I almost never end up using the things I build.
Contributing¶
Contributions are welcome — see CONTRIBUTING.md: PRs are
human-owned (coding agents expected, human judgment required), and changes
are tested against the two tracks the project actually regresses on,
correctness and speed, with the commands, machine, and model quant reported.
Acknowledgments¶
Fucina exists because others built the road first.
- ggml / llama.cpp — Georgi Gerganov and the ggml authors. This project
would not exist without their work. Fucina is an independent Zig runtime,
but it speaks formats ggml defined (GGUF, the block-quantization wire
formats), and several components are direct ports of llama.cpp code: the
quantization row encoders, the Qwen2 and SentencePiece tokenizers, the
Unicode classification tables, the SIMD
expf, and the vendored Metal quantized-GEMM kernel. llama.cpp is also the parity oracle and the performance yardstick throughout. - Ettore Di Giacinto (mudler, of LocalAI) — the Parakeet ASR family is a port of his parakeet.cpp (the ready-to-run GGUF weights come from his conversions); the LocateAnything open-vocabulary detection example is a port of locate-anything.cpp, written with Richard Palethorpe — its converter/quantizer produce the GGUFs the example runs, and it is the parity oracle and CPU yardstick for that port; and the facedetect example (SCRFD + ArcFace + GenderAge + anti-spoof + dense landmarks) is a port of his face-detect.cpp, again the parity oracle and CPU yardstick, with the buffalo_l GGUFs coming from his conversions of the insightface models. The underlying LocateAnything-3B model is NVIDIA's.
- Salvatore Sanfilippo (antirez) — the DeepSeek V4 Flash port follows his ds4 inference engine: it is the reference for the entire architecture (hyper-connections, compressed sliding attention, the FP8/FP4 quantization grids, native MTP), his GGUF conversions are the weights the port runs, and his test fixtures (official API vectors and local logit goldens) are the validation oracle.
- Prism ML — the Ternary Bonsai family is what the
q2_0ternary support serves: the Q2_0 g128 wire format and its reference encoder/decoder come from their llama.cpp fork, which is also the parity oracle for the Ternary-Bonsai-27B port. The Bonsai weights themselves are Apache-2.0 on Hugging Face and are not part of this repository. - Thinking Machines Lab — the Inkling architecture (hybrid local/global attention with a banded content-dependent relative-position bias in place of RoPE, per-layer short causal convolutions, a fine-grained MoE whose shared experts act as routing-softmax sinks, and hierarchical-patch vision plus dMel audio towers); the port follows the open weights at thinkingmachines/Inkling. Daniel Han (Unsloth) — his llama.cpp Inkling architecture PR is the source of truth for the GGUF layout and graph, and his GGUF conversions provide the converter and the parity oracle the port validates against.
- JustVugg's colibri — the out-of-core MoE expert streaming (run mixture models much bigger than RAM by paging routed experts from disk through a pinned-set + LRU tier, with a persistent usage histogram and router-lookahead prefetch) was inspired by colibri's design; Fucina's implementation is independent, streaming ggml quants over the fused kernels.
- Apple MLX — the f32/f16 Metal GEMM is the vendored MLX "steel" kernel.
- guidance-ai / Microsoft — the vendored llguidance engine powers grammar/JSON-schema constrained decoding.
- ServeurpersoCom — the OmniVoice TTS port follows his omnivoice.cpp, which also provided the codec porting groundwork (Higgs Audio v2 / HuBERT / DAC).
- Steven Atkinson — NeuralAmpModelerCore and neural-amp-modeler, the reference for the entire NAM example.
- ZINC — the byte-level BPE tokenizer core was adapted from the ZINC Zig inference engine.
- ZML — the tagged-tensor approach (axis tags carried in the type, operands aligned by name) was inspired by ZML.
- David Reid (miniaudio), Keller Jordan (Muon), Sebastiano Vigna (splitmix64), musl libc and ARM optimized-routines (scalar/SIMD math lineage), k2-fsa/Xiaomi (OmniVoice), NVIDIA NeMo (Parakeet), Boson AI (Higgs Audio v2), Meta (HuBERT), Descript (DAC), kitft (natural-language autoencoders).
The complete inventory — what is vendored, what is ported, what is only a parity reference, and under which license — is in docs/THIRD-PARTY-NOTICES.md.
License¶
MIT — see LICENSE. One documented exception: tools/gen_optim_goldens.py
contains material derived from the APOLLO reference implementation and is
covered by its upstream CC-BY-NC-4.0 terms; see docs/THIRD-PARTY-NOTICES.md.