shrinath-suresh/blogs-docs-splitted huggingface dataset:
\text{in_channels} , iT , iH , iW)
  * **weight** -- filters of shape (\text{out\_channels} ,
    \frac{\text{in\_channels}}{\text{groups}} , kT , kH , kW)

  * **bias** -- optional bias tensor of shape
    (\text{out\_channels}). Default: None

  * **stride** -- the stride of the convolving kernel. Can be a
    single number or a tuple *(sT, sH, sW)*. Default: 1

  * **padding** --

    implicit paddings on both sides of the input. Can be a string
    {'valid', 'same'}, single number or a tuple *(padT, padH,
    padW)*. Default: 0 "padding='valid'" is the same as no
    padding. "padding='same'" pads the input so the output has the
    same shape as the input. However, this mode doesn't support
    any stride values other than 1.

    Warning:

      For "padding='same'", if the "weight" is even-length and
      "dilation" is odd in any dimension, a full "pad()" operation
      may be needed internally. Lowering performance.
dilation -- the spacing between kernel elements. Can be a
        single number or a tuple (dT, dH, dW). Default: 1

groups -- split input into groups, \text{in_channels}
    should be divisible by the number of groups. Default: 1



Examples:
  >>> filters = torch.randn(33, 16, 3, 3, 3)
  >>> inputs = torch.randn(20, 16, 50, 10, 20)
  >>> F.conv3d(inputs, filters)
ReplicationPad3d
class torch.nn.ReplicationPad3d(padding)
Pads the input tensor using replication of the input boundary.
For N-dimensional padding, use "torch.nn.functional.pad()".
Parameters:
      padding (int, tuple) -- the size of the padding. If is
      int, uses the same padding in all boundaries. If a 6-tuple,
      uses (\text{padding_left}, \text{padding_right},
      \text{padding_top}, \text{padding_bottom},
      \text{padding_front}, \text{padding_back})
Shape:
      * Input: (N, C, D_{in}, H_{in}, W_{in}) or (C, D_{in}, H_{in},
        W_{in}).
  * Output: (N, C, D_{out}, H_{out}, W_{out}) or (C, D_{out},
    H_{out}, W_{out}), where

    D_{out} = D_{in} + \text{padding\_front} +
    \text{padding\_back}

    H_{out} = H_{in} + \text{padding\_top} +
    \text{padding\_bottom}

    W_{out} = W_{in} + \text{padding\_left} +
    \text{padding\_right}

Examples:
  >>> m = nn.ReplicationPad3d(3)
Examples:
  >>> m = nn.ReplicationPad3d(3)
  >>> input = torch.randn(16, 3, 8, 320, 480)
  >>> output = m(input)
  >>> # using different paddings for different sides
  >>> m = nn.ReplicationPad3d((3, 3, 6, 6, 1, 1))
  >>> output = m(input)
torch.nn.functional.gaussian_nll_loss
torch.nn.functional.gaussian_nll_loss(input, target, var, full=False, eps=1e-06, reduction='mean')
Gaussian negative log likelihood loss.
See "GaussianNLLLoss" for details.
Parameters:
      * input (Tensor) -- expectation of the Gaussian
        distribution.
  * **target** (*Tensor*) -- sample from the Gaussian
    distribution.

  * **var** (*Tensor*) -- tensor of positive variance(s), one for
    each of the expectations in the input (heteroscedastic), or a
    single one (homoscedastic).

  * **full** (*bool**, **optional*) -- include the constant term
    in the loss calculation. Default: "False".

  * **eps** (*float**, **optional*) -- value added to var, for
    stability. Default: 1e-6.

  * **reduction** (*str**, **optional*) -- specifies the reduction
    to apply to the output: "'none'" | "'mean'" | "'sum'".
torch.frac
torch.frac(input, *, out=None) -> Tensor
Computes the fractional portion of each element in "input".
  \text{out}_{i} = \text{input}_{i} - \left\lfloor
  |\text{input}_{i}| \right\rfloor *
  \operatorname{sgn}(\text{input}_{i})

Example:
  >>> torch.frac(torch.tensor([1, 2.5, -3.2]))
  tensor([ 0.0000,  0.5000, -0.2000])
adaptive_avg_pool3d
class torch.ao.nn.quantized.functional.adaptive_avg_pool3d(input, output_size)
Applies a 3D adaptive average pooling over a quantized input signal
   composed of several quantized input planes.
