Activation Specifications #
Scalar activation functions and their chosen derivatives live in Activation.Math. Tensor
operations map those definitions pointwise, except for shape-dependent operations such as softmax
and log-softmax. The definitions are polymorphic over the scalar Context, allowing the same layer
specification to be interpreted over runtime floats, exact scalars, or verification domains.
The formulas and conventions follow these references:
- PyTorch activations: https://pytorch.org/docs/stable/nn.functional.html
- PyTorch
torch.softmax: https://pytorch.org/docs/stable/generated/torch.softmax.html - ReLU: Vinod Nair and Geoffrey Hinton, "Rectified Linear Units Improve Restricted Boltzmann Machines" (ICML 2010)
- ELU: Djork-Arne Clevert et al., "Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)" (ICLR 2016)
- GELU: Dan Hendrycks and Kevin Gimpel, "Gaussian Error Linear Units (GELUs)" (arXiv:1606.08415)
- Swish / SiLU: Prajit Ramachandran et al., "Searching for Activation Functions" (arXiv:1710.05941)
Activation functions with a parameter-free pointwise interpretation.
This type is shared by model specifications and public model builders. Keeping the choice in the specification layer prevents configuration strings from silently selecting the wrong semantics.
- relu : Kind
Rectified linear unit,
max(0, x). - gelu : Kind
GELU in its tanh approximation (
Math.geluSpec). This is PyTorch'snn.GELU(approximate='tanh'), not the default erf-basednn.GELU(). - silu : Kind
SiLU/Swish,
x * sigmoid(x). - tanh : Kind
Hyperbolic tangent.
- sigmoid : Kind
Logistic sigmoid.
Instances For
Explicit spelling of the existing tanh GELU activation; .gelu remains compatible.
Instances For
Scalar activations #
ReLU: $\operatorname{ReLU}(x)=\max(x,0)$.
PyTorch analogy: torch.nn.functional.relu.
This is the simplest nonlinearity we use throughout TorchLean because it stays meaningful across
many scalar backends (including ones that do not support exp/log).
At zero we return the scalar zero directly. This matters for dual numbers: their equality compares
primals, so this branch clears every tangent at the kink, matching reluDerivSpec. Elementwise
maximum has a different convention and splits a tie equally. Every other input retains the
backend's max result, including a floating-point NaN, which does not compare equal to zero.
Instances For
On an ordered scalar with ordinary equality, ReLU is the usual maximum with zero.
A standard subgradient choice for ReLU:
$\frac{d}{dx}\operatorname{ReLU}(x)=1$ if $x>0$, and $0$ otherwise.
PyTorch analogy: autograd picks a subgradient at $x=0$; our spec commits to a concrete one to make "the derivative" a pure function.
The DecidableRel (· > ·) constraint reflects that this definition branches on $x>0$.
Instances For
Logistic sigmoid, evaluated with a nonpositive exponential argument:
$\operatorname{sigmoid}(x)=1/(1+\exp(-x))$.
For positive inputs we use this expression directly. For zero and negative inputs we use the
equivalent ratio $\exp(x)/(1+\exp(x))$. Both denominators lie between 1 and 2 over the reals.
In floating-point arithmetic this avoids forming exp(-x) when x is a large negative number.
The same branch also matters for dual numbers: differentiating a quotient with an infinite
denominator can produce NaN, even when the sigmoid value has rounded to zero.
PyTorch analogy: torch.nn.functional.sigmoid (or torch.sigmoid).
Instances For
Derivative of sigmoid:
$\operatorname{sigmoid}'(x)=\sigma(x)(1-\sigma(x))$.
The sigmoid value uses the stable branch above, so negative tails retain their small positive derivatives until the exponential itself underflows. On the positive side, the derivative becomes zero once the sigmoid value rounds to one; this is the output-based derivative used by the graph VJP as well.
Instances For
Hyperbolic tangent: tanh(x). PyTorch analogy: torch.tanh.
Instances For
Derivative of tanh:
$\tanh'(x)=1-\tanh^2(x)$.
Instances For
Leaky ReLU:
$\operatorname{leaky\_relu}(x;\alpha)=x$ if $x>0$, else $\alpha x$.
PyTorch analogy: torch.nn.functional.leaky_relu with negative_slope = α.
Instances For
Derivative of leaky ReLU:
$\frac{d}{dx}\operatorname{leaky\_relu}(x;\alpha)=1$ if $x>0$, else $\alpha$.
Instances For
Sinh derivative: cosh(x).
Instances For
Cosh derivative: sinh(x).
Instances For
Logistic form written as $\exp(x)/(\exp(x)+1)$.
This is mathematically the same sigmoid function as sigmoidSpec; we keep it as logisticSpec
because several scalar approximation proofs reason about this exp(x) numerator form directly.
Important naming choice: this is not called scalar softmax. A one-entry softmax is always 1;
the real softmax API in TorchLean is the axis-parametric tensor operation Activation.softmaxSpec.
Instances For
Derivative of logisticSpec, expressed in output form.
Instances For
ELU (Exponential Linear Unit):
$\operatorname{ELU}(x;\alpha)=x$ if $x>0$, else $\alpha(\exp(x)-1)$.
PyTorch analogy: torch.nn.functional.elu with alpha = α.
Instances For
Derivative of ELU:
$\operatorname{ELU}'(x;\alpha)=1$ if $x>0$, else $\alpha\exp(x)$.
Instances For
The rational coefficient 44715 / 1000000 in the standard tanh approximation to GELU.
Instances For
GELU (approximate): the common tanh-based approximation used in many Transformer codebases.
PyTorch analogy: torch.nn.functional.gelu(x, approximate="tanh"). This is not PyTorch's default
nn.GELU(), which uses the exact erf form; TorchLean has no erf primitive, so only the tanh
approximation is specified.
Instances For
GELU derivative for the tanh-based approximation.
Instances For
Swish / SiLU:
$\operatorname{swish}(x)=x\operatorname{sigmoid}(x)$.
PyTorch analogy: torch.nn.functional.silu.
Instances For
Derivative of Swish / SiLU.
Written in terms of sigmoid(x) for the same reason as sigmoidDerivSpec: this is the form
used by AD systems and is convenient to reuse in proofs.
Instances For
Softplus, evaluated without a large positive exponential:
$\operatorname{softplus}(x)=\log(1+\exp(x))$.
The positive branch uses the equivalent expression $x+\log(1+\exp(-x))$; this keeps finite
floating-point inputs finite when exp(x) itself would overflow. The operation remains the
one-argument, unit-scale softplus used throughout TorchLean.
PyTorch analogy: torch.nn.functional.softplus.
Instances For
Derivative of softplus:
$\operatorname{softplus}'(x)=\operatorname{sigmoid}(x)$.
Using the same stable sigmoid keeps the derivative finite in both tails. Evaluating this formula over dual scalars also propagates the softplus second derivative without a large exponential.
Instances For
A smooth log surrogate:
$\operatorname{safe\_log}(x;\varepsilon) =\log(\operatorname{softplus}(x)+\varepsilon)$.
We use this when we want something "log-like" without having to carry side conditions about the input being strictly positive.
Instances For
Derivative of safeLogSpec.
Instances For
A smooth absolute value surrogate:
$\operatorname{smooth\_abs}(x;\varepsilon)=\sqrt{x^2+\varepsilon}$.
Useful when you want an abs-like shape but keep differentiability at 0.
Instances For
Derivative of smoothAbsSpec.
Instances For
Tensor-level tanh (pointwise).
PyTorch analogy: torch.tanh(t) or torch.nn.functional.tanh(t) applied elementwise.
Instances For
Tensor-level ReLU (pointwise).
Instances For
Tensor-level sigmoid (pointwise).
Instances For
Tensor-level ReLU derivative (pointwise), using the scalar subgradient choice in
Activation.Math.reluDerivSpec.
Instances For
Tensor-level sigmoid derivative (pointwise).
Instances For
Derivative of sigmoid when the sigmoid output has already been computed.
Recurrent layers save gate activations during the forward pass, so their backward specs should use
this shared helper instead of re-defining s * (1 - s) locally.
Instances For
Tensor-level tanh derivative (pointwise).
Instances For
Apply a parameter-free pointwise activation to a tensor.
Instances For
Apply the derivative selected by a parameter-free pointwise activation.
Instances For
Softmax on tensors #
These are the shape‑aware softmax definitions used in attention / classification layers. They recurse over outer dimensions and apply a numerically‑stable softmax to the last axis.
Maximum entry of a nonempty vector, returned as a scalar tensor.
The fold is seeded by the first coordinate rather than by a numeric sentinel. Consequently the result is one of the input coordinates for every linearly ordered scalar type. Softmax and log-softmax share this definition so their range-reduction convention cannot drift apart.
Instances For
Max-shifted exponentials shared by stable softmax and log-softmax.
Instances For
Softmax on a length-n vector.
This is the "real" softmax, not the scalar logistic helper in Activation.Math.logisticSpec.
Numerical stability:
We implement the standard stabilized form $\operatorname{softmax}(x)_i=\exp(x_i-m)/\sum_j\exp(x_j-m)$, where $m=\max_i x_i$. Subtracting the max avoids overflow in typical floating-point backends, and it is also a nice canonical form to reference in proofs.
Instances For
Softmax along the last axis (recurses over outer dimensions).
PyTorch analogy: torch.softmax(x, dim=-1).
For s = .scalar we return 1 (there is only one coordinate). For higher-rank tensors we keep
the outer structure and apply softmaxVecSpec at the last axis.
Instances For
Last-axis softmax on a matrix acts independently on each row.
Backward/VJP for last-axis softmax.
If $y=\operatorname{softmax}(x)$ and we are given an upstream gradient $\partial L/\partial y$, then for each last-axis slice:
$$ \frac{\partial L}{\partial x} =y\odot\left( \frac{\partial L}{\partial y} -\left\langle\frac{\partial L}{\partial y},y\right\rangle \right). $$
This is the standard Jacobian-vector product for softmax, written in a way that avoids materializing
the full n×n Jacobian.
Instances For
Numerically stable softmax along any tensor dimension.
The selected dimension is moved to the innermost position, where a private kernel computes each one-dimensional slice, and is then restored. This definition covers outer and interior dimensions without imposing a memory-layout convention on the mathematical tensor.
Instances For
Backward/VJP for softmax along any tensor dimension.
Instances For
Log-softmax on a length-n vector.
Instances For
Log-softmax along the last axis (recurses over outer dimensions).
Instances For
Forward-mode JVP for last-axis log-softmax.
If $y=\operatorname{logsoftmax}(x)$, then each last-axis slice has directional derivative
$dy=dx-\operatorname{replicate}(\langle\exp(y),dx\rangle)$.
Unlike the VJP below, the subtracted scalar is replicated uniformly across the slice; the
softmax probabilities occur only inside the dot product. Taking the already-computed output y
also avoids recomputing the stable forward pass.
Instances For
Backward/VJP for last-axis log-softmax.
If $y=\operatorname{logsoftmax}(x)$, then $\operatorname{softmax}(x)=\exp(y)$ and the vector-Jacobian product is
$$ \frac{\partial L}{\partial x} =\frac{\partial L}{\partial y} -\operatorname{softmax}(x)\sum_i\frac{\partial L}{\partial y_i}. $$
This is the same formula used by PyTorch's stable log_softmax backward path. We take the
already-computed output y rather than the logits x, so runtime backends can avoid recomputing
the max-shifted forward pass during backprop.
Instances For
Numerically stable log-softmax along any tensor dimension.
Instances For
Forward-mode derivative of log-softmax along any tensor dimension.
Instances For
Backward/VJP for log-softmax along any tensor dimension.
Instances For
Tensor-level leaky ReLU (pointwise). PyTorch analogy: torch.nn.functional.leaky_relu.
Instances For
Tensor-level derivative of leaky ReLU (pointwise).
Instances For
Tensor-level ELU (pointwise). PyTorch analogy: torch.nn.functional.elu.
Instances For
Tensor-level derivative of ELU (pointwise).
Instances For
Tensor-level GELU (approximate, pointwise). PyTorch analogy: gelu(..., approximate="tanh").
Instances For
Tensor-level derivative of tanh-approx GELU (pointwise).
Instances For
Tensor-level Swish / SiLU (pointwise).
Instances For
Tensor-level derivative of Swish / SiLU (pointwise).
Instances For
Tensor-level softplus (pointwise).
Instances For
Tensor-level derivative of softplus (pointwise).
Instances For
Tensor-level safeLogSpec (pointwise).
Instances For
Tensor-level derivative of safeLogSpec (pointwise).
Instances For
Tensor-level smoothAbsSpec (pointwise).
Instances For
Tensor-level derivative of smoothAbsSpec (pointwise).
Instances For
A generic pointwise activation VJP helper.
Given:
f'(as a tensor-level derivative function),- the forward input
x, - and an upstream gradient $\partial L/\partial f(x)$,
this returns $\partial L/\partial x$ by the chain rule:
$\frac{\partial L}{\partial x} =\frac{\partial L}{\partial f(x)}\odot f'(x)$.
This matches how most PyTorch elementwise ops behave in backward: multiply upstream gradients by the pointwise derivative mask/value.