liangsu9988 commited on
Commit
3009f23
·
verified ·
1 Parent(s): 6ad992a

Promote latest kernel artifacts to main

Browse files
Files changed (32) hide show
  1. .gitattributes +3 -35
  2. README.md +0 -9
  3. benchmarks/benchmark.py +105 -0
  4. build/CARD.md +58 -0
  5. build/torch211-cxx11-cu128-x86_64-linux/__init__.py +293 -0
  6. build/torch211-cxx11-cu128-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so +3 -0
  7. build/torch211-cxx11-cu128-x86_64-linux/_ops.py +9 -0
  8. build/torch211-cxx11-cu128-x86_64-linux/fa2_seqused_runtime/__init__.py +26 -0
  9. build/torch211-cxx11-cu128-x86_64-linux/metadata.json +25 -0
  10. build/torch211-cxx11-cu130-x86_64-linux/__init__.py +293 -0
  11. build/torch211-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so +3 -0
  12. build/torch211-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  13. build/torch211-cxx11-cu130-x86_64-linux/fa2_seqused_runtime/__init__.py +26 -0
  14. build/torch211-cxx11-cu130-x86_64-linux/metadata.json +25 -0
  15. build/torch212-cxx11-cu130-x86_64-linux/__init__.py +293 -0
  16. build/torch212-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so +3 -0
  17. build/torch212-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  18. build/torch212-cxx11-cu130-x86_64-linux/fa2_seqused_runtime/__init__.py +26 -0
  19. build/torch212-cxx11-cu130-x86_64-linux/metadata.json +25 -0
  20. build/torch212-cxx11-cu132-x86_64-linux/__init__.py +293 -0
  21. build/torch212-cxx11-cu132-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so +3 -0
  22. build/torch212-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  23. build/torch212-cxx11-cu132-x86_64-linux/fa2_seqused_runtime/__init__.py +26 -0
  24. build/torch212-cxx11-cu132-x86_64-linux/metadata.json +25 -0
  25. build/torch213-cxx11-cu130-x86_64-linux/__init__.py +293 -0
  26. build/torch213-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_9ea2146.abi3.so +3 -0
  27. build/torch213-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  28. build/torch213-cxx11-cu130-x86_64-linux/metadata.json +35 -0
  29. build/torch213-cxx11-cu132-x86_64-linux/__init__.py +293 -0
  30. build/torch213-cxx11-cu132-x86_64-linux/_fa2_seqused_runtime_cuda_9ea2146.abi3.so +3 -0
  31. build/torch213-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  32. build/torch213-cxx11-cu132-x86_64-linux/metadata.json +35 -0
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.so filter=lfs diff=lfs merge=lfs -text
2
+ *.pyd filter=lfs diff=lfs merge=lfs -text
3
+ *.dylib filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md DELETED
@@ -1,9 +0,0 @@
1
- # flashrt/fa2-seqused-runtime
2
-
3
- This repository is a compatibility mirror for older `kernels` clients
4
- that resolve repositories through the default Hugging Face model repo API.
5
-
6
- Canonical Kernel Hub repo: https://huggingface.co/kernels/flashrt/fa2-seqused-runtime
7
-
8
- Do not edit this mirror by hand. It is generated from the Kernel Hub
9
- `vN` branches and contains the same `build/**` artifacts.
 
 
 
 
 
 
 
 
 
 
benchmarks/benchmark.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # GROOT DiT self/cross attention.
14
+ ("groot-dit-self", 1, 51, 51, 32, 32, 48, False),
15
+ ("groot-dit-cross", 1, 51, 1024, 32, 32, 48, False),
16
+ # GROOT N1.7 ViT/VL and SigLIP vision attention.
17
+ ("groot-n17-vit", 1, 256, 256, 16, 16, 64, False),
18
+ ("groot-siglip", 2, 256, 256, 16, 16, 72, False),
19
+ # Qwen2.5-VL/LingBot vision attention.
20
+ ("vl-vision", 1, 256, 256, 16, 16, 80, False),
21
+ # Generic runtime/GQA rows.
22
+ ("gqa-decode", 1, 1, 512, 8, 2, 128, False),
23
+ ("gqa-short", 1, 16, 1024, 16, 4, 128, False),
24
+ ("vla-gqa", 1, 49, 2520, 24, 4, 128, False),
25
+ ("gqa-long-kv", 1, 64, 4096, 32, 8, 128, False),
26
+ ("qwen-causal", 1, 1024, 1024, 32, 8, 128, True),
27
+ ("qwen36-causal", 1, 512, 512, 24, 4, 256, True),
28
+ ]
29
+
30
+
31
+ def time_us(fn, warmup=50, repeats=200):
32
+ for _ in range(warmup):
33
+ fn()
34
+ torch.cuda.synchronize()
35
+ samples = []
36
+ for _ in range(repeats):
37
+ start = torch.cuda.Event(enable_timing=True)
38
+ end = torch.cuda.Event(enable_timing=True)
39
+ start.record()
40
+ fn()
41
+ end.record()
42
+ end.synchronize()
43
+ samples.append(start.elapsed_time(end) * 1000.0)
44
+ return statistics.median(samples)
45
+
46
+
47
+ def main():
48
+ parser = argparse.ArgumentParser()
49
+ parser.add_argument("--dtype", choices=("bf16", "fp16"), default="bf16")
50
+ args = parser.parse_args()
51
+ dtype = torch.bfloat16 if args.dtype == "bf16" else torch.float16
52
+ print(
53
+ "Workload,Mode,B,Sq,Sk,Hq,Hkv,D,FlashRT_us,SDPA_expandedGQA_us,Speedup,"
54
+ "MaxAbs,P99Abs,MeanAbs,Cosine"
55
+ )
56
+ for name, batch, sq, sk, hq, hkv, dim, causal in SHAPES:
57
+ if causal and dtype == torch.float16:
58
+ continue
59
+ q = torch.randn(batch, sq, hq, dim, device="cuda", dtype=dtype)
60
+ k = torch.randn(batch, sk, hkv, dim, device="cuda", dtype=dtype)
61
+ v = torch.randn_like(k)
62
+ out, lse = allocate_outputs(q)
63
+ workspace = allocate_workspace(q, k)
64
+ kr = k.repeat_interleave(hq // hkv, dim=2)
65
+ vr = v.repeat_interleave(hq // hkv, dim=2)
66
+
67
+ def flashrt():
68
+ forward_static(
69
+ q,
70
+ k,
71
+ v,
72
+ out=out,
73
+ softmax_lse=lse,
74
+ workspace=workspace,
75
+ causal=causal,
76
+ )
77
+
78
+ def sdpa():
79
+ return F.scaled_dot_product_attention(
80
+ q.permute(0, 2, 1, 3),
81
+ kr.permute(0, 2, 1, 3),
82
+ vr.permute(0, 2, 1, 3),
83
+ is_causal=causal,
84
+ )
85
+
86
+ flashrt_us = time_us(flashrt)
87
+ sdpa_us = time_us(sdpa)
88
+ actual = out.float()
89
+ reference = sdpa().permute(0, 2, 1, 3).float()
90
+ error = (actual - reference).abs()
91
+ cosine = torch.nn.functional.cosine_similarity(
92
+ actual.flatten(), reference.flatten(), dim=0
93
+ ).item()
94
+ print(
95
+ f"{name},{'causal' if causal else 'noncausal'},"
96
+ f"{batch},{sq},{sk},{hq},{hkv},{dim},"
97
+ f"{flashrt_us:.3f},{sdpa_us:.3f},{sdpa_us / flashrt_us:.3f},"
98
+ f"{error.max().item():.9f},"
99
+ f"{torch.quantile(error, 0.99).item():.9f},"
100
+ f"{error.mean().item():.9f},{cosine:.10f}"
101
+ )
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()
build/CARD.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags:
3
+ - kernel
4
+ - cuda
5
+ - flash-attention
6
+ - inference
7
+ - cuda-graphs
8
+ library_name: kernels
9
+ ---
10
+
11
+ # FA2 Seqused Runtime
12
+
13
+ Allocation-free FlashAttention-2 forward operators for CUDA Graph inference.
14
+ The distinguishing feature is a device-resident per-batch `seqused_k`, allowing
15
+ one captured graph to serve changing valid K/V lengths without a host scalar
16
+ read or graph recapture.
17
+
18
+ ## Available functions
19
+
20
+ - `forward(q, k, v, *, softmax_scale=None, causal=False, use_split_kv=True)`
21
+ - `forward_static(q, k, v, *, out, softmax_lse, workspace=None, softmax_scale=None, causal=False)`
22
+ - `forward_seqused_static(q, k, v, seqused_k, *, out, softmax_lse, workspace=None, softmax_scale=None)`
23
+ - `allocate_outputs(q)`
24
+ - `allocate_workspace(q, k, *, num_sms=None)`
25
+ - `recommended_num_splits(batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)`
26
+ - `FA2Workspace`
27
+
28
+ ## Example
29
+
30
+ ```python
31
+ import torch
32
+ from kernels import get_kernel
33
+
34
+ fa2 = get_kernel("flashrt/fa2-seqused-runtime", version=1)
35
+ q = torch.randn(1, 16, 16, 128, device="cuda", dtype=torch.bfloat16)
36
+ k = torch.randn(1, 2048, 4, 128, device="cuda", dtype=torch.bfloat16)
37
+ v = torch.randn_like(k)
38
+ used = torch.tensor([1536], device="cuda", dtype=torch.int32)
39
+ out, lse = fa2.allocate_outputs(q)
40
+ workspace = fa2.allocate_workspace(q, k)
41
+
42
+ fa2.forward_seqused_static(
43
+ q, k, v, used, out=out, softmax_lse=lse, workspace=workspace
44
+ )
45
+ ```
46
+
47
+ The split-KV LSE reset is issued on the current stream and is captured with the
48
+ kernel. Updating `used` on device before replay changes the valid K/V length.
49
+
50
+ Causal calls use FlashAttention's bottom-right-aligned mask when query and KV
51
+ lengths differ. This is the chunked-prefill/verify convention, not PyTorch
52
+ SDPA's top-left `is_causal=True` convention for rectangular inputs.
53
+
54
+ ## Scope
55
+
56
+ This package is forward-only and runtime-oriented. It intentionally does not
57
+ duplicate the complete training, varlen, backward, paged-cache, or dropout API
58
+ of `kernels-community/flash-attn2`.
build/torch211-cxx11-cu128-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = tuple(range(8, 257, 8))
15
+ COMPILED_HEAD_DIM_BUCKETS = (64, 96, 128, 256)
16
+ # Vendored FA2 split-KV partial-head tiles are valid only in these logical
17
+ # ranges. Other supported dimensions use the correct no-split kernel.
18
+ SPLIT_HEAD_DIMS = tuple(range(40, 129, 8)) + tuple(range(232, 257, 8))
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FA2Workspace:
23
+ """Preallocated split-KV workspace for one static attention shape."""
24
+
25
+ softmax_lse_accum: torch.Tensor
26
+ out_accum: torch.Tensor
27
+ num_sms: int
28
+ num_splits: int
29
+
30
+
31
+ def _ceildiv(a: int, b: int) -> int:
32
+ return (a + b - 1) // b
33
+
34
+
35
+ def recommended_num_splits(
36
+ batch: int,
37
+ seqlen_q: int,
38
+ seqlen_k: int,
39
+ heads_q: int,
40
+ head_dim: int,
41
+ num_sms: int,
42
+ ) -> int:
43
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
44
+
45
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
46
+ if any(int(v) <= 0 for v in values):
47
+ raise ValueError("all shape values and num_sms must be positive")
48
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
49
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
50
+ if int(head_dim) not in SPLIT_HEAD_DIMS:
51
+ return 1
52
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
53
+ n_blocks = _ceildiv(seqlen_k, block_n)
54
+ m_blocks = _ceildiv(seqlen_q, 64)
55
+ blocks = batch * heads_q * m_blocks
56
+ effective_sms = num_sms * 2
57
+ if blocks >= 0.8 * effective_sms:
58
+ return 1
59
+ max_splits = min(128, effective_sms, n_blocks)
60
+ efficiencies = [0.0] * (max_splits + 1)
61
+ best = 0.0
62
+ for split in range(1, max_splits + 1):
63
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
64
+ if not eligible:
65
+ continue
66
+ waves = blocks * split / effective_sms
67
+ efficiencies[split] = waves / math.ceil(waves)
68
+ best = max(best, efficiencies[split])
69
+ for split in range(1, max_splits + 1):
70
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
71
+ if eligible and efficiencies[split] >= 0.85 * best:
72
+ return split
73
+ return 1
74
+
75
+
76
+ def allocate_workspace(
77
+ q: torch.Tensor,
78
+ k: torch.Tensor,
79
+ *,
80
+ num_sms: Optional[int] = None,
81
+ ) -> Optional[FA2Workspace]:
82
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
83
+
84
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
85
+ once during runtime setup; never call this helper inside a captured loop.
86
+ """
87
+
88
+ if q.ndim != 4 or k.ndim != 4:
89
+ raise ValueError("q and k must have shape (B, S, H, D)")
90
+ if q.shape[-1] not in SUPPORTED_HEAD_DIMS:
91
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
92
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
93
+ return None
94
+ if num_sms is None:
95
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
96
+ splits = recommended_num_splits(
97
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
98
+ )
99
+ if splits == 1:
100
+ return None
101
+ lse = torch.empty(
102
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
103
+ device=q.device,
104
+ dtype=torch.float32,
105
+ )
106
+ d_rounded = (q.shape[3] + 31) & ~31
107
+ out = torch.empty(
108
+ (splits, q.shape[0], q.shape[2], q.shape[1], d_rounded),
109
+ device=q.device,
110
+ dtype=torch.float32,
111
+ )
112
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
113
+
114
+
115
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
116
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
117
+
118
+ if q.ndim != 4:
119
+ raise ValueError("q must have shape (B, S, H, D)")
120
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
121
+ lse = torch.empty(
122
+ (q.shape[0], q.shape[2], q.shape[1]),
123
+ device=q.device,
124
+ dtype=torch.float32,
125
+ )
126
+ return out, lse
127
+
128
+
129
+ def _workspace_args(
130
+ workspace: Optional[FA2Workspace],
131
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
132
+ if workspace is None:
133
+ return None, None, 0
134
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
135
+
136
+
137
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
138
+ def _forward_static_fake(
139
+ q: torch.Tensor,
140
+ k: torch.Tensor,
141
+ v: torch.Tensor,
142
+ out: torch.Tensor,
143
+ softmax_lse: torch.Tensor,
144
+ softmax_lse_accum: Optional[torch.Tensor],
145
+ out_accum: Optional[torch.Tensor],
146
+ softmax_scale: float,
147
+ causal: bool = False,
148
+ num_sms: int = 0,
149
+ ) -> None:
150
+ del k, v, softmax_scale, causal, num_sms
151
+ if q.ndim != 4 or out.shape != q.shape:
152
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
153
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
154
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
155
+ if (softmax_lse_accum is None) != (out_accum is None):
156
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
157
+ return None
158
+
159
+
160
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
161
+ def _forward_seqused_static_fake(
162
+ q: torch.Tensor,
163
+ k: torch.Tensor,
164
+ v: torch.Tensor,
165
+ seqused_k: torch.Tensor,
166
+ out: torch.Tensor,
167
+ softmax_lse: torch.Tensor,
168
+ softmax_lse_accum: Optional[torch.Tensor],
169
+ out_accum: Optional[torch.Tensor],
170
+ softmax_scale: float,
171
+ num_sms: int = 0,
172
+ ) -> None:
173
+ del seqused_k
174
+ return _forward_static_fake(
175
+ q,
176
+ k,
177
+ v,
178
+ out,
179
+ softmax_lse,
180
+ softmax_lse_accum,
181
+ out_accum,
182
+ softmax_scale,
183
+ False,
184
+ num_sms,
185
+ )
186
+
187
+
188
+ def forward_static(
189
+ q: torch.Tensor,
190
+ k: torch.Tensor,
191
+ v: torch.Tensor,
192
+ *,
193
+ out: torch.Tensor,
194
+ softmax_lse: torch.Tensor,
195
+ workspace: Optional[FA2Workspace] = None,
196
+ softmax_scale: Optional[float] = None,
197
+ causal: bool = False,
198
+ ) -> torch.Tensor:
199
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
200
+
201
+ if softmax_scale is None:
202
+ softmax_scale = q.shape[-1] ** -0.5
203
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
204
+ ops.forward_static(
205
+ q,
206
+ k,
207
+ v,
208
+ out,
209
+ softmax_lse,
210
+ lse_accum,
211
+ out_accum,
212
+ float(softmax_scale),
213
+ bool(causal),
214
+ int(num_sms),
215
+ )
216
+ return out
217
+
218
+
219
+ def forward_seqused_static(
220
+ q: torch.Tensor,
221
+ k: torch.Tensor,
222
+ v: torch.Tensor,
223
+ seqused_k: torch.Tensor,
224
+ *,
225
+ out: torch.Tensor,
226
+ softmax_lse: torch.Tensor,
227
+ workspace: Optional[FA2Workspace] = None,
228
+ softmax_scale: Optional[float] = None,
229
+ ) -> torch.Tensor:
230
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
231
+
232
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
233
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
234
+ reset is captured together with the kernel by CUDA Graphs.
235
+ """
236
+
237
+ if softmax_scale is None:
238
+ softmax_scale = q.shape[-1] ** -0.5
239
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
240
+ if lse_accum is not None:
241
+ lse_accum.fill_(-torch.inf)
242
+ ops.forward_seqused_static(
243
+ q,
244
+ k,
245
+ v,
246
+ seqused_k,
247
+ out,
248
+ softmax_lse,
249
+ lse_accum,
250
+ out_accum,
251
+ float(softmax_scale),
252
+ int(num_sms),
253
+ )
254
+ return out
255
+
256
+
257
+ def forward(
258
+ q: torch.Tensor,
259
+ k: torch.Tensor,
260
+ v: torch.Tensor,
261
+ *,
262
+ softmax_scale: Optional[float] = None,
263
+ causal: bool = False,
264
+ use_split_kv: bool = True,
265
+ ) -> torch.Tensor:
266
+ """Convenience API that allocates outputs and optional split-KV workspace."""
267
+
268
+ out, lse = allocate_outputs(q)
269
+ workspace = allocate_workspace(q, k) if use_split_kv else None
270
+ return forward_static(
271
+ q,
272
+ k,
273
+ v,
274
+ out=out,
275
+ softmax_lse=lse,
276
+ workspace=workspace,
277
+ softmax_scale=softmax_scale,
278
+ causal=causal,
279
+ )
280
+
281
+
282
+ __all__ = [
283
+ "FA2Workspace",
284
+ "COMPILED_HEAD_DIM_BUCKETS",
285
+ "SPLIT_HEAD_DIMS",
286
+ "SUPPORTED_HEAD_DIMS",
287
+ "allocate_outputs",
288
+ "allocate_workspace",
289
+ "forward",
290
+ "forward_seqused_static",
291
+ "forward_static",
292
+ "recommended_num_splits",
293
+ ]
build/torch211-cxx11-cu128-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a8a386d1faad5ebf42e48ef679d1f501c66c9d79b5465fab09c6bff5fc0afd98
3
+ size 422846784
build/torch211-cxx11-cu128-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_8fba1d7
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_8fba1d7
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_8fba1d7::{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,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_8fba1d7",
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
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CN+Ar/JeWCSd1O49S4rR3XPtrtlRXwoY54NuRMsiFDM=",
20
+ "_fa2_seqused_runtime_cuda_8fba1d7.abi3.so": "qKOG0fqtXr9C5I72edH1AcZsnXm1Rl+rCca/9fwK/Zg=",
21
+ "_ops.py": "Y+Kfab+oxrJtjqom/l6BdXWyyPHyICYlblsyFF7Ye6Y=",
22
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ }
25
+ }
build/torch211-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = tuple(range(8, 257, 8))
15
+ COMPILED_HEAD_DIM_BUCKETS = (64, 96, 128, 256)
16
+ # Vendored FA2 split-KV partial-head tiles are valid only in these logical
17
+ # ranges. Other supported dimensions use the correct no-split kernel.
18
+ SPLIT_HEAD_DIMS = tuple(range(40, 129, 8)) + tuple(range(232, 257, 8))
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FA2Workspace:
23
+ """Preallocated split-KV workspace for one static attention shape."""
24
+
25
+ softmax_lse_accum: torch.Tensor
26
+ out_accum: torch.Tensor
27
+ num_sms: int
28
+ num_splits: int
29
+
30
+
31
+ def _ceildiv(a: int, b: int) -> int:
32
+ return (a + b - 1) // b
33
+
34
+
35
+ def recommended_num_splits(
36
+ batch: int,
37
+ seqlen_q: int,
38
+ seqlen_k: int,
39
+ heads_q: int,
40
+ head_dim: int,
41
+ num_sms: int,
42
+ ) -> int:
43
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
44
+
45
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
46
+ if any(int(v) <= 0 for v in values):
47
+ raise ValueError("all shape values and num_sms must be positive")
48
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
49
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
50
+ if int(head_dim) not in SPLIT_HEAD_DIMS:
51
+ return 1
52
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
53
+ n_blocks = _ceildiv(seqlen_k, block_n)
54
+ m_blocks = _ceildiv(seqlen_q, 64)
55
+ blocks = batch * heads_q * m_blocks
56
+ effective_sms = num_sms * 2
57
+ if blocks >= 0.8 * effective_sms:
58
+ return 1
59
+ max_splits = min(128, effective_sms, n_blocks)
60
+ efficiencies = [0.0] * (max_splits + 1)
61
+ best = 0.0
62
+ for split in range(1, max_splits + 1):
63
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
64
+ if not eligible:
65
+ continue
66
+ waves = blocks * split / effective_sms
67
+ efficiencies[split] = waves / math.ceil(waves)
68
+ best = max(best, efficiencies[split])
69
+ for split in range(1, max_splits + 1):
70
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
71
+ if eligible and efficiencies[split] >= 0.85 * best:
72
+ return split
73
+ return 1
74
+
75
+
76
+ def allocate_workspace(
77
+ q: torch.Tensor,
78
+ k: torch.Tensor,
79
+ *,
80
+ num_sms: Optional[int] = None,
81
+ ) -> Optional[FA2Workspace]:
82
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
83
+
84
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
85
+ once during runtime setup; never call this helper inside a captured loop.
86
+ """
87
+
88
+ if q.ndim != 4 or k.ndim != 4:
89
+ raise ValueError("q and k must have shape (B, S, H, D)")
90
+ if q.shape[-1] not in SUPPORTED_HEAD_DIMS:
91
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
92
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
93
+ return None
94
+ if num_sms is None:
95
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
96
+ splits = recommended_num_splits(
97
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
98
+ )
99
+ if splits == 1:
100
+ return None
101
+ lse = torch.empty(
102
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
103
+ device=q.device,
104
+ dtype=torch.float32,
105
+ )
106
+ d_rounded = (q.shape[3] + 31) & ~31
107
+ out = torch.empty(
108
+ (splits, q.shape[0], q.shape[2], q.shape[1], d_rounded),
109
+ device=q.device,
110
+ dtype=torch.float32,
111
+ )
112
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
113
+
114
+
115
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
116
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
117
+
118
+ if q.ndim != 4:
119
+ raise ValueError("q must have shape (B, S, H, D)")
120
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
121
+ lse = torch.empty(
122
+ (q.shape[0], q.shape[2], q.shape[1]),
123
+ device=q.device,
124
+ dtype=torch.float32,
125
+ )
126
+ return out, lse
127
+
128
+
129
+ def _workspace_args(
130
+ workspace: Optional[FA2Workspace],
131
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
132
+ if workspace is None:
133
+ return None, None, 0
134
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
135
+
136
+
137
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
138
+ def _forward_static_fake(
139
+ q: torch.Tensor,
140
+ k: torch.Tensor,
141
+ v: torch.Tensor,
142
+ out: torch.Tensor,
143
+ softmax_lse: torch.Tensor,
144
+ softmax_lse_accum: Optional[torch.Tensor],
145
+ out_accum: Optional[torch.Tensor],
146
+ softmax_scale: float,
147
+ causal: bool = False,
148
+ num_sms: int = 0,
149
+ ) -> None:
150
+ del k, v, softmax_scale, causal, num_sms
151
+ if q.ndim != 4 or out.shape != q.shape:
152
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
153
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
154
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
155
+ if (softmax_lse_accum is None) != (out_accum is None):
156
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
157
+ return None
158
+
159
+
160
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
161
+ def _forward_seqused_static_fake(
162
+ q: torch.Tensor,
163
+ k: torch.Tensor,
164
+ v: torch.Tensor,
165
+ seqused_k: torch.Tensor,
166
+ out: torch.Tensor,
167
+ softmax_lse: torch.Tensor,
168
+ softmax_lse_accum: Optional[torch.Tensor],
169
+ out_accum: Optional[torch.Tensor],
170
+ softmax_scale: float,
171
+ num_sms: int = 0,
172
+ ) -> None:
173
+ del seqused_k
174
+ return _forward_static_fake(
175
+ q,
176
+ k,
177
+ v,
178
+ out,
179
+ softmax_lse,
180
+ softmax_lse_accum,
181
+ out_accum,
182
+ softmax_scale,
183
+ False,
184
+ num_sms,
185
+ )
186
+
187
+
188
+ def forward_static(
189
+ q: torch.Tensor,
190
+ k: torch.Tensor,
191
+ v: torch.Tensor,
192
+ *,
193
+ out: torch.Tensor,
194
+ softmax_lse: torch.Tensor,
195
+ workspace: Optional[FA2Workspace] = None,
196
+ softmax_scale: Optional[float] = None,
197
+ causal: bool = False,
198
+ ) -> torch.Tensor:
199
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
200
+
201
+ if softmax_scale is None:
202
+ softmax_scale = q.shape[-1] ** -0.5
203
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
204
+ ops.forward_static(
205
+ q,
206
+ k,
207
+ v,
208
+ out,
209
+ softmax_lse,
210
+ lse_accum,
211
+ out_accum,
212
+ float(softmax_scale),
213
+ bool(causal),
214
+ int(num_sms),
215
+ )
216
+ return out
217
+
218
+
219
+ def forward_seqused_static(
220
+ q: torch.Tensor,
221
+ k: torch.Tensor,
222
+ v: torch.Tensor,
223
+ seqused_k: torch.Tensor,
224
+ *,
225
+ out: torch.Tensor,
226
+ softmax_lse: torch.Tensor,
227
+ workspace: Optional[FA2Workspace] = None,
228
+ softmax_scale: Optional[float] = None,
229
+ ) -> torch.Tensor:
230
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
231
+
232
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
233
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
234
+ reset is captured together with the kernel by CUDA Graphs.
235
+ """
236
+
237
+ if softmax_scale is None:
238
+ softmax_scale = q.shape[-1] ** -0.5
239
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
240
+ if lse_accum is not None:
241
+ lse_accum.fill_(-torch.inf)
242
+ ops.forward_seqused_static(
243
+ q,
244
+ k,
245
+ v,
246
+ seqused_k,
247
+ out,
248
+ softmax_lse,
249
+ lse_accum,
250
+ out_accum,
251
+ float(softmax_scale),
252
+ int(num_sms),
253
+ )
254
+ return out
255
+
256
+
257
+ def forward(
258
+ q: torch.Tensor,
259
+ k: torch.Tensor,
260
+ v: torch.Tensor,
261
+ *,
262
+ softmax_scale: Optional[float] = None,
263
+ causal: bool = False,
264
+ use_split_kv: bool = True,
265
+ ) -> torch.Tensor:
266
+ """Convenience API that allocates outputs and optional split-KV workspace."""
267
+
268
+ out, lse = allocate_outputs(q)
269
+ workspace = allocate_workspace(q, k) if use_split_kv else None
270
+ return forward_static(
271
+ q,
272
+ k,
273
+ v,
274
+ out=out,
275
+ softmax_lse=lse,
276
+ workspace=workspace,
277
+ softmax_scale=softmax_scale,
278
+ causal=causal,
279
+ )
280
+
281
+
282
+ __all__ = [
283
+ "FA2Workspace",
284
+ "COMPILED_HEAD_DIM_BUCKETS",
285
+ "SPLIT_HEAD_DIMS",
286
+ "SUPPORTED_HEAD_DIMS",
287
+ "allocate_outputs",
288
+ "allocate_workspace",
289
+ "forward",
290
+ "forward_seqused_static",
291
+ "forward_static",
292
+ "recommended_num_splits",
293
+ ]
build/torch211-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:566873c30214a6294f174e7d15e1f5240fb491b0c534eb31d154c91900f8f29c
3
+ size 374469512
build/torch211-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_8fba1d7
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_8fba1d7
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_8fba1d7::{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,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_8fba1d7",
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
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CN+Ar/JeWCSd1O49S4rR3XPtrtlRXwoY54NuRMsiFDM=",
20
+ "_fa2_seqused_runtime_cuda_8fba1d7.abi3.so": "VmhzwwIUpilPF059FeH1JA+0kbDFNOsx0VTJGQD48pw=",
21
+ "_ops.py": "Y+Kfab+oxrJtjqom/l6BdXWyyPHyICYlblsyFF7Ye6Y=",
22
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ }
25
+ }
build/torch212-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = tuple(range(8, 257, 8))
15
+ COMPILED_HEAD_DIM_BUCKETS = (64, 96, 128, 256)
16
+ # Vendored FA2 split-KV partial-head tiles are valid only in these logical
17
+ # ranges. Other supported dimensions use the correct no-split kernel.
18
+ SPLIT_HEAD_DIMS = tuple(range(40, 129, 8)) + tuple(range(232, 257, 8))
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FA2Workspace:
23
+ """Preallocated split-KV workspace for one static attention shape."""
24
+
25
+ softmax_lse_accum: torch.Tensor
26
+ out_accum: torch.Tensor
27
+ num_sms: int
28
+ num_splits: int
29
+
30
+
31
+ def _ceildiv(a: int, b: int) -> int:
32
+ return (a + b - 1) // b
33
+
34
+
35
+ def recommended_num_splits(
36
+ batch: int,
37
+ seqlen_q: int,
38
+ seqlen_k: int,
39
+ heads_q: int,
40
+ head_dim: int,
41
+ num_sms: int,
42
+ ) -> int:
43
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
44
+
45
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
46
+ if any(int(v) <= 0 for v in values):
47
+ raise ValueError("all shape values and num_sms must be positive")
48
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
49
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
50
+ if int(head_dim) not in SPLIT_HEAD_DIMS:
51
+ return 1
52
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
53
+ n_blocks = _ceildiv(seqlen_k, block_n)
54
+ m_blocks = _ceildiv(seqlen_q, 64)
55
+ blocks = batch * heads_q * m_blocks
56
+ effective_sms = num_sms * 2
57
+ if blocks >= 0.8 * effective_sms:
58
+ return 1
59
+ max_splits = min(128, effective_sms, n_blocks)
60
+ efficiencies = [0.0] * (max_splits + 1)
61
+ best = 0.0
62
+ for split in range(1, max_splits + 1):
63
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
64
+ if not eligible:
65
+ continue
66
+ waves = blocks * split / effective_sms
67
+ efficiencies[split] = waves / math.ceil(waves)
68
+ best = max(best, efficiencies[split])
69
+ for split in range(1, max_splits + 1):
70
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
71
+ if eligible and efficiencies[split] >= 0.85 * best:
72
+ return split
73
+ return 1
74
+
75
+
76
+ def allocate_workspace(
77
+ q: torch.Tensor,
78
+ k: torch.Tensor,
79
+ *,
80
+ num_sms: Optional[int] = None,
81
+ ) -> Optional[FA2Workspace]:
82
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
83
+
84
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
85
+ once during runtime setup; never call this helper inside a captured loop.
86
+ """
87
+
88
+ if q.ndim != 4 or k.ndim != 4:
89
+ raise ValueError("q and k must have shape (B, S, H, D)")
90
+ if q.shape[-1] not in SUPPORTED_HEAD_DIMS:
91
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
92
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
93
+ return None
94
+ if num_sms is None:
95
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
96
+ splits = recommended_num_splits(
97
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
98
+ )
99
+ if splits == 1:
100
+ return None
101
+ lse = torch.empty(
102
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
103
+ device=q.device,
104
+ dtype=torch.float32,
105
+ )
106
+ d_rounded = (q.shape[3] + 31) & ~31
107
+ out = torch.empty(
108
+ (splits, q.shape[0], q.shape[2], q.shape[1], d_rounded),
109
+ device=q.device,
110
+ dtype=torch.float32,
111
+ )
112
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
113
+
114
+
115
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
116
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
117
+
118
+ if q.ndim != 4:
119
+ raise ValueError("q must have shape (B, S, H, D)")
120
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
121
+ lse = torch.empty(
122
+ (q.shape[0], q.shape[2], q.shape[1]),
123
+ device=q.device,
124
+ dtype=torch.float32,
125
+ )
126
+ return out, lse
127
+
128
+
129
+ def _workspace_args(
130
+ workspace: Optional[FA2Workspace],
131
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
132
+ if workspace is None:
133
+ return None, None, 0
134
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
135
+
136
+
137
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
138
+ def _forward_static_fake(
139
+ q: torch.Tensor,
140
+ k: torch.Tensor,
141
+ v: torch.Tensor,
142
+ out: torch.Tensor,
143
+ softmax_lse: torch.Tensor,
144
+ softmax_lse_accum: Optional[torch.Tensor],
145
+ out_accum: Optional[torch.Tensor],
146
+ softmax_scale: float,
147
+ causal: bool = False,
148
+ num_sms: int = 0,
149
+ ) -> None:
150
+ del k, v, softmax_scale, causal, num_sms
151
+ if q.ndim != 4 or out.shape != q.shape:
152
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
153
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
154
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
155
+ if (softmax_lse_accum is None) != (out_accum is None):
156
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
157
+ return None
158
+
159
+
160
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
161
+ def _forward_seqused_static_fake(
162
+ q: torch.Tensor,
163
+ k: torch.Tensor,
164
+ v: torch.Tensor,
165
+ seqused_k: torch.Tensor,
166
+ out: torch.Tensor,
167
+ softmax_lse: torch.Tensor,
168
+ softmax_lse_accum: Optional[torch.Tensor],
169
+ out_accum: Optional[torch.Tensor],
170
+ softmax_scale: float,
171
+ num_sms: int = 0,
172
+ ) -> None:
173
+ del seqused_k
174
+ return _forward_static_fake(
175
+ q,
176
+ k,
177
+ v,
178
+ out,
179
+ softmax_lse,
180
+ softmax_lse_accum,
181
+ out_accum,
182
+ softmax_scale,
183
+ False,
184
+ num_sms,
185
+ )
186
+
187
+
188
+ def forward_static(
189
+ q: torch.Tensor,
190
+ k: torch.Tensor,
191
+ v: torch.Tensor,
192
+ *,
193
+ out: torch.Tensor,
194
+ softmax_lse: torch.Tensor,
195
+ workspace: Optional[FA2Workspace] = None,
196
+ softmax_scale: Optional[float] = None,
197
+ causal: bool = False,
198
+ ) -> torch.Tensor:
199
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
200
+
201
+ if softmax_scale is None:
202
+ softmax_scale = q.shape[-1] ** -0.5
203
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
204
+ ops.forward_static(
205
+ q,
206
+ k,
207
+ v,
208
+ out,
209
+ softmax_lse,
210
+ lse_accum,
211
+ out_accum,
212
+ float(softmax_scale),
213
+ bool(causal),
214
+ int(num_sms),
215
+ )
216
+ return out
217
+
218
+
219
+ def forward_seqused_static(
220
+ q: torch.Tensor,
221
+ k: torch.Tensor,
222
+ v: torch.Tensor,
223
+ seqused_k: torch.Tensor,
224
+ *,
225
+ out: torch.Tensor,
226
+ softmax_lse: torch.Tensor,
227
+ workspace: Optional[FA2Workspace] = None,
228
+ softmax_scale: Optional[float] = None,
229
+ ) -> torch.Tensor:
230
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
231
+
232
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
233
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
234
+ reset is captured together with the kernel by CUDA Graphs.
235
+ """
236
+
237
+ if softmax_scale is None:
238
+ softmax_scale = q.shape[-1] ** -0.5
239
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
240
+ if lse_accum is not None:
241
+ lse_accum.fill_(-torch.inf)
242
+ ops.forward_seqused_static(
243
+ q,
244
+ k,
245
+ v,
246
+ seqused_k,
247
+ out,
248
+ softmax_lse,
249
+ lse_accum,
250
+ out_accum,
251
+ float(softmax_scale),
252
+ int(num_sms),
253
+ )
254
+ return out
255
+
256
+
257
+ def forward(
258
+ q: torch.Tensor,
259
+ k: torch.Tensor,
260
+ v: torch.Tensor,
261
+ *,
262
+ softmax_scale: Optional[float] = None,
263
+ causal: bool = False,
264
+ use_split_kv: bool = True,
265
+ ) -> torch.Tensor:
266
+ """Convenience API that allocates outputs and optional split-KV workspace."""
267
+
268
+ out, lse = allocate_outputs(q)
269
+ workspace = allocate_workspace(q, k) if use_split_kv else None
270
+ return forward_static(
271
+ q,
272
+ k,
273
+ v,
274
+ out=out,
275
+ softmax_lse=lse,
276
+ workspace=workspace,
277
+ softmax_scale=softmax_scale,
278
+ causal=causal,
279
+ )
280
+
281
+
282
+ __all__ = [
283
+ "FA2Workspace",
284
+ "COMPILED_HEAD_DIM_BUCKETS",
285
+ "SPLIT_HEAD_DIMS",
286
+ "SUPPORTED_HEAD_DIMS",
287
+ "allocate_outputs",
288
+ "allocate_workspace",
289
+ "forward",
290
+ "forward_seqused_static",
291
+ "forward_static",
292
+ "recommended_num_splits",
293
+ ]
build/torch212-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:13c17c724e5c39f2dfa5d6e14b6dae277a207dd65e63af47bd378dee46c8e3bb
3
+ size 374479848
build/torch212-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_8fba1d7
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_8fba1d7
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_8fba1d7::{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,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_8fba1d7",
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
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CN+Ar/JeWCSd1O49S4rR3XPtrtlRXwoY54NuRMsiFDM=",
20
+ "_fa2_seqused_runtime_cuda_8fba1d7.abi3.so": "E8F8ck5cOfLfpdbhS22uJ3ogfdZeY69HvTeN7kbI47s=",
21
+ "_ops.py": "Y+Kfab+oxrJtjqom/l6BdXWyyPHyICYlblsyFF7Ye6Y=",
22
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ }
25
+ }
build/torch212-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = tuple(range(8, 257, 8))
15
+ COMPILED_HEAD_DIM_BUCKETS = (64, 96, 128, 256)
16
+ # Vendored FA2 split-KV partial-head tiles are valid only in these logical
17
+ # ranges. Other supported dimensions use the correct no-split kernel.
18
+ SPLIT_HEAD_DIMS = tuple(range(40, 129, 8)) + tuple(range(232, 257, 8))
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FA2Workspace:
23
+ """Preallocated split-KV workspace for one static attention shape."""
24
+
25
+ softmax_lse_accum: torch.Tensor
26
+ out_accum: torch.Tensor
27
+ num_sms: int
28
+ num_splits: int
29
+
30
+
31
+ def _ceildiv(a: int, b: int) -> int:
32
+ return (a + b - 1) // b
33
+
34
+
35
+ def recommended_num_splits(
36
+ batch: int,
37
+ seqlen_q: int,
38
+ seqlen_k: int,
39
+ heads_q: int,
40
+ head_dim: int,
41
+ num_sms: int,
42
+ ) -> int:
43
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
44
+
45
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
46
+ if any(int(v) <= 0 for v in values):
47
+ raise ValueError("all shape values and num_sms must be positive")
48
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
49
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
50
+ if int(head_dim) not in SPLIT_HEAD_DIMS:
51
+ return 1
52
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
53
+ n_blocks = _ceildiv(seqlen_k, block_n)
54
+ m_blocks = _ceildiv(seqlen_q, 64)
55
+ blocks = batch * heads_q * m_blocks
56
+ effective_sms = num_sms * 2
57
+ if blocks >= 0.8 * effective_sms:
58
+ return 1
59
+ max_splits = min(128, effective_sms, n_blocks)
60
+ efficiencies = [0.0] * (max_splits + 1)
61
+ best = 0.0
62
+ for split in range(1, max_splits + 1):
63
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
64
+ if not eligible:
65
+ continue
66
+ waves = blocks * split / effective_sms
67
+ efficiencies[split] = waves / math.ceil(waves)
68
+ best = max(best, efficiencies[split])
69
+ for split in range(1, max_splits + 1):
70
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
71
+ if eligible and efficiencies[split] >= 0.85 * best:
72
+ return split
73
+ return 1
74
+
75
+
76
+ def allocate_workspace(
77
+ q: torch.Tensor,
78
+ k: torch.Tensor,
79
+ *,
80
+ num_sms: Optional[int] = None,
81
+ ) -> Optional[FA2Workspace]:
82
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
83
+
84
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
85
+ once during runtime setup; never call this helper inside a captured loop.
86
+ """
87
+
88
+ if q.ndim != 4 or k.ndim != 4:
89
+ raise ValueError("q and k must have shape (B, S, H, D)")
90
+ if q.shape[-1] not in SUPPORTED_HEAD_DIMS:
91
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
92
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
93
+ return None
94
+ if num_sms is None:
95
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
96
+ splits = recommended_num_splits(
97
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
98
+ )
99
+ if splits == 1:
100
+ return None
101
+ lse = torch.empty(
102
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
103
+ device=q.device,
104
+ dtype=torch.float32,
105
+ )
106
+ d_rounded = (q.shape[3] + 31) & ~31
107
+ out = torch.empty(
108
+ (splits, q.shape[0], q.shape[2], q.shape[1], d_rounded),
109
+ device=q.device,
110
+ dtype=torch.float32,
111
+ )
112
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
113
+
114
+
115
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
116
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
117
+
118
+ if q.ndim != 4:
119
+ raise ValueError("q must have shape (B, S, H, D)")
120
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
121
+ lse = torch.empty(
122
+ (q.shape[0], q.shape[2], q.shape[1]),
123
+ device=q.device,
124
+ dtype=torch.float32,
125
+ )
126
+ return out, lse
127
+
128
+
129
+ def _workspace_args(
130
+ workspace: Optional[FA2Workspace],
131
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
132
+ if workspace is None:
133
+ return None, None, 0
134
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
135
+
136
+
137
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
138
+ def _forward_static_fake(
139
+ q: torch.Tensor,
140
+ k: torch.Tensor,
141
+ v: torch.Tensor,
142
+ out: torch.Tensor,
143
+ softmax_lse: torch.Tensor,
144
+ softmax_lse_accum: Optional[torch.Tensor],
145
+ out_accum: Optional[torch.Tensor],
146
+ softmax_scale: float,
147
+ causal: bool = False,
148
+ num_sms: int = 0,
149
+ ) -> None:
150
+ del k, v, softmax_scale, causal, num_sms
151
+ if q.ndim != 4 or out.shape != q.shape:
152
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
153
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
154
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
155
+ if (softmax_lse_accum is None) != (out_accum is None):
156
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
157
+ return None
158
+
159
+
160
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
161
+ def _forward_seqused_static_fake(
162
+ q: torch.Tensor,
163
+ k: torch.Tensor,
164
+ v: torch.Tensor,
165
+ seqused_k: torch.Tensor,
166
+ out: torch.Tensor,
167
+ softmax_lse: torch.Tensor,
168
+ softmax_lse_accum: Optional[torch.Tensor],
169
+ out_accum: Optional[torch.Tensor],
170
+ softmax_scale: float,
171
+ num_sms: int = 0,
172
+ ) -> None:
173
+ del seqused_k
174
+ return _forward_static_fake(
175
+ q,
176
+ k,
177
+ v,
178
+ out,
179
+ softmax_lse,
180
+ softmax_lse_accum,
181
+ out_accum,
182
+ softmax_scale,
183
+ False,
184
+ num_sms,
185
+ )
186
+
187
+
188
+ def forward_static(
189
+ q: torch.Tensor,
190
+ k: torch.Tensor,
191
+ v: torch.Tensor,
192
+ *,
193
+ out: torch.Tensor,
194
+ softmax_lse: torch.Tensor,
195
+ workspace: Optional[FA2Workspace] = None,
196
+ softmax_scale: Optional[float] = None,
197
+ causal: bool = False,
198
+ ) -> torch.Tensor:
199
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
200
+
201
+ if softmax_scale is None:
202
+ softmax_scale = q.shape[-1] ** -0.5
203
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
204
+ ops.forward_static(
205
+ q,
206
+ k,
207
+ v,
208
+ out,
209
+ softmax_lse,
210
+ lse_accum,
211
+ out_accum,
212
+ float(softmax_scale),
213
+ bool(causal),
214
+ int(num_sms),
215
+ )
216
+ return out
217
+
218
+
219
+ def forward_seqused_static(
220
+ q: torch.Tensor,
221
+ k: torch.Tensor,
222
+ v: torch.Tensor,
223
+ seqused_k: torch.Tensor,
224
+ *,
225
+ out: torch.Tensor,
226
+ softmax_lse: torch.Tensor,
227
+ workspace: Optional[FA2Workspace] = None,
228
+ softmax_scale: Optional[float] = None,
229
+ ) -> torch.Tensor:
230
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
231
+
232
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
233
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
234
+ reset is captured together with the kernel by CUDA Graphs.
235
+ """
236
+
237
+ if softmax_scale is None:
238
+ softmax_scale = q.shape[-1] ** -0.5
239
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
240
+ if lse_accum is not None:
241
+ lse_accum.fill_(-torch.inf)
242
+ ops.forward_seqused_static(
243
+ q,
244
+ k,
245
+ v,
246
+ seqused_k,
247
+ out,
248
+ softmax_lse,
249
+ lse_accum,
250
+ out_accum,
251
+ float(softmax_scale),
252
+ int(num_sms),
253
+ )
254
+ return out
255
+
256
+
257
+ def forward(
258
+ q: torch.Tensor,
259
+ k: torch.Tensor,
260
+ v: torch.Tensor,
261
+ *,
262
+ softmax_scale: Optional[float] = None,
263
+ causal: bool = False,
264
+ use_split_kv: bool = True,
265
+ ) -> torch.Tensor:
266
+ """Convenience API that allocates outputs and optional split-KV workspace."""
267
+
268
+ out, lse = allocate_outputs(q)
269
+ workspace = allocate_workspace(q, k) if use_split_kv else None
270
+ return forward_static(
271
+ q,
272
+ k,
273
+ v,
274
+ out=out,
275
+ softmax_lse=lse,
276
+ workspace=workspace,
277
+ softmax_scale=softmax_scale,
278
+ causal=causal,
279
+ )
280
+
281
+
282
+ __all__ = [
283
+ "FA2Workspace",
284
+ "COMPILED_HEAD_DIM_BUCKETS",
285
+ "SPLIT_HEAD_DIMS",
286
+ "SUPPORTED_HEAD_DIMS",
287
+ "allocate_outputs",
288
+ "allocate_workspace",
289
+ "forward",
290
+ "forward_seqused_static",
291
+ "forward_static",
292
+ "recommended_num_splits",
293
+ ]
build/torch212-cxx11-cu132-x86_64-linux/_fa2_seqused_runtime_cuda_8fba1d7.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:38428286c02feeb5039cc0b034f891fc1c938d53ecdfc3d66e67edd483a8afe9
3
+ size 375811104
build/torch212-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_8fba1d7
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_8fba1d7
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_8fba1d7::{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,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_8fba1d7",
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
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CN+Ar/JeWCSd1O49S4rR3XPtrtlRXwoY54NuRMsiFDM=",
20
+ "_fa2_seqused_runtime_cuda_8fba1d7.abi3.so": "OEKChsAv7rUDnMCwNPiR/ByTjVPs38PWbmft1IOor+k=",
21
+ "_ops.py": "Y+Kfab+oxrJtjqom/l6BdXWyyPHyICYlblsyFF7Ye6Y=",
22
+ "fa2_seqused_runtime/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ }
25
+ }
build/torch213-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = tuple(range(8, 257, 8))
15
+ COMPILED_HEAD_DIM_BUCKETS = (64, 96, 128, 256)
16
+ # Vendored FA2 split-KV partial-head tiles are valid only in these logical
17
+ # ranges. Other supported dimensions use the correct no-split kernel.
18
+ SPLIT_HEAD_DIMS = tuple(range(40, 129, 8)) + tuple(range(232, 257, 8))
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FA2Workspace:
23
+ """Preallocated split-KV workspace for one static attention shape."""
24
+
25
+ softmax_lse_accum: torch.Tensor
26
+ out_accum: torch.Tensor
27
+ num_sms: int
28
+ num_splits: int
29
+
30
+
31
+ def _ceildiv(a: int, b: int) -> int:
32
+ return (a + b - 1) // b
33
+
34
+
35
+ def recommended_num_splits(
36
+ batch: int,
37
+ seqlen_q: int,
38
+ seqlen_k: int,
39
+ heads_q: int,
40
+ head_dim: int,
41
+ num_sms: int,
42
+ ) -> int:
43
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
44
+
45
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
46
+ if any(int(v) <= 0 for v in values):
47
+ raise ValueError("all shape values and num_sms must be positive")
48
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
49
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
50
+ if int(head_dim) not in SPLIT_HEAD_DIMS:
51
+ return 1
52
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
53
+ n_blocks = _ceildiv(seqlen_k, block_n)
54
+ m_blocks = _ceildiv(seqlen_q, 64)
55
+ blocks = batch * heads_q * m_blocks
56
+ effective_sms = num_sms * 2
57
+ if blocks >= 0.8 * effective_sms:
58
+ return 1
59
+ max_splits = min(128, effective_sms, n_blocks)
60
+ efficiencies = [0.0] * (max_splits + 1)
61
+ best = 0.0
62
+ for split in range(1, max_splits + 1):
63
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
64
+ if not eligible:
65
+ continue
66
+ waves = blocks * split / effective_sms
67
+ efficiencies[split] = waves / math.ceil(waves)
68
+ best = max(best, efficiencies[split])
69
+ for split in range(1, max_splits + 1):
70
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
71
+ if eligible and efficiencies[split] >= 0.85 * best:
72
+ return split
73
+ return 1
74
+
75
+
76
+ def allocate_workspace(
77
+ q: torch.Tensor,
78
+ k: torch.Tensor,
79
+ *,
80
+ num_sms: Optional[int] = None,
81
+ ) -> Optional[FA2Workspace]:
82
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
83
+
84
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
85
+ once during runtime setup; never call this helper inside a captured loop.
86
+ """
87
+
88
+ if q.ndim != 4 or k.ndim != 4:
89
+ raise ValueError("q and k must have shape (B, S, H, D)")
90
+ if q.shape[-1] not in SUPPORTED_HEAD_DIMS:
91
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
92
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
93
+ return None
94
+ if num_sms is None:
95
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
96
+ splits = recommended_num_splits(
97
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
98
+ )
99
+ if splits == 1:
100
+ return None
101
+ lse = torch.empty(
102
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
103
+ device=q.device,
104
+ dtype=torch.float32,
105
+ )
106
+ d_rounded = (q.shape[3] + 31) & ~31
107
+ out = torch.empty(
108
+ (splits, q.shape[0], q.shape[2], q.shape[1], d_rounded),
109
+ device=q.device,
110
+ dtype=torch.float32,
111
+ )
112
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
113
+
114
+
115
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
116
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
117
+
118
+ if q.ndim != 4:
119
+ raise ValueError("q must have shape (B, S, H, D)")
120
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
121
+ lse = torch.empty(
122
+ (q.shape[0], q.shape[2], q.shape[1]),
123
+ device=q.device,
124
+ dtype=torch.float32,
125
+ )
126
+ return out, lse
127
+
128
+
129
+ def _workspace_args(
130
+ workspace: Optional[FA2Workspace],
131
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
132
+ if workspace is None:
133
+ return None, None, 0
134
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
135
+
136
+
137
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
138
+ def _forward_static_fake(
139
+ q: torch.Tensor,
140
+ k: torch.Tensor,
141
+ v: torch.Tensor,
142
+ out: torch.Tensor,
143
+ softmax_lse: torch.Tensor,
144
+ softmax_lse_accum: Optional[torch.Tensor],
145
+ out_accum: Optional[torch.Tensor],
146
+ softmax_scale: float,
147
+ causal: bool = False,
148
+ num_sms: int = 0,
149
+ ) -> None:
150
+ del k, v, softmax_scale, causal, num_sms
151
+ if q.ndim != 4 or out.shape != q.shape:
152
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
153
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
154
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
155
+ if (softmax_lse_accum is None) != (out_accum is None):
156
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
157
+ return None
158
+
159
+
160
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
161
+ def _forward_seqused_static_fake(
162
+ q: torch.Tensor,
163
+ k: torch.Tensor,
164
+ v: torch.Tensor,
165
+ seqused_k: torch.Tensor,
166
+ out: torch.Tensor,
167
+ softmax_lse: torch.Tensor,
168
+ softmax_lse_accum: Optional[torch.Tensor],
169
+ out_accum: Optional[torch.Tensor],
170
+ softmax_scale: float,
171
+ num_sms: int = 0,
172
+ ) -> None:
173
+ del seqused_k
174
+ return _forward_static_fake(
175
+ q,
176
+ k,
177
+ v,
178
+ out,
179
+ softmax_lse,
180
+ softmax_lse_accum,
181
+ out_accum,
182
+ softmax_scale,
183
+ False,
184
+ num_sms,
185
+ )
186
+
187
+
188
+ def forward_static(
189
+ q: torch.Tensor,
190
+ k: torch.Tensor,
191
+ v: torch.Tensor,
192
+ *,
193
+ out: torch.Tensor,
194
+ softmax_lse: torch.Tensor,
195
+ workspace: Optional[FA2Workspace] = None,
196
+ softmax_scale: Optional[float] = None,
197
+ causal: bool = False,
198
+ ) -> torch.Tensor:
199
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
200
+
201
+ if softmax_scale is None:
202
+ softmax_scale = q.shape[-1] ** -0.5
203
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
204
+ ops.forward_static(
205
+ q,
206
+ k,
207
+ v,
208
+ out,
209
+ softmax_lse,
210
+ lse_accum,
211
+ out_accum,
212
+ float(softmax_scale),
213
+ bool(causal),
214
+ int(num_sms),
215
+ )
216
+ return out
217
+
218
+
219
+ def forward_seqused_static(
220
+ q: torch.Tensor,
221
+ k: torch.Tensor,
222
+ v: torch.Tensor,
223
+ seqused_k: torch.Tensor,
224
+ *,
225
+ out: torch.Tensor,
226
+ softmax_lse: torch.Tensor,
227
+ workspace: Optional[FA2Workspace] = None,
228
+ softmax_scale: Optional[float] = None,
229
+ ) -> torch.Tensor:
230
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
231
+
232
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
233
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
234
+ reset is captured together with the kernel by CUDA Graphs.
235
+ """
236
+
237
+ if softmax_scale is None:
238
+ softmax_scale = q.shape[-1] ** -0.5
239
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
240
+ if lse_accum is not None:
241
+ lse_accum.fill_(-torch.inf)
242
+ ops.forward_seqused_static(
243
+ q,
244
+ k,
245
+ v,
246
+ seqused_k,
247
+ out,
248
+ softmax_lse,
249
+ lse_accum,
250
+ out_accum,
251
+ float(softmax_scale),
252
+ int(num_sms),
253
+ )
254
+ return out
255
+
256
+
257
+ def forward(
258
+ q: torch.Tensor,
259
+ k: torch.Tensor,
260
+ v: torch.Tensor,
261
+ *,
262
+ softmax_scale: Optional[float] = None,
263
+ causal: bool = False,
264
+ use_split_kv: bool = True,
265
+ ) -> torch.Tensor:
266
+ """Convenience API that allocates outputs and optional split-KV workspace."""
267
+
268
+ out, lse = allocate_outputs(q)
269
+ workspace = allocate_workspace(q, k) if use_split_kv else None
270
+ return forward_static(
271
+ q,
272
+ k,
273
+ v,
274
+ out=out,
275
+ softmax_lse=lse,
276
+ workspace=workspace,
277
+ softmax_scale=softmax_scale,
278
+ causal=causal,
279
+ )
280
+
281
+
282
+ __all__ = [
283
+ "FA2Workspace",
284
+ "COMPILED_HEAD_DIM_BUCKETS",
285
+ "SPLIT_HEAD_DIMS",
286
+ "SUPPORTED_HEAD_DIMS",
287
+ "allocate_outputs",
288
+ "allocate_workspace",
289
+ "forward",
290
+ "forward_seqused_static",
291
+ "forward_static",
292
+ "recommended_num_splits",
293
+ ]
build/torch213-cxx11-cu130-x86_64-linux/_fa2_seqused_runtime_cuda_9ea2146.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e78b78088c67872a5149377c538bf8c68537469a984f8c34cfd75ae739274d39
3
+ size 374479696
build/torch213-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_9ea2146
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_9ea2146
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_9ea2146::{op_name}"
build/torch213-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_9ea2146",
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
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CN+Ar/JeWCSd1O49S4rR3XPtrtlRXwoY54NuRMsiFDM=",
20
+ "_fa2_seqused_runtime_cuda_9ea2146.abi3.so": "54t4CIxnhypRSTd8U4v4xoU3RpqYT4w0z9da5zknTTk=",
21
+ "_ops.py": "csDcHTNxl0h1zDNATeVxZ1/hrMeV5W8mdnPqqupklBk="
22
+ }
23
+ },
24
+ "provenance": {
25
+ "kernel-builder": {
26
+ "version": "0.17.0-dev0",
27
+ "sha": "81f55ea30fd8f819dcf93a3c934dd584c895bd2f",
28
+ "dirty": false
29
+ },
30
+ "kernel": {
31
+ "sha": "9ea2146c52cd2b967296f47342fb32a14bb25033",
32
+ "dirty": false
33
+ }
34
+ }
35
+ }
build/torch213-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = tuple(range(8, 257, 8))
15
+ COMPILED_HEAD_DIM_BUCKETS = (64, 96, 128, 256)
16
+ # Vendored FA2 split-KV partial-head tiles are valid only in these logical
17
+ # ranges. Other supported dimensions use the correct no-split kernel.
18
+ SPLIT_HEAD_DIMS = tuple(range(40, 129, 8)) + tuple(range(232, 257, 8))
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class FA2Workspace:
23
+ """Preallocated split-KV workspace for one static attention shape."""
24
+
25
+ softmax_lse_accum: torch.Tensor
26
+ out_accum: torch.Tensor
27
+ num_sms: int
28
+ num_splits: int
29
+
30
+
31
+ def _ceildiv(a: int, b: int) -> int:
32
+ return (a + b - 1) // b
33
+
34
+
35
+ def recommended_num_splits(
36
+ batch: int,
37
+ seqlen_q: int,
38
+ seqlen_k: int,
39
+ heads_q: int,
40
+ head_dim: int,
41
+ num_sms: int,
42
+ ) -> int:
43
+ """Return the exact split count selected by the FlashRT FA2 heuristic."""
44
+
45
+ values = (batch, seqlen_q, seqlen_k, heads_q, head_dim, num_sms)
46
+ if any(int(v) <= 0 for v in values):
47
+ raise ValueError("all shape values and num_sms must be positive")
48
+ if int(head_dim) not in SUPPORTED_HEAD_DIMS:
49
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
50
+ if int(head_dim) not in SPLIT_HEAD_DIMS:
51
+ return 1
52
+ block_n = 256 if head_dim <= 64 else (128 if head_dim <= 128 else 64)
53
+ n_blocks = _ceildiv(seqlen_k, block_n)
54
+ m_blocks = _ceildiv(seqlen_q, 64)
55
+ blocks = batch * heads_q * m_blocks
56
+ effective_sms = num_sms * 2
57
+ if blocks >= 0.8 * effective_sms:
58
+ return 1
59
+ max_splits = min(128, effective_sms, n_blocks)
60
+ efficiencies = [0.0] * (max_splits + 1)
61
+ best = 0.0
62
+ for split in range(1, max_splits + 1):
63
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
64
+ if not eligible:
65
+ continue
66
+ waves = blocks * split / effective_sms
67
+ efficiencies[split] = waves / math.ceil(waves)
68
+ best = max(best, efficiencies[split])
69
+ for split in range(1, max_splits + 1):
70
+ eligible = split == 1 or _ceildiv(n_blocks, split) != _ceildiv(n_blocks, split - 1)
71
+ if eligible and efficiencies[split] >= 0.85 * best:
72
+ return split
73
+ return 1
74
+
75
+
76
+ def allocate_workspace(
77
+ q: torch.Tensor,
78
+ k: torch.Tensor,
79
+ *,
80
+ num_sms: Optional[int] = None,
81
+ ) -> Optional[FA2Workspace]:
82
+ """Allocate the exact split-KV workspace selected for ``q`` and ``k``.
83
+
84
+ Returns ``None`` when the heuristic selects the no-split path. Allocate
85
+ once during runtime setup; never call this helper inside a captured loop.
86
+ """
87
+
88
+ if q.ndim != 4 or k.ndim != 4:
89
+ raise ValueError("q and k must have shape (B, S, H, D)")
90
+ if q.shape[-1] not in SUPPORTED_HEAD_DIMS:
91
+ raise ValueError("head_dim must be a positive multiple of 8 at most 256")
92
+ if q.shape[-1] not in SPLIT_HEAD_DIMS:
93
+ return None
94
+ if num_sms is None:
95
+ num_sms = torch.cuda.get_device_properties(q.device).multi_processor_count
96
+ splits = recommended_num_splits(
97
+ q.shape[0], q.shape[1], k.shape[1], q.shape[2], q.shape[3], num_sms
98
+ )
99
+ if splits == 1:
100
+ return None
101
+ lse = torch.empty(
102
+ (splits, q.shape[0], q.shape[2], q.shape[1]),
103
+ device=q.device,
104
+ dtype=torch.float32,
105
+ )
106
+ d_rounded = (q.shape[3] + 31) & ~31
107
+ out = torch.empty(
108
+ (splits, q.shape[0], q.shape[2], q.shape[1], d_rounded),
109
+ device=q.device,
110
+ dtype=torch.float32,
111
+ )
112
+ return FA2Workspace(lse, out, int(num_sms), int(splits))
113
+
114
+
115
+ def allocate_outputs(q: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
116
+ """Allocate output and LSE tensors for a static ``(B,S,H,D)`` query."""
117
+
118
+ if q.ndim != 4:
119
+ raise ValueError("q must have shape (B, S, H, D)")
120
+ out = torch.empty_strided(q.shape, q.stride(), device=q.device, dtype=q.dtype)
121
+ lse = torch.empty(
122
+ (q.shape[0], q.shape[2], q.shape[1]),
123
+ device=q.device,
124
+ dtype=torch.float32,
125
+ )
126
+ return out, lse
127
+
128
+
129
+ def _workspace_args(
130
+ workspace: Optional[FA2Workspace],
131
+ ) -> tuple[Optional[torch.Tensor], Optional[torch.Tensor], int]:
132
+ if workspace is None:
133
+ return None, None, 0
134
+ return workspace.softmax_lse_accum, workspace.out_accum, int(workspace.num_sms)
135
+
136
+
137
+ @torch.library.register_fake(add_op_namespace_prefix("forward_static"))
138
+ def _forward_static_fake(
139
+ q: torch.Tensor,
140
+ k: torch.Tensor,
141
+ v: torch.Tensor,
142
+ out: torch.Tensor,
143
+ softmax_lse: torch.Tensor,
144
+ softmax_lse_accum: Optional[torch.Tensor],
145
+ out_accum: Optional[torch.Tensor],
146
+ softmax_scale: float,
147
+ causal: bool = False,
148
+ num_sms: int = 0,
149
+ ) -> None:
150
+ del k, v, softmax_scale, causal, num_sms
151
+ if q.ndim != 4 or out.shape != q.shape:
152
+ raise RuntimeError("q/out must have matching (B, S, H, D) shapes")
153
+ if softmax_lse.shape != (q.shape[0], q.shape[2], q.shape[1]):
154
+ raise RuntimeError("softmax_lse must have shape (B, H, S)")
155
+ if (softmax_lse_accum is None) != (out_accum is None):
156
+ raise RuntimeError("split-KV workspace tensors must be both set or both None")
157
+ return None
158
+
159
+
160
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
161
+ def _forward_seqused_static_fake(
162
+ q: torch.Tensor,
163
+ k: torch.Tensor,
164
+ v: torch.Tensor,
165
+ seqused_k: torch.Tensor,
166
+ out: torch.Tensor,
167
+ softmax_lse: torch.Tensor,
168
+ softmax_lse_accum: Optional[torch.Tensor],
169
+ out_accum: Optional[torch.Tensor],
170
+ softmax_scale: float,
171
+ num_sms: int = 0,
172
+ ) -> None:
173
+ del seqused_k
174
+ return _forward_static_fake(
175
+ q,
176
+ k,
177
+ v,
178
+ out,
179
+ softmax_lse,
180
+ softmax_lse_accum,
181
+ out_accum,
182
+ softmax_scale,
183
+ False,
184
+ num_sms,
185
+ )
186
+
187
+
188
+ def forward_static(
189
+ q: torch.Tensor,
190
+ k: torch.Tensor,
191
+ v: torch.Tensor,
192
+ *,
193
+ out: torch.Tensor,
194
+ softmax_lse: torch.Tensor,
195
+ workspace: Optional[FA2Workspace] = None,
196
+ softmax_scale: Optional[float] = None,
197
+ causal: bool = False,
198
+ ) -> torch.Tensor:
199
+ """Run allocation-free FA2 forward into caller-owned static buffers."""
200
+
201
+ if softmax_scale is None:
202
+ softmax_scale = q.shape[-1] ** -0.5
203
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
204
+ ops.forward_static(
205
+ q,
206
+ k,
207
+ v,
208
+ out,
209
+ softmax_lse,
210
+ lse_accum,
211
+ out_accum,
212
+ float(softmax_scale),
213
+ bool(causal),
214
+ int(num_sms),
215
+ )
216
+ return out
217
+
218
+
219
+ def forward_seqused_static(
220
+ q: torch.Tensor,
221
+ k: torch.Tensor,
222
+ v: torch.Tensor,
223
+ seqused_k: torch.Tensor,
224
+ *,
225
+ out: torch.Tensor,
226
+ softmax_lse: torch.Tensor,
227
+ workspace: Optional[FA2Workspace] = None,
228
+ softmax_scale: Optional[float] = None,
229
+ ) -> torch.Tensor:
230
+ """Run BF16 FA2 with device-resident per-batch K/V lengths.
231
+
232
+ Values in ``seqused_k`` must be in ``[1, k.shape[1]]``. When split-KV is
233
+ enabled, the LSE workspace is reset to ``-inf`` on the current stream; that
234
+ reset is captured together with the kernel by CUDA Graphs.
235
+ """
236
+
237
+ if softmax_scale is None:
238
+ softmax_scale = q.shape[-1] ** -0.5
239
+ lse_accum, out_accum, num_sms = _workspace_args(workspace)
240
+ if lse_accum is not None:
241
+ lse_accum.fill_(-torch.inf)
242
+ ops.forward_seqused_static(
243
+ q,
244
+ k,
245
+ v,
246
+ seqused_k,
247
+ out,
248
+ softmax_lse,
249
+ lse_accum,
250
+ out_accum,
251
+ float(softmax_scale),
252
+ int(num_sms),
253
+ )
254
+ return out
255
+
256
+
257
+ def forward(
258
+ q: torch.Tensor,
259
+ k: torch.Tensor,
260
+ v: torch.Tensor,
261
+ *,
262
+ softmax_scale: Optional[float] = None,
263
+ causal: bool = False,
264
+ use_split_kv: bool = True,
265
+ ) -> torch.Tensor:
266
+ """Convenience API that allocates outputs and optional split-KV workspace."""
267
+
268
+ out, lse = allocate_outputs(q)
269
+ workspace = allocate_workspace(q, k) if use_split_kv else None
270
+ return forward_static(
271
+ q,
272
+ k,
273
+ v,
274
+ out=out,
275
+ softmax_lse=lse,
276
+ workspace=workspace,
277
+ softmax_scale=softmax_scale,
278
+ causal=causal,
279
+ )
280
+
281
+
282
+ __all__ = [
283
+ "FA2Workspace",
284
+ "COMPILED_HEAD_DIM_BUCKETS",
285
+ "SPLIT_HEAD_DIMS",
286
+ "SUPPORTED_HEAD_DIMS",
287
+ "allocate_outputs",
288
+ "allocate_workspace",
289
+ "forward",
290
+ "forward_seqused_static",
291
+ "forward_static",
292
+ "recommended_num_splits",
293
+ ]
build/torch213-cxx11-cu132-x86_64-linux/_fa2_seqused_runtime_cuda_9ea2146.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9be77dccfc08ca58a4e17abcaef1b5dbeb8fea968b236b636b6fd17792a9eeeb
3
+ size 375810944
build/torch213-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _fa2_seqused_runtime_cuda_9ea2146
3
+ ops = torch.ops._fa2_seqused_runtime_cuda_9ea2146
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_fa2_seqused_runtime_cuda_9ea2146::{op_name}"
build/torch213-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "fa2-seqused-runtime",
3
+ "id": "_fa2_seqused_runtime_cuda_9ea2146",
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
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "CN+Ar/JeWCSd1O49S4rR3XPtrtlRXwoY54NuRMsiFDM=",
20
+ "_fa2_seqused_runtime_cuda_9ea2146.abi3.so": "m+d9zPwIylik4Xq8rvG12+uP6paLI2tja2/Rd5Kp7us=",
21
+ "_ops.py": "csDcHTNxl0h1zDNATeVxZ1/hrMeV5W8mdnPqqupklBk="
22
+ }
23
+ },
24
+ "provenance": {
25
+ "kernel-builder": {
26
+ "version": "0.17.0-dev0",
27
+ "sha": "81f55ea30fd8f819dcf93a3c934dd584c895bd2f",
28
+ "dirty": false
29
+ },
30
+ "kernel": {
31
+ "sha": "9ea2146c52cd2b967296f47342fb32a14bb25033",
32
+ "dirty": false
33
+ }
34
+ }
35
+ }