Note:
 The input quantization parameters propagate to the output.

See "AdaptiveAvgPool3d" for details and output shape.
Parameters:
      output_size (None) -- the target output size (single
      integer or double-integer tuple)
Return type:
      Tensor
torch.fft.rfftfreq
torch.fft.rfftfreq(n, d=1.0, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor
Computes the sample frequencies for "rfft()" with a signal of size
   "n".
Note:
 "rfft()" returns Hermitian one-sided output, so only the positive
 frequency terms are returned. For a real FFT of length "n" and
 with inputs spaced in length unit "d", the frequencies are:

    f = torch.arange((n + 1) // 2) / (d * n)

Note:
 For even lengths, the Nyquist frequency at "f[n/2]" can be
 thought of as either negative or positive. Unlike "fftfreq()",
 "rfftfreq()" always returns it as positive.

Parameters:
      * n (int) -- the real FFT length
  * **d** (*float**, **optional*) -- The sampling length scale.
    The spacing between individual samples of the FFT input. The
    default assumes unit spacing, dividing that result by the
actual spacing gives the result in physical frequency units.
Keyword Arguments:
      * out (Tensor, optional) -- the output tensor.
  * **dtype** ("torch.dtype", optional) -- the desired data type
    of returned tensor. Default: if "None", uses a global default
    (see "torch.set_default_tensor_type()").

  * **layout** ("torch.layout", optional) -- the desired layout of
    returned Tensor. Default: "torch.strided".

  * **device** ("torch.device", optional) -- the desired device of
    returned tensor. Default: if "None", uses the current device
    for the default tensor type (see
    "torch.set_default_tensor_type()"). "device" will be the CPU
    for CPU tensor types and the current CUDA device for CUDA
    tensor types.

  * **requires_grad** (*bool**, **optional*) -- If autograd should
    record operations on the returned tensor. Default: "False".

-[ Example ]-



torch.fft.rfftfreq(5)
-[ Example ]-



torch.fft.rfftfreq(5)
   tensor([0.0000, 0.2000, 0.4000])
torch.fft.rfftfreq(4)
   tensor([0.0000, 0.2500, 0.5000])



Compared to the output from "fftfreq()", we see that the Nyquist
   frequency at "f[2]" has changed sign: >>> torch.fft.fftfreq(4)
   tensor([ 0.0000,  0.2500, -0.5000, -0.2500])
GRU
class torch.nn.GRU(args, *kwargs)
Applies a multi-layer gated recurrent unit (GRU) RNN to an input
   sequence.
For each element in the input sequence, each layer computes the
   following function:
  \begin{array}{ll}     r_t = \sigma(W_{ir} x_t + b_{ir} + W_{hr}
  h_{(t-1)} + b_{hr}) \\     z_t = \sigma(W_{iz} x_t + b_{iz} +
  W_{hz} h_{(t-1)} + b_{hz}) \\     n_t = \tanh(W_{in} x_t +
  b_{in} + r_t * (W_{hn} h_{(t-1)}+ b_{hn})) \\     h_t = (1 -
  z_t) * n_t + z_t * h_{(t-1)} \end{array}

where h_t is the hidden state at time t, x_t is the input at time
   t, h_{(t-1)} is the hidden state of the layer at time t-1 or
   the initial hidden state at time 0, and r_t, z_t, n_t are the
   reset, update, and new gates, respectively. \sigma is the sigmoid
   function, and * is the Hadamard product.
In a multilayer GRU, the input x^{(l)}_t of the l -th layer (l >=
   2) is the hidden state h^{(l-1)}_t of the previous layer multiplied
by dropout \delta^{(l-1)}_t where each \delta^{(l-1)}_t is a
   Bernoulli random variable which is 0 with probability "dropout".
Parameters:
      * input_size -- The number of expected features in the input
        x
  * **hidden_size** -- The number of features in the hidden state
    *h*

  * **num_layers** -- Number of recurrent layers. E.g., setting
    "num_layers=2" would mean stacking two GRUs together to form a
    *stacked GRU*, with the second GRU taking in outputs of the
    first GRU and computing the final results. Default: 1

  * **bias** -- If "False", then the layer does not use bias
    weights *b_ih* and *b_hh*. Default: "True"

  * **batch_first** -- If "True", then the input and output
    tensors are provided as *(batch, seq, feature)* instead of
    *(seq, batch, feature)*. Note that this does not apply to
    hidden or cell states. See the Inputs/Outputs sections below
    for details.  Default: "False"
for details.  Default: "False"
  * **dropout** -- If non-zero, introduces a *Dropout* layer on
    the outputs of each GRU layer except the last layer, with
    dropout probability equal to "dropout". Default: 0

  * **bidirectional** -- If "True", becomes a bidirectional GRU.
    Default: "False"

Inputs: input, h_0
      * input: tensor of shape (L, H_{in}) for unbatched input,
        (L, N, H_{in}) when "batch_first=False" or (N, L, H_{in}) when
        "batch_first=True" containing the features of the input
        sequence.  The input can also be a packed variable length
        sequence. See "torch.nn.utils.rnn.pack_padded_sequence()" or
        "torch.nn.utils.rnn.pack_sequence()" for details.
  * **h_0**: tensor of shape (D * \text{num\_layers}, H_{out}) or
    (D * \text{num\_layers}, N, H_{out}) containing the initial
    hidden state for the input sequence. Defaults to zeros if not
    provided.

  where:
provided.
  where:

     \begin{aligned}     N ={} & \text{batch size} \\     L ={} &
     \text{sequence length} \\     D ={} & 2 \text{ if
     bidirectional=True otherwise } 1 \\     H_{in} ={} &
     \text{input\_size} \\     H_{out} ={} & \text{hidden\_size}
     \end{aligned}

Outputs: output, h_n
      * output: tensor of shape (L, D * H_{out}) for unbatched
        input, (L, N, D * H_{out}) when "batch_first=False" or (N, L,
        D * H_{out}) when "batch_first=True" containing the output
        features (h_t) from the last layer of the GRU, for each t.
        If a "torch.nn.utils.rnn.PackedSequence" has been given as the
        input, the output will also be a packed sequence.
  * **h_n**: tensor of shape (D * \text{num\_layers}, H_{out}) or
    (D * \text{num\_layers}, N, H_{out}) containing the final
    hidden state for the input sequence.

Variables:
      * weight_ih_l[k] -- the learnable input-hidden weights of
the \text{k}^{th} layer (W_ir|W_iz|W_in), of shape
        (3hidden_size, input_size) for k = 0. Otherwise, the
        shape is (3hidden_size, num_directions * hidden_size)
  * **weight_hh_l[k]** -- the learnable hidden-hidden weights of
    the \text{k}^{th} layer (W_hr|W_hz|W_hn), of shape
    *(3*hidden_size, hidden_size)*

  * **bias_ih_l[k]** -- the learnable input-hidden bias of the
    \text{k}^{th} layer (b_ir|b_iz|b_in), of shape
    *(3*hidden_size)*

  * **bias_hh_l[k]** -- the learnable hidden-hidden bias of the
    \text{k}^{th} layer (b_hr|b_hz|b_hn), of shape
    *(3*hidden_size)*

Note:
 All the weights and biases are initialized from
 \mathcal{U}(-\sqrt{k}, \sqrt{k}) where k =
 \frac{1}{\text{hidden\_size}}

Note:
 For bidirectional GRUs, forward and backward are directions 0 and
 1 respectively. Example of splitting the output layers when
 "batch_first=False": "output.view(seq_len, batch, num_directions,
hidden_size)".
Note:
 "batch_first" argument is ignored for unbatched inputs.

Note:
 If the following conditions are satisfied: 1) cudnn is enabled,
 2) input data is on the GPU 3) input data has dtype
 "torch.float16" 4) V100 GPU is used, 5) input data is not in
 "PackedSequence" format persistent algorithm can be selected to
 improve performance.

