Skip to content
hatimthayyilPublic
forked from nuxyel/bend-ml

About

Machine Learning in Bend 2: Dependent Types, Shapes at the Type Level, and Proven Laws

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

bend-ml: machine learning in Bend 2. Shape errors are type errors; laws checked by the kernel.

CI MIT license Bend 2.0.35 24 laws proved

Two matrices whose shapes do not match fail to fit, and Bend rejects the program. Click for the 60 s video.
the 60 s video: bend-ml.mp4

bend-ml is machine learning for Bend 2. The shape of every tensor lives in its type, so a product of mismatched matrices does not compile, and the structural guarantees (a tokenizer that never loses a byte, a reverse mode that agrees with the forward mode) are laws that Bend's kernel checks. Five packages are on BendHub, and two demos run on them, an MNIST classifier and GPT-2 small, both checked against PyTorch.

01 · A shape error is a type error

bend rejects a (2x3) by (4x5) product: expected Mat<3n, 5n>, observed Mat<4n, 5n>

# (2x3) · (4x5): does not compile
def bad() -> TA.MMul<2n, 3n, 5n>:
  TA.Mat.matmul(2n, 3n, 5n, 0n, TA.Mat.zeros(2n, 3n), TA.Mat.zeros(4n, 5n))

The same holds for every layer of a training step: the gradient of a weight matrix must have the shape of Xᵀ·dY, and a reshape only compiles with a proof that the number of elements does not change.

the reshape case

bend rejects a reshape from 2x6 to 5x3: expected 12n, observed 15n

02 · Laws, not trust

Package Version What it is Proved LAWS
bend-ml-nat-lemmas 0.1.1.0 Nat and List lemmas that Base does not have add_comm, add_assoc, mul_comm, mul_assoc, mul_dist, append_assoc, length_append, product_append... (15)
bend-ml-bpe-tokenizer 0.1.2.0 Byte-level BPE tokenizer (GPT-2 style) roundtrip decode(encode(s)) = s, vocab_bound, dec_append, train_wf, roundtrip_trained
bend-ml-tensor 0.1.2.0 Vec<n> and Mat<r,c> with the shape in the type, over lists reshape_swap, reshape_flat
bend-ml-tensor-array 0.1.3.0 the same guarantees over a flat Array<F32>, ~50x faster cap_ok; typed matmul, matmul_nt, matmul_tn
bend-ml-autograd 0.1.1.0 Automatic differentiation and layers with typed backward reverse_eq_forward
import bend-ml-tensor-array@0.1.3.0/main.bend as TA
import bend-ml-bpe-tokenizer@0.1.2.0/main.bend as BPE

The roundtrip law as Bend states it, and ALL PROOFS CHECK

The five packages: nat-lemmas is imported by bpe-tokenizer, tensor and autograd; tensor-array stands alone; the GPT-2 demo uses bpe-tokenizer, tensor and tensor-array; the MNIST demo uses tensor and tensor-array

Every package passes bend X/main.bend --verdict, the re-check by Bend's Lean-proved kernel, with no @unsafe and no ?TODO. Numbers are a different matter: F32 is not a real number, so the numerics are tested against PyTorch, NumPy and tiktoken instead of proved. docs/AUDIT.md lists the trust base.

03 · It runs

GPT-2 small in Bend generates 'The capital of France is the capital of the French Republic, and'

Demo Result
MNIST, 784-128-10 MLP about 7 s per epoch (v1: 544 s). Loss and hits identical to PyTorch with the same weights and batches: 0.5204771 / 9129, then 0.27043572 / 9298, then 0.2156194 / 9418.
GPT-2 small, 124 M about 0.1 s per token (v1: 3 s). The same tokens as PyTorch on 11 prompts, logits within 2e-4; the tokenizer matches tiktoken on 79 texts. Loading takes ~7 s and ~1.5 GB.

04 · Benchmarks

