add-op-overload
Checklist for adding a new overload (e.g. `mm.dtype`, `addmm.compute_mode`) to an existing ATen op in native_functions.yaml. Use before the first rebuild when adding an overload or new kwarg variant of an existing operator, so every rebuild-triggering edit lands in one build and packet-level Python registrations (meta, decompositions, Inductor lowerings, DTensor) don't silently capture the new overload.Adding an overload to an existing ATen op
Editing native_functions.yaml regenerates headers that most of the tree includes, so it is
close to a full rebuild; so is editing ATen/Context.h (e.g. to add a global setting).
autocast_mode.h reaches only about a dozen translation units, but forgetting it still costs
another build, relink and test cycle. Each missed item below costs a rebuild or ships a
silent bug. Finish all of Part 1 before starting the first build.
Find the closest precedent first and copy its footprint. For matmul ops, that's the
out_dtype overloads (grep -n out_dtype aten/src/ATen/native/native_functions.yaml).
git log --oneline -S'<op>.<overload>' -- aten/src/ATen/native/native_functions.yaml finds
the commit that added it; git show --stat <sha> lists every file it touched.
Part 1: C++ and codegen (needs a rebuild)
-
Schema (
native_functions.yaml): add the functional overload and its_outoverload, each with a new, unique overload name. A thin wrapper that sets state and calls the regular op should useCompositeExplicitAutograd, so it stays visible to autograd, tracing and export instead of decomposing away. -
C++ overload resolution. The C++ API flattens kwarg-only arguments into positional parameters, so the new argument lands where existing callers pass
beta/alphapositionally, e.g.at::addmm_out(result, t, sparse, dense, 0, 1)inaten/src/ATen/native/sparse/SparseTensorMath.cpp.str(c10::string_view): the literal0converts to it as a null pointer, so the call becomes ambiguous and the build fails late.bool/int:0converts by a standard conversion, which beatsScalar's constructor, so the call silently binds to the new overload.
Fix: put
cpp_no_default_args: ['beta', 'alpha'](the trailing defaulted args) on the new overloads. Check every in-tree C++ call of the op against the new signature. If the op previously had a single C++ overload, code that takes its address (at::mmpassed as a function pointer) also stops compiling; wrap it in a lambda. -
Autograd (
tools/autograd/derivatives.yaml): aCompositeExplicitAutogradoverload needs its own entry; it does not inherit the base op's. If backward should keep the new argument's semantics, call the same overload there and thread the argument through the helpers intorch/csrc/autograd/FunctionsManual.{h,cpp}. -
Autocast lists are per-overload. If the base op is in
AT_FORALL_LOWER_PRECISION_FP,AT_FORALL_FP32, etc. inaten/src/ATen/autocast_mode.h, or in theKERNEL_CPU(...)/KERNEL_MPS(...)lists inaten/src/ATen/autocast_mode.cpp, add_(op, overload)/KERNEL_CPU(op, overload, policy)with the same policy. Without this, the wrapper's inner call is autocast but its autograd node is not: forward runs in low precision and backward fails with mixed dtypes (expected mat1 and mat2 to have the same dtype). -
Other per-overload C++ tables (
grep -rnE '"(aten::)?<op>[."]|VMAP_SUPPORT\(<op>,' aten/src/ATen --include=*.cpp):- vmap batch rules (
VMAP_SUPPORT/m.implinaten/src/ATen/functorch/BatchRules*.cpp). Missing means the slow per-example fallback. - Conjugate fallthroughs in
aten/src/ATen/ConjugateFallback.cpp. Missing means conj inputs get materialized first.
- vmap batch rules (
Part 2: Python (no rebuild needed)
- Packet-level registrations capture the new overload. Registering on an
OpOverloadPacket(aten.oprather thanaten.op.default) expands to every overload, including yours, and then silently drops the new argument. Audit:
Typical hits:grep -rnE "aten\.<op>\b[^.]" torch --include=*.py | grep -E "register|lowering|decomp|fallback|flop"register_meta(aten.op)intorch/_meta_registrations.py,register_decomposition([aten.op])intorch/_decomp/decompositions.pyandtorch/_inductor/decomposition.py, and Inductorregister_lowering(aten.op)intorch/_inductor/kernel/. Narrow each one to explicit overloads (aten.op.default, aten.op.out) unless it really handles the new argument. Adding your own registration while a packet one exists fails withduplicate registrations for ....torch/utils/flop_counter.pylooks formulas up by packet, so it can't be narrowed; check that the formula's*args, **kwargsaccept the new overload's arguments. - Inductor: to run the overload eagerly, add
make_fallback(aten.op.overload)intorch/_inductor/lowering.py. It must run beforeimport_submodule(kernel)at the end of that file, becauseget_overloadsonly expands a packet to overloads that don't have a lowering yet.make_fallbackasserts if a decomposition still covers the overload, so narrow the decompositions first. - DTensor: sharding strategies are per-overload (e.g.
register_single_dim_strategy(aten.mm.default)intorch/distributed/tensor/_ops/_matrix_ops.py). Without one, DTensor usesdecomposition_tableif it covers the overload (dropping the argument if a packet decomposition captured it), and otherwise raisesdoes not have a sharding strategy registered. - Expect files:
EXPECTTEST_ACCEPT=1 python test/test_decomp.py -k HasDecomp. The diff should show only the new overloads. - Signatures and docs: update the
get_testing_overrides()lambdas intorch/overrides.pyif the Python signature gains a parameter. Intorch/_torch_docs.py, add a.. function::block with:noindex:for the new signature, as theout_dtypeones do, plus any relevantdocs/source/notes/*.md.
Part 3: Validation checklist for the new overload
- Eager forward and backward, plus
gradcheck(including forward AD) on CPU. torch.autocastforward and backward on CUDA and CPU (catches item 4).- FakeTensor/meta shapes;
torch.export+run_decompositions()keeps the overload; aTorchDispatchModeseesaten.op.overload(catches item 6). torch.compileforward and backward: assert that the generated code calls the overload with the argument, rather than just counting occurrences of the op name.- vmap and DTensor, if they apply (items 5 and 8).
- Use pytest for
-k "a or b"expressions.python test/X.py -kgoes to unittest, which treats the expression as a literal substring and runs 0 tests.