Examples:
  >>> rnn = nn.GRU(10, 20, 2)
  >>> input = torch.randn(5, 3, 10)
  >>> h0 = torch.randn(2, 3, 20)
  >>> output, hn = rnn(input, h0)
SequentialLR
class torch.optim.lr_scheduler.SequentialLR(optimizer, schedulers, milestones, last_epoch=- 1, verbose=False)
Receives the list of schedulers that is expected to be called
   sequentially during optimization process and milestone points that
   provides exact intervals to reflect which scheduler is supposed to
   be called at a given epoch.
Parameters:
      * optimizer (Optimizer) -- Wrapped optimizer.
  * **schedulers** (*list*) -- List of chained schedulers.

  * **milestones** (*list*) -- List of integers that reflects
    milestone points.

  * **last_epoch** (*int*) -- The index of last epoch. Default:
    -1.

  * **verbose** (*bool*) -- Does nothing.

-[ Example ]-



Assuming optimizer uses lr = 1. for all groups
lr = 0.1     if epoch == 0
lr = 0.1     if epoch == 1
lr = 0.9     if epoch == 2
lr = 0.81    if epoch == 3
lr = 0.729   if epoch == 4
lr = 0.729   if epoch == 4
scheduler1 = ConstantLR(self.opt, factor=0.1, total_iters=2)
scheduler2 = ExponentialLR(self.opt, gamma=0.9)
scheduler = SequentialLR(self.opt, schedulers=[scheduler1, scheduler2], milestones=[2])
for epoch in range(100):
    train(...)
    validate(...)
    scheduler.step()



