Commit 9be6d3b
authored
Add sparse_logsumexp: numerically stable sparse log-sum-exp reduction (#87)
* feat: add sparse_logsumexp reduction
Numerically-stable log-sum-exp over the nonzero values of a 2-D sparse
tensor (COO/CSR/CSC), mirroring torch.logsumexp. Reads CSR/CSC indices
directly (no COO coalesce) and reuses the compressed-axis nnz counts to
avoid a redundant bincount. The max-shift is detached so gradients equal
softmax over the participating entries. An include_zeros flag selects
whether structural zeros contribute exp(0)=1 or are treated as -inf.
* test: cover sparse_logsumexp against dense reference
Parametrized over COO/CSR layouts, devices, dtypes, all dim modes and
both include_zeros settings; cross-checks against torch.logsumexp on the
densified tensor. Adds keepdim, CSC, empty row/column, error-path and
gradient tests, and registers the module for doctest execution.
* bench: add sparse_logsumexp benchmark
Compares sparse_logsumexp against a dense torch.logsumexp baseline and a
segment_reduce variant across sizes/densities, on CPU and CUDA, with
graceful handling of dense out-of-memory at large sizes.
* docs: document sparse_logsumexp in API reference and README
* feat: support batched 3-D sparse_logsumexp
Extend the reduction to batched 3-D (batch, rows, cols) inputs, matching
the batched convention of sparse_mm and the solvers. Each slice is reduced
independently by folding the batch index into the scatter segment id, so a
single scatter handles the whole batch; the batch axis itself is not
reducible. Batched inputs go through COO (batched CSR/CSC require equal nnz
per slice in PyTorch). Adds batched tests across dims/include_zeros/keepdim.
* refactor: rename segment->scatter in logsumexp helper
The helper reduces via Tensor.scatter_reduce_ with an arbitrary per-value
output index, so 'segment' (which implies contiguous runs, as in
torch.segment_reduce) was misleading. Rename to _scatter_logsumexp /
scatter_index and note in the docstring that group members need not be
contiguous.
* refactor: use torch.logsumexp directly for scalar reduction
For dim=[0,1] every value falls in one group, so torch.logsumexp does the
reduction directly. The structural-zero mass (n_zeros entries of exp(0)=1)
equals a single entry of value log(n_zeros), appended so the reduction stays
one numerically stable call whose gradient still accounts for the zeros.
* docs: note why scatter is used over a segmented reduction for CSR/CSC
* docs: clarify 'slice' means one batch matrix input[k]
* bench: cover >1M-dim and SuiteSparse matrices for sparse_logsumexp
Extend the random benchmark to 2**16 and 2**20 (>1M rows/cols) where the dense
baseline OOMs, and add sparse_logsumexp_suitesparse.py running on Rothberg/cfd2
(123k x 123k) and Williams/webbase-1M (1M x 1M). Register both in
benchmark_suite.py and track their result CSVs.
* fix: correct stability shift for structural zeros in sparse_logsumexp
With include_zeros=True the structural zeros (value 0) participate in the
reduction, but the max-shift was computed only from the explicit values. When
every explicit value in a group is negative this overflowed (exp(-shift) -> inf),
and a fully dense group (no structural zeros) hit 0 * inf -> nan. Include 0 in
the per-group max where structural zeros are present, and contribute the zero
term only where the count is nonzero. Adds regression tests.
Reported-by: theo-barfoot
* fix: handle +inf values in sparse_logsumexp shift
An explicit +inf made the per-group max +inf, so value - shift computed
inf - inf = nan. Replace any non-finite shift (both +inf and the empty-group
-inf) with 0, so a +inf value correctly yields +inf. Adds regression test.
Reported-by: theo-barfoot
* fix: validate dim before normalising in sparse_logsumexp
dim was reduced modulo ndim before validation, so out-of-range dims silently
wrapped (2 -> 0), a set() dropped duplicates ([0, 0] -> [0]), and an empty
sequence fell through to a wrong path. Validate raw dims first: IndexError for
out-of-range, RuntimeError for empty or repeated dims, matching torch.logsumexp.
Adds regression tests; valid negative dims are unaffected.
Reported-by: theo-barfoot
* test: parametrize sparse_logsumexp tests over index dtype
Add an index_dtype fixture (int32/int64) matching the repo convention and thread
it through the dense-reference test so the CSR/CSC index paths are covered for
both dtypes. COO indices are int64-only in PyTorch, so the fixture varies only
the compressed layouts.
Suggested-by: theo-barfoot
* refactor: guard coalesce with is_coalesced in sparse_logsumexp
Skip the redundant coalesce() call when the COO input is already
coalesced, matching the guard pattern used elsewhere in the repo and at
the CSR/CSC fallback in this same file (line 138).
Suggested-by: tvercaut1 parent 6839ddd commit 9be6d3b
11 files changed
Lines changed: 979 additions & 0 deletions
File tree
- docs/source/api
- torchsparsegradutils
- benchmarks
- results
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
17 | 23 | | |
18 | 24 | | |
19 | 25 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
28 | 35 | | |
29 | 36 | | |
30 | 37 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| 14 | + | |
13 | 15 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
| 22 | + | |
| 23 | + | |
22 | 24 | | |
23 | 25 | | |
24 | 26 | | |
| |||
Lines changed: 81 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 33 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
Lines changed: 175 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
0 commit comments