Documentation

LeanMlir.Proofs.Foundation.DataParallel

Data parallelism: the gradient mean, and what function trained #

Every *dp* artifact in verified_mlir/ is ONE program run on R replicas. Per parameter, after the gradient node and before the optimizer tail, emitGradAllReduce emits stablehlo.all_reduce(add) over replica_groups = [[0..R-1]] followed by a divide by R. That text is emitted OUTSIDE the SHlo AST and is a declared trusted carve-out, so every tie in the repo — r34_net_tiedB, mnv2_net_tiedB, efficientnet_net_tiedG — is stated at the PER-REPLICA gradient node and says so in its own header. This file is the ℝ-level half of closing that disclaimer: piece 1 of planning/archive/proofs_tier_to_paper_nets.md §4d.

What is proved #

What is NOT claimed #

Nothing here is about the emitted all_reduce. den (allReduceMeanF R g) = (1/R) Σ_r den (g r) is §4d piece 2, an SHlo constructor with a den, a pretty and a parser case, and it waits on 4c's batched chains. Until it lands a tie composes with these lemmas only through the reader.

BatchNorm statistics are per replica. Nothing all-reduces μ/var, which is why N in the batch-BN tiers is the PER-CARD batch and why dpMeanGrad_ne_globalBatchGrad is not a curiosity.

That every replica starts from the same parameters, that the checkpoint is read from one replica, and that replica_groups names all R devices are the DRIVER's (VerifiedTrain.lean, ffi/pjrt_ffi.c, PJRT_REPLICAS), not theorems here. dpIterate_lockstep takes the shared start as a HYPOTHESIS — it says what follows from it, not that the driver establishes it. The *-dp-check gates are the empirical evidence for that half.

WHICH examples land where is the one piece of that which does become a theorem, and only for half the nets. dpMeanGrad_eq_globalBatchGrad_of_perExample binds the shard, so for a net with no batch coupling the partition is provably irrelevant and all the driver has to get right is that the slices cover the batch. ⛔ For a batch-BN net the partition changes the function, and nothing here recovers it.

pdiv_const_smul, the scalar-multiple rule this file needed, is Tensor.lean's (moved 2026-09-08).

noncomputable def Proofs.lossGrad {P : } (L : Vec P) (θ : Vec P) :
Vec P

A scalar loss's gradient, in the Vec 1-lifted spelling pdiv reads. SmoothedLossCot writes this out at every use; it is worth a name here because the whole file is about rearranging sums of them.

