Lean Verified Transformers A blog post titled "Lean Verified Transformers" presents a Lean formalization of foundational Transformer properties — including tensor parallelism, data parallelism, batch invariance, permutation invariance, tiling correctness, and sparse-attention locality — with code available at srush/lean-transformer. The post states the text, comments, and structure are human-written while all proofs were written by AI, and argues that as proof costs decline and generated code volume rises, the value of verified code is likely to climb. The work builds a simple neural network library in Lean, defining relu, vector and matrix operations via higher-order functions over Finn → Rat, and a softmax-like normalization, to prove equivariance and invariance properties for specific architectures. This post explores writing formally verified ML code in Lean. Since the cost of proofs is declining rapidly and the amount of code generated is skyrocketing, the value of verified code seems likely to climb. While understanding proofs remains challenging, collaborating with AI to get proofs of easy-to-understand properties seems like a natural middle ground. The goal of this post is to verify foundational properties of Transformers. These are critical properties that are used for parallelization and optimization, including tensor parallelism, data parallelism, batch invariance, permutation invariance, correctness of tiling, and locality of sparse attention models. Code is available at srush/lean-transformer. The text, comments, and structure of the blog are all human-written; the proofs are all written by AI. Hopefully it can also serve as an advanced intro to Lean. Different neural network architectures retain different properties of their input. We generally classify these properties in terms of equivariance and invariance. These allow researchers to reason about what they can learn, and implementers to optimize computation while maintaining equivalence. Our goal will be to prove equivariances and invariances for specific architectures. Notationally, ML definitions often assume the same functions can work on different input shapes, e.g. batch sizes. For this reason, our Lean definition will be a bit complex to allow for functions that are polymorphic over the shape. defEquivariant-- Arguments with { } are implicit{Shape:Typeu}{Input:Shape→Typev}{Output:Shape→Typew}-- Arguments with are explicit f:{shape:Shape}→Inputshape→Outputshape {sourcetarget:Shape} T:Inputsource→Inputtarget S:Outputsource→Outputtarget -- : gives the return type. Here it is a property.:Prop:=∀x,f Tx =S fx defInvariant{Shape:Typeu}{Input:Shape→Typev}{Output:Typew} f:{shape:Shape}→Inputshape→Output {sourcetarget:Shape} T:Inputsource→Inputtarget :Prop:=∀x,f Tx =fx Vectors, Matrices, and Neural Networks We begin by building a simple neural network library in Lean. The relu function takes in a number and returns its non-negative part. Along with the definition, we prove it does what we claim. defrelu z:Rat :Rat:=maxz0theoremrelu non negative-- For all z z:Rat :-- relu is ≥ 0reluz≥0:=z:Rat⊢ reluz≥0-- Do a short grind searchAll goals completed 🐙 Following the style of JAX, we lift scalar functions to operate on vectors. Vectors and tensors are represented as higher-order functions mapping indices to rational numbers. This makes our proofs easier since we do not have to care about storage or efficiency. -- Vector type. Maps a finite set of {0,...,n-1} to a rational.abbrevVector n:Nat :=Finn→Rat-- Examples-- 10, 10, 10, 10, 10 defvector of tens example:Vector5:=fun = 10-- 0, 1, 2, 3 defarange n:Nat :Vectorn:=funi= i-- Greek letters are types.variable{α:Typeu}{β:Typev}{δ:Typew}-- vmap on 1-arg functions.defvmap fn:α- β {n:Nat}: Finn- α - Finn- β :=funa= funi= fn ai -- Example: vector vmap.defvector relu z:Vectorn :Vectorn:= vmaprelu z-- vmap on 2-arg functionsdefvmap2 fn:α- β- δ : Finn- α - Finn- β - Finn- δ :=funab= vmap funi= fn ai bi id-- Add two vectors as + overloadinstance:Add Vectorn whereadd:=vmap2 funab= a+b -- Mul two vectors with overloadinstance:Mul Vectorn wheremul:=vmap2 funab= a b For aggregations, we define a vector scan. Since we are using rationals for simplicity, we do not have an exponential, so we define a "softmax-like" nonlinear normalization instead. -- Fold over vectors.abbrevfori{α:Typeu}{n:Nat} f:Finn→α :Listα:=List.ofFnfdefscan step:σ→α→σ xs:Finn→α initial:σ :σ:=Fin.foldln funstatei= stepstate xsi initial-- Sum is a folddefVector.sum a:Vectorn :Rat:=-- Alternative: scan fun a b = a + b a 0 fori funi= ai .sumdefsoftmax like z:Vectorn :Vectorn:=letweights:Vectorn:=vmap funx= 1+relux zlettotal:=weights.sumvmap funw= w/total weightsdefVector.dot product ab:Vectorn :Rat:= a b .sum As an exercise, let's look at a simple vector theorem. Click the square □ next to each line of the proof and it will show you the current proof state. The proof state divides the context from the goal ⊢. Each step will transform these terms until we can construct the goal. -- Theorem: Multiplication distributes.theoremVector.mul add-- Given vectors a, b, c, of length n abc:Vectorn :-- thena b+c =a b+a c:=n:Nata:Vectornb:Vectornc:Vectorn⊢ a b+c =a b+a c-- Strategy: show equiv for all indices i of the output vectorn:Nata:Vectornb:Vectornc:Vectorni:Finn⊢ a b+c i= a b+a c i-- Apply the rational property to the numbers at position i.All goals completed 🐙 Matrices are defined similarly. We are basically just stacking vmap calls to get our core operations. Note the implementation of matmul in particular, which will be the target of future proofs. Now let us return to our goal of proving network equivariances. Our strategy will be to first show that in general equivariances compose, and then show that they propagate through a neural network. -- Equivariances composetheoremEquivariant.comp-- Boilerplate{Shape:Typeu}{A:Shape→Typev}{B:Shape→Typew}{C:Shape→Typez}{first:{shape:Shape}→Ashape→Bshape}{next:{shape:Shape}→Bshape→Cshape}{sourcetarget:Shape}{T:Asource→Atarget}{S:Bsource→Btarget}{U:Csource→Ctarget}-- If f T x = S f x hfirst:Equivariant Input:=A Output:=B firstTS -- and g S x = U g x hnext:Equivariant Input:=B Output:=C nextSU :-- then g f T x = U g f x Equivariant Input:=A Output:=C funinput= next firstinput TU:=Shape:Type uA:Shape→Type vB:Shape→Type wC:Shape→Type zfirst:{shape:Shape}→Ashape→Bshapenext:{shape:Shape}→Bshape→Cshapesource:Shapetarget:ShapeT:Asource→AtargetS:Bsource→BtargetU:Csource→Ctargethfirst:Equivariant fun{shape}= first TShnext:Equivariant fun{shape}= next SU⊢ Equivariant fun{shape}input= next firstinput TUShape:Type uA:Shape→Type vB:Shape→Type wC:Shape→Type zfirst:{shape:Shape}→Ashape→Bshapenext:{shape:Shape}→Bshape→Cshapesource:Shapetarget:ShapeT:Asource→AtargetS:Bsource→BtargetU:Csource→Ctargethfirst:Equivariant fun{shape}= first TShnext:Equivariant fun{shape}= next SUinput:Asource⊢ fun{shape}input= next firstinput Tinput =U fun{shape}input= next firstinput input All goals completed 🐙-- Equivariances flow through tuplestheoremEquivariant.prod-- Boilerplate{Shape:Typeu}{Input₁:Shape→Typeu₁}{Input₂:Shape→Typeu₂}{Output₁:Shape→Typev₁}{Output₂:Shape→Typev₂}{f:{shape:Shape}→Input₁shape→Output₁shape}{g:{shape:Shape}→Input₂shape→Output₂shape}{sourcetarget:Shape}{T₁:Input₁source→Input₁target}{S₁:Output₁source→Output₁target}{T₂:Input₂source→Input₂target}{S₂:Output₂source→Output₂target}-- If f T1 x = S1 f x hf:Equivariant Input:=Input₁ Output:=Output₁ fT₁S₁ -- and g T2 x = S2 g x hg:Equivariant Input:=Input₂ Output:=Output₂ gT₂S₂ :-- Then