|
| 1 | +Naming and shape conventions |
| 2 | +============================ |
| 3 | + |
| 4 | +This guide defines the terminology used for shapes, batching, sparse layouts, |
| 5 | +and dimensions in ``torchsparsegradutils``. Use it for code, docstrings, |
| 6 | +tests, issues, and pull requests. State the logical object first and then its |
| 7 | +shape: sparse tensor terminology otherwise makes it easy to give different |
| 8 | +concepts the same name. |
| 9 | + |
| 10 | +Core terminology |
| 11 | +---------------- |
| 12 | + |
| 13 | +Use **matrix** for a logical object with row and column axes, and use |
| 14 | +**tensor** for the PyTorch object or for a genuinely non-matrix object. For |
| 15 | +the non-hybrid matrix tensors used by most operations in this repository, |
| 16 | +the row and column axes are the final two axes. Hybrid sparse tensors may |
| 17 | +have trailing dense value dimensions. |
| 18 | + |
| 19 | +* An unbatched sparse matrix has shape ``(n_rows, n_cols)``. |
| 20 | +* A batched sparse matrix (a batch of sparse matrices) has shape |
| 21 | + ``(batch_size, n_rows, n_cols)``. |
| 22 | +* An unbatched square sparse matrix has shape ``(n, n)``; its batched form has |
| 23 | + shape ``(batch_size, n, n)``. |
| 24 | +* A dense matrix of right-hand sides has shape ``(n_rows, n_rhs)``; its |
| 25 | + batched form has shape ``(batch_size, n_rows, n_rhs)``. |
| 26 | +* A vector has shape ``(n,)`` and a batch of vectors has shape |
| 27 | + ``(batch_size, n)``. |
| 28 | + |
| 29 | +``2D``, ``3D``, ``rank 2``, and ``rank 3`` describe only the number of |
| 30 | +tensor axes. They do not identify an axis as a batch, matrix, spatial, or |
| 31 | +dense-value axis. Prefer, for example, “a batched sparse matrix with shape |
| 32 | +``(batch_size, n_rows, n_cols)``” to “a 3D sparse tensor”. |
| 33 | + |
| 34 | +When an API supports at most one batch axis, say so directly. Unless an API |
| 35 | +documents an exception, batch sizes must match exactly and batch broadcasting |
| 36 | +is not supported. |
| 37 | + |
| 38 | +Rank, batch, sparse, and dense dimensions |
| 39 | +------------------------------------------ |
| 40 | + |
| 41 | +These terms are not interchangeable: |
| 42 | + |
| 43 | +* **Tensor rank** is ``tensor.ndim``, the total number of logical axes. |
| 44 | +* A **batch dimension** indexes independent problem instances. Repository |
| 45 | + convention is one leading batch axis. |
| 46 | +* A **sparse dimension** has coordinates represented by sparse indices. For |
| 47 | + COO, ``sparse_dim()`` is the number of leading indexed dimensions. CSR and |
| 48 | + CSC always have two sparse matrix dimensions. |
| 49 | +* A **dense dimension of a sparse tensor** is a trailing dimension stored in |
| 50 | + each specified value. It is not the same as a dense, ``torch.strided`` |
| 51 | + tensor. Most matrix operations here require ``dense_dim() == 0`` unless |
| 52 | + documented otherwise. |
| 53 | + |
| 54 | +For example, a hybrid sparse matrix with shape |
| 55 | +``(n_rows, n_cols, feature_size)`` can have two sparse dimensions and one |
| 56 | +dense feature dimension. A true three-dimensional sparse object, such as a |
| 57 | +sparse volume ``(depth, height, width)``, is different from a rank-3 tensor |
| 58 | +whose leading axis is a batch of matrices. |
| 59 | + |
| 60 | +COO and compressed-layout batching |
| 61 | +---------------------------------- |
| 62 | + |
| 63 | +PyTorch represents batching differently for COO and compressed layouts, so |
| 64 | +documentation must preserve that distinction. |
| 65 | + |
| 66 | +For COO, a tensor of shape ``(batch_size, n_rows, n_cols)`` commonly has three |
| 67 | +sparse dimensions. PyTorch does not label the first one as a batch dimension; |
| 68 | +an API in this package *logically interprets* it as one. COO batch items may |
| 69 | +have different numbers of specified entries, including zero. |
| 70 | + |
| 71 | +For CSR and CSC, PyTorch represents tensors as |
| 72 | +``(*batch_shape, n_rows, n_cols, *dense_shape)``. The normal form supported by |
| 73 | +this package is ``(batch_size, n_rows, n_cols)``. Batched CSR and CSC require |
| 74 | +the same number of specified entries in every batch item; that is a storage |
| 75 | +constraint, not a mathematical requirement of batching. Do not generalise |
| 76 | +this restriction to COO. |
| 77 | + |
| 78 | +Specified-element counts in batched layouts |
| 79 | +-------------------------------------------- |
| 80 | + |
| 81 | +``_nnz()`` is layout-specific for a batched sparse tensor. This distinction is |
| 82 | +exercised by the sparse-matrix multiplication backward tests in |
| 83 | +``torchsparsegradutils/tests/test_sparse_matmul.py``: |
| 84 | + |
| 85 | +* For batched CSR, ``A.grad._nnz()`` is the number of specified entries |
| 86 | + **per batch element**. |
| 87 | +* For batched COO, ``A.grad._nnz()`` is the number of specified entries in the |
| 88 | + **whole tensor**. |
| 89 | + |
| 90 | +Thus, if every batch item has ``nse_per_matrix`` specified entries and the |
| 91 | +batch size is ``batch_size``, the checked invariants are: |
| 92 | + |
| 93 | +.. code-block:: python |
| 94 | +
|
| 95 | + # Batched CSR: PyTorch reports a per-batch-item count. |
| 96 | + assert A_csr.grad._nnz() == nse_per_matrix |
| 97 | +
|
| 98 | + # Batched COO: PyTorch reports a count over the complete tensor. |
| 99 | + assert A_coo.grad._nnz() == nse_per_matrix * batch_size |
| 100 | +
|
| 101 | +This does not make CSR's count more local mathematically; it describes the |
| 102 | +storage-count API exposed by PyTorch. For an unbatched COO or CSR matrix, |
| 103 | +``_nnz()`` is simply the count for that one matrix. |
| 104 | + |
| 105 | +Specified entries, explicit zeros, and ``nnz`` |
| 106 | +---------------------------------------------- |
| 107 | + |
| 108 | +Prefer **specified entry** and **number of specified elements** (``nse``) when |
| 109 | +the distinction matters. Sparse storage can contain an explicit value equal to |
| 110 | +zero, so “number of nonzeros” can be misleading. |
| 111 | + |
| 112 | +* An **explicit zero** is a stored value equal to zero. |
| 113 | +* A **structural** or **implicit zero** is an absent coordinate interpreted as |
| 114 | + the sparse fill value. |
| 115 | +* Existing code and benchmarks may use ``nnz``. When retained, define it as |
| 116 | + the count of stored/specified entries, not necessarily mathematically |
| 117 | + nonzero values. |
| 118 | + |
| 119 | +Avoid calling ``values()`` “nonzero values” unless explicit zeros are known to |
| 120 | +be absent; use “stored values” or “explicit values” instead. |
| 121 | + |
| 122 | +Layout and index names |
| 123 | +---------------------- |
| 124 | + |
| 125 | +Use PyTorch's layout names consistently: sparse COO (``torch.sparse_coo``), |
| 126 | +sparse CSR (``torch.sparse_csr``), sparse CSC (``torch.sparse_csc``), sparse |
| 127 | +BSR (``torch.sparse_bsr``), sparse BSC (``torch.sparse_bsc``), and dense or |
| 128 | +strided (``torch.strided``). Prefer **layout** over mixing “format” and |
| 129 | +“layout” in the same API description. “Compressed sparse layout” can refer to |
| 130 | +CSR, CSC, BSR, and BSC collectively. |
| 131 | + |
| 132 | +Name index arrays after the represented axis: |
| 133 | + |
| 134 | +* COO: ``batch_indices``, ``row_indices``, and ``col_indices``. |
| 135 | +* CSR: ``crow_indices``, ``col_indices``, and ``values``. |
| 136 | +* CSC: ``ccol_indices``, ``row_indices``, and ``values``. |
| 137 | + |
| 138 | +``crow_indices`` and ``ccol_indices`` are compressed pointers, not coordinate |
| 139 | +lists. When converting them to coordinates, say **uncompress**, **expand**, or |
| 140 | +**repeat-interleave the row/column indices**. |
| 141 | + |
| 142 | +Shape symbols and operations |
| 143 | +---------------------------- |
| 144 | + |
| 145 | +Within a function or document, choose one scheme and keep it consistent: |
| 146 | +``batch_size`` (or ``b``), ``nrows``/``n_rows`` (or ``M``), |
| 147 | +``ncols``/``n_cols`` (or ``N``), and ``nrhs``/``n_rhs`` (or ``K``). Short local |
| 148 | +names such as ``b, nrows, ncols`` are appropriate in compact numerical code. |
| 149 | + |
| 150 | +For multiplication, use: |
| 151 | + |
| 152 | +.. code-block:: text |
| 153 | +
|
| 154 | + A: (n_rows, n_inner) or (batch_size, n_rows, n_inner) |
| 155 | + B: (n_inner, n_cols) or (batch_size, n_inner, n_cols) |
| 156 | + C: (n_rows, n_cols) or (batch_size, n_rows, n_cols) |
| 157 | +
|
| 158 | +For solves, use: |
| 159 | + |
| 160 | +.. code-block:: text |
| 161 | +
|
| 162 | + A: (n, n) or (batch_size, n, n) |
| 163 | + B: (n, n_rhs) or (batch_size, n, n_rhs) |
| 164 | + X: same shape as B |
| 165 | +
|
| 166 | +Call ``B`` the **right-hand side**; its final axis is the number of |
| 167 | +right-hand sides, not a batch dimension. For an unbatched matrix, ``A.T`` |
| 168 | +transposes the matrix axes. For a batched matrix, use ``A.mT`` or |
| 169 | +``A.transpose(-2, -1)`` to transpose the final two matrix axes while preserving |
| 170 | +the leading batch axis. Do not use ``A.T`` for batched matrices because it |
| 171 | +reverses all tensor axes. |
| 172 | + |
| 173 | +Reduction, distribution, and memory wording |
| 174 | +-------------------------------------------- |
| 175 | + |
| 176 | +For ``A`` with shape ``(n_rows, n_cols)``, “reduce over rows” means reduce |
| 177 | +axis 0 and produce one value per column; “reduce over columns” means reduce |
| 178 | +axis 1 and produce one value per row. Avoid “row-wise” and “column-wise” |
| 179 | +unless the reduced and retained axes are both stated. |
| 180 | + |
| 181 | +For ``torch.distributions``-compatible code, use |
| 182 | +``sample_shape + batch_shape + event_shape``. Sample dimensions are not batch |
| 183 | +dimensions. Spatial axes, channels, and batch axes are also distinct; use |
| 184 | +``spatial_dims``, ``spatial_shape``, ``num_channels``, and ``batch_size`` as |
| 185 | +appropriate and document a concrete volume ordering such as |
| 186 | +``(depth, height, width)``. |
| 187 | + |
| 188 | +Use **view** only when storage is shared, **copy** when storage is newly |
| 189 | +allocated, and **reshape** when either may occur. “Materialise dense” means |
| 190 | +constructing a full dense tensor, for example with ``to_dense()``. Do not claim |
| 191 | +that an operation has no allocation merely because an input was expanded: |
| 192 | +``stack``, ``cat``, ``clone``, ``contiguous``, and output creation may allocate. |
| 193 | + |
| 194 | +COO coalescing and validation messages |
| 195 | +-------------------------------------- |
| 196 | + |
| 197 | +Only COO tensors are **coalesced** or **uncoalesced**. Coalesced COO has unique, |
| 198 | +sorted coordinates; uncoalesced COO can have duplicate coordinates that sum at |
| 199 | +the same logical position. Coalesce before nonlinear operations on values |
| 200 | +unless the implementation has proved an equivalent treatment of duplicates. |
| 201 | +Do not apply this terminology to CSR or CSC. |
| 202 | + |
| 203 | +Shape-validation errors should describe the accepted logical forms as well as |
| 204 | +the received shape: |
| 205 | + |
| 206 | +.. code-block:: python |
| 207 | +
|
| 208 | + raise ValueError( |
| 209 | + "A must be an unbatched sparse matrix with shape (n_rows, n_cols) " |
| 210 | + "or a batched sparse matrix with shape (batch_size, n_rows, n_cols); " |
| 211 | + f"got shape {tuple(A.shape)}." |
| 212 | + ) |
| 213 | +
|
| 214 | +Before submitting shape-related changes, make sure public inputs and outputs |
| 215 | +state explicit shapes; distinguish batch, matrix, spatial, channel, sample, |
| 216 | +and event axes; and separate COO-specific from compressed-layout-specific |
| 217 | +constraints. |
| 218 | + |
| 219 | +References |
| 220 | +---------- |
| 221 | + |
| 222 | +* `PyTorch sparse tensor documentation <https://docs.pytorch.org/docs/stable/sparse.html>`_ |
| 223 | +* `PyTorch distributions documentation <https://docs.pytorch.org/docs/stable/distributions.html>`_ |
| 224 | +* `PyTorch tensor views documentation <https://docs.pytorch.org/docs/stable/tensor_view.html>`_ |
0 commit comments