get_last_lr()
  Return last computed learning rate by current scheduler.

load_state_dict(state_dict)
  Loads the schedulers state.

  Parameters:
     **state_dict** (*dict*) -- scheduler state. Should be an
     object returned from a call to "state_dict()".

print_lr(is_verbose, group, lr, epoch=None)
  Display the current learning rate.

state_dict()
  Returns the state of the scheduler as a "dict".

  It contains an entry for every variable in self.__dict__ which
  is not the optimizer. The wrapped scheduler states will also be
  saved.
torch.floor_divide
torch.floor_divide(input, other, *, out=None) -> Tensor
Note:
 Before PyTorch 1.13 "torch.floor_divide()" incorrectly performed
 truncation division. To restore the previous behavior use
 "torch.div()" with "rounding_mode='trunc'".

Computes "input" divided by "other", elementwise, and floors the
   result.
  \text{{out}}_i = \text{floor} \left(
  \frac{{\text{{input}}_i}}{{\text{{other}}_i}} \right)

Supports broadcasting to a common shape, type promotion, and
   integer and float inputs.
Parameters:
      * input (Tensor or Number) -- the dividend
  * **other** (*Tensor** or **Number*) -- the divisor

Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
Example:
  >>> a = torch.tensor([4.0, 3.0])
  >>> b = torch.tensor([2.0, 2.0])
  >>> torch.floor_divide(a, b)
  tensor([2.0, 1.0])
  >>> torch.floor_divide(a, 1.4)
  tensor([2.0, 2.0])
prepare_qat_fx
class torch.quantization.quantize_fx.prepare_qat_fx(model, qconfig_mapping, example_inputs, prepare_custom_config=None, backend_config=None)
Prepare a model for quantization aware training
Parameters:
      * model (***) -- torch.nn.Module model
  * **qconfig_mapping** (***) -- see "prepare_fx()"

  * **example_inputs** (***) -- see "prepare_fx()"

  * **prepare_custom_config** (***) -- see "prepare_fx()"

  * **backend_config** (***) -- see "prepare_fx()"

Returns:
      A GraphModule with fake quant modules (configured by
      qconfig_mapping and backend_config), ready for quantization
      aware training
Return type:
      ObservedGraphModule
Example:
  import torch
  from torch.ao.quantization import get_default_qat_qconfig_mapping
  from torch.ao.quantization import prepare_fx

  class Submodule(torch.nn.Module):
      def __init__(self):
          super().__init__()
super().init()
              self.linear = torch.nn.Linear(5, 5)
          def forward(self, x):
              x = self.linear(x)
              return x
  class M(torch.nn.Module):
      def __init__(self):
          super().__init__()
          self.linear = torch.nn.Linear(5, 5)
          self.sub = Submodule()

      def forward(self, x):
          x = self.linear(x)
          x = self.sub(x) + x
          return x

  # initialize a floating point model
  float_model = M().train()
  # (optional, but preferred) load the weights from pretrained model
  # float_model.load_weights(...)

  # define the training loop for quantization aware training
  def train_loop(model, train_data):
      model.train()
      for image, target in data_loader:
          ...

  # qconfig is the configuration for how we insert observers for a particular
  # operator
  # qconfig = get_default_qconfig("fbgemm")
