Build and test
poot is a Rust workspace. The repo pins nightly Rust, LLVM (for kernel lowering), and Vulkan through Nix.
Prerequisites
nix develop
That drops you into a shell with the right rustc, llc, and Vulkan loader. Sanity-check it:
llc --version | grep -iE 'spirv|nvptx' # both targets present
vulkaninfo | grep -i 'deviceName' # a GPU is visible
Common tasks
The common tasks are wrapped as just recipes (run just with no args to list them):
just build # cargo build --workspace
just gate # the local merge gate: fmt-check, clippy with -D warnings, then just test
just test # dead-pub/dependency gates, self-tests, model-free test inventory and doctests; includes crates-standalone (every library builds alone)
just check # cargo check
just clippy # lints
just fmt # format every crate
just gate is the merge gate, and it runs on your own machine: there is no CI. It stops at the first of its three
steps that fails. just npu-build (part of just test) needs the nix develop .#npu shell and fails without it; on
a host that has none (a laptop), set POOT_ALLOW_NO_NPU_SHELL=1 to say so.
just test never needs a GPU or a checkpoint. It runs with every device hidden and required and with no models
directory, so a test that reaches for a device or a checkpoint fails instead of skipping. Tests that need a device or
a checkpoint run in separate lanes, and each device lane fails before any test runs when its device is missing:
just test-device-wgpu # the required migrated wgpu cases (executor contract, packed serving, launch widening, I32, failure lifecycle), wgpu device required
just test-device-kernels # pootc and poot-kernelgen kernel suites, wgpu device required
just test-device-vulkan # raw Vulkan: live caps receipt, executor contract, parity table and driver generation rows, under the validation layer
just test-device-rocm # poot-rocm-device resident probes and packed-serving cases
just test-device-ptx # one PTX probe, on an NVIDIA machine
just test-real-dims # tests named *_real_dims, in release
just test-checkpoint # CPU rows over the small checkpoints in ~/models; a missing model fails
just test-checkpoint-wgpu # every real-checkpoint device row on wgpu (one per model family at least), device and models required
just test-checkpoint-rocm # the same rows on ROCm (also -ptx on an NVIDIA pod, and -vulkan)
just test-checkpoint-large # rows that read a 20B+ checkpoint: a pod or host that holds them, never the dev box
just test-real-dims-long # the real-dimension rows that take minutes, in release
just bench # the criterion benches (CPU-oracle decode, wgpu op dispatch)
A test that loads a checkpoint finds it only through POOT_MODELS_DIR, the directory that holds the models; test code
has no default. Outside the checkpoint lanes an unset variable, or a model missing from the directory, skips the test
and prints the model's name. With POOT_REQUIRE_MODELS=1 either one fails the test, naming the variable or the model.
The checkpoint lanes (just test-checkpoint, just test-checkpoint-wgpu, just test-checkpoint-rocm and the other checkpoint lanes) set POOT_MODELS_DIR to ~/models unless you export another directory, and set
POOT_REQUIRE_MODELS=1, so a lane over a models directory that lacks a model fails instead of passing on skips.
Each checkpoint lane runs its rows by exact name (an #[ignore]d row too): scripts/required-checkpoint-cases.txt for
the CPU lanes, the checkpoint-wgpu.txt, checkpoint-rocm.txt, checkpoint-ptx.txt and checkpoint-vulkan.txt lists in scripts/required-device-cases/ for the device lanes, which also fail a row that
skipped. The lists are hand-maintained. A checkpoint test or an #[ignore]d test that is in no lane's list, and a row
that names no test, all fail just test-lane-coverage.
scripts/test-model-free.sh decides which packages the default lane covers. Its gate_policy names every
workspace package and either gates it or excludes it with a reason, and the lane fails when a package is missing
from the list. The lane runs the tests listed as model-free in scripts/test-model-free-inventory.tsv, which
the script generates from the test binaries: run just test-model-free-inventory-regen after adding, renaming, or
deleting a test. In most packages a test's class is derived (model-free, or real-dims for a test whose name ends
in _real_dims). In packages that mix host tests with device or checkpoint tests (poot-llm, poot-load,
poot-models, poot-serve, pootc, poot-kernelgen and the runtime crates) you choose model-free,
checkpoint (loads a model; a row of scripts/required-checkpoint-cases.txt, run by a just test-checkpoint* lane) or device (dispatches on a device) for each new test
by reading it. scripts/test-model-free.sh self-test (also just test-model-free-inventory) checks the checker
and the lane against known-bad inventories.
just test-lane-coverage (part of just test) fails when a test runs in no lane: a device test, or any test of a
package the model-free lane excludes, must match one of the test-device-* selections in the justfile. Select a
device crate by package(...) there, so a test added later is picked up, and list the new test in
scripts/required-device-cases/<lane>.txt. It also fails when a test a lane selects is not held by the gpu test group
in .config/nextest.toml, because two device tests at once can hang the GPU.
Every device lane ends with the required-case gate. The lane recipe stamps the start of the run
(scripts/check-required-cases.py begin <lane> <results.xml>, which also deletes the old results), runs nextest under
the lane's own profile, then checks (check <lane> <results.xml>): the results must be newer than the stamp and written by that lane's profile, every row of
scripts/required-device-cases/<lane>.txt must have run and passed, and every other passing row must be named with a
reason in <lane>.unlisted.txt. A row that printed a skip report fails the lane whether it is listed or not.
Focused runs are fine during development:
cargo nextest run -p <crate> <filter>
cargo test -p <crate> <filter>
Release builds for real models
Inference tests and generation are much faster in release mode. Always pass --release for anything that
runs a real model or touches GPU dispatch on the hot path.
Kernel disk cache
Compiled kernels are cached on disk and reused across processes. The cache lives under
$POOT_KERNEL_CACHE_DIR when set, else $XDG_CACHE_HOME/poot/kernels, else ~/.cache/poot/kernels. Set
POOT_KERNEL_CACHE=0 (also off or false) to disable the shared cache and use a per-process directory. When a process first opens a kernel root it prunes that root: it keeps the
current epoch and the 8 newest others, removes an old epoch only if it has been idle for more than 24 hours
(each cache hit refreshes the EPOCH sidecar's mtime, which is its last use) and removes compiler scratch
files older than one hour.
Running a binary outside the dev shell
A binary built inside the dev shell links that shell's glibc. Running it outside the shell (for example on a pod) needs a host glibc of at least 2.39, or the toolchain libraries bundled alongside it.
Backends and features
Production backends are selected at runtime with --backend wgpu|rocm|ptx|vulkan. Every backend is built into
every binary; a backend whose device is absent is refused when it opens, and auto probes devices, not features.
See Backends for codegen targets, dispatch models, and capability notes. For serve-time backend choice, see Picking a backend.
Regenerating the diagrams
The architecture diagrams in the README (and the PNGs the website serves from /img/architecture/) are
generated from hand-written SVG sources in website/diagrams/, one per image. To change a label, edit the
SVG and run, inside the dev shell:
just diagrams # render every source to website/static/img/architecture/ plus index.json
just diagrams --only model-tracing,rust-kernels
just diagrams --out DIR # render somewhere else
just diagrams --publish-dir DIR # also copy the PNGs and index.json there
just diagrams --check # fail if a committed PNG differs from its source
Rendering uses the dev shell's pinned resvg and one bundled font, so the output is byte-reproducible. index.json
holds each image's name, size, checksums, source and alt text; the README alt texts are copied from it (each SVG's
<desc>), and just test runs --check. The script writes only to --out and --publish-dir.