Bug Zoo
Bug Zoo collects mistakes that are easy to miss in ordinary machine-learning tests. The program still runs and returns a tensor, loss, or token sequence, but the result no longer has the meaning the caller assumed.
Each example is deliberately small. It states the intended behavior as a Lean definition or theorem, shows where an implementation can depart from it, and identifies any runtime assumption that remains outside the proof. Together they cover attention, decoding, data boundaries, normalization, losses, compilation, floating point, and geometry.
Attention and Autoregressive Decoding
A causal mask should exclude future keys exactly. Replacing $-\infty$ with a large finite negative
number only approximates that behavior and can fail when logits leave the expected range.
AttentionMask.lean
uses hard-mask semantics and proves that every strict-future attention weight is zero.
Incremental decoding introduces a different problem. A key/value cache must contain the same keys
and values that full-sequence attention would have seen, in the same positions.
KVCache.lean
checks the append operation, while
RoPEPosition.lean
records the position assigned to the new token. These contracts isolate the two common off-by-one
errors instead of hiding them inside a generation loop.
Data, Batches, and Normalization State
Tokenizer errors often appear much earlier than the model. A checkpoint may expect one vocabulary
or special-token convention while the data loader supplies another.
TokenizerBoundary.lean
requires imported token ids to inhabit Fin vocabularySize, making the vocabulary bound part of the
object passed to the network. Tokenizer identity and special-token meanings still need their own
agreement checks.
Batching should normally change throughput, not the prediction for an individual sample.
BatchInvariance.lean
states that selecting a row from a batched reference run agrees with evaluating that row alone.
The theorem concerns independent row-wise evaluation; connecting a batching kernel to that
reference remains a separate obligation.
Normalization has its own hidden state. Batch normalization uses learned affine parameters and running statistics at inference time; layer normalization has a degenerate one-feature case that is easy to mishandle. The normalization examples make the axes, epsilon placement, running state, and zero-gradient corner cases explicit. These exact normalization identities are over the reals; floating kernels can have nonzero residuals. The BatchNorm theorem chooses one scale and bias from fixed inference statistics and proves the same affine map works for every input.
Losses, Floating Point, and Compilation
Several examples concern computations that are mathematically familiar but numerically unsafe.
Masking a quotient after division does not repair a division by zero, and a direct implementation
of a logit loss can overflow even when its stable form is finite. AutogradDomain.lean and
StableLoss.lean expose the denominator policy and stable formula before reverse mode is considered.
Adding epsilon shifts a denominator; it does not make every input safe, since a denominator of
-epsilon still becomes zero.
Real-number proofs do not automatically describe a binary32 run.
FloatBoundary.lean
uses FloatLib’s native import/export, finite-input addition/subtraction, and square-root theorems
directly. CompilerBoundary.lean proves that successful lowering of
the supported IR fragment preserves node denotations in a Lean reference evaluator. Its hypothesis
excludes raw logarithm nodes; external compiler and native-kernel conformance remain separate obligations.
The final geometry case starts from tensors exported by a detector. Lean recomputes camera projection and positive depth, then checks that the reported two-dimensional box encloses every projected corner. The detector remains an external producer; the enclosure claim does not.
Run the Examples
Build the complete collection with:
lake build NN.Examples.BugZoo.All
The geometry case has a registered certificate checker:
lake exe verify -- camera-box3d-cert
The other entries are inspected through their checked Lean definitions and theorems. The separate
lake exe verify -- all command runs the ten bundled checks opted in through includeInAll.
External, interactive, and longer workflows are excluded; verify -- list shows the complete registry.
For a runnable PyTorch comparison of one-feature LayerNorm and constant normalization slices:
python3 scripts/verification/normalization_contract_probe.py --device cpu
This optional probe requires PyTorch. It prints version metadata and the output/input-gradient/
scale-gradient residuals from the real-valued reference; --device cuda selects an available GPU.
Residuals depend on the installed provider and hardware and are not proof certificates.
The source files and the contracts they expose are listed below.
| Source file | Bug family | Contract exposed |
|---|---|---|
AttentionMask.lean |
Causal masks, mask polarity, finite sentinels standing in for $-\infty$ | Future positions receive exactly zero attention weight under hard-mask semantics. |
KVCache.lean |
Shifted or malformed key/value caches in autoregressive decoding | The appended key/value vector is exactly the final cache entry. |
RoPEPosition.lean |
Off-by-one or mismatched rotary/absolute positions | Appending a token assigns the next sequence position. |
TokenizerBoundary.lean |
Vocabulary-size and special-token mismatches | Imported token ids inhabit Fin vocabularySize. |
BatchInvariance.lean |
Dynamic batching changing per-sample outputs | Selecting one row from a batched reference run equals evaluating that row alone. |
NormalizationState.lean |
BatchNorm formula/state mistakes | Epsilon placement and eval-time running statistics are explicit objects. |
LayerNormDegenerateAxis.lean |
One-feature LayerNorm corner cases | Over the reals, the output is the bias, with zero input and scale-gradient contribution. |
ConstantNormalizationSlice.lean |
Cancellation in normalization kernels on constant slices | Over the reals, affine normalization returns the bias and contributes zero scale gradient. |
IgnoredLabelLoss.lean |
All-ignored cross-entropy reductions | Ignored labels and the empty-reduction policy are named. |
AutogradDomain.lean |
Masking after undefined division | The reference expression shifts the denominator by epsilon before masking. |
StableLoss.lean |
Numerically unstable losses and domain-sensitive ops | Logit losses use the stable log-softmax path. |
ShapeAndBroadcast.lean |
Missing axes and silent broadcasts | Dimension changes are explicit terms with shape evidence. |
CompilerBoundary.lean |
Optimized graphs silently changing semantics | Successful supported IR lowering preserves reference node denotations, assuming no raw logarithm nodes. |
FloatBoundary.lean |
Real-valued reasoning applied to Float32 runs | FloatLib proves native round trips, finite-input add/sub agreement, and total square-root agreement through native export; configured division has a separate software-model refinement. |
Geometry3DProjection.lean |
Camera convention, depth, layout, and projection-box errors | The checker recomputes projection, positive depth, and 2D box enclosure. |
Two Checked Statements
Under hard-mask semantics, every strict-future key receives exactly zero attention weight. The source theorem has this signature:
theorem trueInfinityMask_future_attention_weight_zero
{n : Nat} (scores : TorchLean.Tensor ℝ [n, n])
(i j : Fin n) (hij : i.val < j.val) :
(Spec.hardMaskedSoftmaxSpec scores (Spec.causalMask n))[(i, j)] = 0
Lean 4.34.0 defines core Float32 operations through Float32.Model. The scalar connection comes
from FloatLib directly. This complete example proves addition agreement for finite operands:
import FloatLib.Floats.Formats.IEEE754
open FloatLib.Floats
example (a b : Float32) (ha : a.isFinite = true) (hb : b.isFinite = true) :
ExecFloat.Binary.ofFloat32 (a + b) =
ExecFloat.Binary.ofFloat32 a + ExecFloat.Binary.ofFloat32 b :=
ExecFloat.Binary.ofFloat32_add_of_isFinite a b ha hb
The finite inputs include signed zeros and subnormals. No premise requires the result to be finite, so overflow is included. A NaN or infinite input lies outside this addition theorem. The separate square-root export theorem covers every configured input; it canonicalizes NaNs at the native boundary because Lean stores one NaN representation. These logical equalities do not certify compiled CPU instructions, CUDA kernels, or a library’s reduction order.