Suppose a classifier returns class 3 on an image. We have learned what happened at one point. A
robustness claim asks a larger question: does class 3 remain ahead of every competitor throughout
a whole neighborhood of that image? That question is the one adversarial examples made unavoidable
(Szegedy et al., 2014), because the neighbors that flip the answer are usually invisible to
the eye and may be absent from a test set.
The difference is visible in the quantifiers. Let f_\theta(x) be the vector of class scores
for parameters \theta, and let c_\theta(x) select a class with maximal score, using a fixed
rule for ties. For the image x_0, a prediction is one computation:
c_\theta(x_0)=y.
A local robustness statement concerns every point in a region. In the score-based expression
below, j names a competing class and the indexed outputs are class scores. The
\ell_\infty distance bounds the change in each input coordinate by \varepsilon:
\forall x,\quad \lVert x-x_0\rVert_\infty\leq\varepsilon
\Longrightarrow
f_\theta(x)_y-f_\theta(x)_j>0
\quad\text{for every }j\ne y.
Over real-valued inputs, the second line ranges over an uncountable set. Sampling can find a
counterexample, but checking sampled predictions alone does not establish the universal claim.
A verifier needs a description of the region and a way to bound the network everywhere inside it.
An attention mask should give a blocked key zero weight. Subtracting a large constant from its
score often produces that result in tests, yet the subtraction can leave almost all the attention
on the blocked key.
For query i, let s_{ij} be its score for key j, and let A_i be the keys that are
allowed to receive attention. When there is at least one allowed key, a hard mask means
Blocked entries never enter the denominator and receive exactly zero weight. A common numerical
shortcut instead subtracts a large positive constant C from a blocked logit before softmax. For a
blocked position j\notin A_i, its weight is
For ordinary logits and a large C, this value may underflow to zero in a particular
floating-point run. Mathematically, however, it is positive for every finite C. Worse, the
shortcut is not safe for arbitrary logits. If a blocked score is C+100, then subtracting C
leaves the very large score 100; the supposedly blocked key can dominate the softmax.
Consider three scores with the third key blocked and a mask shift of C=10^9. The blocked
score is larger than the shift, so subtraction leaves it above the two allowed scores:
-- Make the blocked score exceed the finite penalty so the-- two masking rules disagree.defmoScores:TensorFloat[3]:=Tensor.ofFnfuni=>[1.0,2.0,1000000100.0][i.val]!defmoMask:TensorBool[3]:=Tensor.ofFnfuni=>[true,true,false][i.val]!defmoBig:Float:=1000000000.0defmoShortcut(s:TensorFloat[3]):TensorFloat[3]:=Activation.softmaxVecSpec(Tensor.ofFnfuni=>ifmoMask.getScalarithens.getScalarielses.getScalari-moBig)
Spec.hardMaskedSoftmaxVecSpecmoScoresmoMask is TorchLean's specification applied to those
scores: the hard-mask equation, written directly. moShortcutmoScores subtracts the finite
penalty before softmax:
-- Compare exclusion from normalization with merely-- subtracting a large finite number.[0.268941, 0.731059, 0.000000]#evalSpec.hardMaskedSoftmaxVecSpecmoScoresmoMask[0.000000, 0.000000, 1.000000]#evalmoShortcutmoScores
[0.268941, 0.731059, 0.000000]
[0.000000, 0.000000, 1.000000]
The first two scores differ by one, so their unnormalized allowed weights have ratio
1:\exp(1). Dividing by their sum produces the first two entries of the hard-mask result.
The third score never participates in that sum. In the shortcut, subtracting a billion still leaves
a score of one hundred, much larger than either allowed score. Normalization then gives almost all
the weight to the very position meant to be excluded. The problem is therefore already present in
the forward equation, before gradients, training, or backend selection enter the picture.
Lower the blocked score to 0.5, and the displayed results agree:
-- Lower only the blocked score: this easier input can hide-- the faulty masking rule.defmoTame:TensorFloat[3]:=Tensor.ofFnfuni=>[1.0,2.0,0.5][i.val]![0.268941, 0.731059, 0.000000]#evalSpec.hardMaskedSoftmaxVecSpecmoTamemoMask[0.268941, 0.731059, 0.000000]#evalmoShortcutmoTame
[0.268941, 0.731059, 0.000000]
[0.268941, 0.731059, 0.000000]
scores
hard mask
additive shortcut
[1.0, 2.0, 0.5]
[0.268941, 0.731059, 0.0]
[0.268941, 0.731059, 0.0]
[1.0, 2.0, 1e9+100]
[0.268941, 0.731059, 0.0]
[0.0, 0.0, 1.0]
The shortcut agrees on the first row because \exp(0.5-10^9) underflows to zero in binary64.
Over the reals that exponential remains positive. The second row shows why agreement on moderate
scores cannot justify substituting the shortcut for the hard-mask definition.
The same comparison in PyTorch 2.13.0 uses masked_fill with -inf for the hard mask and
subtraction for the shortcut:
>>> # A forbidden score can exceed a finite penalty;
>>> # replacing it by -inf excludes it.
>>> scores = torch.tensor([1.0, 2.0, 1_000_000_100.0])
>>> mask = torch.tensor([True, True, False])
>>> torch.softmax(scores.masked_fill(~mask, float("-inf")), dim=0)
tensor([0.2689, 0.7311, 0.0000])
>>> torch.softmax(scores - (~mask) * 1_000_000_000.0, dim=0)
tensor([0., 0., 1.])
Both implementations accept the same tensor shapes. The
shape interfaces cannot distinguish them; the
specification layer can, because it defines the hard-mask function with zero
numerators for blocked entries. A runtime provider needs a contract relating its computation to
that function.
To understand which tests can expose the difference, consider where the blocked exponential stops
underflowing. Binary64
exponentials near -745 show the relevant scale. Stable softmax first subtracts the row maximum,
so the threshold for a score also depends on the other scores. Rounding can hide differences even
when a blocked exponential is nonzero:
-- Locate two adjacent integer exponents where Float changes-- from tiny nonzero to zero.(false,true)#eval(Float.exp(-745.0)==0.0,Float.exp(-746.0)==0.0)-- Undo the additive mask shift for this illustrative-- exponent threshold (before softmax's max shift).999999255.000000#evalmoBig-745.0-- What a generator of ordinary logits actually reaches.0.000000#eval((List.range21).mapfunk=>Float.exp((-10.0+k.toFloat)-moBig)).foldlmax0.0
(false,true)
999999255.000000
0.000000
The Boolean pair shows that the exponential at minus 745 is still nonzero, whereas the one at
minus 746 has rounded to zero. The next number translates an exponent threshold back through the
finite mask penalty. It is not a universal cutoff for a softmax row: the stability shift also
depends on the row maximum. The final check samples a grid of moderate scores.
For the moderate allowed scores used here, a blocked score must approach 10^9 before its
exponential survives the masking and stability shifts. Scores in [-10,10] underflow with room to
spare in this example. A random
test restricted to that range never proposes a point where the large-score failure occurs.
Increasing its sample count does not change its support, even if the tests execute both branches
of the mask.
On the large-score example, the blocked key takes the entire displayed distribution, weight
1.0. A specification quantified over all logits includes this case regardless of the
distribution used to generate tests.
The function being checked also depends on preprocessing. Suppose the model consumes normalized
vectors, where \mu_i and \sigma_i are the mean and positive scale for coordinate i:
For example, take the MNIST normalization constants below and the raw pixel range [0,1]:
-- Move the raw endpoints and perturbation radius into the-- coordinates the model sees.defmoMu:Float:=0.1307defmoSigma:Float:=0.3081defmoNormBox(lohi:Float):Float×Float:=((lo-moMu)/moSigma,(hi-moMu)/moSigma)(-0.424213, 2.821487)#evalmoNormBox0.01.00.324570#eval0.1/moSigma
(-0.424213, 2.821487)
0.324570
The positive standard deviation preserves endpoint order. For a perturbation, the mean cancels
between the original and perturbed inputs, leaving the radius divided by moSigma. These Float
calculations identify the change of coordinates; a certified numerical enclosure would also need
to account for endpoint rounding.
A verifier that starts after normalization needs the first pair as its pixel bounds. Using
[0,1] there would describe a different region: the transformed interval is more than three
times as wide and has a different center. The
second number is the matching correction for a perturbation radius. An \varepsilon of 0.1 in
raw pixels is a radius of about 0.3246 after dividing by \sigma, so a verifier fed the raw
radius would check a region roughly a third of the intended size. A successful check would then
cover too little of the intended neighborhood. Both regions have the same tensor shape, so the
coordinate convention must be recorded separately.
The source is
NN/Verification/Builtin/IBPWorkflow.lean.
It constructs a two-input, three-hidden-unit ReLU MLP with an explicit parameter payload. It lowers
that forward program to NN.IR.Graph, places an \ell_\infty box of radius 0.1 around
(0.5,0.8), and runs interval bound propagation.
The two vectors are the computed lower and upper endpoints of an output box. Before accounting
for floating-point rounding, the ideal endpoints are 1.904 and 2.256, whose midpoint is
We can derive these endpoints from the parameters. The first
layer has weights W_1 with rows (0.1,0.2), (0.3,0.4), (0.5,0.6) and bias
b_1=(0.1,0.2,0.3), and the second layer has W_2=(0.7,0.8,0.9) with bias b_2=0.4. The
input box is x_1\in[0.4,0.6], x_2\in[0.7,0.9].
Every weight is positive, so each pre-activation is largest at the box corner (0.6,0.9) and
smallest at (0.4,0.7). At the low corner the three pre-activations are
all positive, so on this box every ReLU is the identity and the network is affine. The output at
the low corner is
(0.7)(0.28)+(0.8)(0.6)+(0.9)(0.92)+0.4=1.904,
and the same computation at (0.6,0.9) gives 2.256. Because the map is affine and monotone
here, those corner values are the exact range, and interval propagation loses nothing.
The corner calculation suggests a bound on the whole box. To prove that bound, define the same
network over the reals, with max 0 for ReLU:
-- Fix the real-valued network and its parameters before-- stating an enclosure for all inputs.noncomputabledefmoNet(x1x2:ℝ):ℝ:=0.7*max0(0.1*x1+0.2*x2+0.1)+0.8*max0(0.3*x1+0.4*x2+0.2)+0.9*max0(0.5*x1+0.6*x2+0.3)+0.4
The four hypotheses below give the lower and upper bounds on the two inputs. The proof first uses
them to show that each pre-activation is nonnegative, replaces the ReLUs by their inputs, and then
proves the two affine inequalities.
-- Prove both output inequalities for any point satisfying-- the four input inequalities.theoremmoEnclosure(x1x2:ℝ)(h1:0.4≤x1)(h2:x1≤0.6)(h3:0.7≤x2)(h4:x2≤0.9):1.904≤moNetx1x2∧moNetx1x2≤2.256:=x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9⊢ 1.904≤moNetx1x2∧moNetx1x2≤2.256-- On this box every pre-activation is positive, so each-- `max 0 ·` is the identity and the network is affine.x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1⊢ 1.904≤moNetx1x2∧moNetx1x2≤2.256havee2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2:=max_eq_right(byx1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1⊢ 0≤0.3*x1+0.4*x2+0.2linarithAll goals completed! 🐙)x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2⊢ 1.904≤moNetx1x2∧moNetx1x2≤2.256havee3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3:=max_eq_right(byx1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2⊢ 0≤0.5*x1+0.6*x2+0.3linarithAll goals completed! 🐙)x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤moNetx1x2∧moNetx1x2≤2.256rw[moNet,x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤0.7*max0(0.1*x1+0.2*x2+0.1)+0.8*max0(0.3*x1+0.4*x2+0.2)+0.9*max0(0.5*x1+0.6*x2+0.3)+0.4∧0.7*max0(0.1*x1+0.2*x2+0.1)+0.8*max0(0.3*x1+0.4*x2+0.2)+0.9*max0(0.5*x1+0.6*x2+0.3)+0.4≤2.256e1,x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤0.7*(0.1*x1+0.2*x2+0.1)+0.8*max0(0.3*x1+0.4*x2+0.2)+0.9*max0(0.5*x1+0.6*x2+0.3)+0.4∧0.7*(0.1*x1+0.2*x2+0.1)+0.8*max0(0.3*x1+0.4*x2+0.2)+0.9*max0(0.5*x1+0.6*x2+0.3)+0.4≤2.256e2,x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*max0(0.5*x1+0.6*x2+0.3)+0.4∧0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*max0(0.5*x1+0.6*x2+0.3)+0.4≤2.256e3x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4∧0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4≤2.256]x1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4∧0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4≤2.256constructorleftx1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4rightx1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4≤2.256<;>leftx1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 1.904≤0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4rightx1:ℝx2:ℝh1:0.4≤x1h2:x1≤0.6h3:0.7≤x2h4:x2≤0.9e1:max0(0.1*x1+0.2*x2+0.1)=0.1*x1+0.2*x2+0.1e2:max0(0.3*x1+0.4*x2+0.2)=0.3*x1+0.4*x2+0.2e3:max0(0.5*x1+0.6*x2+0.3)=0.5*x1+0.6*x2+0.3⊢ 0.7*(0.1*x1+0.2*x2+0.1)+0.8*(0.3*x1+0.4*x2+0.2)+0.9*(0.5*x1+0.6*x2+0.3)+0.4≤2.256nlinarithAll goals completed! 🐙
The variables x1 and x2 remain arbitrary throughout the proof. The names h1 through h4
refer to evidence that those variables lie inside the chosen rectangle; they do not assign values
to them. The result joined by ∧ contains two proofs, one for the lower endpoint and one for the
upper endpoint. Each intermediate equality removes a ReLU only after establishing its input is
nonnegative over the entire rectangle. Once those three equalities are substituted, the remaining
inequalities concern an affine expression, which the arithmetic tactic can discharge.
An enclosure may be loose. Evaluating the two corners proves that these endpoints are attained:
-- Show that neither real endpoint can be tightened: each is-- reached at a box corner.theoremmoLowerCorner:moNet0.40.7=1.904:=by⊢ moNet0.40.7=1.904norm_num[moNet]All goals completed! 🐙theoremmoUpperCorner:moNet0.60.9=2.256:=by⊢ moNet0.60.9=2.256norm_num[moNet]All goals completed! 🐙
Endpoint attainment answers a question that enclosure alone leaves open. A sound procedure could
return a much wider interval and still contain every output. Here the lower-corner equality rules
out raising the lower bound, and the upper-corner equality rules out lowering the upper bound.
These are exact real equalities with the displayed decimal constants interpreted as rationals.
They do not assert that either native endpoint has the same bit representation as those rationals;
the later comparison of encodings concerns that separate numerical question.
Taken together, moEnclosure, moLowerCorner, and moUpperCorner say that
[1.904,2.256] is the exact range of this network over this box. Exactness depends on the
structure of this example. Dependency loss can make intervals overapproximate, especially across
multiple layers; a crossing ReLU or mixed weight signs alone do not force a loose result.
Affine relaxations can retain relationships that separate intervals discard
(Zhang et al., 2018)(Xu et al., 2020).
This theorem concerns \mathbb{R}, while the command above ran in binary32. Connecting the
two requires the arithmetic argument developed in
the soundness chapter. First, consider a source of looseness that arises
even with exact arithmetic: dependencies between intermediate values.
In the preceding network, every weight was positive and every ReLU was active across the whole box.
All intermediate maxima occurred at the same corner, so combining their bounds lost no
information. Consider instead one input feeding two hidden ReLU units with weights +1 and
-1, whose outputs are added together.
Over the box x\in[-1,1] the true range of g is [0,1]. Interval propagation bounds each
hidden unit separately, then adds their intervals without retaining the relationship between them:
-- Interval arithmetic for the two operations we need,-- which is all IBP does at a ReLU and at an addition.defmoReluBox(lohi:Float):Float×Float:=(max0.0lo,max0.0hi)defmoAddBox(ab:Float×Float):Float×Float:=let(aLo,aHi):=alet(bLo,bHi):=b(aLo+bLo,aHi+bHi)defmoAbsF(x:Float):Float:=max0.0x+max0.0(-x)-- `x ∈ [-1, 1]` gives `-x ∈ [-1, 1]`, so both hidden-- units get the same input box.(0.000000, 2.000000)#evalmoAddBox(moReluBox(-1.0)1.0)(moReluBox(-1.0)1.0)
(0.000000, 2.000000)
The lost information is the relation between the two branches. The interval for relu(x) reaches
one at x = 1, while the interval for relu(-x) reaches one at x = -1. Adding interval endpoints
allows those two maxima to occur together. There is no such input. Keeping a common symbolic input
in a linear bound can prevent that combination, which is why a more expensive bound representation
may improve the result even when both procedures use perfectly correct arithmetic.
The propagated box is [0,2]. The function never leaves [0,1]; the theorem below proves the
upper bound. A finite grid
provides an illustrative numerical check:
-- Sample 201 points as a numerical check; the following-- proof handles the whole interval.1.000000#eval((List.range201).mapfunk=>moAbsF(-1.0+0.01*k.toFloat)).foldlmax0.0
1.000000
The upper bound is off by a factor of two on a network with two hidden units. Nothing went wrong in
the arithmetic: \operatorname{ReLU}(x)\in[0,1] and \operatorname{ReLU}(-x)\in[0,1] are both
correct, and [0,1]+[0,1]=[0,2] is the correct interval sum. What is lost is that the two summands
cannot both be 1. Interval arithmetic drops the dependency between them the moment it replaces
each one by a box, and that loss is systematic rather than a rounding artifact.
A CROWN relaxation retains some of this dependency by bounding each ReLU with a line in the
input. On this interval, the line joining the endpoints of the ReLU graph gives the upper bound
The two bounds remain functions of the same variable. Their slopes cancel when they are added:
\frac{x+1}{2}+\frac{-x+1}{2}=1. The first theorem below proves the line bound; the second applies
it at both inputs and adds the resulting inequalities:
-- Bound each ReLU by a line in the same input x so their-- shared dependence is retained.noncomputabledefmoAbsR(x:ℝ):ℝ:=max0x+max0(-x)theoremmoReluRelax(x:ℝ)(h1:-1≤x)(h2:x≤1):max0x≤(x+1)/2:=byx:ℝh1:-1≤xh2:x≤1⊢ max0x≤(x+1)/2rcasesle_total0xwithh|hinlx:ℝh1:-1≤xh2:x≤1h:0≤x⊢ max0x≤(x+1)/2inrx:ℝh1:-1≤xh2:x≤1h:x≤0⊢ max0x≤(x+1)/2·inlx:ℝh1:-1≤xh2:x≤1h:0≤x⊢ max0x≤(x+1)/2rw[max_eq_righthinlx:ℝh1:-1≤xh2:x≤1h:0≤x⊢ x≤(x+1)/2]inlx:ℝh1:-1≤xh2:x≤1h:0≤x⊢ x≤(x+1)/2;linarithAll goals completed! 🐙·inrx:ℝh1:-1≤xh2:x≤1h:x≤0⊢ max0x≤(x+1)/2rw[max_eq_lefthinrx:ℝh1:-1≤xh2:x≤1h:x≤0⊢ 0≤(x+1)/2]inrx:ℝh1:-1≤xh2:x≤1h:x≤0⊢ 0≤(x+1)/2;linarithAll goals completed! 🐙theoremmoAbsUpper(x:ℝ)(h1:-1≤x)(h2:x≤1):moAbsRx≤1:=byx:ℝh1:-1≤xh2:x≤1⊢ moAbsRx≤1-- The same relaxation at `x` and at `-x`. Because both-- lines are linear in `x`, adding them cancels `x`.havehx:=moReluRelaxxh1h2x:ℝh1:-1≤xh2:x≤1hx:max0x≤(x+1)/2⊢ moAbsRx≤1havehn:=moReluRelax(-x)(byx:ℝh1:-1≤xh2:x≤1hx:max0x≤(x+1)/2⊢ -1≤-xlinarithAll goals completed! 🐙)(byx:ℝh1:-1≤xh2:x≤1hx:max0x≤(x+1)/2⊢ -x≤1linarithAll goals completed! 🐙)x:ℝh1:-1≤xh2:x≤1hx:max0x≤(x+1)/2hn:max0(-x)≤(-x+1)/2⊢ moAbsRx≤1rw[moAbsRx:ℝh1:-1≤xh2:x≤1hx:max0x≤(x+1)/2hn:max0(-x)≤(-x+1)/2⊢ max0x+max0(-x)≤1]x:ℝh1:-1≤xh2:x≤1hx:max0x≤(x+1)/2hn:max0(-x)≤(-x+1)/2⊢ max0x+max0(-x)≤1linarithAll goals completed! 🐙theoremmoAbsAttained:moAbsR1=1:=by⊢ moAbsR1=1norm_num[moAbsR]All goals completed! 🐙
The hypotheses of moReluRelax are exactly the range on which its sloping line is an upper bound.
For a negative input, ReLU is zero and the line is nonnegative; for a positive input, the inequality
reduces to x ≤ 1. Applying the same statement to -x gives a second line whose slope has the
opposite sign. Their sum is one, so the dependence on x cancels before any interval endpoints are
chosen. moAbsAttained then checks an input reaching that upper bound. The argument explains the
improvement without relying on the grid experiment.
moAbsUpper recovers 1, and moAbsAttained says 1 is reached, so the linear
relaxation is exact on this example while the interval upper bound was off by a factor of two.
Retaining input dependence is the reason to carry affine bounds through a graph, as developed in
the certificates chapter and the CROWN literature
(Zhang et al., 2018)(Xu et al., 2020).
The linear relaxation is not always exact. Here its two slopes cancel and its upper bound is
attained at an endpoint; piecewise linearity alone does not guarantee that result. Dependency loss
can also grow across layers. Training with an IBP objective can encourage more useful interval
bounds (Gowal et al., 2018).
Looseness alone does not invalidate a sound bound. An IBP result of [0,2] cannot establish
g\leq1, but it also supplies no counterexample. This test is inconclusive; a tighter relaxation
can establish the property, as the affine calculation above does.
The workflow takes an --arithmetic flag. Running the same graph, the same parameters, and the same
input box through the reference IEEE binary32 semantics instead of the host's native Float32
changes the answer:
The upper endpoint printed 2.256001 in the native run and prints 2.256000 here. The bit
patterns expose the difference more precisely than the six-decimal output. Both runs return
binary32 numbers; here are approximate offsets from the ideal decimal endpoints, in units of
10^{-9}:
-- Decode recorded binary32 endpoints and magnify their-- signed offsets for inspection.defmoOff(bits:UInt32)(exact:Float):Float:=((Float32.ofBitsbits).toFloat-exact)*1000000000.0(-194.549560, -75.340271)#eval(moOff10729365161.904,moOff10729365171.904)(518.798828, 280.380249)#eval(moOff10748155682.256,moOff10748155672.256)
(-194.549560, -75.340271)
(518.798828, 280.380249)
The first component of each pair is the native run, the second is the IEEE reference run.
Both lower endpoints lie below the comparison value and both upper endpoints above it.
Multiplication by a billion makes the offsets readable but adds no precision: moOff computes
in host binary64, including its decimal reference value. These are numerical diagnostics, not
exact real identities or a proof of outward rounding for the native implementation.
endpoint
native Float32
IEEE binary32 reference
exact
lower, bits
1072936516
1072936517
not representable
lower, offset
-1.945e-7
-7.534e-8
1.904
upper, bits
1074815568
1074815567
not representable
upper, offset
+5.188e-7
+2.804e-7
2.256
The listed lower endpoints lie below the ideal lower bound and the upper endpoints above the
ideal upper bound, so both listed boxes contain the real-valued range proved earlier. The two
runs differ by exactly one unit in the last place at each endpoint, the ULP near 2.256 being
2.384\times10^{-7}. The native box is wider in this example. This comparison does not establish
a soundness theorem for either backend over arbitrary graphs or inputs.
The relative widths in this example do not establish an ordering for other inputs. A verifier that
computes bounds in one arithmetic and reports them as if they held in another has an unproved step
in the middle, and that step has been turned into working attacks on published verifiers
(Jia and Rinard, 2020). Neither decimal endpoint is even a binary32 number, so there is no
reading of output box hi: [2.256000] under which the printed digits are the computed value.
The first connection can fail when parameters change after verification. Take the network whose
real-valued range we proved and change one weight, as an optimizer step might: the second
layer's W_2=(0.7,0.8,0.9) becomes (0.7,0.8,0.95). The graph structure and parameter
shapes remain the same.
-- Change one readout weight while preserving every tensor-- shape and input coordinate.defmoW2:TensorFloat[3]:=[0.7,0.8,0.9]defmoW2':TensorFloat[3]:=[0.7,0.8,0.95]defmoHidden(x1x2:Float):TensorFloat[3]:=[max0.0(0.1*x1+0.2*x2+0.1),max0.0(0.3*x1+0.4*x2+0.2),max0.0(0.5*x1+0.6*x2+0.3)]defmoOutput(w:TensorFloat[3])(x1x2:Float):Float:=leth:=moHiddenx1x2(Tensor.mulwh).sum+0.4certified upper = 2.256
analyzed payload = 2.256000
deployed payload = 2.313000
still in the box = false
#evaldoleta:=moOutputmoW20.60.9letb:=moOutputmoW2'0.60.9IO.printlns!"certified upper = 2.256"IO.printlns!"analyzed payload = {a}"IO.printlns!"deployed payload = {b}"letok:Bool:=b<=2.256IO.printlns!"still in the box = {ok}"
certified upper = 2.256
analyzed payload = 2.256000
deployed payload = 2.313000
still in the box = false
At the upper corner, the third hidden activation is 1.14. Increasing its readout coefficient by
0.05 raises the prediction from 2.256 to 2.313, overshooting the old bound by 0.057, about a
third of the box's radius. The graph structure and shapes have not changed.
moEnclosure remains true for the weights it names; applying it to the new payload fails
because it describes a different function. A verification pipeline must track the parameter values
it analyzed through to deployment, even when the architecture is unchanged.
This is why Spec.hardMaskedSoftmaxVecSpec and its relatives take their parameters as
arguments, and why denote in the IR chapter takes a payload rather than
reading weights from somewhere ambient. A soundness theorem whose statement mentions \theta
forces the caller to say which \theta, and the two-stage workflows in
that chapter carry the same payload through search and checking for
exactly this reason.
Let g be the graph, \theta its parameter payload, B the input region, and c a
certificate supplied to the checker. A checker soundness theorem usually has the form
The certificate may come from an expensive external search;
the checker validates the resulting artifact. Branch-and-bound
verifiers make the split concrete, exploring a tree of neuron splits and emitting a bound per leaf
(Bunel et al., 2019); the certificates chapter reads such artifacts
and checks the leaves in Lean without reproducing the search.
can be regrouped without changing its value. Binary floating-point addition rounds after each
operation, so regrouping may change the result:
-- Change only the parentheses, then compare the complete-- binary64 encodings.false#eval(0.1+0.2)+0.3==0.1+(0.2+0.3)4603579539098121012#eval((0.1+0.2)+0.3).toBits4603579539098121011#eval(0.1+(0.2+0.3)).toBits
false
4603579539098121012
4603579539098121011
The two integer encodings differ by one, even though decimal formatting hides the distinction.
Each parenthesization chooses a different intermediate sum to round. Real associativity therefore
cannot justify replacing one execution order by the other without an additional numerical argument.
Both sides print as 0.600000; a comparison against a threshold can expose the difference. Fused
multiply-add, reduction order, subnormal handling, NaNs, and overflow introduce further
distinctions; the literature chapter traces how Flocq and its
relatives handle the same questions in Rocq (Boldo and Melquiond, 2011).
The arithmetic named in the statement matters:
exact real-valued specifications;
configurable rounded-real arithmetic with checked positive format precision;
FP32, a rounded-real model with binary32 precision and gradual-underflow parameters but no
upper exponent bound or IEEE special values;
executable bit-level IEEE binary32;
native Float32, host Float, CUDA, and external runtime providers.
A real-valued enclosure theorem cannot be silently relabeled as a theorem about every GPU execution.
A bridge theorem or an explicit backend contract must carry the result across that boundary. The
floating-point chapter develops these relationships in detail.
A change in formula can also reduce overflow risk while preserving the real-valued function.
For smooth-max pooling, let \beta be a nonzero scale and choose a shift p from the extreme
input value as follows:
The shift keeps every exponential argument nonpositive. With \beta=1000 and inputs
\{0,1\}, evaluating the
unshifted sum of exponentials overflows:
-- The larger exponent overflows even though the smooth-- maximum itself is near one.defmoBeta:Float:=1000.0inf#evalFloat.exp(moBeta*0.0)+Float.exp(moBeta*1.0)
inf
Subtracting the maximum first gives an algebraically equivalent expression with finite
intermediates in this example:
-- Shift by the largest input so the exponent arguments are-- zero or negative.defmoP:Float:=1.01.000000#evalmoP+Float.log(Float.exp(moBeta*(0.0-moP))+Float.exp(moBeta*(1.0-moP)))/moBeta
1.000000
For these inputs the shifted exponentials are exp(-1000) and exp(0). The latter supplies one
to the sum, while the former is too small to affect this Float result. In real arithmetic the
smooth maximum is slightly above one, by log(1 + exp(-1000)) / 1000; the printed one is the
rounded evaluation. This is a useful example of separating two goals: avoid an infinite
intermediate, and describe the error of the finite answer that remains. The algebraic rewrite
addresses the first; a numerical error statement is still needed for the second.
TorchLean uses the same shifted weights in the forward and derivative paths, and executable entry
points reject zero or non-finite \beta. The real identity explains the transformation; the
specification over \mathbb{R} fixes the intended function. Validation and backend tests check
its executable form in the selected format.
Lean lets the program, its mathematical interpretation, the checker, and the theorem refer to the
same definitions. A schema change can expose a parser's missing cases, and changing an operation
can invalidate proofs that depend on its old equation. Applying a soundness theorem requires
every hypothesis in its statement, including those that connect the artifact to the intended model.
The named Lean output blocks provide another check: the guide build reruns the broken mask and
checks the two bit patterns of 0.6. Shell transcripts such as the IBP workflow and Python examples
still need separate execution, and prose claims need review. Executable documentation catches
drift in the examples it actually runs.