SparseTensor#

class iskra.sparse.SparseTensor(tensor: Tensor, *, layout: Literal['coo', 'csr'] = 'coo', dtype: dtype | None = None, device: device | None = None, requires_grad: bool = False)[SOURCE]#

Sane PyTorch sparse tensor.

A subclass of torch.Tensor with a sparse layout, either torch.sparse_coo or torch.sparse_csr. Unlike the default PyTorch sparse tensors, it offers many quality-of-life utilities which make working with sparse tensors a bareable experience in PyTorch, such as scalar/vector/matrix multiplications which just work and ensure your gradients remain sparse (which is somehow not the default in PyTorch), indexing, slicing, as well as small helpers here and there to patch missing functionality.

Primarily, this class supports COO tensors; CSR tensor experience might be mixed.

Warning

Constructing SparseTensor with COO values will automaticall call coalesce() for you, as this is required for alias() to work.

Note

SparseTensor is not necessary to use the free-standing functions in this module. You should be able to use most free-standing functions with a normal tensor created via torch.sparse_coo_tensor. You should be careful with things like multiplications in that case (@ will create dense gradients even if the operands are both sparse matrices).

Important

You almost always want to use sp.coo_tensor() or sp.csr_tensor() to construct a SparseTensor object.

Builds upon and extends existing PyTorch sparse tensors, and offers a (somewhat) sane interface and defaults.

Implementation Details

Alright, I’ve spent a bunch of time digging through PyTorch’s subclassing mess, so I am sharing what I learned here, hoping it is useful in the future. So, our goal is to create a subclass of torch.Tensor which has sparse storage under the hood (i.e., torch.layout == torch.sparse_coo or torch.layout == torch.sparse_csr).

PyTorch’s _make_subclass() is not what we need because it terminates autograd history, meaning, e.g., it won’t propagate gradients to the values of a COO tensor if the values are autograd leafs. In essence, _make_subclass() makes a new leaf tensor with the same data as the input. This is not what we want!

PyTorch’s as_subclass() should be the fix, but fails on sparse tensors. Under the hood, it does a couple of things, see pytorch/pytorch First, it creates a view into the tensor, thus sharing memory but having a new tensor object. Second, it sets __class__ to equal the subclass class. Lastly, it enables __torch_dispatch__ on the subclass via set_python_dispatch(), but only if the subclass has a custom __torch_dispatch__ defined.

Sadly, the PyTorch does not implement aliasing for sparse tensors, but we can simply implement a Python alternative; see alias(). Likewiese, we set tensor.__class__ = SparseTensor manually from Python. The only thing we cannot do from Python is __torch_dispatch__. However, __torch_dispatch__ is only needed if you wish to override low-level tensor behavior, such as behavior during autograd or CUDA kernel calls. In our specific scenario, we are not overriding __torch_dispatch__, so we do not really need to worry about set_python_dispatch(). However, if we were, we would probably need to do somehting akin to this: albanD/subclass_zoo This implementation seems to be more complex, so we will only migrate when this is absolutely necessary.

classmethod from_coo(indices: Tensor | tuple[Tensor, ...] | list[Tensor], values: Tensor, size: Size | list[int] | tuple[int, ...] | None = None, *, dtype: dtype | None = None, device: device | None = None, requires_grad: bool = False, check_invariants: bool = False, is_coalesced: bool = True) SparseTensor[SOURCE]#

Constructs a COO SparseTensor from indices and values.

This method wraps torch.sparse_coo_tensor() and does plumbing to convert it to a SparseTensor. The argument and return descriptions reuse the original torch descriptions. See that function for more details.

See also

torch.sparse_coo_tensor(), coo_tensor().

Parameters:
  • indices (Tensor[Int64, [Dim, nnz]] | tuple[Tensor[Int64, [nnz]], …] | list[Tensor[Int64, [nnz]]]) – Initial data for the tensor. Will be cast to a Tensor[Int64, [Dim, nzz]] internally. If indices is a sequence of 1D tensors, we stack them into a index tensor. The indices are the coordinates of the non-zero values in the matrix, and thus should be two-dimensional where the first dimension is the number of tensor dimensions and the second dimension is the number of non-zero values. A sequence of per-dimension index tensors is also accepted and stacked along dimension 0.

  • values (Tensor[DType, [nnz]]) – Initial values for the tensor.

  • size (list[int] | tuple[int, ...] | None) – Size of the sparse tensor. If not provided the size will be inferred as the minimum size big enough to hold all non-zero elements.

  • dtype (torch.dtype) – the desired data type of returned tensor. Default: if None, infers data type from values.

  • device (torch.device) – the desired device of returned tensor. Default: if None, uses the device of the input tensors.

  • requires_grad (bool) – If True, the returned SparseTensor is an autograd leaf. Default: False.

  • check_invariants (bool) – If sparse tensor invariants are checked. Default: False.

  • is_coalesced (bool) – When True, the caller is responsible for providing tensor indices that correspond to a coalesced tensor. If the check_invariants flag is False, no error will be raised if the prerequisites are not met and this will lead to silently incorrect results. To force coalescion please use coalesce() on the resulting Tensor. Default: True.

