ATen "native" functions are the modern mechanism for adding operators and
functions to ATen. Native functions
are declared in `native_functions.yaml` and have implementations defined
in one of the `cpp` files in this directory.
Like all ATen methods/functions, native functions are made available
from both ATen's C++ and Python APIs. In C++, they are made available
either as methods on `Tensor` (`t.mymeth()`) and functions in the ATen
namespace (`at::myfunc()`). In PyTorch, they are made available as
methods on `Variable` or as functions on `torch._C._FunctionBase`.
(It is the user's responsibility to re-export these functions in
a more user-facing module.)
The rest of this document describes how to implement an ATen function.
## Registering a function in `native_functions.yaml`
Every native function must have an entry in
`native_functions.yaml`. The format can be summarized as:
```
- func: func_name(ArgType arg0[=default], ArgType arg1[=default], ...) -> Return
variants: function, method
dispatch:
CPU: func_cpu
CUDA: func_cuda
```
Each component is described in more detail below:
### `func`
```
- func: func_name[.overload_name](ArgType arg0[=default], ArgType arg1[=default], ...) -> Return
```
The `func` entry is a string describing the name of the function and its type
signature.
**Argument types.** These types are permissible as ArgType:
- `Tensor`. A `Tensor` argument translates into a C++ argument of type `const Tensor&`
(except when the argument is "inplace"; in this case, it is simply `Tensor&`).
A trailing `?`, as in `Tensor?`, indicates that the tensor argument is optional
and may be omitted by passing std::nullopt. When a function takes multiple
`Tensor` arguments, these tensors are assumed to be the same type (e.g.,
if one argument is a `FloatTensor`, all other arguments are checked
to be `FloatTensor`s).
`Tensor` or `Tensor?` must sometimes be annotated to indicate aliasing and mutability.
In general annotations can be defined via the following situations:
- `Tensor(a)` - `a` is a set of Tensors that may alias to the same data. The set could have a size of one.
- `Tensor(a!)` - members of `a` may be written to thus mutating the underlying data.
- `Tensor(a! -> a|b)` - Tensor is in set `a`, written to, and after the write is in set `a` AND `b`.
For more details on when and why this needs to happen, please see the section on annotations.
- `Tensor[]`. A `Tensor[]` argument translates into a C++ argument of type `ArrayRef<Tensor>`
(a.k.a. `TensorList`)
- `int[]`. `int[]` accepts an optional length specifier, e.g., `int[2]`, which
has no effect in C++ but extends our Python bindings to accept a bare number, which will be
expanded into an appropriately sized list by repeating the number.
- `int`. Think about this like a Python int. This is translated into a C++ argument of type `int64_t`.
- `float`. Think about this like a Python `float`. It is translated into a C++ argument of type `double`.
- `bool`
- `str`. It is translated into a C++ argument of non-owning type `c10::string_view`
- `Scalar`. `Scalar` supports binding to any numerical types from Python, including integral types,
floating point types, and zero dimensional tensors. `int` and `float` bind to the corresponding Python
numerical types. However, you probably don't want to use `Scalar`;
`float` and `int` argument types should suffice for most algorithms
(you should only use `Scalar` if the operator truly may accept either
type).
- `Generator?`, the state for a random number generator,
- `bool[N]` (where N is `1-4`).
- `*` is a special sentinel argument, which doesn't translate into an actual
argument, but indicates that in the Python bindings, any subsequent arguments
must be specified as keyword arguments (and cannot be provided positionally).
- `?` is trailing question mark that annotates an argument to be an optional type. Grep for
`optional` to find some example usages. In general, most functions will not need to use
this, but there are some cases that we want to use optional for the different types:
- You want to pass a `None` to an ATen function/method from Python and handle the
None type on the C++ side. For example, `clamp(Tensor self, Scalar? min=None, Scalar? max=None)`
can take `None` for its `min` and `max` parameter, but does not dispatch to different
backends if one of the parameters is `None`. Optional type can accept a `None` type
(`nullopt` in C++) from Python and use the [C++ Optional class](https://en.cppreference.com/w/cpp/utility/optional) to interact with the parameters.
- You want a default value, which is fine in Python, but would cause ambiguity in C++.
For example, `norm(Tensor self, Scalar p=2, int dim, bool keepdim=False)` would
cause ambiguity in C++ since its default args must be adjacent (`p` could not
have a default value when `dim` does not). Therefore, we need to make `p` as a
optional Scalar, and make `p=2` when `p` is not passed in (nullopt).
- You want a value to default to the same value as another argument (this cannot be
expressed in C++ default arguments).
Functions with no tensor inputs are called *factory functions*, and
are handled specially by code generation. If your function is behaving
differently than another example, check first and see if one is a
factory while another is not. In some rare cases, factory function might have a
tensor argument. In this case mark it with `category_override: factory`
explicitly.
**Argument names.** Argument names are meaningful; downstream binding code may make use of the specific
argument name you provide, and a rename of an argument name is considered a BC-breaking
change (e.g., you will probably need to update `tools/autograd/derivatives.yaml` at
least, and it may affect Python keyword arguments). For more details please see the section on `variants`.
As a convention we use 'out' to indicate an output argument. This aligns with the
Python bindings. Even if a function might not be used in the Python bindings, we
still advise to follow this convention. Check the generated code when making a change
to make sure you're not breaking the API when renaming an argument name of an
existing function.
**Defaults.** Any suffix of arguments can have a default value defined;
these default values translate into C++/Python default values which
are applied when those positional arguments are not specified.
Here are the supported default values:
* Numbers (e.g., `0` or `5.0` for `int`, `float` and `int[]`
with an explicit length (e.g., `int[2]`)--in the case of `int[]`
a number is replicated to fill the length (e.g., `int[2] x=2`
is equivalent to `int[2] x=[2,2]`).
* Lists of numbers (e.g., `[0, 0]`) for `IntList`.
* Booleans (e.g., `True`) for `bool`.
* Empty initializer lists (e.g., `[]`) for `Tensor` (this implicitly changes
a `Tensor` argument to accept undefined tensors).
* `None` for pointer types (e.g., `Generator?`)
**Returns.** The following are permissible on Return:
Non-tuple return:
```
ReturnType [retarg0]
```
Tuple return:
```
(ReturnType [retarg0], ReturnType [retarg1], ...)
```
The following are permissible on ReturnType:
- `Tensor` and `Tensor[]`, which translate into the C++ types `Tensor` and `std::vector<Tensor>`,
respectively (unless the operation is in-place, in which case the return type
is `Tensor&`.
- A tuple of any number of `Tensor`, e.g., `(Tensor, Tensor)`, translating into
the C++ `std::tuple<Tensor, Tensor>`.
If you need a type that is not listed in this list, it may be possible to extend ATen's
code generation to support it. ATen's philosophy on types to support is that it supports
only simple, universal types, as well as a handful of fundamental Tensor structures
(e.g., `Tensor` and `Generator?`), because these types can be easily ported to any language
bound to ATen (in practice, C++ and Python.)
Return also supp