trnsparse 0.4.2__tar.gz → 0.6.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.
- {trnsparse-0.4.2 → trnsparse-0.6.0}/.github/workflows/ci.yml +3 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/.pre-commit-config.yaml +1 -1
- {trnsparse-0.4.2 → trnsparse-0.6.0}/CHANGELOG.md +153 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/CLAUDE.md +23 -22
- {trnsparse-0.4.2 → trnsparse-0.6.0}/PKG-INFO +22 -8
- {trnsparse-0.4.2 → trnsparse-0.6.0}/README.md +21 -7
- trnsparse-0.6.0/docs/architecture.md +143 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/benchmarks.md +22 -15
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/sparse_attention.md +80 -13
- {trnsparse-0.4.2 → trnsparse-0.6.0}/examples/block_sparse_attention.py +25 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/pyproject.toml +1 -1
- trnsparse-0.6.0/tests/test_attention.py +327 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_iterative.py +91 -0
- trnsparse-0.6.0/tests/test_nki_attn.py +201 -0
- trnsparse-0.6.0/tests/test_nki_sim.py +432 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse/__init__.py +15 -2
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse/iterative.py +150 -0
- trnsparse-0.6.0/trnsparse/nki/dispatch.py +929 -0
- trnsparse-0.6.0/trnsparse/nki/kernels.py +573 -0
- trnsparse-0.6.0/trnsparse/ops.py +625 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse.egg-info/PKG-INFO +22 -8
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse.egg-info/SOURCES.txt +1 -0
- trnsparse-0.4.2/docs/architecture.md +0 -109
- trnsparse-0.4.2/tests/test_attention.py +0 -93
- trnsparse-0.4.2/tests/test_nki_sim.py +0 -153
- trnsparse-0.4.2/trnsparse/nki/dispatch.py +0 -487
- trnsparse-0.4.2/trnsparse/nki/kernels.py +0 -192
- trnsparse-0.4.2/trnsparse/ops.py +0 -277
- {trnsparse-0.4.2 → trnsparse-0.6.0}/.github/workflows/notify-umbrella.yml +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/.github/workflows/publish.yml +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/.gitignore +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/CODE_OF_CONDUCT.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/CONTRIBUTING.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/LICENSE +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/benchmarks/bench_bsr_spmm.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/benchmarks/bench_iterative.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/benchmarks/bench_screening.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/benchmarks/bench_spmm.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/benchmarks/bench_spmv.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/benchmarks/conftest.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/api.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/aws_setup.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/index.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/installation.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/iterative_solvers.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/migration_scipy.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/docs/quickstart.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/examples/sparse_fock.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/infra/terraform/.terraform.lock.hcl +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/infra/terraform/README.md +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/infra/terraform/main.tf +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/mkdocs.yml +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/scripts/bench_to_md.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/scripts/run_benchmarks.sh +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/scripts/run_neuron_tests.sh +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/scripts/run_simulator_tests.sh +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/setup.cfg +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/conftest.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_bsr.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_examples.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_formats.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_nki_bsr.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_nki_screened_spmm.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_nki_spmm.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_ops.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/tests/test_screening.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse/formats.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse/nki/__init__.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse/screening.py +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse.egg-info/dependency_links.txt +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.0}/trnsparse.egg-info/requires.txt +0 -0
- {trnsparse-0.4.2 → trnsparse-0.6.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
|
|
@@ -5,6 +5,159 @@ 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
|
+
## [0.6.0] — 2026-04-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **K-tiling for head_dim > 128** — NKI attention kernels now support
|
|
13
|
+
`head_dim=256` and `head_dim=512` via an inner K-tile loop that splits
|
|
14
|
+
the `Q @ K.T` score computation across `TILE_K=128`-sized chunks of
|
|
15
|
+
`head_dim`, accumulating each into the existing PSUM.
|
|
16
|
+
- All 4 NKI kernels updated: `_attn_stats_kernel`, `_attn_out_kernel`,
|
|
17
|
+
`_attn_bwd_dq_kernel`, `_attn_bwd_dkdv_kernel`.
|
|
18
|
+
- For `head_dim ≤ 128`: single-tile path preserved unchanged — Q is
|
|
19
|
+
loaded once per block-row as a stationary tile (no regression).
|
|
20
|
+
- For `head_dim > 128`: K-tile inner loop (`affine_range(head_dim //
|
|
21
|
+
128)`). `head_dim` must be a multiple of 128 (covers 256, 512).
|
|
22
|
+
- `weights @ V` and all dK/dV accumulation matmuls are unchanged (K=128
|
|
23
|
+
block dimension, unaffected by head_dim growth).
|
|
24
|
+
- `tests/test_attention.py`: `test_head_dim_256_parity`,
|
|
25
|
+
`test_head_dim_256_dilated_parity`, `test_gradcheck_head_dim_256`,
|
|
26
|
+
`test_backward_shapes_head_dim_256` in PyTorch path.
|
|
27
|
+
- `tests/test_nki_sim.py`: `TestAttnKTilingSimulator` — simulator parity
|
|
28
|
+
tests for forward and backward at head_dim=256.
|
|
29
|
+
- `tests/test_nki_attn.py`: `TestAttnHardwareLargeDim` — hardware parity
|
|
30
|
+
tests at seq_len=512, head_dim=256.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `_attn_gather` assertion relaxed from `head_dim ≤ 128` to `head_dim ≤ 128
|
|
35
|
+
or head_dim % 128 == 0`.
|
|
36
|
+
- `block_sparse_attention_tiled` and `nki_bsr_attn_tiled` docstrings updated
|
|
37
|
+
to document K-tiling support.
|
|
38
|
+
|
|
39
|
+
## [0.5.1] — 2026-04-16
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **NKI backward kernel pair** (`_attn_bwd_dq_kernel` + `_attn_bwd_dkdv_kernel`)
|
|
44
|
+
in `trnsparse/nki/kernels.py`. When the `nki` backend is active,
|
|
45
|
+
`_AttnTiledFunction.backward` now dispatches to these kernels instead of
|
|
46
|
+
the Python-loop backward.
|
|
47
|
+
- `_attn_bwd_dq_kernel`: row-first traversal, mirrors `_attn_out_kernel`.
|
|
48
|
+
Accumulates `dQ_m += dS @ K_ki * scale` in a PSUM tile per block-row.
|
|
49
|
+
- `_attn_bwd_dkdv_kernel`: column-first traversal (BSC view built on host).
|
|
50
|
+
For each column block ki, iterates over all block-rows m that attend to ki,
|
|
51
|
+
accumulating `dK_ki += dS.T @ Q_m * scale` and `dV_ki += P.T @ dO_m`.
|
|
52
|
+
- No atomic scatter required — column-first gather on host eliminates NKI's
|
|
53
|
+
lack of cross-block accumulation primitives.
|
|
54
|
+
- **Stats saving in forward** — `_AttnTiledFunction.forward` now saves
|
|
55
|
+
`(Q, K, V, O, row_max, row_denom)` in ctx (6 tensors vs 4). Backward
|
|
56
|
+
receives the pre-computed row stats and skips the two recomputation passes.
|
|
57
|
+
Applies to both PyTorch and NKI paths.
|
|
58
|
+
- `_attn_bwd_gather` and `nki_bsr_attn_bwd` in `trnsparse/nki/dispatch.py`
|
|
59
|
+
— host-side BSC construction and NKI backward orchestration.
|
|
60
|
+
- `tests/test_nki_sim.py`: `TestAttnBwdSimulator` — simulator correctness tests
|
|
61
|
+
for dQ/dK/dV against PyTorch backward (local window + dilated patterns).
|
|
62
|
+
- `tests/test_nki_attn.py`: `TestAttnBwdHardware` — hardware parity tests
|
|
63
|
+
at seq_len=512, head_dim=64.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- `nki_bsr_attn_tiled` accepts `return_stats=True` kwarg (default False) to
|
|
68
|
+
return `(out, row_max, row_denom)`.
|
|
69
|
+
- `_block_sparse_attn_pytorch` accepts `return_stats=True` kwarg similarly.
|
|
70
|
+
- `_block_sparse_attn_backward` accepts optional `row_max` / `row_denom`
|
|
71
|
+
kwargs; when provided, skips the two recomputation BSR passes.
|
|
72
|
+
|
|
73
|
+
## [0.5.0] — 2026-04-16
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- **Block-sparse attention autograd** — `block_sparse_attention_tiled` is now
|
|
78
|
+
differentiable. When any of Q/K/V has `requires_grad=True`, the call routes
|
|
79
|
+
through `_AttnTiledFunction`, which provides a tiled backward using the Flash
|
|
80
|
+
Attention delta identity (`D_i = dO_i · O_i`).
|
|
81
|
+
- **`_block_sparse_attn_backward(Q, K, V, O, dO, mask_bsr, scale)`** — internal
|
|
82
|
+
backward function. Single pass over nonzero BSR blocks; no O(seq_len²)
|
|
83
|
+
intermediate. Row stats (row_max/row_denom) are recomputed in backward.
|
|
84
|
+
- `gradcheck` passes at `atol=1e-3` for local-window and dilated patterns
|
|
85
|
+
(float64, seq_len=256, head_dim=32). See `tests/test_attention.py::TestAttnTiledGrad`.
|
|
86
|
+
|
|
87
|
+
### Notes
|
|
88
|
+
|
|
89
|
+
- NKI backward kernel is a follow-up (v0.5.x). The current backward runs
|
|
90
|
+
on PyTorch even when the NKI forward path is active.
|
|
91
|
+
- Row stats (row_max/row_denom) are recomputed in backward rather than saved
|
|
92
|
+
from forward — avoids O(n_blocks × b) storage in the autograd graph at the
|
|
93
|
+
cost of two extra BSR passes.
|
|
94
|
+
|
|
95
|
+
## [0.4.4] — 2026-04-16
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- **NKI attention kernel pair** (`_attn_stats_kernel` + `_attn_out_kernel`,
|
|
100
|
+
closes [#25](https://github.com/trnsci/trnsparse/issues/25)). Two-pass
|
|
101
|
+
block-sparse attention now runs on the Tensor Engine via these kernels when
|
|
102
|
+
the `nki` backend is active. The `pytorch` backend retains the Python-loop
|
|
103
|
+
reference path from v0.4.3.
|
|
104
|
+
- Pass 1 (`_attn_stats_kernel`): each `(m, ki)` block pair computed
|
|
105
|
+
independently via `nc_matmul`; `nl.max` / `nl.sum` within-tile reductions
|
|
106
|
+
produce `(tile_max, tile_sumexp)` with no carry between iterations.
|
|
107
|
+
- Pass 2 (`_attn_out_kernel`): recomputes scores, applies stable softmax
|
|
108
|
+
using `row_max` / `row_denom` loaded per block-row (static HBM offset),
|
|
109
|
+
accumulates `weights @ V` into a PSUM tile spanning all ki blocks.
|
|
110
|
+
- **Constraint:** `head_dim ≤ 128` (`nc_matmul` partition limit). `head_dim=256`
|
|
111
|
+
requires K-tiling and is a follow-up.
|
|
112
|
+
- `_attn_gather`, `_attn_host_reduction`, `nki_bsr_attn_tiled` in
|
|
113
|
+
`trnsparse/nki/dispatch.py` — host-side gather and orchestration for the kernel
|
|
114
|
+
pair, mirroring the `_bsr_pad_and_gather` pattern.
|
|
115
|
+
- `tests/test_nki_sim.py`: `TestAttnTiledSimulator` — simulator correctness
|
|
116
|
+
tests (local window, dilated; seq_len=256, head_dim=32).
|
|
117
|
+
- `tests/test_nki_attn.py`: hardware tests for local window, dilated, and
|
|
118
|
+
global-token patterns at seq_len=512, head_dim=64.
|
|
119
|
+
|
|
120
|
+
### Notes
|
|
121
|
+
|
|
122
|
+
No autograd wrapping in v0.4.4 — forward-only, matching the existing PyTorch
|
|
123
|
+
path. Attention backward (block-sparse) is a follow-up.
|
|
124
|
+
|
|
125
|
+
## [0.4.3] — 2026-04-16
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
|
|
129
|
+
- **`chebyshev_bsr(A, b, lam_min, lam_max, K)`** — fixed-K Chebyshev
|
|
130
|
+
semi-iteration. Pre-computes all step coefficients from eigenvalue bounds
|
|
131
|
+
before the loop; no inner products during iteration. Same convergence rate
|
|
132
|
+
as CG without the adaptive coefficient computation that blocks NKI fusion.
|
|
133
|
+
Returns `(x, K, rel_residual)` matching the `cg_bsr` signature.
|
|
134
|
+
- **`chebyshev_coeffs(lam_min, lam_max, K)`** — returns the `(alpha, beta)`
|
|
135
|
+
coefficient tensors for the momentum update `x_{k+1} = x_k + α_k r_k +
|
|
136
|
+
β_k (x_k - x_{k-1})`. Useful for inspecting or caching coefficients across
|
|
137
|
+
solves with the same matrix spectrum.
|
|
138
|
+
- **`richardson_bsr(A, b, omega, K)`** — fixed-K Richardson iteration; the
|
|
139
|
+
simplest fixed-point iteration with no coefficient computation at all.
|
|
140
|
+
Optimal omega = `2 / (lam_min + lam_max)`. Baseline comparator for Chebyshev.
|
|
141
|
+
- **`block_sparse_attention_tiled(Q, K, V, mask_bsr)`** — two-pass sparse
|
|
142
|
+
attention that avoids the O(seq_len²) score intermediate. Pass 1 computes
|
|
143
|
+
per-block `(tile_max, tile_sumexp)` statistics over nonzero blocks only
|
|
144
|
+
(O(n_blocks × block_size) stats array, e.g. 5 KB at seq_len=512 vs 1 MB
|
|
145
|
+
dense). Pass 2 recomputes scores, applies stable softmax using host-reduced
|
|
146
|
+
row statistics, and accumulates V. PyTorch reference for the NKI kernel pair
|
|
147
|
+
documented in [#25](https://github.com/trnsci/trnsparse/issues/25).
|
|
148
|
+
- **`docs/sparse_attention.md`**: new "Tiled two-pass path" section explaining
|
|
149
|
+
the algorithm, memory trade-offs, and the NKI kernel follow-up path.
|
|
150
|
+
- **`docs/architecture.md`**: "Known limits" updated — #22 and #25 now have
|
|
151
|
+
v0.4.3 PyTorch references with clear notes on remaining NKI gate conditions.
|
|
152
|
+
|
|
153
|
+
### Notes
|
|
154
|
+
|
|
155
|
+
Addresses the architecture-friendly workarounds identified in the analysis of
|
|
156
|
+
parked NKI-capability-gated issues (#22, #25). Both solvers and the tiled
|
|
157
|
+
attention path share the structural property of fixed-iteration loops with
|
|
158
|
+
statically-determined coefficients — the pattern that maps to `nl.affine_range`
|
|
159
|
+
without scalar carry.
|
|
160
|
+
|
|
8
161
|
## [0.4.2] — 2026-04-15
|
|
9
162
|
|
|
10
163
|
### 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,
|
|
23
|
-
│ ├── ops.py # spmv, spmm,
|
|
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
|
-
│
|
|
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
|
-
│
|
|
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
|
|
37
|
+
## NKI compute strategy (v0.4.x current)
|
|
37
38
|
|
|
38
|
-
**
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
2.
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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.
|
|
3
|
+
Version: 0.6.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
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
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 —
|
|
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
|
|
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
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
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 —
|
|
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
|
|
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
|
|
113
|
-
|
|
114
|
-
Not in raw "one-shot SpMM at N=128 vs CPU." The
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
@@ -142,21 +142,88 @@ The win is at long sequences. At `seq_len=1024`, a window-2 mask still stores
|
|
|
142
142
|
the per-block compute becomes the bottleneck, which is where the Tensor Engine
|
|
143
143
|
thrives.
|
|
144
144
|
|
|
145
|
-
##
|
|
145
|
+
## Tiled two-pass path (v0.4.3)
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
`block_sparse_attention_tiled` eliminates the `O(seq_len²)` score intermediate
|
|
148
|
+
without any new NKI primitives. The algorithm uses two passes over the nonzero
|
|
149
|
+
blocks:
|
|
149
150
|
|
|
150
|
-
1
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
**Pass 1 — per-block statistics**: For each stored block `(m, ki)`, compute
|
|
152
|
+
the score tile `Q[m] @ K[ki].T` and extract its row-wise max (`tile_max`,
|
|
153
|
+
shape `(block_size,)`) and stable row-wise exp-sum (`tile_sumexp`). Every
|
|
154
|
+
`(m, ki)` pair is independent — no iteration-carried state.
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
tiles is the authoring challenge.
|
|
156
|
+
**Host reduction**: For each block-row m, take the max of `tile_max` across all
|
|
157
|
+
its nonzero k-blocks (`row_max`) and compute the corrected denominator
|
|
158
|
+
`row_denom`. This is O(n_stored_blocks × block_size) work — at seq_len=4096
|
|
159
|
+
with window=2, about 160 block pairs × 128 = 20K operations, negligible.
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
**Pass 2 — stable softmax + accumulation**: Recompute each score tile, apply
|
|
162
|
+
`exp(score - row_max) / row_denom`, and accumulate `weights @ V_block` into
|
|
163
|
+
the output via a PSUM-style loop.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
out = trnsparse.block_sparse_attention_tiled(Q, K, V, mask_bsr)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Memory: `n_stored_blocks × block_size × 4` bytes for stats (5 KB at seq_len=512,
|
|
170
|
+
window=1) vs `seq_len² × 4` bytes for the naive dense score matrix (1 MB at
|
|
171
|
+
seq_len=512, 64 MB at seq_len=4096).
|
|
172
|
+
|
|
173
|
+
**On CPU**, the Python-level loops make this path slower than the vectorized
|
|
174
|
+
naive implementation — that's expected. The value of `block_sparse_attention_tiled`
|
|
175
|
+
is algorithmic: it validates the two-pass decomposition that the NKI kernel pair
|
|
176
|
+
(`_attn_stats_kernel` + `_attn_out_kernel`) will implement on hardware, where
|
|
177
|
+
per-block `nc_matmul` calls amortize the dispatch cost and the O(seq_len²)
|
|
178
|
+
allocation is the binding constraint.
|
|
179
|
+
|
|
180
|
+
The NKI implementation path is documented in
|
|
181
|
+
[#25](https://github.com/trnsci/trnsparse/issues/25).
|
|
182
|
+
|
|
183
|
+
## NKI kernel pair (v0.4.4)
|
|
184
|
+
|
|
185
|
+
`block_sparse_attention_tiled` now routes through the NKI kernel pair on the
|
|
186
|
+
`nki` backend. The `pytorch` backend retains the Python-loop reference path.
|
|
187
|
+
|
|
188
|
+
`_attn_stats_kernel` (pass 1) and `_attn_out_kernel` (pass 2) implement the
|
|
189
|
+
two-pass decomposition in `trnsparse/nki/kernels.py`:
|
|
190
|
+
|
|
191
|
+
- **Pass 1** (`_attn_stats_kernel`): each `(m, ki)` block pair is independent;
|
|
192
|
+
no carry. Uses `nl.max` and `nl.sum` reductions within the 128×128 SBUF tile.
|
|
193
|
+
Q block is loaded once as a transposed tile (stationary) and reused across all
|
|
194
|
+
ki blocks in the row.
|
|
195
|
+
- **Pass 2** (`_attn_out_kernel`): recomputes scores from Q and K to avoid
|
|
196
|
+
storing the full score tensor; uses `row_max` / `row_denom` from the host
|
|
197
|
+
reduction (loaded by static `affine_range` offset — legal). Accumulates
|
|
198
|
+
`weights @ V` into a PSUM tile that spans all ki blocks.
|
|
199
|
+
|
|
200
|
+
**Constraint:** `head_dim ≤ 128` in v0.4.4 (`nc_matmul` partition limit).
|
|
201
|
+
`head_dim=256` requires K-tiling and is a follow-up.
|
|
202
|
+
|
|
203
|
+
A runnable reference for both the naive and tiled paths is in
|
|
162
204
|
[`examples/block_sparse_attention.py`](https://github.com/trnsci/trnsparse/blob/main/examples/block_sparse_attention.py).
|
|
205
|
+
|
|
206
|
+
## Autograd (v0.5.0)
|
|
207
|
+
|
|
208
|
+
`block_sparse_attention_tiled` is now differentiable. When any of Q, K, or V
|
|
209
|
+
has `requires_grad=True`, the call is routed through `_AttnTiledFunction`,
|
|
210
|
+
which provides a tiled backward using the Flash Attention delta identity.
|
|
211
|
+
|
|
212
|
+
**Backward algorithm** (single pass over nonzero blocks, no O(seq_len²) intermediate):
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
D_i = dO_i · O_i # row-wise dot — Flash "delta"
|
|
216
|
+
For each stored block (m, ki):
|
|
217
|
+
P = stable_softmax(Q_m @ K_ki.T * scale)
|
|
218
|
+
dP = dO_m @ V_ki.T
|
|
219
|
+
dS = P * (dP - D_m) # softmax backward
|
|
220
|
+
dQ_m += dS @ K_ki * scale
|
|
221
|
+
dK_ki += dS.T @ Q_m * scale
|
|
222
|
+
dV_ki += P.T @ dO_m
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`torch.autograd.gradcheck` passes at `atol=1e-3` for local-window and dilated
|
|
226
|
+
patterns (float64, seq_len=256, head_dim=32).
|
|
227
|
+
|
|
228
|
+
**NKI backward kernel** is a follow-up (v0.5.x). The current backward runs on
|
|
229
|
+
CPU/PyTorch even when the forward used the NKI kernel pair.
|