pytorch/pytorch
Dispatcher
Active contributors: ezyang, bdhirsh
Purpose
The dispatcher is the central op-routing table that decides, for every operator call, which kernel to run. It is what makes "the same Python op works on CPU, CUDA, MPS, autograd, autocast, vmap, FakeTensor, traced symbolic execution, …" possible.
Almost every cross-cutting feature in PyTorch is implemented either as a dispatcher kernel (for some DispatchKey) or by interposing on the dispatcher.
Directory layout
| Path | Contents |
|---|---|
aten/src/ATen/core/dispatch/ |
The dispatcher itself |
aten/src/ATen/core/dispatch/Dispatcher.h/.cpp |
c10::Dispatcher, OperatorEntry, OperatorHandle |
aten/src/ATen/core/dispatch/DispatchKeyExtractor.h |
How dispatch keys are extracted from arguments |
aten/src/ATen/core/op_registration/ |
The m.def/m.impl registration API |
c10/core/DispatchKey.h |
The enum of all dispatch keys |
c10/core/DispatchKeySet.h |
KeySet bitmap and priority arithmetic |
aten/src/ATen/core/Library.h |
TORCH_LIBRARY / TORCH_LIBRARY_IMPL macros |
aten/src/ATen/core/PythonOpRegistrationTrampoline.cpp |
Python-side op registration (torch.library) |
Key abstractions
| Type | File | Purpose |
|---|---|---|
c10::DispatchKey |
c10/core/DispatchKey.h |
Enum: CPU, CUDA, Autograd, Autocast, Functionalize, … |
c10::DispatchKeySet |
c10/core/DispatchKeySet.h |
64-bit bitmap of dispatch keys |
c10::Dispatcher |
aten/src/ATen/core/dispatch/Dispatcher.h |
The singleton routing table |
c10::OperatorEntry |
aten/src/ATen/core/dispatch/OperatorEntry.h |
Per-op kernel table indexed by DispatchKey |
c10::OperatorHandle |
aten/src/ATen/core/dispatch/Dispatcher.h |
Handle returned by findOp |
torch::Library |
aten/src/ATen/core/Library.h |
Builder used by TORCH_LIBRARY macros |
at::FunctionalTensorWrapper |
aten/src/ATen/FunctionalTensorWrapper.h |
Functionalization key kernel |
How it works
Dispatch keys are concerns
Each DispatchKey represents a concern. A simplified prioritized list (highest first):
PythonTLSSnapshot
PythonDispatcher
FuncTorchVmapMode / FuncTorchBatched / FuncTorchGradWrapper
Autocast{CPU,CUDA,XPU,MPS,…}
AutogradOther / Autograd{CPU,CUDA,XPU,MPS,…}
ZeroTensor
Negative
Conjugate
Functionalize
Sparse{CPU,CUDA,…} / SparseCsr{…}
Mkldnn{CPU,CUDA}
Quantized{CPU,CUDA,…}
Meta
CPU / CUDA / MPS / XPU / MTIA / Lazy / XLA / PrivateUse{1,2,3} / Vulkan / MetalThe full enum is ~140 entries; see c10/core/DispatchKey.h. Some are backend keys (CPU, CUDA), some are functionality keys (Autograd, Autocast, Functionalize, Sparse, Quantized), and some are alias keys that fan out (CompositeImplicitAutograd, CompositeExplicitAutograd, …).
Where keys come from
The keyset for a call is the union of:
- The keysets carried by every input
Tensor(each TensorImpl has akey_set_). - Ambient included keys from TLS — e.g., autograd is enabled, autocast is enabled, vmap is on, the Python dispatcher is recording.
- Minus excluded keys from TLS —
c10::AutoDispatchBelowAutograd,c10::InferenceMode, etc., remove keys for the duration of a scope.
DispatchKeyExtractor is generated per op based on which arguments are tensors.
Step semantics
Each kernel typically does its concern's work and then redispatches by calling at::redispatch::<op>(keyset_minus_self, args...). The dispatcher recomputes the highest priority key in the remaining set and jumps to that kernel. So a single user call to at::add may pass through Autograd → Functionalize → CUDA in sequence.
Some keys are fallthrough (no-op redispatch) for ops they don't care about. Some are alias keys (CompositeImplicitAutograd) that say "fill in this slot for every backend key unless overridden". Some are fallback (e.g., LegacyBatchedFallback.cpp) that handle every op generically by looping the inner dim.
Registration
There are three ways to register a kernel:
- Codegen —
aten/src/ATen/native/native_functions.yamlplus torchgen producesRegisterCPU.cpp,RegisterCUDA.cpp, etc. that callm.impl("op_name", kernel). TORCH_LIBRARYandTORCH_LIBRARY_IMPL— explicit C++ macros inaten/src/ATen/core/Library.h. Example:TORCH_LIBRARY_IMPL(aten, Autograd, m) { m.impl("matmul", autograd::matmul); }- Python
torch.library—torch/library.pyandtorch/_library/. A user-facing API that callsPythonOpRegistrationTrampolineto register kernels backed by Python callables.
Performance
The dispatcher is on the hot path of every op call. It is heavily optimized: most lookups are a DispatchKeySet::highestPriorityTypeId() (a __builtin_clz), one indirect call, no allocations. Boxed (IValue-based) and unboxed (typed) call paths exist; the unboxed path is the fast one.
Functionality keys worth knowing
| Key | What it does | Where its kernels live |
|---|---|---|
Autograd* |
Records the op on the tape, builds the backward node, redispatches | torch/csrc/autograd/generated/ |
Autocast* |
Up/down-casts dtype for mixed-precision ops | aten/src/ATen/autocast_mode.cpp |
Functionalize |
Rewrites in-place / view ops into pure functional ones | aten/src/ATen/FunctionalizeFallbackKernel.cpp, FunctionalTensorWrapper.cpp |
FuncTorchBatched |
vmap | aten/src/ATen/functorch/ |
Conjugate |
Lazy .conj() |
aten/src/ATen/ConjugateFallback.cpp |
Negative |
Lazy unary - |
(similar pattern) |
ZeroTensor |
Tensors known to be all zero (used in some autograd shortcuts) | aten/src/ATen/ZeroTensorFallback.cpp |
Sparse* |
Sparse tensor ops | aten/src/ATen/native/sparse/ |
Quantized* |
Quantized ops | aten/src/ATen/native/quantized/ |
Meta |
Shape-only (no data) device | Embedded in structured kernels |
Python |
Python-implemented ops | torch/_library/, torch/library.py |
PythonTLSSnapshot |
Captures Python TLS for retracing | aten/src/ATen/PythonTorchFunctionTLS.cpp |
Integration points
- ATen owns the dispatcher and most kernels. See ATen.
- Autograd is just dispatcher kernels for the
Autograd*keys, generated fromderivatives.yaml. See Autograd. - AOT Autograd uses the Functionalize key + FakeTensor mode + PythonDispatcher to symbolically execute graphs. See AOT Autograd.
torch.compilesits on top of the dispatcher; Inductor emits Triton/C++ that bypass it for known shapes.- Out-of-tree backends (XLA, MTIA, Lazy) register kernels for their own backend keys via
TORCH_LIBRARY_IMPL.
Entry points for modification
- To add a dispatch key: edit
c10/core/DispatchKey.h, updateDispatchKeySet.cpppriorities, and updateaten/src/ATen/core/dispatch/DispatchKeyExtractor.cppif needed. Many of the alias-key fan-outs need updating too — this is rarely a small change. - To add a kernel for an existing key: prefer the
dispatch:table innative_functions.yaml, or write aTORCH_LIBRARY_IMPLblock in your file. - To trace what the dispatcher does at runtime, set
TORCH_SHOW_DISPATCH_TRACE=1— every op call prints its dispatch path.
Key source files
| File | Purpose |
|---|---|
aten/src/ATen/core/dispatch/Dispatcher.h |
Dispatcher API and OperatorHandle |
aten/src/ATen/core/dispatch/Dispatcher.cpp |
Dispatcher implementation |
aten/src/ATen/core/dispatch/OperatorEntry.h/.cpp |
Per-op kernel table |
aten/src/ATen/core/dispatch/DispatchKeyExtractor.h |
How keysets are computed from args |
aten/src/ATen/core/Library.h |
TORCH_LIBRARY macros |
c10/core/DispatchKey.h |
Enum of dispatch keys |
c10/core/DispatchKeySet.h |
KeySet bitmap and priority logic |
aten/src/ATen/PythonTorchFunctionTLS.cpp |
Python TLS dispatch hook |
aten/src/ATen/FunctionalTensorWrapper.cpp |
Functionalize key |
aten/src/ATen/autocast_mode.cpp |
Autocast key |
For the per-key reference, see Primitives / Dispatch keys.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.