liangsu9988 commited on
Commit
03b8715
·
verified ·
1 Parent(s): f2417dc

Publish GROOT Thor Batch 4 (torch213-cxx11-cu130-aarch64-linux, 6ab0803)

Browse files
CARD.md CHANGED
@@ -13,7 +13,10 @@ removing the per-call `-inf` pre-fill. BF16 accepts fused-QKV token strides.
13
 
14
  - `forward(q, k, v, *, scale=None)`
15
  - `forward_static(q, k, v, *, logits, out, scale=None)`
 
 
16
  - `allocate_workspace(q, k)`
 
17
 
18
  Inputs use `(sequence, heads, head_dim)`. `forward_static` is the CUDA Graph
19
  hot-path API: allocate `logits` and `out` once and reuse their addresses.
 
13
 
14
  - `forward(q, k, v, *, scale=None)`
15
  - `forward_static(q, k, v, *, logits, out, scale=None)`
16
+ - `attention_mha_fp16_masked(q, k, v, *, logits, out, scale=None)`
17
+ - `attention_mha_bf16_masked(q, k, v, *, logits, out, qkv_token_stride=None, scale=None)`
18
  - `allocate_workspace(q, k)`
19
+ - `forward_seqused_static(q, k, v, valid_k, *, logits, out, scale=None)`
20
 
21
  Inputs use `(sequence, heads, head_dim)`. `forward_static` is the CUDA Graph
22
  hot-path API: allocate `logits` and `out` once and reuse their addresses.
README.md CHANGED
@@ -11,13 +11,25 @@ import torch
11
  ops = get_kernel("flashrt/masked-mha-runtime", version=1)
12
  logits = ops.allocate_workspace(q, k)
13
  out = torch.empty_like(q, memory_format=torch.contiguous_format)
14
- ops.forward_static(q, k, v, logits=logits, out=out)
15
  ```
16
 
17
- Public functions are `allocate_workspace`, `forward_static`, and `forward`.
 
 
 
18
  Inputs use `(sequence, heads, head_dim)`. The production GROOT gate covers
19
  DiT `(41, 32, 48)`, ViT/LLM sequence lengths `277/1024`, padded boundaries
20
  `1025/2048`, FP16 and BF16, fused strides, and bitwise CUDA Graph replay.
 
 
 
 
 
 
 
 
 
21
 
22
  See [CARD.md](CARD.md) for the complete contract. Source provenance is FlashRT
23
  commit `24df793f4fa2d50780aea03b644208c6e0cb4162`.
 
11
  ops = get_kernel("flashrt/masked-mha-runtime", version=1)
12
  logits = ops.allocate_workspace(q, k)
13
  out = torch.empty_like(q, memory_format=torch.contiguous_format)
14
+ ops.attention_mha_fp16_masked(q, k, v, logits=logits, out=out)
15
  ```
16
 
17
+ Public functions are `allocate_workspace`, `attention_mha_fp16_masked`,
18
+ `attention_mha_bf16_masked`, `forward_static`, `forward`, and
19
+ `forward_seqused_static`. The explicit masked names are additive; the legacy
20
+ `forward_static` entry dispatches the same kernels.
21
  Inputs use `(sequence, heads, head_dim)`. The production GROOT gate covers
22
  DiT `(41, 32, 48)`, ViT/LLM sequence lengths `277/1024`, padded boundaries
23
  `1025/2048`, FP16 and BF16, fused strides, and bitwise CUDA Graph replay.
24
+ `forward_seqused_static` is the FP16 shared-KV path used by static VLA
25
+ decoders: Q is `(Sq,H,D)`, K/V are `(Sk_max,D)`, and a CUDA int32 `valid_k`
26
+ limits softmax without a standalone `-inf` fill. It supports `Sk_max <= 1024`.
27
+
28
+ The BF16 masked entry accepts `qkv_token_stride=` and validates it against the
29
+ actual Tensor token stride. This permits direct views into fused QKV GEMM
30
+ output while rejecting a mismatched layout before launch. Both masked entries
31
+ only read and write the valid `S_kv` columns, so padded workspace bytes never
32
+ participate in softmax or CUDA Graph replay.
33
 
34
  See [CARD.md](CARD.md) for the complete contract. Source provenance is FlashRT
35
  commit `24df793f4fa2d50780aea03b644208c6e0cb4162`.
SYNC.md CHANGED
@@ -4,3 +4,10 @@
4
  - Commit: `24df793f4fa2d50780aea03b644208c6e0cb4162`
5
  - Source: `csrc/kernels/attention_mha_masked.cu`
6
  - Local changes: Tensor validation and `torch.library` binding only.
 
 
 
 
 
 
 
 
4
  - Commit: `24df793f4fa2d50780aea03b644208c6e0cb4162`
5
  - Source: `csrc/kernels/attention_mha_masked.cu`
6
  - Local changes: Tensor validation and `torch.library` binding only.
7
+
8
+ The CUDA source was reverified byte-for-byte against FlashRT revision
9
+ `ff7795a88c257ccf7195c3c066ce69b7fe84be95`. SHA256 is
10
+ `751b1de5e227bfff00f57c30c0a5bfb8efefaeedd7230fb3b2d0c67a28773ff9`.
11
+ The explicit FP16/BF16 public entries are additive binding surfaces over this
12
+ same native implementation; the established `forward_static` entry remains
13
+ unchanged.
VALIDATION.md CHANGED
@@ -3,3 +3,24 @@
3
  Release requires FP16 and BF16 parity against PyTorch SDPA at key lengths
4
  41, 277, 1024, 1025, and 2048; poisoned padded scratch; CUDA Graph replay;
5
  source and installed-artifact runs on SM110; and native-vs-package timing.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  Release requires FP16 and BF16 parity against PyTorch SDPA at key lengths
4
  41, 277, 1024, 1025, and 2048; poisoned padded scratch; CUDA Graph replay;
5
  source and installed-artifact runs on SM110; and native-vs-package timing.