Bar charts: MNIST epoch, v1 544 s, v2 7 s, PyTorch 0.2 s; GPT-2 per token, v1 3 s, v2 0.1 s, PyTorch 55 ms on 1 thread and 21 ms on 16 threads

What moved the needle: lists to a flat Array 49x faster; parallel row blocks 1.8x; GPU on a flat loop 3.8x faster; GPU on our matrix kernels 3 to 18x slower; copying the matrix per task 10x slower

why PyTorch is still ahead

Bend 2.0.35 generates scalar code, with no BLAS or SIMD: on the same matrix product PyTorch does 62 G multiply-adds/s on one thread and Bend with an Array ~2.3 G/s. Parallelism adds ~2 to 4x on this hybrid CPU. The GPU (a user-local CUDA 12 makes ! run on the RTX 4050, see docs/gpu-setup.md) is 3.8x faster on compute-bound flat loops but 3 to 18x slower on our memory-bound kernels, so the benchmarks use the CPU. Full tables: MNIST, GPT-2, and NOTES.md, experiments 1 to 10.

05 · What we learned

  • Types can carry the shapes of a whole training step, and they cost nothing at run time: the dimensions are erased.
  • Proofs cover the structure, tests cover the numbers. Of the 24 laws, the ones that matter most are the tokenizer roundtrip, reverse_eq_forward, the reshape size and the array capacity.
  • The trust base is small and written down: the kernel, Base's F32 primitives, and the fact that Array.new(d) gives 2^d slots.
  • Speed is bounded by the compiler, not by the types. Flat arrays gave ~49x, parallel blocks ~2x; what is left is BLAS and SIMD.
  • Copying is the hidden cost: splitting a matrix for parallel work by copying it costs more than the arithmetic of a matrix · vector.
  • The GPU only helps compute-bound work; our kernels are chains of dependent pointer loads.
  • Measuring corrected some of my early claims: PyTorch takes 21 ms per token, not 150 ms; the GPU is not "slower everywhere"; and the "~10 GB" of GPT-2 memory was virtual size (the resident peak is ~1.5 GB).

06 · Reproduce

Linux x86_64 (or WSL), clang ≥ 14, Python ≥ 3.12, curl, ~3 GB of disk, 8 GB of RAM for the GPT-2 check.

git clone https://github.com/nuxyel/bend-ml.git && cd bend-ml
make setup          # Bend 2.0.35 (SHA256-checked), Lean 4.34.0, Python venv, data; no sudo
make check          # 31 checks, about 1.5 min
make check-full     # 36 checks with GPT-2 and MNIST, about 5 min
make media          # regenerate the figures and the video on this page

make setup-lite skips the 550 MB GPT-2 download. CI runs make check-full on every push. The history was rewritten on 2026-10-04 (English messages, smaller commits), so clone again if you have an older copy.

limits
  • PyTorch is faster (see 04).
  • The GPT-2 weights become trees of nodes; loading streams 1 MB blocks into the arrays, with a ~1.5 GB resident peak (the process reserves ~10 GB of address space).
  • Only Nat, U32 and F32; no F64.
  • Mat<r,c> does not carry "capacity ≥ r·c" in its type: the constructors establish it with the proved cap_ok, and that Array.new(d) gives 2^d slots is trusted.
  • The GPT-2 pre-tokenizer classifies code points up to U+1FFFF with a table generated from Unicode; above that, and for invalid UTF-8, everything counts as a letter.
structure
nat-lemmas/  bpe/  tensor/  tensor-array/  autograd/   packages (main.bend, README, LICENSE)
demos/mnist  demos/gpt2                 demos with README and BENCHMARK
reference/                              PyTorch, tiktoken and check_all.py
poc/  bench/                            proofs of concept and micro-benchmarks
docs/                                   audit guide, launch material, media
NOTES.md                                decisions, findings and debts

MIT · the visual identity uses Bend's own colours, from bend-lang.com

About

Machine Learning in Bend 2: Dependent Types, Shapes at the Type Level, and Proven Laws

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages