Skip to content

1. Introduction and mental model

Fucina is an eager, close-to-metal CPU tensor/autograd runtime plus an LLM/ASR inference stack, written in Zig 0.16. This document is the detailed reference for the whole library: the public API surface, its exact semantics (ownership, errors, defaults, thread-safety), and the internal layers you need to understand to extend it. The structural overview lives in ARCHITECTURE.md; command cheat sheets live in AGENTS.md; per-model getting-started recipes live in the per-example examples/<name>/README.md, with the shared weights table and runtime knobs in RUNNING-MODELS.md.

Every runnable Zig snippet in this document is machine-verified against the tree: zig build snippet-check (§2.7, a CI step) extracts every runnable snippet written as a named test block and runs it against the real modules; snippets that need model assets are non-test fragments the harness ignores and are marked // requires model assets to run.

1.1 The two modules

The build exposes two library modules (§2):

  • fucina (src/fucina.zig) — the tensor library: the public Tensor facade, the ExecContext runtime, autograd, quantized formats, training (optimizers, ES, LoRA), and persistence (GGUF, safetensors, checkpoints).
  • fucina_llm (src/llm.zig) — the model stack built on top: GGUF weight binding, KV caches, tokenizers, samplers, chat sessions, speculative decoding, and the model families (Qwen3, Qwen3.5, Gemma 4, DiffusionGemma, DeepSeek V2/V3, GLM-4.5, DeepSeek V4 Flash, Kimi-K3, Inkling, Parakeet ASR).

Applications (examples/, tools/, bench/) sit above both.

1.2 Mental model

Five ideas carry the whole library:

Eager, explicit execution. There is no graph compiler, no lazy evaluation, no fusion pass. Every tensor operation validates its inputs, allocates its output through the ExecContext, and calls a backend kernel immediately. What you write is what runs, in the order you wrote it.

One public tensor, typed at comptime. fucina.Tensor(spec) is the only user-facing tensor type. Its axis tags (names) and rank are part of the Zig type — checked at compile time — while dimension sizes stay runtime values. The same facade carries no-grad inference and gradient-tracked training: a tensor with gradient state records backward information, a constant does not, and the operation call sites are identical. The raw, untagged tensor underneath is deliberately not exported (§8).

Tags instead of axis numbers. Operations name the axes they act on (x.dot(&ctx, &w, .in) contracts the .in axis) and broadcasting is tag-driven: axes align by name, not by position. The tag algebra is comptime-only data — it compiles down to stride manipulation on the raw tensor with zero runtime tagging cost (§7).

Explicit ownership, deterministic cleanup. Tensors are value handles over reference-counted buffers. Operations return owned results; defer x.deinit() is the norm. Training loops use exec scopes to own the flood of intermediates implicitly (§6). Loaded model weights borrow mmap'd bytes (holder-managed lifetime) or device-resident bytes (freed through storage release hooks) instead of copying (§8, §12).

Multi-dtype with a sealed policy. Tensors span bool/integer dtypes, f16/bf16/f32/f64, and the GGML block-quantized formats. What each dtype branch can do is enforced by the type system, not runtime checks: .f32 is the differentiable branch; other scalar dtypes are constant typed tensors (floats additionally get forward-only math); block-quantized tensors are constant inference tensors that dequantize, gather rows, and serve as matmul right-hand sides (§3, §10). Float compute/output dtypes follow a fixed per-op-family policy (§8.3).

1.3 Layer stack

Top-down; a band depends only on bands at or below it. Acyclicity of the production import graph is machine-enforced by zig build arch-check (§2); the band direction is checked by a development-side dependency-structure lint whose configuration is not part of this tree (see ARCHITECTURE.md):

Band Contents Reference
apps examples/, tools/, bench/ §14
llm fucina_llm module §13, §14
facade src/fucina.zig public root §1§5
autograd + training src/ag/, optim/es/lora/persistence §5, §11, §12
tagged ops src/tagged.zig §7
exec runtime ExecContext, src/exec/ §6
backends CPU SIMD, BLAS, Metal/CUDA §9, §10
tensor/storage/dtype raw value types §8

1.4 A first program

The canonical smoke test — build two variables, contract them, reduce, and differentiate:

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);
}

compiled & run in CI ✓

Everything in this snippet — the context lifecycle, tensor specs, construction, ownership, tagged contraction, backward — is unpacked in §3§6.

1.5 Stability

Fucina is a production-oriented core, not a finished 1.0 product: the package manifest and 0.x tags (v0.1.0) exist so consumers can pin a version (§2.5), but a 0.x tag is a pin, not a semver stability contract — the public API may change between tags (see Current Production Gaps in ARCHITECTURE.md). This reference describes the tree it ships with; sections marked internal (§7 library level, §8, backend internals in §9) document machinery that is explicitly not a stable API.