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
| Movement | What a program can do now |
|---|---|
| Shapes that arrive with the data | vector of float64 and matrix of float64 — rank in the type, extents in the value, one operator vocabulary across static, dynamic, and mixed pairings |
| Any rank | tensor[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 algebra | Solve 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 expansion | RowSpread, ColumnSpread, SliceSpread — replication is a visible verb, and a shape mismatch stays a build error |
| Quantized storage | int8 and uint8 tensor elements; their one arithmetic is WideningProduct, the int32 accumulation visible in the name |
One Sum | every family, every rank, an axis form — and one documented fold, so the same values answer the same bits wherever the word appears |
| The training toolkit | activations 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 records | records hold tracked fields, arrays of records are a model’s depth, and the optimizer walk is a loop you read |
| The image family | Conv2d, MaxPool, AvgPool, the training-form BatchNorm with running statistics as plain values, ChannelScale for inference |
| GPU parity | the 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.