shrinath-suresh/blogs-docs-splitted huggingface dataset:
operators or override operators in existing libraries from Python.
   A user can optionally pass in a dispatch keyname if they only want
   to register kernels corresponding to only one specific dispatch
   key.
To create a library to override operators in an existing library
   (with name ns), set the kind to "IMPL". To create a new library
   (with name ns) to register new operators, set the kind to "DEF".
   :param ns: library name :param kind: "DEF", "IMPL" (default:
   "IMPL") :param dispatch_key: PyTorch dispatch key (default: "")
define(schema, alias_analysis='')
  Defines a new operator and its semantics in the ns namespace.

  Parameters:
     * **schema** -- function schema to define a new operator.

     * **alias_analysis** (*optional*) -- Indicates if the
       aliasing properties of the operator arguments can be
       inferred from the schema (default behavior) or not
       ("CONSERVATIVE").

  Returns:
("CONSERVATIVE").
  Returns:
     name of the operator as inferred from the schema.

  Example::
     >>> my_lib = Library("foo", "DEF")
     >>> my_lib.define("sum(Tensor self) -> Tensor")

impl(op_name, fn, dispatch_key='')
  Registers the function implementation for an operator defined in
  the library.

  Parameters:
     * **op_name** -- operator name (along with the overload) or
       OpOverload object.

     * **fn** -- function that's the operator implementation for
       the input dispatch key.

     * **dispatch_key** -- dispatch key that the input function
       should be registered for. By default, it uses the dispatch
       key that the library was created with.

  Example::
     >>> my_lib = Library("aten", "IMPL")
     >>> def div_cpu(self, other):
     >>>     return self * (1 / other)
     >>> my_lib.impl("div.Tensor", "CPU")

We have also added some function decorators to make it convenient to
Named Tensors
Named Tensors allow users to give explicit names to tensor dimensions.
In most cases, operations that take dimension parameters will accept
dimension names, avoiding the need to track dimensions by position. In
addition, named tensors use names to automatically check that APIs are
being used correctly at runtime, providing extra safety. Names can
also be used to rearrange dimensions, for example, to support
"broadcasting by name" rather than "broadcasting by position".
Warning:
The named tensor API is a prototype feature and subject to change.
Creating named tensors
Factory functions now take a new "names" argument that associates a
name with each dimension.



torch.zeros(2, 3, names=('N', 'C'))
   tensor([[0., 0., 0.],
           [0., 0., 0.]], names=('N', 'C'))



Named dimensions, like regular Tensor dimensions, are ordered.
"tensor.names[i]" is the name of dimension "i" of "tensor".
The following factory functions support named tensors:
"torch.empty()"


"torch.rand()"


"torch.randn()"


"torch.ones()"


"torch.tensor()"


"torch.zeros()"


Named dimensions
See "names" for restrictions on tensor names.
Use "names" to access the dimension names of a tensor and "rename()"
to rename named dimensions.



imgs = torch.randn(1, 2, 2, 3 , names=('N', 'C', 'H', 'W'))
imgs.names
   ('N', 'C', 'H', 'W')