6
+
7
+ The additive `attention_mha_fp16_masked` and
8
+ `attention_mha_bf16_masked` entries must remain bitwise-equal to
9
+ `forward_static`. The BF16 entry additionally checks `qkv_token_stride`
10
+ against the actual Q tensor stride so fused-QKV views fail early instead of
11
+ reading the wrong token row. Release gating includes poisoned padded columns,
12
+ bitwise graph replay, and an explicit/static latency ratio no greater than
13
+ 1.05x.
14
+
15
+ On 2026-08-06, source full passed `10/10` on NVIDIA Thor (SM110), PyTorch
16
+ `2.13.0+cu130`, and CUDA 13.0. The additive `forward_seqused_static` rows cover
17
+ PI0.5 `(Sq,H,D)=(10,8,256)`, `Sk_max=456/968`, and device-resident
18
+ `valid_k=456/712/968`. Worst p99 absolute error was `0.000122`, worst cosine
19
+ was `0.99999982`, and graph replay was bitwise deterministic.
20
+
21
+ On 2026-08-08, the source and clean installed-artifact gates both passed
22
+ `10/10` on the same Thor class. The explicit FP16/BF16 entries measured
23
+ `1.005x/1.000x` of `forward_static`; BF16 fused-stride execution also passed
24
+ `torch.compile(fullgraph=True)` exactly. For the GROOT row
25
+ `Sq=Sk=41,H=32,D=48`, worst p99 absolute error was `0.003906` and cosine was
26
+ `0.99999213`; sequence lengths 1024, 1025, and 2048 also passed.
benchmarks/RESULTS.md CHANGED
@@ -1,5 +1,43 @@
1
  # Results
2
 
3
- Pending the clean SM110 installed-artifact benchmark. No speedup claim is
4
- published until the package is measured against both PyTorch SDPA and the
5
- FlashRT native symbol on the same Thor host.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Results
2
 
3
+ ## NVIDIA Thor installed artifact
4
+
5
+ - Device: NVIDIA Thor, SM110, aarch64
6
+ - Runtime: PyTorch 2.13.0 + CUDA 13.0
7
+ - Variant: `torch213-cxx11-cu130-aarch64-linux`
8
+ - Native source: FlashRT `24df793f4fa2d50780aea03b644208c6e0cb4162`
9
+
10
+ The installed-artifact full gate passed 6/6. Representative BF16 rows:
11
+
12
+ | Shape | p99 absolute error | Cosine | CUDA Graph replay |
13
+ |---|---:|---:|---|
14
+ | `sk=1024` | 0.000452 | 0.99999362 | bitwise |
15
+ | `sk=1025` | 0.000452 | 0.99999356 | bitwise |
16
+ | `sk=2048` | 0.000174 | 0.99999666 | bitwise |
17
+
18
+ The Hub Tensor wrapper and the raw registered native op were measured in the
19
+ same process with CUDA events. Direct wrapper/raw latency was
20
+ `24.7932/24.7544 us` (`1.0016x`). With the caller-owned static buffers used by
21
+ the production CUDA Graph path, wrapper/raw latency was
22
+ `19.0034/19.0034 us` (`1.0000x`).
23
+
24
+ These numbers establish package parity with the FlashRT native symbol. They
25
+ are not presented as a universal speedup over PyTorch SDPA; backend selection
26
+ must still be profiled for the target sequence and padding profile.
27
+
28
+ ## GROOT Thor explicit masked-attention entries
29
+
30
+ Clean installed artifact built from source commit
31
+ `6ab0803f010b2af49474577dddef866938c3bdcd` on NVIDIA Thor (SM110), PyTorch
32
+ 2.13.0+cu130. Measurements use caller-owned buffers, CUDA Graph replay, and
33
+ A-B-B-A ordering.
34
+
35
+ | Entry | Shape | `forward_static` us | Explicit us | Explicit/static |
36
+ |---|---:|---:|---:|---:|
37
+ | `attention_mha_fp16_masked` | `Sq=41, Sk=277, H=32, D=48` | 22.342 | 22.454 | 1.005x |
38
+ | `attention_mha_bf16_masked` | `Sq=41, Sk=277, H=32, D=48` | 22.649 | 22.644 | 1.000x |
39
+
40
+ Both entries are bitwise-equal to the established wrapper. The BF16 entry
41
+ also validates the fused-QKV token stride explicitly. Source and clean
42
+ installed-artifact gates passed `10/10`; BF16 fused-stride execution passed
43
+ `torch.compile(fullgraph=True)` exactly.
build.toml CHANGED
@@ -21,4 +21,9 @@ include = ["csrc"]
21
  cuda-minver = "13"
22
  cuda-capabilities = ["11.0a"]
23
  cuda-flags = ["-O3", "--use_fast_math"]
24
- src = ["csrc/attention_mha_masked.cu", "csrc/attention_mha_masked.cuh"]
 
 
 
 
 
 
21
  cuda-minver = "13"
22
  cuda-capabilities = ["11.0a"]
23
  cuda-flags = ["-O3", "--use_fast_math"]
24
+ src = [
25
+ "csrc/attention_mha_masked.cu",
26
+ "csrc/attention_mha_masked.cuh",
27
+ "csrc/attention_seqused_fused.cu",
28
+ "csrc/attention_seqused_fused.cuh",
29
+ ]
build/torch213-cxx11-cu130-aarch64-linux/__init__.py CHANGED
@@ -23,6 +23,20 @@ def _forward_static_fake(q, k, v, logits, out, scale: float) -> None:
23
  raise RuntimeError("logits stride must cover sequence_kv")
24
 
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
27
  def _forward_seqused_static_fake(q, k, v, valid_k, logits, out, scale: float) -> None:
28
  del scale
