Documentation

LeanMlir.Proofs.Training.SgdDescentMlp

Lipschitz constants for the MLP softmax-CE loss — descent through the ReLU kinks #

SgdDescentLinear discharged sgd_descends' smoothness hypothesis for the Chapter-1 linear net. This file extends the discharge through the Chapter-2 MLP (dense → relu → dense → relu → dense), layer by layer:

The capstones mlp_hidden_sgd_descends / mlp_input_sgd_descends mirror linear_sgd_descends: an η-accurate gradient oracle (the float budgets), the margin(s) at the step radius, the small-step condition, and the two dominance conditions ⇒ one inexact SGD step on that layer's weights provably decreases the cross-entropy loss by ≥ lr·‖∇L‖₂²/2. Every hypothesis is checkable arithmetic at a concrete point; smoothness is proven, not assumed. Bias columns are the same argument with the layer input replaced by the constant 1 and are omitted. The joint all-layers step (every parameter moving at once, logits no longer affine in the moving parameters) is the remaining open rung.

theorem Proofs.relu_entry_lipschitz (n : ) (u v : Vec n) (k : Fin n) :
|relu n u k - relu n v k| |u k - v k|

ReLU is entrywise 1-Lipschitz. FloatBridge.lean proves the same fact for the rounded net (relu exact-in-float); this is the ℝ-side workhorse that lets forward drift pass through a kinked layer unamplified.

theorem Proofs.sign_stable_of_close {zt z c : } (hc : |zt - z| c) (hm : c < |z|) :
zt 0 (0 < zt 0 < z)

Margins freeze signs. If a value drifts by at most c and sits at distance more than c from the kink, the drifted value is still off the kink with the same sign — the ReLU mask cannot flip.

theorem Proofs.sum_abs_flatten_cols {m n : } (d : Vec (m * n)) :
j : Fin n, i : Fin m, |d (finProdFinEquiv (i, j))| = idx : Fin (m * n), |d idx|

The ℓ1 mass of a flattened weight perturbation, summed column by column, is the total ℓ1 mass — finProdFinEquiv partitions the flat index set into the columns.

theorem Proofs.dense_unflatten_diff {m n : } (b : Vec n) (x : Vec m) (v e : Vec (m * n)) (j : Fin n) :
dense (Mat.unflatten (v + e)) b x j - dense (Mat.unflatten v) b x j = i : Fin m, x i * e (finProdFinEquiv (i, j))

The dense pre-activation difference under a weight perturbation, exactly: column j only sees the column-j slice of the perturbation.

theorem Proofs.dense_unflatten_col_drift {m n : } (b : Vec n) (x : Vec m) {a : } (hx : ∀ (i : Fin m), |x i| a) (v e : Vec (m * n)) (j : Fin n) :
|dense (Mat.unflatten (v + e)) b x j - dense (Mat.unflatten v) b x j| a * i : Fin m, |e (finProdFinEquiv (i, j))|

Column-refined drift: the column-j pre-activation moves by at most a times the column-j ℓ1 mass (not the total mass — this is what keeps the hidden-layer Lipschitz constant width-free).

theorem Proofs.dense_unflatten_drift_sum {m n : } (b : Vec n) (x : Vec m) {a : } (hx : ∀ (i : Fin m), |x i| a) (v e : Vec (m * n)) :
j : Fin n, |dense (Mat.unflatten (v + e)) b x j - dense (Mat.unflatten v) b x j| a * idx : Fin (m * n), |e idx|

Summed over all coordinates, the pre-activation drift is bounded by a·‖e‖₁ total — the column masses tile the flat index set.

theorem Proofs.margin_keeps_offkink {m n : } (b : Vec n) (x : Vec m) {a D : } (ha : 0 a) (hx : ∀ (i : Fin m), |x i| a) (v e : Vec (m * n)) (he : idx : Fin (m * n), |e idx| D) (hmargin : ∀ (j : Fin n), a * D < |dense (Mat.unflatten v) b x j|) (t : ) (ht0 : 0 t) (ht1 : t 1) (j : Fin n) :
dense (Mat.unflatten (v + t e)) b x j 0 (0 < dense (Mat.unflatten (v + t e)) b x j 0 < dense (Mat.unflatten v) b x j)

The margin keeps the pre-activation off the kink along the whole segment. With the step's ℓ1 mass at most D and inputs bounded by a, the pre-activation drifts by at most a·D — strictly inside the margin — so every point of [v, v + e] is off the kink with the original sign.

theorem Proofs.ce_dense_input_grad {d₂ d₃ : } (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (label : Fin d₃) (y : Vec d₂) (j : Fin d₂) :
pdiv (fun (z : Vec d₂) (x : Fin 1) => crossEntropy d₃ (dense W₂ b₂ z) label) y j 0 = k : Fin d₃, W₂ j k * (softmax d₃ (dense W₂ b₂ y) k - oneHot d₃ label k)

Loss input-gradient at the logits' input: ∂(CE ∘ dense W₂)/∂yⱼ = ∑ₖ W₂ⱼₖ·(softmax − onehot)ₖ — the pdiv-level form of the backward chain's dense-back step.

theorem Proofs.ce_head_relu_input_grad {d₂ d₃ : } (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (label : Fin d₃) (z : Vec d₂) (hz : ∀ (k : Fin d₂), z k 0) (j : Fin d₂) :
pdiv (fun (y : Vec d₂) (x : Fin 1) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ y)) label) z j 0 = (if z j > 0 then 1 else 0) * k : Fin d₃, W₂ j k * (softmax d₃ (dense W₂ b₂ (relu d₂ z)) k - oneHot d₃ label k)

Loss input-gradient through one ReLU — at an off-kink point the chain picks up the mask: ∂(CE ∘ dense W₂ ∘ relu)/∂zⱼ = relu'(zⱼ)·∑ₖ W₂ⱼₖ·(softmax − onehot)ₖ. The pdiv-level form of the cotangent mlpCotOut1 delivers (cf. mlpCotOut1_denote).

theorem Proofs.mlp_hidden_loss_differentiableAt {d₁ d₂ d₃ : } (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (label : Fin d₃) (w : Vec (d₁ * d₂)) (hz : ∀ (k : Fin d₂), dense (Mat.unflatten w) b₁ a₀ k 0) :
DifferentiableAt (fun (w' : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w') b₁ a₀))) label) w