qconfig = get_default_qconfig("fbgemm")
  # Example of customizing qconfig:
  # qconfig = torch.ao.quantization.QConfig(
  #    activation=FakeQuantize.with_args(observer=MinMaxObserver.with_args(dtype=torch.qint8)),
  #    weight=FakeQuantize.with_args(observer=MinMaxObserver.with_args(dtype=torch.qint8)))
  # `activation` and `weight` are constructors of observer module

  # qconfig_mapping is a collection of quantization configurations, user can
  # set the qconfig for each operator (torch op calls, functional calls, module calls)
  # in the model through qconfig_mapping
  # the following call will get the qconfig_mapping that works best for models
  # that target "fbgemm" backend
  qconfig_mapping = get_default_qat_qconfig("fbgemm")

  # We can customize qconfig_mapping in different ways, please take a look at
  # the docstring for :func:`~torch.ao.quantization.prepare_fx` for different ways
  # to configure this
to configure this
  # example_inputs is a tuple of inputs, that is used to infer the type of the
  # outputs in the model
  # currently it's not used, but please make sure model(*example_inputs) runs
  example_inputs = (torch.randn(1, 3, 224, 224),)

  # TODO: add backend_config after we split the backend_config for fbgemm and qnnpack
  # e.g. backend_config = get_default_backend_config("fbgemm")
  # `prepare_qat_fx` inserts observers in the model based on qconfig_mapping and
  # backend_config, if the configuration for an operator in qconfig_mapping
  # is supported in the backend_config (meaning it's supported by the target
  # hardware), we'll insert fake_quantize modules according to the qconfig_mapping
  # otherwise the configuration in qconfig_mapping will be ignored
  # see :func:`~torch.ao.quantization.prepare_fx` for a detailed explanation of
  # how qconfig_mapping interacts with backend_config
BNReLU3d
class torch.ao.nn.intrinsic.quantized.BNReLU3d(num_features, eps=1e-05, momentum=0.1, device=None, dtype=None)
A BNReLU3d module is a fused module of BatchNorm3d and ReLU
We adopt the same interface as "torch.ao.nn.quantized.BatchNorm3d".
Variables:
      torch.ao.nn.quantized.BatchNorm3d (Same as) --
torch.normal
torch.normal(mean, std, *, generator=None, out=None) -> Tensor
Returns a tensor of random numbers drawn from separate normal
   distributions whose mean and standard deviation are given.
The "mean" is a tensor with the mean of each output element's
   normal distribution
The "std" is a tensor with the standard deviation of each output
   element's normal distribution
The shapes of "mean" and "std" don't need to match, but the total
   number of elements in each tensor need to be the same.
Note:
 When the shapes do not match, the shape of "mean" is used as the
 shape for the returned output tensor

Note:
 When "std" is a CUDA tensor, this function synchronizes its
 device with the CPU.

Parameters:
      * mean (Tensor) -- the tensor of per-element means
  * **std** (*Tensor*) -- the tensor of per-element standard
    deviations

Keyword Arguments:
      * generator ("torch.Generator", optional) -- a pseudorandom
number generator for sampling
  * **out** (*Tensor**, **optional*) -- the output tensor.

Example:
  >>> torch.normal(mean=torch.arange(1., 11.), std=torch.arange(1, 0, -0.1))
  tensor([  1.0425,   3.5672,   2.7969,   4.2925,   4.7229,   6.2134,
            8.0505,   8.1408,   9.0563,  10.0566])

torch.normal(mean=0.0, std, *, out=None) -> Tensor
Similar to the function above, but the means are shared among all
   drawn elements.
Parameters:
      * mean (float, optional) -- the mean for all
        distributions
  * **std** (*Tensor*) -- the tensor of per-element standard
    deviations

Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
Example:
  >>> torch.normal(mean=0.5, std=torch.arange(1., 6.))
  tensor([-1.2793, -1.0732, -2.0687,  5.1177, -1.2303])

torch.normal(mean, std=1.0, *, out=None) -> Tensor
Similar to the function above, but the standard deviations are
shared among all drawn elements.
Parameters:
      * mean (Tensor) -- the tensor of per-element means
  * **std** (*float**, **optional*) -- the standard deviation for
    all distributions

Keyword Arguments:
      out (Tensor, optional) -- the output tensor
Example:
  >>> torch.normal(mean=torch.arange(1., 6.))
  tensor([ 1.1552,  2.6148,  2.6535,  5.8318,  4.2361])