@@ -58,6 +72,43 @@ def forward_static(
58
  return out
59
 
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  def forward(q, k, v, *, scale: Optional[float] = None):
62
  """Convenience allocation wrapper; use ``forward_static`` in hot paths."""
63
  logits = allocate_workspace(q, k)
@@ -83,6 +134,8 @@ def forward_seqused_static(
83
 
84
 
85
  __all__ = [
 
 
86
  "allocate_workspace",
87
  "forward",
88
  "forward_static",
 
23
  raise RuntimeError("logits stride must cover sequence_kv")
24
 
25
 
26
+ @torch.library.register_fake(add_op_namespace_prefix("attention_mha_fp16_masked"))
27
+ def _attention_mha_fp16_masked_fake(q, k, v, logits, out, scale: float) -> None:
28
+ _forward_static_fake(q, k, v, logits, out, scale)
29
+
30
+
31
+ @torch.library.register_fake(add_op_namespace_prefix("attention_mha_bf16_masked"))
32
+ def _attention_mha_bf16_masked_fake(
33
+ q, k, v, logits, out, scale: float, qkv_token_stride: int
34
+ ) -> None:
35
+ _forward_static_fake(q, k, v, logits, out, scale)
36
+ if qkv_token_stride != q.stride(0):
37
+ raise RuntimeError("qkv_token_stride must match q.stride(0)")
38
+
39
+
40
  @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
41
  def _forward_seqused_static_fake(q, k, v, valid_k, logits, out, scale: float) -> None:
42
  del scale
 
72
  return out
73
 
74
 
75
+ def attention_mha_fp16_masked(
76
+ q: torch.Tensor,
77
+ k: torch.Tensor,
78
+ v: torch.Tensor,
79
+ *,
80
+ logits: torch.Tensor,
81
+ out: torch.Tensor,
82
+ scale: Optional[float] = None,
83
+ ) -> torch.Tensor:
84
+ """Run the deterministic valid-column FP16 masked MHA kernel."""
85
+ if scale is None:
86
+ scale = 1.0 / math.sqrt(q.shape[-1])
87
+ ops.attention_mha_fp16_masked(q, k, v, logits, out, float(scale))
88
+ return out
89
+
90
+
91
+ def attention_mha_bf16_masked(
92
+ q: torch.Tensor,
93
+ k: torch.Tensor,
94
+ v: torch.Tensor,
95
+ *,
96
+ logits: torch.Tensor,
97
+ out: torch.Tensor,
98
+ qkv_token_stride: Optional[int] = None,
99
+ scale: Optional[float] = None,
100
+ ) -> torch.Tensor:
101
+ """Run BF16 masked MHA directly from contiguous or fused-QKV views."""
102
+ if scale is None:
103
+ scale = 1.0 / math.sqrt(q.shape[-1])
104
+ if qkv_token_stride is None:
105
+ qkv_token_stride = q.stride(0)
106
+ ops.attention_mha_bf16_masked(
107
+ q, k, v, logits, out, float(scale), int(qkv_token_stride)
108
+ )
109
+ return out
110
+
111
+
112
  def forward(q, k, v, *, scale: Optional[float] = None):
113
  """Convenience allocation wrapper; use ``forward_static`` in hot paths."""
114
  logits = allocate_workspace(q, k)
 
134
 
135
 
136
  __all__ = [
137
+ "attention_mha_bf16_masked",
138
+ "attention_mha_fp16_masked",
139
  "allocate_workspace",
140
  "forward",
141
  "forward_static",
build/torch213-cxx11-cu130-aarch64-linux/{masked_mha_runtime_source_test.abi3.so → _masked_mha_runtime_cuda_6ab0803.abi3.so} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:3b5458ef4502155dc761c2e6742e7d2d7ee2904f85c3e0a6e6e095acc6b64ad0
3
- size 379728
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4a28073db408aaea10e601f842cb523aecfead2965dd28d5928028552dfda4cc
3
+ size 382720
build/torch213-cxx11-cu130-aarch64-linux/_ops.py CHANGED
@@ -1,6 +1,6 @@
1
  import torch
2
- from . import masked_mha_runtime_source_test
3
- ops = torch.ops.masked_mha_runtime_source_test
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
- return f"masked_mha_runtime_source_test::{op_name}"
 
1
  import torch
2
+ from . import _masked_mha_runtime_cuda_6ab0803
3
+ ops = torch.ops._masked_mha_runtime_cuda_6ab0803
4
 
5
  def add_op_namespace_prefix(op_name: str):
6
+ return f"_masked_mha_runtime_cuda_6ab0803::{op_name}"
build/torch213-cxx11-cu130-aarch64-linux/metadata.json CHANGED
@@ -1,27 +1,27 @@
1
  {
2
  "name": "masked-mha-runtime",
3
- "id": "masked_mha_runtime_source_test",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda",
9
  "archs": [
10
- "11.0"
11
  ]
12
  },
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
- "__init__.py": "sZLa3RWzOCoLFNml3p4SGOBbeTUtLYDp2tmIL0FlToo=",
17
- "masked_mha_runtime_source_test.abi3.so": "O1RY70UCFV3HYcLmdC59LX7ikE+Fw+Cm5uCVrMa2StA=",
18
- "_ops.py": "dXHlvnzts0eDL5fgM/I0tMNAsCB3uhZT03SIVpj4bvU=",
19
  "masked_mha_runtime/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
- "sha": "3a505c0",
25
  "dirty": false
26
  },
27
  "validation": {
 
1
  {
2
  "name": "masked-mha-runtime",
3
+ "id": "_masked_mha_runtime_cuda_6ab0803",
4
  "version": 1,
5
  "license": "Apache-2.0",
6
  "python-depends": [],
7
  "backend": {
8
  "type": "cuda",
9
  "archs": [
10
+ "11.0a"
11
  ]
12
  },
13
  "digest": {
14
  "algorithm": "sha256",
15
  "files": {
16
+ "__init__.py": "w4T07fNWmll9bTURpoHVj+llGGGmT6f7hRpFFKZjBoQ=",
17
+ "_masked_mha_runtime_cuda_6ab0803.abi3.so": "SigHPbQIquoQ5gH4QstSOuz+rSll3SjVkoAoVS39pMw=",
18
+ "_ops.py": "tWBn/Gj4KYWoPtEY8bDBbJ0/A61ndt9MyvId4N+G0SE=",
19
  "masked_mha_runtime/__init__.py": "v6p5XMfQzddhi1fLSAw4HX9CyS0rQsidvu9VsT01xi4="
20
  }
21
  },
22
  "provenance": {
23
  "kernel": {
24
+ "sha": "6ab0803",
25
  "dirty": false
26
  },
27
  "validation": {
csrc/attention_seqused_fused.cu ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ================================================================
2
+ // FlashRT — seqused attention with the mask folded into softmax.
3
+ //
4
+ // attention_qkv_fp16_seqused runs QK^T, a standalone -inf mask
5
+ // kernel over rows [valid, S_kv_max), softmax, then AV. The v2 chain
6
+ // folds the seqused bound into the softmax kernel itself: max/sum
7
+ // run over [0, valid) and positions beyond valid are written as
8
+ // zero probabilities (identical to exp(-65504 - max) ~ 0 in the
9
+ // reference), removing one kernel per call.
10
+ //
11
+ // Additive: attention_cublas.cu is untouched.
12
+ // ================================================================
13
+
14
+ #include <cuda_runtime.h>
15
+ #include <cuda_fp16.h>
16
+ #include <cublas_v2.h>
17
+
18
+ #include "attention_seqused_fused.cuh"
19
+
20
+ namespace {
21
+
22
+ #define SMV2_WARP_SIZE 32
23
+ #define SMV2_MAX_COLS 1024
24
+ #define SMV2_ITERS (SMV2_MAX_COLS / SMV2_WARP_SIZE)
25
+
26
+ // One warp per logits row. The register-to-column mapping, the reduction
27
+ // order, and the arithmetic are all identical to softmax_fp16_kernel; the
28
+ // only difference is that out-of-range columns take -1e30 at load time
29
+ // instead of being read back as the -65504 a separate mask kernel wrote.
30
+ // Both exponentiate to exactly zero, so the stored probabilities are
31
+ // bit-identical to the mask + softmax chain.
32
+ __global__ void softmax_fp16_seqused_kernel(
33
+ __half* data, int rows, int cols, const int* __restrict__ seqused_k) {
34
+ int lane = threadIdx.x % SMV2_WARP_SIZE;
35
+ int row = blockIdx.x;
36
+ if (row >= rows) return;
37
+
38
+ int valid = seqused_k[0];
39
+ if (valid < 0) valid = 0;
40
+ if (valid > cols) valid = cols;
41
+
42
+ __half* src = data + row * cols;
43
+ int cols2 = cols / 2;
44
+ __half2* src2 = reinterpret_cast<__half2*>(src);
45
+
46
+ float reg[SMV2_ITERS];
47
+ float mx = -1e30f;
48
+ #pragma unroll
49
+ for (int it = 0; it < SMV2_ITERS / 2; it++) {
50
+ int c2 = it * SMV2_WARP_SIZE + lane;
51
+ if (c2 < cols2) {
52
+ __half2 v2 = src2[c2];
53
+ reg[it*2] = (2 * c2 < valid) ? __half2float(v2.x) : -1e30f;
54
+ reg[it*2+1] = (2 * c2 + 1 < valid) ? __half2float(v2.y) : -1e30f;
55
+ mx = fmaxf(mx, fmaxf(reg[it*2], reg[it*2+1]));
56
+ } else {
57
+ reg[it*2] = -1e30f;
58
+ reg[it*2+1] = -1e30f;
59
+ }
60
+ }
61
+ if ((cols & 1) && lane == 0) {
62
+ float v = (cols - 1 < valid) ? __half2float(src[cols-1]) : -1e30f;
63
+ reg[SMV2_ITERS-1] = v;
64
+ mx = fmaxf(mx, v);
65
+ }
66
+
67
+ #pragma unroll
68
+ for (int o = 16; o > 0; o >>= 1)
69
+ mx = fmaxf(mx, __shfl_xor_sync(0xffffffff, mx, o));
70
+
71
+ float sm = 0.f;
72
+ #pragma unroll
73
+ for (int it = 0; it < SMV2_ITERS; it++) {
74
+ reg[it] = __expf(reg[it] - mx);
75
+ sm += reg[it];
76
+ }
77
+ #pragma unroll
78
+ for (int o = 16; o > 0; o >>= 1)
79
+ sm += __shfl_xor_sync(0xffffffff, sm, o);
80
+
81
+ float inv = 1.f / (sm + 1e-8f);
82
+ #pragma unroll
83
+ for (int it = 0; it < SMV2_ITERS / 2; it++) {
84
+ int c2 = it * SMV2_WARP_SIZE + lane;
85
+ if (c2 < cols2) {
86
+ __half2 v2;
87
+ v2.x = __float2half(reg[it*2] * inv);
88
+ v2.y = __float2half(reg[it*2+1] * inv);
89
+ src2[c2] = v2;
90
+ }
91
+ }
92
+ if ((cols & 1) && lane == 0) {
93
+ src[cols-1] = __float2half(reg[SMV2_ITERS-1] * inv);
94
+ }
95
+ }
96
+
97
+ } // namespace
98
+
99
+ void attention_qkv_fp16_seqused_v2(
100
+ cublasHandle_t handle,
101
+ const __half* Q,
102
+ const __half* K,
103
+ const __half* V,
104
+ __half* logits,
105
+ __half* out,
106
+ int S, int S_kv_max, int NH, int HD,
107
+ const int* seqused_k,
108
+ float attn_scale,
109
+ cudaStream_t stream)
110
+ {
111
+ cublasSetStream(handle, stream);
112
+
113
+ float zero = 0.0f;
114
+ cublasGemmEx(handle,
115
+ CUBLAS_OP_T, CUBLAS_OP_N,
116
+ S_kv_max, S * NH, HD,
117
+ &attn_scale,
118
+ K, CUDA_R_16F, HD,
119
+ Q, CUDA_R_16F, HD,
120
+ &zero,
121
+ logits, CUDA_R_16F, S_kv_max,
122
+ CUBLAS_COMPUTE_32F, CUBLAS_GEMM_DEFAULT);
123
+
124
+ softmax_fp16_seqused_kernel<<<S * NH, SMV2_WARP_SIZE, 0, stream>>>(
125
+ logits, S * NH, S_kv_max, seqused_k);
126
+
127
+ float one = 1.0f;
128
+ cublasGemmEx(handle,
129
+ CUBLAS_OP_N, CUBLAS_OP_N,
130
+ HD, S * NH, S_kv_max,
131
+ &one,
132
+ V, CUDA_R_16F, HD,
133
+ logits, CUDA_R_16F, S_kv_max,
134
+ &zero,
135
+ out, CUDA_R_16F, HD,
136
+ CUBLAS_COMPUTE_32F, CUBLAS_GEMM_DEFAULT);
137
+ }
csrc/attention_seqused_fused.cuh ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ================================================================
2
+ // FlashRT — seqused attention with the mask folded into softmax.
3
+ // Drop-in for attention_qkv_fp16_seqused with one fewer kernel;
4
+ // requires S_kv_max <= 1024 (softmax per-warp register budget).
5
+ // ================================================================
6
+ #pragma once
7
+
8
+ #include <cuda_runtime.h>
9
+ #include <cuda_fp16.h>
10
+ #include <cublas_v2.h>
11
+
12
+ void attention_qkv_fp16_seqused_v2(
13
+ cublasHandle_t handle,
14
+ const __half* Q,
15
+ const __half* K,
16
+ const __half* V,
17
+ __half* logits,
18
+ __half* out,
19
+ int S, int S_kv_max, int NH, int HD,
20
+ const int* seqused_k,
21
+ float attn_scale,
22
+ cudaStream_t stream);
flake.lock ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nodes": {
3
+ "flake-compat": {
4
+ "locked": {
5
+ "lastModified": 1767039857,
6
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
7
+ "owner": "edolstra",
8
+ "repo": "flake-compat",
9
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
10
+ "type": "github"
11
+ },
12
+ "original": {
13
+ "owner": "edolstra",
14
+ "repo": "flake-compat",
15
+ "type": "github"
16
+ }
17
+ },
18
+ "flake-utils": {
19
+ "inputs": {
20
+ "systems": "systems"
21
+ },
22
+ "locked": {
23
+ "lastModified": 1731533236,
24
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
25
+ "owner": "numtide",
26
+ "repo": "flake-utils",
27
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
28
+ "type": "github"
29
+ },
30
+ "original": {
31
+ "owner": "numtide",
32
+ "repo": "flake-utils",
33
+ "type": "github"
34
+ }
35
+ },
36
+ "kernel-builder": {
37
+ "inputs": {
38
+ "flake-compat": "flake-compat",
39
+ "flake-utils": "flake-utils",
40
+ "nixpkgs": "nixpkgs",
41
+ "rust-overlay": "rust-overlay"
42
+ },
43
+ "locked": {
44
+ "lastModified": 1785740631,
45
+ "narHash": "sha256-bK6AXiZSJYaCYK2iTT6Xy+24bab6k0IURV80PB1LmZI=",
46
+ "owner": "huggingface",
47
+ "repo": "kernels",
48
+ "rev": "870e825d881664e39f9287a27a74ef63ff3c545e",
49
+ "type": "github"
50
+ },
51
+ "original": {
52
+ "owner": "huggingface",
53
+ "repo": "kernels",
54
+ "rev": "870e825d881664e39f9287a27a74ef63ff3c545e",
55
+ "type": "github"
56
+ }
57
+ },
58
+ "nixpkgs": {
59
+ "locked": {
60
+ "lastModified": 1776927958,
61
+ "narHash": "sha256-XOzEtft7E0P6TgQViLUOQeGHlEYiQ0+FY24BPEksj6s=",
62
+ "owner": "NixOS",
63
+ "repo": "nixpkgs",
64
+ "rev": "fec2c46cca5bf9767486a290abae51200b656d69",
65
+ "type": "github"
66
+ },
67
+ "original": {
68
+ "owner": "NixOS",
69
+ "ref": "nixos-unstable-small",
70
+ "repo": "nixpkgs",
71
+ "type": "github"
72
+ }
73
+ },
74
+ "root": {
75
+ "inputs": {
76
+ "kernel-builder": "kernel-builder"
77
+ }
78
+ },
79
+ "rust-overlay": {
80
+ "inputs": {
81
+ "nixpkgs": [
82
+ "kernel-builder",
83
+ "nixpkgs"
84
+ ]
85
+ },
86
+ "locked": {
87
+ "lastModified": 1776914043,
88
+ "narHash": "sha256-qug5r56yW1qOsjSI99l3Jm15JNT9CvS2otkXNRNtrPI=",
89
+ "owner": "oxalica",
90
+ "repo": "rust-overlay",
91
+ "rev": "2d35c4358d7de3a0e606a6e8b27925d981c01cc3",
92
+ "type": "github"
93
+ },
94
+ "original": {
95
+ "owner": "oxalica",
96
+ "repo": "rust-overlay",
97
+ "type": "github"
98
+ }
99
+ },
100
+ "systems": {
101
+ "locked": {
102
+ "lastModified": 1681028828,
103
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
104
+ "owner": "nix-systems",
105
+ "repo": "default",
106
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
107
+ "type": "github"
108
+ },
109
+ "original": {
110
+ "owner": "nix-systems",
111
+ "repo": "default",
112
+ "type": "github"
113
+ }
114
+ }
115
+ },
116
+ "root": "root",
117
+ "version": 7
118
+ }
tests/test_masked_mha_runtime.py CHANGED
@@ -38,6 +38,33 @@ class SourceOps:
38
  self.ops.forward_static(q, k, v, logits, out, float(scale))
39
  return out
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  def load_source_ops():
43
  from torch.utils.cpp_extension import load
@@ -62,6 +89,7 @@ def load_source_ops():
62
  sources=[
63
  str(PACKAGE / "torch-ext" / "torch_binding.cpp"),
64
  str(PACKAGE / "csrc" / "attention_mha_masked.cu"),
 
65
  ],
66
  extra_include_paths=[str(PACKAGE / "csrc"), str(REGISTRATION_INCLUDE)],
67
  extra_cflags=["-O3", "-DCUDA_KERNEL"],
@@ -107,6 +135,32 @@ def run_case(ops, dtype, sq, sk, heads, dim, fused_stride=False):
107
  out = torch.empty_like(q, memory_format=torch.contiguous_format)
108
  got = ops.forward_static(q, k, v, logits=logits, out=out)
109
  torch.cuda.synchronize()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  ref = torch.nn.functional.scaled_dot_product_attention(
111
  q.permute(1, 0, 2).unsqueeze(0).float(),
112
  k.permute(1, 0, 2).unsqueeze(0).float(),
@@ -128,9 +182,15 @@ def run_case(ops, dtype, sq, sk, heads, dim, fused_stride=False):
128
  graph = torch.cuda.CUDAGraph()
129
  torch.cuda.synchronize()
130
  with torch.cuda.graph(graph):
131
- ops.forward_static(
132
- static_q, static_k, static_v, logits=logits, out=out
133
- )
 
 
 
 
 
 
134
  graph.replay()
135
  first = out.clone()
136
  graph.replay()
@@ -144,6 +204,54 @@ def run_case(ops, dtype, sq, sk, heads, dim, fused_stride=False):
144
  )
145
 
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  def main():
148
  parser = argparse.ArgumentParser()
149
  parser.add_argument("--backend", choices=["source", "installed"], default="source")
@@ -166,7 +274,13 @@ def main():
166
  ])
167
  for case in cases:
168
  run_case(ops, *case)
169
- print(f"masked-mha-runtime {args.backend} {args.mode}: passed {len(cases)}/{len(cases)}")
 
 
 
 
 
 
170
 
171
 
172
  if __name__ == "__main__":
 
38
  self.ops.forward_static(q, k, v, logits, out, float(scale))
39
  return out
40
 
41
+ def attention_mha_fp16_masked(self, q, k, v, *, logits, out, scale=None):
42
+ scale = q.shape[-1] ** -0.5 if scale is None else scale
43
+ self.ops.attention_mha_fp16_masked(
44
+ q, k, v, logits, out, float(scale)
45
+ )
46
+ return out
47
+
48
+ def attention_mha_bf16_masked(
49
+ self, q, k, v, *, logits, out, qkv_token_stride=None, scale=None
50
+ ):
51
+ scale = q.shape[-1] ** -0.5 if scale is None else scale
52
+ if qkv_token_stride is None:
53
+ qkv_token_stride = q.stride(0)
54
+ self.ops.attention_mha_bf16_masked(
55
+ q, k, v, logits, out, float(scale), int(qkv_token_stride)
56
+ )
57
+ return out
58
+
59
+ def forward_seqused_static(
60
+ self, q, k, v, valid_k, *, logits, out, scale=None
61
+ ):
62
+ scale = q.shape[-1] ** -0.5 if scale is None else scale
63
+ self.ops.forward_seqused_static(
64
+ q, k, v, valid_k, logits, out, float(scale)
65
+ )
66
+ return out
67
+
68
 
69
  def load_source_ops():
70
  from torch.utils.cpp_extension import load
 
89
  sources=[
90
  str(PACKAGE / "torch-ext" / "torch_binding.cpp"),
91
  str(PACKAGE / "csrc" / "attention_mha_masked.cu"),
92
+ str(PACKAGE / "csrc" / "attention_seqused_fused.cu"),
93
  ],
94
  extra_include_paths=[str(PACKAGE / "csrc"), str(REGISTRATION_INCLUDE)],
95
  extra_cflags=["-O3", "-DCUDA_KERNEL"],
 
135
  out = torch.empty_like(q, memory_format=torch.contiguous_format)
136
  got = ops.forward_static(q, k, v, logits=logits, out=out)
137
  torch.cuda.synchronize()
138
+ alias_logits = ops.allocate_workspace(q, k)
139
+ alias_logits.fill_(float("nan"))
140
+ alias_out = torch.empty_like(q, memory_format=torch.contiguous_format)
141
+ if dtype is torch.float16:
142
+ alias_got = ops.attention_mha_fp16_masked(
143
+ q, k, v, logits=alias_logits, out=alias_out
144
+ )
145
+ else:
146
+ alias_got = ops.attention_mha_bf16_masked(
147
+ q, k, v, logits=alias_logits, out=alias_out,
148
+ qkv_token_stride=q.stride(0),
149
+ )
150
+ torch.cuda.synchronize()
151
+ if not torch.equal(alias_got, got):
152
+ raise AssertionError("explicit masked MHA entry differs from forward_static")
153
+ if dtype is torch.bfloat16:
154
+ try:
155
+ ops.attention_mha_bf16_masked(
156
+ q, k, v, logits=alias_logits, out=alias_out,
157
+ qkv_token_stride=q.stride(0) + 1,
158
+ )
159
+ except RuntimeError as exc:
160
+ if "qkv_token_stride" not in str(exc):
161
+ raise
162
+ else:
163
+ raise AssertionError("invalid qkv_token_stride was not rejected")
164
  ref = torch.nn.functional.scaled_dot_product_attention(
165
  q.permute(1, 0, 2).unsqueeze(0).float(),
166
  k.permute(1, 0, 2).unsqueeze(0).float(),
 
182
  graph = torch.cuda.CUDAGraph()
183
  torch.cuda.synchronize()
184
  with torch.cuda.graph(graph):
185
+ if dtype is torch.float16:
186
+ ops.attention_mha_fp16_masked(
187
+ static_q, static_k, static_v, logits=logits, out=out
188
+ )
189
+ else:
190
+ ops.attention_mha_bf16_masked(
191
+ static_q, static_k, static_v, logits=logits, out=out,
192
+ qkv_token_stride=static_q.stride(0),
193
+ )
194
  graph.replay()
195
  first = out.clone()
196
  graph.replay()
 
204
  )
205
 
206
 
207
+ def run_seqused_case(ops, sq, sk, valid, heads=8, dim=256):
208
+ torch.manual_seed(164000 + sq + sk + valid)
209
+ q = torch.randn((sq, heads, dim), device="cuda", dtype=torch.float16)
210
+ k = torch.randn((sk, dim), device="cuda", dtype=torch.float16)
211
+ v = torch.randn_like(k)
212
+ valid_k = torch.tensor([valid], device="cuda", dtype=torch.int32)
213
+ logits = torch.full(
214
+ (sq * heads, sk), float("nan"), device="cuda", dtype=torch.float16
215
+ )
216
+ out = torch.empty_like(q)
217
+ got = ops.forward_seqused_static(
218
+ q, k, v, valid_k, logits=logits, out=out
219
+ )
220
+ torch.cuda.synchronize()
221
+ ref = torch.nn.functional.scaled_dot_product_attention(
222
+ q.permute(1, 0, 2).unsqueeze(0).float(),
223
+ k[:valid].unsqueeze(0).unsqueeze(0).float(),
224
+ v[:valid].unsqueeze(0).unsqueeze(0).float(),
225
+ enable_gqa=True,
226
+ ).squeeze(0).permute(1, 0, 2).half()
227
+ max_abs, p99_abs, cosine = metrics(got, ref)
228
+ if not torch.isfinite(got.float()).all() or cosine < 0.999 or p99_abs > 0.00390625:
229
+ raise AssertionError(
230
+ f"seqused sq={sq} sk={sk} valid={valid} h={heads} d={dim}: "
231
+ f"max={max_abs} p99={p99_abs} cos={cosine}"
232
+ )
233
+ if not torch.equal(logits[:, valid:], torch.zeros_like(logits[:, valid:])):
234
+ raise AssertionError("seqused probabilities beyond valid_k are not zero")
235
+
236
+ residual = torch.randn_like(out)
237
+ graph = torch.cuda.CUDAGraph()
238
+ with torch.cuda.graph(graph):
239
+ ops.forward_seqused_static(
240
+ q, k, v, valid_k, logits=logits, out=out
241
+ )
242
+ graph.replay()
243
+ first = out.clone()
244
+ graph.replay()
245
+ torch.cuda.synchronize()
246
+ if not torch.equal(first, out):
247
+ raise AssertionError("seqused CUDA Graph replay is not bitwise deterministic")
248
+ del residual
249
+ print(
250
+ f"PASS seqused sq={sq} sk={sk} valid={valid} h={heads} d={dim} "
251
+ f"max={max_abs:.6f} p99={p99_abs:.6f} cos={cosine:.8f}"
252
+ )
253
+
254
+
255
  def main():
256
  parser = argparse.ArgumentParser()
257
  parser.add_argument("--backend", choices=["source", "installed"], default="source")
 
274
  ])
275
  for case in cases:
276
  run_case(ops, *case)
277
+ seqused_cases = [(10, 456, 456)]
278
+ if args.mode == "full":
279
+ seqused_cases.extend([(10, 968, 456), (10, 968, 712), (10, 968, 968)])
280
+ for case in seqused_cases:
281
+ run_seqused_case(ops, *case)
282
+ total = len(cases) + len(seqused_cases)
283
+ print(f"masked-mha-runtime {args.backend} {args.mode}: passed {total}/{total}")
284
 
285
 
286
  if __name__ == "__main__":
torch-ext/masked_mha_runtime/__init__.py CHANGED
@@ -23,6 +23,31 @@ def _forward_static_fake(q, k, v, logits, out, scale: float) -> None:
23
  raise RuntimeError("logits stride must cover sequence_kv")
24
 
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  def allocate_workspace(q: torch.Tensor, k: torch.Tensor) -> torch.Tensor:
27
  """Allocate padded logits scratch once, outside the hot path."""
28
  stride = (k.shape[0] + 7) // 8 * 8
@@ -47,6 +72,43 @@ def forward_static(
47
  return out
48
 
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  def forward(q, k, v, *, scale: Optional[float] = None):
51
  """Convenience allocation wrapper; use ``forward_static`` in hot paths."""
52
  logits = allocate_workspace(q, k)
@@ -54,4 +116,28 @@ def forward(q, k, v, *, scale: Optional[float] = None):
54
  return forward_static(q, k, v, logits=logits, out=out, scale=scale)
55
 
56
 
57
- __all__ = ["allocate_workspace", "forward", "forward_static"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  raise RuntimeError("logits stride must cover sequence_kv")
24
 
25
 
26
+ @torch.library.register_fake(add_op_namespace_prefix("attention_mha_fp16_masked"))
27
+ def _attention_mha_fp16_masked_fake(q, k, v, logits, out, scale: float) -> None:
28
+ _forward_static_fake(q, k, v, logits, out, scale)
29
+
30
+
31
+ @torch.library.register_fake(add_op_namespace_prefix("attention_mha_bf16_masked"))
32
+ def _attention_mha_bf16_masked_fake(
33
+ q, k, v, logits, out, scale: float, qkv_token_stride: int
34
+ ) -> None:
35
+ _forward_static_fake(q, k, v, logits, out, scale)
36
+ if qkv_token_stride != q.stride(0):
37
+ raise RuntimeError("qkv_token_stride must match q.stride(0)")
38
+
39
+
40
+ @torch.library.register_fake(add_op_namespace_prefix("forward_seqused_static"))
41
+ def _forward_seqused_static_fake(q, k, v, valid_k, logits, out, scale: float) -> None:
42
+ del scale
43
+ if q.dim() != 3 or k.dim() != 2 or v.dim() != 2:
44
+ raise RuntimeError("q must be (sequence, heads, head_dim); k/v must be (max_sequence, head_dim)")
45
+ if valid_k.numel() != 1 or valid_k.dtype != torch.int32:
46
+ raise RuntimeError("valid_k must be an int32 scalar tensor")
47
+ if out.shape != q.shape or logits.shape != (q.shape[0] * q.shape[1], k.shape[0]):
48
+ raise RuntimeError("caller-owned output shapes do not match q/k")
49
+
50
+
51
  def allocate_workspace(q: torch.Tensor, k: torch.Tensor) -> torch.Tensor:
52
  """Allocate padded logits scratch once, outside the hot path."""
53
  stride = (k.shape[0] + 7) // 8 * 8
 
72
  return out
73
 
74
 
75
+ def attention_mha_fp16_masked(
76
+ q: torch.Tensor,
77
+ k: torch.Tensor,
78
+ v: torch.Tensor,
79
+ *,
80
+ logits: torch.Tensor,
81
+ out: torch.Tensor,
82
+ scale: Optional[float] = None,
83
+ ) -> torch.Tensor:
84
+ """Run the deterministic valid-column FP16 masked MHA kernel."""
85
+ if scale is None:
86
+ scale = 1.0 / math.sqrt(q.shape[-1])
87
+ ops.attention_mha_fp16_masked(q, k, v, logits, out, float(scale))
88
+ return out
89
+
90
+
91
+ def attention_mha_bf16_masked(
92
+ q: torch.Tensor,
93
+ k: torch.Tensor,
94
+ v: torch.Tensor,
95
+ *,
96
+ logits: torch.Tensor,
97
+ out: torch.Tensor,
98
+ qkv_token_stride: Optional[int] = None,
99
+ scale: Optional[float] = None,
100
+ ) -> torch.Tensor:
101
+ """Run BF16 masked MHA directly from contiguous or fused-QKV views."""
102
+ if scale is None:
103
+ scale = 1.0 / math.sqrt(q.shape[-1])
104
+ if qkv_token_stride is None:
105
+ qkv_token_stride = q.stride(0)
106
+ ops.attention_mha_bf16_masked(
107
+ q, k, v, logits, out, float(scale), int(qkv_token_stride)
108
+ )
109
+ return out
110
+
111
+
112
  def forward(q, k, v, *, scale: Optional[float] = None):
113
  """Convenience allocation wrapper; use ``forward_static`` in hot paths."""
114
  logits = allocate_workspace(q, k)
 
116
  return forward_static(q, k, v, logits=logits, out=out, scale=scale)
117
 
118
 
119
+ def forward_seqused_static(
120
+ q: torch.Tensor,
121
+ k: torch.Tensor,
122
+ v: torch.Tensor,
123
+ valid_k: torch.Tensor,
124
+ *,
125
+ logits: torch.Tensor,
126
+ out: torch.Tensor,
127
+ scale: Optional[float] = None,
128
+ ) -> torch.Tensor:
129
+ """Run FP16 shared-KV attention with a graph-resident valid-key count."""
130
+ if scale is None:
131
+ scale = 1.0 / math.sqrt(q.shape[-1])
132
+ ops.forward_seqused_static(q, k, v, valid_k, logits, out, float(scale))
133
+ return out
134
+
135
+
136
+ __all__ = [
137
+ "attention_mha_bf16_masked",
138
+ "attention_mha_fp16_masked",
139
+ "allocate_workspace",
140
+ "forward",
141
+ "forward_static",
142
+ "forward_seqused_static",
143
+ ]
torch-ext/torch_binding.cpp CHANGED
@@ -8,6 +8,7 @@
8
  #include <limits>
9
 
10
  #include "attention_mha_masked.cuh"
 
11
  #include "registration.h"
12
 
13
  namespace {
@@ -94,11 +95,84 @@ void masked_mha_forward_static(
94
  C10_CUDA_KERNEL_LAUNCH_CHECK();
95
  }
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  } // namespace
98
 
99
  TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
100
  ops.def("forward_static(Tensor q, Tensor k, Tensor v, Tensor! logits, Tensor! out, float scale) -> ()");
 
 
 
101
  ops.impl("forward_static", torch::kCUDA, &masked_mha_forward_static);
 
 
 
 
 
 
102
  }
103
 
104
  REGISTER_EXTENSION(TORCH_EXTENSION_NAME)
 
8
  #include <limits>
9
 
10
  #include "attention_mha_masked.cuh"
11
+ #include "attention_seqused_fused.cuh"
12
  #include "registration.h"
13
 
14
  namespace {
 
95
  C10_CUDA_KERNEL_LAUNCH_CHECK();
96
  }
97
 
98
+ void attention_mha_fp16_masked_op(
99
+ torch::Tensor const& q, torch::Tensor const& k, torch::Tensor const& v,
100
+ torch::Tensor& logits, torch::Tensor& out, double scale) {
101
+ TORCH_CHECK(q.scalar_type() == torch::kFloat16,
102
+ "attention_mha_fp16_masked requires FP16 q/k/v");
103
+ masked_mha_forward_static(q, k, v, logits, out, scale);
104
+ }
105
+
106
+ void attention_mha_bf16_masked_op(
107
+ torch::Tensor const& q, torch::Tensor const& k, torch::Tensor const& v,
108
+ torch::Tensor& logits, torch::Tensor& out, double scale,
109
+ int64_t qkv_token_stride) {
110
+ TORCH_CHECK(q.scalar_type() == torch::kBFloat16,
111
+ "attention_mha_bf16_masked requires BF16 q/k/v");
112
+ TORCH_CHECK(qkv_token_stride == q.stride(0),
113
+ "qkv_token_stride must match q.stride(0)");
114
+ masked_mha_forward_static(q, k, v, logits, out, scale);
115
+ }
116
+
117
+ void masked_mha_forward_seqused_static(
118
+ torch::Tensor const& q, torch::Tensor const& k, torch::Tensor const& v,
119
+ torch::Tensor const& valid_k, torch::Tensor& logits, torch::Tensor& out,
120
+ double scale) {
121
+ check_qkv(q, "q", torch::kFloat16);
122
+ TORCH_CHECK(k.is_cuda() && v.is_cuda() && k.scalar_type() == torch::kFloat16 &&
123
+ v.scalar_type() == torch::kFloat16 && k.dim() == 2 &&
124
+ v.dim() == 2 && k.is_contiguous() && v.is_contiguous() &&
125
+ k.sizes() == v.sizes(),
126
+ "k/v must be contiguous FP16 tensors with shape (S_kv_max, D)");
127
+ TORCH_CHECK(q.is_contiguous() && q.size(2) == k.size(1),
128
+ "q must be contiguous and share head_dim with k/v");
129
+ TORCH_CHECK(k.size(0) <= 1024,
130
+ "forward_seqused_static supports S_kv_max <= 1024");
131
+ TORCH_CHECK(valid_k.is_cuda() && valid_k.scalar_type() == torch::kInt &&
132
+ valid_k.numel() == 1 && valid_k.is_contiguous(),
133
+ "valid_k must be a contiguous CUDA int32 scalar tensor");
134
+ TORCH_CHECK(q.get_device() == k.get_device() && q.get_device() == v.get_device() &&
135
+ q.get_device() == valid_k.get_device(),
136
+ "q/k/v/valid_k must be on the same device");
137
+ TORCH_CHECK(logits.is_cuda() && logits.scalar_type() == torch::kFloat16 &&
138
+ logits.is_contiguous() && logits.dim() == 2 &&
139
+ logits.size(0) == q.size(0) * q.size(1) &&
140
+ logits.size(1) == k.size(0),
141
+ "logits must be contiguous FP16 with shape (S_q * H, S_kv_max)");
142
+ TORCH_CHECK(out.is_cuda() && out.scalar_type() == torch::kFloat16 &&
143
+ out.is_contiguous() && out.sizes() == q.sizes(),
144
+ "out must be contiguous FP16 and match q");
145
+
146
+ c10::cuda::CUDAGuard guard(q.device());
147
+ auto stream = at::cuda::getCurrentCUDAStream(q.get_device()).stream();
148
+ auto handle = at::cuda::getCurrentCUDABlasHandle();
149
+ attention_qkv_fp16_seqused_v2(
150
+ handle, static_cast<const __half*>(q.data_ptr()),
151
+ static_cast<const __half*>(k.data_ptr()),
152
+ static_cast<const __half*>(v.data_ptr()),
153
+ static_cast<__half*>(logits.data_ptr()),
154
+ static_cast<__half*>(out.data_ptr()), checked_int(q.size(0), "S_q"),
155
+ checked_int(k.size(0), "S_kv_max"), checked_int(q.size(1), "heads"),
156
+ checked_int(q.size(2), "head_dim"),
157
+ static_cast<const int*>(valid_k.data_ptr()), static_cast<float>(scale),
158
+ stream);
159
+ C10_CUDA_KERNEL_LAUNCH_CHECK();
160
+ }
161
+
162
  } // namespace
163
 
164
  TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
165
  ops.def("forward_static(Tensor q, Tensor k, Tensor v, Tensor! logits, Tensor! out, float scale) -> ()");
166
+ ops.def("attention_mha_fp16_masked(Tensor q, Tensor k, Tensor v, Tensor! logits, Tensor! out, float scale) -> ()");
167
+ ops.def("attention_mha_bf16_masked(Tensor q, Tensor k, Tensor v, Tensor! logits, Tensor! out, float scale, int qkv_token_stride) -> ()");
168
+ ops.def("forward_seqused_static(Tensor q, Tensor k, Tensor v, Tensor valid_k, Tensor! logits, Tensor! out, float scale) -> ()");
169
  ops.impl("forward_static", torch::kCUDA, &masked_mha_forward_static);
170
+ ops.impl("attention_mha_fp16_masked", torch::kCUDA,
171
+ &attention_mha_fp16_masked_op);
172
+ ops.impl("attention_mha_bf16_masked", torch::kCUDA,
173
+ &attention_mha_bf16_masked_op);
174
+ ops.impl("forward_seqused_static", torch::kCUDA,
175
+ &masked_mha_forward_seqused_static);
176
  }
177
 
178
  REGISTER_EXTENSION(TORCH_EXTENSION_NAME)