The loss-of-W₁ map is differentiable wherever the hidden pre-activation is off the kinks.

theorem Proofs.mlp_hidden_loss_gradAt {d₁ d₂ d₃ : } (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (label : Fin d₃) (v : Vec (d₁ * d₂)) (hz : ∀ (k : Fin d₂), dense (Mat.unflatten v) b₁ a₀ k 0) (i : Fin d₁) (j : Fin d₂) :
gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) v (finProdFinEquiv (i, j)) = a₀ i * ((if dense (Mat.unflatten v) b₁ a₀ j > 0 then 1 else 0) * k : Fin d₃, W₂ j k * (softmax d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten v) b₁ a₀))) k - oneHot d₃ label k))

Closed form of the hidden-layer loss gradient at any off-kink parameter point: ∂L/∂W₁_{ij} = a₀ᵢ·relu'(z₁ⱼ)·∑ₖ W₂ⱼₖ·(softmax − onehot)ₖ — the suite's conditional fold (mlp_hidden_total_loss_grad) re-expressed through gradAt with both pdiv factors collapsed to their certified closed forms. The hidden-layer peer of linear_loss_gradAt.

theorem Proofs.mlp_hidden_logit_drift {d₁ d₂ d₃ : } (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) {a w₂ : } (hx : ∀ (i : Fin d₁), |a₀ i| a) (hw₂ : 0 w₂) (hW₂ : ∀ (j : Fin d₂) (k : Fin d₃), |W₂ j k| w₂) (v e : Vec (d₁ * d₂)) (k : Fin d₃) :
|dense W₂ b₂ (relu d₂ (dense (Mat.unflatten (v + e)) b₁ a₀)) k - dense W₂ b₂ (relu d₂ (dense (Mat.unflatten v) b₁ a₀)) k| w₂ * (a * idx : Fin (d₁ * d₂), |e idx|)

The hidden-layer logit drift: a weight perturbation of ℓ1 mass ‖e‖₁ moves every logit by at most w₂·a·‖e‖₁ — through the frozen dense, the 1-Lipschitz ReLU, and the column-tiled ℓ1 mass. No width factor.

theorem Proofs.mlp_hidden_loss_grad_lipschitz {d₁ d₂ d₃ : } (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (label : Fin d₃) {a w₂ D : } (ha : 0 a) (hx : ∀ (i : Fin d₁), |a₀ i| a) (hw₂ : 0 w₂) (hW₂ : ∀ (j : Fin d₂) (k : Fin d₃), |W₂ j k| w₂) (v d : Vec (d₁ * d₂)) (hd : idx : Fin (d₁ * d₂), |d idx| D) (hmargin : ∀ (j : Fin d₂), a * D < |dense (Mat.unflatten v) b₁ a₀ j|) (hsmall : 2 * (w₂ * (a * D)) < 1) (t : ) (ht : t Set.Icc 0 1) (idx : Fin (d₁ * d₂)) :
|gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) (v + t d) idx - gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) v idx| 2 * d₃ * w₂ ^ 2 * a ^ 2 / (1 - 2 * (w₂ * (a * D))) * (t * D)

Segment-Lipschitz gradient for the hidden-layer loss, explicit constant. Under the margin a·D < |z₁ⱼ| (the step cannot flip a ReLU sign — the masks freeze along the whole segment) and the small-step condition 2·w₂·a·D < 1, the gradient entries drift by at most (2·d₃·w₂²·a²/(1−2·w₂·a·D))·(t·D) along [v, v+d] — the exact shape descent_segment consumes. The hidden-layer peer of linear_loss_grad_lipschitz.

theorem Proofs.mlp_hidden_sgd_descends {d₁ d₂ d₃ : } (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (label : Fin d₃) (gh : Vec (d₁ * d₂)) {lr η a w₂ : } (ha : 0 a) (hx : ∀ (i : Fin d₁), |a₀ i| a) (hw₂ : 0 w₂) (hW₂ : ∀ (j : Fin d₂) (k : Fin d₃), |W₂ j k| w₂) (hlr : 0 lr) ( : 0 η) (hgh : ∀ (idx : Fin (d₁ * d₂)), |gh idx - gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| η) (hmargin : ∀ (j : Fin d₂), a * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * η)) < |dense W₁ b₁ a₀ j|) (hsmall : 2 * (w₂ * (a * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * η)))) < 1) (h1 : lr * η * idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| (lr * idx : Fin (d₁ * d₂), gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx ^ 2) / 4) (h2 : 2 * d₃ * w₂ ^ 2 * a ^ 2 / (1 - 2 * (w₂ * (a * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * η))))) * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * η)) ^ 2 (lr * idx : Fin (d₁ * d₂), gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx ^ 2) / 4) :
crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten (W₁.flatten - lr gh)) b₁ a₀))) label crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten W₁.flatten) b₁ a₀))) label - (lr * idx : Fin (d₁ * d₂), gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx ^ 2) / 2

One inexact SGD step on the MLP's hidden weights provably decreases the cross-entropy loss. All of sgd_descends' hypotheses discharged for the loss-of-W₁ map: differentiability along the segment and the segment-Lipschitz constant C = 2·d₃·w₂²·a²/(1−2·w₂·a·D) at step radius D = lr·(‖∇L‖₁ + d₁d₂·η) both come from the margin hypothesis — the step radius is small enough that no hidden ReLU can change sign. Remaining hypotheses are checkable arithmetic: the oracle accuracy η (the float budgets), the margins, the small-step condition, and the two dominance conditions. Conclusion: the loss drops by ≥ lr·‖∇L‖₂²/2. The hidden-layer peer of linear_sgd_descends.

