Custom Python Operators#
Created On: Jun 16, 2026 | Last Updated: Jun 16, 2026 | Last Verified: Nov 05, 2024
When to create a Python custom operator
How to choose between functional and mutable operator contracts
Why the schema and mutation/aliasing contract are required
Where fake kernels, autograd, and other registrations fit
PyTorch offers a large library of operators that work on Tensors, such as
torch.add and torch.sum. However, you might wish to use a new custom
operator with PyTorch, perhaps written by a third-party library. This guide
shows how to wrap Python functions so that they behave like PyTorch native
operators.
Reasons why you may wish to create a custom operator in PyTorch include:
treating an arbitrary Python function as an opaque callable with respect to
torch.compileand/ortorch.exportadding training support to an arbitrary Python function.
Please note that if your operation can be expressed as a composition of
existing PyTorch operators, then there is usually no need to use the custom
operator API. torch.compile, training support, and other PyTorch subsystems
should usually work.
Every custom operator needs:
a stable schema and mutation/aliasing contract;
validation with
torch.library.opcheck;a fake kernel if it returns tensors and must work with
torch.compileortorch.export.
Choose one path:
Functional custom operators: the operator returns fresh tensors and mutates no inputs.
Mutable custom operators: the operator mutates an input or writes into an output buffer. Starting in PyTorch 2.13, this includes PyTorch-style in-place and
out=custom operators.Optional registrations: add autograd,
torch.vmap, Tensor subclass behavior, or other subsystem support after the base operator passesopcheck.
Choose your path
Any custom operator: read Schema and mutation/aliasing contract and Validation. You need a stable schema, representative examples, and
opcheck.Code that returns new tensors and does not mutate inputs: read Functional custom operators. You need
custom_op(..., mutates_args=()), a fake kernel fortorch.compile, andopcheck.A kernel that writes into existing memory: read Mutable custom operators. You need accurate
mutates_argsand one clear mutation pattern.In-place, ``out=``, or maybe-out behavior: read Mutable custom operators and Schema contract. Starting in PyTorch 2.13, tagged in-place and
out=custom operators are available; split maybe-out behavior into separate operators.Training support, ``vmap``, or Tensor subclass behavior: read Adding registrations. Start with a validated base operator, then add the registration for that subsystem.
For Python-less environments or AOTInductor, define the operator and backend kernels in C++ instead. See the C++ custom operator tutorial.
Before you start#
A kernel is the implementation. An operator is the PyTorch-facing contract: name, inputs, outputs, mutation behavior, and subsystem registrations.
A custom operator gives PyTorch an explicit boundary. Use it when tracing into the implementation is impossible or undesirable.
Required: schema and mutation/aliasing contract#
Decide the schema and mutation/aliasing contract before writing registrations. PyTorch uses the schema and registrations to reason about mutation/aliasing; it does not infer the contract from the Python function body.
Two Tensors alias when they share the same underlying storage. For example,
y = x.view(-1) creates a
view y that aliases
x, so writing to y can change x.
The schema must be stable: the mutation and aliasing behavior must be correct and consistent. This means that an operator must not return an output that sometimes aliases its input. Also, the operator may not mutate an input that is not marked as being mutated.
A functional custom operator must return fresh tensors. Do not return an input tensor, a view of an input, or two outputs that alias each other.
A mutable custom operator must list every mutated argument in
mutates_args.A fake kernel must return tensors with the same metadata as the real kernel: shape, dtype, device, layout, strides, and storage offset when relevant.
empty_like(x)is only correct when the real output has the same metadata asx. The functional custom operator page shows an executable example of this metadata mismatch.Fake kernels may inspect metadata, but must not read tensor data.
Avoid “maybe-out” operators. An operator that sometimes allocates a new tensor and sometimes writes into an output buffer has different aliasing contracts for different calls.
Split maybe-out behavior into two operators: one functional operator that allocates and one mutable operator that writes into an output buffer.
Required: validate with opcheck#
torch.library.opcheck validates the registration contract: schema, fake
kernel, autograd registration, and behavior under compilation APIs.
Run opcheck on representative inputs:
each supported device;
important dtypes;
edge shapes such as empty tensors;
important memory formats or non-contiguous strides;
inputs with
requires_grad=Trueif the operator supports training.
opcheck is not a numerical correctness test. Use
torch.testing.assert_close or ordinary unit tests for forward correctness,
and torch.autograd.gradcheck for gradient formulas.
Next steps#
Read one base-contract page first, then add registrations only if needed: