shrinath-suresh/blogs-docs-splitted huggingface dataset:
BackendConfig
class torch.ao.quantization.backend_config.BackendConfig(name='')
Config that defines the set of patterns that can be quantized on a
   given backend, and how reference quantized models can be produced
   from these patterns.
A pattern in this context refers to a module, a functional, an
   operator, or a directed acyclic graph of the above. Each pattern
   supported on the target backend can be individually configured
   through "BackendPatternConfig" in terms of:


The supported input/output activation, weight, and bias data
      types


How observers and quant/dequant ops are inserted in order to
      construct the reference pattern, and


(Optionally) Fusion, QAT, and reference module mappings.


The format of the patterns is described in: https://github.com/pyt
   orch/pytorch/blob/master/torch/ao/quantization/backend_config/READ
   ME.md
Example usage:
  import torch
  from torch.ao.quantization.backend_config import (
BackendConfig,
          BackendPatternConfig,
          DTypeConfig,
          ObservationType,
      )
  weighted_int8_dtype_config = DTypeConfig(
      input_dtype=torch.quint8,
      output_dtype=torch.quint8,
      weight_dtype=torch.qint8,
      bias_dtype=torch.float)

  def fuse_conv2d_relu(is_qat, conv, relu):
      return torch.ao.nn.intrinsic.ConvReLU2d(conv, relu)

  # For quantizing Linear
  linear_config = BackendPatternConfig(torch.nn.Linear)             .set_observation_type(ObservationType.OUTPUT_USE_DIFFERENT_OBSERVER_AS_INPUT)             .add_dtype_config(weighted_int8_dtype_config)             .set_root_module(torch.nn.Linear)             .set_qat_module(torch.ao.nn.qat.Linear)             .set_reference_quantized_module(torch.ao.nn.quantized.reference.Linear)

  # For fusing Conv2d + ReLU into ConvReLU2d
For fusing Conv2d + ReLU into ConvReLU2d
  conv_relu_config = BackendPatternConfig((torch.nn.Conv2d, torch.nn.ReLU))             .set_observation_type(ObservationType.OUTPUT_USE_DIFFERENT_OBSERVER_AS_INPUT)             .add_dtype_config(weighted_int8_dtype_config)             .set_fused_module(torch.ao.nn.intrinsic.ConvReLU2d)             .set_fuser_method(fuse_conv2d_relu)

  # For quantizing ConvReLU2d
  fused_conv_relu_config = BackendPatternConfig(torch.ao.nn.intrinsic.ConvReLU2d)             .set_observation_type(ObservationType.OUTPUT_USE_DIFFERENT_OBSERVER_AS_INPUT)             .add_dtype_config(weighted_int8_dtype_config)             .set_root_module(torch.nn.Conv2d)             .set_qat_module(torch.ao.nn.intrinsic.qat.ConvReLU2d)             .set_reference_quantized_module(torch.ao.nn.quantized.reference.Conv2d)
backend_config = BackendConfig("my_backend")             .set_backend_pattern_config(linear_config)             .set_backend_pattern_config(conv_relu_config)             .set_backend_pattern_config(fused_conv_relu_config)
property configs: List[BackendPatternConfig]
  Return a copy of the list of configs set in this
  *BackendConfig*.

classmethod from_dict(backend_config_dict)
  Create a "BackendConfig" from a dictionary with the following
  items:

     "name": the name of the target backend

     "configs": a list of dictionaries that each represents a
     *BackendPatternConfig*

  Return type:
     *BackendConfig*

set_backend_pattern_config(config)
  Set the config for an pattern that can be run on the target
  backend. This overrides any existing config for the given
  pattern.

  Return type:
     *BackendConfig*

set_backend_pattern_configs(configs)
  Set the configs for patterns that can be run on the target
backend. This overrides any existing config for a given pattern
      if it was previously registered already.
  Return type:
     *BackendConfig*

set_name(name)
  Set the name of the target backend.

  Return type:
     *BackendConfig*

to_dict()
  Convert this "BackendConfig" to a dictionary with the items
  described in "from_dict()".

  Return type:
     *Dict*[str, *Any*]
torch.zeros
torch.zeros(size, , out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor
Returns a tensor filled with the scalar value 0, with the shape
   defined by the variable argument "size".
Parameters:
      size (int...) -- a sequence of integers defining the
      shape of the output tensor. Can be a variable number of
      arguments or a collection like a list or tuple.
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.zeros(2, 3)
  tensor([[ 0.,  0.,  0.],
          [ 0.,  0.,  0.]])

  >>> torch.zeros(5)
  tensor([ 0.,  0.,  0.,  0.,  0.])
torch.jit.save
torch.jit.save(m, f, _extra_files=None)
Save an offline version of this module for use in a separate
   process. The saved module serializes all of the methods,
   submodules, parameters, and attributes of this module. It can be
   loaded into the C++ API using "torch::jit::load(filename)" or into
   the Python API with "torch.jit.load".
To be able to save a module, it must not make any calls to native
   Python functions.  This means that all submodules must be
   subclasses of "ScriptModule" as well.
Danger:
 All modules, no matter their device, are always loaded onto the
 CPU during loading.  This is different from "torch.load()"'s
 semantics and may change in the future.

Parameters:
      * m -- A "ScriptModule" to save.
  * **f** -- A file-like object (has to implement write and flush)
    or a string containing a file name.

  * **_extra_files** -- Map from filename to contents which will
    be stored as part of *f*.
be stored as part of f.
Note:
 torch.jit.save attempts to preserve the behavior of some
 operators across versions. For example, dividing two integer
 tensors in PyTorch 1.5 performed floor division, and if the
 module containing that code is saved in PyTorch 1.5 and loaded in
 PyTorch 1.6 its division behavior will be preserved. The same
 module saved in PyTorch 1.6 will fail to load in PyTorch 1.5,
 however, since the behavior of division changed in 1.6, and 1.5
 does not know how to replicate the 1.6 behavior.

Example:
  import torch
  import io

  class MyModule(torch.nn.Module):
      def forward(self, x):
          return x + 10

  m = torch.jit.script(MyModule())

  # Save to file
  torch.jit.save(m, 'scriptmodule.pt')
  # This line is equivalent to the previous
  m.save("scriptmodule.pt")

  # Save to io.BytesIO buffer
  buffer = io.BytesIO()
  torch.jit.save(m, buffer)
Sequential
class torch.nn.Sequential(*args: Module)
class torch.nn.Sequential(arg: OrderedDict[str, Module])
A sequential container. Modules will be added to it in the order
   they are passed in the constructor. Alternatively, an "OrderedDict"
   of modules can be passed in. The "forward()" method of "Sequential"
   accepts any input and forwards it to the first module it contains.
   It then "chains" outputs to inputs sequentially for each subsequent
   module, finally returning the output of the last module.
The value a "Sequential" provides over manually calling a sequence
   of modules is that it allows treating the whole container as a
   single module, such that performing a transformation on the
   "Sequential" applies to each of the modules it stores (which are
   each a registered submodule of the "Sequential").
What's the difference between a "Sequential" and a
   "torch.nn.ModuleList"? A "ModuleList" is exactly what it sounds
like--a list for storing "Module" s! On the other hand, the layers
   in a "Sequential" are connected in a cascading way.
Example:
  # Using Sequential to create a small model. When `model` is run,
  # input will first be passed to `Conv2d(1,20,5)`. The output of
  # `Conv2d(1,20,5)` will be used as the input to the first
  # `ReLU`; the output of the first `ReLU` will become the input
  # for `Conv2d(20,64,5)`. Finally, the output of
  # `Conv2d(20,64,5)` will be used as input to the second `ReLU`
  model = nn.Sequential(
            nn.Conv2d(1,20,5),
            nn.ReLU(),
            nn.Conv2d(20,64,5),
            nn.ReLU()
          )

  # Using Sequential with OrderedDict. This is functionally the
  # same as the above code
  model = nn.Sequential(OrderedDict([
            ('conv1', nn.Conv2d(1,20,5)),
            ('relu1', nn.ReLU()),
            ('conv2', nn.Conv2d(20,64,5)),
            ('relu2', nn.ReLU())
torch.ones
torch.ones(size, , out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor
Returns a tensor filled with the scalar value 1, with the shape
   defined by the variable argument "size".
Parameters:
      size (int...) -- a sequence of integers defining the
      shape of the output tensor. Can be a variable number of
      arguments or a collection like a list or tuple.
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
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.ones(2, 3)
  tensor([[ 1.,  1.,  1.],
          [ 1.,  1.,  1.]])

  >>> torch.ones(5)
  tensor([ 1.,  1.,  1.,  1.,  1.])
torch.mean
torch.mean(input, *, dtype=None) -> Tensor
Returns the mean value of all elements in the "input" tensor.
Parameters:
      input (Tensor) -- the input tensor.
Keyword Arguments:
      dtype ("torch.dtype", optional) -- the desired data type of
      returned tensor. If specified, the input tensor is casted to
      "dtype" before the operation is performed. This is useful for
      preventing data type overflows. Default: None.
Example:
  >>> a = torch.randn(1, 3)
  >>> a
  tensor([[ 0.2294, -0.5481,  1.3288]])
  >>> torch.mean(a)
  tensor(0.3367)

torch.mean(input, dim, keepdim=False, *, dtype=None, out=None) -> Tensor
Returns the mean value of each row of the "input" tensor in the
   given dimension "dim". If "dim" is a list of dimensions, reduce
   over all of them.
If "keepdim" is "True", the output tensor is of the same size as
   "input" except in the dimension(s) "dim" where it is of size 1.
Otherwise, "dim" is squeezed (see "torch.squeeze()"), resulting in
   the output tensor having 1 (or "len(dim)") fewer dimension(s).
Parameters:
      * input (Tensor) -- the input tensor.
  * **dim** (*int** or **tuple of ints*) -- the dimension or
    dimensions to reduce.

  * **keepdim** (*bool*) -- whether the output tensor has "dim"
    retained or not.

Keyword Arguments:
      * dtype ("torch.dtype", optional) -- the desired data type
        of returned tensor. If specified, the input tensor is casted
        to "dtype" before the operation is performed. This is useful
        for preventing data type overflows. Default: None.
  * **out** (*Tensor**, **optional*) -- the output tensor.

See also:
 "torch.nanmean()" computes the mean value of *non-NaN* elements.

Example:
  >>> a = torch.randn(4, 4)
  >>> a
  tensor([[-0.3841,  0.6320,  0.4254, -0.7384],
          [-0.9644,  1.0131, -0.6549, -1.4279],
[-0.2951, -1.3350, -0.7694,  0.5600],
              [ 1.0842, -0.9580,  0.3623,  0.2343]])
      >>> torch.mean(a, 1)
      tensor([-0.0163, -0.5085, -0.4599,  0.1807])
      >>> torch.mean(a, 1, True)
      tensor([[-0.0163],
              [-0.5085],
              [-0.4599],
              [ 0.1807]])
torch.fft.fft
torch.fft.fft(input, n=None, dim=- 1, norm=None, *, out=None) -> Tensor
Computes the one dimensional discrete Fourier transform of "input".
Note:
 The Fourier domain representation of any real signal satisfies
 the Hermitian property: *X[i] = conj(X[-i])*. This function
 always returns both the positive and negative frequency terms
 even though, for real inputs, the negative frequencies are
 redundant. "rfft()" returns the more compact one-sided
 representation where only the positive frequencies are returned.

Note:
 Supports torch.half and torch.chalf on CUDA with GPU Architecture
 SM53 or greater. However it only supports powers of 2 signal
 length in every transformed dimension.

Parameters:
      * input (Tensor) -- the input tensor
  * **n** (*int**, **optional*) -- Signal length. If given, the
    input will either be zero-padded or trimmed to this length
    before computing the FFT.
before computing the FFT.
  * **dim** (*int**, **optional*) -- The dimension along which to
    take the one dimensional FFT.

  * **norm** (*str**, **optional*) --

    Normalization mode. For the forward transform ("fft()"), these
    correspond to:

    * ""forward"" - normalize by "1/n"

    * ""backward"" - no normalization

    * ""ortho"" - normalize by "1/sqrt(n)" (making the FFT
      orthonormal)

    Calling the backward transform ("ifft()") with the same
    normalization mode will apply an overall normalization of
    "1/n" between the two transforms. This is required to make
    "ifft()" the exact inverse.

    Default is ""backward"" (no normalization).

Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
-[ Example ]-



t = torch.arange(4)
t
   tensor([0, 1, 2, 3])
torch.fft.fft(t)
   tensor([ 6.+0.j, -2.+2.j, -2.+0.j, -2.-2.j])
tensor([ 6.+0.j, -2.+2.j, -2.+0.j, -2.-2.j])



t = torch.tensor([0.+1.j, 2.+3.j, 4.+5.j, 6.+7.j])
torch.fft.fft(t)
   tensor([12.+16.j, -8.+0.j, -4.-4.j,  0.-8.j])
torch.nn.functional.one_hot
torch.nn.functional.one_hot(tensor, num_classes=- 1) -> LongTensor
Takes LongTensor with index values of shape "()" and returns a
   tensor of shape "(, num_classes)" that have zeros everywhere
   except where the index of last dimension matches the corresponding
   value of the input tensor, in which case it will be 1.
See also One-hot on Wikipedia .
Parameters:
      * tensor (LongTensor) -- class values of any shape.
  * **num_classes** (*int*) -- Total number of classes. If set to
    -1, the number of classes will be inferred as one greater than
    the largest class value in the input tensor.

Returns:
      LongTensor that has one more dimension with 1 values at the
      index of last dimension indicated by the input, and 0 everywhere
      else.
-[ Examples ]-



F.one_hot(torch.arange(0, 5) % 3)
   tensor([[1, 0, 0],
           [0, 1, 0],
           [0, 0, 1],
           [1, 0, 0],
[0, 0, 1],
           [1, 0, 0],
           [0, 1, 0]])



F.one_hot(torch.arange(0, 5) % 3, num_classes=5)
   tensor([[1, 0, 0, 0, 0],
           [0, 1, 0, 0, 0],
           [0, 0, 1, 0, 0],
           [1, 0, 0, 0, 0],
           [0, 1, 0, 0, 0]])
F.one_hot(torch.arange(0, 6).view(3,2) % 3)
   tensor([[[1, 0, 0],
            [0, 1, 0]],
           [[0, 0, 1],
            [1, 0, 0]],
           [[0, 1, 0],
            [0, 0, 1]]])
torch.cholesky_solve
torch.cholesky_solve(input, input2, upper=False, *, out=None) -> Tensor
Solves a linear system of equations with a positive semidefinite
   matrix to be inverted given its Cholesky factor matrix u.
If "upper" is "False", u is and lower triangular and c is
   returned such that:
  c = (u u^T)^{{-1}} b

If "upper" is "True" or not provided, u is upper triangular and c
   is returned such that:
  c = (u^T u)^{{-1}} b

torch.cholesky_solve(b, u) can take in 2D inputs b, u or inputs
   that are batches of 2D matrices. If the inputs are batches, then
   returns batched outputs c
Supports real-valued and complex-valued inputs. For the complex-
   valued inputs the transpose operator above is the conjugate
   transpose.
Parameters:
      * input (Tensor) -- input matrix b of size (*, m, k),
        where * is zero or more batch dimensions
  * **input2** (*Tensor*) -- input matrix u of size (*, m, m),
where * is zero of more batch dimensions composed of upper or
        lower triangular Cholesky factor
  * **upper** (*bool**, **optional*) -- whether to consider the
    Cholesky factor as a lower or upper triangular matrix.
    Default: "False".

Keyword Arguments:
      out (Tensor, optional) -- the output tensor for c
Example:
  >>> a = torch.randn(3, 3)
  >>> a = torch.mm(a, a.t()) # make symmetric positive definite
  >>> u = torch.linalg.cholesky(a)
  >>> a
  tensor([[ 0.7747, -1.9549,  1.3086],
          [-1.9549,  6.7546, -5.4114],
          [ 1.3086, -5.4114,  4.8733]])
  >>> b = torch.randn(3, 2)
  >>> b
  tensor([[-0.6355,  0.9891],
          [ 0.1974,  1.4706],
          [-0.4115, -0.6225]])
  >>> torch.cholesky_solve(b, u)
  tensor([[ -8.1625,  19.6097],
          [ -5.8398,  14.2387],
          [ -4.3771,  10.4173]])
  >>> torch.mm(a.inverse(), b)
  tensor([[ -8.1626,  19.6097],
torch.tensor_split
torch.tensor_split(input, indices_or_sections, dim=0) -> List of Tensors
Splits a tensor into multiple sub-tensors, all of which are views
   of "input", along dimension "dim" according to the indices or
   number of sections specified by "indices_or_sections". This
   function is based on NumPy's "numpy.array_split()".
Parameters:
      * input (Tensor) -- the tensor to split
  * **indices_or_sections** (*Tensor**, **int** or **list** or
    **tuple of ints*) --

    If "indices_or_sections" is an integer "n" or a zero
    dimensional long tensor with value "n", "input" is split into
    "n" sections along dimension "dim". If "input" is divisible by
    "n" along dimension "dim", each section will be of equal size,
    "input.size(dim) / n". If "input" is not divisible by "n", the
    sizes of the first "int(input.size(dim) % n)" sections will
    have size "int(input.size(dim) / n) + 1", and the rest will
have size "int(input.size(dim) / n)".
    If "indices_or_sections" is a list or tuple of ints, or a one-
    dimensional long tensor, then "input" is split along dimension
    "dim" at each of the indices in the list, tuple or tensor. For
    instance, "indices_or_sections=[2, 3]" and "dim=0" would
    result in the tensors "input[:2]", "input[2:3]", and
    "input[3:]".

    If "indices_or_sections" is a tensor, it must be a zero-
    dimensional or one-dimensional long tensor on the CPU.

  * **dim** (*int**, **optional*) -- dimension along which to
    split the tensor. Default: "0"

Example:
  >>> x = torch.arange(8)
  >>> torch.tensor_split(x, 3)
  (tensor([0, 1, 2]), tensor([3, 4, 5]), tensor([6, 7]))

  >>> x = torch.arange(7)
  >>> torch.tensor_split(x, 3)
  (tensor([0, 1, 2]), tensor([3, 4]), tensor([5, 6]))
  >>> torch.tensor_split(x, (1, 6))
  (tensor([0]), tensor([1, 2, 3, 4, 5]), tensor([6]))
x = torch.arange(14).reshape(2, 7)
      >>> x
      tensor([[ 0,  1,  2,  3,  4,  5,  6],
              [ 7,  8,  9, 10, 11, 12, 13]])
      >>> torch.tensor_split(x, 3, dim=1)
      (tensor([[0, 1, 2],
              [7, 8, 9]]),
       tensor([[ 3,  4],
              [10, 11]]),
       tensor([[ 5,  6],
              [12, 13]]))
      >>> torch.tensor_split(x, (1, 6), dim=1)
      (tensor([[0],
              [7]]),
       tensor([[ 1,  2,  3,  4,  5],
              [ 8,  9, 10, 11, 12]]),
       tensor([[ 6],
              [13]]))
torch.fft.rfftn
torch.fft.rfftn(input, s=None, dim=None, norm=None, *, out=None) -> Tensor
Computes the N-dimensional discrete Fourier transform of real
   "input".
The FFT of a real signal is Hermitian-symmetric, "X[i_1, ..., i_n]
   = conj(X[-i_1, ..., -i_n])" so the full "fftn()" output contains
   redundant information. "rfftn()" instead omits the negative
   frequencies in the last dimension.
Note:
 Supports torch.half on CUDA with GPU Architecture SM53 or
 greater. However it only supports powers of 2 signal length in
 every transformed dimensions.

Parameters:
      * input (Tensor) -- the input tensor
  * **s** (*Tuple**[**int**]**, **optional*) -- Signal size in the
    transformed dimensions. If given, each dimension "dim[i]" will
    either be zero-padded or trimmed to the length "s[i]" before
    computing the real FFT. If a length "-1" is specified, no
    padding is done in that dimension. Default: "s =
[input.size(d) for d in dim]"
  * **dim** (*Tuple**[**int**]**, **optional*) -- Dimensions to be
    transformed. Default: all dimensions, or the last "len(s)"
    dimensions if "s" is given.

  * **norm** (*str**, **optional*) --

    Normalization mode. For the forward transform ("rfftn()"),
    these correspond to:

    * ""forward"" - normalize by "1/n"

    * ""backward"" - no normalization

    * ""ortho"" - normalize by "1/sqrt(n)" (making the real FFT
      orthonormal)

    Where "n = prod(s)" is the logical FFT size. Calling the
    backward transform ("irfftn()") with the same normalization
    mode will apply an overall normalization of "1/n" between the
    two transforms. This is required to make "irfftn()" the exact
    inverse.

    Default is ""backward"" (no normalization).

Keyword Arguments:
      out (Tensor, optional) -- the output tensor.
-[ Example ]-



t = torch.rand(10, 10)
-[ Example ]-



t = torch.rand(10, 10)
rfftn = torch.fft.rfftn(t)
rfftn.size()
   torch.Size([10, 6])



Compared against the full output from "fftn()", we have all
   elements up to the Nyquist frequency.



fftn = torch.fft.fftn(t)
torch.testing.assert_close(fftn[..., :6], rfftn, check_stride=False)



The discrete Fourier transform is separable, so "rfftn()" here is
   equivalent to a combination of "fft()" and "rfft()":



two_ffts = torch.fft.fft(torch.fft.rfft(t, dim=1), dim=0)
torch.testing.assert_close(rfftn, two_ffts, check_stride=False)
torch.randperm
torch.randperm(n, *, generator=None, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False, pin_memory=False) -> Tensor
Returns a random permutation of integers from "0" to "n - 1".
Parameters:
      n (int) -- the upper bound (exclusive)
Keyword Arguments:
      * generator ("torch.Generator", optional) -- a pseudorandom
        number generator for sampling
  * **out** (*Tensor**, **optional*) -- the output tensor.

  * **dtype** ("torch.dtype", optional) -- the desired data type
    of returned tensor. Default: "torch.int64".

  * **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".

  * **pin_memory** (*bool**, **optional*) -- If set, returned
    tensor would be allocated in the pinned memory. Works only for
    CPU tensors. Default: "False".

Example:
  >>> torch.randperm(4)
  tensor([2, 1, 0, 3])
hardswish
class torch.ao.nn.quantized.functional.hardswish(input, scale, zero_point)
This is the quantized version of "hardswish()".
Parameters:
      * input (Tensor) -- quantized input
  * **scale** (*float*) -- quantization scale of the output tensor

  * **zero_point** (*int*) -- quantization zero point of the
    output tensor

Return type:
      Tensor