fold-attention 0.1.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.
- fold_attention-0.1.0/PKG-INFO +124 -0
- fold_attention-0.1.0/README.md +106 -0
- fold_attention-0.1.0/pyproject.toml +62 -0
- fold_attention-0.1.0/pyproject.toml.orig +74 -0
- fold_attention-0.1.0/src/fold_attention/__init__.py +16 -0
- fold_attention-0.1.0/src/fold_attention/backward/__init__.py +6 -0
- fold_attention-0.1.0/src/fold_attention/backward/ablation.py +29 -0
- fold_attention-0.1.0/src/fold_attention/backward/grid.py +100 -0
- fold_attention-0.1.0/src/fold_attention/backward/kernel.py +1811 -0
- fold_attention-0.1.0/src/fold_attention/backward/launch.py +462 -0
- fold_attention-0.1.0/src/fold_attention/backward/plan.py +227 -0
- fold_attention-0.1.0/src/fold_attention/backward/postprocess.py +344 -0
- fold_attention-0.1.0/src/fold_attention/backward/preprocess.py +346 -0
- fold_attention-0.1.0/src/fold_attention/backward/scheduler.py +126 -0
- fold_attention-0.1.0/src/fold_attention/decode/__init__.py +53 -0
- fold_attention-0.1.0/src/fold_attention/decode/cache.py +443 -0
- fold_attention-0.1.0/src/fold_attention/decode/combine.py +210 -0
- fold_attention-0.1.0/src/fold_attention/decode/config.py +206 -0
- fold_attention-0.1.0/src/fold_attention/decode/device.py +780 -0
- fold_attention-0.1.0/src/fold_attention/decode/front.py +722 -0
- fold_attention-0.1.0/src/fold_attention/decode/heuristics.py +853 -0
- fold_attention-0.1.0/src/fold_attention/decode/kernel.py +1562 -0
- fold_attention-0.1.0/src/fold_attention/decode/launch.py +1362 -0
- fold_attention-0.1.0/src/fold_attention/decode/packed.py +1081 -0
- fold_attention-0.1.0/src/fold_attention/decode/ptx.py +524 -0
- fold_attention-0.1.0/src/fold_attention/decode/quant.py +204 -0
- fold_attention-0.1.0/src/fold_attention/decode/reference.py +591 -0
- fold_attention-0.1.0/src/fold_attention/decode/rows.py +184 -0
- fold_attention-0.1.0/src/fold_attention/decode/wide.py +902 -0
- fold_attention-0.1.0/src/fold_attention/decode/write.py +1009 -0
- fold_attention-0.1.0/src/fold_attention/interface.py +173 -0
- fold_attention-0.1.0/src/fold_attention/kv_cache.py +963 -0
- fold_attention-0.1.0/src/fold_attention/ptx.py +123 -0
- fold_attention-0.1.0/src/fold_attention/utils.py +123 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: fold-attention
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FoldAttention declares softmax’s reference before execution, making attention additive and enabling up to 3.09× faster H100 decode and deterministic backward faster than nondeterministic kernels.
|
|
5
|
+
Author: Sriman Achanta
|
|
6
|
+
Author-email: Sriman Achanta <srimanachanta@gmail.com>
|
|
7
|
+
Requires-Dist: flash-attn-4>=4.0.0b31
|
|
8
|
+
Requires-Dist: numpy>=2.5.3
|
|
9
|
+
Requires-Dist: nvidia-cutlass-dsl>=4.7.1
|
|
10
|
+
Requires-Dist: quack-kernels>=0.6.5
|
|
11
|
+
Requires-Dist: torch>=2.14.0
|
|
12
|
+
Requires-Dist: flash-attn-4[cu13]>=4.0.0b31 ; extra == 'cu13'
|
|
13
|
+
Requires-Dist: nvidia-cutlass-dsl[cu13]>=4.7.1 ; extra == 'cu13'
|
|
14
|
+
Requires-Dist: quack-kernels[cu13]>=0.6.5 ; extra == 'cu13'
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Provides-Extra: cu13
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# FoldAttention
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="assets/hero.png" alt="FoldAttention overview: declared-reference softmax, decode speedup, and deterministic backward throughput">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
FoldAttention declares softmax's reference before execution. Final weights
|
|
26
|
+
make decode contributions additive, while a shared integer grid makes the
|
|
27
|
+
backward deterministic without serializing its reductions.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Install FoldAttention from PyPI:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install fold-attention
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Install the CUDA 13 dependencies with the `cu13` extra:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install "fold-attention[cu13]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
FoldAttention requires Python 3.12 or newer and an NVIDIA SM90 GPU.
|
|
44
|
+
|
|
45
|
+
## Code usage
|
|
46
|
+
|
|
47
|
+
### Training
|
|
48
|
+
|
|
49
|
+
`fold_attn_func` follows FlashAttention's `(batch, seqlen, heads, head_dim)`
|
|
50
|
+
layout. Keys and values may use fewer heads for GQA or MQA.
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from fold_attention import fold_attn_func
|
|
54
|
+
|
|
55
|
+
out = fold_attn_func(q, k, v, causal=True)
|
|
56
|
+
out.backward(dout)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The forward uses FlashAttention-4. The FoldAttention backward produces
|
|
60
|
+
bit-identical gradients across repeated runs, batching, and variable-length
|
|
61
|
+
packing.
|
|
62
|
+
|
|
63
|
+
For packed self-attention, pass `(total_tokens, heads, head_dim)` tensors and
|
|
64
|
+
a CUDA `int32` cumulative-length vector:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from fold_attention import fold_attn_varlen_func
|
|
68
|
+
|
|
69
|
+
out = fold_attn_varlen_func(q, k, v, cu_seqlens, causal=True)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Decode
|
|
73
|
+
|
|
74
|
+
`FoldKVCache` owns one layer's paged cache. `prefill` runs FlashAttention-4
|
|
75
|
+
and writes the cache. `fold_attn_with_kvcache` optionally appends one token per
|
|
76
|
+
request, then attends over the updated cache.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from fold_attention import FoldKVCache, fold_attn_with_kvcache
|
|
80
|
+
|
|
81
|
+
cache = FoldKVCache(
|
|
82
|
+
batch=batch_size,
|
|
83
|
+
n_heads=n_heads,
|
|
84
|
+
n_kv_heads=n_kv_heads,
|
|
85
|
+
head_dim=head_dim,
|
|
86
|
+
max_len=max_len,
|
|
87
|
+
depth=16,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
prompt_out = cache.prefill(q, k, v, cu_seqlens)
|
|
91
|
+
step_out = fold_attn_with_kvcache(q_step, cache, k_step, v_step)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Set `depth=None` for dense decode. A finite depth cuts low-weight keys while
|
|
95
|
+
retaining their normalization mass. Set `v8=True` to store values in two E4M3
|
|
96
|
+
planes.
|
|
97
|
+
|
|
98
|
+
## Benchmarks
|
|
99
|
+
|
|
100
|
+
### Decode speed and accuracy
|
|
101
|
+
|
|
102
|
+
Latency against FP32-relative error on seven real-model generations. Each
|
|
103
|
+
FoldAttention curve sweeps the decode depth.
|
|
104
|
+
|
|
105
|
+
<p align="center">
|
|
106
|
+
<img src="assets/decode-pareto.png" alt="Decode latency against FP32-relative error on seven real-model generations">
|
|
107
|
+
</p>
|
|
108
|
+
|
|
109
|
+
### Deterministic backward
|
|
110
|
+
|
|
111
|
+
Causal backward throughput across MHA and GQA shapes. The rows below each
|
|
112
|
+
panel report FoldAttention throughput relative to the fastest deterministic
|
|
113
|
+
and nondeterministic kernel.
|
|
114
|
+
|
|
115
|
+
<p align="center">
|
|
116
|
+
<img src="assets/backward-throughput.png" alt="Causal attention backward throughput on MHA and GQA shapes">
|
|
117
|
+
</p>
|
|
118
|
+
|
|
119
|
+
The benchmark suite and measurement protocol are documented in
|
|
120
|
+
[`benchmarks/README.md`](benchmarks/README.md).
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
FoldAttention is released under the [Apache License 2.0](LICENSE).
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# FoldAttention
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="assets/hero.png" alt="FoldAttention overview: declared-reference softmax, decode speedup, and deterministic backward throughput">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
FoldAttention declares softmax's reference before execution. Final weights
|
|
8
|
+
make decode contributions additive, while a shared integer grid makes the
|
|
9
|
+
backward deterministic without serializing its reductions.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Install FoldAttention from PyPI:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install fold-attention
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Install the CUDA 13 dependencies with the `cu13` extra:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install "fold-attention[cu13]"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
FoldAttention requires Python 3.12 or newer and an NVIDIA SM90 GPU.
|
|
26
|
+
|
|
27
|
+
## Code usage
|
|
28
|
+
|
|
29
|
+
### Training
|
|
30
|
+
|
|
31
|
+
`fold_attn_func` follows FlashAttention's `(batch, seqlen, heads, head_dim)`
|
|
32
|
+
layout. Keys and values may use fewer heads for GQA or MQA.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from fold_attention import fold_attn_func
|
|
36
|
+
|
|
37
|
+
out = fold_attn_func(q, k, v, causal=True)
|
|
38
|
+
out.backward(dout)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The forward uses FlashAttention-4. The FoldAttention backward produces
|
|
42
|
+
bit-identical gradients across repeated runs, batching, and variable-length
|
|
43
|
+
packing.
|
|
44
|
+
|
|
45
|
+
For packed self-attention, pass `(total_tokens, heads, head_dim)` tensors and
|
|
46
|
+
a CUDA `int32` cumulative-length vector:
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from fold_attention import fold_attn_varlen_func
|
|
50
|
+
|
|
51
|
+
out = fold_attn_varlen_func(q, k, v, cu_seqlens, causal=True)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Decode
|
|
55
|
+
|
|
56
|
+
`FoldKVCache` owns one layer's paged cache. `prefill` runs FlashAttention-4
|
|
57
|
+
and writes the cache. `fold_attn_with_kvcache` optionally appends one token per
|
|
58
|
+
request, then attends over the updated cache.
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from fold_attention import FoldKVCache, fold_attn_with_kvcache
|
|
62
|
+
|
|
63
|
+
cache = FoldKVCache(
|
|
64
|
+
batch=batch_size,
|
|
65
|
+
n_heads=n_heads,
|
|
66
|
+
n_kv_heads=n_kv_heads,
|
|
67
|
+
head_dim=head_dim,
|
|
68
|
+
max_len=max_len,
|
|
69
|
+
depth=16,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
prompt_out = cache.prefill(q, k, v, cu_seqlens)
|
|
73
|
+
step_out = fold_attn_with_kvcache(q_step, cache, k_step, v_step)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Set `depth=None` for dense decode. A finite depth cuts low-weight keys while
|
|
77
|
+
retaining their normalization mass. Set `v8=True` to store values in two E4M3
|
|
78
|
+
planes.
|
|
79
|
+
|
|
80
|
+
## Benchmarks
|
|
81
|
+
|
|
82
|
+
### Decode speed and accuracy
|
|
83
|
+
|
|
84
|
+
Latency against FP32-relative error on seven real-model generations. Each
|
|
85
|
+
FoldAttention curve sweeps the decode depth.
|
|
86
|
+
|
|
87
|
+
<p align="center">
|
|
88
|
+
<img src="assets/decode-pareto.png" alt="Decode latency against FP32-relative error on seven real-model generations">
|
|
89
|
+
</p>
|
|
90
|
+
|
|
91
|
+
### Deterministic backward
|
|
92
|
+
|
|
93
|
+
Causal backward throughput across MHA and GQA shapes. The rows below each
|
|
94
|
+
panel report FoldAttention throughput relative to the fastest deterministic
|
|
95
|
+
and nondeterministic kernel.
|
|
96
|
+
|
|
97
|
+
<p align="center">
|
|
98
|
+
<img src="assets/backward-throughput.png" alt="Causal attention backward throughput on MHA and GQA shapes">
|
|
99
|
+
</p>
|
|
100
|
+
|
|
101
|
+
The benchmark suite and measurement protocol are documented in
|
|
102
|
+
[`benchmarks/README.md`](benchmarks/README.md).
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
FoldAttention is released under the [Apache License 2.0](LICENSE).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fold-attention"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "FoldAttention declares softmax’s reference before execution, making attention additive and enabling up to 3.09× faster H100 decode and deterministic backward faster than nondeterministic kernels."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"flash-attn-4>=4.0.0b31",
|
|
9
|
+
"numpy>=2.5.3",
|
|
10
|
+
"nvidia-cutlass-dsl>=4.7.1",
|
|
11
|
+
"quack-kernels>=0.6.5",
|
|
12
|
+
"torch>=2.14.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[[project.authors]]
|
|
16
|
+
name = "Sriman Achanta"
|
|
17
|
+
email = "srimanachanta@gmail.com"
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
cu13 = [
|
|
21
|
+
"flash-attn-4[cu13]>=4.0.0b31",
|
|
22
|
+
"nvidia-cutlass-dsl[cu13]>=4.7.1",
|
|
23
|
+
"quack-kernels[cu13]>=0.6.5",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = ["pytest>=8.0"]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["uv_build>=0.12.10,<0.13.0"]
|
|
31
|
+
build-backend = "uv_build"
|
|
32
|
+
|
|
33
|
+
[tool.uv.sources.torch]
|
|
34
|
+
index = "pytorch-cu130"
|
|
35
|
+
|
|
36
|
+
[[tool.uv.index]]
|
|
37
|
+
name = "pytorch-cu130"
|
|
38
|
+
url = "https://download.pytorch.org/whl/cu130"
|
|
39
|
+
explicit = true
|
|
40
|
+
|
|
41
|
+
[tool.ty.environment]
|
|
42
|
+
python = ".venv"
|
|
43
|
+
extra-paths = [".venv/lib/python3.14/site-packages/nvidia_cutlass_dsl/dsl_packages"]
|
|
44
|
+
|
|
45
|
+
[tool.ty.analysis]
|
|
46
|
+
allowed-unresolved-imports = ["cuda.bindings.**"]
|
|
47
|
+
replace-imports-with-any = ["cutlass._mlir.**"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 100
|
|
51
|
+
target-version = "py312"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
ignore = [
|
|
55
|
+
"C408",
|
|
56
|
+
"FURB136",
|
|
57
|
+
"PLR1730",
|
|
58
|
+
"SIM102",
|
|
59
|
+
"SIM108",
|
|
60
|
+
"SIM113",
|
|
61
|
+
"SIM114",
|
|
62
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fold-attention"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "FoldAttention declares softmax’s reference before execution, making attention additive and enabling up to 3.09× faster H100 decode and deterministic backward faster than nondeterministic kernels."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Sriman Achanta", email = "srimanachanta@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"flash-attn-4>=4.0.0b31",
|
|
12
|
+
"numpy>=2.5.3",
|
|
13
|
+
"nvidia-cutlass-dsl>=4.7.1",
|
|
14
|
+
"quack-kernels>=0.6.5",
|
|
15
|
+
"torch>=2.14.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = [
|
|
20
|
+
"pytest>=8.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
cu13 = [
|
|
25
|
+
"flash-attn-4[cu13]>=4.0.0b31",
|
|
26
|
+
"nvidia-cutlass-dsl[cu13]>=4.7.1",
|
|
27
|
+
"quack-kernels[cu13]>=0.6.5",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[build-system]
|
|
31
|
+
requires = ["uv_build>=0.12.10,<0.13.0"]
|
|
32
|
+
build-backend = "uv_build"
|
|
33
|
+
|
|
34
|
+
[tool.uv.sources]
|
|
35
|
+
torch = { index = "pytorch-cu130" }
|
|
36
|
+
|
|
37
|
+
[[tool.uv.index]]
|
|
38
|
+
name = "pytorch-cu130"
|
|
39
|
+
url = "https://download.pytorch.org/whl/cu130"
|
|
40
|
+
explicit = true
|
|
41
|
+
|
|
42
|
+
[tool.ty.environment]
|
|
43
|
+
python = ".venv"
|
|
44
|
+
extra-paths = [
|
|
45
|
+
".venv/lib/python3.14/site-packages/nvidia_cutlass_dsl/dsl_packages"
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[tool.ty.analysis]
|
|
49
|
+
allowed-unresolved-imports = [
|
|
50
|
+
"cuda.bindings.**",
|
|
51
|
+
]
|
|
52
|
+
replace-imports-with-any = ["cutlass._mlir.**"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 100
|
|
56
|
+
target-version = "py312"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
ignore = [
|
|
60
|
+
# keyword dicts read as the calls they feed
|
|
61
|
+
"C408",
|
|
62
|
+
# Rewrites that break or change CuTe DSL tracing. A condition on a traced
|
|
63
|
+
# value has no Python bool, so builtin `min`/`max` (PLR1730, FURB136) and
|
|
64
|
+
# a ternary (SIM108) fail to compile; `enumerate` hides a `cutlass.range`
|
|
65
|
+
# loop from the DSL's loop rewrite (SIM113); collapsing or merging `if`
|
|
66
|
+
# statements (SIM102, SIM114) changes the regions a dynamic condition
|
|
67
|
+
# traces into.
|
|
68
|
+
"FURB136",
|
|
69
|
+
"PLR1730",
|
|
70
|
+
"SIM102",
|
|
71
|
+
"SIM108",
|
|
72
|
+
"SIM113",
|
|
73
|
+
"SIM114",
|
|
74
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""FoldAttention: softmax attention whose cross-CTA reductions do not depend
|
|
2
|
+
on the order work runs in.
|
|
3
|
+
|
|
4
|
+
Training uses FlashAttention-4's forward and an SM90 backward whose dQ, dK and
|
|
5
|
+
dV sums are integer or fixed-order reductions, so gradients are the same bits
|
|
6
|
+
on every run and for every batching of a request. Serving decodes over a
|
|
7
|
+
quantised paged cache against a static per-row reference, so split-KV,
|
|
8
|
+
shared-prefix and speculative partials combine by plain addition.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .interface import fold_attn_func, fold_attn_varlen_func, fold_attn_with_kvcache
|
|
12
|
+
from .kv_cache import FoldKVCache
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
|
|
16
|
+
__all__ = ["FoldKVCache", "fold_attn_func", "fold_attn_varlen_func", "fold_attn_with_kvcache"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""The backward with one mechanism changed, for measuring what it buys.
|
|
2
|
+
|
|
3
|
+
Each variant is the shipped kernel (`prepare_backward`) with one choice
|
|
4
|
+
replaced and the rest as it is:
|
|
5
|
+
|
|
6
|
+
- `fp32_dq=True`: dQ's partials summed as fp32 by `cp.reduce.async.bulk
|
|
7
|
+
.add.f32` in the order they land, as FlashAttention sums them. It skips the
|
|
8
|
+
rounding and is not deterministic. P then carries no grid exponent, whose
|
|
9
|
+
subtraction rounds the softmax's argument, so dK and dV move in their last
|
|
10
|
+
bits too.
|
|
11
|
+
- `persistent=False`: a dense call runs one CTA per work tile under
|
|
12
|
+
`SingleTileScheduler` instead of the persistent work list, with no
|
|
13
|
+
canonical dK/dV records.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from .launch import BackwardLaunch, _prepare
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def prepare_backward_variant(
|
|
22
|
+
q, k, v, o, do, lse, *, causal, fp32_dq=False, persistent=True, **kwargs
|
|
23
|
+
) -> BackwardLaunch:
|
|
24
|
+
"""`prepare_backward` with dQ summed as fp32 (`fp32_dq`) and the dense
|
|
25
|
+
scheduler `persistent`; the other keyword arguments are
|
|
26
|
+
`prepare_backward`'s. The defaults are the shipped kernel."""
|
|
27
|
+
return _prepare(
|
|
28
|
+
q, k, v, o, do, lse, causal=causal, dq_fp32=fp32_dq, persistent=persistent, **kwargs
|
|
29
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""The backward's integer grids, derived from bounds the kernels compute.
|
|
2
|
+
|
|
3
|
+
Every kernel that quantises or reconstructs a partial evaluates the same bound
|
|
4
|
+
with the same rounded instructions, so each arrives at the same scale. The
|
|
5
|
+
inputs are the preprocess's maxima (`red.max` over float bits), which do not
|
|
6
|
+
depend on the order CTAs finish in or on anything outside the request.
|
|
7
|
+
|
|
8
|
+
dQ, per (request, KV head): `|sum_j dS_ij K_jd| <= (sum_j |dS_ij|) max|K|`
|
|
9
|
+
and, with P summing to one, `sum_j |dS_ij| <= |dO_i| max_j |V_j| + |D_i|`,
|
|
10
|
+
where `|V_j| <= C_D max|V|` and `C_D` is sqrt(D) rounded up to a power of two.
|
|
11
|
+
dV, on the canonical-record path: `|dV_jd| <= C max_i |dO_id|`, the largest
|
|
12
|
+
element of dO, since the bound is per output component. dK there:
|
|
13
|
+
`|dK_j| <= C (max|dO| C_D max|V| + max|D|) max|Q|`. `C` bounds the column
|
|
14
|
+
mass `sum_{h,i} P_hij`; the only data-free bound is the number of query rows
|
|
15
|
+
`G n`, which is why dK needs a 61-bit grid. Every bound is widened by 2^-6 for
|
|
16
|
+
the bf16 rounding of P and dS.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
|
|
21
|
+
import cutlass
|
|
22
|
+
from cutlass import Float32, Int32
|
|
23
|
+
|
|
24
|
+
from ..ptx import f32_rn
|
|
25
|
+
|
|
26
|
+
# stats[b, h_kv, i], float bits, zero before the call: max |K|, max |V|, max
|
|
27
|
+
# ||dO_i|| over the group's rows, max |D_i| over them, and for the record
|
|
28
|
+
# path max |dO_id| over every element and max |Q| over the group
|
|
29
|
+
ST_K, ST_V, ST_DO, ST_DELTA, ST_DOC, ST_Q = 0, 1, 2, 3, 4, 5
|
|
30
|
+
N_STATS = 8
|
|
31
|
+
|
|
32
|
+
DQ_BITS = 30
|
|
33
|
+
DKV_BITS = 61
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def root_d(D):
|
|
37
|
+
"""sqrt(D) rounded up to a power of two: exact, and a bound."""
|
|
38
|
+
return float(2 ** math.ceil(math.log2(math.sqrt(D))))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def pow2_scale(bound, bits: int) -> Float32:
|
|
42
|
+
"""`2^(bits - ceil(log2 (bound (1 + 2^-6))))`, clamped to [2^-100, 2^100].
|
|
43
|
+
A zero bound takes 2^100, since its partials are all zero."""
|
|
44
|
+
b = f32_rn("mul.rn.f32", bound, Float32(1.0 + 2.0**-6))
|
|
45
|
+
bb = b.bitcast(Int32)
|
|
46
|
+
clog = (
|
|
47
|
+
((bb >> 23) & 0xFF) - 127 + Int32(cutlass.select_((bb & 0x7FFFFF) != 0, Int32(1), Int32(0)))
|
|
48
|
+
)
|
|
49
|
+
sexp = Int32(bits) - clog
|
|
50
|
+
sexp = Int32(cutlass.select_(b > Float32(0.0), sexp, Int32(100)))
|
|
51
|
+
sexp = Int32(cutlass.select_(sexp > 100, Int32(100), sexp))
|
|
52
|
+
sexp = Int32(cutlass.select_(sexp < -100, Int32(-100), sexp))
|
|
53
|
+
return ((sexp + 127) << 23).bitcast(Float32)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def inv_pow2(scale) -> Float32:
|
|
57
|
+
"""`1 / scale` for a power of two, from its bits."""
|
|
58
|
+
return (Int32(0x7F000000) - scale.bitcast(Int32)).bitcast(Float32)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def log2_pow2(scale) -> Float32:
|
|
62
|
+
"""The exponent of a power of two, as a float."""
|
|
63
|
+
return ((scale.bitcast(Int32) >> 23) - Int32(127)).to(Float32)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_stats(mStats, batch_idx, head_kv, which=range(N_STATS)):
|
|
67
|
+
"""The maxima `which` of one (request, KV head), by index."""
|
|
68
|
+
return {i: mStats[batch_idx, head_kv, i].bitcast(Float32) for i in which}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def dq_scale(mStats, batch_idx, head_kv, cd: float) -> Float32:
|
|
72
|
+
"""dQ's scale for one (request, KV head).
|
|
73
|
+
|
|
74
|
+
A constant exponent is free here: `P 2^s` is `exp2(c S - lse + s)`, so
|
|
75
|
+
the softmax's own exponent carries it and the dQ partials leave the GEMM
|
|
76
|
+
on the grid already."""
|
|
77
|
+
st = load_stats(mStats, batch_idx, head_kv, (ST_K, ST_V, ST_DO, ST_DELTA))
|
|
78
|
+
s = f32_rn(
|
|
79
|
+
"add.rn.f32",
|
|
80
|
+
f32_rn("mul.rn.f32", st[ST_DO], f32_rn("mul.rn.f32", st[ST_V], Float32(cd))),
|
|
81
|
+
st[ST_DELTA],
|
|
82
|
+
)
|
|
83
|
+
return pow2_scale(f32_rn("mul.rn.f32", s, st[ST_K]), DQ_BITS)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def dv_scale(st, rows, bits: int) -> Float32:
|
|
87
|
+
"""dV's scale from `load_stats`, for `rows` query rows bounding the column
|
|
88
|
+
mass."""
|
|
89
|
+
return pow2_scale(f32_rn("mul.rn.f32", rows, st[ST_DOC]), bits)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def dk_scale(st, rows, cd: float, bits: int) -> Float32:
|
|
93
|
+
"""dK's scale from `load_stats`, for `rows` query rows bounding the column
|
|
94
|
+
mass."""
|
|
95
|
+
s = f32_rn(
|
|
96
|
+
"add.rn.f32",
|
|
97
|
+
f32_rn("mul.rn.f32", st[ST_DO], f32_rn("mul.rn.f32", st[ST_V], Float32(cd))),
|
|
98
|
+
st[ST_DELTA],
|
|
99
|
+
)
|
|
100
|
+
return pow2_scale(f32_rn("mul.rn.f32", rows, f32_rn("mul.rn.f32", s, st[ST_Q])), bits)
|