trnsparse 0.4.1__tar.gz → 0.4.2__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.1 → trnsparse-0.4.2}/CHANGELOG.md +24 -0
- {trnsparse-0.4.1/trnsparse.egg-info → trnsparse-0.4.2}/PKG-INFO +14 -1
- {trnsparse-0.4.1 → trnsparse-0.4.2}/README.md +13 -0
- trnsparse-0.4.2/docs/sparse_attention.md +162 -0
- trnsparse-0.4.2/examples/block_sparse_attention.py +284 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/mkdocs.yml +2 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/pyproject.toml +1 -1
- trnsparse-0.4.2/tests/test_attention.py +93 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/__init__.py +1 -1
- {trnsparse-0.4.1 → trnsparse-0.4.2/trnsparse.egg-info}/PKG-INFO +14 -1
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse.egg-info/SOURCES.txt +3 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/.github/workflows/ci.yml +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/.github/workflows/notify-umbrella.yml +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/.github/workflows/publish.yml +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/.gitignore +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/.pre-commit-config.yaml +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/CLAUDE.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/CODE_OF_CONDUCT.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/CONTRIBUTING.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/LICENSE +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/benchmarks/bench_bsr_spmm.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/benchmarks/bench_iterative.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/benchmarks/bench_screening.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/benchmarks/bench_spmm.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/benchmarks/bench_spmv.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/benchmarks/conftest.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/api.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/architecture.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/aws_setup.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/benchmarks.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/index.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/installation.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/iterative_solvers.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/migration_scipy.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/docs/quickstart.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/examples/sparse_fock.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/infra/terraform/.terraform.lock.hcl +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/infra/terraform/README.md +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/infra/terraform/main.tf +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/scripts/bench_to_md.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/scripts/run_benchmarks.sh +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/scripts/run_neuron_tests.sh +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/scripts/run_simulator_tests.sh +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/setup.cfg +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/conftest.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_bsr.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_examples.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_formats.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_iterative.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_nki_bsr.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_nki_screened_spmm.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_nki_sim.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_nki_spmm.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_ops.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/tests/test_screening.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/formats.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/iterative.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/nki/__init__.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/nki/dispatch.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/nki/kernels.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/ops.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse/screening.py +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse.egg-info/dependency_links.txt +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse.egg-info/requires.txt +0 -0
- {trnsparse-0.4.1 → trnsparse-0.4.2}/trnsparse.egg-info/top_level.txt +0 -0
|
@@ -5,6 +5,30 @@ 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.4.2] — 2026-04-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`examples/block_sparse_attention.py`** — block-sparse attention
|
|
13
|
+
reference using `BSRMatrix` + `bsr_spmm`. Three mask patterns
|
|
14
|
+
(local window, dilated, global tokens); verifies against a dense
|
|
15
|
+
reference; reports block density and timing for the `bsr_spmm` step.
|
|
16
|
+
Closes [#21](https://github.com/trnsci/trnsparse/issues/21).
|
|
17
|
+
- **`docs/sparse_attention.md`** — writeup: how BSR-128 maps to
|
|
18
|
+
Longformer/BigBird-style attention masks, block density arithmetic,
|
|
19
|
+
pattern construction helpers, and the fused-tile follow-up.
|
|
20
|
+
- **`docs/iterative_solvers.md`** now linked in `mkdocs.yml` nav
|
|
21
|
+
(was present but missing from navigation).
|
|
22
|
+
- **`tests/test_attention.py`** — 8 CPU tests: mask shape/symmetry
|
|
23
|
+
checks + parity against dense reference at `atol=1e-4` for all three
|
|
24
|
+
patterns and the full-attention edge case.
|
|
25
|
+
|
|
26
|
+
### Notes
|
|
27
|
+
|
|
28
|
+
No API changes, no kernel changes. The claim in #21 is: `bsr_spmm` is
|
|
29
|
+
the block-sparse attention primitive; `BSRMatrix` captures the mask.
|
|
30
|
+
The example and docs make that explicit.
|
|
31
|
+
|
|
8
32
|
## [0.4.1] — 2026-04-14
|
|
9
33
|
|
|
10
34
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trnsparse
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
@@ -102,3 +102,16 @@ stats = trnsparse.sparsity_stats(Q)
|
|
|
102
102
|
## License
|
|
103
103
|
|
|
104
104
|
Apache 2.0 — Copyright 2026 Scott Friedman
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Disclaimer
|
|
108
|
+
|
|
109
|
+
trnsci is an **independent open-source project**. It is not sponsored by, endorsed by, or affiliated with Amazon.com, Inc., Amazon Web Services, Inc., or Annapurna Labs Ltd.
|
|
110
|
+
|
|
111
|
+
"AWS", "Amazon", "Trainium", "Inferentia", "NeuronCore", "Neuron SDK", and related identifiers are trademarks of their respective owners and are used here solely for descriptive and interoperability purposes. Use does not imply endorsement, partnership, or any other relationship.
|
|
112
|
+
|
|
113
|
+
All work, opinions, analyses, benchmark results, architectural commentary, and editorial judgments in this repository and on [trnsci.dev](https://trnsci.dev) are those of the project's contributors. They do not represent the views, positions, or commitments of Amazon, AWS, or Annapurna Labs.
|
|
114
|
+
|
|
115
|
+
Feedback directed at the Neuron SDK or Trainium hardware is good-faith ecosystem commentary from independent users. It is not privileged information, is not pre-reviewed by AWS, and should not be read as authoritative about product roadmap, behavior, or quality.
|
|
116
|
+
|
|
117
|
+
For official AWS guidance, see [aws-neuron documentation](https://awsdocs-neuron.readthedocs-hosted.com/) and the [AWS Trainium product page](https://aws.amazon.com/ai/machine-learning/trainium/).
|
|
@@ -71,3 +71,16 @@ stats = trnsparse.sparsity_stats(Q)
|
|
|
71
71
|
## License
|
|
72
72
|
|
|
73
73
|
Apache 2.0 — Copyright 2026 Scott Friedman
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Disclaimer
|
|
77
|
+
|
|
78
|
+
trnsci is an **independent open-source project**. It is not sponsored by, endorsed by, or affiliated with Amazon.com, Inc., Amazon Web Services, Inc., or Annapurna Labs Ltd.
|
|
79
|
+
|
|
80
|
+
"AWS", "Amazon", "Trainium", "Inferentia", "NeuronCore", "Neuron SDK", and related identifiers are trademarks of their respective owners and are used here solely for descriptive and interoperability purposes. Use does not imply endorsement, partnership, or any other relationship.
|
|
81
|
+
|
|
82
|
+
All work, opinions, analyses, benchmark results, architectural commentary, and editorial judgments in this repository and on [trnsci.dev](https://trnsci.dev) are those of the project's contributors. They do not represent the views, positions, or commitments of Amazon, AWS, or Annapurna Labs.
|
|
83
|
+
|
|
84
|
+
Feedback directed at the Neuron SDK or Trainium hardware is good-faith ecosystem commentary from independent users. It is not privileged information, is not pre-reviewed by AWS, and should not be read as authoritative about product roadmap, behavior, or quality.
|
|
85
|
+
|
|
86
|
+
For official AWS guidance, see [aws-neuron documentation](https://awsdocs-neuron.readthedocs-hosted.com/) and the [AWS Trainium product page](https://aws.amazon.com/ai/machine-learning/trainium/).
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Block-sparse attention
|
|
2
|
+
|
|
3
|
+
## The connection
|
|
4
|
+
|
|
5
|
+
Transformer attention computes `O = softmax(Q @ K.T / √d) @ V`. The expensive
|
|
6
|
+
part at long sequence lengths is the `(seq_len, seq_len)` score matrix and the
|
|
7
|
+
final `attn_weights @ V` matmul. Sparse attention restricts which positions can
|
|
8
|
+
attend to each other, zeroing out large regions of the score matrix. What
|
|
9
|
+
remains is a sparse × dense matmul: exactly `bsr_spmm`.
|
|
10
|
+
|
|
11
|
+
The block size that matters is 128. Trainium's Tensor Engine is a 128-partition
|
|
12
|
+
systolic array; `nc_matmul` consumes a 128×K×N tile in one call. A local-window
|
|
13
|
+
attention mask with window granularity of 128 tokens has exactly `2w+1` nonzero
|
|
14
|
+
128×128 blocks per block-row — each nonzero block maps one-to-one to one
|
|
15
|
+
`nc_matmul` call, with no gather overhead. The tile size of the mask and the
|
|
16
|
+
tile size of the hardware coincide.
|
|
17
|
+
|
|
18
|
+
cuSPARSE's BSR is a specialization added on top of CSR. Here it's the other
|
|
19
|
+
way: BSR is what the attention mask asks for, and the hardware is built around
|
|
20
|
+
that shape.
|
|
21
|
+
|
|
22
|
+
## Building patterns
|
|
23
|
+
|
|
24
|
+
### Local window (Longformer-style)
|
|
25
|
+
|
|
26
|
+
Each token block attends to its `window` nearest neighbors in block space:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import trnsparse
|
|
30
|
+
|
|
31
|
+
def local_window_mask(seq_len: int, block_size: int, window: int) -> trnsparse.BSRMatrix:
|
|
32
|
+
"""BSRMatrix for sliding-window attention."""
|
|
33
|
+
import torch
|
|
34
|
+
n_blocks = seq_len // block_size
|
|
35
|
+
block_mask = torch.zeros(n_blocks, n_blocks, dtype=torch.bool)
|
|
36
|
+
for i in range(n_blocks):
|
|
37
|
+
lo, hi = max(0, i - window), min(n_blocks, i + window + 1)
|
|
38
|
+
block_mask[i, lo:hi] = True
|
|
39
|
+
mask_dense = block_mask.repeat_interleave(block_size, 0).repeat_interleave(block_size, 1)
|
|
40
|
+
return trnsparse.BSRMatrix.from_dense(mask_dense.float(), block_size=block_size)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
At `seq_len=4096` and `window=2`: `5` nonzero blocks per row, `5/32 ≈ 15.6%`
|
|
44
|
+
block density vs 100% for dense attention. Memory for the stored blocks:
|
|
45
|
+
`32 rows × 5 blocks × 128 × 128 × 4 bytes ≈ 10 MB` vs 64 MB for the full
|
|
46
|
+
float32 attention matrix.
|
|
47
|
+
|
|
48
|
+
### Dilated sparse
|
|
49
|
+
|
|
50
|
+
Every `stride`-th block column is attended to. Block `(i, j)` is nonzero
|
|
51
|
+
iff `(i - j) % stride == 0`:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
def dilated_mask(seq_len: int, block_size: int, stride: int) -> trnsparse.BSRMatrix:
|
|
55
|
+
import torch
|
|
56
|
+
n_blocks = seq_len // block_size
|
|
57
|
+
block_mask = torch.zeros(n_blocks, n_blocks, dtype=torch.bool)
|
|
58
|
+
for i in range(n_blocks):
|
|
59
|
+
for j in range(n_blocks):
|
|
60
|
+
if (i - j) % stride == 0:
|
|
61
|
+
block_mask[i, j] = True
|
|
62
|
+
mask_dense = block_mask.repeat_interleave(block_size, 0).repeat_interleave(block_size, 1)
|
|
63
|
+
return trnsparse.BSRMatrix.from_dense(mask_dense.float(), block_size=block_size)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Global tokens (BigBird-style)
|
|
67
|
+
|
|
68
|
+
The first `n_global` token-blocks attend to and are attended to by every
|
|
69
|
+
block. Remaining block-rows follow a local window:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
def global_token_mask(
|
|
73
|
+
seq_len: int, block_size: int, window: int, n_global: int
|
|
74
|
+
) -> trnsparse.BSRMatrix:
|
|
75
|
+
import torch
|
|
76
|
+
n_blocks = seq_len // block_size
|
|
77
|
+
block_mask = torch.zeros(n_blocks, n_blocks, dtype=torch.bool)
|
|
78
|
+
block_mask[:n_global, :] = True # global rows attend everywhere
|
|
79
|
+
block_mask[:, :n_global] = True # global columns attended by all
|
|
80
|
+
for i in range(n_global, n_blocks):
|
|
81
|
+
lo, hi = max(0, i - window), min(n_blocks, i + window + 1)
|
|
82
|
+
block_mask[i, lo:hi] = True
|
|
83
|
+
mask_dense = block_mask.repeat_interleave(block_size, 0).repeat_interleave(block_size, 1)
|
|
84
|
+
return trnsparse.BSRMatrix.from_dense(mask_dense.float(), block_size=block_size)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## The computation
|
|
88
|
+
|
|
89
|
+
Given a `mask_bsr` constructed above:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import torch
|
|
93
|
+
|
|
94
|
+
def block_sparse_attention(Q, K, V, mask_bsr):
|
|
95
|
+
"""Q, K, V: (seq_len, head_dim) → (seq_len, head_dim)."""
|
|
96
|
+
scale = Q.shape[-1] ** -0.5
|
|
97
|
+
scores = (Q @ K.T) * scale
|
|
98
|
+
|
|
99
|
+
# Mask out unattended positions before softmax.
|
|
100
|
+
mask_dense = mask_bsr.to_dense().bool()
|
|
101
|
+
masked_scores = scores.masked_fill(~mask_dense, float("-inf"))
|
|
102
|
+
attn_weights = torch.softmax(masked_scores, dim=-1)
|
|
103
|
+
|
|
104
|
+
# Positions outside the mask go to zero after softmax; from_dense
|
|
105
|
+
# with threshold=0 will still store them unless they're exactly zero.
|
|
106
|
+
# Use threshold slightly above zero to keep only the attended blocks.
|
|
107
|
+
attn_bsr = trnsparse.BSRMatrix.from_dense(
|
|
108
|
+
attn_weights, block_size=mask_bsr.block_size, threshold=1e-9
|
|
109
|
+
)
|
|
110
|
+
return trnsparse.bsr_spmm(attn_bsr, V)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**What this materializes**: the full `(seq_len, seq_len)` score matrix. At
|
|
114
|
+
`seq_len=4096` that's 64 MB of float32 even before the matmul. See
|
|
115
|
+
[What's next](#whats-next) for the fused-tile path that avoids this.
|
|
116
|
+
|
|
117
|
+
**Gradients**: `bsr_spmm` is differentiable (see `architecture.md`). The
|
|
118
|
+
backward through `block_sparse_attention` works out-of-the-box with
|
|
119
|
+
`torch.autograd`; the block-selection step (`from_dense`) is non-differentiable
|
|
120
|
+
by construction and is treated as a constant by the autograd graph.
|
|
121
|
+
|
|
122
|
+
## Block density arithmetic
|
|
123
|
+
|
|
124
|
+
For `seq_len = S`, `block_size = b`, `window = w` local-window mask:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
blocks per row = 2w + 1 (clamped at edges)
|
|
128
|
+
block density = (2w+1) / (S/b)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| seq_len | window | n_blocks_per_row | block density |
|
|
132
|
+
|--------:|-------:|-----------------:|:--------------|
|
|
133
|
+
| 1024 | 2 | 5 | 39.1% |
|
|
134
|
+
| 2048 | 2 | 5 | 19.5% |
|
|
135
|
+
| 4096 | 2 | 5 | 9.8% |
|
|
136
|
+
| 4096 | 4 | 9 | 17.6% |
|
|
137
|
+
| 8192 | 2 | 5 | 4.9% |
|
|
138
|
+
|
|
139
|
+
The win is at long sequences. At `seq_len=1024`, a window-2 mask still stores
|
|
140
|
+
40% of blocks — dispatch overhead dominates on Trainium at that size. At
|
|
141
|
+
`seq_len=8192`, 5% density means 95% of the `nc_matmul` calls are skipped and
|
|
142
|
+
the per-block compute becomes the bottleneck, which is where the Tensor Engine
|
|
143
|
+
thrives.
|
|
144
|
+
|
|
145
|
+
## What's next
|
|
146
|
+
|
|
147
|
+
The current path materializes the full `(seq_len, seq_len)` score matrix to
|
|
148
|
+
compute attention weights. A production path would:
|
|
149
|
+
|
|
150
|
+
1. Iterate over nonzero blocks in `mask_bsr`.
|
|
151
|
+
2. For each block `(i, j)`, load `Q[i*b:(i+1)*b]` and `K[j*b:(j+1)*b]` into
|
|
152
|
+
SBUF, compute the score tile, and apply softmax over the block-row.
|
|
153
|
+
3. Multiply the score tile by `V[j*b:(j+1)*b]` and accumulate into the output.
|
|
154
|
+
|
|
155
|
+
This fused tile-level kernel avoids the `O(seq_len²)` intermediate entirely.
|
|
156
|
+
It's the same architectural opportunity as [on-chip iterative solvers](iterative_solvers.md):
|
|
157
|
+
load A once, iterate on-chip. The NKI building block is available (the BSR
|
|
158
|
+
kernel in `nki/kernels.py`); the row-wise softmax over variable numbers of
|
|
159
|
+
tiles is the authoring challenge.
|
|
160
|
+
|
|
161
|
+
A runnable reference for the current (non-fused) path is in
|
|
162
|
+
[`examples/block_sparse_attention.py`](https://github.com/trnsci/trnsparse/blob/main/examples/block_sparse_attention.py).
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Block-sparse attention via BSRMatrix and bsr_spmm.
|
|
2
|
+
|
|
3
|
+
The final matmul in sparse attention — `attn_weights @ V` — is a sparse ×
|
|
4
|
+
dense matrix multiplication. When the attention mask has 128×128 block
|
|
5
|
+
structure (local window, dilated, global tokens), that matmul is exactly
|
|
6
|
+
`bsr_spmm(attn_weights_bsr, V)`. No new kernel; BSRMatrix captures the
|
|
7
|
+
sparsity pattern, and the Tensor Engine sees a sequence of dense 128×128
|
|
8
|
+
tiles with zero gather overhead.
|
|
9
|
+
|
|
10
|
+
Three mask patterns are demonstrated:
|
|
11
|
+
|
|
12
|
+
local — sliding-window (Longformer-style): each token attends to the
|
|
13
|
+
nearest `window` blocks. Block (i, j) is stored iff |i-j| ≤ window.
|
|
14
|
+
dilated — every `stride`-th block column is stored. Covers dilated sparse
|
|
15
|
+
attention variants.
|
|
16
|
+
global — first `n_global` token blocks attend to and are attended to by
|
|
17
|
+
everyone; remaining rows follow a local window.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
python examples/block_sparse_attention.py --demo
|
|
21
|
+
python examples/block_sparse_attention.py --seq-len 512 --pattern dilated
|
|
22
|
+
python examples/block_sparse_attention.py --seq-len 1024 --head-dim 64 --pattern global
|
|
23
|
+
|
|
24
|
+
Note on materialization: this example computes the full score matrix
|
|
25
|
+
`Q @ K.T` before masking. A production path would compute only the nonzero
|
|
26
|
+
tiles of S = Q @ K.T, avoiding the O(seq_len²) intermediate. That fused
|
|
27
|
+
tile-level score computation is the architectural follow-up; here the claim
|
|
28
|
+
being demonstrated is that `bsr_spmm` handles the attn_weights × V step.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import sys
|
|
35
|
+
import time
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
import torch
|
|
39
|
+
|
|
40
|
+
# Allow running from repo root without install.
|
|
41
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
42
|
+
import trnsparse
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Mask pattern helpers
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _local_window_mask(seq_len: int, block_size: int, window: int) -> torch.Tensor:
|
|
50
|
+
"""Boolean mask for local-window (Longformer-style) attention.
|
|
51
|
+
|
|
52
|
+
Block (i, j) in block-row i, block-column j is nonzero iff |i - j| ≤ window.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
bool tensor of shape (seq_len, seq_len).
|
|
56
|
+
"""
|
|
57
|
+
assert seq_len % block_size == 0, "seq_len must be a multiple of block_size"
|
|
58
|
+
n_blocks = seq_len // block_size
|
|
59
|
+
block_mask = torch.zeros(n_blocks, n_blocks, dtype=torch.bool)
|
|
60
|
+
for i in range(n_blocks):
|
|
61
|
+
lo = max(0, i - window)
|
|
62
|
+
hi = min(n_blocks, i + window + 1)
|
|
63
|
+
block_mask[i, lo:hi] = True
|
|
64
|
+
# Expand each block indicator to (block_size, block_size)
|
|
65
|
+
return block_mask.repeat_interleave(block_size, dim=0).repeat_interleave(block_size, dim=1)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _dilated_mask(seq_len: int, block_size: int, stride: int) -> torch.Tensor:
|
|
69
|
+
"""Boolean mask for dilated sparse attention.
|
|
70
|
+
|
|
71
|
+
Block (i, j) is nonzero iff (i - j) % stride == 0 (i.e., block column j
|
|
72
|
+
is a multiple of stride away from block row i, including j == i).
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
bool tensor of shape (seq_len, seq_len).
|
|
76
|
+
"""
|
|
77
|
+
assert seq_len % block_size == 0
|
|
78
|
+
n_blocks = seq_len // block_size
|
|
79
|
+
block_mask = torch.zeros(n_blocks, n_blocks, dtype=torch.bool)
|
|
80
|
+
for i in range(n_blocks):
|
|
81
|
+
for j in range(n_blocks):
|
|
82
|
+
if (i - j) % stride == 0:
|
|
83
|
+
block_mask[i, j] = True
|
|
84
|
+
return block_mask.repeat_interleave(block_size, dim=0).repeat_interleave(block_size, dim=1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _global_token_mask(seq_len: int, block_size: int, window: int, n_global: int) -> torch.Tensor:
|
|
88
|
+
"""Boolean mask with global tokens + local window (BigBird-style).
|
|
89
|
+
|
|
90
|
+
First `n_global` token-blocks attend to all blocks and are attended to
|
|
91
|
+
by all blocks. Remaining rows follow a local window of `window` blocks.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
bool tensor of shape (seq_len, seq_len).
|
|
95
|
+
"""
|
|
96
|
+
assert seq_len % block_size == 0
|
|
97
|
+
n_blocks = seq_len // block_size
|
|
98
|
+
assert 0 <= n_global <= n_blocks
|
|
99
|
+
block_mask = torch.zeros(n_blocks, n_blocks, dtype=torch.bool)
|
|
100
|
+
# Global rows: attend everywhere
|
|
101
|
+
block_mask[:n_global, :] = True
|
|
102
|
+
# Global columns: attended to by everyone
|
|
103
|
+
block_mask[:, :n_global] = True
|
|
104
|
+
# Local window for non-global rows
|
|
105
|
+
for i in range(n_global, n_blocks):
|
|
106
|
+
lo = max(0, i - window)
|
|
107
|
+
hi = min(n_blocks, i + window + 1)
|
|
108
|
+
block_mask[i, lo:hi] = True
|
|
109
|
+
return block_mask.repeat_interleave(block_size, dim=0).repeat_interleave(block_size, dim=1)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Attention computation
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def block_sparse_attention(
|
|
118
|
+
Q: torch.Tensor,
|
|
119
|
+
K: torch.Tensor,
|
|
120
|
+
V: torch.Tensor,
|
|
121
|
+
mask_bsr: trnsparse.BSRMatrix,
|
|
122
|
+
) -> torch.Tensor:
|
|
123
|
+
"""Compute sparse attention output using bsr_spmm for the final matmul.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
Q: (seq_len, head_dim) query tensor.
|
|
127
|
+
K: (seq_len, head_dim) key tensor.
|
|
128
|
+
V: (seq_len, head_dim) value tensor.
|
|
129
|
+
mask_bsr: BSRMatrix encoding which attention blocks are nonzero.
|
|
130
|
+
The blocks themselves are ignored — only the sparsity pattern
|
|
131
|
+
(block_row_ptrs, block_col_indices) is used. The actual weights
|
|
132
|
+
come from softmax(Q @ K.T).
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
(seq_len, head_dim) attention output.
|
|
136
|
+
|
|
137
|
+
Note: this materializes the full (seq_len, seq_len) score matrix Q @ K.T
|
|
138
|
+
before masking. See module docstring on the production alternative.
|
|
139
|
+
"""
|
|
140
|
+
seq_len, head_dim = Q.shape
|
|
141
|
+
scale = head_dim**-0.5
|
|
142
|
+
|
|
143
|
+
# Full score matrix — O(seq_len²). A production path computes only the
|
|
144
|
+
# nonzero tiles of S to avoid this intermediate.
|
|
145
|
+
scores = (Q @ K.T) * scale # (seq_len, seq_len)
|
|
146
|
+
|
|
147
|
+
# Apply additive mask: positions outside the pattern → -inf before softmax.
|
|
148
|
+
mask_dense = mask_bsr.to_dense().bool()
|
|
149
|
+
masked_scores = scores.masked_fill(~mask_dense, float("-inf"))
|
|
150
|
+
|
|
151
|
+
# Row-wise softmax over all attended positions (positions outside mask → 0).
|
|
152
|
+
attn_weights = torch.softmax(masked_scores, dim=-1) # (seq_len, seq_len)
|
|
153
|
+
|
|
154
|
+
# Convert the post-softmax weights to BSR (same pattern, real values now).
|
|
155
|
+
attn_bsr = trnsparse.BSRMatrix.from_dense(attn_weights, block_size=mask_bsr.block_size)
|
|
156
|
+
|
|
157
|
+
# Final matmul via bsr_spmm: (seq_len, seq_len) sparse × (seq_len, head_dim) dense.
|
|
158
|
+
return trnsparse.bsr_spmm(attn_bsr, V) # (seq_len, head_dim)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _dense_reference(
|
|
162
|
+
Q: torch.Tensor,
|
|
163
|
+
K: torch.Tensor,
|
|
164
|
+
V: torch.Tensor,
|
|
165
|
+
mask: torch.Tensor,
|
|
166
|
+
) -> torch.Tensor:
|
|
167
|
+
"""Dense reference: full softmax(Q @ K.T) @ V with additive mask."""
|
|
168
|
+
scale = Q.shape[-1] ** -0.5
|
|
169
|
+
scores = (Q @ K.T) * scale
|
|
170
|
+
masked = scores.masked_fill(~mask, float("-inf"))
|
|
171
|
+
attn = torch.softmax(masked, dim=-1)
|
|
172
|
+
return attn @ V
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
# CLI
|
|
177
|
+
# ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main() -> None:
|
|
181
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
182
|
+
parser.add_argument("--demo", action="store_true", help="run a small demo (seq_len=512)")
|
|
183
|
+
parser.add_argument("--seq-len", type=int, default=512)
|
|
184
|
+
parser.add_argument("--head-dim", type=int, default=64)
|
|
185
|
+
parser.add_argument(
|
|
186
|
+
"--pattern",
|
|
187
|
+
choices=["local", "dilated", "global"],
|
|
188
|
+
default="local",
|
|
189
|
+
help="attention mask pattern",
|
|
190
|
+
)
|
|
191
|
+
parser.add_argument(
|
|
192
|
+
"--window", type=int, default=2, help="block window for local/global patterns"
|
|
193
|
+
)
|
|
194
|
+
parser.add_argument("--stride", type=int, default=2, help="block stride for dilated pattern")
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"--n-global", type=int, default=2, help="global token blocks for global pattern"
|
|
197
|
+
)
|
|
198
|
+
parser.add_argument("--block-size", type=int, default=128, help="BSR block size")
|
|
199
|
+
args = parser.parse_args()
|
|
200
|
+
|
|
201
|
+
if args.demo:
|
|
202
|
+
args.seq_len = 512
|
|
203
|
+
args.head_dim = 64
|
|
204
|
+
args.pattern = "local"
|
|
205
|
+
args.window = 1
|
|
206
|
+
|
|
207
|
+
seq_len = args.seq_len
|
|
208
|
+
head_dim = args.head_dim
|
|
209
|
+
block_size = args.block_size
|
|
210
|
+
|
|
211
|
+
if seq_len % block_size != 0:
|
|
212
|
+
print(f"Error: seq_len ({seq_len}) must be a multiple of block_size ({block_size}).")
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
torch.manual_seed(42)
|
|
216
|
+
Q = torch.randn(seq_len, head_dim)
|
|
217
|
+
K = torch.randn(seq_len, head_dim)
|
|
218
|
+
V = torch.randn(seq_len, head_dim)
|
|
219
|
+
|
|
220
|
+
# Build the mask.
|
|
221
|
+
if args.pattern == "local":
|
|
222
|
+
mask = _local_window_mask(seq_len, block_size, args.window)
|
|
223
|
+
pattern_desc = f"local window (w={args.window})"
|
|
224
|
+
elif args.pattern == "dilated":
|
|
225
|
+
mask = _dilated_mask(seq_len, block_size, args.stride)
|
|
226
|
+
pattern_desc = f"dilated (stride={args.stride})"
|
|
227
|
+
else:
|
|
228
|
+
mask = _global_token_mask(seq_len, block_size, args.window, args.n_global)
|
|
229
|
+
pattern_desc = f"global tokens (n_global={args.n_global}, w={args.window})"
|
|
230
|
+
|
|
231
|
+
mask_bsr = trnsparse.BSRMatrix.from_dense(mask.float(), block_size=block_size)
|
|
232
|
+
n_blocks_total = (seq_len // block_size) ** 2
|
|
233
|
+
block_density = mask_bsr.n_blocks / n_blocks_total
|
|
234
|
+
|
|
235
|
+
print("Block-sparse attention via bsr_spmm:")
|
|
236
|
+
print(f" seq_len: {seq_len}")
|
|
237
|
+
print(f" head_dim: {head_dim}")
|
|
238
|
+
print(f" block_size: {block_size}")
|
|
239
|
+
print(f" pattern: {pattern_desc}")
|
|
240
|
+
print(
|
|
241
|
+
f" n_blocks: {mask_bsr.n_blocks} / {n_blocks_total} ({block_density:.1%} block density)"
|
|
242
|
+
)
|
|
243
|
+
print()
|
|
244
|
+
|
|
245
|
+
# Dense reference.
|
|
246
|
+
out_dense = _dense_reference(Q, K, V, mask)
|
|
247
|
+
|
|
248
|
+
# BSR path.
|
|
249
|
+
out_bsr = block_sparse_attention(Q, K, V, mask_bsr)
|
|
250
|
+
|
|
251
|
+
max_err = (out_bsr - out_dense).abs().max().item()
|
|
252
|
+
print(f" Numerical agreement: max |Δ| = {max_err:.2e}")
|
|
253
|
+
assert max_err < 1e-3, f"BSR and dense outputs disagree: max_err={max_err}"
|
|
254
|
+
|
|
255
|
+
# Timing: attn_weights @ V, BSR vs dense.
|
|
256
|
+
# Pre-compute attn_weights for a fair comparison of just the matmul step.
|
|
257
|
+
scale = head_dim**-0.5
|
|
258
|
+
masked_scores = (Q @ K.T * scale).masked_fill(~mask, float("-inf"))
|
|
259
|
+
attn_weights = torch.softmax(masked_scores, dim=-1)
|
|
260
|
+
attn_bsr_timed = trnsparse.BSRMatrix.from_dense(attn_weights, block_size=block_size)
|
|
261
|
+
|
|
262
|
+
N_REPS = 20
|
|
263
|
+
|
|
264
|
+
t0 = time.perf_counter()
|
|
265
|
+
for _ in range(N_REPS):
|
|
266
|
+
_ = trnsparse.bsr_spmm(attn_bsr_timed, V)
|
|
267
|
+
t_bsr = (time.perf_counter() - t0) / N_REPS
|
|
268
|
+
|
|
269
|
+
t0 = time.perf_counter()
|
|
270
|
+
for _ in range(N_REPS):
|
|
271
|
+
_ = attn_weights @ V
|
|
272
|
+
t_dense = (time.perf_counter() - t0) / N_REPS
|
|
273
|
+
|
|
274
|
+
print(f" Timing (attn_weights @ V, {N_REPS}-rep mean):")
|
|
275
|
+
print(f" bsr_spmm: {t_bsr * 1e6:8.1f} μs")
|
|
276
|
+
print(f" dense @: {t_dense * 1e6:8.1f} μs")
|
|
277
|
+
print()
|
|
278
|
+
print(" Note: on CPU the PyTorch fallback is used. bsr_spmm wins at")
|
|
279
|
+
print(" large seq_len on Trainium, where dispatch overhead amortizes")
|
|
280
|
+
print(" against the tile-level nc_matmul throughput advantage.")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
if __name__ == "__main__":
|
|
284
|
+
main()
|
|
@@ -24,6 +24,8 @@ nav:
|
|
|
24
24
|
- Architecture: architecture.md
|
|
25
25
|
- Migration from scipy.sparse: migration_scipy.md
|
|
26
26
|
- Benchmarks: benchmarks.md
|
|
27
|
+
- Iterative Solvers: iterative_solvers.md
|
|
28
|
+
- Sparse Attention: sparse_attention.md
|
|
27
29
|
- Deployment:
|
|
28
30
|
- AWS Setup for Neuron CI: aws_setup.md
|
|
29
31
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Tests for block-sparse attention example and pattern helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
import trnsparse
|
|
11
|
+
|
|
12
|
+
EXAMPLES = Path(__file__).resolve().parent.parent / "examples"
|
|
13
|
+
sys.path.insert(0, str(EXAMPLES))
|
|
14
|
+
|
|
15
|
+
import block_sparse_attention as demo # noqa: E402
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TestMaskShapes:
|
|
19
|
+
"""Pattern helper sanity checks — block count and coverage."""
|
|
20
|
+
|
|
21
|
+
def test_local_window_mask_shape(self):
|
|
22
|
+
seq_len, block_size, window = 512, 128, 2
|
|
23
|
+
mask = demo._local_window_mask(seq_len, block_size, window)
|
|
24
|
+
assert mask.shape == (seq_len, seq_len)
|
|
25
|
+
assert mask.dtype == torch.bool
|
|
26
|
+
# Each block-row should have at most 2*window+1 nonzero blocks.
|
|
27
|
+
n_blocks = seq_len // block_size
|
|
28
|
+
bsr = trnsparse.BSRMatrix.from_dense(mask.float(), block_size=block_size)
|
|
29
|
+
for i in range(n_blocks):
|
|
30
|
+
row_count = (bsr.block_row_ptrs[i + 1] - bsr.block_row_ptrs[i]).item()
|
|
31
|
+
assert row_count <= 2 * window + 1
|
|
32
|
+
|
|
33
|
+
def test_local_window_mask_symmetric(self):
|
|
34
|
+
mask = demo._local_window_mask(256, 128, 1)
|
|
35
|
+
assert (mask == mask.T).all(), "local window mask should be symmetric"
|
|
36
|
+
|
|
37
|
+
def test_dilated_mask_shape(self):
|
|
38
|
+
seq_len, block_size, stride = 512, 128, 2
|
|
39
|
+
mask = demo._dilated_mask(seq_len, block_size, stride)
|
|
40
|
+
assert mask.shape == (seq_len, seq_len)
|
|
41
|
+
n_blocks = seq_len // block_size
|
|
42
|
+
# For stride=2 and n_blocks=4: blocks 0,2 from row 0; blocks 1,3 from row 1; etc.
|
|
43
|
+
bsr = trnsparse.BSRMatrix.from_dense(mask.float(), block_size=block_size)
|
|
44
|
+
expected_per_row = (n_blocks + stride - 1) // stride
|
|
45
|
+
for i in range(n_blocks):
|
|
46
|
+
row_count = (bsr.block_row_ptrs[i + 1] - bsr.block_row_ptrs[i]).item()
|
|
47
|
+
assert row_count == expected_per_row
|
|
48
|
+
|
|
49
|
+
def test_global_token_mask_global_rows_full(self):
|
|
50
|
+
seq_len, block_size, n_global = 384, 128, 1
|
|
51
|
+
mask = demo._global_token_mask(seq_len, block_size, window=1, n_global=n_global)
|
|
52
|
+
# First n_global*block_size rows must be fully attended.
|
|
53
|
+
assert mask[: n_global * block_size, :].all()
|
|
54
|
+
# First n_global*block_size columns must be fully attended by all rows.
|
|
55
|
+
assert mask[:, : n_global * block_size].all()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestAttentionParity:
|
|
59
|
+
"""bsr_spmm attention matches dense reference."""
|
|
60
|
+
|
|
61
|
+
def _run_parity(self, seq_len, head_dim, block_size, mask):
|
|
62
|
+
torch.manual_seed(0)
|
|
63
|
+
Q = torch.randn(seq_len, head_dim)
|
|
64
|
+
K = torch.randn(seq_len, head_dim)
|
|
65
|
+
V = torch.randn(seq_len, head_dim)
|
|
66
|
+
|
|
67
|
+
mask_bsr = trnsparse.BSRMatrix.from_dense(mask.float(), block_size=block_size)
|
|
68
|
+
out_bsr = demo.block_sparse_attention(Q, K, V, mask_bsr)
|
|
69
|
+
out_dense = demo._dense_reference(Q, K, V, mask)
|
|
70
|
+
|
|
71
|
+
torch.testing.assert_close(out_bsr, out_dense, atol=1e-4, rtol=1e-4)
|
|
72
|
+
assert out_bsr.shape == (seq_len, head_dim)
|
|
73
|
+
|
|
74
|
+
def test_local_window_parity(self):
|
|
75
|
+
seq_len, head_dim, block_size = 256, 32, 128
|
|
76
|
+
mask = demo._local_window_mask(seq_len, block_size, window=1)
|
|
77
|
+
self._run_parity(seq_len, head_dim, block_size, mask)
|
|
78
|
+
|
|
79
|
+
def test_dilated_parity(self):
|
|
80
|
+
seq_len, head_dim, block_size = 256, 32, 128
|
|
81
|
+
mask = demo._dilated_mask(seq_len, block_size, stride=2)
|
|
82
|
+
self._run_parity(seq_len, head_dim, block_size, mask)
|
|
83
|
+
|
|
84
|
+
def test_global_token_parity(self):
|
|
85
|
+
seq_len, head_dim, block_size = 384, 32, 128
|
|
86
|
+
mask = demo._global_token_mask(seq_len, block_size, window=1, n_global=1)
|
|
87
|
+
self._run_parity(seq_len, head_dim, block_size, mask)
|
|
88
|
+
|
|
89
|
+
def test_full_attention_is_dense(self):
|
|
90
|
+
"""A fully-nonzero BSR mask should match plain dense attention."""
|
|
91
|
+
seq_len, head_dim, block_size = 256, 32, 128
|
|
92
|
+
mask = torch.ones(seq_len, seq_len, dtype=torch.bool)
|
|
93
|
+
self._run_parity(seq_len, head_dim, block_size, mask)
|
|
@@ -5,7 +5,7 @@ CSR/COO formats, SpMV, SpMM, and integral screening for
|
|
|
5
5
|
sparse scientific computing. Part of the trnsci scientific computing suite.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.4.
|
|
8
|
+
__version__ = "0.4.2"
|
|
9
9
|
|
|
10
10
|
from .formats import BSRMatrix, COOMatrix, CSRMatrix, eye_sparse, from_dense, from_scipy
|
|
11
11
|
from .iterative import bsr_diagonal, cg_bsr, jacobi_preconditioner_bsr, power_iteration_bsr
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trnsparse
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
@@ -102,3 +102,16 @@ stats = trnsparse.sparsity_stats(Q)
|
|
|
102
102
|
## License
|
|
103
103
|
|
|
104
104
|
Apache 2.0 — Copyright 2026 Scott Friedman
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Disclaimer
|
|
108
|
+
|
|
109
|
+
trnsci is an **independent open-source project**. It is not sponsored by, endorsed by, or affiliated with Amazon.com, Inc., Amazon Web Services, Inc., or Annapurna Labs Ltd.
|
|
110
|
+
|
|
111
|
+
"AWS", "Amazon", "Trainium", "Inferentia", "NeuronCore", "Neuron SDK", and related identifiers are trademarks of their respective owners and are used here solely for descriptive and interoperability purposes. Use does not imply endorsement, partnership, or any other relationship.
|
|
112
|
+
|
|
113
|
+
All work, opinions, analyses, benchmark results, architectural commentary, and editorial judgments in this repository and on [trnsci.dev](https://trnsci.dev) are those of the project's contributors. They do not represent the views, positions, or commitments of Amazon, AWS, or Annapurna Labs.
|
|
114
|
+
|
|
115
|
+
Feedback directed at the Neuron SDK or Trainium hardware is good-faith ecosystem commentary from independent users. It is not privileged information, is not pre-reviewed by AWS, and should not be read as authoritative about product roadmap, behavior, or quality.
|
|
116
|
+
|
|
117
|
+
For official AWS guidance, see [aws-neuron documentation](https://awsdocs-neuron.readthedocs-hosted.com/) and the [AWS Trainium product page](https://aws.amazon.com/ai/machine-learning/trainium/).
|
|
@@ -26,6 +26,8 @@ docs/installation.md
|
|
|
26
26
|
docs/iterative_solvers.md
|
|
27
27
|
docs/migration_scipy.md
|
|
28
28
|
docs/quickstart.md
|
|
29
|
+
docs/sparse_attention.md
|
|
30
|
+
examples/block_sparse_attention.py
|
|
29
31
|
examples/sparse_fock.py
|
|
30
32
|
infra/terraform/.terraform.lock.hcl
|
|
31
33
|
infra/terraform/README.md
|
|
@@ -35,6 +37,7 @@ scripts/run_benchmarks.sh
|
|
|
35
37
|
scripts/run_neuron_tests.sh
|
|
36
38
|
scripts/run_simulator_tests.sh
|
|
37
39
|
tests/conftest.py
|
|
40
|
+
tests/test_attention.py
|
|
38
41
|
tests/test_bsr.py
|
|
39
42
|
tests/test_examples.py
|
|
40
43
|
tests/test_formats.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|