Consider a classifier with two inputs and two possible labels. Give it the point [1.5, 0.0],
and it returns one score for class zero and another for class one. The larger score determines the
prediction. We can evaluate the model at this point and see which class wins. We can also move the
input a little and evaluate it again. Both computations tell us what happened at the points we
chose; neither tells us what happens at every nearby point.
That distinction matters when the input is a measurement. Suppose each coordinate may differ by
up to 0.10 from its recorded value. The possible inputs form a square around [1.5, 0.0].
If the classifier chooses the same label throughout that square, this particular uncertainty
cannot change its decision. If there is a decision boundary inside the square, an input change
within our stated tolerance may cross it. Small input changes can alter classifier predictions,
as Szegedy et al. (2014) demonstrated; evaluating a finite collection of perturbations
leaves open what happens between them.
Neural network verification asks whether a specified property holds for all inputs in a specified
set. Local classification robustness is one such property. Others concern output ranges,
monotonicity, or an inequality used by a controller. The model, the input set, and the property
all belong to the statement: verifying one square around one point does not establish the same
claim everywhere, and preserving a prediction does not show that the prediction is the correct
label in the first place.
I'll start with a classifier because its property is easy to read from the outputs. There are
only two scores to compare, and the same comparison extends directly to more classes. Let f
return the classifier's logits, let X be the input region, and let y and j be the intended
and competing classes. A logit is a score before a probability normalization; it may be negative.
To show that class y stays ahead of class j throughout the region, we seek
\forall x\in X,\qquad f_y(x)-f_j(x)>0.
The subtraction measures the pairwise margin. A positive lower bound on that margin would settle
the claim, provided it refers to the intended model, input region, and arithmetic. Each of those
choices needs an explicit connection to the computation: lowering must preserve the model, the
bound procedure must enclose the graph's values, and accepted certificate data must satisfy the
bound theorem's premises. Rounded execution adds an approximation argument.
For the two-class model, this is one inequality. With ten classes, it is nine inequalities,
one against each competitor. Strict positivity also removes a possible tie: the conclusion does
not depend on which class an implementation selects when two scores are equal. The rest of the
work is finding a lower bound that applies to the whole input region and justifying how it was
computed. The training run below gives us a concrete model and margin to follow.
The maintained workflow trains a 2 -> 8 -> 2 ReLU classifier: two input coordinates, eight
hidden units, and two output scores. Its eight training examples separate by the sign of the
first coordinate. Positive first coordinates have class-zero labels; negative first coordinates
have class-one labels. Training fits that distinction with one-hot cross-entropy and SGD.
Verification then fixes the resulting weights and asks about the model that was actually trained:
-- Keep the parameters produced by these 120 training steps.
let trained ← trainer.train dataset { steps := 120 }
-- Ask whether class zero stays strictly ahead throughout
-- the input box.
let report ← trained.verify center
(radius := 0.10)
(norm := linf)
(property := topLabel 0)
report.printSummary
Tensor shapes use the public notation: [] for a scalar tensor, [n] for a vector, and [m, n]
for a matrix. The call above does not expose recursive shape constructors, graph nodes, flat boxes,
or parameter stores.
Run it with either maintained CPU arithmetic mode:
Terminal
# Run the classifier workflow with its default CPU scalar
# backend.
scripts/lake.sh exe verify -- torchlean-mlp-workflow
# Repeat using the executable IEEE binary32 scalar model.
scripts/lake.sh exe verify -- torchlean-mlp-workflow --arithmetic ieee
The following illustrative values show how to read an abridged report. Rerun the commands above
to obtain the current workflow's output; this table does not establish agreement between backends:
The report lower-bounds class zero by 2.956110 and upper-bounds its only competitor by
-1.277281. Their difference is the positive reported margin 4.233391, so the chosen bound pass
reports certified=true for the requested input box. The semantic and arithmetic bridges below
are required before interpreting that report as a theorem.
The loss describes fitting the training examples. A small training loss says nothing by itself
about the entire perturbation region. The prediction at center line is a
single forward evaluation: 1.885023 beats -3.156008, so the center receives class zero.
The lower and upper arrays answer a different question. Their entries describe bounds on
each score as the input varies. To protect class zero, the unfavorable comparison uses its
lowest allowed score and class one's highest allowed score. Comparing the two center scores
would miss that variation.
Here linf means that each input coordinate may move independently by at most the radius.
In real coordinates the intended region is [1.4, 1.6] × [-0.1, 0.1]. This geometric meaning
is part of the verification request; the representation and rounding of its endpoints still
belong to the arithmetic obligations below.
The reported margin subtracts the competitor's upper endpoint from class zero's lower endpoint.
Using the displayed values gives:
-- Use the unfavorable endpoint for each of the two classes.defcertifiedLower:Float:=2.956110defcompetitorUpper:Float:=-1.277281-- Reconstruct the margin from the six-decimal values in the-- report.4.233391#evalcertifiedLower-competitorUpper
4.233391
The flag tests the computed margin. The six-decimal endpoints above have also been rounded for
display, so subtracting their printed values checks the displayed arithmetic, not the exact
internal margin or a theorem about every point in the box.
A proof of the reported property would connect the source model to the lowered graph, show that
the propagated boxes contain its outputs throughout the input region, and use the positive margin
to compare the two logits. For native binary32 execution, the same argument also needs a bound on
how far its outputs can move from those semantic values.
The obligations also localize maintenance. A lowering change affects the source-to-graph argument;
a new CROWN activation affects bound propagation; and a CUDA deployment claim requires the native
arithmetic link in addition to the real-valued theorem.
The verifier operates on the canonical NN.IR.Graph. An interval or affine form is meaningful only
relative to a denotation of that same graph, parameter store, and input box. A lowering theorem is
therefore part of a source-model claim.
The forward correspondence is a lowering theorem. The typed first-order
proved forward fragment
lowers NN.Verification.Builtin.Proved.ForwardProgram values. Its constructors cover constants,
parameters, arithmetic, ReLU, exp, log, inverse, matrix products, reshapes and
two-axis transposes,
softmax along any valid axis, axis-parametrized LayerNorm, linear and convolution layers, and MSE
loss.
Correctness.lowerForwardProgramToIR_wellFormed proves structural well-formedness, while
Correctness.runForwardIR_eq_evalForward proves equality with the typed program evaluator.
A smaller fact sits underneath it: NN.IR.Graph.checkShapes_sound proves that the shapes
declared in an accepted graph are the shapes its denotation computes.
Both are Lean semantic statements over an abstract scalar Context. They are not statements that a
PyTorch module, CUDA kernel, or vendor library agrees with the graph. General API lowering also
does not inherit the typed-fragment theorem merely because it returns the same IR type.
The following signatures are checked against the imported NN modules when the page builds.
They expose the scalar type, accepted fragment, and success conditions needed to apply each result.
-- The typed source program always lowers to a structurally-- valid graph.@lowerForwardProgramToIR_wellFormed : ∀{α:Type}[inst:TorchLean.Storageα][inst_1:Contextα]{paramShapes:ListSpec.Shape}{inShapeoutShape:Spec.Shape}(p:NN.Verification.Builtin.Proved.ForwardProgramαparamShapesinShapeoutShape)(params:TorchLean.TensorPackαparamShapes),(NN.Verification.Builtin.Proved.lowerForwardProgramToIRpparams).graph.wellFormed=true#check@lowerForwardProgramToIR_wellFormed-- Its graph evaluation agrees with evaluation of the-- original typed program.@runForwardIR_eq_evalForward : ∀{α:Type}[inst:TorchLean.Storageα][inst_1:Contextα]{paramShapes:ListSpec.Shape}{inShapeoutShape:Spec.Shape}(p:NN.Verification.Builtin.Proved.ForwardProgramαparamShapesinShapeoutShape)(params:TorchLean.TensorPackαparamShapes)(x:TorchLean.TensorαinShape),NN.Verification.Builtin.runForwardIR(NN.Verification.Builtin.Proved.lowerForwardProgramToIRpparams)x=NN.Verification.Builtin.Proved.evalForwardpparamsx#check@runForwardIR_eq_evalForward-- Successful shape checking agrees with the shapes of-- successfully computed values.@NN.IR.Graph.checkShapes_sound : ∀{α:Type}[inst:TorchLean.Storageα][inst_1:Contextα](g:NN.IR.Graph)(payload:NN.IR.Payloadα)(input:Spec.SomeTensorα)(vals:Array(Spec.SomeTensorα)),g.checkShapes=Except.ok()→g.denoteAllpayloadinput=Except.okvals→∀(i:ℕ)(hi:i<g.nodes.size)(hiv:i<vals.size),vals[i].shape=g.nodes[i].outShape#check@NN.IR.Graph.checkShapes_sound
Each #check prints a declaration's type. It does not execute the model or apply the theorem to
this classifier. In the first output, read everything before the final expression as the data
the theorem accepts; the expression ending in wellFormed = true is what it establishes. The
second output ends in an equality between two evaluators, both given the same parameters and
input. That equality is the link needed when a bound on one representation is to say something
about the other.
In these signatures, ∀ means “for every,” {...} contains arguments Lean can often infer, and
[...] requests an interface instance, such as the scalar operations in Context α. An arrow
P → Q says that a proof of P yields a proof of Q. Long namespaces tell Lean which definition
is meant; the important reading order is the objects, then the hypotheses, then the conclusion.
For example, the last output needs successful shape checking and successful denotation before it
says anything about a value's shape.
The typed-program results need no additional success premise: the program constructors, shapes,
and scalar interfaces already restrict the objects they accept. The equality includes errors, so a
raw logarithm of a nonpositive input fails in the same way on both sides.
Shape soundness has a different role. Once shape checking and denotation both succeed, it identifies
the shape of each computed value with the declaration at the corresponding node. It supplies no
existence result for the value table: successful denotation is an assumption.
Interval bound propagation assigns each node a box. For an affine layer
y=Wx+b,\qquad x\in[\ell,u],
the usual sign split gives
\ell_y=W^+\ell+W^-u+b,\qquad
u_y=W^+u+W^-\ell+b,
where W^+=\max(W,0) and W^-=\min(W,0). Monotone activations transform endpoints; ReLU maps
[\ell,u] to [\max(0,\ell),\max(0,u)]. Elementwise multiplication needs all endpoint products.
For one weighted input, a nonnegative weight preserves the order of the endpoints and a
nonpositive weight reverses it. Splitting the weight into these two parts gives the lower and upper
bounds below. Summing this scalar argument across a row gives the affine-layer rule.
/-- Every real number is the sum of its positive and
negative parts, written with `max` and `min` so that
both summands have a known sign. -/theoremweight_eq_max_add_min(w:ℝ):w=maxw0+minw0:=w:ℝ⊢ w=maxw0+minw0w:ℝh:w≤0⊢ w=maxw0+minw0w:ℝh:0≤w⊢ w=maxw0+minw0w:ℝh:w≤0⊢ w=maxw0+minw0All goals completed! 🐙w:ℝh:0≤w⊢ w=maxw0+minw0All goals completed! 🐙example(wxlub:ℝ)(hl:l≤x)(hu:x≤u):maxw0*l+minw0*u+b≤w*x+b:=w:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤u⊢ maxw0*l+minw0*u+b≤w*x+b-- Positive weights use the lower input endpoint; negative-- weights use the upper.w:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*l≤maxw0*x⊢ maxw0*l+minw0*u+b≤w*x+bw:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*l≤maxw0*xh2:minw0*u≤minw0*x⊢ maxw0*l+minw0*u+b≤w*x+bcalcmaxw0*l+minw0*u+b≤maxw0*x+minw0*x+b:=w:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*l≤maxw0*xh2:minw0*u≤minw0*x⊢ maxw0*l+minw0*u+b≤maxw0*x+minw0*x+bAll goals completed! 🐙_=w*x+b:=w:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*l≤maxw0*xh2:minw0*u≤minw0*x⊢ maxw0*x+minw0*x+b=w*x+bAll goals completed! 🐙example(wxlub:ℝ)(hl:l≤x)(hu:x≤u):w*x+b≤maxw0*u+minw0*l+b:=byw:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤u⊢ w*x+b≤maxw0*u+minw0*l+b-- Reverse the endpoint choices to obtain an upper bound.haveh1:maxw0*x≤maxw0*u:=mul_le_mul_of_nonneg_lefthu(le_max_right__)w:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*x≤maxw0*u⊢ w*x+b≤maxw0*u+minw0*l+bhaveh2:minw0*x≤minw0*l:=mul_le_mul_of_nonpos_lefthl(min_le_right__)w:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*x≤maxw0*uh2:minw0*x≤minw0*l⊢ w*x+b≤maxw0*u+minw0*l+bcalcw*x+b=maxw0*x+minw0*x+b:=byw:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*x≤maxw0*uh2:minw0*x≤minw0*l⊢ w*x+b=maxw0*x+minw0*x+brw[←add_mul,w:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*x≤maxw0*uh2:minw0*x≤minw0*l⊢ w*x+b=(maxw0+minw0)*x+b←weight_eq_max_add_minw:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*x≤maxw0*uh2:minw0*x≤minw0*l⊢ w*x+b=w*x+b]All goals completed! 🐙_≤maxw0*u+minw0*l+b:=byw:ℝx:ℝl:ℝu:ℝb:ℝhl:l≤xhu:x≤uh1:maxw0*x≤maxw0*uh2:minw0*x≤minw0*l⊢ maxw0*x+minw0*x+b≤maxw0*u+minw0*l+blinarithAll goals completed! 🐙
The graph proof applies local enclosure arguments like these at each node in topological order.
The induction maintains enclosure for every earlier value, so a node can use the bounds on its
parents. Tensor dimensions, parameter lookup, and missing values must also agree between the bound
pass and the semantic evaluator. Directed floating arithmetic adds the obligation that rounded
endpoints remain on the enclosing side of the exact result.
The generic real soundness theorem is cert_encloses_semantics. It requires:
TopoSorted g;
Supported g;
exact local certificate consistency CertLocalOK;
exact local value consistency SemLocalOK;
InputsEnclosed.
It concludes that each available certificate box encloses the matching semantic value. The current
Supported predicate contains input, constant, detach, addition, subtraction, elementwise
multiplication, ReLU, linear, matrix multiplication, concatenation, convolution, tanh, sigmoid,
softplus, safe logarithm, sine, and cosine nodes.
The proof-side real evaluator has the stronger end-to-end theorem
runIBP?_encloses_evalGraphRec. It proves that the particular real runIBP? construction encloses
evalGraphRec, under topological order, supported operations, and enclosed inputs.
The executable engine has a separate theorem on a named core. runIBP_eq_runIBP? proves that the
engine's runIBP computes exactly the proof-side pass on graphs whose nodes are all in
EngineCore (input, constant, detach, addition, subtraction, elementwise multiplication, ReLU,
linear, matrix multiplication, concatenation, convolution, softplus, and safe logarithm),
provided the semantic-support guard
succeeds and the proof-side pass produced a box at every node (IBPCovers).
runIBP_encloses_evalGraphRec then gives enclosure
for the executable engine directly. Both are stated over ℝ; operations outside EngineCore
and every rounded scalar remain outside these two theorems.
Rounded endpoints have the full forward theorem runIBP_encloses_all in
DirectedIBPFullSoundness.
It requires LawfulBoundOps, LawfulNonlinearBoundOps, and LawfulMinBoundOps, together with
a nonnegative real interpretation of the backend's fixed normalizationEpsilon. The reals and
the rounded-real FP32 model satisfy these scalar requirements.
Every box the executable runIBP returns encloses the real value of its node, provided parents
precede their consumers, the seed boxes contain the real inputs, and the real point satisfies
RealNodeEquation at each node. These equations use the actual coordinate maps, reductions,
normalization formulas, and real interpretations of stored parameters; convolution uses the
exact spatial operation. Supported transfers include convolution, binary matrix multiplication,
structural operations, and normalization. The proof derives their intermediate enclosures by
induction.
Invalid shapes, missing parameters, and unavailable arithmetic transfers can still leave an entry
as none; the theorem establishes enclosure whenever a box is returned.
Grouped scaled contractions have a separate numerical-certificate range rule; the direct IBP and
CROWN passes currently reject them.
Random nodes use the actual real Spec.Random.uniform and Spec.Random.mask values at
Spec.Random.keyOf seed id and the node's row-major coordinate. Uniform nodes have no parents;
a mask has one existing scalar parent supplying its keep probability.
The lemmas in RandomSupport prove that each
uniform value lies in [0,1) and each mask value is zero or one, with keep probabilities zero and
one giving the corresponding constant masks. Support follows from these seeded source equations.
Agreement with a separate rounded or native random implementation remains a numerical
correspondence question.
These are the IR's own random-node semantics. Model lowering currently rejects runtime random
draws because their keys count random calls rather than IR nodes; it must not silently substitute
a different mask. Deterministic evaluation-mode dropout does not draw a mask.
GraphPoint.ofRunIBPAll feeds these bounds to the backward proof and derives its node equations
from RealNodeEquation. For checked convolution, positive dilation makes each affine coefficient
a stored weight or zero, so interpreting that coefficient agrees with the real spatial operation.
This step does not assume that ordinary rounded addition is exact. Structural operations use the
same coordinate maps in both directions.
The public theorem backwardObjectiveBox_encloses_runIBP_all in
DirectedBackwardEvaluation
then connects successful forward IBP, directed backward propagation, and final interval evaluation.
It requires exact affine reassociation to be disabled, as it is for FP32,
consistent node identifiers, valid input and output dimensions, an objective of the output
dimension, and an enclosing input box. Every successful .ok query contains the real objective.
No intermediate enclosure or additional backward node equation is assumed.
For a graph in the ibpForwardSupported core, runIBP_encloses, GraphPoint.ofRunIBP, and
backwardObjectiveBox_encloses_runIBP use NodeEquation directly. These theorems need no
LawfulMinBoundOps instance or normalization-epsilon assumption, since the core excludes the
operations that require them.
-- Locally consistent real boxes enclose the corresponding-- semantic values.cert_encloses_semantics : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(cert:Array(Option(NN.MLTheory.CROWN.FlatBoxℝ)))(inputs:Std.HashMapℕVal)(vals:Array(OptionVal)),TopoSortedg→Supportedg→CertLocalOKgpscert→SemLocalOKgpsinputsvals→InputsEnclosedgpsinputs→∀id<g.nodes.size,matchcert[id]!,vals[id]!with|someB,somev=>EnclosesBoxBv|x,x_1=>True#check@cert_encloses_semantics-- Instantiate those boxes with the proof-side interval-- propagation pass.runIBP?_encloses_evalGraphRec : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(inputs:Std.HashMapℕVal),TopoSortedg→Supportedg→InputsEnclosedgpsinputs→∀id<g.nodes.size,match(runIBP?gps)[id]!,(evalGraphRecgpsinputs)[id]!with|someB,somev=>EnclosesBoxBv|x,x_1=>True#check@runIBP?_encloses_evalGraphRec-- On the supported engine core, the executable real pass-- equals that construction.runIBP_eq_runIBP? : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ),TopoSortedg→EngineCoreg→g.crownGraphSemanticsSupportedps=true→IBPCoversg(runIBP?gps)→g.runIBPps=runIBP?gps#check@runIBP_eq_runIBP?-- Transfer enclosure to the executable real pass under its-- coverage hypotheses.runIBP_encloses_evalGraphRec : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(inputs:Std.HashMapℕVal),TopoSortedg→EngineCoreg→IBPCoversg(runIBP?gps)→InputsEnclosedgpsinputs→∀id<g.nodes.size,∀(B:NN.MLTheory.CROWN.FlatBoxℝ)(v:Val),(g.runIBPps)[id]!=someB→(evalGraphRecgpsinputs)[id]!=somev→EnclosesBoxBv#check@runIBP_encloses_evalGraphRec
The first two conclusions examine a pair of optional entries. Enclosure is asserted only when
both a box and a semantic value are present; the other cases return True. Thus these theorems can
justify an available bound without promising that the pass produces every bound an application
needs. IBPCovers supplies the separate coverage condition in the executable-engine results.
The names describe the pieces of the graph induction. TopoSorted puts a node's parents before
that node. Supported restricts the operations to those with a proved transfer rule.
CertLocalOK says that the stored boxes follow those rules, and SemLocalOK says that the stored
values follow the graph's evaluator. InputsEnclosed starts the induction by placing the actual
input values inside the input boxes. For the classifier, the conclusion we ultimately need is
enclosure at the output node; these hypotheses explain how the proof can reach that node.
runIBP_eq_runIBP? identifies the executable real pass with the proof-side construction under its
listed guards. Substituting that equality into the proof-side enclosure result gives a theorem
about the engine. No second arithmetic argument is needed for that substitution.
The theorems establish enclosure, but a box can be sound and too wide to settle the property.
We can measure that excess width by running the pass on a graph whose exact range we can
calculate by hand.
Consider the map x\mapsto (x_0+x_1)-(x_0-x_1), written as two affine layers. Composed, it is
exactly 2x_1, so on the unit box [-1,1]^2 the exact output range is [-2,2]. The bound
engine works on IR graphs rather than on layer combinators, so the graph is spelled out node by
node, and the weights live beside it in a ParamStore:
/-- Two affine layers computing `(x₀+x₁) - (x₀-x₁)`.
Node 1 produces both intermediate coordinates; node 2
subtracts them. -/defdepGraph:NN.IR.Graph:={nodes:=#[{id:=0,parents:=#[],kind:=.input,outShape:=[2]},{id:=1,parents:=#[0],kind:=.linear,outShape:=[2]},{id:=2,parents:=#[1],kind:=.linear,outShape:=[1]}]}/-- The input region: the unit box in the sup norm. -/defdepBox:NN.MLTheory.CROWN.FlatBoxFloat:={dim:=2,lo:=[-1.0,-1.0],hi:=[1.0,1.0]}/-- Weights and biases for the two layers, keyed by the
node they belong to. -/defdepStore:NN.MLTheory.CROWN.Graph.ParamStoreFloat:={inputBoxes:=(Std.HashMap.emptyWithCapacity).insert0depBoxlinearWB:=(Std.HashMap.emptyWithCapacity)|>.insert1{m:=2,n:=2,w:=[[1.0,1.0],[1.0,-1.0]],b:=[0.0,0.0]}|>.insert2{m:=1,n:=2,w:=[[1.0,-1.0]],b:=[0.0]}}
Now run both bound methods on the same store and print the output box of node 2:
-- Both entry points live in `NN.MLTheory.CROWN.Graph`.-- Opening it for this one command keeps the printed-- signatures elsewhere in the chapter fully qualified.openNN.MLTheory.CROWN.GraphinIBP lo=[-4.000000] hi=[4.000000]
CROWN lo=[-2.000000] hi=[2.000000]
#evaldoletboxes:=runIBP(α:=Float)depGraphdepStorematchboxes[2]!with|someb=>IO.printlns!"IBP lo={Spec.prettyb.lo} hi={Spec.prettyb.hi}"|none=>IO.println"no IBP box"matchoutputBoxCROWN?(α:=Float)depGraphdepStoredepBox022with|.okb=>IO.printlns!"CROWN lo={Spec.prettyb.lo} hi={Spec.prettyb.hi}"|.errore=>IO.printlns!"CROWN failed: {e}"
IBP returns twice the exact width. Its box for node 1 is
[-2,2]\times[-2,2], and each coordinate really does range over [-2,2]. What the box cannot
record is that the two coordinates move together: whenever x_0+x_1 is at its maximum, x_0-x_1
is 0. The second layer subtracts them, and interval subtraction has to assume the worst case
that the first is at 2 while the second is at -2, which no input achieves. This is dependency
loss: each interval retains a coordinate's range but loses its relation to the others. Repeating
this loss across layers can make box propagation alone
(Gowal et al., 2018) progressively looser as depth grows.
CROWN (Zhang et al., 2018) carries an affine form of each node in terms of the input
instead of a box, so the cancellation happens symbolically before any interval is evaluated:
1\cdot(x_0+x_1)-1\cdot(x_0-x_1) becomes 2x_1, and only then is the box substituted. The
result is the exact range. That exactness is a property of this graph, not of the method: the graph
is purely affine, so the affine form is the function. An unstable ReLU can introduce a strict
relaxation; stable ReLUs may remain exact. The amount
of improvement over IBP depends on the chosen affine bounds and graph.
The real affine map attains its extrema at box corners. The following Float run checks that the
same node array and corresponding payload produce the expected corner values. It uses
NN.IR.Graph.denote, so it also lets us compare graph evaluation with the bound pass:
/-- The same two layers as executable parameters. -/defdepPayload:NN.IR.PayloadFloat:={linear?:=funid=>ifid=1thensome{outDim:=2,inDim:=2,W:=[[1.0,1.0],[1.0,-1.0]],b:=[0.0,0.0]}elseifid=2thensome{outDim:=1,inDim:=2,W:=[[1.0,-1.0]],b:=[0.0]}elsenone}/-- Evaluate `depGraph` at one input and read off the
single output coordinate. -/defdepEval(x:TorchLean.TensorFloat[2]):ExceptStringFloat:=doletout←NN.IR.Graph.denote(α:=Float)(g:=depGraph)(payload:=depPayload)(input:=Spec.SomeTensor.ofTensorx)(outputId:=2)letvec←NN.IR.Graph.expectShape(α:=Float)(expected:=[1])outpure(TorchLean.Tensor.getScalarvec0)corner value: -2.000000
corner value: 2.000000
corner value: -2.000000
corner value: 2.000000
#evaldoletcorners:List(TorchLean.TensorFloat[2]):=[[-1.0,-1.0],[-1.0,1.0],[1.0,-1.0],[1.0,1.0]]forxincornersdomatchdepEvalxwith|.oky=>IO.printlns!"corner value: {y}"|.errore=>IO.printlns!"failed: {e}"
So \pm 2 are attained and the true range is [-2,2]: CROWN's box is tight here, and IBP's
extra factor of two is pure over-approximation. Neither pass is wrong. Soundness only promises
containment, and a sound bound that is too wide reports "cannot verify" on a network that is in
fact robust. Tighter bounds may cost more computation; input subdivision lets us explore that
tradeoff while keeping the local interval rules fixed.
The box representation stores one interval per scalar. The graph still records the shape that
tells us how those scalars interact. A matrix product multiplies the matrices on the final two
axes, broadcasting the leading axes. Vectors participate without a separate reshape:
The first example has six independent 4\times5 by 5\times6 products. In the second,
one left matrix is reused across two right matrices. The third multiplies one vector by each
right matrix, and the fourth is a dot product with a scalar result. The runtime, interval pass,
and backward pass use the same shape contract and coordinate maps. When an operand is reused
by broadcasting, its gradient accumulates contributions from every use.
The runtime proof shows that
flattening the checked IR product gives the certificate evaluator's product for these shapes.
For rounded endpoints, the directed backward pass bounds a binary product's objective using
its output IBP box. It does not propagate that objective's coefficients through both operands.
This interval fallback can lose correlations that an affine transfer would retain.
Concatenation follows the selected axis, with at least two parents and equal dimensions on every
other axis. A parent can have an empty axis:
Here each of the two leading coordinates gets one slice from the first parent and two from the
third; the middle parent contributes no entries. Appending whole flat buffers would mix those
leading coordinates. The value, interval, and affine transfers instead share the same coordinate
map, and the backward pass uses it to split output coefficients among parent occurrences.
If y=\operatorname{concat}(x,x) has coefficient slices u and v, both slices reach the
same input, giving coefficient u+v. Repeated parent ids therefore require accumulation.
The proof-side certificate evaluator uses this layout too. Both its Supported predicate and
the executable EngineCore theorem include concatenation. The
runtime bridge also proves that the IR
evaluator produces these coordinates along every valid axis. The evaluator moves the selected
axis to the front, concatenates the parents, and applies the inverse permutation. The proof
follows those actual permutations, including when a dimension is empty.
For LayerNorm on [2, 2, 2], choosing axis one means normalizing the suffix [2, 2].
There are two independent rows of four coordinates. If the first row contains 0,1,2,3
and the second contains 4,5,6,7, their means are 1.5 and 5.5. Flattening all eight
coordinates into one normalization would give mean 3.5 and change both outputs. The
scale and bias therefore carry the exact suffix shape [2, 2]; [4] has the same number
of entries but fails the payload shape check.
An empty leading batch, such as [0, 2, 2], has no rows to normalize and produces an empty
output. The normalized suffix itself must remain nonempty.
Softmax value bounds also follow the selected axis. On [2, 3, 1], axes zero and one use
the conservative range [0,1] for each coordinate. Axis two has one entry per softmax row,
so its range is [1,1]. These value rules do not require a directed implementation of
exponential. First and mixed derivative transfers also follow the selected axis, keeping
independent rows separate, for backends that enable the ideal softmax derivative rules.
The native Float and Float32 interval backends return no softmax derivative bound;
their value bounds remain available. LayerNorm derivative transfers normalize the whole configured
suffix and require positive epsilon. Their formulas and numerical proof obligations are explained
in the derivative section below.
Convolution transfers share the IR's validation of channels, groups, stride, dilation, and
asymmetric padding. Any leading batch shape is retained, and each batch coordinate is evaluated
independently. The payload stores the full input-channel axis and selects the channels belonging
to each group. In affine propagation, convolution is represented by a dense matrix over flat
input and output coordinates. That representation can be much larger than the kernel tensor,
even though most coefficients are zero.
The theorem ConvProof.conv_linear_matrix_add_bias_eq_grouped_conv in
the convolution proof establishes this equality for
grouped channels, dilation, asymmetric padding, and any leading batch shape. The interval pass
itself sums the kernel contributions with directed products and additions; it does not need to
construct this matrix. Its real enclosure theorem is in
the directed convolution proof.
For a fixed kernel and bias, the input derivative is the same convolution with zero bias.
The derivative proof establishes this
identity and its adjoint for the same geometry. Graph execution still checks the configuration.
The checked graph enclosure in
the graph transfer proof connects those
checks to the operator theorem. Convolution is included in the real certificate induction and
the executable EngineCore theorem.
The IR correspondence proof derives the
same checked geometry from a successful IR convolution and proves equality with the certificate
evaluator. A claim about rounded execution still needs its numerical correspondence.
The elementwise interval maps in CROWN.Runtime.Ops.IBP preserve arbitrary tensor shapes,
including scalars and empty tensors. This extends where the same scalar formulas can be applied.
It does not change their arithmetic hypotheses or extend EngineCore to LayerNorm or softmax.
For a theorem about a complete graph, the executable operator contract and the theorem's supported
fragment both have to match the graph we are using.
Another general option is to partition an input box and evaluate the same graph on every piece.
NN.MLTheory.CROWN.Graph.refinedIBPOutput? g ps inputId outId splitBudget does this without
changing any layer transfer. It applies to any graph supported by IBP,
including mixed architectures.
For example, splitting [-1,1] at zero tightens the ordinary interval bound on
\max(x,0)+\max(-x,0) from approximately [0,2] to [0,1].
The budget counts total splits, so at most 2b+1 IBP calls are made for budget b. Both children
must produce bounds; a failed branch keeps the parent result. Their results are combined by a hull,
then intersected with the parent bound. The shared split boundary preserves all original inputs,
including the cut itself. Adjacent floating-point endpoints with no interior midpoint stay unsplit.
This can reduce dependency loss across layers, but does not promise improvement everywhere. A
linear transfer may already be tight, and an input-independent activation range may remain broad.
The theorem NN.MLTheory.CROWN.Graph.Refinement.splitAt_covers proves real input coverage of each
split. Sound output bounds still depend on the underlying transfers; subdivision does not establish
universal soundness for rounded LayerNorm or other operations.
Artifact replay can opt in with
NN.Verification.Cert.IBPCert.check g ps outId path (refinement := some (inputId, splitBudget)).
The default is unchanged. This is a graph-level option, separate from the high-level trainer API.
trainer.train returns a snapshot of the trained parameters. Its verify operation uses that
snapshot and accepts a tensor center and named choices such as radius, norm, property,
and algorithm. A result obtained from session.finish uses the same snapshot semantics:
further training or loading a checkpoint into the session does not change the model it verifies.
Import NN.API.Verification to use the trained.verify call shown above. The default method is
alphaCrown, fixed-relaxation alpha-CROWN with the ReLU phases IBP proves; ibp and crown
are available for comparison. On a rounded scalar such as binary32, alphaCrown runs directed
CROWN, and the report names CROWN as the method. To inspect graph nodes and intermediate bounds,
import NN.API.Verification.Lowering.
This high-level verifier belongs to the ordinary binary32 training path. A session opened with
trainer.openTyped preserves its chosen scalar in training and prediction, but supplies no
verifier; calling verify on its result reports that verification is unavailable. Choosing a
wider training format does not by itself supply bound arithmetic or soundness proofs for it.
With PyTorch, we can use auto_LiRPA(Xu et al., 2020) to propagate bounds through
the model. The wrapper takes the perturbation region along with the model input:
# Wrap the same model with an interface that propagates
# bounds.
from auto_LiRPA import BoundedModule, BoundedTensor
from auto_LiRPA.perturbations import PerturbationLpNorm
model = BoundedModule(mlp, torch.empty_like(center))
# Each coordinate may vary by at most 0.10 around the
# center.
ptb = PerturbationLpNorm(norm=float("inf"), eps=0.10)
lb, ub = model.compute_bounds(
x=(BoundedTensor(center, ptb),), method="alpha-CROWN")
# Compare class zero's lower bound with class one's upper
# bound.
print((lb[0, 0] - ub[0, 1]).item())
Both examples ask for bounds over the same kind of perturbation region, then compare one class's
lower bound with another's upper bound. The choice of bound method and arithmetic determines how
those endpoints are computed. In TorchLean, the real-semantic transfer theorems and local
certificate predicates describe the evidence needed to justify them. Applying those theorems to
the high-level Float report still requires proofs about the produced data and rounded arithmetic;
the trust ledger below records those obligations.
CROWN (Zhang et al., 2018) propagates affine lower and upper forms rather than only boxes.
Bounding a network by a convex outer approximation and then optimizing inside it predates
CROWN (Wong and Kolter, 2018); CROWN obtains bounds with a backward pass.
At an uncertain ReLU with \ell<0<u, the secant upper
relaxation is
while a lower relaxation may use a slope \alpha constrained to a valid range. Alpha-CROWN
optimizes these slopes. Alpha-beta-CROWN (Wang et al., 2021) additionally records
branch phases: an active branch uses z\ge 0, and an inactive branch uses z\le 0. Splitting on
a phase can tighten the relaxation, at the cost of additional bound passes.
TorchLean's generic theorem crown_checker_encloses_semantics takes an exact
CrownCertLocalOK hypothesis and a separate CrownTransferSound proof. The local transfer
theorems
alphaCrown_transfer_sound;
phasedCrown_transfer_sound
show that the proposition-level alpha-CROWN step, without and with ReLU phases, satisfies
CrownTransferSound under its explicit real-semantic and enclosure hypotheses. The phased step
rejects phase choices inconsistent with the current IBP interval.
The transfer theorems take the IBP boxes as an assumption, IBPEnclosesVals. The composed
corollaries discharge it from the IBP soundness theorem. alphaCrown_cert_encloses_semantics and
phasedCrown_cert_encloses_semantics conclude enclosure from topological order, supported
operations, locally consistent boxes and values, enclosed inputs, valid slopes, and a certificate
that replays the affine step; alphaCrown_cert_encloses_evalGraphRec fixes the boxes to runIBP?
and the values to evalGraphRec, so only the graph, input, slope, and certificate hypotheses
remain. These are the forms a caller should cite.
The executable outputBoxPhasedCROWN? seeds IBP with the requested input box. On backends that
support exact affine reassociation, runPhasedCROWN takes those intervals, infers every ReLU
phase they already force, and rechecks it while replaying affine bounds. Unstable phases remain
unsplit and use the default alpha relaxation. Rounded scalar backends instead use directed CROWN;
nearest-rounded affine coefficients cannot be treated as exact-real enclosures. Both paths are
distinct from the external Alpha-Beta-CROWN optimizer's branch-and-bound search.
-- Combine local certificate consistency with a proof that-- each transfer encloses values.crown_checker_encloses_semantics : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(step:Array(Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ))→ℕ→Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ))(cert:Array(Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)))(inputs:Std.HashMapℕVal)(vals:Array(OptionVal))(ctx:NN.MLTheory.CROWN.Graph.AffineCtx)(x:TorchLean.Tensorℝ[ctx.inputDim]),TopoSortedg→SemLocalOKgpsinputsvals→CrownCertLocalOKgstepcert→CrownTransferSoundgpsinputsvalsctxxstepcert→∀id<g.nodes.size,∀(b:NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)(v:Val),cert[id]!=someb→vals[id]!=somev→EnclosesAtInputctxxbv#check@crown_checker_encloses_semantics-- Establish that enclosure property for real alpha-CROWN-- transfers.alphaCrown_transfer_sound : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(ibp:Array(Option(NN.MLTheory.CROWN.FlatBoxℝ)))(alpha:Array(Option(NN.MLTheory.CROWN.Graph.FlatTensorℝ)))(cert:Array(Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)))(inputs:Std.HashMapℕVal)(vals:Array(OptionVal))(ctx:NN.MLTheory.CROWN.Graph.AffineCtx)(x:TorchLean.Tensorℝ[ctx.inputDim]),TopoSortedg→SemLocalOKgpsinputsvals→InputsMatchinputsctxx→IBPEnclosesValsibpvals→AlphaOKalpha→CrownTransferSoundgpsinputsvalsctxx(stepAlphagpsibpalphactx)cert#check@alphaCrown_transfer_sound-- The same transfer with ReLU phases that the IBP-- intervals already justify.phasedCrown_transfer_sound : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(ibp:Array(Option(NN.MLTheory.CROWN.FlatBoxℝ)))(alpha:Array(Option(NN.MLTheory.CROWN.Graph.FlatTensorℝ)))(phases:Array(Option(Arrayℤ)))(cert:Array(Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)))(inputs:Std.HashMapℕVal)(vals:Array(OptionVal))(ctx:NN.MLTheory.CROWN.Graph.AffineCtx)(x:TorchLean.Tensorℝ[ctx.inputDim]),TopoSortedg→SemLocalOKgpsinputsvals→InputsMatchinputsctxx→IBPEnclosesValsibpvals→AlphaOKalpha→CrownTransferSoundgpsinputsvalsctxx(stepPhasedgpsibpalphaphasesctx)cert#check@phasedCrown_transfer_sound-- Compose alpha-CROWN with the real IBP and-- graph-consistency hypotheses.alphaCrown_cert_encloses_semantics : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(ibp:Array(Option(NN.MLTheory.CROWN.FlatBoxℝ)))(alpha:Array(Option(NN.MLTheory.CROWN.Graph.FlatTensorℝ)))(cert:Array(Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)))(inputs:Std.HashMapℕVal)(vals:Array(OptionVal))(ctx:NN.MLTheory.CROWN.Graph.AffineCtx)(x:TorchLean.Tensorℝ[ctx.inputDim]),TopoSortedg→Supportedg→CertLocalOKgpsibp→SemLocalOKgpsinputsvals→InputsEnclosedgpsinputs→InputsMatchinputsctxx→AlphaOKalpha→CrownCertLocalOKg(stepAlphagpsibpalphactx)cert→∀id<g.nodes.size,∀(b:NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)(v:Val),cert[id]!=someb→vals[id]!=somev→EnclosesAtInputctxxbv#check@alphaCrown_cert_encloses_semantics-- Fix the interval pass and evaluator, discharging their-- local-consistency premises.alphaCrown_cert_encloses_evalGraphRec : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(alpha:Array(Option(NN.MLTheory.CROWN.Graph.FlatTensorℝ)))(cert:Array(Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)))(inputs:Std.HashMapℕVal)(ctx:NN.MLTheory.CROWN.Graph.AffineCtx)(x:TorchLean.Tensorℝ[ctx.inputDim]),TopoSortedg→Supportedg→InputsEnclosedgpsinputs→InputsMatchinputsctxx→AlphaOKalpha→CrownCertLocalOKg(stepAlphagps(runIBP?gps)alphactx)cert→∀id<g.nodes.size,∀(b:NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)(v:Val),cert[id]!=someb→(evalGraphRecgpsinputs)[id]!=somev→EnclosesAtInputctxxbv#check@alphaCrown_cert_encloses_evalGraphRec-- The composed result for certificates with ReLU-- phases.phasedCrown_cert_encloses_semantics : ∀(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStoreℝ)(ibp:Array(Option(NN.MLTheory.CROWN.FlatBoxℝ)))(alpha:Array(Option(NN.MLTheory.CROWN.Graph.FlatTensorℝ)))(phases:Array(Option(Arrayℤ)))(cert:Array(Option(NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)))(inputs:Std.HashMapℕVal)(vals:Array(OptionVal))(ctx:NN.MLTheory.CROWN.Graph.AffineCtx)(x:TorchLean.Tensorℝ[ctx.inputDim]),TopoSortedg→Supportedg→CertLocalOKgpsibp→SemLocalOKgpsinputsvals→InputsEnclosedgpsinputs→InputsMatchinputsctxx→AlphaOKalpha→CrownCertLocalOKg(stepPhasedgpsibpalphaphasesctx)cert→∀id<g.nodes.size,∀(b:NN.MLTheory.CROWN.Graph.FlatAffineBoundsℝ)(v:Val),cert[id]!=someb→vals[id]!=somev→EnclosesAtInputctxxbv#check@phasedCrown_cert_encloses_semantics
The two transfer theorems have the same semantic hypotheses. The phased version adds per-node ReLU
phases phases and uses stepPhased. It needs no extra domain premise because that step accepts
only phases already justified by the supplied intervals. To assume a phase for an unstable ReLU,
a theorem would also have to restrict the input domain to that branch.
The shared predicate CrownTransferSound states the local enclosure obligation. A different
relaxation can use the generic checker theorem once its step function has a proof of that
obligation.
These theorems should not be confused with the JSON node-certificate checker
CROWNNodeCert.check. That IO Bool function parses finite decimal fields into FloatLib
binary32, recomputes the complete IBP trace from trusted inputs and parameters, and replays affine
nodes from previously recomputed affine data. A serialized interval may be wider than the
recomputed interval but may not move inward. Affine replay data must match exactly at the binary32
level. When the certificate gives ReLU phases, the checker also validates their lengths, entries,
and consistency with IBP.
The shared JSON boundary validates input regions before replay begins. Endpoint boxes require
finite arrays of the declared dimension with lo[i] <= hi[i]; center-radius boxes additionally
require a finite nonnegative radius. Incomplete or mixed schemas are rejected. Artifact formats
that prescribe endpoints, including the alpha-beta-CROWN leaf format, request that exact schema
rather than accepting the alternate center-radius notation.
The final decision is CROWNNodeCert.accepts. Its soundness theorem proves that acceptance
supplies CrownCertLocalOK for the exact FloatLib binary32 replay function used by the checker.
-- If the CROWN decision accepts, its stored affine entries-- satisfy the replay rule.CROWNNodeCert.accepts_eq_true : ∀(cert:NodeReplay.CROWNCertificate)(g:NN.MLTheory.CROWN.Graph)(ps:NN.MLTheory.CROWN.Graph.ParamStore(ExecFloat.Binary823FloatFormat.Encoding.ieee(FloatFormat.Encoding.ieee.defaultBias8)CROWNNodeCert.checkNodeEntries._proof_1CROWNNodeCert.checkNodeEntries._proof_2CROWNNodeCert.checkNodeEntries._proof_3CROWNNodeCert.checkNodeEntries._proof_4))(authoritativeIbp:Array(Option(NN.MLTheory.CROWN.FlatBox(ExecFloat.Binary823FloatFormat.Encoding.ieee(FloatFormat.Encoding.ieee.defaultBias8)CROWNNodeCert.checkNodeEntries._proof_1CROWNNodeCert.checkNodeEntries._proof_2CROWNNodeCert.checkNodeEntries._proof_3CROWNNodeCert.checkNodeEntries._proof_4))))(diagnosticsOk:Bool),CROWNNodeCert.acceptscertgpsauthoritativeIbpdiagnosticsOk=true→CrownCertLocalOKg(CROWNNodeCert.replayStepgpsauthoritativeIbpcert)cert.crown#check@CROWNNodeCert.accepts_eq_true
The output says: for any certificate, graph, parameter store, interval trace, and diagnostic
flag, if this decision returns true, then the affine table in the certificate agrees with the
specified replay rule. The arrow before CrownCertLocalOK separates the assumption from the
conclusion. There is no classifier margin in this statement yet.
The arguments have concrete jobs:
Name in the signature
Meaning in the check
cert
The proposed certificate, including its affine table cert.crown and relaxation data.
g
The graph whose nodes the table describes.
ps
The weights, biases, input boxes, and other parameters used by replay.
authoritativeIbp
The interval table supplied to the decision. The IO wrapper constructs it by rerunning IBP.
diagnosticsOk
The combined Boolean result of the wrapper's additional checks.
replayStep ...
The function that recomputes a node's affine bounds from the table and node index.
Array (Option (FlatBox (ExecFloat.Binary 8 23))) describes the interval table's representation.
There is an array slot for each represented node, and a slot may hold a box (some) or no box
(none). A FlatBox stores lower and upper vectors. Their scalar type selects FloatLib binary32,
including its rounding and exceptional values. A theorem about this table therefore concerns
those executable values; a real-valued conclusion needs a further refinement argument.
We can unpack CrownCertLocalOK precisely. It requires the affine table to have the same length
as the graph's node array. At every valid node index, the table entry must equal the result of
replayStep applied to that table and index. If a producer changes an affine coefficient, the
exact comparison can reject it even when the decimal difference looks small. Since entries are
optional, local consistency alone does not assert that every node has a bound.
The theorem connects an executable Boolean decision to a Lean proposition, which lets a later
proof use the result without reasoning again about every componentwise comparison.
There are two details to keep when applying the result. The pure theorem accepts the interval
trace as an argument; calling it authoritativeIbp does not prove where that trace came from.
The wrapper supplies the recomputation. Likewise, the theorem does not interpret the individual
diagnostics behind diagnosticsOk. Its conclusion is the stated local consistency property.
To recover the classifier claim, we still need a soundness argument saying that these replay
rules enclose the network, bounds at the required output, and a positive class margin.
The real theorem crown_checker_encloses_semantics also uses a local-consistency predicate, but
over real affine data and a real step function. The binary32 result cannot be supplied directly
as that premise. A real enclosure requires a refinement argument connecting the rounded replay
to the real transfers, as well as the remaining semantic hypotheses.
There are two different universal statements in this chain. Local replay checks every node in a
finite graph. Robustness concerns every input in a region, which contains infinitely many real
points. The transfer theorem is what connects them: each node's rule preserves an enclosure for
an arbitrary input satisfying the assumptions, and graph induction carries that fact to the
output. Checking every node is useful because of that argument. The number of entries checked
does not replace it, and the printed #check signatures identify the argument rather than
performing it for the displayed classifier.
Checking The Supplied Binary32 Bounds Against Real Arithmetic🔗
There is a stronger decision for a vector input followed by a chain of linear and ReLU layers:
NN.Verification.Cert.FiniteArtifact.accepts. It reads the same binary32 graph parameters,
input box, affine entries, and requested output inequalities. Each finite word has an exact
rational value. We can therefore check whether the supplied coefficients enclose a real
computation without first replacing them with a different certificate.
Here is the rounding issue that this check catches. Put two scalar linear layers in sequence,
each with weight a=1+2^{-23} and zero bias. Their exact composed weight is
a^2=1+2^{-22}+2^{-46}.
Binary32 rounds that product to 1+2^{-22}. An affine upper bound using the rounded coefficient
can agree perfectly with binary32 replay while falling below the real output at a positive input.
The regression in NN.Tests.Verification.FiniteArtifact constructs this case: replay accepts its
table, and the stronger decision rejects it.
The extra check follows the graph from its input. At each layer, it computes an exact rational
transfer from the already checked parent bounds. The supplied lower affine form must lie below
that transfer throughout the input box, and the supplied upper form must lie above it. The
decision then checks every requested output inequality using those supplied bounds. Strict and
non-strict inequalities are separate choices, so a zero margin cannot satisfy a strict request.
FiniteArtifact.accepts_graph_sound connects this decision to the original graph's denote
function, using the exact real values of its decoded binary32 parameters. For every real input
inside the decoded box, the graph evaluates successfully and all requested inequalities hold.
The statement includes the actual artifact entry at the designated output. Missing bounds,
nonfinite values, empty input or output dimensions, an empty collection of inequalities, and an
output id that does not cover the whole chain are rejected.
This decision covers nonempty vector linear/ReLU chains. The general JSON replay decisions above
retain their local-consistency conclusions. The real computation in the stronger theorem also
differs from a native floating-point run: relating a LibTorch result to that real value still
requires an execution-error bound.
The graph engine does not assume that every scalar type can safely evaluate every interval rule.
BoundOps supplies executable lower and upper addition, subtraction, and multiplication.
LawfulBoundOps is the corresponding proof interface: it interprets each endpoint as a real
number and proves that the lower operation is below exact arithmetic and the upper operation is
above it. Sound arithmetic lemmas require this second interface. NonlinearBoundOps supplies
executable interval transfers for operations such as division, square root, exponential, logarithm,
and layer normalization. A successful computation is not itself a theorem. The separate
LawfulNonlinearBoundOps class proves that every returned interval encloses the corresponding real
operation; sound entrypoints must require this class. A transfer returns none when the scalar
backend has no finite implementation for that operation.
These classes specify directed endpoint operations and separate scalar soundness laws; they do
not implement the full IEEE interval-arithmetic standard.
A point interval already shows why the distinction matters. Both entries of
x=(1,2^{-25}) are exactly representable in binary32, but their real sum
1+2^{-25} rounds to 1 under nearest rounding. Using that same rounded value as both
endpoints would return [1,1] and exclude the sum. The engine's reductions accumulate a lower
sum downward and an upper sum upward. A mean also encloses the coordinate count before division,
since a large count need not be exactly representable in the chosen format.
Average pooling uses the same directed mean for each window, including padded zeros in its count.
Input regions round the center-minus-radius downward and center-plus-radius upward. Eval-mode
BatchNorm encloses the stabilized variance, square root, division, scale, and bias separately.
Its running statistics are fixed, but computing a normalization coefficient still involves
rounding. These executable transfers live in
the interval engine;
their availability does not enlarge the EngineCore theorem fragment stated above.
ℝ and FP32 have lawful nonlinear instances. The FP32 proofs use its exact-real floor and
ceiling rounding theorems. The FloatLib binary32
executable interval adapter instead
checks finite, ordered endpoints and uses certified rational interval kernels followed by outward
binary32 rounding. Its eight IEEE32ExecBounds.*Bounds_containsReal theorems cover division,
exponential, logarithm, square root, sigmoid, tanh, sine, and cosine. Each theorem starts from a
successful transfer and exact finite endpoint decoding, then encloses every real member of the
input interval. These include irrational inputs and trigonometric extrema inside the interval.
Exponential and logarithm now return finite enclosures when their checks succeed. Invalid domains,
exceptional endpoints, reversed intervals, and overflow can return none. Square root clamps the
negative part of an interval crossing zero, matching Real.sqrt; sigmoid and tanh retain their
global codomain fallbacks. layerNormAbsBound still returns none, and
supportsIdealCoupledDerivatives is false. These finite containment theorems do not supply a
global LawfulBoundOps instance for the IEEE carrier, which includes infinities and NaNs, or an
error bound for a native transcendental implementation. Host Float widens basic binary64
arithmetic by one adjacent representable value, but it likewise has no global LawfulBoundOps
instance and makes no enclosure claim for the host transcendental library.
The CROWN output query uses a forward affine sweep when the scalar backend permits exact
reassociation. Rounded backends request directed backward bounds for each output coordinate.
Where a node has an IBP box but no coefficient transfer, that box bounds the active objective
without sending coefficients to the node's parents. Binary matmul and MSE loss use this fallback.
It can lose correlations, so finishing a CROWN pass does not guarantee a tighter result than IBP.
The rounded backward theorem
covers coefficient propagation, the output-box fallback, and final interval evaluation.
backwardObjectiveBox_encloses_runIBP_all uses GraphPoint.ofRunIBPAll to derive the
intermediate enclosures and backward node equations from the input boxes and RealNodeEquation.
Its scalar laws, nonnegative default epsilon, graph-order and dimension premises, and successful
result hypothesis are the ones described above. A returned result is sound under these premises;
unsupported transfers can still prevent success.
ReLU, LayerNorm, and the other nonlinear nodes have no coefficient transfer on rounded backends,
so they pass through their IBP box as well, and a rounded MLP "CROWN" bound is IBP at every ReLU.
Relating these bounds to a separate native execution additionally requires a runtime-approximation
theorem.
The first-derivative interval pass starts with an input direction.
runDirectionalDerivative propagates a point or interval of directions through the graph, using
the value boxes from IBP. A coordinate vector selects a partial derivative.
runScalarDerivative supplies the direction 1 for a scalar input and rejects a larger input
box. Both entrypoints use the same local transfer rules.
For two directions u and v, runMixedSecondDerivative propagates an enclosure of
D^2f(x)[u,v]. At a composed node f=h\circ z, the rule must retain both terms:
The two directions can differ, and the input can have any number of coordinates.
runSecondDirectionalDerivative uses the same direction twice.
For a scalar output, runHessianVectorProduct pairs each coordinate direction e_i with a
fixed v: its ith result encloses D^2f(x)[e_i,v]=(H_f(x)v)_i.
These entrypoints share the mixed-derivative pass, so an unavailable operator transfer leaves the
corresponding result unresolved in each API.
LayerNorm applies this rule independently to each normalized row. Its fixed scale multiplies the
derivative; its fixed bias contributes zero. For example, take a constant row (5,5), scale
(2,3), epsilon 1, and direction (1,0). Centering the direction gives
(1/2,-1/2), so the output derivative is (1,-3/2), whatever the fixed bias.
A direction in another row does not enter this calculation.
Positive epsilon gives a useful bound even when the input row is constant:
The interval transfer uses this bound together with centered input and direction bounds.
The mixed rule includes the change in variance and the upstream mixed derivative. It supports
the complete normalized suffix: shape [2,2,2] at axis one gives two rows of four coordinates.
Nonpositive epsilon, nonfinite endpoints, and inconsistent affine shapes leave the transfer
unresolved.
The theorem layerNormDerivativeRow?_encloses_fderiv in
the row enclosure proof
follows the executed calculation: means, centered radii, the reciprocal standard deviation, and
both terms of the mixed rule. If the incoming boxes enclose the values and derivatives, a successful
row calculation encloses the actual real first and mixed derivatives. The theorem requires the
scalar operation laws and exact interpretations of the literals zero through four; its real-endpoint
corollary discharges those arithmetic assumptions.
This supplies the local LayerNorm step. A derivative certificate for a complete floating-point
graph also needs an induction through its other operations and the corresponding scalar
backend's numerical laws.
Suppose a sound bound procedure produces, for every x in the input box,
f_y(x)\ge L_y,\qquad f_j(x)\le U_j.
Then L_y-U_j>0 proves the pairwise class margin. A multiclass certificate repeats this for every
j\ne y. The arithmetic is elementary; the substantive obligations are that:
the bounds enclose the exact graph semantics;
the graph denotes the intended model;
the input box denotes the intended perturbation set;
any rounded or native execution is related to the exact graph.
For a rounded implementation with coordinate errors
\lvert f_i^{\mathrm{run}}(x)-f_i(x)\rvert\le\varepsilon_i, the transferred margin is
The right-hand side must remain positive. A real CROWN theorem alone does not prove the native
binary32 claim; the FP32 and runtime-approximation sections describe the additional bridge.
Both error terms are subtracted because rounding can move the two scores in opposite unfavorable
directions: down for the chosen class and up for its competitor. This also explains why a very
small positive real margin may be insufficient for a rounded implementation. If the transferred
lower bound is zero or negative, the argument has failed to establish strict dominance. It has
not produced an input where the classifier changes its label. Finding such an input is a
different result from failing to prove that none exists.
TorchLean currently checks several kinds of artifact, each with a deliberately limited meaning.
The graph numerical certificate records source ranges, derived node ranges, a registry name,
and a backend-plan audit. generateChecked reconstructs this data, and executeIEEE32 performs a
bit-level reference replay while checking each intermediate tensor. A GraphRangeContract
contains an executable derive function but no semantic soundness field. A proof-level error trace
therefore also requires a separately constructed ProvedRealEnclosure, whose fields supply the
real denotation and enclosure proof. See the runtime-approximation section for the complete
boundary.
The external alpha-beta-CROWN leaf checker AbCrownLeafCert.check validates the JSON schema,
finite ordered root and leaf boxes, dimensions, containment of each represented leaf in the root,
and the exported lower-bound witness relative to its threshold. It also checks root coverage using
the closed grid cut by leaf and root endpoints, returning an error above one million cells. This
executable check has no accompanying soundness theorem. The lower bounds' relationship to the
network semantics remains a producer obligation.
This yields three distinct uses of "certificate":
a Lean term containing proof fields;
an artifact accepted by a structural or numerical checker;
an externally produced claim whose semantic validity is assumed.
Proof-carrying code in the sense of Necula (1997) requires a sound checker for the
property being claimed. Structural validation alone can be useful without establishing that
stronger property. The command's acceptance predicate, rather than the artifact's name, determines
what evidence is available.
The certificate chapter runs the leaf checker, changes a witness so that it must fail, and explains
which stronger artifact would be needed to obtain root-region soundness.
The verifier registry includes several implemented families that exercise different semantic
objects:
lirpa-mlp, lirpa-cnn, lirpa-attention, lirpa-gru, and lirpa-encoder replay their
corresponding finite LiRPA JSON formats. Acceptance is specific to each format and supported
operator fragment.
camera-box3d-cert checks a camera/3D-box artifact. Its
Box3D
implementation includes interval-operation soundness lemmas and checkCert_sound, connecting a
successful pure check to the stated projection, positive-depth, and image/bounding-box guards.
vnncomp-mnistfc parses the supported VNN-COMP-style MNIST-FC suite and runs the in-repo bound
workflow. This is support for that declared suite, not for every VNN-LIB/ONNX benchmark.
digits and digits-train-certify run the prepared sklearn-digits robustness paths; one checks
supplied weights and the other trains before compiling and reporting bounds.
the three twostage-* commands implement the Lyapunov workflows described in the two-stage
chapter, with distinct external-producer, hybrid, and all-in-Lean boundaries.
scripts/lake.sh exe verify -- list is the authoritative command inventory. A successful run
establishes the
acceptance predicate or reported computation documented for that command; it does not merge these
heterogeneous formats into one global verification theorem.
The proof float FP32 is NF binaryRadix (Model.fexpOf FloatFormat.binary32) nearestEven: a
rounded-real model with gradual
underflow. Its exponent description has no upper bound, so it does not model overflow, NaN,
infinity, or signed-zero payload behavior. FloatLib binary32 is the executable bit-level model.
The finite addition theorem connects executable
addition to the proof-level rounding model under the explicit hypothesis that the result is finite.
Corresponding
multiplication, division, and square-root bridges carry their own finite-path hypotheses. Layer
and MLP theorems then propagate explicit error budgets. Neither layer is an unstated theorem about
native hardware or a vendor reduction schedule.
The generic IEEE32 CROWN theorem likewise leaves the node evaluator and
CrownTransferSound proof to its caller. Choosing an IEEE scalar type does not discharge the
floating refinement obligations.
A verification report should make the following boundary visible:
Evidence
Established in current source
Not established by that evidence
Correctness.runForwardIR_eq_evalForward
typed proved-program and IR evaluator agree
arbitrary frontend or native runtime agreement
runIBP?_encloses_evalGraphRec
proof-side real IBP encloses proof-side real semantics
every executable IBP implementation
runIBP_encloses_evalGraphRec
the executable real engine encloses the semantics on EngineCore graphs with full coverage
node kinds outside EngineCore, rounded scalars, or the JSON checkers
runIBP_encloses_all
every returned box encloses its RealNodeEquation value under the scalar laws, node order,
enclosed inputs, and nonnegative default normalization epsilon
that every node returns a box, or that a native execution satisfies those real equations
backwardObjectiveBox_encloses_runIBP_all
a successful rounded objective query encloses its real value through forward IBP, backward
propagation, and final interval evaluation, under the scalar, graph, and input hypotheses
total success, improved tightness over IBP, or native runtime equivalence
alphaCrown_cert_encloses_evalGraphRec
α-CROWN enclosure with the IBP hypothesis discharged
that a produced certificate satisfies CrownCertLocalOK and AlphaOK
cert_encloses_semantics
enclosure from exact local IBP and semantic hypotheses
that a JSON/Float checker supplied those hypotheses
artifact intervals contain the authoritative FloatLib binary32 IBP trace and affine entries
exactly
match a sequential FloatLib binary32 replay
exact-real CROWN soundness
abcrown-leaf checker succeeds
represented boxes and witness fields pass structural/numeric checks
network-bound provenance; coverage is an executable check without a soundness theorem
numerical range check plus IEEE replay
stored trace and one reference execution pass executable checks
real semantic enclosure without ProvedRealEnclosure
FP32 approximation theorem
rounded-real output is within its stated budget
native IEEE behavior without finite refinement
For a particular report, select the rows that refer to its graph, scalar type, and checker. The
last column identifies the additional evidence needed before composing them into a deployment
claim.