Autograd OpSpecs (spec layer) #
This file defines small OpSpec building blocks (forward + VJP) for common tensor operations.
The definitions are intentionally direct mathematical contracts and live purely in the spec layer.
The declarations follow a uniform pattern:
- Each operation below is an
OpSpec: a pureforwardplus a pure VJPbackward. - Most ops here package
*Specand derivative-spec definitions fromNN/Spec/*.
Where this sits in TorchLean:
NN.Spec.*files define pure denotational semantics: what tensors/layers mean.- This file packages some of those pure definitions as unary
OpSpecs:forwardplus VJP. NN.Runtime.Autograd.*executes programs, tracks parameters, manages tapes/sessions, dispatches CUDA kernels, handles RNG, and lowers graphs for reusable typed execution.
This file adapts operations whose input-gradient VJP is naturally expressed as a single OpSpec.
Larger multi-input or parameterized layers (convolution, attention, batchnorm, pooling, RNG)
still have precise specs and runtime implementations, but their full backward state usually belongs
in layer/runtime code rather than in this compact unary interface.
PyTorch analogy (approximately):
- A spec
OpSpecis like a compacttorch.autograd.Functionwhere we write down the VJP directly. - We do not model PyTorch's mutable
ctx; the spec layer receives the input tensorxdirectly.
Elementwise lifting helpers #
Lift a scalar function to a tensor by pointwise map.
PyTorch analogy: most torch.* pointwise ops are vectorized elementwise maps.
Instances For
Lift an elementwise backward using the chain rule: $\frac{\partial L}{\partial x}=f'(x)\frac{\partial L}{\partial y}$ pointwise.
This is the standard VJP pattern for elementwise ops.
PyTorch analogy: the "local backward" rule for a pointwise op multiplies by the derivative mask.
Instances For
Elementwise ReLU OpSpec on any shape.
PyTorch analogy: torch.relu(x) / torch.nn.functional.relu(x).
Instances For
Elementwise sigmoid OpSpec on any shape.
PyTorch analogy: torch.sigmoid(x).
Instances For
Elementwise tanh OpSpec on any shape.
PyTorch analogy: torch.tanh(x).
Instances For
Elementwise softplus OpSpec on any shape.
PyTorch analogy: torch.nn.functional.softplus(x).
Instances For
Elementwise SiLU (also called Swish) OpSpec on any shape.
PyTorch analogy: torch.nn.functional.silu(x).
Instances For
Elementwise ELU OpSpec on any shape.
Instances For
Elementwise tanh-approximate GELU OpSpec on any shape.
PyTorch analogy: torch.nn.functional.gelu(x, approximate="tanh").
Instances For
Elementwise hyperbolic sine OpSpec.
Instances For
Elementwise hyperbolic cosine OpSpec.
Instances For
Softmax OpSpec along an explicitly selected tensor dimension.
PyTorch analogy: torch.softmax(x, dim=axis).
Instances For
Stable log-softmax OpSpec along an explicitly selected tensor dimension.
Backward recomputes the forward output so the VJP uses the same axis-parametric semantics. Runtime engines may cache that output instead.
Instances For
Linear layers #
Linear layer as an OpSpec: $y=Wx+b$.
This OpSpec only returns the input gradient $\partial L/\partial x$. Parameter gradients for
$W$ and $b$
are not part of OpSpec (those live at the graph/runtime level).
PyTorch analogy: torch.nn.functional.linear forward, with autograd producing gradients for
x, W, and b.
Instances For
Generic elementwise binary OpSpec with captured right-hand tensor and d/dx.
This is a "closure style" op: we treat the RHS tensor as a captured constant and only return the VJP with respect to the LHS input.
PyTorch analogy: in a tape/graph, rhs is typically another node; here we are writing the
"lhs-only" derivative for convenience.
Instances For
Scale by constant scalar.
PyTorch analogy: x * c where c is a scalar constant.
Instances For
Unary elementwise ops #
Negation (-x).
Instances For
Absolute value (uses signSpec for the subgradient).
PyTorch analogy: torch.abs(x). At $x=0$ we pick the subgradient $0$.
Instances For
Smooth absolute value (a differentiable surrogate for abs).
This is useful when you want to avoid a kink at 0 in optimization.
PyTorch analogy: there is no single canonical smoothAbs, but it is similar in spirit to
$\sqrt{x^2+\varepsilon}$-style smoothings.
Instances For
Elementwise exp.
PyTorch analogy: torch.exp(x).
Instances For
Elementwise natural logarithm.
Domain discipline: this is the raw mathematical/PyTorch-style rule. The VJP multiplies by 1/x,
so callers should use it only when the input is strictly positive. Runtime backends are allowed to
reject nonpositive inputs rather than silently manufacture a gradient. Use safeLogOp when the
intended model is the smooth surrogate $\log(\operatorname{softplus}(x)+\varepsilon)$.
PyTorch analogy: torch.log(x).
Instances For
Elementwise smooth logarithm surrogate, $\log(\operatorname{softplus}(x)+\varepsilon)$.
For $\varepsilon>0$ this is defined on every real input. Its VJP multiplies by $\operatorname{sigmoid}(x)/(\operatorname{softplus}(x)+\varepsilon)$.
PyTorch expression: torch.log(torch.nn.functional.softplus(x) + eps).
Instances For
Elementwise square root.
Domain discipline: TorchLean's spec-level sqrtSpec is total by clamping the forward value on
nonpositive inputs. The VJP follows that convention and returns zero where $x\le0$, rather than
introducing an artificial $1/\varepsilon$ spike.
PyTorch analogy: torch.sqrt(x) on the positive region, with an explicit TorchLean subgradient
choice outside the classical domain.
Instances For
Elementwise square, $x^2$.
Instances For
Elementwise power with a captured RHS exponent tensor.
This is the VJP with respect to the base $x$ for $x^{\mathtt{rhs}}$. Domain restrictions are the usual ones for the scalar backend's power operation.
Instances For
Elementwise reciprocal, $1/x$.
Domain discipline: this is the raw reciprocal. Its VJP is $-1/x^2$, so callers should use it only
when zero is excluded by the surrounding invariant. Use safeInvOp when the intended model is
$1/(x+\varepsilon)$.
PyTorch analogy: torch.reciprocal(x) or 1 / x.
Instances For
Elementwise epsilon-shifted reciprocal, $1/(x+\varepsilon)$.
This is the safe API counterpart to invOp: the forward pass delegates to safedivSpec with
unit numerator, and the VJP is the derivative of the same shifted expression.
PyTorch analogy: usually written manually as 1.0 / (x + eps).
Instances For
Binary ops capturing a right-hand tensor #
Add a captured RHS tensor, $x+\mathtt{rhs}$.
Instances For
Subtract a captured RHS tensor, $x-\mathtt{rhs}$.
Instances For
Elementwise multiply by a captured RHS tensor.
Instances For
Elementwise divide by a captured RHS tensor.
Domain discipline: this is the raw division rule. The VJP multiplies by 1/rhs, so callers should
only use it when the captured denominator is known nonzero. Use safeDivOp when the
intended model is x/(rhs+ε).
Instances For
Elementwise safe division by a captured RHS tensor, $x/(\mathtt{rhs}+\varepsilon)$.
PyTorch analogy: usually written manually as x / (rhs + eps).
Instances For
Elementwise minimum with a captured right-hand tensor.
The backward pass gives the input the full upstream gradient where it is strictly smaller than
rhs, zero where it is strictly larger, and half at a tie. This is the same selected gradient as
the two-input tape operation. Capturing rhs removes its gradient output; it does not transfer
its half of a tied gradient to the remaining input.
Away from ties this is the usual derivative. At a tie, minimum is not differentiable, and the half-gradient is the convention used by the runtime.
Instances For
Elementwise maximum with a captured right-hand tensor.
The backward pass gives the input the full upstream gradient where it is strictly larger than
rhs, zero where it is strictly smaller, and half at a tie. As in minOp, the captured tensor
keeps its share of the selected gradient even though this operation returns only the gradient
with respect to the input.
The strict comparisons and their order match the two-input tape operation, including its fallback when neither comparison holds.
Instances For
Leaky ReLU with slope parameter.
PyTorch analogy: torch.nn.functional.leaky_relu(x, negative_slope=alpha_l).
Instances For
Clamp OpSpec with a fixed interval.
We choose the standard subgradient 1 strictly inside the interval and 0 at/outside the
boundaries, matching clampDerivativeSpec.
Instances For
Loss OpSpecs #
MSE loss (returns a scalar), capturing the target.
Instances For
MAE loss (returns a scalar), capturing the target.
Instances For
Huber loss (returns a scalar), capturing the target.
Instances For
Cross-entropy loss (returns a scalar), capturing the target distribution.
This is "cross-entropy between distributions": target is $p$, yhat is $q$.
PyTorch analogy: closer to -(p * log(q)).mean() than to the logits-based
torch.nn.functional.cross_entropy default.
Instances For
Logits-based cross-entropy loss, capturing the target distribution.
Instances For
Binary cross-entropy loss on probability tensors, capturing the target tensor.
Instances For
Cosine-similarity loss, capturing the target tensor.
Instances For
Hinge loss (returns a scalar), capturing the target.
Instances For
Poisson loss (returns a scalar), capturing the target.
Instances For
Log-cosh loss (returns a scalar), capturing the target.
Instances For
Shape/structure ops #
Reshape op (requires a size-equality proof).
PyTorch analogy: x.reshape(...) (or view), but here the shape relationship is explicit.
Instances For
Swap adjacent axes at an arbitrary depth.
Instances For
Fill a tensor with a constant (ignores input).
PyTorch analogy: torch.full_like(x, value) (but here we keep the input only to fit the OpSpec
shape, and ignore its content).
Instances For
Replicate a scalar to any shape; backward sums gradients back to a scalar.
PyTorch analogy: broadcasting a scalar in arithmetic, and in backward accumulating by sum.
Instances For
Apply boolean mask: keep where mask true, else set 0.
PyTorch analogy: torch.where(mask, x, 0).
Instances For
Evaluation-mode dropout, which is the identity in both the forward and backward maps.
Instances For
Masked inverted-dropout OpSpec with an explicit mask.
Instances For
Matrix-rank multiplication with a captured right operand and broadcasted batch prefixes.
Instances For
Matrix-rank multiplication with a captured left operand and broadcasted batch prefixes.
Instances For
One-hot embedding as an OpSpec over the one-hot input. Parameter gradients stay outside
OpSpec; this wrapper returns only dOneHot.
Instances For
Slice a contiguous range along an arbitrary tensor axis.
Instances For
Reductions and broadcasting #
Reduce-sum along axis using a NonemptyAxis proof; backward broadcasts back.
PyTorch analogy: torch.sum(x, dim=axis) (with keepdim=false).
Instances For
Generic broadcasting-aware binary OpSpec.
The caller supplies:
- explicit broadcast proofs (
CanBroadcastTo) for both sides, and - a
reduceBackmap that takes a gradient in the broadcasted shapetand reduces it back to the left shapes1.
PyTorch analogy: this is where PyTorch's implicit broadcasting rules and reduction-of-broadcasted gradients ("sum over broadcasted dimensions") happen. In TorchLean we keep those shape relations explicit.
Instances For
Convenience: broadcasting-aware add with caller-provided reduction.
Instances For
Convenience: broadcasting-aware mul with caller-provided reduction.