torch.normal(mean, std, size, *, out=None) -> Tensor
Similar to the function above, but the means and standard
   deviations are shared among all drawn elements. The resulting
   tensor has size given by "size".
Parameters:
      * mean (float) -- the mean for all distributions
  * **std** (*float*) -- the standard deviation for all
    distributions

  * **size** (*int**...*) -- a sequence of integers defining the
    shape of the output tensor.

Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
Example:
RNNCell
class torch.ao.nn.quantized.dynamic.RNNCell(input_size, hidden_size, bias=True, nonlinearity='tanh', dtype=torch.qint8)
An Elman RNN cell with tanh or ReLU non-linearity. A dynamic
   quantized RNNCell module with floating point tensor as inputs and
   outputs. Weights are quantized to 8 bits. We adopt the same
   interface as torch.nn.RNNCell, please see
   https://pytorch.org/docs/stable/nn.html#torch.nn.RNNCell for
   documentation.
Examples:
  >>> rnn = nn.RNNCell(10, 20)
  >>> input = torch.randn(6, 3, 10)
  >>> hx = torch.randn(3, 20)
  >>> output = []
  >>> for i in range(6):
  ...     hx = rnn(input[i], hx)
  ...     output.append(hx)
torch.Tensor.cpu
Tensor.cpu(memory_format=torch.preserve_format) -> Tensor
Returns a copy of this object in CPU memory.
If this object is already in CPU memory and on the correct device,
   then no copy is performed and the original object is returned.
Parameters:
      memory_format ("torch.memory_format", optional) -- the
      desired memory format of returned Tensor. Default:
      "torch.preserve_format".
torch.select
torch.select(input, dim, index) -> Tensor
Slices the "input" tensor along the selected dimension at the given
   index. This function returns a view of the original tensor with the
   given dimension removed.
Note:
 If "input" is a sparse tensor and returning a view of the tensor
 is not possible, a RuntimeError exception is raised. In this is
 the case, consider using "torch.select_copy()" function.

Parameters:
      * input (Tensor) -- the input tensor.
  * **dim** (*int*) -- the dimension to slice

  * **index** (*int*) -- the index to select with

Note:
 "select()" is equivalent to slicing. For example,
 "tensor.select(0, index)" is equivalent to "tensor[index]" and
 "tensor.select(2, index)" is equivalent to "tensor[:,:,index]".
torch.from_dlpack
torch.from_dlpack(ext_tensor) -> Tensor
Converts a tensor from an external library into a "torch.Tensor".
The returned PyTorch tensor will share the memory with the input
   tensor (which may have come from another library). Note that in-
   place operations will therefore also affect the data of the input
   tensor. This may lead to unexpected issues (e.g., other libraries
   may have read-only flags or immutable data structures), so the user
   should only do this if they know for sure that this is fine.
Parameters:
      ext_tensor (object with "dlpack" attribute, or a DLPack
      capsule) --
  The tensor or DLPack capsule to convert.

  If "ext_tensor" is a tensor (or ndarray) object, it must support
  the "__dlpack__" protocol (i.e., have a "ext_tensor.__dlpack__"
  method). Otherwise "ext_tensor" may be a DLPack capsule, which
  is an opaque "PyCapsule" instance, typically produced by a
  "to_dlpack" function or method.
"to_dlpack" function or method.
Return type:
      Tensor
Examples:
  >>> import torch.utils.dlpack
  >>> t = torch.arange(4)

  # Convert a tensor directly (supported in PyTorch >= 1.10)
  >>> t2 = torch.from_dlpack(t)
  >>> t2[:2] = -1  # show that memory is shared
  >>> t2
  tensor([-1, -1,  2,  3])
  >>> t
  tensor([-1, -1,  2,  3])

  # The old-style DLPack usage, with an intermediate capsule object
  >>> capsule = torch.utils.dlpack.to_dlpack(t)
  >>> capsule
  <capsule object "dltensor" at ...>
  >>> t3 = torch.from_dlpack(capsule)
  >>> t3
  tensor([-1, -1,  2,  3])
  >>> t3[0] = -9  # now we're sharing memory between 3 tensors
  >>> t3
  tensor([-9, -1,  2,  3])
  >>> t2
  tensor([-9, -1,  2,  3])
  >>> t
  tensor([-9, -1,  2,  3])