TorchLean API

NN.Spec.Models.Transformer

Transformer (spec model) #

This file defines Transformer-style spec components in a way that matches the usual PyTorch mental model:

Sinusoidal positional encodings live in NN.Spec.Layers.PositionalEncoding as Spec.sinusoidalPositionalEncodingSpec; causal masks are Spec.causalMask in NN.Spec.Layers.Attention.

Shapes follow the common convention:

PyTorch analogy:

References:

PyTorch docs (for API shape intuition, not semantics):

Configuration helpers #

This file mostly defines reusable transformer building blocks (encoder/decoder layers, attention, layer-norm wrappers, etc.). To make compact model instantiations easier, we also provide a small config record for the common hyperparameters together with a couple of canonical configs (Base/Big).

The core definitions below still expose the hyperparameters as Nat parameters. The config layer is only a named packaging of those parameters, so the mathematical specification remains the parameterized transformer definition.

Common transformer layer hyperparameters.

  • headCount :

    Number of attention heads.

  • embedDim :

    Embedding dimension (d_model).

  • hiddenDim :

    Feedforward hidden dimension (d_ff).

Instances For

    Stack hyperparameters for an encoder/decoder: common layer config plus a layer count.

    Instances For

      Well-formedness conditions for TransformerLayerConfig.

      The divisibility condition keeps the per-head width exact: embedDim / headCount should partition the model dimension without silently dropping a tail through Nat floor division.

      Instances For

        Well-formedness conditions for TransformerStackConfig.

        • layer : config.WF
        Instances For

          Canonical Transformer "base" hyperparameters (Vaswani et al. 2017).

          Instances For

            Canonical Transformer "big" hyperparameters (Vaswani et al. 2017).

            Instances For

              Gradient containers #

              To keep the backward pass readable (and easy to reuse from downstream models like ViT/Seq2Seq), we bundle parameter gradients into records that mirror the parameter records.

              structure Spec.FeedForwardGrads (embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] :

              Gradients for a FeedForward block (field-for-field).

              This container is used by downstream models that want a readable backward pass.

              Instances For
                structure Spec.TransformerEncoderLayerGrads (headCount embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] :

                Gradients for a TransformerEncoderLayer (field-for-field).

                This container is intended to keep the backward pass readable by mirroring the parameter layout.

                Instances For
                  structure Spec.FeedForward (embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] :

                  2-layer position-wise feedforward network used inside Transformer layers.

                  Semantics (per token): ffn(x) = (relu(x * W1 + b1) * W2) + b2.

                  PyTorch analogue: the linear1 / linear2 submodule in torch.nn.TransformerEncoderLayer.

                  Instances For
                    def Spec.FeedForward.forward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {embedDim hiddenDim seqLen : } (ffn : FeedForward embedDim hiddenDim α) (x : TorchLean.Tensor α [seqLen, embedDim]) :
                    TorchLean.Tensor α [seqLen, embedDim]

                    Forward pass for FeedForward.

                    Shape convention: inputs and outputs are (seqLen × embedDim); the feedforward operates independently on each sequence position.

                    Instances For
                      structure Spec.TransformerEncoderLayer (headCount embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] :

                      Transformer encoder layer (post-norm).

                      This follows the common "Add & Norm" structure:

                      1. Self-attention, residual add, LayerNorm
                      2. Feedforward, residual add, LayerNorm

                      PyTorch analogue: torch.nn.TransformerEncoderLayer with norm_first=False (post-norm), ignoring dropout and other configuration knobs.

                      Instances For
                        def Spec.TransformerEncoderLayer.forward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {headCount embedDim hiddenDim seqLen : } (layer : TransformerEncoderLayer headCount embedDim hiddenDim α) (x : TorchLean.Tensor α [seqLen, embedDim]) (h1 : seqLen > 0) (h2 : embedDim > 0) :
                        TorchLean.Tensor α [seqLen, embedDim]

                        Forward pass for a post-norm TransformerEncoderLayer.

                        Input/output shape: (seqLen × embedDim). The proofs h1/h2 are used by layerNorm to justify nondegenerate normalization.

                        Instances For
                          structure Spec.TransformerEncoder (numLayers headCount embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] :

                          Transformer encoder: a stack of TransformerEncoderLayers.

                          PyTorch analogue: torch.nn.TransformerEncoder (a list of layers composed sequentially).

                          Instances For
                            def Spec.TransformerEncoder.forward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {numLayers headCount embedDim hiddenDim seqLen : } (encoder : TransformerEncoder numLayers headCount embedDim hiddenDim α) (x : TorchLean.Tensor α [seqLen, embedDim]) (h1 : seqLen > 0) (h2 : embedDim > 0) :
                            TorchLean.Tensor α [seqLen, embedDim]

                            Forward pass for TransformerEncoder (left-fold over layers).

                            Input/output shape: (seqLen × embedDim).

                            Instances For

                              Decoder notes #

                              We include a small Transformer-style decoder layer for completeness:

                              PyTorch analogy: this corresponds to the core of torch.nn.TransformerDecoderLayer (ignoring dropout and a few configuration knobs).

                              Cross-attention helper #

                              The attention layer provides MultiHeadAttention.forward for the common self-attention case (Q=K=V=x). A decoder block also needs cross-attention, where Q comes from the decoder stream and K,V come from the encoder stream.

                              We keep the helper here small and explicit by following the same structure as the self-attention definition: project, split into heads, run scaled dot-product attention per head, combine heads, then project with Wo.

                              def Spec.multiHeadCrossAttention {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {headCount embedDim nQ nK : } (hQ : nQ 0) (hK : nK 0) (mha : MultiHeadAttention α headCount embedDim (embedDim / headCount)) (qInput : TorchLean.Tensor α [nQ, embedDim]) (kvInput : TorchLean.Tensor α [nK, embedDim]) (mask : Option (TorchLean.Tensor Bool [nQ, nK])) :
                              TorchLean.Tensor α [nQ, embedDim]

                              Cross-attention forward pass using a MultiHeadAttention parameter record.

                              This is the decoder-specific variant of MultiHeadAttention.forward:

                              • queries come from qInput (decoder stream),
                              • keys/values come from kvInput (encoder stream),
                              • an optional boolean mask of shape (nQ × nK) can be applied.

                              Shape conventions:

                              • qInput : (nQ × embedDim),
                              • kvInput : (nK × embedDim),
                              • output : (nQ × embedDim).

                              PyTorch analogue: the cross-attention inside torch.nn.TransformerDecoderLayer, typically implemented via torch.nn.MultiheadAttention with separate query and key/value inputs.

                              Instances For
                                structure Spec.TransformerDecoderLayer (headCount embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] :

                                Transformer decoder layer (post-norm).

                                This mirrors the standard structure:

                                1. Self-attention (decoder stream), residual add, LayerNorm
                                2. Cross-attention (queries from decoder, keys/values from encoder), residual add, LayerNorm
                                3. Feedforward, residual add, LayerNorm

                                PyTorch analogue: torch.nn.TransformerDecoderLayer with norm_first=False (post-norm), ignoring dropout and a few configuration knobs.

                                • selfAttn : MultiHeadAttention α headCount embedDim (embedDim / headCount)

                                  Self-attention block over the decoder sequence.

                                • crossAttn : MultiHeadAttention α headCount embedDim (embedDim / headCount)

                                  Cross-attention block (decoder queries, encoder keys/values).

                                • ffn : FeedForward embedDim hiddenDim α

                                  Position-wise feedforward block.

                                • norm1Scale : TorchLean.Tensor α [embedDim]

                                  Scale of the layer normalization after self-attention.

                                • norm1Bias : TorchLean.Tensor α [embedDim]

                                  Bias of the layer normalization after self-attention.

                                • norm2Scale : TorchLean.Tensor α [embedDim]

                                  Scale of the layer normalization after cross-attention.

                                • norm2Bias : TorchLean.Tensor α [embedDim]

                                  Bias of the layer normalization after cross-attention.

                                • norm3Scale : TorchLean.Tensor α [embedDim]

                                  Scale of the layer normalization after the feed-forward block.

                                • norm3Bias : TorchLean.Tensor α [embedDim]

                                  Bias of the layer normalization after the feed-forward block.

                                Instances For
                                  def Spec.TransformerDecoderLayer.forward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {headCount embedDim hiddenDim seqLen : } (layer : TransformerDecoderLayer headCount embedDim hiddenDim α) (x encoderOutput : TorchLean.Tensor α [seqLen, embedDim]) (h1 : seqLen > 0) (h2 : embedDim > 0) (targetMask memoryMask : Option (TorchLean.Tensor Bool [seqLen, seqLen]) := none) :
                                  TorchLean.Tensor α [seqLen, embedDim]

                                  Forward pass for a post-norm TransformerDecoderLayer.

                                  Input/output shape: (seqLen × embedDim). This spec uses the same seqLen for encoder and decoder streams for simplicity (cross-attention uses nQ = nK = seqLen).

                                  targetMask controls decoder self-attention, and memoryMask controls attention to the encoder output. In both masks, true allows an attention edge and false blocks it. Pass some (causalMask seqLen) as targetMask for autoregressive decoding: position i then uses only target positions 0, ..., i. The source sequence remains available through cross-attention unless memoryMask restricts it separately. Both arguments default to none for unmasked attention.

                                  Instances For
                                    structure Spec.TransformerDecoder (numLayers headCount embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] :

                                    Transformer decoder: a stack of TransformerDecoderLayers.

                                    PyTorch analogue: torch.nn.TransformerDecoder (a list of decoder layers composed sequentially).

                                    Instances For
                                      def Spec.TransformerDecoder.forward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {numLayers headCount embedDim hiddenDim seqLen : } (decoder : TransformerDecoder numLayers headCount embedDim hiddenDim α) (x encoderOutput : TorchLean.Tensor α [seqLen, embedDim]) (h1 : seqLen > 0) (h2 : embedDim > 0) (targetMask memoryMask : Option (TorchLean.Tensor Bool [seqLen, seqLen]) := none) :
                                      TorchLean.Tensor α [seqLen, embedDim]

                                      Forward pass for TransformerDecoder (left-fold over layers).

                                      Input/output shape: (seqLen × embedDim). Every layer receives the same target and memory masks. A causal target mask must reach the whole stack: unmasked attention in any layer would let future target positions enter the earlier residual streams.

                                      Instances For
                                        structure Spec.Transformer (numLayers headCount embedDim hiddenDim : ) (α : Type) [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] :

                                        End-to-end encoder-decoder Transformer (spec model).

                                        This is a seq2seq Transformer wrapper built out of the encoder and decoder stacks above. It models the core tensor algebra of torch.nn.Transformer while making the proof-relevant choices explicit:

                                        • embeddings are modeled as explicit linear projections,
                                        • sequence length is shared between source and target streams,
                                        • we omit dropout, caching, and most configuration knobs.

                                        Shape convention: all activations in this file use (seqLen × embedDim). In a full implementation, outputProjection would usually map to a vocabulary size; here it is kept as an embedDim -> embedDim projection to stay in the "core tensor algebra" setting.

                                        Instances For
                                          def Spec.Transformer.forward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {numLayers headCount embedDim hiddenDim seqLen : } (transformer : Transformer numLayers headCount embedDim hiddenDim α) (input target : TorchLean.Tensor α [seqLen, embedDim]) (h1 : seqLen > 0) (h2 : embedDim > 0) (targetMask memoryMask : Option (TorchLean.Tensor Bool [seqLen, seqLen]) := none) :
                                          TorchLean.Tensor α [seqLen, embedDim]

                                          Forward pass for Transformer.

                                          Runs:

                                          1. source embedding projection,
                                          2. encoder stack,
                                          3. target embedding projection,
                                          4. decoder stack (with cross-attention to the encoder output),
                                          5. output projection.

                                          All tensors in this simplified spec have shape (seqLen × embedDim). targetMask and memoryMask are passed to every decoder layer. For next-token prediction, supply shifted target inputs and targetMask := some (causalMask seqLen); the mask permits the current input token while blocking later inputs. Source encoding stays bidirectional.

                                          Instances For
                                            def Spec.FeedForward.backward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {embedDim hiddenDim seqLen : } (ffn : FeedForward embedDim hiddenDim α) (x outputGrad : TorchLean.Tensor α [seqLen, embedDim]) (h_seq : seqLen > 0) (_h_embed : embedDim > 0) :
                                            FeedForwardGrads embedDim hiddenDim α × TorchLean.Tensor α [seqLen, embedDim]

                                            Backward pass for FeedForward.forward.

                                            Given the input x and an upstream gradient outputGrad = dL/dy (w.r.t. the FFN output), returns:

                                            This is a spec-level backward that reconstructs the forward intermediates (pre-activations and ReLU mask) instead of relying on a mutable tape, similar to the math underlying PyTorch autograd.

                                            Instances For
                                              def Spec.TransformerEncoderLayer.backward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {headCount embedDim hiddenDim seqLen : } (layer : TransformerEncoderLayer headCount embedDim hiddenDim α) (x outputGrad : TorchLean.Tensor α [seqLen, embedDim]) (h1 : seqLen > 0) (h2 : embedDim > 0) :
                                              TransformerEncoderLayerGrads headCount embedDim hiddenDim α × TorchLean.Tensor α [seqLen, embedDim]

                                              Backward pass for TransformerEncoderLayer.forward.

                                              Inputs:

                                              • x: the layer input (seqLen × embedDim),
                                              • outputGrad: upstream gradient w.r.t. the layer output.

                                              Outputs:

                                              The implementation mirrors the forward pass structure (residuals + LayerNorm) and uses layerNormBackward and multiHeadAttentionBackward as its core primitives.

                                              Instances For

                                                Backward pass for an encoder stack #

                                                The encoder is a fixed-length vector of layers applied sequentially. To compute gradients we:

                                                1. re-run the forward pass to collect each layer's input (a small "cache"),
                                                2. traverse layers in reverse, applying TransformerEncoderLayer.backward,
                                                3. return per-layer parameter gradients plus the gradient w.r.t. the encoder input.

                                                This is purely a spec (no mutation, no state), so we do the simplest thing: recompute.

                                                def Spec.TransformerEncoder.backward {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {numLayers headCount embedDim hiddenDim seqLen : } (encoder : TransformerEncoder numLayers headCount embedDim hiddenDim α) (x outputGrad : TorchLean.Tensor α [seqLen, embedDim]) (h1 : seqLen > 0) (h2 : embedDim > 0) :
                                                TorchLean.Tensor (TransformerEncoderLayerGrads headCount embedDim hiddenDim α) [numLayers] × TorchLean.Tensor α [seqLen, embedDim]

                                                Backward pass for TransformerEncoder.forward (a sequential stack of layers).

                                                Returns:

                                                • one parameter-gradient record per layer, in the same order as encoder.layers,
                                                • the gradient w.r.t. the encoder input x.

                                                Because this is a pure spec, we recompute forward intermediates (each layer input) instead of storing a mutable cache.

                                                Instances For
                                                  def Spec.maskedMultiHeadAttention {α : Type} [TorchLean.Storage α] [Context α] [DecidableRel fun (x1 x2 : α) => x1 > x2] {headCount embedDim seqLen : } (mha : MultiHeadAttention α headCount embedDim (embedDim / headCount)) (x : TorchLean.Tensor α [seqLen, embedDim]) (mask : Option (TorchLean.Tensor Bool [seqLen, seqLen])) (h1 : seqLen > 0) :
                                                  TorchLean.Tensor α [seqLen, embedDim]

                                                  Multi-head self-attention with an optional boolean mask.

                                                  This helper prepares the proof obligations required by MultiHeadAttention.forward:

                                                  • derives the required seqLen ≠ 0 proof from h1 : seqLen > 0,
                                                  • forwards the provided mask (typically a causal mask for autoregressive decoding).

                                                  PyTorch analogue: masked self-attention in torch.nn.TransformerDecoderLayer implemented via torch.nn.MultiheadAttention(..., attn_mask=...).

                                                  Instances For