Mica 7.4.0 is two releases that left as one. The plan reserved 7.3 for the numeric foundation — dynamic shapes, higher ranks, the linear-algebra library, the quantized storage families — and 7.4 for the training library built on top of it. The foundation shipped first, and then something better than a release proved it: the training library was built directly on that foundation, batch by batch, and every design decision the foundation had made was field-tested by the library that needed it. By the time both were green there was nothing left for a separate 7.3 to say. So the number 7.3 stays unused, and 7.4 carries both stories — the foundation and the library whose existence is the foundation’s proof.

Everything below is shipped and gated behavior — every claim in this article has a test the release gates run, every printed digit in the validation trainers is an independent oracle’s, and the GPU kernels are proven bitwise against the host arithmetic on real hardware.

The release at a glance

MovementWhat a program can do now
Shapes that arrive with the datavector of float64 and matrix of float64 — rank in the type, extents in the value, one operator vocabulary across static, dynamic, and mixed pairings
Any ranktensor[2, 3, 4] of float64; the batched product pairs trailing matrices slice for slice; Transpose swaps the trailing pair; Reshape re-reads the same cells in a new shape, counts proven at build
Linear algebraSolve and Invert on partial-pivot LU, Cholesky, Householder Qr — each fails LinalgError, so a singular matrix is a failure your program handles, never a value that travels
Explicit expansionRowSpread, ColumnSpread, SliceSpread — replication is a visible verb, and a shape mismatch stays a build error
Quantized storageint8 and uint8 tensor elements; their one arithmetic is WideningProduct, the int32 accumulation visible in the name
One Sumevery family, every rank, an axis form — and one documented fold, so the same values answer the same bits wherever the word appears
The training toolkitactivations and their map twins, RmsNorm, the batched CrossEntropy, Dropout with a visible mask, SgdStep, AdamWStep, ClipGradNorm, CosineSchedule, seeded inits — every recorded verb carrying its reverse rule
Models as recordsrecords hold tracked fields, arrays of records are a model’s depth, and the optimizer walk is a loop you read
The image familyConv2d, MaxPool, AvgPool, the training-form BatchNorm with running statistics as plain values, ChannelScale for inference
GPU paritythe vocabulary above runs device-resident behind the same source spelling; two validation trainers print digit-identical loss columns on both placements

Shapes that arrive with the data

A static tensor’s shape is part of its type; from this release, a program can also declare a tensor whose extents arrive at run time — a file’s row count, a corpus’s length — while the rank stays in the type where the compiler can reason about it:

program Shapes;

imp
    WriteLn, Resize, Rows, Length : std;

var
    a : matrix of float64;
    v : vector of float64;
    y : vector of float64;

begin
    { the extents are values: this program read them, nothing hard-codes them }
    Resize(address a, 3, 4);
    Resize(address v, 4);

    a[0, 0] := 2.0;
    v[0] := 1.5;

    y := a * v;
    WriteLn("%lld rows -> %lld results, y0 = %lf", Rows(a), Length(y), y[0]);
end.

The law behind it is dimension-wise: every extent the compiler can see is proven at build, exactly as before; an extent living in a value is guarded where the operation runs — always on, at every optimization tier. A mismatched inner dimension is a trap with the operation’s own source position, not a wrong answer. And the two worlds mix freely: a static matrix times a dynamic vector, a dynamic result adopted into a static destination, one vocabulary throughout.

Any rank, and the batched algebra

The tensor word declares rank three and above. The product pairs the trailing matrices of two equal-rank operands slice for slice — the batched multiplication attention is made of — with every leading axis and the inner dimension proven at compile time:

program Batched;

imp
    WriteLn, Transpose, Reshape : std;

var
    q  : tensor[2, 3, 4] of float64;
    k  : tensor[2, 3, 4] of float64;
    sc : tensor[2, 3, 3] of float64;
    fl : matrix[6, 3] of float64;

begin
    q[0, 0, 0] := 1.0;
    k[0, 0, 0] := 2.0;

    sc := q * Transpose(k);     { per slice: q's rows against k's, [2,3,3] proven at build }
    fl := Reshape(sc);          { the same cells, read as six flat rows }
    WriteLn("%lf %lf", sc[0, 0, 0], fl[0, 0]);
end.

Reshape takes its shape from where it stands — the assignment target — and the cell counts must agree at compile time. There is no view semantics to reason about and no stride trick to misread: the same row-major cells, re-read in a declared shape.

Linear algebra on the failure channel

The linalg unit is written in Mica and speaks the language’s own error model. A singular system is not a NaN that surfaces three functions later — it is a failure, raised where it happens and handled where you say:

