How a Python call becomes a dispatch on the GPU, in three depths. Read the first section and stop, or keep going — each one assumes the one above it and nothing below it.
In thirty seconds
vkML is a stack of layers, and the dependency direction is enforced rather than agreed. A layer may include only from a layer below it, so every arrow in this diagram points down — if one curved upward, the build would be failing.
Generated from the same include scan that
scripts/check_layering.py runs in CI, so the picture and the gate
cannot disagree. plan is declared in the order and holds no files
yet, so it is drawn dashed rather than hidden.
In five minutes
Nothing runs when you write it. An operator builds a graph
node and returns a Tensor handle. No memory is allocated, no kernel
is dispatched, nothing is computed. Work happens only when something observes a
value — numpy(), item(), a backward pass —
or when realize() is called explicitly.
That deferral is a performance mechanism, not an API style. Batching lets many operations share one GPU submission, and on the development hardware a submission costs about 105 µs against 9 µs for a dispatch. Reducing submissions is worth far more than making any single kernel faster, which is why the graph exists at all.
When a value is observed, the dispatcher walks the graph in topological order, asks a backend whether it supports each node, and submits. A graph runs entirely on one backend: there is no per-node fallback, so an operator the GPU cannot run is an error rather than a silent host transfer — why that is deliberate.
Two backends, and they are not peers. The CPU backend is the oracle: its job is to be right, and every Vulkan result is checked against it. The Vulkan backend is 24 compute shaders compiled to SPIR-V and dispatched with operand addresses in push constants — no descriptor sets at all.
Going deeper
Each of these traces one subsystem through the code, naming the file and line of everything it describes.
Tensors, storage and views
What a handle owns, how a view shares storage, and why strides are in bytes.
The lazy graph and execution
Node construction, the topological walk, and how work reaches a backend.
Autograd
How the backward pass is built, and which operations carry a rule.
The CPU backend
The oracle: pairwise summation, the tolerance policy, and why it is not a fallback.
The Vulkan backend
Pipelines, push constants, memory, and the six kernels behind one matmul.
Shaders and the GLSL layer
The 24 compute shaders, their specialisation constants and shared helpers.
Dtypes, devices and numerics
Five dtypes, software f16 narrowing, and what determinism actually guarantees.
How the architecture stays true
The layer order above is not a diagram of intent. It is checked on every build, and it has already caught a real violation — autograd reaching into api. Alongside it: push-constant blocks against the 128 bytes Vulkan guarantees, the GEMM contraction contract from ADR 0005, and a mutation campaign that breaks each kernel on purpose to confirm the tests notice.
That last one matters more than it sounds. A green suite proves the tests ran, not that they can fail — see Testing and verification.