theorem Proofs.mlp_output_sgd_descends {d₀ d₁ d₂ d₃ : } (W₀ : Mat d₀ d₁) (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) (gh : Vec (d₂ * d₃)) {lr η a : } (ha : 0 a) (hx : ∀ (i : Fin d₂), |relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))) i| a) (hlr : 0 lr) ( : 0 η) (hgh : ∀ (idx : Fin (d₂ * d₃)), |gh idx - gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| η) (hsmall : 2 * (a * (lr * (idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| + ↑(d₂ * d₃) * η))) < 1) (h1 : lr * η * idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| (lr * idx : Fin (d₂ * d₃), gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx ^ 2) / 4) (h2 : 2 * a ^ 2 / (1 - 2 * (a * (lr * (idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| + ↑(d₂ * d₃) * η)))) * (lr * (idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| + ↑(d₂ * d₃) * η)) ^ 2 (lr * idx : Fin (d₂ * d₃), gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx ^ 2) / 4) :
crossEntropy d₃ (dense (Mat.unflatten (W₂.flatten - lr gh)) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label crossEntropy d₃ (dense (Mat.unflatten W₂.flatten) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label - (lr * idx : Fin (d₂ * d₃), gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx ^ 2) / 2

One inexact SGD step on the MLP's output weights provably decreases the cross-entropy loss — for free. The top dense layer sits directly below the softmax-CE loss with no ReLU in between, so the loss-of-W₂ map IS the linear net's loss at the hidden activation a₁ = relu(dense W₁ b₁ (relu(dense W₀ b₀ x))): this is linear_sgd_descends instantiated there. No margin needed — the output layer never crosses a kink.

theorem Proofs.ce_head2_input_grad {d₁ d₂ d₃ : } (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (label : Fin d₃) (z : Vec d₁) (hz0 : ∀ (k : Fin d₁), z k 0) (hz1 : ∀ (k : Fin d₂), dense W₁ b₁ (relu d₁ z) k 0) (j : Fin d₁) :
pdiv (fun (y : Vec d₁) (x : Fin 1) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ y)))) label) z j 0 = (if z j > 0 then 1 else 0) * l : Fin d₂, W₁ j l * ((if dense W₁ b₁ (relu d₁ z) l > 0 then 1 else 0) * k : Fin d₃, W₂ l k * (softmax d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ z)))) k - oneHot d₃ label k))

Loss input-gradient through the relu→dense→relu chain — the two-mask closed form relu'(z₀ⱼ)·∑ₗ W₁ⱼₗ·relu'(z₁ₗ)·∑ₖ W₂ₗₖ·(softmax − onehot)ₖ at a point with both pre-activations off the kinks. The pdiv-level form of the deepest cotangent mlpCotOut0 delivers (cf. mlpCotOut0_denote).

theorem Proofs.mlp_input_loss_differentiableAt {d₀ d₁ d₂ d₃ : } (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) (w : Vec (d₀ * d₁)) (hz0 : ∀ (k : Fin d₁), dense (Mat.unflatten w) b₀ x k 0) (hz1 : ∀ (k : Fin d₂), dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x)) k 0) :
DifferentiableAt (fun (w' : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w') b₀ x))))) label) w

The loss-of-W₀ map is differentiable wherever both pre-activations are off the kinks.

theorem Proofs.mlp_input_loss_gradAt {d₀ d₁ d₂ d₃ : } (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) (v : Vec (d₀ * d₁)) (hz0 : ∀ (k : Fin d₁), dense (Mat.unflatten v) b₀ x k 0) (hz1 : ∀ (k : Fin d₂), dense W₁ b₁ (relu d₁ (dense (Mat.unflatten v) b₀ x)) k 0) (i : Fin d₀) (j : Fin d₁) :
gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) v (finProdFinEquiv (i, j)) = x i * ((if dense (Mat.unflatten v) b₀ x j > 0 then 1 else 0) * l : Fin d₂, W₁ j l * ((if dense W₁ b₁ (relu d₁ (dense (Mat.unflatten v) b₀ x)) l > 0 then 1 else 0) * k : Fin d₃, W₂ l k * (softmax d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten v) b₀ x))))) k - oneHot d₃ label k)))

Closed form of the input-layer loss gradient at any two-margin point: ∂L/∂W₀_{ij} = xᵢ·relu'(z₀ⱼ)·∑ₗ W₁ⱼₗ·relu'(z₁ₗ)·∑ₖ W₂ₗₖ· (softmax − onehot)ₖ — the deepest fold (mlp_input_total_loss_grad) with both pdiv factors collapsed. The input-layer peer of linear_loss_gradAt.

theorem Proofs.mlp_input_logit_drift {d₀ d₁ d₂ d₃ : } (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) {a w₁ w₂ : } (ha : 0 a) (hx : ∀ (i : Fin d₀), |x i| a) (hw₁ : 0 w₁) (hW₁ : ∀ (j : Fin d₁) (l : Fin d₂), |W₁ j l| w₁) (hW₂ : ∀ (l : Fin d₂) (k : Fin d₃), |W₂ l k| w₂) (v e : Vec (d₀ * d₁)) (k : Fin d₃) :
|dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten (v + e)) b₀ x)))) k - dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten v) b₀ x)))) k| w₂ * (d₂ * (w₁ * (a * idx : Fin (d₀ * d₁), |e idx|)))

The input-layer logit drift: through two dense layers and two 1-Lipschitz ReLUs, a weight perturbation of ℓ1 mass ‖e‖₁ moves every logit by at most w₂·d₂·w₁·a·‖e‖₁. The middle layer contributes its ℓ1→ℓ1 operator factor d₂·w₁ — unlike the first hop, the perturbation arriving at layer 1 is no longer column-structured.