Returns:

(SparseTensor[Float, [*Bs, N, M, *Ds]]) – Sparse tensor in COO layout.

classmethod from_csr(crow_indices: Tensor, col_indices: Tensor, values: Tensor, size: Size | list[int] | tuple[int, ...] | None = None, *, dtype: dtype | None = None, device: device | None = None, requires_grad: bool = False) SparseTensor[SOURCE]#

Constructs a CSR SparseTensor from compressed-row data.

This method wraps torch.sparse_csr_tensor() and does plumbing to convert it to a SparseTensor. The argument and return descriptions reuse the original torch descriptions. See that function for more details.

See also

torch.sparse_csr_tensor(), csr_tensor().

Parameters:
  • crow_indices (Tensor[Int64, [*Bs, N + 1]]) – (B+1)-dimensional array of size (*batchsize, nrows + 1). The last element of each batch is the number of non-zeros. This tensor encodes the index in values and col_indices depending on where the given row starts. Each successive number in the tensor subtracted by the number before it denotes the number of elements in a given row.

  • col_indices (Tensor[Int64, [*Bs, nnz]]) – Column co-ordinates of each element in values. (B+1)-dimensional tensor with the same length as values.

  • values (Tensor[Float, [*Bs, nnz, *Ds]]) – Initial values for the tensor. Represents a (1+K)-dimensional tensor where K is the number of dense dimensions.

  • size (list[int] | tuple[int, ...] | None) – Size of the sparse tensor: (*batchsize, nrows, ncols, *densesize). If not provided, the size will be inferred as the minimum size big enough to hold all non-zero elements.

  • dtype (torch.dtype) – the desired data type of returned tensor. Default: if None, infers data type from values.

  • device (torch.device) – the desired device of returned tensor. Default: if None, uses the device of the input tensors.

  • requires_grad (bool) – If True, the returned SparseTensor is an autograd leaf. Default: False.

Returns:

(SparseTensor[Float, [*Bs, N, M, *Ds]]) – Sparse tensor in CSR layout.

__matmul__(other: SparseTensor) SparseTensor[SOURCE]#
__matmul__(other: Tensor) SparseTensor

Matrix multiplication which works with sparse tensors.

Allows the user to write a @ b with sparse tensors and still get the expected behavior (like sparse gradients to sparse tensors).

Important

Wrapper around matmul().

Parameters:

other (SparseTensor | Tensor) – Left-multiply with this tensor.

Returns:

(SparseTensor | Tensor) – Multiplied result tensor.

__rmatmul__(other: SparseTensor) SparseTensor[SOURCE]#
__rmatmul__(other: Tensor) SparseTensor

Matrix multiplication which works with sparse tensors.

Allows the user to write a @ b with sparse tensors and still get the expected behavior (like sparse gradients to sparse tensors).

Important

Wrapper around matmul().

Parameters:

other (SparseTensor | Tensor) – Right-multiply with this tensor.

Returns:

(SparseTensor | Tensor) – Multiplied result tensor.

__mul__(other: SparseTensor) SparseTensor[SOURCE]#
__mul__(other: Tensor) SparseTensor
__mul__(other: Number) SparseTensor

Elementwise multiplication which works with sparse tensors.

Important

Wrapper around mul().

Parameters:

other (SparseTensor | Tensor) – Multiply with this tensor.

Returns:

(SparseTensor | Tensor) – Multiplied result tensor.

__rmul__(other: SparseTensor) SparseTensor[SOURCE]#
__rmul__(other: Tensor) SparseTensor
__rmul__(other: Number) SparseTensor

Elementwise multiplication which works with sparse tensors.

Important

Wrapper around mul().

Parameters:

other (SparseTensor | Tensor) – Multiply with this tensor.

Returns:

(SparseTensor | Tensor) – Multiplied result tensor.

reshape(*shape: int) SparseTensor[SOURCE]#

Reshapes the tensor into a specified shape.

Important

Wrapper around reshape().

Parameters:

*shape (int) – Dimensions for the resulting tensor

Returns:

(SparseTensor) – Reshaped tensor with a view into the same data.

scipy() sparray[SOURCE]#

Constructs a SciPy tensor with the same data (detaches autodiff graph).

Important

Wrapper around to_scipy().

Returns:

(scipy.sparse.coo_array) – SciPy tensor with the same data.

__getitem__(index: Any) SparseTensor[SOURCE]#

Slices the sparse tensor.

Important

Wrapper around get_slice().

Parameters:

index (Any) – Slicing indices. See get_slice() for more information.

Returns:

(SparseTensor) – Sliced sparse tensor.

square() SparseTensor[SOURCE]#

Elementwise square of the matrix.

Important

Wrapper around iskra.sparse.square().

Returns:

(SparseTensor) – Matrix with squared entries.