program Circuit;

imp
    WriteLn, Resize : std;
    Solve, LinalgError : linalg;

var
    a : matrix of float64;
    b : vector of float64;
    x : vector of float64;
    e : LinalgError;

begin
    Resize(address a, 2, 2);
    Resize(address b, 2);
    a[0, 0] := 4.0;
    a[0, 1] := 1.0;
    a[1, 0] := 1.0;
    a[1, 1] := 3.0;
    b[0] := 1.0;
    b[1] := 2.0;

    x := Solve(a, b) on fail e do
    begin
        WriteLn("no solution");
        leave;
    end;

    WriteLn("x = %lf %lf", x[0], x[1]);
end.

Solve and Invert factor with partial pivoting, Cholesky serves the symmetric positive-definite world, and Householder Qr completes the set. All four are ordinary Mica source — readable, steppable, and compiled by the same compiler as your program.

The training toolkit, and models you can read

The autograd tape arrived in 7.2; this release gives it the vocabulary a real model needs, and a shape for the model itself: records hold tracked fields, and an array of records is a model’s depth. The pattern is three visible acts — adopt, record, walk:

    for step := 1 to 3 do
    begin
        alpha := CosineSchedule(step, 1, 4, 0.0625);
        e := ep;                        { the tracked places adopt this step's weights }

        tape
            x := Gather(e, ids);        { the embedding: rows looked up by id, recorded }
            a := RmsNorm(x, gain);
            h := GeluMap(a * w1);
            lg := h * Transpose(e);
            loss := CrossEntropy(lg, targets);
        end;

        Backward(loss);

        gm := Gradient(e);              { the walk is your own loop: no hidden registry }
        nrm := ClipGradNorm(address gm, 1.0);
        AdamWStep(address ep, gm, address me, address ve, step, alpha, 0.9, 0.999, 0.00000001, 0.0625);
    end;

Every verb in the window records its reverse rule; Backward walks the tape once; Gradient answers per place. The optimizer state — the moments, the velocities, the schedule — is variables you declared, updated by verbs you called, in an order you can read. The shipped validation trainer runs this pattern at a transformer’s own shape — batched attention over rank-3 tensors, a causal mask spread per slice, seeded dropout, the batched cross-entropy — and every digit it prints is checked against an independent full-run oracle.

The image family

Convolutional models get the same treatment — the stack, the recorded backward rules, and running statistics as values you own:

        tape
            h1 := Conv2d(xc, wct, 1, 1);
            h2 := BatchNorm(h1, gnt, bst);
            h3 := ReluMap(h2);
            h4 := MaxPool(h3, 2);
            hf := Reshape(h4);
            lg := hf * wlt;
            loss := CrossEntropy(lg, ti);
        end;

BatchNorm is the training form; the running mean and variance are plain vectors your program updates through ChannelMean and ChannelVariance, and inference is ChannelScale over values you computed — no hidden buffers, no mode switch, the same program with different lines.

The GPU: same program, same digits

The whole vocabulary above — the batched algebra, gather, reshape, the norms, the maps, dropout, the image family, and the fused optimizer steps — now runs device-resident. The spelling is the placement word on the declaration and the visible transfer verbs; the mathematics is the same:

    dwc  : tensor[2, 1, 3, 3] of float64 on gpu;

Two validation trainers ship as the proof: the batched transformer and the convolutional classifier, each trained once on the host and once resident on the device in the same program. Their loss columns and trained weights print digit-identical, run for run, and the device floor beneath them is proven leg by leg, bitwise, against the host arithmetic on real hardware — seventy-eight kernel comparisons, every one exact. Determinism per placement is a law, not a hope: the same run answers the same bytes, every time.

Shipped alongside

The user container grows the release’s examples — dynamic tensors, the linear-algebra library, quantized tensors, tracked records, the learning toolkit, the batched trainer, and the convolutional classifier — each with deterministic printed digits, plus the Batched training and Shapes that arrive with the data notebooks, verified cell by cell. The learn area carries the arcs’ chapters: dynamic tensors, linear algebra, quantized tensors, model depth, the learning toolkit, batched training, and image networks. VS Code extension 4.2.0 ships the regenerated grammar (the tensor word arrives in the editor the release it arrives in the language) and snippet support for the new workflows.

Getting it

The packages are on the download page for amd64 and arm64; the container image micalang/mica:7.4.0 follows on Docker Hub. The twelve-chapter AI course now runs on this release end to end — and the GPT it builds toward trains, as ever, in pure Mica.