S-exp GPU

The ML framework built for agents

ML research at the speed of thought.

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 | sh
See the loop ↓

macOS · Linux · one binary, no Python · Agents: curl /llms.txt

Getting started

The loop, in milliseconds.

  1. 01

    Start from a file

    One file holds the data, the model, the objective, the optimizer and the run. Nothing else: the compiler owns the training loop.

    Show minimal.sx
    (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)
    zsh
    $ sexpgpu new minimal.sx --example minimal
    wrote minimal.sx and data/tiny-train.jsonl.gz from the `minimal` example: 32 parameters, 20 steps
  2. 02

    Check it on a laptop

    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.

    zsh
    $ 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
  3. 03

    Run it here, then on any GPU

    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.

    zsh
    $ 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

Why agents get it right

One file is the experiment. The compiler owns the rest.

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 plan and the run agree

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 →

No loop to get wrong

Autodiff, accumulation, evaluation, checkpoints, precision and kernels belong to the compiler, so none of it is subtly wrong in your file. the run file →

A real Lisp, one binary

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 →

The cherry on top

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.

Training step time against 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.

Diffusion denoiserf32
Two-tower InfoNCEf32
Tiny hyperbolic (Lorentz) GPTf32
MLP-Mixerf32
Tiny Mambaf32
Tiny GPT, AdamWbf16
Tiny GPT, Muonf32
Tiny RetNetf32
Geometric transformer, Riemannian Adamf32, 512 sequences a step
Tiny nGPTbf16
Tiny GPT, 8 expertsbf16, top-2
Tiny GPT, AdamWf32
GPT-2 small, 124Mbf16
ResNet-9, CIFAR-10bf16
Show the numbers
modelSexpGPU s/steptorch.compile s/stepstep time
Diffusion denoiser, f320.0013290.0032770.41×
Two-tower InfoNCE, f320.0013150.0032420.41×
Tiny hyperbolic (Lorentz) GPT, f320.023140.043410.53×
MLP-Mixer, f320.0022870.0038750.59×
Tiny Mamba, f320.18010.25180.72×
Tiny GPT, AdamW, bf160.023970.032880.73×
Tiny GPT, Muon, f320.11630.15160.77×
Tiny RetNet, f320.11150.14480.77×
Geometric transformer, Riemannian Adam, f32, 512 sequences a step51.6163.420.81×
Tiny nGPT, bf160.032010.03840.83×
Tiny GPT, 8 experts, bf16, top-20.030150.03480.87×
Tiny GPT, AdamW, f320.10680.12310.87×
GPT-2 small, 124M, bf162.6762.9380.91×
ResNet-9, CIFAR-10, bf160.01140.012490.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.

Hand it to your agent.

Every page of this site answers in markdown, short and dense, one question per page, so an agent reads only what it needs.

zsh
$ curl https://sx.041.io/llms.txt