TorchLean API

NN.Runtime.Optim.Optimizers

Optimizers #

Optimizers for TorchLean runtime training.

This file implements the core math of common gradient-based optimizers as pure functions on typed tensors Tensor α s.

Why “pure functions”?

In PyTorch, optimizers mutate parameters in-place and keep state in Python objects. In TorchLean, we want the update rule itself to be explicit and easy to reuse:

The intent is to mimic the standard textbook formulas closely. We do not try to reproduce every implementation detail of torch.optim.* (e.g. foreach kernels, fused updates, or every optional flag); those live at a different layer than the math we specify here.

How this file fits with the runtime and API:

With this separation, the formula appears once while runtime adapters and API configuration can evolve independently around it.

Why each optimizer has its own State structure:

The generic abstraction lives one layer up:

The result is a collection of canonical state records rather than an inheritance hierarchy.

References (original algorithms / common variants):

PyTorch references (for API/parameter naming):

def Optim.scalarPowNat {α : Type} [One α] [Mul α] (x : α) :
α

Integer exponentiation for scalar optimizer coefficients.

We use an explicit Nat → α recursion instead of x ^ (n : Nat) because Context α provides Pow α α (for runtime scalar exponentiation), but not Pow α Nat.

Instances For

    Shared utilities #

    def Optim.OptimizerUtils.updateMomentumBuf {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (buf : Spec.Tensor α s) (momentum : α) (grads : Spec.Tensor α s) :

    Momentum-style buffer update $\mu\,\mathtt{buf}+g$.

    Instances For
      def Optim.OptimizerUtils.mkAdaptiveLR {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr epsilon : α) (denom : Spec.Tensor α s) :

      Elementwise adaptive learning-rate tensor $\mathtt{lr}/(\sqrt{\mathtt{denom}}+\varepsilon)$.

      This is shared by AdaGrad/RMSProp/Adam-style optimizers.

      Instances For

        SGD #

        structure Optim.SGD.State (α : Type) (s : Spec.Shape) :

        SGD state (per parameter tensor).

        We only store the learning rate here.

        • lr : α

          Learning rate.

        Instances For
          def Optim.SGD.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr : α) :
          Spec.Tensor α sState α s

          Initialize SGD state.

          The parameter tensor is unused; we keep it in the signature so optimizers share the same “init from parameters” calling convention.

          Instances For
            def Optim.SGD.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :

            One SGD step: p ← p - lr * g.

            PyTorch analogy: the core of torch.optim.SGD without momentum/weight-decay extras.

            Instances For

              Momentum SGD #

              Momentum SGD state (per parameter tensor).

              We store a momentum buffer buf and a momentum coefficient $\mu$. Update rule:

              • $\mathtt{buf}\gets\mu\,\mathtt{buf}+g$,
              • $p\gets p-\mathtt{lr}\,\mathtt{buf}$.

              This matches PyTorch's SGD momentum behavior when dampening = 0 and nesterov = false.

              • lr : α

                Learning rate.

              • momentum : α

                Momentum coefficient $\mu$.

              • buf : Spec.Tensor α s

                Momentum buffer buf.

              Instances For
                def Optim.MomentumSGD.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr momentum : α) :
                Spec.Tensor α sState α s

                Initialize momentum SGD with a zero buffer.

                Instances For
                  def Optim.MomentumSGD.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                  State α s × Spec.Tensor α s

                  One momentum-SGD step (returns updated state and parameters).

                  Instances For

                    AdaGrad #

                    structure Optim.AdaGrad.State (α : Type) (s : Spec.Shape) :

                    AdaGrad state (per parameter tensor).

                    We store an accumulator $G$ of squared gradients (same shape as the parameters). The effective step size is scaled by $1/(\sqrt G+\varepsilon)$.

                    • lr : α

                      Base learning rate.

                    • epsilon : α

                      Numerical stability constant $\varepsilon$.

                    • accumulator : Spec.Tensor α s

                      Accumulated squared gradients.

                    Instances For
                      def Optim.AdaGrad.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr epsilon : α) :
                      Spec.Tensor α sState α s

                      Initialize AdaGrad with zero accumulator.

                      Instances For
                        def Optim.AdaGrad.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                        State α s × Spec.Tensor α s

                        One AdaGrad step (returns updated state and parameters).

                        Instances For

                          RMSProp #

                          structure Optim.RMSProp.State (α : Type) (s : Spec.Shape) :

                          RMSProp state (per parameter tensor).

                          We store an EMA of squared gradients (accumulator), often called square_avg in PyTorch code.

                          • lr : α

                            Learning rate.

                          • decay : α

                            Decay coefficient for the EMA of $g^2$ (often called alpha).

                          • epsilon : α

                            Numerical stability constant $\varepsilon$.

                          • accumulator : Spec.Tensor α s

                            EMA of squared gradients.

                          Instances For
                            def Optim.RMSProp.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr decay epsilon : α) :
                            Spec.Tensor α sState α s

                            Initialize RMSProp with zero accumulator.

                            Instances For
                              def Optim.RMSProp.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                              State α s × Spec.Tensor α s

                              One RMSProp step (returns updated state and parameters).

                              Instances For

                                Adam #

                                structure Optim.Adam.State (α : Type) (s : Spec.Shape) :

                                Adam state (per parameter tensor).

                                We store first/second moment EMAs (m, v) and a step counter t used for bias correction.

                                • lr : α

                                  Learning rate.

                                • beta1 : α

                                  First moment decay $\beta_1$.

                                • beta2 : α

                                  Second moment decay $\beta_2$.

                                • epsilon : α

                                  Numerical stability constant $\varepsilon$.

                                • m : Spec.Tensor α s

                                  First moment EMA.

                                • v : Spec.Tensor α s

                                  Second moment EMA.

                                • t :

                                  Step counter (used for bias correction).

                                Instances For
                                  def Optim.Adam.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr beta1 beta2 epsilon : α) :
                                  Spec.Tensor α sState α s

                                  Initialize Adam with $m=0$, $v=0$, and $t=0$.

                                  Instances For
                                    def Optim.Adam.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                                    State α s × Spec.Tensor α s

                                    One Adam step (returns updated state and parameters).

                                    Equations (elementwise):

                                    • $m\gets\beta_1m+(1-\beta_1)g$,
                                    • $v\gets\beta_2v+(1-\beta_2)g^2$,
                                    • $\widehat m\gets m/(1-\beta_1^t)$,
                                    • $\widehat v\gets v/(1-\beta_2^t)$,
                                    • $p\gets p-\mathtt{lr}\,\widehat m/(\sqrt{\widehat v}+\varepsilon)$.

                                    The $\varepsilon$ placement matches Kingma and Ba: it is added after $\sqrt{\widehat v}$.

                                    Instances For
                                      @[simp]
                                      theorem Optim.Adam.update_t {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                                      (update state params grads).1.t = state.t + 1

                                      Adam increments its step counter by one on every update.

                                      AdamW #

                                      structure Optim.AdamW.State (α : Type) (s : Spec.Shape) :

                                      AdamW state (per parameter tensor).

                                      AdamW is “Adam + decoupled weight decay”. The key point is that weight decay is applied as a separate parameter decay term rather than being folded into the gradient that feeds the moments.

                                      • lr : α

                                        Learning rate.

                                      • beta1 : α

                                        First moment decay $\beta_1$.

                                      • beta2 : α

                                        Second moment decay $\beta_2$.

                                      • epsilon : α

                                        Numerical stability constant $\varepsilon$.

                                      • weight_decay : α

                                        Weight decay coefficient wd.

                                      • m : Spec.Tensor α s

                                        First moment EMA.

                                      • v : Spec.Tensor α s

                                        Second moment EMA.

                                      • t :

                                        Step counter (used for bias correction).

                                      Instances For
                                        def Optim.AdamW.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr weight_decay beta1 beta2 epsilon : α) :
                                        Spec.Tensor α sState α s

                                        Initialize AdamW state for a parameter tensor (moments start at 0).

                                        Instances For
                                          def Optim.AdamW.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                                          State α s × Spec.Tensor α s

                                          One AdamW step (returns updated state and parameters).

                                          We implement the decoupled form from the AdamW paper:

                                          • update Adam moments using the raw gradient g,
                                          • apply weight decay directly to the parameters (p ← p - lr * wd * p),
                                          • then apply the Adam update.

                                          This is the same single-step ordering used by torch.optim.AdamW.

                                          Instances For
                                            @[simp]
                                            theorem Optim.AdamW.update_t {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                                            (update state params grads).1.t = state.t + 1

                                            AdamW increments its step counter by one on every update.

                                            Adadelta #

                                            structure Optim.Adadelta.State (α : Type) (s : Spec.Shape) :

                                            Adadelta state (per parameter tensor).

                                            We store two EMAs:

                                            • v: EMA of squared gradients,
                                            • u: EMA of squared updates.
                                            • lr : α

                                              Learning rate (often set to 1 in some presentations; we keep it explicit).

                                            • rho : α

                                              Decay coefficient $\rho$.

                                            • epsilon : α

                                              Numerical stability constant $\varepsilon$.

                                            • v : Spec.Tensor α s

                                              EMA of squared gradients.

                                            • u : Spec.Tensor α s

                                              EMA of squared updates.

                                            Instances For
                                              def Optim.Adadelta.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr rho epsilon : α) :
                                              Spec.Tensor α sState α s

                                              Initialize Adadelta state for a parameter tensor (EMAs start at 0).

                                              Instances For
                                                def Optim.Adadelta.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                                                State α s × Spec.Tensor α s

                                                One Adadelta step (returns updated state and parameters).

                                                Elementwise equations:

                                                • $v\gets\rho v+(1-\rho)g^2$,
                                                • $\Delta p\gets-\mathtt{lr}\, \dfrac{\sqrt{u+\varepsilon}}{\sqrt{v+\varepsilon}}\odot g$,
                                                • $p\gets p+\Delta p$,
                                                • $u\gets\rho u+(1-\rho)(\Delta p)^2$.

                                                The $\varepsilon$ placement is inside the RMS terms, matching Zeiler's Adadelta update.

                                                Instances For

                                                  Projected / low-rank gradient transforms #

                                                  structure Optim.GaLore.Projector (α : Type) (full low : Spec.Shape) :

                                                  A shape-safe gradient projector.

                                                  GaLore-style training periodically builds a low-rank subspace for a large matrix parameter, projects the gradient into that subspace, runs a base optimizer there, and lifts the update back to the original parameter shape. This record is the algebraic interface; the expensive policy that computes or refreshes the projector belongs to the runtime layer.

                                                  Instances For

                                                    Identity projector, used when projected SGD is requested without a projection backend.

                                                    Instances For
                                                      structure Optim.GaLore.SGDState (α : Type) (full low : Spec.Shape) :

                                                      GaLore-style projected SGD state for one tensor.

                                                      This is not a full GaLore implementation by itself: it specifies the update once a projector is available. A practical trainer still needs a refresh schedule and a way to build projectors for large matrix parameters.

                                                      • lr : α

                                                        Learning rate used after the gradient has been projected and lifted.

                                                      • projector : Projector α full low

                                                        Current gradient projector.

                                                      Instances For
                                                        def Optim.GaLore.projectedSGDUpdate {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {full low : Spec.Shape} (state : SGDState α full low) (params grads : Spec.Tensor α full) :
                                                        Spec.Tensor α full

                                                        One projected-SGD update: p ← p - lr * lift(project(g)).

                                                        Instances For

                                                          Muon-style orthogonalized momentum #

                                                          Orthogonalization backend for a matrix-shaped update.

                                                          Muon uses a momentum buffer and then replaces the raw momentum direction by an approximately orthogonalized update, commonly via Newton-Schulz iterations. TorchLean keeps this as an explicit backend so the pure update rule is testable before CUDA kernels are introduced.

                                                          • apply : Spec.Tensor α sSpec.Tensor α s

                                                            Convert a momentum buffer into the direction used for the parameter update.

                                                          Instances For

                                                            The identity orthogonalizer, used when Muon is requested without a matrix backend.

                                                            Instances For
                                                              structure Optim.Muon.State (α : Type) (s : Spec.Shape) :

                                                              Per-parameter state for Muon-style momentum with an explicit orthogonalization backend.

                                                              • lr : α

                                                                Learning rate.

                                                              • momentum : α

                                                                Momentum coefficient.

                                                              • buf : Spec.Tensor α s

                                                                Momentum buffer.

                                                              • orthogonalizer : Orthogonalizer α s

                                                                Backend that turns the momentum buffer into the update direction.

                                                              Instances For
                                                                def Optim.Muon.init {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (lr momentum : α) (orthogonalizer : Orthogonalizer α s) :
                                                                Spec.Tensor α sState α s

                                                                Initialize Muon-style state with a zero momentum buffer.

                                                                Instances For
                                                                  def Optim.Muon.update {α : Type} [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {s : Spec.Shape} (state : State α s) (params grads : Spec.Tensor α s) :
                                                                  State α s × Spec.Tensor α s

                                                                  One Muon-style update:

                                                                  • update the momentum buffer,
                                                                  • orthogonalize the buffer,
                                                                  • subtract the scaled orthogonalized direction.

                                                                  For actual Muon, use a matrix-shaped s and a Newton-Schulz orthogonalizer. The generic shape here keeps the definition reusable for tests and for future batched matrix layouts.

                                                                  Instances For