theorem Proofs.margin_keeps_offkink_mid {d₀ d₁ d₂ : } (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (x : Vec d₀) {a w₁ D : } (ha : 0 a) (hx : ∀ (i : Fin d₀), |x i| a) (hw₁ : 0 w₁) (hW₁ : ∀ (j : Fin d₁) (l : Fin d₂), |W₁ j l| w₁) (v e : Vec (d₀ * d₁)) (he : idx : Fin (d₀ * d₁), |e idx| D) (hmargin1 : ∀ (l : Fin d₂), w₁ * (a * D) < |dense W₁ b₁ (relu d₁ (dense (Mat.unflatten v) b₀ x)) l|) (t : ) (ht0 : 0 t) (ht1 : t 1) (l : Fin d₂) :
dense W₁ b₁ (relu d₁ (dense (Mat.unflatten (v + t e)) b₀ x)) l 0 (0 < dense W₁ b₁ (relu d₁ (dense (Mat.unflatten (v + t e)) b₀ x)) l 0 < dense W₁ b₁ (relu d₁ (dense (Mat.unflatten v) b₀ x)) l)

The layer-1 margin keeps the middle pre-activation off the kink along the segment: the perturbation arrives through one dense + ReLU, so the drift is at most w₁·a·D — the layer-1 analogue of margin_keeps_offkink.

theorem Proofs.mlp_input_loss_grad_lipschitz {d₀ d₁ d₂ d₃ : } (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) {a w₁ w₂ D : } (ha : 0 a) (hx : ∀ (i : Fin d₀), |x i| a) (hw₁ : 0 w₁) (hW₁ : ∀ (j : Fin d₁) (l : Fin d₂), |W₁ j l| w₁) (hw₂ : 0 w₂) (hW₂ : ∀ (l : Fin d₂) (k : Fin d₃), |W₂ l k| w₂) (v d : Vec (d₀ * d₁)) (hd : idx : Fin (d₀ * d₁), |d idx| D) (hmargin0 : ∀ (j : Fin d₁), a * D < |dense (Mat.unflatten v) b₀ x j|) (hmargin1 : ∀ (l : Fin d₂), w₁ * (a * D) < |dense W₁ b₁ (relu d₁ (dense (Mat.unflatten v) b₀ x)) l|) (hsmall : 2 * (w₂ * (d₂ * (w₁ * (a * D)))) < 1) (t : ) (ht : t Set.Icc 0 1) (idx : Fin (d₀ * d₁)) :
|gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) (v + t d) idx - gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) v idx| 2 * d₃ * d₂ ^ 2 * w₁ ^ 2 * w₂ ^ 2 * a ^ 2 / (1 - 2 * (w₂ * (d₂ * (w₁ * (a * D))))) * (t * D)

Segment-Lipschitz gradient for the input-layer loss, explicit constant. Under both margins (neither ReLU layer's sign pattern can change along the step) and the small-step condition, the gradient entries drift by at most (2·d₃·d₂²·w₁²·w₂²·a²/(1−2·w₂·d₂·w₁·a·D))·(t·D). The input-layer peer of mlp_hidden_loss_grad_lipschitz; the extra d₂·w₁ is the middle layer's ℓ1→ℓ1 operator factor.

theorem Proofs.mlp_input_sgd_descends {d₀ d₁ d₂ d₃ : } (W₀ : Mat d₀ d₁) (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) (gh : Vec (d₀ * d₁)) {lr η a w₁ w₂ : } (ha : 0 a) (hx : ∀ (i : Fin d₀), |x i| a) (hw₁ : 0 w₁) (hW₁ : ∀ (j : Fin d₁) (l : Fin d₂), |W₁ j l| w₁) (hw₂ : 0 w₂) (hW₂ : ∀ (l : Fin d₂) (k : Fin d₃), |W₂ l k| w₂) (hlr : 0 lr) ( : 0 η) (hgh : ∀ (idx : Fin (d₀ * d₁)), |gh idx - gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| η) (hmargin0 : ∀ (j : Fin d₁), a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * η)) < |dense W₀ b₀ x j|) (hmargin1 : ∀ (l : Fin d₂), w₁ * (a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * η))) < |dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)) l|) (hsmall : 2 * (w₂ * (d₂ * (w₁ * (a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * η)))))) < 1) (h1 : lr * η * idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| (lr * idx : Fin (d₀ * d₁), gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx ^ 2) / 4) (h2 : 2 * d₃ * d₂ ^ 2 * w₁ ^ 2 * w₂ ^ 2 * a ^ 2 / (1 - 2 * (w₂ * (d₂ * (w₁ * (a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * η))))))) * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * η)) ^ 2 (lr * idx : Fin (d₀ * d₁), gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx ^ 2) / 4) :
crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten (W₀.flatten - lr gh)) b₀ x))))) label crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten W₀.flatten) b₀ x))))) label - (lr * idx : Fin (d₀ * d₁), gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx ^ 2) / 2

One inexact SGD step on the MLP's input weights provably decreases the cross-entropy loss. The deepest descent capstone: both ReLU layers' margins at the step radius D = lr·(‖∇L‖₁ + d₀d₁·η) freeze the masks, the segment-Lipschitz constant C = 2·d₃·d₂²·w₁²·w₂²·a²/(1−2·w₂·d₂·w₁·a·D) is proven, and the loss drops by ≥ lr·‖∇L‖₂²/2. Remaining hypotheses are checkable arithmetic. The input-layer peer of linear_sgd_descends; with this, every MLP weight layer's descent statement is discharged.