Equations
Instances For
    @[reducible, inline]
    abbrev Proofs.LossDifferentiableAt {P : } (L : Vec P) (θ : Vec P) :

    Differentiability of a scalar loss, in the same lifted spelling.

    Equations
    Instances For
      noncomputable def Proofs.dpMean {R P : } (g : Fin RVec P) :
      Vec P

      The all-reduced gradient: (1/R) Σ_r g_r. emitGradAllReduce's all_reduce(add) followed by its divide by R, read as a function of the R per-replica gradients.

      Equations
      Instances For
        noncomputable def Proofs.meanLoss {M P : } (L : Fin MVec P) :
        Vec P

        A mean of losses. Used at two different index meanings and deliberately ONE definition: over replicas it is the function data parallelism minimises, and over examples it is the batch mean a single device minimises. That those coincide under no batch coupling is meanLoss_shard, and it is the content of §4d piece 1.

        Equations
        Instances For
          theorem Proofs.meanLoss_differentiableAt {M P : } (L : Fin MVec P) (θ : Vec P) (hdiff : ∀ (m : Fin M), LossDifferentiableAt (L m) θ) :

          A mean of losses is differentiable where its summands are.

          theorem Proofs.lossGrad_meanLoss {M P : } (L : Fin MVec P) (θ : Vec P) (hdiff : ∀ (m : Fin M), LossDifferentiableAt (L m) θ) :
          lossGrad (meanLoss L) θ = dpMean fun (m : Fin M) => lossGrad (L m) θ

          The gradient of a mean is the mean of the gradients. Linearity of pdiv, and the only analysis in this file: pdiv_const_smul pulls the 1/M out and pdiv_finset_sum splits the sum. Everything downstream is this lemma read at two index meanings.

          theorem Proofs.dpMeanGrad_eq_grad_meanLoss {R P : } (L : Fin RVec P) (θ : Vec P) (hdiff : ∀ (r : Fin R), LossDifferentiableAt (L r) θ) :
          (dpMean fun (r : Fin R) => lossGrad (L r) θ) = lossGrad (meanLoss L) θ

          ⭐⭐ What the collective computes, named as a gradient. The all-reduced mean of the R per-replica gradients IS the gradient of the mean of the R per-replica losses.

          No hypothesis on the losses beyond differentiability — in particular this holds for a training-mode BatchNorm net, where each L r genuinely depends on the whole of replica r's slice. That is the point: it names what a data-parallel run minimises without claiming that function is the global-batch loss (dpMeanGrad_ne_globalBatchGrad).

          theorem Proofs.meanLoss_shard {R N P : } (e : Fin R × Fin N Fin (R * N)) ( : Fin (R * N)Vec P) :
          (meanLoss fun (r : Fin R) => meanLoss fun (n : Fin N) => (e (r, n))) = meanLoss

          ⭐⭐ Sharding a per-example loss is invisible to the mean. If replica r's loss is the mean over its own N examples, then the mean of the R replica losses is the mean over all R·N examples — as FUNCTIONS, before any derivative is taken.

          Stated at an arbitrary e, so which examples land on which replica does not enter: the contiguous slices the DP shim cuts (finProdFinEquiv), the interleave ds.shard produces for the sharded producers, and any other partition all give the same theorem.

          theorem Proofs.dpMeanGrad_eq_globalBatchGrad_of_perExample {R N P : } (e : Fin R × Fin N Fin (R * N)) ( : Fin (R * N)Vec P) (θ : Vec P) (hdiff : ∀ (k : Fin (R * N)), LossDifferentiableAt ( k) θ) :
          (dpMean fun (r : Fin R) => lossGrad (meanLoss fun (n : Fin N) => (e (r, n))) θ) = lossGrad (meanLoss ) θ

          ⭐⭐ For a net with no batch coupling the data-parallel step IS the single-device step at the global batch R·N. meanLoss_shard under lossGrad.

          "No batch coupling" is the hypothesis's shape, not a side condition: it is spelled by giving replica r the loss meanLoss (fun n => ℓ (e (r, n))) — a mean of per-example terms — which a training-mode BatchNorm net's replica loss is not.

          theorem Proofs.dpMeanGrad_eq_globalBatchGrad_contiguous {R N P : } ( : Fin (R * N)Vec P) (θ : Vec P) (hdiff : ∀ (k : Fin (R * N)), LossDifferentiableAt ( k) θ) :
          (dpMean fun (r : Fin R) => lossGrad (meanLoss fun (n : Fin N) => (finProdFinEquiv (r, n))) θ) = lossGrad (meanLoss ) θ

          The contiguous shard — replica r owns examples [N·r, N·r + N) — as an instance. This is the split VerifiedTrain.lean's DP path cuts (elems / replicas).

          noncomputable def Proofs.sliceMean {N : } (xs : Fin N) :

          The batch statistic a training-mode BatchNorm reduces over: the slice's mean.

          Equations
          Instances For
            noncomputable def Proofs.bnToyLoss {N : } (xs : Fin N) (θ : Vec 1) :

            A loss that reads a NONLINEAR function of its own slice's batch statistic — the shape training-mode BatchNorm gives every replica loss in this repo, reduced to the smallest thing that still has it. Linear in the parameter, so the gradient is a constant and the arithmetic is visible; QUADRATIC in the slice mean, which is the coupling.

            Equations
            Instances For
              def Proofs.dpToyShard :
              Fin 2Fin 1

              Replica r's slice in the witness: one example, of value 2r.

              Equations
              Instances For

                The global batch those two slices make: {0, 2}, whose mean is 1.

                Equations
                Instances For

                  The two shards ARE the global batch, under the contiguous split — replica r owns example r. Without this the witness below would be comparing two unrelated datasets and would prove nothing; a comparison against a re-derivation tests the re-derivation.

                  theorem Proofs.lossGrad_smul_coord {P : } (c : ) (j : Fin P) (θ : Vec P) (i : Fin P) :
                  lossGrad (fun (θ' : Vec P) => c * θ' j) θ i = if i = j then c else 0

                  The gradient of a linear form, which is all bnToyLoss needs.

                  theorem Proofs.lossGrad_bnToyLoss {N : } (xs : Fin N) (θ : Vec 1) (i : Fin 1) :
                  lossGrad (bnToyLoss xs) θ i = sliceMean xs ^ 2

                  bnToyLoss's gradient is the squared slice mean, at every coordinate of Vec 1.

                  ⛔⛔ With batch coupling the previous section is FALSE, and here is the witness. Two replicas, one example each — slices {0} and {2}, global batch {0, 2}. The data-parallel mean gradient is (0² + 2²)/2 = 2; the gradient of the global-batch loss is 1² = 1.

                  So a training-BN net trained data-parallel did NOT take a step on the batch-R·N loss, at any learning rate and however small the gradients. What it took a step on is the mean of the R per-replica batch-BN losses, which is dpMeanGrad_eq_grad_meanLoss — a different function, and the only honest answer to "what trained". ⚠ The witness needs no BatchNorm: ANY nonlinear read of a per-slice statistic separates the two, which is why the split is structural rather than a property of the normalisation's formula.

                  theorem Proofs.dpMean_shardSum {R N P : } (e : Fin R × Fin N Fin (R * N)) (c : Fin (R * N)Vec P) :
                  (dpMean fun (r : Fin R) => n : Fin N, c (e (r, n))) = fun (i : Fin P) => 1 / R * m : Fin (R * N), c m i

                  The replica mean of the shard sums is 1/R of the global sum. Pure re-indexing: the R shard sums together run over every one of the R·N examples exactly once.

                  theorem Proofs.dpSyncGrad_eq_globalBatchGrad {R N P : } (e : Fin R × Fin N Fin (R * N)) (c : Fin (R * N)Vec P) :
                  (dpMean fun (r : Fin R) (i : Fin P) => 1 / N * n : Fin N, c (e (r, n)) i) = fun (i : Fin P) => 1 / ↑(R * N) * m : Fin (R * N), c m i

                  ⭐⭐ The positive twin of dpMeanGrad_ne_globalBatchGrad: with SYNCHRONISED statistics the data-parallel step IS the single-device step at the global batch R·N.

                  Write the global-batch gradient as a mean of per-example terms, (1/(R·N)) Σ_m c m — the shared-weight batch sum every *GradB node denotes, at the cotangent the global backward delivers to example m. Under sync-BN each c m depends on the WHOLE batch through the all-reduced statistics; that is allowed, and it is what separates this from dpMeanGrad_eq_globalBatchGrad_of_perExample, whose c m had to be a per-example loss's own gradient. What sync-BN buys (P1–P3, PerChannelBN.lean / DataParallelSync.lean) is that replica r's backward is the shard-r block of the global one, so replica r's gradient node is (1/N) Σ_n c (e (r, n)) — ITS examples' terms of the global sum. Then the collective's mean over R of those is the global mean: meanLoss_shard's arithmetic, for vectors.

                  ⛔ Per-replica BatchNorm fails exactly here: replica r's cotangents are then NOT the shard-r block of any global backward, and the witness above is the two-example case.

                  theorem Proofs.dpSyncGrad_eq_globalBatchGrad_contiguous {R N P : } (c : Fin (R * N)Vec P) :
                  (dpMean fun (r : Fin R) (i : Fin P) => 1 / N * n : Fin N, c (finProdFinEquiv (r, n)) i) = fun (i : Fin P) => 1 / ↑(R * N) * m : Fin (R * N), c m i

                  The contiguous shard — replica r owns examples [N·r, N·r + N) — as an instance, the split VerifiedTrain.lean's DP path cuts.

                  noncomputable def Proofs.dpStep {R P : } {S : Type u_1} (grad : Fin RSVec P) (tail : SVec PS) (st : Fin RS) :
                  Fin RS

                  One data-parallel step, as shipped. Every replica computes its LOCAL gradient from its OWN state copy and its own slice; the collective averages them; every replica applies the same optimizer tail to its own state.

                  S is the replica's whole state — parameters and optimizer moments — because the tail is stateful (Adam's two moments, momentum's velocity, EMA's shadow). Only the gradient is all-reduced, which is exactly what emitGradAllReduce emits.

                  Equations
                  Instances For
                    noncomputable def Proofs.dpSingleStep {R P : } {S : Type u_1} (grad : Fin RSVec P) (tail : SVec PS) (st : S) :
                    S

                    The single-device step the R replicas are running in lockstep: one state, and the DP-mean gradient.

                    Equations
                    Instances For
                      theorem Proofs.dpStep_const {R P : } {S : Type u_1} (grad : Fin RSVec P) (tail : SVec PS) (st : S) :
                      (dpStep grad tail fun (x : Fin R) => st) = fun (x : Fin R) => dpSingleStep grad tail st

                      From a shared state, one DP step lands every replica on the same state again — the induction step, and it is definitional: the all-reduced gradient does not depend on r, and neither does the state the tail is applied to.

                      theorem Proofs.dpIterate_lockstep {R P : } {S : Type u_1} (grad : Fin RSVec P) (tail : SVec PS) (n : ) (st : S) :
                      ((dpStep grad tail)^[n] fun (x : Fin R) => st) = fun (x : Fin R) => (dpSingleStep grad tail)^[n] st

                      ⭐⭐ The lockstep induction. Identical initial states and an identical (all-reduced) update keep the R copies equal at every step, so n steps of the R-replica system are n steps of the single-device one. This is the property VerifiedTrain.lean relies on when it checkpoints from replica 0 — the checkpoint is not "replica 0's answer", it is every replica's.

                      ⚠ The shared start is a HYPOTHESIS (the fun _ => st on the left). That the driver broadcasts it, and that replica_groups names all R devices, is calling logic and the *-dp-check gates are its evidence.

                      theorem Proofs.dpSingleStep_eq_meanLoss_step {R P : } (L : Fin RVec P) (tail : Vec PVec PVec P) (θ : Vec P) (hdiff : ∀ (r : Fin R), LossDifferentiableAt (L r) θ) :
                      dpSingleStep (fun (r : Fin R) (θ' : Vec P) => lossGrad (L r) θ') tail θ = tail θ (lossGrad (meanLoss L) θ)

                      ⭐⭐ What function trained, in one statement. When each replica's gradient is the certified gradient of its own loss, one data-parallel step is one ordinary step on the MEAN of the R per-replica losses.

                      theorem Proofs.dpIterate_eq_meanLossTrain {R P : } (L : Fin RVec P) (tail : Vec PVec PVec P) (hdiff : ∀ (θ : Vec P) (r : Fin R), LossDifferentiableAt (L r) θ) (n : ) (θ : Vec P) :
                      ((dpStep (fun (r : Fin R) (θ' : Vec P) => lossGrad (L r) θ') tail)^[n] fun (x : Fin R) => θ) = fun (x : Fin R) => (fun (θ' : Vec P) => tail θ' (lossGrad (meanLoss L) θ'))^[n] θ

                      ⭐⭐ n steps of R replicas ARE n steps of single-device training on the mean loss. The lockstep induction and the gradient mean, composed — the closing statement of §4d piece 1, and the one that answers the disclaimer every tie in the repo carries.

                      ⚠ Differentiability is asked for at EVERY point, not just at θ, because the trajectory passes through states the statement cannot name. For a relu net that is the one place this file is stronger than it needs to be: the honest weakening is differentiability along the trajectory, and it costs a mutual induction the payoff does not justify.