Skip to content

Commit c7b1026

Browse files
committed
[maskedtensor] Sparsity tutorial [2/4]
1 parent 04e1ba9 commit c7b1026

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
Sparsity
5+
========
6+
**Author**: `George Qi <https://github.com/george-qi>`_
7+
"""
8+
9+
######################################################################
10+
# Sparsity has been an area of rapid growth and importance within PyTorch; if any sparsity terms are confusing below,
11+
# please refer to the `sparsity tutorial <https://pytorch.org/docs/stable/sparse.html>`__ for additional details.
12+
#
13+
# Sparse storage formats have been proven to be powerful in a variety of ways. As a primer, the first use case
14+
# most practitioners think about is when the majority of elements are equal to zero (a high degree of sparsity),
15+
# but even in cases of lower sparsity, certain formats (e.g. BSR) can take advantage of substructures within a matrix.
16+
#
17+
# .. note::
18+
#
19+
# At the moment, MaskedTensor supports COO and CSR tensors with plans to support additional formats
20+
# (e.g. BSR and CSC) in the future. If you have any requests for additional formats, please file a feature request!
21+
#
22+
# Principles
23+
# ----------
24+
#
25+
# When creating a :class:`MaskedTensor` with sparse tensors, there are a few principles that must be observed:
26+
#
27+
# 1. ``data`` and ``mask`` must have the same storage format, whether that's :attr:`torch.strided`, :attr:`torch.sparse_coo`, or :attr:`torch.sparse_csr`
28+
# 2. ``data`` and ``mask`` must have the same size, indicated by :func:`size()`
29+
#
30+
# Sparse COO tensors
31+
# ------------------
32+
#
33+
# In accordance with Principle #1, a sparse COO MaskedTensor is created by passing in two sparse COO tensors,
34+
# which can be initialized by any of its constructors, e.g. :func:`torch.sparse_coo_tensor`.
35+
#
36+
# As a recap of `sparse COO tensors <https://pytorch.org/docs/stable/sparse.html#sparse-coo-tensors>`__, the COO format
37+
# stands for "coordinate format", where the specified elements are stored as tuples of their indices and the
38+
# corresponding values. That is, the following are provided:
39+
#
40+
# * ``indices``: array of size ``(ndim, nse)`` and dtype ``torch.int64``
41+
# * ``values``: array of size `(nse,)` with any integer or floating point dtype
42+
#
43+
# where ``ndim`` is the dimensionality of the tensor and ``nse`` is the number of specified elements
44+
#
45+
# For both sparse COO and CSR tensors, you can construct a :class:`MaskedTensor` by doing either:
46+
#
47+
# 1. ``masked_tensor(sparse_tensor_data, sparse_tensor_mask)``
48+
# 2. ``dense_masked_tensor.to_sparse_coo()`` or ``dense_masked_tensor.to_sparse_csr()``
49+
#
50+
# The second method is easier to illustrate so we've shown that below, but for more on the first and the nuances behind
51+
# the approach, please read the :ref:`sparse-coo-appendix`.
52+
#
53+
54+
values = torch.tensor([[0, 0, 3], [4, 0, 5]])
55+
mask = torch.tensor([[False, False, True], [False, False, True]])
56+
mt = masked_tensor(values, mask)
57+
sparse_coo_mt = mt.to_sparse_coo()
58+
print("mt:\n", mt)
59+
print("mt (sparse coo):\n", sparse_coo_mt)
60+
print("mt data (sparse coo):\n", sparse_coo_mt.get_data())
61+
62+
######################################################################
63+
# Sparse CSR tensors
64+
# ------------------
65+
#
66+
# Similarly, :class:`MaskedTensor` also supports the
67+
# `CSR (Compressed Sparse Row) <https://pytorch.org/docs/stable/sparse.html#sparse-csr-tensor>`__
68+
# sparse tensor format. Instead of storing the tuples of the indices like sparse COO tensors, sparse CSR tensors
69+
# aim to decrease the memory requirements by storing compressed row indices.
70+
# In particular, a CSR sparse tensor consists of three 1-D tensors:
71+
#
72+
# * ``crow_indices``: array of compressed row indices with size ``(size[0] + 1,)``. This array indicates which row
73+
# a given entry in values lives in. The last element is the number of specified elements,
74+
# while crow_indices[i+1] - crow_indices[i] indicates the number of specified elements in row i.
75+
# * ``col_indices``: array of size ``(nnz,)``. Indicates the column indices for each value.
76+
# * ``values``: array of size ``(nnz,)``. Contains the values of the CSR tensor.
77+
#
78+
# Of note, both sparse COO and CSR tensors are in a `beta <https://pytorch.org/docs/stable/index.html>`__ state.
79+
#
80+
# By way of example:
81+
#
82+
83+
mt_sparse_csr = mt.to_sparse_csr()
84+
print("mt (sparse csr):\n", mt_sparse_csr)
85+
print("mt data (sparse csr):\n", mt_sparse_csr.get_data())
86+
87+
######################################################################
88+
# Appendix
89+
# ++++++++
90+
#
91+
# .. _sparse-coo-appendix:
92+
#
93+
# Sparse COO construction
94+
# -----------------------
95+
#
96+
# Recall in our original example, we created a :class:`MaskedTensor` and then converted it to a sparse COO MaskedTensor
97+
# with :meth:`MaskedTensor.to_sparse_coo`.
98+
#
99+
# Alternatively, we can also construct a sparse COO MaskedTensor directly by passing in two sparse COO tensors:
100+
#
101+
102+
values = torch.tensor([[0, 0, 3], [4, 0, 5]]).to_sparse()
103+
mask = torch.tensor([[False, False, True], [False, False, True]]).to_sparse()
104+
mt = masked_tensor(values, mask)
105+
print("values:\n", values)
106+
print("mask:\n", mask)
107+
print("mt:\n", mt)
108+
109+
######################################################################
110+
# Instead of using :meth:`torch.Tensor.to_sparse`, we can also create the sparse COO tensors directly,
111+
# which brings us to a warning:
112+
#
113+
# .. warning::
114+
#
115+
# When using a function like :meth:`MaskedTensor.to_sparse_coo`, if the user does not specify the indices
116+
# like in the above example, then the 0 values will be "unspecified" by default.
117+
#
118+
# Below, we explicitly specify the 0's:
119+
#
120+
121+
values = torch.sparse_coo_tensor(i, v, (2, 3))
122+
mask = torch.sparse_coo_tensor(i, m, (2, 3))
123+
mt2 = masked_tensor(values, mask)
124+
print("values:\n", values)
125+
print("mask:\n", mask)
126+
print("mt2:\n", mt2)
127+
128+
######################################################################
129+
# Note that ``mt`` and ``mt2`` look identical on the surface, and in the vast majority of operations, will yield the same
130+
# result. But this brings us to a detail on the implementation:
131+
#
132+
# ``data`` and ``mask`` -- only for sparse MaskedTensors -- can have a different number of elements (:func:`nnz`)
133+
# **at creation**, but the indices of ``mask`` must then be a subset of the indices of ``data``. In this case,
134+
# ``data`` will assume the shape of ``mask`` by ``data = data.sparse_mask(mask)``; in other words, any of the elements
135+
# in ``data`` that are not ``True`` in ``mask`` (i.e. not specified) will be thrown away.
136+
#
137+
# Therefore, under the hood, the data looks slightly different; ``mt2`` has the "4" value masked out and ``mt`` is completely
138+
# without it. Their underlying data has different shapes, which would make operations like ``mt + mt2`` invalid.
139+
#
140+
141+
print("mt data:\n", mt.get_data())
142+
print("mt2 data:\n", mt2.get_data())
143+
144+
######################################################################
145+
# .. _sparse-csr-appendix:
146+
#
147+
# Sparse CSR construction
148+
# -----------------------
149+
#
150+
# We can also construct a sparse CSR MaskedTensor using sparse CSR tensors,
151+
# and like the example above, this results in a similar treatment under the hood.
152+
#
153+
154+
crow_indices = torch.tensor([0, 2, 4])
155+
col_indices = torch.tensor([0, 1, 0, 1])
156+
values = torch.tensor([1, 2, 3, 4])
157+
mask_values = torch.tensor([True, False, False, True])
158+
159+
csr = torch.sparse_csr_tensor(crow_indices, col_indices, values, dtype=torch.double)
160+
mask = torch.sparse_csr_tensor(crow_indices, col_indices, mask_values, dtype=torch.bool)
161+
162+
mt = masked_tensor(csr, mask)
163+
print("mt:\n", mt)
164+
print("mt data:\n", mt.get_data())

prototype_source/prototype_index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ Prototype features are not available as part of binary distributions like PyPI o
141141
:link: ../prototype/nestedtensor.html
142142
:tags: NestedTensor
143143

144+
.. customcarditem::
145+
:header: Masked Tensor Sparsity
146+
:card_description: Learn about how to leverage sparse layouts (e.g. COO and CSR) in MaskedTensor
147+
:image: ../_static/img/thumbnails/cropped/generic-pytorch-logo.png
148+
:link: ../prototype/maskedtensor_sparsity.html
149+
:tags: MaskedTensor
150+
144151
.. End of tutorial card section
145152
146153
.. raw:: html
@@ -172,3 +179,4 @@ Prototype features are not available as part of binary distributions like PyPI o
172179
prototype/vmap_recipe.html
173180
prototype/vulkan_workflow.html
174181
prototype/nestedtensor.html
182+
prototype/maskedtensor_sparsity.html

0 commit comments

Comments
 (0)