theorem Proofs.mlp_output_float_sgd_descends {d₀ d₁ d₂ d₃ : } (M : FloatModel) (W₀ : Mat d₀ d₁) (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) (fexp : ) {lr a eexp δ : } (ha : 0 a) (hx : ∀ (i : Fin d₂), |relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))) i| a) (hlr : 0 lr) (heexp0 : 0 eexp) (heexp1 : eexp 1) (hδ0 : 0 δ) (hfexp : ∀ (t : ), |fexp t - Real.exp t| eexp * Real.exp t) (hρ1 : FloatModel.smRho M.u eexp d₃ < 1) ( : ∀ (k' : Fin d₃), |M.dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)))) k' - dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)))) k'| δ) (hsmall : 2 * (a * (lr * (idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| + ↑(d₂ * d₃) * FloatModel.mulErr M.u a 1 0 (FloatModel.cotErr M.u eexp δ d₃)))) < 1) (h1 : lr * FloatModel.mulErr M.u a 1 0 (FloatModel.cotErr M.u eexp δ d₃) * idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| (lr * idx : Fin (d₂ * d₃), gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx ^ 2) / 4) (h2 : 2 * a ^ 2 / (1 - 2 * (a * (lr * (idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| + ↑(d₂ * d₃) * FloatModel.mulErr M.u a 1 0 (FloatModel.cotErr M.u eexp δ d₃))))) * (lr * (idx : Fin (d₂ * d₃), |gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx| + ↑(d₂ * d₃) * FloatModel.mulErr M.u a 1 0 (FloatModel.cotErr M.u eexp δ d₃))) ^ 2 (lr * idx : Fin (d₂ * d₃), gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx ^ 2) / 4) :
crossEntropy d₃ (dense (Mat.unflatten (W₂.flatten - lr M.linearFloatGrad W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)))) fexp label)) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label crossEntropy d₃ (dense (Mat.unflatten W₂.flatten) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label - (lr * idx : Fin (d₂ * d₃), gradAt (fun (w : Vec (d₂ * d₃)) => crossEntropy d₃ (dense (Mat.unflatten w) b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) label) W₂.flatten idx ^ 2) / 2

One binary32 SGD step on the MLP's output weights provably decreases the cross-entropy loss — with NO abstract gradient-accuracy parameter. The output-layer rung of the η-composition (Item D / G1 for the MLP). Since the top dense layer sits directly below the softmax-CE loss with no ReLU between, the loss-of-W₂ map is the linear net's loss at the hidden activation a₁ = relu(dense W₁ b₁ (relu(dense W₀ b₀ x))) — so this is linear_float_sgd_descends instantiated there. The gradient is the actual binary32 output-layer gradient M.linearFloatGrad W₂ b₂ a₁ and its accuracy η = mulErr u a 1 0 (cotErr …) is proven (by linear_grad_close, inside the linear theorem), not assumed. No margin needed — the output layer never crosses a kink.

The hidden/input rungs (mlp_{hidden,input}_sgd_descends) still take an abstract η: their float gradients run back through the ReLU masks and the W₂-cotangent fan-in, so the η-composition there needs a per-layer float-backward grad-close (a mlp_w{1,0}_grad_close) under the descent margins — the joint-step refinement flagged at the top of this file, left open.

theorem Proofs.mlp_w1_grad_close {d₁ d₂ d₃ : } (M : FloatModel) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (label : Fin d₃) (fexp : ) {a w₁ β₁ w₂ β₂ eexp : } (ha : 0 a) (hw₁ : 0 w₁) (hβ₁ : 0 β₁) (hw₂ : 0 w₂) (hβ₂ : 0 β₂) (heexp0 : 0 eexp) (heexp1 : eexp 1) (hfexp : ∀ (t : ), |fexp t - Real.exp t| eexp * Real.exp t) (hρ1 : FloatModel.smRho M.u eexp d₃ < 1) (hx : ∀ (i : Fin d₁), |a₀ i| a) (hW₁ : ∀ (i : Fin d₁) (j : Fin d₂), |W₁ i j| w₁) (hb₁ : ∀ (j : Fin d₂), |b₁ j| β₁) (hW₂ : ∀ (i : Fin d₂) (j : Fin d₃), |W₂ i j| w₂) (hb₂ : ∀ (j : Fin d₃), |b₂ j| β₂) (hmargin : ∀ (j' : Fin d₂), FloatModel.layerBudget M.u d₁ w₁ β₁ a 0 < |dense W₁ b₁ a₀ j'|) (i : Fin d₁) (j : Fin d₂) :
|M.mul (a₀ i) (FloatModel.reluMask (M.dense W₁ b₁ a₀) (M.dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) (M.softmaxCECotF fexp (M.dense W₂ b₂ (relu d₂ (M.dense W₁ b₁ a₀))) label)) j) - a₀ i * FloatModel.reluMask (dense W₁ b₁ a₀) (dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) fun (k : Fin d₃) => softmax d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ a₀))) k - oneHot d₃ label k) j| FloatModel.mulErr M.u a (FloatModel.layerAct d₃ w₂ 0 1) 0 (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ a) (FloatModel.layerBudget M.u d₁ w₁ β₁ a 0)) d₃))

The binary32 hidden-layer (W₁) gradient is within an explicit budget of the certified one, per entry — the float-backward grad-close that the hidden η-composition needs. With the layer-1 input activation a₀ frozen exact (the descent moves only W₁), the rendered trainer computes the W₁ gradient as fl(a₀ᵢ · c̃₁ⱼ) where the float layer-1 cotangent c̃₁ = mask(z̃₁, W₂ᵀ·c̃₂) reads the float pre-activation z̃₁ = M.dense W₁ b₁ a₀ and the float softmax−onehot head c̃₂ at the float logits. This is within mulErr M.u a … 0 (layerBudget … (cotErr …)) of the certified a₀ᵢ · mask(z₁, W₂ᵀ·(softmax−onehot))ⱼ (= mlp_hidden_loss_gradAt), built from three reusable closes: the head (softmax_ce_cot_close, accuracy cotErr), the masked W₂ᵀ contraction (cot_step_close, under the quantitative margin E₁ < |z₁ⱼ| — forward rounding must not flip the layer-1 ReLU), and the final input multiply (mul_close, with the exact a₀ operand, ea = 0, exactly as the linear grad-close).

