Errors at your line, before the GPU
Every diagnostic has a code, your line, the shapes it expected and got, and the fix. Agents read it as JSON. check →
The ML framework built for agents
Your agent writes one Lisp file. The compiler checks every shape in milliseconds on a laptop, so mistakes die before they reach a GPU. Then it trains faster than PyTorch.*
$ curl -fsSL https://sx.041.io/install.sh | shmacOS · Linux · one binary, no Python · Agents: curl /llms.txt
The loop, in milliseconds.
One file holds the data, the model, the objective, the optimizer and the run. Nothing else: the compiler owns the training loop.
(require "sexpgpu/nn" cross-entropy gpt)
(require "sexpgpu/optim" sgd)
(defvar train-loader
(loader :sources [(files ["data/tiny-train.jsonl.gz"] :encoding "jsonl.gz")]
:fields [(field :tokens :from "input_ids" :dtype :i32 :shape [17])]
:batch-size 4
:infinite true))
(defun prepare (batch)
(let ((tokens (field batch :tokens)))
(list :inputs (slice tokens 1 0 16) :targets (slice tokens 1 1 17))))
(defvar model (gpt :vocab 32 :layers 2 :dim 32 :heads 2))
(defun objective (prediction targets) (cross-entropy prediction targets))
(defvar optimizer (sgd :lr 0.5))
(defrun :steps 20 :microbatches 2 :precision :f32 :seed 1)$ sexpgpu new minimal.sx --example minimal wrote minimal.sx and data/tiny-train.jsonl.gz from the `minimal` example: 32 parameters, 20 steps
Every shape of every graph, in milliseconds, with no GPU and no data. A mistake names your line, the shapes and the fix, and --json hands it to an agent.
$ sexpgpu check minimal.sx ok: 32 parameters, 38 graphs, 20 steps $ sexpgpu check bad-shape.sx error[E-DIM-001]: matmul: inner dims differ: [2, 16] x [32, 8] --> bad-shape.sx:7:15 | 7 | (lambda (x) (matmul x weight))) | ^^^^^^^^^^^^^^^^^ note: operands: `x` [2, 16] f32, `weight` [32, 8] f32
explain reads back what will run. Train here on the CPU or Metal, then bundle the same run and send it to the cheapest cloud GPU with rrun by 041.
$ sexpgpu run minimal.sx done: 20 steps, train/loss 2.6462 $ sexpgpu bundle minimal.sx -o ~/.sexpgpu/bundles/minimal \ --binary ~/.sexpgpu/linux-x86_64/sexpgpu $ cd ~/.sexpgpu/bundles/minimal $ o41-rrun run --gpu RTX3090,L4,A100 --spot --cuda 13.0 \ --image nvidia/cuda:13.0.3-cudnn-runtime-ubuntu24.04 \ --env SEXPGPU_DEVICE=cuda -f -- ./run.sh lowering: cuda compute_86 NVIDIA GeForce RTX 3090, patterns on done: 20 steps, train/loss 2.6462 [rrun] j-qp6j3k9cpk succeeded exit 0 after 8s
One file is the experiment. The compiler owns the rest.
Every diagnostic has a code, your line, the shapes it expected and got, and the fix. Agents read it as JSON. check →
explain states every knob and where its value came from, the optimizer groups and the memory. diff shows what changed between two runs, and nothing else. explain →
Autodiff, accumulation, evaluation, checkpoints, precision and kernels belong to the compiler, so none of it is subtly wrong in your file. the run file →
Closures and macros at compile time: one macro writes the ablation grid. One binary, no Python: bundle a run once, and on rrun a preempted spot run continues on the next GPU. macros →
And it trains faster than PyTorch.*
The CUDA device compiles the whole step into fused kernels, cuBLAS and cuDNN attention, with no Python anywhere in the loop. On every one of the 14 models we measure it takes less time per step than the same model under torch.compile.
Same model, same data, one type of card: an NVIDIA A100 SXM4 80 GB. Lower is faster: a shorter bar takes less time per step. The line is torch.compile.
| model | SexpGPU s/step | torch.compile s/step | step time |
|---|---|---|---|
| Diffusion denoiser, f32 | 0.001329 | 0.003277 | 0.41× |
| Two-tower InfoNCE, f32 | 0.001315 | 0.003242 | 0.41× |
| Tiny hyperbolic (Lorentz) GPT, f32 | 0.02314 | 0.04341 | 0.53× |
| MLP-Mixer, f32 | 0.002287 | 0.003875 | 0.59× |
| Tiny Mamba, f32 | 0.1801 | 0.2518 | 0.72× |
| Tiny GPT, AdamW, bf16 | 0.02397 | 0.03288 | 0.73× |
| Tiny GPT, Muon, f32 | 0.1163 | 0.1516 | 0.77× |
| Tiny RetNet, f32 | 0.1115 | 0.1448 | 0.77× |
| Geometric transformer, Riemannian Adam, f32, 512 sequences a step | 51.61 | 63.42 | 0.81× |
| Tiny nGPT, bf16 | 0.03201 | 0.0384 | 0.83× |
| Tiny GPT, 8 experts, bf16, top-2 | 0.03015 | 0.0348 | 0.87× |
| Tiny GPT, AdamW, f32 | 0.1068 | 0.1231 | 0.87× |
| GPT-2 small, 124M, bf16 | 2.676 | 2.938 | 0.91× |
| ResNet-9, CIFAR-10, bf16 | 0.0114 | 0.01249 | 0.91× |
* Limited benchmark. 14 training runs on an NVIDIA A100 SXM4 80 GB, each timed against the same model under torch.compile 2.14.0+cu130, on the same data. Step time is the mean of run medians over steady-state steps, measured on 17 October 2026. The two sides of a row may come from different jobs on different cards of that type; the public table records each side's date, commit, driver and versions. No listed shape is slower than torch.compile today. Every shape not listed here is unmeasured. We treat any shape where we are slower as a bug.
Every page of this site answers in markdown, short and dense, one question per page, so an agent reads only what it needs.
$ curl https://sx.041.io/llms.txt