trnsparse 0.4.2__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {trnsparse-0.4.2 → trnsparse-0.7.0}/.github/workflows/ci.yml +3 -0
  2. {trnsparse-0.4.2 → trnsparse-0.7.0}/.pre-commit-config.yaml +1 -1
  3. {trnsparse-0.4.2 → trnsparse-0.7.0}/CHANGELOG.md +208 -0
  4. {trnsparse-0.4.2 → trnsparse-0.7.0}/CLAUDE.md +23 -22
  5. {trnsparse-0.4.2 → trnsparse-0.7.0}/PKG-INFO +22 -8
  6. {trnsparse-0.4.2 → trnsparse-0.7.0}/README.md +21 -7
  7. trnsparse-0.7.0/docs/architecture.md +143 -0
  8. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/benchmarks.md +22 -15
  9. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/iterative_solvers.md +1 -1
  10. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/migration_scipy.md +4 -4
  11. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/quickstart.md +2 -2
  12. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/sparse_attention.md +87 -15
  13. {trnsparse-0.4.2 → trnsparse-0.7.0}/examples/block_sparse_attention.py +25 -0
  14. {trnsparse-0.4.2 → trnsparse-0.7.0}/pyproject.toml +1 -1
  15. trnsparse-0.7.0/tests/test_attention.py +327 -0
  16. trnsparse-0.7.0/tests/test_attn_gather.py +49 -0
  17. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_iterative.py +91 -0
  18. trnsparse-0.7.0/tests/test_nki_attn.py +201 -0
  19. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_nki_bsr.py +5 -1
  20. trnsparse-0.7.0/tests/test_nki_compile.py +143 -0
  21. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_nki_screened_spmm.py +5 -1
  22. trnsparse-0.7.0/tests/test_nki_sim.py +492 -0
  23. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_nki_spmm.py +5 -1
  24. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse/__init__.py +15 -2
  25. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse/iterative.py +150 -0
  26. trnsparse-0.7.0/trnsparse/nki/dispatch.py +994 -0
  27. trnsparse-0.7.0/trnsparse/nki/kernels.py +592 -0
  28. trnsparse-0.7.0/trnsparse/ops.py +625 -0
  29. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse.egg-info/PKG-INFO +22 -8
  30. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse.egg-info/SOURCES.txt +3 -0
  31. trnsparse-0.4.2/docs/architecture.md +0 -109
  32. trnsparse-0.4.2/tests/test_attention.py +0 -93
  33. trnsparse-0.4.2/tests/test_nki_sim.py +0 -153
  34. trnsparse-0.4.2/trnsparse/nki/dispatch.py +0 -487
  35. trnsparse-0.4.2/trnsparse/nki/kernels.py +0 -192
  36. trnsparse-0.4.2/trnsparse/ops.py +0 -277
  37. {trnsparse-0.4.2 → trnsparse-0.7.0}/.github/workflows/notify-umbrella.yml +0 -0
  38. {trnsparse-0.4.2 → trnsparse-0.7.0}/.github/workflows/publish.yml +0 -0
  39. {trnsparse-0.4.2 → trnsparse-0.7.0}/.gitignore +0 -0
  40. {trnsparse-0.4.2 → trnsparse-0.7.0}/CODE_OF_CONDUCT.md +0 -0
  41. {trnsparse-0.4.2 → trnsparse-0.7.0}/CONTRIBUTING.md +0 -0
  42. {trnsparse-0.4.2 → trnsparse-0.7.0}/LICENSE +0 -0
  43. {trnsparse-0.4.2 → trnsparse-0.7.0}/benchmarks/bench_bsr_spmm.py +0 -0
  44. {trnsparse-0.4.2 → trnsparse-0.7.0}/benchmarks/bench_iterative.py +0 -0
  45. {trnsparse-0.4.2 → trnsparse-0.7.0}/benchmarks/bench_screening.py +0 -0
  46. {trnsparse-0.4.2 → trnsparse-0.7.0}/benchmarks/bench_spmm.py +0 -0
  47. {trnsparse-0.4.2 → trnsparse-0.7.0}/benchmarks/bench_spmv.py +0 -0
  48. {trnsparse-0.4.2 → trnsparse-0.7.0}/benchmarks/conftest.py +0 -0
  49. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/api.md +0 -0
  50. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/aws_setup.md +0 -0
  51. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/index.md +0 -0
  52. {trnsparse-0.4.2 → trnsparse-0.7.0}/docs/installation.md +0 -0
  53. {trnsparse-0.4.2 → trnsparse-0.7.0}/examples/sparse_fock.py +0 -0
  54. {trnsparse-0.4.2 → trnsparse-0.7.0}/infra/terraform/.terraform.lock.hcl +0 -0
  55. {trnsparse-0.4.2 → trnsparse-0.7.0}/infra/terraform/README.md +0 -0
  56. {trnsparse-0.4.2 → trnsparse-0.7.0}/infra/terraform/main.tf +0 -0
  57. {trnsparse-0.4.2 → trnsparse-0.7.0}/mkdocs.yml +0 -0
  58. {trnsparse-0.4.2 → trnsparse-0.7.0}/scripts/bench_to_md.py +0 -0
  59. {trnsparse-0.4.2 → trnsparse-0.7.0}/scripts/run_benchmarks.sh +0 -0
  60. {trnsparse-0.4.2 → trnsparse-0.7.0}/scripts/run_neuron_tests.sh +0 -0
  61. {trnsparse-0.4.2 → trnsparse-0.7.0}/scripts/run_simulator_tests.sh +0 -0
  62. {trnsparse-0.4.2 → trnsparse-0.7.0}/setup.cfg +0 -0
  63. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/conftest.py +0 -0
  64. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_bsr.py +0 -0
  65. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_examples.py +0 -0
  66. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_formats.py +0 -0
  67. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_ops.py +0 -0
  68. {trnsparse-0.4.2 → trnsparse-0.7.0}/tests/test_screening.py +0 -0
  69. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse/formats.py +0 -0
  70. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse/nki/__init__.py +0 -0
  71. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse/screening.py +0 -0
  72. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse.egg-info/dependency_links.txt +0 -0
  73. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse.egg-info/requires.txt +0 -0
  74. {trnsparse-0.4.2 → trnsparse-0.7.0}/trnsparse.egg-info/top_level.txt +0 -0
@@ -41,6 +41,8 @@ jobs:
41
41
  # Catches Python-trace-level errors (bad kwargs, dropped ops, shape
