liangsu9988 commited on
Commit
8b5c9e3
·
verified ·
1 Parent(s): e1bbc3c

Uploaded using `kernel-builder`.

Browse files
benchmarks/benchmark.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import statistics
5
+
6
+ import torch
7
+ import torch.nn.functional as F
8
+
9
+ from fa2_seqused_runtime import allocate_outputs, allocate_workspace, forward_static
10
+
11
+
12
+ SHAPES = [
13
+ (1, 1, 512, 8, 2, 128),
14
+ (1, 16, 1024, 16, 4, 128),
15
+ (1, 49, 2520, 24, 4, 128),
16
+ (1, 64, 4096, 32, 8, 128),
17
+ (1, 1024, 1024, 32, 8, 128),
18
+ ]
19
+
20
+
21
+ def time_us(fn, warmup=50, repeats=200):
22
+ for _ in range(warmup):
23
+ fn()
24
+ torch.cuda.synchronize()
25
+ samples = []
26
+ for _ in range(repeats):
27
+ start = torch.cuda.Event(enable_timing=True)
28
+ end = torch.cuda.Event(enable_timing=True)
29
+ start.record()
30
+ fn()
31
+ end.record()
32
+ end.synchronize()
33
+ samples.append(start.elapsed_time(end) * 1000.0)
34
+ return statistics.median(samples)
35
+
36
+
37
+ def main():
38
+ parser = argparse.ArgumentParser()
39
+ parser.add_argument("--dtype", choices=("bf16", "fp16"), default="bf16")
40
+ args = parser.parse_args()
41
+ dtype = torch.bfloat16 if args.dtype == "bf16" else torch.float16
42
+ print("B,Sq,Sk,Hq,Hkv,D,FlashRT_us,SDPA_expandedGQA_us,Speedup")
43
+ for batch, sq, sk, hq, hkv, dim in SHAPES:
44
+ q = torch.randn(batch, sq, hq, dim, device="cuda", dtype=dtype)
45
+ k = torch.randn(batch, sk, hkv, dim, device="cuda", dtype=dtype)
46
+ v = torch.randn_like(k)
47
+ out, lse = allocate_outputs(q)
48
+ workspace = allocate_workspace(q, k)
49
+ kr = k.repeat_interleave(hq // hkv, dim=2)
50
+ vr = v.repeat_interleave(hq // hkv, dim=2)
51
+
52
+ def flashrt():
53
+ forward_static(q, k, v, out=out, softmax_lse=lse, workspace=workspace)
54
+
55
+ def sdpa():
56
+ F.scaled_dot_product_attention(
57
+ q.permute(0, 2, 1, 3),
58
+ kr.permute(0, 2, 1, 3),
59
+ vr.permute(0, 2, 1, 3),
60
+ )
61
+
62
+ flashrt_us = time_us(flashrt)
63
+ sdpa_us = time_us(sdpa)
64
+ print(f"{batch},{sq},{sk},{hq},{hkv},{dim},{flashrt_us:.3f},{sdpa_us:.3f},{sdpa_us / flashrt_us:.3f}")
65
+
66
+
67
+ if __name__ == "__main__":
68
+ main()
build/torch211-cxx11-cu128-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Static-buffer FlashAttention-2 runtime operators from FlashRT."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import math
7
+ from typing import Optional
8
+
9
+ import torch
10
+
11
+ from ._ops import add_op_namespace_prefix, ops
12
+
13
+
14
+ SUPPORTED_HEAD_DIMS = (64, 96, 128, 256)
15
+ SPLIT_HEAD_DIMS = (96, 128, 256)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class FA2Workspace:
20
+ """Preallocated split-KV workspace for one static attention shape."""
21
+
22
+ softmax_lse_accum: torch.Tensor
23
+ out_accum: torch.Tensor
24
+ num_sms: int
25
+ num_splits: int
26
+
27
+
28
+ def _ceildiv(a: int, b: int) -> int:
29
+ return (a + b - 1) // b
30
+
31
+
32
+ def recommended_num_splits(
33
+ batch: int,
34
+ seqlen_q: int,
35
+ seqlen_k: int,
36
+ heads_q: int,
37
+ head_dim: int,
38
+ num_sms: int,
39
+ ) -> int:
40
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
41
+
42
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
43
+ if any(int(v) <= 0 for v in values):
44
+ raise ValueError("all shape values and num_sms must be positive")
45
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
46
+ raise ValueError(f"head_dim must be one of {SUPPORTED_HEAD_DIMS}")
47
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
48
+ n_blocks = _ceildiv(seqlen_k, block_n)
49
+ m_blocks = _ceildiv(seqlen_q, 64)
50
+ blocks = batch * heads_q * m_blocks
51
+ effective_sms = num_sms * 2
52
+ if blocks >= 0.8 * effective_sms:
53
+ return 1
54
+ max_splits = min(128, effective_sms, n_blocks)
55
+ efficiencies = [0.0] * (max_splits + 1)
56
+ best = 0.0
57
+ for split in range(1, max_splits + 1):
58
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
59
+ if not eligible:
60
+ continue
61
+ waves = blocks * split / effective_sms
62
+ efficiencies[split] = waves / math.ceil(waves)
63
+ best = max(best, efficiencies[split])
64
+ for split in range(1, max_splits + 1):
65
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
66
+ if eligible and efficiencies[split] >= 0.85 * best:
67
+ return split
68
+ return 1
69
+
70
+
71
+ def allocate_workspace(
72
+ q: torch.Tensor,
73
+ k: torch.Tensor,
74
+ *,
75
+ num_sms: Optional[int] = None,
76
+ ) -> Optional[FA2Workspace]:
77
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
78
+
79
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
80
+ once during runtime setup; never call this helper inside a captured loop.
81
+ """
82
+
83
+ if q.ndim != 4 or k.ndim != 4:
84
+ raise ValueError("q and k must have shape (B, S, H, D)")
85
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
86
+ return None
87
+ if num_sms is None:
88
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
89
+ splits = recommended_num_splits(
90
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
91
+ )
92
+ if splits == 1:
93
+ return None
94
+ lse = torch.empty(
95
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
96
+ device=q.device,
97
+ dtype=torch.float32,
98
+ )
99
+ out = torch.empty(
100
+ (splits, q.shape[0], q.shape[2], q.shape[1], q.shape[3]),
101
+ device=q.device,
102
+ dtype=torch.float32,
103
+ )
104
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
105
+
106
+
107
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
108
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
109
+
110
+ if q.ndim != 4:
111
+ raise ValueError("q must have shape (B, S, H, D)")
112
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
113
+ lse = torch.empty(
114
+ (q.shape[0], q.shape[2], q.shape[1]),
115
+ device=q.device,
116
+ dtype=torch.float32,
117
+ )
118
+ return out, lse
119
+
120
+
121
+ def _workspace_args(
122
+ workspace: Optional[FA2Workspace],
123
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
124
+ if workspace is None:
125
+ return None, None, 0
126
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
127
+
128
+
129
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
130
+ def _forward_static_fake(
131
+ q: torch.Tensor,
132
+ k: torch.Tensor,
133
+ v: torch.Tensor,
134
+ out: torch.Tensor,
135
+ softmax_lse: torch.Tensor,
136
+ softmax_lse_accum: Optional[torch.Tensor],
137
+ out_accum: Optional[torch.Tensor],
138
+ softmax_scale: float,
139
+ causal: bool = False,
140
+ num_sms: int = 0,
141
+ ) -> None:
142
+ del k, v, softmax_scale, causal, num_sms
143
+ if q.ndim != 4 or out.shape != q.shape:
144
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
145
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
146
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
147
+ if (softmax_lse_accum is None) != (out_accum is None):
148
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
149
+ return None
150
+
151
+
152
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
153
+ def _forward_seqused_static_fake(
154
+ q: torch.Tensor,
155
+ k: torch.Tensor,
156
+ v: torch.Tensor,
157
+ seqused_k: torch.Tensor,
158
+ out: torch.Tensor,
159
+ softmax_lse: torch.Tensor,
160
+ softmax_lse_accum: Optional[torch.Tensor],
161
+ out_accum: Optional[torch.Tensor],
162
+ softmax_scale: float,
163
+ num_sms: int = 0,
164
+ ) -> None:
165
+ del seqused_k
166
+ return _forward_static_fake(
167
+ q,
168
+ k,
169
+ v,
170
+ out,
171
+ softmax_lse,
172
+ softmax_lse_accum,
173
+ out_accum,
174
+ softmax_scale,
175
+ False,
176
+ num_sms,
177
+ )
178
+
179
+
180
+ def forward_static(
181
+ q: torch.Tensor,
182
+ k: torch.Tensor,
183
+ v: torch.Tensor,
184
+ *,
185
+ out: torch.Tensor,
186
+ softmax_lse: torch.Tensor,
187
+ workspace: Optional[FA2Workspace] = None,
188
+ softmax_scale: Optional[float] = None,
189
+ causal: bool = False,
190
+ ) -> torch.Tensor:
191
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
192
+
193
+ if softmax_scale is None:
194
+ softmax_scale = q.shape[-1] ** -0.5
195
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
196
+ ops.forward_static(
197
+ q,
198
+ k,
199
+ v,
200
+ out,
201
+ softmax_lse,
202
+ lse_accum,
203
+ out_accum,
204
+ float(softmax_scale),
205
+ bool(causal),
206
+ int(num_sms),
207
+ )
208
+ return out
209
+
210
+
211
+ def forward_seqused_static(
212
+ q: torch.Tensor,
213
+ k: torch.Tensor,
214
+ v: torch.Tensor,
215
+ seqused_k: torch.Tensor,
216
+ *,
217
+ out: torch.Tensor,
218
+ softmax_lse: torch.Tensor,
219
+ workspace: Optional[FA2Workspace] = None,
220
+ softmax_scale: Optional[float] = None,
221
+ ) -> torch.Tensor:
222
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
223
+
224
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
225
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
226
+ reset is captured together with the kernel by CUDA Graphs.
227
+ """
228
+
229
+ if softmax_scale is None:
230
+ softmax_scale = q.shape[-1] ** -0.5
231
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
232
+ if lse_accum is not None:
233
+ lse_accum.fill_(-torch.inf)
234
+ ops.forward_seqused_static(
235
+ q,
236
+ k,
237
+ v,
238
+ seqused_k,
239
+ out,
240
+ softmax_lse,
241
+ lse_accum,
242
+ out_accum,
243
+ float(softmax_scale),
244
+ int(num_sms),
245
+ )
246
+ return out
247
+
248
+
249
+ def forward(
250
+ q: torch.Tensor,
251
+ k: torch.Tensor,
252
+ v: torch.Tensor,
253
+ *,
254
+ softmax_scale: Optional[float] = None,
255
+ causal: bool = False,
256
+ use_split_kv: bool = True,
257
+ ) -> torch.Tensor:
258
+ """Convenience API that allocates outputs and optional split-KV workspace."""
259
+
260
+ out, lse = allocate_outputs(q)
261
+ workspace = allocate_workspace(q, k) if use_split_kv else None
262
+ return forward_static(
263
+ q,
264
+ k,
265
+ v,
266
+ out=out,
267
+ softmax_lse=lse,
268
+ workspace=workspace,
269
+ softmax_scale=softmax_scale,
270
+ causal=causal,
271
+ )
272
+
273
+
274
+ __all__ = [
275
+ "FA2Workspace",
276
+ "SPLIT_HEAD_DIMS",
277
+ "SUPPORTED_HEAD_DIMS",
278
+ "allocate_outputs",
279
+ "allocate_workspace",
280
+ "forward",
281
+ "forward_seqused_static",
282
+ "forward_static",
283
+ "recommended_num_splits",
284
+ ]
build/torch211-cxx11-cu128-x86_64-linux/_fa2_seqused_runtime_cuda_14f2290.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fe9a3e7eb123677b88be9d407f5570bce390bce7752abe776e2245b0344082bc
3
+ size 562259528
build/torch211-cxx11-cu128-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_14f2290
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_14f2290
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_14f2290::{op_name}"
build/torch211-cxx11-cu128-x86_64-linux/fa2_seqused_runtime/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu128-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_14f2290",
4
+ "version": 1,
5
+ "license": "BSD-3-Clause",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "ck4+/aHijqRRXtVYRzkkIj7bHwvMxxOEnRIKVQSkd6k=",
22
+ "_fa2_seqused_runtime_cuda_14f2290.abi3.so": "/po+frEjZ3uIvp1Af1VwvOOQvOd1Kr53biJFsDRAgrw=",
23
+ "_ops.py": "S1LORAtc4O5qo9aO4HWImXiI6QrfLU+l0PF9N/g+k88=",
24
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ }
27
+ }
build/torch211-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Static-buffer FlashAttention-2 runtime operators from FlashRT."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import math
7
+ from typing import Optional
8
+
9
+ import torch
10
+
11
+ from ._ops import add_op_namespace_prefix, ops
12
+
13
+
14
+ SUPPORTED_HEAD_DIMS = (64, 96, 128, 256)
15
+ SPLIT_HEAD_DIMS = (96, 128, 256)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class FA2Workspace:
20
+ """Preallocated split-KV workspace for one static attention shape."""
21
+
22
+ softmax_lse_accum: torch.Tensor
23
+ out_accum: torch.Tensor
24
+ num_sms: int
25
+ num_splits: int
26
+
27
+
28
+ def _ceildiv(a: int, b: int) -> int:
29
+ return (a + b - 1) // b
30
+
31
+
32
+ def recommended_num_splits(
33
+ batch: int,
34
+ seqlen_q: int,
35
+ seqlen_k: int,
36
+ heads_q: int,
37
+ head_dim: int,
38
+ num_sms: int,
39
+ ) -> int:
40
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
41
+
42
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
43
+ if any(int(v) <= 0 for v in values):
44
+ raise ValueError("all shape values and num_sms must be positive")
45
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
46
+ raise ValueError(f"head_dim must be one of {SUPPORTED_HEAD_DIMS}")
47
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
48
+ n_blocks = _ceildiv(seqlen_k, block_n)
49
+ m_blocks = _ceildiv(seqlen_q, 64)
50
+ blocks = batch * heads_q * m_blocks
51
+ effective_sms = num_sms * 2
52
+ if blocks >= 0.8 * effective_sms:
53
+ return 1
54
+ max_splits = min(128, effective_sms, n_blocks)
55
+ efficiencies = [0.0] * (max_splits + 1)
56
+ best = 0.0
57
+ for split in range(1, max_splits + 1):
58
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
59
+ if not eligible:
60
+ continue
61
+ waves = blocks * split / effective_sms
62
+ efficiencies[split] = waves / math.ceil(waves)
63
+ best = max(best, efficiencies[split])
64
+ for split in range(1, max_splits + 1):
65
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
66
+ if eligible and efficiencies[split] >= 0.85 * best:
67
+ return split
68
+ return 1
69
+
70
+
71
+ def allocate_workspace(
72
+ q: torch.Tensor,
73
+ k: torch.Tensor,
74
+ *,
75
+ num_sms: Optional[int] = None,
76
+ ) -> Optional[FA2Workspace]:
77
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
78
+
79
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
80
+ once during runtime setup; never call this helper inside a captured loop.
81
+ """
82
+
83
+ if q.ndim != 4 or k.ndim != 4:
84
+ raise ValueError("q and k must have shape (B, S, H, D)")
85
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
86
+ return None
87
+ if num_sms is None:
88
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
89
+ splits = recommended_num_splits(
90
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
91
+ )
92
+ if splits == 1:
93
+ return None
94
+ lse = torch.empty(
95
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
96
+ device=q.device,
97
+ dtype=torch.float32,
98
+ )
99
+ out = torch.empty(
100
+ (splits, q.shape[0], q.shape[2], q.shape[1], q.shape[3]),
101
+ device=q.device,
102
+ dtype=torch.float32,
103
+ )
104
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
105
+
106
+
107
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
108
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
109
+
110
+ if q.ndim != 4:
111
+ raise ValueError("q must have shape (B, S, H, D)")
112
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
113
+ lse = torch.empty(
114
+ (q.shape[0], q.shape[2], q.shape[1]),
115
+ device=q.device,
116
+ dtype=torch.float32,
117
+ )
118
+ return out, lse
119
+
120
+
121
+ def _workspace_args(
122
+ workspace: Optional[FA2Workspace],
123
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
124
+ if workspace is None:
125
+ return None, None, 0
126
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
127
+
128
+
129
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
130
+ def _forward_static_fake(
131
+ q: torch.Tensor,
132
+ k: torch.Tensor,
133
+ v: torch.Tensor,
134
+ out: torch.Tensor,
135
+ softmax_lse: torch.Tensor,
136
+ softmax_lse_accum: Optional[torch.Tensor],
137
+ out_accum: Optional[torch.Tensor],
138
+ softmax_scale: float,
139
+ causal: bool = False,
140
+ num_sms: int = 0,
141
+ ) -> None:
142
+ del k, v, softmax_scale, causal, num_sms
143
+ if q.ndim != 4 or out.shape != q.shape:
144
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
145
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
146
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
147
+ if (softmax_lse_accum is None) != (out_accum is None):
148
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
149
+ return None
150
+
151
+
152
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
153
+ def _forward_seqused_static_fake(
154
+ q: torch.Tensor,
155
+ k: torch.Tensor,
156
+ v: torch.Tensor,
157
+ seqused_k: torch.Tensor,
158
+ out: torch.Tensor,
159
+ softmax_lse: torch.Tensor,
160
+ softmax_lse_accum: Optional[torch.Tensor],
161
+ out_accum: Optional[torch.Tensor],
162
+ softmax_scale: float,
163
+ num_sms: int = 0,
164
+ ) -> None:
165
+ del seqused_k
166
+ return _forward_static_fake(
167
+ q,
168
+ k,
169
+ v,
170
+ out,
171
+ softmax_lse,
172
+ softmax_lse_accum,
173
+ out_accum,
174
+ softmax_scale,
175
+ False,
176
+ num_sms,
177
+ )
178
+
179
+
180
+ def forward_static(
181
+ q: torch.Tensor,
182
+ k: torch.Tensor,
183
+ v: torch.Tensor,
184
+ *,
185
+ out: torch.Tensor,
186
+ softmax_lse: torch.Tensor,
187
+ workspace: Optional[FA2Workspace] = None,
188
+ softmax_scale: Optional[float] = None,
189
+ causal: bool = False,
190
+ ) -> torch.Tensor:
191
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
192
+
193
+ if softmax_scale is None:
194
+ softmax_scale = q.shape[-1] ** -0.5
195
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
196
+ ops.forward_static(
197
+ q,
198
+ k,
199
+ v,
200
+ out,
201
+ softmax_lse,
202
+ lse_accum,
203
+ out_accum,
204
+ float(softmax_scale),
205
+ bool(causal),
206
+ int(num_sms),
207
+ )
208
+ return out
209
+
210
+
211
+ def forward_seqused_static(
212
+ q: torch.Tensor,
213
+ k: torch.Tensor,
214
+ v: torch.Tensor,
215
+ seqused_k: torch.Tensor,
216
+ *,
217
+ out: torch.Tensor,
218
+ softmax_lse: torch.Tensor,
219
+ workspace: Optional[FA2Workspace] = None,
220
+ softmax_scale: Optional[float] = None,
221
+ ) -> torch.Tensor:
222
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
223
+
224
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
225
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
226
+ reset is captured together with the kernel by CUDA Graphs.
227
+ """
228
+
229
+ if softmax_scale is None:
230
+ softmax_scale = q.shape[-1] ** -0.5
231
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
232
+ if lse_accum is not None:
233
+ lse_accum.fill_(-torch.inf)
234
+ ops.forward_seqused_static(
235
+ q,
236
+ k,
237
+ v,
238
+ seqused_k,
239
+ out,
240
+ softmax_lse,
241
+ lse_accum,
242
+ out_accum,
243
+ float(softmax_scale),
244
+ int(num_sms),
245
+ )
246
+ return out
247
+
248
+
249
+ def forward(
250
+ q: torch.Tensor,
251
+ k: torch.Tensor,
252
+ v: torch.Tensor,
253
+ *,
254
+ softmax_scale: Optional[float] = None,
255
+ causal: bool = False,
256
+ use_split_kv: bool = True,
257
+ ) -> torch.Tensor:
258
+ """Convenience API that allocates outputs and optional split-KV workspace."""
259
+
260
+ out, lse = allocate_outputs(q)
261
+ workspace = allocate_workspace(q, k) if use_split_kv else None
262
+ return forward_static(
263
+ q,
264
+ k,
265
+ v,
266
+ out=out,
267
+ softmax_lse=lse,
268
+ workspace=workspace,
269
+ softmax_scale=softmax_scale,
270
+ causal=causal,
271
+ )
272
+
273
+
274
+ __all__ = [
275
+ "FA2Workspace",
276
+ "SPLIT_HEAD_DIMS",
277
+ "SUPPORTED_HEAD_DIMS",
278
+ "allocate_outputs",
279
+ "allocate_workspace",
280
+ "forward",
281
+ "forward_seqused_static",
282
+ "forward_static",
283
+ "recommended_num_splits",
284
+ ]
build/torch211-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_14f2290.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:66afc0c0de8bbe37a508a5ba37e429554b2bfd49a94c417c28aea4cc987cfc38
3
+ size 616325528
build/torch211-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_14f2290
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_14f2290
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_14f2290::{op_name}"
build/torch211-cxx11-cu130-x86_64-linux/fa2_seqused_runtime/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_14f2290",
4
+ "version": 1,
5
+ "license": "BSD-3-Clause",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "12.1",
13
+ "8.0",
14
+ "8.6",
15
+ "8.9",
16
+ "9.0"
17
+ ]
18
+ },
19
+ "digest": {
20
+ "algorithm": "sha256",
21
+ "files": {
22
+ "__init__.py": "ck4+/aHijqRRXtVYRzkkIj7bHwvMxxOEnRIKVQSkd6k=",
23
+ "_fa2_seqused_runtime_cuda_14f2290.abi3.so": "Zq/AwN6LvjelCKW6N+QpVUsr/UmpTEF8KK6kzJh8/Dg=",
24
+ "_ops.py": "S1LORAtc4O5qo9aO4HWImXiI6QrfLU+l0PF9N/g+k88=",
25
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
26
+ }
27
+ }
28
+ }
build/torch212-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Static-buffer FlashAttention-2 runtime operators from FlashRT."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import math
7
+ from typing import Optional
8
+
9
+ import torch
10
+
11
+ from ._ops import add_op_namespace_prefix, ops
12
+
13
+
14
+ SUPPORTED_HEAD_DIMS = (64, 96, 128, 256)
15
+ SPLIT_HEAD_DIMS = (96, 128, 256)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class FA2Workspace:
20
+ """Preallocated split-KV workspace for one static attention shape."""
21
+
22
+ softmax_lse_accum: torch.Tensor
23
+ out_accum: torch.Tensor
24
+ num_sms: int
25
+ num_splits: int
26
+
27
+
28
+ def _ceildiv(a: int, b: int) -> int:
29
+ return (a + b - 1) // b
30
+
31
+
32
+ def recommended_num_splits(
33
+ batch: int,
34
+ seqlen_q: int,
35
+ seqlen_k: int,
36
+ heads_q: int,
37
+ head_dim: int,
38
+ num_sms: int,
39
+ ) -> int:
40
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
41
+
42
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
43
+ if any(int(v) <= 0 for v in values):
44
+ raise ValueError("all shape values and num_sms must be positive")
45
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
46
+ raise ValueError(f"head_dim must be one of {SUPPORTED_HEAD_DIMS}")
47
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
48
+ n_blocks = _ceildiv(seqlen_k, block_n)
49
+ m_blocks = _ceildiv(seqlen_q, 64)
50
+ blocks = batch * heads_q * m_blocks
51
+ effective_sms = num_sms * 2
52
+ if blocks >= 0.8 * effective_sms:
53
+ return 1
54
+ max_splits = min(128, effective_sms, n_blocks)
55
+ efficiencies = [0.0] * (max_splits + 1)
56
+ best = 0.0
57
+ for split in range(1, max_splits + 1):
58
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
59
+ if not eligible:
60
+ continue
61
+ waves = blocks * split / effective_sms
62
+ efficiencies[split] = waves / math.ceil(waves)
63
+ best = max(best, efficiencies[split])
64
+ for split in range(1, max_splits + 1):
65
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
66
+ if eligible and efficiencies[split] >= 0.85 * best:
67
+ return split
68
+ return 1
69
+
70
+
71
+ def allocate_workspace(
72
+ q: torch.Tensor,
73
+ k: torch.Tensor,
74
+ *,
75
+ num_sms: Optional[int] = None,
76
+ ) -> Optional[FA2Workspace]:
77
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
78
+
79
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
80
+ once during runtime setup; never call this helper inside a captured loop.
81
+ """
82
+
83
+ if q.ndim != 4 or k.ndim != 4:
84
+ raise ValueError("q and k must have shape (B, S, H, D)")
85
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
86
+ return None
87
+ if num_sms is None:
88
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
89
+ splits = recommended_num_splits(
90
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
91
+ )
92
+ if splits == 1:
93
+ return None
94
+ lse = torch.empty(
95
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
96
+ device=q.device,
97
+ dtype=torch.float32,
98
+ )
99
+ out = torch.empty(
100
+ (splits, q.shape[0], q.shape[2], q.shape[1], q.shape[3]),
101
+ device=q.device,
102
+ dtype=torch.float32,
103
+ )
104
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
105
+
106
+
107
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
108
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
109
+
110
+ if q.ndim != 4:
111
+ raise ValueError("q must have shape (B, S, H, D)")
112
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
113
+ lse = torch.empty(
114
+ (q.shape[0], q.shape[2], q.shape[1]),
115
+ device=q.device,
116
+ dtype=torch.float32,
117
+ )
118
+ return out, lse
119
+
120
+
121
+ def _workspace_args(
122
+ workspace: Optional[FA2Workspace],
123
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
124
+ if workspace is None:
125
+ return None, None, 0
126
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
127
+
128
+
129
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
130
+ def _forward_static_fake(
131
+ q: torch.Tensor,
132
+ k: torch.Tensor,
133
+ v: torch.Tensor,
134
+ out: torch.Tensor,
135
+ softmax_lse: torch.Tensor,
136
+ softmax_lse_accum: Optional[torch.Tensor],
137
+ out_accum: Optional[torch.Tensor],
138
+ softmax_scale: float,
139
+ causal: bool = False,
140
+ num_sms: int = 0,
141
+ ) -> None:
142
+ del k, v, softmax_scale, causal, num_sms
143
+ if q.ndim != 4 or out.shape != q.shape:
144
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
145
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
146
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
147
+ if (softmax_lse_accum is None) != (out_accum is None):
148
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
149
+ return None
150
+
151
+
152
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
153
+ def _forward_seqused_static_fake(
154
+ q: torch.Tensor,
155
+ k: torch.Tensor,
156
+ v: torch.Tensor,
157
+ seqused_k: torch.Tensor,
158
+ out: torch.Tensor,
159
+ softmax_lse: torch.Tensor,
160
+ softmax_lse_accum: Optional[torch.Tensor],
161
+ out_accum: Optional[torch.Tensor],
162
+ softmax_scale: float,
163
+ num_sms: int = 0,
164
+ ) -> None:
165
+ del seqused_k
166
+ return _forward_static_fake(
167
+ q,
168
+ k,
169
+ v,
170
+ out,
171
+ softmax_lse,
172
+ softmax_lse_accum,
173
+ out_accum,
174
+ softmax_scale,
175
+ False,
176
+ num_sms,
177
+ )
178
+
179
+
180
+ def forward_static(
181
+ q: torch.Tensor,
182
+ k: torch.Tensor,
183
+ v: torch.Tensor,
184
+ *,
185
+ out: torch.Tensor,
186
+ softmax_lse: torch.Tensor,
187
+ workspace: Optional[FA2Workspace] = None,
188
+ softmax_scale: Optional[float] = None,
189
+ causal: bool = False,
190
+ ) -> torch.Tensor:
191
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
192
+
193
+ if softmax_scale is None:
194
+ softmax_scale = q.shape[-1] ** -0.5
195
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
196
+ ops.forward_static(
197
+ q,
198
+ k,
199
+ v,
200
+ out,
201
+ softmax_lse,
202
+ lse_accum,
203
+ out_accum,
204
+ float(softmax_scale),
205
+ bool(causal),
206
+ int(num_sms),
207
+ )
208
+ return out
209
+
210
+
211
+ def forward_seqused_static(
212
+ q: torch.Tensor,
213
+ k: torch.Tensor,
214
+ v: torch.Tensor,
215
+ seqused_k: torch.Tensor,
216
+ *,
217
+ out: torch.Tensor,
218
+ softmax_lse: torch.Tensor,
219
+ workspace: Optional[FA2Workspace] = None,
220
+ softmax_scale: Optional[float] = None,
221
+ ) -> torch.Tensor:
222
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
223
+
224
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
225
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
226
+ reset is captured together with the kernel by CUDA Graphs.
227
+ """
228
+
229
+ if softmax_scale is None:
230
+ softmax_scale = q.shape[-1] ** -0.5
231
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
232
+ if lse_accum is not None:
233
+ lse_accum.fill_(-torch.inf)
234
+ ops.forward_seqused_static(
235
+ q,
236
+ k,
237
+ v,
238
+ seqused_k,
239
+ out,
240
+ softmax_lse,
241
+ lse_accum,
242
+ out_accum,
243
+ float(softmax_scale),
244
+ int(num_sms),
245
+ )
246
+ return out
247
+
248
+
249
+ def forward(
250
+ q: torch.Tensor,
251
+ k: torch.Tensor,
252
+ v: torch.Tensor,
253
+ *,
254
+ softmax_scale: Optional[float] = None,
255
+ causal: bool = False,
256
+ use_split_kv: bool = True,
257
+ ) -> torch.Tensor:
258
+ """Convenience API that allocates outputs and optional split-KV workspace."""
259
+
260
+ out, lse = allocate_outputs(q)
261
+ workspace = allocate_workspace(q, k) if use_split_kv else None
262
+ return forward_static(
263
+ q,
264
+ k,
265
+ v,
266
+ out=out,
267
+ softmax_lse=lse,
268
+ workspace=workspace,
269
+ softmax_scale=softmax_scale,
270
+ causal=causal,
271
+ )
272
+
273
+
274
+ __all__ = [
275
+ "FA2Workspace",
276
+ "SPLIT_HEAD_DIMS",
277
+ "SUPPORTED_HEAD_DIMS",
278
+ "allocate_outputs",
279
+ "allocate_workspace",
280
+ "forward",
281
+ "forward_seqused_static",
282
+ "forward_static",
283
+ "recommended_num_splits",
284
+ ]
build/torch212-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_14f2290.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a2d6d38c6d0b929f55d3d6d42bcf69f2cbfa03438a33a7e7801825ccf027da0a
3
+ size 616331520
build/torch212-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_14f2290
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_14f2290
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_14f2290::{op_name}"
build/torch212-cxx11-cu130-x86_64-linux/fa2_seqused_runtime/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_14f2290",
4
+ "version": 1,
5
+ "license": "BSD-3-Clause",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "12.1",
13
+ "8.0",
14
+ "8.6",
15
+ "8.9",
16
+ "9.0"
17
+ ]
18
+ },
19
+ "digest": {
20
+ "algorithm": "sha256",
21
+ "files": {
22
+ "__init__.py": "ck4+/aHijqRRXtVYRzkkIj7bHwvMxxOEnRIKVQSkd6k=",
23
+ "_fa2_seqused_runtime_cuda_14f2290.abi3.so": "otbTjG0Lkp9V09bUK89p8sv6A0OKM6fngBglzPAn2go=",
24
+ "_ops.py": "S1LORAtc4O5qo9aO4HWImXiI6QrfLU+l0PF9N/g+k88=",
25
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
26
+ }
27
+ }
28
+ }
build/torch212-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Static-buffer FlashAttention-2 runtime operators from FlashRT."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import math
7
+ from typing import Optional
8
+
9
+ import torch
10
+
11
+ from ._ops import add_op_namespace_prefix, ops
12
+
13
+
14
+ SUPPORTED_HEAD_DIMS = (64, 96, 128, 256)
15
+ SPLIT_HEAD_DIMS = (96, 128, 256)
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class FA2Workspace:
20
+ """Preallocated split-KV workspace for one static attention shape."""
21
+
22
+ softmax_lse_accum: torch.Tensor
23
+ out_accum: torch.Tensor
24
+ num_sms: int
25
+ num_splits: int
26
+
27
+
28
+ def _ceildiv(a: int, b: int) -> int:
29
+ return (a + b - 1) // b
30
+
31
+
32
+ def recommended_num_splits(
33
+ batch: int,
34
+ seqlen_q: int,
35
+ seqlen_k: int,
36
+ heads_q: int,
37
+ head_dim: int,
38
+ num_sms: int,
39
+ ) -> int:
40
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
41
+
42
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
43
+ if any(int(v) <= 0 for v in values):
44
+ raise ValueError("all shape values and num_sms must be positive")
45
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
46
+ raise ValueError(f"head_dim must be one of {SUPPORTED_HEAD_DIMS}")
47
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
48
+ n_blocks = _ceildiv(seqlen_k, block_n)
49
+ m_blocks = _ceildiv(seqlen_q, 64)
50
+ blocks = batch * heads_q * m_blocks
51
+ effective_sms = num_sms * 2
52
+ if blocks >= 0.8 * effective_sms:
53
+ return 1
54
+ max_splits = min(128, effective_sms, n_blocks)
55
+ efficiencies = [0.0] * (max_splits + 1)
56
+ best = 0.0
57
+ for split in range(1, max_splits + 1):
58
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
59
+ if not eligible:
60
+ continue
61
+ waves = blocks * split / effective_sms
62
+ efficiencies[split] = waves / math.ceil(waves)
63
+ best = max(best, efficiencies[split])
64
+ for split in range(1, max_splits + 1):
65
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
66
+ if eligible and efficiencies[split] >= 0.85 * best:
67
+ return split
68
+ return 1
69
+
70
+
71
+ def allocate_workspace(
72
+ q: torch.Tensor,
73
+ k: torch.Tensor,
74
+ *,
75
+ num_sms: Optional[int] = None,
76
+ ) -> Optional[FA2Workspace]:
77
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
78
+
79
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
80
+ once during runtime setup; never call this helper inside a captured loop.
81
+ """
82
+
83
+ if q.ndim != 4 or k.ndim != 4:
84
+ raise ValueError("q and k must have shape (B, S, H, D)")
85
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
86
+ return None
87
+ if num_sms is None:
88
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
89
+ splits = recommended_num_splits(
90
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
91
+ )
92
+ if splits == 1:
93
+ return None
94
+ lse = torch.empty(
95
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
96
+ device=q.device,
97
+ dtype=torch.float32,
98
+ )
99
+ out = torch.empty(
100
+ (splits, q.shape[0], q.shape[2], q.shape[1], q.shape[3]),
101
+ device=q.device,
102
+ dtype=torch.float32,
103
+ )
104
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
105
+
106
+
107
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
108
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
109
+
110
+ if q.ndim != 4:
111
+ raise ValueError("q must have shape (B, S, H, D)")
112
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
113
+ lse = torch.empty(
114
+ (q.shape[0], q.shape[2], q.shape[1]),
115
+ device=q.device,
116
+ dtype=torch.float32,
117
+ )
118
+ return out, lse
119
+
120
+
121
+ def _workspace_args(
122
+ workspace: Optional[FA2Workspace],
123
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
124
+ if workspace is None:
125
+ return None, None, 0
126
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
127
+
128
+
129
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
130
+ def _forward_static_fake(
131
+ q: torch.Tensor,
132
+ k: torch.Tensor,
133
+ v: torch.Tensor,
134
+ out: torch.Tensor,
135
+ softmax_lse: torch.Tensor,
136
+ softmax_lse_accum: Optional[torch.Tensor],
137
+ out_accum: Optional[torch.Tensor],
138
+ softmax_scale: float,
139
+ causal: bool = False,
140
+ num_sms: int = 0,
141
+ ) -> None:
142
+ del k, v, softmax_scale, causal, num_sms
143
+ if q.ndim != 4 or out.shape != q.shape:
144
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
145
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
146
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
147
+ if (softmax_lse_accum is None) != (out_accum is None):
148
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
149
+ return None
150
+
151
+
152
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
153
+ def _forward_seqused_static_fake(
154
+ q: torch.Tensor,
155
+ k: torch.Tensor,
156
+ v: torch.Tensor,
157
+ seqused_k: torch.Tensor,
158
+ out: torch.Tensor,
159
+ softmax_lse: torch.Tensor,
160
+ softmax_lse_accum: Optional[torch.Tensor],
161
+ out_accum: Optional[torch.Tensor],
162
+ softmax_scale: float,
163
+ num_sms: int = 0,
164
+ ) -> None:
165
+ del seqused_k
166
+ return _forward_static_fake(
167
+ q,
168
+ k,
169
+ v,
170
+ out,
171
+ softmax_lse,
172
+ softmax_lse_accum,
173
+ out_accum,
174
+ softmax_scale,
175
+ False,
176
+ num_sms,
177
+ )
178
+
179
+
180
+ def forward_static(
181
+ q: torch.Tensor,
182
+ k: torch.Tensor,
183
+ v: torch.Tensor,
184
+ *,
185
+ out: torch.Tensor,
186
+ softmax_lse: torch.Tensor,
187
+ workspace: Optional[FA2Workspace] = None,
188
+ softmax_scale: Optional[float] = None,
189
+ causal: bool = False,
190
+ ) -> torch.Tensor:
191
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
192
+
193
+ if softmax_scale is None:
194
+ softmax_scale = q.shape[-1] ** -0.5
195
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
196
+ ops.forward_static(
197
+ q,
198
+ k,
199
+ v,
200
+ out,
201
+ softmax_lse,
202
+ lse_accum,
203
+ out_accum,
204
+ float(softmax_scale),
205
+ bool(causal),
206
+ int(num_sms),
207
+ )
208
+ return out
209
+
210
+
211
+ def forward_seqused_static(
212
+ q: torch.Tensor,
213
+ k: torch.Tensor,
214
+ v: torch.Tensor,
215
+ seqused_k: torch.Tensor,
216
+ *,
217
+ out: torch.Tensor,
218
+ softmax_lse: torch.Tensor,
219
+ workspace: Optional[FA2Workspace] = None,
220
+ softmax_scale: Optional[float] = None,
221
+ ) -> torch.Tensor:
222
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
223
+
224
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
225
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
226
+ reset is captured together with the kernel by CUDA Graphs.
227
+ """
228
+
229
+ if softmax_scale is None:
230
+ softmax_scale = q.shape[-1] ** -0.5
231
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
232
+ if lse_accum is not None:
233
+ lse_accum.fill_(-torch.inf)
234
+ ops.forward_seqused_static(
235
+ q,
236
+ k,
237
+ v,
238
+ seqused_k,
239
+ out,
240
+ softmax_lse,
241
+ lse_accum,
242
+ out_accum,
243
+ float(softmax_scale),
244
+ int(num_sms),
245
+ )
246
+ return out
247
+
248
+
249
+ def forward(
250
+ q: torch.Tensor,
251
+ k: torch.Tensor,
252
+ v: torch.Tensor,
253
+ *,
254
+ softmax_scale: Optional[float] = None,
255
+ causal: bool = False,
256
+ use_split_kv: bool = True,
257
+ ) -> torch.Tensor:
258
+ """Convenience API that allocates outputs and optional split-KV workspace."""
259
+
260
+ out, lse = allocate_outputs(q)
261
+ workspace = allocate_workspace(q, k) if use_split_kv else None
262
+ return forward_static(
263
+ q,
264
+ k,
265
+ v,
266
+ out=out,
267
+ softmax_lse=lse,
268
+ workspace=workspace,
269
+ softmax_scale=softmax_scale,
270
+ causal=causal,
271
+ )
272
+
273
+
274
+ __all__ = [
275
+ "FA2Workspace",
276
+ "SPLIT_HEAD_DIMS",
277
+ "SUPPORTED_HEAD_DIMS",
278
+ "allocate_outputs",
279
+ "allocate_workspace",
280
+ "forward",
281
+ "forward_seqused_static",
282
+ "forward_static",
283
+ "recommended_num_splits",
284
+ ]
build/torch212-cxx11-cu132-x86_64-linux/_fa2_seqused_runtime_cuda_14f2290.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:81d0f3a0b730aea81498d495c349759895a9076f3d12579837f8a9c636a33282
3
+ size 616122672
build/torch212-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_14f2290
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_14f2290
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_14f2290::{op_name}"
build/torch212-cxx11-cu132-x86_64-linux/fa2_seqused_runtime/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_14f2290",
4
+ "version": 1,
5
+ "license": "BSD-3-Clause",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "12.1",
13
+ "8.0",
14
+ "8.6",
15
+ "8.9",
16
+ "9.0"
17
+ ]
18
+ },
19
+ "digest": {
20
+ "algorithm": "sha256",
21
+ "files": {
22
+ "__init__.py": "ck4+/aHijqRRXtVYRzkkIj7bHwvMxxOEnRIKVQSkd6k=",
23
+ "_fa2_seqused_runtime_cuda_14f2290.abi3.so": "gdDzoLcwrqgUmNSVw0l1mJWpB289EleYN/ipxjajMoI=",
24
+ "_ops.py": "S1LORAtc4O5qo9aO4HWImXiI6QrfLU+l0PF9N/g+k88=",
25
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
26
+ }
27
+ }
28
+ }