trnsparse 0.8.0__tar.gz → 0.9.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.8.0 → trnsparse-0.9.0}/CHANGELOG.md +21 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/PKG-INFO +1 -1
- {trnsparse-0.8.0 → trnsparse-0.9.0}/benchmarks/bench_native_trn2.py +60 -13
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/architecture.md +18 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/pyproject.toml +1 -1
- trnsparse-0.9.0/tests/test_core_split.py +53 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_native_dispatch.py +53 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_nki_compile.py +116 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_nki_native.py +104 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/__init__.py +1 -1
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/nki/_native_attn.py +20 -4
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/nki/dispatch.py +40 -15
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/nki/kernels.py +146 -41
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse.egg-info/PKG-INFO +1 -1
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse.egg-info/SOURCES.txt +1 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/.github/workflows/ci.yml +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/.github/workflows/notify-umbrella.yml +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/.github/workflows/publish.yml +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/.gitignore +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/.pre-commit-config.yaml +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/CLAUDE.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/CODE_OF_CONDUCT.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/CONTRIBUTING.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/LICENSE +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/README.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/benchmarks/bench_bsr_spmm.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/benchmarks/bench_iterative.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/benchmarks/bench_screening.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/benchmarks/bench_spmm.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/benchmarks/bench_spmv.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/benchmarks/conftest.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/api.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/aws_setup.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/benchmarks.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/index.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/installation.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/iterative_solvers.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/migration_scipy.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/quickstart.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/docs/sparse_attention.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/examples/block_sparse_attention.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/examples/sparse_fock.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/infra/terraform/.terraform.lock.hcl +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/infra/terraform/README.md +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/infra/terraform/main.tf +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/mkdocs.yml +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/scripts/bench_to_md.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/scripts/run_benchmarks.sh +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/scripts/run_neuron_tests.sh +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/scripts/run_simulator_tests.sh +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/setup.cfg +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/conftest.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_attention.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_attn_gather.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_bsr.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_examples.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_formats.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_iterative.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_nki_attn.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_nki_bsr.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_nki_screened_spmm.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_nki_sim.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_nki_spmm.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_ops.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/tests/test_screening.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/formats.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/iterative.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/nki/__init__.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/nki/_native.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/ops.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse/screening.py +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse.egg-info/dependency_links.txt +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse.egg-info/requires.txt +0 -0
- {trnsparse-0.8.0 → trnsparse-0.9.0}/trnsparse.egg-info/top_level.txt +0 -0
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.0] — 2026-10-30
|
|
11
|
+
|
|
12
|
+
Validated on Trn2 (trn2.3xlarge, Neuron SDK 2.33.0, native TorchNeuron; LNC=1 and LNC=2) — see trnsci/trnsci#35.
|
|
13
|
+
|
|
14
|
+
Headline performance: static BSR SpMM and block-sparse attention split across both LNC=2 cores, with bf16 operands on fp32 PSUM; the fp32 BSR crossover against dense aten at n=8192, width 512 rises from ~27% to ~49% block density, and at seq 8192 attention forward plus backward beats unmasked dense SDPA for all three masks (#36).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
#### Native path: static kernels on both LNC=2 cores, bf16 operands (#30)
|
|
19
|
+
|
|
20
|
+
- `bsr_spmm_static`, `attn_fwd_static` and `attn_bwd_static` split their
|
|
21
|
+
output tiles between the two physical cores of an LNC=2 logical core
|
|
22
|
+
(`program_id`), balanced by stored blocks; each output tile has one
|
|
23
|
+
writer. `TRNSPARSE_CORE_SPLIT=0` turns the split off.
|
|
24
|
+
- bf16 inputs (all operands bf16) run bf16 matmuls with fp32 PSUM
|
|
25
|
+
accumulation; scores and softmax statistics stay fp32, and the attention
|
|
26
|
+
statistics returned by `trnsparse::bsr_attn` are fp32 for bf16 / fp16 Q.
|
|
27
|
+
Mixed fp32/bf16 operands run in fp32.
|
|
28
|
+
- `benchmarks/bench_native_trn2.py`: `--dtype bf16` (matched-precision
|
|
29
|
+
dense aten baselines) and `--fast` (device columns only).
|
|
30
|
+
|
|
10
31
|
## [0.8.0] — 2026-10-30
|
|
11
32
|
|
|
12
33
|
Native TorchNeuron dispatch (Trn2/Trn3) alongside the PyTorch/XLA path (Trn1, maintenance); validated on Trn2 (trn2.3xlarge, Neuron SDK 2.33.0) — see trnsci/trnsci#35.
|
|
@@ -15,6 +15,17 @@ Sections:
|
|
|
15
15
|
attn Block-sparse attention (local / dilated / global masks), forward
|
|
16
16
|
and forward+backward, vs dense SDPA on the device and on CPU.
|
|
17
17
|
|
|
18
|
+
Options for the bsr and attn sections:
|
|
19
|
+
--dtype bf16 run the operands in bf16 (fp32 PSUM inside the kernels)
|
|
20
|
+
and the dense aten baselines in bf16 too, so each side has
|
|
21
|
+
matched precision. Errors are against fp64 on the
|
|
22
|
+
bf16-rounded inputs.
|
|
23
|
+
--fast only the device columns (trnsparse e2e, static kernel,
|
|
24
|
+
dense aten); skips the gather path and the CPU baselines.
|
|
25
|
+
TRNSPARSE_CORE_SPLIT=0 in the environment keeps each static kernel on
|
|
26
|
+
one physical core's partition (the whole kernel on both cores, as
|
|
27
|
+
before the split), for A/B runs.
|
|
28
|
+
|
|
18
29
|
Timing: each callable is run ``--warmup`` times (the first call compiles),
|
|
19
30
|
then ``--reps`` times with ``torch.neuron.synchronize()`` after every call;
|
|
20
31
|
the median is reported in milliseconds. Structure stays on the host, dense
|
|
@@ -106,21 +117,26 @@ def random_bsr(n, density, seed=0):
|
|
|
106
117
|
)
|
|
107
118
|
|
|
108
119
|
|
|
120
|
+
_DTYPES = {"fp32": torch.float32, "bf16": torch.bfloat16}
|
|
121
|
+
|
|
122
|
+
|
|
109
123
|
def section_bsr(args):
|
|
110
124
|
densities = [float(x) for x in args.densities.split(",")]
|
|
111
125
|
widths = [int(x) for x in args.widths.split(",")]
|
|
112
126
|
n = args.size
|
|
127
|
+
dt = _DTYPES[args.dtype]
|
|
113
128
|
for width in widths:
|
|
114
129
|
g = torch.Generator().manual_seed(1)
|
|
115
|
-
Bc = torch.randn(n, width, generator=g)
|
|
116
|
-
Bd = Bc.to(DEV)
|
|
130
|
+
Bc = torch.randn(n, width, generator=g).to(dt).float() # values exact in dt
|
|
131
|
+
Bd = Bc.to(DEV, dt)
|
|
117
132
|
dense_dev_ms = None
|
|
118
133
|
cpu_dense_ms = None
|
|
119
134
|
for dens in densities:
|
|
120
135
|
A = random_bsr(n, dens)
|
|
136
|
+
A.blocks = A.blocks.to(dt).float()
|
|
121
137
|
Ac = A.to_dense()
|
|
122
138
|
Ad_dev = trnsparse.BSRMatrix(
|
|
123
|
-
blocks=A.blocks.to(DEV),
|
|
139
|
+
blocks=A.blocks.to(DEV, dt),
|
|
124
140
|
block_col_indices=A.block_col_indices,
|
|
125
141
|
block_row_ptrs=A.block_row_ptrs,
|
|
126
142
|
shape=A.shape,
|
|
@@ -130,6 +146,8 @@ def section_bsr(args):
|
|
|
130
146
|
K_max = max(counts)
|
|
131
147
|
row = {
|
|
132
148
|
"section": "bsr",
|
|
149
|
+
"dtype": args.dtype,
|
|
150
|
+
"core_split": _core_split(),
|
|
133
151
|
"n": n,
|
|
134
152
|
"width": width,
|
|
135
153
|
"block_density": dens,
|
|
@@ -141,13 +159,28 @@ def section_bsr(args):
|
|
|
141
159
|
out = trnsparse.bsr_spmm(Ad_dev, Bd)
|
|
142
160
|
row["rel_err"] = rel_err(out, ref)
|
|
143
161
|
row["trnsparse_ms"] = safe_bench(lambda: trnsparse.bsr_spmm(Ad_dev, Bd), reps=args.reps)
|
|
162
|
+
ops = _d._get_native_ops()
|
|
163
|
+
if args.fast:
|
|
164
|
+
rp, cl = _d._bsr_structure_lists(A)
|
|
165
|
+
bl = Ad_dev.blocks
|
|
166
|
+
row["static_kernel_ms"] = safe_bench(
|
|
167
|
+
lambda: ops["bsr_spmm_static"](bl, Bd, rp, cl), reps=args.reps
|
|
168
|
+
)
|
|
169
|
+
Ad = Ac.to(DEV, dt)
|
|
170
|
+
if dense_dev_ms is None:
|
|
171
|
+
dense_dev_ms = safe_bench(lambda: Ad @ Bd, reps=args.reps)
|
|
172
|
+
row["dense_dev_ms"] = dense_dev_ms
|
|
173
|
+
Ad = None
|
|
174
|
+
if row["trnsparse_ms"] and dense_dev_ms:
|
|
175
|
+
row["speedup_vs_dense_dev"] = dense_dev_ms / row["trnsparse_ms"]
|
|
176
|
+
emit(row, args.out)
|
|
177
|
+
continue
|
|
144
178
|
|
|
145
179
|
# Split: device-side prep (gather) vs the kernel launch.
|
|
146
180
|
def prep():
|
|
147
181
|
return _d._bsr_pad_and_gather(_d._BlockView(Ad_dev, Ad_dev.blocks), Bd)
|
|
148
182
|
|
|
149
183
|
bp, bg, *_ = prep()
|
|
150
|
-
ops = _d._get_native_ops()
|
|
151
184
|
# Gather path (main before trnsparse#30 static kernels): device-side
|
|
152
185
|
# pad + gather, then the K_max-padded kernel.
|
|
153
186
|
row["gather_prep_ms"] = safe_bench(prep, reps=args.reps)
|
|
@@ -160,7 +193,7 @@ def section_bsr(args):
|
|
|
160
193
|
lambda: ops["bsr_spmm_static"](bl, Bd, rp, cl), reps=args.reps
|
|
161
194
|
)
|
|
162
195
|
|
|
163
|
-
Ad = Ac.to(DEV)
|
|
196
|
+
Ad = Ac.to(DEV, dt)
|
|
164
197
|
if dense_dev_ms is None:
|
|
165
198
|
dense_dev_ms = safe_bench(lambda: Ad @ Bd, reps=args.reps)
|
|
166
199
|
row["dense_dev_ms"] = dense_dev_ms
|
|
@@ -302,11 +335,12 @@ def section_attn(args):
|
|
|
302
335
|
seq = args.size
|
|
303
336
|
nb = seq // P
|
|
304
337
|
F = torch.nn.functional
|
|
338
|
+
dt = _DTYPES[args.dtype]
|
|
305
339
|
for hd in [int(x) for x in args.head_dims.split(",")]:
|
|
306
340
|
g = torch.Generator().manual_seed(0)
|
|
307
|
-
Qc, Kc, Vc = (torch.randn(seq, hd, generator=g) for _ in range(3))
|
|
308
|
-
dOc = torch.randn(seq, hd, generator=g)
|
|
309
|
-
Qd, Kd, Vd, dOd = (t.to(DEV) for t in (Qc, Kc, Vc, dOc))
|
|
341
|
+
Qc, Kc, Vc = (torch.randn(seq, hd, generator=g).to(dt).float() for _ in range(3))
|
|
342
|
+
dOc = torch.randn(seq, hd, generator=g).to(dt).float()
|
|
343
|
+
Qd, Kd, Vd, dOd = (t.to(DEV, dt) for t in (Qc, Kc, Vc, dOc))
|
|
310
344
|
|
|
311
345
|
def sdpa_fwd(q, k, v, m=None):
|
|
312
346
|
return F.scaled_dot_product_attention(q[None], k[None], v[None], attn_mask=m)[0]
|
|
@@ -320,7 +354,9 @@ def section_attn(args):
|
|
|
320
354
|
return q.grad
|
|
321
355
|
|
|
322
356
|
dense_fb = safe_bench(dense_fwd_bwd, reps=args.reps)
|
|
323
|
-
cpu_fwd =
|
|
357
|
+
cpu_fwd = None
|
|
358
|
+
if not args.fast:
|
|
359
|
+
cpu_fwd = safe_bench(lambda: sdpa_fwd(Qc, Kc, Vc), warmup=1, reps=args.cpu_reps)
|
|
324
360
|
for kind in args.masks.split(","):
|
|
325
361
|
bm = block_mask(kind, nb)
|
|
326
362
|
mb = mask_bsr_from_block_mask(bm)
|
|
@@ -328,6 +364,8 @@ def section_attn(args):
|
|
|
328
364
|
counts = bm.sum(1).tolist()
|
|
329
365
|
row = {
|
|
330
366
|
"section": "attn",
|
|
367
|
+
"dtype": args.dtype,
|
|
368
|
+
"core_split": _core_split(),
|
|
331
369
|
"seq": seq,
|
|
332
370
|
"head_dim": hd,
|
|
333
371
|
"mask": kind,
|
|
@@ -365,14 +403,21 @@ def section_attn(args):
|
|
|
365
403
|
row["sdpa_masked_dev_fwd_ms"] = safe_bench(
|
|
366
404
|
lambda: sdpa_fwd(Qd, Kd, Vd, em_d), reps=args.reps
|
|
367
405
|
)
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
406
|
+
if not args.fast:
|
|
407
|
+
row["sdpa_cpu_fwd_ms"] = cpu_fwd
|
|
408
|
+
row["sdpa_masked_cpu_fwd_ms"] = safe_bench(
|
|
409
|
+
lambda: sdpa_fwd(Qc, Kc, Vc, elem_mask), warmup=1, reps=args.cpu_reps
|
|
410
|
+
)
|
|
372
411
|
emit(row, args.out)
|
|
373
412
|
em_d = None
|
|
374
413
|
|
|
375
414
|
|
|
415
|
+
def _core_split():
|
|
416
|
+
from trnsparse.nki import kernels
|
|
417
|
+
|
|
418
|
+
return kernels._core_split_enabled()
|
|
419
|
+
|
|
420
|
+
|
|
376
421
|
def main():
|
|
377
422
|
ap = argparse.ArgumentParser()
|
|
378
423
|
ap.add_argument("--section", required=True, choices=["bsr", "csr", "screen", "attn"])
|
|
@@ -383,6 +428,8 @@ def main():
|
|
|
383
428
|
ap.add_argument("--head-dims", default="64,128")
|
|
384
429
|
ap.add_argument("--masks", default="local,dilated,global")
|
|
385
430
|
ap.add_argument("--no-bwd", action="store_true")
|
|
431
|
+
ap.add_argument("--dtype", default="fp32", choices=sorted(_DTYPES))
|
|
432
|
+
ap.add_argument("--fast", action="store_true")
|
|
386
433
|
ap.add_argument("--reps", type=int, default=10)
|
|
387
434
|
ap.add_argument("--cpu-reps", type=int, default=3)
|
|
388
435
|
ap.add_argument("--out", default="")
|
|
@@ -97,6 +97,24 @@ cost is one compile per distinct structure (seconds), which a reused pattern
|
|
|
97
97
|
amortizes. `TRNSPARSE_STATIC_STRUCTURE=0` restores the gather path. Measured
|
|
98
98
|
crossovers against dense aten: `docs/benchmarks.md`.
|
|
99
99
|
|
|
100
|
+
Under LNC=2 (the Trn2 default) NKI traces each kernel once per physical
|
|
101
|
+
core, with `program_id` 0 and 1 as Python ints. The static kernels split
|
|
102
|
+
their output tiles between the two traces, balanced by stored blocks:
|
|
103
|
+
|
|
104
|
+
- SpMM: block rows and 512-column tiles.
|
|
105
|
+
- Attention forward: block rows.
|
|
106
|
+
- Attention backward: one pool of dQ block rows and dK/dV block columns.
|
|
107
|
+
|
|
108
|
+
Each output tile has exactly one writer. `TRNSPARSE_CORE_SPLIT=0` turns the
|
|
109
|
+
split off, for A/B measurements.
|
|
110
|
+
|
|
111
|
+
The static kernels take fp32 or bf16 operands. When every operand is bf16,
|
|
112
|
+
the matmuls run in bf16 and accumulate in fp32 PSUM. Scores, softmax
|
|
113
|
+
statistics and score-space arithmetic stay fp32; only P and dS are rounded
|
|
114
|
+
to bf16 for their matmuls. Outputs come back in bf16, and the attention
|
|
115
|
+
statistics (`row_max`, `row_denom`) are always fp32. Mixed fp32/bf16
|
|
116
|
+
operands run in fp32.
|
|
117
|
+
|
|
100
118
|
## Formats
|
|
101
119
|
|
|
102
120
|
- **CSR**: row pointer + column indices + values. Preferred for construction,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""CPU tests for the static kernels' work split across physical cores (#30)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from trnsparse.nki import kernels as K
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parametrize("n_prog", [1, 2, 3])
|
|
11
|
+
@pytest.mark.parametrize(
|
|
12
|
+
"costs",
|
|
13
|
+
[
|
|
14
|
+
[],
|
|
15
|
+
[1.0],
|
|
16
|
+
[3, 3, 3, 3],
|
|
17
|
+
[41, 3, 3, 3, 3, 3, 3, 3, 3, 3], # global-token row first
|
|
18
|
+
[0.25, 5, 0.25, 7, 2, 2, 9],
|
|
19
|
+
],
|
|
20
|
+
)
|
|
21
|
+
def test_assign_units_partitions(costs, n_prog):
|
|
22
|
+
owned = [K._assign_units(costs, n_prog, pid) for pid in range(n_prog)]
|
|
23
|
+
flat = sorted(u for o in owned for u in o)
|
|
24
|
+
assert flat == list(range(len(costs))) # every unit exactly once: no shared writes
|
|
25
|
+
for o in owned:
|
|
26
|
+
assert o == sorted(o)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_assign_units_balances_skew():
|
|
30
|
+
costs = [41, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]
|
|
31
|
+
loads = [sum(costs[u] for u in K._assign_units(costs, 2, pid)) for pid in range(2)]
|
|
32
|
+
assert max(loads) == 41 # the heavy row alone; the rest go to the other core
|
|
33
|
+
even = [4] * 10
|
|
34
|
+
loads = [sum(even[u] for u in K._assign_units(even, 2, pid)) for pid in range(2)]
|
|
35
|
+
assert loads == [20, 20]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_assign_units_deterministic():
|
|
39
|
+
costs = [5, 1, 5, 1, 2, 2]
|
|
40
|
+
assert K._assign_units(costs, 2, 0) == K._assign_units(list(costs), 2, 0)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_core_split_env(monkeypatch):
|
|
44
|
+
monkeypatch.setenv("TRNSPARSE_CORE_SPLIT", "0")
|
|
45
|
+
assert not K._core_split_enabled()
|
|
46
|
+
monkeypatch.setenv("TRNSPARSE_CORE_SPLIT", "1")
|
|
47
|
+
assert K._core_split_enabled()
|
|
48
|
+
monkeypatch.delenv("TRNSPARSE_CORE_SPLIT")
|
|
49
|
+
assert K._core_split_enabled()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_row_block_counts():
|
|
53
|
+
assert K._row_block_counts((0, 2, 2, 5)) == [2, 0, 3]
|
|
@@ -845,6 +845,59 @@ class TestNativePathOnCpu:
|
|
|
845
845
|
for g_, w_ in zip(got, want, strict=True):
|
|
846
846
|
torch.testing.assert_close(g_, w_, atol=1e-4, rtol=1e-4)
|
|
847
847
|
|
|
848
|
+
def test_static_kernels_bf16_operand_contract(self, cpu_native, monkeypatch):
|
|
849
|
+
"""bf16 inputs reach the static kernels as bf16 operands; statistics stay fp32."""
|
|
850
|
+
seen = {}
|
|
851
|
+
|
|
852
|
+
def recorder(name, ref, out_dtype_of):
|
|
853
|
+
def run(*args):
|
|
854
|
+
seen[name] = [a.dtype for a in args if isinstance(a, torch.Tensor)]
|
|
855
|
+
f32 = [a.float() if isinstance(a, torch.Tensor) else a for a in args]
|
|
856
|
+
res = ref(*f32)
|
|
857
|
+
dt = out_dtype_of(args)
|
|
858
|
+
if isinstance(res, tuple):
|
|
859
|
+
return (res[0].to(dt),) + tuple(
|
|
860
|
+
r.to(dt if name == "bwd" else torch.float32) for r in res[1:]
|
|
861
|
+
)
|
|
862
|
+
return res.to(dt)
|
|
863
|
+
|
|
864
|
+
return run
|
|
865
|
+
|
|
866
|
+
for attr, name, ref, odt in (
|
|
867
|
+
("_bsr_spmm_static_kernel", "spmm", _ref_bsr_spmm_static, lambda a: a[1].dtype),
|
|
868
|
+
("_attn_fwd_static_kernel", "fwd", _ref_attn_fwd_static, lambda a: a[2].dtype),
|
|
869
|
+
("_attn_bwd_static_kernel", "bwd", _ref_attn_bwd_static, lambda a: a[4].dtype),
|
|
870
|
+
):
|
|
871
|
+
fn = recorder(name, ref, odt)
|
|
872
|
+
monkeypatch.setattr(kernels, attr, fn, raising=False)
|
|
873
|
+
monkeypatch.setattr(dispatch, attr, fn, raising=False)
|
|
874
|
+
|
|
875
|
+
bf = torch.bfloat16
|
|
876
|
+
g = torch.Generator().manual_seed(21)
|
|
877
|
+
dense = torch.randn(256, 256, generator=g)
|
|
878
|
+
dense[:128, 128:] = 0
|
|
879
|
+
A = trnsparse.BSRMatrix.from_dense(dense, block_size=P)
|
|
880
|
+
A.blocks = A.blocks.to(bf)
|
|
881
|
+
C = trnsparse.bsr_spmm(A, torch.randn(256, 64, generator=g).to(bf))
|
|
882
|
+
assert C.dtype == bf and seen["spmm"] == [bf, bf]
|
|
883
|
+
# Mixed precision runs in fp32 and returns the promoted dtype.
|
|
884
|
+
C = trnsparse.bsr_spmm(A, torch.randn(256, 64, generator=g))
|
|
885
|
+
assert C.dtype == torch.float32 and seen["spmm"] == [torch.float32] * 2
|
|
886
|
+
|
|
887
|
+
seq_len, hd = 2 * P, 64
|
|
888
|
+
mask = trnsparse.BSRMatrix.from_dense(torch.ones(seq_len, seq_len), block_size=P)
|
|
889
|
+
Q, K, V, dO = (torch.randn(seq_len, hd, generator=g).to(bf) for _ in range(4))
|
|
890
|
+
out, rm, rd = dispatch.nki_bsr_attn_tiled(Q, K, V, mask, return_stats=True)
|
|
891
|
+
assert out.dtype == bf and seen["fwd"] == [bf] * 3
|
|
892
|
+
assert rm.dtype == torch.float32 and rd.dtype == torch.float32
|
|
893
|
+
o2, rm2, rd2 = torch.ops.trnsparse.bsr_attn(
|
|
894
|
+
Q, K, V, mask.block_col_indices, mask.block_row_ptrs, P
|
|
895
|
+
)
|
|
896
|
+
assert o2.dtype == bf and rm2.dtype == torch.float32 and rd2.dtype == torch.float32
|
|
897
|
+
dQ, dK, dV = dispatch.nki_bsr_attn_bwd(Q, K, V, dO, out, mask, rm, rd)
|
|
898
|
+
assert seen["bwd"] == [bf] * 7 + [torch.float32] * 6
|
|
899
|
+
assert {t.dtype for t in (dQ, dK, dV)} == {bf}
|
|
900
|
+
|
|
848
901
|
def test_column_structure(self):
|
|
849
902
|
assert dispatch._column_structure([0, 2, 2, 5], [0, 1, 0, 1, 2], 3) == (
|
|
850
903
|
[0, 2, 4, 5],
|
|
@@ -183,3 +183,119 @@ def test_kernel_neff_compiles(case, target, tmp_path):
|
|
|
183
183
|
)
|
|
184
184
|
driver._compile_bir_to_neff(bir, opts, inputs)
|
|
185
185
|
assert (tmp_path / "file.neff").stat().st_size > 0
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ── Static kernels on both physical cores, fp32 and bf16 (#30) ───────────────
|
|
189
|
+
#
|
|
190
|
+
# Under LNC=2 the static kernels are traced once per physical core and split
|
|
191
|
+
# their output tiles between the two traces. Verify both traces for every
|
|
192
|
+
# kernel and input dtype, including structures with fewer units than cores
|
|
193
|
+
# (one block row: the BSR kernel splits columns instead) and skewed rows.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _bf16(*shape):
|
|
197
|
+
import ml_dtypes
|
|
198
|
+
|
|
199
|
+
return _f32(*shape).astype(ml_dtypes.bfloat16)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _static_lnc2_cases():
|
|
203
|
+
cases = {}
|
|
204
|
+
for dtype, mk in (("f32", _f32), ("bf16", _bf16)):
|
|
205
|
+
cases[("_bsr_spmm_static_kernel", f"skew-n512-{dtype}")] = lambda mk=mk: (
|
|
206
|
+
mk(6, _P, _P),
|
|
207
|
+
mk(4 * _P, 512),
|
|
208
|
+
(0, 4, 4, 5, 6),
|
|
209
|
+
(0, 1, 2, 3, 0, 3),
|
|
210
|
+
)
|
|
211
|
+
cases[("_bsr_spmm_static_kernel", f"n1024-{dtype}")] = lambda mk=mk: (
|
|
212
|
+
mk(3, _P, _P),
|
|
213
|
+
mk(3 * _P, 1024),
|
|
214
|
+
(0, 2, 2, 3),
|
|
215
|
+
(0, 2, 2),
|
|
216
|
+
)
|
|
217
|
+
cases[("_bsr_spmm_static_kernel", f"onerow-n128-{dtype}")] = lambda mk=mk: (
|
|
218
|
+
mk(2, _P, _P),
|
|
219
|
+
mk(2 * _P, 128),
|
|
220
|
+
(0, 2),
|
|
221
|
+
(0, 1),
|
|
222
|
+
)
|
|
223
|
+
for hd in (64, 128):
|
|
224
|
+
cases[("_attn_fwd_static_kernel", f"hd{hd}-{dtype}")] = lambda hd=hd, mk=mk: (
|
|
225
|
+
mk(hd, 3 * _P),
|
|
226
|
+
mk(hd, 3 * _P),
|
|
227
|
+
mk(3 * _P, hd),
|
|
228
|
+
(0, 2, 2, 5),
|
|
229
|
+
(0, 1, 0, 1, 2),
|
|
230
|
+
)
|
|
231
|
+
cases[("_attn_bwd_static_kernel", f"hd{hd}-{dtype}")] = lambda hd=hd, mk=mk: (
|
|
232
|
+
mk(hd, 3 * _P),
|
|
233
|
+
mk(hd, 3 * _P),
|
|
234
|
+
mk(hd, 3 * _P),
|
|
235
|
+
mk(hd, 3 * _P),
|
|
236
|
+
mk(3 * _P, hd),
|
|
237
|
+
mk(3 * _P, hd),
|
|
238
|
+
mk(3 * _P, hd),
|
|
239
|
+
_f32(3 * _P, 1),
|
|
240
|
+
np.abs(_f32(3 * _P, 1)),
|
|
241
|
+
_f32(3 * _P, 1),
|
|
242
|
+
_f32(1, 3 * _P),
|
|
243
|
+
np.abs(_f32(1, 3 * _P)),
|
|
244
|
+
_f32(1, 3 * _P),
|
|
245
|
+
(0, 2, 2, 5),
|
|
246
|
+
(0, 1, 0, 1, 2),
|
|
247
|
+
(0, 2, 4, 5),
|
|
248
|
+
(0, 2, 0, 2, 2),
|
|
249
|
+
0.125,
|
|
250
|
+
)
|
|
251
|
+
return cases
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
_STATIC_LNC2 = _static_lnc2_cases()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@pytest.mark.parametrize("lnc", [1, 2])
|
|
258
|
+
@pytest.mark.parametrize("case", sorted(_STATIC_LNC2), ids=lambda c: f"{c[0]}-{c[1]}")
|
|
259
|
+
def test_static_kernel_lnc_mlir_verifies(case, lnc):
|
|
260
|
+
from trnsparse.nki import kernels
|
|
261
|
+
|
|
262
|
+
if case[1].endswith("bf16"):
|
|
263
|
+
pytest.importorskip("ml_dtypes")
|
|
264
|
+
kernel = getattr(kernels, case[0])
|
|
265
|
+
result = source_to_mlir(
|
|
266
|
+
kernel, *_STATIC_LNC2[case](), target="trn2", lnc=lnc, frontend=FrontendKind.TRACER
|
|
267
|
+
)
|
|
268
|
+
result.verify()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@pytest.mark.skipif(shutil.which("neuronx-cc") is None, reason="neuronx-cc not on PATH")
|
|
272
|
+
@pytest.mark.parametrize("case", sorted(_STATIC_LNC2), ids=lambda c: f"{c[0]}-{c[1]}")
|
|
273
|
+
def test_static_kernel_lnc2_neff_compiles(case, tmp_path):
|
|
274
|
+
"""Full CPU NEFF compile of both per-core traces (LNC=2, trn2)."""
|
|
275
|
+
try:
|
|
276
|
+
from nki.compiler import driver
|
|
277
|
+
from nki.compiler.frontend import TracerFrontend
|
|
278
|
+
from nki.compiler.ncc_driver import CompileOptions
|
|
279
|
+
except ImportError as e:
|
|
280
|
+
pytest.skip(f"nki compiler driver API not available: {e}")
|
|
281
|
+
if not hasattr(driver, "_compile_bir_to_neff"):
|
|
282
|
+
pytest.skip("nki.compiler.driver._compile_bir_to_neff not available in this nki")
|
|
283
|
+
if case[1].endswith("bf16"):
|
|
284
|
+
pytest.importorskip("ml_dtypes")
|
|
285
|
+
|
|
286
|
+
from trnsparse.nki import kernels
|
|
287
|
+
|
|
288
|
+
kernel = getattr(kernels, case[0])
|
|
289
|
+
fn = getattr(kernel, "func", kernel)
|
|
290
|
+
inputs = dict(zip(inspect.signature(fn).parameters, _STATIC_LNC2[case](), strict=True))
|
|
291
|
+
opts = CompileOptions(
|
|
292
|
+
target="trn2",
|
|
293
|
+
lnc=2,
|
|
294
|
+
artifacts_dir=str(tmp_path),
|
|
295
|
+
output_path=str(tmp_path / "file.neff"),
|
|
296
|
+
)
|
|
297
|
+
bir = driver.compile_to_bir(
|
|
298
|
+
fn, frontend=TracerFrontend(), inputs=inputs, compile_opts=opts, enable_cache=False
|
|
299
|
+
)
|
|
300
|
+
driver._compile_bir_to_neff(bir, opts, inputs)
|
|
301
|
+
assert (tmp_path / "file.neff").stat().st_size > 0
|
|
@@ -234,3 +234,107 @@ class TestTorchCompile:
|
|
|
234
234
|
|
|
235
235
|
out = f(A.values, A.col_indices, A.row_ptrs, B.to(DEV))
|
|
236
236
|
torch.testing.assert_close(out.cpu(), dense @ B, atol=ATOL, rtol=RTOL)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _rel(x, ref):
|
|
240
|
+
x, ref = x.detach().cpu().double(), ref.detach().cpu().double()
|
|
241
|
+
return float((x - ref).norm() / ref.norm())
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _skewed_bsr(n_rows, n_cols, seed):
|
|
245
|
+
"""Block row 0 dense (a global-token row), the rest one or two blocks; a middle row empty."""
|
|
246
|
+
g = torch.Generator().manual_seed(seed)
|
|
247
|
+
bm = torch.zeros(n_rows, n_cols, dtype=torch.bool)
|
|
248
|
+
bm[0, :] = True
|
|
249
|
+
for i in range(1, n_rows):
|
|
250
|
+
bm[i, i % n_cols] = True
|
|
251
|
+
bm[i, (3 * i + 1) % n_cols] = True
|
|
252
|
+
if n_rows > 2:
|
|
253
|
+
bm[n_rows // 2, :] = False
|
|
254
|
+
dense = torch.randn(n_rows * P, n_cols * P, generator=g)
|
|
255
|
+
dense *= bm.repeat_interleave(P, 0).repeat_interleave(P, 1)
|
|
256
|
+
return dense, trnsparse.BSRMatrix.from_dense(dense, block_size=P)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class TestTwoCoreAndBf16:
|
|
260
|
+
"""Static kernels split across both physical cores (LNC=2), fp32 and bf16 (#30).
|
|
261
|
+
|
|
262
|
+
A core that skipped its share would leave whole output tiles unwritten,
|
|
263
|
+
so the fp32 cases use tight tolerances over every row and column tile.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
@pytest.mark.parametrize(
|
|
267
|
+
"n_rows,n_cols,N",
|
|
268
|
+
[(6, 6, 128), (6, 4, 1024), (1, 3, 512), (1, 2, 128), (9, 5, 200)],
|
|
269
|
+
)
|
|
270
|
+
def test_bsr_static_fp32_every_tile(self, n_rows, n_cols, N):
|
|
271
|
+
dense, A = _skewed_bsr(n_rows, n_cols, seed=n_rows * 10 + N)
|
|
272
|
+
B = torch.randn(n_cols * P, N, generator=torch.Generator().manual_seed(1))
|
|
273
|
+
A.blocks = A.blocks.to(DEV)
|
|
274
|
+
C = trnsparse.bsr_spmm(A, B.to(DEV))
|
|
275
|
+
assert C.dtype == torch.float32
|
|
276
|
+
torch.testing.assert_close(C.cpu(), dense @ B, atol=ATOL, rtol=RTOL)
|
|
277
|
+
|
|
278
|
+
@pytest.mark.parametrize("n_rows,n_cols,N", [(6, 6, 128), (6, 4, 1024), (1, 3, 512)])
|
|
279
|
+
def test_bsr_static_bf16(self, n_rows, n_cols, N):
|
|
280
|
+
dense, A = _skewed_bsr(n_rows, n_cols, seed=n_rows * 10 + N)
|
|
281
|
+
B = torch.randn(n_cols * P, N, generator=torch.Generator().manual_seed(1))
|
|
282
|
+
A.blocks = A.blocks.to(DEV, torch.bfloat16)
|
|
283
|
+
Bh = B.to(DEV, torch.bfloat16)
|
|
284
|
+
C = trnsparse.bsr_spmm(A, Bh)
|
|
285
|
+
assert C.dtype == torch.bfloat16 and C.device.type == DEV
|
|
286
|
+
ref = dense.bfloat16().double() @ B.bfloat16().double() # bf16 inputs, exact sum
|
|
287
|
+
assert _rel(C, ref) < 1e-2
|
|
288
|
+
|
|
289
|
+
def test_bsr_static_bf16_grad(self):
|
|
290
|
+
dense, A = _skewed_bsr(4, 4, seed=2)
|
|
291
|
+
B = torch.randn(4 * P, 64, generator=torch.Generator().manual_seed(2))
|
|
292
|
+
A.blocks = A.blocks.to(DEV, torch.bfloat16).requires_grad_(True)
|
|
293
|
+
Bd = B.to(DEV, torch.bfloat16).requires_grad_(True)
|
|
294
|
+
trnsparse.bsr_spmm(A, Bd).float().sum().backward()
|
|
295
|
+
ref = dense.bfloat16().double().T @ torch.ones(4 * P, 64, dtype=torch.float64)
|
|
296
|
+
assert Bd.grad.dtype == torch.bfloat16
|
|
297
|
+
assert _rel(Bd.grad, ref) < 1e-2
|
|
298
|
+
|
|
299
|
+
@pytest.mark.parametrize("dtype,tol", [(torch.float32, 1e-5), (torch.bfloat16, 3e-2)])
|
|
300
|
+
@pytest.mark.parametrize("hd", [64, 128])
|
|
301
|
+
def test_attention_static_two_core(self, dtype, tol, hd):
|
|
302
|
+
from trnsparse.ops import _block_sparse_attn_backward, _block_sparse_attn_pytorch
|
|
303
|
+
|
|
304
|
+
torch.manual_seed(7)
|
|
305
|
+
seq_len = 5 * P
|
|
306
|
+
# Global-token block row/column 0 plus a local band: skewed per-row cost.
|
|
307
|
+
nb = seq_len // P
|
|
308
|
+
bm = torch.zeros(nb, nb)
|
|
309
|
+
bm[0, :] = 1
|
|
310
|
+
bm[:, 0] = 1
|
|
311
|
+
for i in range(nb):
|
|
312
|
+
bm[i, max(0, i - 1) : i + 2] = 1
|
|
313
|
+
mask = trnsparse.BSRMatrix.from_dense(
|
|
314
|
+
bm.repeat_interleave(P, 0).repeat_interleave(P, 1), block_size=P
|
|
315
|
+
)
|
|
316
|
+
Q, K, V, dO = (torch.randn(seq_len, hd).to(dtype) for _ in range(4))
|
|
317
|
+
Qd, Kd, Vd = (t.to(DEV).requires_grad_(True) for t in (Q, K, V))
|
|
318
|
+
out = trnsparse.block_sparse_attention_tiled(Qd, Kd, Vd, mask)
|
|
319
|
+
assert out.dtype == dtype
|
|
320
|
+
q64, k64, v64, do64 = (t.double() for t in (Q, K, V, dO))
|
|
321
|
+
scale = hd**-0.5
|
|
322
|
+
ref, rm, rd = _block_sparse_attn_pytorch(q64, k64, v64, mask, scale, return_stats=True)
|
|
323
|
+
assert _rel(out, ref) < tol
|
|
324
|
+
out.backward(dO.to(DEV))
|
|
325
|
+
dQ, dK, dV = _block_sparse_attn_backward(
|
|
326
|
+
q64, k64, v64, ref, do64, mask, scale, row_max=rm, row_denom=rd
|
|
327
|
+
)
|
|
328
|
+
for got, exp in ((Qd.grad, dQ), (Kd.grad, dK), (Vd.grad, dV)):
|
|
329
|
+
assert got.dtype == dtype
|
|
330
|
+
assert _rel(got, exp) < tol
|
|
331
|
+
|
|
332
|
+
def test_attention_bf16_stats_are_fp32(self):
|
|
333
|
+
seq_len, hd = 2 * P, 64
|
|
334
|
+
mask = _block_causal_mask(seq_len)
|
|
335
|
+
Q, K, V = (torch.randn(seq_len, hd, dtype=torch.bfloat16).to(DEV) for _ in range(3))
|
|
336
|
+
out, rm, rd = torch.ops.trnsparse.bsr_attn(
|
|
337
|
+
Q, K, V, mask.block_col_indices, mask.block_row_ptrs, P
|
|
338
|
+
)
|
|
339
|
+
assert out.dtype == torch.bfloat16
|
|
340
|
+
assert rm.dtype == torch.float32 and rd.dtype == torch.float32
|
|
@@ -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.
|
|
8
|
+
__version__ = "0.9.0"
|
|
9
9
|
|
|
10
10
|
from .formats import BSRMatrix, COOMatrix, CSRMatrix, eye_sparse, from_dense, from_scipy
|
|
11
11
|
from .iterative import (
|
|
@@ -32,6 +32,15 @@ import torch
|
|
|
32
32
|
from . import dispatch as _d
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def _stats_dtype(dtype: torch.dtype) -> torch.dtype:
|
|
36
|
+
"""Softmax statistics dtype: fp32 for low-precision Q (bf16 / fp16), else Q's.
|
|
37
|
+
|
|
38
|
+
The backward recomputes P = exp(S - row_max) / row_denom; a bf16
|
|
39
|
+
row_max would scale every P of the row by up to exp(2^-9 · |row_max|).
|
|
40
|
+
"""
|
|
41
|
+
return torch.float32 if dtype in (torch.bfloat16, torch.float16) else dtype
|
|
42
|
+
|
|
43
|
+
|
|
35
44
|
def _pattern(block_col_indices, block_row_ptrs, seq_len, block_size):
|
|
36
45
|
return _d._MaskPattern(block_col_indices, block_row_ptrs, (seq_len, seq_len), block_size)
|
|
37
46
|
|
|
@@ -47,17 +56,24 @@ def bsr_attn(
|
|
|
47
56
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
48
57
|
mask = _pattern(block_col_indices, block_row_ptrs, Q.shape[0], block_size)
|
|
49
58
|
out, row_max, row_denom = _d.nki_bsr_attn_tiled(Q, K, V, mask, return_stats=True)
|
|
50
|
-
# Fixed output contract (Q's
|
|
51
|
-
|
|
59
|
+
# Fixed output contract (Q's device; out in Q's dtype, the statistics in
|
|
60
|
+
# `_stats_dtype`) to match the fake.
|
|
61
|
+
sdt = _stats_dtype(Q.dtype)
|
|
62
|
+
return (
|
|
63
|
+
out.to(device=Q.device, dtype=Q.dtype),
|
|
64
|
+
row_max.to(device=Q.device, dtype=sdt),
|
|
65
|
+
row_denom.to(device=Q.device, dtype=sdt),
|
|
66
|
+
)
|
|
52
67
|
|
|
53
68
|
|
|
54
69
|
@bsr_attn.register_fake
|
|
55
70
|
def _bsr_attn_fake(Q, K, V, block_col_indices, block_row_ptrs, block_size):
|
|
56
71
|
M_tiles = Q.shape[0] // block_size
|
|
72
|
+
sdt = _stats_dtype(Q.dtype)
|
|
57
73
|
return (
|
|
58
74
|
torch.empty_like(Q),
|
|
59
|
-
Q.new_empty((M_tiles, block_size)),
|
|
60
|
-
Q.new_empty((M_tiles, block_size)),
|
|
75
|
+
Q.new_empty((M_tiles, block_size), dtype=sdt),
|
|
76
|
+
Q.new_empty((M_tiles, block_size), dtype=sdt),
|
|
61
77
|
)
|
|
62
78
|
|
|
63
79
|
|