42
42
  # mismatches) pre-merge without AWS round-trips. MLIR verifier
43
43
  # errors remain hardware-only (simulator explicitly skips compile).
44
+ # TRNSPARSE_REQUIRE_NKI=1 disables the PyTorch fallback so kernel
45
+ # failures propagate — the simulator tests actually validate NKI kernels.
44
46
  runs-on: ubuntu-latest
45
47
  steps:
46
48
  - uses: actions/checkout@v6
@@ -55,4 +57,5 @@ jobs:
55
57
  - name: Run simulator-backed kernel tests
56
58
  env:
57
59
  TRNSPARSE_USE_SIMULATOR: "1"
60
+ TRNSPARSE_REQUIRE_NKI: "1"
58
61
  run: pytest tests/ -v -m nki_simulator --tb=short
@@ -1,6 +1,6 @@
1
1
  repos:
2
2
  - repo: https://github.com/astral-sh/ruff-pre-commit
3
- rev: v0.8.6
3
+ rev: v0.15.11
4
4
  hooks:
5
5
  - id: ruff
6
6
  args: [--fix]
@@ -5,6 +5,214 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.7.0] — 2026-10-28
11
+
12
+ Validated on Trn2 (trn2.3xlarge, Neuron SDK 2.33.0, native TorchNeuron device) — see trnsci/trnsci#35.
13
+
14
+ ### Fixed
15
+
16
+ - **NKI 0.7.0 / neuronx-cc 2.28 kernel compatibility (#29).** Kernels that
17
+ passed `nki.simulate` failed to compile on Trn2 (Neuron SDK 2.33):
18
+ - `_attn_out_kernel`, `_attn_bwd_dq_kernel`, `_attn_bwd_dkdv_kernel`:
19
+ softmax normalisation `nl.divide(exp, row_denom)` by a `(128, 1)`
20
+ denominator lowered to `tensor_scalar_arith`, which no longer supports
21
+ `divide`. Now multiplies by `nl.reciprocal(row_denom)` (hoisted per
22
+ block-row in the forward and dQ kernels).
23
+ - `_screened_spmm_kernel`: the `(128, 1) * (1, 128)` Schwarz outer product
24
+ and the `(1, 1)` threshold subtract produced partition-count mismatches
25
+ (`operand0 partition total elements 128 != dst partition total elements
26
+ 1`). `q_k` is now `nl.broadcast_to` `(128, 128)` and the threshold is
27
+ loaded once and broadcast to `(128, 1)`. The soft-mask tensor-tensor
28
+ divide (fails ISA validation `s3s3d3_tt_valid_op`) is now a reciprocal
29
+ multiply.
30
+ - `nki_bsr_attn_bwd`: the host-side `(..., b) → (..., b, 1)` unsqueeze
31
+ for per-row stats matched `q_scaled` / `k_blocks` / `v_blocks` by shape
32
+ whenever `head_dim == 128` (and in the simulator's padded path), feeding
33
+ the kernels a 4-D tensor. Stats are now selected by name.
34
+ - Dispatch: kernels always receive fp32 (Trainium has no fp64 datapath;
35
+ the native device downcast fp64 silently) and results are cast back to
36
+ the caller's dtype. Fixes `expected m1 and m2 to have the same dtype,
37
+ but got: float != double` in the PyTorch-level backward under fp64
38
+ gradcheck. The hardware gradcheck tests now use `eps=1e-2,
39
+ atol=rtol=1e-2`, sized for an fp32 forward (the ops are linear in each
40
+ input, so the larger eps adds no truncation error).
41
+
42
+ - **Block-sparse attention: two host-side gather bugs** (pre-existing, masked
43
+ on Trn2 by the compile failures above; found with neuronx-cc BIRSim):
44
+ - Rows with fewer than `K_max` blocks were padded with sentinel k-slots that
45
+ gathered column block 0, so block 0 was counted again in the softmax and
46
+ in `weights @ V` (local-window masks: ~49% of outputs wrong). Padded slots
47
+ now have K/V (forward, dQ) or Q/dO (dK/dV) zeroed, and their stats are
48
+ dropped in `_attn_host_reduction`.
49
+ - `_attn_bwd_gather` gathered `dO` by column block instead of the row block
50
+ `dO_m`, breaking dQ whenever a row attends to a block other than itself
51
+ (~99% of dQ wrong). The two simulator xfails attributed to a "PSUM
52
+ accumulate bug" (`test_bwd_dq_parity_kmax2`, `test_backward_head_dim_256`)
53
+ were this bug and now pass.
54
+
55
+ ### Added
56
+
57
+ - `tests/test_attn_gather.py`: CPU regression tests for both gather bugs.
58
+ - `tests/test_nki_compile.py` (`nki_simulator` marker): CPU MLIR
59
+ verification (`source_to_mlir(...).verify()`) of every kernel for trn1
60
+ and trn2, attention kernels at head_dim 64, 128 and 256; plus a full NEFF
61
+ compile per kernel/target when `neuronx-cc` is on PATH.
62
+
63
+ ## [0.6.0] — 2026-04-17
64
+
65
+ ### Added
66
+
67
+ - **K-tiling for head_dim > 128** — NKI attention kernels now support
68
+ `head_dim=256` and `head_dim=512` via an inner K-tile loop that splits
69
+ the `Q @ K.T` score computation across `TILE_K=128`-sized chunks of
70
+ `head_dim`, accumulating each into the existing PSUM.
71
+ - All 4 NKI kernels updated: `_attn_stats_kernel`, `_attn_out_kernel`,
72
+ `_attn_bwd_dq_kernel`, `_attn_bwd_dkdv_kernel`.
73
+ - For `head_dim ≤ 128`: single-tile path preserved unchanged — Q is
74
+ loaded once per block-row as a stationary tile (no regression).
75
+ - For `head_dim > 128`: K-tile inner loop (`affine_range(head_dim //
76
+ 128)`). `head_dim` must be a multiple of 128 (covers 256, 512).
77
+ - `weights @ V` and all dK/dV accumulation matmuls are unchanged (K=128
78
+ block dimension, unaffected by head_dim growth).
79
+ - `tests/test_attention.py`: `test_head_dim_256_parity`,
80
+ `test_head_dim_256_dilated_parity`, `test_gradcheck_head_dim_256`,
81
+ `test_backward_shapes_head_dim_256` in PyTorch path.
82
+ - `tests/test_nki_sim.py`: `TestAttnKTilingSimulator` — simulator parity
83
+ tests for forward and backward at head_dim=256.
84
+ - `tests/test_nki_attn.py`: `TestAttnHardwareLargeDim` — hardware parity
85
+ tests at seq_len=512, head_dim=256.
86
+
87
+ ### Changed
88
+
89
+ - `_attn_gather` assertion relaxed from `head_dim ≤ 128` to `head_dim ≤ 128
90
+ or head_dim % 128 == 0`.
91
+ - `block_sparse_attention_tiled` and `nki_bsr_attn_tiled` docstrings updated
92
+ to document K-tiling support.
93
+
94
+ ## [0.5.1] — 2026-04-16
95
+
96
+ ### Added
97
+
98
+ - **NKI backward kernel pair** (`_attn_bwd_dq_kernel` + `_attn_bwd_dkdv_kernel`)
99
+ in `trnsparse/nki/kernels.py`. When the `nki` backend is active,
100
+ `_AttnTiledFunction.backward` now dispatches to these kernels instead of
101
+ the Python-loop backward.
102
+ - `_attn_bwd_dq_kernel`: row-first traversal, mirrors `_attn_out_kernel`.
103
+ Accumulates `dQ_m += dS @ K_ki * scale` in a PSUM tile per block-row.
104
+ - `_attn_bwd_dkdv_kernel`: column-first traversal (BSC view built on host).
105
+ For each column block ki, iterates over all block-rows m that attend to ki,
106
+ accumulating `dK_ki += dS.T @ Q_m * scale` and `dV_ki += P.T @ dO_m`.
107
+ - No atomic scatter required — column-first gather on host eliminates NKI's
108
+ lack of cross-block accumulation primitives.
109
+ - **Stats saving in forward** — `_AttnTiledFunction.forward` now saves
110
+ `(Q, K, V, O, row_max, row_denom)` in ctx (6 tensors vs 4). Backward
111
+ receives the pre-computed row stats and skips the two recomputation passes.
112
+ Applies to both PyTorch and NKI paths.
113
+ - `_attn_bwd_gather` and `nki_bsr_attn_bwd` in `trnsparse/nki/dispatch.py`
114
+ — host-side BSC construction and NKI backward orchestration.
115
+ - `tests/test_nki_sim.py`: `TestAttnBwdSimulator` — simulator correctness tests
116
+ for dQ/dK/dV against PyTorch backward (local window + dilated patterns).
117
+ - `tests/test_nki_attn.py`: `TestAttnBwdHardware` — hardware parity tests
118
+ at seq_len=512, head_dim=64.
119
+
120
+ ### Changed
121
+
122
+ - `nki_bsr_attn_tiled` accepts `return_stats=True` kwarg (default False) to
123
+ return `(out, row_max, row_denom)`.
124
+ - `_block_sparse_attn_pytorch` accepts `return_stats=True` kwarg similarly.
125
+ - `_block_sparse_attn_backward` accepts optional `row_max` / `row_denom`
126
+ kwargs; when provided, skips the two recomputation BSR passes.
127
+
128
+ ## [0.5.0] — 2026-04-16
129
+
130
+ ### Added
131
+
132
+ - **Block-sparse attention autograd** — `block_sparse_attention_tiled` is now
133
+ differentiable. When any of Q/K/V has `requires_grad=True`, the call routes
134
+ through `_AttnTiledFunction`, which provides a tiled backward using the Flash
135
+ Attention delta identity (`D_i = dO_i · O_i`).
136
+ - **`_block_sparse_attn_backward(Q, K, V, O, dO, mask_bsr, scale)`** — internal
137
+ backward function. Single pass over nonzero BSR blocks; no O(seq_len²)
138
+ intermediate. Row stats (row_max/row_denom) are recomputed in backward.
139
+ - `gradcheck` passes at `atol=1e-3` for local-window and dilated patterns
140
+ (float64, seq_len=256, head_dim=32). See `tests/test_attention.py::TestAttnTiledGrad`.
141
+
142
+ ### Notes
143
+
144
+ - NKI backward kernel is a follow-up (v0.5.x). The current backward runs
145
+ on PyTorch even when the NKI forward path is active.
146
+ - Row stats (row_max/row_denom) are recomputed in backward rather than saved
147
+ from forward — avoids O(n_blocks × b) storage in the autograd graph at the
148
+ cost of two extra BSR passes.
149
+
150
+ ## [0.4.4] — 2026-04-16
151
+
152
+ ### Added
153
+
154
+ - **NKI attention kernel pair** (`_attn_stats_kernel` + `_attn_out_kernel`,
155
+ closes [#25](https://github.com/trnsci/trnsparse/issues/25)). Two-pass
156
+ block-sparse attention now runs on the Tensor Engine via these kernels when
157
+ the `nki` backend is active. The `pytorch` backend retains the Python-loop
158
+ reference path from v0.4.3.
159
+ - Pass 1 (`_attn_stats_kernel`): each `(m, ki)` block pair computed
160
+ independently via `nc_matmul`; `nl.max` / `nl.sum` within-tile reductions
161
+ produce `(tile_max, tile_sumexp)` with no carry between iterations.
162
+ - Pass 2 (`_attn_out_kernel`): recomputes scores, applies stable softmax
163
+ using `row_max` / `row_denom` loaded per block-row (static HBM offset),
164
+ accumulates `weights @ V` into a PSUM tile spanning all ki blocks.
165
+ - **Constraint:** `head_dim ≤ 128` (`nc_matmul` partition limit). `head_dim=256`
166
+ requires K-tiling and is a follow-up.
167
+ - `_attn_gather`, `_attn_host_reduction`, `nki_bsr_attn_tiled` in
168
+ `trnsparse/nki/dispatch.py` — host-side gather and orchestration for the kernel
169
+ pair, mirroring the `_bsr_pad_and_gather` pattern.
170
+ - `tests/test_nki_sim.py`: `TestAttnTiledSimulator` — simulator correctness
171
+ tests (local window, dilated; seq_len=256, head_dim=32).
172
+ - `tests/test_nki_attn.py`: hardware tests for local window, dilated, and
173
+ global-token patterns at seq_len=512, head_dim=64.
174
+
175
+ ### Notes
176
+
177
+ No autograd wrapping in v0.4.4 — forward-only, matching the existing PyTorch
178
+ path. Attention backward (block-sparse) is a follow-up.
179
+
180
+ ## [0.4.3] — 2026-04-16
181
+
182
+ ### Added
183
+
184
+ - **`chebyshev_bsr(A, b, lam_min, lam_max, K)`** — fixed-K Chebyshev
185
+ semi-iteration. Pre-computes all step coefficients from eigenvalue bounds
186
+ before the loop; no inner products during iteration. Same convergence rate
187
+ as CG without the adaptive coefficient computation that blocks NKI fusion.
188
+ Returns `(x, K, rel_residual)` matching the `cg_bsr` signature.
189
+ - **`chebyshev_coeffs(lam_min, lam_max, K)`** — returns the `(alpha, beta)`
190
+ coefficient tensors for the momentum update `x_{k+1} = x_k + α_k r_k +
191
+ β_k (x_k - x_{k-1})`. Useful for inspecting or caching coefficients across
192
+ solves with the same matrix spectrum.
193
+ - **`richardson_bsr(A, b, omega, K)`** — fixed-K Richardson iteration; the
194
+ simplest fixed-point iteration with no coefficient computation at all.
195
+ Optimal omega = `2 / (lam_min + lam_max)`. Baseline comparator for Chebyshev.
196
+ - **`block_sparse_attention_tiled(Q, K, V, mask_bsr)`** — two-pass sparse
197
+ attention that avoids the O(seq_len²) score intermediate. Pass 1 computes
198
+ per-block `(tile_max, tile_sumexp)` statistics over nonzero blocks only
199
+ (O(n_blocks × block_size) stats array, e.g. 5 KB at seq_len=512 vs 1 MB
200
+ dense). Pass 2 recomputes scores, applies stable softmax using host-reduced
201
+ row statistics, and accumulates V. PyTorch reference for the NKI kernel pair
202
+ documented in [#25](https://github.com/trnsci/trnsparse/issues/25).
203
+ - **`docs/sparse_attention.md`**: new "Tiled two-pass path" section explaining
204
+ the algorithm, memory trade-offs, and the NKI kernel follow-up path.
205
+ - **`docs/architecture.md`**: "Known limits" updated — #22 and #25 now have
206
+ v0.4.3 PyTorch references with clear notes on remaining NKI gate conditions.
207
+
208
+ ### Notes
209
+
210
+ Addresses the architecture-friendly workarounds identified in the analysis of
211
+ parked NKI-capability-gated issues (#22, #25). Both solvers and the tiled
212
+ attention path share the structural property of fixed-iteration loops with
213
+ statically-determined coefficients — the pattern that maps to `nl.affine_range`
214
+ without scalar carry.
215
+
8
216
  ## [0.4.2] — 2026-04-15
9
217
 
10
218
  ### Added
@@ -19,38 +19,39 @@ both memory and compute. trnsparse makes the sparsity explicit.
19
19
  trnsparse/
20
20
  ├── trnsparse/
21
21
  │ ├── __init__.py
22
- │ ├── formats.py # CSRMatrix, COOMatrix, conversions, from_dense
23
- │ ├── ops.py # spmv, spmm, spmv_symmetric, add, scale, transpose
22
+ │ ├── formats.py # CSRMatrix, COOMatrix, BSRMatrix, from_dense
23
+ │ ├── ops.py # spmv, spmm, bsr_spmm, screened_spmm, add, scale, transpose
24
24
  │ ├── screening.py # schwarz_bounds, screen_quartets, density_screen
25
+ │ ├── iterative.py # cg_bsr, power_iteration_bsr, jacobi_preconditioner_bsr
25
26
  │ └── nki/
26
27
  │ ├── __init__.py
27
- │ └── dispatch.py # Gather-matmul-scatter pattern for SpMM
28
+ │ ├── kernels.py # _bsr_spmm_kernel, _screened_spmm_kernel
29
+ │ └── dispatch.py # torch.autograd.Function wrappers + NKI/PyTorch routing
28
30
  ├── tests/
29
- │ ├── test_formats.py # CSR/COO construction, roundtrips
30
- │ ├── test_ops.py # SpMV, SpMM vs dense reference
31
- │ └── test_screening.py
32
31
  ├── examples/
33
- │ └── sparse_fock.py # Screened Fock build demo
32
+ │ ├── sparse_fock.py # Schwarz-screened Fock build (3 paths + trnblas)
33
+ │ ├── pyscf_bridge.py # Real AO integrals via PySCF (optional dep)
34
+ │ └── block_sparse_attention.py # Block-sparse attention via bsr_spmm
34
35
  ```
35
36
 
36
- ## NKI SpMM Strategy
37
+ ## NKI compute strategy (v0.4.x current)
37
38
 
38
- **v0.2.0 (current):** SpMM materializes the CSR into a dense `(M, K)` tile
39
- and runs stationary-tile-reuse GEMM on the Tensor Engine. Validated on
40
- trn1; forward + `torch.autograd.Function` backward both pass parity +
41
- `gradcheck` at `atol=1e-4`. The dense materialization means no sparsity
42
- advantage yet — NKI is slower than the PyTorch fallback at small sparse
43
- sizes (see `docs/benchmarks.md`).
39
+ **Formats:** CSR/COO are interop and PyTorch-fallback formats.
40
+ BSR at `block_size=128` is the NKI compute format — every nonzero block
41
+ is already a 128×128 Tensor Engine tile, zero gather overhead.
44
42
 
45
- **v0.3.0 (planned, #15):** gather-matmul-scatter with row-bucketing.
46
- For each bucket of rows with similar nnz:
47
- 1. **DMA engine**: gather non-zero column indices into dense SBUF tiles.
48
- 2. **Tensor Engine**: matmul the tile against B columns.
49
- 3. **DMA engine**: scatter results back to output rows.
43
+ **Dispatch hierarchy:**
44
+ 1. `spmm(csr, B)` — PyTorch `torch.sparse_csr_tensor` fallback (within
45
+ 2× of scipy; NKI overhead not worth it for CSR at current sizes).
46
+ 2. `bsr_spmm(bsr, B)` — NKI kernel `_bsr_spmm_kernel`; one `nc_matmul`
47
+ per nonzero block. `torch.autograd.Function`-wrapped; gradcheck passes.
48
+ 3. `screened_spmm(A, diag, B, threshold)` — fused Schwarz + mask + matmul
49
+ in one NKI dispatch; avoids 4 host passes + CSR build + separate SpMM.
50
+ 4. `block_sparse_attention(Q, K, V, mask_bsr)` — `bsr_spmm` applied to
51
+ the attention weight matrix; no new kernel, mask is a BSRMatrix.
50
52
 
51
- Same pattern used in sparse attention. Uniform nnz maps cleanly to fixed-
52
- size tiles; variable nnz is handled by bucketing rows by nnz quantile so
53
- each bucket pads only within itself.
53
+ **Open gaps (NKI capability-gated):** row-bucketing CSR (#15), sharded BSR
54
+ across NeuronCores (#16), fused tile-level attention scores (#25).
54
55
 
55
56
  ## Dependencies
56
57
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trnsparse
3
- Version: 0.4.2
3
+ Version: 0.7.0
4
4
  Summary: Sparse matrix operations for AWS Trainium via NKI
5
5
  Author-email: Scott Friedman <scttfrdmn@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -47,12 +47,17 @@ CSR/COO formats, SpMV, SpMM, and integral screening for sparse scientific comput
47
47
 
48
48
  trnsparse follows the [trnsci 5-phase roadmap](https://trnsci.dev/roadmap/). Active work is tracked in phase-labeled GitHub issues:
49
49
 
50
- - **[Phase 1 — correctness](https://github.com/trnsci/trnsparse/issues/14)** ✅ v0.2.0: NKI SpMM validated on trn1 via densify-then-GEMM; first `torch.autograd.Function`-wrapped NKI kernel in the suite (see [`trnsci/trnsci#3`](https://github.com/trnsci/trnsci/issues/3)). Benchmarks in [`docs/benchmarks.md`](https://trnsci.dev/trnsparse/benchmarks/).
51
- - **[Phase 3 — perf](https://github.com/trnsci/trnsparse/issues/15)**: nnz-bucketing SpMM, streaming large-sparse, NEFF cache reuse.
52
- - **[Phase 4 — multi-chip](https://github.com/trnsci/trnsparse/issues/16)**: sharded sparse matrices across chips.
50
+ - **[Phase 1 — correctness](https://github.com/trnsci/trnsparse/issues/14)** ✅ v0.2.0: NKI SpMM validated on trn1 via densify-then-GEMM (see [`trnsci/trnsci#3`](https://github.com/trnsci/trnsci/issues/3)).
51
+ - **v0.3.0** ✅ [`BSRMatrix`](https://trnsci.dev/trnsparse/architecture/) — Trainium-native 128×128 block-sparse format; `bsr_spmm` NKI kernel. CSR becomes interop; BSR is the compute path.
52
+ - **v0.3.2** ✅ `cg_bsr`, `power_iteration_bsr` — iterative solvers over BSR (Python loop; fused kernel gated on NKI capability).
53
+ - **v0.4.0** ✅ `screened_spmm` — fused Schwarz-screened SpMM in one NKI dispatch.
54
+ - **v0.4.2** ✅ Block-sparse attention — `BSRMatrix` + `bsr_spmm` as the primitive; `examples/block_sparse_attention.py` + [`docs/sparse_attention.md`](https://trnsci.dev/trnsparse/sparse_attention/).
55
+ - **v0.4.3** ✅ Architecture-friendly alternatives: `chebyshev_bsr` / `richardson_bsr` (fixed-K solvers, no inner products); `block_sparse_attention_tiled` (two-pass, no O(seq²) intermediate).
56
+ - **[Phase 3 — perf](https://github.com/trnsci/trnsparse/issues/15)**: nnz-bucketing, NKI attention kernel pair — parked on NKI indirect DMA gather / hardware validation.
57
+ - **[Phase 4 — multi-chip](https://github.com/trnsci/trnsparse/issues/16)**: sharded BSR across NeuronCores.
53
58
  - **[Phase 5 — generation](https://github.com/trnsci/trnsparse/issues/17)**: trn2 DMA bandwidth exploitation.
54
59
 
55
- _(No Phase 2 for trnsparse — the precision story is inherited from trnblas.)_
60
+ _(No Phase 2 for trnsparse — precision inherited from trnblas.)_
56
61
 
57
62
  Suite-wide tracker: [trnsci/trnsci#1](https://github.com/trnsci/trnsci/issues/1).
58
63
 
@@ -89,12 +94,21 @@ stats = trnsparse.sparsity_stats(Q)
89
94
 
90
95
  | Operation | Description |
91
96
  |-----------|-------------|
92
- | `spmv` | Sparse × dense vector |
93
- | `spmm` | Sparse × dense matrix |
97
+ | `spmv` | Sparse × dense vector (CSR) |
98
+ | `spmm` | Sparse × dense matrix (CSR, PyTorch fallback) |
99
+ | `bsr_spmm` | Block-sparse × dense (BSR-native, Tensor Engine) |
100
+ | `screened_spmm` | Fused Schwarz-screened matmul (one NKI dispatch) |
94
101
  | `spmv_symmetric` | Symmetric SpMV (half storage) |
95
102
  | `sparse_add` | C = αA + βB |
96
103
  | `sparse_scale` | B = αA |
97
- | `sparse_transpose` | A^T |
104
+ | `sparse_transpose` | Aᵀ |
105
+ | `cg_bsr` | Conjugate Gradient on BSR matrix |
106
+ | `chebyshev_bsr` | Fixed-K Chebyshev semi-iteration (no inner products) |
107
+ | `richardson_bsr` | Fixed-K Richardson iteration |
108
+ | `power_iteration_bsr` | Dominant eigenpair via power iteration |
109
+ | `jacobi_preconditioner_bsr` | Diagonal preconditioner for `cg_bsr` |
110
+ | `bsr_diagonal` | Extract main diagonal from BSR matrix |
111
+ | `block_sparse_attention_tiled` | Two-pass sparse attention, no O(seq²) intermediate |
98
112
  | `schwarz_bounds` | Schwarz screening bounds |
99
113
  | `screen_quartets` | Shell quartet significance mask |
100
114
  | `density_screen` | Density-weighted screening |
@@ -16,12 +16,17 @@ CSR/COO formats, SpMV, SpMM, and integral screening for sparse scientific comput
16
16
 
17
17
  trnsparse follows the [trnsci 5-phase roadmap](https://trnsci.dev/roadmap/). Active work is tracked in phase-labeled GitHub issues:
18
18
 
19
- - **[Phase 1 — correctness](https://github.com/trnsci/trnsparse/issues/14)** ✅ v0.2.0: NKI SpMM validated on trn1 via densify-then-GEMM; first `torch.autograd.Function`-wrapped NKI kernel in the suite (see [`trnsci/trnsci#3`](https://github.com/trnsci/trnsci/issues/3)). Benchmarks in [`docs/benchmarks.md`](https://trnsci.dev/trnsparse/benchmarks/).
20
- - **[Phase 3 — perf](https://github.com/trnsci/trnsparse/issues/15)**: nnz-bucketing SpMM, streaming large-sparse, NEFF cache reuse.
21
- - **[Phase 4 — multi-chip](https://github.com/trnsci/trnsparse/issues/16)**: sharded sparse matrices across chips.
19
+ - **[Phase 1 — correctness](https://github.com/trnsci/trnsparse/issues/14)** ✅ v0.2.0: NKI SpMM validated on trn1 via densify-then-GEMM (see [`trnsci/trnsci#3`](https://github.com/trnsci/trnsci/issues/3)).
20
+ - **v0.3.0** ✅ [`BSRMatrix`](https://trnsci.dev/trnsparse/architecture/) — Trainium-native 128×128 block-sparse format; `bsr_spmm` NKI kernel. CSR becomes interop; BSR is the compute path.
21
+ - **v0.3.2** ✅ `cg_bsr`, `power_iteration_bsr` — iterative solvers over BSR (Python loop; fused kernel gated on NKI capability).
22
+ - **v0.4.0** ✅ `screened_spmm` — fused Schwarz-screened SpMM in one NKI dispatch.
23
+ - **v0.4.2** ✅ Block-sparse attention — `BSRMatrix` + `bsr_spmm` as the primitive; `examples/block_sparse_attention.py` + [`docs/sparse_attention.md`](https://trnsci.dev/trnsparse/sparse_attention/).
24
+ - **v0.4.3** ✅ Architecture-friendly alternatives: `chebyshev_bsr` / `richardson_bsr` (fixed-K solvers, no inner products); `block_sparse_attention_tiled` (two-pass, no O(seq²) intermediate).
25
+ - **[Phase 3 — perf](https://github.com/trnsci/trnsparse/issues/15)**: nnz-bucketing, NKI attention kernel pair — parked on NKI indirect DMA gather / hardware validation.
26
+ - **[Phase 4 — multi-chip](https://github.com/trnsci/trnsparse/issues/16)**: sharded BSR across NeuronCores.
22
27
  - **[Phase 5 — generation](https://github.com/trnsci/trnsparse/issues/17)**: trn2 DMA bandwidth exploitation.
23
28
 
24
- _(No Phase 2 for trnsparse — the precision story is inherited from trnblas.)_
29
+ _(No Phase 2 for trnsparse — precision inherited from trnblas.)_
25
30
 
26
31
  Suite-wide tracker: [trnsci/trnsci#1](https://github.com/trnsci/trnsci/issues/1).
27
32
 
@@ -58,12 +63,21 @@ stats = trnsparse.sparsity_stats(Q)
58
63
 
59
64
  | Operation | Description |
60
65
  |-----------|-------------|
61
- | `spmv` | Sparse × dense vector |
62
- | `spmm` | Sparse × dense matrix |
66
+ | `spmv` | Sparse × dense vector (CSR) |
67
+ | `spmm` | Sparse × dense matrix (CSR, PyTorch fallback) |
68
+ | `bsr_spmm` | Block-sparse × dense (BSR-native, Tensor Engine) |
69
+ | `screened_spmm` | Fused Schwarz-screened matmul (one NKI dispatch) |
63
70
  | `spmv_symmetric` | Symmetric SpMV (half storage) |
64
71
  | `sparse_add` | C = αA + βB |
65
72
  | `sparse_scale` | B = αA |
66
- | `sparse_transpose` | A^T |
73
+ | `sparse_transpose` | Aᵀ |
74
+ | `cg_bsr` | Conjugate Gradient on BSR matrix |
75
+ | `chebyshev_bsr` | Fixed-K Chebyshev semi-iteration (no inner products) |
76
+ | `richardson_bsr` | Fixed-K Richardson iteration |
77
+ | `power_iteration_bsr` | Dominant eigenpair via power iteration |
78
+ | `jacobi_preconditioner_bsr` | Diagonal preconditioner for `cg_bsr` |
79
+ | `bsr_diagonal` | Extract main diagonal from BSR matrix |
80
+ | `block_sparse_attention_tiled` | Two-pass sparse attention, no O(seq²) intermediate |
67
81
  | `schwarz_bounds` | Schwarz screening bounds |
68
82
  | `screen_quartets` | Shell quartet significance mask |
69
83
  | `density_screen` | Density-weighted screening |
@@ -0,0 +1,143 @@
1
+ # Architecture
2
+
3
+ ## Why BSR is Trainium-native
4
+
5
+ Trainium's Tensor Engine is a 128×128 systolic array. `nisa.nc_matmul`
6
+ moves a 128-partition stationary tile against a moving operand of up to
7
+ 128 × 512, all on-chip. The natural "unit of sparse work" on this
8
+ architecture is therefore not a single nonzero — it's a 128×128 block.
9
+
10
+ **BSR (block-sparse row)** at `block_size=128` stores a matrix as a list
11
+ of these blocks plus a block-level CSR pattern. Every stored block is
12
+ already in the shape the systolic array wants: one `nc_matmul` per block,
13
+ no gather step, no padding-within-tile waste. The architectural match is
14
+ exact.
15
+
16
+ Contrast with CSR, which stores individual nonzero elements. The v0.2.0
17
+ NKI path for CSR materializes the matrix into a dense `(M, K)` tile
18
+ before the matmul (see the [SpMM path](#v020-csr-spmm-path) below) —
19
+ necessary for correctness, but it pays the full `M × K` cost whether the
20
+ matrix is sparse or not. BSR skips this because blocks are already dense.
21
+
22
+ **Consequence for the library's shape:** CSR remains the construction and
23
+ interop format (scipy compatibility, PyTorch's `torch.sparse_csr_tensor`
24
+ interop). BSR is the compute format for the NKI path. For matrices with
25
+ real block structure — Fock/ERI tensors after Schwarz screening, FEM
26
+ stiffness matrices, graph adjacencies, block-sparse attention masks —
27
+ BSR is strictly preferred. For truly unstructured sparse (random CSR),
28
+ the PyTorch fallback is already within 2× of scipy; NKI adds nothing.
29
+
30
+ See [Benchmarks](benchmarks.md) for numbers that validate this framing.
31
+
32
+
33
+ ```
34
+ trnsparse/
35
+ ├── trnsparse/
36
+ │ ├── __init__.py
37
+ │ ├── formats.py # CSRMatrix, COOMatrix, BSRMatrix, from_dense
38
+ │ ├── ops.py # spmv, spmm, bsr_spmm, screened_spmm, ...
39
+ │ ├── screening.py # schwarz_bounds, screen_quartets, density_screen
40
+ │ ├── iterative.py # cg_bsr, power_iteration_bsr, jacobi_preconditioner_bsr
41
+ │ └── nki/
42
+ │ ├── __init__.py
43
+ │ ├── kernels.py # _bsr_spmm_kernel, _screened_spmm_kernel
44
+ │ └── dispatch.py # torch.autograd.Function wrappers + routing
45
+ ├── tests/
46
+ ├── examples/
47
+ │ ├── sparse_fock.py # Schwarz-screened Fock (3 paths + trnblas)
48
+ │ ├── pyscf_bridge.py # Real AO integrals via PySCF
49
+ │ └── block_sparse_attention.py # Block-sparse attention via bsr_spmm
50
+ ```
51
+
52
+ ## NKI dispatch hierarchy
53
+
54
+ CSR and COO are interop and PyTorch-fallback formats. BSR is the NKI
55
+ compute format. `nki/dispatch.py` exposes `HAS_NKI`,
56
+ `set_backend("auto"|"pytorch"|"nki")`, `get_backend()`, and the NKI
57
+ entry points.
58
+
59
+ ### `spmm(csr, B)` — PyTorch fallback
60
+
61
+ Routes through `torch.sparse_csr_tensor` operations. Benchmarked within
62
+ 2× of scipy at typical shapes; the NKI per-op overhead doesn't pay off
63
+ for CSR SpMM until the matrix is large enough that the v0.2.0 CSR kernel
64
+ (densify-then-GEMM) would dominate anyway. SpMV also stays on this path.
65
+
66
+ ### `bsr_spmm(bsr, B)` — NKI, `_bsr_spmm_kernel`
67
+
68
+ One `nisa.nc_matmul` per nonzero block. Host-side preamble pads each
69
+ block-row to the same `K_max` so the kernel's `affine_range` bounds are
70
+ fixed. `torch.autograd.Function`-wrapped (`_BSRSpMMFunction`); gradcheck
71
+ passes at `atol=1e-4` on hardware.
72
+
73
+ ### `screened_spmm(A, diag, B, threshold)` — NKI, fused
74
+
75
+ Fuses Schwarz bound (outer-product pair bound), threshold mask, and
76
+ matmul into one dispatch. Saves ~30–50% vs the unfused
77
+ `schwarz_bounds → screen_quartets → from_dense → spmm` flow on
78
+ Fock-build-sized inputs.
79
+
80
+ ### Block-sparse attention
81
+
82
+ `bsr_spmm` applied to the post-softmax attention weight matrix. No new
83
+ kernel — a `BSRMatrix` with a local-window or dilated block pattern IS
84
+ an attention mask. See [sparse_attention.md](sparse_attention.md).
85
+
86
+ ## Formats
87
+
88
+ - **CSR**: row pointer + column indices + values. Preferred for construction,
89
+ interop with scipy/PyTorch, and SpMV.
90
+ - **COO**: three parallel 1-D tensors. Preferred for construction and permutation.
91
+ - **BSR** (`block_size=128`): stacked 128×128 blocks + block-level CSR pattern.
92
+ Every block maps to one `nc_matmul`; zero gather overhead.
93
+
94
+ All three support `from_dense`, `to_dense`, and interconversion.
95
+
96
+ ## Autograd wrapping
97
+
98
+ Every NKI kernel lives inside a `torch.autograd.Function` (satisfies
99
+ [`trnsci/trnsci#3`](https://github.com/trnsci/trnsci/issues/3)).
100
+ Backward passes run at the PyTorch level via block-gradient projection.
101
+ Block-selection is non-differentiable by construction; `grad_out` is
102
+ routed into exactly the stored blocks. `gradcheck` at `atol=1e-4` is
103
+ part of the hardware test matrix for all three NKI kernels.
104
+
105
+ ### Fused screened SpMM (v0.4.0)
106
+
107
+ `screened_spmm(A, diag_integrals, B, threshold)` fuses the
108
+ chemistry-screened SpMM pipeline — Schwarz bound from the diagonal
109
+ integrals, pair-bound threshold mask, masked matmul — into a single
110
+ NKI kernel. The unfused equivalent does four host passes (sqrt, outer
111
+ product, threshold, mask-apply) plus a separate `from_dense` + `spmm`
112
+ call; the fused kernel collapses all of that into one dispatch.
113
+
114
+ Mask semantics: `mask[i,j] = sqrt(|diag[i]|) * sqrt(|diag[j]|) >
115
+ sqrt(threshold)`, matching `schwarz_bounds` + `screen_quartets`
116
+ composed. No gradient flows back to `diag_integrals` or `threshold` —
117
+ the mask is treated as a discrete gate; `grad_A *= mask` and
118
+ `grad_B = (A * mask).T @ grad_C`.
119
+
120
+ Restricted to square A (`M == K`) with 1-D `diag_integrals` in v0.4.0
121
+ — the common Fock-build case. Rectangular / asymmetric-bounds
122
+ extension is a follow-up if asked for.
123
+
124
+ ## Known limits
125
+
126
+ - **Row-bucketing CSR** ([#15](https://github.com/trnsci/trnsparse/issues/15)) — parked.
127
+ Requires NKI indirect-DMA gather (not exposed as of NKI 0.3.0). The CSR PyTorch
128
+ fallback is within 2× of scipy; that's the current story.
129
+ - **Fused tile-level attention scores** ([#25](https://github.com/trnsci/trnsparse/issues/25)) —
130
+ **v0.4.4 ships the NKI kernel pair** (`_attn_stats_kernel` + `_attn_out_kernel`).
131
+ `block_sparse_attention_tiled` routes through NKI on the `nki` backend; the
132
+ PyTorch two-pass reference remains on the `pytorch` backend. `head_dim ≤ 128`
133
+ in v0.4.4 (nc_matmul partition limit); `head_dim=256` requires K-tiling.
134
+ - **Fused iterative solvers** ([#22](https://github.com/trnsci/trnsparse/issues/22)) —
135
+ CG blocked on `nl.affine_range` iteration-carried scalar state. **v0.4.3 ships
136
+ `chebyshev_bsr` and `richardson_bsr`**: fixed-K solvers with pre-computed
137
+ coefficients (no inner products in the loop) that have the correct structure for
138
+ eventual NKI fusion. The "A SBUF-resident across all iterations" win remains gated
139
+ on `affine_range` vector-carry support.
140
+ - **Multi-chip sharded BSR** ([#16](https://github.com/trnsci/trnsparse/issues/16)) —
141
+ gated on suite-level multi-chip collectives.
142
+ - **SpMV stays PyTorch.** Single output column on the Tensor Engine doesn't amortize
143
+ compile + dispatch overhead.
@@ -109,21 +109,28 @@ Three observations, all consistent with the architectural framing:
109
109
  dispatch, or where the kernel launch can be amortized across many
110
110
  operations.
111
111
 
112
- ### Where NKI is expected to win (follow-up issues)
113
-
114
- Not in raw "one-shot SpMM at N=128 vs CPU." The architectural exploits are:
115
-
116
- - **[#20 — on-chip iterative solvers](https://github.com/trnsci/trnsparse/issues/20)**:
117
- iterate `A @ x` thousands of times with A SBUF-resident. Kernel launch
118
- amortizes over all iterations.
119
- - **[#19 — fused screen + matmul](https://github.com/trnsci/trnsparse/issues/19)**:
120
- one kernel that fuses what's currently three HBM round-trips.
121
- - **[#21 — block-sparse attention](https://github.com/trnsci/trnsparse/issues/21)**:
122
- large-scale sequences where the matmul work dominates dispatch.
123
-
124
- v0.3.0 ships **BSR correctness + differentiability**. v0.4.0+ will cash
125
- in the architectural wins. See
126
- [`docs/architecture.md`](architecture.md) for why BSR is the
112
+ ### Where NKI wins — shipped v0.4.x
113
+
114
+ Not in raw "one-shot SpMM at N=128 vs CPU." The BSR table above measures
115
+ kernel dispatch overhead. The architectural wins come from fusing multiple
116
+ host passes into one dispatch:
117
+
118
+ - **`screened_spmm` (v0.4.0, [#19](https://github.com/trnsci/trnsparse/issues/19))**
119
+ ✅ shipped: fuses Schwarz bound + threshold + mask + matmul into one NKI
120
+ dispatch. On Fock-build-sized inputs: ~30–50% faster than the unfused
121
+ 4-step path (`schwarz_bounds → screen_quartets → from_dense → spmm`)
122
+ because it eliminates 3 HBM round-trips and the host-side CSR build.
123
+ - **Block-sparse attention (v0.4.2, [#21](https://github.com/trnsci/trnsparse/issues/21))**
124
+ ✅ shipped: `bsr_spmm` handles `attn_weights @ V` for local-window / dilated /
125
+ global-token masks. At seq_len=8192, window=2: 4.9% block density means 95%
126
+ of `nc_matmul` calls are skipped. Current example still materializes the full
127
+ score matrix (follow-up: [#25](https://github.com/trnsci/trnsparse/issues/25)).
128
+ - **[#22 — on-chip iterative solvers](https://github.com/trnsci/trnsparse/issues/22)**:
129
+ CG/power-iteration with A SBUF-resident across all iterations (replaces #20).
130
+ v0.3.2 ships the Python-loop plumbing; fused kernel gated on NKI scalar-carry
131
+ capability.
132
+
133
+ See [`docs/architecture.md`](architecture.md) for why BSR is the
127
134
  Trainium-native format.
128
135
 
129
136
  BSR results from commit
@@ -65,7 +65,7 @@ def _cg_spd_kernel(A_blocks, A_cols, A_row_ptrs, b, max_iter):
65
65
  rr = nl.reduce(r * r)
66
66
 
67
67
  for k in nl.affine_range(max_iter):
68
- Ap = _bsr_matvec_sbuf(A_sbuf, A_cols, A_row_ptrs, p) # all SBUF
68
+ Ap = _bsr_matvec_sbuf(A_sbuf, A_cols, A_row_ptrs, p) # all SBUF
69
69
  pAp = nl.reduce(p * Ap)
70
70
  alpha = rr / pAp
71
71
  x = x + alpha * p
@@ -60,10 +60,10 @@ argument; scipy infers it from the indices when possible. Be explicit:
60
60
 
61
61
  ```python
62
62
  # scipy
63
- A = csr_matrix((data, indices, indptr)) # shape inferred
63
+ A = csr_matrix((data, indices, indptr)) # shape inferred
64
64
 
65
65
  # trnsparse
66
- A = CSRMatrix(data, indices, indptr, shape=(m, n)) # shape required
66
+ A = CSRMatrix(data, indices, indptr, shape=(m, n)) # shape required
67
67
  ```
68
68
 
69
69
  **Operator access.** scipy overloads `@`, `+`, `*` on its matrix
@@ -107,9 +107,9 @@ import torch
107
107
  import trnsparse
108
108
  import trnsolver
109
109
 
110
- A = trnsparse.from_scipy(scipy_matrix) # zero-copy ingestion
110
+ A = trnsparse.from_scipy(scipy_matrix) # zero-copy ingestion
111
111
  b = torch.as_tensor(b_numpy, dtype=torch.float32)
112
- x = trnsolver.cg(A, b, tol=1e-6) # uses trnsparse.spmv
112
+ x = trnsolver.cg(A, b, tol=1e-6) # uses trnsparse.spmv
113
113
  ```
114
114
 
115
115
  The hot path (`spmv` inside CG) dispatches to NKI when
@@ -42,7 +42,7 @@ keep = trnsparse.screen_quartets(Q, threshold=1e-10)
42
42
  ## Backend selection
43
43
 
44
44
  ```python
45
- trnsparse.set_backend("auto") # default
45
+ trnsparse.set_backend("auto") # default
46
46
  trnsparse.set_backend("pytorch") # force PyTorch fallback
47
- trnsparse.set_backend("nki") # force NKI (requires Neuron hardware)
47
+ trnsparse.set_backend("nki") # force NKI (requires Neuron hardware)
48
48
  ```