4.2. Registered Operators
PyTorch registers an operator once. native_functions.yaml declares relu with its schema,
derivatives.yaml gives its backward formula, and eager execution, autograd and torch.export all
refer to that one entry. TorchLean has the same arrangement in Lean. Here the inductive type
TorchLean.Op names every registered operator, and Op.rule? gives each one its rule: the forward
map, the pullback and the node a recorder builds. The CPU tape, the CUDA tape, the eager session,
the verifier's recorder, the graph IR, backend planning and the roundoff analysis all key on these
two definitions, so each operator is defined once for all of them.
The table lives in TorchLean/Tensor/Op/Table.lean, its
rules in TorchLean/Tensor/Op/Table/Rule.lean and the
equation lemmas of each operator family beside them. Every Lean block below is elaborated while
this page is built, and the counts in the text and the tables of the vocabulary are read off the
definitions in the same build.
One Constructor Per Operator
A constructor of TorchLean.Op names one operator together with its attributes. Its type lists
the operator's input ports and output ports:
-- An elementwise operator, a sort with an index output, a
-- selection with a Boolean condition, and a clamp whose
-- bounds are frozen scalars.
#check (Op.relu (s := [3]))
#check (Op.sort (s := [3]) ⟨0, ⊢ 0 < Shape.rank [3] All goals completed! 🐙⟩ false)
#check (Op.select (s := [2]))
#check (Op.clamp (s := [2]))
Shapes are attributes, so Op.relu (s := [3]) and Op.relu (s := [4]) are different operator
values, as two PyTorch calls with different sizes are different nodes of an exported graph. Here
the ascending sort along axis 0 returns the sorted values and, at its second output, an index
tensor whose entries are indices below the extent of that axis. select is PyTorch's
torch.where, with its condition at a Boolean port, and the ports of its data are those of its
element argument (described below). Both bounds of clamp are frozen scalar inputs. An operator
value doesn't know the scalar type, so a scalar attribute such as an epsilon, a slope or a clamp
bound travels as data, at a port that never receives a gradient.
The table has 154 constructors, one for each operator. Operators that share a shape rule
are separate constructors when they compute different functions: Op.max returns the IEEE 754-2019
maximum, which propagates a NaN, and Op.fmax the maximum that ignores a NaN. Likewise
Op.crossEntropy takes logits and Op.crossEntropyProbabilities takes probabilities. A mode of
one operation is an attribute. Op.div takes torch.div's rounding_mode, and Op.sqrt at
.raw (the default) is the IEEE square root, a NaN below zero, while at .clampNegative it's the
root of max x 0. Where neither operand of Op.max is larger (a NaN included), each receives half
of the gradient. PyTorch's maximum gives both the whole gradient at a NaN.
Ports And Element Sorts
A port (TorchLean.Port) is an element sort and a shape, and the element sorts are:
#print Port.Kind
A value port holds tensors of the model's scalar and receives a gradient when its parent needs
one. A frozen port holds the scalar without a gradient: an attribute, or a value the operator
reads without differentiating it, such as the operands of a comparison. A detached port reads a
recorded node without sending it a contribution, as PyTorch's detach does for the teacher of a
distillation loss. An index port holds index tuples below the listed bounds, a bool port
Booleans, and an integer signed width port words of width bits of an integer type, two's
complement when signed. Index, Boolean and integer ports let an operator take an index tensor
(gather, scatter), a mask (select) or integer words (the bitwise operators), or return one
(sort, topk, the comparisons, encodeIndex). A list of scalar tensor shapes can't say that.
For reshape, permute, broadcast, flip, slice, pad, cat, gather, scatter and
select (Op.Element), and for add, sub, mul, div, neg, pow, remainder, max,
min and the comparisons (Op.NumericElement), the element argument defaults to .scalar, the
model's scalar. At that default, Op.Element.port gives the scalar's port of the operand's role:
the data ports of select above are value ports. At .bool or .integer signed width the
operator moves Booleans or integer words as it moves scalars, or computes PyTorch's integer meaning
with the functions of TorchLean.Word (wrapping +, -, * and negation, the order of the type),
without gradients. An integer division or remainder fails its check at a zero divisor, an integer
true division (div .none) has no rule, and a Boolean scatter has a rule only as a replacement.
For a list of ports, the values are nested pairs ending in Unit, so the inputs of Op.mul are
(x, y, ()). Tangents and cotangents exist for value ports only: every other port has Unit
there. Elementwise operators take equal shapes, and broadcasting is the separate operator
broadcast: where PyTorch broadcasts the operands of add implicitly, the capture writes the
broadcast as its own node.
The Rule Of An Operator
Op.rule? op is the rule of op at a scalar α, an Option (Op.Rule α ins outs). A rule holds:
-
forward, from the values of the input ports to the values of the output ports -
backward, the dense pullback, a gradient for every value input -
prepare, the node a recorder builds once from the inputs and the runtime flags of the inputs that need a gradient, whose values and pullback share what the forward pass computed (the weights ofattentionFromScores, the output oflogSoftmax) -
validate, a check of the input values: the smooth max pool rejects a zero or nonfinitebeta, an integerdivwith a rounding mode and an integerremaindera zero divisor,encodeIndexa component its words cannot hold anddecodeIndexa word outside its bound -
the order in which a recorder emits the contributions, an alias rule for two inputs that name one node, and a forward-mode rule where one is registered
In every rule, the fields prepare_value and prepare_eq state that the prepared values are the
forward values and that the prepared pullback is the dense one at each active value input and
nothing at the others. A recorder calls Op.Rule.check, which runs validate and then prepare,
and never forward, so a value the pullback needs is computed once.
Each arm of Op.rule? is restated by an equation lemma named after its constructor, Op.rule?_<op>
(the two forms of cast have Op.rule?_cast_bool and Op.rule?_cast_index). So a proof about one
operator rewrites with its lemma instead of unfolding the whole table:
#check @Op.rule?_relu
A rule is an ordinary Lean value, and running its forward map is evaluation:
-- The forward map of ReLU at Float, on a vector with a
-- negative entry and a zero.
#eval
let x : Tensor Float [3] := [-1.5, 0, 2]
(Op.rule? (α := Float) (Op.relu (s := [3]))).map fun r =>
(r.forward (x, ())).1
Prepared Nodes And Runtime Flags
PyTorch's backward formulas receive needs_input_grad and can skip the products no input needs.
A TorchLean rule receives the same information through prepare. Here the product x * y is
prepared with only x needing a gradient, then pulled back along the cotangent [1, 1]:
-- Prepare the product with the flags (true, false) and pull
-- back one cotangent.
#eval (Op.rule? (α := Float) (Op.mul (s := [2]))).map
fun r =>
let x : Tensor Float [2] := [3, -1]
let y : Tensor Float [2] := [2, 5]
let g : Tensor Float [2] := [1, 1]
let p := r.prepare (x, y, ()) (true, false, ())
(p.value.1, p.pullback (g, ()))
The prepared value is the elementwise product, and the pullback sends g * y = [2, 5] to x and
none to y, whose flag is off. TorchLean.Tape.apply passes the parents' flags the same
way, so on the CPU tape only parents that need a gradient receive a contribution.
Scalar Capabilities
Some operators need an operation that not every scalar type has. Rounding gives the integral
roundings, the rounded divisions, fmod and remainder. Erf gives the error function (for erf
and the exact GELU), and InverseTrig the inverse trigonometric functions atan, asin, acos
and atan2. Cbrt gives the cube root, NextAfter C's nextafter and Bits the interchange word
of a binary format (for bitcast). Op.Capabilities α bundles these six capabilities, found by
instance synthesis. An operator that needs a missing capability has
no rule at that scalar:
-- Float has the integral roundings and the inverse
-- trigonometric functions, but no error function.
#eval ((Op.rule? (α := Float) (Op.floor (s := [2]))).isSome,
(Op.rule? (α := Float) (Op.atan2 (s := [2]))).isSome,
(Op.rule? (α := Float) (Op.erf (s := [2]))).isSome)
For erf at Float, the table returns none instead of substituting an approximation. Float
has Rounding and InverseTrig but no Erf, Cbrt, NextAfter or Bits, and the configured
binary formats of FloatLib have every capability but Erf. Op.need names the capability an
operator's rule reads, and Op.isSome_rule?_iff proves that an operator for which Op.hasRule
holds has a rule at a scalar exactly when the scalar supplies that capability. Op.needsCapability
names the operators whose rule reads a capability, and Op.rule?_capabilities proves that every
other rule is the same for all capability instances.
Checking And Invoking
Op.check checks the attributes that the types do not, such as the window of a pooling (one
parameter per pooled axis, positive kernel extents and strides) and distinct axes for reduce and
mean:
-- A max pool with a zero kernel extent, and a sum over a
-- repeated axis.
#eval (Op.pool (s := [1, 4]) .max [⟨1, ⊢ 1 < Shape.rank [1, 4] All goals completed! 🐙⟩]
[0] [1] [0] [0] [1] false).check
#eval (Op.reduce (s := [2, 3]) .sum
[⟨1, ⊢ 1 < Shape.rank [2, 3] All goals completed! 🐙⟩, ⟨1, ⊢ 1 < Shape.rank [2, 3] All goals completed! 🐙⟩] false
(⊢ Tensor.ReduceOp.sum = Tensor.ReduceOp.max ∨ Tensor.ReduceOp.sum = Tensor.ReduceOp.min →
∀ d ∈ [⟨1, ⋯⟩, ⟨1, ⋯⟩], 0 < Shape.extent [2, 3] d h:Tensor.ReduceOp.sum = Tensor.ReduceOp.max ∨ Tensor.ReduceOp.sum = Tensor.ReduceOp.min⊢ ∀ d ∈ [⟨1, ⋯⟩, ⟨1, ⋯⟩], 0 < Shape.extent [2, 3] d; h✝:Tensor.ReduceOp.sum = Tensor.ReduceOp.max⊢ ∀ d ∈ [⟨1, ⋯⟩, ⟨1, ⋯⟩], 0 < Shape.extent [2, 3] dh✝:Tensor.ReduceOp.sum = Tensor.ReduceOp.min⊢ ∀ d ∈ [⟨1, ⋯⟩, ⟨1, ⋯⟩], 0 < Shape.extent [2, 3] d h✝:Tensor.ReduceOp.sum = Tensor.ReduceOp.max⊢ ∀ d ∈ [⟨1, ⋯⟩, ⟨1, ⋯⟩], 0 < Shape.extent [2, 3] dh✝:Tensor.ReduceOp.sum = Tensor.ReduceOp.min⊢ ∀ d ∈ [⟨1, ⋯⟩, ⟨1, ⋯⟩], 0 < Shape.extent [2, 3] d All goals completed! 🐙)).check
Op.invoke runs one operator on erased values. An erased value, Op.Value α, is a port paired
with a tensor at that port. An invocation checks in stages: the attributes, each input against its
port, the rule's value-domain check, then the prepared node, whose values it returns. A sort
returns two values, the second at an index port, and an input at the wrong shape is refused before
any arithmetic runs:
-- One input vector, shown through a printer of erased
-- values.
def regInput : List (Op.Value Float) :=
[⟨.value [3], ([-1.5, 0, 2] : Tensor Float [3])⟩]
def regShow (r : Except String (List (Op.Value Float))) :
Except String (List String) :=
r.map fun vs => vs.map fun
| ⟨⟨.value, _⟩, x⟩ => s!"{x}"
| ⟨p, _⟩ => s!"a value at {repr p}"
#eval regShow
(Op.invoke (Op.relu (s := [3])) (.ok regInput))
#eval regShow
(Op.invoke (Op.sort (s := [3]) ⟨0, ⊢ 0 < Shape.rank [3] All goals completed! 🐙⟩ false)
(.ok regInput))
#eval regShow
(Op.invoke (Op.relu (s := [2])) (.ok regInput))
TorchLean.IR.Graph.run evaluates a straight-line list of such nodes forward with the same
checks. It's the forward half of a recorder, while the reverse sweep (with its gradient checks)
belongs to the tape.
Op.invoke_eq_ok states what a successful invocation returns: the rule's forward values on the
inputs read at their ports.
A custom node is the one constructor without an arm. Op.custom carries a name and the shapes of
its inputs and output, and the node's checked program lives in its graph's payload, not in the
operator value. Op.customRule builds the rule from that program: its forward map evaluates the
program, and its pullback is the one the caller supplies. That pullback is an executable policy,
with no claim that it's a derivative of the program.
Operator Certificates
A theorem about an operator's arm can be registered as a certificate of that operator with the
attribute @[op_cert Op.<operator> <kind>], followed by the input positions it concerns when the
others are held fixed. The kinds keep apart what the theorems state:
-
algebraic: the pullback is the adjoint of a selected tangent map, with no claim that the map is a derivative. -
derivative: the tangent map is the Fréchet derivative and the pullback its adjoint, on the domain and shapes the theorem states. -
convention: a rule fixed by an automatic-differentiation convention, such asdetachor a subgradient. -
prepared: a prepared pullback returns the dense one, beyond theprepare_eqfield. -
roundoff: a floating-point error bound of a route. -
enclosure: the interval transfer of the graph numerical certificate encloses the operator's real forward value.
On registration, the attribute checks that the declaration is a theorem whose statement mentions
the operator's arm of Op.rule?, and, when the operator's input list reduces to a literal list,
that every listed position is an input. It doesn't check that
the statement says what its kind says. #op_certs prints the registrations of one operator:
#op_certs Op.tanh
#op_certs Op.linear
#op_certs Op.cat
Of these, the linear certificate concerns input 2 (the layer's input), with the weight and bias
held fixed. Across the table, 14 algebraic, 64 derivative, 1 convention, 0 prepared, 0 roundoff and 7 enclosure certificates are registered. The gradient
certificates are in TorchLean/NN/Op and the enclosures
in the graph
numerical certificate. Autograd Proofs explains the adjoint laws and
Fréchet derivatives the gradient kinds name.
The Wire Codec
An operator value carries shapes, axes as Fin values and proofs, which a file cannot hold.
Op.encode writes an erased operator, Σ ins outs, Op ins outs, as a wire record Op.Wire: the
format version, the constructor's name as its tag, and its implicit shapes and attributes in binder
order, one Op.Field each:
#eval Op.encode
⟨_, _, Op.softmax (s := [2, 3]) ⟨1, ⊢ 1 < Shape.rank [2, 3] All goals completed! 🐙⟩⟩
Proof attributes are not sent. Op.decode checks every attribute and decides every proof
attribute, so a record whose axis is outside the rank names no operator:
-- Softmax of a rank-2 shape along axis 1, then axis 2.
#eval
let wire (axis : Nat) : Op.Wire :=
{ version := Op.Wire.formatVersion,
tag := "softmax"
fields := [.shape [2, 3], .nat axis] }
((Op.decode (wire 1)).isSome, (Op.decode (wire 2)).isSome)
Decoding inverts encoding for every constructor:
#check @Op.decode_encode
Equality of erased operators is decided through the records (Op.instDecidableEqErased), and the
ports are recomputed from the attributes, so two operators at different shapes have different
records. Op.decode doesn't run Op.check: a decoded pooling with an ill-formed window is an
operator value, which a recorder rejects before reading its input. A record names the operator and
its attributes, not what it computes at a scalar type, so an artifact whose results must be
reproduced also names the TorchLean revision that evaluates it.
One Table For Every Frontend
Each part of TorchLean that runs, records or plans an operation takes the operator itself:
Consumer | Through the table |
|---|---|
CPU tape |
|
CUDA tape |
|
Eager session |
|
Verifier recorder |
|
Graph IR |
|
Artifacts |
a |
Backend planning |
a capsule's |
Roundoff analysis |
|
Model code written against the Ops interface (TorchLean.Ops) reaches the table
through its instances: the eager instance records each operation with Session.apply, and the
recorder instance behind IR.lowerForward appends it with record. That recorder covers the
verifier fragment only. Most operators are one step of themselves, but scale, clamp, attention,
multi-head attention over leading axes and coordinate selection are recorded through several steps,
and conv and pool are taken only in the layouts and windows the recorder supports. The recorder
refuses random operators, GELU, training batch normalization and every operator that isn't
admissible (Op.Admissible: its attribute check passes, its rule checks no input value and reads
no optional capability, and it has a rule at every scalar). IR.OpGraph.eval_toOp proves that the
graph of registered operators of a typed graph returns the typed values at its outputs, for every
input. No theorem
relates the recorded graph to the source program.
Runtime and Autograd builds a CPU tape with Tape.apply by hand,
GPU and CUDA follows the CUDA entries, and Graph IR
imports a graph into operators and runs it.
The tables of the two tapes need not agree on which operators they cover:
-- The CUDA table has an entry for ReLU and none for
-- `Op.fmax`.
#eval
((TorchLean.LibTorch.Kernel.ofOp
(Op.relu (s := [2]))).isSome,
(TorchLean.LibTorch.Kernel.ofOp
(Op.fmax (s := [2]))).isSome)
At a scalar with the capabilities it needs, every operator for which Op.hasRule holds has a CPU
rule (Op.isSome_rule?_iff), but an eager CUDA session refuses an operator without an entry
(Session.cudaEntry). A CUDA entry implements its operator under a recorded contract, whose
evidence doesn't make the foreign implementation a proved refinement. So an entry need not return
the rule's values at every input: GPU and CUDA describes the contracts and
their evidence.
The Vocabulary
Each family below is a table with one row per operator, computed while this page is built from the definitions at the operator's constructor, with its element argument at the model's scalar where it has one:
-
Rule: the capability of the scalar that the rule reads (
Op.need), and the attributes at which the operator has no rule (Op.hasRule). -
CUDA: whether the LibTorch table has an entry (
TorchLean.LibTorch.Kernel.ofOp). -
Gradient: the kinds of the certificates registered for the pullback at each value input (
TorchLean.Op.certificates), and whether the rule has a forward mode (Op.Rule.jvp?), with the kinds of the certificates registered for it. -
Bounds: which bound propagations have an arm for the operator: the certificate checker (
TorchLean.CROWN.Op.cert?), interval propagation (TorchLean.CROWN.Op.ibp?) and the backward sweep of CROWN (TorchLean.CROWN.Op.backwardRule). The cell ends inenclosurewhen anenclosurecertificate is registered for the operator.
A cell says whether its property holds at every attribute, at some or at none, and a propagation
has an arm for an operator when its definition returns a result at some attributes and inputs. An
arm is a rule, not a theorem. We prove the checker's step against the real value semantics
TorchLean.CROWN.Op.evalNode?, and TorchLean.CROWN.Op.ibp?_encloses proves that the boxes of the
interval rule enclose the real values for the operators of Directed.Supported.
TorchLean.CROWN.Op.backwardRule_sound proves the rules of the CROWN sweep sound at a node whose
real equations hold. In a successful real run, TorchLean.CROWN.Op.outputBoxCROWN?_of_run proves
that the CROWN box of a graph output encloses its value, and TorchLean.CROWN.Op.runCROWN_encloses
proves that the affine bounds of full CROWN bound the values of their nodes. Without an arm of its
own, the backward sweep discharges the coefficient against the operator's box (constants and most
operators) or ends (gather, scatter, pad, flip, contract, logSoftmax, the cumulative and
sorting operators, and custom). ReLU has no arm because the forward pass relaxes it with α slopes
(alphaStep?).
Elementwise arithmetic, with PyTorch's rounding_mode on div, the cube root, C's nextafter
and the guarded forms safeDiv, safeInv, safeLog and smoothAbs:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes |
|
checker, IBP, CROWN; |
| every scalar | yes |
|
checker, IBP, CROWN; |
| every scalar | yes |
|
checker, IBP, CROWN; |
|
| at some attributes |
| IBP |
| every scalar | yes |
| IBP |
| every scalar | yes |
|
IBP; |
| every scalar | yes |
|
IBP; |
| every scalar | no |
| IBP |
|
| no | no certificate; forward mode | none |
| every scalar | yes |
| IBP |
| every scalar | no |
| IBP |
| every scalar | no | no certificate; forward mode | none |
| every scalar | yes |
| IBP |
|
| no | no certificate; forward mode | IBP |
|
| no | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | IBP |
|
| no | no value input; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no |
| none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no |
| none |
| every scalar | no |
| none |
| every scalar | yes |
| checker, IBP |
| every scalar | no |
| none |
Exponentials, logarithms, and trigonometric and hyperbolic functions:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes |
| IBP |
| every scalar | no |
| IBP |
| every scalar | no |
| IBP |
| every scalar | yes |
| IBP |
| every scalar | no |
| IBP |
| every scalar | no |
| IBP |
| every scalar | no |
| IBP |
| every scalar | yes |
| checker, IBP |
| every scalar | yes |
| checker, IBP |
| every scalar | no |
| IBP |
|
| no |
| IBP |
|
| no |
| IBP |
|
| no |
| IBP |
|
| no | no certificate; forward mode | IBP |
| every scalar | no |
| IBP |
| every scalar | no |
| IBP |
| every scalar | no |
| IBP |
| every scalar | no |
| none |
| every scalar | no |
| none |
|
| no |
| IBP |
Roundings and signs:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
|
| no | no certificate; forward mode | IBP |
|
| no | no certificate; forward mode | IBP |
|
| no | no certificate; forward mode | IBP |
|
| no | no certificate; forward mode | IBP |
|
| no | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | IBP |
Extrema, of which max and min propagate a NaN, fmax and fmin ignore one, and clamp
reads its bounds at frozen ports:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes |
| IBP |
| every scalar | yes |
| IBP |
| every scalar | no | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | IBP |
| every scalar | yes |
| IBP |
Activations, with PyTorch's approximate on gelu:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes |
|
checker, IBP; |
| every scalar | yes |
| checker, IBP |
| every scalar | yes |
| checker, IBP |
| every scalar | yes |
| checker, IBP |
| every scalar | yes |
| checker, IBP |
|
| at some attributes |
| IBP |
| every scalar | no |
| IBP |
| every scalar | no |
| IBP |
Comparisons, predicates and logic, whose outputs are Booleans, with select on a Boolean
condition and cast, which reads Booleans or indices as scalars:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no certificate; forward mode | IBP |
| every scalar | no | no value input; forward mode | none |
Integer words: the bitwise operators, the shifts, populationCount, countLeadingZeros and
mulhi compute the functions of TorchLean.Word, bitcast reads each word at another sort of the
same width, and encodeIndex and decodeIndex convert index tuples to and from words in the IR's
layout (Port.irShape):
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
|
| no | no value input; forward mode at some attributes | none |
| every scalar | no | no value input | none |
| every scalar; no rule at some attributes | no | no value input; forward mode at some attributes | none |
Scans and orderings, of which cummax, cummin, sort, topk, argmax and argmin return
indices:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | no | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no value input | none |
| every scalar | no | no value input | none |
Indexing, with gather over any axes and batch dimensions and scatter as a replacement, sum,
product, maximum or minimum:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes | no certificate; forward mode | IBP |
| every scalar | at some attributes | no certificate; forward mode at some attributes | none |
| every scalar | no | no value input | none |
| every scalar | yes | no certificate; forward mode | IBP |
| every scalar | at some attributes | no certificate; forward mode | IBP, CROWN |
| every scalar | no | no certificate; forward mode | IBP |
Data movement:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes | no certificate; forward mode | IBP, CROWN |
| every scalar | yes | no certificate; forward mode | IBP, CROWN |
| every scalar | at some attributes | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | IBP |
| every scalar | no |
| IBP |
| every scalar | at some attributes | no certificate; forward mode | checker, IBP, CROWN |
Reductions and contractions, reduce being a sum, product, maximum or minimum over any axes:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | at some attributes |
| IBP, CROWN |
| every scalar | at some attributes | no certificate; forward mode | IBP |
| every scalar | no |
| IBP |
Products, matmul and scaledMatmul with broadcast batch prefixes and linear over leading
axes:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes | no certificate; forward mode | checker, IBP, CROWN |
| every scalar | yes | no certificate; forward mode | checker, IBP, CROWN |
| every scalar | at some attributes |
inputs 0 and 1: no certificate; input 2: | checker, IBP, CROWN |
Convolution and pooling:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | at some attributes | no certificate; forward mode | checker, IBP, CROWN |
| every scalar | at some attributes | no certificate; forward mode | none |
| every scalar | at some attributes | no certificate; forward mode | IBP |
| every scalar | at some attributes | no certificate; forward mode | none |
Normalization, layerNorm over the last axis, batchNorm with batch statistics and
batchNormRunning with running statistics along any axis:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes |
| IBP |
| every scalar | yes |
| none |
| every scalar | yes |
| IBP |
Softmax and attention:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes |
|
IBP; |
| every scalar | yes |
| IBP |
| every scalar | no | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | none |
| every scalar | yes | no certificate; forward mode | none |
| every scalar | yes | no certificate; forward mode | none |
Losses:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes | no certificate; forward mode | IBP |
| every scalar | no | no certificate; forward mode | none |
| every scalar | yes | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | yes | no certificate; forward mode | none |
| every scalar | no | no certificate; forward mode | none |
| every scalar | yes | no certificate; forward mode | none |
Masks and random draws, each draw under a key, with the mask of dropout an input:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes | no certificate; forward mode | none |
| every scalar | at some attributes | no value input; forward mode | none |
| every scalar | yes | no value input; forward mode | IBP |
| every scalar | yes | no value input; forward mode | IBP |
| every scalar | no | no value input; forward mode | none |
Spectral transforms and the selective scan:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | at some attributes | no certificate; forward mode | none |
| every scalar | at some attributes | no certificate; forward mode | none |
| every scalar | at some attributes | no certificate; forward mode | none |
Graph structure:
Operator | Rule | CUDA | Gradient | Bounds |
|---|---|---|---|---|
| every scalar | yes |
no value input; forward mode ( | checker, IBP, CROWN |
|
from its node's program ( | no | no certificate | none |
Together the tables list all 154 constructors.
Import Coverage
Both the PyTorch capture and the JAX importer translate a framework's operators onto this table. An
operator becomes an IR node whose kind projects onto the registered operator or, where an operand
or result is an index or a Boolean beside a scalar, a custom node holding the operator's record. An
imported operator is computed by its registered operator for the attributes, element types and
inputs the capture accepts, and the capture refuses the others. Some imports also fix a policy.
Cumulative sums and products run in PyTorch's widened accumulation type, and topk fails at
execution where tied entries would decide its selection. A runtime scatter folds the updates of a
repeated target in the row-major order of its index tensor, and an in-place index_put_ is
accepted only into fresh storage. For a training batch norm, the buffer updates are computed out of
place and returned after the program's outputs. And randn is a deterministic Box-Muller transform
of two uniform draws, one clamped away from zero. Operator by operator, the
IR guide and the
PyTorch interop guide say where the import writes
the arithmetic of a framework's kernel and where it departs from eager PyTorch or XLA.
An operator outside a translation is refused with an error that names it. On the PyTorch side, the
capture has no translation for the backward operators of a joint graph, since TorchLean derives
gradients from the rules above. Nor does it translate operators with uninitialized contents, the
strided aliasing view as_strided or _local_scalar_dense, which reads a tensor as a Python
scalar. It also refuses results whose shape depends on the data, nonzero and Boolean mask indices
among them (the bounded nonzero_static imports). The JAX importer rejects control flow, complex
inputs and outputs, an accuracy request on a transcendental primitive and nondefault dot
precisions, and it has no translation for multi-device collectives, random key primitives or host
callbacks.
Input-independent creations fold to constants the framework computes (arange, full, iota),
and clone, alias and copy pass their operand through.