noncomputable def Proofs.FloatModel.mlpHiddenFloatGrad (M : FloatModel) {d₁ d₂ d₃ : } (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (fexp : ) (label : Fin d₃) :
Vec (d₁ * d₂)

The binary32 hidden-layer (W₁) gradient of the MLP loss, exactly as the rendered trainer computes it (with the layer-1 input activation a₀ frozen exact): fl(a₀ᵢ · c̃₁ⱼ) where the float layer-1 cotangent c̃₁ = mask(z̃₁, W₂ᵀ·c̃₂) reads the float pre-activation z̃₁ = M.dense W₁ b₁ a₀ and the float softmax−onehot head c̃₂ at the float logits. Flattened to the Vec (d₁*d₂) parameter layout that gradAt/mlp_hidden_sgd_descends use. The hidden-layer peer of linearFloatGrad.

Equations
  • One or more equations did not get rendered due to their size.
Instances For
    @[simp]
    theorem Proofs.mlpHiddenFloatGrad_apply (M : FloatModel) {d₁ d₂ d₃ : } (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (fexp : ) (label : Fin d₃) (i : Fin d₁) (j : Fin d₂) :
    M.mlpHiddenFloatGrad W₁ b₁ W₂ b₂ a₀ fexp label (finProdFinEquiv (i, j)) = M.mul (a₀ i) (FloatModel.reluMask (M.dense W₁ b₁ a₀) (M.dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) (M.softmaxCECotF fexp (M.dense W₂ b₂ (relu d₂ (M.dense W₁ b₁ a₀))) label)) j)
    theorem Proofs.mlp_hidden_loss_gradAt_reluMask {d₁ d₂ d₃ : } (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (label : Fin d₃) (hz : ∀ (k : Fin d₂), dense W₁ b₁ a₀ k 0) (i : Fin d₁) (j : Fin d₂) :
    gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten (finProdFinEquiv (i, j)) = a₀ i * FloatModel.reluMask (dense W₁ b₁ a₀) (dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) fun (k : Fin d₃) => softmax d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ a₀))) k - oneHot d₃ label k) j

    The certified hidden-layer loss gradient, in the reluMask form that mlp_w1_grad_close bounds against. At an off-kink parameter point (hz), mlp_hidden_loss_gradAt's closed form a₀ᵢ·relu'(z₁ⱼ)·∑ₖ W₂ⱼₖ·(softmax−onehot)ₖ equals the masked-W₂ᵀ- contraction form a₀ᵢ · reluMask(z₁, dense (fun j' i' => W₂ i' j') 0 (softmax−onehot))ⱼ. The bridge that lets the float grad-close (stated with reluMask) discharge mlp_hidden_sgd_descends' abstract η (stated with gradAt).

    theorem Proofs.mlp_hidden_float_sgd_descends {d₁ d₂ d₃ : } (M : FloatModel) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (a₀ : Vec d₁) (label : Fin d₃) (fexp : ) {lr a w₁ β₁ w₂ β₂ eexp : } (ha : 0 a) (hw₁ : 0 w₁) (hβ₁ : 0 β₁) (hw₂ : 0 w₂) (hβ₂ : 0 β₂) (hlr : 0 lr) (heexp0 : 0 eexp) (heexp1 : eexp 1) (hfexp : ∀ (t : ), |fexp t - Real.exp t| eexp * Real.exp t) (hρ1 : FloatModel.smRho M.u eexp d₃ < 1) (hx : ∀ (i : Fin d₁), |a₀ i| a) (hW₁ : ∀ (i : Fin d₁) (j : Fin d₂), |W₁ i j| w₁) (hb₁ : ∀ (j : Fin d₂), |b₁ j| β₁) (hW₂ : ∀ (i : Fin d₂) (j : Fin d₃), |W₂ i j| w₂) (hb₂ : ∀ (j : Fin d₃), |b₂ j| β₂) (hmargin_round : ∀ (j' : Fin d₂), FloatModel.layerBudget M.u d₁ w₁ β₁ a 0 < |dense W₁ b₁ a₀ j'|) (hmargin_step : ∀ (j : Fin d₂), a * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * FloatModel.mulErr M.u a (FloatModel.layerAct d₃ w₂ 0 1) 0 (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ a) (FloatModel.layerBudget M.u d₁ w₁ β₁ a 0)) d₃)))) < |dense W₁ b₁ a₀ j|) (hsmall : 2 * (w₂ * (a * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * FloatModel.mulErr M.u a (FloatModel.layerAct d₃ w₂ 0 1) 0 (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ a) (FloatModel.layerBudget M.u d₁ w₁ β₁ a 0)) d₃)))))) < 1) (h1 : lr * FloatModel.mulErr M.u a (FloatModel.layerAct d₃ w₂ 0 1) 0 (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ a) (FloatModel.layerBudget M.u d₁ w₁ β₁ a 0)) d₃)) * idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| (lr * idx : Fin (d₁ * d₂), gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx ^ 2) / 4) (h2 : 2 * d₃ * w₂ ^ 2 * a ^ 2 / (1 - 2 * (w₂ * (a * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * FloatModel.mulErr M.u a (FloatModel.layerAct d₃ w₂ 0 1) 0 (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ a) (FloatModel.layerBudget M.u d₁ w₁ β₁ a 0)) d₃))))))) * (lr * (idx : Fin (d₁ * d₂), |gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx| + ↑(d₁ * d₂) * FloatModel.mulErr M.u a (FloatModel.layerAct d₃ w₂ 0 1) 0 (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ a) (FloatModel.layerBudget M.u d₁ w₁ β₁ a 0)) d₃)))) ^ 2 (lr * idx : Fin (d₁ * d₂), gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx ^ 2) / 4) :
    crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten (W₁.flatten - lr M.mlpHiddenFloatGrad W₁ b₁ W₂ b₂ a₀ fexp label)) b₁ a₀))) label crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten W₁.flatten) b₁ a₀))) label - (lr * idx : Fin (d₁ * d₂), gradAt (fun (w : Vec (d₁ * d₂)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense (Mat.unflatten w) b₁ a₀))) label) W₁.flatten idx ^ 2) / 2

    One binary32 SGD step on the MLP's hidden weights provably decreases the cross-entropy loss — with NO abstract gradient-accuracy parameter. The hidden-layer rung of the η-composition (Item D / G1 for the MLP). The gradient is the actual binary32 W₁ gradient M.mlpHiddenFloatGrad W₁ b₁ W₂ b₂ a₀ fexp label, and its accuracy η = mulErr u a (layerAct …) 0 (layerBudget … (cotErr …)) is proven by mlp_w1_grad_close (via the reluMaskgradAt bridge mlp_hidden_loss_gradAt_reluMask), not assumed.

    Two margins are carried — the honest, lower-risk first cut (the linear rung also carries several hypotheses): the rounding margin hmargin_round (layerBudget < |z₁|, forward rounding must not flip the layer-1 ReLU — the grad-close precondition) and the step margin hmargin_step (a·D < |z₁|, the parameter step must not flip it along the segment — the smoothness precondition). They are the same shape ("nothing flips the layer-1 ReLU"); collapsing one into the other is left as a refinement. This is the hidden-layer peer of linear_float_sgd_descends / mlp_output_float_sgd_descends.

    theorem Proofs.reluMask_dense_transpose_eq {p n : } (z : Vec p) (W : Mat p n) (c : Vec n) (l : Fin p) :
    (if z l > 0 then 1 else 0) * k : Fin n, W l k * c k = FloatModel.reluMask z (dense (fun (j : Fin n) (i' : Fin p) => W i' j) (fun (x : Fin p) => 0) c) l

    A masked Wᵀ contraction in if-then-else form equals the reluMask form. (relu'(zₗ))·∑ₖ Wₗₖ·cₖ = reluMask z (Wᵀ·c) l — the per-step identity behind the gradAtreluMask bridges (mlp_hidden_/ mlp_input_loss_gradAt_reluMask): one ReLU-sign case split + mul_comm (the transpose dense (fun j i' => W i' j) 0 c reads ∑ₖ cₖ·Wₗₖ).

    noncomputable def Proofs.FloatModel.mlpInputFloatGrad (M : FloatModel) {d₀ d₁ d₂ d₃ : } (W₀ : Mat d₀ d₁) (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (fexp : ) (label : Fin d₃) :
    Vec (d₀ * d₁)

    The binary32 input-layer (W₀) gradient of the MLP loss, exactly as the rendered trainer computes it (x the exact input): fl(xᵢ · c̃₀ⱼ) where the float layer-0 cotangent c̃₀ = mask(z̃₀, W₁ᵀ·c̃₁) reads the float layer-1 cotangent c̃₁ = mask(z̃₁, W₂ᵀ·c̃₂) and the float softmax−onehot head c̃₂, all at the float pre-activations. Flattened to the Vec (d₀*d₁) parameter layout. The two-mask peer of mlpHiddenFloatGrad.

    Equations
    • One or more equations did not get rendered due to their size.
    Instances For
      @[simp]
      theorem Proofs.mlpInputFloatGrad_apply (M : FloatModel) {d₀ d₁ d₂ d₃ : } (W₀ : Mat d₀ d₁) (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (fexp : ) (label : Fin d₃) (i : Fin d₀) (j : Fin d₁) :
      M.mlpInputFloatGrad W₀ b₀ W₁ b₁ W₂ b₂ x fexp label (finProdFinEquiv (i, j)) = M.mul (x i) (FloatModel.reluMask (M.dense W₀ b₀ x) (M.dense (fun (j' : Fin d₂) (i' : Fin d₁) => W₁ i' j') (fun (x : Fin d₁) => 0) (FloatModel.reluMask (M.dense W₁ b₁ (relu d₁ (M.dense W₀ b₀ x))) (M.dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) (M.softmaxCECotF fexp (M.dense W₂ b₂ (relu d₂ (M.dense W₁ b₁ (relu d₁ (M.dense W₀ b₀ x))))) label)))) j)
      theorem Proofs.mlp_input_loss_gradAt_reluMask {d₀ d₁ d₂ d₃ : } (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (W₀ : Mat d₀ d₁) (x : Vec d₀) (label : Fin d₃) (hz0 : ∀ (k : Fin d₁), dense W₀ b₀ x k 0) (hz1 : ∀ (k : Fin d₂), dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)) k 0) (i : Fin d₀) (j : Fin d₁) :
      gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten (finProdFinEquiv (i, j)) = x i * FloatModel.reluMask (dense W₀ b₀ x) (dense (fun (j' : Fin d₂) (i' : Fin d₁) => W₁ i' j') (fun (x : Fin d₁) => 0) (FloatModel.reluMask (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))) (dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) fun (k : Fin d₃) => softmax d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) k - oneHot d₃ label k))) j

      The certified input-layer loss gradient, in the nested reluMask form that mlp_w0_grad_close bounds against. At an off-kink point (both hz0, hz1), mlp_input_loss_gradAt's two-mask if-then-else closed form equals xᵢ · reluMask(z₀, W₁ᵀ·reluMask(z₁, W₂ᵀ·(softmax−onehot)))ⱼ. Two applications of reluMask_dense_transpose_eq (inner W₂ᵀ then outer W₁ᵀ), fired by simp_rw.

      theorem Proofs.mlp_w0_grad_close {d₀ d₁ d₂ d₃ : } (M : FloatModel) (W₀ : Mat d₀ d₁) (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) (fexp : ) {a w₀ β₀ w₁ β₁ w₂ β₂ eexp : } (ha : 0 a) (hw₀ : 0 w₀) (hβ₀ : 0 β₀) (hw₁ : 0 w₁) (hβ₁ : 0 β₁) (hw₂ : 0 w₂) (hβ₂ : 0 β₂) (heexp0 : 0 eexp) (heexp1 : eexp 1) (hfexp : ∀ (t : ), |fexp t - Real.exp t| eexp * Real.exp t) (hρ1 : FloatModel.smRho M.u eexp d₃ < 1) (hx : ∀ (i : Fin d₀), |x i| a) (hW₀ : ∀ (i : Fin d₀) (j : Fin d₁), |W₀ i j| w₀) (hb₀ : ∀ (j : Fin d₁), |b₀ j| β₀) (hW₁ : ∀ (i : Fin d₁) (j : Fin d₂), |W₁ i j| w₁) (hb₁ : ∀ (j : Fin d₂), |b₁ j| β₁) (hW₂ : ∀ (i : Fin d₂) (j : Fin d₃), |W₂ i j| w₂) (hb₂ : ∀ (j : Fin d₃), |b₂ j| β₂) (hmargin0 : ∀ (j' : Fin d₁), FloatModel.layerBudget M.u d₀ w₀ β₀ a 0 < |dense W₀ b₀ x j'|) (hmargin1 : ∀ (l' : Fin d₂), FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0) < |dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)) l'|) (i : Fin d₀) (j : Fin d₁) :
      |M.mul (x i) (FloatModel.reluMask (M.dense W₀ b₀ x) (M.dense (fun (j' : Fin d₂) (i' : Fin d₁) => W₁ i' j') (fun (x : Fin d₁) => 0) (FloatModel.reluMask (M.dense W₁ b₁ (relu d₁ (M.dense W₀ b₀ x))) (M.dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) (M.softmaxCECotF fexp (M.dense W₂ b₂ (relu d₂ (M.dense W₁ b₁ (relu d₁ (M.dense W₀ b₀ x))))) label)))) j) - x i * FloatModel.reluMask (dense W₀ b₀ x) (dense (fun (j' : Fin d₂) (i' : Fin d₁) => W₁ i' j') (fun (x : Fin d₁) => 0) (FloatModel.reluMask (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))) (dense (fun (j' : Fin d₃) (i' : Fin d₂) => W₂ i' j') (fun (x : Fin d₂) => 0) fun (k : Fin d₃) => softmax d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense W₀ b₀ x))))) k - oneHot d₃ label k))) j| FloatModel.mulErr M.u a (FloatModel.layerAct d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1)) 0 (FloatModel.layerBudget M.u d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1) (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a)) (FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0))) d₃)))

      The binary32 input-layer (W₀) gradient is within an explicit budget of the certified one, per entry — the float-backward grad-close for the deepest rung. With x exact, the rendered trainer computes the W₀ gradient fl(xᵢ·c̃₀ⱼ), c̃₀ = mask(z̃₀, W₁ᵀ·mask(z̃₁, W₂ᵀ·c̃₂)) from the float softmax−onehot head c̃₂ back through two ReLU masks. This is within mulErr … 0 (layerBudget … (layerBudget … (cotErr …))) of the certified xᵢ·mask(z₀, W₁ᵀ·mask(z₁, W₂ᵀ·(softmax−onehot)))ⱼ (= mlp_input_loss_gradAt, via mlp_input_loss_gradAt_reluMask). Built like mlp_w1_grad_close with one more cot_step_close: head (softmax_ce_cot_close), masked W₂ᵀ contraction (cot_step_close, under the layer-1 margin E₁ < |z₁|), masked W₁ᵀ contraction (cot_step_close, under the layer-0 margin E₀ < |z₀|), final exact-x multiply (mul_close, ea = 0).

      theorem Proofs.mlp_input_float_sgd_descends {d₀ d₁ d₂ d₃ : } (M : FloatModel) (W₀ : Mat d₀ d₁) (b₀ : Vec d₁) (W₁ : Mat d₁ d₂) (b₁ : Vec d₂) (W₂ : Mat d₂ d₃) (b₂ : Vec d₃) (x : Vec d₀) (label : Fin d₃) (fexp : ) {lr a w₀ β₀ w₁ β₁ w₂ β₂ eexp : } (ha : 0 a) (hw₀ : 0 w₀) (hβ₀ : 0 β₀) (hw₁ : 0 w₁) (hβ₁ : 0 β₁) (hw₂ : 0 w₂) (hβ₂ : 0 β₂) (hlr : 0 lr) (heexp0 : 0 eexp) (heexp1 : eexp 1) (hfexp : ∀ (t : ), |fexp t - Real.exp t| eexp * Real.exp t) (hρ1 : FloatModel.smRho M.u eexp d₃ < 1) (hx : ∀ (i : Fin d₀), |x i| a) (hW₀ : ∀ (i : Fin d₀) (j : Fin d₁), |W₀ i j| w₀) (hb₀ : ∀ (j : Fin d₁), |b₀ j| β₀) (hW₁ : ∀ (i : Fin d₁) (j : Fin d₂), |W₁ i j| w₁) (hb₁ : ∀ (j : Fin d₂), |b₁ j| β₁) (hW₂ : ∀ (i : Fin d₂) (j : Fin d₃), |W₂ i j| w₂) (hb₂ : ∀ (j : Fin d₃), |b₂ j| β₂) (hmargin0_round : ∀ (j' : Fin d₁), FloatModel.layerBudget M.u d₀ w₀ β₀ a 0 < |dense W₀ b₀ x j'|) (hmargin1_round : ∀ (l' : Fin d₂), FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0) < |dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)) l'|) (hmargin0_step : ∀ (j : Fin d₁), a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * FloatModel.mulErr M.u a (FloatModel.layerAct d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1)) 0 (FloatModel.layerBudget M.u d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1) (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a)) (FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0))) d₃))))) < |dense W₀ b₀ x j|) (hmargin1_step : ∀ (l : Fin d₂), w₁ * (a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * FloatModel.mulErr M.u a (FloatModel.layerAct d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1)) 0 (FloatModel.layerBudget M.u d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1) (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a)) (FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0))) d₃)))))) < |dense W₁ b₁ (relu d₁ (dense W₀ b₀ x)) l|) (hsmall : 2 * (w₂ * (d₂ * (w₁ * (a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * FloatModel.mulErr M.u a (FloatModel.layerAct d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1)) 0 (FloatModel.layerBudget M.u d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1) (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a)) (FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0))) d₃))))))))) < 1) (h1 : lr * FloatModel.mulErr M.u a (FloatModel.layerAct d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1)) 0 (FloatModel.layerBudget M.u d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1) (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a)) (FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0))) d₃))) * idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| (lr * idx : Fin (d₀ * d₁), gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx ^ 2) / 4) (h2 : 2 * d₃ * d₂ ^ 2 * w₁ ^ 2 * w₂ ^ 2 * a ^ 2 / (1 - 2 * (w₂ * (d₂ * (w₁ * (a * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * FloatModel.mulErr M.u a (FloatModel.layerAct d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1)) 0 (FloatModel.layerBudget M.u d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1) (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a)) (FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0))) d₃)))))))))) * (lr * (idx : Fin (d₀ * d₁), |gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx| + ↑(d₀ * d₁) * FloatModel.mulErr M.u a (FloatModel.layerAct d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1)) 0 (FloatModel.layerBudget M.u d₂ w₁ 0 (FloatModel.layerAct d₃ w₂ 0 1) (FloatModel.layerBudget M.u d₃ w₂ 0 1 (FloatModel.cotErr M.u eexp (FloatModel.layerBudget M.u d₂ w₂ β₂ (FloatModel.layerAct d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a)) (FloatModel.layerBudget M.u d₁ w₁ β₁ (FloatModel.layerAct d₀ w₀ β₀ a) (FloatModel.layerBudget M.u d₀ w₀ β₀ a 0))) d₃))))) ^ 2 (lr * idx : Fin (d₀ * d₁), gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx ^ 2) / 4) :
      crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten (W₀.flatten - lr M.mlpInputFloatGrad W₀ b₀ W₁ b₁ W₂ b₂ x fexp label)) b₀ x))))) label crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten W₀.flatten) b₀ x))))) label - (lr * idx : Fin (d₀ * d₁), gradAt (fun (w : Vec (d₀ * d₁)) => crossEntropy d₃ (dense W₂ b₂ (relu d₂ (dense W₁ b₁ (relu d₁ (dense (Mat.unflatten w) b₀ x))))) label) W₀.flatten idx ^ 2) / 2

      One binary32 SGD step on the MLP's input weights provably decreases the cross-entropy loss — with NO abstract gradient-accuracy parameter. The input-layer rung of the η-composition (Item D / G1 for the MLP), one mask deeper than the hidden rung. The gradient is the actual binary32 W₀ gradient M.mlpInputFloatGrad, and its accuracy is proven by mlp_w0_grad_close (via the nested reluMaskgradAt bridge mlp_input_loss_gradAt_reluMask), not assumed.

      Four margins are carried as hypotheses (the honest first cut): the two rounding margins hmargin0_round/hmargin1_round (forward rounding must not flip either ReLU — the grad-close preconditions) and the two step margins hmargin0_step/hmargin1_step (the parameter step must not flip either along the segment — the smoothness preconditions). With this, "one binary32 SGD step on any single MLP weight layer provably decreases the loss" is closed for all three layers.