Here is a complete GPU program in Mica. It contains no kernel, no launch configuration, no device pointer, and no memory management — and its two printed lines carry identical digits.
The example is examples/GpuHello
in the tutorial repository.
Getting the file
mica-container/devkit/mica-provision nvidia # once: NVIDIA's cuBLAS and NVRTC, beside the card's driver
make -C mica-container/examples/GpuHello runhost = -0.289062 0.304688 0.093750 -0.015625
device = -0.289062 0.304688 0.093750 -0.015625Running needs an NVIDIA GPU of compute capability 7.5 or newer — the Turing
generation of 2018, and everything after it — with NVIDIA’s driver and two of
NVIDIA’s libraries, cuBLAS and NVRTC. On a machine with the driver,
devkit/mica-provision nvidia installs the two from NVIDIA’s own repository.
The compiler writes the program’s device code as CUDA C. Where the machine that
builds the program has NVRTC, NVIDIA’s runtime compiler, the link compiles that
code ahead for five card generations — sm_80, sm_86, sm_89, sm_90 and
sm_120 — and the program carries the images, so a card of those generations
runs it without compiling anything; mica --inspect names the generations a
program carries, and +mica info -mica says whether its kernels load
precompiled on the card it finds. On any other card, NVRTC builds the code the
first time the program touches the device, which takes a moment, and later runs
reuse NVIDIA’s own cache of what it built. On a machine without
a card the example builds all the same, and running it names what it looked
for. On a machine with several cards a program runs on the first, and
+mica gpu=1 -mica at its start chooses the second; +mica check -mica lists
the cards by the numbers the flag names them by, as the
runtime flags page shows.
The placement is the whole feature
var
a : matrix[4, 4] of float32;
da : matrix[4, 4] of float32 on gpu;on gpu moves the storage, not the type. da is the same
matrix[4, 4] of float32 the host variable is — same shape rules, same
operators, same refusals — and its value lives in device memory. That one
fact decides where computation happens: an expression over device-resident
operands runs on the device, because that is where its data is.
da := ToDevice(a);
db := ToDevice(b);
dc := (da * db) * Rate + da; { the same sentence, computed device-side }
c := ToHost(dc);There is no third verb. A value is host-resident or device-resident, and every crossing is written where it costs — which is the whole discipline a performance reader wants, because the crossings are where GPU programs lose their time.
The wall between the worlds
Mixing the two residencies in one expression is refused at compile time, in a sentence that names the fix (error 5480):
the operator ‘multiplication’ cannot mix storage worlds: got ‘matrix[4, 4] of float32’ and ‘matrix[4, 4] of float32 on gpu’ — an operation across placements would smuggle the very transfer the placement axis makes visible; move one operand with ToDevice or ToHost, so the crossing is written where it costs
And an assignment across the line without a verb is refused the same way (error 5481):
the assignment crosses storage worlds … a copy between host and device memory is a transfer, and a transfer is a verb — write ToDevice or ToHost, so the crossing is visible where it costs
Every transfer in Mica is a verb you can read, so a program’s crossings are exactly the ones its source spells.
The same digits, and why
The two printed lines agree to the last digit because every input in this program is a small multiple of one eighth: every product and every sum is exact, and an exact result is the same wherever it is computed.
That is the device’s rule for all the arithmetic IEEE 754 rounds exactly — addition, subtraction, multiplication, division, the square root, the fused multiply-add, the comparisons and the conversions. On the GPU they give the processor’s bits, in float32 and float64 alike, and the compiler’s test suite holds every one of them to that on real NVIDIA cards.
Two things on the device are NVIDIA’s own. A matrix product is cuBLAS’s: it
sums in its own order and agrees with the processor’s product to float32’s
accuracy. Exp, Ln and Tanh are NVIDIA’s functions in the operand’s own
width, within two units in the last place of the exact result. Every device
result repeats bit for bit, run after run, on the same card.
A training run that wants the card’s tensor cores for its float32 products
names them at build time: --optimize O2,fp_tensor_float32 lets cuBLAS read
each product’s inputs in TF32, ten bits of mantissa beside float32’s range, as
nanoGPT trains. The products then agree with the processor’s to about three
decimal digits and still repeat bit for bit on the card.
The AI course carries this all the way up: a GPU-resident training run repeats its loss column to the last digit on its card, and the column falls with the host run’s beside it.
Where this sits
The device residency covers the tensor verbs — the vocabulary the tracked-values chapter teaches records on the device too, which is what makes GPU-resident training an ordinary Mica program. The requirement, stated once more the way the platform page states it: an NVIDIA GPU of compute capability 7.5 or newer, NVIDIA’s driver, cuBLAS and NVRTC.