renamed_imgs = imgs.rename(H='height', W='width')
renamed_imgs.names
   ('N', 'C', 'height', 'width)



Named tensors can coexist with unnamed tensors; named tensors are
instances of "torch.Tensor". Unnamed tensors have "None"-named
dimensions. Named tensors do not require all dimensions to be named.



imgs = torch.randn(1, 2, 2, 3 , names=(None, 'C', 'H', 'W'))
imgs.names
   (None, 'C', 'H', 'W')



Name propagation semantics
Named tensors use names to automatically check that APIs are being
called correctly at runtime. This occurs in a process called *name
inference*. More formally, name inference consists of the following
two steps:


Check names: an operator may perform automatic checks at runtime
  that check that certain dimension names must match.


Propagate names: name inference propagates names to output
  tensors.


All operations that support named tensors propagate names.



x = torch.randn(3, 3, names=('N', 'C'))
x.abs().names
   ('N', 'C')



match semantics
Two names match if they are equal (string equality) or if at least
one is "None". Nones are essentially a special "wildcard" name.
"unify(A, B)" determines which of the names "A" and "B" to propagate
to the outputs. It returns the more specific of the two names, if
they match. If the names do not match, then it errors.
Note:
In practice, when working with named tensors, one should avoid
  having unnamed dimensions because their handling can be complicated.
  It is recommended to lift all unnamed dimensions to be named
dimensions by using "refine_names()".
Basic name inference rules
Let's see how "match" and "unify" are used in name inference in the
case of adding two one-dim tensors with no broadcasting.
x = torch.randn(3, names=('X',))
   y = torch.randn(3)
   z = torch.randn(3, names=('Z',))
Check names: check that the names of the two tensors match.
For the following examples:



x + y  # match('X', None) is True
x + z  # match('X', 'Z') is False
x + x  # match('X', 'X') is True
x + z
   Error when attempting to broadcast dims ['X'] and dims ['Z']: dim 'X' and dim 'Z' are at the same position from the right but do not match.



Propagate names: unify the names to select which one to
propagate. In the case of "x + y", "unify('X', None) = 'X'" because
"'X'" is more specific than "None".



(x + y).names
   ('X',)
(x + x).names
   ('X',)



For a comprehensive list of name inference rules, see Named Tensors
operator coverage. Here are two common operations that may be useful
to go over:


Binary arithmetic ops: Unifies names from inputs


Matrix multiplication ops: Contracts away dims


Explicit alignment by names
Use "align_as()" or "align_to()" to align tensor dimensions by name to
a specified ordering. This is useful for performing "broadcasting by
names".
# This function is agnostic to the dimension ordering of input,
   # as long as it has a C dimension somewhere.
   def scale_channels(input, scale):
       scale = scale.refine_names('C')
       return input * scale.align_as(input)



num_channels = 3
scale = torch.randn(num_channels, names=('C',))
imgs = torch.rand(3, 3, 3, num_channels, names=('N', 'H', 'W', 'C'))
more_imgs = torch.rand(3, num_channels, 3, 3, names=('N', 'C', 'H', 'W'))
videos = torch.randn(3, num_channels, 3, 3, 3, names=('N', 'C', 'H', 'W', 'D')
scale_channels(imgs, scale)
scale_channels(imgs, scale)
scale_channels(more_imgs, scale)
scale_channels(videos, scale)



Manipulating dimensions
Use "align_to()" to permute large amounts of dimensions without
mentioning all of them as in required by "permute()".



tensor = torch.randn(2, 2, 2, 2, 2, 2)
named_tensor = tensor.refine_names('A', 'B', 'C', 'D', 'E', 'F')



# Move the F (dim 5) and E dimension (dim 4) to the front while keeping
   # the rest in the same order



tensor.permute(5, 4, 0, 1, 2, 3)
named_tensor.align_to('F', 'E', ...)



Use "flatten()" and "unflatten()" to flatten and unflatten dimensions,
respectively. These methods are more verbose than "view()" and
"reshape()", but have more semantic meaning to someone reading the
code.



imgs = torch.randn(32, 3, 128, 128)
named_imgs = imgs.refine_names('N', 'C', 'H', 'W')
flat_imgs = imgs.view(32, -1)
named_flat_imgs = named_imgs.flatten(['C', 'H', 'W'], 'features')
named_flat_imgs.names
   ('N', 'features')
unflattened_imgs = imgs.view(32, 3, 128, 128)
unflattened_named_imgs = named_flat_imgs.unflatten(
           'features', [('C', 3), ('H', 128), ('W', 128)])



Autograd support
Autograd currently supports named tensors in a limited manner:
autograd ignores names on all tensors. Gradient computation is still
correct but we lose the safety that names give us.



x = torch.randn(3, names=('D',))
weight = torch.randn(3, names=('D',), requires_grad=True)
loss = (x - weight).abs()
grad_loss = torch.randn(3)
loss.backward(grad_loss)
weight.grad  # Unnamed for now. Will be named in the future
   tensor([-1.8107, -0.6357,  0.0783])
weight.grad.zero_()
grad_loss = grad_loss.refine_names('C')
loss = (x - weight).abs()
   # Ideally we'd check that the names of loss and grad_loss match but we don't yet.
loss.backward(grad_loss)
weight.grad
loss.backward(grad_loss)
weight.grad
   tensor([-1.8107, -0.6357,  0.0783])



Currently supported operations and subsystems
Operators
See Named Tensors operator coverage for a full list of the supported
torch and tensor operations. We do not yet support the following that
is not covered by the link:

indexing, advanced indexing.

For "torch.nn.functional" operators, we support the following:


"torch.nn.functional.relu()"


"torch.nn.functional.softmax()"


"torch.nn.functional.log_softmax()"


"torch.nn.functional.tanh()"


"torch.nn.functional.sigmoid()"


"torch.nn.functional.dropout()"


Subsystems
Autograd is supported, see Autograd support. Because gradients are
currently unnamed, optimizers may work but are untested.
NN modules are currently unsupported. This can lead to the following
when calling modules with named tensor inputs:

NN module parameters are unnamed, so outputs may be partially named.
NN module forward passes have code that don't support named tensors
  and will error out appropriately.

We also do not support the following subsystems, though some may work
out of the box:


distributions


serialization ("torch.load()", "torch.save()")


multiprocessing


JIT


distributed


ONNX


If any of these would help your use case, please search if an issue
has already been filed and if not, file one.
Named tensor API reference
In this section please find the documentation for named tensor
specific APIs. For a comprehensive reference for how names are
propagated through other PyTorch operators, see Named Tensors operator
coverage.
class torch.Tensor
names
  Stores names for each of this tensor's dimensions.

  "names[idx]" corresponds to the name of tensor dimension "idx".
  Names are either a string if the dimension is named or "None" if
  the dimension is unnamed.

  Dimension names may contain characters or underscore.
Furthermore, a dimension name must be a valid Python variable
      name (i.e., does not start with underscore).
  Tensors may not have two named dimensions with the same name.

  Warning:

    The named tensor API is experimental and subject to change.

rename(names, *rename_map)
  Renames dimension names of "self".

  There are two main usages:

  "self.rename(**rename_map)" returns a view on tensor that has
  dims renamed as specified in the mapping "rename_map".

  "self.rename(*names)" returns a view on tensor, renaming all
  dimensions positionally using "names". Use "self.rename(None)"
  to drop names on a tensor.

  One cannot specify both positional args "names" and keyword args
  "rename_map".

  Examples:

     >>> imgs = torch.rand(2, 3, 5, 7, names=('N', 'C', 'H', 'W'))
     >>> renamed_imgs = imgs.rename(N='batch', C='channels')
     >>> renamed_imgs.names
     ('batch', 'channels', 'H', 'W')
('batch', 'channels', 'H', 'W')
     >>> renamed_imgs = imgs.rename(None)
     >>> renamed_imgs.names
     (None, None, None, None)

     >>> renamed_imgs = imgs.rename('batch', 'channel', 'height', 'width')
     >>> renamed_imgs.names
     ('batch', 'channel', 'height', 'width')

  Warning:

    The named tensor API is experimental and subject to change.

rename_(names, *rename_map)
  In-place version of "rename()".

refine_names(*names)
  Refines the dimension names of "self" according to "names".

  Refining is a special case of renaming that "lifts" unnamed
  dimensions. A "None" dim can be refined to have any name; a
  named dim can only be refined to have the same name.

  Because named tensors can coexist with unnamed tensors, refining
  names gives a nice way to write named-tensor-aware code that
  works with both named and unnamed tensors.

  "names" may contain up to one Ellipsis ("..."). The Ellipsis is
expanded greedily; it is expanded in-place to fill "names" to
      the same length as "self.dim()" using names from the
      corresponding indices of "self.names".
  Python 2 does not support Ellipsis but one may use a string
  literal instead ("'...'").

  Parameters:
     **names** (*iterable of str*) -- The desired names of the
     output tensor. May contain up to one Ellipsis.

  Examples:

     >>> imgs = torch.randn(32, 3, 128, 128)
     >>> named_imgs = imgs.refine_names('N', 'C', 'H', 'W')
     >>> named_imgs.names
     ('N', 'C', 'H', 'W')

     >>> tensor = torch.randn(2, 3, 5, 7, 11)
     >>> tensor = tensor.refine_names('A', ..., 'B', 'C')
     >>> tensor.names
     ('A', None, None, 'B', 'C')

  Warning:

    The named tensor API is experimental and subject to change.

align_as(other) -> Tensor
  Permutes the dimensions of the "self" tensor to match the
dimension order in the "other" tensor, adding size-one dims for
      any new names.
  This operation is useful for explicit broadcasting by names (see
  examples).

  All of the dims of "self" must be named in order to use this
  method. The resulting tensor is a view on the original tensor.

  All dimension names of "self" must be present in "other.names".
  "other" may contain named dimensions that are not in
  "self.names"; the output tensor has a size-one dimension for
  each of those new names.

  To align a tensor to a specific order, use "align_to()".

  Examples:

     # Example 1: Applying a mask
     >>> mask = torch.randint(2, [127, 128], dtype=torch.bool).refine_names('W', 'H')
     >>> imgs = torch.randn(32, 128, 127, 3, names=('N', 'H', 'W', 'C'))
     >>> imgs.masked_fill_(mask.align_as(imgs), 0)


     # Example 2: Applying a per-channel-scale
     >>> def scale_channels(input, scale):
def scale_channels(input, scale):
         >>>    scale = scale.refine_names('C')
         >>>    return input * scale.align_as(input)



     >>> num_channels = 3
     >>> scale = torch.randn(num_channels, names=('C',))
     >>> imgs = torch.rand(32, 128, 128, num_channels, names=('N', 'H', 'W', 'C'))
     >>> more_imgs = torch.rand(32, num_channels, 128, 128, names=('N', 'C', 'H', 'W'))
     >>> videos = torch.randn(3, num_channels, 128, 128, 128, names=('N', 'C', 'H', 'W', 'D'))

     # scale_channels is agnostic to the dimension order of the input
     >>> scale_channels(imgs, scale)
     >>> scale_channels(more_imgs, scale)
     >>> scale_channels(videos, scale)

  Warning:

    The named tensor API is experimental and subject to change.

align_to(*names)
  Permutes the dimensions of the "self" tensor to match the order
  specified in "names", adding size-one dims for any new names.
All of the dims of "self" must be named in order to use this
      method. The resulting tensor is a view on the original tensor.
  All dimension names of "self" must be present in "names".
  "names" may contain additional names that are not in
  "self.names"; the output tensor has a size-one dimension for
  each of those new names.

  "names" may contain up to one Ellipsis ("..."). The Ellipsis is
  expanded to be equal to all dimension names of "self" that are
  not mentioned in "names", in the order that they appear in
  "self".

  Python 2 does not support Ellipsis but one may use a string
  literal instead ("'...'").

  Parameters:
     **names** (*iterable of str*) -- The desired dimension
     ordering of the output tensor. May contain up to one Ellipsis
     that is expanded to all unmentioned dim names of "self".

  Examples:

     >>> tensor = torch.randn(2, 2, 2, 2, 2, 2)
named_tensor = tensor.refine_names('A', 'B', 'C', 'D', 'E', 'F')



     # Move the F and E dims to the front while keeping the rest in order
     >>> named_tensor.align_to('F', 'E', ...)

  Warning:

    The named tensor API is experimental and subject to change.

flatten(dims, out_dim) -> Tensor
  Flattens "dims" into a single dimension with name "out_dim".

  All of *dims* must be consecutive in order in the "self" tensor,
  but not necessary contiguous in memory.

  Examples:

     >>> imgs = torch.randn(32, 3, 128, 128, names=('N', 'C', 'H', 'W'))
     >>> flat_imgs = imgs.flatten(['C', 'H', 'W'], 'features')
     >>> flat_imgs.names, flat_imgs.shape
     (('N', 'features'), torch.Size([32, 49152]))

  Warning:

    The named tensor API is experimental and subject to change.
torch.futures
This package provides a "Future" type that encapsulates an
asynchronous execution and a set of utility functions to simplify
operations on "Future" objects. Currently, the "Future" type is
primarily used by the Distributed RPC Framework.
class torch.futures.Future(*, devices=None)
Wrapper around a "torch._C.Future" which encapsulates an
   asynchronous execution of a callable, e.g. "rpc_async()". It also
   exposes a set of APIs to add callback functions and set results.
Warning:
 GPU support is a beta feature, subject to changes.

add_done_callback(callback)
  Append the given callback function to this "Future", which will
  be run when the "Future" is completed.  Multiple callbacks can
  be added to the same "Future", but the order in which they will
  be executed cannot be guaranteed. The callback must take one
  argument, which is the reference to this "Future". The callback
function can use the "value()" method to get the value. Note
      that if this "Future" is already completed, the given callback
      will be run inline.
  We recommend that you use the "then()" method as it provides a
  way to synchronize after your callback has completed.
  "add_done_callback" can be cheaper if your callback does not
  return anything. But both "then()" and "add_done_callback" use
  the same callback registration API under the hood.

  With respect to GPU tensors, this method behaves in the same way
  as "then()".

  Parameters:
     **callback** ("Future") -- a "Callable" that takes in one
     argument, which is the reference to this "Future".

  Note:

    Note that if the callback function throws, either through the
    original future being completed with an exception and calling
    "fut.wait()", or through other code in the callback, error
    handling must be carefully taken care of. For example, if this
callback later completes additional futures, those futures are
        not marked as completed with an error and the user is
        responsible for handling completion/waiting on those futures
        independently.
  Example::
     >>> def callback(fut):
     ...     print("This will run after the future has finished.")
     ...     print(fut.wait())
     >>> fut = torch.futures.Future()
     >>> fut.add_done_callback(callback)
     >>> fut.set_result(5)
     This will run after the future has finished.
     5

done()
  Return "True" if this "Future" is done. A "Future" is done if it
  has a result or an exception.

  If the value contains tensors that reside on GPUs,
  "Future.done()" will return "True" even if the asynchronous
  kernels that are populating those tensors haven't yet completed
  running on the device, because at such stage the result is
  already usable, provided one performs the appropriate
synchronizations (see "wait()").
  Return type:
     bool

set_exception(result)
  Set an exception for this "Future", which will mark this
  "Future" as completed with an error and trigger all attached
  callbacks. Note that when calling wait()/value() on this
  "Future", the exception set here will be raised inline.

  Parameters:
     **result** (*BaseException*) -- the exception for this
     "Future".

  Example::
     >>> fut = torch.futures.Future()
     >>> fut.set_exception(ValueError("foo"))
     >>> fut.wait()
     Traceback (most recent call last):
     ...
     ValueError: foo

set_result(result)
  Set the result for this "Future", which will mark this "Future"
  as completed and trigger all attached callbacks. Note that a
  "Future" cannot be marked completed twice.

  If the result contains tensors that reside on GPUs, this method
  can be called even if the asynchronous kernels that are
populating those tensors haven't yet completed running on the
      device, provided that the streams on which those kernels were
      enqueued are set as the current ones when this method is called.
      Put simply, it's safe to call this method immediately after
      launching those kernels, without any additional synchronization,
      as long as one doesn't change streams in between. This method
      will record events on all the relevant current streams and will
      use them to ensure proper scheduling for all the consumers of
      this "Future".
  Parameters:
     **result** (*object*) -- the result object of this "Future".

  Example::
     >>> import threading
     >>> import time
     >>> def slow_set_future(fut, value):
     ...     time.sleep(0.5)
     ...     fut.set_result(value)
     >>> fut = torch.futures.Future()
     >>> t = threading.Thread(
     ...     target=slow_set_future,
     ...     args=(fut, torch.ones(2) * 3)
...     args=(fut, torch.ones(2) * 3)
         ... )
         >>> t.start()
         >>> print(fut.wait())
         tensor([3., 3.])
         >>> t.join()
then(callback)
  Append the given callback function to this "Future", which will
  be run when the "Future" is completed.  Multiple callbacks can
  be added to the same "Future", but the order in which they will
  be executed cannot be guaranteed (to enforce a certain order
  consider chaining: "fut.then(cb1).then(cb2)"). The callback must
  take one argument, which is the reference to this "Future". The
  callback function can use the "value()" method to get the value.
  Note that if this "Future" is already completed, the given
  callback will be run immediately inline.

  If the "Future"'s value contains tensors that reside on GPUs,
  the callback might be invoked while the async kernels that are
  populating those tensors haven't yet finished executing on the
device. However, the callback will be invoked with some
      dedicated streams set as current (fetched from a global pool)
      which will be synchronized with those kernels. Hence any
      operation performed by the callback on these tensors will be
      scheduled on the device after the kernels complete. In other
      words, as long as the callback doesn't switch streams, it can
      safely manipulate the result without any additional
      synchronization. This is similar to the non-blocking behavior of
      "wait()".
  Similarly, if the callback returns a value that contains tensors
  that reside on a GPU, it can do so even if the kernels that are
  producing these tensors are still running on the device, as long
  as the callback didn't change streams during its execution. If
  one wants to change streams, one must be careful to re-
  synchronize them with the original streams, that is, those that
  were current when the callback was invoked.

  Parameters:
Parameters:
         callback ("Callable") -- a "Callable" that takes this
         "Future" as the only argument.
  Returns:
     A new "Future" object that holds the return value of the
     "callback" and will be marked as completed when the given
     "callback" finishes.

  Return type:
     *Future*[*S*]

  Note:

    Note that if the callback function throws, either through the
    original future being completed with an exception and calling
    "fut.wait()", or through other code in the callback, the
    future returned by "then" will be marked appropriately with
    the encountered error. However, if this callback later
    completes additional futures, those futures are not marked as
    completed with an error and the user is responsible for
    handling completion/waiting on those futures independently.

  Example::
     >>> def callback(fut):
     ...     print(f"RPC return value is {fut.wait()}.")