Promote latest kernel artifacts to main
Browse files- .gitattributes +3 -35
- CARD.md +64 -0
- README.md +16 -0
- VALIDATION.md +29 -0
- build/torch-cuda/__init__.py +77 -0
- build/torch-cuda/_ops.py +7 -0
- build/torch-cuda/flashrt_fa4/__init__.py +2 -0
- build/torch-cuda/flashrt_fa4/cute/.flake8 +4 -0
- build/torch-cuda/flashrt_fa4/cute/AUTHORS +8 -0
- build/torch-cuda/flashrt_fa4/cute/LICENSE +29 -0
- build/torch-cuda/flashrt_fa4/cute/__init__.py +25 -0
- build/torch-cuda/flashrt_fa4/cute/ampere_helpers.py +103 -0
- build/torch-cuda/flashrt_fa4/cute/barrier.py +71 -0
- build/torch-cuda/flashrt_fa4/cute/blackwell_helpers.py +1113 -0
- build/torch-cuda/flashrt_fa4/cute/block_info.py +156 -0
- build/torch-cuda/flashrt_fa4/cute/block_sparse_utils.py +1437 -0
- build/torch-cuda/flashrt_fa4/cute/block_sparsity.py +638 -0
- build/torch-cuda/flashrt_fa4/cute/cache_utils.py +281 -0
- build/torch-cuda/flashrt_fa4/cute/copy_utils.py +372 -0
- build/torch-cuda/flashrt_fa4/cute/cute_dsl_ptxas.py +151 -0
- build/torch-cuda/flashrt_fa4/cute/cute_dsl_utils.py +156 -0
- build/torch-cuda/flashrt_fa4/cute/fa_logging.py +97 -0
- build/torch-cuda/flashrt_fa4/cute/fast_math.py +21 -0
- build/torch-cuda/flashrt_fa4/cute/flash_fwd_combine.py +698 -0
- build/torch-cuda/flashrt_fa4/cute/flash_fwd_sm100.py +0 -0
- build/torch-cuda/flashrt_fa4/cute/interface_fwd_sm100.py +1595 -0
- build/torch-cuda/flashrt_fa4/cute/mask.py +1470 -0
- build/torch-cuda/flashrt_fa4/cute/mma_sm100_desc.py +296 -0
- build/torch-cuda/flashrt_fa4/cute/named_barrier.py +56 -0
- build/torch-cuda/flashrt_fa4/cute/pack_gqa.py +263 -0
- build/torch-cuda/flashrt_fa4/cute/paged_kv.py +246 -0
- build/torch-cuda/flashrt_fa4/cute/pipeline.py +402 -0
- build/torch-cuda/flashrt_fa4/cute/seqlen_info.py +287 -0
- build/torch-cuda/flashrt_fa4/cute/sm100_hd256_2cta_fmha_forward.py +1882 -0
- build/torch-cuda/flashrt_fa4/cute/softmax.py +628 -0
- build/torch-cuda/flashrt_fa4/cute/testing.py +476 -0
- build/torch-cuda/flashrt_fa4/cute/tile_scheduler.py +1636 -0
- build/torch-cuda/flashrt_fa4/cute/utils.py +948 -0
- build/torch-cuda/metadata.json +72 -0
- build/torch-cuda/quack/__init__.py +0 -0
- build/torch-cuda/quack/activation.py +568 -0
- build/torch-cuda/quack/compile_utils.py +19 -0
- build/torch-cuda/quack/copy_utils.py +1185 -0
- build/torch-cuda/quack/cute_dsl_utils.py +159 -0
- build/torch-cuda/quack/layout_utils.py +331 -0
- build/torch-cuda/quack/sm90_utils.py +161 -0
- build/torch-cuda/quack/utils.py +324 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,3 @@
|
|
| 1 |
-
*.
|
| 2 |
-
*.
|
| 3 |
-
*.
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CARD.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: kernels
|
| 3 |
+
license: bsd-3-clause
|
| 4 |
+
tags: [cuda, blackwell, flash-attention, cute-dsl, inference]
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
# flashrt/fa4-cute-runtime
|
| 8 |
+
|
| 9 |
+
Forward-only FlashAttention-4 CuTe DSL runtime used by FlashRT's GROOT N1.7
|
| 10 |
+
and PI0.5 Thor pipelines. The source is vendored under the private `flashrt_fa4`
|
| 11 |
+
namespace and does not shadow an installed `flash_attn` package.
|
| 12 |
+
|
| 13 |
+
This package adds the dedicated Blackwell D256 2CTA forward path required by
|
| 14 |
+
PI0.5's 8-Q/1-KV-head PaliGemma encoder. The community FlashAttention-4 package
|
| 15 |
+
already covers the D48/D72/D128 family; it does not currently expose D256 on
|
| 16 |
+
SM100/SM110.
|
| 17 |
+
|
| 18 |
+
## Functions
|
| 19 |
+
|
| 20 |
+
- `flash_attn_func`
|
| 21 |
+
- `flash_attn_varlen_func`
|
| 22 |
+
- `forward_static`
|
| 23 |
+
|
| 24 |
+
```python
|
| 25 |
+
from kernels import get_kernel
|
| 26 |
+
|
| 27 |
+
fa4 = get_kernel("flashrt/fa4-cute-runtime", version=1)
|
| 28 |
+
out = fa4.flash_attn_func(q, k, v, causal=False)
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
The vendored forward wrapper returns `(out, lse)`; use `result[0]` when only
|
| 32 |
+
the attention output is needed.
|
| 33 |
+
|
| 34 |
+
For a CUDA Graph hot path, preallocate the output and use the allocation-free
|
| 35 |
+
entry point:
|
| 36 |
+
|
| 37 |
+
```python
|
| 38 |
+
out = torch.empty_like(q)
|
| 39 |
+
fa4.forward_static(q, k, v, out, causal=False)
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
For a padded fixed-shape graph, pass the valid K/V length as a CUDA int32
|
| 43 |
+
tensor. PI0.5 uses this form for its encoder cache:
|
| 44 |
+
|
| 45 |
+
```python
|
| 46 |
+
seqused_k = torch.tensor([valid_k], device="cuda", dtype=torch.int32)
|
| 47 |
+
fa4.forward_static(
|
| 48 |
+
q, k_padded, v_padded, out,
|
| 49 |
+
causal=False,
|
| 50 |
+
pack_gqa=True,
|
| 51 |
+
seqused_k=seqused_k,
|
| 52 |
+
)
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
Inputs follow FlashAttention's `(batch, sequence, heads, head_dim)` contract.
|
| 56 |
+
Qualified model profiles include D72 MHA and D256 GQA (8 Q heads / 1 KV head),
|
| 57 |
+
with both dense and `seqused_k` execution. This package targets SM100-family
|
| 58 |
+
Blackwell forward inference and requires
|
| 59 |
+
CUDA 13 plus `nvidia-cutlass-dsl` 4.4.x, 4.5.x, or 4.6.x. The wrapper selects Thor's
|
| 60 |
+
accepted architecture alias according to the installed DSL version.
|
| 61 |
+
|
| 62 |
+
This is an execution backend rather than a universal SDPA replacement. Select
|
| 63 |
+
it with model-shape profiling; the GROOT causal GQA profile benefits while some
|
| 64 |
+
short vision profiles remain faster on PyTorch SDPA.
|
README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# fa4-cute-runtime
|
| 2 |
+
|
| 3 |
+
Forward-only, SM100-family FlashAttention-4 CuTe DSL package used by FlashRT's
|
| 4 |
+
production GROOT N1.7 and PI0.5 Thor runtimes. In addition to the standard
|
| 5 |
+
D48/D72/D128 path, it includes the dedicated D256 2CTA forward kernel needed
|
| 6 |
+
by PI0.5's PaliGemma encoder.
|
| 7 |
+
|
| 8 |
+
The package exposes `flash_attn_func`, `flash_attn_varlen_func`, and the
|
| 9 |
+
preallocated `forward_static` CUDA Graph entry, including `seqused_k` for
|
| 10 |
+
fixed-shape padded K/V. It keeps the vendored
|
| 11 |
+
implementation in the private `flashrt_fa4` namespace and vendors the required
|
| 12 |
+
quack helper subset, so it cannot replace or mutate upstream `flash_attn`
|
| 13 |
+
imports.
|
| 14 |
+
|
| 15 |
+
See [CARD.md](CARD.md) for usage and [VALIDATION.md](VALIDATION.md) for the
|
| 16 |
+
release gate.
|
VALIDATION.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Validation
|
| 2 |
+
|
| 3 |
+
Required on NVIDIA Thor with CUDA 13 and CUTLASS DSL 4.4.x, 4.5.x, or 4.6.x:
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
python tests/test_fa4_cute_runtime.py --backend source --mode full
|
| 7 |
+
python tests/test_fa4_cute_runtime.py --backend installed --mode full \
|
| 8 |
+
--artifact build/torch-cuda
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
The gate covers non-causal and causal GQA, head dimensions 48/72/128, and the
|
| 12 |
+
PI0.5 D256 encoder family. D256 cases include dense lengths 320/968 and padded
|
| 13 |
+
`seqused_k` lengths 456/712 in a 968-token buffer. Every case reports
|
| 14 |
+
max/p99/mean/cosine metrics and requires bitwise CUDA Graph replay after JIT
|
| 15 |
+
warmup.
|
| 16 |
+
|
| 17 |
+
On 2026-08-06, the current source snapshot passed `8/8` rows on NVIDIA Thor
|
| 18 |
+
(SM110), PyTorch `2.13.0+cu130`, CUDA 13.0, and CUTLASS DSL 4.4.2. D48/D72/D128
|
| 19 |
+
and all four D256 rows passed; worst p99 absolute error was `0.000244` and
|
| 20 |
+
worst cosine was `0.99999982`. CUDA Graph replay was bitwise deterministic.
|
| 21 |
+
The torch-noarch final-layout import probe also passed from the isolated Thor
|
| 22 |
+
environment.
|
| 23 |
+
|
| 24 |
+
The same `8/8` source matrix passed on the same Thor with CUTLASS DSL 4.6.1.
|
| 25 |
+
The 4.6 compatibility aliases cover public API relocations only; D48/D72/D128
|
| 26 |
+
and D256 dense/seqused results matched the 4.4.2 gate.
|
| 27 |
+
|
| 28 |
+
Installed-artifact execution and performance must still be rerun after every
|
| 29 |
+
Hub rebuild. Source qualification alone is not a release artifact claim.
|
build/torch-cuda/__init__.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Forward-only FlashAttention-4 CuTe runtime for SM100-family GPUs."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
from cutlass import cute
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _version_tuple(value: str) -> tuple[int, int]:
|
| 13 |
+
fields = value.split(".")
|
| 14 |
+
return tuple(int(field) for field in fields[:2]) # type: ignore[return-value]
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
_DSL_VERSION = _version_tuple(str(getattr(cutlass, "__version__", "0.0")))
|
| 18 |
+
if not ((4, 4) <= _DSL_VERSION < (4, 7)):
|
| 19 |
+
raise RuntimeError(
|
| 20 |
+
"fa4-cute-runtime requires nvidia-cutlass-dsl 4.4.x, 4.5.x, or 4.6.x; "
|
| 21 |
+
f"found {getattr(cutlass, '__version__', 'unknown')}"
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
os.environ.setdefault(
|
| 25 |
+
"CUTE_DSL_ARCH", "sm_101a" if _DSL_VERSION >= (4, 5) else "sm_110a"
|
| 26 |
+
)
|
| 27 |
+
os.environ.setdefault("FLASH_ATTENTION_ARCH", "sm_100a")
|
| 28 |
+
|
| 29 |
+
# CUTLASS DSL 4.6 promoted these public types out of ``cute.core``. The
|
| 30 |
+
# vendored FA4 sources use the 4.4/4.5 annotation paths; restoring the aliases
|
| 31 |
+
# keeps those annotations importable without changing generated kernels.
|
| 32 |
+
if _DSL_VERSION >= (4, 6):
|
| 33 |
+
if not hasattr(cute.core, "ThrMma"):
|
| 34 |
+
cute.core.ThrMma = cute.ThrMma
|
| 35 |
+
if not hasattr(cute.core, "ThrCopy"):
|
| 36 |
+
cute.core.ThrCopy = cute.ThrCopy
|
| 37 |
+
if not hasattr(cute, "make_fragment"):
|
| 38 |
+
cute.make_fragment = cute.make_rmem_tensor
|
| 39 |
+
|
| 40 |
+
# Kernel Hub imports a noarch variant under a content-derived module name after
|
| 41 |
+
# flattening this package into the variant root. Preserve the public package
|
| 42 |
+
# alias used by the vendored FA4 absolute imports.
|
| 43 |
+
sys.modules.setdefault("fa4_cute_runtime", sys.modules[__name__])
|
| 44 |
+
|
| 45 |
+
# kernel-builder copies only the directory matching ``general.name`` for a
|
| 46 |
+
# torch-noarch package. The private vendor and quack subset therefore live
|
| 47 |
+
# inside this module and use package-qualified imports.
|
| 48 |
+
from .flashrt_fa4.cute import flash_attn_func, flash_attn_varlen_func # noqa: E402
|
| 49 |
+
from .flashrt_fa4.cute.interface_fwd_sm100 import _flash_attn_fwd # noqa: E402
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def forward_static(
|
| 53 |
+
q,
|
| 54 |
+
k,
|
| 55 |
+
v,
|
| 56 |
+
out,
|
| 57 |
+
*,
|
| 58 |
+
softmax_scale=None,
|
| 59 |
+
causal=False,
|
| 60 |
+
pack_gqa=None,
|
| 61 |
+
seqused_k=None,
|
| 62 |
+
):
|
| 63 |
+
"""Run forward attention into a caller-owned output tensor."""
|
| 64 |
+
result, _ = _flash_attn_fwd(
|
| 65 |
+
q,
|
| 66 |
+
k,
|
| 67 |
+
v,
|
| 68 |
+
softmax_scale=softmax_scale,
|
| 69 |
+
causal=causal,
|
| 70 |
+
pack_gqa=pack_gqa,
|
| 71 |
+
seqused_k=seqused_k,
|
| 72 |
+
out=out,
|
| 73 |
+
)
|
| 74 |
+
return result
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
__all__ = ["flash_attn_func", "flash_attn_varlen_func", "forward_static"]
|
build/torch-cuda/_ops.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
_OPS_NAME = '_fa4_cute_runtime_cuda_8aeea60'
|
| 4 |
+
ops = getattr(torch.ops, _OPS_NAME)
|
| 5 |
+
|
| 6 |
+
def add_op_namespace_prefix(op_name: str) -> str:
|
| 7 |
+
return f'{_OPS_NAME}::{op_name}'
|
build/torch-cuda/flashrt_fa4/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pkgutil import extend_path
|
| 2 |
+
__path__ = extend_path(__path__, __name__)
|
build/torch-cuda/flashrt_fa4/cute/.flake8
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[flake8]
|
| 2 |
+
max-line-length = 100
|
| 3 |
+
# W503: line break before binary operator
|
| 4 |
+
ignore = E731, E741, F841, W503
|
build/torch-cuda/flashrt_fa4/cute/AUTHORS
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Tri Dao
|
| 2 |
+
Jay Shah
|
| 3 |
+
Ted Zadouri
|
| 4 |
+
Markus Hoehnerbach
|
| 5 |
+
Vijay Thakkar
|
| 6 |
+
Timmy Liu
|
| 7 |
+
Driss Guessous
|
| 8 |
+
Reuben Stern
|
build/torch-cuda/flashrt_fa4/cute/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BSD 3-Clause License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2022, the respective contributors, as shown by the AUTHORS file.
|
| 4 |
+
All rights reserved.
|
| 5 |
+
|
| 6 |
+
Redistribution and use in source and binary forms, with or without
|
| 7 |
+
modification, are permitted provided that the following conditions are met:
|
| 8 |
+
|
| 9 |
+
* Redistributions of source code must retain the above copyright notice, this
|
| 10 |
+
list of conditions and the following disclaimer.
|
| 11 |
+
|
| 12 |
+
* Redistributions in binary form must reproduce the above copyright notice,
|
| 13 |
+
this list of conditions and the following disclaimer in the documentation
|
| 14 |
+
and/or other materials provided with the distribution.
|
| 15 |
+
|
| 16 |
+
* Neither the name of the copyright holder nor the names of its
|
| 17 |
+
contributors may be used to endorse or promote products derived from
|
| 18 |
+
this software without specific prior written permission.
|
| 19 |
+
|
| 20 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 21 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 22 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 23 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 24 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 25 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 26 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 27 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 28 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 29 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
build/torch-cuda/flashrt_fa4/cute/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Flash Attention CUTE (CUDA Template Engine) implementation.
|
| 2 |
+
|
| 3 |
+
FlashRT vendors a forward / SM100-only subset of FlashAttention-4 for Thor
|
| 4 |
+
(sm_110). The public entry point lives in ``interface_fwd_sm100`` instead of
|
| 5 |
+
the upstream ``interface`` module so that importing this package does NOT pull
|
| 6 |
+
in backward, SM80/SM90/SM120, or MLA kernels. The HD256 2CTA forward kernel is
|
| 7 |
+
kept for Pi0.5 encoder attention; see ``VENDOR.md``.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from importlib.metadata import PackageNotFoundError, version
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
__version__ = version("fa4")
|
| 14 |
+
except PackageNotFoundError:
|
| 15 |
+
__version__ = "0.0.0"
|
| 16 |
+
|
| 17 |
+
from .interface_fwd_sm100 import (
|
| 18 |
+
flash_attn_func,
|
| 19 |
+
flash_attn_varlen_func,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
__all__ = [
|
| 23 |
+
"flash_attn_func",
|
| 24 |
+
"flash_attn_varlen_func",
|
| 25 |
+
]
|
build/torch-cuda/flashrt_fa4/cute/ampere_helpers.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
from typing import Type, Callable, Optional
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def get_smem_layout_atom(dtype: Type[cutlass.Numeric], k_dim: int) -> cute.ComposedLayout:
|
| 9 |
+
dtype_byte = cutlass.const_expr(dtype.width // 8)
|
| 10 |
+
bytes_per_row = cutlass.const_expr(k_dim * dtype_byte)
|
| 11 |
+
smem_k_block_size = (
|
| 12 |
+
cutlass.const_expr(
|
| 13 |
+
128
|
| 14 |
+
if bytes_per_row % 128 == 0
|
| 15 |
+
else (64 if bytes_per_row % 64 == 0 else (32 if bytes_per_row % 32 == 0 else 16))
|
| 16 |
+
)
|
| 17 |
+
// dtype_byte
|
| 18 |
+
)
|
| 19 |
+
swizzle_bits = (
|
| 20 |
+
4
|
| 21 |
+
if smem_k_block_size == 128
|
| 22 |
+
else (3 if smem_k_block_size == 64 else (2 if smem_k_block_size == 32 else 1))
|
| 23 |
+
)
|
| 24 |
+
swizzle_base = 2 if dtype_byte == 4 else (3 if dtype_byte == 2 else 4)
|
| 25 |
+
return cute.make_composed_layout(
|
| 26 |
+
cute.make_swizzle(swizzle_bits, swizzle_base, swizzle_base),
|
| 27 |
+
0,
|
| 28 |
+
cute.make_ordered_layout(
|
| 29 |
+
(8 if cutlass.const_expr(k_dim % 32 == 0) else 16, smem_k_block_size), order=(1, 0)
|
| 30 |
+
),
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@cute.jit
|
| 35 |
+
def gemm(
|
| 36 |
+
tiled_mma: cute.TiledMma,
|
| 37 |
+
acc: cute.Tensor,
|
| 38 |
+
tCrA: cute.Tensor,
|
| 39 |
+
tCrB: cute.Tensor,
|
| 40 |
+
tCsA: cute.Tensor,
|
| 41 |
+
tCsB: cute.Tensor,
|
| 42 |
+
smem_thr_copy_A: cute.TiledCopy,
|
| 43 |
+
smem_thr_copy_B: cute.TiledCopy,
|
| 44 |
+
hook_fn: Optional[Callable] = None,
|
| 45 |
+
A_in_regs: cutlass.Constexpr[bool] = False,
|
| 46 |
+
B_in_regs: cutlass.Constexpr[bool] = False,
|
| 47 |
+
swap_AB: cutlass.Constexpr[bool] = False,
|
| 48 |
+
) -> None:
|
| 49 |
+
if cutlass.const_expr(swap_AB):
|
| 50 |
+
gemm(
|
| 51 |
+
tiled_mma,
|
| 52 |
+
acc,
|
| 53 |
+
tCrB,
|
| 54 |
+
tCrA,
|
| 55 |
+
tCsB,
|
| 56 |
+
tCsA,
|
| 57 |
+
smem_thr_copy_B,
|
| 58 |
+
smem_thr_copy_A,
|
| 59 |
+
hook_fn,
|
| 60 |
+
A_in_regs=B_in_regs,
|
| 61 |
+
B_in_regs=A_in_regs,
|
| 62 |
+
swap_AB=False,
|
| 63 |
+
)
|
| 64 |
+
else:
|
| 65 |
+
tCrA_copy_view = smem_thr_copy_A.retile(tCrA)
|
| 66 |
+
tCrB_copy_view = smem_thr_copy_B.retile(tCrB)
|
| 67 |
+
if cutlass.const_expr(not A_in_regs):
|
| 68 |
+
cute.copy(smem_thr_copy_A, tCsA[None, None, 0], tCrA_copy_view[None, None, 0])
|
| 69 |
+
if cutlass.const_expr(not B_in_regs):
|
| 70 |
+
cute.copy(smem_thr_copy_B, tCsB[None, None, 0], tCrB_copy_view[None, None, 0])
|
| 71 |
+
for k in cutlass.range_constexpr(cute.size(tCsA.shape[2])):
|
| 72 |
+
if k < cute.size(tCsA.shape[2]) - 1:
|
| 73 |
+
if cutlass.const_expr(not A_in_regs):
|
| 74 |
+
cute.copy(
|
| 75 |
+
smem_thr_copy_A, tCsA[None, None, k + 1], tCrA_copy_view[None, None, k + 1]
|
| 76 |
+
)
|
| 77 |
+
if cutlass.const_expr(not B_in_regs):
|
| 78 |
+
cute.copy(
|
| 79 |
+
smem_thr_copy_B, tCsB[None, None, k + 1], tCrB_copy_view[None, None, k + 1]
|
| 80 |
+
)
|
| 81 |
+
cute.gemm(tiled_mma, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 82 |
+
if cutlass.const_expr(k == 0 and hook_fn is not None):
|
| 83 |
+
hook_fn()
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
@cute.jit
|
| 87 |
+
def gemm_rs(
|
| 88 |
+
tiled_mma: cute.TiledMma,
|
| 89 |
+
acc: cute.Tensor,
|
| 90 |
+
tCrA: cute.Tensor,
|
| 91 |
+
tCrB: cute.Tensor,
|
| 92 |
+
tCsB: cute.Tensor,
|
| 93 |
+
smem_thr_copy_B: cute.TiledCopy,
|
| 94 |
+
hook_fn: Optional[Callable] = None,
|
| 95 |
+
) -> None:
|
| 96 |
+
tCrB_copy_view = smem_thr_copy_B.retile(tCrB)
|
| 97 |
+
cute.copy(smem_thr_copy_B, tCsB[None, None, 0], tCrB_copy_view[None, None, 0])
|
| 98 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 99 |
+
if cutlass.const_expr(k < cute.size(tCrA.shape[2]) - 1):
|
| 100 |
+
cute.copy(smem_thr_copy_B, tCsB[None, None, k + 1], tCrB_copy_view[None, None, k + 1])
|
| 101 |
+
cute.gemm(tiled_mma, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 102 |
+
if cutlass.const_expr(k == 0 and hook_fn is not None):
|
| 103 |
+
hook_fn()
|
build/torch-cuda/flashrt_fa4/cute/barrier.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cutlass
|
| 2 |
+
import cutlass.cute as cute
|
| 3 |
+
from cutlass import Int32
|
| 4 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 5 |
+
from cutlass._mlir.dialects import llvm
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@dsl_user_op
|
| 9 |
+
def ld_acquire(lock_ptr: cute.Pointer, *, loc=None, ip=None) -> cutlass.Int32:
|
| 10 |
+
lock_ptr_i64 = lock_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 11 |
+
state = llvm.inline_asm(
|
| 12 |
+
T.i32(),
|
| 13 |
+
[lock_ptr_i64],
|
| 14 |
+
"ld.global.acquire.gpu.b32 $0, [$1];",
|
| 15 |
+
"=r,l",
|
| 16 |
+
has_side_effects=True,
|
| 17 |
+
is_align_stack=False,
|
| 18 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 19 |
+
)
|
| 20 |
+
return cutlass.Int32(state)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@dsl_user_op
|
| 24 |
+
def red_relaxed(
|
| 25 |
+
lock_ptr: cute.Pointer, val: cutlass.Constexpr[Int32], *, loc=None, ip=None
|
| 26 |
+
) -> None:
|
| 27 |
+
lock_ptr_i64 = lock_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 28 |
+
llvm.inline_asm(
|
| 29 |
+
None,
|
| 30 |
+
[lock_ptr_i64, Int32(val).ir_value(loc=loc, ip=ip)],
|
| 31 |
+
"red.relaxed.gpu.global.add.s32 [$0], $1;",
|
| 32 |
+
"l,r",
|
| 33 |
+
has_side_effects=True,
|
| 34 |
+
is_align_stack=False,
|
| 35 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@dsl_user_op
|
| 40 |
+
def red_release(
|
| 41 |
+
lock_ptr: cute.Pointer, val: cutlass.Constexpr[Int32], *, loc=None, ip=None
|
| 42 |
+
) -> None:
|
| 43 |
+
lock_ptr_i64 = lock_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 44 |
+
llvm.inline_asm(
|
| 45 |
+
None,
|
| 46 |
+
[lock_ptr_i64, Int32(val).ir_value(loc=loc, ip=ip)],
|
| 47 |
+
"red.release.gpu.global.add.s32 [$0], $1;",
|
| 48 |
+
"l,r",
|
| 49 |
+
has_side_effects=True,
|
| 50 |
+
is_align_stack=False,
|
| 51 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@cute.jit
|
| 56 |
+
def wait_eq(lock_ptr: cute.Pointer, thread_idx: int | Int32, flag_offset: int, val: Int32) -> None:
|
| 57 |
+
flag_ptr = lock_ptr + flag_offset
|
| 58 |
+
if thread_idx == 0:
|
| 59 |
+
read_val = Int32(0)
|
| 60 |
+
while read_val != val:
|
| 61 |
+
read_val = ld_acquire(flag_ptr)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@cute.jit
|
| 65 |
+
def arrive_inc(
|
| 66 |
+
lock_ptr: cute.Pointer, thread_idx: int | Int32, flag_offset: int, val: cutlass.Constexpr[Int32]
|
| 67 |
+
) -> None:
|
| 68 |
+
flag_ptr = lock_ptr + flag_offset
|
| 69 |
+
if thread_idx == 0:
|
| 70 |
+
red_release(flag_ptr, val)
|
| 71 |
+
# red_relaxed(flag_ptr, val)
|
build/torch-cuda/flashrt_fa4/cute/blackwell_helpers.py
ADDED
|
@@ -0,0 +1,1113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
from typing import Optional, Tuple
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
from cutlass import Int32, Boolean, const_expr
|
| 7 |
+
from cutlass.cute.nvgpu import tcgen05
|
| 8 |
+
from cutlass._mlir.dialects import llvm
|
| 9 |
+
|
| 10 |
+
import fa4_cute_runtime.flashrt_fa4.cute.mma_sm100_desc as sm100_desc
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _tcgen05_mma_kind(op: cute.nvgpu.tcgen05.mma.MmaOp) -> str:
|
| 14 |
+
if isinstance(op, tcgen05.mma.MmaF16BF16Op):
|
| 15 |
+
return "f16"
|
| 16 |
+
if isinstance(op, tcgen05.mma.MmaTF32Op):
|
| 17 |
+
return "tf32"
|
| 18 |
+
if isinstance(op, tcgen05.mma.MmaI8Op):
|
| 19 |
+
return "i8"
|
| 20 |
+
if isinstance(op, tcgen05.mma.MmaFP8Op):
|
| 21 |
+
return "f8f6f4"
|
| 22 |
+
if isinstance(op, tcgen05.mma.MmaMXF8Op):
|
| 23 |
+
return "mxf8f6f4"
|
| 24 |
+
if isinstance(op, tcgen05.mma.MmaMXF4Op):
|
| 25 |
+
return "mxf4"
|
| 26 |
+
if isinstance(op, tcgen05.mma.MmaMXF4NVF4Op):
|
| 27 |
+
return "mxf4nvf4"
|
| 28 |
+
raise TypeError(f"Unsupported tcgen05 MMA op kind: {type(op).__name__}")
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@cute.jit
|
| 32 |
+
def gemm_w_idx(
|
| 33 |
+
tiled_mma: cute.TiledMma,
|
| 34 |
+
acc: cute.Tensor,
|
| 35 |
+
tCrA: cute.Tensor,
|
| 36 |
+
tCrB: cute.Tensor,
|
| 37 |
+
A_idx: Optional[Int32] = None,
|
| 38 |
+
B_idx: Optional[Int32] = None,
|
| 39 |
+
zero_init: bool | Boolean = False,
|
| 40 |
+
swap_AB: bool = False,
|
| 41 |
+
num_unroll_groups: int = 1,
|
| 42 |
+
) -> None:
|
| 43 |
+
if const_expr(swap_AB):
|
| 44 |
+
return gemm_w_idx(
|
| 45 |
+
tiled_mma, acc, tCrB, tCrA, B_idx, A_idx, zero_init=zero_init, swap_AB=False
|
| 46 |
+
)
|
| 47 |
+
else:
|
| 48 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 49 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 50 |
+
|
| 51 |
+
mma_atom = cute.make_mma_atom(tiled_mma.op)
|
| 52 |
+
for k in cutlass.range(
|
| 53 |
+
cute.size(tCrA.shape[2]), unroll=cute.size(tCrA.shape[2]) // num_unroll_groups
|
| 54 |
+
):
|
| 55 |
+
mma_atom.set(tcgen05.Field.ACCUMULATE, not zero_init or k != 0)
|
| 56 |
+
cute.gemm(mma_atom, acc, rA[None, None, k], rB[None, None, k], acc)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@cute.jit
|
| 60 |
+
def gemm_ptx_w_idx(
|
| 61 |
+
tiled_mma: cute.TiledMma,
|
| 62 |
+
acc: cute.Tensor,
|
| 63 |
+
tCrA: cute.Tensor,
|
| 64 |
+
tCrB: cute.Tensor,
|
| 65 |
+
sA: Optional[cute.Tensor],
|
| 66 |
+
sB: cute.Tensor,
|
| 67 |
+
A_idx: Optional[Int32] = None,
|
| 68 |
+
B_idx: Optional[Int32] = None,
|
| 69 |
+
zero_init: bool | Boolean = False,
|
| 70 |
+
cta_group: int = 1,
|
| 71 |
+
**kwargs,
|
| 72 |
+
) -> None:
|
| 73 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 74 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 75 |
+
sA_cur = None
|
| 76 |
+
if const_expr(sA is not None):
|
| 77 |
+
sA_cur = sA if const_expr(A_idx is None) else sA[None, None, None, A_idx]
|
| 78 |
+
sB_cur = sB if const_expr(B_idx is None) else sB[None, None, None, B_idx]
|
| 79 |
+
mma_atom = cute.make_mma_atom(tiled_mma.op)
|
| 80 |
+
acc_tmem_addr = acc.iterator.toint()
|
| 81 |
+
gemm_ptx_partial(
|
| 82 |
+
mma_atom.op,
|
| 83 |
+
acc_tmem_addr,
|
| 84 |
+
rA,
|
| 85 |
+
rB,
|
| 86 |
+
sA_cur,
|
| 87 |
+
sB_cur,
|
| 88 |
+
zero_init=zero_init,
|
| 89 |
+
cta_group=cta_group,
|
| 90 |
+
**kwargs,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
@cute.jit
|
| 95 |
+
def gemm(
|
| 96 |
+
tiled_mma: cute.TiledMma,
|
| 97 |
+
acc: cute.Tensor,
|
| 98 |
+
tCrA: cute.Tensor,
|
| 99 |
+
tCrB: cute.Tensor,
|
| 100 |
+
zero_init: bool | Boolean = False,
|
| 101 |
+
) -> None:
|
| 102 |
+
mma_atom = cute.make_mma_atom(tiled_mma.op)
|
| 103 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 104 |
+
mma_atom.set(tcgen05.Field.ACCUMULATE, not zero_init or k != 0)
|
| 105 |
+
cute.gemm(mma_atom, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def i64_to_i32x2(i: int) -> Tuple[int, int]:
|
| 109 |
+
"""Convert a 64-bit integer to a tuple of two 32-bit integers."""
|
| 110 |
+
return i & 0xFFFF_FFFF, (i >> 32) & 0xFFFF_FFFF
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@cute.jit
|
| 114 |
+
def gemm_ptx(
|
| 115 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 116 |
+
acc: cute.Tensor,
|
| 117 |
+
tCrA: cute.Tensor,
|
| 118 |
+
tCrB: cute.Tensor,
|
| 119 |
+
sA: Optional[cute.Tensor],
|
| 120 |
+
sB: cute.Tensor,
|
| 121 |
+
zero_init: bool | Boolean = False,
|
| 122 |
+
) -> None:
|
| 123 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 124 |
+
if const_expr(not is_ts):
|
| 125 |
+
assert sA is not None, "sA must be provided when a_src is not TMEM"
|
| 126 |
+
sA_layout = sA.layout if sA is not None else None
|
| 127 |
+
sB_layout = sB.layout
|
| 128 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 129 |
+
kind = _tcgen05_mma_kind(op)
|
| 130 |
+
if const_expr(not is_ts):
|
| 131 |
+
sA_swizzle = sA.iterator.type.swizzle_type
|
| 132 |
+
smem_desc_base_a: int = const_expr(
|
| 133 |
+
sm100_desc.make_smem_desc_base(
|
| 134 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 135 |
+
sA_swizzle,
|
| 136 |
+
sm100_desc.Major.K
|
| 137 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 138 |
+
else sm100_desc.Major.MN,
|
| 139 |
+
)
|
| 140 |
+
)
|
| 141 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 142 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 143 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 144 |
+
else:
|
| 145 |
+
smem_desc_base_a = None
|
| 146 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 147 |
+
sB_swizzle = sB.iterator.type.swizzle_type
|
| 148 |
+
smem_desc_base_b: int = const_expr(
|
| 149 |
+
sm100_desc.make_smem_desc_base(
|
| 150 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 151 |
+
sB_swizzle,
|
| 152 |
+
sm100_desc.Major.K
|
| 153 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 154 |
+
else sm100_desc.Major.MN,
|
| 155 |
+
)
|
| 156 |
+
)
|
| 157 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 158 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 159 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 160 |
+
|
| 161 |
+
if const_expr(not is_ts):
|
| 162 |
+
smem_desc_start_a_lo = Int32(smem_desc_base_a_lo) | sm100_desc.make_smem_desc_start_addr(
|
| 163 |
+
sA[None, None, 0].iterator
|
| 164 |
+
)
|
| 165 |
+
else:
|
| 166 |
+
smem_desc_start_a_lo = None
|
| 167 |
+
smem_desc_start_b_lo = Int32(smem_desc_base_b_lo) | sm100_desc.make_smem_desc_start_addr(
|
| 168 |
+
sB[None, None, 0].iterator
|
| 169 |
+
)
|
| 170 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 171 |
+
if const_expr(not is_ts):
|
| 172 |
+
smem_desc_a_lo = smem_desc_start_a_lo + (
|
| 173 |
+
(cute.crd2idx((0, 0, k), sA_layout) * sA.element_type.width // 8) >> 4
|
| 174 |
+
)
|
| 175 |
+
smem_desc_b_lo = smem_desc_start_b_lo + (
|
| 176 |
+
(cute.crd2idx((0, 0, k), sB_layout) * sB.element_type.width // 8) >> 4
|
| 177 |
+
)
|
| 178 |
+
# with cute.arch.elect_one():
|
| 179 |
+
# cute.printf("smem_desc_a_lo = {}, smem_desc_b_lo = {}", smem_desc_a_lo, smem_desc_b_lo)
|
| 180 |
+
# cute.printf("smem_desc_a_lo_correct = {}, smem_desc_b_lo_correct = {}", smem_desc_a_lo_correct, smem_desc_b_lo_correct)
|
| 181 |
+
with cute.arch.elect_one():
|
| 182 |
+
if const_expr(not is_ts):
|
| 183 |
+
llvm.inline_asm(
|
| 184 |
+
None,
|
| 185 |
+
[
|
| 186 |
+
acc.iterator.toint().ir_value(),
|
| 187 |
+
smem_desc_a_lo.ir_value(),
|
| 188 |
+
smem_desc_b_lo.ir_value(),
|
| 189 |
+
Int32(not zero_init or k != 0).ir_value(),
|
| 190 |
+
],
|
| 191 |
+
"{\n\t"
|
| 192 |
+
".reg .pred p;\n\t"
|
| 193 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 194 |
+
".reg .b32 idesc;\n\t"
|
| 195 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 196 |
+
f"mov.b64 smem_desc_a, {{$1, {hex(smem_desc_a_hi)}}};\n\t"
|
| 197 |
+
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 198 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 199 |
+
f"tcgen05.mma.cta_group::1.kind::{kind} [$0], smem_desc_a, smem_desc_b, idesc, p;\n\t"
|
| 200 |
+
"}\n",
|
| 201 |
+
"r,r,r,r",
|
| 202 |
+
has_side_effects=True,
|
| 203 |
+
is_align_stack=False,
|
| 204 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 205 |
+
)
|
| 206 |
+
else:
|
| 207 |
+
llvm.inline_asm(
|
| 208 |
+
None,
|
| 209 |
+
[
|
| 210 |
+
acc.iterator.toint().ir_value(),
|
| 211 |
+
tCrA[None, None, k].iterator.toint().ir_value(),
|
| 212 |
+
smem_desc_b_lo.ir_value(),
|
| 213 |
+
Int32(not zero_init or k != 0).ir_value(),
|
| 214 |
+
],
|
| 215 |
+
"{\n\t"
|
| 216 |
+
".reg .pred p;\n\t"
|
| 217 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 218 |
+
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 219 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 220 |
+
f"tcgen05.mma.cta_group::1.kind::{kind} [$0], [$1], smem_desc_b, {hex(idesc)}, p;\n\t"
|
| 221 |
+
"}\n",
|
| 222 |
+
"r,r,r,r",
|
| 223 |
+
has_side_effects=True,
|
| 224 |
+
is_align_stack=False,
|
| 225 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
@cute.jit
|
| 230 |
+
def gemm_ptx_loop(
|
| 231 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 232 |
+
acc: cute.Tensor,
|
| 233 |
+
tCrA: cute.Tensor,
|
| 234 |
+
tCrB: cute.Tensor,
|
| 235 |
+
sA: Optional[cute.Tensor],
|
| 236 |
+
sB: cute.Tensor,
|
| 237 |
+
zero_init: bool | Boolean = False,
|
| 238 |
+
) -> None:
|
| 239 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 240 |
+
if const_expr(not is_ts):
|
| 241 |
+
assert sA is not None, "sA must be provided when a_src is not TMEM"
|
| 242 |
+
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 243 |
+
sB_layout = sB.layout
|
| 244 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 245 |
+
kind = _tcgen05_mma_kind(op)
|
| 246 |
+
if const_expr(not is_ts):
|
| 247 |
+
sA_swizzle = sA.iterator.type.swizzle_type
|
| 248 |
+
smem_desc_base_a: int = const_expr(
|
| 249 |
+
sm100_desc.make_smem_desc_base(
|
| 250 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 251 |
+
sA_swizzle,
|
| 252 |
+
sm100_desc.Major.K
|
| 253 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 254 |
+
else sm100_desc.Major.MN,
|
| 255 |
+
)
|
| 256 |
+
)
|
| 257 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 258 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 259 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 260 |
+
else:
|
| 261 |
+
smem_desc_base_a = None
|
| 262 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 263 |
+
sB_swizzle = sB.iterator.type.swizzle_type
|
| 264 |
+
smem_desc_base_b: int = const_expr(
|
| 265 |
+
sm100_desc.make_smem_desc_base(
|
| 266 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 267 |
+
sB_swizzle,
|
| 268 |
+
sm100_desc.Major.K
|
| 269 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 270 |
+
else sm100_desc.Major.MN,
|
| 271 |
+
)
|
| 272 |
+
)
|
| 273 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 274 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 275 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 276 |
+
|
| 277 |
+
if const_expr(not is_ts):
|
| 278 |
+
offset_a = [
|
| 279 |
+
(cute.crd2idx((0, 0, k), sA_layout) * sA.element_type.width // 8) >> 4
|
| 280 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2]))
|
| 281 |
+
]
|
| 282 |
+
else:
|
| 283 |
+
offset_a = [
|
| 284 |
+
cute.crd2idx((0, 0, k), sA_layout) * op.a_dtype.width // 32
|
| 285 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2]))
|
| 286 |
+
]
|
| 287 |
+
offset_a_diff = [
|
| 288 |
+
offset_a[k] - offset_a[k - 1] for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 289 |
+
]
|
| 290 |
+
offset_b = [
|
| 291 |
+
(cute.crd2idx((0, 0, k), sB_layout) * sB.element_type.width // 8) >> 4
|
| 292 |
+
for k in cutlass.range_constexpr(cute.size(tCrB.shape[2]))
|
| 293 |
+
]
|
| 294 |
+
offset_b_diff = [
|
| 295 |
+
offset_b[k] - offset_b[k - 1] for k in cutlass.range_constexpr(1, cute.size(tCrB.shape[2]))
|
| 296 |
+
]
|
| 297 |
+
|
| 298 |
+
if const_expr(not is_ts):
|
| 299 |
+
smem_desc_start_a_lo = Int32(
|
| 300 |
+
smem_desc_base_a_lo | sm100_desc.make_smem_desc_start_addr(sA[None, None, 0].iterator)
|
| 301 |
+
)
|
| 302 |
+
else:
|
| 303 |
+
smem_desc_start_a_lo = None
|
| 304 |
+
smem_desc_start_b_lo = Int32(
|
| 305 |
+
smem_desc_base_b_lo | sm100_desc.make_smem_desc_start_addr(sB[None, None, 0].iterator)
|
| 306 |
+
)
|
| 307 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 308 |
+
if const_expr(not is_ts):
|
| 309 |
+
llvm.inline_asm(
|
| 310 |
+
None,
|
| 311 |
+
[
|
| 312 |
+
acc.iterator.toint().ir_value(),
|
| 313 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value(),
|
| 314 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 315 |
+
Int32(not zero_init).ir_value(),
|
| 316 |
+
],
|
| 317 |
+
"{\n\t"
|
| 318 |
+
".reg .pred leader_thread;\n\t"
|
| 319 |
+
".reg .pred p;\n\t"
|
| 320 |
+
".reg .b32 idesc;\n\t"
|
| 321 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 322 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 323 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 324 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 325 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 326 |
+
"mov.b32 smem_desc_a_lo, $1;\n\t"
|
| 327 |
+
"mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 328 |
+
f"mov.b32 smem_desc_a_hi, {hex(smem_desc_a_hi)};\n\t"
|
| 329 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 330 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 331 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 332 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 333 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 334 |
+
+ "".join(
|
| 335 |
+
(
|
| 336 |
+
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 337 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 338 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 339 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 340 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 341 |
+
)
|
| 342 |
+
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 343 |
+
)
|
| 344 |
+
+ "}\n",
|
| 345 |
+
"r,r,r,r",
|
| 346 |
+
has_side_effects=True,
|
| 347 |
+
is_align_stack=False,
|
| 348 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 349 |
+
)
|
| 350 |
+
else:
|
| 351 |
+
llvm.inline_asm(
|
| 352 |
+
None,
|
| 353 |
+
[
|
| 354 |
+
acc.iterator.toint().ir_value(),
|
| 355 |
+
Int32(tCrA[None, None, 0].iterator.toint()).ir_value(),
|
| 356 |
+
Int32(smem_desc_start_b_lo).ir_value(),
|
| 357 |
+
Int32(not zero_init).ir_value(),
|
| 358 |
+
],
|
| 359 |
+
"{\n\t"
|
| 360 |
+
".reg .pred leader_thread;\n\t"
|
| 361 |
+
".reg .pred p;\n\t"
|
| 362 |
+
".reg .b32 idesc;\n\t"
|
| 363 |
+
".reg .b32 tmem_a;\n\t"
|
| 364 |
+
".reg .b32 smem_desc_b_lo;\n\t"
|
| 365 |
+
".reg .b32 smem_desc_b_hi;\n\t"
|
| 366 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 367 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 368 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 369 |
+
"mov.b32 tmem_a, $1;\n\t"
|
| 370 |
+
"mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 371 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 372 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 373 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 374 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 375 |
+
+ "".join(
|
| 376 |
+
(
|
| 377 |
+
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 378 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 379 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 380 |
+
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 381 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 382 |
+
)
|
| 383 |
+
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 384 |
+
)
|
| 385 |
+
+ "}\n",
|
| 386 |
+
"r,r,r,r",
|
| 387 |
+
has_side_effects=True,
|
| 388 |
+
is_align_stack=False,
|
| 389 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
@cute.jit
|
| 394 |
+
def gemm_ptx_partial(
|
| 395 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 396 |
+
acc_tmem_addr: Int32,
|
| 397 |
+
tCrA: cute.Tensor,
|
| 398 |
+
tCrB: cute.Tensor,
|
| 399 |
+
sA: Optional[cute.Tensor],
|
| 400 |
+
sB: cute.Tensor,
|
| 401 |
+
mbar_ptr: Optional[cutlass.Pointer] = None,
|
| 402 |
+
mbar_phase: Optional[Int32] = None,
|
| 403 |
+
split_arrive: Optional[int] = None,
|
| 404 |
+
zero_init: bool | Boolean = False,
|
| 405 |
+
# sA_offset: Int32 = 0,
|
| 406 |
+
# acc_offset: Int32 = 0,
|
| 407 |
+
tA_addr: Optional[Int32] = None,
|
| 408 |
+
cta_group: int = 1,
|
| 409 |
+
) -> None:
|
| 410 |
+
# acc_tmem_addr += acc_offset
|
| 411 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 412 |
+
if const_expr(not is_ts):
|
| 413 |
+
assert sA is not None, "sA must be provided when a_src is not TMEM"
|
| 414 |
+
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 415 |
+
sB_layout = sB.layout
|
| 416 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 417 |
+
kind = _tcgen05_mma_kind(op)
|
| 418 |
+
if const_expr(not is_ts):
|
| 419 |
+
sA_swizzle = sA.iterator.type.swizzle_type
|
| 420 |
+
smem_desc_base_a: int = const_expr(
|
| 421 |
+
sm100_desc.make_smem_desc_base(
|
| 422 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 423 |
+
sA_swizzle,
|
| 424 |
+
sm100_desc.Major.K
|
| 425 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 426 |
+
else sm100_desc.Major.MN,
|
| 427 |
+
)
|
| 428 |
+
)
|
| 429 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 430 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 431 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 432 |
+
else:
|
| 433 |
+
smem_desc_base_a = None
|
| 434 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 435 |
+
sB_swizzle = sB.iterator.type.swizzle_type
|
| 436 |
+
smem_desc_base_b: int = const_expr(
|
| 437 |
+
sm100_desc.make_smem_desc_base(
|
| 438 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 439 |
+
sB_swizzle,
|
| 440 |
+
sm100_desc.Major.K
|
| 441 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 442 |
+
else sm100_desc.Major.MN,
|
| 443 |
+
)
|
| 444 |
+
)
|
| 445 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 446 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 447 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 448 |
+
|
| 449 |
+
tCrA_layout = (
|
| 450 |
+
tCrA.layout
|
| 451 |
+
if const_expr(not is_ts)
|
| 452 |
+
else cute.recast_layout(32, tCrA.element_type.width, tCrA.layout)
|
| 453 |
+
)
|
| 454 |
+
offset_a = [cute.crd2idx((0, 0, k), tCrA_layout) for k in range(cute.size(tCrA.shape[2]))]
|
| 455 |
+
offset_a_diff = [offset_a[k] - offset_a[k - 1] for k in range(1, cute.size(tCrA.shape[2]))]
|
| 456 |
+
offset_b = [cute.crd2idx((0, 0, k), tCrB.layout) for k in range(cute.size(tCrB.shape[2]))]
|
| 457 |
+
offset_b_diff = [offset_b[k] - offset_b[k - 1] for k in range(1, cute.size(tCrB.shape[2]))]
|
| 458 |
+
|
| 459 |
+
if const_expr(not is_ts):
|
| 460 |
+
smem_desc_start_a_lo = Int32(
|
| 461 |
+
smem_desc_base_a_lo | sm100_desc.make_smem_desc_start_addr(sA[None, None, 0].iterator)
|
| 462 |
+
)
|
| 463 |
+
# ) + sA_offset
|
| 464 |
+
else:
|
| 465 |
+
smem_desc_start_a_lo = None
|
| 466 |
+
smem_desc_start_b_lo = Int32(
|
| 467 |
+
smem_desc_base_b_lo | sm100_desc.make_smem_desc_start_addr(sB[None, None, 0].iterator)
|
| 468 |
+
)
|
| 469 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 470 |
+
if const_expr(not is_ts):
|
| 471 |
+
assert mbar_ptr is None, "mbar_ptr must be None when a_src is not TMEM"
|
| 472 |
+
llvm.inline_asm(
|
| 473 |
+
None,
|
| 474 |
+
[
|
| 475 |
+
# acc.iterator.toint().ir_value(),
|
| 476 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value(),
|
| 477 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 478 |
+
Int32(not zero_init).ir_value(),
|
| 479 |
+
Int32(cute.arch.make_warp_uniform(acc_tmem_addr)).ir_value(),
|
| 480 |
+
],
|
| 481 |
+
"{\n\t"
|
| 482 |
+
".reg .pred leader_thread;\n\t"
|
| 483 |
+
".reg .pred p;\n\t"
|
| 484 |
+
".reg .b32 idesc;\n\t"
|
| 485 |
+
".reg .b32 tmem_acc;\n\t"
|
| 486 |
+
".reg .b32 smem_desc_a_lo_start, smem_desc_b_lo_start;\n\t"
|
| 487 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 488 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 489 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 490 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 491 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 492 |
+
# f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 493 |
+
f"mov.b32 tmem_acc, $3;\n\t"
|
| 494 |
+
"mov.b32 smem_desc_a_lo_start, $0;\n\t"
|
| 495 |
+
"mov.b32 smem_desc_b_lo_start, $1;\n\t"
|
| 496 |
+
f"mov.b32 smem_desc_a_hi, {hex(smem_desc_a_hi)};\n\t"
|
| 497 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 498 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo_start, smem_desc_a_hi}};\n\t"
|
| 499 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 500 |
+
"setp.ne.b32 p, $2, 0;\n\t"
|
| 501 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 502 |
+
+ "".join(
|
| 503 |
+
(
|
| 504 |
+
# f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 505 |
+
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 506 |
+
f"add.u32 smem_desc_a_lo, smem_desc_a_lo_start, {hex(offset_a[k])};\n\t"
|
| 507 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 508 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 509 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 510 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 511 |
+
)
|
| 512 |
+
for k in range(1, cute.size(tCrA.shape[2]))
|
| 513 |
+
)
|
| 514 |
+
+ "}\n",
|
| 515 |
+
# "r,r,r",
|
| 516 |
+
"r,r,r,r",
|
| 517 |
+
has_side_effects=True,
|
| 518 |
+
is_align_stack=False,
|
| 519 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 520 |
+
)
|
| 521 |
+
else:
|
| 522 |
+
# For TS gemm, somehow tCrA.iterator.toint() returns 0 no matter what, so we need to
|
| 523 |
+
# explicitly pass in the tA_addr for correctness.
|
| 524 |
+
tA_addr = tCrA[None, None, 0].iterator.toint() if tA_addr is None else tA_addr
|
| 525 |
+
input_args = [
|
| 526 |
+
# Int32(cute.arch.make_warp_uniform(tCrA[None, None, 0].iterator.toint())).ir_value(),
|
| 527 |
+
Int32(cute.arch.make_warp_uniform(tA_addr)).ir_value(),
|
| 528 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 529 |
+
Int32(not zero_init).ir_value(),
|
| 530 |
+
Int32(cute.arch.make_warp_uniform(acc_tmem_addr)).ir_value(),
|
| 531 |
+
]
|
| 532 |
+
if const_expr(mbar_ptr is not None):
|
| 533 |
+
assert mbar_phase is not None, "mbar_phase must be provided when mbar_ptr is not None"
|
| 534 |
+
assert split_arrive is not None, (
|
| 535 |
+
"split_arrive must be provided when mbar_ptr is not None"
|
| 536 |
+
)
|
| 537 |
+
split_arrive_idx = split_arrive // op.shape_mnk[2]
|
| 538 |
+
input_args.append(mbar_ptr.toint().ir_value())
|
| 539 |
+
input_args.append(Int32(mbar_phase).ir_value())
|
| 540 |
+
mbar_wait_str = (
|
| 541 |
+
".reg .pred P1; \n\t"
|
| 542 |
+
"LAB_WAIT: \n\t"
|
| 543 |
+
"mbarrier.try_wait.parity.shared::cta.b64 P1, [$4], $5, 10000000; \n\t"
|
| 544 |
+
"@P1 bra DONE; \n\t"
|
| 545 |
+
"bra LAB_WAIT; \n\t"
|
| 546 |
+
"DONE: \n\t"
|
| 547 |
+
)
|
| 548 |
+
else:
|
| 549 |
+
mbar_wait_str = ""
|
| 550 |
+
llvm.inline_asm(
|
| 551 |
+
None,
|
| 552 |
+
# [
|
| 553 |
+
# # acc.iterator.toint().ir_value(),
|
| 554 |
+
# Int32(tCrA[None, None, 0].iterator.toint()).ir_value(),
|
| 555 |
+
# Int32(smem_desc_start_b_lo).ir_value(),
|
| 556 |
+
# Int32(not zero_init).ir_value(),
|
| 557 |
+
# ],
|
| 558 |
+
input_args,
|
| 559 |
+
"{\n\t"
|
| 560 |
+
".reg .pred leader_thread;\n\t"
|
| 561 |
+
".reg .pred p;\n\t"
|
| 562 |
+
".reg .b32 idesc;\n\t"
|
| 563 |
+
".reg .b32 tmem_acc;\n\t"
|
| 564 |
+
".reg .b32 tmem_a;\n\t"
|
| 565 |
+
".reg .b32 smem_desc_b_lo_start;\n\t"
|
| 566 |
+
".reg .b32 smem_desc_b_lo;\n\t"
|
| 567 |
+
".reg .b32 smem_desc_b_hi;\n\t"
|
| 568 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 569 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 570 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 571 |
+
# f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 572 |
+
f"mov.b32 tmem_acc, $3;\n\t"
|
| 573 |
+
f"mov.b32 tmem_a, $0;\n\t"
|
| 574 |
+
f"mov.b32 smem_desc_b_lo_start, $1;\n\t"
|
| 575 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 576 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 577 |
+
"setp.ne.b32 p, $2, 0;\n\t"
|
| 578 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 579 |
+
+ "".join(
|
| 580 |
+
(
|
| 581 |
+
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 582 |
+
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 583 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 584 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 585 |
+
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 586 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 587 |
+
)
|
| 588 |
+
for k in range(
|
| 589 |
+
1,
|
| 590 |
+
cute.size(tCrA.shape[2]) if const_expr(mbar_ptr is None) else split_arrive_idx,
|
| 591 |
+
)
|
| 592 |
+
)
|
| 593 |
+
+ mbar_wait_str
|
| 594 |
+
+ (
|
| 595 |
+
"".join(
|
| 596 |
+
(
|
| 597 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 598 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 599 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 600 |
+
)
|
| 601 |
+
for k in range(split_arrive_idx, cute.size(tCrA.shape[2]))
|
| 602 |
+
)
|
| 603 |
+
if const_expr(mbar_ptr is not None)
|
| 604 |
+
else ""
|
| 605 |
+
)
|
| 606 |
+
+ "}\n",
|
| 607 |
+
"r,r,r,r" if const_expr(mbar_ptr is None) else "r,r,r,r,r,r",
|
| 608 |
+
has_side_effects=True,
|
| 609 |
+
is_align_stack=False,
|
| 610 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 611 |
+
)
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
@cute.jit
|
| 615 |
+
def gemm_ptx_partial1(
|
| 616 |
+
op: cute.nvgpu.tcgen05.mma.MmaOp,
|
| 617 |
+
acc_tmem_addr: cutlass.Constexpr[int],
|
| 618 |
+
tCrA: cute.Tensor,
|
| 619 |
+
tCrB: cute.Tensor,
|
| 620 |
+
sA_base_addr_for_desc: Int32,
|
| 621 |
+
sA_addr_offset_for_desc: cutlass.Constexpr[int],
|
| 622 |
+
sA_stage: Int32,
|
| 623 |
+
sB_base_addr_for_desc: Int32,
|
| 624 |
+
sB_addr_offset_for_desc: cutlass.Constexpr[int],
|
| 625 |
+
sB_stage: Int32,
|
| 626 |
+
sA_layout: Optional[cute.Layout],
|
| 627 |
+
sB_layout: Optional[cute.Layout],
|
| 628 |
+
sA_swizzle: Optional[cute.Swizzle],
|
| 629 |
+
sB_swizzle: cute.Swizzle,
|
| 630 |
+
zero_init: bool | Boolean = False,
|
| 631 |
+
) -> None:
|
| 632 |
+
is_ts = op.a_src == cute.nvgpu.tcgen05.OperandSource.TMEM
|
| 633 |
+
if const_expr(not is_ts):
|
| 634 |
+
assert sA_layout is not None, "sA_layout must be provided when a_src is not TMEM"
|
| 635 |
+
assert sA_swizzle is not None, "sA_swizzle must be provided when a_src is not TMEM"
|
| 636 |
+
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 637 |
+
kind = _tcgen05_mma_kind(op)
|
| 638 |
+
if const_expr(not is_ts):
|
| 639 |
+
smem_desc_base_a: int = const_expr(
|
| 640 |
+
sm100_desc.make_smem_desc_base(
|
| 641 |
+
cute.recast_layout(128, op.a_dtype.width, sA_layout[0]),
|
| 642 |
+
sA_swizzle,
|
| 643 |
+
sm100_desc.Major.K
|
| 644 |
+
if const_expr(op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 645 |
+
else sm100_desc.Major.MN,
|
| 646 |
+
)
|
| 647 |
+
)
|
| 648 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 649 |
+
smem_desc_base_a_lo = const_expr(smem_desc_base_a_lo)
|
| 650 |
+
smem_desc_a_hi = const_expr(smem_desc_a_hi)
|
| 651 |
+
else:
|
| 652 |
+
smem_desc_base_a = None
|
| 653 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 654 |
+
smem_desc_base_b: int = const_expr(
|
| 655 |
+
sm100_desc.make_smem_desc_base(
|
| 656 |
+
cute.recast_layout(128, op.b_dtype.width, sB_layout[0]),
|
| 657 |
+
sB_swizzle,
|
| 658 |
+
sm100_desc.Major.K
|
| 659 |
+
if const_expr(op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K)
|
| 660 |
+
else sm100_desc.Major.MN,
|
| 661 |
+
)
|
| 662 |
+
)
|
| 663 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 664 |
+
smem_desc_base_b_lo = const_expr(smem_desc_base_b_lo)
|
| 665 |
+
smem_desc_b_hi = const_expr(smem_desc_b_hi)
|
| 666 |
+
mask = [Int32(0)] * 4
|
| 667 |
+
|
| 668 |
+
if const_expr(not is_ts):
|
| 669 |
+
offset_a = [
|
| 670 |
+
(cute.crd2idx((0, 0, k), sA_layout) * op.a_dtype.width // 8) >> 4
|
| 671 |
+
for k in range(cute.size(tCrA.shape[2]))
|
| 672 |
+
]
|
| 673 |
+
else:
|
| 674 |
+
offset_a = [
|
| 675 |
+
cute.crd2idx((0, 0, k), sA_layout) * op.a_dtype.width // 32
|
| 676 |
+
for k in range(cute.size(tCrA.shape[2]))
|
| 677 |
+
]
|
| 678 |
+
offset_a_diff = [offset_a[k] - offset_a[k - 1] for k in range(1, cute.size(tCrA.shape[2]))]
|
| 679 |
+
offset_b = [
|
| 680 |
+
(cute.crd2idx((0, 0, k), sB_layout) * op.b_dtype.width // 8) >> 4
|
| 681 |
+
for k in range(cute.size(tCrB.shape[2]))
|
| 682 |
+
]
|
| 683 |
+
offset_b_diff = [offset_b[k] - offset_b[k - 1] for k in range(1, cute.size(tCrB.shape[2]))]
|
| 684 |
+
|
| 685 |
+
if const_expr(not is_ts):
|
| 686 |
+
# smem_desc_start_a_lo = Int32(smem_desc_base_a_lo | sm100_desc.make_smem_desc_start_addr(sA[None, None, 0].iterator))
|
| 687 |
+
smem_desc_start_a_lo = const_expr(smem_desc_base_a_lo)
|
| 688 |
+
else:
|
| 689 |
+
smem_desc_start_a_lo = None
|
| 690 |
+
# smem_desc_start_b_lo = Int32(smem_desc_base_b_lo | sm100_desc.make_smem_desc_start_addr(sB[None, None, 0].iterator))
|
| 691 |
+
smem_desc_start_b_lo = const_expr(smem_desc_base_b_lo)
|
| 692 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 693 |
+
if const_expr(not is_ts):
|
| 694 |
+
llvm.inline_asm(
|
| 695 |
+
None,
|
| 696 |
+
[
|
| 697 |
+
# acc.iterator.toint().ir_value(),
|
| 698 |
+
# Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value(),
|
| 699 |
+
Int32(sA_base_addr_for_desc).ir_value(),
|
| 700 |
+
Int32(sA_stage).ir_value(),
|
| 701 |
+
# Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 702 |
+
Int32(sB_base_addr_for_desc).ir_value(),
|
| 703 |
+
Int32(sB_stage).ir_value(),
|
| 704 |
+
Int32(not zero_init).ir_value(),
|
| 705 |
+
mask[0].ir_value(),
|
| 706 |
+
mask[1].ir_value(),
|
| 707 |
+
mask[2].ir_value(),
|
| 708 |
+
mask[3].ir_value(),
|
| 709 |
+
],
|
| 710 |
+
"{\n\t"
|
| 711 |
+
".reg .pred leader_thread;\n\t"
|
| 712 |
+
".reg .pred p;\n\t"
|
| 713 |
+
".reg .b32 idesc;\n\t"
|
| 714 |
+
".reg .b32 tmem_acc;\n\t"
|
| 715 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 716 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 717 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 718 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 719 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 720 |
+
f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 721 |
+
# "mov.b32 smem_desc_a_lo, $0;\n\t"
|
| 722 |
+
# f"add.u32 smem_desc_a_lo, $0, {hex(smem_desc_start_a_lo)};\n\t"
|
| 723 |
+
f"mad.lo.u32 smem_desc_a_lo, $1, {hex(sA_addr_offset_for_desc)}, $0;\n\t"
|
| 724 |
+
# "mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 725 |
+
f"mad.lo.u32 smem_desc_b_lo, $3, {hex(sB_addr_offset_for_desc)}, $2;\n\t"
|
| 726 |
+
f"mov.b32 smem_desc_a_hi, {hex(smem_desc_a_hi)};\n\t"
|
| 727 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 728 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 729 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 730 |
+
"setp.ne.b32 p, $4, 0;\n\t"
|
| 731 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, {{$5, $6, $7, $8}}, {pred_str};\n\t"
|
| 732 |
+
+ "".join(
|
| 733 |
+
(
|
| 734 |
+
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 735 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 736 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 737 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 738 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, {{$5, $6, $7, $8}}, 1;\n\t"
|
| 739 |
+
)
|
| 740 |
+
for k in range(1, cute.size(tCrA.shape[2]))
|
| 741 |
+
)
|
| 742 |
+
+ "}\n",
|
| 743 |
+
"r,r,r,r,r,r,r,r,r",
|
| 744 |
+
has_side_effects=True,
|
| 745 |
+
is_align_stack=False,
|
| 746 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 747 |
+
)
|
| 748 |
+
else:
|
| 749 |
+
llvm.inline_asm(
|
| 750 |
+
None,
|
| 751 |
+
[
|
| 752 |
+
# acc.iterator.toint().ir_value(),
|
| 753 |
+
Int32(tCrA[None, None, 0].iterator.toint()).ir_value(),
|
| 754 |
+
Int32(smem_desc_start_b_lo).ir_value(),
|
| 755 |
+
Int32(not zero_init).ir_value(),
|
| 756 |
+
mask[0].ir_value(),
|
| 757 |
+
mask[1].ir_value(),
|
| 758 |
+
mask[2].ir_value(),
|
| 759 |
+
mask[3].ir_value(),
|
| 760 |
+
],
|
| 761 |
+
"{\n\t"
|
| 762 |
+
".reg .pred leader_thread;\n\t"
|
| 763 |
+
".reg .pred p;\n\t"
|
| 764 |
+
".reg .b32 idesc;\n\t"
|
| 765 |
+
".reg .b32 tmem_a;\n\t"
|
| 766 |
+
".reg .b32 smem_desc_b_lo;\n\t"
|
| 767 |
+
".reg .b32 smem_desc_b_hi;\n\t"
|
| 768 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 769 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 770 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 771 |
+
f"mov.b32 tmem_a, $1;\n\t"
|
| 772 |
+
f"mov.b32 smem_desc_b_lo, $2;\n\t"
|
| 773 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 774 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 775 |
+
"setp.ne.b32 p, $3, 0;\n\t"
|
| 776 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], [tmem_a], smem_desc_b, idesc, {{$4, $5, $6, $7}}, {pred_str};\n\t"
|
| 777 |
+
+ "".join(
|
| 778 |
+
(
|
| 779 |
+
f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 780 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 781 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 782 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], [tmem_a], smem_desc_b, idesc, {{$4, $5, $6, $7}}, 1;\n\t"
|
| 783 |
+
)
|
| 784 |
+
for k in range(1, cute.size(tCrA.shape[2]))
|
| 785 |
+
)
|
| 786 |
+
+ "}\n",
|
| 787 |
+
"r,r,r,r,r,r,r,r",
|
| 788 |
+
has_side_effects=True,
|
| 789 |
+
is_align_stack=False,
|
| 790 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 791 |
+
)
|
| 792 |
+
|
| 793 |
+
|
| 794 |
+
@cute.jit
|
| 795 |
+
def gemm_ptx_precomputed(
|
| 796 |
+
acc_tmem_addr: Int32,
|
| 797 |
+
smem_desc_start_a: Int32, # If TS, then this is the tmem start address for A
|
| 798 |
+
smem_desc_start_b: Int32,
|
| 799 |
+
idesc: int,
|
| 800 |
+
smem_desc_base_a: Optional[int],
|
| 801 |
+
smem_desc_base_b: int,
|
| 802 |
+
tCrA_layout: cute.Layout,
|
| 803 |
+
tCrB_layout: cute.Layout,
|
| 804 |
+
mbar_ptr: Optional[cutlass.Pointer] = None,
|
| 805 |
+
mbar_phase: Optional[Int32] = None,
|
| 806 |
+
zero_init: bool | Boolean = False,
|
| 807 |
+
cta_group: int = 1,
|
| 808 |
+
kind: str = "f16",
|
| 809 |
+
) -> None:
|
| 810 |
+
# acc_tmem_addr += acc_offset
|
| 811 |
+
is_ts = const_expr(smem_desc_base_a is None)
|
| 812 |
+
num_k_tile = cute.size(tCrA_layout.shape[2])
|
| 813 |
+
if const_expr(not is_ts):
|
| 814 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 815 |
+
else:
|
| 816 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 817 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 818 |
+
|
| 819 |
+
tCrA_layout = (
|
| 820 |
+
tCrA_layout
|
| 821 |
+
if const_expr(not is_ts)
|
| 822 |
+
# else cute.recast_layout(32, tCrA.element_type.width, tCrA_layout)
|
| 823 |
+
# currently hard-coding the width to 16
|
| 824 |
+
else cute.recast_layout(32, 16, tCrA_layout)
|
| 825 |
+
)
|
| 826 |
+
offset_a = [cute.crd2idx((0, 0, k), tCrA_layout) for k in range(num_k_tile)]
|
| 827 |
+
offset_a_diff = [offset_a[k] - offset_a[k - 1] for k in range(1, num_k_tile)]
|
| 828 |
+
offset_b = [cute.crd2idx((0, 0, k), tCrB_layout) for k in range(num_k_tile)]
|
| 829 |
+
offset_b_diff = [offset_b[k] - offset_b[k - 1] for k in range(1, num_k_tile)]
|
| 830 |
+
|
| 831 |
+
smem_desc_start_a_lo = None
|
| 832 |
+
if const_expr(not is_ts):
|
| 833 |
+
smem_desc_start_a_lo = Int32(smem_desc_base_a_lo | smem_desc_start_a)
|
| 834 |
+
# smem_desc_start_a_lo = smem_desc_start_a
|
| 835 |
+
smem_desc_start_b_lo = Int32(smem_desc_base_b_lo | smem_desc_start_b)
|
| 836 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 837 |
+
if const_expr(not is_ts):
|
| 838 |
+
assert mbar_ptr is None, "mbar_ptr must be None when a_src is not TMEM"
|
| 839 |
+
llvm.inline_asm(
|
| 840 |
+
None,
|
| 841 |
+
[
|
| 842 |
+
# acc.iterator.toint().ir_value(),
|
| 843 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value(),
|
| 844 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 845 |
+
Int32(not zero_init).ir_value(),
|
| 846 |
+
Int32(cute.arch.make_warp_uniform(acc_tmem_addr)).ir_value(),
|
| 847 |
+
],
|
| 848 |
+
"{\n\t"
|
| 849 |
+
".reg .pred leader_thread;\n\t"
|
| 850 |
+
".reg .pred p;\n\t"
|
| 851 |
+
".reg .b32 idesc;\n\t"
|
| 852 |
+
".reg .b32 tmem_acc;\n\t"
|
| 853 |
+
".reg .b32 smem_desc_a_lo_start, smem_desc_b_lo_start;\n\t"
|
| 854 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 855 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 856 |
+
".reg .b64 smem_desc_a, smem_desc_b;\n\t"
|
| 857 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 858 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 859 |
+
# f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 860 |
+
f"mov.b32 tmem_acc, $3;\n\t"
|
| 861 |
+
"mov.b32 smem_desc_a_lo_start, $0;\n\t"
|
| 862 |
+
"mov.b32 smem_desc_b_lo_start, $1;\n\t"
|
| 863 |
+
f"mov.b32 smem_desc_a_hi, {hex(smem_desc_a_hi)};\n\t"
|
| 864 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 865 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo_start, smem_desc_a_hi}};\n\t"
|
| 866 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 867 |
+
"setp.ne.b32 p, $2, 0;\n\t"
|
| 868 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 869 |
+
+ "".join(
|
| 870 |
+
(
|
| 871 |
+
# f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 872 |
+
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 873 |
+
f"add.s32 smem_desc_a_lo, smem_desc_a_lo_start, {hex(offset_a[k])};\n\t"
|
| 874 |
+
f"add.s32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 875 |
+
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 876 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 877 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 878 |
+
)
|
| 879 |
+
for k in range(1, num_k_tile)
|
| 880 |
+
)
|
| 881 |
+
+ "}\n",
|
| 882 |
+
# "r,r,r",
|
| 883 |
+
"r,r,r,r",
|
| 884 |
+
has_side_effects=True,
|
| 885 |
+
is_align_stack=False,
|
| 886 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 887 |
+
)
|
| 888 |
+
else:
|
| 889 |
+
input_args = [
|
| 890 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_a)).ir_value(),
|
| 891 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 892 |
+
Int32(not zero_init).ir_value(),
|
| 893 |
+
Int32(cute.arch.make_warp_uniform(acc_tmem_addr)).ir_value(),
|
| 894 |
+
]
|
| 895 |
+
if const_expr(mbar_ptr is not None):
|
| 896 |
+
assert mbar_phase is not None, "mbar_phase must be provided when mbar_ptr is not None"
|
| 897 |
+
input_args.append(mbar_ptr.toint().ir_value())
|
| 898 |
+
input_args.append(Int32(mbar_phase).ir_value())
|
| 899 |
+
mbar_wait_str = (
|
| 900 |
+
".reg .pred P1; \n\t"
|
| 901 |
+
"LAB_WAIT: \n\t"
|
| 902 |
+
"mbarrier.try_wait.parity.shared::cta.b64 P1, [$4], $5, 10000000; \n\t"
|
| 903 |
+
"@P1 bra DONE; \n\t"
|
| 904 |
+
"bra LAB_WAIT; \n\t"
|
| 905 |
+
"DONE: \n\t"
|
| 906 |
+
)
|
| 907 |
+
else:
|
| 908 |
+
mbar_wait_str = ""
|
| 909 |
+
llvm.inline_asm(
|
| 910 |
+
None,
|
| 911 |
+
# [
|
| 912 |
+
# # acc.iterator.toint().ir_value(),
|
| 913 |
+
# Int32(tCrA_layout[None, None, 0].iterator.toint()).ir_value(),
|
| 914 |
+
# Int32(smem_desc_start_b_lo).ir_value(),
|
| 915 |
+
# Int32(not zero_init).ir_value(),
|
| 916 |
+
# ],
|
| 917 |
+
input_args,
|
| 918 |
+
"{\n\t"
|
| 919 |
+
".reg .pred leader_thread;\n\t"
|
| 920 |
+
".reg .pred p;\n\t"
|
| 921 |
+
".reg .b32 idesc;\n\t"
|
| 922 |
+
".reg .b32 tmem_acc;\n\t"
|
| 923 |
+
".reg .b32 tmem_a;\n\t"
|
| 924 |
+
".reg .b32 smem_desc_b_lo_start;\n\t"
|
| 925 |
+
".reg .b32 smem_desc_b_lo;\n\t"
|
| 926 |
+
".reg .b32 smem_desc_b_hi;\n\t"
|
| 927 |
+
".reg .b64 smem_desc_b;\n\t"
|
| 928 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 929 |
+
f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 930 |
+
# f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 931 |
+
f"mov.b32 tmem_acc, $3;\n\t"
|
| 932 |
+
f"mov.b32 tmem_a, $0;\n\t"
|
| 933 |
+
f"mov.b32 smem_desc_b_lo_start, $1;\n\t"
|
| 934 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 935 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 936 |
+
"setp.ne.b32 p, $2, 0;\n\t"
|
| 937 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 938 |
+
+ "".join(
|
| 939 |
+
(
|
| 940 |
+
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 941 |
+
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 942 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 943 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 944 |
+
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 945 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 946 |
+
)
|
| 947 |
+
for k in range(
|
| 948 |
+
1,
|
| 949 |
+
num_k_tile if const_expr(mbar_ptr is None) else num_k_tile // 4 * 3,
|
| 950 |
+
)
|
| 951 |
+
)
|
| 952 |
+
+ mbar_wait_str
|
| 953 |
+
+ (
|
| 954 |
+
"".join(
|
| 955 |
+
(
|
| 956 |
+
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 957 |
+
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 958 |
+
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 959 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 960 |
+
)
|
| 961 |
+
for k in range(num_k_tile // 4 * 3, num_k_tile)
|
| 962 |
+
)
|
| 963 |
+
if const_expr(mbar_ptr is not None)
|
| 964 |
+
else ""
|
| 965 |
+
)
|
| 966 |
+
+ "}\n",
|
| 967 |
+
"r,r,r,r" if const_expr(mbar_ptr is None) else "r,r,r,r,r,r",
|
| 968 |
+
has_side_effects=True,
|
| 969 |
+
is_align_stack=False,
|
| 970 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 971 |
+
)
|
| 972 |
+
|
| 973 |
+
|
| 974 |
+
@cute.jit
|
| 975 |
+
def declare_ptx_smem_desc(
|
| 976 |
+
smem_desc_start_a: Int32, # If TS, then this is the tmem start address for A
|
| 977 |
+
smem_desc_base_a: Optional[int],
|
| 978 |
+
tCrA_layout: cute.Layout,
|
| 979 |
+
var_name_prefix: str = "smem_desc",
|
| 980 |
+
) -> None:
|
| 981 |
+
is_ts = const_expr(smem_desc_base_a is None)
|
| 982 |
+
num_k_tile = cute.size(tCrA_layout.shape[2])
|
| 983 |
+
smem_desc_base_a_lo, smem_desc_a_hi = None, None
|
| 984 |
+
if const_expr(not is_ts):
|
| 985 |
+
smem_desc_base_a_lo, smem_desc_a_hi = i64_to_i32x2(smem_desc_base_a)
|
| 986 |
+
tCrA_layout = (
|
| 987 |
+
tCrA_layout
|
| 988 |
+
if const_expr(not is_ts)
|
| 989 |
+
# else cute.recast_layout(32, tCrA.element_type.width, tCrA_layout)
|
| 990 |
+
# currently hard-coding the width to 16
|
| 991 |
+
else cute.recast_layout(32, 16, tCrA_layout)
|
| 992 |
+
)
|
| 993 |
+
offset_a = [cute.crd2idx((0, 0, k), tCrA_layout) for k in range(num_k_tile)]
|
| 994 |
+
smem_desc_start_a_lo = None
|
| 995 |
+
if const_expr(not is_ts):
|
| 996 |
+
smem_desc_start_a_lo = Int32(smem_desc_base_a_lo | smem_desc_start_a)
|
| 997 |
+
if const_expr(not is_ts):
|
| 998 |
+
llvm.inline_asm(
|
| 999 |
+
None,
|
| 1000 |
+
[Int32(cute.arch.make_warp_uniform(smem_desc_start_a_lo)).ir_value()],
|
| 1001 |
+
f".reg .b32 {var_name_prefix}_lo;\n\t"
|
| 1002 |
+
f".reg .b64 {var_name_prefix}_<{num_k_tile}>;\n\t"
|
| 1003 |
+
f"mov.b64 {var_name_prefix}_0, {{$0, {hex(smem_desc_a_hi)}}};\n\t"
|
| 1004 |
+
+ "".join(
|
| 1005 |
+
(
|
| 1006 |
+
f"add.s32 {var_name_prefix}_lo, $0, {hex(offset_a[k])};\n\t"
|
| 1007 |
+
f"mov.b64 {var_name_prefix}_{k}, {{{var_name_prefix}_lo, {hex(smem_desc_a_hi)}}};\n\t"
|
| 1008 |
+
)
|
| 1009 |
+
for k in range(1, num_k_tile)
|
| 1010 |
+
),
|
| 1011 |
+
"r",
|
| 1012 |
+
has_side_effects=True,
|
| 1013 |
+
is_align_stack=False,
|
| 1014 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 1015 |
+
)
|
| 1016 |
+
|
| 1017 |
+
|
| 1018 |
+
@cute.jit
|
| 1019 |
+
def declare_ptx_idesc(op: cute.nvgpu.tcgen05.mma.MmaOp, var_name: str = "idesc") -> None:
|
| 1020 |
+
idesc = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 1021 |
+
llvm.inline_asm(
|
| 1022 |
+
None,
|
| 1023 |
+
[],
|
| 1024 |
+
f".reg .b32 {var_name};\n\t" # noqa
|
| 1025 |
+
f"mov.b32 {var_name}, {hex(idesc)};\n\t",
|
| 1026 |
+
constraints="",
|
| 1027 |
+
has_side_effects=True,
|
| 1028 |
+
is_align_stack=False,
|
| 1029 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 1030 |
+
)
|
| 1031 |
+
|
| 1032 |
+
|
| 1033 |
+
@cute.jit
|
| 1034 |
+
def gemm_ptx_precomputed_varname(
|
| 1035 |
+
acc_tmem_addr: Int32,
|
| 1036 |
+
smem_desc_start_b: Int32,
|
| 1037 |
+
# idesc: int,
|
| 1038 |
+
smem_desc_base_b: int,
|
| 1039 |
+
tCrB_layout: cute.Layout,
|
| 1040 |
+
smem_var_name_prefix: str,
|
| 1041 |
+
idesc_var_name: str,
|
| 1042 |
+
smem_offset: int,
|
| 1043 |
+
zero_init: bool | Boolean = False,
|
| 1044 |
+
cta_group: int = 1,
|
| 1045 |
+
kind: str = "f16",
|
| 1046 |
+
) -> None:
|
| 1047 |
+
is_ts = False
|
| 1048 |
+
num_k_tile = cute.size(tCrB_layout.shape[2])
|
| 1049 |
+
smem_desc_base_b_lo, smem_desc_b_hi = i64_to_i32x2(smem_desc_base_b)
|
| 1050 |
+
offset_b = [cute.crd2idx((0, 0, k), tCrB_layout) for k in range(num_k_tile)]
|
| 1051 |
+
|
| 1052 |
+
smem_desc_start_b_lo = Int32(smem_desc_base_b_lo | smem_desc_start_b)
|
| 1053 |
+
pred_str = "p" if isinstance(zero_init, Boolean) else "0" if zero_init else "1"
|
| 1054 |
+
if const_expr(not is_ts):
|
| 1055 |
+
llvm.inline_asm(
|
| 1056 |
+
None,
|
| 1057 |
+
[
|
| 1058 |
+
Int32(cute.arch.make_warp_uniform(smem_desc_start_b_lo)).ir_value(),
|
| 1059 |
+
Int32(not zero_init).ir_value(),
|
| 1060 |
+
Int32(cute.arch.make_warp_uniform(acc_tmem_addr)).ir_value(),
|
| 1061 |
+
],
|
| 1062 |
+
"{\n\t"
|
| 1063 |
+
".reg .pred leader_thread;\n\t"
|
| 1064 |
+
".reg .pred p;\n\t"
|
| 1065 |
+
# ".reg .b32 idesc;\n\t"
|
| 1066 |
+
".reg .b32 tmem_acc;\n\t"
|
| 1067 |
+
".reg .b32 smem_desc_b_lo_start;\n\t"
|
| 1068 |
+
".reg .b32 smem_desc_a_lo, smem_desc_b_lo;\n\t"
|
| 1069 |
+
".reg .b32 smem_desc_a_hi, smem_desc_b_hi;\n\t"
|
| 1070 |
+
# ".reg .b64 smem_desc_b;\n\t"
|
| 1071 |
+
f".reg .b64 smem_desc_b_<{num_k_tile}>;\n\t"
|
| 1072 |
+
"elect.sync _|leader_thread, -1;\n\t"
|
| 1073 |
+
# f"mov.b32 idesc, {hex(idesc)};\n\t"
|
| 1074 |
+
# f"mov.b32 tmem_acc, {hex(acc_tmem_addr)};\n\t"
|
| 1075 |
+
f"mov.b32 tmem_acc, $2;\n\t"
|
| 1076 |
+
"mov.b32 smem_desc_b_lo_start, $0;\n\t"
|
| 1077 |
+
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 1078 |
+
f"mov.b64 {{smem_desc_a_lo, smem_desc_a_hi}}, {smem_var_name_prefix}_0;\n\t"
|
| 1079 |
+
f"add.s32 smem_desc_a_lo, smem_desc_a_lo, {smem_offset};\n\t"
|
| 1080 |
+
f"mov.b64 {smem_var_name_prefix}_0, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 1081 |
+
f"mov.b64 smem_desc_b_0, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 1082 |
+
+ "".join(
|
| 1083 |
+
(
|
| 1084 |
+
f"mov.b64 {{smem_desc_a_lo, smem_desc_a_hi}}, {smem_var_name_prefix}_{k};\n\t"
|
| 1085 |
+
f"add.s32 smem_desc_a_lo, smem_desc_a_lo, {smem_offset};\n\t"
|
| 1086 |
+
f"add.s32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 1087 |
+
f"mov.b64 {smem_var_name_prefix}_{k}, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 1088 |
+
f"mov.b64 smem_desc_b_{k}, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 1089 |
+
)
|
| 1090 |
+
for k in range(1, num_k_tile)
|
| 1091 |
+
)
|
| 1092 |
+
+ "setp.ne.b32 p, $1, 0;\n\t"
|
| 1093 |
+
# f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::f16 [tmem_acc], {smem_var_name_prefix}_0, smem_desc_b, idesc, {pred_str};\n\t"
|
| 1094 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], {smem_var_name_prefix}_0, smem_desc_b_0, {idesc_var_name}, {pred_str};\n\t"
|
| 1095 |
+
+ "".join(
|
| 1096 |
+
(
|
| 1097 |
+
# f"mov.b64 {{smem_desc_a_lo, smem_desc_a_hi}}, {smem_var_name_prefix}_{k};\n\t"
|
| 1098 |
+
# f"add.s32 smem_desc_a_lo, smem_desc_a_lo, {smem_offset};\n\t"
|
| 1099 |
+
# f"add.s32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 1100 |
+
# f"mov.b64 {smem_var_name_prefix}_{k}, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 1101 |
+
# f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 1102 |
+
# f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::f16 [tmem_acc], {smem_var_name_prefix}_{k}, smem_desc_b, idesc, 1;\n\t"
|
| 1103 |
+
# f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::f16 [tmem_acc], {smem_var_name_prefix}_{k}, smem_desc_b, {idesc_var_name}, 1;\n\t"
|
| 1104 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], {smem_var_name_prefix}_{k}, smem_desc_b_{k}, {idesc_var_name}, 1;\n\t"
|
| 1105 |
+
)
|
| 1106 |
+
for k in range(1, num_k_tile)
|
| 1107 |
+
)
|
| 1108 |
+
+ "}\n",
|
| 1109 |
+
"r,r,r",
|
| 1110 |
+
has_side_effects=True,
|
| 1111 |
+
is_align_stack=False,
|
| 1112 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 1113 |
+
)
|
build/torch-cuda/flashrt_fa4/cute/block_info.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
from typing import Tuple, Optional
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
|
| 5 |
+
import cutlass
|
| 6 |
+
import cutlass.cute as cute
|
| 7 |
+
from cutlass import Int32, const_expr
|
| 8 |
+
|
| 9 |
+
from fa4_cute_runtime.flashrt_fa4.cute.seqlen_info import SeqlenInfoQK, SeqlenInfoQKNewK
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass(frozen=True)
|
| 13 |
+
class BlockInfo:
|
| 14 |
+
tile_m: cutlass.Constexpr[int]
|
| 15 |
+
tile_n: cutlass.Constexpr[int]
|
| 16 |
+
is_causal: cutlass.Constexpr[bool]
|
| 17 |
+
is_local: cutlass.Constexpr[bool] = False
|
| 18 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 19 |
+
window_size_left: Optional[Int32] = None
|
| 20 |
+
window_size_right: Optional[Int32] = None
|
| 21 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1
|
| 22 |
+
|
| 23 |
+
@cute.jit
|
| 24 |
+
def get_n_block_min_max(
|
| 25 |
+
self,
|
| 26 |
+
seqlen_info: SeqlenInfoQK,
|
| 27 |
+
m_block: Int32,
|
| 28 |
+
split_idx: Int32 = 0,
|
| 29 |
+
num_splits: Int32 = 1,
|
| 30 |
+
) -> Tuple[Int32, Int32]:
|
| 31 |
+
n_block_max = cute.ceil_div(seqlen_info.seqlen_k, self.tile_n)
|
| 32 |
+
if const_expr(self.is_causal or (self.is_local and self.window_size_right is not None)):
|
| 33 |
+
m_idx_max = (m_block + 1) * self.tile_m
|
| 34 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 35 |
+
m_idx_max = cute.ceil_div(m_idx_max, self.qhead_per_kvhead_packgqa)
|
| 36 |
+
n_idx = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 37 |
+
n_idx_right = n_idx if const_expr(self.is_causal) else n_idx + self.window_size_right
|
| 38 |
+
n_block_max = min(n_block_max, cute.ceil_div(n_idx_right, self.tile_n))
|
| 39 |
+
n_block_min = 0
|
| 40 |
+
if const_expr(self.is_local and self.window_size_left is not None):
|
| 41 |
+
m_idx_min = m_block * self.tile_m
|
| 42 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 43 |
+
m_idx_min = m_idx_min // self.qhead_per_kvhead_packgqa
|
| 44 |
+
n_idx = m_idx_min + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 45 |
+
n_idx_left = n_idx - self.window_size_left
|
| 46 |
+
n_block_min = cutlass.max(n_idx_left // self.tile_n, 0)
|
| 47 |
+
if cutlass.const_expr(self.is_split_kv):
|
| 48 |
+
num_n_blocks_per_split = (
|
| 49 |
+
Int32(0)
|
| 50 |
+
if n_block_max <= n_block_min
|
| 51 |
+
else (n_block_max - n_block_min + num_splits - 1) // num_splits
|
| 52 |
+
)
|
| 53 |
+
n_block_min = n_block_min + split_idx * num_n_blocks_per_split
|
| 54 |
+
n_block_max = cutlass.min(n_block_min + num_n_blocks_per_split, n_block_max)
|
| 55 |
+
return n_block_min, n_block_max
|
| 56 |
+
|
| 57 |
+
@cute.jit
|
| 58 |
+
def get_m_block_min_max(self, seqlen_info: SeqlenInfoQK, n_block: Int32) -> Tuple[Int32, Int32]:
|
| 59 |
+
m_block_max = cute.ceil_div(seqlen_info.seqlen_q, self.tile_m)
|
| 60 |
+
m_block_min = 0
|
| 61 |
+
if const_expr(self.is_causal or (self.is_local and self.window_size_right is not None)):
|
| 62 |
+
n_idx_min = n_block * self.tile_n
|
| 63 |
+
m_idx = n_idx_min + seqlen_info.seqlen_q - seqlen_info.seqlen_k
|
| 64 |
+
m_idx_right = m_idx if const_expr(self.is_causal) else m_idx - self.window_size_right
|
| 65 |
+
m_block_min = max(m_block_min, m_idx_right // self.tile_m)
|
| 66 |
+
if const_expr(self.is_local and self.window_size_left is not None):
|
| 67 |
+
n_idx_max = (n_block + 1) * self.tile_n
|
| 68 |
+
m_idx = n_idx_max + seqlen_info.seqlen_q - seqlen_info.seqlen_k
|
| 69 |
+
m_idx_left = m_idx + self.window_size_left
|
| 70 |
+
m_block_max = min(m_block_max, cute.ceil_div(m_idx_left, self.tile_m))
|
| 71 |
+
return m_block_min, m_block_max
|
| 72 |
+
|
| 73 |
+
@cute.jit
|
| 74 |
+
def get_n_block_k_new_min_max(
|
| 75 |
+
self,
|
| 76 |
+
seqlen_info: SeqlenInfoQKNewK,
|
| 77 |
+
m_block: Int32,
|
| 78 |
+
split_idx: Int32 = 0,
|
| 79 |
+
num_splits: Int32 = 1,
|
| 80 |
+
) -> Tuple[Int32, Int32]:
|
| 81 |
+
"""Get the block range for new K tokens (append KV).
|
| 82 |
+
|
| 83 |
+
First computes the full n_block range via get_n_block_min_max, then maps
|
| 84 |
+
those blocks into the new-K index space by subtracting seqlen_k_og.
|
| 85 |
+
"""
|
| 86 |
+
n_block_min, n_block_max = self.get_n_block_min_max(
|
| 87 |
+
seqlen_info,
|
| 88 |
+
m_block,
|
| 89 |
+
split_idx,
|
| 90 |
+
num_splits,
|
| 91 |
+
)
|
| 92 |
+
idx_k_new_min = cutlass.max(n_block_min * self.tile_n - seqlen_info.seqlen_k_og, 0)
|
| 93 |
+
idx_k_new_max = cutlass.min(
|
| 94 |
+
n_block_max * self.tile_n - seqlen_info.seqlen_k_og, seqlen_info.seqlen_k_new
|
| 95 |
+
)
|
| 96 |
+
n_block_new_min = idx_k_new_min // self.tile_n
|
| 97 |
+
n_block_new_max = (
|
| 98 |
+
cute.ceil_div(idx_k_new_max, self.tile_n)
|
| 99 |
+
if idx_k_new_max > idx_k_new_min
|
| 100 |
+
else n_block_new_min
|
| 101 |
+
)
|
| 102 |
+
return n_block_new_min, n_block_new_max
|
| 103 |
+
|
| 104 |
+
@cute.jit
|
| 105 |
+
def get_n_block_min_causal_local_mask(
|
| 106 |
+
self,
|
| 107 |
+
seqlen_info: SeqlenInfoQK,
|
| 108 |
+
m_block: Int32,
|
| 109 |
+
n_block_min: Int32,
|
| 110 |
+
) -> Int32:
|
| 111 |
+
"""If we have separate iterations with causal or local masking at the start, where do we stop"""
|
| 112 |
+
m_idx_min = m_block * self.tile_m
|
| 113 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 114 |
+
m_idx_min = m_idx_min // self.qhead_per_kvhead_packgqa
|
| 115 |
+
n_idx = m_idx_min + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 116 |
+
n_idx_right = (
|
| 117 |
+
n_idx
|
| 118 |
+
if const_expr(not self.is_local or self.window_size_right is None)
|
| 119 |
+
else n_idx + self.window_size_right
|
| 120 |
+
)
|
| 121 |
+
return cutlass.max(n_block_min, n_idx_right // self.tile_n)
|
| 122 |
+
|
| 123 |
+
@cute.jit
|
| 124 |
+
def get_n_block_min_before_local_mask(
|
| 125 |
+
self,
|
| 126 |
+
seqlen_info: SeqlenInfoQK,
|
| 127 |
+
m_block: Int32,
|
| 128 |
+
n_block_min: Int32,
|
| 129 |
+
) -> Int32:
|
| 130 |
+
"""If we have separate iterations with local masking at the end, where do we stop the non-masked iterations"""
|
| 131 |
+
if const_expr(not self.is_local or self.window_size_left is None):
|
| 132 |
+
return n_block_min
|
| 133 |
+
else:
|
| 134 |
+
m_idx_max = (m_block + 1) * self.tile_m
|
| 135 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 136 |
+
m_idx_max = cute.ceil_div(m_idx_max, self.qhead_per_kvhead_packgqa)
|
| 137 |
+
n_idx = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 138 |
+
n_idx_left = n_idx - self.window_size_left
|
| 139 |
+
return cutlass.max(n_block_min, cute.ceil_div(n_idx_left, self.tile_n))
|
| 140 |
+
|
| 141 |
+
@cute.jit
|
| 142 |
+
def get_n_block_max_for_m_block(
|
| 143 |
+
self,
|
| 144 |
+
seqlen_info: SeqlenInfoQK,
|
| 145 |
+
m_block: Int32,
|
| 146 |
+
n_block_global_max: Int32,
|
| 147 |
+
) -> Int32:
|
| 148 |
+
if const_expr(self.is_causal or self.window_size_right is not None):
|
| 149 |
+
m_idx_max = (m_block + 1) * self.tile_m
|
| 150 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 151 |
+
m_idx_max = cute.ceil_div(m_idx_max, self.qhead_per_kvhead_packgqa)
|
| 152 |
+
n_idx_right = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 153 |
+
if const_expr(self.window_size_right is not None):
|
| 154 |
+
n_idx_right += self.window_size_right
|
| 155 |
+
return min(n_block_global_max, cute.ceil_div(n_idx_right, self.tile_n))
|
| 156 |
+
return n_block_global_max
|
build/torch-cuda/flashrt_fa4/cute/block_sparse_utils.py
ADDED
|
@@ -0,0 +1,1437 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Block-sparse runtime utilities for CUTE DSL kernels.
|
| 3 |
+
|
| 4 |
+
This module contains runtime execution functions for block-sparse attention kernels.
|
| 5 |
+
These utilities are used by CUTE DSL kernels to produce and consume block-sparse loads.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from typing import Callable, Optional
|
| 9 |
+
from functools import partial
|
| 10 |
+
import math
|
| 11 |
+
import cutlass
|
| 12 |
+
import cutlass.cute as cute
|
| 13 |
+
from cutlass import Float32, Int32, const_expr
|
| 14 |
+
|
| 15 |
+
from fa4_cute_runtime.quack import copy_utils
|
| 16 |
+
|
| 17 |
+
# Import data structures from block_sparsity
|
| 18 |
+
from fa4_cute_runtime.flashrt_fa4.cute.block_sparsity import BlockSparseTensors
|
| 19 |
+
from fa4_cute_runtime.flashrt_fa4.cute.named_barrier import NamedBarrierBwd
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# NOTE [SM100 block-sparse empty tiles: mbarrier contract]
|
| 23 |
+
#
|
| 24 |
+
# For block-sparse SM100 forward, a given (m_block, stage) Q tile can have zero active
|
| 25 |
+
# KV blocks (total_block_cnt == 0). In that case there is no seqlen_kv iteration, so
|
| 26 |
+
# the softmax warp-group has no row stats to publish.
|
| 27 |
+
#
|
| 28 |
+
# The correction warp-group seeds fully-masked-row stats and runs the usual correction
|
| 29 |
+
# epilogue so output/LSE have well-defined values. Both warp-groups must still perform
|
| 30 |
+
# the softmax<->correction mbarrier handshake so phases advance correctly across
|
| 31 |
+
# empty->empty and empty->non-empty tile sequences.
|
| 32 |
+
#
|
| 33 |
+
# In the no-sink case, this corresponds to the usual fully-masked-row convention:
|
| 34 |
+
# output is zero and LSE is -inf.
|
| 35 |
+
#
|
| 36 |
+
# Barrier contract (each is `mbar_ptr + <offset> + stage`):
|
| 37 |
+
#
|
| 38 |
+
# Producer/consumer pairs:
|
| 39 |
+
# - `mbar_softmax_corr_full` : softmax arrive -> correction wait
|
| 40 |
+
# - `mbar_softmax_corr_empty` : correction arrive -> softmax wait
|
| 41 |
+
# - `mbar_P_full_O_rescaled` : softmax arrive (+ correction arrive) -> MMA wait
|
| 42 |
+
# - `mbar_P_full_2` : softmax arrive -> MMA wait
|
| 43 |
+
# - `mbar_corr_epi_full_/empty` : correction <-> epilogue (only when epilogue is separate)
|
| 44 |
+
#
|
| 45 |
+
# Empty tile (`total_block_cnt == 0`):
|
| 46 |
+
# - Softmax: skips the seqlen_kv softmax path entirely (no P stores, no `mbar_P_full_*`).
|
| 47 |
+
# It only arrives `mbar_softmax_corr_full` once per stage as a synthetic "no work" signal.
|
| 48 |
+
# At the `softmax_loop` level, softmax unconditionally waits `mbar_softmax_corr_empty`
|
| 49 |
+
# before each tile (when block-sparse) to drain a prior correction arrival and keep
|
| 50 |
+
# phases aligned across non-empty -> empty transitions.
|
| 51 |
+
# - Correction: waits `mbar_softmax_corr_full`, seeds stats + runs `correction_epilogue(scale=0)`,
|
| 52 |
+
# and arrives `mbar_softmax_corr_empty` (and `mbar_corr_epi_full_/empty` when applicable).
|
| 53 |
+
# - No `mbar_P_full_*` barriers are arrived (no P, no MMA O); only the softmax<->correction
|
| 54 |
+
# (and correction<->epilogue) handshakes advance phases.
|
| 55 |
+
#
|
| 56 |
+
# Non-empty tile:
|
| 57 |
+
# - Softmax: runs `softmax_step` (produces P) and uses `mbar_softmax_corr_full/empty` to
|
| 58 |
+
# publish row_max (during seqlen_kv) and final row stats (once per tile), and to advance phases;
|
| 59 |
+
# arrives `mbar_P_full_*` when P is stored.
|
| 60 |
+
# - Correction: waits `mbar_softmax_corr_full`, may rescale/release O, arrives `mbar_softmax_corr_empty`
|
| 61 |
+
# to ack/advance, and arrives `mbar_P_full_O_rescaled` when MMA can proceed.
|
| 62 |
+
#
|
| 63 |
+
# Backward (SM100):
|
| 64 |
+
# - Empty KV tile: for a given `n_block`, `total_m_block_cnt == 0` means no Q tiles contribute.
|
| 65 |
+
# - Both the load and compute loops guard all pipeline work on `process_tile`, so empty tiles
|
| 66 |
+
# skip producer/consumer operations entirely (no per-tile mbarrier phase handshake like forward).
|
| 67 |
+
# - In the `not dKV_postprocess` path, dK/dV for empty KV tiles are explicitly written as zeros
|
| 68 |
+
# even when `process_tile == False` (see `flash_bwd_sm100.py` `should_zero_dKV`).
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@cute.jit
|
| 72 |
+
def load_block_list(
|
| 73 |
+
block_indices: cute.Tensor,
|
| 74 |
+
block_count,
|
| 75 |
+
first_block_preloaded: cutlass.Constexpr,
|
| 76 |
+
kv_producer_state,
|
| 77 |
+
load_K,
|
| 78 |
+
load_V,
|
| 79 |
+
pipeline_k,
|
| 80 |
+
pipeline_v,
|
| 81 |
+
intra_wg_overlap: cutlass.Constexpr,
|
| 82 |
+
):
|
| 83 |
+
"""Iterate over the sparse blocks and load K, V into the pipeline.
|
| 84 |
+
For the intra_wg_overlap case, we overlap the loads of K and V. And this
|
| 85 |
+
means we need to pipeline the last V load from the partial block case,
|
| 86 |
+
with the loads for the full blocks. Set first_block_preloaded when the
|
| 87 |
+
caller has already issued the first K load for the list.
|
| 88 |
+
|
| 89 |
+
Q is loaded separately on its own mbarrier before this function is called.
|
| 90 |
+
|
| 91 |
+
Note:
|
| 92 |
+
we iterate along the block_n indices in reverse.
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
Updated kv_producer_state after processing the block list.
|
| 96 |
+
|
| 97 |
+
"""
|
| 98 |
+
if block_count > 0:
|
| 99 |
+
if const_expr(not intra_wg_overlap):
|
| 100 |
+
for offset in cutlass.range(block_count):
|
| 101 |
+
n_block = block_indices[block_count - 1 - offset]
|
| 102 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 103 |
+
load_K(src_idx=n_block, producer_state=kv_producer_state)
|
| 104 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 105 |
+
load_V(src_idx=n_block, producer_state=kv_producer_state)
|
| 106 |
+
kv_producer_state.advance()
|
| 107 |
+
else:
|
| 108 |
+
n_block_first = block_indices[block_count - 1]
|
| 109 |
+
if const_expr(not first_block_preloaded):
|
| 110 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 111 |
+
load_K(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 112 |
+
|
| 113 |
+
for idx in cutlass.range(block_count - 1, unroll=1):
|
| 114 |
+
n_block_prev = block_indices[block_count - 1 - idx]
|
| 115 |
+
n_block = block_indices[block_count - 2 - idx]
|
| 116 |
+
kv_producer_state_prev = kv_producer_state.clone()
|
| 117 |
+
kv_producer_state.advance()
|
| 118 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 119 |
+
load_K(src_idx=n_block, producer_state=kv_producer_state)
|
| 120 |
+
pipeline_v.producer_acquire(kv_producer_state_prev)
|
| 121 |
+
load_V(src_idx=n_block_prev, producer_state=kv_producer_state_prev)
|
| 122 |
+
|
| 123 |
+
return kv_producer_state
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
@cute.jit
|
| 127 |
+
def finish_overlap_v_load(
|
| 128 |
+
block_indices: cute.Tensor,
|
| 129 |
+
block_count,
|
| 130 |
+
load_V,
|
| 131 |
+
pipeline_v,
|
| 132 |
+
kv_producer_state,
|
| 133 |
+
):
|
| 134 |
+
"""Load the final V block after overlapped K/V loads."""
|
| 135 |
+
if block_count > 0:
|
| 136 |
+
n_block_last = block_indices[0]
|
| 137 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 138 |
+
load_V(src_idx=n_block_last, producer_state=kv_producer_state)
|
| 139 |
+
kv_producer_state.advance()
|
| 140 |
+
|
| 141 |
+
return kv_producer_state
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
@cute.jit
|
| 145 |
+
def sparse_tensor_m_block(
|
| 146 |
+
m_block,
|
| 147 |
+
qhead_per_kvhead: cutlass.Constexpr[int],
|
| 148 |
+
q_subtile_factor: cutlass.Constexpr[int],
|
| 149 |
+
):
|
| 150 |
+
"""Map packed m_block indices to block-sparse tensor indices."""
|
| 151 |
+
block = m_block
|
| 152 |
+
if const_expr(qhead_per_kvhead != 1):
|
| 153 |
+
block = block // qhead_per_kvhead
|
| 154 |
+
if const_expr(q_subtile_factor != 1):
|
| 155 |
+
block = block // q_subtile_factor
|
| 156 |
+
return block
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
@cute.jit
|
| 160 |
+
def produce_block_sparse_loads(
|
| 161 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 162 |
+
batch_idx,
|
| 163 |
+
head_idx,
|
| 164 |
+
m_block,
|
| 165 |
+
kv_producer_state,
|
| 166 |
+
load_K,
|
| 167 |
+
load_V,
|
| 168 |
+
pipeline_k,
|
| 169 |
+
pipeline_v,
|
| 170 |
+
intra_wg_overlap: cutlass.Constexpr,
|
| 171 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 172 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 173 |
+
):
|
| 174 |
+
"""Iterate over the mask and full block lists for a single tile.
|
| 175 |
+
|
| 176 |
+
Q is loaded separately on its own mbarrier before this function is called.
|
| 177 |
+
|
| 178 |
+
The masked (partial) list may leave the last V load pending when intra-warp-group
|
| 179 |
+
overlap is enabled. The first full block must consume that pending V while
|
| 180 |
+
issuing its own K load on the next pipeline stage.
|
| 181 |
+
|
| 182 |
+
In the intra-wg-overlap path, the last masked block leaves its V copy in flight
|
| 183 |
+
while we advance the producer state to start the next full K. Either the full list
|
| 184 |
+
overlaps that pending V load, or, if no full blocks exist, we explicitly drain it.
|
| 185 |
+
|
| 186 |
+
Args:
|
| 187 |
+
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 188 |
+
must be converted to unpacked for sparse tensor indexing.
|
| 189 |
+
"""
|
| 190 |
+
|
| 191 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx, *_ = blocksparse_tensors
|
| 192 |
+
|
| 193 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 194 |
+
|
| 195 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 196 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 197 |
+
|
| 198 |
+
if const_expr(full_block_cnt is not None):
|
| 199 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 200 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 201 |
+
else:
|
| 202 |
+
curr_full_block_cnt = Int32(0)
|
| 203 |
+
curr_full_block_idx = None
|
| 204 |
+
|
| 205 |
+
mask_empty = curr_mask_block_cnt == 0
|
| 206 |
+
full_empty = curr_full_block_cnt == 0
|
| 207 |
+
|
| 208 |
+
if mask_empty:
|
| 209 |
+
# No masked blocks: the full list owns the initial K load.
|
| 210 |
+
kv_producer_state = load_block_list(
|
| 211 |
+
curr_full_block_idx,
|
| 212 |
+
curr_full_block_cnt,
|
| 213 |
+
first_block_preloaded=False,
|
| 214 |
+
kv_producer_state=kv_producer_state,
|
| 215 |
+
load_K=load_K,
|
| 216 |
+
load_V=load_V,
|
| 217 |
+
pipeline_k=pipeline_k,
|
| 218 |
+
pipeline_v=pipeline_v,
|
| 219 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
if const_expr(intra_wg_overlap) and curr_full_block_cnt > 0:
|
| 223 |
+
kv_producer_state = finish_overlap_v_load(
|
| 224 |
+
curr_full_block_idx,
|
| 225 |
+
curr_full_block_cnt,
|
| 226 |
+
load_V,
|
| 227 |
+
pipeline_v,
|
| 228 |
+
kv_producer_state,
|
| 229 |
+
)
|
| 230 |
+
else:
|
| 231 |
+
# Masked blocks present. When overlap is disabled this fully drains the list.
|
| 232 |
+
kv_producer_state = load_block_list(
|
| 233 |
+
curr_mask_block_idx,
|
| 234 |
+
curr_mask_block_cnt,
|
| 235 |
+
first_block_preloaded=False,
|
| 236 |
+
kv_producer_state=kv_producer_state,
|
| 237 |
+
load_K=load_K,
|
| 238 |
+
load_V=load_V,
|
| 239 |
+
pipeline_k=pipeline_k,
|
| 240 |
+
pipeline_v=pipeline_v,
|
| 241 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
if full_empty:
|
| 245 |
+
if const_expr(intra_wg_overlap):
|
| 246 |
+
kv_producer_state = finish_overlap_v_load(
|
| 247 |
+
curr_mask_block_idx,
|
| 248 |
+
curr_mask_block_cnt,
|
| 249 |
+
load_V,
|
| 250 |
+
pipeline_v,
|
| 251 |
+
kv_producer_state,
|
| 252 |
+
)
|
| 253 |
+
else:
|
| 254 |
+
if const_expr(intra_wg_overlap):
|
| 255 |
+
# Bridge the masked list to the full list by overlapping the pending masked V
|
| 256 |
+
# with the first full K load.
|
| 257 |
+
n_block_mask_last = curr_mask_block_idx[0]
|
| 258 |
+
n_block_full_first = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 259 |
+
kv_producer_state_prev = kv_producer_state.clone()
|
| 260 |
+
kv_producer_state.advance()
|
| 261 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 262 |
+
load_K(src_idx=n_block_full_first, producer_state=kv_producer_state)
|
| 263 |
+
pipeline_v.producer_acquire(kv_producer_state_prev)
|
| 264 |
+
load_V(src_idx=n_block_mask_last, producer_state=kv_producer_state_prev)
|
| 265 |
+
|
| 266 |
+
kv_producer_state = load_block_list(
|
| 267 |
+
curr_full_block_idx,
|
| 268 |
+
curr_full_block_cnt,
|
| 269 |
+
first_block_preloaded=True,
|
| 270 |
+
kv_producer_state=kv_producer_state,
|
| 271 |
+
load_K=load_K,
|
| 272 |
+
load_V=load_V,
|
| 273 |
+
pipeline_k=pipeline_k,
|
| 274 |
+
pipeline_v=pipeline_v,
|
| 275 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
kv_producer_state = finish_overlap_v_load(
|
| 279 |
+
curr_full_block_idx,
|
| 280 |
+
curr_full_block_cnt,
|
| 281 |
+
load_V,
|
| 282 |
+
pipeline_v,
|
| 283 |
+
kv_producer_state,
|
| 284 |
+
)
|
| 285 |
+
else:
|
| 286 |
+
# Non-overlap path with both lists: run the full list normally.
|
| 287 |
+
kv_producer_state = load_block_list(
|
| 288 |
+
curr_full_block_idx,
|
| 289 |
+
curr_full_block_cnt,
|
| 290 |
+
first_block_preloaded=False,
|
| 291 |
+
kv_producer_state=kv_producer_state,
|
| 292 |
+
load_K=load_K,
|
| 293 |
+
load_V=load_V,
|
| 294 |
+
pipeline_k=pipeline_k,
|
| 295 |
+
pipeline_v=pipeline_v,
|
| 296 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
return kv_producer_state
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
@cute.jit
|
| 303 |
+
def consume_block_sparse_loads(
|
| 304 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 305 |
+
batch_idx,
|
| 306 |
+
head_idx,
|
| 307 |
+
m_block,
|
| 308 |
+
seqlen,
|
| 309 |
+
kv_consumer_state,
|
| 310 |
+
mma_pv_fn,
|
| 311 |
+
mma_one_n_block,
|
| 312 |
+
process_first_half_block,
|
| 313 |
+
process_last_half_block,
|
| 314 |
+
mask_fn,
|
| 315 |
+
score_mod_fn,
|
| 316 |
+
O_should_accumulate,
|
| 317 |
+
mask_mod,
|
| 318 |
+
fastdiv_mods,
|
| 319 |
+
intra_wg_overlap: cutlass.Constexpr,
|
| 320 |
+
warp_scheduler_barrier_sync: Callable,
|
| 321 |
+
warp_scheduler_barrier_arrive: Callable,
|
| 322 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 323 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 324 |
+
):
|
| 325 |
+
"""Consume the mask and full block lists for a single tile on the consumer side.
|
| 326 |
+
|
| 327 |
+
Mirrors `produce_block_sparse_loads` so that the consumer pipeline uses
|
| 328 |
+
the same sparse tensor indexing.
|
| 329 |
+
|
| 330 |
+
Args:
|
| 331 |
+
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 332 |
+
must be converted to unpacked for sparse tensor indexing.
|
| 333 |
+
"""
|
| 334 |
+
|
| 335 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx, *_ = blocksparse_tensors
|
| 336 |
+
|
| 337 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 338 |
+
|
| 339 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 340 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 341 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 342 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 343 |
+
|
| 344 |
+
processed_any = curr_mask_block_cnt + curr_full_block_cnt > 0
|
| 345 |
+
|
| 346 |
+
if const_expr(not intra_wg_overlap):
|
| 347 |
+
if curr_mask_block_cnt > 0:
|
| 348 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 349 |
+
warp_scheduler_barrier_sync()
|
| 350 |
+
kv_consumer_state = mma_one_n_block(
|
| 351 |
+
kv_consumer_state,
|
| 352 |
+
n_block=mask_n_block,
|
| 353 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 354 |
+
mask_fn=partial(
|
| 355 |
+
mask_fn,
|
| 356 |
+
mask_mod=mask_mod,
|
| 357 |
+
mask_seqlen=True,
|
| 358 |
+
fastdiv_mods=fastdiv_mods if cutlass.const_expr(mask_mod is not None) else None,
|
| 359 |
+
),
|
| 360 |
+
is_first_n_block=True,
|
| 361 |
+
)
|
| 362 |
+
O_should_accumulate = True
|
| 363 |
+
for i in cutlass.range(1, curr_mask_block_cnt):
|
| 364 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1 - i]
|
| 365 |
+
kv_consumer_state = mma_one_n_block(
|
| 366 |
+
kv_consumer_state,
|
| 367 |
+
n_block=mask_n_block,
|
| 368 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 369 |
+
mask_fn=partial(mask_fn, mask_mod=mask_mod, mask_seqlen=False),
|
| 370 |
+
is_first_n_block=False,
|
| 371 |
+
)
|
| 372 |
+
O_should_accumulate = True
|
| 373 |
+
if curr_full_block_cnt == 0:
|
| 374 |
+
warp_scheduler_barrier_arrive()
|
| 375 |
+
|
| 376 |
+
if curr_full_block_cnt > 0:
|
| 377 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 378 |
+
if curr_mask_block_cnt == 0:
|
| 379 |
+
warp_scheduler_barrier_sync()
|
| 380 |
+
kv_consumer_state = mma_one_n_block(
|
| 381 |
+
kv_consumer_state,
|
| 382 |
+
n_block=full_n_block,
|
| 383 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 384 |
+
mask_fn=partial(mask_fn, mask_seqlen=True),
|
| 385 |
+
is_first_n_block=True,
|
| 386 |
+
)
|
| 387 |
+
O_should_accumulate = True
|
| 388 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 389 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 390 |
+
kv_consumer_state = mma_one_n_block(
|
| 391 |
+
kv_consumer_state,
|
| 392 |
+
n_block=full_n_block,
|
| 393 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 394 |
+
mask_fn=partial(mask_fn, mask_seqlen=False),
|
| 395 |
+
is_first_n_block=False,
|
| 396 |
+
)
|
| 397 |
+
O_should_accumulate = True
|
| 398 |
+
else:
|
| 399 |
+
kv_consumer_state = mma_one_n_block(
|
| 400 |
+
kv_consumer_state,
|
| 401 |
+
n_block=full_n_block,
|
| 402 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 403 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 404 |
+
is_first_n_block=False,
|
| 405 |
+
)
|
| 406 |
+
O_should_accumulate = True
|
| 407 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 408 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 409 |
+
kv_consumer_state = mma_one_n_block(
|
| 410 |
+
kv_consumer_state,
|
| 411 |
+
n_block=full_n_block,
|
| 412 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 413 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=False),
|
| 414 |
+
is_first_n_block=False,
|
| 415 |
+
)
|
| 416 |
+
O_should_accumulate = True
|
| 417 |
+
warp_scheduler_barrier_arrive()
|
| 418 |
+
else:
|
| 419 |
+
if curr_mask_block_cnt > 0:
|
| 420 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 421 |
+
kv_consumer_state = process_first_half_block(
|
| 422 |
+
n_block=mask_n_block,
|
| 423 |
+
seqlen=seqlen,
|
| 424 |
+
kv_consumer_state=kv_consumer_state,
|
| 425 |
+
mask_fn=partial(
|
| 426 |
+
mask_fn,
|
| 427 |
+
mask_mod=mask_mod,
|
| 428 |
+
mask_seqlen=True,
|
| 429 |
+
fastdiv_mods=fastdiv_mods if cutlass.const_expr(mask_mod is not None) else None,
|
| 430 |
+
),
|
| 431 |
+
score_mod_fn=score_mod_fn,
|
| 432 |
+
is_first_block=True,
|
| 433 |
+
)
|
| 434 |
+
for i in cutlass.range(1, curr_mask_block_cnt):
|
| 435 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1 - i]
|
| 436 |
+
kv_consumer_state = mma_one_n_block(
|
| 437 |
+
kv_consumer_state,
|
| 438 |
+
n_block=mask_n_block,
|
| 439 |
+
seqlen=seqlen,
|
| 440 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 441 |
+
mask_fn=partial(mask_fn, mask_mod=mask_mod, mask_seqlen=False),
|
| 442 |
+
)
|
| 443 |
+
O_should_accumulate = True
|
| 444 |
+
|
| 445 |
+
if curr_full_block_cnt > 0:
|
| 446 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 447 |
+
if curr_mask_block_cnt == 0:
|
| 448 |
+
kv_consumer_state = process_first_half_block(
|
| 449 |
+
n_block=full_n_block,
|
| 450 |
+
seqlen=seqlen,
|
| 451 |
+
kv_consumer_state=kv_consumer_state,
|
| 452 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 453 |
+
score_mod_fn=score_mod_fn,
|
| 454 |
+
is_first_block=True,
|
| 455 |
+
)
|
| 456 |
+
else:
|
| 457 |
+
kv_consumer_state = mma_one_n_block(
|
| 458 |
+
kv_consumer_state,
|
| 459 |
+
n_block=full_n_block,
|
| 460 |
+
seqlen=seqlen,
|
| 461 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 462 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 463 |
+
)
|
| 464 |
+
O_should_accumulate = True
|
| 465 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 466 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 467 |
+
kv_consumer_state = mma_one_n_block(
|
| 468 |
+
kv_consumer_state,
|
| 469 |
+
n_block=full_n_block,
|
| 470 |
+
seqlen=seqlen,
|
| 471 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 472 |
+
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=False),
|
| 473 |
+
)
|
| 474 |
+
O_should_accumulate = True
|
| 475 |
+
|
| 476 |
+
if curr_mask_block_cnt + curr_full_block_cnt > 0:
|
| 477 |
+
kv_consumer_state = process_last_half_block(
|
| 478 |
+
kv_consumer_state=kv_consumer_state,
|
| 479 |
+
zero_init=not O_should_accumulate,
|
| 480 |
+
)
|
| 481 |
+
O_should_accumulate = True
|
| 482 |
+
|
| 483 |
+
return kv_consumer_state, O_should_accumulate, processed_any
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
@cute.jit
|
| 487 |
+
def load_block_list_sm100(
|
| 488 |
+
block_indices: cute.Tensor,
|
| 489 |
+
block_count,
|
| 490 |
+
load_q_with_first: cutlass.Constexpr,
|
| 491 |
+
q_stage: cutlass.Constexpr,
|
| 492 |
+
kv_producer_state,
|
| 493 |
+
load_Q,
|
| 494 |
+
load_K,
|
| 495 |
+
load_V,
|
| 496 |
+
pipeline_kv,
|
| 497 |
+
):
|
| 498 |
+
"""SM100 version of load_block_list (no intra_wg_overlap, no extra_tx_count)."""
|
| 499 |
+
if block_count > 0:
|
| 500 |
+
# First iteration: load Q alongside K if requested
|
| 501 |
+
n_block_first = block_indices[block_count - 1]
|
| 502 |
+
|
| 503 |
+
if const_expr(load_q_with_first):
|
| 504 |
+
# SM100 loads Q0 and optionally Q1
|
| 505 |
+
load_Q(block=0, stage=0)
|
| 506 |
+
if const_expr(q_stage == 2):
|
| 507 |
+
load_Q(block=1, stage=1)
|
| 508 |
+
|
| 509 |
+
# SM100 doesn't use producer_acquire for pipeline_kv in load path
|
| 510 |
+
# The pipeline barriers are handled inside load_KV
|
| 511 |
+
load_K(block=n_block_first, producer_state=kv_producer_state, page_idx=None)
|
| 512 |
+
kv_producer_state.advance()
|
| 513 |
+
load_V(block=n_block_first, producer_state=kv_producer_state, page_idx=None)
|
| 514 |
+
kv_producer_state.advance()
|
| 515 |
+
|
| 516 |
+
# Remaining blocks
|
| 517 |
+
for offset in cutlass.range(1, block_count):
|
| 518 |
+
n_block = block_indices[block_count - 1 - offset]
|
| 519 |
+
load_K(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
| 520 |
+
kv_producer_state.advance()
|
| 521 |
+
load_V(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
| 522 |
+
kv_producer_state.advance()
|
| 523 |
+
|
| 524 |
+
return kv_producer_state
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
# SM100-specific tile processor using SM100 helpers
|
| 528 |
+
@cute.jit
|
| 529 |
+
def produce_block_sparse_loads_sm100(
|
| 530 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 531 |
+
batch_idx,
|
| 532 |
+
head_idx,
|
| 533 |
+
m_block,
|
| 534 |
+
kv_producer_state,
|
| 535 |
+
load_Q,
|
| 536 |
+
load_K,
|
| 537 |
+
load_V,
|
| 538 |
+
pipeline_kv,
|
| 539 |
+
q_stage: cutlass.Constexpr,
|
| 540 |
+
q_producer_phase: Int32,
|
| 541 |
+
qhead_per_kvhead: cutlass.Constexpr,
|
| 542 |
+
q_subtile_factor: cutlass.Constexpr,
|
| 543 |
+
):
|
| 544 |
+
"""SM100 entry point for sparse block iteration.
|
| 545 |
+
|
| 546 |
+
SM100 uses PipelineTmaUmma which doesn't support extra_tx_count, so we use
|
| 547 |
+
simplified block processing that just calls producer_acquire without extras.
|
| 548 |
+
|
| 549 |
+
Args:
|
| 550 |
+
m_block: which tile of m we are processing
|
| 551 |
+
qhead_per_kvhead: Constexpr pack factor
|
| 552 |
+
"""
|
| 553 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 554 |
+
|
| 555 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx, *_ = blocksparse_tensors
|
| 556 |
+
|
| 557 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 558 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 559 |
+
|
| 560 |
+
if const_expr(full_block_cnt is not None):
|
| 561 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 562 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 563 |
+
else:
|
| 564 |
+
curr_full_block_cnt = Int32(0)
|
| 565 |
+
curr_full_block_idx = None
|
| 566 |
+
|
| 567 |
+
mask_empty = curr_mask_block_cnt == 0
|
| 568 |
+
full_empty = curr_full_block_cnt == 0
|
| 569 |
+
|
| 570 |
+
q_phase_flipped = False
|
| 571 |
+
|
| 572 |
+
if mask_empty:
|
| 573 |
+
# No masked blocks: process full list with Q loading
|
| 574 |
+
kv_producer_state = load_block_list_sm100(
|
| 575 |
+
curr_full_block_idx,
|
| 576 |
+
curr_full_block_cnt,
|
| 577 |
+
load_q_with_first=True,
|
| 578 |
+
q_stage=q_stage,
|
| 579 |
+
kv_producer_state=kv_producer_state,
|
| 580 |
+
load_Q=load_Q,
|
| 581 |
+
load_K=load_K,
|
| 582 |
+
load_V=load_V,
|
| 583 |
+
pipeline_kv=pipeline_kv,
|
| 584 |
+
)
|
| 585 |
+
q_phase_flipped = not full_empty
|
| 586 |
+
else:
|
| 587 |
+
# Process masked blocks with Q loading
|
| 588 |
+
kv_producer_state = load_block_list_sm100(
|
| 589 |
+
curr_mask_block_idx,
|
| 590 |
+
curr_mask_block_cnt,
|
| 591 |
+
load_q_with_first=True,
|
| 592 |
+
q_stage=q_stage,
|
| 593 |
+
kv_producer_state=kv_producer_state,
|
| 594 |
+
load_Q=load_Q,
|
| 595 |
+
load_K=load_K,
|
| 596 |
+
load_V=load_V,
|
| 597 |
+
pipeline_kv=pipeline_kv,
|
| 598 |
+
)
|
| 599 |
+
q_phase_flipped = True
|
| 600 |
+
|
| 601 |
+
if not full_empty:
|
| 602 |
+
# Process full blocks without Q loading
|
| 603 |
+
kv_producer_state = load_block_list_sm100(
|
| 604 |
+
curr_full_block_idx,
|
| 605 |
+
curr_full_block_cnt,
|
| 606 |
+
load_q_with_first=False,
|
| 607 |
+
q_stage=q_stage,
|
| 608 |
+
kv_producer_state=kv_producer_state,
|
| 609 |
+
load_Q=load_Q,
|
| 610 |
+
load_K=load_K,
|
| 611 |
+
load_V=load_V,
|
| 612 |
+
pipeline_kv=pipeline_kv,
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
if q_phase_flipped:
|
| 616 |
+
q_producer_phase ^= 1
|
| 617 |
+
|
| 618 |
+
return kv_producer_state, q_producer_phase
|
| 619 |
+
|
| 620 |
+
|
| 621 |
+
@cute.jit
|
| 622 |
+
def get_total_block_count(
|
| 623 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 624 |
+
batch_idx,
|
| 625 |
+
head_idx,
|
| 626 |
+
m_block,
|
| 627 |
+
qhead_per_kvhead: cutlass.Constexpr,
|
| 628 |
+
q_subtile_factor: cutlass.Constexpr,
|
| 629 |
+
):
|
| 630 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 631 |
+
|
| 632 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx, *_ = blocksparse_tensors
|
| 633 |
+
if const_expr(full_block_cnt is not None):
|
| 634 |
+
return (
|
| 635 |
+
mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 636 |
+
+ full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 637 |
+
)
|
| 638 |
+
else:
|
| 639 |
+
return mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
@cute.jit
|
| 643 |
+
def handle_block_sparse_empty_tile_correction_sm100(
|
| 644 |
+
tidx: Int32,
|
| 645 |
+
q_stage: cutlass.Constexpr,
|
| 646 |
+
m_block_size: cutlass.Constexpr,
|
| 647 |
+
qhead_per_kvhead,
|
| 648 |
+
pack_gqa: cutlass.Constexpr,
|
| 649 |
+
is_split_kv: cutlass.Constexpr,
|
| 650 |
+
learnable_sink,
|
| 651 |
+
mLSE,
|
| 652 |
+
seqlen,
|
| 653 |
+
m_block: Int32,
|
| 654 |
+
head_idx: Int32,
|
| 655 |
+
batch_idx: Int32,
|
| 656 |
+
split_idx: Int32,
|
| 657 |
+
sScale: cute.Tensor,
|
| 658 |
+
stats: list,
|
| 659 |
+
correction_epilogue: Callable,
|
| 660 |
+
thr_mma_pv: cute.core.ThrMma,
|
| 661 |
+
tOtO: cute.Tensor,
|
| 662 |
+
sO: cute.Tensor,
|
| 663 |
+
pipeline_sm_stats: cutlass.pipeline.PipelineAsync,
|
| 664 |
+
sm_stats_barrier: cutlass.pipeline.NamedBarrier,
|
| 665 |
+
pipeline_o_epi: cutlass.pipeline.PipelineAsync,
|
| 666 |
+
sm_stats_consumer_phase: Int32,
|
| 667 |
+
o_corr_consumer_phase: Int32,
|
| 668 |
+
corr_epi_producer_phase: Int32,
|
| 669 |
+
softmax_scale_log2: Float32,
|
| 670 |
+
max_offset: Float32,
|
| 671 |
+
max_offset_scale: Float32,
|
| 672 |
+
mO_cur: Optional[cute.Tensor] = None,
|
| 673 |
+
gO: Optional[cute.Tensor] = None,
|
| 674 |
+
gmem_tiled_copy_O: Optional[cute.TiledCopy] = None,
|
| 675 |
+
):
|
| 676 |
+
"""Handle SM100 forward block-sparse tiles with no active KV blocks.
|
| 677 |
+
|
| 678 |
+
This path is taken when `total_block_cnt == 0`. The softmax warp-group still
|
| 679 |
+
arrives `mbar_softmax_corr_full` (synthetic "no work") so the correction
|
| 680 |
+
warp-group can:
|
| 681 |
+
|
| 682 |
+
- seed fully-masked-row stats (row_sum=1; row_max=-inf when tracked) for LSE
|
| 683 |
+
- run `correction_epilogue` with `scale=0` so the output tile is written as zeros
|
| 684 |
+
(independent of any prior tmem contents)
|
| 685 |
+
- wait on `mbar_softmax_corr_full` and arrive `mbar_softmax_corr_empty`
|
| 686 |
+
(and `mbar_corr_epi_*` when applicable) so phases stay aligned across tiles
|
| 687 |
+
|
| 688 |
+
This helper intentionally does not touch `mbar_P_full_*` since no P is produced.
|
| 689 |
+
See NOTE [SM100 block-sparse empty tiles: mbarrier contract].
|
| 690 |
+
"""
|
| 691 |
+
LOG2_E = Float32(math.log2(math.e))
|
| 692 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % 4
|
| 693 |
+
|
| 694 |
+
for stage in cutlass.range_constexpr(q_stage):
|
| 695 |
+
row_sum_value = Float32(1.0)
|
| 696 |
+
row_max_value = (
|
| 697 |
+
-Float32.inf if const_expr(mLSE is not None or learnable_sink is not None) else None
|
| 698 |
+
)
|
| 699 |
+
if const_expr(learnable_sink is not None):
|
| 700 |
+
sink_val = -Float32.inf
|
| 701 |
+
if const_expr(not pack_gqa):
|
| 702 |
+
sink_val = Float32(learnable_sink[head_idx])
|
| 703 |
+
elif tidx < m_block_size:
|
| 704 |
+
q_head_idx = (
|
| 705 |
+
(q_stage * m_block + stage) * m_block_size + tidx
|
| 706 |
+
) % qhead_per_kvhead + head_idx * qhead_per_kvhead
|
| 707 |
+
sink_val = Float32(learnable_sink[q_head_idx])
|
| 708 |
+
if sink_val != -Float32.inf and (const_expr(not is_split_kv) or split_idx == 0):
|
| 709 |
+
if row_max_value == -Float32.inf:
|
| 710 |
+
row_max_value = sink_val * (LOG2_E / softmax_scale_log2)
|
| 711 |
+
row_sum_value = max_offset_scale
|
| 712 |
+
else:
|
| 713 |
+
row_sum_value = row_sum_value + cute.math.exp2(
|
| 714 |
+
sink_val * LOG2_E - row_max_value * softmax_scale_log2 + max_offset,
|
| 715 |
+
fastmath=True,
|
| 716 |
+
)
|
| 717 |
+
if tidx < m_block_size:
|
| 718 |
+
scale_row_idx = tidx + stage * m_block_size
|
| 719 |
+
sScale[scale_row_idx] = row_sum_value
|
| 720 |
+
if const_expr(mLSE is not None or learnable_sink is not None):
|
| 721 |
+
sScale[scale_row_idx + q_stage * m_block_size] = row_max_value
|
| 722 |
+
acc_flag = row_sum_value == Float32(0.0) or row_sum_value != row_sum_value
|
| 723 |
+
stats[stage] = (row_sum_value, row_max_value, acc_flag)
|
| 724 |
+
|
| 725 |
+
# See NOTE [SM100 block-sparse empty tiles: mbarrier contract].
|
| 726 |
+
# pipeline_sm_stats.consumer_wait_w_index_phase(stage, sm_stats_consumer_phase)
|
| 727 |
+
sm_stats_barrier.arrive_and_wait_w_index(index=stage * 4 + warp_idx)
|
| 728 |
+
pipeline_sm_stats.consumer_release_w_index(stage)
|
| 729 |
+
|
| 730 |
+
if const_expr(gmem_tiled_copy_O is None):
|
| 731 |
+
pipeline_o_epi.producer_acquire_w_index_phase(stage, corr_epi_producer_phase)
|
| 732 |
+
|
| 733 |
+
gO_stage = gO[None, None, stage] if const_expr(gO is not None) else None
|
| 734 |
+
correction_epilogue(
|
| 735 |
+
thr_mma_pv,
|
| 736 |
+
tOtO[None, None, None, stage],
|
| 737 |
+
tidx,
|
| 738 |
+
stage,
|
| 739 |
+
m_block,
|
| 740 |
+
seqlen.seqlen_q,
|
| 741 |
+
Float32(0.0), # zero scale ensures empty tile writes zeros into staged outputs
|
| 742 |
+
sO[None, None, stage],
|
| 743 |
+
mO_cur,
|
| 744 |
+
gO_stage,
|
| 745 |
+
gmem_tiled_copy_O,
|
| 746 |
+
)
|
| 747 |
+
if const_expr(gmem_tiled_copy_O is None):
|
| 748 |
+
pipeline_o_epi.producer_commit_w_index(stage)
|
| 749 |
+
|
| 750 |
+
sm_stats_consumer_phase ^= 1
|
| 751 |
+
corr_epi_producer_phase ^= 1
|
| 752 |
+
|
| 753 |
+
return (
|
| 754 |
+
sm_stats_consumer_phase,
|
| 755 |
+
o_corr_consumer_phase,
|
| 756 |
+
corr_epi_producer_phase,
|
| 757 |
+
)
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
@cute.jit
|
| 761 |
+
def softmax_block_sparse_sm100(
|
| 762 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 763 |
+
batch_idx,
|
| 764 |
+
head_idx,
|
| 765 |
+
m_block,
|
| 766 |
+
softmax_step: Callable,
|
| 767 |
+
mask_fn: Callable,
|
| 768 |
+
mask_fn_none: Callable,
|
| 769 |
+
mma_si_consumer_phase: Int32,
|
| 770 |
+
si_corr_producer_phase: Int32,
|
| 771 |
+
s0_s1_sequence_phase: Int32,
|
| 772 |
+
pipeline_sm_stats: cutlass.pipeline.PipelineAsync,
|
| 773 |
+
sm_stats_barrier: cutlass.pipeline.NamedBarrier,
|
| 774 |
+
q_stage: cutlass.Constexpr,
|
| 775 |
+
stage_idx: Int32,
|
| 776 |
+
check_m_boundary: bool,
|
| 777 |
+
qhead_per_kvhead: cutlass.Constexpr,
|
| 778 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 779 |
+
):
|
| 780 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % 4
|
| 781 |
+
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 782 |
+
|
| 783 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx, *_ = blocksparse_tensors
|
| 784 |
+
|
| 785 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 786 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 787 |
+
|
| 788 |
+
if const_expr(full_block_cnt is not None):
|
| 789 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block_sparse]
|
| 790 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block_sparse, None]
|
| 791 |
+
else:
|
| 792 |
+
curr_full_block_cnt = Int32(0)
|
| 793 |
+
curr_full_block_idx = None
|
| 794 |
+
|
| 795 |
+
total_block_cnt = curr_mask_block_cnt + curr_full_block_cnt
|
| 796 |
+
|
| 797 |
+
if total_block_cnt == 0:
|
| 798 |
+
sm_stats_barrier.arrive_w_index(index=stage_idx * 4 + warp_idx)
|
| 799 |
+
else:
|
| 800 |
+
if curr_mask_block_cnt > 0:
|
| 801 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 802 |
+
(
|
| 803 |
+
mma_si_consumer_phase,
|
| 804 |
+
si_corr_producer_phase,
|
| 805 |
+
s0_s1_sequence_phase,
|
| 806 |
+
) = softmax_step(
|
| 807 |
+
mma_si_consumer_phase,
|
| 808 |
+
si_corr_producer_phase,
|
| 809 |
+
s0_s1_sequence_phase,
|
| 810 |
+
mask_n_block,
|
| 811 |
+
is_first=True,
|
| 812 |
+
mask_fn=partial(mask_fn, mask_seqlen=True, check_q_boundary=check_m_boundary),
|
| 813 |
+
)
|
| 814 |
+
for i in cutlass.range(1, curr_mask_block_cnt):
|
| 815 |
+
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1 - i]
|
| 816 |
+
(
|
| 817 |
+
mma_si_consumer_phase,
|
| 818 |
+
si_corr_producer_phase,
|
| 819 |
+
s0_s1_sequence_phase,
|
| 820 |
+
) = softmax_step(
|
| 821 |
+
mma_si_consumer_phase,
|
| 822 |
+
si_corr_producer_phase,
|
| 823 |
+
s0_s1_sequence_phase,
|
| 824 |
+
mask_n_block,
|
| 825 |
+
mask_fn=partial(mask_fn, mask_seqlen=False, check_q_boundary=check_m_boundary),
|
| 826 |
+
)
|
| 827 |
+
|
| 828 |
+
if curr_full_block_cnt > 0:
|
| 829 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1]
|
| 830 |
+
if curr_mask_block_cnt == 0:
|
| 831 |
+
(
|
| 832 |
+
mma_si_consumer_phase,
|
| 833 |
+
si_corr_producer_phase,
|
| 834 |
+
s0_s1_sequence_phase,
|
| 835 |
+
) = softmax_step(
|
| 836 |
+
mma_si_consumer_phase,
|
| 837 |
+
si_corr_producer_phase,
|
| 838 |
+
s0_s1_sequence_phase,
|
| 839 |
+
full_n_block,
|
| 840 |
+
is_first=True,
|
| 841 |
+
mask_fn=partial(
|
| 842 |
+
mask_fn_none, mask_seqlen=True, check_q_boundary=check_m_boundary
|
| 843 |
+
),
|
| 844 |
+
)
|
| 845 |
+
else:
|
| 846 |
+
(
|
| 847 |
+
mma_si_consumer_phase,
|
| 848 |
+
si_corr_producer_phase,
|
| 849 |
+
s0_s1_sequence_phase,
|
| 850 |
+
) = softmax_step(
|
| 851 |
+
mma_si_consumer_phase,
|
| 852 |
+
si_corr_producer_phase,
|
| 853 |
+
s0_s1_sequence_phase,
|
| 854 |
+
full_n_block,
|
| 855 |
+
is_first=False,
|
| 856 |
+
mask_fn=partial(
|
| 857 |
+
mask_fn_none, mask_seqlen=False, check_q_boundary=check_m_boundary
|
| 858 |
+
),
|
| 859 |
+
)
|
| 860 |
+
for i in cutlass.range(1, curr_full_block_cnt):
|
| 861 |
+
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 862 |
+
(
|
| 863 |
+
mma_si_consumer_phase,
|
| 864 |
+
si_corr_producer_phase,
|
| 865 |
+
s0_s1_sequence_phase,
|
| 866 |
+
) = softmax_step(
|
| 867 |
+
mma_si_consumer_phase,
|
| 868 |
+
si_corr_producer_phase,
|
| 869 |
+
s0_s1_sequence_phase,
|
| 870 |
+
full_n_block,
|
| 871 |
+
mask_fn=partial(
|
| 872 |
+
mask_fn_none, mask_seqlen=False, check_q_boundary=check_m_boundary
|
| 873 |
+
),
|
| 874 |
+
)
|
| 875 |
+
|
| 876 |
+
return (
|
| 877 |
+
mma_si_consumer_phase,
|
| 878 |
+
si_corr_producer_phase,
|
| 879 |
+
s0_s1_sequence_phase,
|
| 880 |
+
total_block_cnt == 0,
|
| 881 |
+
)
|
| 882 |
+
|
| 883 |
+
|
| 884 |
+
# =============================================================================
|
| 885 |
+
# Backward-specific block-sparse helpers (SM100)
|
| 886 |
+
# =============================================================================
|
| 887 |
+
#
|
| 888 |
+
# In backward, iteration is transposed compared to forward:
|
| 889 |
+
# - Forward: outer loop over m_blocks (Q tiles), inner loop over n_blocks (KV tiles)
|
| 890 |
+
# - Backward: outer loop over n_blocks (KV tiles), inner loop over m_blocks (Q tiles)
|
| 891 |
+
#
|
| 892 |
+
# The backward block-sparse tensors use "Q direction" indexing:
|
| 893 |
+
# - q_block_cnt[batch, head, n_block] → count of m_blocks to process for this KV tile
|
| 894 |
+
# - q_block_idx[batch, head, n_block, :] → indices of m_blocks to process
|
| 895 |
+
#
|
| 896 |
+
|
| 897 |
+
|
| 898 |
+
@cute.jit
|
| 899 |
+
def get_total_q_block_count_bwd(
|
| 900 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 901 |
+
batch_idx,
|
| 902 |
+
head_idx,
|
| 903 |
+
n_block,
|
| 904 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 905 |
+
m_block_max: int = 0,
|
| 906 |
+
):
|
| 907 |
+
"""Count total tile iterations for given n_block (KV tile) in backward."""
|
| 908 |
+
q_block_cnt, _, full_block_cnt, _, *_ = blocksparse_tensors
|
| 909 |
+
total = q_block_cnt[batch_idx, head_idx, n_block]
|
| 910 |
+
if const_expr(full_block_cnt is not None):
|
| 911 |
+
total = total + full_block_cnt[batch_idx, head_idx, n_block]
|
| 912 |
+
return total * subtile_factor
|
| 913 |
+
|
| 914 |
+
|
| 915 |
+
@cute.jit
|
| 916 |
+
def produce_block_sparse_q_loads_bwd_sm100(
|
| 917 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 918 |
+
batch_idx,
|
| 919 |
+
head_idx,
|
| 920 |
+
n_block,
|
| 921 |
+
# Pipeline states (will be returned after advancing)
|
| 922 |
+
producer_state_Q_LSE,
|
| 923 |
+
producer_state_dO_dPsum,
|
| 924 |
+
# Pipelines
|
| 925 |
+
pipeline_Q,
|
| 926 |
+
pipeline_LSE,
|
| 927 |
+
pipeline_dO,
|
| 928 |
+
pipeline_dPsum,
|
| 929 |
+
# Load functions
|
| 930 |
+
load_K,
|
| 931 |
+
load_V,
|
| 932 |
+
load_Q,
|
| 933 |
+
load_dO,
|
| 934 |
+
copy_stats,
|
| 935 |
+
# Global tensors for LSE/dPsum
|
| 936 |
+
gLSE,
|
| 937 |
+
sLSE,
|
| 938 |
+
gdPsum,
|
| 939 |
+
sdPsum,
|
| 940 |
+
# TMA copy bytes for extra_tx_count
|
| 941 |
+
tma_copy_bytes_K,
|
| 942 |
+
tma_copy_bytes_V,
|
| 943 |
+
# Flags for which loads to perform
|
| 944 |
+
should_load_Q: cutlass.Constexpr,
|
| 945 |
+
should_load_dO: cutlass.Constexpr,
|
| 946 |
+
# Subtiling factor and bounds
|
| 947 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 948 |
+
m_block_max: int = 0,
|
| 949 |
+
):
|
| 950 |
+
"""SM100 backward block sparse loading with subtiling.
|
| 951 |
+
|
| 952 |
+
Returns updated (producer_state_Q_LSE, producer_state_dO_dPsum).
|
| 953 |
+
First iteration loads K/V alongside Q/dO; subsequent iterations load only Q/dO.
|
| 954 |
+
"""
|
| 955 |
+
(
|
| 956 |
+
curr_q_cnt,
|
| 957 |
+
curr_q_idx,
|
| 958 |
+
curr_full_cnt,
|
| 959 |
+
curr_full_idx,
|
| 960 |
+
loop_count,
|
| 961 |
+
) = get_block_sparse_iteration_info_bwd(
|
| 962 |
+
blocksparse_tensors, batch_idx, head_idx, n_block, subtile_factor, m_block_max
|
| 963 |
+
)
|
| 964 |
+
|
| 965 |
+
for iter_idx in cutlass.range(loop_count, unroll=1):
|
| 966 |
+
m_block, _ = get_m_block_from_iter_bwd(
|
| 967 |
+
iter_idx,
|
| 968 |
+
curr_q_cnt,
|
| 969 |
+
curr_q_idx,
|
| 970 |
+
curr_full_cnt,
|
| 971 |
+
curr_full_idx,
|
| 972 |
+
subtile_factor,
|
| 973 |
+
m_block_max,
|
| 974 |
+
)
|
| 975 |
+
m_block_safe = m_block
|
| 976 |
+
if m_block_max > 0:
|
| 977 |
+
m_block_safe = cutlass.min(m_block, m_block_max - 1)
|
| 978 |
+
|
| 979 |
+
if iter_idx == 0:
|
| 980 |
+
# First block: load K/V alongside Q/dO
|
| 981 |
+
if const_expr(should_load_Q):
|
| 982 |
+
pipeline_Q.producer_acquire(producer_state_Q_LSE, extra_tx_count=tma_copy_bytes_K)
|
| 983 |
+
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q_LSE))
|
| 984 |
+
load_Q(m_block_safe, producer_state=producer_state_Q_LSE)
|
| 985 |
+
pipeline_Q.producer_commit(producer_state_Q_LSE)
|
| 986 |
+
pipeline_LSE.producer_acquire(producer_state_Q_LSE)
|
| 987 |
+
with cute.arch.elect_one():
|
| 988 |
+
copy_stats(
|
| 989 |
+
gLSE[None, m_block_safe],
|
| 990 |
+
sLSE[None, producer_state_Q_LSE.index],
|
| 991 |
+
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
| 992 |
+
)
|
| 993 |
+
producer_state_Q_LSE.advance()
|
| 994 |
+
if const_expr(should_load_dO):
|
| 995 |
+
pipeline_dO.producer_acquire(
|
| 996 |
+
producer_state_dO_dPsum, extra_tx_count=tma_copy_bytes_V
|
| 997 |
+
)
|
| 998 |
+
load_V(tma_bar_ptr=pipeline_dO.producer_get_barrier(producer_state_dO_dPsum))
|
| 999 |
+
load_dO(m_block_safe, producer_state=producer_state_dO_dPsum)
|
| 1000 |
+
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 1001 |
+
pipeline_dPsum.producer_acquire(producer_state_dO_dPsum)
|
| 1002 |
+
with cute.arch.elect_one():
|
| 1003 |
+
copy_stats(
|
| 1004 |
+
gdPsum[None, m_block_safe],
|
| 1005 |
+
sdPsum[None, producer_state_dO_dPsum.index],
|
| 1006 |
+
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dO_dPsum),
|
| 1007 |
+
)
|
| 1008 |
+
producer_state_dO_dPsum.advance()
|
| 1009 |
+
else:
|
| 1010 |
+
# Subsequent blocks: just load Q/dO (K/V already loaded)
|
| 1011 |
+
if const_expr(should_load_Q):
|
| 1012 |
+
pipeline_Q.producer_acquire(producer_state_Q_LSE)
|
| 1013 |
+
load_Q(m_block_safe, producer_state=producer_state_Q_LSE)
|
| 1014 |
+
pipeline_Q.producer_commit(producer_state_Q_LSE)
|
| 1015 |
+
pipeline_LSE.producer_acquire(producer_state_Q_LSE)
|
| 1016 |
+
with cute.arch.elect_one():
|
| 1017 |
+
copy_stats(
|
| 1018 |
+
gLSE[None, m_block_safe],
|
| 1019 |
+
sLSE[None, producer_state_Q_LSE.index],
|
| 1020 |
+
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
| 1021 |
+
)
|
| 1022 |
+
producer_state_Q_LSE.advance()
|
| 1023 |
+
if const_expr(should_load_dO):
|
| 1024 |
+
pipeline_dO.producer_acquire(producer_state_dO_dPsum)
|
| 1025 |
+
load_dO(m_block_safe, producer_state=producer_state_dO_dPsum)
|
| 1026 |
+
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 1027 |
+
pipeline_dPsum.producer_acquire(producer_state_dO_dPsum)
|
| 1028 |
+
with cute.arch.elect_one():
|
| 1029 |
+
copy_stats(
|
| 1030 |
+
gdPsum[None, m_block_safe],
|
| 1031 |
+
sdPsum[None, producer_state_dO_dPsum.index],
|
| 1032 |
+
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dO_dPsum),
|
| 1033 |
+
)
|
| 1034 |
+
producer_state_dO_dPsum.advance()
|
| 1035 |
+
|
| 1036 |
+
return producer_state_Q_LSE, producer_state_dO_dPsum
|
| 1037 |
+
|
| 1038 |
+
|
| 1039 |
+
@cute.jit
|
| 1040 |
+
def get_block_sparse_iteration_info_bwd(
|
| 1041 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1042 |
+
batch_idx,
|
| 1043 |
+
head_idx,
|
| 1044 |
+
n_block,
|
| 1045 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 1046 |
+
m_block_max: int = 0,
|
| 1047 |
+
):
|
| 1048 |
+
"""Extract block-sparse iteration info for backward pass.
|
| 1049 |
+
|
| 1050 |
+
Returns (curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count).
|
| 1051 |
+
"""
|
| 1052 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1053 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1054 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1055 |
+
|
| 1056 |
+
if const_expr(full_cnt is not None):
|
| 1057 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1058 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1059 |
+
else:
|
| 1060 |
+
curr_full_cnt = Int32(0)
|
| 1061 |
+
curr_full_idx = None
|
| 1062 |
+
|
| 1063 |
+
sparse_block_count = curr_q_cnt
|
| 1064 |
+
if const_expr(full_cnt is not None):
|
| 1065 |
+
sparse_block_count = sparse_block_count + curr_full_cnt
|
| 1066 |
+
total_count = sparse_block_count * subtile_factor
|
| 1067 |
+
|
| 1068 |
+
return curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count
|
| 1069 |
+
|
| 1070 |
+
|
| 1071 |
+
@cute.jit
|
| 1072 |
+
def get_m_block_from_iter_bwd(
|
| 1073 |
+
iter_idx,
|
| 1074 |
+
curr_q_cnt,
|
| 1075 |
+
curr_q_idx: cute.Tensor,
|
| 1076 |
+
curr_full_cnt,
|
| 1077 |
+
curr_full_idx: Optional[cute.Tensor],
|
| 1078 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 1079 |
+
m_block_max: int = 0,
|
| 1080 |
+
):
|
| 1081 |
+
"""Derive m_block index and is_full_block flag from iteration index.
|
| 1082 |
+
|
| 1083 |
+
Returns (m_block, is_full_block):
|
| 1084 |
+
- m_block: The actual Q-tile block index
|
| 1085 |
+
- is_full_block: True if this is a full block (no mask_mod needed)
|
| 1086 |
+
"""
|
| 1087 |
+
sparse_iter_idx = iter_idx // subtile_factor
|
| 1088 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1089 |
+
|
| 1090 |
+
sparse_m_block = Int32(0)
|
| 1091 |
+
is_full_block = False
|
| 1092 |
+
if const_expr(curr_full_idx is not None):
|
| 1093 |
+
if sparse_iter_idx < curr_q_cnt:
|
| 1094 |
+
sparse_m_block = curr_q_idx[sparse_iter_idx]
|
| 1095 |
+
else:
|
| 1096 |
+
sparse_m_block = curr_full_idx[sparse_iter_idx - curr_q_cnt]
|
| 1097 |
+
is_full_block = True
|
| 1098 |
+
else:
|
| 1099 |
+
sparse_m_block = curr_q_idx[sparse_iter_idx]
|
| 1100 |
+
|
| 1101 |
+
return sparse_m_block * subtile_factor + subtile_offset, is_full_block
|
| 1102 |
+
|
| 1103 |
+
|
| 1104 |
+
@cute.jit
|
| 1105 |
+
def _load_q_do_block_sm90(
|
| 1106 |
+
m_block,
|
| 1107 |
+
producer_state_Q,
|
| 1108 |
+
producer_state_dO,
|
| 1109 |
+
pipeline_Q,
|
| 1110 |
+
pipeline_dO,
|
| 1111 |
+
load_K,
|
| 1112 |
+
load_V,
|
| 1113 |
+
load_Q,
|
| 1114 |
+
load_dO,
|
| 1115 |
+
load_LSE,
|
| 1116 |
+
load_dPsum,
|
| 1117 |
+
tma_copy_bytes_K,
|
| 1118 |
+
tma_copy_bytes_V,
|
| 1119 |
+
Q_stage_eq_dO_stage: cutlass.Constexpr,
|
| 1120 |
+
load_kv: bool,
|
| 1121 |
+
):
|
| 1122 |
+
"""Load one Q/dO block, optionally loading K/V on first iteration."""
|
| 1123 |
+
if load_kv:
|
| 1124 |
+
pipeline_Q.producer_acquire(producer_state_Q, extra_tx_count=tma_copy_bytes_K)
|
| 1125 |
+
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q))
|
| 1126 |
+
else:
|
| 1127 |
+
pipeline_Q.producer_acquire(producer_state_Q)
|
| 1128 |
+
load_Q(m_block, producer_state=producer_state_Q)
|
| 1129 |
+
load_LSE(m_block, producer_state=producer_state_Q)
|
| 1130 |
+
|
| 1131 |
+
producer_state_dO_cur = (
|
| 1132 |
+
producer_state_dO if const_expr(not Q_stage_eq_dO_stage) else producer_state_Q
|
| 1133 |
+
)
|
| 1134 |
+
if load_kv:
|
| 1135 |
+
pipeline_dO.producer_acquire(producer_state_dO_cur, extra_tx_count=tma_copy_bytes_V)
|
| 1136 |
+
load_V(tma_bar_ptr=pipeline_dO.producer_get_barrier(producer_state_dO_cur))
|
| 1137 |
+
else:
|
| 1138 |
+
pipeline_dO.producer_acquire(producer_state_dO_cur)
|
| 1139 |
+
load_dO(m_block, producer_state=producer_state_dO_cur)
|
| 1140 |
+
load_dPsum(m_block, producer_state=producer_state_dO_cur)
|
| 1141 |
+
|
| 1142 |
+
producer_state_Q.advance()
|
| 1143 |
+
producer_state_dO.advance()
|
| 1144 |
+
return producer_state_Q, producer_state_dO
|
| 1145 |
+
|
| 1146 |
+
|
| 1147 |
+
@cute.jit
|
| 1148 |
+
def produce_block_sparse_q_loads_bwd_sm90(
|
| 1149 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1150 |
+
batch_idx,
|
| 1151 |
+
head_idx,
|
| 1152 |
+
n_block,
|
| 1153 |
+
producer_state_Q,
|
| 1154 |
+
producer_state_dO,
|
| 1155 |
+
pipeline_Q,
|
| 1156 |
+
pipeline_dO,
|
| 1157 |
+
load_K,
|
| 1158 |
+
load_V,
|
| 1159 |
+
load_Q,
|
| 1160 |
+
load_dO,
|
| 1161 |
+
load_LSE,
|
| 1162 |
+
load_dPsum,
|
| 1163 |
+
tma_copy_bytes_K,
|
| 1164 |
+
tma_copy_bytes_V,
|
| 1165 |
+
Q_stage_eq_dO_stage: cutlass.Constexpr,
|
| 1166 |
+
subtile_factor: cutlass.Constexpr,
|
| 1167 |
+
m_block_max: int,
|
| 1168 |
+
):
|
| 1169 |
+
"""SM90 backward block sparse loading with separate partial/full loops.
|
| 1170 |
+
|
| 1171 |
+
K/V are loaded with the first valid block. Iterates partial blocks first,
|
| 1172 |
+
then full blocks, matching consumer order.
|
| 1173 |
+
|
| 1174 |
+
Returns updated (producer_state_Q, producer_state_dO).
|
| 1175 |
+
"""
|
| 1176 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1177 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1178 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1179 |
+
|
| 1180 |
+
if const_expr(full_cnt is not None):
|
| 1181 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1182 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1183 |
+
else:
|
| 1184 |
+
curr_full_cnt = Int32(0)
|
| 1185 |
+
curr_full_idx = None
|
| 1186 |
+
|
| 1187 |
+
kv_loaded = False
|
| 1188 |
+
|
| 1189 |
+
for iter_idx in cutlass.range(curr_q_cnt * subtile_factor, unroll=1):
|
| 1190 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1191 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1192 |
+
m_block = curr_q_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1193 |
+
|
| 1194 |
+
if m_block < m_block_max:
|
| 1195 |
+
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
| 1196 |
+
m_block,
|
| 1197 |
+
producer_state_Q,
|
| 1198 |
+
producer_state_dO,
|
| 1199 |
+
pipeline_Q,
|
| 1200 |
+
pipeline_dO,
|
| 1201 |
+
load_K,
|
| 1202 |
+
load_V,
|
| 1203 |
+
load_Q,
|
| 1204 |
+
load_dO,
|
| 1205 |
+
load_LSE,
|
| 1206 |
+
load_dPsum,
|
| 1207 |
+
tma_copy_bytes_K,
|
| 1208 |
+
tma_copy_bytes_V,
|
| 1209 |
+
Q_stage_eq_dO_stage,
|
| 1210 |
+
load_kv=not kv_loaded,
|
| 1211 |
+
)
|
| 1212 |
+
kv_loaded = True
|
| 1213 |
+
|
| 1214 |
+
if const_expr(full_cnt is not None):
|
| 1215 |
+
for iter_idx in cutlass.range(curr_full_cnt * subtile_factor, unroll=1):
|
| 1216 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1217 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1218 |
+
m_block = curr_full_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1219 |
+
|
| 1220 |
+
if m_block < m_block_max:
|
| 1221 |
+
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
| 1222 |
+
m_block,
|
| 1223 |
+
producer_state_Q,
|
| 1224 |
+
producer_state_dO,
|
| 1225 |
+
pipeline_Q,
|
| 1226 |
+
pipeline_dO,
|
| 1227 |
+
load_K,
|
| 1228 |
+
load_V,
|
| 1229 |
+
load_Q,
|
| 1230 |
+
load_dO,
|
| 1231 |
+
load_LSE,
|
| 1232 |
+
load_dPsum,
|
| 1233 |
+
tma_copy_bytes_K,
|
| 1234 |
+
tma_copy_bytes_V,
|
| 1235 |
+
Q_stage_eq_dO_stage,
|
| 1236 |
+
load_kv=not kv_loaded,
|
| 1237 |
+
)
|
| 1238 |
+
kv_loaded = True
|
| 1239 |
+
|
| 1240 |
+
return producer_state_Q, producer_state_dO
|
| 1241 |
+
|
| 1242 |
+
|
| 1243 |
+
@cute.jit
|
| 1244 |
+
def consume_block_sparse_mma_bwd_sm90(
|
| 1245 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1246 |
+
batch_idx,
|
| 1247 |
+
head_idx,
|
| 1248 |
+
n_block,
|
| 1249 |
+
consumer_state_Q,
|
| 1250 |
+
consumer_state_dO,
|
| 1251 |
+
mma_one_m_block_fn,
|
| 1252 |
+
mask,
|
| 1253 |
+
mask_mod,
|
| 1254 |
+
is_causal: cutlass.Constexpr,
|
| 1255 |
+
is_local: cutlass.Constexpr,
|
| 1256 |
+
thr_mma_SdP,
|
| 1257 |
+
score_mod_fn=None,
|
| 1258 |
+
score_mod_bwd_fn=None,
|
| 1259 |
+
subtile_factor: cutlass.Constexpr = 1,
|
| 1260 |
+
m_block_max: int = 0,
|
| 1261 |
+
aux_tensors=None,
|
| 1262 |
+
fastdiv_mods=(None, None),
|
| 1263 |
+
):
|
| 1264 |
+
"""SM90 backward block sparse MMA consumption with separate partial/full loops.
|
| 1265 |
+
|
| 1266 |
+
Partial blocks are processed first (with mask_mod applied), then full blocks
|
| 1267 |
+
(without mask_mod). This ensures mask_mod is only applied where needed.
|
| 1268 |
+
|
| 1269 |
+
Returns updated (consumer_state_Q, consumer_state_dO).
|
| 1270 |
+
"""
|
| 1271 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1272 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1273 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1274 |
+
|
| 1275 |
+
if const_expr(full_cnt is not None):
|
| 1276 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1277 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1278 |
+
else:
|
| 1279 |
+
curr_full_cnt = Int32(0)
|
| 1280 |
+
curr_full_idx = None
|
| 1281 |
+
|
| 1282 |
+
dKV_accumulate = False
|
| 1283 |
+
|
| 1284 |
+
mask_fn_partial = partial(
|
| 1285 |
+
mask.apply_mask,
|
| 1286 |
+
batch_idx=batch_idx,
|
| 1287 |
+
head_idx=head_idx,
|
| 1288 |
+
n_block=n_block,
|
| 1289 |
+
thr_mma=thr_mma_SdP,
|
| 1290 |
+
mask_seqlen=True,
|
| 1291 |
+
mask_causal=is_causal,
|
| 1292 |
+
mask_local=is_local,
|
| 1293 |
+
mask_mod=mask_mod,
|
| 1294 |
+
aux_tensors=aux_tensors,
|
| 1295 |
+
fastdiv_mods=fastdiv_mods,
|
| 1296 |
+
)
|
| 1297 |
+
|
| 1298 |
+
mask_fn_full = partial(
|
| 1299 |
+
mask.apply_mask,
|
| 1300 |
+
batch_idx=batch_idx,
|
| 1301 |
+
head_idx=head_idx,
|
| 1302 |
+
n_block=n_block,
|
| 1303 |
+
thr_mma=thr_mma_SdP,
|
| 1304 |
+
mask_seqlen=True,
|
| 1305 |
+
mask_causal=is_causal,
|
| 1306 |
+
mask_local=is_local,
|
| 1307 |
+
aux_tensors=aux_tensors,
|
| 1308 |
+
fastdiv_mods=fastdiv_mods,
|
| 1309 |
+
)
|
| 1310 |
+
|
| 1311 |
+
for iter_idx in cutlass.range(curr_q_cnt * subtile_factor, unroll=1):
|
| 1312 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1313 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1314 |
+
m_block = curr_q_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1315 |
+
|
| 1316 |
+
if m_block < m_block_max:
|
| 1317 |
+
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
| 1318 |
+
m_block,
|
| 1319 |
+
consumer_state_Q,
|
| 1320 |
+
consumer_state_dO,
|
| 1321 |
+
mask_fn=mask_fn_partial,
|
| 1322 |
+
score_mod_fn=score_mod_fn,
|
| 1323 |
+
score_mod_bwd_fn=score_mod_bwd_fn,
|
| 1324 |
+
dKV_accumulate=dKV_accumulate,
|
| 1325 |
+
)
|
| 1326 |
+
dKV_accumulate = True
|
| 1327 |
+
|
| 1328 |
+
if const_expr(full_cnt is not None):
|
| 1329 |
+
for iter_idx in cutlass.range(curr_full_cnt * subtile_factor, unroll=1):
|
| 1330 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1331 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1332 |
+
m_block = curr_full_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1333 |
+
|
| 1334 |
+
if m_block < m_block_max:
|
| 1335 |
+
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
| 1336 |
+
m_block,
|
| 1337 |
+
consumer_state_Q,
|
| 1338 |
+
consumer_state_dO,
|
| 1339 |
+
mask_fn=mask_fn_full,
|
| 1340 |
+
score_mod_fn=score_mod_fn,
|
| 1341 |
+
score_mod_bwd_fn=score_mod_bwd_fn,
|
| 1342 |
+
dKV_accumulate=dKV_accumulate,
|
| 1343 |
+
)
|
| 1344 |
+
dKV_accumulate = True
|
| 1345 |
+
|
| 1346 |
+
return consumer_state_Q, consumer_state_dO
|
| 1347 |
+
|
| 1348 |
+
|
| 1349 |
+
@cute.jit
|
| 1350 |
+
def _store_one_dQaccum_sm90(
|
| 1351 |
+
m_block,
|
| 1352 |
+
sdQaccum: cute.Tensor,
|
| 1353 |
+
gdQaccum: cute.Tensor,
|
| 1354 |
+
num_dQ_warp_groups: cutlass.Constexpr,
|
| 1355 |
+
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1356 |
+
tma_copy_bytes_dQ,
|
| 1357 |
+
):
|
| 1358 |
+
"""Store dQaccum for a single m_block."""
|
| 1359 |
+
for warp_group_idx in cutlass.range_constexpr(num_dQ_warp_groups):
|
| 1360 |
+
cute.arch.cp_async_bulk_wait_group(num_dQ_warp_groups - 1 - warp_group_idx, read=True)
|
| 1361 |
+
cute.arch.barrier_arrive(
|
| 1362 |
+
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1363 |
+
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1364 |
+
)
|
| 1365 |
+
for warp_group_idx in cutlass.range_constexpr(num_dQ_warp_groups):
|
| 1366 |
+
cute.arch.barrier(
|
| 1367 |
+
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1368 |
+
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1369 |
+
)
|
| 1370 |
+
with cute.arch.elect_one():
|
| 1371 |
+
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1372 |
+
sdQaccum[None, warp_group_idx].iterator,
|
| 1373 |
+
gdQaccum[(None, warp_group_idx), m_block].iterator,
|
| 1374 |
+
tma_copy_bytes_dQ,
|
| 1375 |
+
)
|
| 1376 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1377 |
+
|
| 1378 |
+
|
| 1379 |
+
@cute.jit
|
| 1380 |
+
def dQaccum_store_block_sparse_bwd_sm90(
|
| 1381 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1382 |
+
batch_idx,
|
| 1383 |
+
head_idx,
|
| 1384 |
+
n_block,
|
| 1385 |
+
sdQaccum: cute.Tensor,
|
| 1386 |
+
gdQaccum: cute.Tensor,
|
| 1387 |
+
subtile_factor: cutlass.Constexpr,
|
| 1388 |
+
m_block_max: int,
|
| 1389 |
+
num_dQ_warp_groups: cutlass.Constexpr,
|
| 1390 |
+
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1391 |
+
tma_copy_bytes_dQ,
|
| 1392 |
+
):
|
| 1393 |
+
"""SM90 backward block sparse dQaccum store with separate partial/full loops.
|
| 1394 |
+
|
| 1395 |
+
Iterates partial blocks first, then full blocks, matching producer/consumer order.
|
| 1396 |
+
"""
|
| 1397 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1398 |
+
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1399 |
+
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1400 |
+
|
| 1401 |
+
if const_expr(full_cnt is not None):
|
| 1402 |
+
curr_full_cnt = full_cnt[batch_idx, head_idx, n_block]
|
| 1403 |
+
curr_full_idx = full_idx[batch_idx, head_idx, n_block, None]
|
| 1404 |
+
else:
|
| 1405 |
+
curr_full_cnt = Int32(0)
|
| 1406 |
+
curr_full_idx = None
|
| 1407 |
+
|
| 1408 |
+
for iter_idx in cutlass.range(curr_q_cnt * subtile_factor, unroll=1):
|
| 1409 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1410 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1411 |
+
m_block = curr_q_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1412 |
+
|
| 1413 |
+
if m_block < m_block_max:
|
| 1414 |
+
_store_one_dQaccum_sm90(
|
| 1415 |
+
m_block,
|
| 1416 |
+
sdQaccum,
|
| 1417 |
+
gdQaccum,
|
| 1418 |
+
num_dQ_warp_groups,
|
| 1419 |
+
num_threads_per_warp_group,
|
| 1420 |
+
tma_copy_bytes_dQ,
|
| 1421 |
+
)
|
| 1422 |
+
|
| 1423 |
+
if const_expr(full_cnt is not None):
|
| 1424 |
+
for iter_idx in cutlass.range(curr_full_cnt * subtile_factor, unroll=1):
|
| 1425 |
+
sparse_idx = iter_idx // subtile_factor
|
| 1426 |
+
subtile_offset = iter_idx % subtile_factor
|
| 1427 |
+
m_block = curr_full_idx[sparse_idx] * subtile_factor + subtile_offset
|
| 1428 |
+
|
| 1429 |
+
if m_block < m_block_max:
|
| 1430 |
+
_store_one_dQaccum_sm90(
|
| 1431 |
+
m_block,
|
| 1432 |
+
sdQaccum,
|
| 1433 |
+
gdQaccum,
|
| 1434 |
+
num_dQ_warp_groups,
|
| 1435 |
+
num_threads_per_warp_group,
|
| 1436 |
+
tma_copy_bytes_dQ,
|
| 1437 |
+
)
|
build/torch-cuda/flashrt_fa4/cute/block_sparsity.py
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Block-sparsity utilities for FlexAttention
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from typing import Callable, NamedTuple, Tuple
|
| 6 |
+
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from fa4_cute_runtime.flashrt_fa4.cute.cute_dsl_utils import get_broadcast_dims, to_cute_tensor
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def ceildiv(a: int, b: int) -> int:
|
| 14 |
+
return (a + b - 1) // b
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class BlockSparseTensors(NamedTuple):
|
| 18 |
+
mask_block_cnt: cute.Tensor
|
| 19 |
+
mask_block_idx: cute.Tensor
|
| 20 |
+
full_block_cnt: cute.Tensor | None
|
| 21 |
+
full_block_idx: cute.Tensor | None
|
| 22 |
+
dq_write_order: cute.Tensor | None = None
|
| 23 |
+
dq_write_order_full: cute.Tensor | None = None
|
| 24 |
+
|
| 25 |
+
def __new_from_mlir_values__(self, values):
|
| 26 |
+
if len(values) == 2:
|
| 27 |
+
values = (*values, None, None, None, None)
|
| 28 |
+
elif len(values) == 4:
|
| 29 |
+
values = (*values, None, None)
|
| 30 |
+
return BlockSparseTensors(*values)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class BlockSparseTensorsTorch(NamedTuple):
|
| 34 |
+
mask_block_cnt: torch.Tensor
|
| 35 |
+
mask_block_idx: torch.Tensor
|
| 36 |
+
full_block_cnt: torch.Tensor | None = None
|
| 37 |
+
full_block_idx: torch.Tensor | None = None
|
| 38 |
+
block_size: tuple[int, int] | None = None
|
| 39 |
+
dq_write_order: torch.Tensor | None = None
|
| 40 |
+
dq_write_order_full: torch.Tensor | None = None
|
| 41 |
+
spt: bool | None = None
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _ordered_to_dense_simple(
|
| 45 |
+
num_blocks: torch.Tensor,
|
| 46 |
+
indices: torch.Tensor,
|
| 47 |
+
num_cols: int,
|
| 48 |
+
) -> torch.Tensor:
|
| 49 |
+
"""Convert ordered sparse representation to dense binary matrix.
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
num_blocks: [B, H, num_rows] count of valid entries per row
|
| 53 |
+
indices: [B, H, num_rows, max_entries] column indices (valid entries packed left)
|
| 54 |
+
num_cols: total number of columns
|
| 55 |
+
|
| 56 |
+
Returns:
|
| 57 |
+
dense: [B, H, num_rows, num_cols] binary int32 matrix
|
| 58 |
+
"""
|
| 59 |
+
B, H, num_rows, max_entries = indices.shape
|
| 60 |
+
device = indices.device
|
| 61 |
+
dense = torch.zeros(B, H, num_rows, num_cols + 1, dtype=torch.int32, device=device)
|
| 62 |
+
col_range = torch.arange(max_entries, device=device)
|
| 63 |
+
valid = col_range[None, None, None, :] < num_blocks[:, :, :, None]
|
| 64 |
+
safe_indices = torch.where(valid, indices.long(), num_cols)
|
| 65 |
+
row_idx = torch.arange(num_rows, device=device)[None, None, :, None].expand_as(indices)
|
| 66 |
+
b_idx = torch.arange(B, device=device)[:, None, None, None].expand_as(indices)
|
| 67 |
+
h_idx = torch.arange(H, device=device)[None, :, None, None].expand_as(indices)
|
| 68 |
+
dense[b_idx, h_idx, row_idx, safe_indices] = 1
|
| 69 |
+
return dense[:, :, :, :num_cols]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def compute_dq_write_order(
|
| 73 |
+
fwd_mask_cnt: torch.Tensor,
|
| 74 |
+
fwd_mask_idx: torch.Tensor,
|
| 75 |
+
fwd_full_cnt: torch.Tensor | None,
|
| 76 |
+
fwd_full_idx: torch.Tensor | None,
|
| 77 |
+
bwd_mask_cnt: torch.Tensor,
|
| 78 |
+
bwd_mask_idx: torch.Tensor,
|
| 79 |
+
bwd_full_cnt: torch.Tensor | None,
|
| 80 |
+
bwd_full_idx: torch.Tensor | None,
|
| 81 |
+
spt: bool = False,
|
| 82 |
+
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
| 83 |
+
"""Compute dQ write-order metadata for deterministic block-sparse backward.
|
| 84 |
+
|
| 85 |
+
For each (n_block, i) in the backward iteration, computes the semaphore
|
| 86 |
+
lock value: the rank of n_block in the combined (partial + full) sorted
|
| 87 |
+
contributor list for the target m_block.
|
| 88 |
+
|
| 89 |
+
Lock values are assigned in ascending n_block order (or descending if spt=True)
|
| 90 |
+
to guarantee deadlock-freedom with the CTA scheduling order.
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
fwd_mask_cnt: [B, H, num_m_blocks] partial contributor counts per m_block
|
| 94 |
+
fwd_mask_idx: [B, H, num_m_blocks, max_kv] partial contributor n_block indices (ascending)
|
| 95 |
+
fwd_full_cnt: [B, H, num_m_blocks] full contributor counts per m_block (optional)
|
| 96 |
+
fwd_full_idx: [B, H, num_m_blocks, max_kv] full contributor n_block indices (optional)
|
| 97 |
+
bwd_mask_cnt: [B, H, num_n_blocks] partial iteration counts per n_block
|
| 98 |
+
bwd_mask_idx: [B, H, num_n_blocks, max_q] partial iteration m_block indices
|
| 99 |
+
bwd_full_cnt: [B, H, num_n_blocks] full iteration counts per n_block (optional)
|
| 100 |
+
bwd_full_idx: [B, H, num_n_blocks, max_q] full iteration m_block indices (optional)
|
| 101 |
+
spt: if True, reverse ordering (highest n_block gets lock_value=0)
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
(dq_write_order, dq_write_order_full): tensors parallel to bwd_mask_idx
|
| 105 |
+
and bwd_full_idx respectively, containing lock values.
|
| 106 |
+
"""
|
| 107 |
+
device = fwd_mask_idx.device
|
| 108 |
+
B, H, num_m, max_kv_partial = fwd_mask_idx.shape
|
| 109 |
+
_, _, num_n, max_q_partial = bwd_mask_idx.shape
|
| 110 |
+
|
| 111 |
+
has_full = fwd_full_cnt is not None and fwd_full_idx is not None
|
| 112 |
+
|
| 113 |
+
dense_partial = _ordered_to_dense_simple(fwd_mask_cnt, fwd_mask_idx, num_n)
|
| 114 |
+
if has_full:
|
| 115 |
+
dense_full = _ordered_to_dense_simple(fwd_full_cnt, fwd_full_idx, num_n)
|
| 116 |
+
dense = (dense_partial + dense_full).clamp(max=1)
|
| 117 |
+
else:
|
| 118 |
+
dense = dense_partial
|
| 119 |
+
|
| 120 |
+
cumsum = dense.cumsum(dim=-1)
|
| 121 |
+
rank_table = (cumsum - dense).to(torch.int32)
|
| 122 |
+
|
| 123 |
+
if spt:
|
| 124 |
+
total_per_m = cumsum[:, :, :, -1:]
|
| 125 |
+
rank_table = (total_per_m - 1 - rank_table).to(torch.int32)
|
| 126 |
+
|
| 127 |
+
def _gather_write_order(bwd_idx, bwd_cnt):
|
| 128 |
+
b_i = torch.arange(B, device=device)[:, None, None, None].expand_as(bwd_idx)
|
| 129 |
+
h_i = torch.arange(H, device=device)[None, :, None, None].expand_as(bwd_idx)
|
| 130 |
+
n_i = torch.arange(bwd_idx.shape[2], device=device)[None, None, :, None].expand_as(bwd_idx)
|
| 131 |
+
m_vals = bwd_idx.long().clamp(0, num_m - 1)
|
| 132 |
+
return rank_table[b_i, h_i, m_vals, n_i].to(torch.int32)
|
| 133 |
+
|
| 134 |
+
dq_write_order = _gather_write_order(bwd_mask_idx, bwd_mask_cnt)
|
| 135 |
+
|
| 136 |
+
dq_write_order_full = None
|
| 137 |
+
if has_full and bwd_full_cnt is not None and bwd_full_idx is not None:
|
| 138 |
+
dq_write_order_full = _gather_write_order(bwd_full_idx, bwd_full_cnt)
|
| 139 |
+
|
| 140 |
+
return dq_write_order, dq_write_order_full
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def compute_dq_write_order_from_block_mask(
|
| 144 |
+
block_mask,
|
| 145 |
+
spt: bool = False,
|
| 146 |
+
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
| 147 |
+
(
|
| 148 |
+
_seq_q,
|
| 149 |
+
_seq_k,
|
| 150 |
+
kv_mask_cnt,
|
| 151 |
+
kv_mask_idx,
|
| 152 |
+
full_kv_cnt,
|
| 153 |
+
full_kv_idx,
|
| 154 |
+
q_mask_cnt,
|
| 155 |
+
q_mask_idx,
|
| 156 |
+
full_q_cnt,
|
| 157 |
+
full_q_idx,
|
| 158 |
+
*_,
|
| 159 |
+
) = block_mask.as_tuple()
|
| 160 |
+
return compute_dq_write_order(
|
| 161 |
+
kv_mask_cnt,
|
| 162 |
+
kv_mask_idx,
|
| 163 |
+
full_kv_cnt,
|
| 164 |
+
full_kv_idx,
|
| 165 |
+
q_mask_cnt,
|
| 166 |
+
q_mask_idx,
|
| 167 |
+
full_q_cnt,
|
| 168 |
+
full_q_idx,
|
| 169 |
+
spt=spt,
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def get_sparse_q_block_size(
|
| 174 |
+
tensors: BlockSparseTensorsTorch | None,
|
| 175 |
+
seqlen_q: int,
|
| 176 |
+
) -> int | None:
|
| 177 |
+
"""Return the Q sparse block size, or None when sparsity is unset or ambiguous."""
|
| 178 |
+
if tensors is None:
|
| 179 |
+
return None
|
| 180 |
+
if tensors.block_size is not None:
|
| 181 |
+
return tensors.block_size[0]
|
| 182 |
+
num_m_blocks = tensors.mask_block_idx.shape[2]
|
| 183 |
+
min_block_size = ceildiv(seqlen_q, num_m_blocks)
|
| 184 |
+
max_block_size = seqlen_q if num_m_blocks == 1 else (seqlen_q - 1) // (num_m_blocks - 1)
|
| 185 |
+
if min_block_size != max_block_size:
|
| 186 |
+
return None
|
| 187 |
+
return min_block_size
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def _expand_sparsity_tensor(
|
| 191 |
+
tensor: torch.Tensor,
|
| 192 |
+
expected_shape: Tuple[int, ...],
|
| 193 |
+
tensor_name: str,
|
| 194 |
+
context: str | None,
|
| 195 |
+
hint: str | Callable[[], str] | None,
|
| 196 |
+
) -> torch.Tensor:
|
| 197 |
+
"""Check if we need to expand the tensor to expected shape, and do so if possible."""
|
| 198 |
+
needs_expand = tensor.shape != expected_shape
|
| 199 |
+
if not needs_expand:
|
| 200 |
+
return tensor
|
| 201 |
+
can_expand = all(map(lambda cur, tgt: cur == tgt or cur == 1, tensor.shape, expected_shape))
|
| 202 |
+
if not can_expand:
|
| 203 |
+
context_clause = f" ({context})" if context else ""
|
| 204 |
+
resolved_hint = hint() if callable(hint) else hint
|
| 205 |
+
hint_clause = f" Hint: {resolved_hint}" if resolved_hint else ""
|
| 206 |
+
raise ValueError(
|
| 207 |
+
f"{tensor_name}{context_clause} with shape {tensor.shape} cannot be expanded to expected shape {expected_shape}."
|
| 208 |
+
f"{hint_clause}"
|
| 209 |
+
)
|
| 210 |
+
return tensor.expand(*expected_shape)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def _check_and_expand_block(
|
| 214 |
+
name: str,
|
| 215 |
+
cnt: torch.Tensor | None,
|
| 216 |
+
idx: torch.Tensor | None,
|
| 217 |
+
expected_count_shape: Tuple[int, int, int],
|
| 218 |
+
expected_index_shape: Tuple[int, int, int, int],
|
| 219 |
+
context: str | None,
|
| 220 |
+
hint: str | Callable[[], str] | None,
|
| 221 |
+
) -> Tuple[torch.Tensor | None, torch.Tensor | None]:
|
| 222 |
+
if (cnt is None) != (idx is None):
|
| 223 |
+
raise ValueError(
|
| 224 |
+
f"{name}_block_cnt and {name}_block_idx must both be provided or both be None"
|
| 225 |
+
)
|
| 226 |
+
if cnt is None or idx is None:
|
| 227 |
+
return None, None
|
| 228 |
+
if cnt.dtype != torch.int32 or idx.dtype != torch.int32:
|
| 229 |
+
raise ValueError(f"{name}_block tensors must have dtype torch.int32")
|
| 230 |
+
if cnt.device != idx.device:
|
| 231 |
+
raise ValueError(f"{name}_block_cnt and {name}_block_idx must be on the same device")
|
| 232 |
+
if not cnt.is_cuda or not idx.is_cuda:
|
| 233 |
+
raise ValueError(f"{name}_block tensors must live on CUDA")
|
| 234 |
+
expanded_cnt = _expand_sparsity_tensor(
|
| 235 |
+
cnt, expected_count_shape, f"{name}_block_cnt", context, hint
|
| 236 |
+
)
|
| 237 |
+
# [Note] Allow Compact block sparse indices
|
| 238 |
+
# Allow the last dimension (n_blocks) of idx to be <= expected, since
|
| 239 |
+
# FA4 only accesses indices 0..cnt-1 per query tile. This enables compact
|
| 240 |
+
# index tensors that avoid O(N^2) memory at long sequence lengths.
|
| 241 |
+
if idx.ndim == 4 and idx.shape[3] <= expected_index_shape[3]:
|
| 242 |
+
expected_index_shape = (*expected_index_shape[:3], idx.shape[3])
|
| 243 |
+
expanded_idx = _expand_sparsity_tensor(
|
| 244 |
+
idx, expected_index_shape, f"{name}_block_idx", context, hint
|
| 245 |
+
)
|
| 246 |
+
return expanded_cnt, expanded_idx
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def _check_and_expand_metadata_tensor(
|
| 250 |
+
name: str,
|
| 251 |
+
tensor: torch.Tensor | None,
|
| 252 |
+
expected_shape: Tuple[int, ...],
|
| 253 |
+
context: str | None,
|
| 254 |
+
hint: str | Callable[[], str] | None,
|
| 255 |
+
device: torch.device,
|
| 256 |
+
) -> torch.Tensor | None:
|
| 257 |
+
if tensor is None:
|
| 258 |
+
return None
|
| 259 |
+
if tensor.dtype != torch.int32:
|
| 260 |
+
raise ValueError(f"{name} must have dtype torch.int32")
|
| 261 |
+
if tensor.device != device:
|
| 262 |
+
raise ValueError(f"{name} must be on the same device as block sparse tensors")
|
| 263 |
+
if not tensor.is_cuda:
|
| 264 |
+
raise ValueError(f"{name} must live on CUDA")
|
| 265 |
+
return _expand_sparsity_tensor(tensor, expected_shape, name, context, hint)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def get_block_sparse_expected_shapes(
|
| 269 |
+
batch_size: int,
|
| 270 |
+
num_head: int,
|
| 271 |
+
seqlen_q: int,
|
| 272 |
+
seqlen_k: int,
|
| 273 |
+
m_block_size: int,
|
| 274 |
+
n_block_size: int,
|
| 275 |
+
q_stage: int,
|
| 276 |
+
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int]]:
|
| 277 |
+
"""Return (expected_count_shape, expected_index_shape) for block sparse normalization."""
|
| 278 |
+
m_block_size_effective = q_stage * m_block_size
|
| 279 |
+
expected_m_blocks = ceildiv(seqlen_q, m_block_size_effective)
|
| 280 |
+
expected_n_blocks = ceildiv(seqlen_k, n_block_size)
|
| 281 |
+
expected_count_shape = (batch_size, num_head, expected_m_blocks)
|
| 282 |
+
expected_index_shape = (batch_size, num_head, expected_m_blocks, expected_n_blocks)
|
| 283 |
+
return expected_count_shape, expected_index_shape
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def infer_block_sparse_expected_shapes(
|
| 287 |
+
tensors: BlockSparseTensorsTorch,
|
| 288 |
+
*,
|
| 289 |
+
batch_size: int,
|
| 290 |
+
num_head: int,
|
| 291 |
+
seqlen_q: int,
|
| 292 |
+
seqlen_k: int,
|
| 293 |
+
m_block_size: int,
|
| 294 |
+
n_block_size: int,
|
| 295 |
+
q_stage: int,
|
| 296 |
+
context: str,
|
| 297 |
+
sparse_block_size_q: int | None = None,
|
| 298 |
+
sparse_block_size_kv: int | None = None,
|
| 299 |
+
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int], int]:
|
| 300 |
+
"""Infer shapes and scaling for block-sparse tensors.
|
| 301 |
+
|
| 302 |
+
Expectations:
|
| 303 |
+
- mask_block_cnt is (B, H, M) and mask_block_idx is (B, H, M, N).
|
| 304 |
+
- Batch/head dims may be 1 for broadcast, or match the requested sizes.
|
| 305 |
+
- sparse_block_size_kv must match tile_n.
|
| 306 |
+
- sparse_block_size_q must be a multiple of q_stage * tile_m.
|
| 307 |
+
- If sparse_block_size_q is omitted and seqlen_q/num_m_blocks is ambiguous,
|
| 308 |
+
the caller must provide block_size to disambiguate. TODO will make this required in a future PR.
|
| 309 |
+
"""
|
| 310 |
+
base_m_block = q_stage * m_block_size
|
| 311 |
+
base_n_block = n_block_size
|
| 312 |
+
if sparse_block_size_kv is None:
|
| 313 |
+
sparse_block_size_kv = base_n_block
|
| 314 |
+
if sparse_block_size_kv != base_n_block:
|
| 315 |
+
raise ValueError(f"Block sparse tensors{context} require BLOCK_SIZE_KV={base_n_block}.")
|
| 316 |
+
if tensors.mask_block_idx is None:
|
| 317 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 318 |
+
num_m_blocks = tensors.mask_block_idx.shape[2]
|
| 319 |
+
|
| 320 |
+
if sparse_block_size_q is None:
|
| 321 |
+
sparse_block_size_q = get_sparse_q_block_size(tensors, seqlen_q)
|
| 322 |
+
if sparse_block_size_q is None and base_m_block != 1:
|
| 323 |
+
raise ValueError(
|
| 324 |
+
f"Block sparse tensors{context} require explicit sparse_block_size[0] "
|
| 325 |
+
f"to disambiguate block size for seqlen_q={seqlen_q} and num_m_blocks={num_m_blocks}."
|
| 326 |
+
)
|
| 327 |
+
if sparse_block_size_q is None:
|
| 328 |
+
sparse_block_size_q = ceildiv(seqlen_q, num_m_blocks)
|
| 329 |
+
|
| 330 |
+
if sparse_block_size_q % base_m_block != 0:
|
| 331 |
+
raise ValueError(
|
| 332 |
+
f"Block sparse tensors{context} have block size {sparse_block_size_q}, "
|
| 333 |
+
f"which must be a multiple of {base_m_block}."
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
expected_m_blocks = ceildiv(seqlen_q, sparse_block_size_q)
|
| 337 |
+
expected_n_blocks = ceildiv(seqlen_k, sparse_block_size_kv)
|
| 338 |
+
q_subtile_factor = sparse_block_size_q // base_m_block
|
| 339 |
+
expected_count_shape = (batch_size, num_head, expected_m_blocks)
|
| 340 |
+
expected_index_shape = (batch_size, num_head, expected_m_blocks, expected_n_blocks)
|
| 341 |
+
|
| 342 |
+
mask_block_cnt = tensors.mask_block_cnt
|
| 343 |
+
mask_block_idx = tensors.mask_block_idx
|
| 344 |
+
if mask_block_cnt is None or mask_block_idx is None:
|
| 345 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 346 |
+
if mask_block_cnt.ndim != 3 or mask_block_idx.ndim != 4:
|
| 347 |
+
raise ValueError(
|
| 348 |
+
f"Block sparse tensors{context} must have shapes (B, H, M) and (B, H, M, N)."
|
| 349 |
+
)
|
| 350 |
+
for dim_name, cur, tgt in (
|
| 351 |
+
("batch", mask_block_cnt.shape[0], expected_count_shape[0]),
|
| 352 |
+
("head", mask_block_cnt.shape[1], expected_count_shape[1]),
|
| 353 |
+
):
|
| 354 |
+
if cur != tgt and cur != 1:
|
| 355 |
+
raise ValueError(f"Block sparse tensors{context} {dim_name} dim must be {tgt} or 1.")
|
| 356 |
+
for dim_name, cur, tgt in (
|
| 357 |
+
("batch", mask_block_idx.shape[0], expected_index_shape[0]),
|
| 358 |
+
("head", mask_block_idx.shape[1], expected_index_shape[1]),
|
| 359 |
+
):
|
| 360 |
+
if cur != tgt and cur != 1:
|
| 361 |
+
raise ValueError(f"Block sparse tensors{context} {dim_name} dim must be {tgt} or 1.")
|
| 362 |
+
if mask_block_cnt.shape[2] != mask_block_idx.shape[2]:
|
| 363 |
+
raise ValueError(f"Block sparse tensors{context} must share the same m-block dimension.")
|
| 364 |
+
# [Note] Allow Compact block sparse indices: FA4 only accesses indices 0..cnt-1
|
| 365 |
+
# per query tile, so idx.shape[3] can be <= expected_n_blocks.
|
| 366 |
+
if mask_block_idx.shape[3] > expected_n_blocks:
|
| 367 |
+
raise ValueError(
|
| 368 |
+
f"Block sparse tensors{context} n-block dimension must be <= {expected_n_blocks}."
|
| 369 |
+
)
|
| 370 |
+
if expected_m_blocks != num_m_blocks:
|
| 371 |
+
raise ValueError(
|
| 372 |
+
f"Block sparse tensors{context} m-block dimension {num_m_blocks} does not match "
|
| 373 |
+
f"sparse_block_size_q={sparse_block_size_q}. "
|
| 374 |
+
f"Set BlockSparseTensorsTorch.block_size to match the BlockMask BLOCK_SIZE."
|
| 375 |
+
)
|
| 376 |
+
return expected_count_shape, expected_index_shape, q_subtile_factor
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
def get_block_sparse_expected_shapes_bwd(
|
| 380 |
+
batch_size: int,
|
| 381 |
+
num_head: int,
|
| 382 |
+
seqlen_q: int,
|
| 383 |
+
seqlen_k: int,
|
| 384 |
+
m_block_size: int,
|
| 385 |
+
n_block_size: int,
|
| 386 |
+
subtile_factor: int,
|
| 387 |
+
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int]]:
|
| 388 |
+
"""Return (expected_count_shape, expected_index_shape) for backward block sparse normalization.
|
| 389 |
+
|
| 390 |
+
Backward uses Q-direction indexing (transposed from forward), where shapes are
|
| 391 |
+
indexed by N-blocks first, then M-blocks. The sparse_block_size_q is determined
|
| 392 |
+
by subtile_factor * m_block_size.
|
| 393 |
+
"""
|
| 394 |
+
sparse_block_size_q = subtile_factor * m_block_size
|
| 395 |
+
expected_m_blocks = ceildiv(seqlen_q, sparse_block_size_q)
|
| 396 |
+
expected_n_blocks = ceildiv(seqlen_k, n_block_size)
|
| 397 |
+
expected_count_shape = (batch_size, num_head, expected_n_blocks)
|
| 398 |
+
expected_index_shape = (batch_size, num_head, expected_n_blocks, expected_m_blocks)
|
| 399 |
+
return expected_count_shape, expected_index_shape
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def normalize_block_sparse_tensors(
|
| 403 |
+
tensors: BlockSparseTensorsTorch,
|
| 404 |
+
*,
|
| 405 |
+
expected_count_shape: Tuple[int, int, int],
|
| 406 |
+
expected_index_shape: Tuple[int, int, int, int],
|
| 407 |
+
context: str | None = None,
|
| 408 |
+
hint: str | Callable[[], str] | None = None,
|
| 409 |
+
) -> BlockSparseTensorsTorch:
|
| 410 |
+
if tensors.mask_block_cnt is None or tensors.mask_block_idx is None:
|
| 411 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 412 |
+
|
| 413 |
+
mask_cnt, mask_idx = _check_and_expand_block(
|
| 414 |
+
"mask",
|
| 415 |
+
tensors.mask_block_cnt,
|
| 416 |
+
tensors.mask_block_idx,
|
| 417 |
+
expected_count_shape,
|
| 418 |
+
expected_index_shape,
|
| 419 |
+
context,
|
| 420 |
+
hint,
|
| 421 |
+
)
|
| 422 |
+
if mask_cnt is None or mask_idx is None:
|
| 423 |
+
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 424 |
+
|
| 425 |
+
full_cnt, full_idx = _check_and_expand_block(
|
| 426 |
+
"full",
|
| 427 |
+
tensors.full_block_cnt,
|
| 428 |
+
tensors.full_block_idx,
|
| 429 |
+
expected_count_shape,
|
| 430 |
+
expected_index_shape,
|
| 431 |
+
context,
|
| 432 |
+
hint,
|
| 433 |
+
)
|
| 434 |
+
if full_cnt is not None and mask_cnt.device != full_cnt.device:
|
| 435 |
+
raise ValueError("All block sparse tensors must be on the same device")
|
| 436 |
+
|
| 437 |
+
dq_write_order = _check_and_expand_metadata_tensor(
|
| 438 |
+
"dq_write_order",
|
| 439 |
+
tensors.dq_write_order,
|
| 440 |
+
tuple(mask_idx.shape),
|
| 441 |
+
context,
|
| 442 |
+
hint,
|
| 443 |
+
mask_cnt.device,
|
| 444 |
+
)
|
| 445 |
+
dq_write_order_full = _check_and_expand_metadata_tensor(
|
| 446 |
+
"dq_write_order_full",
|
| 447 |
+
tensors.dq_write_order_full,
|
| 448 |
+
tuple(full_idx.shape) if full_idx is not None else expected_index_shape,
|
| 449 |
+
context,
|
| 450 |
+
hint,
|
| 451 |
+
mask_cnt.device,
|
| 452 |
+
)
|
| 453 |
+
spt = tensors.spt
|
| 454 |
+
if spt is not None and not isinstance(spt, bool):
|
| 455 |
+
raise ValueError("spt must be a bool when provided")
|
| 456 |
+
if spt is not None and dq_write_order is None:
|
| 457 |
+
raise ValueError("spt requires dq_write_order to be provided")
|
| 458 |
+
|
| 459 |
+
return BlockSparseTensorsTorch(
|
| 460 |
+
mask_block_cnt=mask_cnt,
|
| 461 |
+
mask_block_idx=mask_idx,
|
| 462 |
+
full_block_cnt=full_cnt,
|
| 463 |
+
full_block_idx=full_idx,
|
| 464 |
+
block_size=tensors.block_size,
|
| 465 |
+
dq_write_order=dq_write_order,
|
| 466 |
+
dq_write_order_full=dq_write_order_full,
|
| 467 |
+
spt=spt,
|
| 468 |
+
)
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
def is_block_sparsity_enabled(tensors: BlockSparseTensorsTorch) -> bool:
|
| 472 |
+
return any(t is not None for t in (tensors.full_block_cnt, tensors.mask_block_cnt))
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def get_block_sparse_broadcast_pattern(
|
| 476 |
+
tensors: BlockSparseTensorsTorch,
|
| 477 |
+
) -> Tuple[Tuple[bool, ...], ...] | None:
|
| 478 |
+
"""Return broadcast pattern for block sparse tensors by checking actual strides.
|
| 479 |
+
|
| 480 |
+
Returns a tuple of broadcast patterns (one per tensor) where each pattern
|
| 481 |
+
is a tuple of bools indicating which dims have stride=0.
|
| 482 |
+
This is used in compile keys to ensure kernels are recompiled when
|
| 483 |
+
broadcast patterns change, since CuTe's mark_layout_dynamic() keeps
|
| 484 |
+
stride=0 as static.
|
| 485 |
+
|
| 486 |
+
The tensors should already be expanded/normalized before calling this function.
|
| 487 |
+
|
| 488 |
+
Returns None if block sparsity is not enabled.
|
| 489 |
+
"""
|
| 490 |
+
if not is_block_sparsity_enabled(tensors):
|
| 491 |
+
return None
|
| 492 |
+
|
| 493 |
+
patterns = []
|
| 494 |
+
for tensor in (
|
| 495 |
+
tensors.mask_block_cnt,
|
| 496 |
+
tensors.mask_block_idx,
|
| 497 |
+
tensors.full_block_cnt,
|
| 498 |
+
tensors.full_block_idx,
|
| 499 |
+
tensors.dq_write_order,
|
| 500 |
+
tensors.dq_write_order_full,
|
| 501 |
+
):
|
| 502 |
+
if tensor is not None:
|
| 503 |
+
patterns.append(get_broadcast_dims(tensor))
|
| 504 |
+
else:
|
| 505 |
+
patterns.append(None)
|
| 506 |
+
return tuple(patterns)
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
def normalize_block_sparse_config(
|
| 510 |
+
tensors: BlockSparseTensorsTorch,
|
| 511 |
+
*,
|
| 512 |
+
batch_size: int,
|
| 513 |
+
num_head: int,
|
| 514 |
+
seqlen_q: int,
|
| 515 |
+
seqlen_k: int,
|
| 516 |
+
block_size: tuple[int, int],
|
| 517 |
+
q_stage: int,
|
| 518 |
+
) -> tuple[BlockSparseTensorsTorch, Tuple[Tuple[bool, ...], ...] | None, int]:
|
| 519 |
+
m_block_size, n_block_size = block_size
|
| 520 |
+
if tensors.block_size is None:
|
| 521 |
+
sparse_block_size_q, sparse_block_size_kv = None, n_block_size
|
| 522 |
+
else:
|
| 523 |
+
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 524 |
+
if sparse_block_size_kv != n_block_size:
|
| 525 |
+
raise ValueError(
|
| 526 |
+
f"Block sparsity requires sparse_block_size[1]={n_block_size} to match tile_n."
|
| 527 |
+
)
|
| 528 |
+
expected_count_shape, expected_index_shape, q_subtile_factor = (
|
| 529 |
+
infer_block_sparse_expected_shapes(
|
| 530 |
+
tensors,
|
| 531 |
+
batch_size=batch_size,
|
| 532 |
+
num_head=num_head,
|
| 533 |
+
seqlen_q=seqlen_q,
|
| 534 |
+
seqlen_k=seqlen_k,
|
| 535 |
+
m_block_size=m_block_size,
|
| 536 |
+
n_block_size=n_block_size,
|
| 537 |
+
q_stage=q_stage,
|
| 538 |
+
context="forward",
|
| 539 |
+
sparse_block_size_q=sparse_block_size_q,
|
| 540 |
+
sparse_block_size_kv=sparse_block_size_kv,
|
| 541 |
+
)
|
| 542 |
+
)
|
| 543 |
+
normalized_tensors = normalize_block_sparse_tensors(
|
| 544 |
+
tensors,
|
| 545 |
+
expected_count_shape=expected_count_shape,
|
| 546 |
+
expected_index_shape=expected_index_shape,
|
| 547 |
+
)
|
| 548 |
+
return (
|
| 549 |
+
normalized_tensors,
|
| 550 |
+
get_block_sparse_broadcast_pattern(normalized_tensors),
|
| 551 |
+
q_subtile_factor,
|
| 552 |
+
)
|
| 553 |
+
|
| 554 |
+
|
| 555 |
+
def normalize_block_sparse_config_bwd(
|
| 556 |
+
tensors: BlockSparseTensorsTorch,
|
| 557 |
+
*,
|
| 558 |
+
batch_size: int,
|
| 559 |
+
num_head: int,
|
| 560 |
+
seqlen_q: int,
|
| 561 |
+
seqlen_k: int,
|
| 562 |
+
block_size: tuple[int, int],
|
| 563 |
+
subtile_factor: int,
|
| 564 |
+
) -> tuple[BlockSparseTensorsTorch, Tuple[Tuple[bool, ...], ...] | None]:
|
| 565 |
+
m_block_size, n_block_size = block_size
|
| 566 |
+
if tensors.block_size is None:
|
| 567 |
+
sparse_block_size_q, sparse_block_size_kv = subtile_factor * m_block_size, n_block_size
|
| 568 |
+
else:
|
| 569 |
+
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 570 |
+
if sparse_block_size_q != subtile_factor * m_block_size:
|
| 571 |
+
raise ValueError(
|
| 572 |
+
f"Block sparsity expects sparse_block_size_q={subtile_factor * m_block_size} "
|
| 573 |
+
f"for subtile_factor={subtile_factor}."
|
| 574 |
+
)
|
| 575 |
+
if sparse_block_size_kv != n_block_size:
|
| 576 |
+
raise ValueError(
|
| 577 |
+
f"Block sparsity expects sparse_block_size[1]={n_block_size} to match tile_n."
|
| 578 |
+
)
|
| 579 |
+
expected_count_shape, expected_index_shape = get_block_sparse_expected_shapes_bwd(
|
| 580 |
+
batch_size,
|
| 581 |
+
num_head,
|
| 582 |
+
seqlen_q,
|
| 583 |
+
seqlen_k,
|
| 584 |
+
m_block_size,
|
| 585 |
+
n_block_size,
|
| 586 |
+
subtile_factor,
|
| 587 |
+
)
|
| 588 |
+
normalized_tensors = normalize_block_sparse_tensors(
|
| 589 |
+
tensors,
|
| 590 |
+
expected_count_shape=expected_count_shape,
|
| 591 |
+
expected_index_shape=expected_index_shape,
|
| 592 |
+
context="_flash_attn_bwd",
|
| 593 |
+
hint=lambda: (
|
| 594 |
+
f"Backward expects Q-direction block-sparse tensors (q_mask_cnt/q_mask_idx, "
|
| 595 |
+
f"and optionally full_q_cnt/full_q_idx). Regenerate the backward BlockMask with "
|
| 596 |
+
f"BLOCK_SIZE=({subtile_factor * m_block_size}, {n_block_size})."
|
| 597 |
+
),
|
| 598 |
+
)
|
| 599 |
+
return normalized_tensors, get_block_sparse_broadcast_pattern(normalized_tensors)
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
def to_cute_block_sparse_tensors(
|
| 603 |
+
tensors: BlockSparseTensorsTorch, enable_tvm_ffi: bool = True
|
| 604 |
+
) -> BlockSparseTensors | None:
|
| 605 |
+
"""Convert torch block sparsity tensors to CuTe tensors, optionally for tvm ffi"""
|
| 606 |
+
if not is_block_sparsity_enabled(tensors):
|
| 607 |
+
return None
|
| 608 |
+
mask_block_cnt_tensor, mask_block_idx_tensor = [
|
| 609 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 610 |
+
for t in (tensors.mask_block_cnt, tensors.mask_block_idx)
|
| 611 |
+
]
|
| 612 |
+
full_block_cnt_tensor, full_block_idx_tensor = [
|
| 613 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 614 |
+
if t is not None
|
| 615 |
+
else None
|
| 616 |
+
for t in (tensors.full_block_cnt, tensors.full_block_idx)
|
| 617 |
+
]
|
| 618 |
+
dq_write_order_tensor, dq_write_order_full_tensor = [
|
| 619 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 620 |
+
if t is not None
|
| 621 |
+
else None
|
| 622 |
+
for t in (tensors.dq_write_order, tensors.dq_write_order_full)
|
| 623 |
+
]
|
| 624 |
+
|
| 625 |
+
return BlockSparseTensors(
|
| 626 |
+
mask_block_cnt_tensor,
|
| 627 |
+
mask_block_idx_tensor,
|
| 628 |
+
full_block_cnt_tensor,
|
| 629 |
+
full_block_idx_tensor,
|
| 630 |
+
dq_write_order_tensor,
|
| 631 |
+
dq_write_order_full_tensor,
|
| 632 |
+
)
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
def fast_sampling(mask_mod):
|
| 636 |
+
"""Convenience decorator to mark mask_mod as safe for 5-point fast sampling"""
|
| 637 |
+
mask_mod.use_fast_sampling = True
|
| 638 |
+
return mask_mod
|
build/torch-cuda/flashrt_fa4/cute/cache_utils.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Manage Ahead-of-Time (AOT) compiled kernels
|
| 2 |
+
import fcntl
|
| 3 |
+
import hashlib
|
| 4 |
+
import os
|
| 5 |
+
import pickle
|
| 6 |
+
import sys
|
| 7 |
+
import tempfile
|
| 8 |
+
import time
|
| 9 |
+
from functools import lru_cache
|
| 10 |
+
from getpass import getuser
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Hashable, TypeAlias
|
| 13 |
+
|
| 14 |
+
import ctypes
|
| 15 |
+
|
| 16 |
+
import cutlass
|
| 17 |
+
import cutlass.cute as cute
|
| 18 |
+
import tvm_ffi
|
| 19 |
+
from cutlass.cutlass_dsl import JitCompiledFunction
|
| 20 |
+
from fa4_cute_runtime.flashrt_fa4.cute.fa_logging import fa_log
|
| 21 |
+
|
| 22 |
+
# Pre-load cute DSL runtime libraries with RTLD_GLOBAL so that their symbols
|
| 23 |
+
# (e.g. _cudaLibraryLoadData) are visible to .so modules loaded later via dlopen.
|
| 24 |
+
# Upstream cute.runtime.load_module loads these without RTLD_GLOBAL, which causes
|
| 25 |
+
# "undefined symbol" errors when loading cached kernels from disk.
|
| 26 |
+
for _lib_path in cute.runtime.find_runtime_libraries(enable_tvm_ffi=False):
|
| 27 |
+
if Path(_lib_path).exists():
|
| 28 |
+
ctypes.CDLL(_lib_path, mode=ctypes.RTLD_GLOBAL)
|
| 29 |
+
|
| 30 |
+
CompileKeyType: TypeAlias = tuple[Hashable, ...]
|
| 31 |
+
CallableFunction: TypeAlias = JitCompiledFunction | tvm_ffi.Function
|
| 32 |
+
|
| 33 |
+
# Enable cache via `FLASH_ATTENTION_CUTE_DSL_CACHE_ENABLED=1`
|
| 34 |
+
CUTE_DSL_CACHE_ENABLED: bool = os.getenv("FLASH_ATTENTION_CUTE_DSL_CACHE_ENABLED", "0") == "1"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# Customize cache dir via `FLASH_ATTENTION_CUTE_DSL_CACHE_DIR`, default is
|
| 38 |
+
# `/tmp/${USER}/flash_attention_cute_dsl_cache``
|
| 39 |
+
CUTE_DSL_CACHE_DIR: str | None = os.getenv("FLASH_ATTENTION_CUTE_DSL_CACHE_DIR", None)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def get_cache_path() -> Path:
|
| 43 |
+
if CUTE_DSL_CACHE_DIR is not None:
|
| 44 |
+
cache_dir = Path(CUTE_DSL_CACHE_DIR)
|
| 45 |
+
else:
|
| 46 |
+
cache_dir = Path(tempfile.gettempdir()) / getuser() / "flash_attention_cute_dsl_cache"
|
| 47 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 48 |
+
return cache_dir
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@lru_cache(maxsize=1)
|
| 52 |
+
def _compute_source_fingerprint() -> str:
|
| 53 |
+
"""
|
| 54 |
+
Hash all CuTe Python sources plus runtime ABI stamps into a short fingerprint.
|
| 55 |
+
|
| 56 |
+
The fingerprint changes whenever:
|
| 57 |
+
- Any .py file under flash_attn/cute is added, removed, renamed, or modified.
|
| 58 |
+
- The Python minor version changes (e.g. 3.13 -> 3.14).
|
| 59 |
+
- The cutlass or tvm_ffi package version changes.
|
| 60 |
+
|
| 61 |
+
Computed once per process and cached.
|
| 62 |
+
"""
|
| 63 |
+
cute_root = Path(__file__).resolve().parent
|
| 64 |
+
h = hashlib.sha256()
|
| 65 |
+
|
| 66 |
+
h.update(f"py{sys.version_info.major}.{sys.version_info.minor}".encode())
|
| 67 |
+
h.update(f"cutlass={cutlass.__version__}".encode())
|
| 68 |
+
h.update(f"tvm_ffi={tvm_ffi.__version__}".encode())
|
| 69 |
+
|
| 70 |
+
for src in sorted(cute_root.rglob("*.py")):
|
| 71 |
+
if not src.is_file():
|
| 72 |
+
continue
|
| 73 |
+
h.update(src.relative_to(cute_root).as_posix().encode())
|
| 74 |
+
content = src.read_bytes()
|
| 75 |
+
h.update(len(content).to_bytes(8, "little"))
|
| 76 |
+
h.update(content)
|
| 77 |
+
|
| 78 |
+
return h.hexdigest()
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class FileLock:
|
| 82 |
+
"""Context manager for advisory file locks using fcntl.flock.
|
| 83 |
+
|
| 84 |
+
Supports exclusive (write) and shared (read) locks.
|
| 85 |
+
Always blocks with polling until the lock is acquired or timeout is reached.
|
| 86 |
+
|
| 87 |
+
Usage:
|
| 88 |
+
with FileLock(lock_path, exclusive=True, timeout=15, label="abc"):
|
| 89 |
+
# do work under lock
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
lock_path: Path,
|
| 95 |
+
exclusive: bool,
|
| 96 |
+
timeout: float = 15,
|
| 97 |
+
label: str = "",
|
| 98 |
+
):
|
| 99 |
+
"""
|
| 100 |
+
Args:
|
| 101 |
+
lock_path: Path to the lock file on disk.
|
| 102 |
+
exclusive: True for exclusive (write) lock, False for shared (read) lock.
|
| 103 |
+
timeout: Max seconds to wait for lock acquisition before raising RuntimeError.
|
| 104 |
+
label: Optional human-readable label for error messages.
|
| 105 |
+
"""
|
| 106 |
+
self.lock_path: Path = lock_path
|
| 107 |
+
self.exclusive: bool = exclusive
|
| 108 |
+
self.timeout: float = timeout
|
| 109 |
+
self.label: str = label
|
| 110 |
+
self._fd: int = -1
|
| 111 |
+
|
| 112 |
+
@property
|
| 113 |
+
def _lock_label(self) -> str:
|
| 114 |
+
kind = "exclusive" if self.exclusive else "shared"
|
| 115 |
+
return f"{kind} {self.label}" if self.label else kind
|
| 116 |
+
|
| 117 |
+
def __enter__(self) -> "FileLock":
|
| 118 |
+
open_flags = os.O_WRONLY | os.O_CREAT if self.exclusive else os.O_RDONLY | os.O_CREAT
|
| 119 |
+
lock_type = fcntl.LOCK_EX if self.exclusive else fcntl.LOCK_SH
|
| 120 |
+
|
| 121 |
+
self._fd = os.open(str(self.lock_path), open_flags)
|
| 122 |
+
|
| 123 |
+
deadline = time.monotonic() + self.timeout
|
| 124 |
+
acquired = False
|
| 125 |
+
while time.monotonic() < deadline:
|
| 126 |
+
try:
|
| 127 |
+
fcntl.flock(self._fd, lock_type | fcntl.LOCK_NB)
|
| 128 |
+
acquired = True
|
| 129 |
+
break
|
| 130 |
+
except OSError:
|
| 131 |
+
time.sleep(0.1)
|
| 132 |
+
if not acquired:
|
| 133 |
+
os.close(self._fd)
|
| 134 |
+
self._fd = None
|
| 135 |
+
raise RuntimeError(
|
| 136 |
+
f"Timed out after {self.timeout}s waiting for "
|
| 137 |
+
f"{self._lock_label} lock: {self.lock_path}"
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
return self
|
| 141 |
+
|
| 142 |
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
| 143 |
+
if self._fd is not None:
|
| 144 |
+
fcntl.flock(self._fd, fcntl.LOCK_UN)
|
| 145 |
+
os.close(self._fd)
|
| 146 |
+
self._fd = None
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class JITCache:
|
| 150 |
+
"""
|
| 151 |
+
In-memory cache for compiled functions.
|
| 152 |
+
"""
|
| 153 |
+
|
| 154 |
+
def __init__(self):
|
| 155 |
+
self.cache: dict[CompileKeyType, CallableFunction] = {}
|
| 156 |
+
|
| 157 |
+
def __setitem__(self, key: CompileKeyType, fn: JitCompiledFunction) -> None:
|
| 158 |
+
self.cache[key] = fn
|
| 159 |
+
|
| 160 |
+
def __getitem__(self, key: CompileKeyType) -> CallableFunction:
|
| 161 |
+
return self.cache[key]
|
| 162 |
+
|
| 163 |
+
def __contains__(self, key: CompileKeyType) -> bool:
|
| 164 |
+
return key in self.cache
|
| 165 |
+
|
| 166 |
+
def clear(self) -> None:
|
| 167 |
+
"""
|
| 168 |
+
Clear in-memory cache of compiled functions
|
| 169 |
+
"""
|
| 170 |
+
self.cache.clear()
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
class JITPersistentCache(JITCache):
|
| 174 |
+
"""
|
| 175 |
+
In-memory cache for compiled functions, which is also backed by persistent storage.
|
| 176 |
+
Use cutedsl ahead-of-time (AOT) compilation, only supporting enable_tvm_ffi=True
|
| 177 |
+
"""
|
| 178 |
+
|
| 179 |
+
EXPORT_FUNCTION_PREFIX = "func"
|
| 180 |
+
LOCK_TIMEOUT_SECONDS = 15
|
| 181 |
+
|
| 182 |
+
def __init__(self, cache_path: Path):
|
| 183 |
+
super().__init__()
|
| 184 |
+
cache_path.mkdir(parents=True, exist_ok=True)
|
| 185 |
+
self.cache_path: Path = cache_path
|
| 186 |
+
|
| 187 |
+
def __setitem__(self, key: CompileKeyType, fn: JitCompiledFunction) -> None:
|
| 188 |
+
JITCache.__setitem__(self, key, fn)
|
| 189 |
+
self._try_export_to_storage(key, fn)
|
| 190 |
+
|
| 191 |
+
def __getitem__(self, key: CompileKeyType) -> CallableFunction:
|
| 192 |
+
# Use __contains__ to try populating in-memory cache with persistent storage
|
| 193 |
+
self.__contains__(key)
|
| 194 |
+
return JITCache.__getitem__(self, key)
|
| 195 |
+
|
| 196 |
+
def __contains__(self, key: CompileKeyType) -> bool:
|
| 197 |
+
# Checks in-memory cache first, then tries loading from storage.
|
| 198 |
+
# When returning True, guarantees the in-memory cache is populated.
|
| 199 |
+
if JITCache.__contains__(self, key):
|
| 200 |
+
return True
|
| 201 |
+
return self._try_load_from_storage(key)
|
| 202 |
+
|
| 203 |
+
def _try_load_from_storage(self, key: CompileKeyType) -> bool:
|
| 204 |
+
"""
|
| 205 |
+
Try to load a function from persistent storage into in-memory cache.
|
| 206 |
+
Returns True if loaded successfully, False if not found on disk.
|
| 207 |
+
Holds a shared lock during loading to prevent concurrent writes.
|
| 208 |
+
"""
|
| 209 |
+
sha256_hex = self._key_to_hash(key)
|
| 210 |
+
obj_path = self.cache_path / f"{sha256_hex}.o"
|
| 211 |
+
with FileLock(
|
| 212 |
+
self._lock_path(sha256_hex),
|
| 213 |
+
exclusive=False,
|
| 214 |
+
timeout=self.LOCK_TIMEOUT_SECONDS,
|
| 215 |
+
label=sha256_hex,
|
| 216 |
+
):
|
| 217 |
+
if obj_path.exists():
|
| 218 |
+
fa_log(1, f"Loading compiled function from disk: {obj_path}")
|
| 219 |
+
m = cute.runtime.load_module(str(obj_path), enable_tvm_ffi=True)
|
| 220 |
+
fn = getattr(m, self.EXPORT_FUNCTION_PREFIX)
|
| 221 |
+
JITCache.__setitem__(self, key, fn)
|
| 222 |
+
return True
|
| 223 |
+
else:
|
| 224 |
+
fa_log(1, f"Cache miss on disk for key hash {sha256_hex}")
|
| 225 |
+
return False
|
| 226 |
+
|
| 227 |
+
def _try_export_to_storage(self, key: CompileKeyType, fn: JitCompiledFunction) -> None:
|
| 228 |
+
"""Export a compiled function to persistent storage under exclusive lock."""
|
| 229 |
+
sha256_hex = self._key_to_hash(key)
|
| 230 |
+
with FileLock(
|
| 231 |
+
self._lock_path(sha256_hex),
|
| 232 |
+
exclusive=True,
|
| 233 |
+
timeout=self.LOCK_TIMEOUT_SECONDS,
|
| 234 |
+
label=sha256_hex,
|
| 235 |
+
):
|
| 236 |
+
obj_path = self.cache_path / f"{sha256_hex}.o"
|
| 237 |
+
if obj_path.exists():
|
| 238 |
+
# Another process already exported.
|
| 239 |
+
fa_log(1, f"Skipping export, already on disk: {obj_path}")
|
| 240 |
+
return
|
| 241 |
+
fa_log(1, f"Exporting compiled function to disk: {obj_path}")
|
| 242 |
+
fn.export_to_c(
|
| 243 |
+
object_file_path=str(obj_path),
|
| 244 |
+
function_name=self.EXPORT_FUNCTION_PREFIX,
|
| 245 |
+
)
|
| 246 |
+
fa_log(1, f"Successfully exported compiled function to disk: {obj_path}")
|
| 247 |
+
|
| 248 |
+
def _key_to_hash(self, key: CompileKeyType) -> str:
|
| 249 |
+
return hashlib.sha256(pickle.dumps(key)).hexdigest()
|
| 250 |
+
|
| 251 |
+
def _lock_path(self, sha256_hex: str) -> Path:
|
| 252 |
+
return self.cache_path / f"{sha256_hex}.lock"
|
| 253 |
+
|
| 254 |
+
def clear(self) -> None:
|
| 255 |
+
"""
|
| 256 |
+
Not only clear the in-memory cache. Also purge persistent compilation cache.
|
| 257 |
+
"""
|
| 258 |
+
fa_log(1, f"Clearing persistent cache at {self.cache_path}")
|
| 259 |
+
super().clear()
|
| 260 |
+
for child in self.cache_path.iterdir():
|
| 261 |
+
child.unlink()
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def get_jit_cache(name: str | None = None) -> JITCache:
|
| 265 |
+
"""
|
| 266 |
+
JIT cache factory.
|
| 267 |
+
`name` is an optional identifier to create subdirectories to manage cache.
|
| 268 |
+
|
| 269 |
+
When persistent caching is enabled, artifacts are namespaced under a
|
| 270 |
+
source fingerprint directory so that code or dependency changes
|
| 271 |
+
automatically invalidate stale entries.
|
| 272 |
+
"""
|
| 273 |
+
if CUTE_DSL_CACHE_ENABLED:
|
| 274 |
+
path = get_cache_path() / _compute_source_fingerprint()
|
| 275 |
+
if name:
|
| 276 |
+
path = path / name
|
| 277 |
+
fa_log(1, f"Creating persistent JIT cache at {path}")
|
| 278 |
+
return JITPersistentCache(path)
|
| 279 |
+
else:
|
| 280 |
+
fa_log(1, "Persistent cache disabled, using in-memory JIT cache")
|
| 281 |
+
return JITCache()
|
build/torch-cuda/flashrt_fa4/cute/copy_utils.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional, Type, Callable
|
| 5 |
+
|
| 6 |
+
import cutlass
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
from cutlass import Float32, Int32, const_expr
|
| 9 |
+
from cutlass.cute.nvgpu import cpasync
|
| 10 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 11 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 12 |
+
from cutlass._mlir.dialects import llvm
|
| 13 |
+
import cutlass.pipeline
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dsl_user_op
|
| 17 |
+
def cvt_copy(
|
| 18 |
+
atom: cute.CopyAtom,
|
| 19 |
+
src: cute.Tensor,
|
| 20 |
+
dst: cute.Tensor,
|
| 21 |
+
*,
|
| 22 |
+
pred: Optional[cute.Tensor] = None,
|
| 23 |
+
loc=None,
|
| 24 |
+
ip=None,
|
| 25 |
+
**kwargs,
|
| 26 |
+
) -> None:
|
| 27 |
+
assert isinstance(src.iterator, cute.Pointer) and src.memspace == cute.AddressSpace.rmem
|
| 28 |
+
if const_expr(src.element_type != dst.element_type):
|
| 29 |
+
src_cvt = cute.make_fragment_like(src, dst.element_type, loc=loc, ip=ip)
|
| 30 |
+
src_cvt.store(src.load().to(dst.element_type))
|
| 31 |
+
src = src_cvt
|
| 32 |
+
cute.copy(atom, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@dsl_user_op
|
| 36 |
+
def load_s2r(src: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
| 37 |
+
dst = cute.make_fragment_like(src, src.element_type, loc=loc, ip=ip)
|
| 38 |
+
cute.autovec_copy(src, dst, loc=loc, ip=ip)
|
| 39 |
+
return dst
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@dsl_user_op
|
| 43 |
+
def get_copy_atom(
|
| 44 |
+
dtype: Type[cutlass.Numeric], num_copy_elems: int, is_async: bool = False, *, loc=None, ip=None
|
| 45 |
+
) -> cute.CopyAtom:
|
| 46 |
+
num_copy_bits = const_expr(min(128, num_copy_elems * dtype.width))
|
| 47 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 48 |
+
return cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dsl_user_op
|
| 52 |
+
def make_tmem_copy(
|
| 53 |
+
tmem_copy_atom: cute.CopyAtom, num_wg: int = 1, *, loc=None, ip=None
|
| 54 |
+
) -> cute.CopyAtom:
|
| 55 |
+
num_dp, num_bits, num_rep, _ = sm100_utils.get_tmem_copy_properties(tmem_copy_atom)
|
| 56 |
+
assert num_dp == 32
|
| 57 |
+
assert num_bits == 32
|
| 58 |
+
tiler_mn = (cute.make_layout((128 * num_rep * num_wg // 32, 32), stride=(32, 1)),)
|
| 59 |
+
layout_tv = cute.make_layout(
|
| 60 |
+
((32, 4, num_wg), (num_rep, 32)), stride=((0, 1, 4 * num_rep), (4, 4 * num_rep * num_wg))
|
| 61 |
+
)
|
| 62 |
+
return cute.make_tiled_copy(tmem_copy_atom, layout_tv, tiler_mn)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@dsl_user_op
|
| 66 |
+
def copy(
|
| 67 |
+
src: cute.Tensor,
|
| 68 |
+
dst: cute.Tensor,
|
| 69 |
+
*,
|
| 70 |
+
pred: Optional[cute.Tensor] = None,
|
| 71 |
+
num_copy_elems: int = 1,
|
| 72 |
+
is_async: bool = False,
|
| 73 |
+
loc=None,
|
| 74 |
+
ip=None,
|
| 75 |
+
**kwargs,
|
| 76 |
+
) -> None:
|
| 77 |
+
copy_atom = get_copy_atom(src.element_type, num_copy_elems, is_async)
|
| 78 |
+
cute.copy(copy_atom, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def tiled_copy_1d(
|
| 82 |
+
dtype: Type[cutlass.Numeric], num_threads: int, num_copy_elems: int = 1, is_async: bool = False
|
| 83 |
+
) -> cute.TiledCopy:
|
| 84 |
+
num_copy_bits = num_copy_elems * dtype.width
|
| 85 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 86 |
+
copy_atom = cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 87 |
+
thr_layout = cute.make_layout(num_threads)
|
| 88 |
+
val_layout = cute.make_layout(num_copy_elems)
|
| 89 |
+
return cute.make_tiled_copy_tv(copy_atom, thr_layout, val_layout)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def tiled_copy_2d(
|
| 93 |
+
dtype: Type[cutlass.Numeric], major_mode_size: int, num_threads: int, is_async: bool = False
|
| 94 |
+
) -> cute.TiledCopy:
|
| 95 |
+
num_copy_bits = math.gcd(major_mode_size, 128 // dtype.width) * dtype.width
|
| 96 |
+
copy_elems = num_copy_bits // dtype.width
|
| 97 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 98 |
+
copy_atom = cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 99 |
+
gmem_threads_per_row = major_mode_size // copy_elems
|
| 100 |
+
assert num_threads % gmem_threads_per_row == 0
|
| 101 |
+
thr_layout = cute.make_ordered_layout(
|
| 102 |
+
(num_threads // gmem_threads_per_row, gmem_threads_per_row),
|
| 103 |
+
order=(1, 0),
|
| 104 |
+
)
|
| 105 |
+
val_layout = cute.make_layout((1, copy_elems))
|
| 106 |
+
return cute.make_tiled_copy_tv(copy_atom, thr_layout, val_layout)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
@dsl_user_op
|
| 110 |
+
def atomic_add_fp32x4(
|
| 111 |
+
a: Float32, b: Float32, c: Float32, d: Float32, gmem_ptr: cute.Pointer, *, loc=None, ip=None
|
| 112 |
+
) -> None:
|
| 113 |
+
gmem_ptr_i64 = gmem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 114 |
+
# cache_hint = cutlass.Int64(0x12F0000000000000)
|
| 115 |
+
llvm.inline_asm(
|
| 116 |
+
None,
|
| 117 |
+
[
|
| 118 |
+
gmem_ptr_i64,
|
| 119 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 120 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 121 |
+
Float32(c).ir_value(loc=loc, ip=ip),
|
| 122 |
+
Float32(d).ir_value(loc=loc, ip=ip),
|
| 123 |
+
],
|
| 124 |
+
# [gmem_ptr_i64, Float32(a).ir_value(loc=loc, ip=ip), cache_hint.ir_value()],
|
| 125 |
+
"{\n\t"
|
| 126 |
+
# ".reg .b128 abcd;\n\t"
|
| 127 |
+
# "mov.b128 abcd, {$1, $2, $3, $4};\n\t"
|
| 128 |
+
".reg .v4 .f32 abcd;\n\t"
|
| 129 |
+
# "mov.b128 abcd, {$1, $2, $3, $4};\n\t"
|
| 130 |
+
"mov.f32 abcd.x, $1;\n\t"
|
| 131 |
+
"mov.f32 abcd.y, $2;\n\t"
|
| 132 |
+
"mov.f32 abcd.z, $3;\n\t"
|
| 133 |
+
"mov.f32 abcd.w, $4;\n\t"
|
| 134 |
+
"red.global.add.v4.f32 [$0], abcd;\n\t"
|
| 135 |
+
# "red.global.add.L2::cache_hint.v4.f32 [$0], abcd, 0x14F0000000000000;\n\t"
|
| 136 |
+
"}\n",
|
| 137 |
+
# "red.global.add.L2::cache_hint.f32 [$0], $1, 0x12F0000000000000;",
|
| 138 |
+
# "red.global.add.L2::cache_hint.f32 [$0], $1, $2;",
|
| 139 |
+
"l,f,f,f,f",
|
| 140 |
+
# "l,f,l",
|
| 141 |
+
has_side_effects=True,
|
| 142 |
+
is_align_stack=False,
|
| 143 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
@dsl_user_op
|
| 148 |
+
def set_block_rank(
|
| 149 |
+
smem_ptr: cute.Pointer, peer_cta_rank_in_cluster: Int32, *, loc=None, ip=None
|
| 150 |
+
) -> Int32:
|
| 151 |
+
"""Map the given smem pointer to the address at another CTA rank in the cluster."""
|
| 152 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 153 |
+
return Int32(
|
| 154 |
+
llvm.inline_asm(
|
| 155 |
+
T.i32(),
|
| 156 |
+
[smem_ptr_i32, peer_cta_rank_in_cluster.ir_value()],
|
| 157 |
+
"mapa.shared::cluster.u32 $0, $1, $2;",
|
| 158 |
+
"=r,r,r",
|
| 159 |
+
has_side_effects=False,
|
| 160 |
+
is_align_stack=False,
|
| 161 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 162 |
+
)
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
@dsl_user_op
|
| 167 |
+
def store_shared_remote_fp32x4(
|
| 168 |
+
a: Float32,
|
| 169 |
+
b: Float32,
|
| 170 |
+
c: Float32,
|
| 171 |
+
d: Float32,
|
| 172 |
+
smem_ptr: cute.Pointer,
|
| 173 |
+
mbar_ptr: cute.Pointer,
|
| 174 |
+
peer_cta_rank_in_cluster: Int32,
|
| 175 |
+
*,
|
| 176 |
+
loc=None,
|
| 177 |
+
ip=None,
|
| 178 |
+
) -> None:
|
| 179 |
+
remote_smem_ptr_i32 = set_block_rank(
|
| 180 |
+
smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 181 |
+
).ir_value()
|
| 182 |
+
remote_mbar_ptr_i32 = set_block_rank(
|
| 183 |
+
mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 184 |
+
).ir_value()
|
| 185 |
+
llvm.inline_asm(
|
| 186 |
+
None,
|
| 187 |
+
[
|
| 188 |
+
remote_smem_ptr_i32,
|
| 189 |
+
remote_mbar_ptr_i32,
|
| 190 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 191 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 192 |
+
Float32(c).ir_value(loc=loc, ip=ip),
|
| 193 |
+
Float32(d).ir_value(loc=loc, ip=ip),
|
| 194 |
+
],
|
| 195 |
+
"{\n\t"
|
| 196 |
+
".reg .v4 .f32 abcd;\n\t"
|
| 197 |
+
"mov.f32 abcd.x, $2;\n\t"
|
| 198 |
+
"mov.f32 abcd.y, $3;\n\t"
|
| 199 |
+
"mov.f32 abcd.z, $4;\n\t"
|
| 200 |
+
"mov.f32 abcd.w, $5;\n\t"
|
| 201 |
+
"st.async.shared::cluster.mbarrier::complete_tx::bytes.v4.f32 [$0], abcd, [$1];\n\t"
|
| 202 |
+
"}\n",
|
| 203 |
+
"r,r,f,f,f,f",
|
| 204 |
+
has_side_effects=True,
|
| 205 |
+
is_align_stack=False,
|
| 206 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
@dsl_user_op
|
| 211 |
+
def cpasync_bulk_s2cluster(
|
| 212 |
+
smem_src_ptr: cute.Pointer,
|
| 213 |
+
smem_dst_ptr: cute.Pointer,
|
| 214 |
+
mbar_ptr: cute.Pointer,
|
| 215 |
+
size: int | Int32,
|
| 216 |
+
peer_cta_rank_in_cluster: Int32,
|
| 217 |
+
*,
|
| 218 |
+
loc=None,
|
| 219 |
+
ip=None,
|
| 220 |
+
):
|
| 221 |
+
smem_src_ptr_i32 = smem_src_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 222 |
+
smem_dst_ptr_i32 = set_block_rank(
|
| 223 |
+
smem_dst_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 224 |
+
).ir_value()
|
| 225 |
+
mbar_ptr_i32 = set_block_rank(mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip).ir_value()
|
| 226 |
+
llvm.inline_asm(
|
| 227 |
+
None,
|
| 228 |
+
[
|
| 229 |
+
smem_dst_ptr_i32,
|
| 230 |
+
smem_src_ptr_i32,
|
| 231 |
+
mbar_ptr_i32,
|
| 232 |
+
Int32(size).ir_value(loc=loc, ip=ip),
|
| 233 |
+
],
|
| 234 |
+
"cp.async.bulk.shared::cluster.shared::cta.mbarrier::complete_tx::bytes [$0], [$1], $3, [$2];",
|
| 235 |
+
"r,r,r,r",
|
| 236 |
+
has_side_effects=True,
|
| 237 |
+
is_align_stack=False,
|
| 238 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
@dsl_user_op
|
| 243 |
+
def cpasync_bulk_g2s(
|
| 244 |
+
gmem_ptr: cute.Pointer,
|
| 245 |
+
smem_ptr: cute.Pointer,
|
| 246 |
+
tma_bar_ptr: cute.Pointer,
|
| 247 |
+
size: int | Int32,
|
| 248 |
+
*,
|
| 249 |
+
loc=None,
|
| 250 |
+
ip=None,
|
| 251 |
+
):
|
| 252 |
+
gmem_ptr_i64 = gmem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 253 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 254 |
+
mbar_ptr_i32 = tma_bar_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 255 |
+
llvm.inline_asm(
|
| 256 |
+
None,
|
| 257 |
+
[gmem_ptr_i64, smem_ptr_i32, mbar_ptr_i32, Int32(size).ir_value()],
|
| 258 |
+
"cp.async.bulk.shared::cta.global.mbarrier::complete_tx::bytes [$1], [$0], $3, [$2];",
|
| 259 |
+
"l,r,r,r",
|
| 260 |
+
has_side_effects=True,
|
| 261 |
+
is_align_stack=False,
|
| 262 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
@dsl_user_op
|
| 267 |
+
def cpasync_reduce_bulk_add_f32(
|
| 268 |
+
smem_ptr: cute.Pointer,
|
| 269 |
+
gmem_ptr: cute.Pointer,
|
| 270 |
+
store_bytes: int | Int32,
|
| 271 |
+
*,
|
| 272 |
+
loc=None,
|
| 273 |
+
ip=None,
|
| 274 |
+
):
|
| 275 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 276 |
+
# cache_hint = cutlass.Int64(0x14F0000000000000) # EVICT_LAST
|
| 277 |
+
llvm.inline_asm(
|
| 278 |
+
None,
|
| 279 |
+
[gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value()],
|
| 280 |
+
"cp.reduce.async.bulk.global.shared::cta.bulk_group.add.f32 [$0], [$1], $2;",
|
| 281 |
+
"l,r,r",
|
| 282 |
+
# [gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value(), cache_hint.ir_value()],
|
| 283 |
+
# "cp.reduce.async.bulk.global.shared::cta.bulk_group.L2::cache_hint.add.f32 [$0], [$1], $2, $3;",
|
| 284 |
+
# "l,r,r,l",
|
| 285 |
+
has_side_effects=True,
|
| 286 |
+
is_align_stack=False,
|
| 287 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def cpasync_bulk_get_copy_fn(
|
| 292 |
+
src_tensor: cute.Tensor,
|
| 293 |
+
dst_tensor: cute.Tensor,
|
| 294 |
+
single_stage: bool = False,
|
| 295 |
+
**kwargs,
|
| 296 |
+
) -> Callable:
|
| 297 |
+
# src_is_smem = const_expr(
|
| 298 |
+
# isinstance(src_tensor.iterator, cute.Pointer)
|
| 299 |
+
# and src_tensor.memspace == cute.AddressSpace.smem
|
| 300 |
+
# )
|
| 301 |
+
group_rank_src = const_expr(cute.rank(src_tensor) - (1 if not single_stage else 0))
|
| 302 |
+
group_rank_dst = const_expr(cute.rank(dst_tensor) - (1 if not single_stage else 0))
|
| 303 |
+
# ((atom_v, rest_v), STAGE), ((atom_v, rest_v), RestK)
|
| 304 |
+
src = cute.group_modes(src_tensor, 0, group_rank_src)
|
| 305 |
+
dst = cute.group_modes(dst_tensor, 0, group_rank_dst)
|
| 306 |
+
|
| 307 |
+
def copy_bulk(src_idx, dst_idx, **new_kwargs):
|
| 308 |
+
size = const_expr(cute.size(src.shape[:-1]) * src.element_type.width // 8)
|
| 309 |
+
cpasync_bulk_g2s(
|
| 310 |
+
src[None, src_idx].iterator,
|
| 311 |
+
dst[None, dst_idx].iterator,
|
| 312 |
+
size=size,
|
| 313 |
+
**new_kwargs,
|
| 314 |
+
**kwargs,
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
def copy_bulk_single_stage(**new_kwargs):
|
| 318 |
+
size = const_expr(cute.size(src.shape) * src.element_type.width // 8)
|
| 319 |
+
cpasync_bulk_g2s(src.iterator, dst.iterator, size=size, **new_kwargs, **kwargs)
|
| 320 |
+
|
| 321 |
+
return copy_bulk if const_expr(not single_stage) else copy_bulk_single_stage
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def tma_get_copy_fn(
|
| 325 |
+
atom: cute.CopyAtom,
|
| 326 |
+
cta_coord: cute.Coord,
|
| 327 |
+
cta_layout: cute.Layout,
|
| 328 |
+
src_tensor: cute.Tensor,
|
| 329 |
+
dst_tensor: cute.Tensor,
|
| 330 |
+
filter_zeros: bool = False,
|
| 331 |
+
single_stage: bool = False,
|
| 332 |
+
**kwargs,
|
| 333 |
+
) -> Callable:
|
| 334 |
+
src_is_smem = const_expr(
|
| 335 |
+
isinstance(src_tensor.iterator, cute.Pointer)
|
| 336 |
+
and src_tensor.memspace == cute.AddressSpace.smem
|
| 337 |
+
)
|
| 338 |
+
smem_tensor, gmem_tensor = (src_tensor, dst_tensor) if src_is_smem else (dst_tensor, src_tensor)
|
| 339 |
+
group_rank_smem = const_expr(cute.rank(smem_tensor) - (1 if not single_stage else 0))
|
| 340 |
+
group_rank_gmem = const_expr(cute.rank(gmem_tensor) - (1 if not single_stage else 0))
|
| 341 |
+
# ((atom_v, rest_v), STAGE), ((atom_v, rest_v), RestK)
|
| 342 |
+
s, g = cpasync.tma_partition(
|
| 343 |
+
atom,
|
| 344 |
+
cta_coord,
|
| 345 |
+
cta_layout,
|
| 346 |
+
cute.group_modes(smem_tensor, 0, group_rank_smem),
|
| 347 |
+
cute.group_modes(gmem_tensor, 0, group_rank_gmem),
|
| 348 |
+
)
|
| 349 |
+
if const_expr(filter_zeros):
|
| 350 |
+
s = cute.filter_zeros(s)
|
| 351 |
+
g = cute.filter_zeros(g)
|
| 352 |
+
src, dst = (s, g) if src_is_smem else (g, s)
|
| 353 |
+
|
| 354 |
+
def copy_tma(src_idx, dst_idx, **new_kwargs):
|
| 355 |
+
cute.copy(atom, src[None, src_idx], dst[None, dst_idx], **new_kwargs, **kwargs)
|
| 356 |
+
|
| 357 |
+
def copy_tma_single_stage(**new_kwargs):
|
| 358 |
+
cute.copy(atom, src, dst, **new_kwargs, **kwargs)
|
| 359 |
+
|
| 360 |
+
return (copy_tma if const_expr(not single_stage) else copy_tma_single_stage), s, g
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
def tma_producer_copy_fn(copy: Callable, pipeline: cutlass.pipeline.PipelineAsync):
|
| 364 |
+
def copy_fn(src_idx, producer_state: cutlass.pipeline.PipelineState, **new_kwargs):
|
| 365 |
+
copy(
|
| 366 |
+
src_idx=src_idx,
|
| 367 |
+
dst_idx=producer_state.index,
|
| 368 |
+
tma_bar_ptr=pipeline.producer_get_barrier(producer_state),
|
| 369 |
+
**new_kwargs,
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
return copy_fn
|
build/torch-cuda/flashrt_fa4/cute/cute_dsl_ptxas.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
System ptxas replacement for CUTLASS DSL.
|
| 3 |
+
Environment variables:
|
| 4 |
+
CUTE_DSL_PTXAS_PATH - Path to ptxas (e.g., /usr/local/cuda/bin/ptxas)
|
| 5 |
+
CUTE_DSL_PTXAS_VERBOSE - Set to 1 for verbose output
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import re
|
| 11 |
+
import ctypes
|
| 12 |
+
import subprocess
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import cutlass
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
CUTE_DSL_PTXAS_PATH = os.environ.get("CUTE_DSL_PTXAS_PATH", None)
|
| 19 |
+
VERBOSE = os.environ.get("CUTE_DSL_PTXAS_VERBOSE", "0") == "1"
|
| 20 |
+
|
| 21 |
+
_original_load_cuda_library = None
|
| 22 |
+
_user_wanted_ptx = False # True if user originally set CUTE_DSL_KEEP_PTX=1
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _log(msg):
|
| 26 |
+
if VERBOSE:
|
| 27 |
+
print(f"[ptxas] {msg}", file=sys.stderr)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _get_ptx(compiled_func) -> tuple[str, Path] | None:
|
| 31 |
+
"""Find and read PTX file, stripping null bytes."""
|
| 32 |
+
func_name = getattr(compiled_func, "function_name", None)
|
| 33 |
+
if not func_name:
|
| 34 |
+
return None
|
| 35 |
+
|
| 36 |
+
dump_dir = os.environ.get("CUTE_DSL_DUMP_DIR", Path.cwd())
|
| 37 |
+
for ptx_path in Path(dump_dir).glob(f"*{func_name}*.ptx"):
|
| 38 |
+
content = ptx_path.read_text().rstrip("\x00")
|
| 39 |
+
if ".entry " in content and content.rstrip().endswith("}"):
|
| 40 |
+
_log(f"Found PTX: {ptx_path}")
|
| 41 |
+
return content, ptx_path
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _compile_ptx(ptx_path: Path, ptx_content: str) -> bytes:
|
| 46 |
+
"""Compile PTX to cubin using system ptxas."""
|
| 47 |
+
# Extract arch from PTX
|
| 48 |
+
match = re.search(r"\.target\s+(sm_\d+[a-z]?)", ptx_content)
|
| 49 |
+
arch = match.group(1) if match else "sm_90a"
|
| 50 |
+
|
| 51 |
+
# Write stripped content back if needed
|
| 52 |
+
if ptx_path.read_text() != ptx_content:
|
| 53 |
+
ptx_path.write_text(ptx_content)
|
| 54 |
+
|
| 55 |
+
# Compile
|
| 56 |
+
cubin_tmp = ptx_path.with_suffix(".cubin.tmp")
|
| 57 |
+
try:
|
| 58 |
+
assert CUTE_DSL_PTXAS_PATH is not None
|
| 59 |
+
result = subprocess.run(
|
| 60 |
+
[CUTE_DSL_PTXAS_PATH, f"-arch={arch}", "-O3", "-o", str(cubin_tmp), str(ptx_path)],
|
| 61 |
+
capture_output=True,
|
| 62 |
+
text=True,
|
| 63 |
+
)
|
| 64 |
+
if result.returncode != 0:
|
| 65 |
+
raise RuntimeError(f"ptxas failed: {result.stderr}")
|
| 66 |
+
|
| 67 |
+
cubin_data = cubin_tmp.read_bytes()
|
| 68 |
+
_log(f"Compiled {ptx_path.name} -> {len(cubin_data)} bytes ({arch})")
|
| 69 |
+
|
| 70 |
+
# Save cubin if CUTE_DSL_KEEP_CUBIN is set
|
| 71 |
+
if os.environ.get("CUTE_DSL_KEEP_CUBIN", "0") == "1":
|
| 72 |
+
cubin_out = ptx_path.with_suffix(".cubin")
|
| 73 |
+
cubin_out.write_bytes(cubin_data)
|
| 74 |
+
_log(f"Saved: {cubin_out}")
|
| 75 |
+
|
| 76 |
+
return cubin_data
|
| 77 |
+
finally:
|
| 78 |
+
cubin_tmp.unlink(missing_ok=True)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _patched_load_cuda_library(self):
|
| 82 |
+
"""Replacement for _load_cuda_library that uses system ptxas."""
|
| 83 |
+
|
| 84 |
+
result = _get_ptx(self)
|
| 85 |
+
if not result:
|
| 86 |
+
_log("PTX not found, falling back to embedded ptxas")
|
| 87 |
+
return _original_load_cuda_library(self)
|
| 88 |
+
|
| 89 |
+
ptx_content, ptx_path = result
|
| 90 |
+
|
| 91 |
+
try:
|
| 92 |
+
cubin = _compile_ptx(ptx_path, ptx_content)
|
| 93 |
+
except Exception as e:
|
| 94 |
+
_log(f"Compilation failed ({e}), falling back to embedded ptxas")
|
| 95 |
+
return _original_load_cuda_library(self)
|
| 96 |
+
|
| 97 |
+
# Load cubin
|
| 98 |
+
import cuda.bindings.runtime as cuda_runtime
|
| 99 |
+
|
| 100 |
+
err, library = cuda_runtime.cudaLibraryLoadData(cubin, None, None, 0, None, None, 0)
|
| 101 |
+
if err != cuda_runtime.cudaError_t.cudaSuccess:
|
| 102 |
+
_log(f"cudaLibraryLoadData failed ({err}), falling back to embedded ptxas")
|
| 103 |
+
return _original_load_cuda_library(self)
|
| 104 |
+
|
| 105 |
+
# Register kernels on all devices
|
| 106 |
+
_, cuda_load_to_device = self._get_cuda_init_and_load()
|
| 107 |
+
lib_ptr = ctypes.c_void_p(int(library))
|
| 108 |
+
dev_id = ctypes.c_int32(0)
|
| 109 |
+
err_val = ctypes.c_int32(0)
|
| 110 |
+
args = (ctypes.c_void_p * 3)(
|
| 111 |
+
ctypes.cast(ctypes.pointer(lib_ptr), ctypes.c_void_p),
|
| 112 |
+
ctypes.cast(ctypes.pointer(dev_id), ctypes.c_void_p),
|
| 113 |
+
ctypes.cast(ctypes.pointer(err_val), ctypes.c_void_p),
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
for dev in range(self.num_devices):
|
| 117 |
+
dev_id.value = dev
|
| 118 |
+
cuda_load_to_device(args)
|
| 119 |
+
if err_val.value != 0:
|
| 120 |
+
_log("cuda_load_to_device failed, falling back to embedded ptxas")
|
| 121 |
+
return _original_load_cuda_library(self)
|
| 122 |
+
|
| 123 |
+
_log(f"Loaded kernel from {ptx_path.name}")
|
| 124 |
+
|
| 125 |
+
# Delete PTX if user didn't originally want it kept
|
| 126 |
+
if not _user_wanted_ptx:
|
| 127 |
+
ptx_path.unlink(missing_ok=True)
|
| 128 |
+
|
| 129 |
+
return [cuda_runtime.cudaLibrary_t(lib_ptr.value)]
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def patch():
|
| 133 |
+
"""Install system ptxas hook. Call before importing cutlass."""
|
| 134 |
+
global _original_load_cuda_library, _user_wanted_ptx
|
| 135 |
+
|
| 136 |
+
assert CUTE_DSL_PTXAS_PATH is not None
|
| 137 |
+
if not os.path.isfile(CUTE_DSL_PTXAS_PATH) or not os.access(CUTE_DSL_PTXAS_PATH, os.X_OK):
|
| 138 |
+
raise RuntimeError(f"ptxas not found: {CUTE_DSL_PTXAS_PATH}")
|
| 139 |
+
|
| 140 |
+
# Track if user originally wanted PTX kept
|
| 141 |
+
_user_wanted_ptx = os.environ.get("CUTE_DSL_KEEP_PTX", "0") == "1"
|
| 142 |
+
# os.environ['CUTE_DSL_KEEP_PTX'] = '1'
|
| 143 |
+
assert os.environ.get("CUTE_DSL_KEEP_PTX", "0") == "1", (
|
| 144 |
+
"Require CUTE_DSL_KEEP_PTX=1 to use system's ptxas"
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
cls = cutlass.cutlass_dsl.cuda_jit_executor.CudaDialectJitCompiledFunction
|
| 148 |
+
_original_load_cuda_library = cls._load_cuda_library
|
| 149 |
+
cls._load_cuda_library = _patched_load_cuda_library
|
| 150 |
+
_log("Patch applied")
|
| 151 |
+
return
|
build/torch-cuda/flashrt_fa4/cute/cute_dsl_utils.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Tuple
|
| 4 |
+
from functools import lru_cache
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from triton.tools.disasm import extract
|
| 10 |
+
except ImportError:
|
| 11 |
+
extract = None
|
| 12 |
+
|
| 13 |
+
import cutlass
|
| 14 |
+
import cutlass.cute as cute
|
| 15 |
+
from cutlass.cutlass_dsl import NumericMeta
|
| 16 |
+
from cutlass.cute.runtime import from_dlpack
|
| 17 |
+
|
| 18 |
+
StaticTypes = (cutlass.Constexpr, NumericMeta, int, bool, str, float, type(None))
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
load_cubin_module_data_og = cutlass.base_dsl.runtime.cuda.load_cubin_module_data
|
| 22 |
+
cute_compile_og = cute.compile
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
torch2cute_dtype_map = {
|
| 26 |
+
torch.float16: cutlass.Float16,
|
| 27 |
+
torch.bfloat16: cutlass.BFloat16,
|
| 28 |
+
torch.float32: cutlass.Float32,
|
| 29 |
+
torch.float8_e4m3fn: cutlass.Float8E4M3FN,
|
| 30 |
+
torch.float8_e5m2: cutlass.Float8E5M2,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@lru_cache
|
| 35 |
+
def get_max_active_clusters(cluster_size):
|
| 36 |
+
return cutlass.utils.HardwareInfo().get_max_active_clusters(cluster_size=cluster_size)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
@lru_cache
|
| 40 |
+
def get_device_capacity(device: torch.device = None) -> Tuple[int, int]:
|
| 41 |
+
return torch.cuda.get_device_capability(device)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def assume_strides_aligned(t):
|
| 45 |
+
"""Assume all strides except the last are divisible by 128 bits.
|
| 46 |
+
|
| 47 |
+
Python int strides (e.g., stride=0 from GQA expand) are kept as-is
|
| 48 |
+
since they're static and don't need alignment assumptions.
|
| 49 |
+
"""
|
| 50 |
+
divby = 128 // t.element_type.width
|
| 51 |
+
strides = tuple(s if isinstance(s, int) else cute.assume(s, divby=divby) for s in t.stride[:-1])
|
| 52 |
+
return (*strides, t.stride[-1])
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def assume_tensor_aligned(t):
|
| 56 |
+
"""Rebuild a tensor with 128-bit aligned stride assumptions. Passes through None."""
|
| 57 |
+
if t is None:
|
| 58 |
+
return None
|
| 59 |
+
return cute.make_tensor(t.iterator, cute.make_layout(t.shape, stride=assume_strides_aligned(t)))
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def to_cute_tensor(t, assumed_align=16, leading_dim=-1, fully_dynamic=False, enable_tvm_ffi=True):
|
| 63 |
+
"""Convert torch tensor to cute tensor for TVM FFI. leading_dim=-1 defaults to t.ndim-1."""
|
| 64 |
+
# NOTE: torch 2.9.1 doesn't support fp8 via DLPack but 2.11.0 nightly does
|
| 65 |
+
# currently export raw bytes as uint8 and tell cutlass correct type
|
| 66 |
+
# can directly export as fp8 when torch supports it
|
| 67 |
+
if t.dtype in (torch.float8_e4m3fn, torch.float8_e5m2):
|
| 68 |
+
tensor = from_dlpack(
|
| 69 |
+
t.view(torch.uint8).detach(),
|
| 70 |
+
assumed_align=assumed_align,
|
| 71 |
+
enable_tvm_ffi=enable_tvm_ffi,
|
| 72 |
+
)
|
| 73 |
+
tensor.element_type = (
|
| 74 |
+
cutlass.Float8E4M3FN if t.dtype == torch.float8_e4m3fn else cutlass.Float8E5M2
|
| 75 |
+
)
|
| 76 |
+
else:
|
| 77 |
+
tensor = from_dlpack(t.detach(), assumed_align=assumed_align, enable_tvm_ffi=enable_tvm_ffi)
|
| 78 |
+
if fully_dynamic:
|
| 79 |
+
return tensor.mark_layout_dynamic()
|
| 80 |
+
if leading_dim == -1:
|
| 81 |
+
leading_dim = t.ndim - 1
|
| 82 |
+
return tensor.mark_layout_dynamic(leading_dim=leading_dim)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def to_cute_aux_tensor(t, enable_tvm_ffi=True):
|
| 86 |
+
"""Convert torch tensor to cute tensor for TVM FFI, tailored to FlexAttention aux tensors.
|
| 87 |
+
This allows the user to specify alignment and leading dimension for aux tensors used in
|
| 88 |
+
custom score_mod callables.
|
| 89 |
+
"""
|
| 90 |
+
assumed_align: int = getattr(t, "__assumed_align__", None)
|
| 91 |
+
leading_dim: int = getattr(t, "__leading_dim__", None)
|
| 92 |
+
fully_dynamic: bool = leading_dim is None
|
| 93 |
+
|
| 94 |
+
return to_cute_tensor(
|
| 95 |
+
t,
|
| 96 |
+
assumed_align=assumed_align,
|
| 97 |
+
leading_dim=leading_dim,
|
| 98 |
+
fully_dynamic=fully_dynamic,
|
| 99 |
+
enable_tvm_ffi=enable_tvm_ffi,
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def get_aux_tensor_metadata(aux_tensors):
|
| 104 |
+
return tuple(
|
| 105 |
+
(
|
| 106 |
+
getattr(t, "__assumed_align__", 0),
|
| 107 |
+
getattr(t, "__leading_dim__", -1),
|
| 108 |
+
hasattr(t, "__leading_dim__"),
|
| 109 |
+
)
|
| 110 |
+
for t in aux_tensors
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def get_broadcast_dims(tensor: torch.Tensor) -> Tuple[bool, ...]:
|
| 115 |
+
"""Return tuple of bools indicating which dims have stride=0 (broadcast).
|
| 116 |
+
|
| 117 |
+
This is useful for compile keys since CuTe's mark_layout_dynamic() keeps
|
| 118 |
+
stride=0 as static, meaning kernels compiled with different broadcast
|
| 119 |
+
patterns are not interchangeable.
|
| 120 |
+
"""
|
| 121 |
+
return tuple(s == 0 for s in tensor.stride())
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
# credit: monellz (https://github.com/NVIDIA/cutlass/issues/2658#issuecomment-3630564264)
|
| 125 |
+
def dump_kernel_attributes(compiled_kernel):
|
| 126 |
+
from cuda.bindings import driver
|
| 127 |
+
from cutlass.utils import HardwareInfo
|
| 128 |
+
import torch
|
| 129 |
+
|
| 130 |
+
device_id = torch.cuda.current_device()
|
| 131 |
+
hardware_info = HardwareInfo(device_id=device_id)
|
| 132 |
+
cubin_data = compiled_kernel.artifacts.CUBIN
|
| 133 |
+
assert cubin_data is not None, "cubin_data is None, need '--keep-cubin' option when compiling"
|
| 134 |
+
cuda_library = hardware_info._checkCudaErrors(
|
| 135 |
+
driver.cuLibraryLoadData(cubin_data, None, None, 0, None, None, 0)
|
| 136 |
+
)
|
| 137 |
+
kernels = hardware_info._checkCudaErrors(driver.cuLibraryEnumerateKernels(1, cuda_library))
|
| 138 |
+
kernel = hardware_info._checkCudaErrors(driver.cuKernelGetFunction(kernels[0]))
|
| 139 |
+
# more metrics: https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__EXEC.html#group__CUDA__EXEC_1g5e92a1b0d8d1b82cb00dcfb2de15961b
|
| 140 |
+
local_size_bytes = hardware_info._checkCudaErrors(
|
| 141 |
+
driver.cuFuncGetAttribute(
|
| 142 |
+
driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES,
|
| 143 |
+
kernel,
|
| 144 |
+
)
|
| 145 |
+
)
|
| 146 |
+
num_regs = hardware_info._checkCudaErrors(
|
| 147 |
+
driver.cuFuncGetAttribute(
|
| 148 |
+
driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_NUM_REGS,
|
| 149 |
+
kernel,
|
| 150 |
+
)
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
print("--- Kernel Info ---")
|
| 154 |
+
print(f"local_size_bytes: {local_size_bytes}")
|
| 155 |
+
print(f"num_regs: {num_regs}")
|
| 156 |
+
print("--- End Kernel Info ---")
|
build/torch-cuda/flashrt_fa4/cute/fa_logging.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
"""Unified FlashAttention logging controlled by a single ``FA_LOG_LEVEL`` env var.
|
| 4 |
+
|
| 5 |
+
Host-side messages go through Python ``logging`` (logger name ``flash_attn``).
|
| 6 |
+
A default ``StreamHandler`` is attached automatically when ``FA_LOG_LEVEL >= 1``
|
| 7 |
+
so that standalone scripts get output without extra setup; applications that
|
| 8 |
+
configure their own logging can remove or replace it via the standard API.
|
| 9 |
+
|
| 10 |
+
FA_LOG_LEVEL mapping::
|
| 11 |
+
|
| 12 |
+
0 off nothing logged
|
| 13 |
+
1 host host-side summaries only (no kernel printf)
|
| 14 |
+
2 kernel host + curated kernel traces
|
| 15 |
+
3 max host + all kernel traces (noisy, perf hit)
|
| 16 |
+
|
| 17 |
+
Set via environment variable::
|
| 18 |
+
|
| 19 |
+
FA_LOG_LEVEL=1 python train.py
|
| 20 |
+
|
| 21 |
+
Device-side ``cute.printf`` calls are compile-time eliminated via
|
| 22 |
+
``cutlass.const_expr`` when the log level is below the callsite threshold,
|
| 23 |
+
so there is zero performance cost when device logging is off.
|
| 24 |
+
Changing the log level after kernel compilation requires a recompile
|
| 25 |
+
(the level participates in the forward compile key).
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
import logging
|
| 29 |
+
import os
|
| 30 |
+
import sys
|
| 31 |
+
|
| 32 |
+
import cutlass.cute as cute
|
| 33 |
+
from cutlass import const_expr
|
| 34 |
+
|
| 35 |
+
_LOG_LEVEL_NAMES = {"off": 0, "host": 1, "kernel": 2, "max": 3}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _parse_log_level(raw: str) -> int:
|
| 39 |
+
if raw in _LOG_LEVEL_NAMES:
|
| 40 |
+
return _LOG_LEVEL_NAMES[raw]
|
| 41 |
+
try:
|
| 42 |
+
level = int(raw)
|
| 43 |
+
except ValueError:
|
| 44 |
+
return 0
|
| 45 |
+
return max(0, min(level, 3))
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
_fa_log_level: int = _parse_log_level(os.environ.get("FA_LOG_LEVEL", "0"))
|
| 49 |
+
|
| 50 |
+
_logger = logging.getLogger("flashrt_fa4")
|
| 51 |
+
_logger.addHandler(logging.NullHandler())
|
| 52 |
+
_default_handler: logging.Handler | None = None
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _configure_default_handler() -> None:
|
| 56 |
+
global _default_handler
|
| 57 |
+
if _fa_log_level >= 1:
|
| 58 |
+
if _default_handler is None:
|
| 59 |
+
_default_handler = logging.StreamHandler(sys.stdout)
|
| 60 |
+
_default_handler.setFormatter(logging.Formatter("[FA] %(message)s"))
|
| 61 |
+
_logger.addHandler(_default_handler)
|
| 62 |
+
_logger.setLevel(logging.DEBUG)
|
| 63 |
+
else:
|
| 64 |
+
if _default_handler is not None:
|
| 65 |
+
_logger.removeHandler(_default_handler)
|
| 66 |
+
_default_handler = None
|
| 67 |
+
_logger.setLevel(logging.WARNING)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
_configure_default_handler()
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def get_fa_log_level() -> int:
|
| 74 |
+
return _fa_log_level
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def set_fa_log_level(level: int | str) -> None:
|
| 78 |
+
"""Set the FA log level programmatically.
|
| 79 |
+
|
| 80 |
+
Host logging takes effect immediately. Device logging changes only
|
| 81 |
+
affect kernels compiled after this call (new compile-key selection).
|
| 82 |
+
"""
|
| 83 |
+
global _fa_log_level
|
| 84 |
+
if isinstance(level, str):
|
| 85 |
+
level = _parse_log_level(level)
|
| 86 |
+
_fa_log_level = max(0, min(int(level), 3))
|
| 87 |
+
_configure_default_handler()
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def fa_log(level: int, msg: str):
|
| 91 |
+
if _fa_log_level >= level:
|
| 92 |
+
_logger.info(msg)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def fa_printf(level: int, fmt, *args):
|
| 96 |
+
if const_expr(_fa_log_level >= level):
|
| 97 |
+
cute.printf(fmt, *args)
|
build/torch-cuda/flashrt_fa4/cute/fast_math.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import cutlass
|
| 4 |
+
import cutlass.cute as cute
|
| 5 |
+
from cutlass import Int32
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@cute.jit
|
| 9 |
+
def clz(x: Int32) -> Int32:
|
| 10 |
+
# for i in cutlass.range_constexpr(32):
|
| 11 |
+
# if (1 << (31 - i)) & x:
|
| 12 |
+
# return Int32(i)
|
| 13 |
+
# return Int32(32)
|
| 14 |
+
# Early exit is not supported yet
|
| 15 |
+
res = Int32(32)
|
| 16 |
+
done = False
|
| 17 |
+
for i in cutlass.range(32):
|
| 18 |
+
if ((1 << (31 - i)) & x) and not done:
|
| 19 |
+
res = Int32(i)
|
| 20 |
+
done = True
|
| 21 |
+
return res
|
build/torch-cuda/flashrt_fa4/cute/flash_fwd_combine.py
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_fwd_combine_kernel.h
|
| 3 |
+
# from Cutlass C++ to Cute-DSL.
|
| 4 |
+
import math
|
| 5 |
+
from typing import Type, Optional
|
| 6 |
+
from functools import partial
|
| 7 |
+
|
| 8 |
+
import cuda.bindings.driver as cuda
|
| 9 |
+
|
| 10 |
+
import cutlass
|
| 11 |
+
import cutlass.cute as cute
|
| 12 |
+
from cutlass.cute.nvgpu import cpasync
|
| 13 |
+
from cutlass import Float32, Int32, Boolean, const_expr
|
| 14 |
+
|
| 15 |
+
from fa4_cute_runtime.flashrt_fa4.cute import utils
|
| 16 |
+
from fa4_cute_runtime.flashrt_fa4.cute.cute_dsl_utils import assume_tensor_aligned
|
| 17 |
+
from fa4_cute_runtime.flashrt_fa4.cute.seqlen_info import SeqlenInfo
|
| 18 |
+
from cutlass.cute import FastDivmodDivisor
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class FlashAttentionForwardCombine:
|
| 22 |
+
def __init__(
|
| 23 |
+
self,
|
| 24 |
+
dtype: Type[cutlass.Numeric],
|
| 25 |
+
dtype_partial: Type[cutlass.Numeric],
|
| 26 |
+
head_dim: int,
|
| 27 |
+
tile_m: int = 8,
|
| 28 |
+
k_block_size: int = 64,
|
| 29 |
+
log_max_splits: int = 4,
|
| 30 |
+
num_threads: int = 256,
|
| 31 |
+
stages: int = 4,
|
| 32 |
+
):
|
| 33 |
+
"""
|
| 34 |
+
Forward combine kernel for split attention computation.
|
| 35 |
+
|
| 36 |
+
:param dtype: output data type
|
| 37 |
+
:param dtype_partial: partial accumulation data type
|
| 38 |
+
:param head_dim: head dimension
|
| 39 |
+
:param tile_m: m block size
|
| 40 |
+
:param k_block_size: k block size
|
| 41 |
+
:param log_max_splits: log2 of maximum splits
|
| 42 |
+
:param num_threads: number of threads
|
| 43 |
+
:param varlen: whether using variable length sequences
|
| 44 |
+
:param stages: number of pipeline stages
|
| 45 |
+
"""
|
| 46 |
+
self.dtype = dtype
|
| 47 |
+
self.dtype_partial = dtype_partial
|
| 48 |
+
self.head_dim = head_dim
|
| 49 |
+
self.tile_m = tile_m
|
| 50 |
+
self.k_block_size = k_block_size
|
| 51 |
+
self.max_splits = 1 << log_max_splits
|
| 52 |
+
self.num_threads = num_threads
|
| 53 |
+
self.is_even_k = head_dim % k_block_size == 0
|
| 54 |
+
self.stages = stages
|
| 55 |
+
|
| 56 |
+
@staticmethod
|
| 57 |
+
def can_implement(
|
| 58 |
+
dtype,
|
| 59 |
+
dtype_partial,
|
| 60 |
+
head_dim,
|
| 61 |
+
tile_m,
|
| 62 |
+
k_block_size,
|
| 63 |
+
log_max_splits,
|
| 64 |
+
num_threads,
|
| 65 |
+
) -> bool:
|
| 66 |
+
"""Check if the kernel can be implemented with the given parameters."""
|
| 67 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16, cutlass.Float32]:
|
| 68 |
+
return False
|
| 69 |
+
if dtype_partial not in [cutlass.Float16, cutlass.BFloat16, Float32]:
|
| 70 |
+
return False
|
| 71 |
+
if head_dim % 8 != 0:
|
| 72 |
+
return False
|
| 73 |
+
if num_threads % 32 != 0:
|
| 74 |
+
return False
|
| 75 |
+
if tile_m % 8 != 0:
|
| 76 |
+
return False
|
| 77 |
+
max_splits = 1 << log_max_splits
|
| 78 |
+
if max_splits > 256:
|
| 79 |
+
return False
|
| 80 |
+
if (tile_m * max_splits) % num_threads != 0:
|
| 81 |
+
return False
|
| 82 |
+
return True
|
| 83 |
+
|
| 84 |
+
def _setup_attributes(self):
|
| 85 |
+
# GMEM copy setup for O partial
|
| 86 |
+
universal_copy_bits = 128
|
| 87 |
+
async_copy_elems = universal_copy_bits // self.dtype_partial.width
|
| 88 |
+
assert self.k_block_size % async_copy_elems == 0
|
| 89 |
+
|
| 90 |
+
k_block_gmem = (
|
| 91 |
+
128 if self.k_block_size % 128 == 0 else (64 if self.k_block_size % 64 == 0 else 32)
|
| 92 |
+
)
|
| 93 |
+
gmem_threads_per_row = k_block_gmem // async_copy_elems
|
| 94 |
+
assert self.num_threads % gmem_threads_per_row == 0
|
| 95 |
+
|
| 96 |
+
# Async copy atom for O partial load
|
| 97 |
+
atom_async_copy_partial = cute.make_copy_atom(
|
| 98 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 99 |
+
self.dtype_partial,
|
| 100 |
+
num_bits_per_copy=universal_copy_bits,
|
| 101 |
+
)
|
| 102 |
+
tOpartial_layout = cute.make_ordered_layout(
|
| 103 |
+
(self.num_threads // gmem_threads_per_row, gmem_threads_per_row),
|
| 104 |
+
order=(1, 0),
|
| 105 |
+
)
|
| 106 |
+
vOpartial_layout = cute.make_layout((1, async_copy_elems)) # 4 vals per load
|
| 107 |
+
self.gmem_tiled_copy_O_partial = cute.make_tiled_copy_tv(
|
| 108 |
+
atom_async_copy_partial, tOpartial_layout, vOpartial_layout
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
# GMEM copy setup for final O (use universal copy for store)
|
| 112 |
+
atom_universal_copy = cute.make_copy_atom(
|
| 113 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 114 |
+
self.dtype,
|
| 115 |
+
num_bits_per_copy=async_copy_elems * self.dtype.width,
|
| 116 |
+
)
|
| 117 |
+
self.gmem_tiled_copy_O = cute.make_tiled_copy_tv(
|
| 118 |
+
atom_universal_copy,
|
| 119 |
+
tOpartial_layout,
|
| 120 |
+
vOpartial_layout, # 4 vals per store
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# LSE copy setup with async copy (alignment = 1)
|
| 124 |
+
lse_copy_bits = Float32.width # 1 element per copy, width is in bits
|
| 125 |
+
m_block_smem = (
|
| 126 |
+
128
|
| 127 |
+
if self.tile_m % 128 == 0
|
| 128 |
+
else (
|
| 129 |
+
64
|
| 130 |
+
if self.tile_m % 64 == 0
|
| 131 |
+
else (32 if self.tile_m % 32 == 0 else (16 if self.tile_m % 16 == 0 else 8))
|
| 132 |
+
)
|
| 133 |
+
)
|
| 134 |
+
gmem_threads_per_row_lse = m_block_smem
|
| 135 |
+
assert self.num_threads % gmem_threads_per_row_lse == 0
|
| 136 |
+
|
| 137 |
+
# Async copy atom for LSE load
|
| 138 |
+
atom_async_copy_lse = cute.make_copy_atom(
|
| 139 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.ALWAYS),
|
| 140 |
+
Float32,
|
| 141 |
+
num_bits_per_copy=lse_copy_bits,
|
| 142 |
+
)
|
| 143 |
+
tLSE_layout = cute.make_ordered_layout(
|
| 144 |
+
(self.num_threads // gmem_threads_per_row_lse, gmem_threads_per_row_lse),
|
| 145 |
+
order=(1, 0),
|
| 146 |
+
)
|
| 147 |
+
vLSE_layout = cute.make_layout(1)
|
| 148 |
+
self.gmem_tiled_copy_LSE = cute.make_tiled_copy_tv(
|
| 149 |
+
atom_async_copy_lse, tLSE_layout, vLSE_layout
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 153 |
+
# Shared memory
|
| 154 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 155 |
+
|
| 156 |
+
# Shared memory to register copy for LSE
|
| 157 |
+
self.smem_threads_per_col_lse = self.num_threads // m_block_smem
|
| 158 |
+
assert 32 % self.smem_threads_per_col_lse == 0 # Must divide warp size
|
| 159 |
+
|
| 160 |
+
s2r_layout_atom_lse = cute.make_ordered_layout(
|
| 161 |
+
(self.smem_threads_per_col_lse, self.num_threads // self.smem_threads_per_col_lse),
|
| 162 |
+
order=(0, 1),
|
| 163 |
+
)
|
| 164 |
+
self.s2r_tiled_copy_LSE = cute.make_tiled_copy_tv(
|
| 165 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32),
|
| 166 |
+
s2r_layout_atom_lse,
|
| 167 |
+
cute.make_layout(1),
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
# LSE shared memory layout with swizzling to avoid bank conflicts
|
| 171 |
+
# This works for kBlockMSmem = 8, 16, 32, 64, 128, no bank conflicts
|
| 172 |
+
if const_expr(m_block_smem == 8):
|
| 173 |
+
smem_lse_swizzle = cute.make_swizzle(5, 0, 5)
|
| 174 |
+
elif const_expr(m_block_smem == 16):
|
| 175 |
+
smem_lse_swizzle = cute.make_swizzle(4, 0, 4)
|
| 176 |
+
else:
|
| 177 |
+
smem_lse_swizzle = cute.make_swizzle(3, 2, 3)
|
| 178 |
+
smem_layout_atom_lse = cute.make_composed_layout(
|
| 179 |
+
smem_lse_swizzle, 0, cute.make_ordered_layout((8, m_block_smem), order=(1, 0))
|
| 180 |
+
)
|
| 181 |
+
self.smem_layout_lse = cute.tile_to_shape(
|
| 182 |
+
smem_layout_atom_lse, (self.max_splits, self.tile_m), (0, 1)
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
# O partial shared memory layout (simple layout for pipeline stages)
|
| 186 |
+
self.smem_layout_o = cute.make_ordered_layout(
|
| 187 |
+
(self.tile_m, self.k_block_size, self.stages), order=(1, 0, 2)
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
@cute.jit
|
| 191 |
+
def __call__(
|
| 192 |
+
self,
|
| 193 |
+
mO_partial: cute.Tensor,
|
| 194 |
+
mLSE_partial: cute.Tensor,
|
| 195 |
+
mO: cute.Tensor,
|
| 196 |
+
mLSE: Optional[cute.Tensor] = None,
|
| 197 |
+
cu_seqlens: Optional[cute.Tensor] = None,
|
| 198 |
+
seqused: Optional[cute.Tensor] = None,
|
| 199 |
+
num_splits_dynamic_ptr: Optional[cute.Tensor] = None,
|
| 200 |
+
varlen_batch_idx: Optional[cute.Tensor] = None,
|
| 201 |
+
semaphore_to_reset: Optional[cute.Tensor] = None,
|
| 202 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 203 |
+
stream: cuda.CUstream = None,
|
| 204 |
+
):
|
| 205 |
+
# Type checking
|
| 206 |
+
if const_expr(not (mO_partial.element_type == self.dtype_partial)):
|
| 207 |
+
raise TypeError("O partial tensor must match dtype_partial")
|
| 208 |
+
if const_expr(not (mO.element_type == self.dtype)):
|
| 209 |
+
raise TypeError("O tensor must match dtype")
|
| 210 |
+
if const_expr(mLSE_partial.element_type not in [Float32]):
|
| 211 |
+
raise TypeError("LSE partial tensor must be Float32")
|
| 212 |
+
if const_expr(mLSE is not None and mLSE.element_type not in [Float32]):
|
| 213 |
+
raise TypeError("LSE tensor must be Float32")
|
| 214 |
+
|
| 215 |
+
# Shape validation - input tensors are in user format, need to be converted to kernel format
|
| 216 |
+
if const_expr(len(mO_partial.shape) not in [4, 5]):
|
| 217 |
+
raise ValueError(
|
| 218 |
+
"O partial tensor must have 4 or 5 dimensions: (num_splits, batch, seqlen, nheads, headdim) or (num_splits, total_q, nheads, headdim)"
|
| 219 |
+
)
|
| 220 |
+
if const_expr(len(mLSE_partial.shape) not in [3, 4]):
|
| 221 |
+
raise ValueError(
|
| 222 |
+
"LSE partial tensor must have 3 or 4 dimensions: (num_splits, batch, seqlen, nheads) or (num_splits, total_q, nheads)"
|
| 223 |
+
)
|
| 224 |
+
if const_expr(len(mO.shape) not in [3, 4]):
|
| 225 |
+
raise ValueError(
|
| 226 |
+
"O tensor must have 3 or 4 dimensions: (batch, seqlen, nheads, headdim) or (total_q, nheads, headdim)"
|
| 227 |
+
)
|
| 228 |
+
if const_expr(mLSE is not None and len(mLSE.shape) not in [2, 3]):
|
| 229 |
+
raise ValueError(
|
| 230 |
+
"LSE tensor must have 2 or 3 dimensions: (batch, seqlen, nheads) or (total_q, nheads)"
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
mO_partial, mO = [assume_tensor_aligned(t) for t in (mO_partial, mO)]
|
| 234 |
+
# (num_splits, b, seqlen, h, d) -> (seqlen, d, num_splits, h, b)
|
| 235 |
+
# or (num_splits, total_q, h, d) -> (total_q, d, num_splits, h)
|
| 236 |
+
O_partial_layout_transpose = (
|
| 237 |
+
[2, 4, 0, 3, 1] if const_expr(cu_seqlens is None) else [1, 3, 0, 2]
|
| 238 |
+
)
|
| 239 |
+
# (b, seqlen, h, d) -> (seqlen, d, h, b) or (total_q, h, d) -> (total_q, d, h)
|
| 240 |
+
mO_partial = cute.make_tensor(
|
| 241 |
+
mO_partial.iterator, cute.select(mO_partial.layout, mode=O_partial_layout_transpose)
|
| 242 |
+
)
|
| 243 |
+
O_layout_transpose = [1, 3, 2, 0] if const_expr(cu_seqlens is None) else [0, 2, 1]
|
| 244 |
+
mO = cute.make_tensor(mO.iterator, cute.select(mO.layout, mode=O_layout_transpose))
|
| 245 |
+
# (num_splits, b, seqlen, h) -> (seqlen, num_splits, h, b)
|
| 246 |
+
# or (num_splits, total_q, h) -> (total_q, num_splits, h)
|
| 247 |
+
LSE_partial_layout_transpose = [2, 0, 3, 1] if const_expr(cu_seqlens is None) else [1, 0, 2]
|
| 248 |
+
mLSE_partial = cute.make_tensor(
|
| 249 |
+
mLSE_partial.iterator,
|
| 250 |
+
cute.select(mLSE_partial.layout, mode=LSE_partial_layout_transpose),
|
| 251 |
+
)
|
| 252 |
+
# (b, seqlen, h) -> (seqlen, h, b) or (total_q, h) -> (total_q, h)
|
| 253 |
+
LSE_layout_transpose = [1, 2, 0] if const_expr(cu_seqlens is None) else [0, 1]
|
| 254 |
+
mLSE = (
|
| 255 |
+
cute.make_tensor(mLSE.iterator, cute.select(mLSE.layout, mode=LSE_layout_transpose))
|
| 256 |
+
if mLSE is not None
|
| 257 |
+
else None
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
# Determine if we have variable length sequences
|
| 261 |
+
varlen = const_expr(cu_seqlens is not None or seqused is not None)
|
| 262 |
+
|
| 263 |
+
self._setup_attributes()
|
| 264 |
+
|
| 265 |
+
@cute.struct
|
| 266 |
+
class SharedStorage:
|
| 267 |
+
sLSE: cute.struct.Align[
|
| 268 |
+
cute.struct.MemRange[Float32, cute.cosize(self.smem_layout_lse)], 128
|
| 269 |
+
]
|
| 270 |
+
sMaxValidSplit: cute.struct.Align[cute.struct.MemRange[Int32, self.tile_m], 128]
|
| 271 |
+
sO: cute.struct.Align[
|
| 272 |
+
cute.struct.MemRange[self.dtype_partial, cute.cosize(self.smem_layout_o)], 128
|
| 273 |
+
]
|
| 274 |
+
|
| 275 |
+
smem_size = SharedStorage.size_in_bytes()
|
| 276 |
+
|
| 277 |
+
# Grid dimensions: (ceil_div(seqlen, m_block), ceil_div(head_dim, k_block), num_head * batch)
|
| 278 |
+
seqlen = mO_partial.shape[0]
|
| 279 |
+
num_head = mO_partial.shape[3]
|
| 280 |
+
batch_size = (
|
| 281 |
+
mO_partial.shape[4]
|
| 282 |
+
if const_expr(cu_seqlens is None)
|
| 283 |
+
else Int32(cu_seqlens.shape[0] - 1)
|
| 284 |
+
)
|
| 285 |
+
|
| 286 |
+
# Create FastDivmodDivisor objects for efficient division
|
| 287 |
+
seqlen_divmod = FastDivmodDivisor(seqlen)
|
| 288 |
+
head_divmod = FastDivmodDivisor(num_head)
|
| 289 |
+
|
| 290 |
+
grid_dim = (
|
| 291 |
+
cute.ceil_div(seqlen * num_head, self.tile_m),
|
| 292 |
+
cute.ceil_div(self.head_dim, self.k_block_size),
|
| 293 |
+
batch_size,
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
self.kernel(
|
| 297 |
+
mO_partial,
|
| 298 |
+
mLSE_partial,
|
| 299 |
+
mO,
|
| 300 |
+
mLSE,
|
| 301 |
+
cu_seqlens,
|
| 302 |
+
seqused,
|
| 303 |
+
num_splits_dynamic_ptr,
|
| 304 |
+
varlen_batch_idx,
|
| 305 |
+
semaphore_to_reset,
|
| 306 |
+
SharedStorage,
|
| 307 |
+
self.smem_layout_lse,
|
| 308 |
+
self.smem_layout_o,
|
| 309 |
+
self.gmem_tiled_copy_O_partial,
|
| 310 |
+
self.gmem_tiled_copy_O,
|
| 311 |
+
self.gmem_tiled_copy_LSE,
|
| 312 |
+
self.s2r_tiled_copy_LSE,
|
| 313 |
+
seqlen_divmod,
|
| 314 |
+
head_divmod,
|
| 315 |
+
varlen,
|
| 316 |
+
).launch(
|
| 317 |
+
grid=grid_dim,
|
| 318 |
+
block=[self.num_threads, 1, 1],
|
| 319 |
+
smem=smem_size,
|
| 320 |
+
stream=stream,
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
@cute.kernel
|
| 324 |
+
def kernel(
|
| 325 |
+
self,
|
| 326 |
+
mO_partial: cute.Tensor,
|
| 327 |
+
mLSE_partial: cute.Tensor,
|
| 328 |
+
mO: cute.Tensor,
|
| 329 |
+
mLSE: Optional[cute.Tensor],
|
| 330 |
+
cu_seqlens: Optional[cute.Tensor],
|
| 331 |
+
seqused: Optional[cute.Tensor],
|
| 332 |
+
num_splits_dynamic_ptr: Optional[cute.Tensor],
|
| 333 |
+
varlen_batch_idx: Optional[cute.Tensor],
|
| 334 |
+
semaphore_to_reset: Optional[cute.Tensor],
|
| 335 |
+
SharedStorage: cutlass.Constexpr,
|
| 336 |
+
smem_layout_lse: cute.Layout | cute.ComposedLayout,
|
| 337 |
+
smem_layout_o: cute.Layout,
|
| 338 |
+
gmem_tiled_copy_O_partial: cute.TiledCopy,
|
| 339 |
+
gmem_tiled_copy_O: cute.TiledCopy,
|
| 340 |
+
gmem_tiled_copy_LSE: cute.TiledCopy,
|
| 341 |
+
s2r_tiled_copy_LSE: cute.TiledCopy,
|
| 342 |
+
seqlen_divmod: FastDivmodDivisor,
|
| 343 |
+
head_divmod: FastDivmodDivisor,
|
| 344 |
+
varlen: cutlass.Constexpr[bool],
|
| 345 |
+
):
|
| 346 |
+
# Thread and block indices
|
| 347 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 348 |
+
m_block, k_block, maybe_virtual_batch = cute.arch.block_idx()
|
| 349 |
+
|
| 350 |
+
# Map virtual batch index to real batch index (for persistent tile schedulers)
|
| 351 |
+
batch_idx = (
|
| 352 |
+
varlen_batch_idx[maybe_virtual_batch]
|
| 353 |
+
if const_expr(varlen_batch_idx is not None)
|
| 354 |
+
else maybe_virtual_batch
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 358 |
+
# Get shared memory buffer
|
| 359 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 360 |
+
smem = cutlass.utils.SmemAllocator()
|
| 361 |
+
storage = smem.allocate(SharedStorage)
|
| 362 |
+
sLSE = storage.sLSE.get_tensor(smem_layout_lse)
|
| 363 |
+
sMaxValidSplit = storage.sMaxValidSplit.get_tensor((self.tile_m,))
|
| 364 |
+
sO = storage.sO.get_tensor(smem_layout_o)
|
| 365 |
+
|
| 366 |
+
# Handle semaphore reset — wait for dependent grids first
|
| 367 |
+
if const_expr(semaphore_to_reset is not None):
|
| 368 |
+
if (
|
| 369 |
+
tidx == 0
|
| 370 |
+
and m_block == cute.arch.grid_dim()[0] - 1
|
| 371 |
+
and k_block == cute.arch.grid_dim()[1] - 1
|
| 372 |
+
and maybe_virtual_batch == cute.arch.grid_dim()[2] - 1
|
| 373 |
+
):
|
| 374 |
+
cute.arch.griddepcontrol_wait()
|
| 375 |
+
semaphore_to_reset[0] = 0
|
| 376 |
+
|
| 377 |
+
# Get number of splits (use maybe_virtual_batch for per-batch-slot splits)
|
| 378 |
+
num_splits = (
|
| 379 |
+
num_splits_dynamic_ptr[maybe_virtual_batch]
|
| 380 |
+
if const_expr(num_splits_dynamic_ptr is not None)
|
| 381 |
+
else mLSE_partial.shape[1]
|
| 382 |
+
)
|
| 383 |
+
# Handle variable length sequences using SeqlenInfo
|
| 384 |
+
seqlen_info = SeqlenInfo.create(
|
| 385 |
+
batch_idx=batch_idx,
|
| 386 |
+
seqlen_static=mO_partial.shape[0],
|
| 387 |
+
cu_seqlens=cu_seqlens,
|
| 388 |
+
seqused=seqused,
|
| 389 |
+
# Don't need to pass in tile size since we won't use offset_padded
|
| 390 |
+
)
|
| 391 |
+
seqlen, offset = seqlen_info.seqlen, seqlen_info.offset
|
| 392 |
+
|
| 393 |
+
# Extract number of heads (head index will be determined dynamically)
|
| 394 |
+
num_head = mO_partial.shape[3]
|
| 395 |
+
max_idx = seqlen * num_head
|
| 396 |
+
|
| 397 |
+
# Early exit for single split if dynamic
|
| 398 |
+
if (const_expr(num_splits_dynamic_ptr is None) or num_splits > 1) and (
|
| 399 |
+
const_expr(not varlen) or m_block * self.tile_m < max_idx
|
| 400 |
+
):
|
| 401 |
+
# Wait for dependent grids (e.g., the main attention kernel that produces O_partial/LSE_partial)
|
| 402 |
+
cute.arch.griddepcontrol_wait()
|
| 403 |
+
|
| 404 |
+
# ===============================
|
| 405 |
+
# Step 1: Load LSE_partial from gmem to shared memory
|
| 406 |
+
# ===============================
|
| 407 |
+
|
| 408 |
+
mLSE_partial_cur = seqlen_info.offset_batch(mLSE_partial, batch_idx, dim=3)
|
| 409 |
+
mLSE_partial_copy = cute.tiled_divide(mLSE_partial_cur, (1,))
|
| 410 |
+
gmem_thr_copy_LSE = gmem_tiled_copy_LSE.get_slice(tidx)
|
| 411 |
+
tLSEsLSE = gmem_thr_copy_LSE.partition_D(sLSE)
|
| 412 |
+
# Create identity tensor for coordinate tracking
|
| 413 |
+
cLSE = cute.make_identity_tensor((self.max_splits, self.tile_m))
|
| 414 |
+
tLSEcLSE = gmem_thr_copy_LSE.partition_S(cLSE)
|
| 415 |
+
|
| 416 |
+
# Load LSE partial values
|
| 417 |
+
for m in cutlass.range(cute.size(tLSEcLSE, mode=[2]), unroll_full=True):
|
| 418 |
+
mi = tLSEcLSE[0, 0, m][1] # Get m coordinate
|
| 419 |
+
idx = m_block * self.tile_m + mi
|
| 420 |
+
if idx < max_idx:
|
| 421 |
+
# Calculate actual sequence position and head using FastDivmodDivisor
|
| 422 |
+
if const_expr(not varlen):
|
| 423 |
+
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 424 |
+
else:
|
| 425 |
+
head_idx = idx // seqlen
|
| 426 |
+
m_idx = idx - head_idx * seqlen
|
| 427 |
+
mLSE_partial_cur_copy = mLSE_partial_copy[None, m_idx, None, head_idx]
|
| 428 |
+
for s in cutlass.range(cute.size(tLSEcLSE, mode=[1]), unroll_full=True):
|
| 429 |
+
si = tLSEcLSE[0, s, 0][0] # Get split coordinate
|
| 430 |
+
if si < num_splits:
|
| 431 |
+
cute.copy(
|
| 432 |
+
gmem_thr_copy_LSE,
|
| 433 |
+
mLSE_partial_cur_copy[None, si],
|
| 434 |
+
tLSEsLSE[None, s, m],
|
| 435 |
+
)
|
| 436 |
+
else:
|
| 437 |
+
tLSEsLSE[None, s, m].fill(-Float32.inf)
|
| 438 |
+
# Don't need to zero out the rest of the LSEs, as we will not write the output to gmem
|
| 439 |
+
cute.arch.cp_async_commit_group()
|
| 440 |
+
|
| 441 |
+
# ===============================
|
| 442 |
+
# Step 2: Load O_partial for pipeline stages
|
| 443 |
+
# ===============================
|
| 444 |
+
|
| 445 |
+
gmem_thr_copy_O_partial = gmem_tiled_copy_O_partial.get_slice(tidx)
|
| 446 |
+
cO = cute.make_identity_tensor((self.tile_m, self.k_block_size))
|
| 447 |
+
tOcO = gmem_thr_copy_O_partial.partition_D(cO)
|
| 448 |
+
tOsO_partial = gmem_thr_copy_O_partial.partition_D(sO)
|
| 449 |
+
mO_partial_cur = seqlen_info.offset_batch(mO_partial, batch_idx, dim=4)
|
| 450 |
+
|
| 451 |
+
# Precompute these values to avoid recomputing them in the loop
|
| 452 |
+
num_rows = const_expr(cute.size(tOcO, mode=[1]))
|
| 453 |
+
tOmidx = cute.make_rmem_tensor(num_rows, cutlass.Int32)
|
| 454 |
+
tOhidx = cute.make_rmem_tensor(num_rows, cutlass.Int32)
|
| 455 |
+
tOrOptr = cute.make_rmem_tensor(num_rows, cutlass.Int64)
|
| 456 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 457 |
+
mi = tOcO[0, m, 0][0] # m coordinate
|
| 458 |
+
idx = m_block * self.tile_m + mi
|
| 459 |
+
if const_expr(not varlen):
|
| 460 |
+
tOhidx[m], tOmidx[m] = divmod(idx, seqlen_divmod)
|
| 461 |
+
else:
|
| 462 |
+
tOhidx[m] = idx // seqlen
|
| 463 |
+
tOmidx[m] = idx - tOhidx[m] * seqlen
|
| 464 |
+
tOrOptr[m] = utils.elem_pointer(
|
| 465 |
+
mO_partial_cur, (tOmidx[m], k_block * self.k_block_size, 0, tOhidx[m])
|
| 466 |
+
).toint()
|
| 467 |
+
if idx >= max_idx:
|
| 468 |
+
tOhidx[m] = -1
|
| 469 |
+
|
| 470 |
+
tOpO = None
|
| 471 |
+
if const_expr(not self.is_even_k):
|
| 472 |
+
tOpO = cute.make_rmem_tensor(cute.size(tOcO, mode=[2]), Boolean)
|
| 473 |
+
for k in cutlass.range(cute.size(tOpO), unroll_full=True):
|
| 474 |
+
tOpO[k] = tOcO[0, 0, k][1] < mO_partial.shape[1] - k_block * self.k_block_size
|
| 475 |
+
# if cute.arch.thread_idx()[0] == 0 and k_block == 1: cute.print_tensor(tOpO)
|
| 476 |
+
|
| 477 |
+
load_O_partial = partial(
|
| 478 |
+
self.load_O_partial,
|
| 479 |
+
gmem_tiled_copy_O_partial,
|
| 480 |
+
tOrOptr,
|
| 481 |
+
tOsO_partial,
|
| 482 |
+
tOhidx,
|
| 483 |
+
tOpO,
|
| 484 |
+
tOcO,
|
| 485 |
+
mO_partial_cur.layout,
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
# Load first few stages of O_partial
|
| 489 |
+
for stage in cutlass.range(self.stages - 1, unroll_full=True):
|
| 490 |
+
if stage < num_splits:
|
| 491 |
+
load_O_partial(stage, stage)
|
| 492 |
+
cute.arch.cp_async_commit_group()
|
| 493 |
+
|
| 494 |
+
# ===============================
|
| 495 |
+
# Step 3: Load and transpose LSE from smem to registers
|
| 496 |
+
# ===============================
|
| 497 |
+
|
| 498 |
+
# Wait for LSE and initial O partial stages to complete
|
| 499 |
+
cute.arch.cp_async_wait_group(self.stages - 1)
|
| 500 |
+
cute.arch.sync_threads()
|
| 501 |
+
# if cute.arch.thread_idx()[0] == 0:
|
| 502 |
+
# # cute.print_tensor(sLSE)
|
| 503 |
+
# for i in range(64):
|
| 504 |
+
# cute.printf("sLSE[%d, 0] = %f", i, sLSE[i, 0])
|
| 505 |
+
# cute.arch.sync_threads()
|
| 506 |
+
|
| 507 |
+
s2r_thr_copy_LSE = s2r_tiled_copy_LSE.get_slice(tidx)
|
| 508 |
+
ts2rsLSE = s2r_thr_copy_LSE.partition_S(sLSE)
|
| 509 |
+
ts2rrLSE = cute.make_rmem_tensor_like(ts2rsLSE)
|
| 510 |
+
cute.copy(s2r_tiled_copy_LSE, ts2rsLSE, ts2rrLSE)
|
| 511 |
+
|
| 512 |
+
# ===============================
|
| 513 |
+
# Step 4: Compute final LSE along split dimension
|
| 514 |
+
# ===============================
|
| 515 |
+
|
| 516 |
+
lse_sum = cute.make_rmem_tensor(cute.size(ts2rrLSE, mode=[2]), Float32)
|
| 517 |
+
ts2rcLSE = s2r_thr_copy_LSE.partition_D(cLSE)
|
| 518 |
+
# We compute the max valid split for each row to short-circuit the computation later
|
| 519 |
+
max_valid_split = cute.make_rmem_tensor(cute.size(ts2rrLSE, mode=[2]), Int32)
|
| 520 |
+
assert cute.size(ts2rrLSE, mode=[0]) == 1
|
| 521 |
+
# Compute max, scales, and final LSE for each row
|
| 522 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 523 |
+
# Find max LSE value across splits
|
| 524 |
+
threads_per_col = const_expr(self.smem_threads_per_col_lse)
|
| 525 |
+
lse_max = cute.arch.warp_reduction_max(
|
| 526 |
+
ts2rrLSE[None, None, m]
|
| 527 |
+
.load()
|
| 528 |
+
.reduce(cute.ReductionOp.MAX, init_val=-Float32.inf, reduction_profile=0),
|
| 529 |
+
threads_in_group=threads_per_col,
|
| 530 |
+
)
|
| 531 |
+
# if cute.arch.thread_idx()[0] == 0: cute.printf(lse_max)
|
| 532 |
+
# Find max valid split index
|
| 533 |
+
max_valid_idx = -1
|
| 534 |
+
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 535 |
+
if ts2rrLSE[0, s, m] != -Float32.inf:
|
| 536 |
+
max_valid_idx = ts2rcLSE[0, s, 0][0] # Get split coordinate
|
| 537 |
+
# if cute.arch.thread_idx()[0] < 32: cute.printf(max_valid_idx)
|
| 538 |
+
max_valid_split[m] = cute.arch.warp_reduction_max(
|
| 539 |
+
max_valid_idx, threads_in_group=threads_per_col
|
| 540 |
+
)
|
| 541 |
+
# Compute exp scales and sum
|
| 542 |
+
lse_max_cur = (
|
| 543 |
+
0.0 if lse_max == -Float32.inf else lse_max
|
| 544 |
+
) # In case all local LSEs are -inf
|
| 545 |
+
LOG2_E = math.log2(math.e)
|
| 546 |
+
lse_sum_cur = 0.0
|
| 547 |
+
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 548 |
+
scale = cute.math.exp2(
|
| 549 |
+
ts2rrLSE[0, s, m] * LOG2_E - (lse_max_cur * LOG2_E), fastmath=True
|
| 550 |
+
)
|
| 551 |
+
lse_sum_cur += scale
|
| 552 |
+
ts2rrLSE[0, s, m] = scale # Store scale for later use
|
| 553 |
+
lse_sum_cur = cute.arch.warp_reduction_sum(
|
| 554 |
+
lse_sum_cur, threads_in_group=threads_per_col
|
| 555 |
+
)
|
| 556 |
+
lse_sum[m] = cute.math.log(lse_sum_cur, fastmath=True) + lse_max
|
| 557 |
+
# Normalize scales
|
| 558 |
+
inv_sum = (
|
| 559 |
+
0.0 if (lse_sum_cur == 0.0 or lse_sum_cur != lse_sum_cur) else 1.0 / lse_sum_cur
|
| 560 |
+
)
|
| 561 |
+
ts2rrLSE[None, None, m].store(ts2rrLSE[None, None, m].load() * inv_sum)
|
| 562 |
+
# Store the scales exp(lse - lse_logsum) back to smem
|
| 563 |
+
cute.copy(s2r_tiled_copy_LSE, ts2rrLSE, ts2rsLSE)
|
| 564 |
+
|
| 565 |
+
# Store max valid split to smem
|
| 566 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 567 |
+
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 568 |
+
mi = ts2rcLSE[0, 0, m][1]
|
| 569 |
+
if mi < self.tile_m:
|
| 570 |
+
sMaxValidSplit[mi] = max_valid_split[m]
|
| 571 |
+
|
| 572 |
+
# ===============================
|
| 573 |
+
# Step 5: Store final LSE to gmem
|
| 574 |
+
# ===============================
|
| 575 |
+
|
| 576 |
+
if const_expr(mLSE is not None):
|
| 577 |
+
if const_expr(cu_seqlens is None):
|
| 578 |
+
mLSE_cur = mLSE[None, None, batch_idx]
|
| 579 |
+
else:
|
| 580 |
+
mLSE_cur = cute.domain_offset((offset, 0), mLSE)
|
| 581 |
+
if k_block == 0: # Only first k_block writes LSE when mLSE is provided
|
| 582 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 583 |
+
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 584 |
+
mi = ts2rcLSE[0, 0, m][1]
|
| 585 |
+
idx = m_block * self.tile_m + mi
|
| 586 |
+
if idx < max_idx:
|
| 587 |
+
if const_expr(not varlen):
|
| 588 |
+
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 589 |
+
else:
|
| 590 |
+
head_idx = idx // seqlen
|
| 591 |
+
m_idx = idx - head_idx * seqlen
|
| 592 |
+
mLSE_cur[m_idx, head_idx] = lse_sum[m]
|
| 593 |
+
|
| 594 |
+
# ===============================
|
| 595 |
+
# Step 6: Read O_partial and accumulate final O
|
| 596 |
+
# ===============================
|
| 597 |
+
|
| 598 |
+
cute.arch.sync_threads()
|
| 599 |
+
|
| 600 |
+
# Get max valid split for this thread
|
| 601 |
+
thr_max_valid_split = sMaxValidSplit[tOcO[0, 0, 0][0]]
|
| 602 |
+
for m in cutlass.range(1, cute.size(tOcO, mode=[1]), unroll_full=True):
|
| 603 |
+
thr_max_valid_split = max(thr_max_valid_split, sMaxValidSplit[tOcO[0, m, 0][0]])
|
| 604 |
+
|
| 605 |
+
tOrO_partial = cute.make_rmem_tensor_like(tOsO_partial[None, None, None, 0])
|
| 606 |
+
tOrO = cute.make_rmem_tensor_like(tOrO_partial, Float32)
|
| 607 |
+
tOrO.fill(0.0)
|
| 608 |
+
|
| 609 |
+
stage_load = self.stages - 1
|
| 610 |
+
stage_compute = 0
|
| 611 |
+
|
| 612 |
+
# Main accumulation loop
|
| 613 |
+
for s in cutlass.range(thr_max_valid_split + 1, unroll=4):
|
| 614 |
+
# Get scales for this split
|
| 615 |
+
scale = cute.make_rmem_tensor(num_rows, Float32)
|
| 616 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 617 |
+
scale[m] = sLSE[s, tOcO[0, m, 0][0]] # Get scale from smem
|
| 618 |
+
|
| 619 |
+
# Load next stage if needed
|
| 620 |
+
split_to_load = s + self.stages - 1
|
| 621 |
+
if split_to_load <= thr_max_valid_split:
|
| 622 |
+
load_O_partial(split_to_load, stage_load)
|
| 623 |
+
cute.arch.cp_async_commit_group()
|
| 624 |
+
stage_load = 0 if stage_load == self.stages - 1 else stage_load + 1
|
| 625 |
+
|
| 626 |
+
# Wait for the current stage to be ready
|
| 627 |
+
cute.arch.cp_async_wait_group(self.stages - 1)
|
| 628 |
+
# We don't need __syncthreads() because each thread is just reading its own data from smem
|
| 629 |
+
# Copy from smem to registers
|
| 630 |
+
cute.autovec_copy(tOsO_partial[None, None, None, stage_compute], tOrO_partial)
|
| 631 |
+
stage_compute = 0 if stage_compute == self.stages - 1 else stage_compute + 1
|
| 632 |
+
|
| 633 |
+
# Accumulate scaled partial results
|
| 634 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 635 |
+
if tOhidx[m] >= 0 and scale[m] > 0.0:
|
| 636 |
+
tOrO[None, m, None].store(
|
| 637 |
+
tOrO[None, m, None].load()
|
| 638 |
+
+ scale[m] * tOrO_partial[None, m, None].load().to(Float32)
|
| 639 |
+
)
|
| 640 |
+
|
| 641 |
+
# ===============================
|
| 642 |
+
# Step 7: Write final O to gmem
|
| 643 |
+
# ===============================
|
| 644 |
+
|
| 645 |
+
rO = cute.make_rmem_tensor_like(tOrO, self.dtype)
|
| 646 |
+
rO.store(tOrO.load().to(self.dtype))
|
| 647 |
+
mO_cur = seqlen_info.offset_batch(mO, batch_idx, dim=3)
|
| 648 |
+
if const_expr(cu_seqlens is None):
|
| 649 |
+
mO_cur = mO[None, None, None, batch_idx]
|
| 650 |
+
else:
|
| 651 |
+
mO_cur = cute.domain_offset((offset, 0, 0), mO)
|
| 652 |
+
mO_cur = utils.domain_offset_aligned((0, k_block * self.k_block_size, 0), mO_cur)
|
| 653 |
+
elems_per_store = const_expr(cute.size(gmem_tiled_copy_O.layout_tv_tiled[1]))
|
| 654 |
+
# mO_cur_copy = cute.tiled_divide(mO_cur, (1, elems_per_store,))
|
| 655 |
+
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 656 |
+
# Write final results
|
| 657 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 658 |
+
if tOhidx[m] >= 0:
|
| 659 |
+
mO_cur_copy = cute.tiled_divide(
|
| 660 |
+
mO_cur[tOmidx[m], None, tOhidx[m]], (elems_per_store,)
|
| 661 |
+
)
|
| 662 |
+
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 663 |
+
k_idx = tOcO[0, 0, k][1] // elems_per_store
|
| 664 |
+
if const_expr(self.is_even_k) or tOpO[k]:
|
| 665 |
+
cute.copy(gmem_thr_copy_O, rO[None, m, k], mO_cur_copy[None, k_idx])
|
| 666 |
+
|
| 667 |
+
@cute.jit
|
| 668 |
+
def load_O_partial(
|
| 669 |
+
self,
|
| 670 |
+
gmem_tiled_copy_O_partial: cute.TiledCopy,
|
| 671 |
+
tOrOptr: cute.Tensor,
|
| 672 |
+
tOsO_partial: cute.Tensor,
|
| 673 |
+
tOhidx: cute.Tensor,
|
| 674 |
+
tOpO: Optional[cute.Tensor],
|
| 675 |
+
tOcO: cute.Tensor,
|
| 676 |
+
mO_cur_partial_layout: cute.Layout,
|
| 677 |
+
split: Int32,
|
| 678 |
+
stage: Int32,
|
| 679 |
+
) -> None:
|
| 680 |
+
elems_per_load = const_expr(cute.size(gmem_tiled_copy_O_partial.layout_tv_tiled[1]))
|
| 681 |
+
tOsO_partial_cur = tOsO_partial[None, None, None, stage]
|
| 682 |
+
for m in cutlass.range(cute.size(tOcO, [1]), unroll_full=True):
|
| 683 |
+
if tOhidx[m] >= 0:
|
| 684 |
+
o_gmem_ptr = cute.make_ptr(
|
| 685 |
+
tOsO_partial.element_type, tOrOptr[m], cute.AddressSpace.gmem, assumed_align=16
|
| 686 |
+
)
|
| 687 |
+
mO_partial_cur = cute.make_tensor(
|
| 688 |
+
o_gmem_ptr, cute.slice_(mO_cur_partial_layout, (0, None, None, 0))
|
| 689 |
+
)
|
| 690 |
+
mO_partial_cur_copy = cute.tiled_divide(mO_partial_cur, (elems_per_load,))
|
| 691 |
+
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 692 |
+
k_idx = tOcO[0, 0, k][1] // elems_per_load
|
| 693 |
+
if const_expr(tOpO is None) or tOpO[k]:
|
| 694 |
+
cute.copy(
|
| 695 |
+
gmem_tiled_copy_O_partial,
|
| 696 |
+
mO_partial_cur_copy[None, k_idx, split],
|
| 697 |
+
tOsO_partial_cur[None, m, k],
|
| 698 |
+
)
|
build/torch-cuda/flashrt_fa4/cute/flash_fwd_sm100.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/torch-cuda/flashrt_fa4/cute/interface_fwd_sm100.py
ADDED
|
@@ -0,0 +1,1595 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# [2025-07-04] Version in Cute-DSL, for Hopper and Blackwell. You'll need install nvidia-cutlass-dsl==4.2.0.
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import math
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
from typing import Optional, Tuple, Callable
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
import cuda.bindings.driver as cuda
|
| 14 |
+
|
| 15 |
+
import cutlass
|
| 16 |
+
import cutlass.cute as cute
|
| 17 |
+
from cutlass import Int32, Float32
|
| 18 |
+
from fa4_cute_runtime.quack.compile_utils import make_fake_tensor as fake_tensor
|
| 19 |
+
from fa4_cute_runtime.flashrt_fa4.cute.cache_utils import get_jit_cache
|
| 20 |
+
from fa4_cute_runtime.flashrt_fa4.cute.testing import is_fake_mode
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
if os.environ.get("CUTE_DSL_PTXAS_PATH", None) is not None:
|
| 24 |
+
from fa4_cute_runtime.flashrt_fa4.cute import cute_dsl_ptxas # noqa: F401
|
| 25 |
+
|
| 26 |
+
# Patch to dump ptx and then use system ptxas to compile to cubin
|
| 27 |
+
cute_dsl_ptxas.patch()
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
from fa4_cute_runtime.flashrt_fa4.cute import utils
|
| 31 |
+
from fa4_cute_runtime.flashrt_fa4.cute import fa_logging
|
| 32 |
+
from fa4_cute_runtime.flashrt_fa4.cute.cute_dsl_utils import (
|
| 33 |
+
to_cute_tensor, to_cute_aux_tensor, get_aux_tensor_metadata, get_broadcast_dims,
|
| 34 |
+
)
|
| 35 |
+
from fa4_cute_runtime.flashrt_fa4.cute.flash_fwd_sm100 import FlashAttentionForwardSm100, DescaleTensors
|
| 36 |
+
from fa4_cute_runtime.flashrt_fa4.cute.flash_fwd_combine import FlashAttentionForwardCombine
|
| 37 |
+
|
| 38 |
+
# --- FlashRT Thor vendor: forward / SM100-only trim -------------------------
|
| 39 |
+
# This vendored copy keeps ONLY the SM100 (Blackwell, including sm_110/Thor)
|
| 40 |
+
# forward kernel. The SM80/SM90/SM120 forward kernels, every backward kernel,
|
| 41 |
+
# the MLA forward kernel, and the head_dim=256 2CTA kernels were removed (see
|
| 42 |
+
# VENDOR.md). The names below are still referenced by dead arch-dispatch
|
| 43 |
+
# branches inside `_flash_attn_fwd`; on Blackwell those branches never run. If
|
| 44 |
+
# one is reached on unsupported hardware it raises a clear error instead of an
|
| 45 |
+
# obscure NameError. Backward is not provided (inference / forward only).
|
| 46 |
+
def _fa4_trimmed(_name):
|
| 47 |
+
def _raise(*_a, **_k):
|
| 48 |
+
raise RuntimeError(
|
| 49 |
+
f"{_name} was removed from FlashRT's forward/SM100-only FA4 vendor. "
|
| 50 |
+
"This build supports Blackwell SM100/SM110 forward attention only; "
|
| 51 |
+
"use upstream flash-attn for other architectures or training."
|
| 52 |
+
)
|
| 53 |
+
return _raise
|
| 54 |
+
|
| 55 |
+
FlashAttentionForwardSm80 = _fa4_trimmed("FlashAttentionForwardSm80")
|
| 56 |
+
FlashAttentionForwardSm90 = _fa4_trimmed("FlashAttentionForwardSm90")
|
| 57 |
+
FlashAttentionForwardSm120 = _fa4_trimmed("FlashAttentionForwardSm120")
|
| 58 |
+
FlashAttentionMLAForwardSm100 = _fa4_trimmed("FlashAttentionMLAForwardSm100")
|
| 59 |
+
from fa4_cute_runtime.flashrt_fa4.cute.sm100_hd256_2cta_fmha_forward import (
|
| 60 |
+
BlackwellFusedMultiHeadAttentionForward,
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
from fa4_cute_runtime.flashrt_fa4.cute.block_sparsity import (
|
| 64 |
+
BlockSparseTensorsTorch,
|
| 65 |
+
get_sparse_q_block_size,
|
| 66 |
+
to_cute_block_sparse_tensors,
|
| 67 |
+
normalize_block_sparse_config,
|
| 68 |
+
normalize_block_sparse_config_bwd,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
def _parse_arch_str(arch_str):
|
| 72 |
+
"""Parse arch string (e.g. 'sm_80', 'sm_90a', '80', '100') to int (e.g. 80, 90, 100)."""
|
| 73 |
+
import re
|
| 74 |
+
match = re.match(r"^(?:sm_?|SM_?)?(\d+)(\d)([af]?)$", arch_str)
|
| 75 |
+
if not match:
|
| 76 |
+
raise ValueError(f"Invalid arch format: {arch_str}")
|
| 77 |
+
major, minor, _ = match.groups()
|
| 78 |
+
return int(major) * 10 + int(minor)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@lru_cache(maxsize=None)
|
| 82 |
+
def _get_device_arch():
|
| 83 |
+
"""Cached device arch check.
|
| 84 |
+
|
| 85 |
+
Override with FLASH_ATTENTION_ARCH (e.g. 'sm_80' or '80') to select which
|
| 86 |
+
kernel path to use (SM80/SM90/SM100/SM120) independently of the compilation
|
| 87 |
+
target (CUTE_DSL_ARCH).
|
| 88 |
+
|
| 89 |
+
For CPU-only compilation (no GPU), set both:
|
| 90 |
+
FLASH_ATTENTION_ARCH=sm_80 (kernel selection)
|
| 91 |
+
CUTE_DSL_ARCH=sm_80 (compilation target)
|
| 92 |
+
"""
|
| 93 |
+
arch_override = os.environ.get("FLASH_ATTENTION_ARCH", None)
|
| 94 |
+
if arch_override is not None:
|
| 95 |
+
return _parse_arch_str(arch_override)
|
| 96 |
+
major, minor = torch.cuda.get_device_capability()
|
| 97 |
+
return major * 10 + int(minor)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
# Resolve the dispatch architecture once when the module is imported. The Hub
|
| 101 |
+
# wrapper sets FLASH_ATTENTION_ARCH before importing this module, so tracing a
|
| 102 |
+
# model never executes device discovery or parses environment strings.
|
| 103 |
+
_IMPORT_DEVICE_ARCH = _get_device_arch()
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def _validate_head_dims(head_dim: int, head_dim_v: int, compute_capability: int, alignment: int) -> None:
|
| 107 |
+
"""Validate head dimension constraints based on compute capability."""
|
| 108 |
+
is_deepseek_shape = head_dim == 192 and head_dim_v == 128
|
| 109 |
+
is_deepseek_mla_absorbed_shape = head_dim == 64 and head_dim_v == 512
|
| 110 |
+
is_dedicate_kernel_shape = head_dim == 256 and head_dim_v == 256
|
| 111 |
+
is_standard_range = 8 <= head_dim <= 128 and 8 <= head_dim_v <= 128
|
| 112 |
+
|
| 113 |
+
is_sm90_range = 8 <= head_dim <= 256 and 8 <= head_dim_v <= 256
|
| 114 |
+
if compute_capability == 9:
|
| 115 |
+
assert is_sm90_range and head_dim % alignment == 0 and head_dim_v % alignment == 0, (
|
| 116 |
+
f"(head_dim, head_dim_v)=({head_dim}, {head_dim_v}) is not supported on SM90. "
|
| 117 |
+
f"head_dim and head_dim_v must be between 8 and 256 and divisible by {alignment}."
|
| 118 |
+
)
|
| 119 |
+
elif compute_capability in [10, 11]:
|
| 120 |
+
assert (is_standard_range or is_deepseek_shape or is_deepseek_mla_absorbed_shape or is_dedicate_kernel_shape) and head_dim % alignment == 0 and head_dim_v % alignment == 0, (
|
| 121 |
+
f"(head_dim, head_dim_v)=({head_dim}, {head_dim_v}) is not supported on SM100/SM110. "
|
| 122 |
+
f"head_dim and head_dim_v must be between 8 and 128 and divisible by {alignment}, or (192, 128) for DeepSeek, or (256, 256) for hd256."
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
@dataclass(frozen=True)
|
| 127 |
+
class FwdConfig:
|
| 128 |
+
m_block_size: int
|
| 129 |
+
n_block_size: int
|
| 130 |
+
mma_pv_is_rs: bool
|
| 131 |
+
intra_wg_overlap: bool
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _tile_size_fwd_sm90(head_dim, head_dim_v, is_causal, is_local, sparse_block_size_q=None):
|
| 135 |
+
"""Return FwdConfig for SM90 forward.
|
| 136 |
+
|
| 137 |
+
Tile sizes and flags based on tile_size_fwd_sm90 in hopper/tile_size.h, adjusted
|
| 138 |
+
for the Python kernel's different register/smem tradeoffs (benchmarked on H100 SXM).
|
| 139 |
+
|
| 140 |
+
When sparse_block_size_q is set, tile_m must divide it. For head_dim <= 96 the
|
| 141 |
+
optimal tile_m=192 is used when compatible, otherwise we fall back to 128.
|
| 142 |
+
"""
|
| 143 |
+
if head_dim <= 64:
|
| 144 |
+
# C++: 192×192 non-causal, 192×128 causal/local.
|
| 145 |
+
# Python: 192×128 RS+OL is consistently best across seqlens.
|
| 146 |
+
if sparse_block_size_q is not None and sparse_block_size_q % 192 != 0:
|
| 147 |
+
return FwdConfig(128, 128, True, True)
|
| 148 |
+
return FwdConfig(192, 128, True, True)
|
| 149 |
+
elif head_dim <= 96:
|
| 150 |
+
# C++: 192×144 noRS+OL for all cases.
|
| 151 |
+
# Python: RS is catastrophic with 192× tiles (~300 vs ~600 TFLOPS).
|
| 152 |
+
# noRS+OL is always required. Causal: 192×128 slightly better short seqlen.
|
| 153 |
+
if sparse_block_size_q is not None and sparse_block_size_q % 192 != 0:
|
| 154 |
+
return FwdConfig(128, 128, False, True)
|
| 155 |
+
if is_causal or is_local:
|
| 156 |
+
return FwdConfig(192, 128, False, True)
|
| 157 |
+
else:
|
| 158 |
+
return FwdConfig(192, 144, False, True)
|
| 159 |
+
elif head_dim <= 128:
|
| 160 |
+
return FwdConfig(128, 128, True, True)
|
| 161 |
+
elif head_dim <= 192:
|
| 162 |
+
tile_n = 96 if is_local else (128 if head_dim_v <= 128 else 112)
|
| 163 |
+
return FwdConfig(128, tile_n, True, True)
|
| 164 |
+
else: # hdim 256
|
| 165 |
+
tile_n = 64 if is_local else 80
|
| 166 |
+
return FwdConfig(128, tile_n, True, True)
|
| 167 |
+
|
| 168 |
+
@dataclass(frozen=True)
|
| 169 |
+
class BwdConfig:
|
| 170 |
+
m_block_size: int
|
| 171 |
+
n_block_size: int
|
| 172 |
+
num_stages_Q: int
|
| 173 |
+
num_stages_dO: int
|
| 174 |
+
num_stages_PdS: int
|
| 175 |
+
SdP_swapAB: bool
|
| 176 |
+
dKV_swapAB: bool
|
| 177 |
+
dQ_swapAB: bool
|
| 178 |
+
AtomLayoutMSdP: int
|
| 179 |
+
AtomLayoutNdKV: int
|
| 180 |
+
AtomLayoutMdQ: int
|
| 181 |
+
num_wg: int = 2 # MMA warp groups (total threads = (num_wg + 1) * 128)
|
| 182 |
+
dQ_single_wg: bool = False
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def _tile_size_bwd_sm90(head_dim, head_dim_v, causal, local, sparse_block_size_q=None):
|
| 186 |
+
"""Return BwdConfig for SM90.
|
| 187 |
+
|
| 188 |
+
Configs based on C++ FA3 hopper/flash_bwd_launch_template.h,
|
| 189 |
+
benchmarked on H100 SXM.
|
| 190 |
+
"""
|
| 191 |
+
if head_dim <= 64:
|
| 192 |
+
# C++ FA3: 128, 128, 64, ..., 2, 2, true, false, false, 2, 1, 2, 2
|
| 193 |
+
return BwdConfig(
|
| 194 |
+
m_block_size=128, n_block_size=128,
|
| 195 |
+
num_stages_Q=2, num_stages_dO=2, num_stages_PdS=2,
|
| 196 |
+
SdP_swapAB=True, dKV_swapAB=False, dQ_swapAB=False,
|
| 197 |
+
AtomLayoutMSdP=1, AtomLayoutNdKV=2, AtomLayoutMdQ=2,
|
| 198 |
+
)
|
| 199 |
+
elif head_dim <= 96:
|
| 200 |
+
# C++ FA3: 64, 128, 96, dQ_swapAB=False
|
| 201 |
+
return BwdConfig(
|
| 202 |
+
m_block_size=64, n_block_size=128,
|
| 203 |
+
num_stages_Q=2, num_stages_dO=2, num_stages_PdS=2,
|
| 204 |
+
SdP_swapAB=True, dKV_swapAB=False, dQ_swapAB=False,
|
| 205 |
+
AtomLayoutMSdP=1, AtomLayoutNdKV=2, AtomLayoutMdQ=1,
|
| 206 |
+
dQ_single_wg=True,
|
| 207 |
+
)
|
| 208 |
+
elif head_dim <= 128:
|
| 209 |
+
# C++ FA3: causal/local: 64, 128; non-causal: 80, 128 with dQ_swapAB
|
| 210 |
+
is_causal_or_local = causal or local
|
| 211 |
+
m_block_size = 64 if is_causal_or_local else 80
|
| 212 |
+
if sparse_block_size_q is not None and sparse_block_size_q % m_block_size != 0:
|
| 213 |
+
m_block_size = 64
|
| 214 |
+
return BwdConfig(
|
| 215 |
+
m_block_size=m_block_size,
|
| 216 |
+
n_block_size=128,
|
| 217 |
+
num_stages_Q=2, num_stages_dO=2, num_stages_PdS=2,
|
| 218 |
+
SdP_swapAB=True, dKV_swapAB=False,
|
| 219 |
+
dQ_swapAB=m_block_size % 64 != 0,
|
| 220 |
+
AtomLayoutMSdP=1, AtomLayoutNdKV=2, AtomLayoutMdQ=1,
|
| 221 |
+
)
|
| 222 |
+
elif head_dim <= 192:
|
| 223 |
+
hdimv128 = head_dim_v <= 128
|
| 224 |
+
if hdimv128:
|
| 225 |
+
return BwdConfig(
|
| 226 |
+
m_block_size=64, n_block_size=96,
|
| 227 |
+
num_stages_Q=2, num_stages_dO=2, num_stages_PdS=1,
|
| 228 |
+
SdP_swapAB=False, dKV_swapAB=True, dQ_swapAB=False,
|
| 229 |
+
AtomLayoutMSdP=1, AtomLayoutNdKV=2, AtomLayoutMdQ=1,
|
| 230 |
+
num_wg=2,
|
| 231 |
+
)
|
| 232 |
+
else:
|
| 233 |
+
return BwdConfig(
|
| 234 |
+
m_block_size=64, n_block_size=96,
|
| 235 |
+
num_stages_Q=2, num_stages_dO=1, num_stages_PdS=1,
|
| 236 |
+
SdP_swapAB=False, dKV_swapAB=True, dQ_swapAB=False,
|
| 237 |
+
AtomLayoutMSdP=1, AtomLayoutNdKV=2, AtomLayoutMdQ=1,
|
| 238 |
+
num_wg=2,
|
| 239 |
+
)
|
| 240 |
+
else:
|
| 241 |
+
# hdim 256
|
| 242 |
+
return BwdConfig(
|
| 243 |
+
m_block_size=64, n_block_size=64,
|
| 244 |
+
num_stages_Q=1, num_stages_dO=1, num_stages_PdS=1,
|
| 245 |
+
SdP_swapAB=False, dKV_swapAB=False, dQ_swapAB=False,
|
| 246 |
+
AtomLayoutMSdP=1, AtomLayoutNdKV=1, AtomLayoutMdQ=1,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def maybe_contiguous(x):
|
| 252 |
+
return x.contiguous() if x is not None and x.stride(-1) != 1 else x
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _validate_tensor(t, name, expected_shape, expected_dtype, expected_device):
|
| 256 |
+
assert t.shape == expected_shape, f"{name} shape {t.shape} != expected {expected_shape}"
|
| 257 |
+
assert t.dtype == expected_dtype, f"{name} dtype {t.dtype} != expected {expected_dtype}"
|
| 258 |
+
assert t.device == expected_device, f"{name} device {t.device} != expected {expected_device}"
|
| 259 |
+
if not is_fake_mode():
|
| 260 |
+
assert t.is_cuda, f"{name} must be on CUDA"
|
| 261 |
+
|
| 262 |
+
torch2cute_dtype_map = {
|
| 263 |
+
torch.float16: cutlass.Float16,
|
| 264 |
+
torch.bfloat16: cutlass.BFloat16,
|
| 265 |
+
torch.float32: cutlass.Float32,
|
| 266 |
+
torch.float8_e4m3fn: cutlass.Float8E4M3FN,
|
| 267 |
+
torch.float8_e5m2: cutlass.Float8E5M2,
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def num_splits_heuristic(total_mblocks, num_SMs, num_n_blocks, max_splits):
|
| 272 |
+
# If num_n_blocks is too small, use 1 split. For example, we never split for hdim = 128 and seqlen_k = 512.
|
| 273 |
+
if num_n_blocks <= 4:
|
| 274 |
+
return 1
|
| 275 |
+
|
| 276 |
+
# NOTE: We should revisit this heuristic after persistence is supported for split KV.
|
| 277 |
+
# Sometimes, it's ideal to over-schedule splits for better efficiency.
|
| 278 |
+
return min(num_SMs // total_mblocks, max_splits, num_n_blocks)
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _resolve_causal_local_window(causal, window_size_left, window_size_right, mask_mod=None):
|
| 282 |
+
"""Resolve causal/local/window settings into canonical form.
|
| 283 |
+
|
| 284 |
+
Returns (causal, local, window_size_left, window_size_right).
|
| 285 |
+
"""
|
| 286 |
+
if mask_mod is not None:
|
| 287 |
+
return False, False, window_size_left, window_size_right
|
| 288 |
+
if causal:
|
| 289 |
+
window_size_right = 0
|
| 290 |
+
if window_size_left is not None and window_size_right is not None and window_size_left + window_size_right < 0:
|
| 291 |
+
window_size_left = None
|
| 292 |
+
window_size_right = None
|
| 293 |
+
if window_size_left is not None or window_size_right is not None:
|
| 294 |
+
if window_size_left is None and window_size_right == 0:
|
| 295 |
+
causal, local = True, False
|
| 296 |
+
window_size_right = None
|
| 297 |
+
else:
|
| 298 |
+
causal, local = False, True
|
| 299 |
+
else:
|
| 300 |
+
local = False
|
| 301 |
+
return causal, local, window_size_left, window_size_right
|
| 302 |
+
|
| 303 |
+
def _flash_attn_fwd(
|
| 304 |
+
q: torch.Tensor,
|
| 305 |
+
k: torch.Tensor,
|
| 306 |
+
v: torch.Tensor,
|
| 307 |
+
qv: Optional[torch.Tensor] = None,
|
| 308 |
+
cu_seqlens_q: Optional[torch.Tensor] = None,
|
| 309 |
+
cu_seqlens_k: Optional[torch.Tensor] = None,
|
| 310 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 311 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 312 |
+
max_seqlen_q: Optional[int] = None,
|
| 313 |
+
max_seqlen_k: Optional[int] = None,
|
| 314 |
+
min_seqlen_k: Optional[int] = None,
|
| 315 |
+
page_table: Optional[torch.Tensor] = None,
|
| 316 |
+
softmax_scale: Optional[float] = None,
|
| 317 |
+
causal: bool = False,
|
| 318 |
+
softcap: Optional[float] = None,
|
| 319 |
+
window_size_left: Optional[int] = None,
|
| 320 |
+
window_size_right: Optional[int] = None,
|
| 321 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 322 |
+
tile_mn: Optional[Tuple[int, int]] = None,
|
| 323 |
+
mma_pv_is_rs: Optional[bool] = None,
|
| 324 |
+
intra_wg_overlap: Optional[bool] = None,
|
| 325 |
+
num_threads: int = 384,
|
| 326 |
+
num_splits: int = 1,
|
| 327 |
+
pack_gqa: Optional[bool] = None,
|
| 328 |
+
_arch: Optional[int] = None,
|
| 329 |
+
score_mod: Optional[Callable] = None,
|
| 330 |
+
mask_mod: Optional[Callable] = None,
|
| 331 |
+
block_sparse_tensors: Optional[BlockSparseTensorsTorch] = None,
|
| 332 |
+
return_lse: bool = False,
|
| 333 |
+
out: Optional[torch.Tensor] = None,
|
| 334 |
+
lse: Optional[torch.Tensor] = None,
|
| 335 |
+
aux_tensors: Optional[list[torch.Tensor]] = None,
|
| 336 |
+
q_descale: Optional[torch.Tensor] = None,
|
| 337 |
+
k_descale: Optional[torch.Tensor] = None,
|
| 338 |
+
v_descale: Optional[torch.Tensor] = None,
|
| 339 |
+
gather_kv_indices: Optional[torch.Tensor] = None,
|
| 340 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 341 |
+
"""Forward pass for FlashAttention.
|
| 342 |
+
|
| 343 |
+
Args:
|
| 344 |
+
...
|
| 345 |
+
score_mod: A callable that takes the attention scores and applies a modification.
|
| 346 |
+
mask_mod: A callable that takes token position information and selectively masks
|
| 347 |
+
block_sparse_tensors: A tuple of tensors used for block sparsity.
|
| 348 |
+
return_lse: Whether to return the log softmax of the attention scores. If set to True will always calculate
|
| 349 |
+
The returned LSE supports taking gradient.
|
| 350 |
+
out: Optional pre-allocated output tensor. If None, will be allocated internally.
|
| 351 |
+
lse: Optional pre-allocated log-sum-exp tensor. If None, will be allocated when needed.
|
| 352 |
+
aux_tensors: Some score_mods will want to read from global aux_tensors. This is how we thread them through to the inner kernel.
|
| 353 |
+
"""
|
| 354 |
+
q, k, v = [maybe_contiguous(t) for t in (q, k, v)]
|
| 355 |
+
q_descale, k_descale, v_descale = [maybe_contiguous(t) for t in (q_descale, k_descale, v_descale)]
|
| 356 |
+
num_head, head_dim = q.shape[-2:]
|
| 357 |
+
if cu_seqlens_q is None:
|
| 358 |
+
batch_size, seqlen_q = q.shape[:2]
|
| 359 |
+
total_q = batch_size * seqlen_q
|
| 360 |
+
else:
|
| 361 |
+
batch_size = cu_seqlens_q.shape[0] - 1
|
| 362 |
+
seqlen_q = None
|
| 363 |
+
total_q = q.shape[0]
|
| 364 |
+
if page_table is not None:
|
| 365 |
+
assert cu_seqlens_k is None, "page_table is not supported with cu_seqlens_k"
|
| 366 |
+
assert page_table.dtype == torch.int32, "page_table must be int32"
|
| 367 |
+
assert page_table.stride(-1) == 1, "page_table must be contiguous in the last dimension"
|
| 368 |
+
max_num_pages_per_seq = page_table.shape[1]
|
| 369 |
+
assert page_table.shape == (batch_size, max_num_pages_per_seq)
|
| 370 |
+
num_pages, page_size = k.shape[:2]
|
| 371 |
+
seqlen_k = num_pages * page_size
|
| 372 |
+
else:
|
| 373 |
+
num_pages, page_size = None, None
|
| 374 |
+
seqlen_k = k.shape[-3]
|
| 375 |
+
num_head_kv = k.shape[-2]
|
| 376 |
+
head_dim_v = v.shape[-1]
|
| 377 |
+
if cu_seqlens_k is None:
|
| 378 |
+
if page_table is None:
|
| 379 |
+
assert k.shape == (batch_size, seqlen_k, num_head_kv, head_dim)
|
| 380 |
+
assert v.shape == (batch_size, seqlen_k, num_head_kv, head_dim_v)
|
| 381 |
+
else:
|
| 382 |
+
assert k.shape == (num_pages, page_size, num_head_kv, head_dim)
|
| 383 |
+
assert v.shape == (num_pages, page_size, num_head_kv, head_dim_v)
|
| 384 |
+
else:
|
| 385 |
+
assert k.shape == (seqlen_k, num_head_kv, head_dim)
|
| 386 |
+
assert v.shape == (seqlen_k, num_head_kv, head_dim_v)
|
| 387 |
+
assert cu_seqlens_k.shape == (batch_size + 1,), (
|
| 388 |
+
"cu_seqlens_k must have shape (batch_size + 1,)"
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
if cu_seqlens_q is not None:
|
| 392 |
+
assert cu_seqlens_q.shape == (batch_size + 1,), (
|
| 393 |
+
"cu_seqlens_q must have shape (batch_size + 1,)"
|
| 394 |
+
)
|
| 395 |
+
assert seqused_q is None or seqused_q.shape == (batch_size,), (
|
| 396 |
+
"seqused_q must have shape (batch_size,)"
|
| 397 |
+
)
|
| 398 |
+
assert seqused_k is None or seqused_k.shape == (batch_size,), (
|
| 399 |
+
"seqused_k must have shape (batch_size,)"
|
| 400 |
+
)
|
| 401 |
+
assert q.dtype in [torch.float16, torch.bfloat16, torch.float8_e4m3fn, torch.float8_e5m2], (
|
| 402 |
+
"inputs must be float16, bfloat16, fp8 e4m3fn, or fp8 e5m2"
|
| 403 |
+
)
|
| 404 |
+
assert q.dtype == k.dtype == v.dtype, "inputs must have the same dtype"
|
| 405 |
+
for t in [cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k]:
|
| 406 |
+
if t is not None:
|
| 407 |
+
assert t.dtype == torch.int32, (
|
| 408 |
+
"cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k must be int32"
|
| 409 |
+
)
|
| 410 |
+
assert t.stride(0) == 1, (
|
| 411 |
+
"cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k must be contiguous"
|
| 412 |
+
)
|
| 413 |
+
if learnable_sink is not None:
|
| 414 |
+
assert learnable_sink.shape == (num_head,)
|
| 415 |
+
assert learnable_sink.dtype == torch.bfloat16, "learnable_sink must be bfloat16"
|
| 416 |
+
|
| 417 |
+
if not is_fake_mode():
|
| 418 |
+
assert all(
|
| 419 |
+
t is None or t.is_cuda
|
| 420 |
+
for t in (
|
| 421 |
+
q,
|
| 422 |
+
k,
|
| 423 |
+
v,
|
| 424 |
+
q_descale,
|
| 425 |
+
k_descale,
|
| 426 |
+
v_descale,
|
| 427 |
+
cu_seqlens_q,
|
| 428 |
+
cu_seqlens_k,
|
| 429 |
+
seqused_q,
|
| 430 |
+
seqused_k,
|
| 431 |
+
page_table,
|
| 432 |
+
learnable_sink,
|
| 433 |
+
)
|
| 434 |
+
), "inputs must be on CUDA device"
|
| 435 |
+
arch = _IMPORT_DEVICE_ARCH if _arch is None else _arch
|
| 436 |
+
assert arch // 10 in [8, 9, 10, 11, 12], "Unsupported compute capability. Supported: 8.x, 9.x, 10.x, 11.x, 12.x"
|
| 437 |
+
assert num_head % num_head_kv == 0, "num_head must be divisible by num_head_kv"
|
| 438 |
+
alignment = 16 // q.element_size()
|
| 439 |
+
if arch // 10 not in [8, 12]:
|
| 440 |
+
_validate_head_dims(head_dim, head_dim_v, arch // 10, alignment)
|
| 441 |
+
if softmax_scale is None:
|
| 442 |
+
softmax_scale = 1.0 / math.sqrt(head_dim) if qv is None else 1.0 / math.sqrt(head_dim + head_dim_v)
|
| 443 |
+
if softcap == 0.0:
|
| 444 |
+
softcap = None
|
| 445 |
+
qhead_per_kvhead = num_head // num_head_kv
|
| 446 |
+
if pack_gqa is None:
|
| 447 |
+
pack_gqa = qhead_per_kvhead > 1
|
| 448 |
+
|
| 449 |
+
is_fp8 = q.dtype in (torch.float8_e4m3fn, torch.float8_e5m2)
|
| 450 |
+
if is_fp8 and (q.requires_grad or k.requires_grad or v.requires_grad):
|
| 451 |
+
raise NotImplementedError("FA4 CuTe FP8 backward is not supported yet (forward-only).")
|
| 452 |
+
out_torch_dtype = torch.bfloat16 if is_fp8 else q.dtype
|
| 453 |
+
device = q.device
|
| 454 |
+
q_batch_seqlen_shape = (batch_size, seqlen_q) if cu_seqlens_q is None else (total_q,)
|
| 455 |
+
lse_shape = (batch_size, num_head, seqlen_q) if cu_seqlens_q is None else (num_head, total_q)
|
| 456 |
+
requires_grad = q.requires_grad or k.requires_grad or v.requires_grad
|
| 457 |
+
|
| 458 |
+
if out is None:
|
| 459 |
+
out = torch.empty(
|
| 460 |
+
*q_batch_seqlen_shape, num_head, head_dim_v, dtype=out_torch_dtype, device=device
|
| 461 |
+
)
|
| 462 |
+
else:
|
| 463 |
+
_validate_tensor(out, "out", (*q_batch_seqlen_shape, num_head, head_dim_v), out_torch_dtype, device)
|
| 464 |
+
|
| 465 |
+
if lse is None:
|
| 466 |
+
lse = (
|
| 467 |
+
torch.empty(lse_shape, dtype=torch.float32, device=device)
|
| 468 |
+
if requires_grad or return_lse
|
| 469 |
+
else None
|
| 470 |
+
)
|
| 471 |
+
elif lse is not None:
|
| 472 |
+
_validate_tensor(lse, "lse", lse_shape, torch.float32, device)
|
| 473 |
+
|
| 474 |
+
if seqlen_k == 0:
|
| 475 |
+
out.zero_()
|
| 476 |
+
if lse is not None:
|
| 477 |
+
lse.fill_(float("-inf"))
|
| 478 |
+
return out, lse
|
| 479 |
+
|
| 480 |
+
if is_fp8:
|
| 481 |
+
for t, name in ((q_descale, "q_descale"), (k_descale, "k_descale"), (v_descale, "v_descale")):
|
| 482 |
+
if t is not None:
|
| 483 |
+
_validate_tensor(t, name, (batch_size, num_head_kv), torch.float32, device)
|
| 484 |
+
else:
|
| 485 |
+
assert q_descale is None and k_descale is None and v_descale is None, (
|
| 486 |
+
"q_descale/k_descale/v_descale are only supported for FP8 inputs"
|
| 487 |
+
)
|
| 488 |
+
|
| 489 |
+
dtype = torch2cute_dtype_map[q.dtype]
|
| 490 |
+
if is_fp8:
|
| 491 |
+
assert arch // 10 == 10, "FP8 is only supported on SM100 (compute capability 10.x) for FA4 CuTe."
|
| 492 |
+
use_block_sparsity = block_sparse_tensors is not None
|
| 493 |
+
|
| 494 |
+
causal, local, window_size_left, window_size_right = _resolve_causal_local_window(
|
| 495 |
+
causal, window_size_left, window_size_right, mask_mod
|
| 496 |
+
)
|
| 497 |
+
|
| 498 |
+
requested_use_clc_scheduler = utils._get_use_clc_scheduler_default()
|
| 499 |
+
requested_disable_2cta = utils._get_disable_2cta_default()
|
| 500 |
+
|
| 501 |
+
current_stream = cute.runtime.make_fake_stream(use_tvm_ffi_env_stream=True)
|
| 502 |
+
|
| 503 |
+
# SM80/SM120: uses SM80 MMA, 128 threads (4 warps)
|
| 504 |
+
if arch // 10 in [8, 12]:
|
| 505 |
+
num_threads = 128
|
| 506 |
+
|
| 507 |
+
fwd_cfg = FwdConfig(128, 128, True, True) # default
|
| 508 |
+
if tile_mn is None:
|
| 509 |
+
if arch // 10 == 12:
|
| 510 |
+
# SM120 tile sizes tuned for 99 KB SMEM capacity:
|
| 511 |
+
# D<=64: 128x128 → 48 KB (good occupancy)
|
| 512 |
+
# D>64: 128x64 → 64 KB (128x128 would use 96 KB, hurting occupancy)
|
| 513 |
+
if head_dim <= 64:
|
| 514 |
+
fwd_cfg = FwdConfig(128, 128, True, True)
|
| 515 |
+
else:
|
| 516 |
+
fwd_cfg = FwdConfig(128, 64, True, True)
|
| 517 |
+
elif arch // 10 == 8:
|
| 518 |
+
fwd_cfg = FwdConfig(128, 64, True, True) # SM80, should tune
|
| 519 |
+
elif arch // 10 == 9:
|
| 520 |
+
sparse_q = get_sparse_q_block_size(block_sparse_tensors, seqlen_q)
|
| 521 |
+
fwd_cfg = _tile_size_fwd_sm90(head_dim, head_dim_v, causal, local, sparse_block_size_q=sparse_q)
|
| 522 |
+
else:
|
| 523 |
+
fwd_cfg = FwdConfig(tile_mn[0], tile_mn[1], fwd_cfg.mma_pv_is_rs, fwd_cfg.intra_wg_overlap)
|
| 524 |
+
tile_m, tile_n = fwd_cfg.m_block_size, fwd_cfg.n_block_size
|
| 525 |
+
if mma_pv_is_rs is None:
|
| 526 |
+
mma_pv_is_rs = fwd_cfg.mma_pv_is_rs
|
| 527 |
+
if intra_wg_overlap is None:
|
| 528 |
+
intra_wg_overlap = fwd_cfg.intra_wg_overlap
|
| 529 |
+
|
| 530 |
+
# TODO: fix GQA + SplitKV + non-varlen
|
| 531 |
+
if pack_gqa and num_splits != 1 and cu_seqlens_q is None:
|
| 532 |
+
pack_gqa = False
|
| 533 |
+
|
| 534 |
+
if pack_gqa and qv is not None and 128 % qhead_per_kvhead != 0:
|
| 535 |
+
pack_gqa = False
|
| 536 |
+
|
| 537 |
+
if max_seqlen_q is None:
|
| 538 |
+
max_seqlen_q = seqlen_q if cu_seqlens_q is None else total_q
|
| 539 |
+
if max_seqlen_k is None:
|
| 540 |
+
max_seqlen_k = seqlen_k
|
| 541 |
+
if cu_seqlens_k is None and seqused_k is None:
|
| 542 |
+
min_seqlen_k = seqlen_k
|
| 543 |
+
seqlen_q_packgqa = max_seqlen_q * qhead_per_kvhead
|
| 544 |
+
if arch // 10 == 10:
|
| 545 |
+
q_stage = 2 if seqlen_q_packgqa > tile_m else 1
|
| 546 |
+
else:
|
| 547 |
+
q_stage = 1
|
| 548 |
+
|
| 549 |
+
m_block_size_effective = q_stage * tile_m
|
| 550 |
+
seqlen_k_loaded = max_seqlen_k if not local else max(0, min(max_seqlen_k, (window_size_right or max_seqlen_k) + (window_size_left or max_seqlen_k) + 1 + tile_m))
|
| 551 |
+
num_m_blocks = (seqlen_q_packgqa + m_block_size_effective - 1) // m_block_size_effective
|
| 552 |
+
total_mblocks = batch_size * num_head_kv * num_m_blocks
|
| 553 |
+
num_n_blocks = (seqlen_k_loaded + tile_n - 1) // tile_n
|
| 554 |
+
num_SMs = 132 if is_fake_mode() else torch.cuda.get_device_properties(device).multi_processor_count
|
| 555 |
+
if num_splits < 1:
|
| 556 |
+
num_splits = num_splits_heuristic(total_mblocks, num_SMs, num_n_blocks, 128)
|
| 557 |
+
|
| 558 |
+
# SplitKV uses float32 partial output, which doubles the O buffer size
|
| 559 |
+
# in shared memory, causing OOM for diff-headdim (192, 128)
|
| 560 |
+
if arch // 10 in [10, 11] and head_dim != head_dim_v and num_splits > 1:
|
| 561 |
+
if num_n_blocks >= 64 and head_dim_v != 512:
|
| 562 |
+
tile_n = 64
|
| 563 |
+
num_n_blocks = (seqlen_k_loaded + tile_n - 1) // tile_n
|
| 564 |
+
num_splits = num_splits_heuristic(total_mblocks, num_SMs, num_n_blocks, 128)
|
| 565 |
+
else:
|
| 566 |
+
num_splits = 1
|
| 567 |
+
|
| 568 |
+
is_split_kv = num_splits > 1
|
| 569 |
+
if is_split_kv:
|
| 570 |
+
out_partial = torch.empty(num_splits, *q_batch_seqlen_shape, num_head, head_dim_v, dtype=torch.float32, device=device)
|
| 571 |
+
lse_partial = torch.empty(num_splits, *lse_shape, dtype=torch.float32, device=device)
|
| 572 |
+
|
| 573 |
+
use_2cta_instrs = (
|
| 574 |
+
arch // 10 in [10, 11]
|
| 575 |
+
and not requested_disable_2cta
|
| 576 |
+
and not causal
|
| 577 |
+
and not local
|
| 578 |
+
and not is_split_kv
|
| 579 |
+
and cu_seqlens_q is None
|
| 580 |
+
and seqused_q is None
|
| 581 |
+
and not use_block_sparsity
|
| 582 |
+
and page_size in [None, 128]
|
| 583 |
+
and int(math.ceil(head_dim / 16) * 16) in [128, 192]
|
| 584 |
+
and int(math.ceil(head_dim_v / 16) * 16) == 128
|
| 585 |
+
and seqlen_q_packgqa > 2 * tile_m
|
| 586 |
+
and (tile_m % qhead_per_kvhead == 0 or not pack_gqa)
|
| 587 |
+
)
|
| 588 |
+
|
| 589 |
+
# hd=256 2CTA forward uses the dedicated kernel on both SM100 and SM110.
|
| 590 |
+
use_dedicated_hd256_kernel = (
|
| 591 |
+
arch // 10 in [10, 11]
|
| 592 |
+
and head_dim == 256
|
| 593 |
+
and head_dim_v == 256
|
| 594 |
+
)
|
| 595 |
+
use_2cta_instrs = use_2cta_instrs or use_dedicated_hd256_kernel
|
| 596 |
+
|
| 597 |
+
if softcap is not None:
|
| 598 |
+
assert score_mod is None, "softcap and score_mod cannot be used together"
|
| 599 |
+
score_mod = utils.create_softcap_scoremod(softcap)
|
| 600 |
+
elif score_mod is not None:
|
| 601 |
+
if arch // 10 == 8:
|
| 602 |
+
raise NotImplementedError("Custom user-provided score_mod is not supported on SM8x architectures.")
|
| 603 |
+
|
| 604 |
+
# hash score and mask mods for compile cache
|
| 605 |
+
score_mod_hash = utils.hash_callable(score_mod) if score_mod is not None else False
|
| 606 |
+
mask_mod_hash = utils.hash_callable(mask_mod) if mask_mod is not None else False
|
| 607 |
+
|
| 608 |
+
is_varlen = (
|
| 609 |
+
cu_seqlens_q is not None
|
| 610 |
+
or cu_seqlens_k is not None
|
| 611 |
+
or seqused_q is not None
|
| 612 |
+
or seqused_k is not None
|
| 613 |
+
)
|
| 614 |
+
|
| 615 |
+
# CLC regressed for varlen MHA and dense noncausal. Imbalanced varlen shapes
|
| 616 |
+
# keep more K/V blocks in flight and hurt L2; dense noncausal mostly just
|
| 617 |
+
# pays work-stealing overhead.
|
| 618 |
+
is_varlen_mha = is_varlen and qhead_per_kvhead == 1
|
| 619 |
+
is_dense_noncausal = not is_varlen and not causal and not local
|
| 620 |
+
use_clc_scheduler = requested_use_clc_scheduler and not is_varlen_mha and not is_dense_noncausal
|
| 621 |
+
|
| 622 |
+
if mask_mod is not None:
|
| 623 |
+
if is_varlen:
|
| 624 |
+
raise NotImplementedError(
|
| 625 |
+
"mask_mod with aux_tensors is not yet supported for varlen sequences. This will be fixed in a future PR."
|
| 626 |
+
)
|
| 627 |
+
|
| 628 |
+
if use_block_sparsity:
|
| 629 |
+
if is_varlen:
|
| 630 |
+
raise NotImplementedError(
|
| 631 |
+
"Block sparsity is not yet supported for varlen sequences. This will be fixed in a future PR."
|
| 632 |
+
)
|
| 633 |
+
# NB: pack_gqa requires block sparse head dim == 1 (broadcasted)
|
| 634 |
+
if pack_gqa and block_sparse_tensors.mask_block_cnt.shape[1] != 1:
|
| 635 |
+
pack_gqa = False
|
| 636 |
+
if is_split_kv:
|
| 637 |
+
raise NotImplementedError(
|
| 638 |
+
"Block sparsity is not yet supported with SplitKV. TODO: partition sparse block lists per split."
|
| 639 |
+
)
|
| 640 |
+
|
| 641 |
+
# See get_broadcast_dims for why this is needed in compile key
|
| 642 |
+
block_sparse_broadcast_pattern = None
|
| 643 |
+
normalized_block_sparse_tensors = None
|
| 644 |
+
q_subtile_factor = None
|
| 645 |
+
if block_sparse_tensors is not None:
|
| 646 |
+
if seqlen_q is None:
|
| 647 |
+
raise ValueError("Block sparsity requires fixed-length sequences (seqlen_q must be known).")
|
| 648 |
+
(
|
| 649 |
+
normalized_block_sparse_tensors,
|
| 650 |
+
block_sparse_broadcast_pattern,
|
| 651 |
+
q_subtile_factor,
|
| 652 |
+
) = normalize_block_sparse_config(
|
| 653 |
+
block_sparse_tensors,
|
| 654 |
+
batch_size=batch_size,
|
| 655 |
+
num_head=num_head,
|
| 656 |
+
seqlen_q=seqlen_q,
|
| 657 |
+
seqlen_k=seqlen_k,
|
| 658 |
+
block_size=(tile_m, tile_n),
|
| 659 |
+
q_stage=q_stage,
|
| 660 |
+
)
|
| 661 |
+
if aux_tensors is not None:
|
| 662 |
+
aux_tensor_metadata = get_aux_tensor_metadata(aux_tensors)
|
| 663 |
+
else:
|
| 664 |
+
aux_tensor_metadata = None
|
| 665 |
+
|
| 666 |
+
if qv is not None:
|
| 667 |
+
assert arch // 10 in [10, 11], "only support Blackwell arch with qv"
|
| 668 |
+
assert qv.shape[:-1] == q.shape[:-1]
|
| 669 |
+
assert qv.shape[-1] == head_dim_v
|
| 670 |
+
assert head_dim == 64 and head_dim_v == 512, "only support MLA weight absorbed shape with qv"
|
| 671 |
+
assert not local, "local not yet supported with qv"
|
| 672 |
+
assert page_table is None, "page table not yet supported with qv"
|
| 673 |
+
assert q_descale is None and k_descale is None and v_descale is None, (
|
| 674 |
+
"q_descale/k_descale/v_descale are not yet supported with qv"
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
assert not is_split_kv, "split kv not supported with qv"
|
| 678 |
+
assert learnable_sink is None
|
| 679 |
+
assert softcap is None
|
| 680 |
+
assert score_mod is None
|
| 681 |
+
assert mask_mod is None
|
| 682 |
+
|
| 683 |
+
qv = maybe_contiguous(qv)
|
| 684 |
+
|
| 685 |
+
gather_kv_length = 2048
|
| 686 |
+
sparse_kv = gather_kv_indices is not None
|
| 687 |
+
disable_sparse_kv_bitmask = False
|
| 688 |
+
if sparse_kv:
|
| 689 |
+
assert gather_kv_indices.shape[:-1] == q.shape[:-2]
|
| 690 |
+
gather_kv_length = gather_kv_indices.shape[-1]
|
| 691 |
+
assert gather_kv_length % 256 == 0
|
| 692 |
+
if min_seqlen_k is None or causal:
|
| 693 |
+
disable_sparse_kv_bitmask = False
|
| 694 |
+
else:
|
| 695 |
+
# seqlen_k_boundary = min_seqlen_k - max_seqlen_q + 1 if causal else min_seqlen_k
|
| 696 |
+
seqlen_k_boundary = min_seqlen_k
|
| 697 |
+
disable_sparse_kv_bitmask = seqlen_k_boundary >= gather_kv_length
|
| 698 |
+
else:
|
| 699 |
+
assert gather_kv_indices is None, "gather_kv_indices is only supported with qv"
|
| 700 |
+
gather_kv_length = None
|
| 701 |
+
sparse_kv = None
|
| 702 |
+
disable_sparse_kv_bitmask = None
|
| 703 |
+
|
| 704 |
+
compile_key = (
|
| 705 |
+
dtype,
|
| 706 |
+
head_dim,
|
| 707 |
+
head_dim_v,
|
| 708 |
+
qhead_per_kvhead,
|
| 709 |
+
causal,
|
| 710 |
+
score_mod_hash,
|
| 711 |
+
mask_mod_hash,
|
| 712 |
+
use_block_sparsity,
|
| 713 |
+
block_sparse_broadcast_pattern,
|
| 714 |
+
aux_tensor_metadata,
|
| 715 |
+
lse is None,
|
| 716 |
+
cu_seqlens_q is None,
|
| 717 |
+
cu_seqlens_k is None,
|
| 718 |
+
seqused_q is None,
|
| 719 |
+
seqused_k is None,
|
| 720 |
+
page_table is not None,
|
| 721 |
+
window_size_left is not None,
|
| 722 |
+
window_size_right is not None,
|
| 723 |
+
learnable_sink is not None,
|
| 724 |
+
q_descale is not None,
|
| 725 |
+
k_descale is not None,
|
| 726 |
+
v_descale is not None,
|
| 727 |
+
tile_m,
|
| 728 |
+
tile_n,
|
| 729 |
+
q_stage,
|
| 730 |
+
num_threads,
|
| 731 |
+
is_split_kv,
|
| 732 |
+
pack_gqa,
|
| 733 |
+
arch,
|
| 734 |
+
page_size not in [None, tile_n], # paged KV non-TMA
|
| 735 |
+
use_2cta_instrs,
|
| 736 |
+
q_subtile_factor,
|
| 737 |
+
mma_pv_is_rs,
|
| 738 |
+
intra_wg_overlap,
|
| 739 |
+
use_clc_scheduler,
|
| 740 |
+
qv is not None,
|
| 741 |
+
gather_kv_length,
|
| 742 |
+
sparse_kv,
|
| 743 |
+
disable_sparse_kv_bitmask,
|
| 744 |
+
fa_logging.get_fa_log_level(),
|
| 745 |
+
)
|
| 746 |
+
|
| 747 |
+
if compile_key not in _flash_attn_fwd.compile_cache:
|
| 748 |
+
(
|
| 749 |
+
cu_seqlens_q_tensor,
|
| 750 |
+
cu_seqlens_k_tensor,
|
| 751 |
+
seqused_q_tensor,
|
| 752 |
+
seqused_k_tensor,
|
| 753 |
+
learnable_sink_tensor,
|
| 754 |
+
) = [
|
| 755 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=0)
|
| 756 |
+
if t is not None
|
| 757 |
+
else None
|
| 758 |
+
for t in (cu_seqlens_q, cu_seqlens_k, seqused_q, seqused_k, learnable_sink)
|
| 759 |
+
]
|
| 760 |
+
page_table_tensor = (
|
| 761 |
+
to_cute_tensor(page_table, assumed_align=4, leading_dim=1)
|
| 762 |
+
if page_table is not None
|
| 763 |
+
else None
|
| 764 |
+
)
|
| 765 |
+
q_tensor, k_tensor, v_tensor, o_tensor = [
|
| 766 |
+
to_cute_tensor(t) for t in (q, k, v, out if not is_split_kv else out_partial)
|
| 767 |
+
]
|
| 768 |
+
if is_split_kv:
|
| 769 |
+
lse_tensor = to_cute_tensor(lse_partial, assumed_align=4)
|
| 770 |
+
elif lse is not None:
|
| 771 |
+
lse_tensor = to_cute_tensor(lse, assumed_align=4)
|
| 772 |
+
else:
|
| 773 |
+
lse_tensor = None
|
| 774 |
+
|
| 775 |
+
q_descale_tensor = (
|
| 776 |
+
to_cute_tensor(q_descale, assumed_align=4, leading_dim=1)
|
| 777 |
+
if q_descale is not None
|
| 778 |
+
else None
|
| 779 |
+
)
|
| 780 |
+
k_descale_tensor = (
|
| 781 |
+
to_cute_tensor(k_descale, assumed_align=4, leading_dim=1)
|
| 782 |
+
if k_descale is not None
|
| 783 |
+
else None
|
| 784 |
+
)
|
| 785 |
+
v_descale_tensor = (
|
| 786 |
+
to_cute_tensor(v_descale, assumed_align=4, leading_dim=1)
|
| 787 |
+
if v_descale is not None
|
| 788 |
+
else None
|
| 789 |
+
)
|
| 790 |
+
descale_tensors_tensor = (
|
| 791 |
+
DescaleTensors(
|
| 792 |
+
q_descale=q_descale_tensor,
|
| 793 |
+
k_descale=k_descale_tensor,
|
| 794 |
+
v_descale=v_descale_tensor,
|
| 795 |
+
)
|
| 796 |
+
if q_descale_tensor is not None
|
| 797 |
+
or k_descale_tensor is not None
|
| 798 |
+
or v_descale_tensor is not None
|
| 799 |
+
else None
|
| 800 |
+
)
|
| 801 |
+
|
| 802 |
+
sparse_tensors = None
|
| 803 |
+
if normalized_block_sparse_tensors is not None:
|
| 804 |
+
sparse_tensors = to_cute_block_sparse_tensors(normalized_block_sparse_tensors)
|
| 805 |
+
|
| 806 |
+
cute_aux_tensors = None
|
| 807 |
+
aux_tensor_metadata = None
|
| 808 |
+
if aux_tensors is not None:
|
| 809 |
+
cute_aux_tensors = [to_cute_aux_tensor(buf) for buf in aux_tensors]
|
| 810 |
+
|
| 811 |
+
qv_tensor = to_cute_tensor(qv) if qv is not None else None
|
| 812 |
+
gather_kv_indices_tensor = to_cute_tensor(gather_kv_indices) if gather_kv_indices is not None else None
|
| 813 |
+
|
| 814 |
+
if arch // 10 == 8:
|
| 815 |
+
assert page_table is None, "paged KV not supported on SM 8.0"
|
| 816 |
+
assert not is_split_kv, "SplitKV not supported on SM 8.0"
|
| 817 |
+
fa_fwd = FlashAttentionForwardSm80(
|
| 818 |
+
dtype,
|
| 819 |
+
head_dim,
|
| 820 |
+
head_dim_v,
|
| 821 |
+
qhead_per_kvhead,
|
| 822 |
+
is_causal=causal,
|
| 823 |
+
is_local=local,
|
| 824 |
+
pack_gqa=pack_gqa,
|
| 825 |
+
tile_m=tile_m,
|
| 826 |
+
tile_n=tile_n,
|
| 827 |
+
num_stages=1,
|
| 828 |
+
num_threads=num_threads,
|
| 829 |
+
Q_in_regs=False,
|
| 830 |
+
score_mod=score_mod,
|
| 831 |
+
mask_mod=mask_mod,
|
| 832 |
+
has_aux_tensors=aux_tensors is not None,
|
| 833 |
+
)
|
| 834 |
+
elif arch // 10 == 9:
|
| 835 |
+
assert not is_split_kv, "SplitKV not supported on SM 9.0"
|
| 836 |
+
fa_fwd = FlashAttentionForwardSm90(
|
| 837 |
+
dtype,
|
| 838 |
+
head_dim,
|
| 839 |
+
head_dim_v,
|
| 840 |
+
qhead_per_kvhead,
|
| 841 |
+
is_causal=causal,
|
| 842 |
+
is_local=local,
|
| 843 |
+
pack_gqa=pack_gqa,
|
| 844 |
+
tile_m=tile_m,
|
| 845 |
+
tile_n=tile_n,
|
| 846 |
+
# num_stages=1,
|
| 847 |
+
num_stages=2,
|
| 848 |
+
num_threads=num_threads,
|
| 849 |
+
Q_in_regs=False,
|
| 850 |
+
intra_wg_overlap=intra_wg_overlap,
|
| 851 |
+
mma_pv_is_rs=mma_pv_is_rs,
|
| 852 |
+
mask_mod=mask_mod,
|
| 853 |
+
score_mod=score_mod,
|
| 854 |
+
has_aux_tensors=aux_tensors is not None,
|
| 855 |
+
q_subtile_factor=q_subtile_factor,
|
| 856 |
+
paged_kv_non_tma=page_size not in [None, tile_n],
|
| 857 |
+
)
|
| 858 |
+
elif arch // 10 in [10, 11]:
|
| 859 |
+
if qv is not None:
|
| 860 |
+
fa_fwd = FlashAttentionMLAForwardSm100(
|
| 861 |
+
is_causal=causal,
|
| 862 |
+
use_cpasync_load_KV=sparse_kv,
|
| 863 |
+
topk_length=gather_kv_length,
|
| 864 |
+
is_topk_gather=sparse_kv,
|
| 865 |
+
pack_gqa=pack_gqa,
|
| 866 |
+
qhead_per_kvhead=qhead_per_kvhead,
|
| 867 |
+
nheads_kv=num_head_kv,
|
| 868 |
+
is_varlen_q=cu_seqlens_q is not None or seqused_q is not None,
|
| 869 |
+
disable_bitmask=disable_sparse_kv_bitmask,
|
| 870 |
+
)
|
| 871 |
+
else:
|
| 872 |
+
if use_dedicated_hd256_kernel:
|
| 873 |
+
# hd=256 2CTA forward: check for currently unsupported features
|
| 874 |
+
assert softcap is None, "SM100 forward with head_dim=256 does not support softcap"
|
| 875 |
+
assert not use_block_sparsity, \
|
| 876 |
+
"SM100 forward with head_dim=256 does not support block sparsity"
|
| 877 |
+
assert learnable_sink is None, \
|
| 878 |
+
"SM100 forward with head_dim=256 does not support learnable_sink"
|
| 879 |
+
assert seqused_q is None, \
|
| 880 |
+
"SM100 forward with head_dim=256 does not support seqused_q"
|
| 881 |
+
if page_table is not None:
|
| 882 |
+
assert max_seqlen_k % page_size == 0, (
|
| 883 |
+
f"SM100 hd256 2CTA paged KV requires max_seqlen_k divisible by "
|
| 884 |
+
f"page_size ({page_size}), got max_seqlen_k={max_seqlen_k}"
|
| 885 |
+
)
|
| 886 |
+
assert page_table.shape[1] == max_seqlen_k // page_size, (
|
| 887 |
+
f"SM100 hd256 2CTA paged KV requires page_table.shape[1] == "
|
| 888 |
+
f"max_seqlen_k // page_size ({max_seqlen_k} // {page_size} = "
|
| 889 |
+
f"{max_seqlen_k // page_size}), got {page_table.shape[1]}; "
|
| 890 |
+
f"pass page_table[:, :{max_seqlen_k // page_size}] to slice to "
|
| 891 |
+
f"the actual sequence length"
|
| 892 |
+
)
|
| 893 |
+
assert page_table.stride(0) == page_table.shape[1], (
|
| 894 |
+
f"SM100 hd256 2CTA paged KV requires a fully contiguous page_table "
|
| 895 |
+
f"(stride(0)={page_table.stride(0)} must equal "
|
| 896 |
+
f"shape[1]={page_table.shape[1]})"
|
| 897 |
+
)
|
| 898 |
+
# pack_gqa is an auto-selected optimization; disable it for hd256 kernel
|
| 899 |
+
pack_gqa = False
|
| 900 |
+
|
| 901 |
+
flash_fwd_obj_cls = (
|
| 902 |
+
BlackwellFusedMultiHeadAttentionForward
|
| 903 |
+
if use_dedicated_hd256_kernel
|
| 904 |
+
else FlashAttentionForwardSm100
|
| 905 |
+
)
|
| 906 |
+
|
| 907 |
+
fa_fwd = flash_fwd_obj_cls(
|
| 908 |
+
head_dim,
|
| 909 |
+
head_dim_v,
|
| 910 |
+
qhead_per_kvhead=qhead_per_kvhead,
|
| 911 |
+
is_causal=causal,
|
| 912 |
+
is_local=local,
|
| 913 |
+
is_split_kv=is_split_kv,
|
| 914 |
+
pack_gqa=pack_gqa,
|
| 915 |
+
m_block_size=tile_m,
|
| 916 |
+
n_block_size=tile_n,
|
| 917 |
+
q_stage=q_stage,
|
| 918 |
+
is_persistent=not causal
|
| 919 |
+
and not local
|
| 920 |
+
and cu_seqlens_q is None
|
| 921 |
+
and seqused_q is None
|
| 922 |
+
and not is_split_kv,
|
| 923 |
+
score_mod=score_mod,
|
| 924 |
+
mask_mod=mask_mod,
|
| 925 |
+
has_aux_tensors=aux_tensors is not None,
|
| 926 |
+
paged_kv_non_tma=page_size not in [None, tile_n],
|
| 927 |
+
is_varlen_q=cu_seqlens_q is not None or seqused_q is not None,
|
| 928 |
+
q_subtile_factor=q_subtile_factor,
|
| 929 |
+
use_2cta_instrs=use_2cta_instrs,
|
| 930 |
+
use_clc_scheduler=use_clc_scheduler,
|
| 931 |
+
)
|
| 932 |
+
elif arch // 10 == 12:
|
| 933 |
+
# SM120 (Blackwell GeForce / DGX Spark): uses SM80 MMA with SM120 SMEM capacity
|
| 934 |
+
assert not use_block_sparsity, "Block sparsity not supported on SM 12.0"
|
| 935 |
+
assert page_table is None, "Paged KV not supported on SM 12.0 in this PR"
|
| 936 |
+
assert not is_split_kv, "SplitKV not supported on SM 12.0 in this PR"
|
| 937 |
+
fa_fwd = FlashAttentionForwardSm120(
|
| 938 |
+
dtype,
|
| 939 |
+
head_dim,
|
| 940 |
+
head_dim_v,
|
| 941 |
+
qhead_per_kvhead,
|
| 942 |
+
is_causal=causal,
|
| 943 |
+
is_local=local,
|
| 944 |
+
pack_gqa=pack_gqa,
|
| 945 |
+
tile_m=tile_m,
|
| 946 |
+
tile_n=tile_n,
|
| 947 |
+
num_stages=1,
|
| 948 |
+
num_threads=num_threads,
|
| 949 |
+
Q_in_regs=False,
|
| 950 |
+
score_mod=score_mod,
|
| 951 |
+
mask_mod=mask_mod,
|
| 952 |
+
has_aux_tensors=aux_tensors is not None,
|
| 953 |
+
)
|
| 954 |
+
else:
|
| 955 |
+
raise ValueError(
|
| 956 |
+
f"Unsupported compute capability: {arch}. Supported: 8.x, 9.x, 10.x, 11.x, 12.x"
|
| 957 |
+
)
|
| 958 |
+
# TODO: check @can_implement
|
| 959 |
+
if qv is not None:
|
| 960 |
+
_flash_attn_fwd.compile_cache[compile_key] = cute.compile(
|
| 961 |
+
fa_fwd,
|
| 962 |
+
q_tensor,
|
| 963 |
+
qv_tensor,
|
| 964 |
+
k_tensor,
|
| 965 |
+
v_tensor,
|
| 966 |
+
o_tensor,
|
| 967 |
+
lse_tensor,
|
| 968 |
+
softmax_scale,
|
| 969 |
+
cu_seqlens_q_tensor,
|
| 970 |
+
cu_seqlens_k_tensor,
|
| 971 |
+
seqused_q_tensor,
|
| 972 |
+
seqused_k_tensor,
|
| 973 |
+
gather_kv_indices_tensor,
|
| 974 |
+
page_table_tensor,
|
| 975 |
+
window_size_left,
|
| 976 |
+
window_size_right,
|
| 977 |
+
current_stream,
|
| 978 |
+
options="--enable-tvm-ffi",
|
| 979 |
+
)
|
| 980 |
+
else:
|
| 981 |
+
compile_args = [
|
| 982 |
+
fa_fwd,
|
| 983 |
+
q_tensor,
|
| 984 |
+
k_tensor,
|
| 985 |
+
v_tensor,
|
| 986 |
+
o_tensor,
|
| 987 |
+
lse_tensor,
|
| 988 |
+
softmax_scale,
|
| 989 |
+
cu_seqlens_q_tensor,
|
| 990 |
+
cu_seqlens_k_tensor,
|
| 991 |
+
seqused_q_tensor,
|
| 992 |
+
seqused_k_tensor,
|
| 993 |
+
page_table_tensor,
|
| 994 |
+
window_size_left,
|
| 995 |
+
window_size_right,
|
| 996 |
+
learnable_sink_tensor,
|
| 997 |
+
sparse_tensors,
|
| 998 |
+
cute_aux_tensors,
|
| 999 |
+
current_stream,
|
| 1000 |
+
]
|
| 1001 |
+
if arch // 10 in [10, 11]:
|
| 1002 |
+
compile_args.insert(-3, descale_tensors_tensor)
|
| 1003 |
+
_flash_attn_fwd.compile_cache[compile_key] = cute.compile(*compile_args, options="--enable-tvm-ffi")
|
| 1004 |
+
|
| 1005 |
+
if not is_fake_mode():
|
| 1006 |
+
q_call, k_call, v_call = q.detach(), k.detach(), v.detach()
|
| 1007 |
+
qv_call = qv.detach() if qv is not None else None
|
| 1008 |
+
if is_fp8:
|
| 1009 |
+
# need uint8 workaround until we pin torch >= 2.11.0 where fp8 export is supported
|
| 1010 |
+
q_call = q_call.view(torch.uint8)
|
| 1011 |
+
k_call = k_call.view(torch.uint8)
|
| 1012 |
+
v_call = v_call.view(torch.uint8)
|
| 1013 |
+
if qv_call is not None:
|
| 1014 |
+
qv_call = qv_call.view(torch.uint8)
|
| 1015 |
+
descale_tensors = (
|
| 1016 |
+
DescaleTensors(q_descale=q_descale, k_descale=k_descale, v_descale=v_descale)
|
| 1017 |
+
if q_descale is not None or k_descale is not None or v_descale is not None
|
| 1018 |
+
else None
|
| 1019 |
+
)
|
| 1020 |
+
if qv is not None:
|
| 1021 |
+
_flash_attn_fwd.compile_cache[compile_key](
|
| 1022 |
+
q_call,
|
| 1023 |
+
qv_call,
|
| 1024 |
+
k_call,
|
| 1025 |
+
v_call,
|
| 1026 |
+
out.detach(),
|
| 1027 |
+
lse,
|
| 1028 |
+
softmax_scale,
|
| 1029 |
+
cu_seqlens_q,
|
| 1030 |
+
cu_seqlens_k,
|
| 1031 |
+
seqused_q,
|
| 1032 |
+
seqused_k,
|
| 1033 |
+
gather_kv_indices,
|
| 1034 |
+
page_table,
|
| 1035 |
+
window_size_left,
|
| 1036 |
+
window_size_right,
|
| 1037 |
+
)
|
| 1038 |
+
else:
|
| 1039 |
+
call_args = [
|
| 1040 |
+
q_call,
|
| 1041 |
+
k_call,
|
| 1042 |
+
v_call,
|
| 1043 |
+
out.detach() if not is_split_kv else out_partial,
|
| 1044 |
+
lse_partial if is_split_kv else lse,
|
| 1045 |
+
softmax_scale,
|
| 1046 |
+
cu_seqlens_q,
|
| 1047 |
+
cu_seqlens_k,
|
| 1048 |
+
seqused_q,
|
| 1049 |
+
seqused_k,
|
| 1050 |
+
page_table,
|
| 1051 |
+
window_size_left,
|
| 1052 |
+
window_size_right,
|
| 1053 |
+
learnable_sink,
|
| 1054 |
+
]
|
| 1055 |
+
if arch // 10 in [10, 11]:
|
| 1056 |
+
call_args.append(descale_tensors)
|
| 1057 |
+
call_args.extend([
|
| 1058 |
+
(
|
| 1059 |
+
normalized_block_sparse_tensors.mask_block_cnt,
|
| 1060 |
+
normalized_block_sparse_tensors.mask_block_idx,
|
| 1061 |
+
normalized_block_sparse_tensors.full_block_cnt,
|
| 1062 |
+
normalized_block_sparse_tensors.full_block_idx,
|
| 1063 |
+
normalized_block_sparse_tensors.dq_write_order,
|
| 1064 |
+
normalized_block_sparse_tensors.dq_write_order_full,
|
| 1065 |
+
)
|
| 1066 |
+
if normalized_block_sparse_tensors is not None
|
| 1067 |
+
else None,
|
| 1068 |
+
aux_tensors,
|
| 1069 |
+
])
|
| 1070 |
+
_flash_attn_fwd.compile_cache[compile_key](*call_args)
|
| 1071 |
+
if is_split_kv:
|
| 1072 |
+
_flash_attn_fwd_combine(
|
| 1073 |
+
out_partial,
|
| 1074 |
+
lse_partial.transpose(-1, -2),
|
| 1075 |
+
out,
|
| 1076 |
+
lse.transpose(-1, -2) if lse is not None else None,
|
| 1077 |
+
cu_seqlens_q,
|
| 1078 |
+
seqused_q,
|
| 1079 |
+
)
|
| 1080 |
+
return out, lse
|
| 1081 |
+
|
| 1082 |
+
|
| 1083 |
+
_flash_attn_fwd.compile_cache = get_jit_cache("fwd")
|
| 1084 |
+
|
| 1085 |
+
|
| 1086 |
+
class FlashAttnFunc(torch.autograd.Function):
|
| 1087 |
+
@staticmethod
|
| 1088 |
+
def forward(
|
| 1089 |
+
ctx,
|
| 1090 |
+
q: torch.Tensor,
|
| 1091 |
+
k: torch.Tensor,
|
| 1092 |
+
v: torch.Tensor,
|
| 1093 |
+
qv: Optional[torch.Tensor] = None,
|
| 1094 |
+
gather_kv_indices: Optional[torch.Tensor] = None,
|
| 1095 |
+
softmax_scale: Optional[float] = None,
|
| 1096 |
+
causal: bool = False,
|
| 1097 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1098 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1099 |
+
softcap: float = 0.0,
|
| 1100 |
+
num_splits: int = 1,
|
| 1101 |
+
pack_gqa: Optional[bool] = None,
|
| 1102 |
+
deterministic: bool = False,
|
| 1103 |
+
score_mod: Optional[Callable] = None,
|
| 1104 |
+
score_mod_bwd: Optional[Callable] = None,
|
| 1105 |
+
mask_mod: Optional[Callable] = None,
|
| 1106 |
+
aux_tensors: Optional[list] = None,
|
| 1107 |
+
block_sparse_tensors: Optional[BlockSparseTensorsTorch] = None,
|
| 1108 |
+
block_sparse_tensors_bwd: Optional[BlockSparseTensorsTorch] = None,
|
| 1109 |
+
return_lse: bool = False,
|
| 1110 |
+
):
|
| 1111 |
+
out, lse = _flash_attn_fwd(
|
| 1112 |
+
q,
|
| 1113 |
+
k,
|
| 1114 |
+
v,
|
| 1115 |
+
qv=qv,
|
| 1116 |
+
softmax_scale=softmax_scale,
|
| 1117 |
+
causal=causal,
|
| 1118 |
+
window_size_left=window_size[0],
|
| 1119 |
+
window_size_right=window_size[1],
|
| 1120 |
+
learnable_sink=learnable_sink,
|
| 1121 |
+
softcap=softcap,
|
| 1122 |
+
num_splits=num_splits,
|
| 1123 |
+
pack_gqa=pack_gqa,
|
| 1124 |
+
score_mod=score_mod,
|
| 1125 |
+
mask_mod=mask_mod,
|
| 1126 |
+
aux_tensors=aux_tensors,
|
| 1127 |
+
block_sparse_tensors=block_sparse_tensors,
|
| 1128 |
+
return_lse=return_lse,
|
| 1129 |
+
gather_kv_indices=gather_kv_indices,
|
| 1130 |
+
)
|
| 1131 |
+
ctx.save_for_backward(q, k, v, out, lse, *(aux_tensors or ()))
|
| 1132 |
+
ctx.softmax_scale = softmax_scale
|
| 1133 |
+
ctx.causal = causal
|
| 1134 |
+
ctx.window_size = window_size
|
| 1135 |
+
ctx.softcap = softcap
|
| 1136 |
+
ctx.deterministic = deterministic
|
| 1137 |
+
ctx.return_lse = return_lse
|
| 1138 |
+
ctx.score_mod = score_mod
|
| 1139 |
+
ctx.score_mod_bwd = score_mod_bwd
|
| 1140 |
+
ctx.mask_mod = mask_mod
|
| 1141 |
+
ctx.block_sparse_tensors_bwd = block_sparse_tensors_bwd
|
| 1142 |
+
ctx.set_materialize_grads(False)
|
| 1143 |
+
return out, lse
|
| 1144 |
+
|
| 1145 |
+
@staticmethod
|
| 1146 |
+
def backward(ctx, dout, dlse):
|
| 1147 |
+
raise NotImplementedError(
|
| 1148 |
+
"FlashRT's FA4 vendor is forward / inference-only; the backward "
|
| 1149 |
+
"kernels were removed (see VENDOR.md)."
|
| 1150 |
+
)
|
| 1151 |
+
|
| 1152 |
+
|
| 1153 |
+
class FlashAttnVarlenFunc(torch.autograd.Function):
|
| 1154 |
+
@staticmethod
|
| 1155 |
+
def forward(
|
| 1156 |
+
ctx,
|
| 1157 |
+
q: torch.Tensor,
|
| 1158 |
+
k: torch.Tensor,
|
| 1159 |
+
v: torch.Tensor,
|
| 1160 |
+
qv: Optional[torch.Tensor] = None,
|
| 1161 |
+
cu_seqlens_q: Optional[torch.Tensor] = None,
|
| 1162 |
+
cu_seqlens_k: Optional[torch.Tensor] = None,
|
| 1163 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 1164 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 1165 |
+
max_seqlen_q: Optional[int] = None,
|
| 1166 |
+
max_seqlen_k: Optional[int] = None,
|
| 1167 |
+
min_seqlen_k: Optional[int] = None,
|
| 1168 |
+
gather_kv_indices: Optional[torch.Tensor] = None,
|
| 1169 |
+
page_table: Optional[torch.Tensor] = None,
|
| 1170 |
+
softmax_scale: Optional[float] = None,
|
| 1171 |
+
causal: bool = False,
|
| 1172 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1173 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1174 |
+
softcap: float = 0.0,
|
| 1175 |
+
num_splits: int = 1,
|
| 1176 |
+
pack_gqa: Optional[bool] = None,
|
| 1177 |
+
deterministic: bool = False,
|
| 1178 |
+
score_mod: Optional[Callable] = None,
|
| 1179 |
+
score_mod_bwd: Optional[Callable] = None,
|
| 1180 |
+
aux_tensors: Optional[list] = None,
|
| 1181 |
+
return_lse: bool = False,
|
| 1182 |
+
):
|
| 1183 |
+
out, lse = _flash_attn_fwd(
|
| 1184 |
+
q,
|
| 1185 |
+
k,
|
| 1186 |
+
v,
|
| 1187 |
+
qv=qv,
|
| 1188 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 1189 |
+
cu_seqlens_k=cu_seqlens_k,
|
| 1190 |
+
seqused_q=seqused_q,
|
| 1191 |
+
seqused_k=seqused_k,
|
| 1192 |
+
max_seqlen_q=max_seqlen_q,
|
| 1193 |
+
max_seqlen_k=max_seqlen_k,
|
| 1194 |
+
min_seqlen_k=min_seqlen_k,
|
| 1195 |
+
page_table=page_table,
|
| 1196 |
+
softmax_scale=softmax_scale,
|
| 1197 |
+
causal=causal,
|
| 1198 |
+
window_size_left=window_size[0],
|
| 1199 |
+
window_size_right=window_size[1],
|
| 1200 |
+
learnable_sink=learnable_sink,
|
| 1201 |
+
softcap=softcap,
|
| 1202 |
+
num_splits=num_splits,
|
| 1203 |
+
pack_gqa=pack_gqa,
|
| 1204 |
+
score_mod=score_mod,
|
| 1205 |
+
aux_tensors=aux_tensors,
|
| 1206 |
+
return_lse=return_lse,
|
| 1207 |
+
gather_kv_indices=gather_kv_indices,
|
| 1208 |
+
)
|
| 1209 |
+
ctx.save_for_backward(
|
| 1210 |
+
q,
|
| 1211 |
+
k,
|
| 1212 |
+
v,
|
| 1213 |
+
out,
|
| 1214 |
+
lse,
|
| 1215 |
+
cu_seqlens_q,
|
| 1216 |
+
cu_seqlens_k,
|
| 1217 |
+
seqused_q,
|
| 1218 |
+
seqused_k,
|
| 1219 |
+
*(aux_tensors or ()),
|
| 1220 |
+
)
|
| 1221 |
+
ctx.softmax_scale = softmax_scale
|
| 1222 |
+
ctx.causal = causal
|
| 1223 |
+
ctx.window_size = window_size
|
| 1224 |
+
ctx.softcap = softcap
|
| 1225 |
+
ctx.deterministic = deterministic
|
| 1226 |
+
ctx.max_seqlen_q = max_seqlen_q
|
| 1227 |
+
ctx.max_seqlen_k = max_seqlen_k
|
| 1228 |
+
ctx.return_lse = return_lse
|
| 1229 |
+
ctx.score_mod = score_mod
|
| 1230 |
+
ctx.score_mod_bwd = score_mod_bwd
|
| 1231 |
+
ctx.set_materialize_grads(False)
|
| 1232 |
+
return out, lse
|
| 1233 |
+
|
| 1234 |
+
@staticmethod
|
| 1235 |
+
def backward(ctx, dout, dlse):
|
| 1236 |
+
raise NotImplementedError(
|
| 1237 |
+
"FlashRT's FA4 vendor is forward / inference-only; the backward "
|
| 1238 |
+
"kernels were removed (see VENDOR.md)."
|
| 1239 |
+
)
|
| 1240 |
+
|
| 1241 |
+
|
| 1242 |
+
def flash_attn_func(
|
| 1243 |
+
q: torch.Tensor,
|
| 1244 |
+
k: torch.Tensor,
|
| 1245 |
+
v: torch.Tensor,
|
| 1246 |
+
qv: Optional[torch.Tensor] = None,
|
| 1247 |
+
gather_kv_indices: Optional[torch.Tensor] = None,
|
| 1248 |
+
softmax_scale: Optional[float] = None,
|
| 1249 |
+
causal: bool = False,
|
| 1250 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1251 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1252 |
+
softcap: float = 0.0,
|
| 1253 |
+
num_splits: int = 1,
|
| 1254 |
+
pack_gqa: Optional[bool] = None,
|
| 1255 |
+
deterministic: bool = False,
|
| 1256 |
+
score_mod: Optional[Callable] = None,
|
| 1257 |
+
score_mod_bwd: Optional[Callable] = None,
|
| 1258 |
+
mask_mod: Optional[Callable] = None,
|
| 1259 |
+
aux_tensors: Optional[list] = None,
|
| 1260 |
+
block_sparse_tensors: Optional[BlockSparseTensorsTorch] = None,
|
| 1261 |
+
block_sparse_tensors_bwd: Optional[BlockSparseTensorsTorch] = None,
|
| 1262 |
+
return_lse: bool = False,
|
| 1263 |
+
):
|
| 1264 |
+
return FlashAttnFunc.apply(
|
| 1265 |
+
q,
|
| 1266 |
+
k,
|
| 1267 |
+
v,
|
| 1268 |
+
qv,
|
| 1269 |
+
gather_kv_indices,
|
| 1270 |
+
softmax_scale,
|
| 1271 |
+
causal,
|
| 1272 |
+
window_size,
|
| 1273 |
+
learnable_sink,
|
| 1274 |
+
softcap,
|
| 1275 |
+
num_splits,
|
| 1276 |
+
pack_gqa,
|
| 1277 |
+
deterministic,
|
| 1278 |
+
score_mod,
|
| 1279 |
+
score_mod_bwd,
|
| 1280 |
+
mask_mod,
|
| 1281 |
+
aux_tensors,
|
| 1282 |
+
block_sparse_tensors,
|
| 1283 |
+
block_sparse_tensors_bwd,
|
| 1284 |
+
return_lse,
|
| 1285 |
+
)
|
| 1286 |
+
|
| 1287 |
+
|
| 1288 |
+
def flash_attn_varlen_func(
|
| 1289 |
+
q: torch.Tensor,
|
| 1290 |
+
k: torch.Tensor,
|
| 1291 |
+
v: torch.Tensor,
|
| 1292 |
+
qv: Optional[torch.Tensor] = None,
|
| 1293 |
+
cu_seqlens_q: Optional[torch.Tensor] = None,
|
| 1294 |
+
cu_seqlens_k: Optional[torch.Tensor] = None,
|
| 1295 |
+
max_seqlen_q: Optional[int] = None,
|
| 1296 |
+
max_seqlen_k: Optional[int] = None,
|
| 1297 |
+
min_seqlen_k: Optional[int] = None,
|
| 1298 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 1299 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 1300 |
+
gather_kv_indices: Optional[torch.Tensor] = None,
|
| 1301 |
+
page_table: Optional[torch.Tensor] = None,
|
| 1302 |
+
softmax_scale: Optional[float] = None,
|
| 1303 |
+
causal: bool = False,
|
| 1304 |
+
window_size: Tuple[Optional[int], Optional[int]] = (None, None),
|
| 1305 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 1306 |
+
softcap: float = 0.0,
|
| 1307 |
+
num_splits: int = 1,
|
| 1308 |
+
pack_gqa: Optional[bool] = None,
|
| 1309 |
+
deterministic: bool = False,
|
| 1310 |
+
score_mod: Optional[Callable] = None,
|
| 1311 |
+
score_mod_bwd: Optional[Callable] = None,
|
| 1312 |
+
aux_tensors: Optional[list] = None,
|
| 1313 |
+
return_lse: bool = False,
|
| 1314 |
+
):
|
| 1315 |
+
"""
|
| 1316 |
+
Explanation of some optional arguments:
|
| 1317 |
+
|
| 1318 |
+
qv: we write the MLA weight absorbed formula as
|
| 1319 |
+
O = softmax(scale * (Q @ K.T + Qv @ V.T)) @ V
|
| 1320 |
+
where Q = q_pe, Qv = q_nope, K = pe_cache, V = kv_cache.
|
| 1321 |
+
|
| 1322 |
+
gather_kv_indices: a tensor of shape (batch, seqlen_q, gather_kv_length) or
|
| 1323 |
+
(total_q, gather_kv_length) if there is cu_seqlens_q.
|
| 1324 |
+
Currently, only used for topk sparsity with MLA absorption kernel.
|
| 1325 |
+
|
| 1326 |
+
min_seqlen_k: for varlen, specifies the minimum kv sequence length for any batch.
|
| 1327 |
+
Used with gather_kv_indices to determine if we need oob masking.
|
| 1328 |
+
"""
|
| 1329 |
+
return FlashAttnVarlenFunc.apply(
|
| 1330 |
+
q,
|
| 1331 |
+
k,
|
| 1332 |
+
v,
|
| 1333 |
+
qv,
|
| 1334 |
+
cu_seqlens_q,
|
| 1335 |
+
cu_seqlens_k,
|
| 1336 |
+
seqused_q,
|
| 1337 |
+
seqused_k,
|
| 1338 |
+
max_seqlen_q,
|
| 1339 |
+
max_seqlen_k,
|
| 1340 |
+
min_seqlen_k,
|
| 1341 |
+
gather_kv_indices,
|
| 1342 |
+
page_table,
|
| 1343 |
+
softmax_scale,
|
| 1344 |
+
causal,
|
| 1345 |
+
window_size,
|
| 1346 |
+
learnable_sink,
|
| 1347 |
+
softcap,
|
| 1348 |
+
num_splits,
|
| 1349 |
+
pack_gqa,
|
| 1350 |
+
deterministic,
|
| 1351 |
+
score_mod,
|
| 1352 |
+
score_mod_bwd,
|
| 1353 |
+
aux_tensors,
|
| 1354 |
+
return_lse,
|
| 1355 |
+
)
|
| 1356 |
+
|
| 1357 |
+
|
| 1358 |
+
def _compile_fwd_combine(
|
| 1359 |
+
dtype, dtype_partial, head_dim, tile_m, k_block_size, log_max_splits,
|
| 1360 |
+
has_cu_seqlens, has_seqused, has_lse, has_varlen_batch_idx,
|
| 1361 |
+
):
|
| 1362 |
+
"""Compile fwd combine kernel using cute fake tensors (no real GPU tensors needed)."""
|
| 1363 |
+
sym = cute.sym_int
|
| 1364 |
+
div = 128 // dtype_partial.width # 16-byte alignment in elements
|
| 1365 |
+
|
| 1366 |
+
fa_combine = FlashAttentionForwardCombine(
|
| 1367 |
+
dtype=dtype,
|
| 1368 |
+
dtype_partial=dtype_partial,
|
| 1369 |
+
head_dim=head_dim,
|
| 1370 |
+
tile_m=tile_m,
|
| 1371 |
+
k_block_size=k_block_size,
|
| 1372 |
+
log_max_splits=log_max_splits,
|
| 1373 |
+
)
|
| 1374 |
+
if not fa_combine.can_implement(
|
| 1375 |
+
dtype, dtype_partial, head_dim, tile_m, k_block_size, log_max_splits,
|
| 1376 |
+
num_threads=256,
|
| 1377 |
+
):
|
| 1378 |
+
raise RuntimeError(
|
| 1379 |
+
"FlashAttention combine kernel cannot be implemented with given parameters"
|
| 1380 |
+
)
|
| 1381 |
+
|
| 1382 |
+
if has_cu_seqlens:
|
| 1383 |
+
# Varlen: (num_splits, total_q, nheads, headdim)
|
| 1384 |
+
num_splits, total_q, nheads = sym(), sym(), sym()
|
| 1385 |
+
mO_partial = fake_tensor(dtype_partial, (num_splits, total_q, nheads, head_dim), divisibility=div)
|
| 1386 |
+
mLSE_partial = fake_tensor(Float32, (num_splits, total_q, nheads), divisibility=1, leading_dim=1)
|
| 1387 |
+
mO = fake_tensor(dtype, (total_q, nheads, head_dim), divisibility=div)
|
| 1388 |
+
mLSE = fake_tensor(Float32, (total_q, nheads), divisibility=1, leading_dim=0) if has_lse else None
|
| 1389 |
+
else:
|
| 1390 |
+
# Batched: (num_splits, batch, seqlen, nheads, headdim)
|
| 1391 |
+
num_splits, batch, seqlen, nheads = sym(), sym(), sym(), sym()
|
| 1392 |
+
mO_partial = fake_tensor(dtype_partial, (num_splits, batch, seqlen, nheads, head_dim), divisibility=div)
|
| 1393 |
+
mLSE_partial = fake_tensor(Float32, (num_splits, batch, seqlen, nheads), divisibility=1, leading_dim=2)
|
| 1394 |
+
mO = fake_tensor(dtype, (batch, seqlen, nheads, head_dim), divisibility=div)
|
| 1395 |
+
mLSE = fake_tensor(Float32, (batch, seqlen, nheads), divisibility=1, leading_dim=1) if has_lse else None
|
| 1396 |
+
batch = mO_partial.shape[1]
|
| 1397 |
+
|
| 1398 |
+
batch_for_1d = batch if not has_cu_seqlens else sym()
|
| 1399 |
+
batchp1 = sym()
|
| 1400 |
+
mCuSeqlens = fake_tensor(Int32, (batchp1,), divisibility=1) if has_cu_seqlens else None
|
| 1401 |
+
mSeqused = fake_tensor(Int32, (batch_for_1d,), divisibility=1) if has_seqused else None
|
| 1402 |
+
mNumSplitsDynamic = None # Not parametrized in compile_key
|
| 1403 |
+
mVarlenBatchIdx = fake_tensor(Int32, (batch_for_1d,), divisibility=1) if has_varlen_batch_idx else None
|
| 1404 |
+
mSemaphore = None # Not parametrized in compile_key
|
| 1405 |
+
|
| 1406 |
+
return cute.compile(
|
| 1407 |
+
fa_combine,
|
| 1408 |
+
mO_partial, mLSE_partial, mO, mLSE,
|
| 1409 |
+
mCuSeqlens, mSeqused, mNumSplitsDynamic, mVarlenBatchIdx, mSemaphore,
|
| 1410 |
+
cute.runtime.make_fake_stream(use_tvm_ffi_env_stream=True),
|
| 1411 |
+
options="--enable-tvm-ffi",
|
| 1412 |
+
)
|
| 1413 |
+
|
| 1414 |
+
|
| 1415 |
+
def _flash_attn_fwd_combine(
|
| 1416 |
+
out_partial: torch.Tensor,
|
| 1417 |
+
lse_partial: torch.Tensor,
|
| 1418 |
+
out: torch.Tensor,
|
| 1419 |
+
lse: Optional[torch.Tensor] = None,
|
| 1420 |
+
cu_seqlens: Optional[torch.Tensor] = None,
|
| 1421 |
+
seqused: Optional[torch.Tensor] = None,
|
| 1422 |
+
num_splits_dynamic_ptr: Optional[torch.Tensor] = None,
|
| 1423 |
+
varlen_batch_idx: Optional[torch.Tensor] = None,
|
| 1424 |
+
semaphore_to_reset: Optional[torch.Tensor] = None,
|
| 1425 |
+
) -> None:
|
| 1426 |
+
"""Forward combine kernel for split attention computation.
|
| 1427 |
+
|
| 1428 |
+
Combines partial outputs and log-sum-exp values from multiple splits
|
| 1429 |
+
of attention computation into final outputs.
|
| 1430 |
+
|
| 1431 |
+
Args:
|
| 1432 |
+
out_partial: Partial outputs tensor (num_splits, batch, seqlen, nheads, headdim) or
|
| 1433 |
+
(num_splits, total_q, nheads, headdim) if there's cu_seqlens
|
| 1434 |
+
lse_partial: Partial LSE tensor (num_splits, batch, seqlen, nheads) or
|
| 1435 |
+
(num_splits, total_q, nheads) if there's cu_seqlens
|
| 1436 |
+
out: Output tensor (batch, seqlen, nheads, headdim) or (total_q, nheads, headdim) if there's cu_seqlens
|
| 1437 |
+
lse: Output LSE tensor (batch, seqlen, nheads) or (total_q, nheads) if there's cu_seqlens.
|
| 1438 |
+
cu_seqlens: Cumulative sequence lengths for variable length sequences
|
| 1439 |
+
seqused: Used sequence lengths for each batch
|
| 1440 |
+
num_splits_dynamic_ptr: Dynamic number of splits per batch
|
| 1441 |
+
semaphore_to_reset: Semaphore for synchronization
|
| 1442 |
+
k_block_size: Block size for head dimension
|
| 1443 |
+
|
| 1444 |
+
Returns:
|
| 1445 |
+
None
|
| 1446 |
+
"""
|
| 1447 |
+
assert out_partial.dtype in [torch.float16, torch.bfloat16, torch.float32], (
|
| 1448 |
+
"out_partial must be fp16, bf16, or fp32"
|
| 1449 |
+
)
|
| 1450 |
+
if not is_fake_mode():
|
| 1451 |
+
assert out_partial.is_cuda and lse_partial.is_cuda, "tensors must be on CUDA device"
|
| 1452 |
+
# Determine if this is variable length based on dimensions
|
| 1453 |
+
is_varlen = out_partial.dim() == 4
|
| 1454 |
+
# Validate optional tensors
|
| 1455 |
+
for t, name in [
|
| 1456 |
+
(cu_seqlens, "cu_seqlens"),
|
| 1457 |
+
(seqused, "seqused"),
|
| 1458 |
+
(num_splits_dynamic_ptr, "num_splits_dynamic_ptr"),
|
| 1459 |
+
]:
|
| 1460 |
+
if t is not None:
|
| 1461 |
+
if not is_fake_mode():
|
| 1462 |
+
assert t.is_cuda, f"{name} must be on CUDA device"
|
| 1463 |
+
assert t.is_contiguous(), f"{name} must be contiguous"
|
| 1464 |
+
head_dim = out_partial.shape[-1]
|
| 1465 |
+
num_splits = out_partial.shape[0]
|
| 1466 |
+
assert num_splits <= 256
|
| 1467 |
+
# If hdim is 96 or 192, it's faster to round them to 128 or 256 respectively
|
| 1468 |
+
# so that kBlockM is smaller and we have more parallelism.
|
| 1469 |
+
k_block_size = 64 if head_dim <= 64 else 128
|
| 1470 |
+
# We want kBlockM to be as small as possible to maximize parallelism.
|
| 1471 |
+
# E.g., if hdim is 64, we want kBlockM to be 16 so that we can use 256 threads, each reading 4 elements (floats).
|
| 1472 |
+
tile_m = 8 if k_block_size % 128 == 0 else (16 if k_block_size % 64 == 0 else 32)
|
| 1473 |
+
log_max_splits = max(math.ceil(math.log2(num_splits)), 4)
|
| 1474 |
+
if tile_m == 8:
|
| 1475 |
+
# If kBlockM == 8 then the minimum number of splits is 32.
|
| 1476 |
+
# TODO: we can deal w this by using 128 threads instead
|
| 1477 |
+
log_max_splits = max(log_max_splits, 5)
|
| 1478 |
+
|
| 1479 |
+
# Create combine kernel configuration
|
| 1480 |
+
dtype = torch2cute_dtype_map[out.dtype]
|
| 1481 |
+
dtype_partial = torch2cute_dtype_map[out_partial.dtype]
|
| 1482 |
+
compile_key = (
|
| 1483 |
+
dtype,
|
| 1484 |
+
dtype_partial,
|
| 1485 |
+
head_dim,
|
| 1486 |
+
tile_m,
|
| 1487 |
+
k_block_size,
|
| 1488 |
+
log_max_splits,
|
| 1489 |
+
cu_seqlens is not None,
|
| 1490 |
+
seqused is not None,
|
| 1491 |
+
lse is not None,
|
| 1492 |
+
varlen_batch_idx is not None,
|
| 1493 |
+
)
|
| 1494 |
+
if compile_key not in _flash_attn_fwd_combine.compile_cache:
|
| 1495 |
+
_flash_attn_fwd_combine.compile_cache[compile_key] = _compile_fwd_combine(
|
| 1496 |
+
*compile_key
|
| 1497 |
+
)
|
| 1498 |
+
if not is_fake_mode():
|
| 1499 |
+
_flash_attn_fwd_combine.compile_cache[compile_key](
|
| 1500 |
+
out_partial, lse_partial, out, lse,
|
| 1501 |
+
cu_seqlens, seqused, num_splits_dynamic_ptr, varlen_batch_idx,
|
| 1502 |
+
semaphore_to_reset,
|
| 1503 |
+
)
|
| 1504 |
+
|
| 1505 |
+
|
| 1506 |
+
_flash_attn_fwd_combine.compile_cache = get_jit_cache("fwd_combine")
|
| 1507 |
+
|
| 1508 |
+
|
| 1509 |
+
def flash_attn_combine(
|
| 1510 |
+
out_partial: torch.Tensor,
|
| 1511 |
+
lse_partial: torch.Tensor,
|
| 1512 |
+
out: Optional[torch.Tensor] = None,
|
| 1513 |
+
out_dtype: Optional[torch.dtype] = None,
|
| 1514 |
+
cu_seqlens: Optional[torch.Tensor] = None,
|
| 1515 |
+
seqused: Optional[torch.Tensor] = None,
|
| 1516 |
+
varlen_batch_idx: Optional[torch.Tensor] = None,
|
| 1517 |
+
return_lse: bool = True,
|
| 1518 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 1519 |
+
"""Flash Attention combine function for split attention computation.
|
| 1520 |
+
|
| 1521 |
+
Combines partial outputs and log-sum-exp values from multiple splits
|
| 1522 |
+
of attention computation into final outputs. This is the main user-facing
|
| 1523 |
+
interface for the combine kernel.
|
| 1524 |
+
|
| 1525 |
+
Args:
|
| 1526 |
+
out_partial: Partial outputs tensor with shape:
|
| 1527 |
+
- (num_splits, batch_size, seqlen, num_heads, head_size) for regular batched input
|
| 1528 |
+
- (num_splits, total_q, num_heads, head_size) for variable length input
|
| 1529 |
+
lse_partial: Partial LSE tensor with shape:
|
| 1530 |
+
- (num_splits, batch_size, seqlen, num_heads) for regular batched input
|
| 1531 |
+
- (num_splits, total_q, num_heads) for variable length input
|
| 1532 |
+
out: Optional output tensor. If None, will be created automatically.
|
| 1533 |
+
out_dtype: Optional output dtype. If None, will use fp16/bf16 based on input.
|
| 1534 |
+
cu_seqlens: Cumulative sequence lengths for variable length sequences
|
| 1535 |
+
seqused: Used sequence lengths for each batch
|
| 1536 |
+
varlen_batch_idx: Optional mapping from virtual batch index to real batch index
|
| 1537 |
+
(int32 tensor of shape (batch_size,)). Used by persistent tile schedulers
|
| 1538 |
+
that reorder batch processing for load balancing.
|
| 1539 |
+
return_lse: Whether to return the combined LSE tensor. Default is True.
|
| 1540 |
+
|
| 1541 |
+
Returns:
|
| 1542 |
+
Tuple of (out, lse) where:
|
| 1543 |
+
- out: Combined output tensor with shape (batch_size, seqlen, num_heads, head_size)
|
| 1544 |
+
or (total_q, num_heads, head_size) for varlen
|
| 1545 |
+
- lse: Combined log-sum-exp tensor with shape (batch_size, seqlen, num_heads)
|
| 1546 |
+
or (total_q, num_heads) for varlen. None if return_lse=False
|
| 1547 |
+
|
| 1548 |
+
Note:
|
| 1549 |
+
This function expects the input tensors to be in the format produced by
|
| 1550 |
+
split attention computation, where the first dimension is num_splits.
|
| 1551 |
+
The permuting from user format to kernel format is now done inside the kernel.
|
| 1552 |
+
"""
|
| 1553 |
+
# Input validation
|
| 1554 |
+
assert out_partial.dim() in [4, 5], "out_partial must have 4 or 5 dimensions"
|
| 1555 |
+
# Determine if this is variable length based on dimensions
|
| 1556 |
+
is_varlen = out_partial.dim() == 4
|
| 1557 |
+
if is_varlen:
|
| 1558 |
+
# Variable length: (num_splits, total_q, num_heads, head_size)
|
| 1559 |
+
num_splits, total_q, num_heads, head_size = out_partial.shape
|
| 1560 |
+
batch_size = 1 # Treat as single batch for varlen
|
| 1561 |
+
seqlen = total_q
|
| 1562 |
+
else:
|
| 1563 |
+
# Regular batched: (num_splits, batch_size, seqlen, num_heads, head_size)
|
| 1564 |
+
num_splits, batch_size, seqlen, num_heads, head_size = out_partial.shape
|
| 1565 |
+
# Determine output dtype
|
| 1566 |
+
if out_dtype is None:
|
| 1567 |
+
out_dtype = out_partial.dtype
|
| 1568 |
+
# Create output if not provided
|
| 1569 |
+
device = out_partial.device
|
| 1570 |
+
if out is None:
|
| 1571 |
+
if is_varlen:
|
| 1572 |
+
out = torch.empty(total_q, num_heads, head_size, dtype=out_dtype, device=device)
|
| 1573 |
+
else:
|
| 1574 |
+
out = torch.empty(
|
| 1575 |
+
batch_size, seqlen, num_heads, head_size, dtype=out_dtype, device=device
|
| 1576 |
+
)
|
| 1577 |
+
# Create lse output only if requested
|
| 1578 |
+
if return_lse:
|
| 1579 |
+
if is_varlen:
|
| 1580 |
+
lse = torch.empty(num_heads, total_q, dtype=torch.float32, device=device)
|
| 1581 |
+
else:
|
| 1582 |
+
lse = torch.empty(batch_size, num_heads, seqlen, dtype=torch.float32, device=device)
|
| 1583 |
+
lse = lse.transpose(-1, -2)
|
| 1584 |
+
else:
|
| 1585 |
+
lse = None
|
| 1586 |
+
_flash_attn_fwd_combine(
|
| 1587 |
+
out_partial,
|
| 1588 |
+
lse_partial,
|
| 1589 |
+
out,
|
| 1590 |
+
lse,
|
| 1591 |
+
cu_seqlens,
|
| 1592 |
+
seqused,
|
| 1593 |
+
varlen_batch_idx=varlen_batch_idx,
|
| 1594 |
+
)
|
| 1595 |
+
return out, lse
|
build/torch-cuda/flashrt_fa4/cute/mask.py
ADDED
|
@@ -0,0 +1,1470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Optional, Callable, TypeAlias, Tuple
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
import enum
|
| 6 |
+
|
| 7 |
+
import cutlass
|
| 8 |
+
import cutlass.cute as cute
|
| 9 |
+
from cutlass import Float32, Int32, Uint32, const_expr
|
| 10 |
+
from cutlass.cutlass_dsl import min as dsl_min
|
| 11 |
+
|
| 12 |
+
from fa4_cute_runtime.quack import layout_utils
|
| 13 |
+
import fa4_cute_runtime.flashrt_fa4.cute.utils as utils
|
| 14 |
+
from fa4_cute_runtime.flashrt_fa4.cute.block_info import BlockInfo
|
| 15 |
+
from fa4_cute_runtime.flashrt_fa4.cute.seqlen_info import SeqlenInfoQK
|
| 16 |
+
|
| 17 |
+
MaskGenFn: TypeAlias = Callable[[int], Uint32]
|
| 18 |
+
MASK_R2P_CHUNK_SIZE: int = 32
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@cute.jit
|
| 22 |
+
def r2p_bitmask_below(limit: Int32, s: int) -> Uint32:
|
| 23 |
+
"""32-bit R2P bitmask keeping positions < limit (exclusive upper bound).
|
| 24 |
+
|
| 25 |
+
Positions 0..limit-1 in chunk `s` get bit=1 (keep), the rest bit=0 (mask).
|
| 26 |
+
Uses inline PTX to avoid shift-by-type-width UB.
|
| 27 |
+
"""
|
| 28 |
+
m = max((s + 1) * MASK_R2P_CHUNK_SIZE - limit, 0)
|
| 29 |
+
return utils.shr_u32(Uint32(0xFFFFFFFF), Uint32(m))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@cute.jit
|
| 33 |
+
def r2p_bitmask_above(limit: Int32, s: int) -> Uint32:
|
| 34 |
+
"""32-bit R2P bitmask keeping positions >= limit (inclusive lower bound).
|
| 35 |
+
|
| 36 |
+
Positions limit..31 in chunk `s` get bit=1 (keep), the rest bit=0 (mask).
|
| 37 |
+
Uses inline PTX to avoid shift-by-type-width UB.
|
| 38 |
+
"""
|
| 39 |
+
n = max(limit - s * MASK_R2P_CHUNK_SIZE, 0)
|
| 40 |
+
return utils.shl_u32(Uint32(0xFFFFFFFF), Uint32(n))
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
@cute.jit
|
| 44 |
+
def mask_r2p_lambda(
|
| 45 |
+
X: cute.Tensor,
|
| 46 |
+
mask_gen_fn: cutlass.Constexpr[MaskGenFn],
|
| 47 |
+
rank1: bool = False,
|
| 48 |
+
) -> None:
|
| 49 |
+
"""Apply R2P masking with a custom bitmask generator.
|
| 50 |
+
|
| 51 |
+
mask_gen_fn(chunk_idx: constexpr int) -> Uint32:
|
| 52 |
+
Returns a 32-bit bitmask for the chunk. Bit i set means column
|
| 53 |
+
chunk_idx * chunk_size + i is KEPT; bit i clear means masked to -inf.
|
| 54 |
+
"""
|
| 55 |
+
ncol = const_expr(cute.size(X.shape[cute.rank(X) - 1]) if not rank1 else cute.size(X.shape))
|
| 56 |
+
# 32-column chunks. The mask_gen_fn returns a Uint32 bitmask (1=keep).
|
| 57 |
+
CHUNK_SIZE = MASK_R2P_CHUNK_SIZE
|
| 58 |
+
for s in cutlass.range_constexpr(cute.ceil_div(ncol, CHUNK_SIZE)):
|
| 59 |
+
mask = mask_gen_fn(s)
|
| 60 |
+
# This needs to be range_constexpr, o/w the compiler can't generate the R2P instruction
|
| 61 |
+
for i in cutlass.range_constexpr(min(CHUNK_SIZE, ncol - s * CHUNK_SIZE)):
|
| 62 |
+
in_bound = cutlass.Boolean(mask & (Uint32(1) << i))
|
| 63 |
+
c = s * CHUNK_SIZE + i
|
| 64 |
+
if const_expr(rank1):
|
| 65 |
+
X[c] = X[c] if in_bound else -Float32.inf
|
| 66 |
+
else:
|
| 67 |
+
for r in cutlass.range_constexpr(cute.size(X.shape[0])):
|
| 68 |
+
X[r, c] = X[r, c] if in_bound else -Float32.inf
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@cute.jit
|
| 72 |
+
def sm90_col_to_r2p_idx(col_limit: Int32) -> Int32:
|
| 73 |
+
"""Transform SM90 MMA column coordinate to R2P element index.
|
| 74 |
+
|
| 75 |
+
SM90 MMA accumulator column indices are non-contiguous: 0, 1, 8, 9, 16, 17, ...
|
| 76 |
+
Element indices are contiguous: 0, 1, 2, 3, 4, 5, ...
|
| 77 |
+
This converts a column-space threshold to element-space for r2p_bitmask_below/above.
|
| 78 |
+
"""
|
| 79 |
+
return col_limit // 8 * 2 + min(col_limit % 8, 2)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@cute.jit
|
| 83 |
+
def row_to_r2p_idx(x: Int32, num_rep: int, num_wg: int) -> Int32:
|
| 84 |
+
"""Convert a row coordinate to an R2P element index in the warp-group interleaved layout.
|
| 85 |
+
|
| 86 |
+
In the SM100 backward pass, 2 warp groups share TMEM. The TMEM load atom
|
| 87 |
+
distributes rows in an interleaved pattern: elements 0..num_rep-1 map to
|
| 88 |
+
rows 0..num_rep-1 (warp group 0), elements num_rep..2*num_rep-1 map to
|
| 89 |
+
rows num_rep*num_wg..num_rep*num_wg+num_rep-1 (warp group 1), and so on.
|
| 90 |
+
Row-coordinate thresholds (causal limits, window bounds, uih_len) must be
|
| 91 |
+
converted to element indices before use with r2p_bitmask_above/below.
|
| 92 |
+
|
| 93 |
+
Rows not owned by this thread (in the gap between warp groups) are clamped
|
| 94 |
+
to the boundary element index, which is safe because R2P thresholds are
|
| 95 |
+
monotonic.
|
| 96 |
+
|
| 97 |
+
Example with num_rep=16, num_wg=2:
|
| 98 |
+
row 0 -> elem 0, row 15 -> elem 15,
|
| 99 |
+
row 16 -> elem 16 (clamped), row 31 -> elem 16 (clamped),
|
| 100 |
+
row 32 -> elem 16, row 33 -> elem 17, row 47 -> elem 31.
|
| 101 |
+
"""
|
| 102 |
+
return x // (num_rep * num_wg) * num_rep + min(x % (num_rep * num_wg), num_rep)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
@dataclass(frozen=True)
|
| 106 |
+
class AttentionMask:
|
| 107 |
+
tile_m: cutlass.Constexpr[int]
|
| 108 |
+
tile_n: cutlass.Constexpr[int]
|
| 109 |
+
seqlen_info: SeqlenInfoQK
|
| 110 |
+
window_size_left: Optional[Int32] = None
|
| 111 |
+
window_size_right: Optional[Int32] = None
|
| 112 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1 # only pass in if we're doing PackGQA
|
| 113 |
+
swap_AB: cutlass.Constexpr[bool] = False
|
| 114 |
+
|
| 115 |
+
@property
|
| 116 |
+
def seqlen_q(self) -> Int32:
|
| 117 |
+
return self.seqlen_info.seqlen_q
|
| 118 |
+
|
| 119 |
+
@property
|
| 120 |
+
def seqlen_k(self) -> Int32:
|
| 121 |
+
return self.seqlen_info.seqlen_k
|
| 122 |
+
|
| 123 |
+
@cute.jit
|
| 124 |
+
def apply_mask(
|
| 125 |
+
self,
|
| 126 |
+
acc_S: cute.Tensor,
|
| 127 |
+
batch_idx: cutlass.Int32,
|
| 128 |
+
head_idx: cutlass.Int32,
|
| 129 |
+
m_block: cutlass.Int32,
|
| 130 |
+
n_block: cutlass.Int32,
|
| 131 |
+
thr_mma: cute.TiledMma,
|
| 132 |
+
mask_seqlen: cutlass.Constexpr[bool],
|
| 133 |
+
mask_causal: cutlass.Constexpr[bool],
|
| 134 |
+
mask_local: cutlass.Constexpr[bool] = False,
|
| 135 |
+
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 136 |
+
aux_tensors: Optional[list] = None,
|
| 137 |
+
fastdiv_mods=(None, None),
|
| 138 |
+
) -> None:
|
| 139 |
+
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 140 |
+
acc_S_mn = layout_utils.reshape_acc_to_mn(acc_S, transpose=self.swap_AB)
|
| 141 |
+
acc_shape = (self.tile_m, self.tile_n)
|
| 142 |
+
cS = cute.make_identity_tensor(acc_shape if not self.swap_AB else acc_shape[::-1])
|
| 143 |
+
tScS_mn = layout_utils.reshape_acc_to_mn(thr_mma.partition_C(cS), transpose=self.swap_AB)
|
| 144 |
+
# We use t0ScS as these indices are known at compile time. We then must subtract the
|
| 145 |
+
# column limit by the thread column offset.
|
| 146 |
+
t0ScS_mn = layout_utils.reshape_acc_to_mn(
|
| 147 |
+
thr_mma.get_slice(0).partition_C(cS), transpose=self.swap_AB
|
| 148 |
+
)
|
| 149 |
+
ROW = 0 if const_expr(not self.swap_AB) else 1
|
| 150 |
+
COL = 1 if const_expr(not self.swap_AB) else 0
|
| 151 |
+
thr_col_offset = tScS_mn[0][COL]
|
| 152 |
+
# To handle edge cases of completely masked out rows where n_block_max = 0,
|
| 153 |
+
# we treat negative n_blocks as 0th n_block
|
| 154 |
+
# TODO: find more transparent solution
|
| 155 |
+
if n_block < 0:
|
| 156 |
+
n_block = 0
|
| 157 |
+
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n - thr_col_offset
|
| 158 |
+
if const_expr(not mask_causal and not mask_local and mask_mod is None):
|
| 159 |
+
if const_expr(mask_seqlen):
|
| 160 |
+
r2p = const_expr(not self.swap_AB)
|
| 161 |
+
if const_expr(not r2p):
|
| 162 |
+
# traverse column index.
|
| 163 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 164 |
+
oob = t0ScS_mn[0, c][COL] >= seqlenk_col_limit
|
| 165 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 166 |
+
acc_S_mn[r, c] = -Float32.inf if oob else acc_S_mn[r, c]
|
| 167 |
+
else:
|
| 168 |
+
seqlenk_col_limit_r2p = sm90_col_to_r2p_idx(seqlenk_col_limit)
|
| 169 |
+
mask_r2p_lambda(acc_S_mn, lambda s: r2p_bitmask_below(seqlenk_col_limit_r2p, s))
|
| 170 |
+
|
| 171 |
+
elif const_expr(
|
| 172 |
+
not mask_causal and not mask_local and mask_mod is not None
|
| 173 |
+
): # FlexAttention mask mod
|
| 174 |
+
nrow = const_expr(cute.size(tScS_mn.shape[0]))
|
| 175 |
+
ncol = const_expr(cute.size(tScS_mn.shape[1]))
|
| 176 |
+
has_fastdiv = const_expr(
|
| 177 |
+
fastdiv_mods is not None
|
| 178 |
+
and fastdiv_mods[0] is not None
|
| 179 |
+
and fastdiv_mods[1] is not None
|
| 180 |
+
)
|
| 181 |
+
wrap_aux_indices = const_expr(
|
| 182 |
+
has_fastdiv and mask_seqlen and const_expr(aux_tensors is not None)
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
for r in cutlass.range_constexpr(nrow):
|
| 186 |
+
# Respect swap_AB: ROW/COL determine which coordinate component corresponds to Q/KV.
|
| 187 |
+
local_row = tScS_mn[r, 0][ROW]
|
| 188 |
+
global_row_idx = local_row + m_block * self.tile_m
|
| 189 |
+
row_for_mod = global_row_idx
|
| 190 |
+
head_idx_for_mod = head_idx
|
| 191 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 192 |
+
head_offset = global_row_idx % self.qhead_per_kvhead_packgqa
|
| 193 |
+
head_idx_for_mod = head_idx * self.qhead_per_kvhead_packgqa + head_offset
|
| 194 |
+
row_for_mod = global_row_idx // self.qhead_per_kvhead_packgqa
|
| 195 |
+
row_for_seqlen = row_for_mod
|
| 196 |
+
if const_expr(wrap_aux_indices):
|
| 197 |
+
_, row_for_mod = divmod(row_for_mod, fastdiv_mods[0])
|
| 198 |
+
|
| 199 |
+
for col in cutlass.range_constexpr(ncol):
|
| 200 |
+
col_idx_local = t0ScS_mn[0, col][COL]
|
| 201 |
+
# Convert to absolute column index
|
| 202 |
+
global_col_idx = thr_col_offset + col_idx_local + n_block * self.tile_n
|
| 203 |
+
col_for_mod = global_col_idx
|
| 204 |
+
if const_expr(wrap_aux_indices):
|
| 205 |
+
_, col_for_mod = divmod(global_col_idx, fastdiv_mods[1])
|
| 206 |
+
|
| 207 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 208 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32)
|
| 209 |
+
q_idx_ssa = utils.scalar_to_ssa(row_for_mod, cutlass.Int32)
|
| 210 |
+
kv_idx_ssa = utils.scalar_to_ssa(col_for_mod, cutlass.Int32)
|
| 211 |
+
mask_value = mask_mod(
|
| 212 |
+
batch_idx_ssa,
|
| 213 |
+
head_idx_ssa,
|
| 214 |
+
q_idx_ssa,
|
| 215 |
+
kv_idx_ssa,
|
| 216 |
+
self.seqlen_info,
|
| 217 |
+
aux_tensors,
|
| 218 |
+
)
|
| 219 |
+
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 220 |
+
if const_expr(mask_seqlen):
|
| 221 |
+
out_of_bounds = (row_for_seqlen >= self.seqlen_q) or (
|
| 222 |
+
global_col_idx >= self.seqlen_k
|
| 223 |
+
)
|
| 224 |
+
if out_of_bounds:
|
| 225 |
+
acc_S_mn[r, col] = -cutlass.Float32.inf
|
| 226 |
+
else:
|
| 227 |
+
acc_S_mn[r, col] = acc_S_mn[r, col] if cond else -cutlass.Float32.inf
|
| 228 |
+
else:
|
| 229 |
+
acc_S_mn[r, col] = acc_S_mn[r, col] if cond else -cutlass.Float32.inf
|
| 230 |
+
|
| 231 |
+
else: # Causal or local
|
| 232 |
+
if const_expr(not self.swap_AB):
|
| 233 |
+
# If PackGQA, we split the work of compute divmod among threads in the same row
|
| 234 |
+
threads_per_row = thr_mma.tv_layout_C.shape[0][0]
|
| 235 |
+
mma_m_idx = None
|
| 236 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 237 |
+
assert not self.swap_AB, "swap_AB with PackGQA not supported yet"
|
| 238 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, (
|
| 239 |
+
"threads_per_row must divide WARP_SIZE"
|
| 240 |
+
)
|
| 241 |
+
assert cute.size(acc_S_mn.shape[0]) <= threads_per_row
|
| 242 |
+
tidx = thr_mma.thr_idx
|
| 243 |
+
mma_m_idx = (
|
| 244 |
+
m_block * self.tile_m + tScS_mn[tidx % threads_per_row, 0][0]
|
| 245 |
+
) // self.qhead_per_kvhead_packgqa
|
| 246 |
+
causal_row_offset = (
|
| 247 |
+
1 + self.seqlen_k - n_block * self.tile_n - self.seqlen_q - thr_col_offset
|
| 248 |
+
)
|
| 249 |
+
if const_expr(mask_causal):
|
| 250 |
+
r2p = const_expr(not self.swap_AB) # R2P trick, see apply_mask_sm100
|
| 251 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 252 |
+
# get the column index limit based on current row. Only consider the row index, so the column index sets to 0.
|
| 253 |
+
if const_expr(self.qhead_per_kvhead_packgqa == 1):
|
| 254 |
+
row_idx = tScS_mn[r, 0][0] + m_block * self.tile_m
|
| 255 |
+
else:
|
| 256 |
+
row_idx = utils.shuffle_sync(
|
| 257 |
+
mma_m_idx, r % threads_per_row, width=threads_per_row
|
| 258 |
+
)
|
| 259 |
+
col_limit_right = row_idx + causal_row_offset
|
| 260 |
+
if const_expr(mask_seqlen):
|
| 261 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 262 |
+
if const_expr(not r2p):
|
| 263 |
+
# traverse column index.
|
| 264 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 265 |
+
acc_S_mn[r, c] = (
|
| 266 |
+
-Float32.inf
|
| 267 |
+
if t0ScS_mn[0, c][1] >= col_limit_right
|
| 268 |
+
else acc_S_mn[r, c]
|
| 269 |
+
)
|
| 270 |
+
else:
|
| 271 |
+
col_limit_r2p = sm90_col_to_r2p_idx(col_limit_right)
|
| 272 |
+
mask_r2p_lambda(
|
| 273 |
+
acc_S_mn[r, None],
|
| 274 |
+
lambda s: r2p_bitmask_below(col_limit_r2p, s),
|
| 275 |
+
rank1=True,
|
| 276 |
+
)
|
| 277 |
+
else: # Local
|
| 278 |
+
local_row_offset_right = (
|
| 279 |
+
causal_row_offset + self.window_size_right
|
| 280 |
+
if const_expr(self.window_size_right is not None)
|
| 281 |
+
else None
|
| 282 |
+
)
|
| 283 |
+
local_row_offset_left = (
|
| 284 |
+
causal_row_offset - 1 - self.window_size_left
|
| 285 |
+
if const_expr(self.window_size_left is not None)
|
| 286 |
+
else None
|
| 287 |
+
)
|
| 288 |
+
r2p_local = const_expr(not self.swap_AB)
|
| 289 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 290 |
+
if const_expr(self.qhead_per_kvhead_packgqa == 1):
|
| 291 |
+
row_idx = tScS_mn[r, 0][0] + m_block * self.tile_m
|
| 292 |
+
else:
|
| 293 |
+
row_idx = utils.shuffle_sync(
|
| 294 |
+
mma_m_idx, r % threads_per_row, width=threads_per_row
|
| 295 |
+
)
|
| 296 |
+
if const_expr(self.window_size_right is not None):
|
| 297 |
+
col_limit_right = row_idx + local_row_offset_right
|
| 298 |
+
else:
|
| 299 |
+
col_limit_right = self.tile_n
|
| 300 |
+
if const_expr(mask_seqlen):
|
| 301 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 302 |
+
col_limit_left = (
|
| 303 |
+
row_idx + local_row_offset_left
|
| 304 |
+
if const_expr(self.window_size_left is not None)
|
| 305 |
+
else 0
|
| 306 |
+
)
|
| 307 |
+
if const_expr(not r2p_local):
|
| 308 |
+
# traverse column index.
|
| 309 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 310 |
+
col_idx = t0ScS_mn[0, c][1]
|
| 311 |
+
if col_idx >= col_limit_right or col_idx < col_limit_left:
|
| 312 |
+
acc_S_mn[r, c] = -Float32.inf
|
| 313 |
+
else:
|
| 314 |
+
col_limit_right_r2p = sm90_col_to_r2p_idx(col_limit_right)
|
| 315 |
+
col_limit_left_r2p = sm90_col_to_r2p_idx(col_limit_left)
|
| 316 |
+
|
| 317 |
+
def mask_gen_fn(s: int) -> Uint32:
|
| 318 |
+
return r2p_bitmask_below(
|
| 319 |
+
col_limit_right_r2p, s
|
| 320 |
+
) & r2p_bitmask_above(col_limit_left_r2p, s)
|
| 321 |
+
|
| 322 |
+
mask_r2p_lambda(acc_S_mn[r, None], mask_gen_fn, rank1=True)
|
| 323 |
+
else: # swap_AB
|
| 324 |
+
assert self.qhead_per_kvhead_packgqa == 1
|
| 325 |
+
thr_row_offset = tScS_mn[0][ROW]
|
| 326 |
+
causal_row_offset = (
|
| 327 |
+
seqlenk_col_limit - self.seqlen_q + m_block * self.tile_m + thr_row_offset
|
| 328 |
+
)
|
| 329 |
+
if const_expr(mask_causal):
|
| 330 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 331 |
+
col0 = t0ScS_mn[0, c][COL]
|
| 332 |
+
# If col0 is beyond the column limit, we want to mask out the entire
|
| 333 |
+
# column, by setting row limit to be self.tile_m.
|
| 334 |
+
row_limit_top = (
|
| 335 |
+
self.tile_m
|
| 336 |
+
if col0 >= seqlenk_col_limit and mask_seqlen
|
| 337 |
+
else col0 - causal_row_offset
|
| 338 |
+
)
|
| 339 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 340 |
+
acc_S_mn[r, c] = (
|
| 341 |
+
-Float32.inf
|
| 342 |
+
if t0ScS_mn[r, 0][ROW] < row_limit_top
|
| 343 |
+
else acc_S_mn[r, c]
|
| 344 |
+
)
|
| 345 |
+
else:
|
| 346 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 347 |
+
col0 = t0ScS_mn[0, c][COL]
|
| 348 |
+
# If col0 is beyond the column limit, we want to mask out the entire
|
| 349 |
+
# column, by setting row limit to be self.tile_m.
|
| 350 |
+
row_limit_top = (
|
| 351 |
+
self.tile_m
|
| 352 |
+
if col0 >= seqlenk_col_limit and mask_seqlen
|
| 353 |
+
else (
|
| 354 |
+
col0 - causal_row_offset - self.window_size_right
|
| 355 |
+
if const_expr(self.window_size_right is not None)
|
| 356 |
+
else 0
|
| 357 |
+
)
|
| 358 |
+
)
|
| 359 |
+
row_limit_bot = (
|
| 360 |
+
col0 - causal_row_offset + self.window_size_left
|
| 361 |
+
if const_expr(self.window_size_left is not None)
|
| 362 |
+
else self.tile_m
|
| 363 |
+
)
|
| 364 |
+
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 365 |
+
row_idx = t0ScS_mn[r, 0][ROW]
|
| 366 |
+
acc_S_mn[r, c] = (
|
| 367 |
+
-Float32.inf
|
| 368 |
+
if row_idx < row_limit_top or row_idx > row_limit_bot
|
| 369 |
+
else acc_S_mn[r, c]
|
| 370 |
+
)
|
| 371 |
+
|
| 372 |
+
@cute.jit
|
| 373 |
+
def apply_mask_sm100(
|
| 374 |
+
self,
|
| 375 |
+
acc_S: cute.Tensor,
|
| 376 |
+
m_block: Int32,
|
| 377 |
+
n_block: Int32,
|
| 378 |
+
thr_mma: cute.TiledMma,
|
| 379 |
+
thr_tmem_load: cute.TiledCopy,
|
| 380 |
+
mask_seqlen: cutlass.Constexpr[bool],
|
| 381 |
+
mask_causal: cutlass.Constexpr[bool],
|
| 382 |
+
mask_local: cutlass.Constexpr[bool] = False,
|
| 383 |
+
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 384 |
+
batch_idx: Int32 = None,
|
| 385 |
+
head_idx: Int32 = None,
|
| 386 |
+
aux_tensors: Optional[list] = None,
|
| 387 |
+
fastdiv_mods=(None, None),
|
| 388 |
+
head_divmod=None,
|
| 389 |
+
check_q_boundary: bool = False,
|
| 390 |
+
r2p: bool = True,
|
| 391 |
+
rBitmask: Optional[cute.Tensor] = None,
|
| 392 |
+
) -> None:
|
| 393 |
+
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 394 |
+
acc_shape = (self.tile_m, self.tile_n)
|
| 395 |
+
cS = cute.make_identity_tensor(acc_shape if not self.swap_AB else acc_shape[::-1])
|
| 396 |
+
tScS = thr_mma.partition_C(cS)
|
| 397 |
+
tScS = tScS[(None, None), 0, 0]
|
| 398 |
+
tScS_t2r = thr_tmem_load.partition_D(tScS)
|
| 399 |
+
# To handle edge cases of completely masked out rows where n_block_max = 0,
|
| 400 |
+
# we treat negative n_blocks as 0th n_block
|
| 401 |
+
# TODO: find more transparent solution
|
| 402 |
+
if n_block < 0:
|
| 403 |
+
n_block = 0
|
| 404 |
+
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n
|
| 405 |
+
|
| 406 |
+
if const_expr(rBitmask is not None):
|
| 407 |
+
ncol_packed = const_expr(cute.size(rBitmask.shape[0]))
|
| 408 |
+
for i in cutlass.range_constexpr(ncol_packed):
|
| 409 |
+
col_start = 32 * i # mask is bit-packed into uint32
|
| 410 |
+
curr_mask_val = rBitmask[i]
|
| 411 |
+
for j in cutlass.range_constexpr(32):
|
| 412 |
+
curr_col = col_start + j
|
| 413 |
+
mask = (curr_mask_val >> j) & 1
|
| 414 |
+
acc_S[curr_col] = acc_S[curr_col] if cutlass.Boolean(mask) else -Float32.inf
|
| 415 |
+
|
| 416 |
+
elif const_expr(not mask_causal and not mask_local and mask_mod is None):
|
| 417 |
+
if const_expr(mask_seqlen):
|
| 418 |
+
if const_expr(not r2p):
|
| 419 |
+
for i in cutlass.range(cute.size(tScS_t2r.shape), unroll_full=True):
|
| 420 |
+
# if tScS_t2r[i][1] >= seqlenk_col_limit:
|
| 421 |
+
# acc_S[i] = -Float32.inf
|
| 422 |
+
# For some reason the 2 lines above generate really bad SASS
|
| 423 |
+
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= seqlenk_col_limit else acc_S[i]
|
| 424 |
+
else:
|
| 425 |
+
mask_r2p_lambda(
|
| 426 |
+
acc_S,
|
| 427 |
+
lambda s: r2p_bitmask_below(seqlenk_col_limit, s),
|
| 428 |
+
rank1=True,
|
| 429 |
+
)
|
| 430 |
+
|
| 431 |
+
elif const_expr(not mask_causal and not mask_local and mask_mod is not None):
|
| 432 |
+
# Block sparse case w/ mask_mod
|
| 433 |
+
has_fastdiv = const_expr(
|
| 434 |
+
fastdiv_mods is not None
|
| 435 |
+
and fastdiv_mods[0] is not None
|
| 436 |
+
and fastdiv_mods[1] is not None
|
| 437 |
+
)
|
| 438 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 439 |
+
|
| 440 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 441 |
+
for i in cutlass.range_constexpr(ncol):
|
| 442 |
+
row_coord = tScS_t2r[i][0] if not self.swap_AB else tScS_t2r[i][1]
|
| 443 |
+
col_coord = tScS_t2r[i][1] if not self.swap_AB else tScS_t2r[i][0]
|
| 444 |
+
global_row = row_coord + m_block * self.tile_m
|
| 445 |
+
global_col = col_coord + n_block * self.tile_n
|
| 446 |
+
|
| 447 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 448 |
+
assert head_divmod is not None
|
| 449 |
+
mask_row, head_offset = divmod(global_row, head_divmod)
|
| 450 |
+
head_idx_for_mod = head_idx * self.qhead_per_kvhead_packgqa + head_offset
|
| 451 |
+
else:
|
| 452 |
+
head_idx_for_mod = head_idx
|
| 453 |
+
mask_row = global_row
|
| 454 |
+
|
| 455 |
+
mask_row_for_mod = mask_row
|
| 456 |
+
if const_expr(has_fastdiv and aux_tensors is not None):
|
| 457 |
+
if check_q_boundary:
|
| 458 |
+
_, mask_row_for_mod = divmod(mask_row, fastdiv_mods[0])
|
| 459 |
+
global_col_for_mod = global_col
|
| 460 |
+
if const_expr(has_fastdiv and mask_seqlen and aux_tensors is not None):
|
| 461 |
+
_, global_col_for_mod = divmod(global_col, fastdiv_mods[1])
|
| 462 |
+
|
| 463 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32)
|
| 464 |
+
mask_row_ssa = utils.scalar_to_ssa(mask_row_for_mod, cutlass.Int32)
|
| 465 |
+
kv_idx_ssa = utils.scalar_to_ssa(global_col_for_mod, cutlass.Int32)
|
| 466 |
+
mask_value = mask_mod(
|
| 467 |
+
batch_idx_ssa,
|
| 468 |
+
head_idx_ssa,
|
| 469 |
+
mask_row_ssa,
|
| 470 |
+
kv_idx_ssa,
|
| 471 |
+
self.seqlen_info,
|
| 472 |
+
aux_tensors,
|
| 473 |
+
)
|
| 474 |
+
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 475 |
+
acc_S[i] = acc_S[i] if cond else -Float32.inf
|
| 476 |
+
if const_expr(mask_seqlen):
|
| 477 |
+
acc_S[i] = -Float32.inf if global_col >= self.seqlen_k else acc_S[i]
|
| 478 |
+
if check_q_boundary:
|
| 479 |
+
acc_S[i] = -Float32.inf if mask_row >= self.seqlen_q else acc_S[i]
|
| 480 |
+
|
| 481 |
+
else: # Causal or local
|
| 482 |
+
causal_row_offset = self.seqlen_k - n_block * self.tile_n - self.seqlen_q
|
| 483 |
+
row_idx = tScS_t2r[0][0] + m_block * self.tile_m
|
| 484 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 485 |
+
row_idx = row_idx // self.qhead_per_kvhead_packgqa
|
| 486 |
+
if const_expr(mask_causal):
|
| 487 |
+
col_limit_right = row_idx + causal_row_offset + 1
|
| 488 |
+
if const_expr(mask_seqlen):
|
| 489 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 490 |
+
# if cute.arch.thread_idx()[0] % 32 == 0:
|
| 491 |
+
# cute.printf("tidx = %d, tidx tmem = %d, row_idx = %d, col_limit_right = %d, causal_row_offset = %d\n", cute.arch.thread_idx()[0], thr_tmem_load.thr_idx, row_idx, col_limit_right, causal_row_offset)
|
| 492 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 493 |
+
if const_expr(not r2p):
|
| 494 |
+
for i in cutlass.range(ncol, unroll_full=True):
|
| 495 |
+
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= col_limit_right else acc_S[i]
|
| 496 |
+
else:
|
| 497 |
+
mask_r2p_lambda(
|
| 498 |
+
acc_S,
|
| 499 |
+
lambda s: r2p_bitmask_below(col_limit_right, s),
|
| 500 |
+
rank1=True,
|
| 501 |
+
)
|
| 502 |
+
else:
|
| 503 |
+
local_row_offset_right = (
|
| 504 |
+
causal_row_offset + 1 + self.window_size_right
|
| 505 |
+
if const_expr(self.window_size_right is not None)
|
| 506 |
+
else None
|
| 507 |
+
)
|
| 508 |
+
local_row_offset_left = (
|
| 509 |
+
causal_row_offset - self.window_size_left
|
| 510 |
+
if const_expr(self.window_size_left is not None)
|
| 511 |
+
else None
|
| 512 |
+
)
|
| 513 |
+
if const_expr(self.window_size_right is not None):
|
| 514 |
+
col_limit_right = row_idx + local_row_offset_right
|
| 515 |
+
else:
|
| 516 |
+
col_limit_right = self.tile_n
|
| 517 |
+
if const_expr(mask_seqlen):
|
| 518 |
+
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 519 |
+
col_limit_left = (
|
| 520 |
+
row_idx + local_row_offset_left
|
| 521 |
+
if const_expr(self.window_size_left is not None)
|
| 522 |
+
else 0
|
| 523 |
+
)
|
| 524 |
+
if const_expr(not r2p):
|
| 525 |
+
# if cute.arch.thread_idx()[0] == 0 or cute.arch.thread_idx()[0] == 128: cute.printf("m_block = {}, n_block = {}, row_idx = {}, causal_row_offset = {}, col_limit_right = {}, col_limit_left = {}", m_block, n_block, row_idx, causal_row_offset, col_limit_right, col_limit_left)
|
| 526 |
+
for i in cutlass.range(cute.size(tScS_t2r.shape), unroll_full=True):
|
| 527 |
+
col_idx = tScS_t2r[i][1]
|
| 528 |
+
acc_S[i] = (
|
| 529 |
+
-Float32.inf
|
| 530 |
+
if col_idx >= col_limit_right or col_idx < col_limit_left
|
| 531 |
+
else acc_S[i]
|
| 532 |
+
)
|
| 533 |
+
else:
|
| 534 |
+
# Dual-bound R2P masking for SM100.
|
| 535 |
+
# Masks elements where: NOT (col_limit_left <= col < col_limit_right)
|
| 536 |
+
|
| 537 |
+
def mask_gen_fn(s: int) -> Uint32:
|
| 538 |
+
return r2p_bitmask_below(col_limit_right, s) & r2p_bitmask_above(
|
| 539 |
+
col_limit_left, s
|
| 540 |
+
)
|
| 541 |
+
|
| 542 |
+
mask_r2p_lambda(acc_S, mask_gen_fn, rank1=True)
|
| 543 |
+
|
| 544 |
+
@cute.jit
|
| 545 |
+
def apply_mask_sm100_transposed(
|
| 546 |
+
self,
|
| 547 |
+
acc_S: cute.Tensor,
|
| 548 |
+
tScS_t2r: cute.Tensor,
|
| 549 |
+
t0ScS_t2r: cute.Tensor,
|
| 550 |
+
m_block: cutlass.Int32,
|
| 551 |
+
n_block: cutlass.Int32,
|
| 552 |
+
mask_seqlen: cutlass.Constexpr,
|
| 553 |
+
mask_causal: cutlass.Constexpr,
|
| 554 |
+
mask_local: cutlass.Constexpr,
|
| 555 |
+
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 556 |
+
batch_idx: Int32 = None,
|
| 557 |
+
head_idx: Int32 = None,
|
| 558 |
+
aux_tensors: Optional[list] = None,
|
| 559 |
+
fastdiv_mods=(None, None),
|
| 560 |
+
is_full_block: bool = False,
|
| 561 |
+
check_m_boundary: bool = True,
|
| 562 |
+
) -> None:
|
| 563 |
+
"""
|
| 564 |
+
Backward pass: mask S = K @ Q.T where n_block tiles seqlen_k and m_block tiles seqlen_q.
|
| 565 |
+
|
| 566 |
+
Coordinate convention:
|
| 567 |
+
- ROW corresponds to Q (m_block)
|
| 568 |
+
- COL corresponds to KV (n_block)
|
| 569 |
+
|
| 570 |
+
is_full_block: If True, skip mask_mod (all elements valid). Only apply seqlen masking.
|
| 571 |
+
check_m_boundary: If False, skip seqlen_q boundary check (optimization for non-boundary m_blocks).
|
| 572 |
+
When iterating m_blocks in forward order, only the last m_block may be partial.
|
| 573 |
+
"""
|
| 574 |
+
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 575 |
+
ROW = 0 if const_expr(not self.swap_AB) else 1
|
| 576 |
+
COL = 1 if const_expr(not self.swap_AB) else 0
|
| 577 |
+
# assert t0ScS_t2r[0][COL] == 0, "col0 == 0" # tmp comment for 2-cta bwd
|
| 578 |
+
thr_col_offset = tScS_t2r[0][COL]
|
| 579 |
+
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n - thr_col_offset
|
| 580 |
+
|
| 581 |
+
if const_expr(not mask_causal and not mask_local and mask_mod is not None):
|
| 582 |
+
# Block sparse case with mask_mod (backward)
|
| 583 |
+
#
|
| 584 |
+
# Coordinate convention: ROW → Q (m_block), COL → KV (n_block).
|
| 585 |
+
# These already account for swap_AB.
|
| 586 |
+
#
|
| 587 |
+
# FULL blocks: mask_mod returns True for all elements, so skip it.
|
| 588 |
+
# Still need seqlen bounds check (elements may be OOB on last m_block).
|
| 589 |
+
# PARTIAL blocks: apply mask_mod element-wise, then seqlen bounds.
|
| 590 |
+
if is_full_block:
|
| 591 |
+
if const_expr(mask_seqlen):
|
| 592 |
+
if seqlenk_col_limit <= 0:
|
| 593 |
+
# Entire tile is OOB for K
|
| 594 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 595 |
+
acc_S[i] = -cutlass.Float32.inf
|
| 596 |
+
elif check_m_boundary:
|
| 597 |
+
# Last m_block: check Q and K boundaries
|
| 598 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 599 |
+
for i in cutlass.range_constexpr(ncol):
|
| 600 |
+
row_coord = tScS_t2r[i][ROW]
|
| 601 |
+
col_coord = tScS_t2r[i][COL]
|
| 602 |
+
global_q = row_coord + m_block * self.tile_m
|
| 603 |
+
global_kv = col_coord + n_block * self.tile_n
|
| 604 |
+
q_out_of_bounds = global_q >= self.seqlen_q
|
| 605 |
+
kv_out_of_bounds = global_kv >= self.seqlen_k
|
| 606 |
+
out_of_bounds = q_out_of_bounds or kv_out_of_bounds
|
| 607 |
+
acc_S[i] = -cutlass.Float32.inf if out_of_bounds else acc_S[i]
|
| 608 |
+
else:
|
| 609 |
+
# Partial block
|
| 610 |
+
has_fastdiv = const_expr(
|
| 611 |
+
fastdiv_mods is not None
|
| 612 |
+
and fastdiv_mods[0] is not None
|
| 613 |
+
and fastdiv_mods[1] is not None
|
| 614 |
+
)
|
| 615 |
+
wrap_aux_indices = const_expr(
|
| 616 |
+
has_fastdiv and mask_seqlen and const_expr(aux_tensors is not None)
|
| 617 |
+
)
|
| 618 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 619 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32)
|
| 620 |
+
|
| 621 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 622 |
+
for i in cutlass.range_constexpr(ncol):
|
| 623 |
+
row_coord = tScS_t2r[i][ROW]
|
| 624 |
+
col_coord = tScS_t2r[i][COL]
|
| 625 |
+
global_q = row_coord + m_block * self.tile_m
|
| 626 |
+
global_kv = col_coord + n_block * self.tile_n
|
| 627 |
+
|
| 628 |
+
q_idx_for_mod = global_q
|
| 629 |
+
kv_idx_for_mod = global_kv
|
| 630 |
+
if const_expr(wrap_aux_indices):
|
| 631 |
+
_, q_idx_for_mod = divmod(global_q, fastdiv_mods[0])
|
| 632 |
+
_, kv_idx_for_mod = divmod(global_kv, fastdiv_mods[1])
|
| 633 |
+
|
| 634 |
+
q_idx_ssa = utils.scalar_to_ssa(q_idx_for_mod, cutlass.Int32)
|
| 635 |
+
kv_idx_ssa = utils.scalar_to_ssa(kv_idx_for_mod, cutlass.Int32)
|
| 636 |
+
|
| 637 |
+
mask_value = mask_mod(
|
| 638 |
+
batch_idx_ssa,
|
| 639 |
+
head_idx_ssa,
|
| 640 |
+
q_idx_ssa,
|
| 641 |
+
kv_idx_ssa,
|
| 642 |
+
self.seqlen_info,
|
| 643 |
+
aux_tensors,
|
| 644 |
+
)
|
| 645 |
+
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 646 |
+
acc_S[i] = acc_S[i] if cond else -cutlass.Float32.inf
|
| 647 |
+
|
| 648 |
+
if const_expr(mask_seqlen):
|
| 649 |
+
# check_m_boundary=False skips q check for non-boundary m_blocks
|
| 650 |
+
q_out_of_bounds = check_m_boundary and (global_q >= self.seqlen_q)
|
| 651 |
+
kv_out_of_bounds = global_kv >= self.seqlen_k
|
| 652 |
+
out_of_bounds = q_out_of_bounds or kv_out_of_bounds
|
| 653 |
+
acc_S[i] = -cutlass.Float32.inf if out_of_bounds else acc_S[i]
|
| 654 |
+
|
| 655 |
+
elif const_expr(not mask_causal and not mask_local):
|
| 656 |
+
if const_expr(mask_seqlen):
|
| 657 |
+
if seqlenk_col_limit <= 0:
|
| 658 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 659 |
+
acc_S[i] = -cutlass.Float32.inf
|
| 660 |
+
else: # Causal or local
|
| 661 |
+
thr_row_offset = tScS_t2r[0][ROW]
|
| 662 |
+
seqlenq_row_limit = self.seqlen_q - m_block * self.tile_m - thr_row_offset
|
| 663 |
+
causal_offset = seqlenq_row_limit - seqlenk_col_limit
|
| 664 |
+
if const_expr(mask_causal):
|
| 665 |
+
# tidx = cute.arch.thread_idx()[0] % 256
|
| 666 |
+
# if tidx < 32:
|
| 667 |
+
# cute.printf("tidx = {}, {} {}, {} {}", tidx, tScS_t2r[0][0], tScS_t2r[0][1], tScS_t2r[1][0], tScS_t2r[1][1])
|
| 668 |
+
row_limit_top = causal_offset
|
| 669 |
+
if const_expr(mask_seqlen):
|
| 670 |
+
# If col is beyond the column limit, we want to mask out the entire
|
| 671 |
+
# column, by setting row limit to be self.tile_m.
|
| 672 |
+
if seqlenk_col_limit <= 0:
|
| 673 |
+
row_limit_top = self.tile_m
|
| 674 |
+
r2p = True
|
| 675 |
+
if const_expr(not r2p):
|
| 676 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 677 |
+
acc_S[i] = (
|
| 678 |
+
-cutlass.Float32.inf if t0ScS_t2r[i][ROW] < row_limit_top else acc_S[i]
|
| 679 |
+
)
|
| 680 |
+
else:
|
| 681 |
+
num_rep = cute.size(tScS_t2r, mode=[0]) # 16 or 32
|
| 682 |
+
num_wg = 2
|
| 683 |
+
row_limit = row_to_r2p_idx(row_limit_top, num_rep, num_wg)
|
| 684 |
+
mask_r2p_lambda(
|
| 685 |
+
acc_S,
|
| 686 |
+
lambda s: r2p_bitmask_above(row_limit, s),
|
| 687 |
+
rank1=True,
|
| 688 |
+
)
|
| 689 |
+
else:
|
| 690 |
+
if const_expr(self.window_size_right is not None):
|
| 691 |
+
row_limit_top = causal_offset - self.window_size_right
|
| 692 |
+
else:
|
| 693 |
+
row_limit_top = 0
|
| 694 |
+
if const_expr(self.window_size_left is not None):
|
| 695 |
+
row_limit_bot = causal_offset + self.window_size_left
|
| 696 |
+
if const_expr(mask_seqlen):
|
| 697 |
+
if seqlenk_col_limit <= 0:
|
| 698 |
+
row_limit_top = self.tile_m
|
| 699 |
+
r2p = True
|
| 700 |
+
if const_expr(not r2p):
|
| 701 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 702 |
+
row_idx = t0ScS_t2r[i][ROW]
|
| 703 |
+
local_mask = row_idx < row_limit_top
|
| 704 |
+
if const_expr(self.window_size_left is not None):
|
| 705 |
+
local_mask |= row_idx > row_limit_bot
|
| 706 |
+
acc_S[i] = -cutlass.Float32.inf if local_mask else acc_S[i]
|
| 707 |
+
else:
|
| 708 |
+
|
| 709 |
+
def mask_gen_fn(s: int) -> Uint32:
|
| 710 |
+
num_rep = cute.size(tScS_t2r, mode=[0])
|
| 711 |
+
num_wg = 2
|
| 712 |
+
|
| 713 |
+
row_limit = row_to_r2p_idx(row_limit_top, num_rep, num_wg)
|
| 714 |
+
mask = r2p_bitmask_above(row_limit, s)
|
| 715 |
+
|
| 716 |
+
if const_expr(self.window_size_left is not None):
|
| 717 |
+
row_limit_bottom = row_to_r2p_idx(row_limit_bot + 1, num_rep, num_wg)
|
| 718 |
+
mask = mask & r2p_bitmask_below(row_limit_bottom, s)
|
| 719 |
+
|
| 720 |
+
return mask
|
| 721 |
+
|
| 722 |
+
mask_r2p_lambda(
|
| 723 |
+
acc_S,
|
| 724 |
+
mask_gen_fn,
|
| 725 |
+
rank1=True,
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
# -----------------------------------------------------------------------------
|
| 730 |
+
# SM100 FMHA fused-mask policy layer (separate from generic mask primitives).
|
| 731 |
+
# -----------------------------------------------------------------------------
|
| 732 |
+
|
| 733 |
+
|
| 734 |
+
class Sm100MaskEnum(enum.Enum):
|
| 735 |
+
"""Enumeration of mask types for FMHA operations.
|
| 736 |
+
|
| 737 |
+
- RESIDUAL_MASK: Residual mask for handling variable sequence lengths
|
| 738 |
+
- WINDOW_MASK: Window mask for attention which also includes causal and no mask
|
| 739 |
+
- WINDOW_MASK_INFERENCE: Same as the window mask, but has the limitation that the end of q is aligned with the end of k
|
| 740 |
+
- WINDOW_MASK_BWD: Window mask for backward pass
|
| 741 |
+
- WINDOW_MASK_BWD_INFERENCE: Same as the window mask for backward pass, but has the limitation that the end of q is aligned with the end of k
|
| 742 |
+
"""
|
| 743 |
+
|
| 744 |
+
NO_MASK = enum.auto()
|
| 745 |
+
RESIDUAL_MASK = enum.auto()
|
| 746 |
+
CAUSAL_MASK = enum.auto()
|
| 747 |
+
WINDOW_MASK = enum.auto()
|
| 748 |
+
WINDOW_MASK_INFERENCE = enum.auto()
|
| 749 |
+
# Deprecated the following types
|
| 750 |
+
WINDOW_MASK_BWD = enum.auto()
|
| 751 |
+
WINDOW_MASK_BWD_INFERENCE = enum.auto()
|
| 752 |
+
RESIDUAL_MASK_BWD = enum.auto()
|
| 753 |
+
|
| 754 |
+
|
| 755 |
+
class Sm100FusedMask:
|
| 756 |
+
"""A fused mask implementation for FMHA operations.
|
| 757 |
+
|
| 758 |
+
This class handles different types of attention masks including no mask,
|
| 759 |
+
residual mask for variable sequence lengths, and causal mask for
|
| 760 |
+
autoregressive attention patterns.
|
| 761 |
+
|
| 762 |
+
The class provides methods to:
|
| 763 |
+
- Calculate trip counts for different mask types
|
| 764 |
+
- Apply masks to attention scores
|
| 765 |
+
- Handle masked and unmasked trip calculations
|
| 766 |
+
"""
|
| 767 |
+
|
| 768 |
+
def get_trip_count(
|
| 769 |
+
mask_type: Sm100MaskEnum,
|
| 770 |
+
blk_coord: cute.Coord,
|
| 771 |
+
tile_shape: cute.Shape,
|
| 772 |
+
seqlen_q: Int32,
|
| 773 |
+
seqlen_k: Int32,
|
| 774 |
+
window_size_left: Optional[Int32] = None,
|
| 775 |
+
window_size_right: Optional[Int32] = None,
|
| 776 |
+
) -> Int32:
|
| 777 |
+
"""
|
| 778 |
+
Calculate the number of trips needed for the current block.
|
| 779 |
+
|
| 780 |
+
The trip count depends on the mask type and the block coordinates.
|
| 781 |
+
For causal masks, it considers the autoregressive constraint.
|
| 782 |
+
|
| 783 |
+
:param mask_type: Type of mask to use
|
| 784 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 785 |
+
:param blk_coord: Block coordinates.
|
| 786 |
+
:type blk_coord: cute.Coord
|
| 787 |
+
:param tile_shape: Shape of the tile.
|
| 788 |
+
:type tile_shape: cute.Shape
|
| 789 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 790 |
+
:type seqlen_q: Int32
|
| 791 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 792 |
+
:type seqlen_k: Int32
|
| 793 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 794 |
+
:type window_size_left: Optional[Int32]
|
| 795 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 796 |
+
:type window_size_right: Optional[Int32]
|
| 797 |
+
|
| 798 |
+
:return: Number of trips needed.
|
| 799 |
+
:rtype: Int32
|
| 800 |
+
"""
|
| 801 |
+
result = 0
|
| 802 |
+
offset = 0
|
| 803 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 804 |
+
offset = seqlen_k - seqlen_q
|
| 805 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 806 |
+
offset = seqlen_q - seqlen_k
|
| 807 |
+
if cutlass.const_expr(mask_type == Sm100MaskEnum.RESIDUAL_MASK):
|
| 808 |
+
result = cute.ceil_div(seqlen_k, tile_shape[1])
|
| 809 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.RESIDUAL_MASK_BWD):
|
| 810 |
+
result = cute.ceil_div(seqlen_q, tile_shape[0])
|
| 811 |
+
if cutlass.const_expr(
|
| 812 |
+
mask_type == Sm100MaskEnum.WINDOW_MASK
|
| 813 |
+
or mask_type == Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 814 |
+
):
|
| 815 |
+
if cutlass.const_expr(window_size_right is None):
|
| 816 |
+
result = cute.ceil_div(seqlen_k, tile_shape[1])
|
| 817 |
+
else:
|
| 818 |
+
max_idx_q = (blk_coord[0] + 1) * tile_shape[0]
|
| 819 |
+
idx_k = max_idx_q + offset + window_size_right
|
| 820 |
+
tmp_blocks_k = cute.ceil_div(idx_k, tile_shape[1])
|
| 821 |
+
max_blocks_k = cute.ceil_div(seqlen_k, tile_shape[1])
|
| 822 |
+
result = dsl_min(max_blocks_k, tmp_blocks_k)
|
| 823 |
+
if cutlass.const_expr(
|
| 824 |
+
mask_type == Sm100MaskEnum.WINDOW_MASK_BWD
|
| 825 |
+
or mask_type == Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 826 |
+
):
|
| 827 |
+
if cutlass.const_expr(window_size_left is None):
|
| 828 |
+
result = cute.ceil_div(seqlen_q, tile_shape[0])
|
| 829 |
+
else:
|
| 830 |
+
max_idx_k = (blk_coord[1] + 1) * tile_shape[1]
|
| 831 |
+
idx_k = max_idx_k + offset + window_size_left
|
| 832 |
+
tmp_blocks_q = cute.ceil_div(idx_k, tile_shape[0])
|
| 833 |
+
max_blocks_q = cute.ceil_div(seqlen_q, tile_shape[0])
|
| 834 |
+
result = dsl_min(max_blocks_q, tmp_blocks_q)
|
| 835 |
+
start_block = Sm100FusedMask.get_trip_start(
|
| 836 |
+
mask_type,
|
| 837 |
+
blk_coord,
|
| 838 |
+
tile_shape,
|
| 839 |
+
seqlen_q,
|
| 840 |
+
seqlen_k,
|
| 841 |
+
window_size_left,
|
| 842 |
+
window_size_right,
|
| 843 |
+
)
|
| 844 |
+
result = result - start_block
|
| 845 |
+
return result
|
| 846 |
+
|
| 847 |
+
@cute.jit
|
| 848 |
+
def get_trip_start_count_via_block_info(
|
| 849 |
+
blk_coord: cute.Coord,
|
| 850 |
+
tile_shape: cute.Shape,
|
| 851 |
+
seqlen_q: Int32,
|
| 852 |
+
seqlen_k: Int32,
|
| 853 |
+
is_causal: cutlass.Constexpr[bool] = False,
|
| 854 |
+
is_local: cutlass.Constexpr[bool] = False,
|
| 855 |
+
window_size_left: Optional[Int32] = None,
|
| 856 |
+
window_size_right: Optional[Int32] = None,
|
| 857 |
+
) -> Tuple[Int32, Int32]:
|
| 858 |
+
block_info = BlockInfo(
|
| 859 |
+
tile_m=tile_shape[0],
|
| 860 |
+
tile_n=tile_shape[1],
|
| 861 |
+
is_causal=is_causal,
|
| 862 |
+
is_local=is_local and not is_causal,
|
| 863 |
+
window_size_left=window_size_left,
|
| 864 |
+
window_size_right=window_size_right,
|
| 865 |
+
)
|
| 866 |
+
|
| 867 |
+
seqlen_info = SeqlenInfoQK(
|
| 868 |
+
offset_q=Int32(0),
|
| 869 |
+
offset_k=Int32(0),
|
| 870 |
+
padded_offset_q=Int32(0),
|
| 871 |
+
padded_offset_k=Int32(0),
|
| 872 |
+
seqlen_q=seqlen_q,
|
| 873 |
+
seqlen_k=seqlen_k,
|
| 874 |
+
has_cu_seqlens_q=False,
|
| 875 |
+
has_cu_seqlens_k=False,
|
| 876 |
+
has_seqused_q=False,
|
| 877 |
+
has_seqused_k=False,
|
| 878 |
+
)
|
| 879 |
+
n_block_min, n_block_max = block_info.get_n_block_min_max(seqlen_info, blk_coord[0])
|
| 880 |
+
return n_block_min, n_block_max - n_block_min
|
| 881 |
+
|
| 882 |
+
@cute.jit
|
| 883 |
+
def get_trip_mask_bounds_via_block_info(
|
| 884 |
+
blk_coord: cute.Coord,
|
| 885 |
+
tile_shape: cute.Shape,
|
| 886 |
+
seqlen_q: Int32,
|
| 887 |
+
seqlen_k: Int32,
|
| 888 |
+
is_causal: cutlass.Constexpr[bool] = False,
|
| 889 |
+
is_local: cutlass.Constexpr[bool] = False,
|
| 890 |
+
window_size_left: Optional[Int32] = None,
|
| 891 |
+
window_size_right: Optional[Int32] = None,
|
| 892 |
+
) -> Tuple[Int32, Int32]:
|
| 893 |
+
"""Return SM100-style mask boundaries for dense iteration.
|
| 894 |
+
|
| 895 |
+
Returns:
|
| 896 |
+
- n_block_min_causal_local_mask: right-side masked region start
|
| 897 |
+
- n_block_min_before_local_mask: start of fully unmasked middle region
|
| 898 |
+
"""
|
| 899 |
+
block_info = BlockInfo(
|
| 900 |
+
tile_m=tile_shape[0],
|
| 901 |
+
tile_n=tile_shape[1],
|
| 902 |
+
is_causal=is_causal,
|
| 903 |
+
is_local=is_local and not is_causal,
|
| 904 |
+
window_size_left=window_size_left,
|
| 905 |
+
window_size_right=window_size_right,
|
| 906 |
+
)
|
| 907 |
+
seqlen_info = SeqlenInfoQK(
|
| 908 |
+
offset_q=Int32(0),
|
| 909 |
+
offset_k=Int32(0),
|
| 910 |
+
padded_offset_q=Int32(0),
|
| 911 |
+
padded_offset_k=Int32(0),
|
| 912 |
+
seqlen_q=seqlen_q,
|
| 913 |
+
seqlen_k=seqlen_k,
|
| 914 |
+
has_cu_seqlens_q=False,
|
| 915 |
+
has_cu_seqlens_k=False,
|
| 916 |
+
has_seqused_q=False,
|
| 917 |
+
has_seqused_k=False,
|
| 918 |
+
)
|
| 919 |
+
n_block_min, _ = block_info.get_n_block_min_max(seqlen_info, blk_coord[0])
|
| 920 |
+
n_block_min_causal_local_mask = block_info.get_n_block_min_causal_local_mask(
|
| 921 |
+
seqlen_info, blk_coord[0], n_block_min
|
| 922 |
+
)
|
| 923 |
+
n_block_min_before_local_mask = block_info.get_n_block_min_before_local_mask(
|
| 924 |
+
seqlen_info, blk_coord[0], n_block_min
|
| 925 |
+
)
|
| 926 |
+
return n_block_min_causal_local_mask, n_block_min_before_local_mask
|
| 927 |
+
|
| 928 |
+
@cute.jit
|
| 929 |
+
def get_trip_start(
|
| 930 |
+
mask_type: Sm100MaskEnum,
|
| 931 |
+
blk_coord: cute.Coord,
|
| 932 |
+
tile_shape: cute.Shape,
|
| 933 |
+
seqlen_q: Int32,
|
| 934 |
+
seqlen_k: Int32,
|
| 935 |
+
window_size_left: Optional[Int32] = None,
|
| 936 |
+
window_size_right: Optional[Int32] = None,
|
| 937 |
+
) -> Int32:
|
| 938 |
+
"""
|
| 939 |
+
Get the start of the trip for the current block.
|
| 940 |
+
|
| 941 |
+
:param mask_type: Type of mask to use
|
| 942 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 943 |
+
:param blk_coord: Block coordinates.
|
| 944 |
+
:type blk_coord: cute.Coord
|
| 945 |
+
:param tile_shape: Shape of the tile.
|
| 946 |
+
:type tile_shape: cute.Shape
|
| 947 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 948 |
+
:type seqlen_q: Int32
|
| 949 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 950 |
+
:type seqlen_k: Int32
|
| 951 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 952 |
+
:type window_size_left: Optional[Int32]
|
| 953 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 954 |
+
:type window_size_right: Optional[Int32]
|
| 955 |
+
"""
|
| 956 |
+
result = 0
|
| 957 |
+
offset = 0
|
| 958 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 959 |
+
offset = seqlen_k - seqlen_q
|
| 960 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 961 |
+
offset = seqlen_q - seqlen_k
|
| 962 |
+
if cutlass.const_expr(
|
| 963 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK
|
| 964 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 965 |
+
):
|
| 966 |
+
if cutlass.const_expr(window_size_left is not None):
|
| 967 |
+
min_idx_q = blk_coord[0] * tile_shape[0]
|
| 968 |
+
idx_k = min_idx_q + offset - window_size_left
|
| 969 |
+
tmp_blocks_k = idx_k // tile_shape[1]
|
| 970 |
+
result = max(tmp_blocks_k, result)
|
| 971 |
+
if cutlass.const_expr(
|
| 972 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK_BWD
|
| 973 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 974 |
+
):
|
| 975 |
+
if cutlass.const_expr(window_size_right is not None):
|
| 976 |
+
min_idx_k = blk_coord[1] * tile_shape[1]
|
| 977 |
+
idx_q = min_idx_k + offset - window_size_right
|
| 978 |
+
tmp_blocks_q = idx_q // tile_shape[0]
|
| 979 |
+
result = max(tmp_blocks_q, result)
|
| 980 |
+
return result
|
| 981 |
+
|
| 982 |
+
@cute.jit
|
| 983 |
+
def get_leading_mask_id(
|
| 984 |
+
mask_type: Sm100MaskEnum,
|
| 985 |
+
blk_coord: cute.Coord,
|
| 986 |
+
tile_shape: cute.Shape,
|
| 987 |
+
seqlen_q: Int32,
|
| 988 |
+
seqlen_k: Int32,
|
| 989 |
+
window_size_left: Optional[Int32] = None,
|
| 990 |
+
window_size_right: Optional[Int32] = None,
|
| 991 |
+
) -> Tuple[Int32, Int32]:
|
| 992 |
+
"""
|
| 993 |
+
Get the begin and end tile idx for the leading mask.
|
| 994 |
+
|
| 995 |
+
:param mask_type: Type of mask to use
|
| 996 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 997 |
+
:param blk_coord: Block coordinates.
|
| 998 |
+
:type blk_coord: cute.Coord
|
| 999 |
+
:param tile_shape: Shape of the tile.
|
| 1000 |
+
:type tile_shape: cute.Shape
|
| 1001 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1002 |
+
:type seqlen_q: Int32
|
| 1003 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1004 |
+
:type seqlen_k: Int32
|
| 1005 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1006 |
+
:type window_size_left: Optional[Int32]
|
| 1007 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1008 |
+
:type window_size_right: Optional[Int32]
|
| 1009 |
+
|
| 1010 |
+
:return: Tuple of (begin, end) tile idx for the leading mask.
|
| 1011 |
+
:rtype: Tuple[Int32, Int32]
|
| 1012 |
+
"""
|
| 1013 |
+
offset = 0
|
| 1014 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 1015 |
+
offset = seqlen_k - seqlen_q
|
| 1016 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 1017 |
+
offset = seqlen_q - seqlen_k
|
| 1018 |
+
leading_mask_begin = Sm100FusedMask.get_trip_start(
|
| 1019 |
+
mask_type,
|
| 1020 |
+
blk_coord,
|
| 1021 |
+
tile_shape,
|
| 1022 |
+
seqlen_q,
|
| 1023 |
+
seqlen_k,
|
| 1024 |
+
window_size_left,
|
| 1025 |
+
window_size_right,
|
| 1026 |
+
)
|
| 1027 |
+
trip_count = Sm100FusedMask.get_trip_count(
|
| 1028 |
+
mask_type,
|
| 1029 |
+
blk_coord,
|
| 1030 |
+
tile_shape,
|
| 1031 |
+
seqlen_q,
|
| 1032 |
+
seqlen_k,
|
| 1033 |
+
window_size_left,
|
| 1034 |
+
window_size_right,
|
| 1035 |
+
)
|
| 1036 |
+
|
| 1037 |
+
leading_mask_end = leading_mask_begin
|
| 1038 |
+
if cutlass.const_expr(
|
| 1039 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK
|
| 1040 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1041 |
+
):
|
| 1042 |
+
if cutlass.const_expr(window_size_left is not None):
|
| 1043 |
+
min_idx_q = (blk_coord[0] + 1) * tile_shape[0] + offset - window_size_left
|
| 1044 |
+
leading_mask_end = dsl_min(
|
| 1045 |
+
cute.ceil_div(min_idx_q, tile_shape[1]) - 1,
|
| 1046 |
+
trip_count + leading_mask_begin - 1,
|
| 1047 |
+
)
|
| 1048 |
+
else:
|
| 1049 |
+
leading_mask_end = leading_mask_begin - 1
|
| 1050 |
+
elif cutlass.const_expr(
|
| 1051 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK_BWD
|
| 1052 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 1053 |
+
):
|
| 1054 |
+
if cutlass.const_expr(window_size_right is not None):
|
| 1055 |
+
min_idx_k = (blk_coord[1] + 1) * tile_shape[1] + offset - window_size_right
|
| 1056 |
+
leading_mask_end = cute.ceil_div(min_idx_k, tile_shape[0]) - 1
|
| 1057 |
+
else:
|
| 1058 |
+
leading_mask_end = leading_mask_begin - 1
|
| 1059 |
+
return leading_mask_begin, leading_mask_end
|
| 1060 |
+
|
| 1061 |
+
@cute.jit
|
| 1062 |
+
def get_trailing_mask_id(
|
| 1063 |
+
mask_type: Sm100MaskEnum,
|
| 1064 |
+
blk_coord: cute.Coord,
|
| 1065 |
+
tile_shape: cute.Shape,
|
| 1066 |
+
seqlen_q: Int32,
|
| 1067 |
+
seqlen_k: Int32,
|
| 1068 |
+
window_size_left: Optional[Int32] = None,
|
| 1069 |
+
window_size_right: Optional[Int32] = None,
|
| 1070 |
+
) -> Tuple[Optional[Int32], Optional[Int32]]:
|
| 1071 |
+
"""
|
| 1072 |
+
Get the begin and end tile idx for the trailing mask.
|
| 1073 |
+
|
| 1074 |
+
:param mask_type: Type of mask to use
|
| 1075 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1076 |
+
:param blk_coord: Block coordinates.
|
| 1077 |
+
:type blk_coord: cute.Coord
|
| 1078 |
+
:param tile_shape: Shape of the tile.
|
| 1079 |
+
:type tile_shape: cute.Shape
|
| 1080 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1081 |
+
:type seqlen_q: Int32
|
| 1082 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1083 |
+
:type seqlen_k: Int32
|
| 1084 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1085 |
+
:type window_size_left: Optional[Int32]
|
| 1086 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1087 |
+
:type window_size_right: Optional[Int32]
|
| 1088 |
+
|
| 1089 |
+
:return: Tuple of (begin, end) tile idx for the trailing mask.
|
| 1090 |
+
:rtype: Tuple[Int32, Int32]
|
| 1091 |
+
"""
|
| 1092 |
+
offset = 0
|
| 1093 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 1094 |
+
offset = seqlen_k - seqlen_q
|
| 1095 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 1096 |
+
offset = seqlen_q - seqlen_k
|
| 1097 |
+
trip_start = Sm100FusedMask.get_trip_start(
|
| 1098 |
+
mask_type,
|
| 1099 |
+
blk_coord,
|
| 1100 |
+
tile_shape,
|
| 1101 |
+
seqlen_q,
|
| 1102 |
+
seqlen_k,
|
| 1103 |
+
window_size_left,
|
| 1104 |
+
window_size_right,
|
| 1105 |
+
)
|
| 1106 |
+
trip_count = Sm100FusedMask.get_trip_count(
|
| 1107 |
+
mask_type,
|
| 1108 |
+
blk_coord,
|
| 1109 |
+
tile_shape,
|
| 1110 |
+
seqlen_q,
|
| 1111 |
+
seqlen_k,
|
| 1112 |
+
window_size_left,
|
| 1113 |
+
window_size_right,
|
| 1114 |
+
)
|
| 1115 |
+
|
| 1116 |
+
trailing_mask_begin, trailing_mask_end = None, None
|
| 1117 |
+
if cutlass.const_expr(
|
| 1118 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK
|
| 1119 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1120 |
+
):
|
| 1121 |
+
if cutlass.const_expr(window_size_right is not None):
|
| 1122 |
+
min_idx_q = blk_coord[0] * tile_shape[0] + offset + window_size_right
|
| 1123 |
+
trailing_mask_begin = dsl_min(
|
| 1124 |
+
min_idx_q // tile_shape[1], trip_count + trip_start - 1
|
| 1125 |
+
)
|
| 1126 |
+
trailing_mask_end = trip_count + trip_start - 1
|
| 1127 |
+
else:
|
| 1128 |
+
# last tile, we always apply mask on it regardless whether it's a residual tile
|
| 1129 |
+
trailing_mask_begin = trip_count + trip_start - 1
|
| 1130 |
+
trailing_mask_end = trip_count + trip_start - 1
|
| 1131 |
+
else:
|
| 1132 |
+
if cutlass.const_expr(window_size_left is not None):
|
| 1133 |
+
min_idx_k = blk_coord[1] * tile_shape[1] + offset + window_size_left + 1
|
| 1134 |
+
max_idx_k = (blk_coord[1] + 1) * tile_shape[1] + offset + window_size_left
|
| 1135 |
+
trailing_mask_begin = dsl_min(
|
| 1136 |
+
cute.ceil_div(min_idx_k, tile_shape[0]) - 1,
|
| 1137 |
+
trip_count + trip_start - 1,
|
| 1138 |
+
)
|
| 1139 |
+
trailing_mask_end = dsl_min(
|
| 1140 |
+
cute.ceil_div(max_idx_k, tile_shape[0]) - 1,
|
| 1141 |
+
trip_count + trip_start - 1,
|
| 1142 |
+
)
|
| 1143 |
+
else:
|
| 1144 |
+
# last tile, we always apply mask on it regardless whether it's a residual tile
|
| 1145 |
+
trailing_mask_begin = trip_count + trip_start - 1
|
| 1146 |
+
trailing_mask_end = trip_count + trip_start - 1
|
| 1147 |
+
|
| 1148 |
+
return trailing_mask_begin, trailing_mask_end
|
| 1149 |
+
|
| 1150 |
+
@cute.jit
|
| 1151 |
+
def get_masked_leading_count(
|
| 1152 |
+
mask_type: Sm100MaskEnum,
|
| 1153 |
+
blk_coord: cute.Coord,
|
| 1154 |
+
tile_shape: cute.Shape,
|
| 1155 |
+
seqlen_q: Int32,
|
| 1156 |
+
seqlen_k: Int32,
|
| 1157 |
+
window_size_left: Optional[Int32] = None,
|
| 1158 |
+
window_size_right: Optional[Int32] = None,
|
| 1159 |
+
) -> Int32:
|
| 1160 |
+
"""
|
| 1161 |
+
Calculate the number of masked trips for the leading mask.
|
| 1162 |
+
|
| 1163 |
+
This is used for blocks that need special handling due to masking.
|
| 1164 |
+
|
| 1165 |
+
:param mask_type: Type of mask to use
|
| 1166 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1167 |
+
:param blk_coord: Block coordinates.
|
| 1168 |
+
:type blk_coord: cute.Coord
|
| 1169 |
+
:param tile_shape: Shape of the tile.
|
| 1170 |
+
:type tile_shape: cute.Shape
|
| 1171 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1172 |
+
:type seqlen_q: Int32
|
| 1173 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1174 |
+
:type seqlen_k: Int32
|
| 1175 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1176 |
+
:type window_size_left: Optional[Int32]
|
| 1177 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1178 |
+
:type window_size_right: Optional[Int32]
|
| 1179 |
+
|
| 1180 |
+
:return: Number of masked trips.
|
| 1181 |
+
:rtype: Int32
|
| 1182 |
+
"""
|
| 1183 |
+
result = 0
|
| 1184 |
+
if cutlass.const_expr(
|
| 1185 |
+
mask_type is not Sm100MaskEnum.RESIDUAL_MASK
|
| 1186 |
+
and mask_type is not Sm100MaskEnum.RESIDUAL_MASK_BWD
|
| 1187 |
+
):
|
| 1188 |
+
if cutlass.const_expr(window_size_left is not None or window_size_right is not None):
|
| 1189 |
+
leading_mask_begin, leading_mask_end = Sm100FusedMask.get_leading_mask_id(
|
| 1190 |
+
mask_type,
|
| 1191 |
+
blk_coord,
|
| 1192 |
+
tile_shape,
|
| 1193 |
+
seqlen_q,
|
| 1194 |
+
seqlen_k,
|
| 1195 |
+
window_size_left,
|
| 1196 |
+
window_size_right,
|
| 1197 |
+
)
|
| 1198 |
+
result = max(leading_mask_end - leading_mask_begin + 1, 0)
|
| 1199 |
+
|
| 1200 |
+
return result
|
| 1201 |
+
|
| 1202 |
+
@cute.jit
|
| 1203 |
+
def get_masked_trailing_count(
|
| 1204 |
+
mask_type: Sm100MaskEnum,
|
| 1205 |
+
blk_coord: cute.Coord,
|
| 1206 |
+
tile_shape: cute.Shape,
|
| 1207 |
+
seqlen_q: Int32,
|
| 1208 |
+
seqlen_k: Int32,
|
| 1209 |
+
window_size_left: Optional[Int32] = None,
|
| 1210 |
+
window_size_right: Optional[Int32] = None,
|
| 1211 |
+
rem_count: Optional[Int32] = 0,
|
| 1212 |
+
) -> Int32:
|
| 1213 |
+
"""
|
| 1214 |
+
Calculate the number of masked trips for the trailing mask.
|
| 1215 |
+
|
| 1216 |
+
This is used for blocks that need special handling due to masking.
|
| 1217 |
+
|
| 1218 |
+
:param mask_type: Type of mask to use
|
| 1219 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1220 |
+
:param blk_coord: Block coordinates.
|
| 1221 |
+
:type blk_coord: cute.Coord
|
| 1222 |
+
:param tile_shape: Shape of the tile.
|
| 1223 |
+
:type tile_shape: cute.Shape
|
| 1224 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1225 |
+
:type seqlen_q: Int32
|
| 1226 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1227 |
+
:type seqlen_k: Int32
|
| 1228 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1229 |
+
:type window_size_left: Optional[Int32]
|
| 1230 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1231 |
+
:type window_size_right: Optional[Int32]
|
| 1232 |
+
:param rem_count: Remaining count from previous calculations.
|
| 1233 |
+
:type rem_count: Int32
|
| 1234 |
+
|
| 1235 |
+
:return: Number of masked trips.
|
| 1236 |
+
:rtype: Int32
|
| 1237 |
+
"""
|
| 1238 |
+
result = 0
|
| 1239 |
+
|
| 1240 |
+
if cutlass.const_expr(
|
| 1241 |
+
mask_type is not Sm100MaskEnum.RESIDUAL_MASK
|
| 1242 |
+
and mask_type is not Sm100MaskEnum.RESIDUAL_MASK_BWD
|
| 1243 |
+
):
|
| 1244 |
+
if cutlass.const_expr(window_size_left is not None or window_size_right is not None):
|
| 1245 |
+
trailing_mask_begin, trailing_mask_end = Sm100FusedMask.get_trailing_mask_id(
|
| 1246 |
+
mask_type,
|
| 1247 |
+
blk_coord,
|
| 1248 |
+
tile_shape,
|
| 1249 |
+
seqlen_q,
|
| 1250 |
+
seqlen_k,
|
| 1251 |
+
window_size_left,
|
| 1252 |
+
window_size_right,
|
| 1253 |
+
)
|
| 1254 |
+
leading_mask_begin, leading_mask_end = Sm100FusedMask.get_leading_mask_id(
|
| 1255 |
+
mask_type,
|
| 1256 |
+
blk_coord,
|
| 1257 |
+
tile_shape,
|
| 1258 |
+
seqlen_q,
|
| 1259 |
+
seqlen_k,
|
| 1260 |
+
window_size_left,
|
| 1261 |
+
window_size_right,
|
| 1262 |
+
)
|
| 1263 |
+
if cutlass.const_expr(
|
| 1264 |
+
trailing_mask_begin is not None and trailing_mask_end is not None
|
| 1265 |
+
):
|
| 1266 |
+
if trailing_mask_begin <= leading_mask_end:
|
| 1267 |
+
result = max(trailing_mask_end - leading_mask_end, 0)
|
| 1268 |
+
else:
|
| 1269 |
+
result = max(trailing_mask_end - trailing_mask_begin + 1, 0)
|
| 1270 |
+
else:
|
| 1271 |
+
if seqlen_k % tile_shape[1] != 0:
|
| 1272 |
+
result = 1
|
| 1273 |
+
else:
|
| 1274 |
+
result = 0
|
| 1275 |
+
|
| 1276 |
+
return result + rem_count
|
| 1277 |
+
|
| 1278 |
+
@cute.jit
|
| 1279 |
+
def get_unmasked_trip_count(
|
| 1280 |
+
mask_type: Sm100MaskEnum,
|
| 1281 |
+
blk_coord: cute.Coord,
|
| 1282 |
+
tile_shape: cute.Shape,
|
| 1283 |
+
seqlen_q: Int32,
|
| 1284 |
+
seqlen_k: Int32,
|
| 1285 |
+
window_size_left: Optional[Int32] = None,
|
| 1286 |
+
window_size_right: Optional[Int32] = None,
|
| 1287 |
+
) -> Int32:
|
| 1288 |
+
"""
|
| 1289 |
+
Calculate the number of unmasked trips for the current block.
|
| 1290 |
+
|
| 1291 |
+
This represents the number of trips that don't require special
|
| 1292 |
+
masking treatment.
|
| 1293 |
+
|
| 1294 |
+
:param mask_type: Type of mask to use
|
| 1295 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1296 |
+
:param blk_coord: Block coordinates.
|
| 1297 |
+
:type blk_coord: cute.Coord
|
| 1298 |
+
:param tile_shape: Shape of the tile.
|
| 1299 |
+
:type tile_shape: cute.Shape
|
| 1300 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1301 |
+
:type seqlen_q: Int32
|
| 1302 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1303 |
+
:type seqlen_k: Int32
|
| 1304 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1305 |
+
:type window_size_left: Optional[Int32]
|
| 1306 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1307 |
+
:type window_size_right: Optional[Int32]
|
| 1308 |
+
|
| 1309 |
+
:return: Number of unmasked trips.
|
| 1310 |
+
:rtype: Int32
|
| 1311 |
+
"""
|
| 1312 |
+
result = (
|
| 1313 |
+
Sm100FusedMask.get_trip_count(
|
| 1314 |
+
mask_type,
|
| 1315 |
+
blk_coord,
|
| 1316 |
+
tile_shape,
|
| 1317 |
+
seqlen_q,
|
| 1318 |
+
seqlen_k,
|
| 1319 |
+
window_size_left,
|
| 1320 |
+
window_size_right,
|
| 1321 |
+
)
|
| 1322 |
+
- Sm100FusedMask.get_masked_leading_count(
|
| 1323 |
+
mask_type,
|
| 1324 |
+
blk_coord,
|
| 1325 |
+
tile_shape,
|
| 1326 |
+
seqlen_q,
|
| 1327 |
+
seqlen_k,
|
| 1328 |
+
window_size_left,
|
| 1329 |
+
window_size_right,
|
| 1330 |
+
)
|
| 1331 |
+
- Sm100FusedMask.get_masked_trailing_count(
|
| 1332 |
+
mask_type,
|
| 1333 |
+
blk_coord,
|
| 1334 |
+
tile_shape,
|
| 1335 |
+
seqlen_q,
|
| 1336 |
+
seqlen_k,
|
| 1337 |
+
window_size_left,
|
| 1338 |
+
window_size_right,
|
| 1339 |
+
0,
|
| 1340 |
+
)
|
| 1341 |
+
)
|
| 1342 |
+
return result
|
| 1343 |
+
|
| 1344 |
+
@cute.jit
|
| 1345 |
+
def apply_mask(
|
| 1346 |
+
mask_type: Sm100MaskEnum,
|
| 1347 |
+
acc_qk: cute.Tensor,
|
| 1348 |
+
index_qk: cute.Tensor,
|
| 1349 |
+
seqlen_q: Int32,
|
| 1350 |
+
seqlen_k: Int32,
|
| 1351 |
+
window_size_left: Optional[int] = None,
|
| 1352 |
+
window_size_right: Optional[int] = None,
|
| 1353 |
+
index_transform: cutlass.Constexpr = lambda index_q, index_k: (
|
| 1354 |
+
index_q,
|
| 1355 |
+
index_k,
|
| 1356 |
+
),
|
| 1357 |
+
):
|
| 1358 |
+
"""
|
| 1359 |
+
Apply the appropriate mask to the attention scores.
|
| 1360 |
+
|
| 1361 |
+
This method modifies the attention scores (acc_qk) based on the mask type
|
| 1362 |
+
and the positions in the index tensor.
|
| 1363 |
+
|
| 1364 |
+
:param mask_type: Type of mask to use
|
| 1365 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1366 |
+
:param acc_qk: Accumulated QK attention scores tensor.
|
| 1367 |
+
:type acc_qk: cute.Tensor
|
| 1368 |
+
:param index_qk: Index tensor containing position information.
|
| 1369 |
+
:type index_qk: cute.Tensor
|
| 1370 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1371 |
+
:type seqlen_k: Int32
|
| 1372 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1373 |
+
:type seqlen_q: Optional[int]
|
| 1374 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1375 |
+
:type window_size_left: Optional[int]
|
| 1376 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1377 |
+
:type window_size_right: Optional[int]
|
| 1378 |
+
"""
|
| 1379 |
+
offset = 0
|
| 1380 |
+
# NOTE: causal masking in this repo aligns the *end* of Q with the *end* of K
|
| 1381 |
+
# when seqlen_k != seqlen_q (same as the test/reference implementation):
|
| 1382 |
+
# k_index <= q_index + (seqlen_k - seqlen_q) + window_right
|
| 1383 |
+
# In our kernels, causal is represented by (window_left is None, window_right is not None).
|
| 1384 |
+
if cutlass.const_expr(window_size_left is None and window_size_right is not None):
|
| 1385 |
+
offset = seqlen_k - seqlen_q
|
| 1386 |
+
elif cutlass.const_expr(
|
| 1387 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1388 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 1389 |
+
):
|
| 1390 |
+
offset = seqlen_k - seqlen_q
|
| 1391 |
+
for i in cutlass.range_constexpr(cute.size(acc_qk), unroll_full=True):
|
| 1392 |
+
index_q, index_k = index_transform(*index_qk[i])
|
| 1393 |
+
if cutlass.const_expr(window_size_left is not None or window_size_right is not None):
|
| 1394 |
+
if cutlass.const_expr(window_size_left is None):
|
| 1395 |
+
if index_q + offset + window_size_right < index_k:
|
| 1396 |
+
acc_qk[i] = -Float32.inf
|
| 1397 |
+
if index_k >= seqlen_k or index_q >= seqlen_q: # residual mask
|
| 1398 |
+
acc_qk[i] = -Float32.inf
|
| 1399 |
+
elif cutlass.const_expr(window_size_right is None):
|
| 1400 |
+
if index_q + offset - window_size_left > index_k:
|
| 1401 |
+
acc_qk[i] = -Float32.inf
|
| 1402 |
+
if index_k >= seqlen_k or index_q >= seqlen_q: # residual mask
|
| 1403 |
+
acc_qk[i] = -Float32.inf
|
| 1404 |
+
else:
|
| 1405 |
+
max_K_index = dsl_min(index_q + offset + window_size_right, seqlen_k)
|
| 1406 |
+
min_K_index = max(0, index_q + offset - window_size_left)
|
| 1407 |
+
if index_k > max_K_index or index_k < min_K_index:
|
| 1408 |
+
acc_qk[i] = -Float32.inf
|
| 1409 |
+
if index_k >= seqlen_k or index_q >= seqlen_q: # residual mask
|
| 1410 |
+
acc_qk[i] = -Float32.inf
|
| 1411 |
+
|
| 1412 |
+
if cutlass.const_expr(
|
| 1413 |
+
mask_type == Sm100MaskEnum.RESIDUAL_MASK
|
| 1414 |
+
or mask_type == Sm100MaskEnum.RESIDUAL_MASK_BWD
|
| 1415 |
+
):
|
| 1416 |
+
if index_k >= seqlen_k or index_q >= seqlen_q:
|
| 1417 |
+
acc_qk[i] = -Float32.inf
|
| 1418 |
+
|
| 1419 |
+
@cute.jit
|
| 1420 |
+
def apply_mask_via_causal_local(
|
| 1421 |
+
acc_qk: cute.Tensor,
|
| 1422 |
+
index_qk: cute.Tensor,
|
| 1423 |
+
seqlen_q: Int32,
|
| 1424 |
+
seqlen_k: Int32,
|
| 1425 |
+
apply_semantic_window: cutlass.Constexpr[bool] = True,
|
| 1426 |
+
is_causal: cutlass.Constexpr[bool] = False,
|
| 1427 |
+
is_local: cutlass.Constexpr[bool] = False,
|
| 1428 |
+
window_size_left: Optional[int] = None,
|
| 1429 |
+
window_size_right: Optional[int] = None,
|
| 1430 |
+
index_transform: cutlass.Constexpr = lambda index_q, index_k: (
|
| 1431 |
+
index_q,
|
| 1432 |
+
index_k,
|
| 1433 |
+
),
|
| 1434 |
+
):
|
| 1435 |
+
"""Apply forward mask without mask_type.
|
| 1436 |
+
|
| 1437 |
+
- If apply_semantic_window=True, apply causal/local window constraints.
|
| 1438 |
+
- Always apply residual OOB masking (index_k>=seqlen_k or index_q>=seqlen_q).
|
| 1439 |
+
"""
|
| 1440 |
+
offset = 0
|
| 1441 |
+
if cutlass.const_expr(apply_semantic_window):
|
| 1442 |
+
# Match WINDOW_MASK_INFERENCE semantics: end-align Q/K when lengths differ.
|
| 1443 |
+
offset = seqlen_k - seqlen_q
|
| 1444 |
+
for i in cutlass.range_constexpr(cute.size(acc_qk), unroll_full=True):
|
| 1445 |
+
index_q, index_k = index_transform(*index_qk[i])
|
| 1446 |
+
if cutlass.const_expr(apply_semantic_window):
|
| 1447 |
+
if cutlass.const_expr(is_causal and not is_local):
|
| 1448 |
+
# Pure causal; tolerate both external forms:
|
| 1449 |
+
# - (None, None) from interface
|
| 1450 |
+
# - (None, 0) from fused-mask-style callers
|
| 1451 |
+
right = 0 if const_expr(window_size_right is None) else window_size_right
|
| 1452 |
+
if index_q + offset + right < index_k:
|
| 1453 |
+
acc_qk[i] = -Float32.inf
|
| 1454 |
+
elif cutlass.const_expr(
|
| 1455 |
+
is_local or window_size_left is not None or window_size_right is not None
|
| 1456 |
+
):
|
| 1457 |
+
if cutlass.const_expr(window_size_left is None):
|
| 1458 |
+
if index_q + offset + window_size_right < index_k:
|
| 1459 |
+
acc_qk[i] = -Float32.inf
|
| 1460 |
+
elif cutlass.const_expr(window_size_right is None):
|
| 1461 |
+
if index_q + offset - window_size_left > index_k:
|
| 1462 |
+
acc_qk[i] = -Float32.inf
|
| 1463 |
+
else:
|
| 1464 |
+
max_K_index = dsl_min(index_q + offset + window_size_right, seqlen_k)
|
| 1465 |
+
min_K_index = max(0, index_q + offset - window_size_left)
|
| 1466 |
+
if index_k > max_K_index or index_k < min_K_index:
|
| 1467 |
+
acc_qk[i] = -Float32.inf
|
| 1468 |
+
# Residual mask is always needed for boundary protection.
|
| 1469 |
+
if index_k >= seqlen_k or index_q >= seqlen_q:
|
| 1470 |
+
acc_qk[i] = -Float32.inf
|
build/torch-cuda/flashrt_fa4/cute/mma_sm100_desc.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
# Ported Cutlass code from C++ to Python:
|
| 3 |
+
# https://github.com/NVIDIA/cutlass/blob/main/include/cute/arch/mma_sm100_desc.hpp
|
| 4 |
+
# https://github.com/NVIDIA/cutlass/blob/main/include/cute/atom/mma_traits_sm100.hpp
|
| 5 |
+
|
| 6 |
+
from enum import IntEnum
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.cute as cute
|
| 10 |
+
|
| 11 |
+
# ---------------------------------------------------------------------------
|
| 12 |
+
# Enumerations that match the HW encodings (values MUST stay identical)
|
| 13 |
+
# ---------------------------------------------------------------------------
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Major(IntEnum): # matrix “layout” in the ISA docs
|
| 17 |
+
K = 0
|
| 18 |
+
MN = 1
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class ScaleIn(IntEnum): # negate flags
|
| 22 |
+
One = 0
|
| 23 |
+
Neg = 1
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Saturate(IntEnum):
|
| 27 |
+
False_ = 0
|
| 28 |
+
True_ = 1
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class CFormat(IntEnum): # 2-bit field (bits 4-5)
|
| 32 |
+
F16 = 0
|
| 33 |
+
F32 = 1
|
| 34 |
+
S32 = 2
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class F16F32Format(IntEnum): # 3-bit field (A/B element type)
|
| 38 |
+
F16 = 0
|
| 39 |
+
BF16 = 1
|
| 40 |
+
TF32 = 2
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class S8Format(IntEnum):
|
| 44 |
+
UINT8 = 0
|
| 45 |
+
INT8 = 1
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class MXF8F6F4Format(IntEnum):
|
| 49 |
+
E4M3 = 0
|
| 50 |
+
E5M2 = 1
|
| 51 |
+
E2M3 = 3
|
| 52 |
+
E3M2 = 4
|
| 53 |
+
E2M1 = 5
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class MaxShift(IntEnum):
|
| 57 |
+
NoShift = 0
|
| 58 |
+
MaxShift8 = 1
|
| 59 |
+
MaxShift16 = 2
|
| 60 |
+
MaxShift32 = 3
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
# ---------------------------------------------------------------------------
|
| 64 |
+
# CUTLASS-type → encoding helpers
|
| 65 |
+
# ---------------------------------------------------------------------------
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def to_UMMA_format(cutlass_type) -> int:
|
| 69 |
+
"""
|
| 70 |
+
Map a CUTLASS scalar class to the 3-bit encoding for Matrix A/B.
|
| 71 |
+
"""
|
| 72 |
+
if cutlass_type is cutlass.Int8:
|
| 73 |
+
return S8Format.INT8
|
| 74 |
+
# Unsigned 8-bit (if available in your CUTLASS build)
|
| 75 |
+
if cutlass_type is cutlass.Uint8:
|
| 76 |
+
return S8Format.UINT8
|
| 77 |
+
# FP-16 / BF-16
|
| 78 |
+
if cutlass_type is cutlass.Float16:
|
| 79 |
+
return F16F32Format.F16
|
| 80 |
+
if cutlass_type is cutlass.BFloat16:
|
| 81 |
+
return F16F32Format.BF16
|
| 82 |
+
# TensorFloat-32 (8-bit exponent, 10-bit mantissa packed in 19 bits)
|
| 83 |
+
if cutlass_type is cutlass.TFloat32:
|
| 84 |
+
return F16F32Format.TF32
|
| 85 |
+
# Float-8 / Float-6 / Float-4 – add whenever CUTLASS exposes them
|
| 86 |
+
if cutlass_type is cutlass.Float8E4M3FN:
|
| 87 |
+
return MXF8F6F4Format.E4M3
|
| 88 |
+
if cutlass_type is cutlass.Float8E5M2:
|
| 89 |
+
return MXF8F6F4Format.E5M2
|
| 90 |
+
raise TypeError(f"Unsupported CUTLASS scalar type for A/B: {cutlass_type!r}")
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def to_C_format(cutlass_type) -> int:
|
| 94 |
+
"""
|
| 95 |
+
Map a CUTLASS scalar class to the 2-bit accumulator encoding.
|
| 96 |
+
"""
|
| 97 |
+
if cutlass_type is cutlass.Float16:
|
| 98 |
+
return CFormat.F16
|
| 99 |
+
if cutlass_type is cutlass.Float32:
|
| 100 |
+
return CFormat.F32
|
| 101 |
+
if cutlass_type is cutlass.Int32:
|
| 102 |
+
return CFormat.S32
|
| 103 |
+
raise TypeError(f"Unsupported CUTLASS scalar type for accumulator: {cutlass_type!r}")
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# ---------------------------------------------------------------------------
|
| 107 |
+
# The constructor – accepts only CUTLASS scalar classes
|
| 108 |
+
# ---------------------------------------------------------------------------
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def make_instr_desc(
|
| 112 |
+
a_type, # CUTLASS scalar class, e.g. cutlass.Int8
|
| 113 |
+
b_type,
|
| 114 |
+
c_type,
|
| 115 |
+
M: int, # 64, 128 or 256
|
| 116 |
+
N: int, # 8 … 256 (multiple of 8)
|
| 117 |
+
a_major: Major,
|
| 118 |
+
b_major: Major,
|
| 119 |
+
a_neg: ScaleIn = ScaleIn.One,
|
| 120 |
+
b_neg: ScaleIn = ScaleIn.One,
|
| 121 |
+
c_sat: Saturate = Saturate.False_,
|
| 122 |
+
is_sparse: bool = False,
|
| 123 |
+
max_shift: MaxShift = MaxShift.NoShift,
|
| 124 |
+
) -> int:
|
| 125 |
+
"""
|
| 126 |
+
Build the 32-bit instruction descriptor for Blackwell MMA.
|
| 127 |
+
All matrix/accumulator **types must be CUTLASS scalar classes** –
|
| 128 |
+
passing integers is forbidden.
|
| 129 |
+
"""
|
| 130 |
+
# --- encode element formats -------------------------------------------------
|
| 131 |
+
a_fmt = int(to_UMMA_format(a_type))
|
| 132 |
+
b_fmt = int(to_UMMA_format(b_type))
|
| 133 |
+
c_fmt = int(to_C_format(c_type))
|
| 134 |
+
|
| 135 |
+
# --- range checks on M/N -----------------------------------------------------
|
| 136 |
+
if M not in (64, 128, 256):
|
| 137 |
+
raise ValueError("M must be 64, 128 or 256")
|
| 138 |
+
if N < 8 or N > 256 or (N & 7):
|
| 139 |
+
raise ValueError("N must be a multiple of 8 in the range 8…256")
|
| 140 |
+
|
| 141 |
+
m_dim = M >> 4 # 5-bit field
|
| 142 |
+
n_dim = N >> 3 # 6-bit field
|
| 143 |
+
|
| 144 |
+
# fmt: off
|
| 145 |
+
# --- pack the bit-fields -----------------------------------------------------
|
| 146 |
+
desc = 0
|
| 147 |
+
desc |= (0 & 0x3) << 0 # sparse_id2 (always 0 here)
|
| 148 |
+
desc |= (int(is_sparse) & 0x1) << 2 # sparse_flag
|
| 149 |
+
desc |= (int(c_sat) & 0x1) << 3 # saturate
|
| 150 |
+
desc |= (c_fmt & 0x3) << 4 # c_format
|
| 151 |
+
desc |= (a_fmt & 0x7) << 7 # a_format
|
| 152 |
+
desc |= (b_fmt & 0x7) << 10 # b_format
|
| 153 |
+
desc |= (int(a_neg) & 0x1) << 13 # a_negate
|
| 154 |
+
desc |= (int(b_neg) & 0x1) << 14 # b_negate
|
| 155 |
+
desc |= (int(a_major) & 0x1) << 15 # a_major
|
| 156 |
+
desc |= (int(b_major) & 0x1) << 16 # b_major
|
| 157 |
+
desc |= (n_dim & 0x3F) << 17 # n_dim (6 bits)
|
| 158 |
+
desc |= (m_dim & 0x1F) << 24 # m_dim (5 bits)
|
| 159 |
+
desc |= (int(max_shift) & 0x3) << 30 # max_shift (2 bits)
|
| 160 |
+
# fmt: on
|
| 161 |
+
|
| 162 |
+
return desc & 0xFFFF_FFFF # ensure 32-bit result
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def mma_op_to_idesc(op: cute.nvgpu.tcgen05.mma.MmaOp):
|
| 166 |
+
return make_instr_desc(
|
| 167 |
+
op.a_dtype,
|
| 168 |
+
op.b_dtype,
|
| 169 |
+
op.acc_dtype,
|
| 170 |
+
op.shape_mnk[0],
|
| 171 |
+
op.shape_mnk[1],
|
| 172 |
+
Major.K if op.a_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K else Major.MN,
|
| 173 |
+
Major.K if op.b_major_mode == cute.nvgpu.tcgen05.mma.OperandMajorMode.K else Major.MN,
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
class LayoutType(IntEnum): # occupies the top-3 bits [61:64)
|
| 178 |
+
SWIZZLE_NONE = 0 # (a.k.a. “INTERLEAVE” in older docs)
|
| 179 |
+
SWIZZLE_128B_BASE32B = 1
|
| 180 |
+
SWIZZLE_128B = 2
|
| 181 |
+
SWIZZLE_64B = 4
|
| 182 |
+
SWIZZLE_32B = 6
|
| 183 |
+
# values 3,5,7 are reserved / illegal for UMMA
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# ---------------------------------------------------------------------------
|
| 187 |
+
# Helpers – figure out the SWIZZLE_* family from the tensor layout
|
| 188 |
+
# ---------------------------------------------------------------------------
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _layout_type(swizzle: cute.Swizzle) -> LayoutType:
|
| 192 |
+
B, M, S = swizzle.num_bits, swizzle.num_base, swizzle.num_shift
|
| 193 |
+
|
| 194 |
+
if M == 4: # Swizzle<*,4,3>
|
| 195 |
+
if S != 3:
|
| 196 |
+
raise ValueError("Unexpected swizzle shift – want S==3 for M==4")
|
| 197 |
+
return {
|
| 198 |
+
0: LayoutType.SWIZZLE_NONE,
|
| 199 |
+
1: LayoutType.SWIZZLE_32B,
|
| 200 |
+
2: LayoutType.SWIZZLE_64B,
|
| 201 |
+
3: LayoutType.SWIZZLE_128B,
|
| 202 |
+
}[B] # KeyError ⇒ invalid B→ raise
|
| 203 |
+
if M == 5: # Swizzle<2,5,2> (the only legal triple for M==5)
|
| 204 |
+
if (B, S) != (2, 2):
|
| 205 |
+
raise ValueError("Only Swizzle<2,5,2> supported for 128B_BASE32B")
|
| 206 |
+
return LayoutType.SWIZZLE_128B_BASE32B
|
| 207 |
+
|
| 208 |
+
# Any other (M,B,S) triple is not a UMMA-legal shared-memory layout
|
| 209 |
+
raise ValueError("Unsupported swizzle triple for UMMA smem descriptor")
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def make_smem_desc_base(layout: cute.Layout, swizzle: cute.Swizzle, major: Major) -> int:
|
| 213 |
+
"""
|
| 214 |
+
Convert a 2-D *shared-memory* Cute layout into the Blackwell 64-bit
|
| 215 |
+
smem-descriptor, without the smem start address.
|
| 216 |
+
layout must correspond to layout of an uint128 tensor.
|
| 217 |
+
"""
|
| 218 |
+
# ------------------------------------------------------------------ meta
|
| 219 |
+
layout_type = _layout_type(swizzle) # resolve SWIZZLE_* family
|
| 220 |
+
|
| 221 |
+
VERSION = 1 # bits 46–47
|
| 222 |
+
LBO_MODE = 0 # bit 52
|
| 223 |
+
BASE_OFFSET = 0 # bits 49–51 (CUTLASS always 0)
|
| 224 |
+
|
| 225 |
+
# ---------------------------------------------------------- strides (units: uint128_t = 16 B)
|
| 226 |
+
swizzle_atom_mn_size = {
|
| 227 |
+
LayoutType.SWIZZLE_NONE: 1,
|
| 228 |
+
LayoutType.SWIZZLE_32B: 2,
|
| 229 |
+
LayoutType.SWIZZLE_64B: 4,
|
| 230 |
+
LayoutType.SWIZZLE_128B: 8,
|
| 231 |
+
LayoutType.SWIZZLE_128B_BASE32B: 8,
|
| 232 |
+
}[layout_type]
|
| 233 |
+
|
| 234 |
+
if major is Major.MN:
|
| 235 |
+
swizzle_atom_k_size = 4 if layout_type is LayoutType.SWIZZLE_128B_BASE32B else 8
|
| 236 |
+
canonical_layout = cute.logical_divide(layout, (swizzle_atom_mn_size, swizzle_atom_k_size))
|
| 237 |
+
if not cute.is_congruent(canonical_layout, ((1, 1), (1, 1))):
|
| 238 |
+
raise ValueError("Not a canonical UMMA_MN Layout: Expected profile failure.")
|
| 239 |
+
stride_00 = canonical_layout.stride[0][0]
|
| 240 |
+
if layout_type is not LayoutType.SWIZZLE_NONE and stride_00 != 1:
|
| 241 |
+
raise ValueError("Not a canonical UMMA_MN Layout: Expected stride failure.")
|
| 242 |
+
stride_10 = canonical_layout.stride[1][0]
|
| 243 |
+
if stride_10 != swizzle_atom_mn_size:
|
| 244 |
+
raise ValueError("Not a canonical UMMA_MN Layout: Expected stride failure.")
|
| 245 |
+
stride_01, stride_11 = canonical_layout.stride[0][1], canonical_layout.stride[1][1]
|
| 246 |
+
if layout_type is LayoutType.SWIZZLE_NONE:
|
| 247 |
+
stride_byte_offset, leading_byte_offset = stride_01, stride_11
|
| 248 |
+
else:
|
| 249 |
+
stride_byte_offset, leading_byte_offset = stride_11, stride_01
|
| 250 |
+
else:
|
| 251 |
+
if layout_type == LayoutType.SWIZZLE_128B_BASE32B:
|
| 252 |
+
raise ValueError("SWIZZLE_128B_BASE32B is invalid for Major-K")
|
| 253 |
+
if not cute.size(layout.shape[0]) % 8 == 0:
|
| 254 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected MN-size multiple of 8.")
|
| 255 |
+
canonical_layout = cute.logical_divide(layout, (8, 2))
|
| 256 |
+
if not cute.is_congruent(canonical_layout, ((1, 1), (1, 1))):
|
| 257 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected profile failure.")
|
| 258 |
+
stride_00 = canonical_layout.stride[0][0]
|
| 259 |
+
if stride_00 != swizzle_atom_mn_size:
|
| 260 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected stride failure.")
|
| 261 |
+
stride_10 = canonical_layout.stride[1][0]
|
| 262 |
+
if layout_type is not LayoutType.SWIZZLE_NONE and stride_10 != 1:
|
| 263 |
+
raise ValueError("Not a canonical UMMA_K Layout: Expected stride failure.")
|
| 264 |
+
stride_01 = canonical_layout.stride[0][1]
|
| 265 |
+
stride_byte_offset, leading_byte_offset = stride_01, stride_10
|
| 266 |
+
|
| 267 |
+
# ------------------------------------------------------------------ pack
|
| 268 |
+
desc = 0
|
| 269 |
+
# leading_byte_offset_ [16:30)
|
| 270 |
+
desc |= (leading_byte_offset & 0x3FFF) << 16
|
| 271 |
+
# stride_byte_offset_ [32:46)
|
| 272 |
+
desc |= (stride_byte_offset & 0x3FFF) << 32
|
| 273 |
+
# version_ [46:48)
|
| 274 |
+
desc |= (VERSION & 0x3) << 46
|
| 275 |
+
# base_offset_ [49:52)
|
| 276 |
+
desc |= (BASE_OFFSET & 0x7) << 49
|
| 277 |
+
# lbo_mode_ [52:53)
|
| 278 |
+
desc |= (LBO_MODE & 0x1) << 52
|
| 279 |
+
# layout_type_ [61:64)
|
| 280 |
+
desc |= (int(layout_type) & 0x7) << 61
|
| 281 |
+
|
| 282 |
+
return desc & 0xFFFF_FFFF_FFFF_FFFF # force 64-bit width
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def make_smem_desc_start_addr(start_addr: cute.Pointer) -> cutlass.Int32:
|
| 286 |
+
# 14 bits, remove 4 LSB (bits 0-13 in desc)
|
| 287 |
+
return (start_addr.toint() & 0x3FFFF) >> 4
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def smem_desc_base_from_tensor(sA: cute.Tensor, major: Major) -> int:
|
| 291 |
+
sA_swizzle = sA.iterator.type.swizzle_type
|
| 292 |
+
return make_smem_desc_base(
|
| 293 |
+
cute.recast_layout(128, sA.element_type.width, sA.layout[0]),
|
| 294 |
+
sA_swizzle,
|
| 295 |
+
major,
|
| 296 |
+
)
|
build/torch-cuda/flashrt_fa4/cute/named_barrier.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import enum
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class NamedBarrierFwd(enum.IntEnum):
|
| 7 |
+
Epilogue = enum.auto() # starts from 1 as barrier 0 is reserved for sync_threads()
|
| 8 |
+
WarpSchedulerWG1 = enum.auto()
|
| 9 |
+
WarpSchedulerWG2 = enum.auto()
|
| 10 |
+
WarpSchedulerWG3 = enum.auto()
|
| 11 |
+
PFull = enum.auto()
|
| 12 |
+
PEmpty = enum.auto()
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class NamedBarrierFwdSm100(enum.IntEnum):
|
| 16 |
+
Epilogue = enum.auto() # starts from 1 as barrier 0 is reserved for sync_threads()
|
| 17 |
+
TmemPtr = enum.auto()
|
| 18 |
+
SoftmaxStatsW0 = enum.auto()
|
| 19 |
+
SoftmaxStatsW1 = enum.auto()
|
| 20 |
+
SoftmaxStatsW2 = enum.auto()
|
| 21 |
+
SoftmaxStatsW3 = enum.auto()
|
| 22 |
+
SoftmaxStatsW4 = enum.auto()
|
| 23 |
+
SoftmaxStatsW5 = enum.auto()
|
| 24 |
+
SoftmaxStatsW6 = enum.auto()
|
| 25 |
+
SoftmaxStatsW7 = enum.auto()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class NamedBarrierBwd(enum.IntEnum):
|
| 29 |
+
Epilogue = enum.auto()
|
| 30 |
+
WarpSchedulerWG1 = enum.auto()
|
| 31 |
+
WarpSchedulerWG2 = enum.auto()
|
| 32 |
+
WarpSchedulerWG3 = enum.auto()
|
| 33 |
+
PdS = enum.auto()
|
| 34 |
+
dQFullWG0 = enum.auto()
|
| 35 |
+
dQFullWG1 = enum.auto()
|
| 36 |
+
dQFullWG2 = enum.auto()
|
| 37 |
+
dQEmptyWG0 = enum.auto()
|
| 38 |
+
dQEmptyWG1 = enum.auto()
|
| 39 |
+
dQEmptyWG2 = enum.auto()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class NamedBarrierBwdSm100(enum.IntEnum):
|
| 43 |
+
EpilogueWG1 = enum.auto()
|
| 44 |
+
EpilogueWG2 = enum.auto()
|
| 45 |
+
Compute = enum.auto()
|
| 46 |
+
dQaccReduce = enum.auto()
|
| 47 |
+
TmemPtr = enum.auto()
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class NamedBarrierFwdSm100_MLA2CTA(enum.IntEnum):
|
| 51 |
+
Epilogue = enum.auto()
|
| 52 |
+
TmemPtr = enum.auto()
|
| 53 |
+
Cpasync = enum.auto()
|
| 54 |
+
Softmax = enum.auto()
|
| 55 |
+
SoftmaxStatsFull = enum.auto()
|
| 56 |
+
SoftmaxStatsEmpty = enum.auto()
|
build/torch-cuda/flashrt_fa4/cute/pack_gqa.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import Union, Tuple
|
| 5 |
+
|
| 6 |
+
import cutlass
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
from cutlass.cute.nvgpu import cpasync
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
from fa4_cute_runtime.quack import layout_utils
|
| 12 |
+
import fa4_cute_runtime.flashrt_fa4.cute.utils as utils
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def pack_gqa_layout(T, qhead_per_kvhead, nheads_kv, head_idx):
|
| 16 |
+
"""Reshape a tensor to fold qhead_per_kvhead into the seqlen dimension (mode 0).
|
| 17 |
+
|
| 18 |
+
The head dimension is at mode ``head_idx``. Modes before it (1..head_idx-1)
|
| 19 |
+
are kept as-is (e.g. headdim for Q/O tensors), and modes after it are kept
|
| 20 |
+
as-is (e.g. batch).
|
| 21 |
+
|
| 22 |
+
For Q/O tensors (head_idx=2):
|
| 23 |
+
(seqlen_q, headdim, nheads, batch, ...) -> ((qhead_per_kvhead, seqlen_q), headdim, nheads_kv, batch, ...)
|
| 24 |
+
For LSE tensors (head_idx=1):
|
| 25 |
+
(seqlen_q, nheads, batch, ...) -> ((qhead_per_kvhead, seqlen_q), nheads_kv, batch, ...)
|
| 26 |
+
"""
|
| 27 |
+
head_stride = T.stride[head_idx]
|
| 28 |
+
shape_packed = (
|
| 29 |
+
(qhead_per_kvhead, T.shape[0]),
|
| 30 |
+
*[T.shape[i] for i in range(1, head_idx)],
|
| 31 |
+
nheads_kv,
|
| 32 |
+
*[T.shape[i] for i in range(head_idx + 1, len(T.shape))],
|
| 33 |
+
)
|
| 34 |
+
stride_packed = (
|
| 35 |
+
(head_stride, T.stride[0]),
|
| 36 |
+
*[T.stride[i] for i in range(1, head_idx)],
|
| 37 |
+
head_stride * qhead_per_kvhead,
|
| 38 |
+
*[T.stride[i] for i in range(head_idx + 1, len(T.shape))],
|
| 39 |
+
)
|
| 40 |
+
return cute.make_tensor(T.iterator, cute.make_layout(shape_packed, stride=stride_packed))
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def make_packgqa_tiled_tma_atom(
|
| 44 |
+
op: cute.atom.CopyOp,
|
| 45 |
+
gmem_tensor: cute.Tensor,
|
| 46 |
+
smem_layout: Union[cute.Layout, cute.ComposedLayout],
|
| 47 |
+
cta_tiler: Tuple[int, int],
|
| 48 |
+
qhead_per_kvhead: int,
|
| 49 |
+
head_idx: int,
|
| 50 |
+
):
|
| 51 |
+
# This packing and unpacking of the layout is so that we keep the same TMA dimension as usual.
|
| 52 |
+
# e.g. for (seqlen, d, nheads, b) layout, we still have 4D TMA after packing to
|
| 53 |
+
# ((nheads, seqlen), d, b).
|
| 54 |
+
# If we instead pack directly to ((qhead_per_kvhead, seqlen), d, nheads_kv, b) we'd have 5D TMA.
|
| 55 |
+
# Pack headdim and seqlen dim into 1: (seqlen, d, nheads, b) -> ((nheads, seqlen), d, b)
|
| 56 |
+
gmem_tensor = layout_utils.select(
|
| 57 |
+
gmem_tensor, [head_idx, *range(head_idx), *range(head_idx + 1, cute.rank(gmem_tensor))]
|
| 58 |
+
)
|
| 59 |
+
gmem_tensor = cute.group_modes(gmem_tensor, 0, 2)
|
| 60 |
+
assert cta_tiler[0] % qhead_per_kvhead == 0, (
|
| 61 |
+
"CTA tile size in the seqlen dimension must be divisible by qhead_per_kvhead"
|
| 62 |
+
)
|
| 63 |
+
tma_atom, tma_tensor = cpasync.make_tiled_tma_atom(
|
| 64 |
+
op,
|
| 65 |
+
gmem_tensor,
|
| 66 |
+
smem_layout,
|
| 67 |
+
((qhead_per_kvhead, cta_tiler[0] // qhead_per_kvhead), cta_tiler[1]), # No mcast
|
| 68 |
+
)
|
| 69 |
+
# Unpack from ((nheads, seqlen), d, b) -> ((qhead_per_kvhead, seqlen), d, nheads_kv, b)
|
| 70 |
+
T = tma_tensor
|
| 71 |
+
shape_packed = (
|
| 72 |
+
(qhead_per_kvhead, T.shape[0][1]),
|
| 73 |
+
*[T.shape[i] for i in range(1, head_idx)],
|
| 74 |
+
T.shape[0][0] // qhead_per_kvhead,
|
| 75 |
+
*[T.shape[i] for i in range(head_idx, len(T.shape))],
|
| 76 |
+
)
|
| 77 |
+
stride_packed = (
|
| 78 |
+
*[T.stride[i] for i in range(head_idx)],
|
| 79 |
+
T.stride[0][0] * qhead_per_kvhead,
|
| 80 |
+
*[T.stride[i] for i in range(head_idx, len(T.shape))],
|
| 81 |
+
)
|
| 82 |
+
tma_tensor = cute.make_tensor(T.iterator, cute.make_layout(shape_packed, stride=stride_packed))
|
| 83 |
+
return tma_atom, tma_tensor
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def unpack_gqa_layout(T, qhead_per_kvhead, head_idx):
|
| 87 |
+
"""Reverse of pack_gqa_layout: unfold qhead_per_kvhead from the seqlen dimension (mode 0).
|
| 88 |
+
|
| 89 |
+
The head dimension is at mode ``head_idx``. Modes before it (1..head_idx-1)
|
| 90 |
+
are kept as-is (e.g. headdim for Q/O tensors), and modes after it are kept
|
| 91 |
+
as-is (e.g. batch).
|
| 92 |
+
|
| 93 |
+
For Q/O tensors (head_idx=2):
|
| 94 |
+
((qhead_per_kvhead, seqlen_q), headdim, nheads_kv, batch, ...) -> (seqlen_q, headdim, nheads, batch, ...)
|
| 95 |
+
For LSE tensors (head_idx=1):
|
| 96 |
+
((qhead_per_kvhead, seqlen_q), nheads_kv, batch, ...) -> (seqlen_q, nheads, batch, ...)
|
| 97 |
+
"""
|
| 98 |
+
seqlen_stride = T.stride[0][1]
|
| 99 |
+
head_stride = T.stride[0][0]
|
| 100 |
+
shape_unpacked = (
|
| 101 |
+
T.shape[0][1],
|
| 102 |
+
*[T.shape[i] for i in range(1, head_idx)],
|
| 103 |
+
T.shape[head_idx] * qhead_per_kvhead,
|
| 104 |
+
*[T.shape[i] for i in range(head_idx + 1, len(T.shape))],
|
| 105 |
+
)
|
| 106 |
+
stride_unpacked = (
|
| 107 |
+
seqlen_stride,
|
| 108 |
+
*[T.stride[i] for i in range(1, head_idx)],
|
| 109 |
+
head_stride,
|
| 110 |
+
*[T.stride[i] for i in range(head_idx + 1, len(T.shape))],
|
| 111 |
+
)
|
| 112 |
+
return cute.make_tensor(T.iterator, cute.make_layout(shape_unpacked, stride=stride_unpacked))
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@dataclass
|
| 116 |
+
class PackGQA:
|
| 117 |
+
m_block_size: cutlass.Constexpr[int]
|
| 118 |
+
head_dim_padded: cutlass.Constexpr[int]
|
| 119 |
+
check_hdim_oob: cutlass.Constexpr[bool]
|
| 120 |
+
qhead_per_kvhead: cutlass.Constexpr[bool]
|
| 121 |
+
|
| 122 |
+
@cute.jit
|
| 123 |
+
def compute_ptr(
|
| 124 |
+
self,
|
| 125 |
+
tensor: cute.Tensor,
|
| 126 |
+
cRows: cute.Tensor,
|
| 127 |
+
tidx: cutlass.Int32,
|
| 128 |
+
block: cutlass.Int32,
|
| 129 |
+
threads_per_row: cutlass.Constexpr[int],
|
| 130 |
+
num_threads: cutlass.Constexpr[int],
|
| 131 |
+
):
|
| 132 |
+
num_ptr_per_thread = cute.ceil_div(cute.size(cRows), threads_per_row)
|
| 133 |
+
tPrPtr = cute.make_fragment(num_ptr_per_thread, cutlass.Int64)
|
| 134 |
+
for i in cutlass.range_constexpr(num_ptr_per_thread):
|
| 135 |
+
row = i * num_threads + cRows[tidx % threads_per_row][0]
|
| 136 |
+
idx = block * self.m_block_size + row
|
| 137 |
+
m_idx = idx // self.qhead_per_kvhead
|
| 138 |
+
h_idx = idx - m_idx * self.qhead_per_kvhead
|
| 139 |
+
tPrPtr[i] = utils.elem_pointer(tensor, ((h_idx, m_idx),)).toint()
|
| 140 |
+
return tPrPtr
|
| 141 |
+
|
| 142 |
+
@cute.jit
|
| 143 |
+
def load_Q(
|
| 144 |
+
self,
|
| 145 |
+
mQ: cute.Tensor, # ((qhead_per_kvhead, seqlen_q), headdim)
|
| 146 |
+
sQ: cute.Tensor, # (m_block_size, head_dim_padded)
|
| 147 |
+
gmem_tiled_copy: cute.TiledCopy,
|
| 148 |
+
tidx: cutlass.Int32,
|
| 149 |
+
block: cutlass.Int32,
|
| 150 |
+
seqlen: cutlass.Int32,
|
| 151 |
+
):
|
| 152 |
+
gmem_thr_copy = gmem_tiled_copy.get_slice(tidx)
|
| 153 |
+
cQ = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 154 |
+
tQsQ = gmem_thr_copy.partition_D(sQ)
|
| 155 |
+
tQcQ = gmem_thr_copy.partition_S(cQ)
|
| 156 |
+
t0QcQ = gmem_thr_copy.get_slice(0).partition_S(cQ)
|
| 157 |
+
tQpQ = utils.predicate_k(tQcQ, limit=mQ.shape[1])
|
| 158 |
+
tQcQ_row = tQcQ[0, None, 0]
|
| 159 |
+
threads_per_row = gmem_tiled_copy.layout_tv_tiled.shape[0][0]
|
| 160 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, "threads_per_row must divide WARP_SIZE"
|
| 161 |
+
num_threads = gmem_tiled_copy.size
|
| 162 |
+
tPrQPtr = self.compute_ptr(mQ[None, 0], tQcQ_row, tidx, block, threads_per_row, num_threads)
|
| 163 |
+
for m in cutlass.range_constexpr(cute.size(tQsQ.shape[1])):
|
| 164 |
+
q_ptr_i64 = utils.shuffle_sync(
|
| 165 |
+
tPrQPtr[m // threads_per_row], m % threads_per_row, width=threads_per_row
|
| 166 |
+
)
|
| 167 |
+
q_gmem_ptr = cute.make_ptr(
|
| 168 |
+
mQ.element_type, q_ptr_i64, cute.AddressSpace.gmem, assumed_align=16
|
| 169 |
+
)
|
| 170 |
+
if (
|
| 171 |
+
t0QcQ[0, m, 0][0]
|
| 172 |
+
< seqlen * self.qhead_per_kvhead - block * self.m_block_size - tQcQ_row[0][0]
|
| 173 |
+
):
|
| 174 |
+
mQ_cur = cute.make_tensor(q_gmem_ptr, (self.head_dim_padded,))
|
| 175 |
+
elems_per_load = cute.size(tQsQ.shape[0][0])
|
| 176 |
+
mQ_cur_copy = cute.tiled_divide(mQ_cur, (elems_per_load,))
|
| 177 |
+
for k in cutlass.range_constexpr(cute.size(tQsQ.shape[2])):
|
| 178 |
+
ki = tQcQ[0, 0, k][1] // elems_per_load
|
| 179 |
+
cute.copy(
|
| 180 |
+
gmem_thr_copy,
|
| 181 |
+
mQ_cur_copy[None, ki],
|
| 182 |
+
tQsQ[None, m, k],
|
| 183 |
+
pred=tQpQ[None, m, k] if cutlass.const_expr(self.check_hdim_oob) else None,
|
| 184 |
+
)
|
| 185 |
+
# We don't need to clear the sQ smem tiles since we'll only write out the valid outputs
|
| 186 |
+
|
| 187 |
+
@cute.jit
|
| 188 |
+
def store_LSE(
|
| 189 |
+
self,
|
| 190 |
+
mLSE: cute.Tensor, # (qhead_per_kvhead, seqlen_q)
|
| 191 |
+
tLSErLSE: cute.Tensor, # (m_block_size, head_dim_padded)
|
| 192 |
+
tiled_mma: cute.TiledMma,
|
| 193 |
+
tidx: cutlass.Int32,
|
| 194 |
+
block: cutlass.Int32,
|
| 195 |
+
seqlen: cutlass.Int32,
|
| 196 |
+
):
|
| 197 |
+
thr_mma = tiled_mma.get_slice(tidx)
|
| 198 |
+
caccO = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 199 |
+
taccOcO = thr_mma.partition_C(caccO)
|
| 200 |
+
taccOcO_row = layout_utils.reshape_acc_to_mn(taccOcO)[None, 0]
|
| 201 |
+
assert cute.size(tLSErLSE) == cute.size(taccOcO_row)
|
| 202 |
+
threads_per_row = tiled_mma.tv_layout_C.shape[0][0]
|
| 203 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, "threads_per_row must divide WARP_SIZE"
|
| 204 |
+
assert cute.size(tLSErLSE) <= threads_per_row
|
| 205 |
+
num_threads = tiled_mma.size
|
| 206 |
+
tPrLSEPtr = self.compute_ptr(mLSE, taccOcO_row, tidx, block, threads_per_row, num_threads)
|
| 207 |
+
for m in cutlass.range_constexpr(cute.size(tLSErLSE)):
|
| 208 |
+
lse_ptr_i64 = utils.shuffle_sync(
|
| 209 |
+
tPrLSEPtr[m // threads_per_row],
|
| 210 |
+
m % threads_per_row,
|
| 211 |
+
width=threads_per_row,
|
| 212 |
+
)
|
| 213 |
+
lse_gmem_ptr = cute.make_ptr(
|
| 214 |
+
mLSE.element_type, lse_ptr_i64, cute.AddressSpace.gmem, assumed_align=4
|
| 215 |
+
)
|
| 216 |
+
row = block * self.m_block_size + taccOcO_row[m][0]
|
| 217 |
+
# Only the thread corresponding to column 0 writes out the lse to gmem
|
| 218 |
+
if taccOcO[0][1] == 0 and row < seqlen * self.qhead_per_kvhead:
|
| 219 |
+
mLSE_copy = cute.make_tensor(lse_gmem_ptr, (1,))
|
| 220 |
+
mLSE_copy[0] = tLSErLSE[m]
|
| 221 |
+
|
| 222 |
+
@cute.jit
|
| 223 |
+
def store_O(
|
| 224 |
+
self,
|
| 225 |
+
mO: cute.Tensor, # ((qhead_per_kvhead, seqlen_q), headdim)
|
| 226 |
+
tOrO: cute.Tensor, # (m_block_size, head_dim_padded) split across threads according to gmem_tiled_copy
|
| 227 |
+
gmem_tiled_copy: cute.TiledCopy,
|
| 228 |
+
tidx: cutlass.Int32,
|
| 229 |
+
block: cutlass.Int32,
|
| 230 |
+
seqlen: cutlass.Int32,
|
| 231 |
+
):
|
| 232 |
+
gmem_thr_copy = gmem_tiled_copy.get_slice(tidx)
|
| 233 |
+
cO = cute.make_identity_tensor((self.m_block_size, self.head_dim_padded))
|
| 234 |
+
tOcO = gmem_thr_copy.partition_S(cO)
|
| 235 |
+
t0OcO = gmem_thr_copy.get_slice(0).partition_S(cO)
|
| 236 |
+
tOpO = utils.predicate_k(tOcO, limit=mO.shape[1])
|
| 237 |
+
tOcO_row = tOcO[0, None, 0]
|
| 238 |
+
threads_per_row = gmem_tiled_copy.layout_tv_tiled.shape[0][0]
|
| 239 |
+
assert cute.arch.WARP_SIZE % threads_per_row == 0, "threads_per_row must divide WARP_SIZE"
|
| 240 |
+
num_threads = gmem_tiled_copy.size
|
| 241 |
+
tPrOPtr = self.compute_ptr(mO[None, 0], tOcO_row, tidx, block, threads_per_row, num_threads)
|
| 242 |
+
for m in cutlass.range_constexpr(cute.size(tOrO.shape[1])):
|
| 243 |
+
o_ptr_i64 = utils.shuffle_sync(
|
| 244 |
+
tPrOPtr[m // threads_per_row], m % threads_per_row, width=threads_per_row
|
| 245 |
+
)
|
| 246 |
+
o_gmem_ptr = cute.make_ptr(
|
| 247 |
+
mO.element_type, o_ptr_i64, cute.AddressSpace.gmem, assumed_align=16
|
| 248 |
+
)
|
| 249 |
+
if (
|
| 250 |
+
t0OcO[0, m, 0][0]
|
| 251 |
+
< seqlen * self.qhead_per_kvhead - block * self.m_block_size - tOcO_row[0][0]
|
| 252 |
+
):
|
| 253 |
+
mO_cur = cute.make_tensor(o_gmem_ptr, (self.head_dim_padded,))
|
| 254 |
+
elems_per_load = cute.size(tOrO.shape[0][0])
|
| 255 |
+
mO_cur_copy = cute.tiled_divide(mO_cur, (elems_per_load,))
|
| 256 |
+
for k in cutlass.range_constexpr(cute.size(tOrO.shape[2])):
|
| 257 |
+
ki = tOcO[0, 0, k][1] // elems_per_load
|
| 258 |
+
cute.copy(
|
| 259 |
+
gmem_thr_copy,
|
| 260 |
+
tOrO[None, m, k],
|
| 261 |
+
mO_cur_copy[None, ki],
|
| 262 |
+
pred=tOpO[None, m, k] if cutlass.const_expr(self.check_hdim_oob) else None,
|
| 263 |
+
)
|
build/torch-cuda/flashrt_fa4/cute/paged_kv.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Type
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
from cutlass.cute.nvgpu import cpasync
|
| 7 |
+
from cutlass import Int32, const_expr
|
| 8 |
+
|
| 9 |
+
from fa4_cute_runtime.flashrt_fa4.cute import utils
|
| 10 |
+
from fa4_cute_runtime.quack.cute_dsl_utils import ParamsBase
|
| 11 |
+
from cutlass.cute import FastDivmodDivisor
|
| 12 |
+
|
| 13 |
+
import math
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class PagedKVManager(ParamsBase):
|
| 18 |
+
mPageTable: cute.Tensor
|
| 19 |
+
mK_paged: cute.Tensor
|
| 20 |
+
mV_paged: cute.Tensor
|
| 21 |
+
thread_idx: Int32
|
| 22 |
+
|
| 23 |
+
page_size_divmod: FastDivmodDivisor
|
| 24 |
+
seqlen_k: Int32
|
| 25 |
+
leftpad_k: Int32
|
| 26 |
+
n_block_size: Int32
|
| 27 |
+
num_threads: cutlass.Constexpr[Int32]
|
| 28 |
+
head_dim_padded: cutlass.Constexpr[Int32]
|
| 29 |
+
head_dim_v_padded: cutlass.Constexpr[Int32]
|
| 30 |
+
|
| 31 |
+
arch: cutlass.Constexpr[Int32]
|
| 32 |
+
v_gmem_transposed: cutlass.Constexpr[bool]
|
| 33 |
+
|
| 34 |
+
gmem_threads_per_row: cutlass.Constexpr[Int32]
|
| 35 |
+
page_entry_per_thread: Int32
|
| 36 |
+
async_copy_elems: Int32
|
| 37 |
+
|
| 38 |
+
gmem_tiled_copy_KV: cute.TiledCopy
|
| 39 |
+
gmem_thr_copy_KV: cute.TiledCopy
|
| 40 |
+
tPrPage: cute.Tensor
|
| 41 |
+
tPrPageOffset: cute.Tensor
|
| 42 |
+
tKpK: cute.Tensor
|
| 43 |
+
tVpV: cute.Tensor
|
| 44 |
+
|
| 45 |
+
@staticmethod
|
| 46 |
+
def create(
|
| 47 |
+
mPageTable: cute.Tensor,
|
| 48 |
+
mK_paged: cute.Tensor,
|
| 49 |
+
mV_paged: cute.Tensor,
|
| 50 |
+
page_size_divmod: FastDivmodDivisor,
|
| 51 |
+
bidb: Int32,
|
| 52 |
+
bidh: Int32,
|
| 53 |
+
thread_idx: Int32,
|
| 54 |
+
seqlen_k: Int32,
|
| 55 |
+
leftpad_k: Int32,
|
| 56 |
+
n_block_size: cutlass.Constexpr[Int32],
|
| 57 |
+
head_dim_padded: cutlass.Constexpr[Int32],
|
| 58 |
+
head_dim_v_padded: cutlass.Constexpr[Int32],
|
| 59 |
+
num_threads: cutlass.Constexpr[Int32],
|
| 60 |
+
dtype: Type[cutlass.Numeric],
|
| 61 |
+
arch: cutlass.Constexpr[int] = 100,
|
| 62 |
+
):
|
| 63 |
+
# SM100 transposes V in gmem to (dv, page_size, num_pages);
|
| 64 |
+
# SM90 keeps V as (page_size, dv, num_pages), same layout as K.
|
| 65 |
+
v_gmem_transposed = arch != 90
|
| 66 |
+
universal_copy_bits = 128
|
| 67 |
+
async_copy_elems = universal_copy_bits // dtype.width
|
| 68 |
+
dtype_bytes = dtype.width // 8
|
| 69 |
+
gmem_k_block_size = math.gcd(
|
| 70 |
+
head_dim_padded,
|
| 71 |
+
head_dim_v_padded,
|
| 72 |
+
128 // dtype_bytes,
|
| 73 |
+
)
|
| 74 |
+
assert gmem_k_block_size % async_copy_elems == 0
|
| 75 |
+
gmem_threads_per_row = gmem_k_block_size // async_copy_elems
|
| 76 |
+
assert cute.arch.WARP_SIZE % gmem_threads_per_row == 0
|
| 77 |
+
atom_async_copy = cute.make_copy_atom(
|
| 78 |
+
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 79 |
+
dtype,
|
| 80 |
+
num_bits_per_copy=universal_copy_bits,
|
| 81 |
+
)
|
| 82 |
+
thr_layout = cute.make_ordered_layout(
|
| 83 |
+
(num_threads // gmem_threads_per_row, gmem_threads_per_row),
|
| 84 |
+
order=(1, 0),
|
| 85 |
+
)
|
| 86 |
+
val_layout = cute.make_layout((1, async_copy_elems))
|
| 87 |
+
gmem_tiled_copy_KV = cute.make_tiled_copy_tv(atom_async_copy, thr_layout, val_layout)
|
| 88 |
+
gmem_thr_copy_KV = gmem_tiled_copy_KV.get_slice(thread_idx)
|
| 89 |
+
page_entry_per_thread = n_block_size // num_threads
|
| 90 |
+
|
| 91 |
+
tPrPage = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
| 92 |
+
tPrPageOffset = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
| 93 |
+
|
| 94 |
+
mPageTable = mPageTable[bidb, None]
|
| 95 |
+
mK_paged = mK_paged[None, None, bidh, None]
|
| 96 |
+
mV_paged = mV_paged[None, None, bidh, None]
|
| 97 |
+
|
| 98 |
+
cK = cute.make_identity_tensor((n_block_size, head_dim_padded))
|
| 99 |
+
tKcK = gmem_thr_copy_KV.partition_S(cK)
|
| 100 |
+
tKpK = utils.predicate_k(tKcK, limit=mK_paged.shape[1])
|
| 101 |
+
|
| 102 |
+
if const_expr(head_dim_padded == head_dim_v_padded):
|
| 103 |
+
tVpV = tKpK
|
| 104 |
+
else:
|
| 105 |
+
cV = cute.make_identity_tensor((n_block_size, head_dim_v_padded))
|
| 106 |
+
tVcV = gmem_thr_copy_KV.partition_S(cV)
|
| 107 |
+
# When V is transposed in gmem, dv is shape[0]; otherwise dv is shape[1] (same as K)
|
| 108 |
+
tVpV = utils.predicate_k(tVcV, limit=mV_paged.shape[0 if v_gmem_transposed else 1])
|
| 109 |
+
|
| 110 |
+
return PagedKVManager(
|
| 111 |
+
mPageTable,
|
| 112 |
+
mK_paged,
|
| 113 |
+
mV_paged,
|
| 114 |
+
thread_idx,
|
| 115 |
+
page_size_divmod,
|
| 116 |
+
seqlen_k,
|
| 117 |
+
leftpad_k,
|
| 118 |
+
n_block_size,
|
| 119 |
+
num_threads,
|
| 120 |
+
head_dim_padded,
|
| 121 |
+
head_dim_v_padded,
|
| 122 |
+
arch,
|
| 123 |
+
v_gmem_transposed,
|
| 124 |
+
gmem_threads_per_row,
|
| 125 |
+
page_entry_per_thread,
|
| 126 |
+
async_copy_elems,
|
| 127 |
+
gmem_tiled_copy_KV,
|
| 128 |
+
gmem_thr_copy_KV,
|
| 129 |
+
tPrPage,
|
| 130 |
+
tPrPageOffset,
|
| 131 |
+
tKpK,
|
| 132 |
+
tVpV,
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
@cute.jit
|
| 136 |
+
def load_page_table(self, n_block: Int32):
|
| 137 |
+
for i in cutlass.range(self.page_entry_per_thread, unroll=1):
|
| 138 |
+
row = (
|
| 139 |
+
i * self.num_threads
|
| 140 |
+
+ (self.thread_idx % self.gmem_threads_per_row)
|
| 141 |
+
* (self.num_threads // self.gmem_threads_per_row)
|
| 142 |
+
+ (self.thread_idx // self.gmem_threads_per_row)
|
| 143 |
+
)
|
| 144 |
+
row_idx = n_block * self.n_block_size + row
|
| 145 |
+
|
| 146 |
+
page_idx, page_offset = divmod(row_idx + self.leftpad_k, self.page_size_divmod)
|
| 147 |
+
|
| 148 |
+
is_valid = (
|
| 149 |
+
(i + 1) * self.num_threads <= self.n_block_size or row < self.n_block_size
|
| 150 |
+
) and row_idx < self.seqlen_k
|
| 151 |
+
page = self.mPageTable[page_idx] if is_valid else 0
|
| 152 |
+
|
| 153 |
+
self.tPrPage[i] = page
|
| 154 |
+
self.tPrPageOffset[i] = page_offset
|
| 155 |
+
|
| 156 |
+
@cute.jit
|
| 157 |
+
def compute_X_ptr(self, K_or_V: str):
|
| 158 |
+
tPrXPtr = cute.make_rmem_tensor((self.page_entry_per_thread,), cutlass.Int64)
|
| 159 |
+
mX = self.mK_paged if const_expr(K_or_V == "K") else self.mV_paged
|
| 160 |
+
# K is always (page_size, d, num_pages). V matches K when not transposed,
|
| 161 |
+
# but is (dv, page_size, num_pages) when transposed (SM100).
|
| 162 |
+
transposed = const_expr(K_or_V == "V" and self.v_gmem_transposed)
|
| 163 |
+
for i in cutlass.range(self.page_entry_per_thread, unroll=1):
|
| 164 |
+
page = self.tPrPage[i]
|
| 165 |
+
page_offset = self.tPrPageOffset[i]
|
| 166 |
+
if const_expr(transposed):
|
| 167 |
+
tPrXPtr[i] = utils.elem_pointer(mX, (0, page_offset, page)).toint()
|
| 168 |
+
else:
|
| 169 |
+
tPrXPtr[i] = utils.elem_pointer(mX, (page_offset, 0, page)).toint()
|
| 170 |
+
return tPrXPtr
|
| 171 |
+
|
| 172 |
+
@cute.jit
|
| 173 |
+
def _flatten_smem_sm100(self, sX: cute.Tensor, K_or_V: str):
|
| 174 |
+
"""Flatten SM100 smem ((a,b), cta_split, k) to (a,(b,k)); transpose V to (d,page_size)."""
|
| 175 |
+
sX_pi = cute.make_tensor(
|
| 176 |
+
sX.iterator,
|
| 177 |
+
cute.make_layout(
|
| 178 |
+
(sX.shape[0][0], (sX.shape[0][1], sX.shape[2])),
|
| 179 |
+
stride=(sX.stride[0][0], (sX.stride[0][1], sX.stride[2])),
|
| 180 |
+
),
|
| 181 |
+
)
|
| 182 |
+
if const_expr(K_or_V == "V"):
|
| 183 |
+
sX_pi = cute.make_tensor(sX_pi.iterator, cute.select(sX_pi.layout, mode=[1, 0]))
|
| 184 |
+
return sX_pi
|
| 185 |
+
|
| 186 |
+
@cute.jit
|
| 187 |
+
def _copy_row_async(
|
| 188 |
+
self,
|
| 189 |
+
tXsX: cute.Tensor,
|
| 190 |
+
tXcX: cute.Tensor,
|
| 191 |
+
mX_paged_cur_copy: cute.Tensor,
|
| 192 |
+
m: Int32,
|
| 193 |
+
should_load: cute.Tensor,
|
| 194 |
+
):
|
| 195 |
+
"""Issue cp.async copies for one row across all k-tiles."""
|
| 196 |
+
for k in cutlass.range_constexpr(cute.size(tXsX, mode=[2])):
|
| 197 |
+
ki = tXcX[0, 0, k][1] // self.async_copy_elems
|
| 198 |
+
mX_paged_cur_copy_ki = mX_paged_cur_copy[None, ki]
|
| 199 |
+
tXsX_k = tXsX[None, m, k]
|
| 200 |
+
mX_paged_cur_copy_ki = cute.make_tensor(mX_paged_cur_copy_ki.iterator, tXsX_k.layout)
|
| 201 |
+
cute.copy(
|
| 202 |
+
self.gmem_tiled_copy_KV,
|
| 203 |
+
mX_paged_cur_copy_ki,
|
| 204 |
+
tXsX_k,
|
| 205 |
+
pred=should_load,
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
@cute.jit
|
| 209 |
+
def load_KV(self, n_block: Int32, sX: cute.Tensor, K_or_V: str):
|
| 210 |
+
assert K_or_V in ("K", "V")
|
| 211 |
+
|
| 212 |
+
tPrXPtr = self.compute_X_ptr(K_or_V)
|
| 213 |
+
|
| 214 |
+
if const_expr(self.arch == 90):
|
| 215 |
+
# SM90: sX is already stage-sliced by caller (sK[None, None, stage]).
|
| 216 |
+
# Flatten hierarchical modes to get (n_block_size, head_dim).
|
| 217 |
+
sX_pi = cute.group_modes(sX, 0, 1)
|
| 218 |
+
# SM90 does NOT transpose V here (it's transposed via utils.transpose_view before MMA)
|
| 219 |
+
else:
|
| 220 |
+
sX_pi = self._flatten_smem_sm100(sX, K_or_V)
|
| 221 |
+
|
| 222 |
+
head_dim = self.head_dim_v_padded if const_expr(K_or_V == "V") else self.head_dim_padded
|
| 223 |
+
cX = cute.make_identity_tensor((self.n_block_size, head_dim))
|
| 224 |
+
tXsX = self.gmem_thr_copy_KV.partition_D(sX_pi)
|
| 225 |
+
tXcX = self.gmem_thr_copy_KV.partition_S(cX)
|
| 226 |
+
tXc0X = self.gmem_thr_copy_KV.get_slice(0).partition_S(cX)
|
| 227 |
+
|
| 228 |
+
seqlenk_row_limit = (
|
| 229 |
+
self.seqlen_k - n_block * self.n_block_size - tXcX[0][0] if n_block >= 0 else 0
|
| 230 |
+
)
|
| 231 |
+
for m in cutlass.range_constexpr(cute.size(tXsX, mode=[1])):
|
| 232 |
+
row_valid = tXc0X[0, m, 0][0] < seqlenk_row_limit
|
| 233 |
+
should_load = cute.make_fragment_like(tXsX[(0, None), m, 0], cute.Boolean)
|
| 234 |
+
should_load.fill(row_valid)
|
| 235 |
+
|
| 236 |
+
x_ptr_i64 = utils.shuffle_sync(
|
| 237 |
+
tPrXPtr[m // self.gmem_threads_per_row],
|
| 238 |
+
m % self.gmem_threads_per_row,
|
| 239 |
+
width=self.gmem_threads_per_row,
|
| 240 |
+
)
|
| 241 |
+
x_gmem_ptr = cute.make_ptr(
|
| 242 |
+
self.mK_paged.element_type, x_ptr_i64, cute.AddressSpace.gmem, assumed_align=16
|
| 243 |
+
)
|
| 244 |
+
mX_paged_cur = cute.make_tensor(x_gmem_ptr, cute.make_layout((head_dim,)))
|
| 245 |
+
mX_paged_cur_copy = cute.tiled_divide(mX_paged_cur, (self.async_copy_elems,))
|
| 246 |
+
self._copy_row_async(tXsX, tXcX, mX_paged_cur_copy, m, should_load)
|
build/torch-cuda/flashrt_fa4/cute/pipeline.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
|
| 6 |
+
import cutlass.cute as cute
|
| 7 |
+
from cutlass import Boolean, Int32, const_expr
|
| 8 |
+
from cutlass.cutlass_dsl import if_generate, dsl_user_op
|
| 9 |
+
from cutlass.pipeline import PipelineState
|
| 10 |
+
from cutlass.pipeline import PipelineUserType
|
| 11 |
+
from cutlass.pipeline import NamedBarrier as NamedBarrierOg
|
| 12 |
+
from cutlass.pipeline import PipelineAsync as PipelineAsyncOg
|
| 13 |
+
from cutlass.pipeline import PipelineCpAsync as PipelineCpAsyncOg
|
| 14 |
+
from cutlass.pipeline import PipelineTmaAsync as PipelineTmaAsyncOg
|
| 15 |
+
from cutlass.pipeline import PipelineTmaUmma as PipelineTmaUmmaOg
|
| 16 |
+
from cutlass.pipeline import PipelineUmmaAsync as PipelineUmmaAsyncOg
|
| 17 |
+
from cutlass.pipeline import PipelineAsyncUmma as PipelineAsyncUmmaOg
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _override_create(parent_cls, child_cls):
|
| 21 |
+
"""Create a static factory that constructs parent_cls then re-classes to child_cls."""
|
| 22 |
+
|
| 23 |
+
@staticmethod
|
| 24 |
+
def create(*args, **kwargs):
|
| 25 |
+
obj = parent_cls.create(*args, **kwargs)
|
| 26 |
+
# Can't assign to __class__ directly since the dataclass is frozen
|
| 27 |
+
object.__setattr__(obj, "__class__", child_cls)
|
| 28 |
+
return obj
|
| 29 |
+
|
| 30 |
+
return create
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _make_state(index: Int32, phase: Int32) -> PipelineState:
|
| 34 |
+
"""Construct a PipelineState from index and phase (count/stages unused by callers)."""
|
| 35 |
+
return PipelineState(stages=0, count=Int32(0), index=index, phase=phase)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class PipelineStateSimple:
|
| 39 |
+
"""
|
| 40 |
+
Pipeline state contains an index and phase bit corresponding to the current position in the circular buffer.
|
| 41 |
+
Use a single Int32 to store both the index and phase bit, then we use divmod to get the
|
| 42 |
+
index and phase. If stages is a power of 2, divmod turns into bit twiddling.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
def __init__(self, stages: int, phase_index: Int32):
|
| 46 |
+
self._stages = stages
|
| 47 |
+
self._phase_index = phase_index
|
| 48 |
+
|
| 49 |
+
def clone(self) -> "PipelineStateSimple":
|
| 50 |
+
return PipelineStateSimple(self.stages, self._phase_index)
|
| 51 |
+
|
| 52 |
+
@property
|
| 53 |
+
def stages(self) -> int:
|
| 54 |
+
return self._stages
|
| 55 |
+
|
| 56 |
+
@property
|
| 57 |
+
def index(self) -> Int32:
|
| 58 |
+
if const_expr(self._stages == 1):
|
| 59 |
+
return Int32(0)
|
| 60 |
+
else:
|
| 61 |
+
return self._phase_index % self._stages
|
| 62 |
+
|
| 63 |
+
@property
|
| 64 |
+
def phase(self) -> Int32:
|
| 65 |
+
# PTX docs say that the phase parity needs to be 0 or 1, so by right we need to
|
| 66 |
+
# take modulo 2. But in practice just passing the phase in without modulo works fine.
|
| 67 |
+
if const_expr(self._stages == 1):
|
| 68 |
+
return self._phase_index
|
| 69 |
+
else:
|
| 70 |
+
return self._phase_index // self._stages
|
| 71 |
+
|
| 72 |
+
def advance(self):
|
| 73 |
+
if const_expr(self._stages == 1):
|
| 74 |
+
self._phase_index ^= 1
|
| 75 |
+
else:
|
| 76 |
+
self._phase_index += 1
|
| 77 |
+
|
| 78 |
+
def __extract_mlir_values__(self):
|
| 79 |
+
phase_index = self._phase_index
|
| 80 |
+
return [phase_index.ir_value()]
|
| 81 |
+
|
| 82 |
+
def __new_from_mlir_values__(self, values):
|
| 83 |
+
return PipelineStateSimple(self.stages, Int32(values[0]))
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def make_pipeline_state(type: PipelineUserType, stages: int):
|
| 87 |
+
"""
|
| 88 |
+
Creates a pipeline state. Producers are assumed to start with an empty buffer and have a flipped phase bit of 1.
|
| 89 |
+
"""
|
| 90 |
+
if type is PipelineUserType.Producer:
|
| 91 |
+
return PipelineStateSimple(stages, Int32(stages))
|
| 92 |
+
elif type is PipelineUserType.Consumer:
|
| 93 |
+
return PipelineStateSimple(stages, Int32(0))
|
| 94 |
+
else:
|
| 95 |
+
assert False, "Error: invalid PipelineUserType specified for make_pipeline_state."
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# ── Shared helpers ───────────────────────────────────────────────────────────
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _call_with_elect_one(parent_method, self, state, elect_one, syncwarp, loc, ip):
|
| 102 |
+
"""Optionally wrap a parent pipeline method call in sync_warp + elect_one."""
|
| 103 |
+
if const_expr(elect_one):
|
| 104 |
+
if const_expr(syncwarp):
|
| 105 |
+
cute.arch.sync_warp()
|
| 106 |
+
with cute.arch.elect_one():
|
| 107 |
+
parent_method(self, state, loc=loc, ip=ip)
|
| 108 |
+
else:
|
| 109 |
+
parent_method(self, state, loc=loc, ip=ip)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
# ── Mixin: _w_index / _w_index_phase variants that delegate to parent ───────
|
| 113 |
+
# Each parent class has PipelineState-based methods (producer_acquire, producer_commit,
|
| 114 |
+
# consumer_wait, consumer_release). The _w_index_phase variants just construct a
|
| 115 |
+
# PipelineState from (index, phase) and delegate.
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class _PipelineIndexPhaseMixin:
|
| 119 |
+
"""Mixin providing _w_index_phase / _w_index methods that delegate to PipelineState-based parents."""
|
| 120 |
+
|
| 121 |
+
@dsl_user_op
|
| 122 |
+
def producer_acquire_w_index_phase(
|
| 123 |
+
self,
|
| 124 |
+
index: Int32,
|
| 125 |
+
phase: Int32,
|
| 126 |
+
try_acquire_token: Optional[Boolean] = None,
|
| 127 |
+
*,
|
| 128 |
+
loc=None,
|
| 129 |
+
ip=None,
|
| 130 |
+
):
|
| 131 |
+
state = _make_state(index, phase)
|
| 132 |
+
# Call the parent's producer_acquire (which takes PipelineState)
|
| 133 |
+
self.producer_acquire(state, try_acquire_token, loc=loc, ip=ip)
|
| 134 |
+
|
| 135 |
+
@dsl_user_op
|
| 136 |
+
def producer_commit_w_index(self, index: Int32, *, loc=None, ip=None):
|
| 137 |
+
state = _make_state(index, Int32(0))
|
| 138 |
+
self.producer_commit(state, loc=loc, ip=ip)
|
| 139 |
+
|
| 140 |
+
@dsl_user_op
|
| 141 |
+
def consumer_wait_w_index_phase(
|
| 142 |
+
self,
|
| 143 |
+
index: Int32,
|
| 144 |
+
phase: Int32,
|
| 145 |
+
try_wait_token: Optional[Boolean] = None,
|
| 146 |
+
*,
|
| 147 |
+
loc=None,
|
| 148 |
+
ip=None,
|
| 149 |
+
):
|
| 150 |
+
state = _make_state(index, phase)
|
| 151 |
+
self.consumer_wait(state, try_wait_token, loc=loc, ip=ip)
|
| 152 |
+
|
| 153 |
+
@dsl_user_op
|
| 154 |
+
def consumer_release_w_index(self, index: Int32, *, loc=None, ip=None):
|
| 155 |
+
state = _make_state(index, Int32(0))
|
| 156 |
+
self.consumer_release(state, loc=loc, ip=ip)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
# ── NamedBarrier ─────────────────────────────────────────────────────────────
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
@dataclass(frozen=True)
|
| 163 |
+
class NamedBarrier(NamedBarrierOg):
|
| 164 |
+
create = _override_create(NamedBarrierOg, None) # patched below
|
| 165 |
+
|
| 166 |
+
@dsl_user_op
|
| 167 |
+
def arrive_w_index(self, index: Int32, *, loc=None, ip=None) -> None:
|
| 168 |
+
"""
|
| 169 |
+
The aligned flavor of arrive is used when all threads in the CTA will execute the
|
| 170 |
+
same instruction. See PTX documentation.
|
| 171 |
+
"""
|
| 172 |
+
cute.arch.barrier_arrive(
|
| 173 |
+
barrier_id=self.barrier_id + index,
|
| 174 |
+
number_of_threads=self.num_threads,
|
| 175 |
+
loc=loc,
|
| 176 |
+
ip=ip,
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
@dsl_user_op
|
| 180 |
+
def arrive_and_wait_w_index(self, index: Int32, *, loc=None, ip=None) -> None:
|
| 181 |
+
cute.arch.barrier(
|
| 182 |
+
barrier_id=self.barrier_id + index,
|
| 183 |
+
number_of_threads=self.num_threads,
|
| 184 |
+
loc=loc,
|
| 185 |
+
ip=ip,
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
NamedBarrier.create = _override_create(NamedBarrierOg, NamedBarrier)
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
# ── PipelineAsync ────────────────────────────────────────────────────────────
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
@dataclass(frozen=True)
|
| 196 |
+
class PipelineAsync(_PipelineIndexPhaseMixin, PipelineAsyncOg):
|
| 197 |
+
"""
|
| 198 |
+
PipelineAsync with optional elect_one for producer_commit and consumer_release.
|
| 199 |
+
|
| 200 |
+
When elect_one_*=True (set at create time), only one elected thread per warp
|
| 201 |
+
signals the barrier arrive. This is useful when the mask count is set to 1 per warp.
|
| 202 |
+
|
| 203 |
+
Args (to create):
|
| 204 |
+
elect_one_commit: If True, only elected thread signals producer_commit.
|
| 205 |
+
syncwarp_before_commit: If True (default), issue syncwarp before elect_one.
|
| 206 |
+
elect_one_release: If True, only elected thread signals consumer_release.
|
| 207 |
+
syncwarp_before_release: If True (default), issue syncwarp before elect_one.
|
| 208 |
+
Set syncwarp to False when threads are already converged (e.g. after wgmma wait_group).
|
| 209 |
+
"""
|
| 210 |
+
|
| 211 |
+
_elect_one_commit: bool = False
|
| 212 |
+
_syncwarp_before_commit: bool = True
|
| 213 |
+
_elect_one_release: bool = False
|
| 214 |
+
_syncwarp_before_release: bool = True
|
| 215 |
+
|
| 216 |
+
@staticmethod
|
| 217 |
+
def create(
|
| 218 |
+
*args,
|
| 219 |
+
elect_one_commit: bool = False,
|
| 220 |
+
syncwarp_before_commit: bool = True,
|
| 221 |
+
elect_one_release: bool = False,
|
| 222 |
+
syncwarp_before_release: bool = True,
|
| 223 |
+
**kwargs,
|
| 224 |
+
):
|
| 225 |
+
obj = PipelineAsyncOg.create(*args, **kwargs)
|
| 226 |
+
object.__setattr__(obj, "__class__", PipelineAsync)
|
| 227 |
+
object.__setattr__(obj, "_elect_one_commit", elect_one_commit)
|
| 228 |
+
object.__setattr__(obj, "_syncwarp_before_commit", syncwarp_before_commit)
|
| 229 |
+
object.__setattr__(obj, "_elect_one_release", elect_one_release)
|
| 230 |
+
object.__setattr__(obj, "_syncwarp_before_release", syncwarp_before_release)
|
| 231 |
+
return obj
|
| 232 |
+
|
| 233 |
+
@dsl_user_op
|
| 234 |
+
def producer_commit(self, state: PipelineState, *, loc=None, ip=None):
|
| 235 |
+
_call_with_elect_one(
|
| 236 |
+
PipelineAsyncOg.producer_commit,
|
| 237 |
+
self,
|
| 238 |
+
state,
|
| 239 |
+
self._elect_one_commit,
|
| 240 |
+
self._syncwarp_before_commit,
|
| 241 |
+
loc,
|
| 242 |
+
ip,
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
@dsl_user_op
|
| 246 |
+
def consumer_release(self, state: PipelineState, *, loc=None, ip=None):
|
| 247 |
+
_call_with_elect_one(
|
| 248 |
+
PipelineAsyncOg.consumer_release,
|
| 249 |
+
self,
|
| 250 |
+
state,
|
| 251 |
+
self._elect_one_release,
|
| 252 |
+
self._syncwarp_before_release,
|
| 253 |
+
loc,
|
| 254 |
+
ip,
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
# _w_index variants inherited from _PipelineIndexPhaseMixin, which delegate
|
| 258 |
+
# to producer_commit / consumer_release above.
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
# ── PipelineCpAsync ──────────────────────────────────────────────────────────
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
@dataclass(frozen=True)
|
| 265 |
+
class PipelineCpAsync(_PipelineIndexPhaseMixin, PipelineCpAsyncOg):
|
| 266 |
+
_elect_one_release: bool = False
|
| 267 |
+
_syncwarp_before_release: bool = True
|
| 268 |
+
|
| 269 |
+
@staticmethod
|
| 270 |
+
def create(
|
| 271 |
+
*args,
|
| 272 |
+
elect_one_release: bool = False,
|
| 273 |
+
syncwarp_before_release: bool = True,
|
| 274 |
+
**kwargs,
|
| 275 |
+
):
|
| 276 |
+
obj = PipelineCpAsyncOg.create(*args, **kwargs)
|
| 277 |
+
object.__setattr__(obj, "__class__", PipelineCpAsync)
|
| 278 |
+
object.__setattr__(obj, "_elect_one_release", elect_one_release)
|
| 279 |
+
object.__setattr__(obj, "_syncwarp_before_release", syncwarp_before_release)
|
| 280 |
+
return obj
|
| 281 |
+
|
| 282 |
+
@dsl_user_op
|
| 283 |
+
def consumer_release(self, state: PipelineState, *, loc=None, ip=None):
|
| 284 |
+
_call_with_elect_one(
|
| 285 |
+
PipelineCpAsyncOg.consumer_release,
|
| 286 |
+
self,
|
| 287 |
+
state,
|
| 288 |
+
self._elect_one_release,
|
| 289 |
+
self._syncwarp_before_release,
|
| 290 |
+
loc,
|
| 291 |
+
ip,
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
# _w_index variants inherited from _PipelineIndexPhaseMixin.
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# ── PipelineTmaAsync ────────────────────────────────────────────────────────
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
@dataclass(frozen=True)
|
| 301 |
+
class PipelineTmaAsync(_PipelineIndexPhaseMixin, PipelineTmaAsyncOg):
|
| 302 |
+
"""Override producer_acquire to take in extra_tx_count parameter."""
|
| 303 |
+
|
| 304 |
+
@dsl_user_op
|
| 305 |
+
def producer_acquire(
|
| 306 |
+
self,
|
| 307 |
+
state: PipelineState,
|
| 308 |
+
try_acquire_token: Optional[Boolean] = None,
|
| 309 |
+
extra_tx_count: int = 0,
|
| 310 |
+
*,
|
| 311 |
+
loc=None,
|
| 312 |
+
ip=None,
|
| 313 |
+
):
|
| 314 |
+
"""
|
| 315 |
+
TMA producer commit conditionally waits on buffer empty and sets the transaction barrier for leader threadblocks.
|
| 316 |
+
"""
|
| 317 |
+
if_generate(
|
| 318 |
+
try_acquire_token is None or try_acquire_token == 0,
|
| 319 |
+
lambda: self.sync_object_empty.wait(state.index, state.phase, loc=loc, ip=ip),
|
| 320 |
+
loc=loc,
|
| 321 |
+
ip=ip,
|
| 322 |
+
)
|
| 323 |
+
if const_expr(extra_tx_count == 0):
|
| 324 |
+
self.sync_object_full.arrive(state.index, self.producer_mask, loc=loc, ip=ip)
|
| 325 |
+
else:
|
| 326 |
+
tx_count = self.sync_object_full.tx_count + extra_tx_count
|
| 327 |
+
self.sync_object_full.arrive_and_expect_tx(state.index, tx_count, loc=loc, ip=ip)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
PipelineTmaAsync.create = _override_create(PipelineTmaAsyncOg, PipelineTmaAsync)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
# ── PipelineTmaUmma ─────────────────────────────────────────────────────────
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
@dataclass(frozen=True)
|
| 337 |
+
class PipelineTmaUmma(_PipelineIndexPhaseMixin, PipelineTmaUmmaOg):
|
| 338 |
+
"""Override producer_acquire to take in extra_tx_count parameter."""
|
| 339 |
+
|
| 340 |
+
@dsl_user_op
|
| 341 |
+
def producer_acquire(
|
| 342 |
+
self,
|
| 343 |
+
state: PipelineState,
|
| 344 |
+
try_acquire_token: Optional[Boolean] = None,
|
| 345 |
+
extra_tx_count: int = 0,
|
| 346 |
+
*,
|
| 347 |
+
loc=None,
|
| 348 |
+
ip=None,
|
| 349 |
+
):
|
| 350 |
+
"""
|
| 351 |
+
TMA producer commit conditionally waits on buffer empty and sets the transaction barrier for leader threadblocks.
|
| 352 |
+
"""
|
| 353 |
+
if_generate(
|
| 354 |
+
try_acquire_token is None or try_acquire_token == 0,
|
| 355 |
+
lambda: self.sync_object_empty.wait(state.index, state.phase, loc=loc, ip=ip),
|
| 356 |
+
loc=loc,
|
| 357 |
+
ip=ip,
|
| 358 |
+
)
|
| 359 |
+
if const_expr(extra_tx_count == 0):
|
| 360 |
+
if_generate(
|
| 361 |
+
self.is_leader_cta,
|
| 362 |
+
lambda: self.sync_object_full.arrive(
|
| 363 |
+
state.index, self.producer_mask, loc=loc, ip=ip
|
| 364 |
+
),
|
| 365 |
+
loc=loc,
|
| 366 |
+
ip=ip,
|
| 367 |
+
)
|
| 368 |
+
else:
|
| 369 |
+
tx_count = self.sync_object_full.tx_count + extra_tx_count
|
| 370 |
+
if_generate(
|
| 371 |
+
self.is_leader_cta,
|
| 372 |
+
lambda: self.sync_object_full.arrive_and_expect_tx(
|
| 373 |
+
state.index, tx_count, loc=loc, ip=ip
|
| 374 |
+
),
|
| 375 |
+
loc=loc,
|
| 376 |
+
ip=ip,
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
PipelineTmaUmma.create = _override_create(PipelineTmaUmmaOg, PipelineTmaUmma)
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
# ── PipelineUmmaAsync ───────────────────────────────────────────────────────
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
@dataclass(frozen=True)
|
| 387 |
+
class PipelineUmmaAsync(_PipelineIndexPhaseMixin, PipelineUmmaAsyncOg):
|
| 388 |
+
pass
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
PipelineUmmaAsync.create = _override_create(PipelineUmmaAsyncOg, PipelineUmmaAsync)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
# ── PipelineAsyncUmma ───────────────────────────────────────────────────────
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
@dataclass(frozen=True)
|
| 398 |
+
class PipelineAsyncUmma(_PipelineIndexPhaseMixin, PipelineAsyncUmmaOg):
|
| 399 |
+
pass
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
PipelineAsyncUmma.create = _override_create(PipelineAsyncUmmaOg, PipelineAsyncUmma)
|
build/torch-cuda/flashrt_fa4/cute/seqlen_info.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Optional
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
from cutlass import Int32, const_expr
|
| 7 |
+
|
| 8 |
+
from fa4_cute_runtime.quack import copy_utils
|
| 9 |
+
|
| 10 |
+
"""
|
| 11 |
+
This consolidates all the info related to sequence length. This is so that we can do all
|
| 12 |
+
the gmem reads once at the beginning of each tile, rather than having to repeat these reads
|
| 13 |
+
to compute various things like n_block_min, n_block_max, etc.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass(frozen=True)
|
| 18 |
+
class SeqlenInfo:
|
| 19 |
+
offset: Int32
|
| 20 |
+
offset_padded: Int32
|
| 21 |
+
seqlen: Int32
|
| 22 |
+
has_cu_seqlens: cutlass.Constexpr[bool] = False
|
| 23 |
+
|
| 24 |
+
@staticmethod
|
| 25 |
+
def create(
|
| 26 |
+
batch_idx: Int32,
|
| 27 |
+
seqlen_static: Int32,
|
| 28 |
+
cu_seqlens: Optional[cute.Tensor] = None,
|
| 29 |
+
seqused: Optional[cute.Tensor] = None,
|
| 30 |
+
tile: cutlass.Constexpr[int] = 128,
|
| 31 |
+
):
|
| 32 |
+
offset = 0 if const_expr(cu_seqlens is None) else cu_seqlens[batch_idx]
|
| 33 |
+
offset_padded = (
|
| 34 |
+
0
|
| 35 |
+
if const_expr(cu_seqlens is None)
|
| 36 |
+
# Add divby so that the compiler knows the alignment when moving by offset_padded
|
| 37 |
+
else cute.assume((offset + batch_idx * tile) // tile * tile, divby=tile)
|
| 38 |
+
)
|
| 39 |
+
if const_expr(seqused is not None):
|
| 40 |
+
seqlen = seqused[batch_idx]
|
| 41 |
+
elif const_expr(cu_seqlens is not None):
|
| 42 |
+
seqlen = cu_seqlens[batch_idx + 1] - cu_seqlens[batch_idx]
|
| 43 |
+
else:
|
| 44 |
+
seqlen = seqlen_static
|
| 45 |
+
return SeqlenInfo(offset, offset_padded, seqlen, has_cu_seqlens=cu_seqlens is not None)
|
| 46 |
+
|
| 47 |
+
def offset_batch(
|
| 48 |
+
self,
|
| 49 |
+
mT: cute.Tensor,
|
| 50 |
+
batch_idx: Int32,
|
| 51 |
+
dim: int,
|
| 52 |
+
padded: cutlass.Constexpr[bool] = False,
|
| 53 |
+
multiple: int = 1,
|
| 54 |
+
) -> cute.Tensor:
|
| 55 |
+
"""Offset a tensor by batch index. batch dim is at position `dim`, seqlen is at dim=0."""
|
| 56 |
+
if const_expr(not self.has_cu_seqlens):
|
| 57 |
+
idx = (None,) * dim + (batch_idx,) + (None,) * (cute.rank(mT) - 1 - dim)
|
| 58 |
+
return mT[idx]
|
| 59 |
+
else:
|
| 60 |
+
off = multiple * (self.offset if const_expr(not padded) else self.offset_padded)
|
| 61 |
+
offset = off if const_expr(cute.rank(mT.shape[0]) == 1) else (0, off)
|
| 62 |
+
idx = (offset,) + (None,) * (cute.rank(mT) - 1)
|
| 63 |
+
return cute.domain_offset(idx, mT)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@dataclass(frozen=True)
|
| 67 |
+
class SeqlenInfoQK:
|
| 68 |
+
offset_q: Int32
|
| 69 |
+
offset_k: Int32
|
| 70 |
+
padded_offset_q: Int32
|
| 71 |
+
padded_offset_k: Int32
|
| 72 |
+
seqlen_q: Int32
|
| 73 |
+
seqlen_k: Int32
|
| 74 |
+
has_cu_seqlens_q: cutlass.Constexpr[bool]
|
| 75 |
+
has_cu_seqlens_k: cutlass.Constexpr[bool]
|
| 76 |
+
has_seqused_q: cutlass.Constexpr[bool]
|
| 77 |
+
has_seqused_k: cutlass.Constexpr[bool]
|
| 78 |
+
|
| 79 |
+
@staticmethod
|
| 80 |
+
def create(
|
| 81 |
+
batch_idx: Int32,
|
| 82 |
+
seqlen_q_static: Int32,
|
| 83 |
+
seqlen_k_static: Int32,
|
| 84 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 85 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 86 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 87 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 88 |
+
tile_m: cutlass.Constexpr[Int32] = 128,
|
| 89 |
+
tile_n: cutlass.Constexpr[Int32] = 128,
|
| 90 |
+
):
|
| 91 |
+
offset_q = 0 if const_expr(mCuSeqlensQ is None) else mCuSeqlensQ[batch_idx]
|
| 92 |
+
offset_k = 0 if const_expr(mCuSeqlensK is None) else mCuSeqlensK[batch_idx]
|
| 93 |
+
padded_offset_q = (
|
| 94 |
+
0
|
| 95 |
+
if const_expr(mCuSeqlensQ is None)
|
| 96 |
+
else cute.assume((offset_q + batch_idx * tile_m) // tile_m * tile_m, divby=tile_m)
|
| 97 |
+
)
|
| 98 |
+
padded_offset_k = (
|
| 99 |
+
0
|
| 100 |
+
if const_expr(mCuSeqlensK is None)
|
| 101 |
+
else cute.assume((offset_k + batch_idx * tile_n) // tile_n * tile_n, divby=tile_n)
|
| 102 |
+
)
|
| 103 |
+
if const_expr(mSeqUsedQ is not None):
|
| 104 |
+
seqlen_q = mSeqUsedQ[batch_idx]
|
| 105 |
+
else:
|
| 106 |
+
seqlen_q = (
|
| 107 |
+
seqlen_q_static
|
| 108 |
+
if const_expr(mCuSeqlensQ is None)
|
| 109 |
+
else mCuSeqlensQ[batch_idx + 1] - offset_q
|
| 110 |
+
)
|
| 111 |
+
if const_expr(mSeqUsedK is not None):
|
| 112 |
+
seqlen_k = mSeqUsedK[batch_idx]
|
| 113 |
+
else:
|
| 114 |
+
seqlen_k = (
|
| 115 |
+
seqlen_k_static
|
| 116 |
+
if const_expr(mCuSeqlensK is None)
|
| 117 |
+
else mCuSeqlensK[batch_idx + 1] - offset_k
|
| 118 |
+
)
|
| 119 |
+
return SeqlenInfoQK(
|
| 120 |
+
offset_q,
|
| 121 |
+
offset_k,
|
| 122 |
+
padded_offset_q,
|
| 123 |
+
padded_offset_k,
|
| 124 |
+
seqlen_q,
|
| 125 |
+
seqlen_k,
|
| 126 |
+
has_cu_seqlens_q=mCuSeqlensQ is not None,
|
| 127 |
+
has_cu_seqlens_k=mCuSeqlensK is not None,
|
| 128 |
+
has_seqused_q=mSeqUsedQ is not None,
|
| 129 |
+
has_seqused_k=mSeqUsedK is not None,
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
def offset_batch_Q(
|
| 133 |
+
self,
|
| 134 |
+
mQ: cute.Tensor,
|
| 135 |
+
batch_idx: Int32,
|
| 136 |
+
dim: int,
|
| 137 |
+
padded: cutlass.Constexpr[bool] = False,
|
| 138 |
+
ragged: cutlass.Constexpr[bool] = False,
|
| 139 |
+
) -> cute.Tensor:
|
| 140 |
+
"""Seqlen must be the first dimension of mQ"""
|
| 141 |
+
if const_expr(not ragged):
|
| 142 |
+
if const_expr(not self.has_cu_seqlens_q):
|
| 143 |
+
idx = (None,) * dim + (batch_idx,) + (None,) * (cute.rank(mQ) - 1 - dim)
|
| 144 |
+
return mQ[idx]
|
| 145 |
+
else:
|
| 146 |
+
offset_q = self.offset_q if const_expr(not padded) else self.padded_offset_q
|
| 147 |
+
offset_q = offset_q if const_expr(cute.rank(mQ.shape[0]) == 1) else (None, offset_q)
|
| 148 |
+
idx = (offset_q,) + (None,) * (cute.rank(mQ) - 1)
|
| 149 |
+
return cute.domain_offset(idx, mQ)
|
| 150 |
+
else:
|
| 151 |
+
if const_expr(not self.has_cu_seqlens_q):
|
| 152 |
+
offset_q = 0
|
| 153 |
+
idx = (None,) * dim + (batch_idx,) + (None,) * (cute.rank(mQ) - 1 - dim)
|
| 154 |
+
mQ = mQ[idx]
|
| 155 |
+
else:
|
| 156 |
+
offset_q = self.offset_q if const_expr(not padded) else self.padded_offset_q
|
| 157 |
+
if const_expr(cute.rank(mQ.shape[0]) == 1):
|
| 158 |
+
return copy_utils.offset_ragged_tensor(
|
| 159 |
+
mQ, offset_q, self.seqlen_q, ragged_dim=0, ptr_shift=True
|
| 160 |
+
)
|
| 161 |
+
else: # PackGQA
|
| 162 |
+
assert cute.rank(mQ.shape[0]) == 2
|
| 163 |
+
# Unpack before calling offset_ragged_tensor, then pack
|
| 164 |
+
idx = ((None, None),) + (None,) * (cute.rank(mQ) - 1)
|
| 165 |
+
mQ = mQ[idx]
|
| 166 |
+
mQ = copy_utils.offset_ragged_tensor(
|
| 167 |
+
mQ, offset_q, self.seqlen_q, ragged_dim=1, ptr_shift=True
|
| 168 |
+
)
|
| 169 |
+
return cute.group_modes(mQ, 0, 2)
|
| 170 |
+
|
| 171 |
+
def offset_batch_K(
|
| 172 |
+
self,
|
| 173 |
+
mK: cute.Tensor,
|
| 174 |
+
batch_idx: Int32,
|
| 175 |
+
dim: int,
|
| 176 |
+
padded: cutlass.Constexpr[bool] = False,
|
| 177 |
+
ragged: cutlass.Constexpr[bool] = False,
|
| 178 |
+
multiple: int = 1,
|
| 179 |
+
) -> cute.Tensor:
|
| 180 |
+
"""Seqlen must be the first dimension of mK"""
|
| 181 |
+
if const_expr(not ragged):
|
| 182 |
+
if const_expr(not self.has_cu_seqlens_k):
|
| 183 |
+
idx = (None,) * dim + (batch_idx,) + (None,) * (cute.rank(mK) - 1 - dim)
|
| 184 |
+
return mK[idx]
|
| 185 |
+
else:
|
| 186 |
+
offset_k = self.offset_k if const_expr(not padded) else self.padded_offset_k
|
| 187 |
+
offset_k *= multiple
|
| 188 |
+
idx = (offset_k,) + (None,) * (cute.rank(mK) - 1)
|
| 189 |
+
return cute.domain_offset(idx, mK)
|
| 190 |
+
else:
|
| 191 |
+
if const_expr(not self.has_cu_seqlens_k):
|
| 192 |
+
offset_k = 0
|
| 193 |
+
idx = (None,) * dim + (batch_idx,) + (None,) * (cute.rank(mK) - 1 - dim)
|
| 194 |
+
mK = mK[idx]
|
| 195 |
+
else:
|
| 196 |
+
offset_k = self.offset_k if const_expr(not padded) else self.padded_offset_k
|
| 197 |
+
offset_k *= multiple
|
| 198 |
+
return copy_utils.offset_ragged_tensor(
|
| 199 |
+
mK, offset_k, self.seqlen_k, ragged_dim=0, ptr_shift=True
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
@dataclass(frozen=True)
|
| 204 |
+
class SeqlenInfoQKNewK:
|
| 205 |
+
"""Sequence length info for append-KV with left-padding and new K support.
|
| 206 |
+
|
| 207 |
+
Extends SeqlenInfoQK with:
|
| 208 |
+
- leftpad_k: left padding for K (tokens to skip at the start of the KV cache)
|
| 209 |
+
- offset_k_new: offset into the new K tensor
|
| 210 |
+
- seqlen_k_og: original K length (before appending new K), excluding leftpad
|
| 211 |
+
- seqlen_k_new: length of new K to append
|
| 212 |
+
- seqlen_k: total K length (seqlen_k_og + seqlen_k_new)
|
| 213 |
+
- seqlen_rotary: position for rotary embedding computation
|
| 214 |
+
"""
|
| 215 |
+
|
| 216 |
+
leftpad_k: Int32
|
| 217 |
+
offset_q: Int32
|
| 218 |
+
offset_k: Int32
|
| 219 |
+
offset_k_new: Int32
|
| 220 |
+
seqlen_q: Int32
|
| 221 |
+
seqlen_k_og: Int32
|
| 222 |
+
seqlen_k_new: Int32
|
| 223 |
+
seqlen_k: Int32
|
| 224 |
+
seqlen_rotary: Int32
|
| 225 |
+
|
| 226 |
+
@staticmethod
|
| 227 |
+
def create(
|
| 228 |
+
batch_idx: Int32,
|
| 229 |
+
seqlen_q_static: Int32,
|
| 230 |
+
seqlen_k_static: Int32,
|
| 231 |
+
shape_K_new_0: Int32,
|
| 232 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 233 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 234 |
+
mCuSeqlensKNew: Optional[cute.Tensor] = None,
|
| 235 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 236 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 237 |
+
mLeftpadK: Optional[cute.Tensor] = None,
|
| 238 |
+
mSeqlensRotary: Optional[cute.Tensor] = None,
|
| 239 |
+
):
|
| 240 |
+
leftpad_k = 0 if const_expr(mLeftpadK is None) else mLeftpadK[batch_idx]
|
| 241 |
+
offset_q = 0 if const_expr(mCuSeqlensQ is None) else mCuSeqlensQ[batch_idx]
|
| 242 |
+
if const_expr(mCuSeqlensK is not None):
|
| 243 |
+
offset_k = mCuSeqlensK[batch_idx] + leftpad_k
|
| 244 |
+
else:
|
| 245 |
+
offset_k = leftpad_k if const_expr(mCuSeqlensQ is not None) else 0
|
| 246 |
+
offset_k_new = 0 if const_expr(mCuSeqlensKNew is None) else mCuSeqlensKNew[batch_idx]
|
| 247 |
+
# seqlen_q
|
| 248 |
+
if const_expr(mSeqUsedQ is not None):
|
| 249 |
+
seqlen_q = mSeqUsedQ[batch_idx]
|
| 250 |
+
elif const_expr(mCuSeqlensQ is not None):
|
| 251 |
+
seqlen_q = mCuSeqlensQ[batch_idx + 1] - mCuSeqlensQ[batch_idx]
|
| 252 |
+
else:
|
| 253 |
+
seqlen_q = seqlen_q_static
|
| 254 |
+
# seqlen_k_og: original K length (excluding leftpad)
|
| 255 |
+
if const_expr(mSeqUsedK is not None):
|
| 256 |
+
seqlen_k_og = mSeqUsedK[batch_idx] - leftpad_k
|
| 257 |
+
elif const_expr(mCuSeqlensK is not None):
|
| 258 |
+
seqlen_k_og = mCuSeqlensK[batch_idx + 1] - mCuSeqlensK[batch_idx] - leftpad_k
|
| 259 |
+
else:
|
| 260 |
+
seqlen_k_og = (
|
| 261 |
+
seqlen_k_static - leftpad_k
|
| 262 |
+
if const_expr(mCuSeqlensQ is not None)
|
| 263 |
+
else seqlen_k_static
|
| 264 |
+
)
|
| 265 |
+
# seqlen_k_new
|
| 266 |
+
if const_expr(mCuSeqlensKNew is None):
|
| 267 |
+
seqlen_k_new = 0 if const_expr(mCuSeqlensQ is None) else shape_K_new_0
|
| 268 |
+
else:
|
| 269 |
+
seqlen_k_new = mCuSeqlensKNew[batch_idx + 1] - mCuSeqlensKNew[batch_idx]
|
| 270 |
+
seqlen_k = seqlen_k_og if const_expr(mCuSeqlensQ is None) else seqlen_k_og + seqlen_k_new
|
| 271 |
+
|
| 272 |
+
# seqlen_rotary: defaults to seqlen_k_og + leftpad_k unless explicitly provided
|
| 273 |
+
if const_expr(mSeqlensRotary is not None):
|
| 274 |
+
seqlen_rotary = mSeqlensRotary[batch_idx]
|
| 275 |
+
else:
|
| 276 |
+
seqlen_rotary = seqlen_k_og + leftpad_k
|
| 277 |
+
return SeqlenInfoQKNewK(
|
| 278 |
+
leftpad_k,
|
| 279 |
+
offset_q,
|
| 280 |
+
offset_k,
|
| 281 |
+
offset_k_new,
|
| 282 |
+
seqlen_q,
|
| 283 |
+
seqlen_k_og,
|
| 284 |
+
seqlen_k_new,
|
| 285 |
+
seqlen_k,
|
| 286 |
+
seqlen_rotary,
|
| 287 |
+
)
|
build/torch-cuda/flashrt_fa4/cute/sm100_hd256_2cta_fmha_forward.py
ADDED
|
@@ -0,0 +1,1882 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Siyu Wang, Shengbin Di, Yuxi Chi, Johnsonms, Linfeng Zheng, Haoyan Huang, Lanbo Li, Yun Zhong, Man Yuan, Minmin Sun, Yong Li, Wei Lin.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Tuple, Optional
|
| 5 |
+
|
| 6 |
+
import cuda.bindings.driver as cuda
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.cute as cute
|
| 10 |
+
import cutlass.cute.nvgpu.tcgen05 as tcgen05
|
| 11 |
+
import cutlass.utils as utils
|
| 12 |
+
import cutlass.pipeline as pipeline
|
| 13 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 14 |
+
from cutlass.cute.typing import Int32, Int64, Float32
|
| 15 |
+
|
| 16 |
+
from cutlass.utils import ClcDynamicPersistentTileScheduler
|
| 17 |
+
from fa4_cute_runtime.flashrt_fa4.cute.tile_scheduler import (
|
| 18 |
+
ClcState,
|
| 19 |
+
compute_sm100_fmha_grid as compute_grid,
|
| 20 |
+
compute_sm100_fmha_grid_clc as compute_grid_clc,
|
| 21 |
+
make_sm100_thread_cooperative_group as make_thread_cooperative_group,
|
| 22 |
+
Sm100FmhaStaticTileScheduler as FmhaStaticTileScheduler,
|
| 23 |
+
Sm100FmhaStaticTileSchedulerParams as FmhaStaticTileSchedulerParams,
|
| 24 |
+
Sm100FmhaClcDynamicTileScheduler as FmhaClcDynamicTileScheduler,
|
| 25 |
+
Sm100FmhaClcDynamicTileSchedulerParams as FmhaClcDynamicTileSchedulerParams,
|
| 26 |
+
)
|
| 27 |
+
from fa4_cute_runtime.flashrt_fa4.cute.mask import (
|
| 28 |
+
Sm100FusedMask as FusedMask,
|
| 29 |
+
)
|
| 30 |
+
from fa4_cute_runtime.flashrt_fa4.cute.tile_scheduler import SM100_TMEM_CAPACITY_COLUMNS
|
| 31 |
+
from fa4_cute_runtime.flashrt_fa4.cute.flash_fwd_sm100 import DescaleTensors, _TUNING_CONFIG
|
| 32 |
+
from fa4_cute_runtime.flashrt_fa4.cute.utils import ex2_emulation_2
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class BlackwellFusedMultiHeadAttentionForward:
|
| 36 |
+
def __init__(
|
| 37 |
+
self,
|
| 38 |
+
head_dim: int,
|
| 39 |
+
head_dim_v: Optional[int] = None,
|
| 40 |
+
qhead_per_kvhead: int = 1,
|
| 41 |
+
is_causal: bool = False,
|
| 42 |
+
is_local: bool = False,
|
| 43 |
+
is_split_kv: bool = False,
|
| 44 |
+
pack_gqa: bool = False,
|
| 45 |
+
q_subtile_factor: int | None = None,
|
| 46 |
+
m_block_size: int = 128,
|
| 47 |
+
n_block_size: int = 128,
|
| 48 |
+
q_stage: int = 2,
|
| 49 |
+
is_persistent: bool = True,
|
| 50 |
+
score_mod=None,
|
| 51 |
+
mask_mod=None,
|
| 52 |
+
has_aux_tensors: bool = False,
|
| 53 |
+
paged_kv_non_tma: bool = False,
|
| 54 |
+
is_varlen_q: bool = False,
|
| 55 |
+
use_2cta_instrs: bool = False,
|
| 56 |
+
use_clc_scheduler: bool = False,
|
| 57 |
+
):
|
| 58 |
+
head_dim_v = head_dim if head_dim_v is None else head_dim_v
|
| 59 |
+
assert head_dim == 256 and head_dim_v == 256, (
|
| 60 |
+
"SM100 dedicated kernel only supports (head_dim, head_dim_v) = (256, 256)"
|
| 61 |
+
)
|
| 62 |
+
assert score_mod is None, "SM100 forward with head_dim=256 does not support score_mod"
|
| 63 |
+
assert mask_mod is None, "SM100 forward with head_dim=256 does not support mask_mod"
|
| 64 |
+
assert not has_aux_tensors, "SM100 forward with head_dim=256 does not support aux tensors"
|
| 65 |
+
assert not paged_kv_non_tma, (
|
| 66 |
+
"SM100 hd256 2CTA supports TMA paged KV only (page_size must equal tile_n=128)"
|
| 67 |
+
)
|
| 68 |
+
assert not pack_gqa, "SM100 forward with head_dim=256 does not support pack_gqa"
|
| 69 |
+
assert not is_split_kv, "SM100 forward with head_dim=256 does not support SplitKV"
|
| 70 |
+
assert q_subtile_factor is None, (
|
| 71 |
+
"SM100 forward with head_dim=256 does not support q_subtile_factor"
|
| 72 |
+
)
|
| 73 |
+
assert m_block_size == 128 and n_block_size == 128, (
|
| 74 |
+
"SM100 dedicated kernel only supports tile_m=128 and tile_n=128"
|
| 75 |
+
)
|
| 76 |
+
# q_stage / persistence / scheduler knobs are accepted for interface parity,
|
| 77 |
+
# but this dedicated kernel uses fixed internal settings.
|
| 78 |
+
|
| 79 |
+
qk_acc_dtype = cutlass.Float32
|
| 80 |
+
pv_acc_dtype = cutlass.Float32
|
| 81 |
+
mma_tiler = (128, 128, head_dim)
|
| 82 |
+
self.qk_acc_dtype = qk_acc_dtype
|
| 83 |
+
self.pv_acc_dtype = pv_acc_dtype
|
| 84 |
+
self.qhead_per_kvhead = qhead_per_kvhead
|
| 85 |
+
self.mma_tiler = mma_tiler
|
| 86 |
+
assert mma_tiler[0] == 128 and mma_tiler[1] == 128, "Only 128x128 tile impl is supported"
|
| 87 |
+
assert mma_tiler[2] == 256, "Only 256 is supported for 128x128 tile impl"
|
| 88 |
+
self.cta_tiler = (
|
| 89 |
+
mma_tiler[0],
|
| 90 |
+
mma_tiler[1],
|
| 91 |
+
mma_tiler[2],
|
| 92 |
+
)
|
| 93 |
+
self.qk_mma_tiler = (
|
| 94 |
+
2 * mma_tiler[0],
|
| 95 |
+
mma_tiler[1],
|
| 96 |
+
min(self.cta_tiler[2], 128),
|
| 97 |
+
)
|
| 98 |
+
self.pv_mma_tiler = self.qk_mma_tiler
|
| 99 |
+
self.pv_block_tiler = (
|
| 100 |
+
self.pv_mma_tiler[0] // 2,
|
| 101 |
+
self.pv_mma_tiler[1],
|
| 102 |
+
self.pv_mma_tiler[2],
|
| 103 |
+
)
|
| 104 |
+
self.iterations_qk = self.cta_tiler[2] // self.qk_mma_tiler[2]
|
| 105 |
+
self.iterations_pv = self.cta_tiler[2] // self.pv_mma_tiler[1]
|
| 106 |
+
self.cluster_shape_mn = (2, 1)
|
| 107 |
+
self.tmem_warp_shape_mn = (4, 1)
|
| 108 |
+
# Dedicated hd256 kernel uses fixed scheduling policy.
|
| 109 |
+
self.is_persistent = False
|
| 110 |
+
self.is_causal = is_causal
|
| 111 |
+
self.is_local = is_local
|
| 112 |
+
self.use_semantic_trip_range = is_causal or is_local
|
| 113 |
+
self.use_clc_scheduler = False
|
| 114 |
+
|
| 115 |
+
self.softmax_warp_ids = (0, 1, 2, 3)
|
| 116 |
+
self.correction_warp_ids = (4, 5, 6, 7)
|
| 117 |
+
self.mma_warp_id = 8
|
| 118 |
+
self.load_warp_id = 9
|
| 119 |
+
self.empty_warp_id = (10, 11)
|
| 120 |
+
self.sched_warp_id = self.empty_warp_id[0] if use_clc_scheduler else None
|
| 121 |
+
self.tmem_alloc_cols = SM100_TMEM_CAPACITY_COLUMNS
|
| 122 |
+
|
| 123 |
+
self.threads_per_warp = 32
|
| 124 |
+
self.threads_per_cta = self.threads_per_warp * len(
|
| 125 |
+
(
|
| 126 |
+
*self.softmax_warp_ids, # this is to get a round num threads
|
| 127 |
+
*self.correction_warp_ids,
|
| 128 |
+
self.mma_warp_id,
|
| 129 |
+
self.load_warp_id,
|
| 130 |
+
*self.empty_warp_id,
|
| 131 |
+
)
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
self.tmem_alloc_barrier = pipeline.NamedBarrier(
|
| 135 |
+
barrier_id=1,
|
| 136 |
+
num_threads=self.threads_per_cta,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
self.tmem_s_offset = 0
|
| 140 |
+
self.tmem_o_offset = 256
|
| 141 |
+
self.tmem_p_offset = self.tmem_s_offset
|
| 142 |
+
|
| 143 |
+
_tune_key = (True, is_causal, 256, False) # hd256: always 2cta, no sm103 variant
|
| 144 |
+
_tune = _TUNING_CONFIG.get(_tune_key, {})
|
| 145 |
+
self.num_regs_softmax = _tune.get("num_regs_softmax", 256)
|
| 146 |
+
self.num_regs_correction = _tune.get("num_regs_correction", 160)
|
| 147 |
+
self.num_regs_other = 32 # fixed for hd256; not derived from 512 budget like other kernels
|
| 148 |
+
self.ex2_emu_freq = _tune.get("ex2_emu_freq", 4)
|
| 149 |
+
self.ex2_emu_res = _tune.get("ex2_emu_res", 3)
|
| 150 |
+
self.ex2_emu_start_frg = _tune.get("ex2_emu_start_frg", 0)
|
| 151 |
+
|
| 152 |
+
self.buffer_align_bytes = 1024
|
| 153 |
+
|
| 154 |
+
def _setup_attributes(self):
|
| 155 |
+
self.q_stage = self.iterations_qk
|
| 156 |
+
self.kv_stage = 4
|
| 157 |
+
self.qk_acc_stage = 2
|
| 158 |
+
self.mma_corr_stage = 1
|
| 159 |
+
if cutlass.const_expr(self.use_clc_scheduler):
|
| 160 |
+
self.num_clc_stage = 1
|
| 161 |
+
self.num_clc_response_bytes = 16
|
| 162 |
+
|
| 163 |
+
@cute.jit
|
| 164 |
+
def __call__(
|
| 165 |
+
self,
|
| 166 |
+
mQ: cute.Tensor,
|
| 167 |
+
mK: cute.Tensor,
|
| 168 |
+
mV: cute.Tensor,
|
| 169 |
+
mO: cute.Tensor,
|
| 170 |
+
mLSE: Optional[cute.Tensor],
|
| 171 |
+
softmax_scale: Float32,
|
| 172 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 173 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 174 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 175 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 176 |
+
mPageTable: Optional[cute.Tensor] = None,
|
| 177 |
+
window_size_left: Int32 | int | None = None,
|
| 178 |
+
window_size_right: Int32 | int | None = None,
|
| 179 |
+
learnable_sink: Optional[cute.Tensor] = None,
|
| 180 |
+
descale_tensors: Optional[DescaleTensors] = None,
|
| 181 |
+
blocksparse_tensors: Optional[cute.Tensor] = None,
|
| 182 |
+
aux_tensors: Optional[list] = None,
|
| 183 |
+
stream: cuda.CUstream = None,
|
| 184 |
+
):
|
| 185 |
+
# Keep parity with FlashAttentionForwardSm100.__call__ interface.
|
| 186 |
+
# (TODO@wangsiyu) Implement these features.
|
| 187 |
+
assert mSeqUsedQ is None, (
|
| 188 |
+
"SM100 forward with head_dim=256 does not support seqused_q"
|
| 189 |
+
)
|
| 190 |
+
assert learnable_sink is None, (
|
| 191 |
+
"SM100 forward with head_dim=256 does not support learnable_sink"
|
| 192 |
+
)
|
| 193 |
+
assert blocksparse_tensors is None, (
|
| 194 |
+
"SM100 forward with head_dim=256 does not support block sparsity"
|
| 195 |
+
)
|
| 196 |
+
assert aux_tensors is None, "SM100 forward with head_dim=256 does not support aux_tensors"
|
| 197 |
+
assert not self.is_local, (
|
| 198 |
+
"SM100 forward with head_dim=256 does not support local attention yet"
|
| 199 |
+
)
|
| 200 |
+
assert window_size_left is None and window_size_right is None, (
|
| 201 |
+
"SM100 forward with head_dim=256 does not support runtime window_size overrides"
|
| 202 |
+
)
|
| 203 |
+
assert descale_tensors is None, (
|
| 204 |
+
"SM100 forward with head_dim=256 does not support descale_tensors"
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
q_tensor, k_tensor, v_tensor, o_tensor = mQ, mK, mV, mO
|
| 208 |
+
lse_tensor = mLSE
|
| 209 |
+
cum_seqlen_q = mCuSeqlensQ
|
| 210 |
+
cum_seqlen_k = mCuSeqlensK
|
| 211 |
+
|
| 212 |
+
q_rank = len(mQ.shape)
|
| 213 |
+
k_rank = len(mK.shape)
|
| 214 |
+
if cutlass.const_expr(cum_seqlen_q is not None):
|
| 215 |
+
# Varlen path accepts either legacy 5D tensors or standard 3D tensors.
|
| 216 |
+
if cutlass.const_expr(q_rank == 5):
|
| 217 |
+
s_q = mQ.shape[1]
|
| 218 |
+
h_q = mQ.shape[2] * mQ.shape[3]
|
| 219 |
+
d = mQ.shape[4]
|
| 220 |
+
elif cutlass.const_expr(q_rank == 3):
|
| 221 |
+
s_q = mQ.shape[0]
|
| 222 |
+
h_q = mQ.shape[1]
|
| 223 |
+
d = mQ.shape[2]
|
| 224 |
+
else:
|
| 225 |
+
raise RuntimeError(f"hd256 forward varlen expects q rank 3 or 5, got rank {q_rank}")
|
| 226 |
+
else:
|
| 227 |
+
# Non-varlen path accepts either legacy 5D tensors or standard 4D tensors.
|
| 228 |
+
if cutlass.const_expr(q_rank == 5):
|
| 229 |
+
s_q = mQ.shape[1]
|
| 230 |
+
h_q = mQ.shape[2] * mQ.shape[3]
|
| 231 |
+
d = mQ.shape[4]
|
| 232 |
+
elif cutlass.const_expr(q_rank == 4):
|
| 233 |
+
s_q = mQ.shape[1]
|
| 234 |
+
h_q = mQ.shape[2]
|
| 235 |
+
d = mQ.shape[3]
|
| 236 |
+
else:
|
| 237 |
+
raise RuntimeError(
|
| 238 |
+
f"hd256 forward non-varlen expects q rank 4 or 5, got rank {q_rank}"
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
if cutlass.const_expr(cum_seqlen_k is not None):
|
| 242 |
+
if cutlass.const_expr(k_rank == 5):
|
| 243 |
+
s_k = mK.shape[1]
|
| 244 |
+
h_k = mK.shape[2]
|
| 245 |
+
elif cutlass.const_expr(k_rank == 3):
|
| 246 |
+
s_k = mK.shape[0]
|
| 247 |
+
h_k = mK.shape[1]
|
| 248 |
+
else:
|
| 249 |
+
raise RuntimeError(f"hd256 forward varlen expects k rank 3 or 5, got rank {k_rank}")
|
| 250 |
+
else:
|
| 251 |
+
if cutlass.const_expr(k_rank == 5):
|
| 252 |
+
s_k = mK.shape[1]
|
| 253 |
+
h_k = mK.shape[2]
|
| 254 |
+
elif cutlass.const_expr(k_rank == 4):
|
| 255 |
+
s_k = mK.shape[1]
|
| 256 |
+
h_k = mK.shape[2]
|
| 257 |
+
else:
|
| 258 |
+
raise RuntimeError(
|
| 259 |
+
f"hd256 forward non-varlen expects k rank 4 or 5, got rank {k_rank}"
|
| 260 |
+
)
|
| 261 |
+
if cutlass.const_expr(cum_seqlen_q is not None):
|
| 262 |
+
b = mCuSeqlensQ.shape[0] - 1
|
| 263 |
+
elif cutlass.const_expr(cum_seqlen_k is not None):
|
| 264 |
+
b = mCuSeqlensK.shape[0] - 1
|
| 265 |
+
else:
|
| 266 |
+
b = mQ.shape[0]
|
| 267 |
+
|
| 268 |
+
scale_softmax = softmax_scale
|
| 269 |
+
scale_softmax_log2 = softmax_scale * math.log2(math.exp(1.0))
|
| 270 |
+
scale_output = 1.0
|
| 271 |
+
s_lse = s_q
|
| 272 |
+
h_r = h_q // h_k
|
| 273 |
+
s_q64 = Int64(s_q)
|
| 274 |
+
s_k64 = Int64(s_k)
|
| 275 |
+
s_lse64 = Int64(s_lse)
|
| 276 |
+
d64 = cute.assume(Int64(d), divby=128)
|
| 277 |
+
h_r64 = Int64(h_r)
|
| 278 |
+
h_k64 = Int64(h_k)
|
| 279 |
+
b64 = Int64(b)
|
| 280 |
+
s_q_total = (
|
| 281 |
+
q_tensor.shape[1]
|
| 282 |
+
if cum_seqlen_q is not None and q_rank == 5
|
| 283 |
+
else (q_tensor.shape[0] if cum_seqlen_q is not None else s_q64)
|
| 284 |
+
)
|
| 285 |
+
s_k_total = (
|
| 286 |
+
k_tensor.shape[1]
|
| 287 |
+
if cum_seqlen_k is not None and k_rank == 5
|
| 288 |
+
else (k_tensor.shape[0] if cum_seqlen_k is not None else s_k64)
|
| 289 |
+
)
|
| 290 |
+
stride_b_qo = h_r64 * h_k64 * s_q64 * d64 if cum_seqlen_q is None else 0
|
| 291 |
+
stride_b_kv = h_k64 * s_k64 * d64 if cum_seqlen_k is None else 0
|
| 292 |
+
b_lse = b64 if cum_seqlen_q is None else 1
|
| 293 |
+
stride_b_lse = h_r64 * h_k64 * s_lse64 if cum_seqlen_q is None else 0
|
| 294 |
+
|
| 295 |
+
# (s, d, ((h_r, h_k), b))
|
| 296 |
+
q_layout = cute.make_layout(
|
| 297 |
+
(s_q_total, d, ((h_r, h_k), b)),
|
| 298 |
+
stride=(d64 * h_r64 * h_k64, 1, ((d64, d64 * h_r64), stride_b_qo)),
|
| 299 |
+
)
|
| 300 |
+
q = cute.make_tensor(q_tensor.iterator, q_layout)
|
| 301 |
+
if cutlass.const_expr(mPageTable is not None):
|
| 302 |
+
# Paged: K layout (num_pages, page_size, h_k, d); page_table maps kv_coord→physical page.
|
| 303 |
+
num_pages = k_tensor.shape[0]
|
| 304 |
+
page_size = k_tensor.shape[1]
|
| 305 |
+
page_size64 = Int64(page_size)
|
| 306 |
+
max_seqlen_k_paged = Int32(mPageTable.shape[1] * page_size)
|
| 307 |
+
k_paged_layout = cute.make_layout(
|
| 308 |
+
(page_size, d, h_k, num_pages),
|
| 309 |
+
stride=(d64 * h_k64, 1, d64, page_size64 * d64 * h_k64),
|
| 310 |
+
)
|
| 311 |
+
k = cute.make_tensor(k_tensor.iterator, k_paged_layout)
|
| 312 |
+
v_paged_layout = cute.make_layout(
|
| 313 |
+
(d, page_size, h_k, num_pages),
|
| 314 |
+
stride=(1, d64 * h_k64, d64, page_size64 * d64 * h_k64),
|
| 315 |
+
)
|
| 316 |
+
v = cute.make_tensor(v_tensor.iterator, v_paged_layout)
|
| 317 |
+
page_table_layout = cute.make_layout(
|
| 318 |
+
(b, mPageTable.shape[1]),
|
| 319 |
+
stride=(Int64(mPageTable.shape[1]), 1),
|
| 320 |
+
)
|
| 321 |
+
page_table = cute.make_tensor(mPageTable.iterator, page_table_layout)
|
| 322 |
+
else:
|
| 323 |
+
# (s, d, ((h_r, h_k), b)), 0-stride for h_r to broadcast
|
| 324 |
+
k_layout = cute.make_layout(
|
| 325 |
+
(s_k_total, d, ((h_r, h_k), b)),
|
| 326 |
+
stride=(d64 * h_k64, 1, ((0, d64), stride_b_kv)),
|
| 327 |
+
)
|
| 328 |
+
k = cute.make_tensor(k_tensor.iterator, k_layout)
|
| 329 |
+
# (d, s, ((h_r, h_k), b)), 0-stride for h_r to broadcast
|
| 330 |
+
v_layout = cute.make_layout(
|
| 331 |
+
(d, s_k_total, ((h_r, h_k), b)),
|
| 332 |
+
stride=(1, d64 * h_k64, ((0, d64), stride_b_kv)),
|
| 333 |
+
)
|
| 334 |
+
v = cute.make_tensor(v_tensor.iterator, v_layout)
|
| 335 |
+
page_table = None
|
| 336 |
+
max_seqlen_k_paged = None
|
| 337 |
+
# (s, d, ((h_r, h_k), b))
|
| 338 |
+
o_layout = cute.make_layout(
|
| 339 |
+
(s_q_total, d, ((h_r, h_k), b)),
|
| 340 |
+
stride=(d64 * h_r64 * h_k64, 1, ((d64, d64 * h_r64), stride_b_qo)),
|
| 341 |
+
)
|
| 342 |
+
o = cute.make_tensor(o_tensor.iterator, o_layout)
|
| 343 |
+
if cutlass.const_expr(lse_tensor is not None):
|
| 344 |
+
# (s, ((h_r, h_k), b))
|
| 345 |
+
lse_layout = cute.make_layout(
|
| 346 |
+
(s_lse64, ((h_r, h_k), b_lse)),
|
| 347 |
+
stride=(1, ((s_lse64, h_r64 * s_lse64), stride_b_lse)),
|
| 348 |
+
)
|
| 349 |
+
lse = cute.make_tensor(lse_tensor.iterator, lse_layout)
|
| 350 |
+
else:
|
| 351 |
+
lse = None
|
| 352 |
+
|
| 353 |
+
# setup static attributes before smem/grid/tma computation
|
| 354 |
+
self.q_dtype = q.element_type
|
| 355 |
+
self.k_dtype = k.element_type
|
| 356 |
+
self.v_dtype = v.element_type
|
| 357 |
+
self.o_dtype = o.element_type
|
| 358 |
+
self.tilePlikeFP32 = self.qk_mma_tiler[1] // Float32.width * self.q_dtype.width
|
| 359 |
+
|
| 360 |
+
if cutlass.const_expr(self.use_clc_scheduler):
|
| 361 |
+
self.tile_sched_params, grid = compute_grid_clc(
|
| 362 |
+
(s_q, o.shape[1], o.shape[2]) if cum_seqlen_q is not None else o.shape,
|
| 363 |
+
self.cta_tiler,
|
| 364 |
+
(*self.cluster_shape_mn, 1),
|
| 365 |
+
)
|
| 366 |
+
else:
|
| 367 |
+
self.tile_sched_params, grid = compute_grid(
|
| 368 |
+
(s_q, o.shape[1], o.shape[2]) if cum_seqlen_q is not None else o.shape,
|
| 369 |
+
self.cta_tiler,
|
| 370 |
+
self.is_persistent,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
self.q_major_mode = utils.LayoutEnum.from_tensor(q).mma_major_mode()
|
| 374 |
+
self.k_major_mode = utils.LayoutEnum.from_tensor(k).mma_major_mode()
|
| 375 |
+
self.v_major_mode = utils.LayoutEnum.from_tensor(v).mma_major_mode()
|
| 376 |
+
self.o_layout = utils.LayoutEnum.from_tensor(o)
|
| 377 |
+
|
| 378 |
+
if cutlass.const_expr(self.q_major_mode != tcgen05.OperandMajorMode.K):
|
| 379 |
+
raise RuntimeError("The layout of q is not supported")
|
| 380 |
+
if cutlass.const_expr(self.k_major_mode != tcgen05.OperandMajorMode.K):
|
| 381 |
+
raise RuntimeError("The layout of k is not supported")
|
| 382 |
+
if cutlass.const_expr(self.v_major_mode != tcgen05.OperandMajorMode.MN):
|
| 383 |
+
raise RuntimeError("The layout of v is not supported")
|
| 384 |
+
|
| 385 |
+
# check type consistency
|
| 386 |
+
if cutlass.const_expr(self.q_dtype != self.k_dtype):
|
| 387 |
+
raise TypeError(f"Type mismatch: {self.q_dtype} != {self.k_dtype}")
|
| 388 |
+
if cutlass.const_expr(self.q_dtype != self.v_dtype):
|
| 389 |
+
raise TypeError(f"Type mismatch: {self.q_dtype} != {self.v_dtype}")
|
| 390 |
+
self._setup_attributes()
|
| 391 |
+
|
| 392 |
+
cta_group = tcgen05.CtaGroup.TWO
|
| 393 |
+
# the intermediate tensor p is from tmem & k-major
|
| 394 |
+
p_source = tcgen05.OperandSource.TMEM
|
| 395 |
+
p_major_mode = tcgen05.OperandMajorMode.K
|
| 396 |
+
qk_tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 397 |
+
self.q_dtype,
|
| 398 |
+
self.q_major_mode,
|
| 399 |
+
self.k_major_mode,
|
| 400 |
+
self.qk_acc_dtype,
|
| 401 |
+
cta_group,
|
| 402 |
+
self.qk_mma_tiler[:2],
|
| 403 |
+
)
|
| 404 |
+
pv_tiled_mma = sm100_utils.make_trivial_tiled_mma(
|
| 405 |
+
self.v_dtype,
|
| 406 |
+
p_major_mode,
|
| 407 |
+
self.v_major_mode,
|
| 408 |
+
self.pv_acc_dtype,
|
| 409 |
+
cta_group,
|
| 410 |
+
self.pv_mma_tiler[:2],
|
| 411 |
+
p_source,
|
| 412 |
+
)
|
| 413 |
+
|
| 414 |
+
self.cluster_shape_mnk = (*self.cluster_shape_mn, 1)
|
| 415 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 416 |
+
cute.make_layout(self.cluster_shape_mnk),
|
| 417 |
+
(qk_tiled_mma.thr_id.shape,),
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
self.epi_tile = self.pv_block_tiler[:2]
|
| 421 |
+
|
| 422 |
+
q_smem_layout_staged = sm100_utils.make_smem_layout_a(
|
| 423 |
+
qk_tiled_mma,
|
| 424 |
+
self.qk_mma_tiler,
|
| 425 |
+
self.q_dtype,
|
| 426 |
+
self.q_stage,
|
| 427 |
+
)
|
| 428 |
+
k_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
| 429 |
+
qk_tiled_mma,
|
| 430 |
+
self.qk_mma_tiler,
|
| 431 |
+
self.k_dtype,
|
| 432 |
+
self.kv_stage,
|
| 433 |
+
)
|
| 434 |
+
p_tmem_layout_staged = sm100_utils.make_smem_layout_a(
|
| 435 |
+
pv_tiled_mma,
|
| 436 |
+
self.pv_mma_tiler,
|
| 437 |
+
self.q_dtype,
|
| 438 |
+
self.qk_acc_stage,
|
| 439 |
+
)
|
| 440 |
+
p_tmem_layout = cute.select(p_tmem_layout_staged, mode=[0, 1, 2])
|
| 441 |
+
v_smem_layout_staged = sm100_utils.make_smem_layout_b(
|
| 442 |
+
pv_tiled_mma,
|
| 443 |
+
self.pv_mma_tiler,
|
| 444 |
+
self.v_dtype,
|
| 445 |
+
self.kv_stage,
|
| 446 |
+
)
|
| 447 |
+
# TMA load for Q
|
| 448 |
+
tma_load_op = cute.nvgpu.cpasync.CopyBulkTensorTileG2SOp(cta_group)
|
| 449 |
+
|
| 450 |
+
q_smem_layout = cute.select(q_smem_layout_staged, mode=[0, 1, 2])
|
| 451 |
+
tma_atom_q, tma_tensor_q = cute.nvgpu.make_tiled_tma_atom_A(
|
| 452 |
+
tma_load_op,
|
| 453 |
+
q,
|
| 454 |
+
q_smem_layout,
|
| 455 |
+
self.qk_mma_tiler,
|
| 456 |
+
qk_tiled_mma,
|
| 457 |
+
self.cluster_layout_vmnk.shape,
|
| 458 |
+
)
|
| 459 |
+
|
| 460 |
+
# TMA load for K
|
| 461 |
+
k_smem_layout = cute.select(k_smem_layout_staged, mode=[0, 1, 2])
|
| 462 |
+
tma_atom_k, tma_tensor_k = cute.nvgpu.make_tiled_tma_atom_B(
|
| 463 |
+
tma_load_op,
|
| 464 |
+
k,
|
| 465 |
+
k_smem_layout,
|
| 466 |
+
self.qk_mma_tiler,
|
| 467 |
+
qk_tiled_mma,
|
| 468 |
+
self.cluster_layout_vmnk.shape,
|
| 469 |
+
)
|
| 470 |
+
# TMA load for V
|
| 471 |
+
v_smem_layout = cute.select(v_smem_layout_staged, mode=[0, 1, 2])
|
| 472 |
+
tma_atom_v, tma_tensor_v = cute.nvgpu.make_tiled_tma_atom_B(
|
| 473 |
+
tma_load_op,
|
| 474 |
+
v,
|
| 475 |
+
v_smem_layout,
|
| 476 |
+
self.pv_mma_tiler,
|
| 477 |
+
pv_tiled_mma,
|
| 478 |
+
self.cluster_layout_vmnk.shape,
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
q_copy_size = cute.size_in_bytes(self.q_dtype, q_smem_layout)
|
| 482 |
+
k_copy_size = cute.size_in_bytes(self.k_dtype, k_smem_layout)
|
| 483 |
+
self.tma_copy_q_bytes = q_copy_size * cute.size(qk_tiled_mma.thr_id.shape)
|
| 484 |
+
self.tma_copy_kv_bytes = k_copy_size * cute.size(qk_tiled_mma.thr_id.shape)
|
| 485 |
+
|
| 486 |
+
@cute.struct
|
| 487 |
+
class SharedStorage:
|
| 488 |
+
# TMA G2S load barriers: LOAD warp (producer) -> MMA warp (consumer)
|
| 489 |
+
load_q_mbar_ptr: cute.struct.MemRange[
|
| 490 |
+
Int64, self.q_stage * 2
|
| 491 |
+
] # load_q_{producer,consumer}
|
| 492 |
+
load_kv_mbar_ptr: cute.struct.MemRange[
|
| 493 |
+
Int64, self.kv_stage * 2
|
| 494 |
+
] # load_kv_{producer,consumer}
|
| 495 |
+
mma_s_mbar_ptr: cute.struct.MemRange[Int64, self.qk_acc_stage * 2]
|
| 496 |
+
p_mma_mbar_ptr: cute.struct.MemRange[Int64, self.qk_acc_stage * 2]
|
| 497 |
+
# Softmax -> Correction signaling barriers (row_max/row_sum vec ready)
|
| 498 |
+
s_corr_mbar_ptr: cute.struct.MemRange[
|
| 499 |
+
Int64, self.qk_acc_stage * 2
|
| 500 |
+
] # s_corr_{producer,consumer}
|
| 501 |
+
sum_mbar_ptr: cute.struct.MemRange[Int64, 2]
|
| 502 |
+
# MMA -> Correction ownership barriers for O_partial tokens (online rescale/finalize)
|
| 503 |
+
mma_corr_mbar_ptr: cute.struct.MemRange[
|
| 504 |
+
Int64, self.mma_corr_stage * 2
|
| 505 |
+
] # mma_corr_{producer,consumer}
|
| 506 |
+
# A CTA-wide "TMEM lifetime" barrier used to safely deallocate TMEM after all users finish.
|
| 507 |
+
tmem_dealloc_mbar_ptr: Int64
|
| 508 |
+
# Tmem holding buffer
|
| 509 |
+
tmem_holding_buf: Int32
|
| 510 |
+
# CLC pipeline barriers and response buffer
|
| 511 |
+
clc_mbar_ptr: cute.struct.MemRange[Int64, 2]
|
| 512 |
+
clc_response: cute.struct.MemRange[Int32, 4]
|
| 513 |
+
|
| 514 |
+
self.shared_storage = SharedStorage
|
| 515 |
+
|
| 516 |
+
grid = cute.round_up(grid, self.cluster_shape_mnk)
|
| 517 |
+
# Launch the kernel synchronously
|
| 518 |
+
self.kernel(
|
| 519 |
+
qk_tiled_mma,
|
| 520 |
+
pv_tiled_mma,
|
| 521 |
+
tma_atom_q,
|
| 522 |
+
tma_tensor_q,
|
| 523 |
+
tma_atom_k,
|
| 524 |
+
tma_tensor_k,
|
| 525 |
+
tma_atom_v,
|
| 526 |
+
tma_tensor_v,
|
| 527 |
+
o,
|
| 528 |
+
cum_seqlen_q,
|
| 529 |
+
cum_seqlen_k,
|
| 530 |
+
mSeqUsedK,
|
| 531 |
+
lse,
|
| 532 |
+
scale_softmax_log2,
|
| 533 |
+
scale_softmax,
|
| 534 |
+
scale_output,
|
| 535 |
+
page_table,
|
| 536 |
+
max_seqlen_k_paged,
|
| 537 |
+
window_size_left,
|
| 538 |
+
window_size_right,
|
| 539 |
+
self.cluster_layout_vmnk,
|
| 540 |
+
q_smem_layout_staged,
|
| 541 |
+
k_smem_layout_staged,
|
| 542 |
+
p_tmem_layout,
|
| 543 |
+
v_smem_layout_staged,
|
| 544 |
+
self.tile_sched_params,
|
| 545 |
+
).launch(
|
| 546 |
+
grid=grid,
|
| 547 |
+
block=[self.threads_per_cta, 1, 1],
|
| 548 |
+
cluster=self.cluster_shape_mnk,
|
| 549 |
+
stream=stream,
|
| 550 |
+
min_blocks_per_mp=1,
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
# GPU device kernel
|
| 554 |
+
@cute.kernel
|
| 555 |
+
def kernel(
|
| 556 |
+
self,
|
| 557 |
+
qk_tiled_mma: cute.TiledMma,
|
| 558 |
+
pv_tiled_mma: cute.TiledMma,
|
| 559 |
+
tma_atom_q: cute.CopyAtom,
|
| 560 |
+
mQ_qdl: cute.Tensor,
|
| 561 |
+
tma_atom_k: cute.CopyAtom,
|
| 562 |
+
mK_kdl: cute.Tensor,
|
| 563 |
+
tma_atom_v: cute.CopyAtom,
|
| 564 |
+
mV_dkl: cute.Tensor,
|
| 565 |
+
mO_qdl: cute.Tensor,
|
| 566 |
+
cum_seqlen_q: Optional[cute.Tensor],
|
| 567 |
+
cum_seqlen_k: Optional[cute.Tensor],
|
| 568 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 569 |
+
mLSE: Optional[cute.Tensor],
|
| 570 |
+
scale_softmax_log2: Float32,
|
| 571 |
+
scale_softmax: Float32,
|
| 572 |
+
scale_output: Float32,
|
| 573 |
+
mPageTable: Optional[cute.Tensor],
|
| 574 |
+
max_seqlen_k: Optional[Int32],
|
| 575 |
+
window_size_left: Optional[Int32],
|
| 576 |
+
window_size_right: Optional[Int32],
|
| 577 |
+
cluster_layout_vmnk: cute.Layout,
|
| 578 |
+
q_smem_layout_staged: cute.ComposedLayout,
|
| 579 |
+
k_smem_layout_staged: cute.ComposedLayout,
|
| 580 |
+
p_tmem_layout_staged: cute.ComposedLayout,
|
| 581 |
+
v_smem_layout_staged: cute.ComposedLayout,
|
| 582 |
+
tile_sched_params: FmhaStaticTileSchedulerParams | FmhaClcDynamicTileSchedulerParams,
|
| 583 |
+
):
|
| 584 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 585 |
+
|
| 586 |
+
#
|
| 587 |
+
# Prefetch tma desc
|
| 588 |
+
#
|
| 589 |
+
if warp_idx == self.load_warp_id:
|
| 590 |
+
cute.nvgpu.cpasync.prefetch_descriptor(tma_atom_q)
|
| 591 |
+
cute.nvgpu.cpasync.prefetch_descriptor(tma_atom_k)
|
| 592 |
+
cute.nvgpu.cpasync.prefetch_descriptor(tma_atom_v)
|
| 593 |
+
|
| 594 |
+
bidx, _, _ = cute.arch.block_idx()
|
| 595 |
+
mma_tile_coord_v = bidx % cute.size(qk_tiled_mma.thr_id.shape)
|
| 596 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 597 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(cta_rank_in_cluster)
|
| 598 |
+
|
| 599 |
+
# Alloc
|
| 600 |
+
smem = utils.SmemAllocator()
|
| 601 |
+
storage = smem.allocate(self.shared_storage)
|
| 602 |
+
|
| 603 |
+
load_q_producer, load_q_consumer = pipeline.PipelineTmaUmma.create(
|
| 604 |
+
num_stages=self.q_stage,
|
| 605 |
+
producer_group=make_thread_cooperative_group(len([self.load_warp_id])),
|
| 606 |
+
consumer_group=make_thread_cooperative_group(len([self.mma_warp_id])),
|
| 607 |
+
tx_count=self.tma_copy_q_bytes,
|
| 608 |
+
barrier_storage=storage.load_q_mbar_ptr.data_ptr(),
|
| 609 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 610 |
+
defer_sync=True,
|
| 611 |
+
).make_participants()
|
| 612 |
+
load_kv_producer, load_kv_consumer = pipeline.PipelineTmaUmma.create(
|
| 613 |
+
num_stages=self.kv_stage,
|
| 614 |
+
producer_group=make_thread_cooperative_group(len([self.load_warp_id])),
|
| 615 |
+
consumer_group=make_thread_cooperative_group(len([self.mma_warp_id])),
|
| 616 |
+
tx_count=self.tma_copy_kv_bytes,
|
| 617 |
+
barrier_storage=storage.load_kv_mbar_ptr.data_ptr(),
|
| 618 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 619 |
+
defer_sync=True,
|
| 620 |
+
).make_participants()
|
| 621 |
+
mma_s_producer, mma_s_consumer = pipeline.PipelineUmmaAsync.create(
|
| 622 |
+
num_stages=self.qk_acc_stage,
|
| 623 |
+
producer_group=make_thread_cooperative_group(len([self.mma_warp_id])),
|
| 624 |
+
consumer_group=make_thread_cooperative_group(
|
| 625 |
+
len(self.softmax_warp_ids) * self.threads_per_warp * self.cluster_shape_mnk[0],
|
| 626 |
+
),
|
| 627 |
+
barrier_storage=storage.mma_s_mbar_ptr.data_ptr(),
|
| 628 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 629 |
+
defer_sync=True,
|
| 630 |
+
).make_participants()
|
| 631 |
+
p_mma_producer, p_mma_consumer = pipeline.PipelineAsyncUmma.create(
|
| 632 |
+
num_stages=self.qk_acc_stage,
|
| 633 |
+
producer_group=make_thread_cooperative_group(
|
| 634 |
+
len(self.softmax_warp_ids) * self.threads_per_warp * self.cluster_shape_mnk[0],
|
| 635 |
+
),
|
| 636 |
+
consumer_group=make_thread_cooperative_group(len([self.mma_warp_id])),
|
| 637 |
+
barrier_storage=storage.p_mma_mbar_ptr.data_ptr(),
|
| 638 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 639 |
+
defer_sync=True,
|
| 640 |
+
).make_participants()
|
| 641 |
+
s_corr_producer, s_corr_consumer = pipeline.PipelineAsync.create(
|
| 642 |
+
num_stages=self.qk_acc_stage,
|
| 643 |
+
producer_group=make_thread_cooperative_group(
|
| 644 |
+
self.threads_per_warp * len(self.softmax_warp_ids)
|
| 645 |
+
),
|
| 646 |
+
consumer_group=make_thread_cooperative_group(
|
| 647 |
+
self.threads_per_warp * len(self.correction_warp_ids)
|
| 648 |
+
),
|
| 649 |
+
barrier_storage=storage.s_corr_mbar_ptr.data_ptr(),
|
| 650 |
+
defer_sync=True,
|
| 651 |
+
).make_participants()
|
| 652 |
+
sum_producer, sum_consumer = pipeline.PipelineAsync.create(
|
| 653 |
+
num_stages=1,
|
| 654 |
+
producer_group=make_thread_cooperative_group(
|
| 655 |
+
self.threads_per_warp * len(self.softmax_warp_ids)
|
| 656 |
+
),
|
| 657 |
+
consumer_group=make_thread_cooperative_group(
|
| 658 |
+
self.threads_per_warp * len(self.correction_warp_ids)
|
| 659 |
+
),
|
| 660 |
+
barrier_storage=storage.sum_mbar_ptr.data_ptr(),
|
| 661 |
+
defer_sync=True,
|
| 662 |
+
).make_participants()
|
| 663 |
+
mma_corr_producer, mma_corr_consumer = pipeline.PipelineUmmaAsync.create(
|
| 664 |
+
num_stages=self.mma_corr_stage,
|
| 665 |
+
producer_group=make_thread_cooperative_group(len([self.mma_warp_id])),
|
| 666 |
+
consumer_group=make_thread_cooperative_group(
|
| 667 |
+
len(self.correction_warp_ids) * self.threads_per_warp * self.cluster_shape_mnk[0],
|
| 668 |
+
),
|
| 669 |
+
barrier_storage=storage.mma_corr_mbar_ptr.data_ptr(),
|
| 670 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 671 |
+
defer_sync=True,
|
| 672 |
+
).make_participants()
|
| 673 |
+
# Tensor memory dealloc barrier init
|
| 674 |
+
tmem = utils.TmemAllocator(
|
| 675 |
+
storage.tmem_holding_buf,
|
| 676 |
+
barrier_for_retrieve=self.tmem_alloc_barrier,
|
| 677 |
+
allocator_warp_id=self.correction_warp_ids[0],
|
| 678 |
+
is_two_cta=True,
|
| 679 |
+
two_cta_tmem_dealloc_mbar_ptr=storage.tmem_dealloc_mbar_ptr,
|
| 680 |
+
)
|
| 681 |
+
tmem.allocate(self.tmem_alloc_cols)
|
| 682 |
+
tmem.wait_for_alloc()
|
| 683 |
+
tmem_ptr = tmem.retrieve_ptr(self.qk_acc_dtype)
|
| 684 |
+
# Initialize CLC state if using dynamic scheduler
|
| 685 |
+
if cutlass.const_expr(self.use_clc_scheduler):
|
| 686 |
+
clc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 687 |
+
cluster_size = cute.size(self.cluster_shape_mnk)
|
| 688 |
+
num_clc_consumer_threads = self.threads_per_warp * (
|
| 689 |
+
1 # sched_warp (CTA 0 only)
|
| 690 |
+
+ cluster_size
|
| 691 |
+
* (
|
| 692 |
+
len(self.softmax_warp_ids)
|
| 693 |
+
+ len(self.correction_warp_ids)
|
| 694 |
+
+ 1 # mma_warp
|
| 695 |
+
+ 1 # load_warp
|
| 696 |
+
)
|
| 697 |
+
)
|
| 698 |
+
clc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 699 |
+
pipeline.Agent.Thread, num_clc_consumer_threads
|
| 700 |
+
)
|
| 701 |
+
clc_response_ptr = storage.clc_response.data_ptr()
|
| 702 |
+
clc = ClcState.create(
|
| 703 |
+
hw_scheduler=ClcDynamicPersistentTileScheduler.create(
|
| 704 |
+
self.tile_sched_params.clc_hw_params(),
|
| 705 |
+
cute.arch.block_idx(),
|
| 706 |
+
cute.arch.grid_dim(),
|
| 707 |
+
clc_response_ptr,
|
| 708 |
+
),
|
| 709 |
+
pipeline=pipeline.PipelineClcFetchAsync.create(
|
| 710 |
+
barrier_storage=storage.clc_mbar_ptr.data_ptr(),
|
| 711 |
+
num_stages=self.num_clc_stage,
|
| 712 |
+
producer_group=clc_pipeline_producer_group,
|
| 713 |
+
consumer_group=clc_pipeline_consumer_group,
|
| 714 |
+
tx_count=self.num_clc_response_bytes,
|
| 715 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 716 |
+
defer_sync=True,
|
| 717 |
+
),
|
| 718 |
+
consumer_state=pipeline.make_pipeline_state(
|
| 719 |
+
pipeline.PipelineUserType.Consumer, self.num_clc_stage
|
| 720 |
+
),
|
| 721 |
+
producer_state=pipeline.make_pipeline_state(
|
| 722 |
+
pipeline.PipelineUserType.Producer, self.num_clc_stage
|
| 723 |
+
),
|
| 724 |
+
)
|
| 725 |
+
else:
|
| 726 |
+
clc = None
|
| 727 |
+
clc_response_ptr = None
|
| 728 |
+
|
| 729 |
+
# Cluster arrive after barrier init
|
| 730 |
+
pipeline.pipeline_init_arrive(cluster_shape_mn=cluster_layout_vmnk, is_relaxed=True)
|
| 731 |
+
|
| 732 |
+
sQ = smem.allocate_tensor(
|
| 733 |
+
element_type=self.q_dtype,
|
| 734 |
+
layout=q_smem_layout_staged.outer,
|
| 735 |
+
swizzle=q_smem_layout_staged.inner,
|
| 736 |
+
byte_alignment=128,
|
| 737 |
+
)
|
| 738 |
+
sK = smem.allocate_tensor(
|
| 739 |
+
element_type=self.k_dtype,
|
| 740 |
+
layout=k_smem_layout_staged.outer,
|
| 741 |
+
swizzle=k_smem_layout_staged.inner,
|
| 742 |
+
byte_alignment=128,
|
| 743 |
+
)
|
| 744 |
+
# K and V now use separate memory since we removed the transform stage
|
| 745 |
+
sV = smem.allocate_tensor(
|
| 746 |
+
element_type=self.v_dtype,
|
| 747 |
+
layout=v_smem_layout_staged.outer,
|
| 748 |
+
swizzle=v_smem_layout_staged.inner,
|
| 749 |
+
byte_alignment=128,
|
| 750 |
+
)
|
| 751 |
+
|
| 752 |
+
sSum = smem.allocate_tensor(
|
| 753 |
+
element_type=self.qk_acc_dtype,
|
| 754 |
+
layout=cute.make_layout(len(self.softmax_warp_ids) * self.threads_per_warp),
|
| 755 |
+
byte_alignment=128,
|
| 756 |
+
)
|
| 757 |
+
qk_thr_mma = qk_tiled_mma.get_slice(mma_tile_coord_v) # default 1sm
|
| 758 |
+
pv_thr_mma = pv_tiled_mma.get_slice(mma_tile_coord_v) # default 1sm
|
| 759 |
+
tSrQ = qk_thr_mma.make_fragment_A(sQ)
|
| 760 |
+
tSrK = qk_thr_mma.make_fragment_B(sK)
|
| 761 |
+
tOrV = pv_thr_mma.make_fragment_B(sV)
|
| 762 |
+
qk_acc_shape = qk_thr_mma.partition_shape_C((self.qk_mma_tiler[0], self.qk_mma_tiler[1]))
|
| 763 |
+
tStS = qk_thr_mma.make_fragment_C(cute.append(qk_acc_shape, self.qk_acc_stage))
|
| 764 |
+
pv_acc_shape = pv_thr_mma.partition_shape_C((self.pv_mma_tiler[0], self.pv_mma_tiler[1]))
|
| 765 |
+
tOtO = pv_thr_mma.make_fragment_C(pv_acc_shape)
|
| 766 |
+
tOtO_layout = cute.append(
|
| 767 |
+
tOtO.layout,
|
| 768 |
+
cute.make_layout(
|
| 769 |
+
self.iterations_pv,
|
| 770 |
+
stride=self.pv_mma_tiler[1] // self.tmem_warp_shape_mn[1],
|
| 771 |
+
),
|
| 772 |
+
)
|
| 773 |
+
tStS = cute.make_tensor(tStS.iterator + self.tmem_s_offset, tStS.layout)
|
| 774 |
+
tOtO_staged = cute.make_tensor(tOtO.iterator + self.tmem_o_offset, tOtO_layout)
|
| 775 |
+
|
| 776 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 777 |
+
# EMPTY
|
| 778 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 779 |
+
for _i in cutlass.range_constexpr(len(self.empty_warp_id)):
|
| 780 |
+
if warp_idx == self.empty_warp_id[_i]:
|
| 781 |
+
cute.arch.warpgroup_reg_dealloc(self.num_regs_other)
|
| 782 |
+
|
| 783 |
+
if cutlass.const_expr(self.use_clc_scheduler):
|
| 784 |
+
tile_sched = FmhaClcDynamicTileScheduler.create(
|
| 785 |
+
tile_sched_params,
|
| 786 |
+
cute.arch.block_idx(),
|
| 787 |
+
cute.arch.grid_dim(),
|
| 788 |
+
clc_response_ptr,
|
| 789 |
+
clc,
|
| 790 |
+
)
|
| 791 |
+
else:
|
| 792 |
+
blk_idx = cute.arch.block_idx()
|
| 793 |
+
tile_sched = FmhaStaticTileScheduler(
|
| 794 |
+
tile_sched_params, blk_idx[0], blk_idx, cute.arch.grid_dim()
|
| 795 |
+
)
|
| 796 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 797 |
+
|
| 798 |
+
# Cluster wait
|
| 799 |
+
pipeline.pipeline_init_wait(cluster_shape_mn=cluster_layout_vmnk)
|
| 800 |
+
|
| 801 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 802 |
+
# LOAD
|
| 803 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 804 |
+
if warp_idx == self.load_warp_id:
|
| 805 |
+
cute.arch.warpgroup_reg_dealloc(self.num_regs_other)
|
| 806 |
+
while work_tile.is_valid_tile:
|
| 807 |
+
curr_block_coord = work_tile.tile_idx # (q_tile_idx, 0, (head_idx, batch_idx))
|
| 808 |
+
mma_block_coord = (
|
| 809 |
+
curr_block_coord[0] // cute.size(qk_tiled_mma.thr_id.shape),
|
| 810 |
+
curr_block_coord[1],
|
| 811 |
+
curr_block_coord[2],
|
| 812 |
+
)
|
| 813 |
+
continue_cond = False
|
| 814 |
+
batch_coord = curr_block_coord[2][1]
|
| 815 |
+
seqlen_q = mQ_qdl.shape[0]
|
| 816 |
+
seqlen_k = (
|
| 817 |
+
mK_kdl.shape[0] if cutlass.const_expr(mPageTable is None) else max_seqlen_k
|
| 818 |
+
)
|
| 819 |
+
cuseqlen_q = Int32(0)
|
| 820 |
+
cuseqlen_k = Int32(0)
|
| 821 |
+
block_offset = (
|
| 822 |
+
Int32(0),
|
| 823 |
+
Int32(0),
|
| 824 |
+
Int32(0),
|
| 825 |
+
((Int32(0), Int32(0)), Int32(0)),
|
| 826 |
+
)
|
| 827 |
+
if cutlass.const_expr(cum_seqlen_q is not None):
|
| 828 |
+
cuseqlen_q = cum_seqlen_q[batch_coord]
|
| 829 |
+
seqlen_q = cum_seqlen_q[batch_coord + 1] - cuseqlen_q
|
| 830 |
+
if cutlass.const_expr(cum_seqlen_k is not None):
|
| 831 |
+
cuseqlen_k = cum_seqlen_k[batch_coord]
|
| 832 |
+
seqlen_k = cum_seqlen_k[batch_coord + 1] - cuseqlen_k
|
| 833 |
+
block_offset = (
|
| 834 |
+
cuseqlen_q,
|
| 835 |
+
cuseqlen_k,
|
| 836 |
+
Int32(0),
|
| 837 |
+
((Int32(0), Int32(0)), Int32(0)),
|
| 838 |
+
)
|
| 839 |
+
continue_cond = not FmhaStaticTileScheduler.check_valid_work_for_seqlen_q(
|
| 840 |
+
self.qk_mma_tiler[0],
|
| 841 |
+
mma_block_coord[0],
|
| 842 |
+
seqlen_q,
|
| 843 |
+
)
|
| 844 |
+
if cutlass.const_expr(mSeqUsedK is not None):
|
| 845 |
+
seqlen_k = mSeqUsedK[batch_coord]
|
| 846 |
+
if not continue_cond:
|
| 847 |
+
mQ_qdl_ = cute.domain_offset(cute.select(block_offset, mode=[0, 2, 3]), mQ_qdl)
|
| 848 |
+
# Local tile partition global tensors
|
| 849 |
+
q_cta_layout = cute.make_layout(
|
| 850 |
+
cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape
|
| 851 |
+
)
|
| 852 |
+
# (bM, bK, loopM, loopK, loopL)
|
| 853 |
+
gQ_qdl = cute.flat_divide(mQ_qdl_, cute.select(self.qk_mma_tiler, mode=[0, 2]))
|
| 854 |
+
tSgQ_qdl = qk_thr_mma.partition_A(gQ_qdl)
|
| 855 |
+
tQsQ, tQgQ_qdl = cute.nvgpu.cpasync.tma_partition(
|
| 856 |
+
tma_atom_q,
|
| 857 |
+
block_in_cluster_coord_vmnk[2],
|
| 858 |
+
q_cta_layout,
|
| 859 |
+
cute.group_modes(sQ, 0, 3),
|
| 860 |
+
cute.group_modes(tSgQ_qdl, 0, 3),
|
| 861 |
+
)
|
| 862 |
+
kv_cta_layout = cute.make_layout(
|
| 863 |
+
cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape
|
| 864 |
+
)
|
| 865 |
+
if cutlass.const_expr(mPageTable is None):
|
| 866 |
+
# Dense path: domain_offset K/V by batch block, select batch via mma_block_coord[2].
|
| 867 |
+
mK_kdl_ = cute.domain_offset(
|
| 868 |
+
cute.select(block_offset, mode=[1, 2, 3]), mK_kdl
|
| 869 |
+
)
|
| 870 |
+
mV_dkl_ = cute.domain_offset(
|
| 871 |
+
cute.select(block_offset, mode=[2, 1, 3]), mV_dkl
|
| 872 |
+
)
|
| 873 |
+
gK_kdl = cute.flat_divide(
|
| 874 |
+
mK_kdl_, cute.select(self.qk_mma_tiler, mode=[1, 2])
|
| 875 |
+
)
|
| 876 |
+
tSgK_kdl = qk_thr_mma.partition_B(gK_kdl)
|
| 877 |
+
tKsK, tKgK_kdl = cute.nvgpu.cpasync.tma_partition(
|
| 878 |
+
tma_atom_k,
|
| 879 |
+
block_in_cluster_coord_vmnk[1],
|
| 880 |
+
kv_cta_layout,
|
| 881 |
+
cute.group_modes(sK, 0, 3),
|
| 882 |
+
cute.group_modes(tSgK_kdl, 0, 3),
|
| 883 |
+
)
|
| 884 |
+
gV_dkl = cute.flat_divide(
|
| 885 |
+
mV_dkl_, cute.select(self.pv_mma_tiler, mode=[1, 2])
|
| 886 |
+
)
|
| 887 |
+
tSgV_dkl = pv_thr_mma.partition_B(gV_dkl)
|
| 888 |
+
tVsV, tVgV_dkl = cute.nvgpu.cpasync.tma_partition(
|
| 889 |
+
tma_atom_v,
|
| 890 |
+
block_in_cluster_coord_vmnk[1],
|
| 891 |
+
kv_cta_layout,
|
| 892 |
+
cute.group_modes(sV, 0, 3),
|
| 893 |
+
cute.group_modes(tSgV_dkl, 0, 3),
|
| 894 |
+
)
|
| 895 |
+
# ((atom_v, rest_v), RestN, RestK)
|
| 896 |
+
tKgK = tKgK_kdl[None, None, None, mma_block_coord[2]]
|
| 897 |
+
tVgV = tVgV_dkl[None, None, None, mma_block_coord[2]]
|
| 898 |
+
else:
|
| 899 |
+
# Paged path: slice K/V by KV head, keep num_pages dim for page_idx-based TMA.
|
| 900 |
+
head_kv_coord = curr_block_coord[2][0] // self.qhead_per_kvhead
|
| 901 |
+
mK_kdl_ = mK_kdl[None, None, head_kv_coord, None]
|
| 902 |
+
mV_dkl_ = mV_dkl[None, None, head_kv_coord, None]
|
| 903 |
+
gK_kdl = cute.flat_divide(
|
| 904 |
+
mK_kdl_, cute.select(self.qk_mma_tiler, mode=[1, 2])
|
| 905 |
+
)
|
| 906 |
+
tSgK_kdl = qk_thr_mma.partition_B(gK_kdl)
|
| 907 |
+
tKsK, tKgK_kdl = cute.nvgpu.cpasync.tma_partition(
|
| 908 |
+
tma_atom_k,
|
| 909 |
+
block_in_cluster_coord_vmnk[1],
|
| 910 |
+
kv_cta_layout,
|
| 911 |
+
cute.group_modes(sK, 0, 3),
|
| 912 |
+
cute.group_modes(tSgK_kdl, 0, 3),
|
| 913 |
+
)
|
| 914 |
+
gV_dkl = cute.flat_divide(
|
| 915 |
+
mV_dkl_, cute.select(self.pv_mma_tiler, mode=[1, 2])
|
| 916 |
+
)
|
| 917 |
+
tSgV_dkl = pv_thr_mma.partition_B(gV_dkl)
|
| 918 |
+
tVsV, tVgV_dkl = cute.nvgpu.cpasync.tma_partition(
|
| 919 |
+
tma_atom_v,
|
| 920 |
+
block_in_cluster_coord_vmnk[1],
|
| 921 |
+
kv_cta_layout,
|
| 922 |
+
cute.group_modes(sV, 0, 3),
|
| 923 |
+
cute.group_modes(tSgV_dkl, 0, 3),
|
| 924 |
+
)
|
| 925 |
+
tKgK = tKgK_kdl
|
| 926 |
+
tVgV = tVgV_dkl
|
| 927 |
+
# ((atom_v, rest_v), RestK)
|
| 928 |
+
tQgQ = tQgQ_qdl[None, mma_block_coord[0], None, mma_block_coord[2]]
|
| 929 |
+
|
| 930 |
+
seqlen_kv_loop_start, seqlen_kv_loop_steps = (
|
| 931 |
+
FusedMask.get_trip_start_count_via_block_info(
|
| 932 |
+
mma_block_coord,
|
| 933 |
+
self.qk_mma_tiler,
|
| 934 |
+
seqlen_q,
|
| 935 |
+
seqlen_k,
|
| 936 |
+
self.is_causal,
|
| 937 |
+
self.is_local,
|
| 938 |
+
window_size_left,
|
| 939 |
+
window_size_right,
|
| 940 |
+
)
|
| 941 |
+
)
|
| 942 |
+
seqlen_kv_loop_end = seqlen_kv_loop_start + seqlen_kv_loop_steps
|
| 943 |
+
# Q
|
| 944 |
+
for iter in cutlass.range(self.iterations_qk, unroll=1):
|
| 945 |
+
q_handle = load_q_producer.acquire_and_advance()
|
| 946 |
+
cute.copy(
|
| 947 |
+
tma_atom_q,
|
| 948 |
+
tQgQ[None, iter],
|
| 949 |
+
tQsQ[None, q_handle.index],
|
| 950 |
+
tma_bar_ptr=q_handle.barrier,
|
| 951 |
+
)
|
| 952 |
+
|
| 953 |
+
# K0
|
| 954 |
+
kv_coord = seqlen_kv_loop_start
|
| 955 |
+
k_page_idx = (
|
| 956 |
+
mPageTable[batch_coord, kv_coord]
|
| 957 |
+
if cutlass.const_expr(mPageTable is not None)
|
| 958 |
+
else None
|
| 959 |
+
)
|
| 960 |
+
for iter in cutlass.range(self.iterations_qk, unroll=1):
|
| 961 |
+
k_handle = load_kv_producer.acquire_and_advance()
|
| 962 |
+
cute.copy(
|
| 963 |
+
tma_atom_k,
|
| 964 |
+
tKgK[None, kv_coord, iter]
|
| 965 |
+
if cutlass.const_expr(mPageTable is None)
|
| 966 |
+
else tKgK[None, 0, iter, k_page_idx],
|
| 967 |
+
tKsK[None, k_handle.index],
|
| 968 |
+
tma_bar_ptr=k_handle.barrier,
|
| 969 |
+
)
|
| 970 |
+
kv_coord += 1
|
| 971 |
+
# v_page_idx_prev carries K[i-1]'s page index for use as V[i-1]'s page
|
| 972 |
+
# (K and V for the same KV block share the same physical page).
|
| 973 |
+
# Also serves as the Vend page index when seqlen_kv_loop_steps == 1.
|
| 974 |
+
v_page_idx_prev = (
|
| 975 |
+
k_page_idx if cutlass.const_expr(mPageTable is not None) else None
|
| 976 |
+
)
|
| 977 |
+
# Prefetch K1 page after K0 TMA dispatch to hide L2 latency.
|
| 978 |
+
if cutlass.const_expr(mPageTable is not None):
|
| 979 |
+
if seqlen_kv_loop_steps > 1:
|
| 980 |
+
k_page_idx = mPageTable[batch_coord, kv_coord]
|
| 981 |
+
|
| 982 |
+
for i in cutlass.range(1, seqlen_kv_loop_steps, 1, unroll=1):
|
| 983 |
+
# Ki: k_page_idx was prefetched at end of previous iteration
|
| 984 |
+
# (or in the prologue for i==1); L2 latency already hidden.
|
| 985 |
+
for iter in cutlass.range(self.iterations_qk, unroll=1):
|
| 986 |
+
k_handle = load_kv_producer.acquire_and_advance()
|
| 987 |
+
cute.copy(
|
| 988 |
+
tma_atom_k,
|
| 989 |
+
tKgK[None, kv_coord, iter]
|
| 990 |
+
if cutlass.const_expr(mPageTable is None)
|
| 991 |
+
else tKgK[None, 0, iter, k_page_idx],
|
| 992 |
+
tKsK[None, k_handle.index],
|
| 993 |
+
tma_bar_ptr=k_handle.barrier,
|
| 994 |
+
)
|
| 995 |
+
# Vi-1: reuse v_page_idx_prev (= K[i-1]'s page), no extra GMEM read.
|
| 996 |
+
for iter in cutlass.range(self.iterations_pv, unroll=1):
|
| 997 |
+
v_handle = load_kv_producer.acquire_and_advance()
|
| 998 |
+
cute.copy(
|
| 999 |
+
tma_atom_v,
|
| 1000 |
+
tVgV[None, iter, kv_coord - 1]
|
| 1001 |
+
if cutlass.const_expr(mPageTable is None)
|
| 1002 |
+
else tVgV[None, iter, 0, v_page_idx_prev],
|
| 1003 |
+
tVsV[None, v_handle.index],
|
| 1004 |
+
tma_bar_ptr=v_handle.barrier,
|
| 1005 |
+
)
|
| 1006 |
+
v_page_idx_prev = (
|
| 1007 |
+
k_page_idx if cutlass.const_expr(mPageTable is not None) else None
|
| 1008 |
+
)
|
| 1009 |
+
kv_coord += 1
|
| 1010 |
+
# Prefetch next K page while V TMA is in flight.
|
| 1011 |
+
if cutlass.const_expr(mPageTable is not None):
|
| 1012 |
+
if kv_coord < seqlen_kv_loop_end:
|
| 1013 |
+
k_page_idx = mPageTable[batch_coord, kv_coord]
|
| 1014 |
+
# Vend: reuse v_page_idx_prev (= K[end-1]'s page), no extra GMEM read.
|
| 1015 |
+
for iter in cutlass.range(self.iterations_pv, unroll=1):
|
| 1016 |
+
v_handle = load_kv_producer.acquire_and_advance()
|
| 1017 |
+
cute.copy(
|
| 1018 |
+
tma_atom_v,
|
| 1019 |
+
tVgV[None, iter, seqlen_kv_loop_end - 1]
|
| 1020 |
+
if cutlass.const_expr(mPageTable is None)
|
| 1021 |
+
else tVgV[None, iter, 0, v_page_idx_prev],
|
| 1022 |
+
tVsV[None, v_handle.index],
|
| 1023 |
+
tma_bar_ptr=v_handle.barrier,
|
| 1024 |
+
)
|
| 1025 |
+
|
| 1026 |
+
work_tile = tile_sched.advance_to_next_work()
|
| 1027 |
+
# End of persistent scheduler loop
|
| 1028 |
+
load_kv_producer.tail()
|
| 1029 |
+
load_q_producer.tail()
|
| 1030 |
+
|
| 1031 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1032 |
+
# MMA
|
| 1033 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1034 |
+
if warp_idx == self.mma_warp_id:
|
| 1035 |
+
cute.arch.warpgroup_reg_dealloc(self.num_regs_other)
|
| 1036 |
+
|
| 1037 |
+
while work_tile.is_valid_tile:
|
| 1038 |
+
curr_block_coord = work_tile.tile_idx
|
| 1039 |
+
mma_block_coord = (
|
| 1040 |
+
curr_block_coord[0] // cute.size(qk_tiled_mma.thr_id.shape),
|
| 1041 |
+
curr_block_coord[1],
|
| 1042 |
+
curr_block_coord[2],
|
| 1043 |
+
)
|
| 1044 |
+
continue_cond = False
|
| 1045 |
+
seqlen_q = mQ_qdl.shape[0]
|
| 1046 |
+
seqlen_k = (
|
| 1047 |
+
mK_kdl.shape[0] if cutlass.const_expr(mPageTable is None) else max_seqlen_k
|
| 1048 |
+
)
|
| 1049 |
+
batch_coord = curr_block_coord[2][1]
|
| 1050 |
+
if cutlass.const_expr(cum_seqlen_q is not None):
|
| 1051 |
+
cuseqlen_q = cum_seqlen_q[batch_coord]
|
| 1052 |
+
seqlen_q = cum_seqlen_q[batch_coord + 1] - cuseqlen_q
|
| 1053 |
+
continue_cond = not FmhaStaticTileScheduler.check_valid_work_for_seqlen_q(
|
| 1054 |
+
self.qk_mma_tiler[0],
|
| 1055 |
+
mma_block_coord[0],
|
| 1056 |
+
seqlen_q,
|
| 1057 |
+
)
|
| 1058 |
+
if cutlass.const_expr(mSeqUsedK is not None):
|
| 1059 |
+
seqlen_k = mSeqUsedK[batch_coord]
|
| 1060 |
+
|
| 1061 |
+
if not continue_cond:
|
| 1062 |
+
if cutlass.const_expr(cum_seqlen_k is not None):
|
| 1063 |
+
cuseqlen_k = cum_seqlen_k[batch_coord]
|
| 1064 |
+
seqlen_k = cum_seqlen_k[batch_coord + 1] - cuseqlen_k
|
| 1065 |
+
|
| 1066 |
+
seqlen_kv_loop_start, seqlen_kv_loop_steps = (
|
| 1067 |
+
FusedMask.get_trip_start_count_via_block_info(
|
| 1068 |
+
mma_block_coord,
|
| 1069 |
+
self.qk_mma_tiler,
|
| 1070 |
+
seqlen_q,
|
| 1071 |
+
seqlen_k,
|
| 1072 |
+
self.is_causal,
|
| 1073 |
+
self.is_local,
|
| 1074 |
+
window_size_left,
|
| 1075 |
+
window_size_right,
|
| 1076 |
+
)
|
| 1077 |
+
)
|
| 1078 |
+
seqlen_kv_loop_end = seqlen_kv_loop_start + seqlen_kv_loop_steps
|
| 1079 |
+
|
| 1080 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(
|
| 1081 |
+
cute.arch.block_idx_in_cluster()
|
| 1082 |
+
)
|
| 1083 |
+
is_leader_cta = cta_rank_in_cluster % 2 == 0
|
| 1084 |
+
load_q_releaser = load_q_consumer.clone()
|
| 1085 |
+
pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 1086 |
+
if seqlen_kv_loop_steps > 1:
|
| 1087 |
+
# QK0
|
| 1088 |
+
if is_leader_cta:
|
| 1089 |
+
s_handle = mma_s_producer.acquire_and_advance()
|
| 1090 |
+
tStS_slice = tStS[None, None, None, s_handle.index]
|
| 1091 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 1092 |
+
for iter in cutlass.range(self.iterations_qk, unroll=1):
|
| 1093 |
+
load_q_consumer.wait_and_advance()
|
| 1094 |
+
tSrQ_slice = tSrQ[None, None, None, iter]
|
| 1095 |
+
k_handle = load_kv_consumer.wait_and_advance()
|
| 1096 |
+
tSrK_trans_slice = tSrK[None, None, None, k_handle.index]
|
| 1097 |
+
num_kphases = cute.size(tSrQ_slice, mode=[2])
|
| 1098 |
+
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
|
| 1099 |
+
kphase_coord = (None, None, kphase_idx)
|
| 1100 |
+
cute.gemm(
|
| 1101 |
+
qk_tiled_mma,
|
| 1102 |
+
tStS_slice,
|
| 1103 |
+
tSrQ_slice[kphase_coord],
|
| 1104 |
+
tSrK_trans_slice[kphase_coord],
|
| 1105 |
+
tStS_slice,
|
| 1106 |
+
)
|
| 1107 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1108 |
+
k_handle.release()
|
| 1109 |
+
s_handle.commit()
|
| 1110 |
+
for i in cutlass.range(1, seqlen_kv_loop_steps - 1, 1, unroll=1):
|
| 1111 |
+
# QKi
|
| 1112 |
+
if is_leader_cta:
|
| 1113 |
+
s_handle = mma_s_producer.acquire_and_advance()
|
| 1114 |
+
tStS_slice = tStS[None, None, None, s_handle.index]
|
| 1115 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 1116 |
+
for iter in cutlass.range(self.iterations_qk, unroll=1):
|
| 1117 |
+
tSrQ_slice = tSrQ[None, None, None, iter]
|
| 1118 |
+
k_handle = load_kv_consumer.wait_and_advance()
|
| 1119 |
+
tSrK_trans_slice = tSrK[None, None, None, k_handle.index]
|
| 1120 |
+
num_kphases = cute.size(tSrQ_slice, mode=[2])
|
| 1121 |
+
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
|
| 1122 |
+
kphase_coord = (None, None, kphase_idx)
|
| 1123 |
+
cute.gemm(
|
| 1124 |
+
qk_tiled_mma,
|
| 1125 |
+
tStS_slice,
|
| 1126 |
+
tSrQ_slice[kphase_coord],
|
| 1127 |
+
tSrK_trans_slice[kphase_coord],
|
| 1128 |
+
tStS_slice,
|
| 1129 |
+
)
|
| 1130 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1131 |
+
k_handle.release()
|
| 1132 |
+
s_handle.commit()
|
| 1133 |
+
|
| 1134 |
+
# PVi-1
|
| 1135 |
+
p_handle = p_mma_consumer.wait_and_advance()
|
| 1136 |
+
o_handle = mma_corr_producer.acquire_and_advance()
|
| 1137 |
+
pv_whether_acc = pv_tiled_mma.get(tcgen05.Field.ACCUMULATE)
|
| 1138 |
+
for iter in cutlass.range(self.iterations_pv, unroll=1):
|
| 1139 |
+
v_handle = load_kv_consumer.wait_and_advance()
|
| 1140 |
+
pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, pv_whether_acc)
|
| 1141 |
+
tOtO_slice = tOtO_staged[None, None, None, iter]
|
| 1142 |
+
tStS_slice = tStS[None, None, None, p_handle.index]
|
| 1143 |
+
tP = cute.make_tensor(
|
| 1144 |
+
tStS_slice.iterator, p_tmem_layout_staged.outer
|
| 1145 |
+
)
|
| 1146 |
+
tOrP = pv_thr_mma.make_fragment_A(tP)
|
| 1147 |
+
tOrP_slice = cute.make_tensor(
|
| 1148 |
+
cute.recast_ptr(tStS_slice.iterator, dtype=self.q_dtype),
|
| 1149 |
+
tOrP.layout,
|
| 1150 |
+
)
|
| 1151 |
+
tOrV_slice = tOrV[None, None, None, v_handle.index]
|
| 1152 |
+
num_kphases = cute.size(tOrV_slice, mode=[2])
|
| 1153 |
+
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
|
| 1154 |
+
kphase_coord = (None, None, kphase_idx)
|
| 1155 |
+
cute.gemm(
|
| 1156 |
+
pv_tiled_mma,
|
| 1157 |
+
tOtO_slice,
|
| 1158 |
+
tOrP_slice[kphase_coord],
|
| 1159 |
+
tOrV_slice[kphase_coord],
|
| 1160 |
+
tOtO_slice,
|
| 1161 |
+
)
|
| 1162 |
+
pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1163 |
+
v_handle.release()
|
| 1164 |
+
o_handle.commit()
|
| 1165 |
+
p_handle.release()
|
| 1166 |
+
if is_leader_cta:
|
| 1167 |
+
# QKend
|
| 1168 |
+
s_handle = mma_s_producer.acquire_and_advance()
|
| 1169 |
+
tStS_slice = tStS[None, None, None, s_handle.index]
|
| 1170 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 1171 |
+
for iter in cutlass.range(self.iterations_qk, unroll=1):
|
| 1172 |
+
tSrQ_slice = tSrQ[None, None, None, iter]
|
| 1173 |
+
k_handle = load_kv_consumer.wait_and_advance()
|
| 1174 |
+
tSrK_trans_slice = tSrK[None, None, None, k_handle.index]
|
| 1175 |
+
num_kphases = cute.size(tSrQ_slice, mode=[2])
|
| 1176 |
+
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
|
| 1177 |
+
kphase_coord = (None, None, kphase_idx)
|
| 1178 |
+
cute.gemm(
|
| 1179 |
+
qk_tiled_mma,
|
| 1180 |
+
tStS_slice,
|
| 1181 |
+
tSrQ_slice[kphase_coord],
|
| 1182 |
+
tSrK_trans_slice[kphase_coord],
|
| 1183 |
+
tStS_slice,
|
| 1184 |
+
)
|
| 1185 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1186 |
+
k_handle.release()
|
| 1187 |
+
load_q_releaser.release()
|
| 1188 |
+
load_q_releaser.advance()
|
| 1189 |
+
s_handle.commit()
|
| 1190 |
+
|
| 1191 |
+
# PVend-1
|
| 1192 |
+
p_handle = p_mma_consumer.wait_and_advance()
|
| 1193 |
+
o_handle = mma_corr_producer.acquire_and_advance()
|
| 1194 |
+
pv_whether_acc = pv_tiled_mma.get(tcgen05.Field.ACCUMULATE)
|
| 1195 |
+
for iter in cutlass.range(self.iterations_pv, unroll=1):
|
| 1196 |
+
v_handle = load_kv_consumer.wait_and_advance()
|
| 1197 |
+
pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, pv_whether_acc)
|
| 1198 |
+
tOtO_slice = tOtO_staged[None, None, None, iter]
|
| 1199 |
+
tStS_slice = tStS[None, None, None, p_handle.index]
|
| 1200 |
+
tP = cute.make_tensor(
|
| 1201 |
+
tStS_slice.iterator, p_tmem_layout_staged.outer
|
| 1202 |
+
)
|
| 1203 |
+
tOrP = pv_thr_mma.make_fragment_A(tP)
|
| 1204 |
+
tOrP_slice = cute.make_tensor(
|
| 1205 |
+
cute.recast_ptr(tStS_slice.iterator, dtype=self.q_dtype),
|
| 1206 |
+
tOrP.layout,
|
| 1207 |
+
)
|
| 1208 |
+
tOrV_slice = tOrV[None, None, None, v_handle.index]
|
| 1209 |
+
num_kphases = cute.size(tOrV_slice, mode=[2])
|
| 1210 |
+
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
|
| 1211 |
+
kphase_coord = (None, None, kphase_idx)
|
| 1212 |
+
cute.gemm(
|
| 1213 |
+
pv_tiled_mma,
|
| 1214 |
+
tOtO_slice,
|
| 1215 |
+
tOrP_slice[kphase_coord],
|
| 1216 |
+
tOrV_slice[kphase_coord],
|
| 1217 |
+
tOtO_slice,
|
| 1218 |
+
)
|
| 1219 |
+
pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1220 |
+
v_handle.release()
|
| 1221 |
+
o_handle.commit()
|
| 1222 |
+
p_handle.release()
|
| 1223 |
+
else:
|
| 1224 |
+
if is_leader_cta:
|
| 1225 |
+
# QK0
|
| 1226 |
+
s_handle = mma_s_producer.acquire_and_advance()
|
| 1227 |
+
tStS_slice = tStS[None, None, None, s_handle.index]
|
| 1228 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
|
| 1229 |
+
for iter in cutlass.range(self.iterations_qk, unroll=1):
|
| 1230 |
+
load_q_consumer.wait_and_advance()
|
| 1231 |
+
tSrQ_slice = tSrQ[None, None, None, iter]
|
| 1232 |
+
k_handle = load_kv_consumer.wait_and_advance()
|
| 1233 |
+
tSrK_trans_slice = tSrK[None, None, None, k_handle.index]
|
| 1234 |
+
num_kphases = cute.size(tSrQ_slice, mode=[2])
|
| 1235 |
+
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
|
| 1236 |
+
kphase_coord = (None, None, kphase_idx)
|
| 1237 |
+
cute.gemm(
|
| 1238 |
+
qk_tiled_mma,
|
| 1239 |
+
tStS_slice,
|
| 1240 |
+
tSrQ_slice[kphase_coord],
|
| 1241 |
+
tSrK_trans_slice[kphase_coord],
|
| 1242 |
+
tStS_slice,
|
| 1243 |
+
)
|
| 1244 |
+
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1245 |
+
k_handle.release()
|
| 1246 |
+
load_q_releaser.release()
|
| 1247 |
+
load_q_releaser.advance()
|
| 1248 |
+
s_handle.commit()
|
| 1249 |
+
|
| 1250 |
+
if is_leader_cta:
|
| 1251 |
+
# PVend
|
| 1252 |
+
p_handle = p_mma_consumer.wait_and_advance()
|
| 1253 |
+
o_handle = mma_corr_producer.acquire_and_advance()
|
| 1254 |
+
pv_whether_acc = pv_tiled_mma.get(tcgen05.Field.ACCUMULATE)
|
| 1255 |
+
for iter in cutlass.range(self.iterations_pv, unroll=1):
|
| 1256 |
+
v_handle = load_kv_consumer.wait_and_advance()
|
| 1257 |
+
pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, pv_whether_acc)
|
| 1258 |
+
tOtO_slice = tOtO_staged[None, None, None, iter]
|
| 1259 |
+
tStS_slice = tStS[None, None, None, p_handle.index]
|
| 1260 |
+
tP = cute.make_tensor(tStS_slice.iterator, p_tmem_layout_staged.outer)
|
| 1261 |
+
tOrP = pv_thr_mma.make_fragment_A(tP)
|
| 1262 |
+
tOrP_slice = cute.make_tensor(
|
| 1263 |
+
cute.recast_ptr(tStS_slice.iterator, dtype=self.q_dtype),
|
| 1264 |
+
tOrP.layout,
|
| 1265 |
+
)
|
| 1266 |
+
tOrV_slice = tOrV[None, None, None, v_handle.index]
|
| 1267 |
+
num_kphases = cute.size(tOrV_slice, mode=[2])
|
| 1268 |
+
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
|
| 1269 |
+
kphase_coord = (None, None, kphase_idx)
|
| 1270 |
+
cute.gemm(
|
| 1271 |
+
pv_tiled_mma,
|
| 1272 |
+
tOtO_slice,
|
| 1273 |
+
tOrP_slice[kphase_coord],
|
| 1274 |
+
tOrV_slice[kphase_coord],
|
| 1275 |
+
tOtO_slice,
|
| 1276 |
+
)
|
| 1277 |
+
pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
|
| 1278 |
+
v_handle.release()
|
| 1279 |
+
o_handle.commit()
|
| 1280 |
+
p_handle.release()
|
| 1281 |
+
work_tile = tile_sched.advance_to_next_work()
|
| 1282 |
+
# End of persistent scheduler loop
|
| 1283 |
+
mma_s_producer.tail()
|
| 1284 |
+
mma_corr_producer.tail()
|
| 1285 |
+
|
| 1286 |
+
if warp_idx < self.correction_warp_ids[0] and warp_idx >= self.softmax_warp_ids[0]:
|
| 1287 |
+
# increase register after decreasing
|
| 1288 |
+
cute.arch.warpgroup_reg_alloc(self.num_regs_softmax)
|
| 1289 |
+
|
| 1290 |
+
while work_tile.is_valid_tile:
|
| 1291 |
+
curr_block_coord = work_tile.tile_idx
|
| 1292 |
+
mma_block_coord = (
|
| 1293 |
+
curr_block_coord[0] // cute.size(qk_tiled_mma.thr_id.shape),
|
| 1294 |
+
curr_block_coord[1],
|
| 1295 |
+
curr_block_coord[2],
|
| 1296 |
+
)
|
| 1297 |
+
batch_coord = curr_block_coord[2][1]
|
| 1298 |
+
continue_cond = False
|
| 1299 |
+
seqlen_q = mQ_qdl.shape[0]
|
| 1300 |
+
seqlen_k = (
|
| 1301 |
+
mK_kdl.shape[0] if cutlass.const_expr(mPageTable is None) else max_seqlen_k
|
| 1302 |
+
)
|
| 1303 |
+
cuseqlen_q = Int32(0)
|
| 1304 |
+
if cutlass.const_expr(cum_seqlen_q is not None):
|
| 1305 |
+
cuseqlen_q = cum_seqlen_q[batch_coord]
|
| 1306 |
+
seqlen_q = cum_seqlen_q[batch_coord + 1] - cuseqlen_q
|
| 1307 |
+
continue_cond = not FmhaStaticTileScheduler.check_valid_work_for_seqlen_q(
|
| 1308 |
+
self.qk_mma_tiler[0],
|
| 1309 |
+
mma_block_coord[0],
|
| 1310 |
+
seqlen_q,
|
| 1311 |
+
)
|
| 1312 |
+
if cutlass.const_expr(mSeqUsedK is not None):
|
| 1313 |
+
seqlen_k = mSeqUsedK[batch_coord]
|
| 1314 |
+
if not continue_cond:
|
| 1315 |
+
if cutlass.const_expr(cum_seqlen_k is not None):
|
| 1316 |
+
cuseqlen_k = cum_seqlen_k[batch_coord]
|
| 1317 |
+
seqlen_k = cum_seqlen_k[batch_coord + 1] - cuseqlen_k
|
| 1318 |
+
|
| 1319 |
+
row_max = -Float32.inf
|
| 1320 |
+
row_max_prev = -Float32.inf
|
| 1321 |
+
row_sum = 0.0
|
| 1322 |
+
|
| 1323 |
+
start_count, trip_count = FusedMask.get_trip_start_count_via_block_info(
|
| 1324 |
+
mma_block_coord,
|
| 1325 |
+
self.qk_mma_tiler,
|
| 1326 |
+
seqlen_q,
|
| 1327 |
+
seqlen_k,
|
| 1328 |
+
self.is_causal,
|
| 1329 |
+
self.is_local,
|
| 1330 |
+
window_size_left,
|
| 1331 |
+
window_size_right,
|
| 1332 |
+
)
|
| 1333 |
+
end_count = start_count + trip_count
|
| 1334 |
+
if cutlass.const_expr(self.use_semantic_trip_range):
|
| 1335 |
+
n_block_min_causal_local_mask, n_block_min_before_local_mask = (
|
| 1336 |
+
FusedMask.get_trip_mask_bounds_via_block_info(
|
| 1337 |
+
mma_block_coord,
|
| 1338 |
+
self.qk_mma_tiler,
|
| 1339 |
+
seqlen_q,
|
| 1340 |
+
seqlen_k,
|
| 1341 |
+
self.is_causal,
|
| 1342 |
+
self.is_local,
|
| 1343 |
+
window_size_left,
|
| 1344 |
+
window_size_right,
|
| 1345 |
+
)
|
| 1346 |
+
)
|
| 1347 |
+
cS_base = cute.make_identity_tensor(
|
| 1348 |
+
(self.qk_mma_tiler[0], self.qk_mma_tiler[1])
|
| 1349 |
+
)
|
| 1350 |
+
cS = cute.domain_offset((mma_block_coord[0] * self.qk_mma_tiler[0], 0), cS_base)
|
| 1351 |
+
tScS = qk_thr_mma.partition_C(cS)
|
| 1352 |
+
|
| 1353 |
+
for step in cutlass.range(start_count, end_count, 1, unroll=1):
|
| 1354 |
+
cS_iter = cute.domain_offset((0, step * self.qk_mma_tiler[1]), cS)
|
| 1355 |
+
tScS_iter = qk_thr_mma.partition_C(cS_iter)
|
| 1356 |
+
if cutlass.const_expr(self.use_semantic_trip_range):
|
| 1357 |
+
need_apply_mask = (
|
| 1358 |
+
step >= n_block_min_causal_local_mask
|
| 1359 |
+
or step < n_block_min_before_local_mask
|
| 1360 |
+
)
|
| 1361 |
+
else:
|
| 1362 |
+
# Residual path only needs seqlen masking on the last K tile.
|
| 1363 |
+
need_apply_mask = step == end_count - 1
|
| 1364 |
+
# Si -> Pi
|
| 1365 |
+
(
|
| 1366 |
+
row_max,
|
| 1367 |
+
row_sum,
|
| 1368 |
+
mma_s_consumer,
|
| 1369 |
+
p_mma_producer,
|
| 1370 |
+
s_corr_producer,
|
| 1371 |
+
) = self.softmax_step(
|
| 1372 |
+
(need_apply_mask, window_size_left, window_size_right),
|
| 1373 |
+
(
|
| 1374 |
+
row_max_prev,
|
| 1375 |
+
row_sum,
|
| 1376 |
+
seqlen_q,
|
| 1377 |
+
seqlen_k,
|
| 1378 |
+
scale_softmax_log2,
|
| 1379 |
+
),
|
| 1380 |
+
(tStS, tScS_iter),
|
| 1381 |
+
(mma_s_consumer, p_mma_producer, s_corr_producer),
|
| 1382 |
+
)
|
| 1383 |
+
row_max_prev = row_max
|
| 1384 |
+
sum_producer = self.store_sum_max(
|
| 1385 |
+
row_max,
|
| 1386 |
+
mLSE,
|
| 1387 |
+
row_sum,
|
| 1388 |
+
sSum,
|
| 1389 |
+
sum_producer,
|
| 1390 |
+
curr_block_coord,
|
| 1391 |
+
seqlen_q,
|
| 1392 |
+
cum_seqlen_q,
|
| 1393 |
+
cuseqlen_q,
|
| 1394 |
+
scale_softmax,
|
| 1395 |
+
)
|
| 1396 |
+
work_tile = tile_sched.advance_to_next_work()
|
| 1397 |
+
p_mma_producer.tail()
|
| 1398 |
+
s_corr_producer.tail()
|
| 1399 |
+
|
| 1400 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1401 |
+
# Correction
|
| 1402 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1403 |
+
if warp_idx >= self.correction_warp_ids[0] and warp_idx < self.mma_warp_id:
|
| 1404 |
+
cute.arch.warpgroup_reg_dealloc(self.num_regs_correction)
|
| 1405 |
+
|
| 1406 |
+
while work_tile.is_valid_tile:
|
| 1407 |
+
curr_block_coord = work_tile.tile_idx
|
| 1408 |
+
mma_block_coord = (
|
| 1409 |
+
curr_block_coord[0] // cute.size(qk_tiled_mma.thr_id.shape),
|
| 1410 |
+
curr_block_coord[1],
|
| 1411 |
+
curr_block_coord[2],
|
| 1412 |
+
)
|
| 1413 |
+
batch_coord = curr_block_coord[2][1]
|
| 1414 |
+
seqlen_q = mQ_qdl.shape[0]
|
| 1415 |
+
seqlen_k = (
|
| 1416 |
+
mK_kdl.shape[0] if cutlass.const_expr(mPageTable is None) else max_seqlen_k
|
| 1417 |
+
)
|
| 1418 |
+
continue_cond = False
|
| 1419 |
+
cuseqlen_q = Int32(0)
|
| 1420 |
+
if cutlass.const_expr(cum_seqlen_q is not None):
|
| 1421 |
+
cuseqlen_q = cum_seqlen_q[batch_coord]
|
| 1422 |
+
seqlen_q = cum_seqlen_q[batch_coord + 1] - cuseqlen_q
|
| 1423 |
+
continue_cond = not FmhaStaticTileScheduler.check_valid_work_for_seqlen_q(
|
| 1424 |
+
self.qk_mma_tiler[0],
|
| 1425 |
+
mma_block_coord[0],
|
| 1426 |
+
seqlen_q,
|
| 1427 |
+
)
|
| 1428 |
+
if cutlass.const_expr(mSeqUsedK is not None):
|
| 1429 |
+
seqlen_k = mSeqUsedK[batch_coord]
|
| 1430 |
+
|
| 1431 |
+
if not continue_cond:
|
| 1432 |
+
if cutlass.const_expr(cum_seqlen_k is not None):
|
| 1433 |
+
cuseqlen_k = cum_seqlen_k[batch_coord]
|
| 1434 |
+
seqlen_k = cum_seqlen_k[batch_coord + 1] - cuseqlen_k
|
| 1435 |
+
|
| 1436 |
+
mO_qdl_eff = mO_qdl
|
| 1437 |
+
if cutlass.const_expr(cum_seqlen_q is not None):
|
| 1438 |
+
block_offset_o = (
|
| 1439 |
+
cuseqlen_q,
|
| 1440 |
+
Int32(0),
|
| 1441 |
+
Int32(0),
|
| 1442 |
+
((Int32(0), Int32(0)), Int32(0)),
|
| 1443 |
+
)
|
| 1444 |
+
mO_qdl_eff = cute.domain_offset(
|
| 1445 |
+
cute.select(block_offset_o, mode=[0, 2, 3]), mO_qdl
|
| 1446 |
+
)
|
| 1447 |
+
|
| 1448 |
+
# (bM, bN, loopM, loopN, loopL)
|
| 1449 |
+
gO_qdl = cute.flat_divide(
|
| 1450 |
+
mO_qdl_eff, cute.select(self.pv_block_tiler, mode=[0, 1])
|
| 1451 |
+
)
|
| 1452 |
+
cO_qdl = cute.flat_divide(
|
| 1453 |
+
cute.make_identity_tensor(mO_qdl_eff.shape),
|
| 1454 |
+
cute.select(self.pv_block_tiler, mode=[0, 1]),
|
| 1455 |
+
)
|
| 1456 |
+
|
| 1457 |
+
_, seqlen_kv_loop_steps = FusedMask.get_trip_start_count_via_block_info(
|
| 1458 |
+
mma_block_coord,
|
| 1459 |
+
self.qk_mma_tiler,
|
| 1460 |
+
seqlen_q,
|
| 1461 |
+
seqlen_k,
|
| 1462 |
+
self.is_causal,
|
| 1463 |
+
self.is_local,
|
| 1464 |
+
window_size_left,
|
| 1465 |
+
window_size_right,
|
| 1466 |
+
)
|
| 1467 |
+
gO_staged = gO_qdl[None, None, curr_block_coord[0], None, curr_block_coord[2]]
|
| 1468 |
+
cO_staged = cO_qdl[None, None, curr_block_coord[0], None, curr_block_coord[2]]
|
| 1469 |
+
cS = cute.make_identity_tensor((self.qk_mma_tiler[0], self.qk_mma_tiler[1]))
|
| 1470 |
+
tScS = qk_thr_mma.partition_C(cS)
|
| 1471 |
+
|
| 1472 |
+
# Empty step as the first step is no need for correction
|
| 1473 |
+
stats_handle = s_corr_consumer.wait_and_advance()
|
| 1474 |
+
stats_handle.release()
|
| 1475 |
+
for step in cutlass.range(1, seqlen_kv_loop_steps, 1, unroll=1):
|
| 1476 |
+
# Oi-1 -> Oi
|
| 1477 |
+
mma_corr_consumer, s_corr_consumer = self.correction_rescale(
|
| 1478 |
+
scale_softmax_log2,
|
| 1479 |
+
(s_corr_consumer, tStS, tScS),
|
| 1480 |
+
(mma_corr_consumer, tOtO_staged, cO_staged),
|
| 1481 |
+
self.epi_tile,
|
| 1482 |
+
)
|
| 1483 |
+
# O_partial -> O_final
|
| 1484 |
+
mma_corr_consumer, sum_consumer = self.correction_epilog(
|
| 1485 |
+
(seqlen_q, scale_output),
|
| 1486 |
+
(sum_consumer, sSum),
|
| 1487 |
+
(mma_corr_consumer, gO_staged, cO_staged, tOtO_staged),
|
| 1488 |
+
self.epi_tile,
|
| 1489 |
+
)
|
| 1490 |
+
work_tile = tile_sched.advance_to_next_work()
|
| 1491 |
+
# NOTE: tmem.free() moved to kernel end to enable cluster-wide sync
|
| 1492 |
+
|
| 1493 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1494 |
+
# Scheduler Warp (only for CLC dynamic scheduler)
|
| 1495 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1496 |
+
if cutlass.const_expr(self.use_clc_scheduler):
|
| 1497 |
+
is_first_cta_in_cluster = cta_rank_in_cluster == 0
|
| 1498 |
+
|
| 1499 |
+
if warp_idx == self.sched_warp_id and is_first_cta_in_cluster:
|
| 1500 |
+
cute.arch.warpgroup_reg_dealloc(self.num_regs_other)
|
| 1501 |
+
while work_tile.is_valid_tile:
|
| 1502 |
+
tile_sched.prefetch_next_work()
|
| 1503 |
+
work_tile = tile_sched.advance_to_next_work()
|
| 1504 |
+
tile_sched.producer_tail()
|
| 1505 |
+
|
| 1506 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1507 |
+
# Empty warps reg dealloc
|
| 1508 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1509 |
+
if cutlass.const_expr(self.use_clc_scheduler):
|
| 1510 |
+
if warp_idx > self.load_warp_id:
|
| 1511 |
+
if not (warp_idx == self.sched_warp_id and is_first_cta_in_cluster):
|
| 1512 |
+
cute.arch.warpgroup_reg_dealloc(self.num_regs_other)
|
| 1513 |
+
else:
|
| 1514 |
+
if warp_idx > self.load_warp_id:
|
| 1515 |
+
cute.arch.warpgroup_reg_dealloc(self.num_regs_other)
|
| 1516 |
+
|
| 1517 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1518 |
+
# Cooperative TMEM Deallocation (2CTA)
|
| 1519 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1520 |
+
# All warps (including scheduler) have finished by this point.
|
| 1521 |
+
# Cluster-wide sync ensures both CTAs reach here before dealloc.
|
| 1522 |
+
cute.arch.cluster_arrive()
|
| 1523 |
+
cute.arch.cluster_wait()
|
| 1524 |
+
tmem.relinquish_alloc_permit()
|
| 1525 |
+
tmem.free(tmem_ptr)
|
| 1526 |
+
|
| 1527 |
+
return
|
| 1528 |
+
|
| 1529 |
+
@cute.jit
|
| 1530 |
+
def softmax_step(
|
| 1531 |
+
self,
|
| 1532 |
+
mask_args: Tuple,
|
| 1533 |
+
value_args: Tuple,
|
| 1534 |
+
tensor_args: Tuple,
|
| 1535 |
+
pipeline_args: Tuple,
|
| 1536 |
+
) -> Tuple[Float32, Float32, pipeline.PipelineConsumer, pipeline.PipelineProducer]:
|
| 1537 |
+
need_apply_mask, window_size_left, window_size_right = mask_args
|
| 1538 |
+
row_max, row_sum, seqlen_q, seqlen_k, scale_softmax_log2 = value_args
|
| 1539 |
+
tStS, tScS = tensor_args
|
| 1540 |
+
mma_s_consumer, p_mma_producer, s_corr_producer = pipeline_args
|
| 1541 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 1542 |
+
thread_idx = tidx % (self.threads_per_warp * len(self.softmax_warp_ids))
|
| 1543 |
+
s_handle = mma_s_consumer.wait_and_advance()
|
| 1544 |
+
tStS_slice = tStS[(None, None), 0, 0, s_handle.index]
|
| 1545 |
+
tScS_slice = tScS[(None, None), 0, 0]
|
| 1546 |
+
tmem_load_atom = cute.make_copy_atom(
|
| 1547 |
+
tcgen05.Ld32x32bOp(tcgen05.Repetition(32)), self.qk_acc_dtype
|
| 1548 |
+
)
|
| 1549 |
+
tmem_tiled_load = tcgen05.make_tmem_copy(tmem_load_atom, tStS_slice)
|
| 1550 |
+
thr_load = tmem_tiled_load.get_slice(thread_idx)
|
| 1551 |
+
tTMEM_LOADtS = thr_load.partition_S(tStS_slice)
|
| 1552 |
+
tTMEM_LOADcS = thr_load.partition_D(tScS_slice)
|
| 1553 |
+
tTMEM_LOADrS = cute.make_rmem_tensor(tTMEM_LOADcS.shape, self.qk_acc_dtype)
|
| 1554 |
+
cute.copy(tmem_tiled_load, tTMEM_LOADtS, tTMEM_LOADrS)
|
| 1555 |
+
|
| 1556 |
+
cute.arch.fence_view_async_tmem_load()
|
| 1557 |
+
s_handle.release()
|
| 1558 |
+
if need_apply_mask:
|
| 1559 |
+
FusedMask.apply_mask_via_causal_local(
|
| 1560 |
+
tTMEM_LOADrS,
|
| 1561 |
+
tTMEM_LOADcS,
|
| 1562 |
+
seqlen_q,
|
| 1563 |
+
seqlen_k,
|
| 1564 |
+
self.use_semantic_trip_range,
|
| 1565 |
+
self.is_causal,
|
| 1566 |
+
self.is_local,
|
| 1567 |
+
window_size_left,
|
| 1568 |
+
window_size_right,
|
| 1569 |
+
)
|
| 1570 |
+
old_row_max = row_max
|
| 1571 |
+
row_max = tTMEM_LOADrS.load().reduce(cute.ReductionOp.MAX, row_max, 0)
|
| 1572 |
+
row_max_safe = row_max
|
| 1573 |
+
if row_max == -cutlass.Float32.inf:
|
| 1574 |
+
row_max_safe = 0.0
|
| 1575 |
+
|
| 1576 |
+
stats_handle = s_corr_producer.acquire_and_advance()
|
| 1577 |
+
stats_layout = cute.composition(
|
| 1578 |
+
tStS_slice.layout, cute.make_layout((tStS_slice.shape[0], 2))
|
| 1579 |
+
)
|
| 1580 |
+
stats_c_layout = cute.composition(
|
| 1581 |
+
tScS_slice.layout, cute.make_layout((tScS_slice.shape[0], 2))
|
| 1582 |
+
)
|
| 1583 |
+
tOtStats = cute.make_tensor(tStS_slice.iterator + self.tilePlikeFP32, stats_layout)
|
| 1584 |
+
tOcStats = cute.make_tensor(tScS_slice.iterator, stats_c_layout)
|
| 1585 |
+
tmem_store_stats_atom = cute.make_copy_atom(
|
| 1586 |
+
tcgen05.copy.St32x32bOp(tcgen05.copy.Repetition(2)),
|
| 1587 |
+
self.qk_acc_dtype,
|
| 1588 |
+
)
|
| 1589 |
+
tiled_tmem_store_stats = tcgen05.make_tmem_copy(tmem_store_stats_atom, tOtStats)
|
| 1590 |
+
thr_tmem_store_stats = tiled_tmem_store_stats.get_slice(thread_idx)
|
| 1591 |
+
tTMEM_STOREcStats = thr_tmem_store_stats.partition_S(tOcStats)
|
| 1592 |
+
tTMEM_STORErStats = cute.make_rmem_tensor(tTMEM_STOREcStats.shape, self.qk_acc_dtype)
|
| 1593 |
+
tTMEM_STORErStats[0] = old_row_max
|
| 1594 |
+
tTMEM_STORErStats[1] = row_max_safe
|
| 1595 |
+
tTMEM_STOREtStats = thr_tmem_store_stats.partition_D(tOtStats)
|
| 1596 |
+
cute.copy(tiled_tmem_store_stats, tTMEM_STORErStats, tTMEM_STOREtStats)
|
| 1597 |
+
cute.arch.fence_view_async_tmem_store()
|
| 1598 |
+
stats_handle.commit()
|
| 1599 |
+
|
| 1600 |
+
scale = scale_softmax_log2
|
| 1601 |
+
minus_row_max_scale = (0.0 - row_max_safe) * scale
|
| 1602 |
+
# Acquire P write slot early — overlaps any pipeline stall with exp2 compute
|
| 1603 |
+
p_handle = p_mma_producer.acquire_and_advance()
|
| 1604 |
+
# Fragment-based FMA + exp2 + bf16 conversion
|
| 1605 |
+
# Trades SFU for FMA via polynomial emulation on a fraction of elements
|
| 1606 |
+
ex2_frg_tile = 32
|
| 1607 |
+
ex2_frg_cnt = cute.size(tTMEM_LOADrS) // ex2_frg_tile
|
| 1608 |
+
tTMEM_LOADrS_ex2 = cute.logical_divide(tTMEM_LOADrS, cute.make_layout(ex2_frg_tile))
|
| 1609 |
+
tTMEM_STORErP = cute.make_rmem_tensor(tTMEM_LOADrS.shape, self.q_dtype)
|
| 1610 |
+
tTMEM_STORErP_ex2 = cute.logical_divide(tTMEM_STORErP, cute.make_layout(ex2_frg_tile))
|
| 1611 |
+
for j in cutlass.range_constexpr(ex2_frg_cnt):
|
| 1612 |
+
for k in cutlass.range_constexpr(0, ex2_frg_tile, 2):
|
| 1613 |
+
tTMEM_LOADrS_ex2[k, j], tTMEM_LOADrS_ex2[k + 1, j] = cute.arch.fma_packed_f32x2(
|
| 1614 |
+
(tTMEM_LOADrS_ex2[k, j], tTMEM_LOADrS_ex2[k + 1, j]),
|
| 1615 |
+
(scale, scale),
|
| 1616 |
+
(minus_row_max_scale, minus_row_max_scale),
|
| 1617 |
+
)
|
| 1618 |
+
if cutlass.const_expr(self.ex2_emu_freq == 0):
|
| 1619 |
+
tTMEM_LOADrS_ex2[k, j] = cute.math.exp2(tTMEM_LOADrS_ex2[k, j], fastmath=True)
|
| 1620 |
+
tTMEM_LOADrS_ex2[k + 1, j] = cute.math.exp2(
|
| 1621 |
+
tTMEM_LOADrS_ex2[k + 1, j], fastmath=True
|
| 1622 |
+
)
|
| 1623 |
+
else:
|
| 1624 |
+
if cutlass.const_expr(
|
| 1625 |
+
k % self.ex2_emu_freq < self.ex2_emu_freq - self.ex2_emu_res
|
| 1626 |
+
or j >= ex2_frg_cnt - 1
|
| 1627 |
+
or j < self.ex2_emu_start_frg
|
| 1628 |
+
):
|
| 1629 |
+
tTMEM_LOADrS_ex2[k, j] = cute.math.exp2(
|
| 1630 |
+
tTMEM_LOADrS_ex2[k, j], fastmath=True
|
| 1631 |
+
)
|
| 1632 |
+
tTMEM_LOADrS_ex2[k + 1, j] = cute.math.exp2(
|
| 1633 |
+
tTMEM_LOADrS_ex2[k + 1, j], fastmath=True
|
| 1634 |
+
)
|
| 1635 |
+
else:
|
| 1636 |
+
tTMEM_LOADrS_ex2[k, j], tTMEM_LOADrS_ex2[k + 1, j] = ex2_emulation_2(
|
| 1637 |
+
tTMEM_LOADrS_ex2[k, j], tTMEM_LOADrS_ex2[k + 1, j]
|
| 1638 |
+
)
|
| 1639 |
+
tTMEM_STORErP_ex2[None, j].store(tTMEM_LOADrS_ex2[None, j].load().to(self.q_dtype))
|
| 1640 |
+
tmem_store_atom = cute.make_copy_atom(
|
| 1641 |
+
tcgen05.St32x32bOp(tcgen05.Repetition(32)), self.qk_acc_dtype
|
| 1642 |
+
)
|
| 1643 |
+
tilePlikeFP32 = tStS_slice.shape[1] // Float32.width * self.q_dtype.width
|
| 1644 |
+
tStS_P_layout = cute.composition(
|
| 1645 |
+
tStS_slice.layout, cute.make_layout((tStS_slice.shape[0], tilePlikeFP32))
|
| 1646 |
+
)
|
| 1647 |
+
tStS_P = cute.make_tensor(tStS_slice.iterator, tStS_P_layout)
|
| 1648 |
+
tScS_P_layout = cute.composition(
|
| 1649 |
+
tScS_slice.layout, cute.make_layout((tScS_slice.shape[0], tilePlikeFP32))
|
| 1650 |
+
)
|
| 1651 |
+
tScS_P = cute.make_tensor(tScS_slice.iterator, tScS_P_layout)
|
| 1652 |
+
tmem_tiled_store = tcgen05.make_tmem_copy(tmem_store_atom, tStS_P)
|
| 1653 |
+
thr_store = tmem_tiled_store.get_slice(thread_idx)
|
| 1654 |
+
tTMEM_STOREtP = thr_store.partition_D(tStS_P)
|
| 1655 |
+
tTMEM_STOREcS = thr_store.partition_S(tScS_P)
|
| 1656 |
+
tTMEM_STORErP_ = cute.make_tensor(
|
| 1657 |
+
cute.recast_ptr(tTMEM_STORErP.iterator, dtype=self.qk_acc_dtype),
|
| 1658 |
+
tTMEM_STOREcS.shape,
|
| 1659 |
+
)
|
| 1660 |
+
cute.copy(tmem_tiled_store, tTMEM_STORErP_, tTMEM_STOREtP)
|
| 1661 |
+
cute.arch.fence_view_async_tmem_store()
|
| 1662 |
+
|
| 1663 |
+
p_handle.commit()
|
| 1664 |
+
acc_scale_ = scale * (old_row_max - row_max_safe)
|
| 1665 |
+
acc_scale = cute.math.exp2(acc_scale_, fastmath=True) * 0.5
|
| 1666 |
+
# TODO: calc row sum with TensorSSA
|
| 1667 |
+
row_sum *= acc_scale
|
| 1668 |
+
local_row_sum_0 = (row_sum, row_sum)
|
| 1669 |
+
local_row_sum_1 = (0.0, 0.0)
|
| 1670 |
+
local_row_sum_2 = (0.0, 0.0)
|
| 1671 |
+
local_row_sum_3 = (0.0, 0.0)
|
| 1672 |
+
reduction_unroll = 4
|
| 1673 |
+
frg_tile = cute.size(tTMEM_LOADrS) // reduction_unroll
|
| 1674 |
+
tTMEM_LOADrS_frg = cute.logical_divide(tTMEM_LOADrS, cute.make_layout(frg_tile))
|
| 1675 |
+
for j in cutlass.range_constexpr(0, cute.size(tTMEM_LOADrS_frg, mode=[0]), 2):
|
| 1676 |
+
local_row_sum_0 = cute.arch.add_packed_f32x2(
|
| 1677 |
+
local_row_sum_0, (tTMEM_LOADrS_frg[j, 0], tTMEM_LOADrS_frg[j + 1, 0])
|
| 1678 |
+
)
|
| 1679 |
+
local_row_sum_1 = cute.arch.add_packed_f32x2(
|
| 1680 |
+
local_row_sum_1, (tTMEM_LOADrS_frg[j, 1], tTMEM_LOADrS_frg[j + 1, 1])
|
| 1681 |
+
)
|
| 1682 |
+
local_row_sum_2 = cute.arch.add_packed_f32x2(
|
| 1683 |
+
local_row_sum_2, (tTMEM_LOADrS_frg[j, 2], tTMEM_LOADrS_frg[j + 1, 2])
|
| 1684 |
+
)
|
| 1685 |
+
local_row_sum_3 = cute.arch.add_packed_f32x2(
|
| 1686 |
+
local_row_sum_3, (tTMEM_LOADrS_frg[j, 3], tTMEM_LOADrS_frg[j + 1, 3])
|
| 1687 |
+
)
|
| 1688 |
+
local_row_sum_0 = cute.arch.add_packed_f32x2(local_row_sum_0, local_row_sum_1)
|
| 1689 |
+
local_row_sum_2 = cute.arch.add_packed_f32x2(local_row_sum_2, local_row_sum_3)
|
| 1690 |
+
local_row_sum_0 = cute.arch.add_packed_f32x2(local_row_sum_0, local_row_sum_2)
|
| 1691 |
+
row_sum = local_row_sum_0[0] + local_row_sum_0[1]
|
| 1692 |
+
return row_max, row_sum, mma_s_consumer, p_mma_producer, s_corr_producer
|
| 1693 |
+
|
| 1694 |
+
@cute.jit
|
| 1695 |
+
def correction_rescale(
|
| 1696 |
+
self,
|
| 1697 |
+
scale_softmax_log2: Float32,
|
| 1698 |
+
stats_args: tuple,
|
| 1699 |
+
o_args: tuple,
|
| 1700 |
+
epi_tile: cute.Tile,
|
| 1701 |
+
) -> pipeline.PipelineConsumer:
|
| 1702 |
+
(s_corr_consumer, tStS, tScS) = stats_args
|
| 1703 |
+
(mma_o_consumer, tOtO_staged, cO_staged) = o_args
|
| 1704 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 1705 |
+
thread_idx = tidx % (self.threads_per_warp * len(self.softmax_warp_ids))
|
| 1706 |
+
|
| 1707 |
+
stats_handle = s_corr_consumer.wait_and_advance()
|
| 1708 |
+
tStS_slice = tStS[(None, None), 0, 0, stats_handle.index]
|
| 1709 |
+
tScS_slice = tScS[(None, None), 0, 0]
|
| 1710 |
+
stats_layout = cute.composition(
|
| 1711 |
+
tStS_slice.layout, cute.make_layout((tStS_slice.shape[0], 2))
|
| 1712 |
+
)
|
| 1713 |
+
stats_c_layout = cute.composition(
|
| 1714 |
+
tScS_slice.layout, cute.make_layout((tScS_slice.shape[0], 2))
|
| 1715 |
+
)
|
| 1716 |
+
tOtStats = cute.make_tensor(tStS_slice.iterator + self.tilePlikeFP32, stats_layout)
|
| 1717 |
+
tOcStats = cute.make_tensor(tScS_slice.iterator, stats_c_layout)
|
| 1718 |
+
tmem_load_stats_atom = cute.make_copy_atom(
|
| 1719 |
+
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(2)),
|
| 1720 |
+
self.qk_acc_dtype,
|
| 1721 |
+
)
|
| 1722 |
+
tiled_tmem_load_stats = tcgen05.make_tmem_copy(tmem_load_stats_atom, tOtStats)
|
| 1723 |
+
thr_tmem_load_stats = tiled_tmem_load_stats.get_slice(thread_idx)
|
| 1724 |
+
tTMEM_LOADtStats = thr_tmem_load_stats.partition_S(tOtStats)
|
| 1725 |
+
tTMEM_LOADcStats = thr_tmem_load_stats.partition_D(tOcStats)
|
| 1726 |
+
tTMEM_LOADrStats = cute.make_rmem_tensor(tTMEM_LOADcStats.shape, self.qk_acc_dtype)
|
| 1727 |
+
cute.copy(tiled_tmem_load_stats, tTMEM_LOADtStats, tTMEM_LOADrStats)
|
| 1728 |
+
|
| 1729 |
+
scale = scale_softmax_log2 * (tTMEM_LOADrStats[0] - tTMEM_LOADrStats[1])
|
| 1730 |
+
scale = cute.math.exp2(scale, fastmath=True)
|
| 1731 |
+
stats_handle.release()
|
| 1732 |
+
o_handle = mma_o_consumer.wait_and_advance()
|
| 1733 |
+
for iter in cutlass.range(self.iterations_pv, unroll_full=True):
|
| 1734 |
+
tOtO = tOtO_staged[(None, None), 0, 0, iter]
|
| 1735 |
+
cO = cO_staged[None, None, iter]
|
| 1736 |
+
tOtO_epi = cute.zipped_divide(tOtO, epi_tile)
|
| 1737 |
+
cO_epi = cute.zipped_divide(cO, epi_tile)
|
| 1738 |
+
tmem_load_atom = cute.make_copy_atom(
|
| 1739 |
+
tcgen05.Ld32x32bOp(tcgen05.Repetition(16)),
|
| 1740 |
+
self.pv_acc_dtype,
|
| 1741 |
+
)
|
| 1742 |
+
tmem_tiled_load = tcgen05.make_tmem_copy(tmem_load_atom, tOtO_epi)
|
| 1743 |
+
thr_load = tmem_tiled_load.get_slice(thread_idx)
|
| 1744 |
+
tmem_store_atom = cute.make_copy_atom(
|
| 1745 |
+
tcgen05.St32x32bOp(tcgen05.Repetition(16)),
|
| 1746 |
+
self.pv_acc_dtype,
|
| 1747 |
+
)
|
| 1748 |
+
tmem_store_atom = tcgen05.make_tmem_copy(tmem_store_atom, tOtO_epi)
|
| 1749 |
+
thr_store = tmem_store_atom.get_slice(thread_idx)
|
| 1750 |
+
tTMEM_LOADtO = thr_load.partition_S(tOtO_epi)
|
| 1751 |
+
tTMEM_LOADcO = thr_load.partition_D(cO_epi)
|
| 1752 |
+
tTMEM_STOREtO = thr_store.partition_D(tOtO_epi)
|
| 1753 |
+
tTMrO = cute.make_rmem_tensor_like(
|
| 1754 |
+
cute.append(
|
| 1755 |
+
cute.make_layout(tTMEM_LOADcO[None, 0, 0].shape),
|
| 1756 |
+
cute.make_layout(2, stride=cute.size(tTMEM_LOADcO[None, 0, 0].shape)),
|
| 1757 |
+
),
|
| 1758 |
+
self.pv_acc_dtype,
|
| 1759 |
+
)
|
| 1760 |
+
tTMEM_LOADtO_0 = tTMEM_LOADtO[None, 0, 0]
|
| 1761 |
+
cute.copy(tmem_tiled_load, tTMEM_LOADtO_0, tTMrO[None, 0])
|
| 1762 |
+
iter_num = cute.size(tTMEM_LOADtO, mode=[1])
|
| 1763 |
+
for i in cutlass.range(1, iter_num, unroll_full=True):
|
| 1764 |
+
tTMEM_LOADtO_i = tTMEM_LOADtO[None, i, 0]
|
| 1765 |
+
cute.copy(tmem_tiled_load, tTMEM_LOADtO_i, tTMrO[None, i % 2])
|
| 1766 |
+
for j in cutlass.range(0, cute.size(tTMrO, mode=[0]), 2, unroll_full=True):
|
| 1767 |
+
tTMrO[j, (i - 1) % 2], tTMrO[j + 1, (i - 1) % 2] = cute.arch.mul_packed_f32x2(
|
| 1768 |
+
(tTMrO[j, (i - 1) % 2], tTMrO[j + 1, (i - 1) % 2]),
|
| 1769 |
+
(scale, scale),
|
| 1770 |
+
)
|
| 1771 |
+
tTMEM_STOREtO_prev_i = tTMEM_STOREtO[None, i - 1, 0]
|
| 1772 |
+
cute.copy(tmem_store_atom, tTMrO[None, (i - 1) % 2], tTMEM_STOREtO_prev_i)
|
| 1773 |
+
|
| 1774 |
+
for j in cutlass.range(0, cute.size(tTMrO, mode=[0]), 2, unroll_full=True):
|
| 1775 |
+
tTMrO[j, (iter_num - 1) % 2], tTMrO[j + 1, (iter_num - 1) % 2] = (
|
| 1776 |
+
cute.arch.mul_packed_f32x2(
|
| 1777 |
+
(
|
| 1778 |
+
tTMrO[j, (iter_num - 1) % 2],
|
| 1779 |
+
tTMrO[j + 1, (iter_num - 1) % 2],
|
| 1780 |
+
),
|
| 1781 |
+
(scale, scale),
|
| 1782 |
+
)
|
| 1783 |
+
)
|
| 1784 |
+
cute.copy(
|
| 1785 |
+
tmem_store_atom,
|
| 1786 |
+
tTMrO[None, (iter_num - 1) % 2],
|
| 1787 |
+
tTMEM_STOREtO[None, iter_num - 1, 0],
|
| 1788 |
+
)
|
| 1789 |
+
cute.arch.fence_view_async_tmem_store()
|
| 1790 |
+
o_handle.release()
|
| 1791 |
+
return mma_o_consumer, s_corr_consumer
|
| 1792 |
+
|
| 1793 |
+
@cute.jit
|
| 1794 |
+
def correction_epilog(
|
| 1795 |
+
self,
|
| 1796 |
+
value_args: Tuple,
|
| 1797 |
+
sum_args: Tuple,
|
| 1798 |
+
o_args: Tuple,
|
| 1799 |
+
epi_tile: cute.Tile,
|
| 1800 |
+
) -> Tuple[pipeline.PipelineConsumer, pipeline.PipelineProducer]:
|
| 1801 |
+
(seqlen_q, scale_output) = value_args
|
| 1802 |
+
(sum_consumer, sSum) = sum_args
|
| 1803 |
+
(mma_o_consumer, gO_staged, cO_staged, tOtO_staged) = o_args
|
| 1804 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 1805 |
+
thread_idx = tidx % (self.threads_per_warp * len(self.softmax_warp_ids))
|
| 1806 |
+
sum_handle = sum_consumer.wait_and_advance()
|
| 1807 |
+
row_sum = sSum[thread_idx]
|
| 1808 |
+
cute.arch.fence_view_async_shared()
|
| 1809 |
+
sum_handle.release()
|
| 1810 |
+
scale = scale_output / row_sum
|
| 1811 |
+
o_handle = mma_o_consumer.wait_and_advance()
|
| 1812 |
+
for iter in cutlass.range(self.iterations_pv):
|
| 1813 |
+
gO = gO_staged[None, None, iter]
|
| 1814 |
+
cO = cO_staged[None, None, iter]
|
| 1815 |
+
tOtO = tOtO_staged[(None, None), 0, 0, iter]
|
| 1816 |
+
tOtO_epi = cute.zipped_divide(tOtO, epi_tile)
|
| 1817 |
+
cO_epi = cute.zipped_divide(cO, epi_tile)
|
| 1818 |
+
gO_epi = cute.zipped_divide(gO, epi_tile)
|
| 1819 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 1820 |
+
thread_idx = tidx % (self.threads_per_warp * len(self.softmax_warp_ids))
|
| 1821 |
+
tmem_copy_atom = cute.make_copy_atom(
|
| 1822 |
+
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(32)), self.pv_acc_dtype
|
| 1823 |
+
)
|
| 1824 |
+
tiled_tmem_load = tcgen05.make_tmem_copy(tmem_copy_atom, tOtO_epi)
|
| 1825 |
+
thr_tmem_load = tiled_tmem_load.get_slice(thread_idx)
|
| 1826 |
+
tTMEM_LOADtO = thr_tmem_load.partition_S(tOtO_epi)
|
| 1827 |
+
tTMEM_LOADgO = thr_tmem_load.partition_D(gO_epi)
|
| 1828 |
+
tTMEM_LOADcO = thr_tmem_load.partition_D(cO_epi)
|
| 1829 |
+
for i in cutlass.range(cute.size(tTMEM_LOADtO, mode=[1]), unroll_full=True):
|
| 1830 |
+
tTMEM_LOADtO_i = tTMEM_LOADtO[None, i, 0]
|
| 1831 |
+
tTMEM_LOADgO_i = tTMEM_LOADgO[None, i, 0]
|
| 1832 |
+
tTMEM_LOADcO_i = tTMEM_LOADcO[None, i, 0]
|
| 1833 |
+
tTMrO = cute.make_rmem_tensor(tTMEM_LOADcO[None, 0, i].shape, self.pv_acc_dtype)
|
| 1834 |
+
cute.copy(tiled_tmem_load, tTMEM_LOADtO_i, tTMrO)
|
| 1835 |
+
for j in cutlass.range(0, cute.size(tTMrO), 2, unroll_full=True):
|
| 1836 |
+
tTMrO[j], tTMrO[j + 1] = cute.arch.mul_packed_f32x2(
|
| 1837 |
+
(tTMrO[j], tTMrO[j + 1]),
|
| 1838 |
+
(scale, scale),
|
| 1839 |
+
)
|
| 1840 |
+
tSMrO = cute.make_rmem_tensor(tTMrO.shape, self.o_dtype)
|
| 1841 |
+
o_vec = tTMrO.load()
|
| 1842 |
+
tSMrO.store(o_vec.to(self.o_dtype))
|
| 1843 |
+
if cute.elem_less(tTMEM_LOADcO_i[0][0], seqlen_q):
|
| 1844 |
+
cute.autovec_copy(tSMrO, tTMEM_LOADgO_i)
|
| 1845 |
+
o_handle.release()
|
| 1846 |
+
return mma_o_consumer, sum_consumer
|
| 1847 |
+
|
| 1848 |
+
@cute.jit
|
| 1849 |
+
def store_sum_max(
|
| 1850 |
+
self,
|
| 1851 |
+
row_max,
|
| 1852 |
+
mLSE,
|
| 1853 |
+
row_sum,
|
| 1854 |
+
sSum,
|
| 1855 |
+
sum_producer,
|
| 1856 |
+
current_block_coord,
|
| 1857 |
+
seqlen_q,
|
| 1858 |
+
cum_seqlen_q,
|
| 1859 |
+
cuseqlen_q,
|
| 1860 |
+
scale_softmax,
|
| 1861 |
+
):
|
| 1862 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 1863 |
+
thread_idx = tidx % (self.threads_per_warp * len(self.softmax_warp_ids))
|
| 1864 |
+
sum_handle = sum_producer.acquire_and_advance()
|
| 1865 |
+
sSum[thread_idx] = row_sum
|
| 1866 |
+
cute.arch.fence_view_async_shared()
|
| 1867 |
+
sum_handle.commit()
|
| 1868 |
+
|
| 1869 |
+
if cutlass.const_expr(mLSE is not None):
|
| 1870 |
+
q_idx = current_block_coord[0] * self.cta_tiler[0] + tidx
|
| 1871 |
+
hb_idx = (
|
| 1872 |
+
(current_block_coord[2][0], Int32(0))
|
| 1873 |
+
if cutlass.const_expr(cum_seqlen_q is not None)
|
| 1874 |
+
else current_block_coord[2]
|
| 1875 |
+
)
|
| 1876 |
+
lse_value = scale_softmax * row_max + cute.math.log(row_sum, fastmath=True)
|
| 1877 |
+
if cute.elem_less(q_idx, seqlen_q):
|
| 1878 |
+
global_q_idx = (
|
| 1879 |
+
q_idx + cuseqlen_q if cutlass.const_expr(cum_seqlen_q is not None) else q_idx
|
| 1880 |
+
)
|
| 1881 |
+
mLSE[global_q_idx, hb_idx] = lse_value
|
| 1882 |
+
return sum_producer
|
build/torch-cuda/flashrt_fa4/cute/softmax.py
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import operator
|
| 5 |
+
from typing import Tuple
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.cute as cute
|
| 10 |
+
from cutlass import Float32, Boolean
|
| 11 |
+
|
| 12 |
+
from fa4_cute_runtime.quack import layout_utils
|
| 13 |
+
import fa4_cute_runtime.flashrt_fa4.cute.utils as utils
|
| 14 |
+
from fa4_cute_runtime.quack.cute_dsl_utils import ParamsBase
|
| 15 |
+
from fa4_cute_runtime.flashrt_fa4.cute.seqlen_info import SeqlenInfoQK
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class Softmax(ParamsBase):
|
| 20 |
+
scale_log2: Float32
|
| 21 |
+
num_rows: cutlass.Constexpr[int]
|
| 22 |
+
row_max: cute.Tensor
|
| 23 |
+
row_sum: cute.Tensor
|
| 24 |
+
arch: cutlass.Constexpr[int] = 80
|
| 25 |
+
softmax_scale: Float32 | None = None
|
| 26 |
+
|
| 27 |
+
@staticmethod
|
| 28 |
+
def create(
|
| 29 |
+
scale_log2: Float32,
|
| 30 |
+
num_rows: cutlass.Constexpr[int],
|
| 31 |
+
arch: cutlass.Constexpr[int] = 80,
|
| 32 |
+
softmax_scale: Float32 | None = None,
|
| 33 |
+
):
|
| 34 |
+
row_max = cute.make_rmem_tensor(num_rows, Float32)
|
| 35 |
+
row_sum = cute.make_rmem_tensor(num_rows, Float32)
|
| 36 |
+
return Softmax(scale_log2, num_rows, row_max, row_sum, arch, softmax_scale)
|
| 37 |
+
|
| 38 |
+
def reset(self) -> None:
|
| 39 |
+
self.row_max.fill(-Float32.inf)
|
| 40 |
+
self.row_sum.fill(0.0)
|
| 41 |
+
|
| 42 |
+
def _compute_row_max(
|
| 43 |
+
self, acc_S_row: cute.TensorSSA, init_val: float | Float32 | None = None
|
| 44 |
+
) -> Float32:
|
| 45 |
+
return utils.fmax_reduce(acc_S_row, init_val, arch=self.arch)
|
| 46 |
+
|
| 47 |
+
def _compute_row_sum(
|
| 48 |
+
self, acc_S_row_exp: cute.TensorSSA, init_val: float | Float32 | None = None
|
| 49 |
+
) -> Float32:
|
| 50 |
+
return utils.fadd_reduce(acc_S_row_exp, init_val, arch=self.arch)
|
| 51 |
+
|
| 52 |
+
@cute.jit
|
| 53 |
+
def online_softmax(
|
| 54 |
+
self,
|
| 55 |
+
acc_S: cute.Tensor,
|
| 56 |
+
is_first: cutlass.Constexpr[bool] = False,
|
| 57 |
+
check_inf: cutlass.Constexpr[bool] = True,
|
| 58 |
+
) -> cute.Tensor:
|
| 59 |
+
"""Apply online softmax and return the row_scale to rescale O.
|
| 60 |
+
|
| 61 |
+
:param acc_S: acc_S tensor
|
| 62 |
+
:type acc_S: cute.Tensor
|
| 63 |
+
:param is_first: is first n_block
|
| 64 |
+
:type is_first: cutlass.Constexpr
|
| 65 |
+
"""
|
| 66 |
+
# Change acc_S to M,N layout view.
|
| 67 |
+
acc_S_mn = layout_utils.reshape_acc_to_mn(acc_S)
|
| 68 |
+
row_scale = cute.make_fragment_like(self.row_max, Float32)
|
| 69 |
+
|
| 70 |
+
row_max = self.row_max
|
| 71 |
+
row_sum = self.row_sum
|
| 72 |
+
scale_log2 = self.scale_log2
|
| 73 |
+
arch = self.arch
|
| 74 |
+
|
| 75 |
+
# Each iteration processes one row of acc_S
|
| 76 |
+
for r in cutlass.range(cute.size(row_max), unroll_full=True):
|
| 77 |
+
acc_S_row = acc_S_mn[r, None].load() # (n_block_size)
|
| 78 |
+
|
| 79 |
+
row_max_cur = utils.fmax_reduce(
|
| 80 |
+
acc_S_row,
|
| 81 |
+
init_val=row_max[r] if cutlass.const_expr(not is_first) else None,
|
| 82 |
+
arch=arch,
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
row_max_cur = cute.arch.warp_reduction_max(row_max_cur, threads_in_group=4)
|
| 86 |
+
# Update row_max before changing row_max_cur to safe value for -inf
|
| 87 |
+
row_max_prev = row_max[r]
|
| 88 |
+
row_max[r] = row_max_cur
|
| 89 |
+
|
| 90 |
+
if cutlass.const_expr(check_inf):
|
| 91 |
+
row_max_cur = 0.0 if row_max_cur == -Float32.inf else row_max_cur
|
| 92 |
+
|
| 93 |
+
if cutlass.const_expr(is_first):
|
| 94 |
+
row_max_cur_scaled = row_max_cur * scale_log2
|
| 95 |
+
acc_S_row_exp = cute.math.exp2(
|
| 96 |
+
acc_S_row * scale_log2 - row_max_cur_scaled, fastmath=True
|
| 97 |
+
)
|
| 98 |
+
acc_S_row_sum = utils.fadd_reduce(acc_S_row_exp, init_val=None, arch=arch)
|
| 99 |
+
row_scale[r] = 1.0
|
| 100 |
+
else:
|
| 101 |
+
row_max_cur_scaled = row_max_cur * scale_log2
|
| 102 |
+
acc_S_row_exp = cute.math.exp2(
|
| 103 |
+
acc_S_row * scale_log2 - row_max_cur_scaled, fastmath=True
|
| 104 |
+
)
|
| 105 |
+
# row_scale[r] = cute.math.exp2(row_max_prev * self.scale_log2 - row_max_cur_scaled)
|
| 106 |
+
row_scale[r] = cute.math.exp2(
|
| 107 |
+
(row_max_prev - row_max_cur) * scale_log2, fastmath=True
|
| 108 |
+
)
|
| 109 |
+
acc_S_row_sum = utils.fadd_reduce(
|
| 110 |
+
acc_S_row_exp, init_val=row_sum[r] * row_scale[r], arch=arch
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
row_sum[r] = acc_S_row_sum
|
| 114 |
+
acc_S_mn[r, None].store(acc_S_row_exp)
|
| 115 |
+
|
| 116 |
+
return row_scale
|
| 117 |
+
|
| 118 |
+
@cute.jit
|
| 119 |
+
def finalize(
|
| 120 |
+
self, final_scale: Float32 = 1.0, sink_val: Float32 | cute.Tensor | None = None
|
| 121 |
+
) -> cute.Tensor:
|
| 122 |
+
"""Finalize the online softmax by computing the scale and logsumexp."""
|
| 123 |
+
if cutlass.const_expr(sink_val is not None and isinstance(sink_val, cute.Tensor)):
|
| 124 |
+
assert cute.size(sink_val) == cute.size(self.row_sum)
|
| 125 |
+
row_sum = self.row_sum
|
| 126 |
+
row_max = self.row_max
|
| 127 |
+
scale_log2 = self.scale_log2
|
| 128 |
+
|
| 129 |
+
# quad reduction for row_sum as we didn't do it during each iteration of online softmax
|
| 130 |
+
row_sum.store(utils.warp_reduce(row_sum.load(), operator.add, width=4))
|
| 131 |
+
row_scale = cute.make_fragment_like(row_max, Float32)
|
| 132 |
+
|
| 133 |
+
for r in cutlass.range(cute.size(row_sum), unroll_full=True):
|
| 134 |
+
if cutlass.const_expr(sink_val is not None):
|
| 135 |
+
sink_val_cur = sink_val if not isinstance(sink_val, cute.Tensor) else sink_val[r]
|
| 136 |
+
LOG2_E = math.log2(math.e)
|
| 137 |
+
row_sum[r] += cute.math.exp2(
|
| 138 |
+
sink_val_cur * LOG2_E - row_max[r] * scale_log2, fastmath=True
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
# if row_sum is zero or nan, set acc_O_mn_row to 1.0
|
| 142 |
+
acc_O_mn_row_is_zero_or_nan = row_sum[r] == 0.0 or row_sum[r] != row_sum[r]
|
| 143 |
+
row_scale[r] = (
|
| 144 |
+
cute.arch.rcp_approx(row_sum[r] if not acc_O_mn_row_is_zero_or_nan else 1.0)
|
| 145 |
+
) * final_scale
|
| 146 |
+
row_sum_cur = row_sum[r]
|
| 147 |
+
LN2 = math.log(2.0)
|
| 148 |
+
row_sum[r] = (
|
| 149 |
+
(row_max[r] * scale_log2 + cute.math.log2(row_sum_cur, fastmath=True)) * LN2
|
| 150 |
+
if not acc_O_mn_row_is_zero_or_nan
|
| 151 |
+
else -Float32.inf
|
| 152 |
+
)
|
| 153 |
+
return row_scale
|
| 154 |
+
|
| 155 |
+
@cute.jit
|
| 156 |
+
def rescale_O(self, acc_O: cute.Tensor, row_scale: cute.Tensor) -> None:
|
| 157 |
+
"""Scale each row of acc_O by the given scale tensor.
|
| 158 |
+
:param acc_O: input tensor
|
| 159 |
+
:type acc_O: cute.Tensor
|
| 160 |
+
:param row_scale: row_scale tensor
|
| 161 |
+
:type row_scale: cute.Tensor
|
| 162 |
+
"""
|
| 163 |
+
acc_O_mn = layout_utils.reshape_acc_to_mn(acc_O)
|
| 164 |
+
assert cute.size(row_scale) == cute.size(acc_O_mn, mode=[0])
|
| 165 |
+
for r in cutlass.range(cute.size(row_scale), unroll_full=True):
|
| 166 |
+
acc_O_mn[r, None].store(acc_O_mn[r, None].load() * row_scale[r])
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
@dataclass
|
| 170 |
+
class SoftmaxSm100(Softmax):
|
| 171 |
+
rescale_threshold: cutlass.Constexpr[float] = 0.0
|
| 172 |
+
max_offset: cutlass.Constexpr[int] = 0
|
| 173 |
+
|
| 174 |
+
@staticmethod
|
| 175 |
+
def create(
|
| 176 |
+
scale_log2: Float32,
|
| 177 |
+
rescale_threshold: cutlass.Constexpr[float] = 0.0,
|
| 178 |
+
softmax_scale: Float32 | None = None,
|
| 179 |
+
max_offset: cutlass.Constexpr[int] = 0,
|
| 180 |
+
):
|
| 181 |
+
num_rows = 1
|
| 182 |
+
arch = 100
|
| 183 |
+
row_max = cute.make_rmem_tensor(num_rows, Float32)
|
| 184 |
+
row_sum = cute.make_rmem_tensor(num_rows, Float32)
|
| 185 |
+
return SoftmaxSm100(
|
| 186 |
+
scale_log2,
|
| 187 |
+
num_rows,
|
| 188 |
+
row_max,
|
| 189 |
+
row_sum,
|
| 190 |
+
arch,
|
| 191 |
+
softmax_scale,
|
| 192 |
+
rescale_threshold=rescale_threshold,
|
| 193 |
+
max_offset=max_offset,
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
@cute.jit
|
| 197 |
+
def compute_row_max_local(self, acc_S_row: cute.TensorSSA, is_first: Boolean) -> Float32:
|
| 198 |
+
if cutlass.const_expr(is_first):
|
| 199 |
+
row_max_new = self._compute_row_max(acc_S_row)
|
| 200 |
+
else:
|
| 201 |
+
row_max_old = self.row_max[0]
|
| 202 |
+
row_max_new = self._compute_row_max(acc_S_row, init_val=row_max_old)
|
| 203 |
+
return row_max_new
|
| 204 |
+
|
| 205 |
+
@cute.jit
|
| 206 |
+
def update_row_max_from_local(
|
| 207 |
+
self,
|
| 208 |
+
row_max_new: Float32,
|
| 209 |
+
is_first: Boolean,
|
| 210 |
+
) -> Tuple[Float32, Float32]:
|
| 211 |
+
if cutlass.const_expr(is_first):
|
| 212 |
+
row_max_safe = row_max_new if row_max_new != -cutlass.Float32.inf else 0.0
|
| 213 |
+
acc_scale = 0.0
|
| 214 |
+
else:
|
| 215 |
+
row_max_old = self.row_max[0]
|
| 216 |
+
row_max_safe = row_max_new if row_max_new != -cutlass.Float32.inf else 0.0
|
| 217 |
+
acc_scale_ = (row_max_old - row_max_safe) * self.scale_log2
|
| 218 |
+
acc_scale = cute.math.exp2(acc_scale_)
|
| 219 |
+
if cutlass.const_expr(self.rescale_threshold > 0.0):
|
| 220 |
+
if acc_scale_ >= -self.rescale_threshold:
|
| 221 |
+
row_max_new = row_max_old
|
| 222 |
+
row_max_safe = row_max_old
|
| 223 |
+
acc_scale = 1.0
|
| 224 |
+
self.row_max[0] = row_max_new
|
| 225 |
+
return row_max_safe, acc_scale
|
| 226 |
+
|
| 227 |
+
@cute.jit
|
| 228 |
+
def update_row_max(self, acc_S_row: cute.TensorSSA, is_first: int) -> Tuple[Float32, Float32]:
|
| 229 |
+
if cutlass.const_expr(is_first):
|
| 230 |
+
row_max_new = self._compute_row_max(acc_S_row)
|
| 231 |
+
row_max_safe = row_max_new if row_max_new != -cutlass.Float32.inf else 0.0
|
| 232 |
+
acc_scale = 0.0
|
| 233 |
+
else:
|
| 234 |
+
row_max_old = self.row_max[0]
|
| 235 |
+
row_max_new = self._compute_row_max(acc_S_row, init_val=row_max_old)
|
| 236 |
+
row_max_safe = row_max_new if row_max_new != -cutlass.Float32.inf else 0.0
|
| 237 |
+
acc_scale_ = (row_max_old - row_max_safe) * self.scale_log2
|
| 238 |
+
acc_scale = cute.math.exp2(acc_scale_, fastmath=True)
|
| 239 |
+
if cutlass.const_expr(self.rescale_threshold > 0.0):
|
| 240 |
+
if acc_scale_ >= -self.rescale_threshold:
|
| 241 |
+
row_max_new = row_max_old
|
| 242 |
+
row_max_safe = row_max_old
|
| 243 |
+
acc_scale = 1.0
|
| 244 |
+
self.row_max[0] = row_max_new
|
| 245 |
+
return row_max_safe, acc_scale
|
| 246 |
+
|
| 247 |
+
def update_row_sum(
|
| 248 |
+
self, acc_S_row_exp: cute.TensorSSA, row_scale: Float32, is_first: int = False
|
| 249 |
+
) -> None:
|
| 250 |
+
init_val = self.row_sum[0] * row_scale if cutlass.const_expr(not is_first) else None
|
| 251 |
+
# self.row_sum[0] = self._compute_row_sum(acc_S_row_exp, init_val=self.row_sum[0] * row_scale)
|
| 252 |
+
self.row_sum[0] = self._compute_row_sum(acc_S_row_exp, init_val=init_val)
|
| 253 |
+
# tmp = self._compute_row_sum(acc_S_row_exp)
|
| 254 |
+
# self.row_sum[0] = self.row_sum[0] * row_scale + tmp
|
| 255 |
+
|
| 256 |
+
@cute.jit
|
| 257 |
+
def scale_subtract_rowmax(
|
| 258 |
+
self,
|
| 259 |
+
acc_S_row: cute.Tensor,
|
| 260 |
+
row_max: Float32,
|
| 261 |
+
):
|
| 262 |
+
assert cute.size(acc_S_row.shape) % 2 == 0, "acc_S_row must have an even number of elements"
|
| 263 |
+
row_max_scaled = row_max * self.scale_log2
|
| 264 |
+
max_offset = Float32(self.max_offset)
|
| 265 |
+
bias = max_offset - row_max_scaled
|
| 266 |
+
for i in cutlass.range(0, cute.size(acc_S_row.shape), 2, unroll_full=True):
|
| 267 |
+
acc_S_row[i], acc_S_row[i + 1] = cute.arch.fma_packed_f32x2(
|
| 268 |
+
(acc_S_row[i], acc_S_row[i + 1]),
|
| 269 |
+
(self.scale_log2, self.scale_log2),
|
| 270 |
+
(bias, bias),
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
@cute.jit
|
| 274 |
+
def apply_exp2_convert(
|
| 275 |
+
self,
|
| 276 |
+
acc_S_row: cute.Tensor,
|
| 277 |
+
acc_S_row_converted: cute.Tensor,
|
| 278 |
+
ex2_emu_freq: cutlass.Constexpr[int] = 0,
|
| 279 |
+
ex2_emu_res: cutlass.Constexpr[int] = 4,
|
| 280 |
+
ex2_emu_start_frg: cutlass.Constexpr[int] = 0,
|
| 281 |
+
):
|
| 282 |
+
assert cute.size(acc_S_row.shape) % 2 == 0, "acc_S_row must have an even number of elements"
|
| 283 |
+
frg_tile = 32
|
| 284 |
+
assert frg_tile % 2 == 0
|
| 285 |
+
frg_cnt = cute.size(acc_S_row) // frg_tile
|
| 286 |
+
assert cute.size(acc_S_row) % frg_tile == 0
|
| 287 |
+
acc_S_row_frg = cute.logical_divide(acc_S_row, cute.make_layout(frg_tile))
|
| 288 |
+
acc_S_row_converted_frg = cute.logical_divide(
|
| 289 |
+
acc_S_row_converted, cute.make_layout(frg_tile)
|
| 290 |
+
)
|
| 291 |
+
for j in cutlass.range_constexpr(frg_cnt):
|
| 292 |
+
for k in cutlass.range_constexpr(0, cute.size(acc_S_row_frg, mode=[0]), 2):
|
| 293 |
+
# acc_S_row_frg[k, j] = cute.math.exp2(acc_S_row_frg[k, j], fastmath=True)
|
| 294 |
+
# acc_S_row_frg[k + 1, j] = cute.math.exp2(acc_S_row_frg[k + 1, j], fastmath=True)
|
| 295 |
+
if cutlass.const_expr(ex2_emu_freq == 0):
|
| 296 |
+
acc_S_row_frg[k, j] = cute.math.exp2(acc_S_row_frg[k, j], fastmath=True)
|
| 297 |
+
acc_S_row_frg[k + 1, j] = cute.math.exp2(acc_S_row_frg[k + 1, j], fastmath=True)
|
| 298 |
+
else:
|
| 299 |
+
if cutlass.const_expr(
|
| 300 |
+
k % ex2_emu_freq < ex2_emu_freq - ex2_emu_res
|
| 301 |
+
or j >= frg_cnt - 1
|
| 302 |
+
or j < ex2_emu_start_frg
|
| 303 |
+
):
|
| 304 |
+
acc_S_row_frg[k, j] = cute.math.exp2(acc_S_row_frg[k, j], fastmath=True)
|
| 305 |
+
acc_S_row_frg[k + 1, j] = cute.math.exp2(
|
| 306 |
+
acc_S_row_frg[k + 1, j], fastmath=True
|
| 307 |
+
)
|
| 308 |
+
else:
|
| 309 |
+
# acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j] = utils.e2e_asm2(acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j])
|
| 310 |
+
acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j] = utils.ex2_emulation_2(
|
| 311 |
+
acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j]
|
| 312 |
+
)
|
| 313 |
+
acc_S_row_converted_frg[None, j].store(
|
| 314 |
+
acc_S_row_frg[None, j].load().to(acc_S_row_converted.element_type)
|
| 315 |
+
)
|
| 316 |
+
|
| 317 |
+
@cute.jit
|
| 318 |
+
def scale_apply_exp2_convert(
|
| 319 |
+
self,
|
| 320 |
+
acc_S_row: cute.Tensor,
|
| 321 |
+
row_max: Float32,
|
| 322 |
+
acc_S_row_converted: cute.Tensor,
|
| 323 |
+
):
|
| 324 |
+
assert cute.size(acc_S_row.shape) % 2 == 0, "acc_S_row must have an even number of elements"
|
| 325 |
+
minus_row_max_scaled = -row_max * self.scale_log2
|
| 326 |
+
for i in cutlass.range_constexpr(0, cute.size(acc_S_row.shape), 2):
|
| 327 |
+
acc_S_row[i], acc_S_row[i + 1] = cute.arch.fma_packed_f32x2(
|
| 328 |
+
(acc_S_row[i], acc_S_row[i + 1]),
|
| 329 |
+
(self.scale_log2, self.scale_log2),
|
| 330 |
+
(minus_row_max_scaled, minus_row_max_scaled),
|
| 331 |
+
)
|
| 332 |
+
|
| 333 |
+
# for i in cutlass.range_constexpr(0, cute.size(acc_S_row.shape), 2):
|
| 334 |
+
# acc_S_row[i], acc_S_row[i + 1] = cute.arch.fma_packed_f32x2(
|
| 335 |
+
# (acc_S_row[i], acc_S_row[i + 1]),
|
| 336 |
+
# (self.scale_log2, self.scale_log2),
|
| 337 |
+
# (minus_row_max_scaled, minus_row_max_scaled),
|
| 338 |
+
# )
|
| 339 |
+
# acc_S_row[i] = cute.math.exp2(acc_S_row[i], fastmath=True)
|
| 340 |
+
# acc_S_row[i + 1] = cute.math.exp2(acc_S_row[i + 1], fastmath=True)
|
| 341 |
+
|
| 342 |
+
frg_tile = 32
|
| 343 |
+
assert frg_tile % 2 == 0
|
| 344 |
+
frg_cnt = cute.size(acc_S_row) // frg_tile
|
| 345 |
+
assert cute.size(acc_S_row) % frg_tile == 0
|
| 346 |
+
acc_S_row_frg = cute.logical_divide(acc_S_row, cute.make_layout(frg_tile))
|
| 347 |
+
acc_S_row_converted_frg = cute.logical_divide(
|
| 348 |
+
acc_S_row_converted, cute.make_layout(frg_tile)
|
| 349 |
+
)
|
| 350 |
+
for j in cutlass.range_constexpr(frg_cnt):
|
| 351 |
+
for k in cutlass.range_constexpr(0, cute.size(acc_S_row_frg, mode=[0]), 2):
|
| 352 |
+
# acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j] = (
|
| 353 |
+
# cute.arch.fma_packed_f32x2(
|
| 354 |
+
# (acc_S_row_frg[k, j], acc_S_row_frg[k + 1, j]),
|
| 355 |
+
# (self.scale_log2, self.scale_log2),
|
| 356 |
+
# (minus_row_max_scaled, minus_row_max_scaled),
|
| 357 |
+
# )
|
| 358 |
+
# )
|
| 359 |
+
# acc_S_row_frg[k, j] = cute.math.exp2(acc_S_row_frg[k, j], fastmath=True)
|
| 360 |
+
# acc_S_row_frg[k + 1, j] = cute.math.exp2(acc_S_row_frg[k + 1, j], fastmath=True)
|
| 361 |
+
acc_S_row_frg[k, j] = cute.math.exp2(acc_S_row_frg[k, j], fastmath=True)
|
| 362 |
+
acc_S_row_frg[k + 1, j] = cute.math.exp2(acc_S_row_frg[k + 1, j], fastmath=True)
|
| 363 |
+
acc_S_row_converted_frg[None, j].store(
|
| 364 |
+
acc_S_row_frg[None, j].load().to(acc_S_row_converted.element_type)
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
@cute.jit
|
| 369 |
+
def floor_if_packed(
|
| 370 |
+
q_idx,
|
| 371 |
+
qhead_per_kvhead: cutlass.Constexpr[int],
|
| 372 |
+
) -> cute.Tensor:
|
| 373 |
+
"""Convert q_idx to packed format for Pack-GQA."""
|
| 374 |
+
if cutlass.const_expr(qhead_per_kvhead == 1):
|
| 375 |
+
return q_idx
|
| 376 |
+
return q_idx // qhead_per_kvhead
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
@cute.jit
|
| 380 |
+
def apply_score_mod_inner(
|
| 381 |
+
score_tensor,
|
| 382 |
+
index_tensor,
|
| 383 |
+
score_mod: cutlass.Constexpr,
|
| 384 |
+
batch_idx,
|
| 385 |
+
head_idx,
|
| 386 |
+
softmax_scale,
|
| 387 |
+
vec_size: cutlass.Constexpr,
|
| 388 |
+
qk_acc_dtype: cutlass.Constexpr,
|
| 389 |
+
aux_tensors,
|
| 390 |
+
fastdiv_mods,
|
| 391 |
+
seqlen_info: SeqlenInfoQK,
|
| 392 |
+
constant_q_idx: cutlass.Constexpr,
|
| 393 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 394 |
+
transpose_indices: cutlass.Constexpr[bool] = False,
|
| 395 |
+
):
|
| 396 |
+
"""Shared implementation for applying score modification.
|
| 397 |
+
|
| 398 |
+
Args:
|
| 399 |
+
score_tensor: The scores to modify (acc_S for flash_fwd, tSrS_t2r for sm100)
|
| 400 |
+
index_tensor: Index positions (tScS for flash_fwd, tScS_t2r for sm100)
|
| 401 |
+
score_mod: The score modification function to apply
|
| 402 |
+
batch_idx: Batch index
|
| 403 |
+
head_idx: Head index
|
| 404 |
+
softmax_scale: Scale to apply
|
| 405 |
+
vec_size: Vector size for processing elements
|
| 406 |
+
qk_acc_dtype: Data type for accumulator
|
| 407 |
+
aux_tensors: Optional aux_tensors for FlexAttention
|
| 408 |
+
fastdiv_mods: Tuple of (seqlen_q_divmod, seqlen_k_divmod) for wrapping
|
| 409 |
+
seqlen_info: Sequence length info
|
| 410 |
+
constant_q_idx: If provided, use this constant for all q_idx values
|
| 411 |
+
If None, compute q_idx per-element
|
| 412 |
+
qhead_per_kvhead_packgqa: Pack-GQA replication factor. Divide q_idx by this
|
| 413 |
+
when greater than 1 so score mods see logical heads.
|
| 414 |
+
transpose_indices: If True, swap q_idx/kv_idx in index_tensor (for bwd kernel where S is transposed)
|
| 415 |
+
"""
|
| 416 |
+
# Index positions in the index_tensor tuple
|
| 417 |
+
# Forward: index_tensor[...][0] = q_idx, index_tensor[...][1] = kv_idx
|
| 418 |
+
# Backward (transposed): index_tensor[...][0] = kv_idx, index_tensor[...][1] = q_idx
|
| 419 |
+
if cutlass.const_expr(transpose_indices):
|
| 420 |
+
q_idx_pos = cutlass.const_expr(1)
|
| 421 |
+
kv_idx_pos = cutlass.const_expr(0)
|
| 422 |
+
else:
|
| 423 |
+
q_idx_pos = cutlass.const_expr(0)
|
| 424 |
+
kv_idx_pos = cutlass.const_expr(1)
|
| 425 |
+
|
| 426 |
+
n_vals = cutlass.const_expr(cute.size(score_tensor.shape))
|
| 427 |
+
score_vec = cute.make_rmem_tensor(vec_size, qk_acc_dtype)
|
| 428 |
+
kv_idx_vec = cute.make_rmem_tensor(vec_size, cutlass.Int32)
|
| 429 |
+
|
| 430 |
+
# SSA values for batch (constant across all elements)
|
| 431 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 432 |
+
|
| 433 |
+
# Handle q_idx based on whether it's constant
|
| 434 |
+
q_idx_vec = cute.make_rmem_tensor(vec_size, cutlass.Int32)
|
| 435 |
+
|
| 436 |
+
# For Pack-GQA with non-constant q_idx, we need per-element head indices
|
| 437 |
+
# since a thread may process multiple query head indices
|
| 438 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 439 |
+
head_idx_vec = cute.make_rmem_tensor(vec_size, cutlass.Int32)
|
| 440 |
+
|
| 441 |
+
for i in cutlass.range(0, n_vals, vec_size, unroll_full=True):
|
| 442 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 443 |
+
score_vec[j] = score_tensor[i + j] * softmax_scale
|
| 444 |
+
|
| 445 |
+
# Extract head offset from packed q_idx for Pack-GQA
|
| 446 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 447 |
+
q_idx_packed = index_tensor[i + j][q_idx_pos]
|
| 448 |
+
# Building up the logical q_head idx: final_q_head = kv_head * qhead_per_kvhead + (q_physical % qhead_per_kvhead)
|
| 449 |
+
q_idx_logical = q_idx_packed // qhead_per_kvhead
|
| 450 |
+
head_offset = q_idx_packed - q_idx_logical * qhead_per_kvhead
|
| 451 |
+
head_idx_vec[j] = head_idx * qhead_per_kvhead + head_offset
|
| 452 |
+
|
| 453 |
+
# If we will do loads we mod, in order to not read OOB
|
| 454 |
+
if cutlass.const_expr(aux_tensors is not None and fastdiv_mods is not None):
|
| 455 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 456 |
+
seqlen_q_divmod, seqlen_k_divmod = fastdiv_mods
|
| 457 |
+
q_idx_floored = floor_if_packed(
|
| 458 |
+
index_tensor[i + j][q_idx_pos], qhead_per_kvhead
|
| 459 |
+
)
|
| 460 |
+
_, q_idx_wrapped = divmod(q_idx_floored, seqlen_q_divmod)
|
| 461 |
+
q_idx_vec[j] = q_idx_wrapped
|
| 462 |
+
else:
|
| 463 |
+
_, seqlen_k_divmod = fastdiv_mods
|
| 464 |
+
|
| 465 |
+
_, kv_idx_wrapped = divmod(index_tensor[i + j][kv_idx_pos], seqlen_k_divmod)
|
| 466 |
+
kv_idx_vec[j] = kv_idx_wrapped
|
| 467 |
+
else:
|
| 468 |
+
# No bounds checking - direct indexing
|
| 469 |
+
if constant_q_idx is None:
|
| 470 |
+
q_idx_vec[j] = floor_if_packed(index_tensor[i + j][q_idx_pos], qhead_per_kvhead)
|
| 471 |
+
kv_idx_vec[j] = index_tensor[i + j][kv_idx_pos]
|
| 472 |
+
|
| 473 |
+
# Convert to SSA for score_mod call
|
| 474 |
+
score_ssa = score_vec.load()
|
| 475 |
+
kv_idx_ssa = kv_idx_vec.load()
|
| 476 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 477 |
+
q_idx_ssa = q_idx_vec.load()
|
| 478 |
+
else:
|
| 479 |
+
# NB we do not apply Pack-GQA division here, as constant_q_idx is assumed to already be logical
|
| 480 |
+
q_idx_const = constant_q_idx
|
| 481 |
+
q_idx_ssa = utils.scalar_to_ssa(q_idx_const, cutlass.Int32).broadcast_to((vec_size,))
|
| 482 |
+
|
| 483 |
+
# Compute head_idx_ssa: per-element for Pack-GQA with non-constant q_idx, constant otherwise
|
| 484 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 485 |
+
head_idx_ssa = head_idx_vec.load()
|
| 486 |
+
else:
|
| 487 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 488 |
+
|
| 489 |
+
aux_args = []
|
| 490 |
+
if cutlass.const_expr(aux_tensors is not None):
|
| 491 |
+
aux_args = aux_tensors
|
| 492 |
+
|
| 493 |
+
post_mod_scores = score_mod(
|
| 494 |
+
score_ssa,
|
| 495 |
+
batch_idx_ssa,
|
| 496 |
+
head_idx_ssa,
|
| 497 |
+
q_idx=q_idx_ssa,
|
| 498 |
+
kv_idx=kv_idx_ssa,
|
| 499 |
+
seqlen_info=seqlen_info,
|
| 500 |
+
aux_tensors=aux_args,
|
| 501 |
+
)
|
| 502 |
+
|
| 503 |
+
# Write back modified scores
|
| 504 |
+
score_vec.store(post_mod_scores)
|
| 505 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 506 |
+
score_tensor[i + j] = score_vec[j]
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
@cute.jit
|
| 510 |
+
def apply_score_mod_bwd_inner(
|
| 511 |
+
grad_tensor,
|
| 512 |
+
score_tensor,
|
| 513 |
+
index_tensor,
|
| 514 |
+
score_mod_bwd: cutlass.Constexpr,
|
| 515 |
+
batch_idx,
|
| 516 |
+
head_idx,
|
| 517 |
+
softmax_scale,
|
| 518 |
+
vec_size: cutlass.Constexpr,
|
| 519 |
+
qk_acc_dtype: cutlass.Constexpr,
|
| 520 |
+
aux_tensors,
|
| 521 |
+
fastdiv_mods,
|
| 522 |
+
seqlen_info,
|
| 523 |
+
constant_q_idx: cutlass.Constexpr,
|
| 524 |
+
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 525 |
+
transpose_indices: cutlass.Constexpr[bool] = False,
|
| 526 |
+
):
|
| 527 |
+
"""Apply backward score modification (joint graph).
|
| 528 |
+
|
| 529 |
+
Args:
|
| 530 |
+
grad_tensor: in/out: dlogits rewritten in-place with d(scaled_scores)
|
| 531 |
+
score_tensor: pre-mod scores (unscaled QK tile), scaled by softmax_scale internally
|
| 532 |
+
index_tensor: Index positions (same as forward)
|
| 533 |
+
score_mod_bwd: The backward score modification function (joint graph)
|
| 534 |
+
batch_idx: Batch index
|
| 535 |
+
head_idx: Head index
|
| 536 |
+
softmax_scale: Scale to apply to score_tensor
|
| 537 |
+
vec_size: Vector size for processing elements
|
| 538 |
+
qk_acc_dtype: Data type for accumulator
|
| 539 |
+
aux_tensors: Optional aux_tensors for FlexAttention
|
| 540 |
+
fastdiv_mods: Tuple of (seqlen_q_divmod, seqlen_k_divmod) for wrapping
|
| 541 |
+
seqlen_info: Sequence length info
|
| 542 |
+
constant_q_idx: If provided, use this constant for all q_idx values
|
| 543 |
+
qhead_per_kvhead: Pack-GQA replication factor
|
| 544 |
+
transpose_indices: If True, swap q_idx/kv_idx in index_tensor
|
| 545 |
+
"""
|
| 546 |
+
# Index positions in the index_tensor tuple
|
| 547 |
+
# Forward: index_tensor[...][0] = q_idx, index_tensor[...][1] = kv_idx
|
| 548 |
+
# Backward (transposed): index_tensor[...][0] = kv_idx, index_tensor[...][1] = q_idx
|
| 549 |
+
if cutlass.const_expr(transpose_indices):
|
| 550 |
+
q_idx_pos = cutlass.const_expr(1)
|
| 551 |
+
kv_idx_pos = cutlass.const_expr(0)
|
| 552 |
+
else:
|
| 553 |
+
q_idx_pos = cutlass.const_expr(0)
|
| 554 |
+
kv_idx_pos = cutlass.const_expr(1)
|
| 555 |
+
n_vals = cutlass.const_expr(cute.size(grad_tensor.shape))
|
| 556 |
+
grad_vec = cute.make_fragment(vec_size, qk_acc_dtype)
|
| 557 |
+
score_vec = cute.make_fragment(vec_size, qk_acc_dtype)
|
| 558 |
+
kv_idx_vec = cute.make_fragment(vec_size, cutlass.Int32)
|
| 559 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 560 |
+
q_idx_vec = cute.make_fragment(vec_size, cutlass.Int32)
|
| 561 |
+
|
| 562 |
+
# For Pack-GQA with non-constant q_idx, we need per-element head indices
|
| 563 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 564 |
+
head_idx_vec = cute.make_fragment(vec_size, cutlass.Int32)
|
| 565 |
+
|
| 566 |
+
for i in cutlass.range(0, n_vals, vec_size, unroll_full=True):
|
| 567 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 568 |
+
grad_vec[j] = grad_tensor[i + j]
|
| 569 |
+
# Scale score so joint graph sees same value as forward score_mod
|
| 570 |
+
score_vec[j] = score_tensor[i + j] * softmax_scale
|
| 571 |
+
|
| 572 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 573 |
+
q_idx_packed = index_tensor[i + j][q_idx_pos]
|
| 574 |
+
q_idx_logical = q_idx_packed // qhead_per_kvhead
|
| 575 |
+
head_offset = q_idx_packed - q_idx_logical * qhead_per_kvhead
|
| 576 |
+
head_idx_vec[j] = head_idx * qhead_per_kvhead + head_offset
|
| 577 |
+
|
| 578 |
+
if cutlass.const_expr(aux_tensors is not None and fastdiv_mods is not None):
|
| 579 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 580 |
+
seqlen_q_divmod, seqlen_k_divmod = fastdiv_mods
|
| 581 |
+
q_idx_floored = floor_if_packed(
|
| 582 |
+
index_tensor[i + j][q_idx_pos], qhead_per_kvhead
|
| 583 |
+
)
|
| 584 |
+
_, q_idx_wrapped = divmod(q_idx_floored, seqlen_q_divmod)
|
| 585 |
+
q_idx_vec[j] = q_idx_wrapped
|
| 586 |
+
else:
|
| 587 |
+
_, seqlen_k_divmod = fastdiv_mods
|
| 588 |
+
|
| 589 |
+
_, kv_idx_wrapped = divmod(index_tensor[i + j][kv_idx_pos], seqlen_k_divmod)
|
| 590 |
+
kv_idx_vec[j] = kv_idx_wrapped
|
| 591 |
+
else:
|
| 592 |
+
# No bounds checking - direct indexing
|
| 593 |
+
if constant_q_idx is None:
|
| 594 |
+
q_idx_vec[j] = floor_if_packed(index_tensor[i + j][q_idx_pos], qhead_per_kvhead)
|
| 595 |
+
kv_idx_vec[j] = index_tensor[i + j][kv_idx_pos]
|
| 596 |
+
|
| 597 |
+
grad_ssa = grad_vec.load()
|
| 598 |
+
score_ssa = score_vec.load()
|
| 599 |
+
kv_idx_ssa = kv_idx_vec.load()
|
| 600 |
+
|
| 601 |
+
if cutlass.const_expr(constant_q_idx is None):
|
| 602 |
+
q_idx_ssa = q_idx_vec.load()
|
| 603 |
+
else:
|
| 604 |
+
q_idx_ssa = utils.scalar_to_ssa(constant_q_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 605 |
+
|
| 606 |
+
if cutlass.const_expr(qhead_per_kvhead > 1 and constant_q_idx is None):
|
| 607 |
+
head_idx_ssa = head_idx_vec.load()
|
| 608 |
+
else:
|
| 609 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32).broadcast_to((vec_size,))
|
| 610 |
+
|
| 611 |
+
aux_args = []
|
| 612 |
+
if cutlass.const_expr(aux_tensors is not None):
|
| 613 |
+
aux_args = aux_tensors
|
| 614 |
+
|
| 615 |
+
grad_out_ssa = score_mod_bwd(
|
| 616 |
+
grad_ssa,
|
| 617 |
+
score_ssa,
|
| 618 |
+
batch_idx_ssa,
|
| 619 |
+
head_idx_ssa,
|
| 620 |
+
q_idx=q_idx_ssa,
|
| 621 |
+
kv_idx=kv_idx_ssa,
|
| 622 |
+
seqlen_info=seqlen_info,
|
| 623 |
+
aux_tensors=aux_args,
|
| 624 |
+
)
|
| 625 |
+
|
| 626 |
+
grad_vec.store(grad_out_ssa)
|
| 627 |
+
for j in cutlass.range(vec_size, unroll_full=True):
|
| 628 |
+
grad_tensor[i + j] = grad_vec[j]
|
build/torch-cuda/flashrt_fa4/cute/testing.py
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from contextlib import nullcontext
|
| 3 |
+
from functools import wraps
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from einops import rearrange, repeat
|
| 9 |
+
from torch._guards import active_fake_mode
|
| 10 |
+
from torch._subclasses.fake_tensor import FakeTensorMode
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class IndexFirstAxis(torch.autograd.Function):
|
| 14 |
+
@staticmethod
|
| 15 |
+
def forward(ctx, input, indices):
|
| 16 |
+
ctx.save_for_backward(indices)
|
| 17 |
+
assert input.ndim >= 2
|
| 18 |
+
ctx.first_axis_dim, other_shape = input.shape[0], input.shape[1:]
|
| 19 |
+
second_dim = other_shape.numel()
|
| 20 |
+
return torch.gather(
|
| 21 |
+
rearrange(input, "b ... -> b (...)"),
|
| 22 |
+
0,
|
| 23 |
+
repeat(indices, "z -> z d", d=second_dim),
|
| 24 |
+
).reshape(-1, *other_shape)
|
| 25 |
+
|
| 26 |
+
@staticmethod
|
| 27 |
+
def backward(ctx, grad_output):
|
| 28 |
+
(indices,) = ctx.saved_tensors
|
| 29 |
+
assert grad_output.ndim >= 2
|
| 30 |
+
other_shape = grad_output.shape[1:]
|
| 31 |
+
grad_output = rearrange(grad_output, "b ... -> b (...)")
|
| 32 |
+
grad_input = torch.zeros(
|
| 33 |
+
[ctx.first_axis_dim, grad_output.shape[1]],
|
| 34 |
+
device=grad_output.device,
|
| 35 |
+
dtype=grad_output.dtype,
|
| 36 |
+
)
|
| 37 |
+
grad_input.scatter_(0, repeat(indices, "z -> z d", d=grad_output.shape[1]), grad_output)
|
| 38 |
+
return grad_input.reshape(ctx.first_axis_dim, *other_shape), None
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
index_first_axis = IndexFirstAxis.apply
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class IndexPutFirstAxis(torch.autograd.Function):
|
| 45 |
+
@staticmethod
|
| 46 |
+
def forward(ctx, values, indices, first_axis_dim):
|
| 47 |
+
ctx.save_for_backward(indices)
|
| 48 |
+
assert indices.ndim == 1
|
| 49 |
+
assert values.ndim >= 2
|
| 50 |
+
output = torch.zeros(
|
| 51 |
+
first_axis_dim, *values.shape[1:], device=values.device, dtype=values.dtype
|
| 52 |
+
)
|
| 53 |
+
output[indices] = values
|
| 54 |
+
return output
|
| 55 |
+
|
| 56 |
+
@staticmethod
|
| 57 |
+
def backward(ctx, grad_output):
|
| 58 |
+
(indices,) = ctx.saved_tensors
|
| 59 |
+
grad_values = grad_output[indices]
|
| 60 |
+
return grad_values, None, None
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
index_put_first_axis = IndexPutFirstAxis.apply
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def unpad_input(hidden_states, attention_mask, unused_mask=None):
|
| 67 |
+
all_masks = (attention_mask + unused_mask) if unused_mask is not None else attention_mask
|
| 68 |
+
seqlens_in_batch = all_masks.sum(dim=-1, dtype=torch.int32)
|
| 69 |
+
used_seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32)
|
| 70 |
+
in_fake_mode = active_fake_mode() is not None
|
| 71 |
+
if not in_fake_mode:
|
| 72 |
+
indices = torch.nonzero(all_masks.flatten(), as_tuple=False).flatten()
|
| 73 |
+
max_seqlen_in_batch = seqlens_in_batch.max().item()
|
| 74 |
+
else:
|
| 75 |
+
# torch.nonzero and .item() are not supported in FakeTensorMode
|
| 76 |
+
batch_size, seqlen = attention_mask.shape
|
| 77 |
+
indices = torch.arange(batch_size * seqlen, device=hidden_states.device)
|
| 78 |
+
max_seqlen_in_batch = seqlen
|
| 79 |
+
cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.int32), (1, 0))
|
| 80 |
+
return (
|
| 81 |
+
index_first_axis(rearrange(hidden_states, "b s ... -> (b s) ..."), indices),
|
| 82 |
+
indices,
|
| 83 |
+
cu_seqlens,
|
| 84 |
+
max_seqlen_in_batch,
|
| 85 |
+
used_seqlens_in_batch,
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def pad_input(hidden_states, indices, batch, seqlen):
|
| 90 |
+
output = index_put_first_axis(hidden_states, indices, batch * seqlen)
|
| 91 |
+
return rearrange(output, "(b s) ... -> b s ...", b=batch)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def generate_random_padding_mask(
|
| 95 |
+
max_seqlen, batch_size, device, mode="random", zero_lengths=False, min_seqlen=None
|
| 96 |
+
):
|
| 97 |
+
assert mode in ["full", "random", "third"]
|
| 98 |
+
min_seqlen = min_seqlen if min_seqlen is not None else 0 if zero_lengths else 1
|
| 99 |
+
if mode == "full":
|
| 100 |
+
lengths = torch.full((batch_size, 1), max_seqlen, device=device, dtype=torch.int32)
|
| 101 |
+
elif mode == "random":
|
| 102 |
+
lengths = torch.randint(
|
| 103 |
+
max(min_seqlen, max_seqlen - 20),
|
| 104 |
+
max_seqlen + 1,
|
| 105 |
+
(batch_size, 1),
|
| 106 |
+
device=device,
|
| 107 |
+
)
|
| 108 |
+
else:
|
| 109 |
+
lengths = torch.randint(
|
| 110 |
+
max(min_seqlen, max_seqlen // 3),
|
| 111 |
+
max_seqlen + 1,
|
| 112 |
+
(batch_size, 1),
|
| 113 |
+
device=device,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
if zero_lengths:
|
| 117 |
+
for i in range(batch_size):
|
| 118 |
+
if i % 5 == 0:
|
| 119 |
+
lengths[i] = 0
|
| 120 |
+
lengths[-1] = 0
|
| 121 |
+
padding_mask = (
|
| 122 |
+
repeat(torch.arange(max_seqlen, device=device), "s -> b s", b=batch_size) < lengths
|
| 123 |
+
)
|
| 124 |
+
return padding_mask
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def generate_qkv(
|
| 128 |
+
q,
|
| 129 |
+
k,
|
| 130 |
+
v,
|
| 131 |
+
query_padding_mask=None,
|
| 132 |
+
key_padding_mask=None,
|
| 133 |
+
qv=None,
|
| 134 |
+
kvpacked=False,
|
| 135 |
+
qkvpacked=False,
|
| 136 |
+
query_unused_mask=None,
|
| 137 |
+
key_unused_mask=None,
|
| 138 |
+
):
|
| 139 |
+
assert not (kvpacked and qkvpacked)
|
| 140 |
+
batch_size, seqlen_q, nheads, d = q.shape
|
| 141 |
+
d_v = v.shape[-1]
|
| 142 |
+
_, seqlen_k, nheads_k, _ = k.shape
|
| 143 |
+
assert k.shape == (batch_size, seqlen_k, nheads_k, d)
|
| 144 |
+
assert v.shape == (batch_size, seqlen_k, nheads_k, d_v)
|
| 145 |
+
if query_unused_mask is not None or key_unused_mask is not None:
|
| 146 |
+
assert not kvpacked
|
| 147 |
+
assert not qkvpacked
|
| 148 |
+
|
| 149 |
+
if query_padding_mask is not None:
|
| 150 |
+
q_unpad, indices_q, cu_seqlens_q, max_seqlen_q, seqused_q = unpad_input(
|
| 151 |
+
q, query_padding_mask, query_unused_mask
|
| 152 |
+
)
|
| 153 |
+
output_pad_fn = lambda output_unpad: pad_input(
|
| 154 |
+
output_unpad, indices_q, batch_size, seqlen_q
|
| 155 |
+
)
|
| 156 |
+
qv_unpad = rearrange(qv, "b s ... -> (b s) ...")[indices_q] if qv is not None else None
|
| 157 |
+
else:
|
| 158 |
+
q_unpad = rearrange(q, "b s h d -> (b s) h d")
|
| 159 |
+
cu_seqlens_q = torch.arange(
|
| 160 |
+
0, (batch_size + 1) * seqlen_q, step=seqlen_q, dtype=torch.int32, device=q_unpad.device
|
| 161 |
+
)
|
| 162 |
+
seqused_q = None
|
| 163 |
+
max_seqlen_q = seqlen_q
|
| 164 |
+
output_pad_fn = lambda output_unpad: rearrange(
|
| 165 |
+
output_unpad, "(b s) h d -> b s h d", b=batch_size
|
| 166 |
+
)
|
| 167 |
+
qv_unpad = rearrange(qv, "b s ... -> (b s) ...") if qv is not None else None
|
| 168 |
+
|
| 169 |
+
if key_padding_mask is not None:
|
| 170 |
+
k_unpad, indices_k, cu_seqlens_k, max_seqlen_k, seqused_k = unpad_input(
|
| 171 |
+
k, key_padding_mask, key_unused_mask
|
| 172 |
+
)
|
| 173 |
+
v_unpad, *_ = unpad_input(v, key_padding_mask, key_unused_mask)
|
| 174 |
+
else:
|
| 175 |
+
k_unpad = rearrange(k, "b s h d -> (b s) h d")
|
| 176 |
+
v_unpad = rearrange(v, "b s h d -> (b s) h d")
|
| 177 |
+
cu_seqlens_k = torch.arange(
|
| 178 |
+
0, (batch_size + 1) * seqlen_k, step=seqlen_k, dtype=torch.int32, device=k_unpad.device
|
| 179 |
+
)
|
| 180 |
+
seqused_k = None
|
| 181 |
+
max_seqlen_k = seqlen_k
|
| 182 |
+
|
| 183 |
+
if qkvpacked:
|
| 184 |
+
assert (query_padding_mask == key_padding_mask).all()
|
| 185 |
+
assert nheads == nheads_k
|
| 186 |
+
qkv_unpad = torch.stack([q_unpad, k_unpad, v_unpad], dim=1)
|
| 187 |
+
qkv = torch.stack([q, k, v], dim=2)
|
| 188 |
+
if query_padding_mask is not None:
|
| 189 |
+
dqkv_pad_fn = lambda dqkv_unpad: pad_input(dqkv_unpad, indices_q, batch_size, seqlen_q)
|
| 190 |
+
else:
|
| 191 |
+
dqkv_pad_fn = lambda dqkv_unpad: rearrange(
|
| 192 |
+
dqkv_unpad, "(b s) t h d -> b s t h d", b=batch_size
|
| 193 |
+
)
|
| 194 |
+
return (
|
| 195 |
+
qkv_unpad.detach().requires_grad_(),
|
| 196 |
+
cu_seqlens_q,
|
| 197 |
+
max_seqlen_q,
|
| 198 |
+
qkv.detach().requires_grad_(),
|
| 199 |
+
output_pad_fn,
|
| 200 |
+
dqkv_pad_fn,
|
| 201 |
+
)
|
| 202 |
+
elif kvpacked:
|
| 203 |
+
kv_unpad = torch.stack([k_unpad, v_unpad], dim=1)
|
| 204 |
+
kv = torch.stack([k, v], dim=2)
|
| 205 |
+
dq_pad_fn = output_pad_fn
|
| 206 |
+
if key_padding_mask is not None:
|
| 207 |
+
dkv_pad_fn = lambda dkv_unpad: pad_input(dkv_unpad, indices_k, batch_size, seqlen_k)
|
| 208 |
+
else:
|
| 209 |
+
dkv_pad_fn = lambda dkv_unpad: rearrange(
|
| 210 |
+
dkv_unpad, "(b s) t h d -> b s t h d", b=batch_size
|
| 211 |
+
)
|
| 212 |
+
return (
|
| 213 |
+
q_unpad.detach().requires_grad_(),
|
| 214 |
+
kv_unpad.detach().requires_grad_(),
|
| 215 |
+
cu_seqlens_q,
|
| 216 |
+
cu_seqlens_k,
|
| 217 |
+
max_seqlen_q,
|
| 218 |
+
max_seqlen_k,
|
| 219 |
+
q.detach().requires_grad_(),
|
| 220 |
+
kv.detach().requires_grad_(),
|
| 221 |
+
output_pad_fn,
|
| 222 |
+
dq_pad_fn,
|
| 223 |
+
dkv_pad_fn,
|
| 224 |
+
)
|
| 225 |
+
else:
|
| 226 |
+
dq_pad_fn = output_pad_fn
|
| 227 |
+
if key_padding_mask is not None:
|
| 228 |
+
dk_pad_fn = lambda dk_unpad: pad_input(dk_unpad, indices_k, batch_size, seqlen_k)
|
| 229 |
+
else:
|
| 230 |
+
dk_pad_fn = lambda dk_unpad: rearrange(dk_unpad, "(b s) h d -> b s h d", b=batch_size)
|
| 231 |
+
return (
|
| 232 |
+
q_unpad.detach().requires_grad_(),
|
| 233 |
+
k_unpad.detach().requires_grad_(),
|
| 234 |
+
v_unpad.detach().requires_grad_(),
|
| 235 |
+
qv_unpad.detach() if qv is not None else None,
|
| 236 |
+
cu_seqlens_q,
|
| 237 |
+
cu_seqlens_k,
|
| 238 |
+
seqused_q,
|
| 239 |
+
seqused_k,
|
| 240 |
+
max_seqlen_q,
|
| 241 |
+
max_seqlen_k,
|
| 242 |
+
q.detach().requires_grad_(),
|
| 243 |
+
k.detach().requires_grad_(),
|
| 244 |
+
v.detach().requires_grad_(),
|
| 245 |
+
qv.detach() if qv is not None else None,
|
| 246 |
+
output_pad_fn,
|
| 247 |
+
dq_pad_fn,
|
| 248 |
+
dk_pad_fn,
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
def construct_local_mask(
|
| 253 |
+
seqlen_q,
|
| 254 |
+
seqlen_k,
|
| 255 |
+
window_size=(None, None),
|
| 256 |
+
sink_token_length=0,
|
| 257 |
+
query_padding_mask=None,
|
| 258 |
+
key_padding_mask=None,
|
| 259 |
+
key_leftpad=None,
|
| 260 |
+
device=None,
|
| 261 |
+
):
|
| 262 |
+
row_idx = rearrange(torch.arange(seqlen_q, device=device, dtype=torch.long), "s -> s 1")
|
| 263 |
+
col_idx = torch.arange(seqlen_k, device=device, dtype=torch.long)
|
| 264 |
+
if key_leftpad is not None:
|
| 265 |
+
key_leftpad = rearrange(key_leftpad, "b -> b 1 1 1")
|
| 266 |
+
col_idx = repeat(col_idx, "s -> b 1 1 s", b=key_leftpad.shape[0])
|
| 267 |
+
col_idx = torch.where(col_idx >= key_leftpad, col_idx - key_leftpad, 2**32)
|
| 268 |
+
sk = (
|
| 269 |
+
seqlen_k
|
| 270 |
+
if key_padding_mask is None
|
| 271 |
+
else rearrange(key_padding_mask.sum(-1), "b -> b 1 1 1")
|
| 272 |
+
)
|
| 273 |
+
sq = (
|
| 274 |
+
seqlen_q
|
| 275 |
+
if query_padding_mask is None
|
| 276 |
+
else rearrange(query_padding_mask.sum(-1), "b -> b 1 1 1")
|
| 277 |
+
)
|
| 278 |
+
if window_size[0] is None:
|
| 279 |
+
return col_idx > row_idx + sk - sq + window_size[1]
|
| 280 |
+
else:
|
| 281 |
+
sk = torch.full_like(col_idx, seqlen_k) if key_padding_mask is None else sk
|
| 282 |
+
if window_size[1] is None:
|
| 283 |
+
local_mask_left = col_idx > sk
|
| 284 |
+
else:
|
| 285 |
+
local_mask_left = col_idx > torch.minimum(row_idx + sk - sq + window_size[1], sk)
|
| 286 |
+
return torch.logical_or(
|
| 287 |
+
local_mask_left,
|
| 288 |
+
torch.logical_and(
|
| 289 |
+
col_idx < row_idx + sk - sq - window_size[0], col_idx >= sink_token_length
|
| 290 |
+
),
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def construct_chunk_mask(
|
| 295 |
+
seqlen_q,
|
| 296 |
+
seqlen_k,
|
| 297 |
+
attention_chunk,
|
| 298 |
+
query_padding_mask=None,
|
| 299 |
+
key_padding_mask=None,
|
| 300 |
+
key_leftpad=None,
|
| 301 |
+
device=None,
|
| 302 |
+
):
|
| 303 |
+
row_idx = rearrange(torch.arange(seqlen_q, device=device, dtype=torch.long), "s -> s 1")
|
| 304 |
+
col_idx = torch.arange(seqlen_k, device=device, dtype=torch.long)
|
| 305 |
+
if key_leftpad is not None:
|
| 306 |
+
key_leftpad = rearrange(key_leftpad, "b -> b 1 1 1")
|
| 307 |
+
col_idx = repeat(col_idx, "s -> b 1 1 s", b=key_leftpad.shape[0])
|
| 308 |
+
col_idx = torch.where(col_idx >= key_leftpad, col_idx - key_leftpad, 2**32)
|
| 309 |
+
sk = (
|
| 310 |
+
seqlen_k
|
| 311 |
+
if key_padding_mask is None
|
| 312 |
+
else rearrange(key_padding_mask.sum(-1), "b -> b 1 1 1")
|
| 313 |
+
)
|
| 314 |
+
sq = (
|
| 315 |
+
seqlen_q
|
| 316 |
+
if query_padding_mask is None
|
| 317 |
+
else rearrange(query_padding_mask.sum(-1), "b -> b 1 1 1")
|
| 318 |
+
)
|
| 319 |
+
sk = torch.full_like(col_idx, seqlen_k) if key_padding_mask is None else sk
|
| 320 |
+
col_limit_left_chunk = row_idx + sk - sq - (row_idx + sk - sq) % attention_chunk
|
| 321 |
+
return torch.logical_or(
|
| 322 |
+
col_idx < col_limit_left_chunk, col_idx >= col_limit_left_chunk + attention_chunk
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def attention_ref(
|
| 327 |
+
q,
|
| 328 |
+
k,
|
| 329 |
+
v,
|
| 330 |
+
query_padding_mask=None,
|
| 331 |
+
key_padding_mask=None,
|
| 332 |
+
key_leftpad=None,
|
| 333 |
+
attn_bias=None,
|
| 334 |
+
dropout_p=0.0,
|
| 335 |
+
dropout_mask=None,
|
| 336 |
+
causal=False,
|
| 337 |
+
qv=None,
|
| 338 |
+
q_descale=None,
|
| 339 |
+
k_descale=None,
|
| 340 |
+
v_descale=None,
|
| 341 |
+
window_size=(None, None),
|
| 342 |
+
attention_chunk=0,
|
| 343 |
+
sink_token_length=0,
|
| 344 |
+
learnable_sink: Optional[torch.Tensor] = None,
|
| 345 |
+
softcap=0.0,
|
| 346 |
+
upcast=True,
|
| 347 |
+
reorder_ops=False,
|
| 348 |
+
intermediate_dtype=None,
|
| 349 |
+
return_lse=False,
|
| 350 |
+
gather_kv_indices=None,
|
| 351 |
+
):
|
| 352 |
+
if causal:
|
| 353 |
+
window_size = (window_size[0], 0)
|
| 354 |
+
dtype_og = q.dtype
|
| 355 |
+
if upcast:
|
| 356 |
+
q, k, v = q.float(), k.float(), v.float()
|
| 357 |
+
qv = qv.float() if qv is not None else None
|
| 358 |
+
if q_descale is not None:
|
| 359 |
+
q_descale = repeat(q_descale, "b h -> b 1 (h g) 1", g=q.shape[2] // k.shape[2])
|
| 360 |
+
q = (q.float() * q_descale).to(q.dtype)
|
| 361 |
+
qv = (qv.float() * q_descale).to(qv.dtype) if qv is not None else None
|
| 362 |
+
if k_descale is not None:
|
| 363 |
+
k = (k.float() * rearrange(k_descale, "b h -> b 1 h 1")).to(dtype=k.dtype)
|
| 364 |
+
if v_descale is not None:
|
| 365 |
+
v = (v.float() * rearrange(v_descale, "b h -> b 1 h 1")).to(dtype=v.dtype)
|
| 366 |
+
seqlen_q, seqlen_k = q.shape[1], k.shape[1]
|
| 367 |
+
k = repeat(k, "b s h d -> b s (h g) d", g=q.shape[2] // k.shape[2])
|
| 368 |
+
v = repeat(v, "b s h d -> b s (h g) d", g=q.shape[2] // v.shape[2])
|
| 369 |
+
d = q.shape[-1]
|
| 370 |
+
dv = v.shape[-1]
|
| 371 |
+
softmax_scale = 1.0 / math.sqrt(d if qv is None else d + dv)
|
| 372 |
+
if not reorder_ops:
|
| 373 |
+
scores = torch.einsum("bthd,bshd->bhts", q * softmax_scale, k)
|
| 374 |
+
else:
|
| 375 |
+
scores = torch.einsum("bthd,bshd->bhts", q, k * softmax_scale)
|
| 376 |
+
if qv is not None:
|
| 377 |
+
scores = scores + torch.einsum("bthd,bshd->bhts", qv * softmax_scale, v)
|
| 378 |
+
if softcap > 0:
|
| 379 |
+
scores = torch.tanh(scores / softcap) * softcap
|
| 380 |
+
if key_padding_mask is not None:
|
| 381 |
+
scores.masked_fill_(rearrange(~key_padding_mask, "b s -> b 1 1 s"), float("-inf"))
|
| 382 |
+
local_mask = None
|
| 383 |
+
if window_size[0] is not None or window_size[1] is not None:
|
| 384 |
+
local_mask = construct_local_mask(
|
| 385 |
+
seqlen_q,
|
| 386 |
+
seqlen_k,
|
| 387 |
+
window_size,
|
| 388 |
+
sink_token_length,
|
| 389 |
+
query_padding_mask,
|
| 390 |
+
key_padding_mask,
|
| 391 |
+
key_leftpad=key_leftpad,
|
| 392 |
+
device=q.device,
|
| 393 |
+
)
|
| 394 |
+
if attention_chunk > 0:
|
| 395 |
+
chunk_mask = construct_chunk_mask(
|
| 396 |
+
seqlen_q,
|
| 397 |
+
seqlen_k,
|
| 398 |
+
attention_chunk,
|
| 399 |
+
query_padding_mask,
|
| 400 |
+
key_padding_mask,
|
| 401 |
+
key_leftpad=key_leftpad,
|
| 402 |
+
device=q.device,
|
| 403 |
+
)
|
| 404 |
+
local_mask = (
|
| 405 |
+
torch.logical_or(local_mask, chunk_mask) if local_mask is not None else chunk_mask
|
| 406 |
+
)
|
| 407 |
+
if gather_kv_indices is not None:
|
| 408 |
+
batch = q.shape[0]
|
| 409 |
+
topk_len = gather_kv_indices.shape[2]
|
| 410 |
+
if topk_len < seqlen_k:
|
| 411 |
+
topk_index_mask = torch.full(
|
| 412 |
+
(batch, seqlen_q, seqlen_k), False, device="cuda"
|
| 413 |
+
).scatter_(-1, gather_kv_indices, True)
|
| 414 |
+
scores.masked_fill_(rearrange(~topk_index_mask, "b t s -> b 1 t s"), float("-inf"))
|
| 415 |
+
if local_mask is not None:
|
| 416 |
+
scores.masked_fill_(local_mask, float("-inf"))
|
| 417 |
+
if attn_bias is not None:
|
| 418 |
+
scores = scores + attn_bias
|
| 419 |
+
# After all masks are applied, before softmax:
|
| 420 |
+
# scores shape: [b, h, t, s]
|
| 421 |
+
lse = torch.logsumexp(scores, dim=-1) # [b, h, t]
|
| 422 |
+
if learnable_sink is None:
|
| 423 |
+
attention = torch.softmax(scores, dim=-1).to(v.dtype)
|
| 424 |
+
else:
|
| 425 |
+
scores_fp32 = scores.to(torch.float32)
|
| 426 |
+
logits_max = torch.amax(scores_fp32, dim=-1, keepdim=True)
|
| 427 |
+
learnable_sink = rearrange(learnable_sink, "h -> h 1 1")
|
| 428 |
+
logits_or_sinks_max = torch.maximum(learnable_sink, logits_max)
|
| 429 |
+
unnormalized_scores = torch.exp(scores_fp32 - logits_or_sinks_max)
|
| 430 |
+
normalizer = unnormalized_scores.sum(dim=-1, keepdim=True) + torch.exp(
|
| 431 |
+
learnable_sink - logits_or_sinks_max
|
| 432 |
+
)
|
| 433 |
+
# LSE with sink: log(Z) = log(normalizer) + max
|
| 434 |
+
lse = (torch.log(normalizer.squeeze(-1)) + logits_or_sinks_max.squeeze(-1)).to(dtype_og)
|
| 435 |
+
attention = (unnormalized_scores / normalizer).to(v.dtype)
|
| 436 |
+
if query_padding_mask is not None:
|
| 437 |
+
attention = attention.masked_fill(rearrange(~query_padding_mask, "b s -> b 1 s 1"), 0.0)
|
| 438 |
+
if key_padding_mask is not None:
|
| 439 |
+
attention = attention.masked_fill(rearrange(~key_padding_mask, "b s -> b 1 1 s"), 0.0)
|
| 440 |
+
if local_mask is not None:
|
| 441 |
+
attention = attention.masked_fill(torch.all(local_mask, dim=-1, keepdim=True), 0.0)
|
| 442 |
+
dropout_scaling = 1.0 / (1 - dropout_p)
|
| 443 |
+
if dropout_mask is not None:
|
| 444 |
+
attention_drop = attention.masked_fill(~dropout_mask, 0.0)
|
| 445 |
+
else:
|
| 446 |
+
attention_drop = attention
|
| 447 |
+
if intermediate_dtype is not None:
|
| 448 |
+
attention_drop = attention_drop.to(intermediate_dtype).to(attention_drop.dtype)
|
| 449 |
+
output = torch.einsum("bhts,bshd->bthd", attention_drop, v * dropout_scaling)
|
| 450 |
+
if query_padding_mask is not None:
|
| 451 |
+
output.masked_fill_(rearrange(~query_padding_mask, "b s -> b s 1 1"), 0.0)
|
| 452 |
+
if return_lse:
|
| 453 |
+
return output.to(dtype_og), attention.to(dtype_og), lse.to(dtype_og)
|
| 454 |
+
return output.to(dtype=dtype_og), attention.to(dtype=dtype_og)
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
def maybe_fake_tensor_mode(fake: bool = True):
|
| 458 |
+
"""
|
| 459 |
+
One way to populate/pre-compile cache is to use torch fake tensor mode,
|
| 460 |
+
which does not allocate actual GPU tensors but retains tensor shape/dtype
|
| 461 |
+
metadata for cute.compile.
|
| 462 |
+
"""
|
| 463 |
+
|
| 464 |
+
def decorator(fn):
|
| 465 |
+
@wraps(fn)
|
| 466 |
+
def wrapper(*args, **kwargs):
|
| 467 |
+
with FakeTensorMode() if fake else nullcontext():
|
| 468 |
+
return fn(*args, **kwargs)
|
| 469 |
+
|
| 470 |
+
return wrapper
|
| 471 |
+
|
| 472 |
+
return decorator
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def is_fake_mode() -> bool:
|
| 476 |
+
return active_fake_mode() is not None
|
build/torch-cuda/flashrt_fa4/cute/tile_scheduler.py
ADDED
|
@@ -0,0 +1,1636 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao, Siyu Wang, Shengbin Di, Yuxi Chi, Johnsonms, Linfeng Zheng, Haoyan Huang, Lanbo Li, Yun Zhong, Man Yuan, Minmin Sun, Yong Li, Wei Lin.
|
| 2 |
+
|
| 3 |
+
from enum import IntEnum, auto
|
| 4 |
+
from typing import Optional, Tuple, Protocol, runtime_checkable
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
from typing import override
|
| 9 |
+
except ImportError: # Python < 3.12
|
| 10 |
+
from typing_extensions import override
|
| 11 |
+
|
| 12 |
+
import cutlass
|
| 13 |
+
from cutlass.pipeline import PipelineClcFetchAsync, PipelineState
|
| 14 |
+
from cutlass._mlir import ir
|
| 15 |
+
import cutlass.cute as cute
|
| 16 |
+
from cutlass import Int32, const_expr
|
| 17 |
+
from cutlass.cute import FastDivmodDivisor
|
| 18 |
+
from cutlass.utils import ClcDynamicPersistentTileScheduler, ClcDynamicPersistentTileSchedulerParams
|
| 19 |
+
from cutlass.cute.typing import Boolean
|
| 20 |
+
from cutlass.cutlass_dsl import (
|
| 21 |
+
min as dsl_min,
|
| 22 |
+
extract_mlir_values,
|
| 23 |
+
new_from_mlir_values,
|
| 24 |
+
)
|
| 25 |
+
from cutlass.utils.hardware_info import HardwareInfo
|
| 26 |
+
|
| 27 |
+
from fa4_cute_runtime.quack.cute_dsl_utils import ParamsBase
|
| 28 |
+
|
| 29 |
+
import fa4_cute_runtime.flashrt_fa4.cute.utils as utils
|
| 30 |
+
from fa4_cute_runtime.flashrt_fa4.cute.fast_math import clz
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class SchedulingMode(IntEnum):
|
| 34 |
+
NONE = auto()
|
| 35 |
+
STATIC = auto()
|
| 36 |
+
DYNAMIC = auto()
|
| 37 |
+
CLC = auto()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass
|
| 41 |
+
class ClcState(ParamsBase):
|
| 42 |
+
"""Owns the runtime state shared by CLC-capable tile schedulers.
|
| 43 |
+
|
| 44 |
+
`FlashAttentionForwardSm100` constructs this state because it owns the CLC
|
| 45 |
+
response buffer, mbarrier storage, and launch geometry needed to initialize
|
| 46 |
+
the hardware scheduler and async pipeline. Individual tile schedulers then
|
| 47 |
+
consume this state and map the returned hardware work tiles into their own
|
| 48 |
+
logical `WorkTileInfo` coordinates.
|
| 49 |
+
|
| 50 |
+
To add CLC support to a scheduler:
|
| 51 |
+
- implement `clc_problem_shape(params)` so the kernel can create the hardware scheduler
|
| 52 |
+
- accept `clc: ClcState | None` in `create(...)` / `__init__`
|
| 53 |
+
- map `clc.initial_work_tile_info()` and `clc.get_current_work()` into scheduler coordinates
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
_hw_scheduler: ClcDynamicPersistentTileScheduler
|
| 57 |
+
_pipeline: PipelineClcFetchAsync
|
| 58 |
+
_consumer_state: PipelineState
|
| 59 |
+
_producer_state: PipelineState
|
| 60 |
+
|
| 61 |
+
@staticmethod
|
| 62 |
+
def create(
|
| 63 |
+
*,
|
| 64 |
+
hw_scheduler: ClcDynamicPersistentTileScheduler,
|
| 65 |
+
pipeline: PipelineClcFetchAsync,
|
| 66 |
+
consumer_state: PipelineState,
|
| 67 |
+
producer_state: PipelineState,
|
| 68 |
+
) -> "ClcState":
|
| 69 |
+
return ClcState(hw_scheduler, pipeline, consumer_state, producer_state)
|
| 70 |
+
|
| 71 |
+
def initial_work_tile_info(self):
|
| 72 |
+
return self._hw_scheduler.initial_work_tile_info()
|
| 73 |
+
|
| 74 |
+
def get_current_work(self):
|
| 75 |
+
return self._hw_scheduler.get_current_work()
|
| 76 |
+
|
| 77 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 78 |
+
self._pipeline.producer_acquire(self._producer_state, loc=loc, ip=ip)
|
| 79 |
+
mbarrier_addr = self._pipeline.producer_get_barrier(self._producer_state, loc=loc, ip=ip)
|
| 80 |
+
self._hw_scheduler.advance_to_next_work(mbarrier_addr, loc=loc, ip=ip)
|
| 81 |
+
self._producer_state.advance(loc=loc, ip=ip)
|
| 82 |
+
|
| 83 |
+
def consumer_wait(self, *, loc=None, ip=None):
|
| 84 |
+
self._pipeline.consumer_wait(self._consumer_state, loc=loc, ip=ip)
|
| 85 |
+
|
| 86 |
+
def consumer_release(self, *, loc=None, ip=None):
|
| 87 |
+
self._pipeline.consumer_release(self._consumer_state, loc=loc, ip=ip)
|
| 88 |
+
self._consumer_state.advance(loc=loc, ip=ip)
|
| 89 |
+
|
| 90 |
+
def producer_tail(self, *, loc=None, ip=None):
|
| 91 |
+
self._pipeline.producer_tail(self._producer_state, loc=loc, ip=ip)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class WorkTileInfo(cutlass.utils.WorkTileInfo):
|
| 95 |
+
"""Altered WorkTileInfo which includes four axes: (block, head, batch, split)"""
|
| 96 |
+
|
| 97 |
+
@override
|
| 98 |
+
def __new_from_mlir_values__(self, values: list[ir.Value]) -> "WorkTileInfo":
|
| 99 |
+
assert len(values) == 5
|
| 100 |
+
new_tile_idx = cutlass.new_from_mlir_values(self._tile_idx, values[:-1])
|
| 101 |
+
new_is_valid_tile = cutlass.new_from_mlir_values(self._is_valid_tile, [values[-1]])
|
| 102 |
+
return WorkTileInfo(new_tile_idx, new_is_valid_tile)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
@runtime_checkable
|
| 106 |
+
class TileSchedulerProtocol(Protocol):
|
| 107 |
+
"""Protocol defining the interface all tile schedulers must implement.
|
| 108 |
+
|
| 109 |
+
Schedulers are responsible for:
|
| 110 |
+
1. Coordinate mapping: linear tile index -> (m_block, head, batch, split)
|
| 111 |
+
2. Work distribution: how to get the next tile (static grid-stride vs CLC dynamic)
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
def get_current_work(self) -> WorkTileInfo:
|
| 115 |
+
"""Get the current work tile coordinates."""
|
| 116 |
+
...
|
| 117 |
+
|
| 118 |
+
def initial_work_tile_info(self) -> WorkTileInfo:
|
| 119 |
+
"""Get the initial work tile for this CTA."""
|
| 120 |
+
...
|
| 121 |
+
|
| 122 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 123 |
+
"""Consumer-side advance: move to next tile and return it.
|
| 124 |
+
|
| 125 |
+
For static schedulers: grid-stride increment + get_current_work.
|
| 126 |
+
For CLC schedulers: consumer wait + get_current_work + consumer release + state advance.
|
| 127 |
+
"""
|
| 128 |
+
...
|
| 129 |
+
|
| 130 |
+
def prefetch_next_work(self, *, loc=None, ip=None) -> None:
|
| 131 |
+
"""Producer-side prefetch of next work tile (no-op for static schedulers).
|
| 132 |
+
|
| 133 |
+
For CLC schedulers: producer acquire + issue CLC query + producer state advance.
|
| 134 |
+
Only called by the scheduler warp.
|
| 135 |
+
"""
|
| 136 |
+
...
|
| 137 |
+
|
| 138 |
+
def producer_tail(self, *, loc=None, ip=None) -> None:
|
| 139 |
+
"""Producer-side cleanup after the last tile.
|
| 140 |
+
|
| 141 |
+
No-op for static schedulers. For CLC schedulers: pipeline producer_tail.
|
| 142 |
+
"""
|
| 143 |
+
...
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
@dataclass
|
| 147 |
+
class TileSchedulerArguments(ParamsBase):
|
| 148 |
+
num_block: Int32
|
| 149 |
+
num_head: Int32
|
| 150 |
+
num_batch: Int32
|
| 151 |
+
num_splits: Int32
|
| 152 |
+
seqlen_k: Int32
|
| 153 |
+
headdim: Int32
|
| 154 |
+
headdim_v: Int32
|
| 155 |
+
total_q: Int32
|
| 156 |
+
tile_shape_mn: cutlass.Constexpr[Tuple[int, int]]
|
| 157 |
+
cluster_shape_mn: cutlass.Constexpr[Tuple[int, int]] = (1, 1)
|
| 158 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None
|
| 159 |
+
mSeqUsedQ: Optional[cute.Tensor] = None
|
| 160 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1
|
| 161 |
+
element_size: cutlass.Constexpr[int] = 2
|
| 162 |
+
is_persistent: cutlass.Constexpr[bool] = False
|
| 163 |
+
lpt: cutlass.Constexpr[bool] = False
|
| 164 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 165 |
+
head_swizzle: cutlass.Constexpr[bool] = False
|
| 166 |
+
use_cluster_idx: cutlass.Constexpr[bool] = False
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
class SingleTileScheduler:
|
| 170 |
+
@dataclass
|
| 171 |
+
class Params(ParamsBase):
|
| 172 |
+
num_block: Int32
|
| 173 |
+
num_head: Int32
|
| 174 |
+
num_batch: Int32
|
| 175 |
+
num_splits: Int32
|
| 176 |
+
num_splits_divmod: FastDivmodDivisor
|
| 177 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 178 |
+
cluster_shape_mn: cutlass.Constexpr[Tuple[int, int]] = (1, 1)
|
| 179 |
+
use_cluster_idx: cutlass.Constexpr[bool] = False
|
| 180 |
+
|
| 181 |
+
@staticmethod
|
| 182 |
+
def create(
|
| 183 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 184 |
+
) -> "SingleTileScheduler.Params":
|
| 185 |
+
return SingleTileScheduler.Params(
|
| 186 |
+
args.num_block,
|
| 187 |
+
args.num_head,
|
| 188 |
+
args.num_batch,
|
| 189 |
+
args.num_splits,
|
| 190 |
+
FastDivmodDivisor(args.num_splits),
|
| 191 |
+
args.is_split_kv,
|
| 192 |
+
args.cluster_shape_mn,
|
| 193 |
+
args.use_cluster_idx,
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
def __init__(self, params: Params, blk_coord: cute.Coord, *, loc=None, ip=None):
|
| 197 |
+
self.params = params
|
| 198 |
+
self._blk_coord = blk_coord
|
| 199 |
+
self._is_first_block = True
|
| 200 |
+
self._loc = loc
|
| 201 |
+
self._ip = ip
|
| 202 |
+
|
| 203 |
+
@staticmethod
|
| 204 |
+
def to_underlying_arguments(
|
| 205 |
+
args: TileSchedulerArguments,
|
| 206 |
+
*,
|
| 207 |
+
scheduling_mode: SchedulingMode = SchedulingMode.STATIC,
|
| 208 |
+
loc=None,
|
| 209 |
+
ip=None,
|
| 210 |
+
) -> Params:
|
| 211 |
+
assert scheduling_mode == SchedulingMode.STATIC, (
|
| 212 |
+
f"SingleTileScheduler only supports STATIC, got {scheduling_mode!r}"
|
| 213 |
+
)
|
| 214 |
+
return SingleTileScheduler.Params.create(args, loc=loc, ip=ip)
|
| 215 |
+
|
| 216 |
+
@staticmethod
|
| 217 |
+
def create(
|
| 218 |
+
params: Params, clc: ClcState | None = None, *, loc=None, ip=None
|
| 219 |
+
) -> "SingleTileScheduler":
|
| 220 |
+
if const_expr(cute.size(params.cluster_shape_mn) == 1 or not params.use_cluster_idx):
|
| 221 |
+
blk_coord = cute.arch.block_idx()
|
| 222 |
+
else:
|
| 223 |
+
blk_coord = cute.arch.cluster_idx()
|
| 224 |
+
return SingleTileScheduler(params, blk_coord, loc=loc, ip=ip)
|
| 225 |
+
|
| 226 |
+
# called by host
|
| 227 |
+
@staticmethod
|
| 228 |
+
def get_grid_shape(
|
| 229 |
+
params: Params,
|
| 230 |
+
*,
|
| 231 |
+
loc=None,
|
| 232 |
+
ip=None,
|
| 233 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 234 |
+
# TODO: this hard-codes the fact that we only use cluster = (1, 1) or (2, 1)
|
| 235 |
+
assert params.cluster_shape_mn[1] == 1, "Only cluster_shape_mn[1] == 1 is supported"
|
| 236 |
+
if const_expr(params.use_cluster_idx):
|
| 237 |
+
# Grid must have num_block * cluster_m physical blocks so that there are num_block clusters
|
| 238 |
+
grid_x = params.num_block * params.cluster_shape_mn[0]
|
| 239 |
+
else:
|
| 240 |
+
grid_x = cute.round_up(params.num_block, params.cluster_shape_mn[0])
|
| 241 |
+
return (
|
| 242 |
+
grid_x,
|
| 243 |
+
params.num_head * params.num_splits,
|
| 244 |
+
params.num_batch,
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 248 |
+
block_idx, head_idx, batch_idx = self._blk_coord
|
| 249 |
+
if const_expr(self.params.is_split_kv):
|
| 250 |
+
head_idx, split_idx = divmod(head_idx, self.params.num_splits_divmod)
|
| 251 |
+
else:
|
| 252 |
+
split_idx = Int32(0)
|
| 253 |
+
return WorkTileInfo(
|
| 254 |
+
(block_idx, head_idx, batch_idx, split_idx),
|
| 255 |
+
self._is_first_block,
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 259 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 260 |
+
|
| 261 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 262 |
+
pass
|
| 263 |
+
|
| 264 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 265 |
+
self._is_first_block = False
|
| 266 |
+
return self.get_current_work()
|
| 267 |
+
|
| 268 |
+
def producer_tail(self, *, loc=None, ip=None):
|
| 269 |
+
pass
|
| 270 |
+
|
| 271 |
+
def __extract_mlir_values__(self):
|
| 272 |
+
values, self._values_pos = [], []
|
| 273 |
+
for obj in [self.params, self._blk_coord]:
|
| 274 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 275 |
+
values += obj_values
|
| 276 |
+
self._values_pos.append(len(obj_values))
|
| 277 |
+
return values
|
| 278 |
+
|
| 279 |
+
def __new_from_mlir_values__(self, values):
|
| 280 |
+
obj_list = []
|
| 281 |
+
for obj, n_items in zip([self.params, self._blk_coord], self._values_pos):
|
| 282 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 283 |
+
values = values[n_items:]
|
| 284 |
+
return SingleTileScheduler(*(tuple(obj_list)), loc=self._loc)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
class StaticPersistentTileScheduler:
|
| 288 |
+
@dataclass
|
| 289 |
+
class Params(ParamsBase):
|
| 290 |
+
num_block_cluster_divmod: FastDivmodDivisor
|
| 291 |
+
num_head_divmod: FastDivmodDivisor
|
| 292 |
+
total_blocks_cluster: Int32
|
| 293 |
+
cluster_shape_m: cutlass.Constexpr[int] = 1
|
| 294 |
+
|
| 295 |
+
@staticmethod
|
| 296 |
+
def create(
|
| 297 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 298 |
+
) -> "StaticPersistentTileScheduler.Params":
|
| 299 |
+
num_block_cluster = cute.ceil_div(args.num_block, cute.size(args.cluster_shape_mn))
|
| 300 |
+
total_blocks_cluster = num_block_cluster * args.num_head * args.num_batch
|
| 301 |
+
return StaticPersistentTileScheduler.Params(
|
| 302 |
+
FastDivmodDivisor(num_block_cluster),
|
| 303 |
+
FastDivmodDivisor(args.num_head),
|
| 304 |
+
total_blocks_cluster,
|
| 305 |
+
cluster_shape_m=args.cluster_shape_mn[0],
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
def __init__(self, params: Params, tile_idx: Int32, *, loc=None, ip=None):
|
| 309 |
+
self.params = params
|
| 310 |
+
self._tile_idx = tile_idx
|
| 311 |
+
self._loc = loc
|
| 312 |
+
self._ip = ip
|
| 313 |
+
|
| 314 |
+
@staticmethod
|
| 315 |
+
def to_underlying_arguments(
|
| 316 |
+
args: TileSchedulerArguments,
|
| 317 |
+
*,
|
| 318 |
+
scheduling_mode: SchedulingMode = SchedulingMode.STATIC,
|
| 319 |
+
loc=None,
|
| 320 |
+
ip=None,
|
| 321 |
+
) -> Params:
|
| 322 |
+
assert scheduling_mode == SchedulingMode.STATIC, (
|
| 323 |
+
f"StaticPersistentTileScheduler only supports STATIC, got {scheduling_mode!r}"
|
| 324 |
+
)
|
| 325 |
+
return StaticPersistentTileScheduler.Params.create(args, loc=loc, ip=ip)
|
| 326 |
+
|
| 327 |
+
@staticmethod
|
| 328 |
+
def create(
|
| 329 |
+
params: Params, clc: ClcState | None = None, *, loc=None, ip=None
|
| 330 |
+
) -> "StaticPersistentTileScheduler":
|
| 331 |
+
if const_expr(cute.size(params.cluster_shape_m) == 1):
|
| 332 |
+
tile_idx = cute.arch.block_idx()[0]
|
| 333 |
+
else:
|
| 334 |
+
tile_idx = cute.arch.cluster_idx()[0]
|
| 335 |
+
return StaticPersistentTileScheduler(params, tile_idx, loc=loc, ip=ip)
|
| 336 |
+
|
| 337 |
+
@staticmethod
|
| 338 |
+
def get_grid_shape(
|
| 339 |
+
params: Params,
|
| 340 |
+
*,
|
| 341 |
+
loc=None,
|
| 342 |
+
ip=None,
|
| 343 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 344 |
+
hardware_info = cutlass.utils.HardwareInfo()
|
| 345 |
+
sm_count = hardware_info.get_device_multiprocessor_count()
|
| 346 |
+
max_ctas = (sm_count // params.cluster_shape_m) * params.cluster_shape_m
|
| 347 |
+
grid_x = cutlass.min(max_ctas, params.total_blocks_cluster * params.cluster_shape_m)
|
| 348 |
+
return (grid_x, Int32(1), Int32(1))
|
| 349 |
+
|
| 350 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 351 |
+
hn_idx, block_idx = divmod(self._tile_idx, self.params.num_block_cluster_divmod)
|
| 352 |
+
batch_idx, head_idx = divmod(hn_idx, self.params.num_head_divmod)
|
| 353 |
+
is_valid = self._tile_idx < self.params.total_blocks_cluster
|
| 354 |
+
return WorkTileInfo(
|
| 355 |
+
(Int32(block_idx), Int32(head_idx), Int32(batch_idx), Int32(0)), is_valid
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 359 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 360 |
+
|
| 361 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 362 |
+
pass
|
| 363 |
+
|
| 364 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 365 |
+
if const_expr(self.params.cluster_shape_m == 1):
|
| 366 |
+
self._tile_idx += cute.arch.grid_dim()[0]
|
| 367 |
+
else:
|
| 368 |
+
self._tile_idx += cute.arch.cluster_dim()[0]
|
| 369 |
+
return self.get_current_work()
|
| 370 |
+
|
| 371 |
+
def producer_tail(self, *, loc=None, ip=None):
|
| 372 |
+
pass
|
| 373 |
+
|
| 374 |
+
def __extract_mlir_values__(self):
|
| 375 |
+
values, self._values_pos = [], []
|
| 376 |
+
for obj in [self.params, self._tile_idx]:
|
| 377 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 378 |
+
values += obj_values
|
| 379 |
+
self._values_pos.append(len(obj_values))
|
| 380 |
+
return values
|
| 381 |
+
|
| 382 |
+
def __new_from_mlir_values__(self, values):
|
| 383 |
+
obj_list = []
|
| 384 |
+
for obj, n_items in zip(
|
| 385 |
+
[self.params, self._tile_idx],
|
| 386 |
+
self._values_pos,
|
| 387 |
+
):
|
| 388 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 389 |
+
values = values[n_items:]
|
| 390 |
+
return StaticPersistentTileScheduler(*(tuple(obj_list)), loc=self._loc)
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
class SingleTileLPTScheduler:
|
| 394 |
+
@dataclass
|
| 395 |
+
class Params(ParamsBase):
|
| 396 |
+
total_blocks: Int32
|
| 397 |
+
num_splits: Int32
|
| 398 |
+
num_block: Int32
|
| 399 |
+
num_head: Int32
|
| 400 |
+
num_batch: Int32
|
| 401 |
+
l2_minor: Int32
|
| 402 |
+
num_head_divmod: FastDivmodDivisor
|
| 403 |
+
l2_minor_divmod: FastDivmodDivisor
|
| 404 |
+
l2_major_divmod: FastDivmodDivisor
|
| 405 |
+
l2_minor_residual_divmod: FastDivmodDivisor
|
| 406 |
+
num_hb_quotient: Int32
|
| 407 |
+
num_splits_divmod: FastDivmodDivisor
|
| 408 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 409 |
+
cluster_shape_m: cutlass.Constexpr[int] = 1
|
| 410 |
+
scheduling_mode: cutlass.Constexpr[SchedulingMode] = SchedulingMode.STATIC
|
| 411 |
+
lpt: cutlass.Constexpr[bool] = True
|
| 412 |
+
use_cluster_idx: cutlass.Constexpr[bool] = True
|
| 413 |
+
|
| 414 |
+
@staticmethod
|
| 415 |
+
@cute.jit
|
| 416 |
+
def create(
|
| 417 |
+
args: TileSchedulerArguments,
|
| 418 |
+
*,
|
| 419 |
+
scheduling_mode: SchedulingMode = SchedulingMode.STATIC,
|
| 420 |
+
loc=None,
|
| 421 |
+
ip=None,
|
| 422 |
+
) -> "SingleTileLPTScheduler.Params":
|
| 423 |
+
assert scheduling_mode in (SchedulingMode.STATIC, SchedulingMode.CLC), (
|
| 424 |
+
f"Only STATIC and CLC are supported, got {scheduling_mode!r}"
|
| 425 |
+
)
|
| 426 |
+
size_one_kv_head = args.seqlen_k * (args.headdim + args.headdim_v) * args.element_size
|
| 427 |
+
size_one_head = size_one_kv_head
|
| 428 |
+
size_l2 = 50 * 1024 * 1024 # 40 MB for K & V
|
| 429 |
+
# Swizzle is the size of each "section". Round swizzle to a power of 2
|
| 430 |
+
# Need to be careful about the case where only one head will fit
|
| 431 |
+
# swizzle is how many heads can fit in L2
|
| 432 |
+
# Seems faster if swizzle is a power of 2
|
| 433 |
+
log2_floor = lambda n: 31 - clz(n)
|
| 434 |
+
swizzle = 1 if size_l2 < size_one_head else (1 << log2_floor(size_l2 // size_one_head))
|
| 435 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 436 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 437 |
+
num_hb_quotient = (args.num_head * args.num_batch) // swizzle
|
| 438 |
+
num_hb_remainder = (args.num_head * args.num_batch) % swizzle
|
| 439 |
+
return SingleTileLPTScheduler.Params(
|
| 440 |
+
total_blocks=args.num_block * args.num_head * args.num_batch,
|
| 441 |
+
num_block=args.num_block,
|
| 442 |
+
num_head=args.num_head,
|
| 443 |
+
num_batch=args.num_batch,
|
| 444 |
+
l2_minor=Int32(swizzle),
|
| 445 |
+
num_head_divmod=FastDivmodDivisor(args.num_head),
|
| 446 |
+
l2_minor_divmod=FastDivmodDivisor(swizzle),
|
| 447 |
+
l2_major_divmod=FastDivmodDivisor(swizzle * args.num_block),
|
| 448 |
+
l2_minor_residual_divmod=FastDivmodDivisor(max(num_hb_remainder, 1)),
|
| 449 |
+
num_hb_quotient=Int32(num_hb_quotient),
|
| 450 |
+
num_splits=args.num_splits,
|
| 451 |
+
num_splits_divmod=FastDivmodDivisor(args.num_splits),
|
| 452 |
+
is_split_kv=args.is_split_kv,
|
| 453 |
+
cluster_shape_m=args.cluster_shape_mn[0],
|
| 454 |
+
scheduling_mode=scheduling_mode,
|
| 455 |
+
lpt=args.lpt,
|
| 456 |
+
use_cluster_idx=args.use_cluster_idx,
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
def __init__(
|
| 460 |
+
self,
|
| 461 |
+
params: Params,
|
| 462 |
+
tile_idx: Int32,
|
| 463 |
+
split_idx: Int32,
|
| 464 |
+
clc: ClcState | None = None,
|
| 465 |
+
*,
|
| 466 |
+
loc=None,
|
| 467 |
+
ip=None,
|
| 468 |
+
):
|
| 469 |
+
self.params = params
|
| 470 |
+
self._tile_idx = tile_idx
|
| 471 |
+
self._split_idx = split_idx
|
| 472 |
+
self.clc = clc
|
| 473 |
+
self._loc = loc
|
| 474 |
+
self._ip = ip
|
| 475 |
+
|
| 476 |
+
@staticmethod
|
| 477 |
+
def to_underlying_arguments(
|
| 478 |
+
args: TileSchedulerArguments,
|
| 479 |
+
*,
|
| 480 |
+
scheduling_mode: SchedulingMode = SchedulingMode.STATIC,
|
| 481 |
+
loc=None,
|
| 482 |
+
ip=None,
|
| 483 |
+
) -> Params:
|
| 484 |
+
return SingleTileLPTScheduler.Params.create(
|
| 485 |
+
args, scheduling_mode=scheduling_mode, loc=loc, ip=ip
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
@staticmethod
|
| 489 |
+
def _clc_grid_shape(params: Params):
|
| 490 |
+
num_batch_splits = (
|
| 491 |
+
params.num_batch * params.num_splits
|
| 492 |
+
if const_expr(params.is_split_kv)
|
| 493 |
+
else params.num_batch
|
| 494 |
+
)
|
| 495 |
+
return (
|
| 496 |
+
cute.round_up(params.num_block, params.cluster_shape_m),
|
| 497 |
+
params.num_head,
|
| 498 |
+
num_batch_splits,
|
| 499 |
+
)
|
| 500 |
+
|
| 501 |
+
@staticmethod
|
| 502 |
+
@cute.jit
|
| 503 |
+
def clc_problem_shape(params: Params):
|
| 504 |
+
return ClcDynamicPersistentTileSchedulerParams(
|
| 505 |
+
problem_shape_ntile_mnl=SingleTileLPTScheduler._clc_grid_shape(params),
|
| 506 |
+
cluster_shape_mnk=(params.cluster_shape_m, 1, 1),
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
@staticmethod
|
| 510 |
+
@cute.jit
|
| 511 |
+
def create(
|
| 512 |
+
params: Params, clc: ClcState | None = None, *, loc=None, ip=None
|
| 513 |
+
) -> "SingleTileLPTScheduler":
|
| 514 |
+
if const_expr(params.scheduling_mode == SchedulingMode.CLC):
|
| 515 |
+
return SingleTileLPTScheduler(
|
| 516 |
+
params, cute.arch.block_idx()[0], Int32(0), clc, loc=loc, ip=ip
|
| 517 |
+
)
|
| 518 |
+
tile_idx, split_idx, _ = cute.arch.block_idx()
|
| 519 |
+
return SingleTileLPTScheduler(params, tile_idx, split_idx, loc=loc, ip=ip)
|
| 520 |
+
|
| 521 |
+
@staticmethod
|
| 522 |
+
def get_grid_shape(
|
| 523 |
+
params: Params,
|
| 524 |
+
*,
|
| 525 |
+
loc=None,
|
| 526 |
+
ip=None,
|
| 527 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 528 |
+
if const_expr(params.scheduling_mode == SchedulingMode.CLC):
|
| 529 |
+
return SingleTileLPTScheduler._clc_grid_shape(params)
|
| 530 |
+
return (params.total_blocks, params.num_splits, Int32(1))
|
| 531 |
+
|
| 532 |
+
@cute.jit
|
| 533 |
+
def clc_work_to_coords(self, work) -> WorkTileInfo:
|
| 534 |
+
"""Convert CLC response (block, head, batch_split) to WorkTileInfo.
|
| 535 |
+
|
| 536 |
+
CLC returns raw grid coordinates — no L2 swizzle (hardware decides order).
|
| 537 |
+
We only apply cluster division, optional LPT block reversal, and split_kv unpacking.
|
| 538 |
+
"""
|
| 539 |
+
block_idx = work.tile_idx[0]
|
| 540 |
+
if const_expr(self.params.cluster_shape_m > 1):
|
| 541 |
+
block_idx = block_idx // self.params.cluster_shape_m
|
| 542 |
+
if const_expr(self.params.lpt):
|
| 543 |
+
# Longest-processing-time-first: reverse block order
|
| 544 |
+
if const_expr(self.params.cluster_shape_m > 1 and not self.params.use_cluster_idx):
|
| 545 |
+
num_block = self.params.num_block // self.params.cluster_shape_m
|
| 546 |
+
else:
|
| 547 |
+
num_block = self.params.num_block
|
| 548 |
+
block_idx = num_block - 1 - block_idx
|
| 549 |
+
split_idx = Int32(0)
|
| 550 |
+
if const_expr(self.params.is_split_kv):
|
| 551 |
+
batch_idx, split_idx = divmod(work.tile_idx[2], self.params.num_splits_divmod)
|
| 552 |
+
else:
|
| 553 |
+
batch_idx = work.tile_idx[2]
|
| 554 |
+
if const_expr(self.params.cluster_shape_m > 1 and not self.params.use_cluster_idx):
|
| 555 |
+
bidx_in_cluster = cute.arch.block_in_cluster_idx()
|
| 556 |
+
block_idx = block_idx * self.params.cluster_shape_m + bidx_in_cluster[0]
|
| 557 |
+
return WorkTileInfo(
|
| 558 |
+
(Int32(block_idx), Int32(work.tile_idx[1]), Int32(batch_idx), Int32(split_idx)),
|
| 559 |
+
work.is_valid_tile,
|
| 560 |
+
)
|
| 561 |
+
|
| 562 |
+
@cute.jit
|
| 563 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 564 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 565 |
+
work = self.clc.get_current_work()
|
| 566 |
+
self._tile_idx = work.tile_idx[0]
|
| 567 |
+
return self.clc_work_to_coords(work)
|
| 568 |
+
# Static path: L2-swizzled coordinate mapping
|
| 569 |
+
params = self.params
|
| 570 |
+
# Implement LPT scheduling coordinate calculation
|
| 571 |
+
bidhb, l2_mod = divmod(self._tile_idx, params.l2_major_divmod)
|
| 572 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 573 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 574 |
+
block, bidhb_residual = 0, 0
|
| 575 |
+
if bidhb < params.num_hb_quotient:
|
| 576 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_divmod)
|
| 577 |
+
else:
|
| 578 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_residual_divmod)
|
| 579 |
+
bidhb_actual = bidhb * params.l2_minor + bidhb_residual
|
| 580 |
+
batch_idx, head_idx = divmod(bidhb_actual, params.num_head_divmod)
|
| 581 |
+
# Longest-processing-time-first
|
| 582 |
+
if const_expr(params.lpt):
|
| 583 |
+
block = params.num_block - 1 - block
|
| 584 |
+
is_valid = self._tile_idx < params.total_blocks
|
| 585 |
+
return WorkTileInfo(
|
| 586 |
+
(Int32(block), Int32(head_idx), Int32(batch_idx), Int32(self._split_idx)), is_valid
|
| 587 |
+
)
|
| 588 |
+
|
| 589 |
+
@cute.jit
|
| 590 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 591 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 592 |
+
work = self.clc.initial_work_tile_info()
|
| 593 |
+
self._tile_idx = work.tile_idx[0]
|
| 594 |
+
return self.clc_work_to_coords(work)
|
| 595 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 596 |
+
|
| 597 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 598 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 599 |
+
self.clc.prefetch_next_work(loc=loc, ip=ip)
|
| 600 |
+
|
| 601 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 602 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 603 |
+
self.clc.consumer_wait(loc=loc, ip=ip)
|
| 604 |
+
work = self.get_current_work()
|
| 605 |
+
self.clc.consumer_release(loc=loc, ip=ip)
|
| 606 |
+
return work
|
| 607 |
+
# Single tile scheduler - set to invalid tile_idx to indicate no more work
|
| 608 |
+
self._tile_idx = self.params.total_blocks
|
| 609 |
+
return self.get_current_work()
|
| 610 |
+
|
| 611 |
+
def producer_tail(self, *, loc=None, ip=None):
|
| 612 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 613 |
+
self.clc.producer_tail(loc=loc, ip=ip)
|
| 614 |
+
|
| 615 |
+
def __extract_mlir_values__(self):
|
| 616 |
+
values, self._values_pos = [], []
|
| 617 |
+
objs = [self.params, self._tile_idx, self._split_idx]
|
| 618 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 619 |
+
objs += [self.clc]
|
| 620 |
+
for obj in objs:
|
| 621 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 622 |
+
values += obj_values
|
| 623 |
+
self._values_pos.append(len(obj_values))
|
| 624 |
+
return values
|
| 625 |
+
|
| 626 |
+
def __new_from_mlir_values__(self, values):
|
| 627 |
+
obj_list = []
|
| 628 |
+
objs = [self.params, self._tile_idx, self._split_idx]
|
| 629 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 630 |
+
objs += [self.clc]
|
| 631 |
+
for obj, n_items in zip(objs, self._values_pos):
|
| 632 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 633 |
+
values = values[n_items:]
|
| 634 |
+
return self.__class__(*obj_list, loc=self._loc)
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
class SingleTileLPTBwdScheduler:
|
| 638 |
+
@dataclass
|
| 639 |
+
class Params(ParamsBase):
|
| 640 |
+
total_blocks: Int32
|
| 641 |
+
num_block: Int32
|
| 642 |
+
l2_minor: Int32
|
| 643 |
+
num_head_divmod: FastDivmodDivisor
|
| 644 |
+
l2_minor_divmod: FastDivmodDivisor
|
| 645 |
+
l2_major_divmod: FastDivmodDivisor
|
| 646 |
+
l2_minor_residual_divmod: FastDivmodDivisor
|
| 647 |
+
num_hb_quotient: Int32
|
| 648 |
+
cluster_shape_mn: cutlass.Constexpr[Tuple[int, int]] = (1, 1)
|
| 649 |
+
spt: cutlass.Constexpr[bool] = True
|
| 650 |
+
|
| 651 |
+
@staticmethod
|
| 652 |
+
@cute.jit
|
| 653 |
+
def create(
|
| 654 |
+
args: TileSchedulerArguments, *, loc=None, ip=None
|
| 655 |
+
) -> "SingleTileLPTBwdScheduler.Params":
|
| 656 |
+
size_l2 = 50 * 1024 * 1024
|
| 657 |
+
size_one_qdo_head = args.seqlen_k * (args.headdim + args.headdim_v) * args.element_size
|
| 658 |
+
size_one_dqaccum_head = args.seqlen_k * (args.headdim) * 4
|
| 659 |
+
# size_one_dqaccum_head = 0
|
| 660 |
+
size_one_head = size_one_qdo_head + size_one_dqaccum_head
|
| 661 |
+
log2_floor = lambda n: 31 - clz(n)
|
| 662 |
+
swizzle = 1 if size_l2 < size_one_head else (1 << log2_floor(size_l2 // size_one_head))
|
| 663 |
+
# swizzle = 8
|
| 664 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 665 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 666 |
+
num_hb_quotient = (args.num_head * args.num_batch) // swizzle
|
| 667 |
+
num_hb_remainder = (args.num_head * args.num_batch) % swizzle
|
| 668 |
+
num_block = cute.ceil_div(args.num_block, args.cluster_shape_mn[0])
|
| 669 |
+
return SingleTileLPTBwdScheduler.Params(
|
| 670 |
+
total_blocks=(num_block * args.cluster_shape_mn[0])
|
| 671 |
+
* args.num_head
|
| 672 |
+
* args.num_batch,
|
| 673 |
+
num_block=num_block,
|
| 674 |
+
l2_minor=Int32(swizzle),
|
| 675 |
+
num_head_divmod=FastDivmodDivisor(args.num_head),
|
| 676 |
+
l2_minor_divmod=FastDivmodDivisor(swizzle),
|
| 677 |
+
l2_major_divmod=FastDivmodDivisor(swizzle * num_block),
|
| 678 |
+
l2_minor_residual_divmod=FastDivmodDivisor(
|
| 679 |
+
max(num_hb_remainder, 1)
|
| 680 |
+
), # don't divide by 0
|
| 681 |
+
num_hb_quotient=Int32(num_hb_quotient),
|
| 682 |
+
cluster_shape_mn=args.cluster_shape_mn,
|
| 683 |
+
spt=args.lpt,
|
| 684 |
+
)
|
| 685 |
+
|
| 686 |
+
def __init__(self, params: Params, tile_idx: Int32, *, loc=None, ip=None):
|
| 687 |
+
self.params = params
|
| 688 |
+
self._tile_idx = tile_idx
|
| 689 |
+
self._loc = loc
|
| 690 |
+
self._ip = ip
|
| 691 |
+
|
| 692 |
+
@staticmethod
|
| 693 |
+
def to_underlying_arguments(
|
| 694 |
+
args: TileSchedulerArguments,
|
| 695 |
+
*,
|
| 696 |
+
scheduling_mode: SchedulingMode = SchedulingMode.STATIC,
|
| 697 |
+
loc=None,
|
| 698 |
+
ip=None,
|
| 699 |
+
) -> Params:
|
| 700 |
+
assert scheduling_mode == SchedulingMode.STATIC, (
|
| 701 |
+
f"SingleTileLPTBwdScheduler only supports STATIC, got {scheduling_mode!r}"
|
| 702 |
+
)
|
| 703 |
+
return SingleTileLPTBwdScheduler.Params.create(args, loc=loc, ip=ip)
|
| 704 |
+
|
| 705 |
+
@staticmethod
|
| 706 |
+
@cute.jit
|
| 707 |
+
def create(params: Params, *, loc=None, ip=None) -> "SingleTileLPTBwdScheduler":
|
| 708 |
+
tile_idx = cute.arch.block_idx()[0]
|
| 709 |
+
return SingleTileLPTBwdScheduler(params, tile_idx, loc=loc, ip=ip)
|
| 710 |
+
|
| 711 |
+
# called by host
|
| 712 |
+
@staticmethod
|
| 713 |
+
def get_grid_shape(
|
| 714 |
+
params: Params,
|
| 715 |
+
*,
|
| 716 |
+
loc=None,
|
| 717 |
+
ip=None,
|
| 718 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 719 |
+
return (params.total_blocks, Int32(1), Int32(1))
|
| 720 |
+
|
| 721 |
+
@cute.jit
|
| 722 |
+
def get_current_work(self, *, loc=None, ip=None) -> cutlass.utils.WorkTileInfo:
|
| 723 |
+
cluster_idx = self._tile_idx // self.params.cluster_shape_mn[0]
|
| 724 |
+
params = self.params
|
| 725 |
+
# Implement LPT scheduling coordinate calculation
|
| 726 |
+
bidhb, l2_mod = divmod(cluster_idx, params.l2_major_divmod)
|
| 727 |
+
# If we're in the last section (called residual), we don't want to divide by
|
| 728 |
+
# swizzle. Instead we want to divide by the remainder.
|
| 729 |
+
block, bidhb_residual = 0, 0
|
| 730 |
+
if bidhb < params.num_hb_quotient:
|
| 731 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_divmod)
|
| 732 |
+
else:
|
| 733 |
+
block, bidhb_residual = divmod(l2_mod, params.l2_minor_residual_divmod)
|
| 734 |
+
bidhb_actual = bidhb * params.l2_minor + bidhb_residual
|
| 735 |
+
batch_idx, head_idx = divmod(bidhb_actual, params.num_head_divmod)
|
| 736 |
+
if cutlass.const_expr(params.spt):
|
| 737 |
+
block = params.num_block - 1 - block
|
| 738 |
+
if cutlass.const_expr(params.cluster_shape_mn[0] > 1):
|
| 739 |
+
bidx_in_cluster = cute.arch.block_in_cluster_idx()
|
| 740 |
+
block = block * params.cluster_shape_mn[0] + bidx_in_cluster[0]
|
| 741 |
+
is_valid = self._tile_idx < params.total_blocks
|
| 742 |
+
return WorkTileInfo((Int32(block), Int32(head_idx), Int32(batch_idx), Int32(0)), is_valid)
|
| 743 |
+
|
| 744 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 745 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 746 |
+
|
| 747 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 748 |
+
pass
|
| 749 |
+
|
| 750 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 751 |
+
# Single tile scheduler - set to invalid tile_idx to indicate no more work
|
| 752 |
+
self._tile_idx = self.params.total_blocks
|
| 753 |
+
return self.get_current_work()
|
| 754 |
+
|
| 755 |
+
def __extract_mlir_values__(self):
|
| 756 |
+
values, self._values_pos = [], []
|
| 757 |
+
for obj in [self.params, self._tile_idx]:
|
| 758 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 759 |
+
values += obj_values
|
| 760 |
+
self._values_pos.append(len(obj_values))
|
| 761 |
+
return values
|
| 762 |
+
|
| 763 |
+
def __new_from_mlir_values__(self, values):
|
| 764 |
+
obj_list = []
|
| 765 |
+
for obj, n_items in zip([self.params, self._tile_idx], self._values_pos):
|
| 766 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 767 |
+
values = values[n_items:]
|
| 768 |
+
return self.__class__(*(tuple(obj_list)), loc=self._loc)
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
class SingleTileVarlenScheduler:
|
| 772 |
+
@dataclass
|
| 773 |
+
class Params(ParamsBase):
|
| 774 |
+
num_head: Int32
|
| 775 |
+
num_batch: Int32
|
| 776 |
+
total_q: Int32
|
| 777 |
+
num_splits: Int32
|
| 778 |
+
max_kvblock_in_l2: Int32
|
| 779 |
+
tile_shape_mn: cutlass.Constexpr[Tuple[int, int]]
|
| 780 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None
|
| 781 |
+
mSeqUsedQ: Optional[cute.Tensor] = None
|
| 782 |
+
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1
|
| 783 |
+
lpt: cutlass.Constexpr[bool] = False
|
| 784 |
+
is_split_kv: cutlass.Constexpr[bool] = False
|
| 785 |
+
head_swizzle: cutlass.Constexpr[bool] = False
|
| 786 |
+
cluster_shape_m: cutlass.Constexpr[int] = 1
|
| 787 |
+
scheduling_mode: cutlass.Constexpr[SchedulingMode] = SchedulingMode.STATIC
|
| 788 |
+
|
| 789 |
+
@staticmethod
|
| 790 |
+
@cute.jit
|
| 791 |
+
def create(
|
| 792 |
+
args: TileSchedulerArguments,
|
| 793 |
+
*,
|
| 794 |
+
scheduling_mode: SchedulingMode = SchedulingMode.STATIC,
|
| 795 |
+
loc=None,
|
| 796 |
+
ip=None,
|
| 797 |
+
) -> "SingleTileVarlenScheduler.Params":
|
| 798 |
+
assert scheduling_mode in (SchedulingMode.STATIC, SchedulingMode.CLC), (
|
| 799 |
+
f"Only STATIC and CLC are supported, got {scheduling_mode!r}"
|
| 800 |
+
)
|
| 801 |
+
size_l2 = 50 * 1024 * 1024 # 50 MB for K & V
|
| 802 |
+
# if backward, this is qdo block size
|
| 803 |
+
kv_block_size = (
|
| 804 |
+
(args.headdim + args.headdim_v) * args.element_size * args.tile_shape_mn[1]
|
| 805 |
+
)
|
| 806 |
+
# if backward, add dqaccum block size to calculate swizzle
|
| 807 |
+
if args.head_swizzle:
|
| 808 |
+
kv_block_size += args.headdim * 4 * args.tile_shape_mn[1]
|
| 809 |
+
max_kvblock_in_l2 = size_l2 // kv_block_size
|
| 810 |
+
assert args.mCuSeqlensQ is not None or args.mSeqUsedQ is not None, (
|
| 811 |
+
"At least one of mCuSeqlensQ or mSeqUsedQ must be provided"
|
| 812 |
+
)
|
| 813 |
+
assert args.cluster_shape_mn[1] == 1, "Only cluster_shape_mn[1] == 1 is supported"
|
| 814 |
+
# TODO: Support varlen CLC with cluster_shape_m > 1 by refactoring the
|
| 815 |
+
# flattened-tile decode so cluster unpacking semantics are explicit.
|
| 816 |
+
assert scheduling_mode != SchedulingMode.CLC or args.cluster_shape_mn[0] == 1, (
|
| 817 |
+
"Varlen CLC currently requires cluster_shape_mn[0] == 1"
|
| 818 |
+
)
|
| 819 |
+
return SingleTileVarlenScheduler.Params(
|
| 820 |
+
num_head=args.num_head,
|
| 821 |
+
num_batch=args.num_batch,
|
| 822 |
+
total_q=args.total_q,
|
| 823 |
+
num_splits=args.num_splits,
|
| 824 |
+
max_kvblock_in_l2=max_kvblock_in_l2,
|
| 825 |
+
tile_shape_mn=args.tile_shape_mn,
|
| 826 |
+
mCuSeqlensQ=args.mCuSeqlensQ,
|
| 827 |
+
mSeqUsedQ=args.mSeqUsedQ,
|
| 828 |
+
qhead_per_kvhead_packgqa=args.qhead_per_kvhead_packgqa,
|
| 829 |
+
lpt=args.lpt,
|
| 830 |
+
is_split_kv=args.is_split_kv,
|
| 831 |
+
head_swizzle=args.head_swizzle,
|
| 832 |
+
cluster_shape_m=args.cluster_shape_mn[0],
|
| 833 |
+
scheduling_mode=scheduling_mode,
|
| 834 |
+
)
|
| 835 |
+
|
| 836 |
+
def __init__(
|
| 837 |
+
self,
|
| 838 |
+
params: Params,
|
| 839 |
+
tile_idx: Int32,
|
| 840 |
+
split_idx: Int32,
|
| 841 |
+
clc: ClcState | None = None,
|
| 842 |
+
*,
|
| 843 |
+
loc=None,
|
| 844 |
+
ip=None,
|
| 845 |
+
):
|
| 846 |
+
self.params = params
|
| 847 |
+
self._tile_idx = tile_idx
|
| 848 |
+
self._split_idx = split_idx
|
| 849 |
+
self._is_first_block = True
|
| 850 |
+
self.clc = clc
|
| 851 |
+
self._loc = loc
|
| 852 |
+
self._ip = ip
|
| 853 |
+
|
| 854 |
+
@staticmethod
|
| 855 |
+
def to_underlying_arguments(
|
| 856 |
+
args: TileSchedulerArguments,
|
| 857 |
+
*,
|
| 858 |
+
scheduling_mode: SchedulingMode = SchedulingMode.STATIC,
|
| 859 |
+
loc=None,
|
| 860 |
+
ip=None,
|
| 861 |
+
) -> Params:
|
| 862 |
+
return SingleTileVarlenScheduler.Params.create(
|
| 863 |
+
args, scheduling_mode=scheduling_mode, loc=loc, ip=ip
|
| 864 |
+
)
|
| 865 |
+
|
| 866 |
+
@staticmethod
|
| 867 |
+
@cute.jit
|
| 868 |
+
def clc_problem_shape(params: Params):
|
| 869 |
+
return ClcDynamicPersistentTileSchedulerParams(
|
| 870 |
+
problem_shape_ntile_mnl=SingleTileVarlenScheduler.get_grid_shape(params),
|
| 871 |
+
cluster_shape_mnk=(1, 1, 1),
|
| 872 |
+
)
|
| 873 |
+
|
| 874 |
+
@staticmethod
|
| 875 |
+
@cute.jit
|
| 876 |
+
def create(
|
| 877 |
+
params: Params, clc: ClcState | None = None, *, loc=None, ip=None
|
| 878 |
+
) -> "SingleTileVarlenScheduler":
|
| 879 |
+
if const_expr(params.scheduling_mode == SchedulingMode.CLC):
|
| 880 |
+
block_idx = cute.arch.block_idx()
|
| 881 |
+
split_idx = Int32(0)
|
| 882 |
+
if const_expr(params.is_split_kv):
|
| 883 |
+
split_idx = block_idx[1]
|
| 884 |
+
return SingleTileVarlenScheduler(
|
| 885 |
+
params,
|
| 886 |
+
block_idx[0],
|
| 887 |
+
split_idx,
|
| 888 |
+
clc,
|
| 889 |
+
loc=loc,
|
| 890 |
+
ip=ip,
|
| 891 |
+
)
|
| 892 |
+
tile_idx, split_idx, _ = cute.arch.block_idx()
|
| 893 |
+
return SingleTileVarlenScheduler(params, tile_idx, split_idx, loc=loc, ip=ip)
|
| 894 |
+
|
| 895 |
+
# called by host
|
| 896 |
+
@staticmethod
|
| 897 |
+
def get_grid_shape(
|
| 898 |
+
params: Params,
|
| 899 |
+
*,
|
| 900 |
+
loc=None,
|
| 901 |
+
ip=None,
|
| 902 |
+
) -> Tuple[Int32, Int32, Int32]:
|
| 903 |
+
total_blocks_max = (
|
| 904 |
+
params.total_q
|
| 905 |
+
+ params.num_batch * (params.cluster_shape_m * params.tile_shape_mn[0] - 1)
|
| 906 |
+
) // params.tile_shape_mn[0]
|
| 907 |
+
# Round down to nearest multiple of cluster since odd excess is always padding.
|
| 908 |
+
total_blocks_max = total_blocks_max // params.cluster_shape_m * params.cluster_shape_m
|
| 909 |
+
return (total_blocks_max * params.num_head, params.num_splits, Int32(1))
|
| 910 |
+
|
| 911 |
+
@cute.jit
|
| 912 |
+
def _get_num_m_blocks(self, lane: Int32, bidb_start: Int32) -> Int32:
|
| 913 |
+
params = self.params
|
| 914 |
+
batch_idx = lane + bidb_start
|
| 915 |
+
if cutlass.const_expr(params.mSeqUsedQ is not None):
|
| 916 |
+
seqlen = Int32(0)
|
| 917 |
+
if batch_idx < params.num_batch:
|
| 918 |
+
seqlen = params.mSeqUsedQ[batch_idx]
|
| 919 |
+
else:
|
| 920 |
+
assert params.mCuSeqlensQ is not None
|
| 921 |
+
cur_cu_seqlen = Int32(0)
|
| 922 |
+
if batch_idx <= params.num_batch:
|
| 923 |
+
cur_cu_seqlen = params.mCuSeqlensQ[batch_idx]
|
| 924 |
+
next_cu_seqlen = cute.arch.shuffle_sync_down(cur_cu_seqlen, offset=1)
|
| 925 |
+
seqlen = next_cu_seqlen - cur_cu_seqlen
|
| 926 |
+
if cutlass.const_expr(params.qhead_per_kvhead_packgqa > 1):
|
| 927 |
+
seqlen *= params.qhead_per_kvhead_packgqa
|
| 928 |
+
return (
|
| 929 |
+
cute.ceil_div(cute.ceil_div(seqlen, params.tile_shape_mn[0]), params.cluster_shape_m)
|
| 930 |
+
if batch_idx < params.num_batch and lane < cute.arch.WARP_SIZE - 1
|
| 931 |
+
else Int32(0)
|
| 932 |
+
)
|
| 933 |
+
|
| 934 |
+
@cute.jit
|
| 935 |
+
def _varlen_coord_map(self) -> WorkTileInfo:
|
| 936 |
+
"""Map self._tile_idx to (block, head, batch) via warp-level prefix sums."""
|
| 937 |
+
params = self.params
|
| 938 |
+
lane_idx = cute.arch.lane_idx()
|
| 939 |
+
num_m_blocks = self._get_num_m_blocks(lane_idx, bidb_start=0)
|
| 940 |
+
num_m_blocks_cumulative = utils.warp_prefix_sum(num_m_blocks, lane_idx)
|
| 941 |
+
# Total number of blocks for the next 31 batches
|
| 942 |
+
m_blocks_in_group = cute.arch.shuffle_sync(num_m_blocks_cumulative, cute.arch.WARP_SIZE - 1)
|
| 943 |
+
# Same for all lanes
|
| 944 |
+
group_end_tile = m_blocks_in_group * params.num_head
|
| 945 |
+
# if cute.arch.thread_idx()[0] == 128 + 31: cute.printf("SingleTileVarlenScheduler: tile_idx=%d, group_end_tile = %d, num_m_blocks=%d, num_m_blocks_cumulative = %d, m_blocks_in_group = %d", self._tile_idx, group_end_tile, num_m_blocks, num_m_blocks_cumulative, m_blocks_in_group)
|
| 946 |
+
block, head_idx, batch_idx = Int32(0), Int32(0), Int32(0)
|
| 947 |
+
next_tile_idx = self._tile_idx // params.cluster_shape_m
|
| 948 |
+
while group_end_tile <= next_tile_idx:
|
| 949 |
+
batch_idx += cute.arch.WARP_SIZE - 1
|
| 950 |
+
if batch_idx >= params.num_batch:
|
| 951 |
+
batch_idx = Int32(params.num_batch)
|
| 952 |
+
group_end_tile = next_tile_idx + 1
|
| 953 |
+
else:
|
| 954 |
+
num_m_blocks = self._get_num_m_blocks(lane_idx, bidb_start=batch_idx)
|
| 955 |
+
num_m_blocks_cumulative = utils.warp_prefix_sum(num_m_blocks, lane_idx)
|
| 956 |
+
m_blocks_in_group = cute.arch.shuffle_sync(
|
| 957 |
+
num_m_blocks_cumulative, cute.arch.WARP_SIZE - 1
|
| 958 |
+
)
|
| 959 |
+
group_end_tile += m_blocks_in_group * params.num_head
|
| 960 |
+
is_valid = False
|
| 961 |
+
if batch_idx >= params.num_batch:
|
| 962 |
+
block, head_idx, batch_idx = Int32(0), Int32(0), Int32(params.num_batch)
|
| 963 |
+
else:
|
| 964 |
+
group_start_tile = group_end_tile - m_blocks_in_group * params.num_head
|
| 965 |
+
# if cute.arch.thread_idx()[0] == 128 + 31: cute.printf("SingleTileVarlenScheduler: tile_idx=%d, group_end_tile = %d, num_m_blocks=%d, batch_idx = %d", self._tile_idx, group_end_tile, num_m_blocks, batch_idx)
|
| 966 |
+
# The next problem to process is the first one that does not have ending tile position
|
| 967 |
+
# that is greater than or equal to tile index.
|
| 968 |
+
batch_idx_in_group = cute.arch.popc(
|
| 969 |
+
cute.arch.vote_ballot_sync(
|
| 970 |
+
group_start_tile + num_m_blocks_cumulative * params.num_head <= next_tile_idx
|
| 971 |
+
)
|
| 972 |
+
)
|
| 973 |
+
batch_idx += batch_idx_in_group
|
| 974 |
+
num_m_blocks_prev_lane = (
|
| 975 |
+
0
|
| 976 |
+
if batch_idx_in_group == 0
|
| 977 |
+
else cute.arch.shuffle_sync(num_m_blocks_cumulative, batch_idx_in_group - 1)
|
| 978 |
+
)
|
| 979 |
+
num_m_blocks = cute.arch.shuffle_sync(num_m_blocks, batch_idx_in_group)
|
| 980 |
+
mh_block = next_tile_idx - group_start_tile - num_m_blocks_prev_lane * params.num_head
|
| 981 |
+
if cutlass.const_expr(params.lpt or params.head_swizzle):
|
| 982 |
+
# This is a version of the SingleTileLPTScheduler, complicated by the fact that
|
| 983 |
+
# the seqlen can vary per batch.
|
| 984 |
+
# TODO: is there any case where num_m_blocks is 0?
|
| 985 |
+
# TODO: by right we should read the seqlen_kv but we're assuming seqlen_q == seqlen_k here
|
| 986 |
+
num_n_blocks = (
|
| 987 |
+
num_m_blocks
|
| 988 |
+
* params.tile_shape_mn[0]
|
| 989 |
+
* params.cluster_shape_m
|
| 990 |
+
// params.qhead_per_kvhead_packgqa
|
| 991 |
+
// params.tile_shape_mn[1]
|
| 992 |
+
)
|
| 993 |
+
# nheads_in_l2 = min(max(self.max_kvblock_in_l2 // num_n_blocks, 1), self.num_head)
|
| 994 |
+
# Seems faster to have this be a power of 2
|
| 995 |
+
nheads_in_l2 = (
|
| 996 |
+
16
|
| 997 |
+
if num_n_blocks * 16 <= params.max_kvblock_in_l2
|
| 998 |
+
else (
|
| 999 |
+
8
|
| 1000 |
+
if num_n_blocks * 8 <= params.max_kvblock_in_l2
|
| 1001 |
+
else (
|
| 1002 |
+
4
|
| 1003 |
+
if num_n_blocks * 4 <= params.max_kvblock_in_l2
|
| 1004 |
+
else (2 if num_n_blocks * 2 <= params.max_kvblock_in_l2 else 1)
|
| 1005 |
+
)
|
| 1006 |
+
)
|
| 1007 |
+
)
|
| 1008 |
+
nheads_in_l2 = min(nheads_in_l2, params.num_head)
|
| 1009 |
+
mh_in_l2 = nheads_in_l2 * num_m_blocks
|
| 1010 |
+
section_idx = mh_block // mh_in_l2
|
| 1011 |
+
l2_mod = mh_block - section_idx * mh_in_l2
|
| 1012 |
+
# Deal with tail section
|
| 1013 |
+
nheads_in_this_section = (
|
| 1014 |
+
nheads_in_l2
|
| 1015 |
+
if nheads_in_l2 * (section_idx + 1) <= params.num_head
|
| 1016 |
+
else params.num_head - section_idx * nheads_in_l2
|
| 1017 |
+
)
|
| 1018 |
+
block = l2_mod // nheads_in_this_section
|
| 1019 |
+
head_idx_residual = l2_mod - block * nheads_in_this_section
|
| 1020 |
+
head_idx = section_idx * nheads_in_l2 + head_idx_residual
|
| 1021 |
+
if cutlass.const_expr(params.lpt):
|
| 1022 |
+
block = num_m_blocks - 1 - block
|
| 1023 |
+
else:
|
| 1024 |
+
head_idx = mh_block // num_m_blocks
|
| 1025 |
+
block = mh_block - head_idx * num_m_blocks
|
| 1026 |
+
is_valid = self._is_first_block and batch_idx < params.num_batch
|
| 1027 |
+
if cutlass.const_expr(params.cluster_shape_m > 1):
|
| 1028 |
+
bidx_in_cluster = cute.arch.block_in_cluster_idx()
|
| 1029 |
+
block = block * params.cluster_shape_m + bidx_in_cluster[0]
|
| 1030 |
+
# if cute.arch.thread_idx()[0] == 128: cute.printf("SingleTileVarlenScheduler: tile_idx=%d, batch_idx=%d, head_idx=%d, block=%d, is_valid = %d", self._tile_idx, batch_idx, head_idx, block, is_valid)
|
| 1031 |
+
split_idx = self._split_idx if const_expr(params.is_split_kv) else Int32(0)
|
| 1032 |
+
return WorkTileInfo((Int32(block), Int32(head_idx), Int32(batch_idx), split_idx), is_valid)
|
| 1033 |
+
|
| 1034 |
+
@cute.jit
|
| 1035 |
+
def get_current_work(self, *, loc=None, ip=None) -> WorkTileInfo:
|
| 1036 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 1037 |
+
clc_work = self.clc.get_current_work()
|
| 1038 |
+
# Default to grid_dim (one past last valid flat index) so _varlen_coord_map
|
| 1039 |
+
# returns is_valid=False when CLC is exhausted. CLC tile_idx is garbage when
|
| 1040 |
+
# invalid, so we can't trust it. Local-then-assign avoids CuTe DSL structural
|
| 1041 |
+
# mismatch on self inside the runtime if.
|
| 1042 |
+
new_tile_idx = cute.arch.grid_dim()[0]
|
| 1043 |
+
new_split_idx = Int32(0)
|
| 1044 |
+
if clc_work.is_valid_tile:
|
| 1045 |
+
new_tile_idx = clc_work.tile_idx[0]
|
| 1046 |
+
if const_expr(self.params.is_split_kv):
|
| 1047 |
+
new_split_idx = clc_work.tile_idx[1]
|
| 1048 |
+
self._tile_idx = new_tile_idx
|
| 1049 |
+
self._split_idx = new_split_idx
|
| 1050 |
+
return self._varlen_coord_map()
|
| 1051 |
+
|
| 1052 |
+
@cute.jit
|
| 1053 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 1054 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 1055 |
+
clc_work = self.clc.initial_work_tile_info()
|
| 1056 |
+
# See get_current_work for why grid_dim and local-then-assign.
|
| 1057 |
+
new_tile_idx = cute.arch.grid_dim()[0]
|
| 1058 |
+
new_split_idx = Int32(0)
|
| 1059 |
+
if clc_work.is_valid_tile:
|
| 1060 |
+
new_tile_idx = clc_work.tile_idx[0]
|
| 1061 |
+
if const_expr(self.params.is_split_kv):
|
| 1062 |
+
new_split_idx = clc_work.tile_idx[1]
|
| 1063 |
+
self._tile_idx = new_tile_idx
|
| 1064 |
+
self._split_idx = new_split_idx
|
| 1065 |
+
return self._varlen_coord_map()
|
| 1066 |
+
|
| 1067 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 1068 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 1069 |
+
self.clc.prefetch_next_work(loc=loc, ip=ip)
|
| 1070 |
+
|
| 1071 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 1072 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 1073 |
+
self.clc.consumer_wait(loc=loc, ip=ip)
|
| 1074 |
+
work = self.get_current_work()
|
| 1075 |
+
self.clc.consumer_release(loc=loc, ip=ip)
|
| 1076 |
+
return work
|
| 1077 |
+
self._is_first_block = False
|
| 1078 |
+
return self.get_current_work()
|
| 1079 |
+
|
| 1080 |
+
def producer_tail(self, *, loc=None, ip=None):
|
| 1081 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 1082 |
+
self.clc.producer_tail(loc=loc, ip=ip)
|
| 1083 |
+
|
| 1084 |
+
def __extract_mlir_values__(self):
|
| 1085 |
+
values, self._values_pos = [], []
|
| 1086 |
+
objs = [self.params, self._tile_idx, self._split_idx]
|
| 1087 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 1088 |
+
objs += [self.clc]
|
| 1089 |
+
for obj in objs:
|
| 1090 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 1091 |
+
values += obj_values
|
| 1092 |
+
self._values_pos.append(len(obj_values))
|
| 1093 |
+
return values
|
| 1094 |
+
|
| 1095 |
+
def __new_from_mlir_values__(self, values):
|
| 1096 |
+
obj_list = []
|
| 1097 |
+
objs = [self.params, self._tile_idx, self._split_idx]
|
| 1098 |
+
if const_expr(self.params.scheduling_mode == SchedulingMode.CLC):
|
| 1099 |
+
objs += [self.clc]
|
| 1100 |
+
for obj, n_items in zip(objs, self._values_pos):
|
| 1101 |
+
obj_list.append(cutlass.new_from_mlir_values(obj, values[:n_items]))
|
| 1102 |
+
values = values[n_items:]
|
| 1103 |
+
return self.__class__(*obj_list, loc=self._loc)
|
| 1104 |
+
|
| 1105 |
+
|
| 1106 |
+
# -----------------------------------------------------------------------------
|
| 1107 |
+
# SM100 FMHA-specific schedulers (kept separate from generic schedulers).
|
| 1108 |
+
# -----------------------------------------------------------------------------
|
| 1109 |
+
|
| 1110 |
+
|
| 1111 |
+
class Sm100FmhaStaticTileSchedulerParams:
|
| 1112 |
+
"""A class to represent parameters for the FMHA (Fused Multi-Head Attention) static tile scheduler.
|
| 1113 |
+
|
| 1114 |
+
This class holds the configuration parameters needed to initialize and configure
|
| 1115 |
+
the tile scheduler for FMHA operations.
|
| 1116 |
+
|
| 1117 |
+
:ivar is_persistent: Whether to use persistent kernel mode.
|
| 1118 |
+
:type is_persistent: bool
|
| 1119 |
+
:ivar problem_shape_mbh: Problem shape in (M, B, H) format.
|
| 1120 |
+
:type problem_shape_mbh: cute.Shape
|
| 1121 |
+
"""
|
| 1122 |
+
|
| 1123 |
+
def __init__(
|
| 1124 |
+
self,
|
| 1125 |
+
is_persistent: bool,
|
| 1126 |
+
problem_shape_mbh: cute.Shape,
|
| 1127 |
+
*,
|
| 1128 |
+
loc=None,
|
| 1129 |
+
ip=None,
|
| 1130 |
+
):
|
| 1131 |
+
"""
|
| 1132 |
+
Initializes the Sm100FmhaStaticTileSchedulerParams with the given parameters.
|
| 1133 |
+
|
| 1134 |
+
:param is_persistent: Whether to use persistent kernel mode.
|
| 1135 |
+
:type is_persistent: bool
|
| 1136 |
+
:param problem_shape_mbh: Problem shape in (M, B, H) format.
|
| 1137 |
+
:type problem_shape_mbh: cute.Shape
|
| 1138 |
+
"""
|
| 1139 |
+
self.is_persistent = is_persistent
|
| 1140 |
+
self.problem_shape_mbh = problem_shape_mbh
|
| 1141 |
+
self._loc = loc
|
| 1142 |
+
self._ip = ip
|
| 1143 |
+
|
| 1144 |
+
def __extract_mlir_values__(self):
|
| 1145 |
+
values, self._values_pos = [], []
|
| 1146 |
+
for obj in [self.problem_shape_mbh]:
|
| 1147 |
+
obj_values = extract_mlir_values(obj)
|
| 1148 |
+
values += obj_values
|
| 1149 |
+
self._values_pos.append(len(obj_values))
|
| 1150 |
+
return values
|
| 1151 |
+
|
| 1152 |
+
def __new_from_mlir_values__(self, values):
|
| 1153 |
+
obj_list = []
|
| 1154 |
+
for obj, n_items in zip([self.problem_shape_mbh], self._values_pos):
|
| 1155 |
+
obj_list.append(new_from_mlir_values(obj, values[:n_items]))
|
| 1156 |
+
values = values[n_items:]
|
| 1157 |
+
return Sm100FmhaStaticTileSchedulerParams(
|
| 1158 |
+
self.is_persistent, *(tuple(obj_list)), loc=self._loc
|
| 1159 |
+
)
|
| 1160 |
+
|
| 1161 |
+
|
| 1162 |
+
class Sm100FmhaStaticTileScheduler:
|
| 1163 |
+
"""A static tile scheduler for FMHA (Fused Multi-Head Attention) operations.
|
| 1164 |
+
|
| 1165 |
+
This class manages the scheduling of work tiles for FMHA kernels, supporting
|
| 1166 |
+
both persistent and non-persistent kernel modes. It tracks the current work
|
| 1167 |
+
position and advances through the problem space efficiently.
|
| 1168 |
+
|
| 1169 |
+
:ivar _params: Scheduler parameters.
|
| 1170 |
+
:type _params: Sm100FmhaStaticTileSchedulerParams
|
| 1171 |
+
:ivar _blk_coord: Block coordinates.
|
| 1172 |
+
:type _blk_coord: cute.Coord
|
| 1173 |
+
:ivar _grid_shape: Grid shape for the kernel.
|
| 1174 |
+
:type _grid_shape: cute.Shape
|
| 1175 |
+
:ivar _is_persistent: Whether to use persistent kernel mode.
|
| 1176 |
+
:type _is_persistent: bool
|
| 1177 |
+
:ivar _current_work_linear_idx: Current linear work index.
|
| 1178 |
+
:type _current_work_linear_idx: Int32
|
| 1179 |
+
:ivar _problem_shape_mbh: Problem shape in (M, B, H) format.
|
| 1180 |
+
:type _problem_shape_mbh: cute.Layout
|
| 1181 |
+
:ivar _num_blocks: Number of blocks in the problem.
|
| 1182 |
+
:type _num_blocks: Int32
|
| 1183 |
+
:ivar _is_first_block: Whether this is the first block.
|
| 1184 |
+
:type _is_first_block: bool
|
| 1185 |
+
:ivar num_persistent_sm: Number of persistent SMs.
|
| 1186 |
+
:type num_persistent_sm: Int32
|
| 1187 |
+
"""
|
| 1188 |
+
|
| 1189 |
+
def __init__(
|
| 1190 |
+
self,
|
| 1191 |
+
params: Sm100FmhaStaticTileSchedulerParams,
|
| 1192 |
+
current_work_linear_idx: Int32,
|
| 1193 |
+
blk_coord: cute.Coord,
|
| 1194 |
+
grid_shape: cute.Shape,
|
| 1195 |
+
*,
|
| 1196 |
+
loc=None,
|
| 1197 |
+
ip=None,
|
| 1198 |
+
):
|
| 1199 |
+
"""
|
| 1200 |
+
Initializes the Sm100FmhaStaticTileScheduler with the given parameters.
|
| 1201 |
+
|
| 1202 |
+
:param params: Scheduler parameters.
|
| 1203 |
+
:type params: Sm100FmhaStaticTileSchedulerParams
|
| 1204 |
+
:param current_work_linear_idx: Current linear work index.
|
| 1205 |
+
:type current_work_linear_idx: Int32
|
| 1206 |
+
:param blk_coord: Block coordinates.
|
| 1207 |
+
:type blk_coord: cute.Coord
|
| 1208 |
+
:param grid_shape: Grid shape for the kernel.
|
| 1209 |
+
:type grid_shape: cute.Shape
|
| 1210 |
+
"""
|
| 1211 |
+
self._params = params
|
| 1212 |
+
self._blk_coord = blk_coord
|
| 1213 |
+
self._grid_shape = grid_shape
|
| 1214 |
+
self._is_persistent = params.is_persistent
|
| 1215 |
+
self._current_work_linear_idx = current_work_linear_idx
|
| 1216 |
+
self._problem_shape_mbh = cute.make_layout(params.problem_shape_mbh, loc=loc, ip=ip)
|
| 1217 |
+
self._num_blocks = cute.size(self._problem_shape_mbh, loc=loc, ip=ip)
|
| 1218 |
+
self._is_first_block = True
|
| 1219 |
+
self.num_persistent_sm = cute.size(grid_shape, loc=loc, ip=ip)
|
| 1220 |
+
self._loc = loc
|
| 1221 |
+
self._ip = ip
|
| 1222 |
+
|
| 1223 |
+
# called by host
|
| 1224 |
+
@staticmethod
|
| 1225 |
+
def get_grid_shape(
|
| 1226 |
+
params: Sm100FmhaStaticTileSchedulerParams,
|
| 1227 |
+
*,
|
| 1228 |
+
loc=None,
|
| 1229 |
+
ip=None,
|
| 1230 |
+
) -> cute.Shape:
|
| 1231 |
+
"""
|
| 1232 |
+
Determine the grid shape for the FMHA kernel.
|
| 1233 |
+
|
| 1234 |
+
For persistent kernels, the grid shape is limited by the number of SMs
|
| 1235 |
+
(Streaming Multiprocessors) available on the device. For non-persistent
|
| 1236 |
+
kernels, the grid shape matches the problem shape.
|
| 1237 |
+
|
| 1238 |
+
:param params: Scheduler parameters.
|
| 1239 |
+
:type params: Sm100FmhaStaticTileSchedulerParams
|
| 1240 |
+
|
| 1241 |
+
:return: Grid shape as (M, B, H) tuple.
|
| 1242 |
+
:rtype: cute.Shape
|
| 1243 |
+
"""
|
| 1244 |
+
if params.is_persistent:
|
| 1245 |
+
hardware_info = HardwareInfo()
|
| 1246 |
+
sm_count = hardware_info.get_device_multiprocessor_count()
|
| 1247 |
+
return (
|
| 1248 |
+
dsl_min(sm_count, cute.size(params.problem_shape_mbh, loc=loc, ip=ip)),
|
| 1249 |
+
1,
|
| 1250 |
+
1,
|
| 1251 |
+
)
|
| 1252 |
+
else:
|
| 1253 |
+
return params.problem_shape_mbh
|
| 1254 |
+
|
| 1255 |
+
@staticmethod
|
| 1256 |
+
def check_valid_work_for_seqlen_q(
|
| 1257 |
+
q_tiler: int,
|
| 1258 |
+
current_idx: Int32,
|
| 1259 |
+
seqlen_q: Int32,
|
| 1260 |
+
) -> Boolean:
|
| 1261 |
+
"""
|
| 1262 |
+
Check if the current work index is valid for the given query sequence length.
|
| 1263 |
+
|
| 1264 |
+
This method verifies that the current work tile index multiplied by the
|
| 1265 |
+
query tiler size is within the bounds of the query sequence length.
|
| 1266 |
+
|
| 1267 |
+
:param q_tiler: Query tiler size.
|
| 1268 |
+
:type q_tiler: int
|
| 1269 |
+
:param current_idx: Current work index.
|
| 1270 |
+
:type current_idx: Int32
|
| 1271 |
+
:param seqlen_q: Query sequence length.
|
| 1272 |
+
:type seqlen_q: Int32
|
| 1273 |
+
|
| 1274 |
+
:return: True if the work is valid, False otherwise.
|
| 1275 |
+
:rtype: Boolean
|
| 1276 |
+
"""
|
| 1277 |
+
return current_idx * q_tiler < seqlen_q
|
| 1278 |
+
|
| 1279 |
+
def get_current_work(self, *, loc=None, ip=None) -> cutlass.utils.WorkTileInfo:
|
| 1280 |
+
"""
|
| 1281 |
+
Get information about the current work tile.
|
| 1282 |
+
|
| 1283 |
+
Determines if the current work is valid and computes the tile coordinates
|
| 1284 |
+
based on whether the kernel is persistent or non-persistent.
|
| 1285 |
+
|
| 1286 |
+
:return: WorkTileInfo containing tile coordinates and validity flag.
|
| 1287 |
+
:rtype: WorkTileInfo
|
| 1288 |
+
"""
|
| 1289 |
+
is_valid = (
|
| 1290 |
+
self._current_work_linear_idx < self._num_blocks
|
| 1291 |
+
if self._is_persistent
|
| 1292 |
+
else self._is_first_block
|
| 1293 |
+
)
|
| 1294 |
+
|
| 1295 |
+
blk_coord = (0, 0, 0)
|
| 1296 |
+
if self._is_persistent:
|
| 1297 |
+
blk_coord = self._problem_shape_mbh.get_hier_coord(
|
| 1298 |
+
self._current_work_linear_idx, loc=loc, ip=ip
|
| 1299 |
+
)
|
| 1300 |
+
else:
|
| 1301 |
+
blk_coord = self._blk_coord
|
| 1302 |
+
|
| 1303 |
+
# cur_tile_coord is (mid, 0, (bid, hid))
|
| 1304 |
+
cur_tile_coord = (
|
| 1305 |
+
blk_coord[0],
|
| 1306 |
+
0,
|
| 1307 |
+
(blk_coord[1], blk_coord[2]),
|
| 1308 |
+
)
|
| 1309 |
+
|
| 1310 |
+
return cutlass.utils.WorkTileInfo(cur_tile_coord, is_valid)
|
| 1311 |
+
|
| 1312 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 1313 |
+
"""
|
| 1314 |
+
Get the initial work tile information.
|
| 1315 |
+
|
| 1316 |
+
:return: Initial WorkTileInfo.
|
| 1317 |
+
:rtype: WorkTileInfo
|
| 1318 |
+
"""
|
| 1319 |
+
return self.get_current_work(loc=loc, ip=ip)
|
| 1320 |
+
|
| 1321 |
+
def advance_to_next_work(self, *, advance_count=1, loc=None, ip=None):
|
| 1322 |
+
"""
|
| 1323 |
+
Advance to the next work tile and return it.
|
| 1324 |
+
|
| 1325 |
+
For persistent kernels, advances by the number of persistent SMs.
|
| 1326 |
+
For non-persistent kernels, marks that the first block has been processed.
|
| 1327 |
+
"""
|
| 1328 |
+
if self._is_persistent:
|
| 1329 |
+
self._current_work_linear_idx += advance_count * self.num_persistent_sm
|
| 1330 |
+
self._is_first_block = False
|
| 1331 |
+
return self.get_current_work()
|
| 1332 |
+
|
| 1333 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 1334 |
+
"""No-op for static scheduler."""
|
| 1335 |
+
pass
|
| 1336 |
+
|
| 1337 |
+
def producer_tail(self, *, loc=None, ip=None):
|
| 1338 |
+
"""No-op for static scheduler."""
|
| 1339 |
+
pass
|
| 1340 |
+
|
| 1341 |
+
def __extract_mlir_values__(self):
|
| 1342 |
+
values = extract_mlir_values(self._params)
|
| 1343 |
+
values.extend(extract_mlir_values(self._current_work_linear_idx))
|
| 1344 |
+
values.extend(extract_mlir_values(self._blk_coord))
|
| 1345 |
+
values.extend(extract_mlir_values(self._grid_shape))
|
| 1346 |
+
return values
|
| 1347 |
+
|
| 1348 |
+
def __new_from_mlir_values__(self, values):
|
| 1349 |
+
assert len(values) == 10
|
| 1350 |
+
new_params = new_from_mlir_values(self._params, values[0:3])
|
| 1351 |
+
new_current_work_linear_idx = new_from_mlir_values(
|
| 1352 |
+
self._current_work_linear_idx, [values[3]]
|
| 1353 |
+
)
|
| 1354 |
+
new_blk_coord = new_from_mlir_values(self._blk_coord, values[4:7])
|
| 1355 |
+
new_grid_shape = new_from_mlir_values(self._grid_shape, values[7:])
|
| 1356 |
+
return Sm100FmhaStaticTileScheduler(
|
| 1357 |
+
new_params, new_current_work_linear_idx, new_blk_coord, new_grid_shape
|
| 1358 |
+
)
|
| 1359 |
+
|
| 1360 |
+
|
| 1361 |
+
def compute_sm100_fmha_grid(
|
| 1362 |
+
o_shape: cute.Shape,
|
| 1363 |
+
cta_tiler: Tuple[int, int, int],
|
| 1364 |
+
is_persistent: bool,
|
| 1365 |
+
) -> Tuple[Sm100FmhaStaticTileSchedulerParams, Tuple[int, int, int]]:
|
| 1366 |
+
"""Compute grid parameters for FMHA (static scheduler).
|
| 1367 |
+
|
| 1368 |
+
The output tensor o has shape (s, d, ((h_r, h_k), b)).
|
| 1369 |
+
"""
|
| 1370 |
+
tile_sched_params = Sm100FmhaStaticTileSchedulerParams(
|
| 1371 |
+
is_persistent,
|
| 1372 |
+
(
|
| 1373 |
+
cute.ceil_div(cute.size(o_shape[0]), cta_tiler[0]),
|
| 1374 |
+
cute.size(o_shape[2][0]),
|
| 1375 |
+
cute.size(o_shape[2][1]),
|
| 1376 |
+
),
|
| 1377 |
+
)
|
| 1378 |
+
grid = Sm100FmhaStaticTileScheduler.get_grid_shape(tile_sched_params)
|
| 1379 |
+
return tile_sched_params, grid
|
| 1380 |
+
|
| 1381 |
+
|
| 1382 |
+
##############################################################################
|
| 1383 |
+
# Fmha CLC dynamic tile scheduler
|
| 1384 |
+
##############################################################################
|
| 1385 |
+
|
| 1386 |
+
|
| 1387 |
+
class Sm100FmhaClcDynamicTileSchedulerParams:
|
| 1388 |
+
"""Parameters for FMHA CLC dynamic persistent tile scheduler.
|
| 1389 |
+
|
| 1390 |
+
This class manages the layout of tiles for CLC (Cluster Launch Control)
|
| 1391 |
+
based dynamic scheduling, adapted for FMHA's (M, B, H) problem shape.
|
| 1392 |
+
|
| 1393 |
+
:ivar problem_shape_mbh: Problem shape in (M, B, H) format.
|
| 1394 |
+
:type problem_shape_mbh: cute.Shape
|
| 1395 |
+
:ivar cluster_shape_mnk: Cluster shape in (M, N, K) format.
|
| 1396 |
+
:type cluster_shape_mnk: cute.Shape
|
| 1397 |
+
"""
|
| 1398 |
+
|
| 1399 |
+
def __init__(
|
| 1400 |
+
self,
|
| 1401 |
+
problem_shape_mbh: cute.Shape,
|
| 1402 |
+
cluster_shape_mnk: cute.Shape,
|
| 1403 |
+
*,
|
| 1404 |
+
loc=None,
|
| 1405 |
+
ip=None,
|
| 1406 |
+
):
|
| 1407 |
+
self.problem_shape_mbh = problem_shape_mbh
|
| 1408 |
+
self._cluster_shape_mnk = cluster_shape_mnk
|
| 1409 |
+
self.cluster_shape_mn = cluster_shape_mnk[:2]
|
| 1410 |
+
self._loc = loc
|
| 1411 |
+
self._ip = ip
|
| 1412 |
+
|
| 1413 |
+
# FMHA uses linear indexing over (M, B, H), convert to (M, N, L) style
|
| 1414 |
+
# For FMHA: M dim is tile count along sequence, N=1, L=(B*H)
|
| 1415 |
+
self.problem_shape_ntile_mnl = (
|
| 1416 |
+
problem_shape_mbh[0], # M tiles
|
| 1417 |
+
1, # N tiles (always 1 for FMHA)
|
| 1418 |
+
problem_shape_mbh[1] * problem_shape_mbh[2], # L = B * H
|
| 1419 |
+
)
|
| 1420 |
+
|
| 1421 |
+
# Create layout for cluster-to-tile mapping
|
| 1422 |
+
self.problem_layout_ncluster_mnl = cute.make_layout(
|
| 1423 |
+
cute.ceil_div(self.problem_shape_ntile_mnl, cluster_shape_mnk[:2]),
|
| 1424 |
+
loc=loc,
|
| 1425 |
+
ip=ip,
|
| 1426 |
+
)
|
| 1427 |
+
|
| 1428 |
+
def __extract_mlir_values__(self):
|
| 1429 |
+
values, self._values_pos = [], []
|
| 1430 |
+
for obj in [
|
| 1431 |
+
self.problem_shape_mbh,
|
| 1432 |
+
self._cluster_shape_mnk,
|
| 1433 |
+
]:
|
| 1434 |
+
obj_values = extract_mlir_values(obj)
|
| 1435 |
+
values += obj_values
|
| 1436 |
+
self._values_pos.append(len(obj_values))
|
| 1437 |
+
return values
|
| 1438 |
+
|
| 1439 |
+
def __new_from_mlir_values__(self, values):
|
| 1440 |
+
obj_list = []
|
| 1441 |
+
values_copy = list(values)
|
| 1442 |
+
for obj, n_items in zip(
|
| 1443 |
+
[self.problem_shape_mbh, self._cluster_shape_mnk],
|
| 1444 |
+
self._values_pos,
|
| 1445 |
+
):
|
| 1446 |
+
obj_list.append(new_from_mlir_values(obj, values_copy[:n_items]))
|
| 1447 |
+
values_copy = values_copy[n_items:]
|
| 1448 |
+
return Sm100FmhaClcDynamicTileSchedulerParams(*(tuple(obj_list)), loc=self._loc)
|
| 1449 |
+
|
| 1450 |
+
def get_grid_shape(self, *, loc=None, ip=None) -> Tuple[int, int, int]:
|
| 1451 |
+
"""Compute grid shape aligned with cluster shape."""
|
| 1452 |
+
return cute.round_up(self.problem_shape_ntile_mnl, self._cluster_shape_mnk)
|
| 1453 |
+
|
| 1454 |
+
def clc_hw_params(self) -> ClcDynamicPersistentTileSchedulerParams:
|
| 1455 |
+
"""Return params for the upstream CLC hardware scheduler."""
|
| 1456 |
+
return ClcDynamicPersistentTileSchedulerParams(
|
| 1457 |
+
problem_shape_ntile_mnl=self.problem_shape_ntile_mnl,
|
| 1458 |
+
cluster_shape_mnk=self._cluster_shape_mnk,
|
| 1459 |
+
)
|
| 1460 |
+
|
| 1461 |
+
|
| 1462 |
+
class Sm100FmhaClcDynamicTileScheduler:
|
| 1463 |
+
"""CLC dynamic persistent tile scheduler for FMHA.
|
| 1464 |
+
|
| 1465 |
+
This scheduler uses Blackwell's Cluster Launch Control hardware mechanism
|
| 1466 |
+
for dynamic tile distribution, providing automatic load balancing.
|
| 1467 |
+
Adapted for FMHA's (M, B, H) problem shape.
|
| 1468 |
+
"""
|
| 1469 |
+
|
| 1470 |
+
def __init__(
|
| 1471 |
+
self,
|
| 1472 |
+
params: Sm100FmhaClcDynamicTileSchedulerParams,
|
| 1473 |
+
cta_id_in_cluster: cute.Coord,
|
| 1474 |
+
num_tiles_executed: Int32,
|
| 1475 |
+
clc_response_ptr: cute.Pointer,
|
| 1476 |
+
block_idx: Tuple,
|
| 1477 |
+
clc: ClcState = None,
|
| 1478 |
+
*,
|
| 1479 |
+
loc=None,
|
| 1480 |
+
ip=None,
|
| 1481 |
+
):
|
| 1482 |
+
self.params = params
|
| 1483 |
+
self.cta_id_in_cluster = cta_id_in_cluster
|
| 1484 |
+
self._num_tiles_executed = num_tiles_executed
|
| 1485 |
+
self._clc_response_ptr = clc_response_ptr
|
| 1486 |
+
self._block_idx = block_idx
|
| 1487 |
+
self.clc = clc
|
| 1488 |
+
self._loc = loc
|
| 1489 |
+
self._ip = ip
|
| 1490 |
+
|
| 1491 |
+
def __extract_mlir_values__(self):
|
| 1492 |
+
values = extract_mlir_values(self.cta_id_in_cluster)
|
| 1493 |
+
values.extend(extract_mlir_values(self._num_tiles_executed))
|
| 1494 |
+
values.extend(extract_mlir_values(self._clc_response_ptr))
|
| 1495 |
+
values.extend(extract_mlir_values(self._block_idx))
|
| 1496 |
+
if self.clc is not None:
|
| 1497 |
+
values.extend(extract_mlir_values(self.clc))
|
| 1498 |
+
return values
|
| 1499 |
+
|
| 1500 |
+
def __new_from_mlir_values__(self, values):
|
| 1501 |
+
new_cta_id_in_cluster = new_from_mlir_values(self.cta_id_in_cluster, values[0:3])
|
| 1502 |
+
new_num_tiles_executed = new_from_mlir_values(self._num_tiles_executed, [values[3]])
|
| 1503 |
+
new_clc_response_ptr = new_from_mlir_values(self._clc_response_ptr, [values[4]])
|
| 1504 |
+
new_block_idx = new_from_mlir_values(self._block_idx, values[5:8])
|
| 1505 |
+
new_clc = None
|
| 1506 |
+
if self.clc is not None:
|
| 1507 |
+
new_clc = new_from_mlir_values(self.clc, values[8:])
|
| 1508 |
+
return Sm100FmhaClcDynamicTileScheduler(
|
| 1509 |
+
self.params,
|
| 1510 |
+
new_cta_id_in_cluster,
|
| 1511 |
+
new_num_tiles_executed,
|
| 1512 |
+
new_clc_response_ptr,
|
| 1513 |
+
new_block_idx,
|
| 1514 |
+
new_clc,
|
| 1515 |
+
)
|
| 1516 |
+
|
| 1517 |
+
@staticmethod
|
| 1518 |
+
def create(
|
| 1519 |
+
params: Sm100FmhaClcDynamicTileSchedulerParams,
|
| 1520 |
+
block_idx: Tuple,
|
| 1521 |
+
grid_dim: Tuple,
|
| 1522 |
+
clc_response_ptr: cute.Pointer,
|
| 1523 |
+
clc: ClcState = None,
|
| 1524 |
+
*,
|
| 1525 |
+
loc=None,
|
| 1526 |
+
ip=None,
|
| 1527 |
+
):
|
| 1528 |
+
"""Create a CLC dynamic tile scheduler instance."""
|
| 1529 |
+
bidx, bidy, bidz = block_idx
|
| 1530 |
+
|
| 1531 |
+
# CTA id in cluster
|
| 1532 |
+
cta_id_in_cluster = (
|
| 1533 |
+
Int32(bidx % params.cluster_shape_mn[0]),
|
| 1534 |
+
Int32(bidy % params.cluster_shape_mn[1]),
|
| 1535 |
+
Int32(0),
|
| 1536 |
+
)
|
| 1537 |
+
|
| 1538 |
+
num_tiles_executed = Int32(0)
|
| 1539 |
+
|
| 1540 |
+
return Sm100FmhaClcDynamicTileScheduler(
|
| 1541 |
+
params,
|
| 1542 |
+
cta_id_in_cluster,
|
| 1543 |
+
num_tiles_executed,
|
| 1544 |
+
clc_response_ptr,
|
| 1545 |
+
block_idx,
|
| 1546 |
+
clc,
|
| 1547 |
+
)
|
| 1548 |
+
|
| 1549 |
+
@staticmethod
|
| 1550 |
+
def get_grid_shape(
|
| 1551 |
+
params: Sm100FmhaClcDynamicTileSchedulerParams,
|
| 1552 |
+
*,
|
| 1553 |
+
loc=None,
|
| 1554 |
+
ip=None,
|
| 1555 |
+
) -> Tuple[int, int, int]:
|
| 1556 |
+
"""Get grid shape for kernel launch."""
|
| 1557 |
+
return params.get_grid_shape(loc=loc, ip=ip)
|
| 1558 |
+
|
| 1559 |
+
def work_tile_info_from_clc_response(self, result_addr: cute.Pointer, *, loc=None, ip=None):
|
| 1560 |
+
"""Parse CLC response and convert to FMHA tile coordinates."""
|
| 1561 |
+
m_idx, n_idx, l_idx, vld = cute.arch.clc_response(result_addr, loc=loc, ip=ip)
|
| 1562 |
+
cute.arch.fence_proxy("async.shared", space="cta")
|
| 1563 |
+
|
| 1564 |
+
# CLC returns first CTA coordinates: m_idx=x, l_idx=z
|
| 1565 |
+
# l_idx is the L (batch) dimension; decode to (bid, hid)
|
| 1566 |
+
hid = l_idx % self.params.problem_shape_mbh[2]
|
| 1567 |
+
bid = l_idx // self.params.problem_shape_mbh[2]
|
| 1568 |
+
|
| 1569 |
+
cta_idx_in_cluster, cta_idy_in_cluster, _ = self.cta_id_in_cluster
|
| 1570 |
+
cur_tile_coord = (
|
| 1571 |
+
m_idx + cta_idx_in_cluster, # M dimension
|
| 1572 |
+
0, # N always 0 for FMHA
|
| 1573 |
+
(bid, hid), # (B, H) packed
|
| 1574 |
+
)
|
| 1575 |
+
|
| 1576 |
+
return cutlass.utils.WorkTileInfo(cur_tile_coord, vld)
|
| 1577 |
+
|
| 1578 |
+
def get_current_work(self, *, loc=None, ip=None):
|
| 1579 |
+
"""Get current work tile from CLC response."""
|
| 1580 |
+
return self.work_tile_info_from_clc_response(self._clc_response_ptr, loc=loc, ip=ip)
|
| 1581 |
+
|
| 1582 |
+
def initial_work_tile_info(self, *, loc=None, ip=None):
|
| 1583 |
+
"""Get initial work tile based on block index."""
|
| 1584 |
+
bidx, bidy, bidz = self._block_idx
|
| 1585 |
+
# bidz is the L (batch) dimension; decode to (bid, hid)
|
| 1586 |
+
hid = bidz % self.params.problem_shape_mbh[2]
|
| 1587 |
+
bid = bidz // self.params.problem_shape_mbh[2]
|
| 1588 |
+
return cutlass.utils.WorkTileInfo((bidx, 0, (bid, hid)), True)
|
| 1589 |
+
|
| 1590 |
+
def advance_to_next_work(self, *, loc=None, ip=None):
|
| 1591 |
+
"""Consumer-side advance: wait for next tile, read coordinates, release."""
|
| 1592 |
+
self.clc.consumer_wait(loc=loc, ip=ip)
|
| 1593 |
+
work = self.get_current_work(loc=loc, ip=ip)
|
| 1594 |
+
self.clc.consumer_release(loc=loc, ip=ip)
|
| 1595 |
+
self._num_tiles_executed += Int32(1)
|
| 1596 |
+
return work
|
| 1597 |
+
|
| 1598 |
+
def prefetch_next_work(self, *, loc=None, ip=None):
|
| 1599 |
+
"""Producer-side: issue CLC query for next tile."""
|
| 1600 |
+
self.clc.prefetch_next_work(loc=loc, ip=ip)
|
| 1601 |
+
|
| 1602 |
+
def producer_tail(self, *, loc=None, ip=None):
|
| 1603 |
+
"""Producer-side cleanup after last tile."""
|
| 1604 |
+
self.clc.producer_tail(loc=loc, ip=ip)
|
| 1605 |
+
|
| 1606 |
+
@property
|
| 1607 |
+
def num_tiles_executed(self) -> Int32:
|
| 1608 |
+
return self._num_tiles_executed
|
| 1609 |
+
|
| 1610 |
+
|
| 1611 |
+
def compute_sm100_fmha_grid_clc(
|
| 1612 |
+
o_shape: cute.Shape,
|
| 1613 |
+
cta_tiler: Tuple[int, int, int],
|
| 1614 |
+
cluster_shape_mnk: Tuple[int, int, int],
|
| 1615 |
+
) -> Tuple[Sm100FmhaClcDynamicTileSchedulerParams, Tuple[int, int, int]]:
|
| 1616 |
+
"""Compute grid parameters for FMHA with CLC dynamic scheduling."""
|
| 1617 |
+
problem_shape_mbh = (
|
| 1618 |
+
cute.ceil_div(cute.size(o_shape[0]), cta_tiler[0]),
|
| 1619 |
+
cute.size(o_shape[2][0]),
|
| 1620 |
+
cute.size(o_shape[2][1]),
|
| 1621 |
+
)
|
| 1622 |
+
tile_sched_params = Sm100FmhaClcDynamicTileSchedulerParams(problem_shape_mbh, cluster_shape_mnk)
|
| 1623 |
+
grid = Sm100FmhaClcDynamicTileScheduler.get_grid_shape(tile_sched_params)
|
| 1624 |
+
return tile_sched_params, grid
|
| 1625 |
+
|
| 1626 |
+
|
| 1627 |
+
##############################################################################
|
| 1628 |
+
# Fused Mask
|
| 1629 |
+
##############################################################################
|
| 1630 |
+
|
| 1631 |
+
|
| 1632 |
+
def make_sm100_thread_cooperative_group(size: int):
|
| 1633 |
+
return cutlass.pipeline.CooperativeGroup(cutlass.pipeline.Agent.Thread, size)
|
| 1634 |
+
|
| 1635 |
+
|
| 1636 |
+
SM100_TMEM_CAPACITY_COLUMNS = 512
|
build/torch-cuda/flashrt_fa4/cute/utils.py
ADDED
|
@@ -0,0 +1,948 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import hashlib
|
| 5 |
+
import inspect
|
| 6 |
+
import os
|
| 7 |
+
from functools import partial
|
| 8 |
+
from typing import Type, Callable, Optional, Tuple, overload
|
| 9 |
+
|
| 10 |
+
import cutlass
|
| 11 |
+
import cutlass.cute as cute
|
| 12 |
+
|
| 13 |
+
from cutlass import Float32, Int32, const_expr
|
| 14 |
+
from cutlass.cute import FastDivmodDivisor
|
| 15 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 16 |
+
from cutlass._mlir.dialects import nvvm, llvm
|
| 17 |
+
from cutlass.cute.runtime import from_dlpack
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
from fa4_cute_runtime.quack import activation as quack_activation
|
| 21 |
+
|
| 22 |
+
_MIXER_ATTRS = ("__vec_size__",)
|
| 23 |
+
|
| 24 |
+
# Obtained from sollya:
|
| 25 |
+
# fpminimax(exp(x * log(2.0)), 1, [|1,24...|],[0;1],relative);
|
| 26 |
+
POLY_EX2 = {
|
| 27 |
+
0: (1.0),
|
| 28 |
+
1: (
|
| 29 |
+
1.0,
|
| 30 |
+
0.922497093677520751953125,
|
| 31 |
+
),
|
| 32 |
+
2: (
|
| 33 |
+
1.0,
|
| 34 |
+
0.6657850742340087890625,
|
| 35 |
+
0.330107033252716064453125,
|
| 36 |
+
),
|
| 37 |
+
3: (
|
| 38 |
+
1.0,
|
| 39 |
+
0.695146143436431884765625,
|
| 40 |
+
0.227564394474029541015625,
|
| 41 |
+
0.077119089663028717041015625,
|
| 42 |
+
),
|
| 43 |
+
4: (
|
| 44 |
+
1.0,
|
| 45 |
+
0.693042695522308349609375,
|
| 46 |
+
0.2412912547588348388671875,
|
| 47 |
+
5.2225358784198760986328125e-2,
|
| 48 |
+
1.3434938155114650726318359375e-2,
|
| 49 |
+
),
|
| 50 |
+
5: (
|
| 51 |
+
1.0,
|
| 52 |
+
0.693151414394378662109375,
|
| 53 |
+
0.24016360938549041748046875,
|
| 54 |
+
5.5802188813686370849609375e-2,
|
| 55 |
+
9.01452265679836273193359375e-3,
|
| 56 |
+
1.86810153536498546600341796875e-3,
|
| 57 |
+
),
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
_fa_clc_enabled: bool = os.environ.get("FA_CLC", "0") == "1"
|
| 61 |
+
_fa_disable_2cta_enabled: bool = os.environ.get("FA_DISABLE_2CTA", "0") == "1"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _is_cuda_12() -> bool:
|
| 65 |
+
"""Check if the CUDA toolkit version is 12.x.
|
| 66 |
+
|
| 67 |
+
2CTA forward non-causal has a codegen regression on CUDA 12 that causes
|
| 68 |
+
~18% slowdown compared to 1CTA. This is fixed in CUDA 13.x.
|
| 69 |
+
"""
|
| 70 |
+
try:
|
| 71 |
+
import torch
|
| 72 |
+
|
| 73 |
+
cuda_version = torch.version.cuda
|
| 74 |
+
if cuda_version is not None:
|
| 75 |
+
major = cuda_version.split(".")[0]
|
| 76 |
+
return int(major) == 12
|
| 77 |
+
except Exception:
|
| 78 |
+
pass
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
_fa_disable_2cta_cuda12: bool = _is_cuda_12()
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _get_use_clc_scheduler_default() -> bool:
|
| 86 |
+
return _fa_clc_enabled
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _get_disable_2cta_default() -> bool:
|
| 90 |
+
return _fa_disable_2cta_enabled or _fa_disable_2cta_cuda12
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _compute_base_hash(func: Callable) -> str:
|
| 94 |
+
"""Compute hash from source code or bytecode and closure values."""
|
| 95 |
+
try:
|
| 96 |
+
data = inspect.getsource(func).encode()
|
| 97 |
+
except (OSError, TypeError):
|
| 98 |
+
if hasattr(func, "__code__") and func.__code__ is not None:
|
| 99 |
+
data = func.__code__.co_code
|
| 100 |
+
else:
|
| 101 |
+
data = repr(func).encode()
|
| 102 |
+
|
| 103 |
+
hasher = hashlib.sha256(data)
|
| 104 |
+
|
| 105 |
+
if hasattr(func, "__closure__") and func.__closure__ is not None:
|
| 106 |
+
for cell in func.__closure__:
|
| 107 |
+
hasher.update(repr(cell.cell_contents).encode())
|
| 108 |
+
|
| 109 |
+
return hasher.hexdigest()
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def hash_callable(
|
| 113 |
+
func: Callable, mixer_attrs: Tuple[str] = _MIXER_ATTRS, set_cute_hash: bool = True
|
| 114 |
+
) -> str:
|
| 115 |
+
"""Hash a callable based on the source code or bytecode and closure values.
|
| 116 |
+
Fast-path: if the callable (or its __wrapped__ base) has a ``__cute_hash__``
|
| 117 |
+
attribute, that value is returned immediately as the base hash, then
|
| 118 |
+
metadata dunders are mixed in to produce the final dict-key hash.
|
| 119 |
+
set_cute_hash: whether or not to set func.__cute_hash__
|
| 120 |
+
"""
|
| 121 |
+
# Resolve base hash
|
| 122 |
+
if hasattr(func, "__cute_hash__"):
|
| 123 |
+
base_hash = func.__cute_hash__
|
| 124 |
+
else:
|
| 125 |
+
# Unwrap decorated functions (e.g., cute.jit wrappers).
|
| 126 |
+
base_func = getattr(func, "__wrapped__", func)
|
| 127 |
+
|
| 128 |
+
if hasattr(base_func, "__cute_hash__"):
|
| 129 |
+
base_hash = base_func.__cute_hash__
|
| 130 |
+
else:
|
| 131 |
+
base_hash = _compute_base_hash(base_func)
|
| 132 |
+
|
| 133 |
+
if set_cute_hash:
|
| 134 |
+
base_func.__cute_hash__ = base_hash
|
| 135 |
+
|
| 136 |
+
# Mix in mutable metadata dunders
|
| 137 |
+
mixer_values = tuple(getattr(func, attr, None) for attr in mixer_attrs)
|
| 138 |
+
|
| 139 |
+
if all(v is None for v in mixer_values):
|
| 140 |
+
return base_hash
|
| 141 |
+
|
| 142 |
+
hasher = hashlib.sha256(base_hash.encode())
|
| 143 |
+
|
| 144 |
+
for attr, val in zip(_MIXER_ATTRS, mixer_values):
|
| 145 |
+
hasher.update(f"{attr}={val!r}".encode())
|
| 146 |
+
|
| 147 |
+
return hasher.hexdigest()
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def create_softcap_scoremod(softcap_val):
|
| 151 |
+
@cute.jit
|
| 152 |
+
def scoremod_premask_fn(
|
| 153 |
+
acc_S_SSA, batch_idx, head_idx, q_idx, kv_idx, seqlen_info, aux_tensors
|
| 154 |
+
):
|
| 155 |
+
scores = acc_S_SSA / softcap_val
|
| 156 |
+
return softcap_val * cute.math.tanh(scores, fastmath=True)
|
| 157 |
+
|
| 158 |
+
return scoremod_premask_fn
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def create_softcap_scoremod_bwd(softcap_val):
|
| 162 |
+
@cute.jit
|
| 163 |
+
def scoremod_bwd_fn(
|
| 164 |
+
grad_out_SSA, score_SSA, batch_idx, head_idx, q_idx, kv_idx, seqlen_info, aux_tensors
|
| 165 |
+
):
|
| 166 |
+
scores = score_SSA / softcap_val
|
| 167 |
+
tanh_scores = cute.math.tanh(scores, fastmath=True)
|
| 168 |
+
return grad_out_SSA * (1.0 - tanh_scores * tanh_scores)
|
| 169 |
+
|
| 170 |
+
return scoremod_bwd_fn
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
LOG2_E = math.log2(math.e)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def compute_softmax_scale_log2(softmax_scale, score_mod):
|
| 177 |
+
"""Compute softmax_scale_log2 and adjusted softmax_scale based on whether score_mod is used.
|
| 178 |
+
|
| 179 |
+
When score_mod is None, fold the log2(e) factor into softmax_scale_log2 and set softmax_scale
|
| 180 |
+
to None. When score_mod is present, keep softmax_scale separate so it can be applied before
|
| 181 |
+
the score_mod, and set softmax_scale_log2 to just the change-of-base constant.
|
| 182 |
+
|
| 183 |
+
Returns (softmax_scale_log2, softmax_scale).
|
| 184 |
+
"""
|
| 185 |
+
if const_expr(score_mod is None):
|
| 186 |
+
return softmax_scale * LOG2_E, None
|
| 187 |
+
else:
|
| 188 |
+
return LOG2_E, softmax_scale
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def compute_fastdiv_mods(mQ, mK, qhead_per_kvhead, pack_gqa, aux_tensors, mPageTable=None):
|
| 192 |
+
"""Compute FastDivmodDivisor pairs for aux_tensors index computation.
|
| 193 |
+
|
| 194 |
+
Returns a (seqlen_q_divmod, seqlen_k_divmod) tuple, or None if aux_tensors is None.
|
| 195 |
+
"""
|
| 196 |
+
if const_expr(aux_tensors is None):
|
| 197 |
+
return None
|
| 198 |
+
seqlen_q = cute.size(mQ.shape[0]) // (qhead_per_kvhead if const_expr(pack_gqa) else 1)
|
| 199 |
+
seqlen_k = (
|
| 200 |
+
cute.size(mK.shape[0])
|
| 201 |
+
if const_expr(mPageTable is None)
|
| 202 |
+
else mK.shape[0] * mPageTable.shape[1]
|
| 203 |
+
)
|
| 204 |
+
return (FastDivmodDivisor(seqlen_q), FastDivmodDivisor(seqlen_k))
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def convert_from_dlpack(x, leading_dim, alignment=16, divisibility=1) -> cute.Tensor:
|
| 208 |
+
return (
|
| 209 |
+
from_dlpack(x, assumed_align=alignment)
|
| 210 |
+
.mark_layout_dynamic(leading_dim=leading_dim)
|
| 211 |
+
.mark_compact_shape_dynamic(
|
| 212 |
+
mode=leading_dim, stride_order=x.dim_order(), divisibility=divisibility
|
| 213 |
+
)
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def convert_from_dlpack_compact_dynamic(
|
| 218 |
+
x,
|
| 219 |
+
*,
|
| 220 |
+
dynamic_modes: tuple[int, ...],
|
| 221 |
+
alignment: int = 16,
|
| 222 |
+
stride_order=None,
|
| 223 |
+
divisibility: int = 1,
|
| 224 |
+
enable_tvm_ffi: bool = False,
|
| 225 |
+
) -> cute.Tensor:
|
| 226 |
+
"""Convert via DLPack and mark selected compact dimensions as dynamic."""
|
| 227 |
+
if isinstance(dynamic_modes, int):
|
| 228 |
+
dynamic_modes = (dynamic_modes,)
|
| 229 |
+
if stride_order is None:
|
| 230 |
+
stride_order = x.dim_order()
|
| 231 |
+
t = (
|
| 232 |
+
from_dlpack(x, assumed_align=alignment, enable_tvm_ffi=True)
|
| 233 |
+
if enable_tvm_ffi
|
| 234 |
+
else from_dlpack(x, assumed_align=alignment)
|
| 235 |
+
)
|
| 236 |
+
for mode in dynamic_modes:
|
| 237 |
+
t = t.mark_compact_shape_dynamic(
|
| 238 |
+
mode=mode,
|
| 239 |
+
stride_order=stride_order,
|
| 240 |
+
divisibility=divisibility,
|
| 241 |
+
)
|
| 242 |
+
return t
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def convert_from_dlpack_leading_static(
|
| 246 |
+
x, leading_dim, alignment=16, static_modes=None, stride_order=None
|
| 247 |
+
) -> cute.Tensor:
|
| 248 |
+
if stride_order is None:
|
| 249 |
+
stride_order = x.dim_order()
|
| 250 |
+
x_ = from_dlpack(x, assumed_align=alignment)
|
| 251 |
+
for i in range(x.ndim):
|
| 252 |
+
if i != leading_dim and (static_modes is None or i not in static_modes):
|
| 253 |
+
x_ = x_.mark_compact_shape_dynamic(mode=i, stride_order=stride_order)
|
| 254 |
+
return x_
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def make_tiled_copy_A(
|
| 258 |
+
copy_atom: cute.CopyAtom, tiled_mma: cute.TiledMma, swapAB: cutlass.Constexpr[bool] = False
|
| 259 |
+
) -> cute.TiledCopy:
|
| 260 |
+
if const_expr(swapAB):
|
| 261 |
+
return cute.make_tiled_copy_B(copy_atom, tiled_mma)
|
| 262 |
+
else:
|
| 263 |
+
return cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def make_tiled_copy_B(
|
| 267 |
+
copy_atom: cute.CopyAtom, tiled_mma: cute.TiledMma, swapAB: cutlass.Constexpr[bool] = False
|
| 268 |
+
) -> cute.TiledCopy:
|
| 269 |
+
if const_expr(swapAB):
|
| 270 |
+
return cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 271 |
+
else:
|
| 272 |
+
return cute.make_tiled_copy_B(copy_atom, tiled_mma)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def mma_make_fragment_A(
|
| 276 |
+
smem: cute.Tensor, thr_mma: cute.core.ThrMma, swapAB: cutlass.Constexpr[bool] = False
|
| 277 |
+
) -> cute.Tensor:
|
| 278 |
+
if const_expr(swapAB):
|
| 279 |
+
return mma_make_fragment_B(smem, thr_mma)
|
| 280 |
+
else:
|
| 281 |
+
return thr_mma.make_fragment_A(thr_mma.partition_A(smem))
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def mma_make_fragment_B(
|
| 285 |
+
smem: cute.Tensor, thr_mma: cute.core.ThrMma, swapAB: cutlass.Constexpr[bool] = False
|
| 286 |
+
) -> cute.Tensor:
|
| 287 |
+
if const_expr(swapAB):
|
| 288 |
+
return mma_make_fragment_A(smem, thr_mma)
|
| 289 |
+
else:
|
| 290 |
+
return thr_mma.make_fragment_B(thr_mma.partition_B(smem))
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def get_smem_store_atom(
|
| 294 |
+
arch: cutlass.Constexpr[int], element_type: Type[cute.Numeric], transpose: bool = False
|
| 295 |
+
) -> cute.CopyAtom:
|
| 296 |
+
if const_expr(arch < 90 or element_type.width != 16):
|
| 297 |
+
return cute.make_copy_atom(
|
| 298 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 299 |
+
element_type,
|
| 300 |
+
num_bits_per_copy=2 * element_type.width,
|
| 301 |
+
)
|
| 302 |
+
else:
|
| 303 |
+
return cute.make_copy_atom(
|
| 304 |
+
cute.nvgpu.warp.StMatrix8x8x16bOp(transpose=transpose, num_matrices=4),
|
| 305 |
+
element_type,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
@cute.jit
|
| 310 |
+
def warp_reduce(
|
| 311 |
+
val: cute.TensorSSA | cute.Numeric,
|
| 312 |
+
op: Callable,
|
| 313 |
+
width: cutlass.Constexpr[int] = cute.arch.WARP_SIZE,
|
| 314 |
+
) -> cute.TensorSSA | cute.Numeric:
|
| 315 |
+
if const_expr(isinstance(val, cute.TensorSSA)):
|
| 316 |
+
res = cute.make_fragment(val.shape, val.dtype)
|
| 317 |
+
res.store(val)
|
| 318 |
+
for i in cutlass.range_constexpr(cute.size(val.shape)):
|
| 319 |
+
res[i] = warp_reduce(res[i], op, width)
|
| 320 |
+
return res.load()
|
| 321 |
+
else:
|
| 322 |
+
for i in cutlass.range_constexpr(int(math.log2(width))):
|
| 323 |
+
val = op(val, cute.arch.shuffle_sync_bfly(val, offset=1 << i))
|
| 324 |
+
return val
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
@dsl_user_op
|
| 328 |
+
def smid(*, loc=None, ip=None) -> Int32:
|
| 329 |
+
return Int32(
|
| 330 |
+
llvm.inline_asm(
|
| 331 |
+
T.i32(),
|
| 332 |
+
[],
|
| 333 |
+
"mov.u32 $0, %smid;",
|
| 334 |
+
"=r",
|
| 335 |
+
has_side_effects=False,
|
| 336 |
+
is_align_stack=False,
|
| 337 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 338 |
+
)
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
@dsl_user_op
|
| 343 |
+
def fmax(
|
| 344 |
+
a: float | Float32, b: float | Float32, c: float | Float32 | None = None, *, loc=None, ip=None
|
| 345 |
+
) -> Float32:
|
| 346 |
+
from cutlass import CUDA_VERSION
|
| 347 |
+
|
| 348 |
+
# * NVVM call based on nvvm version
|
| 349 |
+
if CUDA_VERSION.major == 12 and CUDA_VERSION.minor == 9:
|
| 350 |
+
# Old API: requires explicit result type as first positional argument
|
| 351 |
+
return Float32(
|
| 352 |
+
nvvm.fmax(
|
| 353 |
+
T.f32(),
|
| 354 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 355 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 356 |
+
c=Float32(c).ir_value(loc=loc, ip=ip) if c is not None else None,
|
| 357 |
+
loc=loc,
|
| 358 |
+
ip=ip,
|
| 359 |
+
)
|
| 360 |
+
)
|
| 361 |
+
else:
|
| 362 |
+
# New API: infers result type automatically
|
| 363 |
+
return Float32(
|
| 364 |
+
nvvm.fmax(
|
| 365 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 366 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 367 |
+
c=Float32(c).ir_value(loc=loc, ip=ip) if c is not None else None,
|
| 368 |
+
loc=loc,
|
| 369 |
+
ip=ip,
|
| 370 |
+
)
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
@cute.jit
|
| 375 |
+
def fmax_reduce(
|
| 376 |
+
x: cute.TensorSSA, init_val: float | Float32 | None = None, arch: cutlass.Constexpr[int] = 80
|
| 377 |
+
) -> Float32:
|
| 378 |
+
if const_expr(arch < 100 or cute.size(x.shape) % 8 != 0):
|
| 379 |
+
# if const_expr(init_val is None):
|
| 380 |
+
# init_val = -cutlass.Float32.if
|
| 381 |
+
# return x.reduce(cute.ReductionOp.MAX, init_val, 0)
|
| 382 |
+
res = cute.make_fragment(x.shape, Float32)
|
| 383 |
+
res.store(x)
|
| 384 |
+
# local_max = [res[0], res[1]]
|
| 385 |
+
# for i in cutlass.range_constexpr(2, cute.size(x.shape), 2):
|
| 386 |
+
# local_max[0] = fmax(local_max[0], res[i + 0])
|
| 387 |
+
# local_max[1] = fmax(local_max[1], res[i + 1])
|
| 388 |
+
# local_max[0] = fmax(local_max[0], local_max[1])
|
| 389 |
+
# return local_max[0] if const_expr(init_val is None) else fmax(local_max[0], init_val)
|
| 390 |
+
local_max = [res[0], res[1], res[2], res[3]]
|
| 391 |
+
for i in cutlass.range_constexpr(4, cute.size(x.shape), 4):
|
| 392 |
+
local_max[0] = fmax(local_max[0], res[i + 0])
|
| 393 |
+
local_max[1] = fmax(local_max[1], res[i + 1])
|
| 394 |
+
local_max[2] = fmax(local_max[2], res[i + 2])
|
| 395 |
+
local_max[3] = fmax(local_max[3], res[i + 3])
|
| 396 |
+
local_max[0] = fmax(local_max[0], local_max[1])
|
| 397 |
+
local_max[2] = fmax(local_max[2], local_max[3])
|
| 398 |
+
local_max[0] = fmax(local_max[0], local_max[2])
|
| 399 |
+
return local_max[0] if const_expr(init_val is None) else fmax(local_max[0], init_val)
|
| 400 |
+
else:
|
| 401 |
+
# [2025-06-15] x.reduce only seems to use 50% 3-input max and 50% 2-input max
|
| 402 |
+
# We instead force the 3-input max.
|
| 403 |
+
res = cute.make_fragment(x.shape, Float32)
|
| 404 |
+
res.store(x)
|
| 405 |
+
local_max_0 = (
|
| 406 |
+
fmax(init_val, res[0], res[1])
|
| 407 |
+
if const_expr(init_val is not None)
|
| 408 |
+
else fmax(res[0], res[1])
|
| 409 |
+
)
|
| 410 |
+
local_max = [
|
| 411 |
+
local_max_0,
|
| 412 |
+
fmax(res[2], res[3]),
|
| 413 |
+
fmax(res[4], res[5]),
|
| 414 |
+
fmax(res[6], res[7]),
|
| 415 |
+
]
|
| 416 |
+
for i in cutlass.range_constexpr(8, cute.size(x.shape), 8):
|
| 417 |
+
local_max[0] = fmax(local_max[0], res[i], res[i + 1])
|
| 418 |
+
local_max[1] = fmax(local_max[1], res[i + 2], res[i + 3])
|
| 419 |
+
local_max[2] = fmax(local_max[2], res[i + 4], res[i + 5])
|
| 420 |
+
local_max[3] = fmax(local_max[3], res[i + 6], res[i + 7])
|
| 421 |
+
local_max[0] = fmax(local_max[0], local_max[1])
|
| 422 |
+
return fmax(local_max[0], local_max[2], local_max[3])
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
@cute.jit
|
| 426 |
+
def fadd_reduce(
|
| 427 |
+
x: cute.TensorSSA, init_val: float | Float32 | None = None, arch: cutlass.Constexpr[int] = 80
|
| 428 |
+
) -> Float32:
|
| 429 |
+
if const_expr(arch < 100 or cute.size(x.shape) % 8 != 0):
|
| 430 |
+
if const_expr(init_val is None):
|
| 431 |
+
init_val = Float32.zero
|
| 432 |
+
return x.reduce(cute.ReductionOp.ADD, init_val, 0)
|
| 433 |
+
# res = cute.make_fragment(x.shape, Float32)
|
| 434 |
+
# res.store(x)
|
| 435 |
+
# local_sum = [res[0], res[1], res[2], res[3]]
|
| 436 |
+
# for i in cutlass.range_constexpr(4, cute.size(x.shape), 4):
|
| 437 |
+
# local_sum[0] += res[i + 0]
|
| 438 |
+
# local_sum[1] += res[i + 1]
|
| 439 |
+
# local_sum[2] += res[i + 2]
|
| 440 |
+
# local_sum[3] += res[i + 3]
|
| 441 |
+
# local_sum[0] += local_sum[1]
|
| 442 |
+
# local_sum[2] += local_sum[3]
|
| 443 |
+
# local_sum[0] += local_sum[2]
|
| 444 |
+
# return local_sum[0] if const_expr(init_val is None) else local_sum[0] + init_val
|
| 445 |
+
else:
|
| 446 |
+
res = cute.make_fragment(x.shape, Float32)
|
| 447 |
+
res.store(x)
|
| 448 |
+
local_sum_0 = (
|
| 449 |
+
cute.arch.add_packed_f32x2((init_val, 0.0), (res[0], res[1]))
|
| 450 |
+
# cute.arch.add_packed_f32x2((init_val / 2, init_val / 2), (res[0], res[1]))
|
| 451 |
+
if const_expr(init_val is not None)
|
| 452 |
+
else (res[0], res[1])
|
| 453 |
+
)
|
| 454 |
+
local_sum = [local_sum_0, (res[2], res[3]), (res[4], res[5]), (res[6], res[7])]
|
| 455 |
+
for i in cutlass.range_constexpr(8, cute.size(x.shape), 8):
|
| 456 |
+
local_sum[0] = cute.arch.add_packed_f32x2(local_sum[0], (res[i + 0], res[i + 1]))
|
| 457 |
+
local_sum[1] = cute.arch.add_packed_f32x2(local_sum[1], (res[i + 2], res[i + 3]))
|
| 458 |
+
local_sum[2] = cute.arch.add_packed_f32x2(local_sum[2], (res[i + 4], res[i + 5]))
|
| 459 |
+
local_sum[3] = cute.arch.add_packed_f32x2(local_sum[3], (res[i + 6], res[i + 7]))
|
| 460 |
+
local_sum[0] = cute.arch.add_packed_f32x2(local_sum[0], local_sum[1])
|
| 461 |
+
local_sum[2] = cute.arch.add_packed_f32x2(local_sum[2], local_sum[3])
|
| 462 |
+
local_sum[0] = cute.arch.add_packed_f32x2(local_sum[0], local_sum[2])
|
| 463 |
+
return local_sum[0][0] + local_sum[0][1]
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
@dsl_user_op
|
| 467 |
+
def atomic_add_fp32(a: float | Float32, gmem_ptr: cute.Pointer, *, loc=None, ip=None) -> None:
|
| 468 |
+
# gmem_ptr_i64 = gmem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 469 |
+
# # cache_hint = cutlass.Int64(0x12F0000000000000)
|
| 470 |
+
# llvm.inline_asm(
|
| 471 |
+
# None,
|
| 472 |
+
# [gmem_ptr_i64, Float32(a).ir_value(loc=loc, ip=ip)],
|
| 473 |
+
# # [gmem_ptr_i64, Float32(a).ir_value(loc=loc, ip=ip), cache_hint.ir_value()],
|
| 474 |
+
# "red.global.add.f32 [$0], $1;",
|
| 475 |
+
# # "red.global.add.L2::cache_hint.f32 [$0], $1, 0x12F0000000000000;",
|
| 476 |
+
# # "red.global.add.L2::cache_hint.f32 [$0], $1, $2;",
|
| 477 |
+
# "l,f",
|
| 478 |
+
# # "l,f,l",
|
| 479 |
+
# has_side_effects=True,
|
| 480 |
+
# is_align_stack=False,
|
| 481 |
+
# asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 482 |
+
# )
|
| 483 |
+
nvvm.atomicrmw(
|
| 484 |
+
res=T.f32(), op=nvvm.AtomicOpKind.FADD, ptr=gmem_ptr.llvm_ptr, a=Float32(a).ir_value()
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
@dsl_user_op
|
| 489 |
+
def elem_pointer(x: cute.Tensor, coord: cute.Coord, *, loc=None, ip=None) -> cute.Pointer:
|
| 490 |
+
return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
@cute.jit
|
| 494 |
+
def predicate_k(tAcA: cute.Tensor, limit: cutlass.Int32) -> cute.Tensor:
|
| 495 |
+
# Only compute predicates for the "k" dimension. For the mn dimension, we will use "if"
|
| 496 |
+
tApA = cute.make_fragment(
|
| 497 |
+
cute.make_layout(
|
| 498 |
+
(cute.size(tAcA, mode=[0, 1]), cute.size(tAcA, mode=[1]), cute.size(tAcA, mode=[2])),
|
| 499 |
+
stride=(cute.size(tAcA, mode=[2]), 0, 1),
|
| 500 |
+
),
|
| 501 |
+
cutlass.Boolean,
|
| 502 |
+
)
|
| 503 |
+
for rest_v in cutlass.range_constexpr(tApA.shape[0]):
|
| 504 |
+
for rest_k in cutlass.range_constexpr(tApA.shape[2]):
|
| 505 |
+
tApA[rest_v, 0, rest_k] = cute.elem_less(tAcA[(0, rest_v), 0, rest_k][1], limit)
|
| 506 |
+
return tApA
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
def canonical_warp_group_idx(sync: bool = True) -> cutlass.Int32:
|
| 510 |
+
warp_group_idx = cute.arch.thread_idx()[0] // 128
|
| 511 |
+
if const_expr(sync):
|
| 512 |
+
warp_group_idx = cute.arch.make_warp_uniform(warp_group_idx)
|
| 513 |
+
return warp_group_idx
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
# @dsl_user_op
|
| 517 |
+
# def warp_vote_any_lt(a: float | Float32, b: float | Float32, *, loc=None, ip=None) -> cutlass.Boolean:
|
| 518 |
+
# mask = cutlass.Int32(-1)
|
| 519 |
+
# return cutlass.Boolean(
|
| 520 |
+
# llvm.inline_asm(
|
| 521 |
+
# T.i32(),
|
| 522 |
+
# [Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip), mask.ir_value(loc=loc, ip=ip)],
|
| 523 |
+
# ".pred p1, p2;\n"
|
| 524 |
+
# "setp.lt.f32 p1, $1, $2;\n"
|
| 525 |
+
# "vote.sync.any.pred p2, p1, $3;\n"
|
| 526 |
+
# "selp.u32 $0, 1, 0, p2;",
|
| 527 |
+
# # "selp.u32 $0, 1, 0, p1;",
|
| 528 |
+
# "=r,f,f,r",
|
| 529 |
+
# has_side_effects=False,
|
| 530 |
+
# is_align_stack=False,
|
| 531 |
+
# asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 532 |
+
# )
|
| 533 |
+
# )
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
@cute.jit
|
| 537 |
+
def shuffle_sync(
|
| 538 |
+
value: cute.Numeric,
|
| 539 |
+
offset: cute.typing.Int,
|
| 540 |
+
width: cutlass.Constexpr[int] = cute.arch.WARP_SIZE,
|
| 541 |
+
) -> cute.Numeric:
|
| 542 |
+
assert value.width % 32 == 0, "value type must be a multiple of 32 bits"
|
| 543 |
+
# 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000
|
| 544 |
+
mask = cute.arch.WARP_SIZE - width
|
| 545 |
+
clamp = cute.arch.WARP_SIZE - 1
|
| 546 |
+
mask_and_clamp = mask << 8 | clamp
|
| 547 |
+
# important: need stride 1 and not 0 for recast_tensor to work
|
| 548 |
+
val = cute.make_rmem_tensor(cute.make_layout((1,), stride=(1,)), type(value))
|
| 549 |
+
val[0] = value
|
| 550 |
+
val_i32 = cute.recast_tensor(val, cutlass.Int32)
|
| 551 |
+
for i in cutlass.range_constexpr(cute.size(val_i32)):
|
| 552 |
+
val_i32[i] = cute.arch.shuffle_sync(val_i32[i], offset, mask_and_clamp=mask_and_clamp)
|
| 553 |
+
return val[0]
|
| 554 |
+
|
| 555 |
+
|
| 556 |
+
@dsl_user_op
|
| 557 |
+
def shl_u32(val: cutlass.Uint32, shift: cutlass.Uint32, *, loc=None, ip=None) -> cutlass.Uint32:
|
| 558 |
+
"""
|
| 559 |
+
Left-shift val by shift bits using PTX shl.b32 (sign-agnostic).
|
| 560 |
+
|
| 561 |
+
Named ``shl_u32`` (not ``shl_b32``) because python type annotations
|
| 562 |
+
distinguish signed/unsigned.
|
| 563 |
+
|
| 564 |
+
PTX semantics (§9.7.8.8): "Shift amounts greater than the register width N
|
| 565 |
+
are clamped to N." So ``shl.b32 d, a, 32`` is well-defined and yields 0.
|
| 566 |
+
|
| 567 |
+
This differs from C/C++ and LLVM IR, where shifting by >= the type width is
|
| 568 |
+
undefined behavior. CuTeDSL compiles through MLIR -> LLVM IR, so a plain
|
| 569 |
+
Python-level ``Uint32(x) << Uint32(n)`` inherits LLVM's UB: the optimizer
|
| 570 |
+
may treat the result as poison and eliminate dependent code. Inline PTX
|
| 571 |
+
bypasses the LLVM IR shift entirely — the instruction is emitted verbatim
|
| 572 |
+
into PTX where clamping makes it safe for all shift amounts.
|
| 573 |
+
"""
|
| 574 |
+
return cutlass.Uint32(
|
| 575 |
+
llvm.inline_asm(
|
| 576 |
+
T.i32(),
|
| 577 |
+
[
|
| 578 |
+
cutlass.Uint32(val).ir_value(loc=loc, ip=ip),
|
| 579 |
+
cutlass.Uint32(shift).ir_value(loc=loc, ip=ip),
|
| 580 |
+
],
|
| 581 |
+
"shl.b32 $0, $1, $2;",
|
| 582 |
+
"=r,r,r",
|
| 583 |
+
has_side_effects=False,
|
| 584 |
+
is_align_stack=False,
|
| 585 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 586 |
+
)
|
| 587 |
+
)
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
@dsl_user_op
|
| 591 |
+
def shr_u32(val: cutlass.Uint32, shift: cutlass.Uint32, *, loc=None, ip=None) -> cutlass.Uint32:
|
| 592 |
+
"""
|
| 593 |
+
Unsigned right-shift val by shift bits using PTX shr.u32 (zero-fills).
|
| 594 |
+
|
| 595 |
+
See ``shl_u32`` docstring for why inline PTX is used instead of plain
|
| 596 |
+
CuTeDSL shift operators (LLVM shift-by-type-width UB).
|
| 597 |
+
"""
|
| 598 |
+
return cutlass.Uint32(
|
| 599 |
+
llvm.inline_asm(
|
| 600 |
+
T.i32(),
|
| 601 |
+
[
|
| 602 |
+
cutlass.Uint32(val).ir_value(loc=loc, ip=ip),
|
| 603 |
+
cutlass.Uint32(shift).ir_value(loc=loc, ip=ip),
|
| 604 |
+
],
|
| 605 |
+
"shr.u32 $0, $1, $2;",
|
| 606 |
+
"=r,r,r",
|
| 607 |
+
has_side_effects=False,
|
| 608 |
+
is_align_stack=False,
|
| 609 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 610 |
+
)
|
| 611 |
+
)
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
@cute.jit
|
| 615 |
+
def warp_prefix_sum(val: cutlass.Int32, lane: Optional[cutlass.Int32] = None) -> cutlass.Int32:
|
| 616 |
+
if const_expr(lane is None):
|
| 617 |
+
lane = cute.arch.lane_idx()
|
| 618 |
+
# if cute.arch.thread_idx()[0] >= 128 and cute.arch.thread_idx()[0] < 128 + 32 and cute.arch.block_idx()[0] == 0: cute.printf("tidx = %d, val = %d", cute.arch.thread_idx()[0] % 32, val)
|
| 619 |
+
for i in cutlass.range_constexpr(int(math.log2(cute.arch.WARP_SIZE))):
|
| 620 |
+
offset = 1 << i
|
| 621 |
+
# Very important that we set mask_and_clamp to 0
|
| 622 |
+
partial_sum = cute.arch.shuffle_sync_up(val, offset=offset, mask_and_clamp=0)
|
| 623 |
+
if lane >= offset:
|
| 624 |
+
val += partial_sum
|
| 625 |
+
# if cute.arch.thread_idx()[0] >= 128 and cute.arch.thread_idx()[0] < 128 + 32 and cute.arch.block_idx()[0] == 0: cute.printf("tidx = %d, partial_sum = %d, val = %d", cute.arch.thread_idx()[0] % 32, partial_sum, val)
|
| 626 |
+
return val
|
| 627 |
+
|
| 628 |
+
|
| 629 |
+
@dsl_user_op
|
| 630 |
+
def cvt_f16x2_f32(
|
| 631 |
+
a: float | Float32, b: float | Float32, to_dtype: Type, *, loc=None, ip=None
|
| 632 |
+
) -> cutlass.Int32:
|
| 633 |
+
assert to_dtype in [cutlass.BFloat16, cutlass.Float16], "to_dtype must be BFloat16 or Float16"
|
| 634 |
+
return cutlass.Int32(
|
| 635 |
+
llvm.inline_asm(
|
| 636 |
+
T.i32(),
|
| 637 |
+
[Float32(a).ir_value(loc=loc, ip=ip), Float32(b).ir_value(loc=loc, ip=ip)],
|
| 638 |
+
f"cvt.rn.{'bf16x2' if to_dtype is cutlass.BFloat16 else 'f16x2'}.f32 $0, $2, $1;",
|
| 639 |
+
"=r,f,f",
|
| 640 |
+
has_side_effects=False,
|
| 641 |
+
is_align_stack=False,
|
| 642 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 643 |
+
)
|
| 644 |
+
)
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
@overload
|
| 648 |
+
def cvt_f16(src: cute.Tensor, dst: cute.Tensor) -> None: ...
|
| 649 |
+
|
| 650 |
+
|
| 651 |
+
@overload
|
| 652 |
+
def cvt_f16(src: cute.Tensor, dtype: Type[cute.Numeric]) -> cute.Tensor: ...
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
@cute.jit
|
| 656 |
+
def cvt_f16(src: cute.Tensor, dst_or_dtype):
|
| 657 |
+
"""Convert Float32 tensor to Float16/BFloat16.
|
| 658 |
+
|
| 659 |
+
Args:
|
| 660 |
+
src: Source tensor with Float32 element type
|
| 661 |
+
dst_or_dtype: Either a destination tensor or a dtype (Float16/BFloat16)
|
| 662 |
+
|
| 663 |
+
Returns:
|
| 664 |
+
None if dst is a tensor, or a new tensor if dtype is provided
|
| 665 |
+
"""
|
| 666 |
+
if const_expr(isinstance(dst_or_dtype, type)):
|
| 667 |
+
# dtype variant: create new tensor and call the tensor variant
|
| 668 |
+
dtype = dst_or_dtype
|
| 669 |
+
dst = cute.make_fragment(src.shape, dtype)
|
| 670 |
+
cvt_f16(src, dst)
|
| 671 |
+
return dst
|
| 672 |
+
else:
|
| 673 |
+
# tensor variant: write to dst
|
| 674 |
+
dst = dst_or_dtype
|
| 675 |
+
assert cute.size(dst.shape) == cute.size(src.shape), "dst and src must have the same size"
|
| 676 |
+
assert cute.size(src.shape) % 2 == 0, "src must have an even number of elements"
|
| 677 |
+
assert dst.element_type in [cutlass.BFloat16, cutlass.Float16], (
|
| 678 |
+
"dst must be BFloat16 or Float16"
|
| 679 |
+
)
|
| 680 |
+
assert src.element_type is Float32, "src must be Float32"
|
| 681 |
+
dst_i32 = cute.recast_tensor(dst, cutlass.Int32)
|
| 682 |
+
assert cute.size(dst_i32.shape) * 2 == cute.size(src.shape)
|
| 683 |
+
for i in cutlass.range_constexpr(cute.size(dst_i32)):
|
| 684 |
+
dst_i32[i] = cvt_f16x2_f32(src[2 * i], src[2 * i + 1], dst.element_type)
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
@dsl_user_op
|
| 688 |
+
@cute.jit
|
| 689 |
+
def evaluate_polynomial(x: Float32, poly: Tuple[Float32, ...], *, loc=None, ip=None) -> Float32:
|
| 690 |
+
deg = len(poly) - 1
|
| 691 |
+
out = poly[deg]
|
| 692 |
+
for i in cutlass.range_constexpr(deg - 1, -1, -1):
|
| 693 |
+
out = out * x + poly[i]
|
| 694 |
+
return out
|
| 695 |
+
|
| 696 |
+
|
| 697 |
+
@dsl_user_op
|
| 698 |
+
@cute.jit
|
| 699 |
+
def evaluate_polynomial_2(
|
| 700 |
+
x: Float32, y: Float32, poly: Tuple[Float32, ...], *, loc=None, ip=None
|
| 701 |
+
) -> Tuple[Float32, Float32]:
|
| 702 |
+
deg = len(poly) - 1
|
| 703 |
+
out = (poly[deg], poly[deg])
|
| 704 |
+
for i in cutlass.range_constexpr(deg - 1, -1, -1):
|
| 705 |
+
out = cute.arch.fma_packed_f32x2(out, (x, y), (poly[i], poly[i]))
|
| 706 |
+
return out
|
| 707 |
+
|
| 708 |
+
|
| 709 |
+
@dsl_user_op
|
| 710 |
+
def add_round_down(x: float | Float32, y: float | Float32, *, loc=None, ip=None) -> Float32:
|
| 711 |
+
# There's probably a way to call llvm or nvvm to do this instead of ptx
|
| 712 |
+
return cutlass.Float32(
|
| 713 |
+
llvm.inline_asm(
|
| 714 |
+
T.f32(),
|
| 715 |
+
[Float32(x).ir_value(loc=loc, ip=ip), Float32(y).ir_value(loc=loc, ip=ip)],
|
| 716 |
+
"add.rm.ftz.f32 $0, $1, $2;",
|
| 717 |
+
"=f,f,f",
|
| 718 |
+
has_side_effects=False,
|
| 719 |
+
is_align_stack=False,
|
| 720 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 721 |
+
)
|
| 722 |
+
)
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
@dsl_user_op
|
| 726 |
+
def combine_int_frac_ex2(x_rounded: Float32, frac_ex2: Float32, *, loc=None, ip=None) -> Float32:
|
| 727 |
+
return cutlass.Float32(
|
| 728 |
+
llvm.inline_asm(
|
| 729 |
+
T.f32(),
|
| 730 |
+
[
|
| 731 |
+
Float32(x_rounded).ir_value(loc=loc, ip=ip),
|
| 732 |
+
Float32(frac_ex2).ir_value(loc=loc, ip=ip),
|
| 733 |
+
],
|
| 734 |
+
"{\n\t"
|
| 735 |
+
".reg .s32 x_rounded_i, frac_ex_i, x_rounded_e, out_i;\n\t"
|
| 736 |
+
"mov.b32 x_rounded_i, $1;\n\t"
|
| 737 |
+
"mov.b32 frac_ex_i, $2;\n\t"
|
| 738 |
+
"shl.b32 x_rounded_e, x_rounded_i, 23;\n\t"
|
| 739 |
+
# add.u32 generates IMAD instruction and add.s32 generates LEA instruction
|
| 740 |
+
# IMAD uses the FMA pipeline and LEA uses the ALU pipeline, afaik
|
| 741 |
+
"add.s32 out_i, x_rounded_e, frac_ex_i;\n\t"
|
| 742 |
+
"mov.b32 $0, out_i;\n\t"
|
| 743 |
+
"}\n",
|
| 744 |
+
"=f,f,f",
|
| 745 |
+
has_side_effects=False,
|
| 746 |
+
is_align_stack=False,
|
| 747 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 748 |
+
)
|
| 749 |
+
)
|
| 750 |
+
|
| 751 |
+
|
| 752 |
+
@dsl_user_op
|
| 753 |
+
def ex2_emulation(x: Float32, *, poly_degree: int = 3, loc=None, ip=None) -> Float32:
|
| 754 |
+
assert poly_degree in POLY_EX2, f"Polynomial degree {poly_degree} not supported"
|
| 755 |
+
# We assume x <= 127.0
|
| 756 |
+
fp32_round_int = float(2**23 + 2**22)
|
| 757 |
+
x_clamped = cute.arch.fmax(x, -127.0)
|
| 758 |
+
# We want to round down here, so that the fractional part is in [0, 1)
|
| 759 |
+
x_rounded = add_round_down(x_clamped, fp32_round_int, loc=loc, ip=ip)
|
| 760 |
+
# The integer floor of x is now in the last 8 bits of x_rounded
|
| 761 |
+
# We assume the next 2 ops round to nearest even. The rounding mode is important.
|
| 762 |
+
x_rounded_back = x_rounded - fp32_round_int
|
| 763 |
+
x_frac = x_clamped - x_rounded_back
|
| 764 |
+
x_frac_ex2 = evaluate_polynomial(x_frac, POLY_EX2[poly_degree], loc=loc, ip=ip)
|
| 765 |
+
return combine_int_frac_ex2(x_rounded, x_frac_ex2, loc=loc, ip=ip)
|
| 766 |
+
|
| 767 |
+
|
| 768 |
+
# TODO: check that the ex2_emulation_2 produces the same SASS as the ptx version
|
| 769 |
+
@dsl_user_op
|
| 770 |
+
def ex2_emulation_2(
|
| 771 |
+
x: Float32, y: Float32, *, poly_degree: int = 3, loc=None, ip=None
|
| 772 |
+
) -> Tuple[Float32, Float32]:
|
| 773 |
+
# We assume x <= 127.0 and y <= 127.0
|
| 774 |
+
fp32_round_int = float(2**23 + 2**22)
|
| 775 |
+
xy_clamped = (cute.arch.fmax(x, -127.0), cute.arch.fmax(y, -127.0))
|
| 776 |
+
# We want to round down here, so that the fractional part is in [0, 1)
|
| 777 |
+
xy_rounded = cute.arch.add_packed_f32x2(xy_clamped, (fp32_round_int, fp32_round_int), rnd="rm")
|
| 778 |
+
# The integer floor of x & y are now in the last 8 bits of xy_rounded
|
| 779 |
+
# We want the next 2 ops to round to nearest even. The rounding mode is important.
|
| 780 |
+
xy_rounded_back = quack_activation.sub_packed_f32x2(
|
| 781 |
+
xy_rounded, (fp32_round_int, fp32_round_int)
|
| 782 |
+
)
|
| 783 |
+
xy_frac = quack_activation.sub_packed_f32x2(xy_clamped, xy_rounded_back)
|
| 784 |
+
xy_frac_ex2 = evaluate_polynomial_2(*xy_frac, POLY_EX2[poly_degree], loc=loc, ip=ip)
|
| 785 |
+
x_out = combine_int_frac_ex2(xy_rounded[0], xy_frac_ex2[0], loc=loc, ip=ip)
|
| 786 |
+
y_out = combine_int_frac_ex2(xy_rounded[1], xy_frac_ex2[1], loc=loc, ip=ip)
|
| 787 |
+
return x_out, y_out
|
| 788 |
+
|
| 789 |
+
|
| 790 |
+
@dsl_user_op
|
| 791 |
+
def e2e_asm2(x: Float32, y: Float32, *, loc=None, ip=None) -> Tuple[Float32, Float32]:
|
| 792 |
+
out_f32x2 = llvm.inline_asm(
|
| 793 |
+
llvm.StructType.get_literal([T.f32(), T.f32()]),
|
| 794 |
+
[Float32(x).ir_value(loc=loc, ip=ip), Float32(y, loc=loc, ip=ip).ir_value()],
|
| 795 |
+
"{\n\t"
|
| 796 |
+
".reg .f32 f1, f2, f3, f4, f5, f6, f7;\n\t"
|
| 797 |
+
".reg .b64 l1, l2, l3, l4, l5, l6, l7, l8, l9, l10;\n\t"
|
| 798 |
+
".reg .s32 r1, r2, r3, r4, r5, r6, r7, r8;\n\t"
|
| 799 |
+
"max.ftz.f32 f1, $2, 0fC2FE0000;\n\t"
|
| 800 |
+
"max.ftz.f32 f2, $3, 0fC2FE0000;\n\t"
|
| 801 |
+
"mov.b64 l1, {f1, f2};\n\t"
|
| 802 |
+
"mov.f32 f3, 0f4B400000;\n\t"
|
| 803 |
+
"mov.b64 l2, {f3, f3};\n\t"
|
| 804 |
+
"add.rm.ftz.f32x2 l7, l1, l2;\n\t"
|
| 805 |
+
"sub.rn.ftz.f32x2 l8, l7, l2;\n\t"
|
| 806 |
+
"sub.rn.ftz.f32x2 l9, l1, l8;\n\t"
|
| 807 |
+
"mov.f32 f7, 0f3D9DF09D;\n\t"
|
| 808 |
+
"mov.b64 l6, {f7, f7};\n\t"
|
| 809 |
+
"mov.f32 f6, 0f3E6906A4;\n\t"
|
| 810 |
+
"mov.b64 l5, {f6, f6};\n\t"
|
| 811 |
+
"mov.f32 f5, 0f3F31F519;\n\t"
|
| 812 |
+
"mov.b64 l4, {f5, f5};\n\t"
|
| 813 |
+
"mov.f32 f4, 0f3F800000;\n\t"
|
| 814 |
+
"mov.b64 l3, {f4, f4};\n\t"
|
| 815 |
+
"fma.rn.ftz.f32x2 l10, l9, l6, l5;\n\t"
|
| 816 |
+
"fma.rn.ftz.f32x2 l10, l10, l9, l4;\n\t"
|
| 817 |
+
"fma.rn.ftz.f32x2 l10, l10, l9, l3;\n\t"
|
| 818 |
+
"mov.b64 {r1, r2}, l7;\n\t"
|
| 819 |
+
"mov.b64 {r3, r4}, l10;\n\t"
|
| 820 |
+
"shl.b32 r5, r1, 23;\n\t"
|
| 821 |
+
"add.s32 r7, r5, r3;\n\t"
|
| 822 |
+
"shl.b32 r6, r2, 23;\n\t"
|
| 823 |
+
"add.s32 r8, r6, r4;\n\t"
|
| 824 |
+
"mov.b32 $0, r7;\n\t"
|
| 825 |
+
"mov.b32 $1, r8;\n\t"
|
| 826 |
+
"}\n",
|
| 827 |
+
"=r,=r,f,f",
|
| 828 |
+
has_side_effects=False,
|
| 829 |
+
is_align_stack=False,
|
| 830 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 831 |
+
)
|
| 832 |
+
out0 = Float32(llvm.extractvalue(T.f32(), out_f32x2, [0], loc=loc, ip=ip))
|
| 833 |
+
out1 = Float32(llvm.extractvalue(T.f32(), out_f32x2, [1], loc=loc, ip=ip))
|
| 834 |
+
return out0, out1
|
| 835 |
+
|
| 836 |
+
|
| 837 |
+
@dsl_user_op
|
| 838 |
+
def domain_offset_aligned(
|
| 839 |
+
coord: cute.Coord, tensor: cute.Tensor, *, loc=None, ip=None
|
| 840 |
+
) -> cute.Tensor:
|
| 841 |
+
assert isinstance(tensor.iterator, cute.Pointer)
|
| 842 |
+
# We assume that applying the offset does not change the pointer alignment
|
| 843 |
+
new_ptr = cute.make_ptr(
|
| 844 |
+
tensor.element_type,
|
| 845 |
+
elem_pointer(tensor, coord).toint(),
|
| 846 |
+
tensor.memspace,
|
| 847 |
+
assumed_align=tensor.iterator.alignment,
|
| 848 |
+
)
|
| 849 |
+
return cute.make_tensor(new_ptr, tensor.layout)
|
| 850 |
+
|
| 851 |
+
|
| 852 |
+
@dsl_user_op
|
| 853 |
+
def warp_reduction(
|
| 854 |
+
val: cute.Numeric, op: Callable, *, threads_in_group: int = 32, loc=None, ip=None
|
| 855 |
+
) -> cute.Numeric:
|
| 856 |
+
"""Warp-wide reduction helper for a custom binary op."""
|
| 857 |
+
offset = threads_in_group // 2
|
| 858 |
+
while offset > 0:
|
| 859 |
+
val = op(
|
| 860 |
+
val,
|
| 861 |
+
cute.arch.shuffle_sync_bfly(
|
| 862 |
+
val, offset=offset, mask=-1, mask_and_clamp=31, loc=loc, ip=ip
|
| 863 |
+
),
|
| 864 |
+
)
|
| 865 |
+
offset //= 2
|
| 866 |
+
return val
|
| 867 |
+
|
| 868 |
+
|
| 869 |
+
warp_reduction_max = partial(
|
| 870 |
+
warp_reduction, op=lambda x, y: fmax(x, y) if isinstance(x, Float32) else max(x, y)
|
| 871 |
+
)
|
| 872 |
+
warp_reduction_sum = partial(warp_reduction, op=lambda x, y: x + y) # noqa: FURB118
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
@dsl_user_op
|
| 876 |
+
def make_cotiled_copy(
|
| 877 |
+
atom: cute.CopyAtom, atom_layout_tv: cute.Layout, data_layout: cute.Layout, *, loc=None, ip=None
|
| 878 |
+
) -> cute.TiledCopy:
|
| 879 |
+
"""Compatibility wrapper for deprecated CuTeDSL `make_cotiled_copy`."""
|
| 880 |
+
assert cute.is_static(atom_layout_tv.type), "atom_layout_tv must be static"
|
| 881 |
+
assert cute.is_static(data_layout.type), "data_layout must be static"
|
| 882 |
+
|
| 883 |
+
inv_layout_ = cute.left_inverse(data_layout, loc=loc, ip=ip)
|
| 884 |
+
inv_data_layout = cute.make_layout(
|
| 885 |
+
(inv_layout_.shape, (1)), stride=(inv_layout_.stride, (0)), loc=loc, ip=ip
|
| 886 |
+
)
|
| 887 |
+
layout_tv_data = cute.composition(inv_data_layout, atom_layout_tv, loc=loc, ip=ip)
|
| 888 |
+
|
| 889 |
+
atom_layout_v_to_check = cute.coalesce(
|
| 890 |
+
cute.make_layout(atom_layout_tv.shape[1], stride=atom_layout_tv.stride[1], loc=loc, ip=ip),
|
| 891 |
+
loc=loc,
|
| 892 |
+
ip=ip,
|
| 893 |
+
)
|
| 894 |
+
data_layout_v_to_check = cute.coalesce(
|
| 895 |
+
cute.composition(
|
| 896 |
+
data_layout,
|
| 897 |
+
cute.make_layout(
|
| 898 |
+
layout_tv_data.shape[1], stride=layout_tv_data.stride[1], loc=loc, ip=ip
|
| 899 |
+
),
|
| 900 |
+
loc=loc,
|
| 901 |
+
ip=ip,
|
| 902 |
+
),
|
| 903 |
+
loc=loc,
|
| 904 |
+
ip=ip,
|
| 905 |
+
)
|
| 906 |
+
assert data_layout_v_to_check == atom_layout_v_to_check, (
|
| 907 |
+
"the memory pointed to by atom_layout_tv does not exist in the data_layout."
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
flat_data_shape = cute.product_each(data_layout.shape, loc=loc, ip=ip)
|
| 911 |
+
tiler = tuple(
|
| 912 |
+
cute.filter(
|
| 913 |
+
cute.composition(
|
| 914 |
+
cute.make_layout(
|
| 915 |
+
flat_data_shape,
|
| 916 |
+
stride=tuple(0 if j != i else 1 for j in range(cute.rank(flat_data_shape))),
|
| 917 |
+
loc=loc,
|
| 918 |
+
ip=ip,
|
| 919 |
+
),
|
| 920 |
+
layout_tv_data,
|
| 921 |
+
loc=loc,
|
| 922 |
+
ip=ip,
|
| 923 |
+
),
|
| 924 |
+
loc=loc,
|
| 925 |
+
ip=ip,
|
| 926 |
+
)
|
| 927 |
+
for i in range(cute.rank(flat_data_shape))
|
| 928 |
+
)
|
| 929 |
+
tile2data = cute.composition(
|
| 930 |
+
cute.make_layout(flat_data_shape, loc=loc, ip=ip), tiler, loc=loc, ip=ip
|
| 931 |
+
)
|
| 932 |
+
layout_tv = cute.composition(
|
| 933 |
+
cute.left_inverse(tile2data, loc=loc, ip=ip), layout_tv_data, loc=loc, ip=ip
|
| 934 |
+
)
|
| 935 |
+
return cute.make_tiled_copy(atom, layout_tv, tiler, loc=loc, ip=ip)
|
| 936 |
+
|
| 937 |
+
|
| 938 |
+
@cute.jit
|
| 939 |
+
def scalar_to_ssa(a: cute.Numeric, dtype) -> cute.TensorSSA:
|
| 940 |
+
"""Convert a scalar to a cute TensorSSA of shape (1,) and given dtype"""
|
| 941 |
+
vec = cute.make_fragment(1, dtype)
|
| 942 |
+
vec[0] = a
|
| 943 |
+
return vec.load()
|
| 944 |
+
|
| 945 |
+
|
| 946 |
+
def ssa_to_scalar(val):
|
| 947 |
+
"""Could inline but nice for reflecting the above api"""
|
| 948 |
+
return val[0]
|
build/torch-cuda/metadata.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "fa4-cute-runtime",
|
| 3 |
+
"id": "_fa4_cute_runtime_cuda_8aeea60",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "BSD-3-Clause",
|
| 6 |
+
"python-depends": [
|
| 7 |
+
"einops",
|
| 8 |
+
"tvm-ffi",
|
| 9 |
+
"nvidia-cutlass-dsl"
|
| 10 |
+
],
|
| 11 |
+
"backend": {
|
| 12 |
+
"type": "cuda"
|
| 13 |
+
},
|
| 14 |
+
"digest": {
|
| 15 |
+
"algorithm": "sha256",
|
| 16 |
+
"files": {
|
| 17 |
+
"__init__.py": "wgqRL1xQbZK6DKTDEQYqQNAIg06tQIZskiJbTjx+8PQ=",
|
| 18 |
+
"_ops.py": "NL8H1HgBavEWrAJ2FyNptJaExyt1DvqNuv+zUEHq22g=",
|
| 19 |
+
"flashrt_fa4/__init__.py": "QDJyS5jtJnGFsSuOx43ZvLBCOrHImm8NrZk5f9URWdk=",
|
| 20 |
+
"flashrt_fa4/cute/.flake8": "jLOWNT+97fgCh5KqNCiEnnvZxySm6klT2jJzeGBXDsk=",
|
| 21 |
+
"flashrt_fa4/cute/AUTHORS": "RieEHCBsm/mQ03zC7L+neKYy2FcxqvtMy1kjgzTSgh0=",
|
| 22 |
+
"flashrt_fa4/cute/LICENSE": "jJzLlsBl5wYTW2y60nm3IdphVuUfOl8nxrMymvlBbXM=",
|
| 23 |
+
"flashrt_fa4/cute/__init__.py": "zQElR2Z4rodSQX+AQTGxyRqAbw6Nq+r2SpzYnktxKDA=",
|
| 24 |
+
"flashrt_fa4/cute/ampere_helpers.py": "ytrBcKfaN54nw20EQ2BIDXEM+ojQgtrSud7NWOSEcRE=",
|
| 25 |
+
"flashrt_fa4/cute/barrier.py": "dnr4B6p1IbjRXubJU09HRaetIiSw2iuBbMScy56mwRk=",
|
| 26 |
+
"flashrt_fa4/cute/blackwell_helpers.py": "Ulor2QDzVaUocBxnbECUXZgLz/Vey7gg4DJX4HwTSzA=",
|
| 27 |
+
"flashrt_fa4/cute/block_info.py": "7bH6QXtV1EgXfCpWNeK50J2gil55Lha8qP8cmahzQlI=",
|
| 28 |
+
"flashrt_fa4/cute/block_sparse_utils.py": "7TVkMb87s86l1hcAJT9pRpEdox13kYBllA4Ul+ZbH+M=",
|
| 29 |
+
"flashrt_fa4/cute/block_sparsity.py": "RF+myNEB3zvIcweIbtcFW5LQSTGbifwoGk8jwO9mfGY=",
|
| 30 |
+
"flashrt_fa4/cute/cache_utils.py": "csDsYBIK4bM5ldSyW1qsi97ZL7jraeRluRIuBY17lCQ=",
|
| 31 |
+
"flashrt_fa4/cute/copy_utils.py": "D1Y+7rZlRgLRVB9yAre4g4bE29vSStawglg01l5QjAk=",
|
| 32 |
+
"flashrt_fa4/cute/cute_dsl_ptxas.py": "IfBnTJ9amdfDOQkuSdWCLTh7CkZziIvs/xrAc8taxhk=",
|
| 33 |
+
"flashrt_fa4/cute/cute_dsl_utils.py": "6CSKoO3Fk72y6QOXKBqRsFR7szO6PGcsRePOQu1GsHo=",
|
| 34 |
+
"flashrt_fa4/cute/fa_logging.py": "RwCKGmzRI+2251TAGvQll1GVIrKXzqG+AyhXy+yhmKo=",
|
| 35 |
+
"flashrt_fa4/cute/fast_math.py": "fl+CK/TQujaWdVjzrpoUCLQQy3JsMSIs1hcDVp0b5zg=",
|
| 36 |
+
"flashrt_fa4/cute/flash_fwd_combine.py": "nB6loyKvlPBSWANvzucQ1BthoQk9Cvse5SAc8jXunzI=",
|
| 37 |
+
"flashrt_fa4/cute/flash_fwd_sm100.py": "1X8zTB7D897uSJyKG9R7vjw0taY3EM7/4+0DnMABORc=",
|
| 38 |
+
"flashrt_fa4/cute/interface_fwd_sm100.py": "m7lfZi9qA3AGZEKuo/njgg/cmW6jbFuOUsqOnSbL1ns=",
|
| 39 |
+
"flashrt_fa4/cute/mask.py": "Lc/ZVBCHqw9CJTmyrrATbkA57SszRGNKrPOWa45vVeU=",
|
| 40 |
+
"flashrt_fa4/cute/mma_sm100_desc.py": "rG7Ciy8aS3l8ORp4B7iPg7QwRS1BgmLD36ew81FuOfk=",
|
| 41 |
+
"flashrt_fa4/cute/named_barrier.py": "6eJoAiSva6gmDjaq5Lsv3PWJt1vQ0HBQ33xMWeHHFt0=",
|
| 42 |
+
"flashrt_fa4/cute/pack_gqa.py": "NKgz4fVByUXspujafNnz1e6P1pcZ2vJj3uU6F+V4BPU=",
|
| 43 |
+
"flashrt_fa4/cute/paged_kv.py": "fhPZXtc8KsT0tO0fS1ze1NNr8r7jIksNS0Q0I7m2shU=",
|
| 44 |
+
"flashrt_fa4/cute/pipeline.py": "hJvOkccr4KXHRYl54QyCkPReBSU6g2AOMPuVQMYe1Wo=",
|
| 45 |
+
"flashrt_fa4/cute/seqlen_info.py": "QhiL+UP8MldON58Ita7+UdvHgxWpYAHTFxIUxQT01J8=",
|
| 46 |
+
"flashrt_fa4/cute/sm100_hd256_2cta_fmha_forward.py": "M4HeDqxnkI+MAIwn/AzJIHuE2VIR7OjbMJ3yQpxtgtA=",
|
| 47 |
+
"flashrt_fa4/cute/softmax.py": "x7+7+qmjdZF0/iXYo9MMfKTPLdT8/yLauG/JaNDWaKo=",
|
| 48 |
+
"flashrt_fa4/cute/testing.py": "IpK1idwE0qaCLGWqs6WI+u2MejB/VOE0hQBRmuEweW8=",
|
| 49 |
+
"flashrt_fa4/cute/tile_scheduler.py": "ykdClNE/ZwwP6NrzgOshiUOxEAThIU8tYBLqiqnaIbM=",
|
| 50 |
+
"flashrt_fa4/cute/utils.py": "SFlf4ThdwaKvvn7ocEEH3JjBtusgQ4ahHYKlS1WiZ1U=",
|
| 51 |
+
"quack/__init__.py": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
|
| 52 |
+
"quack/activation.py": "D7ODhhhoBPOTIlyul3IhxaaZyYgy8qRZxSRVKhOgUnA=",
|
| 53 |
+
"quack/compile_utils.py": "qJ3oTsDlbAiddrJHtEO7LPYVqn/s+neNfiw+/KvfXZU=",
|
| 54 |
+
"quack/copy_utils.py": "Em283YfONKRESV+TF+4vJ4663cm9G7PuBdrTsGvQkSA=",
|
| 55 |
+
"quack/cute_dsl_utils.py": "blyzks11vGX0tCvNIFQKIHaa8dpHE0cpdnQxQJePJLc=",
|
| 56 |
+
"quack/layout_utils.py": "Fb3wDjm6B5lU5E54Ub0qCBlshIKzj84IaTQA8JxoIsg=",
|
| 57 |
+
"quack/sm90_utils.py": "V9+7RlVPvS4huOX2zBj6sD+hSlzOdwUR3UwaRPiIfIQ=",
|
| 58 |
+
"quack/utils.py": "CobF28mmgrsAauAH07dyZ8bBQoJrs4HuFE6KPRjM+6k="
|
| 59 |
+
}
|
| 60 |
+
},
|
| 61 |
+
"provenance": {
|
| 62 |
+
"kernel-builder": {
|
| 63 |
+
"version": "0.17.0-dev0",
|
| 64 |
+
"sha": "81f55ea30fd8f819dcf93a3c934dd584c895bd2f",
|
| 65 |
+
"dirty": false
|
| 66 |
+
},
|
| 67 |
+
"kernel": {
|
| 68 |
+
"sha": "8aeea60b17c970bc431c7001c6f5008d1010ed6f",
|
| 69 |
+
"dirty": false
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
build/torch-cuda/quack/__init__.py
ADDED
|
File without changes
|
build/torch-cuda/quack/activation.py
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Tuple
|
| 5 |
+
from functools import partial
|
| 6 |
+
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
from cutlass import Float32, Boolean, const_expr
|
| 9 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 10 |
+
from cutlass._mlir.dialects import llvm, nvvm
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
F32_or_F32x2 = Float32 | Tuple[Float32, Float32]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
sub_packed_f32x2 = partial(
|
| 17 |
+
cute.arch.calc_packed_f32x2_op,
|
| 18 |
+
src_c=None,
|
| 19 |
+
calc_func=nvvm.sub_packed_f32x2,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@dsl_user_op
|
| 24 |
+
def tanh(a: float | Float32, *, loc=None, ip=None) -> Float32:
|
| 25 |
+
return Float32(
|
| 26 |
+
llvm.inline_asm(
|
| 27 |
+
T.f32(),
|
| 28 |
+
[Float32(a).ir_value(loc=loc, ip=ip)],
|
| 29 |
+
"tanh.approx.f32 $0, $1;",
|
| 30 |
+
"=f,f",
|
| 31 |
+
has_side_effects=False,
|
| 32 |
+
is_align_stack=False,
|
| 33 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 34 |
+
)
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@dsl_user_op
|
| 39 |
+
def sigmoid(x: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 40 |
+
if const_expr(not isinstance(x, tuple)):
|
| 41 |
+
# return 0.5 + 0.5 * cute.math.tanh(0.5 * x, fastmath=True)
|
| 42 |
+
return 0.5 + 0.5 * tanh(0.5 * x)
|
| 43 |
+
else:
|
| 44 |
+
x_half = cute.arch.mul_packed_f32x2((0.5, 0.5), x)
|
| 45 |
+
tanh_x_half = (tanh(x_half[0]), tanh(x_half[1]))
|
| 46 |
+
return cute.arch.fma_packed_f32x2(tanh_x_half, (0.5, 0.5), (0.5, 0.5))
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@dsl_user_op
|
| 50 |
+
def dsigmoid_from_output(out: Float32, dout: Float32, *, loc=None, ip=None) -> Float32:
|
| 51 |
+
# return dout * out * (1.0 - out)
|
| 52 |
+
return dout * (out - out * out)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@dsl_user_op
|
| 56 |
+
def relu(x: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 57 |
+
if const_expr(not isinstance(x, tuple)):
|
| 58 |
+
return cute.arch.fmax(x, Float32(0.0))
|
| 59 |
+
else:
|
| 60 |
+
return cute.arch.fmax(x[0], Float32(0.0)), cute.arch.fmax(x[1], Float32(0.0))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@dsl_user_op
|
| 64 |
+
@cute.jit
|
| 65 |
+
def drelu(
|
| 66 |
+
x: F32_or_F32x2, dout: F32_or_F32x2, *, loc=None, ip=None
|
| 67 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2]:
|
| 68 |
+
if const_expr(not isinstance(x, tuple)):
|
| 69 |
+
x_pos = Boolean(x > 0)
|
| 70 |
+
return dout if x_pos else Float32(0.0), cute.arch.fmax(x, Float32(0.0))
|
| 71 |
+
else:
|
| 72 |
+
x0_pos = Boolean(x[0] > 0)
|
| 73 |
+
x1_pos = Boolean(x[1] > 0)
|
| 74 |
+
dx = (dout[0] if x0_pos else Float32(0.0), dout[1] if x1_pos else Float32(0.0))
|
| 75 |
+
return dx, relu(x)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@dsl_user_op
|
| 79 |
+
def relu_sq(x: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 80 |
+
if const_expr(not isinstance(x, tuple)):
|
| 81 |
+
return cute.arch.fmax(x, Float32(0.0)) * x
|
| 82 |
+
else:
|
| 83 |
+
relu_x = (cute.arch.fmax(x[0], Float32(0.0)), cute.arch.fmax(x[1], Float32(0.0)))
|
| 84 |
+
return cute.arch.mul_packed_f32x2(relu_x, x)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
@dsl_user_op
|
| 88 |
+
@cute.jit
|
| 89 |
+
def drelu_sq(
|
| 90 |
+
x: F32_or_F32x2, dout: F32_or_F32x2, *, loc=None, ip=None
|
| 91 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2]:
|
| 92 |
+
"""
|
| 93 |
+
ReLU squared backward pass: computes gradient w.r.t. x and recomputes forward
|
| 94 |
+
Given: relu_sq_out = max(x, 0) * x, and dout = grad w.r.t. relu_sq_out
|
| 95 |
+
Returns: (dx, relu_sq_out) where:
|
| 96 |
+
- dx = dout * 2 * x if x > 0, else 0
|
| 97 |
+
- relu_sq_out = max(x, 0) * x
|
| 98 |
+
"""
|
| 99 |
+
if const_expr(not isinstance(x, tuple)):
|
| 100 |
+
relu_x = relu(x)
|
| 101 |
+
relu_sq_out = relu_x * x
|
| 102 |
+
# Derivative: d/dx[max(x,0) * x] = 2*x if x > 0, else 0
|
| 103 |
+
dx = 2.0 * (dout * relu_x)
|
| 104 |
+
return dx, relu_sq_out
|
| 105 |
+
else:
|
| 106 |
+
relu_x = relu(x)
|
| 107 |
+
relu_sq_out = cute.arch.mul_packed_f32x2(relu_x, x)
|
| 108 |
+
dx = cute.arch.mul_packed_f32x2((2.0, 2.0), cute.arch.mul_packed_f32x2(dout, relu_x))
|
| 109 |
+
return dx, relu_sq_out
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@dsl_user_op
|
| 113 |
+
def gelu_tanh_approx(x: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 114 |
+
"""
|
| 115 |
+
gelu(x) = 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
|
| 116 |
+
= 0.5 * x * (1 + tanh(x * (0.797885 + 0.0356774 * x * x)))
|
| 117 |
+
"""
|
| 118 |
+
sqrt_2_over_pi = math.sqrt(2 / math.pi) # ~0.797885
|
| 119 |
+
sqrt_2_over_pi_coeff = 0.044715 * sqrt_2_over_pi # ~0.0356774
|
| 120 |
+
if const_expr(not isinstance(x, tuple)):
|
| 121 |
+
return 0.5 * (
|
| 122 |
+
x
|
| 123 |
+
# Currently cute.math.tanh(x, fastmath=True) generates very slow code
|
| 124 |
+
# * (1 + cute.math.tanh(x * (sqrt_2_over_pi + sqrt_2_over_pi_coeff * (x * x)), fastmath=True))
|
| 125 |
+
* (1.0 + tanh(x * (sqrt_2_over_pi + sqrt_2_over_pi_coeff * (x * x))))
|
| 126 |
+
)
|
| 127 |
+
else:
|
| 128 |
+
x_sq = cute.arch.mul_packed_f32x2(x, x)
|
| 129 |
+
x_sq_scaled = cute.arch.fma_packed_f32x2(
|
| 130 |
+
x_sq, (sqrt_2_over_pi_coeff, sqrt_2_over_pi_coeff), (sqrt_2_over_pi, sqrt_2_over_pi)
|
| 131 |
+
)
|
| 132 |
+
z = cute.arch.mul_packed_f32x2(x, x_sq_scaled)
|
| 133 |
+
tanh_z = (tanh(z[0]), tanh(z[1]))
|
| 134 |
+
x_tanh_z = cute.arch.fma_packed_f32x2(tanh_z, x, x)
|
| 135 |
+
return cute.arch.mul_packed_f32x2((0.5, 0.5), x_tanh_z)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
@dsl_user_op
|
| 139 |
+
def dgelu_tanh_approx(
|
| 140 |
+
x: F32_or_F32x2, dout: F32_or_F32x2, *, loc=None, ip=None
|
| 141 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2]:
|
| 142 |
+
"""
|
| 143 |
+
GELU tanh approximation backward pass: computes gradient w.r.t. x and recomputes forward
|
| 144 |
+
Given: gelu_out = 0.5 * x * (1 + tanh(x * (c1 + c2 * x^2))), and dout = grad w.r.t. gelu_out
|
| 145 |
+
Returns: (dx, gelu_out)
|
| 146 |
+
|
| 147 |
+
Derivative uses the chain rule:
|
| 148 |
+
d/dx[gelu(x)] = 0.5 * (1 + tanh(z)) + 0.5 * x * sech^2(z) * dz/dx
|
| 149 |
+
where z = x * (c1 + c2 * x^2), dz/dx = c1 + 3 * c2 * x^2
|
| 150 |
+
and sech^2(z) = 1 - tanh^2(z)
|
| 151 |
+
"""
|
| 152 |
+
sqrt_2_over_pi = math.sqrt(2 / math.pi) # c1 ~0.797885
|
| 153 |
+
sqrt_2_over_pi_coeff = 0.044715 * sqrt_2_over_pi # c2 ~0.0356774
|
| 154 |
+
sqrt_2_over_pi_coeff_3 = 3.0 * sqrt_2_over_pi_coeff # c3 ~0.01070322
|
| 155 |
+
|
| 156 |
+
if const_expr(not isinstance(x, tuple)):
|
| 157 |
+
# Compute z = x * (c1 + c2 * x^2)
|
| 158 |
+
x_sq = x * x
|
| 159 |
+
# tanh_z = cute.math.tanh(x * (sqrt_2_over_pi + sqrt_2_over_pi_coeff * x_sq), fastmath=True)
|
| 160 |
+
tanh_z = tanh(x * (sqrt_2_over_pi + sqrt_2_over_pi_coeff * x_sq))
|
| 161 |
+
half_tanh_z_plus_one = 0.5 + 0.5 * tanh_z
|
| 162 |
+
gelu_out = x * half_tanh_z_plus_one
|
| 163 |
+
|
| 164 |
+
# Compute gradient
|
| 165 |
+
# sech^2(z) = 1 - tanh^2(z)
|
| 166 |
+
sech2_z = 1 - tanh_z * tanh_z
|
| 167 |
+
# dz/dx = c1 + 3 * c2 * x^2
|
| 168 |
+
dz_dx = sqrt_2_over_pi + sqrt_2_over_pi_coeff_3 * x_sq
|
| 169 |
+
# d/dx[gelu(x)] = 0.5 * (1 + tanh(z)) + 0.5 * x * sech^2(z) * dz/dx
|
| 170 |
+
dgelu = half_tanh_z_plus_one + x * (0.5 * (sech2_z * dz_dx))
|
| 171 |
+
|
| 172 |
+
dx = dout * dgelu
|
| 173 |
+
return dx, gelu_out
|
| 174 |
+
else:
|
| 175 |
+
# Compute z = x * (c1 + c2 * x^2)
|
| 176 |
+
x_sq = cute.arch.mul_packed_f32x2(x, x)
|
| 177 |
+
x_sq_scaled = cute.arch.fma_packed_f32x2(
|
| 178 |
+
x_sq, (sqrt_2_over_pi_coeff, sqrt_2_over_pi_coeff), (sqrt_2_over_pi, sqrt_2_over_pi)
|
| 179 |
+
)
|
| 180 |
+
z = cute.arch.mul_packed_f32x2(x, x_sq_scaled)
|
| 181 |
+
tanh_z = (tanh(z[0]), tanh(z[1]))
|
| 182 |
+
half_tanh_z_plus_one = cute.arch.fma_packed_f32x2(tanh_z, (0.5, 0.5), (0.5, 0.5))
|
| 183 |
+
gelu_out = cute.arch.mul_packed_f32x2(x, half_tanh_z_plus_one)
|
| 184 |
+
|
| 185 |
+
# Compute gradient
|
| 186 |
+
# sech^2(z) = 1 - tanh^2(z)
|
| 187 |
+
sech2_z = cute.arch.fma_packed_f32x2(tanh_z, (-tanh_z[0], -tanh_z[1]), (1.0, 1.0))
|
| 188 |
+
# dz/dx = c1 + 3 * c2 * x^2
|
| 189 |
+
dz_dx = cute.arch.fma_packed_f32x2(
|
| 190 |
+
x_sq, (sqrt_2_over_pi_coeff_3, sqrt_2_over_pi_coeff_3), (sqrt_2_over_pi, sqrt_2_over_pi)
|
| 191 |
+
)
|
| 192 |
+
# d/dx[gelu(x)] = 0.5 * (1 + tanh(z)) + 0.5 * x * sech^2(z) * dz/dx
|
| 193 |
+
sech2_dz_dx = cute.arch.mul_packed_f32x2(sech2_z, dz_dx)
|
| 194 |
+
x_sech2_dz_dx = cute.arch.mul_packed_f32x2(x, sech2_dz_dx)
|
| 195 |
+
dgelu = cute.arch.fma_packed_f32x2(x_sech2_dz_dx, (0.5, 0.5), half_tanh_z_plus_one)
|
| 196 |
+
|
| 197 |
+
dx = cute.arch.mul_packed_f32x2(dout, dgelu)
|
| 198 |
+
return dx, gelu_out
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
@dsl_user_op
|
| 202 |
+
@cute.jit
|
| 203 |
+
def softplus(x: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 204 |
+
if const_expr(not isinstance(x, tuple)):
|
| 205 |
+
use_linear = Boolean(x > 20.0)
|
| 206 |
+
return (
|
| 207 |
+
cute.math.log(Float32(cute.math.exp(x, fastmath=True)) + 1.0, fastmath=True)
|
| 208 |
+
if not use_linear
|
| 209 |
+
else x
|
| 210 |
+
)
|
| 211 |
+
else:
|
| 212 |
+
log2_e = math.log2(math.e)
|
| 213 |
+
x_log2e = cute.arch.mul_packed_f32x2(x, (log2_e, log2_e))
|
| 214 |
+
x_exp = (cute.math.exp(x_log2e[0], fastmath=True), cute.math.exp(x_log2e[1], fastmath=True))
|
| 215 |
+
x_exp_p1 = cute.arch.add_packed_f32x2(x_exp, (1.0, 1.0))
|
| 216 |
+
log_x_exp_p1 = (
|
| 217 |
+
cute.math.log2(x_exp_p1[0], fastmath=True),
|
| 218 |
+
cute.math.log2(x_exp_p1[1], fastmath=True),
|
| 219 |
+
)
|
| 220 |
+
ln2 = math.log(2.0)
|
| 221 |
+
softplus_x = cute.arch.mul_packed_f32x2(log_x_exp_p1, (ln2, ln2))
|
| 222 |
+
use_linear_0 = Boolean(x[0] > 20.0)
|
| 223 |
+
use_linear_1 = Boolean(x[1] > 20.0)
|
| 224 |
+
return (
|
| 225 |
+
softplus_x[0] if not use_linear_0 else x[0],
|
| 226 |
+
softplus_x[1] if not use_linear_1 else x[1],
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
@dsl_user_op
|
| 231 |
+
@cute.jit
|
| 232 |
+
def dsoftplus_from_output(out: Float32, dout: Float32, *, loc=None, ip=None) -> Float32:
|
| 233 |
+
use_linear = Boolean(out > 20.0)
|
| 234 |
+
# dx = dout * (1.0 - cute.math.exp(-out, fastmath=True)) if not use_linear else dout
|
| 235 |
+
dx = dout - dout * cute.math.exp(-out, fastmath=True)
|
| 236 |
+
return dx if not use_linear else dout
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
@dsl_user_op
|
| 240 |
+
def silu(x: F32_or_F32x2, *, already_halved: bool = False, loc=None, ip=None) -> F32_or_F32x2:
|
| 241 |
+
"""
|
| 242 |
+
silu(x) = x * sigmoid(x) = x * (1 + tanh(x / 2)) / 2 = (0.5 * x) * tanh(0.5 * x) + (0.5 * x)
|
| 243 |
+
This compiles down to 3 SASS instructions: FMUL to get 0.5 * x, MUFU.TANH, and FFMA.
|
| 244 |
+
"""
|
| 245 |
+
if const_expr(not isinstance(x, tuple)):
|
| 246 |
+
x_half = 0.5 * x if const_expr(not already_halved) else x
|
| 247 |
+
# return x_half * cute.math.tanh(x_half, fastmath=True) + x_half
|
| 248 |
+
return x_half * tanh(x_half) + x_half
|
| 249 |
+
else:
|
| 250 |
+
x_half = cute.arch.mul_packed_f32x2((0.5, 0.5), x) if const_expr(not already_halved) else x
|
| 251 |
+
tanh_x_half = (tanh(x_half[0]), tanh(x_half[1]))
|
| 252 |
+
return cute.arch.fma_packed_f32x2(x_half, tanh_x_half, x_half)
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
@dsl_user_op
|
| 256 |
+
def swiglu(x: F32_or_F32x2, y: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 257 |
+
if const_expr(not isinstance(x, tuple)):
|
| 258 |
+
return silu(x) * y
|
| 259 |
+
else:
|
| 260 |
+
return cute.arch.mul_packed_f32x2(silu(x), y)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
@dsl_user_op
|
| 264 |
+
def dswiglu(
|
| 265 |
+
x: F32_or_F32x2,
|
| 266 |
+
y: F32_or_F32x2,
|
| 267 |
+
dout: F32_or_F32x2,
|
| 268 |
+
*,
|
| 269 |
+
already_halved: bool = False,
|
| 270 |
+
loc=None,
|
| 271 |
+
ip=None,
|
| 272 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2, F32_or_F32x2]:
|
| 273 |
+
"""
|
| 274 |
+
SwiGLU backward pass: computes gradients w.r.t. x (gate) and y (up projection)
|
| 275 |
+
Given: swiglu_out = silu(x) * y, and dout = grad w.r.t. swiglu_out
|
| 276 |
+
Returns: (dx, dy, swiglu_out) where dx = dout * y * d_silu(x), dy = dout * silu(x)
|
| 277 |
+
|
| 278 |
+
d_silu(x) = sigmoid(x) * (1 + x * (1 - sigmoid(x)))
|
| 279 |
+
|
| 280 |
+
This has been optimized to use fewer instructions (i.e. we expand things out
|
| 281 |
+
to use FFMA instead of FADD and FMUL).
|
| 282 |
+
"""
|
| 283 |
+
if const_expr(not isinstance(x, tuple)):
|
| 284 |
+
# Compute sigmoid(x) using tanh: sigmoid(x) = 0.5 * (1 + tanh(0.5 * x))
|
| 285 |
+
# FMUL, MUFU.TANH, then FFMA
|
| 286 |
+
if const_expr(not already_halved):
|
| 287 |
+
sigmoid_x = sigmoid(x)
|
| 288 |
+
silu_x = x * sigmoid_x # FMUL
|
| 289 |
+
else:
|
| 290 |
+
tanh_x = tanh(x) # MUFU.TANH
|
| 291 |
+
sigmoid_x = 0.5 * tanh_x + 0.5 # FFMA
|
| 292 |
+
silu_x = x * tanh_x + x # FFMA
|
| 293 |
+
silu_x_dout = silu_x * dout # FMUL
|
| 294 |
+
# d_silu(x) * dout
|
| 295 |
+
# = sigmoid_x * (1 + x * (1 - sigmoid_x)) * dout
|
| 296 |
+
# = (sigmoid_x + sigmoid_x * x * (1 - sigmoid_x)) * dout
|
| 297 |
+
# = (sigmoid_x + silu_x * (1 - sigmoid_x)) * dout
|
| 298 |
+
# = (sigmoid_x + silu_x - silu_x * sigmoid_x) * dout
|
| 299 |
+
# = (sigmoid_x - silu_x * sigmoid_x) * dout + silu_x * dout
|
| 300 |
+
d_silu_x_dout = (sigmoid_x - silu_x * sigmoid_x) * dout + silu_x_dout # FFMA, FFMA
|
| 301 |
+
dx = d_silu_x_dout * y # FMUL
|
| 302 |
+
dy = silu_x_dout
|
| 303 |
+
swiglu_out = silu_x * y # FMUL
|
| 304 |
+
# Overall it's 1 MUFU.TANH, 5 FMUL, 3 FFMA
|
| 305 |
+
return dx, dy, swiglu_out
|
| 306 |
+
else:
|
| 307 |
+
# Compute sigmoid(x) and silu(x)
|
| 308 |
+
if const_expr(not already_halved):
|
| 309 |
+
sigmoid_x = sigmoid(x)
|
| 310 |
+
silu_x = cute.arch.mul_packed_f32x2(x, sigmoid_x)
|
| 311 |
+
else:
|
| 312 |
+
tanh_x = (tanh(x[0]), tanh(x[1]))
|
| 313 |
+
sigmoid_x = cute.arch.fma_packed_f32x2(tanh_x, (0.5, 0.5), (0.5, 0.5))
|
| 314 |
+
silu_x = cute.arch.fma_packed_f32x2(x, tanh_x, x)
|
| 315 |
+
silu_x_dout = cute.arch.mul_packed_f32x2(silu_x, dout)
|
| 316 |
+
# d_silu(x) * dout = (sigmoid_x - silu_x * sigmoid_x) * dout + silu_x * dout
|
| 317 |
+
sigmoid_x_minus_silu_x_sigmoid_x = cute.arch.fma_packed_f32x2(
|
| 318 |
+
sigmoid_x, (-silu_x[0], -silu_x[1]), sigmoid_x
|
| 319 |
+
)
|
| 320 |
+
d_silu_x_dout = cute.arch.fma_packed_f32x2(
|
| 321 |
+
sigmoid_x_minus_silu_x_sigmoid_x, dout, silu_x_dout
|
| 322 |
+
)
|
| 323 |
+
dx = cute.arch.mul_packed_f32x2(d_silu_x_dout, y)
|
| 324 |
+
dy = silu_x_dout
|
| 325 |
+
swiglu_out = cute.arch.mul_packed_f32x2(silu_x, y)
|
| 326 |
+
return dx, dy, swiglu_out
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
@dsl_user_op
|
| 330 |
+
def swiglu_oai(
|
| 331 |
+
x: F32_or_F32x2, y: F32_or_F32x2, alpha: float = 1.702, *, loc=None, ip=None
|
| 332 |
+
) -> F32_or_F32x2:
|
| 333 |
+
"""The swiglu variant used in gpt-oss, which has a scaling factor on x and bias of 1 to y.
|
| 334 |
+
https://github.com/openai/gpt-oss/blob/7be9334950053a888e24887a57dac797a17d6e00/gpt_oss/torch/model.py#L249
|
| 335 |
+
x * sigmoid(alpha * x) * (y + 1)
|
| 336 |
+
Compile down to FMUL, FMUL, TANH, FFMA, FFMA
|
| 337 |
+
"""
|
| 338 |
+
# Compute sigmoid(alpha * x) using tanh: sigmoid(z) = 0.5 * (1 + tanh(z/2))
|
| 339 |
+
if const_expr(not isinstance(x, tuple)):
|
| 340 |
+
x_half = 0.5 * x
|
| 341 |
+
# silu_x = x_half * cute.math.tanh(alpha * x_half, fastmath=True) + x_half
|
| 342 |
+
silu_x = x_half * tanh(alpha * x_half) + x_half
|
| 343 |
+
return silu_x * y + silu_x
|
| 344 |
+
else:
|
| 345 |
+
x_half = cute.arch.mul_packed_f32x2((0.5, 0.5), x)
|
| 346 |
+
alpha_x_half = cute.arch.mul_packed_f32x2((alpha, alpha), x_half)
|
| 347 |
+
tanh_alpha_x_half = (tanh(alpha_x_half[0]), tanh(alpha_x_half[1]))
|
| 348 |
+
silu_x = cute.arch.fma_packed_f32x2(x_half, tanh_alpha_x_half, x_half)
|
| 349 |
+
return cute.arch.fma_packed_f32x2(silu_x, y, silu_x)
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
@dsl_user_op
|
| 353 |
+
def dswiglu_oai(
|
| 354 |
+
x: F32_or_F32x2, y: F32_or_F32x2, dout: F32_or_F32x2, alpha: float = 1.702, *, loc=None, ip=None
|
| 355 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2, F32_or_F32x2]:
|
| 356 |
+
"""
|
| 357 |
+
Swiglu OAI backward pass: computes gradients w.r.t. x and y
|
| 358 |
+
Given: swiglu_oai_out = x * sigmoid(alpha * x) * (y + 1), and dout = grad w.r.t. swiglu_oai_out
|
| 359 |
+
Returns: (dx, dy, swiglu_oai_out)
|
| 360 |
+
|
| 361 |
+
Derivative of x * sigmoid(alpha * x) w.r.t. x:
|
| 362 |
+
d/dx[x * sigmoid(alpha * x)] = sigmoid(alpha * x) + alpha * x * sigmoid(alpha * x) * (1 - sigmoid(alpha * x))
|
| 363 |
+
"""
|
| 364 |
+
if const_expr(not isinstance(x, tuple)):
|
| 365 |
+
# Compute sigmoid(alpha * x) using tanh: sigmoid(z) = 0.5 * (1 + tanh(z/2))
|
| 366 |
+
alpha_x_half = (0.5 * alpha) * x # FMUL
|
| 367 |
+
# MUFU.TANH, then FFMA
|
| 368 |
+
# sigmoid_alpha_x = 0.5 + 0.5 * cute.math.tanh(alpha_x_half, fastmath=True)
|
| 369 |
+
sigmoid_alpha_x = 0.5 + 0.5 * tanh(alpha_x_half)
|
| 370 |
+
silu_x = x * sigmoid_alpha_x # FMUL
|
| 371 |
+
silu_x_dout = silu_x * dout # FMUL
|
| 372 |
+
# FFMA, FFMA, FMUL
|
| 373 |
+
d_silu_x_dout = (sigmoid_alpha_x + alpha * (silu_x - silu_x * sigmoid_alpha_x)) * dout
|
| 374 |
+
dx = d_silu_x_dout * y + d_silu_x_dout # FFMA, instead of multiply by y + 1
|
| 375 |
+
dy = silu_x_dout
|
| 376 |
+
swiglu_out = silu_x * y + silu_x # FFMA, instead of multiply by y + 1
|
| 377 |
+
# Overall it's 1 MUFU.TANH, 4 FMUL, 5 FFMA
|
| 378 |
+
return dx, dy, swiglu_out
|
| 379 |
+
else:
|
| 380 |
+
# Compute sigmoid(alpha * x)
|
| 381 |
+
alpha_x_half = cute.arch.mul_packed_f32x2(((0.5 * alpha), (0.5 * alpha)), x)
|
| 382 |
+
tanh_alpha_x_half = (tanh(alpha_x_half[0]), tanh(alpha_x_half[1]))
|
| 383 |
+
sigmoid_alpha_x = cute.arch.fma_packed_f32x2(tanh_alpha_x_half, (0.5, 0.5), (0.5, 0.5))
|
| 384 |
+
silu_x = cute.arch.mul_packed_f32x2(x, sigmoid_alpha_x)
|
| 385 |
+
silu_x_dout = cute.arch.mul_packed_f32x2(silu_x, dout)
|
| 386 |
+
# d_silu_x_dout = (sigmoid_alpha_x + alpha * (silu_x - silu_x * sigmoid_alpha_x)) * dout
|
| 387 |
+
silu_x_minus_product = cute.arch.fma_packed_f32x2(
|
| 388 |
+
silu_x, (-sigmoid_alpha_x[0], -sigmoid_alpha_x[1]), silu_x
|
| 389 |
+
)
|
| 390 |
+
sigmoid_plus_alpha_diff = cute.arch.fma_packed_f32x2(
|
| 391 |
+
(alpha, alpha), silu_x_minus_product, sigmoid_alpha_x
|
| 392 |
+
)
|
| 393 |
+
d_silu_x_dout = cute.arch.mul_packed_f32x2(sigmoid_plus_alpha_diff, dout)
|
| 394 |
+
dx = cute.arch.fma_packed_f32x2(d_silu_x_dout, y, d_silu_x_dout)
|
| 395 |
+
dy = silu_x_dout
|
| 396 |
+
swiglu_out = cute.arch.fma_packed_f32x2(silu_x, y, silu_x)
|
| 397 |
+
return dx, dy, swiglu_out
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
@dsl_user_op
|
| 401 |
+
def glu(x: F32_or_F32x2, y: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 402 |
+
"""GLU: Gated Linear Unit
|
| 403 |
+
glu(x, y) = sigmoid(x) * y
|
| 404 |
+
Using tanh to compute sigmoid: sigmoid(x) = 0.5 * (1 + tanh(x/2))
|
| 405 |
+
"""
|
| 406 |
+
if const_expr(not isinstance(x, tuple)):
|
| 407 |
+
sigmoid_x = sigmoid(x) # FMUL, MUFU.TANH, then FFMA
|
| 408 |
+
return sigmoid_x * y # FMUL
|
| 409 |
+
else:
|
| 410 |
+
sigmoid_x = sigmoid(x)
|
| 411 |
+
return cute.arch.mul_packed_f32x2(sigmoid_x, y)
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
@dsl_user_op
|
| 415 |
+
def dglu(
|
| 416 |
+
x: F32_or_F32x2, y: F32_or_F32x2, dout: F32_or_F32x2, *, loc=None, ip=None
|
| 417 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2, F32_or_F32x2]:
|
| 418 |
+
"""
|
| 419 |
+
GLU backward pass: computes gradients w.r.t. x (gate) and y (up projection)
|
| 420 |
+
Given: glu_out = sigmoid(x) * y, and dout = grad w.r.t. glu_out
|
| 421 |
+
Returns: (dx, dy, glu_out) where:
|
| 422 |
+
- dx = dout * y * sigmoid(x) * (1 - sigmoid(x))
|
| 423 |
+
- dy = dout * sigmoid(x)
|
| 424 |
+
- glu_out = sigmoid(x) * y
|
| 425 |
+
"""
|
| 426 |
+
if const_expr(not isinstance(x, tuple)):
|
| 427 |
+
# Compute sigmoid(x) using tanh: sigmoid(x) = 0.5 * (1 + tanh(x/2))
|
| 428 |
+
sigmoid_x = sigmoid(x) # FMUL, MUFU.TANH, then FFMA
|
| 429 |
+
sigmoid_x_dout = sigmoid_x * dout # FMUL
|
| 430 |
+
glu_out = sigmoid_x * y # FMUL
|
| 431 |
+
# dx = y * sigmoid(x) * (1 - sigmoid(x)) * dout
|
| 432 |
+
# = y * (1 - sigmoid(x)) * sigmoid_x_dout
|
| 433 |
+
# = (y - y * sigmoid(x)) * sigmoid_x_dout
|
| 434 |
+
# = (y - glu_out) * sigmoid_x_dout
|
| 435 |
+
dx = (y - glu_out) * sigmoid_x_dout # FADD, FMUL
|
| 436 |
+
dy = sigmoid_x_dout
|
| 437 |
+
# Total: 1 MUFU.TANH, 4 FMUL, 1 FADD, 1 FFMA
|
| 438 |
+
return dx, dy, glu_out
|
| 439 |
+
else:
|
| 440 |
+
sigmoid_x = sigmoid(x)
|
| 441 |
+
sigmoid_x_dout = cute.arch.mul_packed_f32x2(sigmoid_x, dout)
|
| 442 |
+
glu_out = cute.arch.mul_packed_f32x2(sigmoid_x, y)
|
| 443 |
+
# dx = (y - glu_out) * sigmoid_x_dout
|
| 444 |
+
y_minus_glu_out = sub_packed_f32x2(y, glu_out)
|
| 445 |
+
dx = cute.arch.mul_packed_f32x2(y_minus_glu_out, sigmoid_x_dout)
|
| 446 |
+
dy = sigmoid_x_dout
|
| 447 |
+
return dx, dy, glu_out
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
@dsl_user_op
|
| 451 |
+
def reglu(x: F32_or_F32x2, y: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 452 |
+
"""ReGLU: ReLU Gated Linear Unit
|
| 453 |
+
reglu(x, y) = relu(x) * y = max(x, 0) * y
|
| 454 |
+
"""
|
| 455 |
+
if const_expr(not isinstance(x, tuple)):
|
| 456 |
+
return cute.arch.fmax(x, Float32(0.0)) * y
|
| 457 |
+
else:
|
| 458 |
+
relu_x = relu(x)
|
| 459 |
+
return cute.arch.mul_packed_f32x2(relu_x, y)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
@dsl_user_op
|
| 463 |
+
@cute.jit
|
| 464 |
+
def dreglu(
|
| 465 |
+
x: F32_or_F32x2, y: F32_or_F32x2, dout: F32_or_F32x2, *, loc=None, ip=None
|
| 466 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2, F32_or_F32x2]:
|
| 467 |
+
"""
|
| 468 |
+
ReGLU backward pass: computes gradients w.r.t. x (gate) and y (up projection)
|
| 469 |
+
Given: reglu_out = relu(x) * y, and dout = grad w.r.t. reglu_out
|
| 470 |
+
Returns: (dx, dy, reglu_out) where:
|
| 471 |
+
- dx = dout * y if x > 0, else 0
|
| 472 |
+
- dy = dout * relu(x)
|
| 473 |
+
- reglu_out = relu(x) * y
|
| 474 |
+
"""
|
| 475 |
+
if const_expr(not isinstance(x, tuple)):
|
| 476 |
+
x_pos = Boolean(x > 0)
|
| 477 |
+
relu_x = cute.arch.fmax(x, Float32(0.0))
|
| 478 |
+
dx = (dout * y) if x_pos else Float32(0.0)
|
| 479 |
+
dy = dout * relu_x
|
| 480 |
+
reglu_out = relu_x * y
|
| 481 |
+
return dx, dy, reglu_out
|
| 482 |
+
else:
|
| 483 |
+
x0_pos = Boolean(x[0] > 0)
|
| 484 |
+
x1_pos = Boolean(x[1] > 0)
|
| 485 |
+
relu_x = relu(x)
|
| 486 |
+
dout_y = cute.arch.mul_packed_f32x2(dout, y)
|
| 487 |
+
dx = ((dout_y[0] if x0_pos else Float32(0.0)), (dout_y[1] if x1_pos else Float32(0.0)))
|
| 488 |
+
dy = cute.arch.mul_packed_f32x2(dout, relu_x)
|
| 489 |
+
reglu_out = cute.arch.mul_packed_f32x2(relu_x, y)
|
| 490 |
+
return dx, dy, reglu_out
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
@dsl_user_op
|
| 494 |
+
def geglu(x: F32_or_F32x2, y: F32_or_F32x2, *, loc=None, ip=None) -> F32_or_F32x2:
|
| 495 |
+
"""GeGLU: GELU Gated Linear Unit
|
| 496 |
+
geglu(x, y) = gelu(x) * y
|
| 497 |
+
Uses the tanh approximation of GELU
|
| 498 |
+
"""
|
| 499 |
+
if const_expr(not isinstance(x, tuple)):
|
| 500 |
+
return gelu_tanh_approx(x) * y
|
| 501 |
+
else:
|
| 502 |
+
return cute.arch.mul_packed_f32x2(gelu_tanh_approx(x), y)
|
| 503 |
+
|
| 504 |
+
|
| 505 |
+
@dsl_user_op
|
| 506 |
+
def dgeglu(
|
| 507 |
+
x: F32_or_F32x2, y: F32_or_F32x2, dout: F32_or_F32x2, *, loc=None, ip=None
|
| 508 |
+
) -> Tuple[F32_or_F32x2, F32_or_F32x2, F32_or_F32x2]:
|
| 509 |
+
"""
|
| 510 |
+
GeGLU backward pass: computes gradients w.r.t. x (gate) and y (up projection)
|
| 511 |
+
Given: geglu_out = gelu(x) * y, and dout = grad w.r.t. geglu_out
|
| 512 |
+
Returns: (dx, dy, geglu_out) where:
|
| 513 |
+
- dx = dout * y * d_gelu(x)
|
| 514 |
+
- dy = dout * gelu(x)
|
| 515 |
+
- geglu_out = gelu(x) * y
|
| 516 |
+
"""
|
| 517 |
+
if const_expr(not isinstance(x, tuple)):
|
| 518 |
+
# Reuse dgelu_tanh_approx to compute d_gelu(x) * dout and gelu(x)
|
| 519 |
+
dgelu_x_dout, gelu_x = dgelu_tanh_approx(x, dout)
|
| 520 |
+
# Compute gradients for geglu
|
| 521 |
+
dx = dgelu_x_dout * y
|
| 522 |
+
dy = gelu_x * dout
|
| 523 |
+
geglu_out = gelu_x * y
|
| 524 |
+
return dx, dy, geglu_out
|
| 525 |
+
else:
|
| 526 |
+
# Reuse dgelu_tanh_approx to compute d_gelu(x) * dout and gelu(x)
|
| 527 |
+
dgelu_x_dout, gelu_x = dgelu_tanh_approx(x, dout)
|
| 528 |
+
# Compute gradients for geglu
|
| 529 |
+
dx = cute.arch.mul_packed_f32x2(dgelu_x_dout, y)
|
| 530 |
+
dy = cute.arch.mul_packed_f32x2(gelu_x, dout)
|
| 531 |
+
geglu_out = cute.arch.mul_packed_f32x2(gelu_x, y)
|
| 532 |
+
return dx, dy, geglu_out
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
# ============================================================================
|
| 536 |
+
# Activation name -> function maps
|
| 537 |
+
# ============================================================================
|
| 538 |
+
|
| 539 |
+
act_fn_map = {
|
| 540 |
+
None: None,
|
| 541 |
+
"silu": silu,
|
| 542 |
+
"relu": relu,
|
| 543 |
+
"relu_sq": relu_sq,
|
| 544 |
+
"gelu_tanh_approx": gelu_tanh_approx,
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
dact_fn_map = {
|
| 548 |
+
None: None,
|
| 549 |
+
"relu": drelu,
|
| 550 |
+
"relu_sq": drelu_sq,
|
| 551 |
+
"gelu_tanh_approx": dgelu_tanh_approx,
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
gate_fn_map = {
|
| 555 |
+
"swiglu": swiglu,
|
| 556 |
+
"swiglu_oai": swiglu_oai,
|
| 557 |
+
"reglu": reglu,
|
| 558 |
+
"geglu": geglu,
|
| 559 |
+
"glu": glu,
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
dgate_fn_map = {
|
| 563 |
+
"swiglu": dswiglu,
|
| 564 |
+
"swiglu_oai": dswiglu_oai,
|
| 565 |
+
"reglu": dreglu,
|
| 566 |
+
"geglu": dgeglu,
|
| 567 |
+
"glu": dglu,
|
| 568 |
+
}
|
build/torch-cuda/quack/compile_utils.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def make_fake_tensor(dtype, shape, divisibility=1, leading_dim=-1) -> Optional[cute.Tensor]:
|
| 9 |
+
if leading_dim < 0:
|
| 10 |
+
leading_dim = len(shape) + leading_dim
|
| 11 |
+
if dtype is None:
|
| 12 |
+
return None
|
| 13 |
+
stride = tuple(
|
| 14 |
+
cute.sym_int64(divisibility=divisibility) if i != leading_dim else 1
|
| 15 |
+
for i in range(len(shape))
|
| 16 |
+
)
|
| 17 |
+
return cute.runtime.make_fake_tensor(
|
| 18 |
+
dtype, shape, stride=stride, assumed_align=divisibility * dtype.width // 8
|
| 19 |
+
)
|
build/torch-cuda/quack/copy_utils.py
ADDED
|
@@ -0,0 +1,1185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Optional, Type, Tuple, Callable, Sequence
|
| 4 |
+
from functools import partial
|
| 5 |
+
|
| 6 |
+
import cutlass
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
|
| 9 |
+
from cutlass import Int32, Int16, Boolean, const_expr
|
| 10 |
+
from cutlass.cute.nvgpu import cpasync, warp, warpgroup
|
| 11 |
+
from cutlass.cute.nvgpu.tcgen05.mma import CtaGroup # noqa
|
| 12 |
+
from cutlass.cutlass_dsl import dsl_user_op
|
| 13 |
+
import cutlass.pipeline
|
| 14 |
+
from cutlass._mlir.dialects import llvm
|
| 15 |
+
from cutlass._mlir import ir
|
| 16 |
+
from cutlass._mlir.dialects import cute_nvgpu as _cute_nvgpu_ir
|
| 17 |
+
|
| 18 |
+
from . import layout_utils
|
| 19 |
+
from .utils import make_vector
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
Sm100MmaPeerBitMask = 0xFEFFFFFF
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dsl_user_op
|
| 26 |
+
def cvt_copy(
|
| 27 |
+
tiled_copy: cute.TiledCopy,
|
| 28 |
+
src: cute.Tensor,
|
| 29 |
+
dst: cute.Tensor,
|
| 30 |
+
*,
|
| 31 |
+
pred: Optional[cute.Tensor] = None,
|
| 32 |
+
retile: bool = False,
|
| 33 |
+
loc=None,
|
| 34 |
+
ip=None,
|
| 35 |
+
**kwargs,
|
| 36 |
+
) -> None:
|
| 37 |
+
assert isinstance(src.iterator, cute.Pointer) and src.memspace == cute.AddressSpace.rmem
|
| 38 |
+
if const_expr(src.element_type != dst.element_type):
|
| 39 |
+
src_cvt = cute.make_rmem_tensor_like(src, dst.element_type)
|
| 40 |
+
src_cvt.store(src.load().to(dst.element_type))
|
| 41 |
+
src = src_cvt
|
| 42 |
+
if const_expr(retile):
|
| 43 |
+
src = tiled_copy.retile(src)
|
| 44 |
+
cute.copy(tiled_copy, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@dsl_user_op
|
| 48 |
+
def sr_cvt_copy(
|
| 49 |
+
tiled_copy: cute.TiledCopy,
|
| 50 |
+
src: cute.Tensor,
|
| 51 |
+
dst: cute.Tensor,
|
| 52 |
+
seed: Int32,
|
| 53 |
+
tidx: Int32,
|
| 54 |
+
*,
|
| 55 |
+
loc=None,
|
| 56 |
+
ip=None,
|
| 57 |
+
) -> None:
|
| 58 |
+
"""Like cvt_copy but uses stochastic rounding for FP32 -> BF16 conversion."""
|
| 59 |
+
assert isinstance(src.iterator, cute.Pointer) and src.memspace == cute.AddressSpace.rmem
|
| 60 |
+
from .rounding import convert_f32_to_bf16_sr
|
| 61 |
+
from cutlass.cute.tensor import TensorSSA
|
| 62 |
+
|
| 63 |
+
src_cvt = cute.make_rmem_tensor_like(src, dst.element_type)
|
| 64 |
+
src_vec = src.load()
|
| 65 |
+
raw_vec = convert_f32_to_bf16_sr(src_vec, seed, tidx, loc=loc, ip=ip)
|
| 66 |
+
src_cvt.store(TensorSSA(raw_vec, src_vec.shape, dst.element_type))
|
| 67 |
+
src = src_cvt
|
| 68 |
+
cute.copy(tiled_copy, src, dst, loc=loc, ip=ip)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
@dsl_user_op
|
| 72 |
+
def load_s2r(src: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
| 73 |
+
dst = cute.make_rmem_tensor_like(src, src.element_type, loc=loc, ip=ip)
|
| 74 |
+
cute.autovec_copy(src, dst, loc=loc, ip=ip)
|
| 75 |
+
return dst
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@dsl_user_op
|
| 79 |
+
def load_s2r_retile(
|
| 80 |
+
tiled_copy: cute.TiledCopy,
|
| 81 |
+
src: cute.Tensor,
|
| 82 |
+
dst_shape: cute.Tensor | cute.Shape,
|
| 83 |
+
*,
|
| 84 |
+
loc=None,
|
| 85 |
+
ip=None,
|
| 86 |
+
) -> cute.Tensor:
|
| 87 |
+
# Will also accept dst_shape being a tensor, in which case we write into that tensor
|
| 88 |
+
if const_expr(not isinstance(dst_shape, cute.Tensor)):
|
| 89 |
+
dst = cute.make_rmem_tensor(dst_shape, src.element_type, loc=loc, ip=ip)
|
| 90 |
+
else:
|
| 91 |
+
dst = dst_shape
|
| 92 |
+
cute.copy(tiled_copy, src, tiled_copy.retile(dst), loc=loc, ip=ip)
|
| 93 |
+
return dst
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
@dsl_user_op
|
| 97 |
+
def load_t2r(
|
| 98 |
+
thr_copy: cute.ThrCopy, shape: cute.Shape, src: cute.Tensor, *, loc=None, ip=None
|
| 99 |
+
) -> cute.Tensor:
|
| 100 |
+
cDst = cute.make_identity_tensor(shape)
|
| 101 |
+
dst = cute.make_rmem_tensor(thr_copy.partition_D(cDst).shape, src.element_type, loc=loc, ip=ip)
|
| 102 |
+
cute.copy(thr_copy, src, dst, loc=loc, ip=ip)
|
| 103 |
+
return dst
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@dsl_user_op
|
| 107 |
+
def get_copy_atom(
|
| 108 |
+
dtype: Type[cutlass.Numeric], num_copy_elems: int, is_async: bool = False, *, loc=None, ip=None
|
| 109 |
+
) -> cute.CopyAtom:
|
| 110 |
+
num_copy_bits = const_expr(min(128, num_copy_elems * dtype.width))
|
| 111 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 112 |
+
return cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@dsl_user_op
|
| 116 |
+
def copy(
|
| 117 |
+
src: cute.Tensor,
|
| 118 |
+
dst: cute.Tensor,
|
| 119 |
+
*,
|
| 120 |
+
pred: Optional[cute.Tensor] = None,
|
| 121 |
+
is_async: bool = False,
|
| 122 |
+
loc=None,
|
| 123 |
+
ip=None,
|
| 124 |
+
**kwargs,
|
| 125 |
+
) -> None:
|
| 126 |
+
num_copy_elems = src.shape[0][0]
|
| 127 |
+
copy_atom = get_copy_atom(src.element_type, num_copy_elems, is_async)
|
| 128 |
+
cute.copy(copy_atom, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def tiled_copy_1d(
|
| 132 |
+
dtype: Type[cutlass.Numeric], num_threads: int, num_copy_elems: int = 1, is_async: bool = False
|
| 133 |
+
) -> cute.TiledCopy:
|
| 134 |
+
num_copy_bits = num_copy_elems * dtype.width
|
| 135 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 136 |
+
copy_atom = cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 137 |
+
thr_layout = cute.make_layout(num_threads)
|
| 138 |
+
val_layout = cute.make_layout(num_copy_elems)
|
| 139 |
+
return cute.make_tiled_copy_tv(copy_atom, thr_layout, val_layout)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def tiled_copy_2d(
|
| 143 |
+
dtype: Type[cutlass.Numeric],
|
| 144 |
+
threads_per_row: int,
|
| 145 |
+
num_threads: int,
|
| 146 |
+
num_copy_elems: int = 1,
|
| 147 |
+
is_async: bool = False,
|
| 148 |
+
) -> cute.TiledCopy:
|
| 149 |
+
num_copy_bits = num_copy_elems * dtype.width
|
| 150 |
+
copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 151 |
+
copy_atom = cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 152 |
+
assert num_threads % threads_per_row == 0
|
| 153 |
+
thr_layout = cute.make_ordered_layout(
|
| 154 |
+
(num_threads // threads_per_row, threads_per_row),
|
| 155 |
+
order=(1, 0),
|
| 156 |
+
)
|
| 157 |
+
val_layout = cute.make_layout((1, num_copy_elems))
|
| 158 |
+
return cute.make_tiled_copy_tv(copy_atom, thr_layout, val_layout)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
@cute.jit
|
| 162 |
+
def predicate_k(tAcA: cute.Tensor, limit: Int32) -> cute.Tensor:
|
| 163 |
+
# Only compute predicates for the "k" dimension. For the mn dimension, we will use "if"
|
| 164 |
+
tApA = cute.make_rmem_tensor(
|
| 165 |
+
cute.make_layout(
|
| 166 |
+
(cute.size(tAcA, mode=[0, 1]), cute.size(tAcA, mode=[1]), cute.size(tAcA, mode=[2])),
|
| 167 |
+
stride=(cute.size(tAcA, mode=[2]), 0, 1),
|
| 168 |
+
),
|
| 169 |
+
Boolean,
|
| 170 |
+
)
|
| 171 |
+
for rest_v in cutlass.range_constexpr(tApA.shape[0]):
|
| 172 |
+
for rest_k in cutlass.range_constexpr(tApA.shape[2]):
|
| 173 |
+
tApA[rest_v, 0, rest_k] = cute.elem_less(tAcA[(0, rest_v), 0, rest_k][1], limit)
|
| 174 |
+
return tApA
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
# def tiled_copy_2d(
|
| 178 |
+
# dtype: Type[cutlass.Numeric], major_mode_size: int, num_threads: int, is_async: bool = False
|
| 179 |
+
# ) -> cute.TiledCopy:
|
| 180 |
+
# num_copy_bits = math.gcd(major_mode_size, 128 // dtype.width) * dtype.width
|
| 181 |
+
# copy_elems = num_copy_bits // dtype.width
|
| 182 |
+
# copy_op = cpasync.CopyG2SOp() if is_async else cute.nvgpu.CopyUniversalOp()
|
| 183 |
+
# copy_atom = cute.make_copy_atom(copy_op, dtype, num_bits_per_copy=num_copy_bits)
|
| 184 |
+
# gmem_threads_per_row = major_mode_size // copy_elems
|
| 185 |
+
# assert num_threads % gmem_threads_per_row == 0
|
| 186 |
+
# thr_layout = cute.make_ordered_layout(
|
| 187 |
+
# (num_threads // gmem_threads_per_row, gmem_threads_per_row),
|
| 188 |
+
# order=(1, 0),
|
| 189 |
+
# )
|
| 190 |
+
# val_layout = cute.make_layout((1, copy_elems))
|
| 191 |
+
# return cute.make_tiled_copy_tv(copy_atom, thr_layout, val_layout)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
# Ragged tensor trick for TMA: encodes variable-length sequences into a higher-rank
|
| 195 |
+
# tensor so that TMA's out-of-bounds checking handles sequence boundaries.
|
| 196 |
+
#
|
| 197 |
+
# Given a tensor T with a ragged dimension (variable-length across batches), we create
|
| 198 |
+
# a higher-rank tensor where the ragged dim is replaced with a fixed size `big_int`, and
|
| 199 |
+
# extra dim(s) are appended. When indexing into a specific sequence at (offset, length),
|
| 200 |
+
# `offset_ragged_tensor` computes coordinates such that:
|
| 201 |
+
# ragged_coord = big_int - length (OOB check clamps reads past the sequence end)
|
| 202 |
+
# extra_coord(s) = f(offset, length) (selects the correct memory region)
|
| 203 |
+
#
|
| 204 |
+
# ptr_shift=True: 1-extra-dim approach (adds 1 dim, supports up to 4D input):
|
| 205 |
+
# Shape: (*before, big_int, *after, max_int)
|
| 206 |
+
# Stride: (*original_strides, stride_r) where stride_r = T.stride[ragged_dim]
|
| 207 |
+
# Pointer shifted backward by big_int * stride_r elements.
|
| 208 |
+
# Address for coords (big_int - length) in ragged dim, (offset + length) in extra dim:
|
| 209 |
+
# addr = (base - big_int * s_r) + (big_int - length) * s_r + (offset + length) * s_r
|
| 210 |
+
# = base + offset * s_r [correct]
|
| 211 |
+
# Works for epilogue TMA store. Does NOT work for TMA load with large big_int
|
| 212 |
+
# — the shifted pointer must land in physically mapped GPU memory.
|
| 213 |
+
#
|
| 214 |
+
# ptr_shift=False: 2-extra-dim approach (adds 2 dims, supports up to 3D input):
|
| 215 |
+
# Shape: (*before, big_int, *after, max_int, max_int)
|
| 216 |
+
# Stride: (*before_strides, stride_r, *after_strides, 2^34 - stride_r, stride_r)
|
| 217 |
+
# No pointer shift. Uses 64-bit address wraparound to cancel the ragged offset.
|
| 218 |
+
# Let W = 2^34 - stride_r. Address for coords (big_int - length) in ragged dim,
|
| 219 |
+
# big_int in extra dim 0, (offset + length) in extra dim 1:
|
| 220 |
+
# addr = base + (big_int - length) * s_r + big_int * W + (offset + length) * s_r
|
| 221 |
+
# = base + big_int * (s_r + W) - length * s_r + (offset + length) * s_r
|
| 222 |
+
# = base + big_int * 2^34 + offset * s_r
|
| 223 |
+
# Since big_int = 2^30: big_int * 2^34 = 2^64 ≡ 0 (mod 2^64), so:
|
| 224 |
+
# addr = base + offset * s_r [correct]
|
| 225 |
+
# Works for all TMA paths since the base pointer is never shifted.
|
| 226 |
+
#
|
| 227 |
+
# Ragged tensor was adapted from the implementation from Triton, but here we have an option that
|
| 228 |
+
# only needs 1 extra dimension instead of 2.
|
| 229 |
+
# https://github.com/triton-lang/triton/blob/main/python/triton/tools/ragged_tma.py
|
| 230 |
+
BIG_INT = 2**30
|
| 231 |
+
MAX_INT = 2**31 - 1
|
| 232 |
+
BIG_INT_INV = 2**64 // BIG_INT
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
@dsl_user_op
|
| 236 |
+
def create_ragged_tensor_for_tma(
|
| 237 |
+
T: cute.Tensor,
|
| 238 |
+
ragged_dim: int = 0,
|
| 239 |
+
ptr_shift: bool = False,
|
| 240 |
+
*,
|
| 241 |
+
loc=None,
|
| 242 |
+
ip=None,
|
| 243 |
+
) -> cute.Tensor:
|
| 244 |
+
rank = cute.rank(T)
|
| 245 |
+
if ragged_dim < 0:
|
| 246 |
+
ragged_dim += rank
|
| 247 |
+
if ptr_shift:
|
| 248 |
+
assert rank <= 4, "ptr_shift ragged tensor only supports up to 4 dimensions"
|
| 249 |
+
new_shape = T.shape[:ragged_dim] + (BIG_INT,) + T.shape[ragged_dim + 1 :] + (MAX_INT,)
|
| 250 |
+
new_stride = T.stride + (T.stride[ragged_dim],)
|
| 251 |
+
ptr_offset = (None,) * ragged_dim + (-BIG_INT,) + (None,) * (rank - ragged_dim - 1)
|
| 252 |
+
new_ptr = cute.domain_offset(ptr_offset, T).iterator
|
| 253 |
+
return cute.make_tensor(new_ptr, cute.make_layout(new_shape, stride=new_stride))
|
| 254 |
+
else:
|
| 255 |
+
assert rank <= 3, "non-ptr_shift ragged tensor only supports up to 3 dimensions"
|
| 256 |
+
stride_r = T.stride[ragged_dim]
|
| 257 |
+
new_shape = (
|
| 258 |
+
T.shape[:ragged_dim] + (BIG_INT,) + T.shape[ragged_dim + 1 :] + (MAX_INT, MAX_INT)
|
| 259 |
+
)
|
| 260 |
+
new_stride = (
|
| 261 |
+
T.stride[:ragged_dim]
|
| 262 |
+
+ (stride_r,)
|
| 263 |
+
+ T.stride[ragged_dim + 1 :]
|
| 264 |
+
+ (BIG_INT_INV - stride_r, stride_r)
|
| 265 |
+
)
|
| 266 |
+
return cute.make_tensor(T.iterator, cute.make_layout(new_shape, stride=new_stride))
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
@dsl_user_op
|
| 270 |
+
def offset_ragged_tensor(
|
| 271 |
+
T: cute.Tensor,
|
| 272 |
+
offset: Int32,
|
| 273 |
+
length: Int32,
|
| 274 |
+
ragged_dim: int = 0,
|
| 275 |
+
ptr_shift: bool = False,
|
| 276 |
+
*,
|
| 277 |
+
loc=None,
|
| 278 |
+
ip=None,
|
| 279 |
+
) -> cute.Tensor:
|
| 280 |
+
rank = cute.rank(T)
|
| 281 |
+
if ragged_dim < 0:
|
| 282 |
+
ragged_dim += rank
|
| 283 |
+
big_int = cute.size(T, mode=[ragged_dim])
|
| 284 |
+
offset_val = big_int - length
|
| 285 |
+
if ptr_shift:
|
| 286 |
+
# 1-extra-dim: rank = original_rank + 1
|
| 287 |
+
assert rank >= ragged_dim + 2
|
| 288 |
+
offset_tuple = (None,) * ragged_dim + (offset_val,) + (None,) * (rank - ragged_dim - 2)
|
| 289 |
+
index_tuple = (None,) * (rank - 1) + (offset + length,)
|
| 290 |
+
else:
|
| 291 |
+
# 2-extra-dim: rank = original_rank + 2, last 2 modes are the wraparound dims
|
| 292 |
+
assert rank >= ragged_dim + 3
|
| 293 |
+
offset_tuple = (None,) * ragged_dim + (offset_val,) + (None,) * (rank - ragged_dim - 3)
|
| 294 |
+
index_tuple = (None,) * (rank - 2) + (big_int, offset + length)
|
| 295 |
+
return cute.domain_offset(offset_tuple, T[index_tuple])
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def swizzle_int(ptr_int: Int32, b: int, m: int, s: int) -> Int32:
|
| 299 |
+
bit_msk = (1 << b) - 1
|
| 300 |
+
yyy_msk = bit_msk << (m + s)
|
| 301 |
+
return ptr_int ^ ((ptr_int & yyy_msk) >> s)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def swizzle_ptr(ptr: cute.Pointer):
|
| 305 |
+
swz = ptr.type.swizzle_type
|
| 306 |
+
ptr_int = swizzle_int(ptr.toint(), swz.num_bits, swz.num_base, swz.num_shift)
|
| 307 |
+
return cute.make_ptr(ptr.dtype, ptr_int, ptr.memspace, assumed_align=ptr.alignment)
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def as_position_independent_swizzle_tensor(tensor: cute.Tensor) -> cute.Tensor:
|
| 311 |
+
outer = tensor.layout
|
| 312 |
+
width = tensor.element_type.width
|
| 313 |
+
swizzle_type = tensor.iterator.type.swizzle_type
|
| 314 |
+
inner = cute.make_swizzle(swizzle_type.num_bits, swizzle_type.num_base, swizzle_type.num_shift)
|
| 315 |
+
# Need to recast the swizzle from byte (e.g. <3, 4, 3> to element units (e.g. <3, 3, 3> for
|
| 316 |
+
# for 16 bits and <3, 2, 3> for 32 bits)
|
| 317 |
+
new_layout = cute.recast_layout(
|
| 318 |
+
width, 8, cute.make_composed_layout(inner, 0, cute.recast_layout(8, width, outer))
|
| 319 |
+
)
|
| 320 |
+
# recast_ptr to remove the pointer swizzle
|
| 321 |
+
return cute.make_tensor(cute.recast_ptr(tensor.iterator, dtype=tensor.element_type), new_layout)
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def partition_D_position_independent(
|
| 325 |
+
thr_copy: cute.core.ThrCopy, tensor: cute.Tensor
|
| 326 |
+
) -> cute.Tensor:
|
| 327 |
+
return cute.make_tensor(
|
| 328 |
+
swizzle_ptr(thr_copy.partition_D(tensor).iterator),
|
| 329 |
+
thr_copy.partition_D(as_position_independent_swizzle_tensor(tensor)).layout,
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def partition_S_position_independent(
|
| 334 |
+
thr_copy: cute.core.ThrCopy, tensor: cute.Tensor
|
| 335 |
+
) -> cute.Tensor:
|
| 336 |
+
return cute.make_tensor(
|
| 337 |
+
swizzle_ptr(thr_copy.partition_S(tensor).iterator),
|
| 338 |
+
thr_copy.partition_S(as_position_independent_swizzle_tensor(tensor)).layout,
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
@dsl_user_op
|
| 343 |
+
def sm90_get_smem_load_op(
|
| 344 |
+
layout_c: cutlass.utils.LayoutEnum,
|
| 345 |
+
elem_ty_c: Type[cutlass.Numeric],
|
| 346 |
+
*,
|
| 347 |
+
loc=None,
|
| 348 |
+
ip=None,
|
| 349 |
+
) -> cute.CopyAtom:
|
| 350 |
+
"""
|
| 351 |
+
Selects the largest vectorized smem load atom available subject to constraint of gmem layout.
|
| 352 |
+
|
| 353 |
+
Parameters:
|
| 354 |
+
-----------
|
| 355 |
+
layout_c : LayoutEnum
|
| 356 |
+
The layout enum of the output tensor D.
|
| 357 |
+
|
| 358 |
+
elem_ty_c : Type[Numeric]
|
| 359 |
+
The element type for output tensor D.
|
| 360 |
+
|
| 361 |
+
Returns:
|
| 362 |
+
--------
|
| 363 |
+
Either SmemLoadMatrix or SimtSyncCopy, based on the input parameters.
|
| 364 |
+
"""
|
| 365 |
+
|
| 366 |
+
if not isinstance(elem_ty_c, cutlass.cutlass_dsl.NumericMeta):
|
| 367 |
+
raise TypeError(f"elem_ty_c must be a Numeric, but got {elem_ty_c}")
|
| 368 |
+
is_m_major = layout_c.is_m_major_c()
|
| 369 |
+
if elem_ty_c.width == 16:
|
| 370 |
+
return cute.make_copy_atom(warp.LdMatrix8x8x16bOp(is_m_major, 4), elem_ty_c, loc=loc, ip=ip)
|
| 371 |
+
else:
|
| 372 |
+
return cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), elem_ty_c, loc=loc, ip=ip)
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
def get_smem_store_atom(
|
| 376 |
+
arch: cutlass.Constexpr[int],
|
| 377 |
+
element_type: Type[cute.Numeric],
|
| 378 |
+
transpose: bool = False,
|
| 379 |
+
major_mode_size: Optional[int] = None,
|
| 380 |
+
) -> cute.CopyAtom:
|
| 381 |
+
if const_expr(arch < 90 or element_type.width != 16):
|
| 382 |
+
return cute.make_copy_atom(
|
| 383 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 384 |
+
element_type,
|
| 385 |
+
num_bits_per_copy=(2 if not transpose else 1) * element_type.width,
|
| 386 |
+
)
|
| 387 |
+
else:
|
| 388 |
+
num_matrices = (
|
| 389 |
+
4
|
| 390 |
+
if major_mode_size is None or major_mode_size % 16 == 0
|
| 391 |
+
else (2 if major_mode_size % 8 == 0 else 1)
|
| 392 |
+
)
|
| 393 |
+
return cute.make_copy_atom(
|
| 394 |
+
warp.StMatrix8x8x16bOp(transpose=transpose, num_matrices=num_matrices),
|
| 395 |
+
element_type,
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def get_smem_load_atom(
|
| 400 |
+
arch: cutlass.Constexpr[int],
|
| 401 |
+
element_type: Type[cute.Numeric],
|
| 402 |
+
transpose: bool = False,
|
| 403 |
+
major_mode_size: Optional[int] = None,
|
| 404 |
+
) -> cute.CopyAtom:
|
| 405 |
+
if const_expr(arch < 90 or element_type.width != 16):
|
| 406 |
+
return cute.make_copy_atom(
|
| 407 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 408 |
+
element_type,
|
| 409 |
+
num_bits_per_copy=(2 if not transpose else 1) * element_type.width,
|
| 410 |
+
)
|
| 411 |
+
else:
|
| 412 |
+
num_matrices = (
|
| 413 |
+
4
|
| 414 |
+
if major_mode_size is None or major_mode_size % 16 == 0
|
| 415 |
+
else (2 if major_mode_size % 8 == 0 else 1)
|
| 416 |
+
)
|
| 417 |
+
return cute.make_copy_atom(
|
| 418 |
+
warp.LdMatrix8x8x16bOp(transpose=transpose, num_matrices=num_matrices),
|
| 419 |
+
element_type,
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
def get_smem_store_C(
|
| 424 |
+
tiled_mma: cute.TiledMma,
|
| 425 |
+
sC: cute.Tensor,
|
| 426 |
+
tidx: Int32,
|
| 427 |
+
arch: int,
|
| 428 |
+
transpose: bool = False,
|
| 429 |
+
position_independent=False,
|
| 430 |
+
major_mode_size: Optional[int] = None,
|
| 431 |
+
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 432 |
+
dtype = sC.element_type
|
| 433 |
+
copy_atom = get_smem_store_atom(arch, dtype, transpose, major_mode_size=major_mode_size)
|
| 434 |
+
tiled_copy = cute.make_tiled_copy_C(copy_atom, tiled_mma)
|
| 435 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 436 |
+
if const_expr(not position_independent):
|
| 437 |
+
tRS_sC = thr_copy.partition_D(sC)
|
| 438 |
+
else:
|
| 439 |
+
tRS_sC = partition_D_position_independent(thr_copy, sC)
|
| 440 |
+
|
| 441 |
+
def copy_fn(src: cute.Tensor, dst_idx: Optional[Int32] = None, **new_kwargs):
|
| 442 |
+
dst_tensor = tRS_sC if const_expr(dst_idx is None) else tRS_sC[None, None, None, dst_idx]
|
| 443 |
+
cvt_copy(tiled_copy, src, dst_tensor, retile=True, **new_kwargs)
|
| 444 |
+
|
| 445 |
+
return copy_fn, thr_copy, tRS_sC
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
def get_smem_load_C(
|
| 449 |
+
tiled_mma: cute.TiledMma,
|
| 450 |
+
sC: cute.Tensor,
|
| 451 |
+
tidx: Int32,
|
| 452 |
+
arch: int,
|
| 453 |
+
transpose: bool = False,
|
| 454 |
+
position_independent=False,
|
| 455 |
+
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 456 |
+
dtype = sC.element_type
|
| 457 |
+
copy_atom = get_smem_load_atom(arch, dtype, transpose)
|
| 458 |
+
tiled_copy = cute.make_tiled_copy_C(copy_atom, tiled_mma)
|
| 459 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 460 |
+
if const_expr(not position_independent):
|
| 461 |
+
tSR_sC = thr_copy.partition_S(sC)
|
| 462 |
+
else:
|
| 463 |
+
tSR_sC = partition_S_position_independent(thr_copy, sC)
|
| 464 |
+
copy_atom_RS = get_smem_store_atom(arch, dtype, transpose)
|
| 465 |
+
thr_copy_RS = cute.make_tiled_copy_C(copy_atom_RS, tiled_mma).get_slice(tidx)
|
| 466 |
+
tRS_shape = thr_copy_RS.partition_S(cute.make_identity_tensor(sC.shape[:2])).shape
|
| 467 |
+
|
| 468 |
+
def copy_fn(src_idx: Optional[Int32] = None, **new_kwargs):
|
| 469 |
+
src_tensor = tSR_sC if const_expr(src_idx is None) else tSR_sC[None, None, None, src_idx]
|
| 470 |
+
return load_s2r_retile(tiled_copy, src_tensor, dst_shape=tRS_shape, **new_kwargs)
|
| 471 |
+
|
| 472 |
+
return copy_fn, thr_copy, tSR_sC
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def epilog_smem_copy_atom(
|
| 476 |
+
tiled_mma: cute.TiledMma, epi_tile: cute.Shape, transpose: bool = False
|
| 477 |
+
) -> cute.TiledCopy:
|
| 478 |
+
copy_atom_C = cute.make_copy_atom(
|
| 479 |
+
warp.StMatrix8x8x16bOp(transpose, num_matrices=4 if epi_tile[1] % 16 == 0 else 2),
|
| 480 |
+
cutlass.Float16, # this is just to get the right source layout
|
| 481 |
+
)
|
| 482 |
+
tiled_copy_C_atom = cute.make_tiled_copy_C_atom(copy_atom_C, tiled_mma)
|
| 483 |
+
return tiled_copy_C_atom
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def get_smem_store_epi(
|
| 487 |
+
tiled_mma: cute.TiledMma,
|
| 488 |
+
epi_tile: cute.Shape,
|
| 489 |
+
sC: Optional[cute.Tensor],
|
| 490 |
+
tidx: Int32,
|
| 491 |
+
arch: int,
|
| 492 |
+
transpose: bool = False,
|
| 493 |
+
position_independent=False,
|
| 494 |
+
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 495 |
+
dtype = sC.element_type if const_expr(sC is not None) else cutlass.Float16
|
| 496 |
+
tiled_copy_C_atom = epilog_smem_copy_atom(tiled_mma, epi_tile)
|
| 497 |
+
copy_atom = get_smem_store_atom(arch, dtype, transpose)
|
| 498 |
+
tiled_copy = cute.make_tiled_copy_S(copy_atom, tiled_copy_C_atom)
|
| 499 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 500 |
+
tRS_sC = None
|
| 501 |
+
if const_expr(sC is not None):
|
| 502 |
+
if const_expr(not position_independent):
|
| 503 |
+
tRS_sC = thr_copy.partition_D(sC)
|
| 504 |
+
else:
|
| 505 |
+
tRS_sC = partition_D_position_independent(thr_copy, sC)
|
| 506 |
+
sC_shape = sC.shape[:2] if sC is not None else epi_tile
|
| 507 |
+
# (R2S, R2S_M, R2S_N, PIPE_C)
|
| 508 |
+
tRS_rC_shape = thr_copy.partition_S(cute.make_identity_tensor(sC_shape)).shape
|
| 509 |
+
tRS_rC = cute.make_rmem_tensor(tRS_rC_shape, tiled_mma.op.acc_dtype)
|
| 510 |
+
|
| 511 |
+
def copy_fn(src: cute.Tensor, dst_idx: Int32, **new_kwargs):
|
| 512 |
+
cvt_copy(tiled_copy, src, tRS_sC[None, None, None, dst_idx], **new_kwargs)
|
| 513 |
+
|
| 514 |
+
return copy_fn if const_expr(sC is not None) else None, thr_copy, tRS_sC, tRS_rC
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
def get_smem_store_A(
|
| 518 |
+
tiled_mma: cute.TiledMma, sA: cute.Tensor, tidx: Int32, arch: int, position_independent=False
|
| 519 |
+
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 520 |
+
dtype = sA.element_type
|
| 521 |
+
transpose = tiled_mma.op.a_major_mode == warpgroup.OperandMajorMode.MN
|
| 522 |
+
copy_atom = get_smem_store_atom(arch, dtype, transpose)
|
| 523 |
+
tiled_copy = cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 524 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 525 |
+
if const_expr(not position_independent):
|
| 526 |
+
tRS_sA = thr_copy.partition_D(sA)
|
| 527 |
+
else:
|
| 528 |
+
tRS_sA = partition_D_position_independent(thr_copy, sA)
|
| 529 |
+
|
| 530 |
+
def copy_fn(src: cute.Tensor, dst_idx: Int32, **new_kwargs):
|
| 531 |
+
cvt_copy(tiled_copy, src, tRS_sA[None, None, None, dst_idx], retile=True, **new_kwargs)
|
| 532 |
+
|
| 533 |
+
return copy_fn, thr_copy, tRS_sA
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
def get_smem_load_A(
|
| 537 |
+
tiled_mma: cute.TiledMma,
|
| 538 |
+
sA: cute.Tensor,
|
| 539 |
+
tidx: Int32,
|
| 540 |
+
arch: int,
|
| 541 |
+
with_dst_tensor: bool = False,
|
| 542 |
+
position_independent=False,
|
| 543 |
+
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 544 |
+
dtype = sA.element_type
|
| 545 |
+
transpose = tiled_mma.op.a_major_mode == warpgroup.OperandMajorMode.MN
|
| 546 |
+
copy_atom = get_smem_load_atom(arch, dtype, transpose)
|
| 547 |
+
tiled_copy = cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 548 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 549 |
+
if const_expr(not position_independent):
|
| 550 |
+
tSR_sA = thr_copy.partition_S(sA)
|
| 551 |
+
else:
|
| 552 |
+
tSR_sA = partition_S_position_independent(thr_copy, sA)
|
| 553 |
+
tRS_shape = tiled_mma.partition_shape_A(sA.shape[:2])
|
| 554 |
+
|
| 555 |
+
def copy_fn(src_idx: Int32, **new_kwargs):
|
| 556 |
+
return load_s2r_retile(
|
| 557 |
+
tiled_copy, tSR_sA[None, None, None, src_idx], dst_shape=tRS_shape, **new_kwargs
|
| 558 |
+
)
|
| 559 |
+
|
| 560 |
+
def copy_fn_w_dst_tensor(src_idx: Int32, dst: cute.Tensor, **new_kwargs):
|
| 561 |
+
return load_s2r_retile(tiled_copy, tSR_sA[None, None, None, src_idx], dst, **new_kwargs)
|
| 562 |
+
|
| 563 |
+
return copy_fn if not with_dst_tensor else copy_fn_w_dst_tensor, thr_copy, tSR_sA
|
| 564 |
+
|
| 565 |
+
|
| 566 |
+
@dsl_user_op
|
| 567 |
+
def cpasync_reduce_bulk_add_f32(
|
| 568 |
+
smem_ptr: cute.Pointer,
|
| 569 |
+
gmem_ptr: cute.Pointer,
|
| 570 |
+
store_bytes: int | Int32,
|
| 571 |
+
*,
|
| 572 |
+
loc=None,
|
| 573 |
+
ip=None,
|
| 574 |
+
):
|
| 575 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 576 |
+
# cache_hint = cutlass.Int64(0x14F0000000000000) # EVICT_LAST
|
| 577 |
+
llvm.inline_asm(
|
| 578 |
+
None,
|
| 579 |
+
[gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value()],
|
| 580 |
+
"cp.reduce.async.bulk.global.shared::cta.bulk_group.add.f32 [$0], [$1], $2;",
|
| 581 |
+
"l,r,r",
|
| 582 |
+
# [gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value(), cache_hint.ir_value()],
|
| 583 |
+
# "cp.reduce.async.bulk.global.shared::cta.bulk_group.L2::cache_hint.add.f32 [$0], [$1], $2, $3;",
|
| 584 |
+
# "l,r,r,l",
|
| 585 |
+
has_side_effects=True,
|
| 586 |
+
is_align_stack=False,
|
| 587 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
@dsl_user_op
|
| 592 |
+
def get_tma_desc_addr(tma_atom: cute.CopyAtom, *, loc=None, ip=None) -> cute.Pointer:
|
| 593 |
+
"""
|
| 594 |
+
Get the address of the TMA descriptor embedded in a TMA Copy Atom.
|
| 595 |
+
|
| 596 |
+
Extracts the constant memory address of the TMA descriptor for use with
|
| 597 |
+
custom PTX instructions.
|
| 598 |
+
|
| 599 |
+
:param tma_atom: TMA Copy Atom from make_tiled_tma_atom
|
| 600 |
+
:return: Pointer to TMA descriptor in constant memory
|
| 601 |
+
|
| 602 |
+
Example:
|
| 603 |
+
>>> desc_ptr = get_tma_descriptor_address(tma_atom)
|
| 604 |
+
"""
|
| 605 |
+
exec_atom = _cute_nvgpu_ir.atom_make_exec_tma(tma_atom._trait.value, loc=loc, ip=ip)
|
| 606 |
+
tma_desc_ptr_type = ir.Type.parse(
|
| 607 |
+
"!cute.ptr<!cute_nvgpu.tma_descriptor_tiled, generic, align<128>>"
|
| 608 |
+
)
|
| 609 |
+
return _cute_nvgpu_ir.get_tma_desc_addr(tma_desc_ptr_type, exec_atom, loc=loc, ip=ip)
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
@dsl_user_op
|
| 613 |
+
def tma_gather4_load(
|
| 614 |
+
tma_desc_ptr: cute.Pointer,
|
| 615 |
+
dst_smem_ptr: cute.Pointer,
|
| 616 |
+
mbarrier_ptr: cute.Pointer,
|
| 617 |
+
col_idx: Int32,
|
| 618 |
+
row_indices: Sequence[Int32],
|
| 619 |
+
*,
|
| 620 |
+
num_cta: int = 1,
|
| 621 |
+
multicast_mask=None,
|
| 622 |
+
loc=None,
|
| 623 |
+
ip=None,
|
| 624 |
+
) -> None:
|
| 625 |
+
"""
|
| 626 |
+
Perform TMA gather4 load from global memory to shared memory.
|
| 627 |
+
|
| 628 |
+
Issues PTX instruction:
|
| 629 |
+
cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4.mbarrier::complete_tx::bytes
|
| 630 |
+
[dstMem], [tensorMap, {col_idx, row0, row1, row2, row3}], [smem_bar];
|
| 631 |
+
|
| 632 |
+
This loads 4 rows (specified by row_indices) from a 2D tensor at the given
|
| 633 |
+
column index into shared memory, using the TMA descriptor.
|
| 634 |
+
|
| 635 |
+
:param tma_desc_ptr: Pointer to TMA descriptor in constant memory (128-byte aligned)
|
| 636 |
+
:type tma_desc_ptr: Pointer
|
| 637 |
+
:param dst_smem_ptr: Destination address in shared memory
|
| 638 |
+
:type dst_smem_ptr: Pointer
|
| 639 |
+
:param mbarrier_ptr: Pointer to mbarrier in shared memory for completion tracking
|
| 640 |
+
:type mbarrier_ptr: Pointer
|
| 641 |
+
:param col_idx: Column index
|
| 642 |
+
:type col_idx: Int32
|
| 643 |
+
:param row_indices: Sequence of exactly 4 row indices
|
| 644 |
+
:type row_indices: Sequence[Int32]
|
| 645 |
+
:param num_cta: Number of CTAs participating (default: 1)
|
| 646 |
+
:type num_cta: int
|
| 647 |
+
:param multicast_mask: Optional multicast mask
|
| 648 |
+
:type multicast_mask: Int16
|
| 649 |
+
|
| 650 |
+
Requirements:
|
| 651 |
+
- row_indices must contain exactly 4 elements
|
| 652 |
+
- Compute capability >= SM_100 (Blackwell)
|
| 653 |
+
- TMA descriptor must be properly initialized for 2D tensor
|
| 654 |
+
|
| 655 |
+
Example:
|
| 656 |
+
>>> from cutlass.cute.nvgpu import cpasync
|
| 657 |
+
>>> from cutlass.cute import core
|
| 658 |
+
>>>
|
| 659 |
+
>>> # Create TMA descriptor
|
| 660 |
+
>>> tma_atom, tma_tensor = cpasync.make_tiled_tma_atom(...)
|
| 661 |
+
>>> tma_desc_ptr = get_tma_descriptor_address(tma_atom)
|
| 662 |
+
>>>
|
| 663 |
+
>>> # Compute indices (typically from kernel logic)
|
| 664 |
+
>>> col_idx = core.get(...) or 5 # Int32 value
|
| 665 |
+
>>> row_indices = [core.get(...) for _ in range(4)] # 4 Int32 values
|
| 666 |
+
>>>
|
| 667 |
+
>>> # Gather 4 rows at computed column
|
| 668 |
+
>>> tma_gather4_load(
|
| 669 |
+
... tma_desc_ptr=tma_desc_ptr,
|
| 670 |
+
... dst_smem_ptr=smem_ptr,
|
| 671 |
+
... mbarrier_ptr=barrier_ptr,
|
| 672 |
+
... col_idx=col_idx,
|
| 673 |
+
... row_indices=row_indices
|
| 674 |
+
... )
|
| 675 |
+
"""
|
| 676 |
+
if len(row_indices) != 4:
|
| 677 |
+
raise ValueError(f"gather4 requires exactly 4 row indices, got {len(row_indices)}")
|
| 678 |
+
col_val = Int32(col_idx).ir_value()
|
| 679 |
+
row_vals = [Int32(row_idx).ir_value() for row_idx in row_indices]
|
| 680 |
+
# Convert pointers to integer addresses
|
| 681 |
+
desc_addr = tma_desc_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 682 |
+
dst_addr = dst_smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 683 |
+
mbar_addr = mbarrier_ptr.toint(loc=loc, ip=ip)
|
| 684 |
+
if num_cta > 1:
|
| 685 |
+
# Executed by both CTAs. Set peer bit to 0 so that the
|
| 686 |
+
# transaction bytes will update CTA0's barrier.
|
| 687 |
+
mbar_addr = mbar_addr & Sm100MmaPeerBitMask
|
| 688 |
+
mbar_addr = mbar_addr.ir_value()
|
| 689 |
+
# Handle multicast_mask - may already be ir.Value or Python int
|
| 690 |
+
multicast_mask_val = None
|
| 691 |
+
if multicast_mask is not None:
|
| 692 |
+
multicast_mask_val = Int16(multicast_mask).ir_value()
|
| 693 |
+
assert multicast_mask_val is None, "multicast is not supported yet"
|
| 694 |
+
# Emit inline PTX for TMA gather4
|
| 695 |
+
# PTX: cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4.mbarrier::complete_tx::bytes
|
| 696 |
+
# [dstMem], [tensorMap, {col, row0, row1, row2, row3}], [smem_bar];
|
| 697 |
+
ptx = (
|
| 698 |
+
f"cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4.mbarrier::complete_tx::bytes.cta_group::{num_cta} "
|
| 699 |
+
"[$0], [$1, {$2, $3, $4, $5, $6}], [$7];"
|
| 700 |
+
)
|
| 701 |
+
|
| 702 |
+
llvm.inline_asm(
|
| 703 |
+
None,
|
| 704 |
+
[
|
| 705 |
+
dst_addr,
|
| 706 |
+
desc_addr,
|
| 707 |
+
col_val,
|
| 708 |
+
row_vals[0],
|
| 709 |
+
row_vals[1],
|
| 710 |
+
row_vals[2],
|
| 711 |
+
row_vals[3],
|
| 712 |
+
mbar_addr,
|
| 713 |
+
],
|
| 714 |
+
ptx,
|
| 715 |
+
"r,l,r,r,r,r,r,r", # constraints: register, long, 6x register
|
| 716 |
+
has_side_effects=True,
|
| 717 |
+
is_align_stack=False,
|
| 718 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 719 |
+
loc=loc,
|
| 720 |
+
ip=ip,
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
def cpasync_bulk_get_copy_fn(
|
| 725 |
+
src_tensor: cute.Tensor,
|
| 726 |
+
dst_tensor: cute.Tensor,
|
| 727 |
+
single_stage: bool = False,
|
| 728 |
+
**kwargs,
|
| 729 |
+
) -> Callable:
|
| 730 |
+
group_rank_src = const_expr(cute.rank(src_tensor) - (1 if not single_stage else 0))
|
| 731 |
+
group_rank_dst = const_expr(cute.rank(dst_tensor) - (1 if not single_stage else 0))
|
| 732 |
+
# ((atom_v, rest_v), STAGE), ((atom_v, rest_v), RestK)
|
| 733 |
+
src = cute.group_modes(src_tensor, 0, group_rank_src)
|
| 734 |
+
dst = cute.group_modes(dst_tensor, 0, group_rank_dst)
|
| 735 |
+
|
| 736 |
+
def copy_bulk(src_idx, dst_idx, tma_bar_ptr: cute.Pointer, **new_kwargs):
|
| 737 |
+
atom = cute.make_copy_atom(cpasync.CopyBulkG2SOp(), src.element_type)
|
| 738 |
+
with cute.arch.elect_one():
|
| 739 |
+
cute.copy(
|
| 740 |
+
atom,
|
| 741 |
+
src[None, src_idx],
|
| 742 |
+
dst[None, dst_idx],
|
| 743 |
+
mbar_ptr=tma_bar_ptr,
|
| 744 |
+
**new_kwargs,
|
| 745 |
+
**kwargs,
|
| 746 |
+
)
|
| 747 |
+
|
| 748 |
+
def copy_bulk_single_stage(tma_bar_ptr: cute.Pointer, **new_kwargs):
|
| 749 |
+
atom = cute.make_copy_atom(cpasync.CopyBulkG2SOp(), src.element_type)
|
| 750 |
+
with cute.arch.elect_one():
|
| 751 |
+
cute.copy(atom, src, dst, mbar_ptr=tma_bar_ptr, **new_kwargs, **kwargs)
|
| 752 |
+
|
| 753 |
+
return copy_bulk if const_expr(not single_stage) else copy_bulk_single_stage
|
| 754 |
+
|
| 755 |
+
|
| 756 |
+
@dsl_user_op
|
| 757 |
+
def tma_get_copy_fn(
|
| 758 |
+
atom: cute.CopyAtom,
|
| 759 |
+
cta_coord: cute.Coord,
|
| 760 |
+
cta_layout: cute.Layout,
|
| 761 |
+
src_tensor: cute.Tensor,
|
| 762 |
+
dst_tensor: cute.Tensor,
|
| 763 |
+
filter_zeros: bool = False,
|
| 764 |
+
single_stage: bool = False,
|
| 765 |
+
*,
|
| 766 |
+
loc=None,
|
| 767 |
+
ip=None,
|
| 768 |
+
**kwargs,
|
| 769 |
+
) -> Callable:
|
| 770 |
+
src_is_smem = const_expr(
|
| 771 |
+
isinstance(src_tensor.iterator, cute.Pointer)
|
| 772 |
+
and src_tensor.memspace == cute.AddressSpace.smem
|
| 773 |
+
)
|
| 774 |
+
smem_tensor, gmem_tensor = (src_tensor, dst_tensor) if src_is_smem else (dst_tensor, src_tensor)
|
| 775 |
+
group_rank_smem = const_expr(cute.rank(smem_tensor) - (1 if not single_stage else 0))
|
| 776 |
+
group_rank_gmem = const_expr(cute.rank(gmem_tensor) - (1 if not single_stage else 0))
|
| 777 |
+
# ((atom_v, rest_v), STAGE), ((atom_v, rest_v), RestK)
|
| 778 |
+
s, g = cpasync.tma_partition(
|
| 779 |
+
atom,
|
| 780 |
+
cta_coord,
|
| 781 |
+
cta_layout,
|
| 782 |
+
cute.group_modes(smem_tensor, 0, group_rank_smem),
|
| 783 |
+
cute.group_modes(gmem_tensor, 0, group_rank_gmem),
|
| 784 |
+
loc=loc,
|
| 785 |
+
ip=ip,
|
| 786 |
+
)
|
| 787 |
+
if const_expr(filter_zeros):
|
| 788 |
+
s = cute.filter_zeros(s)
|
| 789 |
+
g = cute.filter_zeros(g)
|
| 790 |
+
src, dst = (s, g) if src_is_smem else (g, s)
|
| 791 |
+
|
| 792 |
+
@dsl_user_op
|
| 793 |
+
def copy_tma(src_idx, dst_idx, *, loc=None, ip=None, **new_kwargs):
|
| 794 |
+
cute.copy(
|
| 795 |
+
atom, src[None, src_idx], dst[None, dst_idx], **new_kwargs, **kwargs, loc=loc, ip=ip
|
| 796 |
+
)
|
| 797 |
+
|
| 798 |
+
@dsl_user_op
|
| 799 |
+
def copy_tma_single_stage(*, loc=None, ip=None, **new_kwargs):
|
| 800 |
+
cute.copy(atom, src, dst, **new_kwargs, **kwargs, loc=loc, ip=ip)
|
| 801 |
+
|
| 802 |
+
return (copy_tma if const_expr(not single_stage) else copy_tma_single_stage), s, g
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
def tma_producer_copy_fn(copy: Callable, pipeline: cutlass.pipeline.PipelineAsync):
|
| 806 |
+
def copy_fn(src_idx, producer_state: cutlass.pipeline.PipelineState, **new_kwargs):
|
| 807 |
+
copy(
|
| 808 |
+
src_idx=src_idx,
|
| 809 |
+
dst_idx=producer_state.index,
|
| 810 |
+
tma_bar_ptr=pipeline.producer_get_barrier(producer_state),
|
| 811 |
+
**new_kwargs,
|
| 812 |
+
)
|
| 813 |
+
|
| 814 |
+
return copy_fn
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
@cute.jit
|
| 818 |
+
def gather_m_get_copy_fn(
|
| 819 |
+
thr_copy_A: cute.ThrCopy,
|
| 820 |
+
mA: cute.Tensor, # (whatever, K)
|
| 821 |
+
sA: cute.Tensor, # (tile_M, tile_K, STAGE)
|
| 822 |
+
gsAIdx: cute.Tensor, # (tile_M), either gmem or smem
|
| 823 |
+
limit_m: Int32,
|
| 824 |
+
limit_k: Int32,
|
| 825 |
+
) -> Callable:
|
| 826 |
+
tile_M, tile_K = cute.size(sA, mode=[0]), cute.size(sA, mode=[1])
|
| 827 |
+
tAsA = partition_D_position_independent(thr_copy_A, sA)
|
| 828 |
+
# k-major
|
| 829 |
+
assert tAsA.shape[2] == 1
|
| 830 |
+
tAsA = cute.group_modes(cute.slice_(tAsA, (None, None, 0, None)), 0, 2)
|
| 831 |
+
|
| 832 |
+
is_even_m_smem = tile_M % thr_copy_A.tiler_mn[0].shape == 0
|
| 833 |
+
if const_expr(not is_even_m_smem):
|
| 834 |
+
limit_m = min(limit_m, tile_M)
|
| 835 |
+
elems_per_load = cute.size(tAsA.shape[0][0])
|
| 836 |
+
cA = cute.make_identity_tensor((tile_M, tile_K))
|
| 837 |
+
tAcA = thr_copy_A.partition_S(cA)
|
| 838 |
+
t0AcA = thr_copy_A.get_slice(0).partition_S(cA)
|
| 839 |
+
# Instead of comparing tAcA to limit_m, we instead compare t0AcA to limit_m - tAcA[0][0]
|
| 840 |
+
# since we know that tAcA[m][0] = t0AcA[m][0] + tAcA[0][0].
|
| 841 |
+
# This is so that when we do the comparison, t0AcA is known at compile time.
|
| 842 |
+
limit_m = limit_m - tAcA[0][0]
|
| 843 |
+
limit_k = limit_k - tAcA[0][1]
|
| 844 |
+
# Read and cache indices for A
|
| 845 |
+
rows_per_thread = const_expr(cute.size(tAcA.shape, mode=[1]))
|
| 846 |
+
cols_per_thread = const_expr(cute.size(tAcA.shape, mode=[2]))
|
| 847 |
+
tApA_m = cute.make_rmem_tensor(rows_per_thread, Boolean)
|
| 848 |
+
for m in cutlass.range(rows_per_thread, unroll_full=True):
|
| 849 |
+
tApA_m[m] = t0AcA[0, m, 0][0] < limit_m
|
| 850 |
+
m_idx = cute.make_rmem_tensor(rows_per_thread, Int32)
|
| 851 |
+
for m in cutlass.range(rows_per_thread, unroll_full=True):
|
| 852 |
+
row_idx = tAcA[0, m, 0][0]
|
| 853 |
+
if tApA_m[m]:
|
| 854 |
+
m_idx[m] = gsAIdx[row_idx]
|
| 855 |
+
else:
|
| 856 |
+
m_idx[m] = 0 # It's ok to load row 0 in the case of OOB
|
| 857 |
+
|
| 858 |
+
mA_k = cute.logical_divide(mA, (None, tile_K))
|
| 859 |
+
|
| 860 |
+
def copy_fn(src_idx, dst_idx, pred: bool = False):
|
| 861 |
+
tApA_k = None
|
| 862 |
+
if const_expr(pred):
|
| 863 |
+
tApA_k = cute.make_rmem_tensor(cols_per_thread, Boolean)
|
| 864 |
+
limit_k_cur = limit_k - src_idx * tile_K
|
| 865 |
+
for k in cutlass.range(cols_per_thread, unroll_full=True):
|
| 866 |
+
tApA_k[k] = t0AcA[0, 0, k][1] < limit_k_cur
|
| 867 |
+
mA_cur = mA_k[None, (None, src_idx)]
|
| 868 |
+
for m in cutlass.range_constexpr(tAcA.shape[1]):
|
| 869 |
+
# cute.tiled_divide(mA_cur[m_idx[m], None], (elems_per_load,)) would give shape
|
| 870 |
+
# ((elems_per_load), thread_per_row)
|
| 871 |
+
# But we actually want shape ((elems_per_load, 1), thread_per_row) to match tAsA
|
| 872 |
+
# So we append 1s to the last dimension and then do tiled_divide, then slice.
|
| 873 |
+
mA_row = cute.tiled_divide(
|
| 874 |
+
cute.append_ones(mA_cur[m_idx[m], None], up_to_rank=2), (elems_per_load, 1)
|
| 875 |
+
)[None, None, 0]
|
| 876 |
+
if const_expr(is_even_m_smem) or tApA_m[m]:
|
| 877 |
+
# There's only 1 load per row
|
| 878 |
+
assert cute.size(tAcA.shape, mode=[2]) == 1
|
| 879 |
+
ki = tAcA[0, 0, 0][1] // elems_per_load
|
| 880 |
+
cute.copy(thr_copy_A, mA_row[None, ki], tAsA[(None, m), dst_idx], pred=tApA_k)
|
| 881 |
+
|
| 882 |
+
return copy_fn
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
@cute.jit
|
| 886 |
+
def gather_k_get_copy_fn(
|
| 887 |
+
thr_copy_A: cute.ThrCopy,
|
| 888 |
+
mA: cute.Tensor, # (tile_M, whatever)
|
| 889 |
+
sA: cute.Tensor, # (tile_M, tile_K, STAGE)
|
| 890 |
+
gsAIdx: cute.Tensor, # (tile_K, RestK), either gmem or smem
|
| 891 |
+
limit_m: Int32,
|
| 892 |
+
limit_k: Int32,
|
| 893 |
+
) -> Callable:
|
| 894 |
+
gAIdx, sAIdx = None, None
|
| 895 |
+
if const_expr(gsAIdx.memspace == cute.AddressSpace.gmem):
|
| 896 |
+
gAIdx = gsAIdx
|
| 897 |
+
else:
|
| 898 |
+
assert gsAIdx.memspace == cute.AddressSpace.smem
|
| 899 |
+
sAIdx = gsAIdx
|
| 900 |
+
tile_shape_mk = (cute.size(sA, mode=[0]), cute.size(sA, mode=[1]))
|
| 901 |
+
# (atom_v, CPY_M, 1, STAGE)
|
| 902 |
+
tAsA = thr_copy_A.partition_D(sA)
|
| 903 |
+
# m-major
|
| 904 |
+
tAsA = cute.group_modes(tAsA, 0, 3)
|
| 905 |
+
|
| 906 |
+
is_even_m_smem = tile_shape_mk[0] % thr_copy_A.tiler_mn[0].shape == 0
|
| 907 |
+
if const_expr(not is_even_m_smem):
|
| 908 |
+
limit_m = min(limit_m, tile_shape_mk[0])
|
| 909 |
+
elems_per_load = cute.size(tAsA.shape[0][0])
|
| 910 |
+
cA = cute.make_identity_tensor(tile_shape_mk)
|
| 911 |
+
tAcA = thr_copy_A.partition_S(cA)
|
| 912 |
+
t0AcA = thr_copy_A.get_slice(0).partition_S(cA)
|
| 913 |
+
# Instead of comparing tAcA to limit_m, we instead compare t0AcA to limit_m - tAcA[0][0]
|
| 914 |
+
# since we know that tAcA[m][0] = t0AcA[m][0] + tAcA[0][0].
|
| 915 |
+
# This is so that when we do the comparison, t0AcA is known at compile time.
|
| 916 |
+
limit_m = limit_m - tAcA[0][0]
|
| 917 |
+
limit_k = limit_k - tAcA[0][1]
|
| 918 |
+
# Read and cache indices for A
|
| 919 |
+
rows_per_thread = const_expr(cute.size(tAcA.shape, mode=[1]))
|
| 920 |
+
cols_per_thread = const_expr(cute.size(tAcA.shape, mode=[2]))
|
| 921 |
+
tApA_m = cute.make_rmem_tensor(rows_per_thread, Boolean)
|
| 922 |
+
for m in cutlass.range(rows_per_thread, unroll_full=True):
|
| 923 |
+
tApA_m[m] = t0AcA[0, m, 0][0] < limit_m
|
| 924 |
+
threads_per_col = const_expr(thr_copy_A.tiler_mn[0].shape // elems_per_load)
|
| 925 |
+
# This is very convoluted but idk a better way
|
| 926 |
+
# for tile_M=128, flat_divide gives (8, 16, K),
|
| 927 |
+
# then logical_divide gives ((8, 1), (8, 2), K).
|
| 928 |
+
tidx = thr_copy_A.thr_idx
|
| 929 |
+
tAmA = cute.logical_divide(
|
| 930 |
+
cute.flat_divide(mA, (elems_per_load,)), (elems_per_load, threads_per_col)
|
| 931 |
+
)[None, (tidx % threads_per_col, None), None] # ((8, 1), 2, K)
|
| 932 |
+
|
| 933 |
+
def prefetch_from_gmem_fn(src_idx, pred: bool = False) -> Tuple[cute.Tensor, cute.Tensor]:
|
| 934 |
+
# Prefetch mAIdx early, even before smem is free
|
| 935 |
+
tApA_k = None
|
| 936 |
+
if const_expr(pred):
|
| 937 |
+
tApA_k = cute.make_rmem_tensor(cols_per_thread, Boolean)
|
| 938 |
+
limit_k_cur = limit_k - src_idx * tile_shape_mk[1]
|
| 939 |
+
for k in cutlass.range(cols_per_thread, unroll_full=True):
|
| 940 |
+
tApA_k[k] = t0AcA[0, 0, k][1] < limit_k_cur
|
| 941 |
+
gAIdx_cur = gAIdx[None, src_idx]
|
| 942 |
+
k_idx = cute.make_rmem_tensor(cols_per_thread, Int32)
|
| 943 |
+
for k in cutlass.range(cols_per_thread):
|
| 944 |
+
col_idx = tAcA[0, 0, k][1]
|
| 945 |
+
if const_expr(not pred):
|
| 946 |
+
k_idx[k] = gAIdx_cur[col_idx]
|
| 947 |
+
else:
|
| 948 |
+
if tApA_k[k]:
|
| 949 |
+
k_idx[k] = gAIdx_cur[col_idx]
|
| 950 |
+
else:
|
| 951 |
+
k_idx[k] = -1
|
| 952 |
+
return k_idx, tApA_k
|
| 953 |
+
|
| 954 |
+
def prefetch_from_smem_fn(
|
| 955 |
+
a_prefetch_pipeline, src_idx, dst_idx, a_prefetch_consumer_state, pred: bool = False
|
| 956 |
+
) -> Tuple[cute.Tensor, cute.Tensor]:
|
| 957 |
+
tApA_k = None
|
| 958 |
+
if const_expr(pred):
|
| 959 |
+
tApA_k = cute.make_rmem_tensor(cols_per_thread, Boolean)
|
| 960 |
+
limit_k_cur = limit_k - src_idx * tile_shape_mk[1]
|
| 961 |
+
for k in cutlass.range(cols_per_thread, unroll_full=True):
|
| 962 |
+
tApA_k[k] = t0AcA[0, 0, k][1] < limit_k_cur
|
| 963 |
+
a_prefetch_pipeline.consumer_wait(a_prefetch_consumer_state)
|
| 964 |
+
sAIdx_cur = sAIdx[None, dst_idx]
|
| 965 |
+
k_idx = cute.make_rmem_tensor(cols_per_thread, Int32)
|
| 966 |
+
for k in cutlass.range(cols_per_thread):
|
| 967 |
+
col_idx = tAcA[0, 0, k][1]
|
| 968 |
+
k_idx[k] = sAIdx_cur[col_idx]
|
| 969 |
+
cute.arch.sync_warp()
|
| 970 |
+
with cute.arch.elect_one():
|
| 971 |
+
a_prefetch_pipeline.consumer_release(a_prefetch_consumer_state)
|
| 972 |
+
return k_idx, tApA_k
|
| 973 |
+
|
| 974 |
+
def copy_fn(
|
| 975 |
+
src_idx, dst_idx, k_idx_tApA_k: Tuple[cute.Tensor, cute.Tensor], pred: bool = False
|
| 976 |
+
):
|
| 977 |
+
k_idx, tApA_k = k_idx_tApA_k
|
| 978 |
+
tApA_k_pred = None
|
| 979 |
+
if const_expr(pred):
|
| 980 |
+
tApA_k_pred = cute.prepend_ones(tApA_k, up_to_rank=2) # (1, cols_per_thread)
|
| 981 |
+
for k in cutlass.range_constexpr(tAcA.shape[2]):
|
| 982 |
+
# copy_A(tAmA[None, None, k_idx[k]], tAsA[(None, None, k), smem_idx], pred=cute.prepend_ones(tApA_m, up_to_rank=2))
|
| 983 |
+
for m in cutlass.range_constexpr(tAcA.shape[1]):
|
| 984 |
+
if tApA_m[m]:
|
| 985 |
+
cute.copy(
|
| 986 |
+
thr_copy_A,
|
| 987 |
+
tAmA[None, m, k_idx[k]],
|
| 988 |
+
tAsA[(None, m, k), dst_idx],
|
| 989 |
+
pred=None if const_expr(tApA_k_pred is None) else tApA_k_pred[None, k],
|
| 990 |
+
)
|
| 991 |
+
|
| 992 |
+
return copy_fn, prefetch_from_gmem_fn if const_expr(
|
| 993 |
+
gAIdx is not None
|
| 994 |
+
) else prefetch_from_smem_fn
|
| 995 |
+
|
| 996 |
+
|
| 997 |
+
@cute.jit
|
| 998 |
+
def gather_m_get_tma_copy_fn(
|
| 999 |
+
tma_atom: cute.CopyAtom,
|
| 1000 |
+
mA: cute.Tensor, # (whatever, K)
|
| 1001 |
+
sA: cute.Tensor, # ((4, 32), (64, 1), STAGE)
|
| 1002 |
+
sAIdx: cute.Tensor, # (tile_M),
|
| 1003 |
+
warp_idx: Int32,
|
| 1004 |
+
num_warps: int,
|
| 1005 |
+
num_cta: int = 1,
|
| 1006 |
+
) -> Callable:
|
| 1007 |
+
tile_M = cute.size(sAIdx, mode=[0])
|
| 1008 |
+
tile_K = cute.size(sA[None, None, 0]) // tile_M
|
| 1009 |
+
assert tile_M % 4 == 0
|
| 1010 |
+
# cta_group = 1 if tma_atom.op.cta_group == CtaGroup.ONE else 2
|
| 1011 |
+
cta_group = num_cta # Somehow all tma_atom has CtaGroup.ONE inside the kernel
|
| 1012 |
+
|
| 1013 |
+
copy_AIdx_s2r = cute.make_tiled_copy_tv(
|
| 1014 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Int32, num_bits_per_copy=128),
|
| 1015 |
+
cute.make_layout(num_warps), # thr_layout
|
| 1016 |
+
cute.make_layout(4), # val_layout
|
| 1017 |
+
)
|
| 1018 |
+
warp_copy_AIdx_s2r = copy_AIdx_s2r.get_slice(warp_idx)
|
| 1019 |
+
tSR_sAIdx = warp_copy_AIdx_s2r.partition_S(sAIdx)
|
| 1020 |
+
# ((4, 1), 8, (64, 1), STAGE)
|
| 1021 |
+
tSR_sA = warp_copy_AIdx_s2r.partition_S(sA)
|
| 1022 |
+
tSR_rAIdx = load_s2r(tSR_sAIdx)
|
| 1023 |
+
tma_desc_ptr = get_tma_desc_addr(tma_atom)
|
| 1024 |
+
tma_gather4_load_fn = partial(tma_gather4_load, tma_desc_ptr, num_cta=cta_group)
|
| 1025 |
+
|
| 1026 |
+
def copy_fn(src_idx, dst_idx, tma_bar_ptr: cute.Pointer):
|
| 1027 |
+
tSR_sA_cur = tSR_sA[None, None, None, dst_idx]
|
| 1028 |
+
col_idx = tile_K * src_idx
|
| 1029 |
+
for m in cutlass.range(cute.size(tSR_rAIdx, mode=[1]), unroll_full=True):
|
| 1030 |
+
row_indices = [tSR_rAIdx[v, m] for v in range(4)]
|
| 1031 |
+
smem_ptr = tSR_sA_cur[None, m, None].iterator
|
| 1032 |
+
with cute.arch.elect_one():
|
| 1033 |
+
tma_gather4_load_fn(smem_ptr, tma_bar_ptr, col_idx, row_indices)
|
| 1034 |
+
|
| 1035 |
+
return copy_fn
|
| 1036 |
+
|
| 1037 |
+
|
| 1038 |
+
@cute.jit
|
| 1039 |
+
def gather_k_get_tma_copy_fn(
|
| 1040 |
+
tma_atom: cute.CopyAtom,
|
| 1041 |
+
sA: cute.Tensor, # ((4, tile_K/4), (tile_M,), STAGE) — K-grouped load layout
|
| 1042 |
+
sAIdx: cute.Tensor, # (tile_K, a_prefetch_stage) — K indices in smem
|
| 1043 |
+
col_idx: Int32, # M offset in global tensor (contiguous dim for M-major)
|
| 1044 |
+
warp_idx: Int32,
|
| 1045 |
+
num_warps: int,
|
| 1046 |
+
num_cta: int = 1,
|
| 1047 |
+
) -> Tuple[Callable, Callable]:
|
| 1048 |
+
"""Build a copy function for TMA gather4 in K dimension (M-major A).
|
| 1049 |
+
|
| 1050 |
+
Each gather4 instruction loads 4 K-columns × tile_M contiguous M-elements.
|
| 1051 |
+
col_idx is the absolute M position in the global tensor.
|
| 1052 |
+
K indices come from sAIdx (prefetched to smem by the scheduler warp).
|
| 1053 |
+
|
| 1054 |
+
Returns copy_fn(src_idx, dst_idx, tma_bar_ptr) which:
|
| 1055 |
+
Issues gather4 calls with those K indices as row_indices
|
| 1056 |
+
"""
|
| 1057 |
+
tile_K = cute.size(sAIdx, mode=[0])
|
| 1058 |
+
assert tile_K % 4 == 0
|
| 1059 |
+
cta_group = num_cta
|
| 1060 |
+
|
| 1061 |
+
# Tiled copy for loading K indices from smem to registers (4 per vector, across warps)
|
| 1062 |
+
copy_AIdx_s2r = cute.make_tiled_copy_tv(
|
| 1063 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Int32, num_bits_per_copy=128),
|
| 1064 |
+
cute.make_layout(num_warps), # thr_layout
|
| 1065 |
+
cute.make_layout(4), # val_layout — 4 K indices per gather4
|
| 1066 |
+
)
|
| 1067 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 1068 |
+
warp_copy_AIdx_s2r = copy_AIdx_s2r.get_slice(warp_idx)
|
| 1069 |
+
tSR_sAIdx = warp_copy_AIdx_s2r.partition_S(sAIdx) # (((4,1),4,4))
|
| 1070 |
+
# ((4,1),4,(64,2),(1,4)):((64,0),1024,(1,4096),(0,8192))
|
| 1071 |
+
tSR_sA = warp_copy_AIdx_s2r.partition_S(layout_utils.transpose_view(sA))
|
| 1072 |
+
tma_desc_ptr = get_tma_desc_addr(tma_atom)
|
| 1073 |
+
tma_gather4_load_fn = partial(tma_gather4_load, tma_desc_ptr, num_cta=cta_group)
|
| 1074 |
+
|
| 1075 |
+
def prefetch_from_smem_fn(
|
| 1076 |
+
a_prefetch_pipeline,
|
| 1077 |
+
src_idx,
|
| 1078 |
+
dst_idx,
|
| 1079 |
+
a_prefetch_consumer_state,
|
| 1080 |
+
) -> cute.Tensor:
|
| 1081 |
+
a_prefetch_pipeline.consumer_wait(a_prefetch_consumer_state)
|
| 1082 |
+
tSR_rAIdx = load_s2r(tSR_sAIdx[None, None, dst_idx])
|
| 1083 |
+
cute.arch.sync_warp()
|
| 1084 |
+
with cute.arch.elect_one():
|
| 1085 |
+
a_prefetch_pipeline.consumer_release(a_prefetch_consumer_state)
|
| 1086 |
+
return tSR_rAIdx
|
| 1087 |
+
|
| 1088 |
+
def copy_fn(src_idx, dst_idx, tSR_rAIdx, tma_bar_ptr: cute.Pointer):
|
| 1089 |
+
# Issue gather4: col_idx = M position, row_indices = 4 K positions
|
| 1090 |
+
tSR_sA_cur = tSR_sA[None, None, None, dst_idx]
|
| 1091 |
+
gather_dim = cute.size(tSR_sA_cur, mode=[2, 0]) # Typically 64
|
| 1092 |
+
for k in cutlass.range(cute.size(tSR_rAIdx, mode=[1]), unroll_full=True):
|
| 1093 |
+
row_indices = [tSR_rAIdx[v, k] for v in range(4)]
|
| 1094 |
+
for m in cutlass.range(cute.size(tSR_sA_cur, mode=[2, 1]), unroll_full=True):
|
| 1095 |
+
smem_ptr = tSR_sA_cur[None, k, (None, m)].iterator
|
| 1096 |
+
with cute.arch.elect_one():
|
| 1097 |
+
tma_gather4_load_fn(
|
| 1098 |
+
smem_ptr, tma_bar_ptr, col_idx + m * gather_dim, row_indices
|
| 1099 |
+
)
|
| 1100 |
+
|
| 1101 |
+
return copy_fn, prefetch_from_smem_fn
|
| 1102 |
+
|
| 1103 |
+
|
| 1104 |
+
# ---------------------------------------------------------------------------
|
| 1105 |
+
# Store helpers
|
| 1106 |
+
# ---------------------------------------------------------------------------
|
| 1107 |
+
|
| 1108 |
+
|
| 1109 |
+
@dsl_user_op
|
| 1110 |
+
@cute.jit
|
| 1111 |
+
def store(
|
| 1112 |
+
ptr: cute.Pointer,
|
| 1113 |
+
val,
|
| 1114 |
+
pred: Optional[Boolean] = None,
|
| 1115 |
+
cop: cutlass.Constexpr = None,
|
| 1116 |
+
*,
|
| 1117 |
+
loc=None,
|
| 1118 |
+
ip=None,
|
| 1119 |
+
):
|
| 1120 |
+
"""Store a scalar value via cute.arch.store.
|
| 1121 |
+
|
| 1122 |
+
ptr: cute.Pointer (any address space).
|
| 1123 |
+
val: DSL Numeric value.
|
| 1124 |
+
pred: None → unconditional. DSL Boolean → skipped when pred == 0.
|
| 1125 |
+
cop: Cache operator — "wb" (default), "cg", "cs" (streaming), "wt".
|
| 1126 |
+
"""
|
| 1127 |
+
if const_expr(pred is None):
|
| 1128 |
+
cute.arch.store(ptr.llvm_ptr, type(val)(val), cop=cop, loc=loc, ip=ip)
|
| 1129 |
+
else:
|
| 1130 |
+
if pred:
|
| 1131 |
+
cute.arch.store(ptr.llvm_ptr, type(val)(val), cop=cop, loc=loc, ip=ip)
|
| 1132 |
+
|
| 1133 |
+
|
| 1134 |
+
@dsl_user_op
|
| 1135 |
+
@cute.jit
|
| 1136 |
+
def store_v2(
|
| 1137 |
+
ptr: cute.Pointer,
|
| 1138 |
+
v0,
|
| 1139 |
+
v1,
|
| 1140 |
+
pred: Optional[Boolean] = None,
|
| 1141 |
+
cop: cutlass.Constexpr = None,
|
| 1142 |
+
*,
|
| 1143 |
+
loc=None,
|
| 1144 |
+
ip=None,
|
| 1145 |
+
):
|
| 1146 |
+
"""Vectorized store of 2 elements via cute.arch.store.
|
| 1147 |
+
|
| 1148 |
+
Packs v0, v1 into an MLIR <2 x T> vector.
|
| 1149 |
+
ptr: cute.Pointer (any address space, must be aligned for vector width).
|
| 1150 |
+
cop: Cache operator — "wb" (default), "cg", "cs" (streaming), "wt".
|
| 1151 |
+
"""
|
| 1152 |
+
vec = make_vector(type(v0), v0, v1, loc=loc, ip=ip)
|
| 1153 |
+
if const_expr(pred is None):
|
| 1154 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
| 1155 |
+
else:
|
| 1156 |
+
if pred:
|
| 1157 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
| 1158 |
+
|
| 1159 |
+
|
| 1160 |
+
@dsl_user_op
|
| 1161 |
+
@cute.jit
|
| 1162 |
+
def store_v4(
|
| 1163 |
+
ptr: cute.Pointer,
|
| 1164 |
+
v0,
|
| 1165 |
+
v1,
|
| 1166 |
+
v2,
|
| 1167 |
+
v3,
|
| 1168 |
+
pred: Optional[Boolean] = None,
|
| 1169 |
+
cop: cutlass.Constexpr = None,
|
| 1170 |
+
*,
|
| 1171 |
+
loc=None,
|
| 1172 |
+
ip=None,
|
| 1173 |
+
):
|
| 1174 |
+
"""Vectorized store of 4 elements via cute.arch.store.
|
| 1175 |
+
|
| 1176 |
+
Packs v0–v3 into an MLIR <4 x T> vector.
|
| 1177 |
+
ptr: cute.Pointer (any address space, must be aligned for vector width).
|
| 1178 |
+
cop: Cache operator — "wb" (default), "cg", "cs" (streaming), "wt".
|
| 1179 |
+
"""
|
| 1180 |
+
vec = make_vector(type(v0), v0, v1, v2, v3, loc=loc, ip=ip)
|
| 1181 |
+
if const_expr(pred is None):
|
| 1182 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
| 1183 |
+
else:
|
| 1184 |
+
if pred:
|
| 1185 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
build/torch-cuda/quack/cute_dsl_utils.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Tuple, get_origin
|
| 4 |
+
from functools import lru_cache
|
| 5 |
+
from dataclasses import dataclass, fields
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import re
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
from triton.tools.disasm import extract
|
| 14 |
+
except ImportError:
|
| 15 |
+
extract = None
|
| 16 |
+
|
| 17 |
+
import cutlass
|
| 18 |
+
import cutlass.cute as cute
|
| 19 |
+
from cutlass import Int32, Int64, Float16, BFloat16, Float32
|
| 20 |
+
from cutlass.base_dsl.tvm_ffi_builder import spec
|
| 21 |
+
from cutlass.cutlass_dsl import NumericMeta
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
StaticTypes = (cutlass.Constexpr, NumericMeta, int, bool, str, float, type(None))
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
load_cubin_module_data_og = cutlass.base_dsl.runtime.cuda.load_cubin_module_data
|
| 28 |
+
cute_compile_og = cute.compile
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Patch TVM-FFI converter to handle Constexpr type annotations as compile-time constants.
|
| 32 |
+
# Fields annotated with cutlass.Constexpr[T] are emitted as ConstNone (not runtime args).
|
| 33 |
+
# At call time, pass None for these fields; the compile-time value is baked in.
|
| 34 |
+
import cutlass.cute._tvm_ffi_args_spec_converter as _converter_module # noqa
|
| 35 |
+
|
| 36 |
+
_original_convert_single_arg = _converter_module._convert_single_arg
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _patched_convert_single_arg(arg, arg_name, arg_type, ctx):
|
| 40 |
+
if arg_type is not None and get_origin(arg_type) is cutlass.Constexpr:
|
| 41 |
+
return spec.ConstNone(arg_name)
|
| 42 |
+
# If arg is a NamedTuple but arg_type doesn't have _fields (e.g. annotated as tuple),
|
| 43 |
+
# redirect so the converter uses the NamedTuple's own type hints.
|
| 44 |
+
if (
|
| 45 |
+
isinstance(arg, tuple)
|
| 46 |
+
and hasattr(type(arg), "_fields")
|
| 47 |
+
and (arg_type is None or not hasattr(arg_type, "_fields"))
|
| 48 |
+
):
|
| 49 |
+
return _original_convert_single_arg(arg, arg_name, type(arg), ctx)
|
| 50 |
+
return _original_convert_single_arg(arg, arg_name, arg_type, ctx)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
_converter_module._convert_single_arg = _patched_convert_single_arg
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
torch2cute_dtype_map = {
|
| 57 |
+
torch.float16: Float16,
|
| 58 |
+
torch.bfloat16: BFloat16,
|
| 59 |
+
torch.float32: Float32,
|
| 60 |
+
torch.int32: Int32,
|
| 61 |
+
torch.int64: Int64,
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@lru_cache
|
| 66 |
+
def get_max_active_clusters(cluster_size):
|
| 67 |
+
return cutlass.utils.HardwareInfo().get_max_active_clusters(cluster_size=cluster_size)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _parse_arch_str(arch_str: str) -> Tuple[int, int]:
|
| 71 |
+
"""Parse arch string (e.g. 'sm_90', 'sm90', '90', 'sm_100a') to (major, minor) tuple."""
|
| 72 |
+
match = re.match(r"^(?:sm_?)?(\d+)(\d)([af]?)$", arch_str.strip(), re.IGNORECASE)
|
| 73 |
+
if not match:
|
| 74 |
+
raise ValueError(f"Invalid QUACK_ARCH format: {arch_str!r} (expected e.g. '90', 'sm_90')")
|
| 75 |
+
major, minor, _ = match.groups()
|
| 76 |
+
return int(major), int(minor)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@lru_cache
|
| 80 |
+
def get_device_capacity(device: torch.device = None) -> Tuple[int, int]:
|
| 81 |
+
"""Return (major, minor) device capability.
|
| 82 |
+
|
| 83 |
+
Override with QUACK_ARCH (e.g. 'sm_90' or '90') for CPU-only compilation
|
| 84 |
+
without a GPU present.
|
| 85 |
+
"""
|
| 86 |
+
arch_override = os.environ.get("QUACK_ARCH")
|
| 87 |
+
if arch_override is not None:
|
| 88 |
+
return _parse_arch_str(arch_override)
|
| 89 |
+
return torch.cuda.get_device_capability(device)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _partition_fields(obj):
|
| 93 |
+
"""Split dataclass fields into (constexpr_dict, non_constexpr_dict) by type."""
|
| 94 |
+
all_fields = {field.name: getattr(obj, field.name) for field in fields(obj)}
|
| 95 |
+
constexpr = {n: f for n, f in all_fields.items() if isinstance(f, StaticTypes)}
|
| 96 |
+
non_constexpr = {n: f for n, f in all_fields.items() if not isinstance(f, StaticTypes)}
|
| 97 |
+
return constexpr, non_constexpr
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _new_from_mlir_values(self, values):
|
| 101 |
+
constexpr_fields, non_constexpr_fields = _partition_fields(self)
|
| 102 |
+
for (name, field), n_items in zip(non_constexpr_fields.items(), self._values_pos):
|
| 103 |
+
non_constexpr_fields[name] = cutlass.new_from_mlir_values(field, values[:n_items])
|
| 104 |
+
values = values[n_items:]
|
| 105 |
+
return self.__class__(**non_constexpr_fields, **constexpr_fields)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def _namedtuple_new_from_mlir_values(self, values):
|
| 109 |
+
"""Generic __new_from_mlir_values__ for NamedTuples.
|
| 110 |
+
|
| 111 |
+
Applied to NamedTuple classes via the ``@mlir_namedtuple`` decorator.
|
| 112 |
+
|
| 113 |
+
Fields that are None or Constexpr (StaticTypes) are preserved from ``self`` (the compile-time
|
| 114 |
+
template). Only non-static fields consume MLIR values. Multi-value fields (e.g. cute.Tensor)
|
| 115 |
+
consume the correct number of values via ``cutlass.new_from_mlir_values``.
|
| 116 |
+
|
| 117 |
+
Constexpr fields (annotated ``cutlass.Constexpr[T]``) are baked into the compiled kernel via
|
| 118 |
+
a converter patch (see above). At call time, pass None for these fields.
|
| 119 |
+
"""
|
| 120 |
+
from cutlass.base_dsl.typing import get_mlir_types
|
| 121 |
+
|
| 122 |
+
values = list(values)
|
| 123 |
+
new_fields = []
|
| 124 |
+
for field_val in self:
|
| 125 |
+
if field_val is None or isinstance(field_val, StaticTypes):
|
| 126 |
+
new_fields.append(field_val)
|
| 127 |
+
else:
|
| 128 |
+
n_items = len(get_mlir_types(field_val))
|
| 129 |
+
new_fields.append(cutlass.new_from_mlir_values(field_val, values[:n_items]))
|
| 130 |
+
values = values[n_items:]
|
| 131 |
+
return self.__class__(*new_fields)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def mlir_namedtuple(cls):
|
| 135 |
+
"""Decorator that adds MLIR value reconstruction to a NamedTuple class.
|
| 136 |
+
|
| 137 |
+
Usage::
|
| 138 |
+
|
| 139 |
+
@mlir_namedtuple
|
| 140 |
+
class MyArgs(NamedTuple):
|
| 141 |
+
tensor_arg: cute.Tensor
|
| 142 |
+
const_arg: cutlass.Constexpr[int] = 0
|
| 143 |
+
"""
|
| 144 |
+
cls.__new_from_mlir_values__ = _namedtuple_new_from_mlir_values
|
| 145 |
+
return cls
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
@dataclass
|
| 149 |
+
class ParamsBase:
|
| 150 |
+
def __extract_mlir_values__(self):
|
| 151 |
+
_, non_constexpr_fields = _partition_fields(self)
|
| 152 |
+
values, self._values_pos = [], []
|
| 153 |
+
for obj in non_constexpr_fields.values():
|
| 154 |
+
obj_values = cutlass.extract_mlir_values(obj)
|
| 155 |
+
values += obj_values
|
| 156 |
+
self._values_pos.append(len(obj_values))
|
| 157 |
+
return values
|
| 158 |
+
|
| 159 |
+
__new_from_mlir_values__ = _new_from_mlir_values
|
build/torch-cuda/quack/layout_utils.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
import cutlass
|
| 5 |
+
import cutlass.cute as cute
|
| 6 |
+
|
| 7 |
+
from cutlass import Int32, const_expr
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def transpose_view(a: cute.Tensor) -> cute.Tensor:
|
| 11 |
+
"""Transpose the first two dimensions of a tensor on smem."""
|
| 12 |
+
shape = (a.shape[1], a.shape[0], *a.shape[2:])
|
| 13 |
+
order = (1, 0, *range(2, cute.rank(a)))
|
| 14 |
+
return cute.composition(a, cute.make_ordered_layout(shape, order=order))
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def select(a: cute.Tensor, mode: list[int]) -> cute.Tensor:
|
| 18 |
+
return cute.make_tensor(a.iterator, cute.select(a.layout, mode))
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def expand(a: cute.Tensor, dim: int, size: Int32 | int) -> cute.Tensor:
|
| 22 |
+
shape = (*a.shape[:dim], size, *a.shape[dim:])
|
| 23 |
+
stride = (*a.layout.stride[:dim], 0, *a.layout.stride[dim:])
|
| 24 |
+
return cute.make_tensor(a.iterator, cute.make_layout(shape, stride=stride))
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@cute.jit
|
| 28 |
+
def permute_gated_Cregs_b16(t: cute.Tensor) -> None:
|
| 29 |
+
assert t.element_type.width == 16
|
| 30 |
+
assert cute.size(t.shape) % 4 == 0, "Tensor size must be a multiple of 4 for b16 permutation"
|
| 31 |
+
t_u32 = cute.recast_tensor(t, Int32)
|
| 32 |
+
|
| 33 |
+
quad_idx = cute.arch.lane_idx() % 4
|
| 34 |
+
lane_03 = quad_idx == 0 or quad_idx == 3
|
| 35 |
+
selector_upper = Int32(0x5410) if lane_03 else Int32(0x1054)
|
| 36 |
+
selector_lower = Int32(0x7632) if lane_03 else Int32(0x3276)
|
| 37 |
+
# upper_map = [0, 3, 1, 2]
|
| 38 |
+
# lower_map = [1, 2, 0, 3]
|
| 39 |
+
# upper_idx = upper_map[quad_idx]
|
| 40 |
+
# indexing isn't supported so we have to do arithmetic
|
| 41 |
+
upper_idx = quad_idx // 2 if quad_idx % 2 == 0 else 3 - quad_idx // 2
|
| 42 |
+
lower_idx = upper_idx ^ 1
|
| 43 |
+
|
| 44 |
+
# 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000
|
| 45 |
+
width = 4
|
| 46 |
+
mask = cute.arch.WARP_SIZE - width
|
| 47 |
+
clamp = cute.arch.WARP_SIZE - 1
|
| 48 |
+
mask_and_clamp = mask << 8 | clamp
|
| 49 |
+
|
| 50 |
+
for i in cutlass.range(cute.size(t_u32.shape) // 2, unroll_full=True):
|
| 51 |
+
upper, lower = t_u32[i * 2 + 0], t_u32[i * 2 + 1]
|
| 52 |
+
upper0 = upper if lane_03 else lower
|
| 53 |
+
lower0 = lower if lane_03 else upper
|
| 54 |
+
upper0 = cute.arch.shuffle_sync(upper0, offset=upper_idx, mask_and_clamp=mask_and_clamp)
|
| 55 |
+
lower0 = cute.arch.shuffle_sync(lower0, offset=lower_idx, mask_and_clamp=mask_and_clamp)
|
| 56 |
+
t_u32[i * 2 + 0] = cute.arch.prmt(upper0, lower0, selector_upper)
|
| 57 |
+
t_u32[i * 2 + 1] = cute.arch.prmt(upper0, lower0, selector_lower)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@cute.jit
|
| 61 |
+
def permute_Cregs_b32_for_stsm(t: cute.Tensor) -> None:
|
| 62 |
+
"""Permute and shuffle within 4 threads to change the layout from
|
| 63 |
+
T0 | T1 | T2 | T3
|
| 64 |
+
a b | c d | e f | g h
|
| 65 |
+
to
|
| 66 |
+
T0 | T1 | T2 | T3 | T0 | T1 | T2 | T3
|
| 67 |
+
a | b | c | d | e | f | g | h
|
| 68 |
+
This is so that we can use STSM (instead of STS.64) to store C registers without bank conflict.
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
assert t.element_type.width == 32
|
| 72 |
+
assert cute.size(t.shape) % 4 == 0, "Tensor size must be a multiple of 4 for b32 permutation"
|
| 73 |
+
|
| 74 |
+
quad_idx = cute.arch.lane_idx() % 4
|
| 75 |
+
# left_map = [0, 2, 1, 3]
|
| 76 |
+
# right_map = [2, 0, 3, 1]
|
| 77 |
+
# indexing isn't supported so we have to do arithmetic
|
| 78 |
+
left_idx = quad_idx // 2 if quad_idx % 2 == 0 else 2 + quad_idx // 2
|
| 79 |
+
right_idx = left_idx ^ 0b10
|
| 80 |
+
|
| 81 |
+
# 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000
|
| 82 |
+
width = 4
|
| 83 |
+
mask = cute.arch.WARP_SIZE - width
|
| 84 |
+
clamp = cute.arch.WARP_SIZE - 1
|
| 85 |
+
mask_and_clamp = mask << 8 | clamp
|
| 86 |
+
|
| 87 |
+
for i in cutlass.range(cute.size(t.shape) // 4, unroll_full=True):
|
| 88 |
+
for r in cutlass.range(2, unroll_full=True):
|
| 89 |
+
left, right = t[i * 4 + r * 2 + 0], t[i * 4 + r * 2 + 1]
|
| 90 |
+
# a b | c d | e f | g h -> a b | c d | f e | h g
|
| 91 |
+
left0 = left if quad_idx < 2 else right
|
| 92 |
+
right0 = right if quad_idx < 2 else left
|
| 93 |
+
# a b | c d | f e | h g -> a b | f d | c e | h g
|
| 94 |
+
left0 = cute.arch.shuffle_sync(left0, offset=left_idx, mask_and_clamp=mask_and_clamp)
|
| 95 |
+
# a b | f d | c e | h g -> a e | f b | c g | h d
|
| 96 |
+
right0 = cute.arch.shuffle_sync(right0, offset=right_idx, mask_and_clamp=mask_and_clamp)
|
| 97 |
+
# a e | f b | c g | h d -> a e | b f | c g | d h
|
| 98 |
+
t[i * 4 + r * 2 + 0] = left0 if quad_idx % 2 == 0 else right0
|
| 99 |
+
t[i * 4 + r * 2 + 1] = right0 if quad_idx % 2 == 0 else left0
|
| 100 |
+
t[i * 4 + 1], t[i * 4 + 2] = t[i * 4 + 2], t[i * 4 + 1]
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
@cute.jit
|
| 104 |
+
def permute_Cregs_b32_for_ldsm(t: cute.Tensor) -> None:
|
| 105 |
+
"""Permute and shuffle within 4 threads to change the layout from
|
| 106 |
+
T0 | T1 | T2 | T3 | T0 | T1 | T2 | T3
|
| 107 |
+
a | b | c | d | e | f | g | h
|
| 108 |
+
to
|
| 109 |
+
T0 | T1 | T2 | T3
|
| 110 |
+
a b | c d | e f | g h
|
| 111 |
+
This is so that we can use LDSM (instead of LDS.64) to store C registers without bank conflict.
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
assert t.element_type.width == 32
|
| 115 |
+
assert cute.size(t.shape) % 4 == 0, "Tensor size must be a multiple of 4 for b32 permutation"
|
| 116 |
+
|
| 117 |
+
quad_idx = cute.arch.lane_idx() % 4
|
| 118 |
+
# left_map = [0, 2, 1, 3]
|
| 119 |
+
# right_map = [1, 3, 0, 2]
|
| 120 |
+
# indexing isn't supported so we have to do arithmetic
|
| 121 |
+
left_idx = quad_idx // 2 if quad_idx % 2 == 0 else 2 + quad_idx // 2
|
| 122 |
+
right_idx = left_idx ^ 0b01
|
| 123 |
+
|
| 124 |
+
# 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000
|
| 125 |
+
width = 4
|
| 126 |
+
mask = cute.arch.WARP_SIZE - width
|
| 127 |
+
clamp = cute.arch.WARP_SIZE - 1
|
| 128 |
+
mask_and_clamp = mask << 8 | clamp
|
| 129 |
+
|
| 130 |
+
# This is just the inverse of permute_Cregs_b32_for_stsm
|
| 131 |
+
for i in cutlass.range(cute.size(t.shape) // 4, unroll_full=True):
|
| 132 |
+
t[i * 4 + 1], t[i * 4 + 2] = t[i * 4 + 2], t[i * 4 + 1]
|
| 133 |
+
for r in cutlass.range(2, unroll_full=True):
|
| 134 |
+
left, right = t[i * 4 + r * 2 + 0], t[i * 4 + r * 2 + 1]
|
| 135 |
+
# a e | b f | c g | d h -> a e | f b | c g | h d
|
| 136 |
+
left0 = left if quad_idx % 2 == 0 else right
|
| 137 |
+
right0 = right if quad_idx % 2 == 0 else left
|
| 138 |
+
# a e | f b | c g | h d -> a b | f d | c e | h g
|
| 139 |
+
right0 = cute.arch.shuffle_sync(right0, offset=right_idx, mask_and_clamp=mask_and_clamp)
|
| 140 |
+
# a b | f d | c e | h g -> a b | c d | f e | h g
|
| 141 |
+
left0 = cute.arch.shuffle_sync(left0, offset=left_idx, mask_and_clamp=mask_and_clamp)
|
| 142 |
+
# a b | c d | f e | h g -> a b | c d | e f | g h
|
| 143 |
+
t[i * 4 + r * 2 + 0] = left0 if quad_idx < 2 else right0
|
| 144 |
+
t[i * 4 + r * 2 + 1] = right0 if quad_idx < 2 else left0
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
@cute.jit
|
| 148 |
+
def concat_layout(*layouts: cute.Layout) -> cute.Layout:
|
| 149 |
+
return cute.make_layout(
|
| 150 |
+
tuple(l.shape for l in layouts),
|
| 151 |
+
stride=tuple(l.stride for l in layouts),
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def convert_layout_acc_mn(acc_layout: cute.Layout, transpose: bool = False) -> cute.Layout:
|
| 156 |
+
"""
|
| 157 |
+
For Sm80, convert ((2, 2), MMA_M, MMA_N, ...) to ((2, MMA_M), (2, MMA_N), ...).
|
| 158 |
+
For Sm90, convert ((2, 2, V), MMA_M, MMA_N, ...) to ((2, MMA_M), (2, V, MMA_N), ...).
|
| 159 |
+
"""
|
| 160 |
+
acc_layout_col_major = cute.make_layout(acc_layout.shape)
|
| 161 |
+
shape = (
|
| 162 |
+
(acc_layout_col_major.shape[0][1], acc_layout_col_major.shape[1]), # MMA_M
|
| 163 |
+
(
|
| 164 |
+
acc_layout_col_major.shape[0][0],
|
| 165 |
+
*acc_layout_col_major.shape[0][2:],
|
| 166 |
+
acc_layout_col_major.shape[2],
|
| 167 |
+
), # MMA_N
|
| 168 |
+
*acc_layout_col_major.shape[3:],
|
| 169 |
+
)
|
| 170 |
+
stride = (
|
| 171 |
+
(acc_layout_col_major.stride[0][1], acc_layout_col_major.stride[1]), # MMA_M
|
| 172 |
+
(
|
| 173 |
+
acc_layout_col_major.stride[0][0],
|
| 174 |
+
*acc_layout_col_major.stride[0][2:],
|
| 175 |
+
acc_layout_col_major.stride[2],
|
| 176 |
+
), # MMA_N
|
| 177 |
+
*acc_layout_col_major.stride[3:],
|
| 178 |
+
)
|
| 179 |
+
if const_expr(transpose):
|
| 180 |
+
shape = (shape[1], shape[0], *shape[2:])
|
| 181 |
+
stride = (stride[1], stride[0], *stride[2:])
|
| 182 |
+
acc_layout_mn = cute.make_layout(shape, stride=stride)
|
| 183 |
+
return cute.composition(acc_layout, acc_layout_mn)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def make_acc_tensor_mn_view(acc: cute.Tensor, transpose: bool = False) -> cute.Tensor:
|
| 187 |
+
return cute.make_tensor(acc.iterator, convert_layout_acc_mn(acc.layout, transpose=transpose))
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def reshape_acc_to_mn(acc: cute.Tensor, transpose: bool = False) -> cute.Tensor:
|
| 191 |
+
return cute.make_tensor(acc.iterator, convert_layout_acc_mn(acc.layout, transpose=transpose))
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
@cute.jit
|
| 195 |
+
def convert_layout_acc_frgA(acc_layout: cute.Layout) -> cute.Layout:
|
| 196 |
+
# For back to back gemm, convert layout of acc0 to gemm 1 accept layout.
|
| 197 |
+
# For Sm80, as the mma instruction shape is 16x8x16, we need to convert from (4, MMA_M, MMA_N) to ((4, 2), MMA_M, MMA_N / 2)
|
| 198 |
+
# For Sm90, FP16/BF16, convert acc_layout from ((2, 2, N / 8), MMA_M, MMA_N) to ((2, 2, 2), MMA_M, (N / 16, MMA_N))
|
| 199 |
+
# If N / 8 is odd, we'll convert to ((2, 2, 1), MMA_M, N / 8, MMA_N).
|
| 200 |
+
# TODO: Sm90 FP8
|
| 201 |
+
if const_expr(cute.rank(acc_layout.shape[0]) == 3): # Sm90
|
| 202 |
+
div = 2 if const_expr(acc_layout.shape[0][2] % 2 == 0) else 1
|
| 203 |
+
l = cute.logical_divide(
|
| 204 |
+
acc_layout, ((None, None, div), None, None)
|
| 205 |
+
) # ((2, 2, (2, N / 16)), MMA_M, MMA_N)
|
| 206 |
+
rA_mma_view = cute.make_layout(
|
| 207 |
+
(
|
| 208 |
+
(l.shape[0][0], l.shape[0][1], l.shape[0][2][0]),
|
| 209 |
+
l.shape[1],
|
| 210 |
+
(l.shape[0][2][1], l.shape[2]),
|
| 211 |
+
),
|
| 212 |
+
stride=(
|
| 213 |
+
(l.stride[0][0], l.stride[0][1], l.stride[0][2][0]),
|
| 214 |
+
l.stride[1],
|
| 215 |
+
(l.stride[0][2][1], l.stride[2]),
|
| 216 |
+
),
|
| 217 |
+
)
|
| 218 |
+
else: # Sm80
|
| 219 |
+
# (4, MMA_M, MMA_N) -> (4, MMA_M, (2, MMA_N / 2))
|
| 220 |
+
l = cute.logical_divide(acc_layout, (None, None, 2))
|
| 221 |
+
rA_mma_view = cute.make_layout(
|
| 222 |
+
(
|
| 223 |
+
(l.shape[0], l.shape[2][0]),
|
| 224 |
+
l.shape[1],
|
| 225 |
+
l.shape[2][1],
|
| 226 |
+
),
|
| 227 |
+
stride=(
|
| 228 |
+
(l.stride[0], l.stride[2][0]),
|
| 229 |
+
l.stride[1],
|
| 230 |
+
l.stride[2][1],
|
| 231 |
+
),
|
| 232 |
+
)
|
| 233 |
+
return rA_mma_view
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def reshape_acc_to_frgA(acc: cute.Tensor) -> cute.Tensor:
|
| 237 |
+
return cute.make_tensor(acc.iterator, convert_layout_acc_frgA(acc.layout))
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def convert_layout_zero_stride(
|
| 241 |
+
input: cute.Tensor | cute.Layout, ref_layout: cute.Layout
|
| 242 |
+
) -> cute.Layout:
|
| 243 |
+
layout = input.layout if const_expr(isinstance(input, cute.Tensor)) else input
|
| 244 |
+
# Group the modes with non-zero stride in the ref_layout together,
|
| 245 |
+
# and the modes with zero stride together
|
| 246 |
+
layout_flat = cute.flatten(layout)
|
| 247 |
+
ref_layout_flat = cute.flatten(ref_layout)
|
| 248 |
+
nonzero_modes = [i for i in range(cute.rank(layout_flat)) if ref_layout_flat[i].stride != 0]
|
| 249 |
+
zero_modes = [i for i in range(cute.rank(layout_flat)) if ref_layout_flat[i].stride == 0]
|
| 250 |
+
# There's an edge case when all modes are zero stride
|
| 251 |
+
new_shape = (
|
| 252 |
+
tuple(layout_flat[i].shape for i in nonzero_modes) if len(nonzero_modes) > 0 else (1,),
|
| 253 |
+
tuple(layout_flat[i].shape for i in zero_modes),
|
| 254 |
+
)
|
| 255 |
+
new_stride = (
|
| 256 |
+
tuple(layout_flat[i].stride for i in nonzero_modes) if len(nonzero_modes) > 0 else (0,),
|
| 257 |
+
tuple(layout_flat[i].stride for i in zero_modes),
|
| 258 |
+
)
|
| 259 |
+
out_layout = cute.make_layout(new_shape, stride=new_stride)
|
| 260 |
+
if const_expr(isinstance(input, cute.Tensor)):
|
| 261 |
+
return cute.make_tensor(input.iterator, out_layout)
|
| 262 |
+
else:
|
| 263 |
+
return out_layout
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def mma_partition_C_vec(
|
| 267 |
+
sVec: cute.Tensor, thr_mma: cute.core.ThrMma, expand_shape: int, is_colvec: bool
|
| 268 |
+
) -> cute.Tensor:
|
| 269 |
+
assert cute.rank(sVec) == 2
|
| 270 |
+
assert sVec.stride[0] == 1
|
| 271 |
+
stage = sVec.shape[1]
|
| 272 |
+
shape = (
|
| 273 |
+
(sVec.shape[0], expand_shape, stage)
|
| 274 |
+
if const_expr(is_colvec)
|
| 275 |
+
else (expand_shape, sVec.shape[0], stage)
|
| 276 |
+
)
|
| 277 |
+
stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1])
|
| 278 |
+
sVec_mma = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride))
|
| 279 |
+
tC_sVec = make_acc_tensor_mn_view(thr_mma.partition_C(sVec_mma))
|
| 280 |
+
return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def mma_partition_A_vec(
|
| 284 |
+
sVec: cute.Tensor, thr_mma: cute.core.ThrMma, expand_shape: int, is_colvec: bool
|
| 285 |
+
) -> cute.Tensor:
|
| 286 |
+
assert cute.rank(sVec) == 2
|
| 287 |
+
assert sVec.stride[0] == 1
|
| 288 |
+
stage = sVec.shape[1]
|
| 289 |
+
shape = (
|
| 290 |
+
(sVec.shape[0], expand_shape, stage)
|
| 291 |
+
if const_expr(is_colvec)
|
| 292 |
+
else (expand_shape, sVec.shape[0], stage)
|
| 293 |
+
)
|
| 294 |
+
stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1])
|
| 295 |
+
sVec_mma = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride))
|
| 296 |
+
tC_sVec = make_acc_tensor_mn_view(thr_mma.partition_A(sVec_mma))
|
| 297 |
+
return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def copy_partition_S_vec(
|
| 301 |
+
sVec: cute.Tensor, thr_copy: cute.core.ThrCopy, expand_shape: int, is_colvec: bool
|
| 302 |
+
) -> cute.Tensor:
|
| 303 |
+
assert cute.rank(sVec) == 2
|
| 304 |
+
assert sVec.stride[0] == 1
|
| 305 |
+
stage = sVec.shape[1]
|
| 306 |
+
shape = (
|
| 307 |
+
(sVec.shape[0], expand_shape, stage)
|
| 308 |
+
if const_expr(is_colvec)
|
| 309 |
+
else (expand_shape, sVec.shape[0], stage)
|
| 310 |
+
)
|
| 311 |
+
stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1])
|
| 312 |
+
sVec_thr = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride))
|
| 313 |
+
tC_sVec = reshape_acc_to_mn(thr_copy.partition_S(sVec_thr))
|
| 314 |
+
return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def copy_partition_D_vec(
|
| 318 |
+
sVec: cute.Tensor, thr_copy: cute.core.ThrCopy, expand_shape: int, is_colvec: bool
|
| 319 |
+
) -> cute.Tensor:
|
| 320 |
+
assert cute.rank(sVec) == 2
|
| 321 |
+
assert sVec.stride[0] == 1
|
| 322 |
+
stage = sVec.shape[1]
|
| 323 |
+
shape = (
|
| 324 |
+
(sVec.shape[0], expand_shape, stage)
|
| 325 |
+
if const_expr(is_colvec)
|
| 326 |
+
else (expand_shape, sVec.shape[0], stage)
|
| 327 |
+
)
|
| 328 |
+
stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1])
|
| 329 |
+
sVec_thr = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride))
|
| 330 |
+
tC_sVec = reshape_acc_to_mn(thr_copy.partition_D(sVec_thr))
|
| 331 |
+
return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]
|
build/torch-cuda/quack/sm90_utils.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Tri Dao.
|
| 2 |
+
|
| 3 |
+
from typing import Type, Union, Optional
|
| 4 |
+
|
| 5 |
+
import cutlass
|
| 6 |
+
import cutlass.cute as cute
|
| 7 |
+
import cutlass.utils.hopper_helpers as sm90_utils_og
|
| 8 |
+
from cutlass.cute.nvgpu import warpgroup
|
| 9 |
+
from cutlass.cutlass_dsl import Numeric, dsl_user_op
|
| 10 |
+
from cutlass import Float32, Int32, Boolean, const_expr
|
| 11 |
+
from cutlass.utils import LayoutEnum
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dsl_user_op
|
| 15 |
+
def make_smem_layout(
|
| 16 |
+
dtype: Type[Numeric],
|
| 17 |
+
layout: LayoutEnum,
|
| 18 |
+
tile: cute.Tile,
|
| 19 |
+
stage: Optional[int] = None,
|
| 20 |
+
major_mode_size: Optional[int] = None,
|
| 21 |
+
*,
|
| 22 |
+
loc=None,
|
| 23 |
+
ip=None,
|
| 24 |
+
) -> Union[cute.Layout, cute.ComposedLayout]:
|
| 25 |
+
shape = cute.product_each(cute.shape(tile, loc=loc, ip=ip), loc=loc, ip=ip)
|
| 26 |
+
if const_expr(major_mode_size is None):
|
| 27 |
+
major_mode_size = shape[1] if layout.is_n_major_c() else shape[0]
|
| 28 |
+
smem_layout_atom = warpgroup.make_smem_layout_atom(
|
| 29 |
+
sm90_utils_og.get_smem_layout_atom(layout, dtype, major_mode_size),
|
| 30 |
+
dtype,
|
| 31 |
+
)
|
| 32 |
+
order = (1, 0, 2) if const_expr(layout.is_m_major_c()) else (0, 1, 2)
|
| 33 |
+
smem_layout_staged = cute.tile_to_shape(
|
| 34 |
+
smem_layout_atom,
|
| 35 |
+
cute.append(shape, stage) if const_expr(stage is not None) else shape,
|
| 36 |
+
order=order if const_expr(stage is not None) else order[:2],
|
| 37 |
+
)
|
| 38 |
+
return smem_layout_staged
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# For compatibility with blackwell_helpers.py
|
| 42 |
+
make_smem_layout_epi = make_smem_layout
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@dsl_user_op
|
| 46 |
+
def partition_for_epilogue(
|
| 47 |
+
cT: cute.Tensor,
|
| 48 |
+
epi_tile: cute.Tile,
|
| 49 |
+
tiled_copy: cute.TiledCopy,
|
| 50 |
+
tidx: Int32,
|
| 51 |
+
reference_src: bool, # do register tensors reference the src or dst layout of the tiled copy
|
| 52 |
+
*,
|
| 53 |
+
loc=None,
|
| 54 |
+
ip=None,
|
| 55 |
+
) -> cute.Tensor:
|
| 56 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 57 |
+
cT_epi = cute.flat_divide(cT, epi_tile)
|
| 58 |
+
# (CPY, CPY_M, CPY_N, EPI_M, EPI_N)
|
| 59 |
+
if const_expr(reference_src):
|
| 60 |
+
return thr_copy.partition_S(cT_epi, loc=loc, ip=ip)
|
| 61 |
+
else:
|
| 62 |
+
return thr_copy.partition_D(cT_epi, loc=loc, ip=ip)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@cute.jit
|
| 66 |
+
def gemm(
|
| 67 |
+
tiled_mma: cute.TiledMma,
|
| 68 |
+
acc: cute.Tensor,
|
| 69 |
+
tCrA: cute.Tensor,
|
| 70 |
+
tCrB: cute.Tensor,
|
| 71 |
+
zero_init: cutlass.Constexpr[bool] = False,
|
| 72 |
+
wg_wait: cutlass.Constexpr[int] = 0,
|
| 73 |
+
# A_in_regs: cutlass.Constexpr[bool] = False,
|
| 74 |
+
swap_AB: cutlass.Constexpr[bool] = False,
|
| 75 |
+
) -> None:
|
| 76 |
+
if const_expr(swap_AB):
|
| 77 |
+
gemm(tiled_mma, acc, tCrB, tCrA, zero_init=zero_init, wg_wait=wg_wait, swap_AB=False)
|
| 78 |
+
else:
|
| 79 |
+
warpgroup.fence()
|
| 80 |
+
# We make a new mma_atom since we'll be modifying its attribute (accumulate).
|
| 81 |
+
# Otherwise the compiler complains "operand #0 does not dominate this use"
|
| 82 |
+
mma_atom = cute.make_mma_atom(tiled_mma.op)
|
| 83 |
+
mma_atom.set(warpgroup.Field.ACCUMULATE, not zero_init)
|
| 84 |
+
for k in cutlass.range_constexpr(cute.size(tCrA.shape[2])):
|
| 85 |
+
cute.gemm(mma_atom, acc, tCrA[None, None, k], tCrB[None, None, k], acc)
|
| 86 |
+
mma_atom.set(warpgroup.Field.ACCUMULATE, True)
|
| 87 |
+
warpgroup.commit_group()
|
| 88 |
+
if const_expr(wg_wait >= 0):
|
| 89 |
+
warpgroup.wait_group(wg_wait)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def gemm_zero_init(
|
| 93 |
+
tiled_mma: cute.TiledMma,
|
| 94 |
+
shape: cute.Shape,
|
| 95 |
+
tCrA: cute.Tensor,
|
| 96 |
+
tCrB: cute.Tensor,
|
| 97 |
+
A_idx: Optional[Int32] = None,
|
| 98 |
+
B_idx: Optional[Int32] = None,
|
| 99 |
+
wg_wait: int = -1,
|
| 100 |
+
swap_AB: bool = False,
|
| 101 |
+
) -> cute.Tensor:
|
| 102 |
+
if const_expr(swap_AB):
|
| 103 |
+
return gemm_zero_init(
|
| 104 |
+
tiled_mma, shape[::-1], tCrB, tCrA, B_idx, A_idx, wg_wait, swap_AB=False
|
| 105 |
+
)
|
| 106 |
+
else:
|
| 107 |
+
acc = cute.make_rmem_tensor(tiled_mma.partition_shape_C(shape), Float32)
|
| 108 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 109 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 110 |
+
gemm(tiled_mma, acc, rA, rB, zero_init=True, wg_wait=wg_wait)
|
| 111 |
+
return acc
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def gemm_w_idx(
|
| 115 |
+
tiled_mma: cute.TiledMma,
|
| 116 |
+
acc: cute.Tensor,
|
| 117 |
+
tCrA: cute.Tensor,
|
| 118 |
+
tCrB: cute.Tensor,
|
| 119 |
+
zero_init: Boolean,
|
| 120 |
+
A_idx: Optional[Int32] = None,
|
| 121 |
+
B_idx: Optional[Int32] = None,
|
| 122 |
+
wg_wait: int = -1,
|
| 123 |
+
swap_AB: bool = False,
|
| 124 |
+
) -> None:
|
| 125 |
+
if const_expr(swap_AB):
|
| 126 |
+
gemm_w_idx(tiled_mma, acc, tCrB, tCrA, zero_init, B_idx, A_idx, wg_wait, swap_AB=False)
|
| 127 |
+
else:
|
| 128 |
+
rA = tCrA if const_expr(A_idx is None) else tCrA[None, None, None, A_idx]
|
| 129 |
+
rB = tCrB if const_expr(B_idx is None) else tCrB[None, None, None, B_idx]
|
| 130 |
+
gemm(tiled_mma, acc, rA, rB, zero_init=zero_init, wg_wait=wg_wait)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def partition_fragment_ABC(
|
| 134 |
+
thr_mma: cute.ThrMma,
|
| 135 |
+
shape_mnk: cute.Shape,
|
| 136 |
+
sA: Optional[cute.Tensor],
|
| 137 |
+
sB: Optional[cute.Tensor],
|
| 138 |
+
swap_AB: bool = False,
|
| 139 |
+
):
|
| 140 |
+
is_rs = thr_mma.op.a_src == warpgroup.OperandSource.RMEM
|
| 141 |
+
if const_expr(not swap_AB):
|
| 142 |
+
acc = cute.make_rmem_tensor(thr_mma.partition_shape_C(shape_mnk[:2]), Float32)
|
| 143 |
+
if const_expr(not is_rs):
|
| 144 |
+
assert sA is not None
|
| 145 |
+
tCrA = thr_mma.make_fragment_A(thr_mma.partition_A(sA))
|
| 146 |
+
else:
|
| 147 |
+
tCrA = thr_mma.make_fragment_A(thr_mma.partition_shape_A((shape_mnk[0], shape_mnk[2])))
|
| 148 |
+
assert sB is not None
|
| 149 |
+
tCrB = thr_mma.make_fragment_B(thr_mma.partition_B(sB))
|
| 150 |
+
else:
|
| 151 |
+
acc = cute.make_rmem_tensor(
|
| 152 |
+
thr_mma.partition_shape_C((shape_mnk[1], shape_mnk[0])), Float32
|
| 153 |
+
)
|
| 154 |
+
if const_expr(not is_rs):
|
| 155 |
+
assert sB is not None
|
| 156 |
+
tCrB = thr_mma.make_fragment_A(thr_mma.partition_A(sB))
|
| 157 |
+
else: # B in rmem
|
| 158 |
+
tCrB = thr_mma.make_fragment_A(thr_mma.partition_shape_A((shape_mnk[1], shape_mnk[2])))
|
| 159 |
+
assert sA is not None
|
| 160 |
+
tCrA = thr_mma.make_fragment_B(thr_mma.partition_B(sA))
|
| 161 |
+
return acc, tCrA, tCrB
|
build/torch-cuda/quack/utils.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional, Tuple, Union
|
| 5 |
+
|
| 6 |
+
import cutlass
|
| 7 |
+
import cutlass.cute as cute
|
| 8 |
+
|
| 9 |
+
from cutlass import Float32, Int32, const_expr
|
| 10 |
+
from cutlass._mlir.dialects import arith as _arith
|
| 11 |
+
from cutlass._mlir.dialects import llvm, nvvm, vector
|
| 12 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dsl_user_op
|
| 16 |
+
def elem_pointer(x: cute.Tensor, coord: cute.Coord, *, loc=None, ip=None) -> cute.Pointer:
|
| 17 |
+
return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@cute.jit
|
| 21 |
+
def load_scalar_or_pointer(x, dtype=Float32):
|
| 22 |
+
if const_expr(isinstance(x, cute.Pointer)):
|
| 23 |
+
return dtype(cute.make_tensor(x, cute.make_layout(1))[0])
|
| 24 |
+
else:
|
| 25 |
+
return x
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@dsl_user_op
|
| 29 |
+
def set_block_rank(
|
| 30 |
+
smem_ptr: cute.Pointer, peer_cta_rank_in_cluster: Int32, *, loc=None, ip=None
|
| 31 |
+
) -> Int32:
|
| 32 |
+
"""Map the given smem pointer to the address at another CTA rank in the cluster."""
|
| 33 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip).ir_value()
|
| 34 |
+
return Int32(
|
| 35 |
+
llvm.inline_asm(
|
| 36 |
+
T.i32(),
|
| 37 |
+
[smem_ptr_i32, peer_cta_rank_in_cluster.ir_value()],
|
| 38 |
+
"mapa.shared::cluster.u32 $0, $1, $2;",
|
| 39 |
+
"=r,r,r",
|
| 40 |
+
has_side_effects=False,
|
| 41 |
+
is_align_stack=False,
|
| 42 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 43 |
+
)
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@dsl_user_op
|
| 48 |
+
def store_shared_remote(
|
| 49 |
+
val: float | Float32 | Int32 | cutlass.Int64,
|
| 50 |
+
smem_ptr: cute.Pointer,
|
| 51 |
+
mbar_ptr: cute.Pointer,
|
| 52 |
+
peer_cta_rank_in_cluster: cute.typing.Int,
|
| 53 |
+
*,
|
| 54 |
+
loc=None,
|
| 55 |
+
ip=None,
|
| 56 |
+
) -> None:
|
| 57 |
+
remote_smem_ptr_i32 = set_block_rank(
|
| 58 |
+
smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 59 |
+
).ir_value()
|
| 60 |
+
remote_mbar_ptr_i32 = set_block_rank(
|
| 61 |
+
mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 62 |
+
).ir_value()
|
| 63 |
+
if const_expr(isinstance(val, float)):
|
| 64 |
+
val = Float32(val)
|
| 65 |
+
assert isinstance(val, (Float32, Int32, cutlass.Int64)), "val must be Float32, Int32, or Int64"
|
| 66 |
+
suffix = {Float32: "f32", Int32: "s32", cutlass.Int64: "s64"}[type(val)]
|
| 67 |
+
constraint = {Float32: "f", Int32: "r", cutlass.Int64: "l"}[type(val)]
|
| 68 |
+
llvm.inline_asm(
|
| 69 |
+
None,
|
| 70 |
+
[remote_smem_ptr_i32, val.ir_value(loc=loc, ip=ip), remote_mbar_ptr_i32],
|
| 71 |
+
f"st.async.shared::cluster.mbarrier::complete_tx::bytes.{suffix} [$0], $1, [$2];",
|
| 72 |
+
f"r,{constraint},r",
|
| 73 |
+
has_side_effects=True,
|
| 74 |
+
is_align_stack=False,
|
| 75 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@dsl_user_op
|
| 80 |
+
def store_shared_remote_x4(
|
| 81 |
+
val0: Float32 | Int32,
|
| 82 |
+
val1: Float32 | Int32,
|
| 83 |
+
val2: Float32 | Int32,
|
| 84 |
+
val3: Float32 | Int32,
|
| 85 |
+
smem_ptr: cute.Pointer,
|
| 86 |
+
mbar_ptr: cute.Pointer,
|
| 87 |
+
peer_cta_rank_in_cluster: cute.typing.Int,
|
| 88 |
+
*,
|
| 89 |
+
loc=None,
|
| 90 |
+
ip=None,
|
| 91 |
+
) -> None:
|
| 92 |
+
remote_smem_ptr_i32 = set_block_rank(
|
| 93 |
+
smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 94 |
+
).ir_value()
|
| 95 |
+
remote_mbar_ptr_i32 = set_block_rank(
|
| 96 |
+
mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip
|
| 97 |
+
).ir_value()
|
| 98 |
+
assert isinstance(val0, (Float32, Int32)), "val must be Float32, or Int32"
|
| 99 |
+
dtype = Float32 if isinstance(val0, Float32) else Int32
|
| 100 |
+
suffix = {Float32: "f32", Int32: "s32"}[dtype]
|
| 101 |
+
constraint = {Float32: "f", Int32: "r"}[dtype]
|
| 102 |
+
llvm.inline_asm(
|
| 103 |
+
None,
|
| 104 |
+
[
|
| 105 |
+
remote_smem_ptr_i32,
|
| 106 |
+
remote_mbar_ptr_i32,
|
| 107 |
+
dtype(val0).ir_value(loc=loc, ip=ip),
|
| 108 |
+
dtype(val1).ir_value(loc=loc, ip=ip),
|
| 109 |
+
dtype(val2).ir_value(loc=loc, ip=ip),
|
| 110 |
+
dtype(val3).ir_value(loc=loc, ip=ip),
|
| 111 |
+
],
|
| 112 |
+
"{\n\t"
|
| 113 |
+
f".reg .v4 .{suffix} abcd;\n\t"
|
| 114 |
+
f"mov.{suffix} abcd.x, $2;\n\t"
|
| 115 |
+
f"mov.{suffix} abcd.y, $3;\n\t"
|
| 116 |
+
f"mov.{suffix} abcd.z, $4;\n\t"
|
| 117 |
+
f"mov.{suffix} abcd.w, $5;\n\t"
|
| 118 |
+
f"st.async.shared::cluster.mbarrier::complete_tx::bytes.v4.{suffix} [$0], abcd, [$1];\n\t"
|
| 119 |
+
"}\n",
|
| 120 |
+
f"r,r,{constraint},{constraint},{constraint},{constraint}",
|
| 121 |
+
has_side_effects=True,
|
| 122 |
+
is_align_stack=False,
|
| 123 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
@dsl_user_op
|
| 128 |
+
def fmin(a: Union[float, Float32], b: Union[float, Float32], *, loc=None, ip=None) -> Float32:
|
| 129 |
+
if cutlass.const_expr(cutlass.CUDA_VERSION.major) == 12:
|
| 130 |
+
return Float32(
|
| 131 |
+
nvvm.fmin(
|
| 132 |
+
T.f32(),
|
| 133 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 134 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 135 |
+
loc=loc,
|
| 136 |
+
ip=ip,
|
| 137 |
+
)
|
| 138 |
+
)
|
| 139 |
+
return Float32(
|
| 140 |
+
nvvm.fmin(
|
| 141 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 142 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 143 |
+
loc=loc,
|
| 144 |
+
ip=ip,
|
| 145 |
+
)
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
@dsl_user_op
|
| 150 |
+
def sqrt(a: float | Float32, *, loc=None, ip=None) -> Float32:
|
| 151 |
+
return Float32(
|
| 152 |
+
llvm.inline_asm(
|
| 153 |
+
T.f32(),
|
| 154 |
+
[Float32(a).ir_value(loc=loc, ip=ip)],
|
| 155 |
+
"sqrt.approx.f32 $0, $1;",
|
| 156 |
+
"=f,f",
|
| 157 |
+
has_side_effects=False,
|
| 158 |
+
is_align_stack=False,
|
| 159 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 160 |
+
)
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@dsl_user_op
|
| 165 |
+
def ceil(a: float | Float32, *, loc=None, ip=None) -> Int32:
|
| 166 |
+
return Int32(
|
| 167 |
+
llvm.inline_asm(
|
| 168 |
+
T.i32(),
|
| 169 |
+
[Float32(a).ir_value(loc=loc, ip=ip)],
|
| 170 |
+
"cvt.rpi.ftz.s32.f32 $0, $1;",
|
| 171 |
+
"=r,f",
|
| 172 |
+
has_side_effects=False,
|
| 173 |
+
is_align_stack=False,
|
| 174 |
+
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 175 |
+
)
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
@cute.jit
|
| 180 |
+
def fill_oob(tXsX: cute.Tensor, tXpX: Optional[cute.Tensor], fill_value: cute.Numeric) -> None:
|
| 181 |
+
"""Fill out-of-bounds values in shared memory tensor.
|
| 182 |
+
|
| 183 |
+
Args:
|
| 184 |
+
tXsX: Shared memory tensor to fill
|
| 185 |
+
tXpX: Predicate tensor indicating valid elements
|
| 186 |
+
fill_value: Value to fill OOB locations with
|
| 187 |
+
"""
|
| 188 |
+
tXrX_fill = cute.make_rmem_tensor_like(tXsX[(None, 0), None, 0])
|
| 189 |
+
tXrX_fill.fill(fill_value)
|
| 190 |
+
for rest_v in cutlass.range_constexpr(tXsX.shape[0][1]):
|
| 191 |
+
for rest_k in cutlass.range_constexpr(tXsX.shape[2]):
|
| 192 |
+
if const_expr(tXpX is not None):
|
| 193 |
+
if not tXpX[rest_v, 0, rest_k]:
|
| 194 |
+
cute.autovec_copy(tXrX_fill, tXsX[(None, rest_v), None, rest_k])
|
| 195 |
+
else:
|
| 196 |
+
cute.autovec_copy(tXrX_fill, tXsX[(None, rest_v), None, rest_k])
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
# ---------------------------------------------------------------------------
|
| 200 |
+
# General-purpose DSL store / vector helpers
|
| 201 |
+
# ---------------------------------------------------------------------------
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
@dsl_user_op
|
| 205 |
+
def make_vector(elem_type, *values, loc=None, ip=None):
|
| 206 |
+
"""Build an MLIR vector <N x elem_type> from N scalar DSL values.
|
| 207 |
+
|
| 208 |
+
Example: make_vector(cutlass.Uint32, v0, v1) -> <2 x i32> MLIR vector
|
| 209 |
+
"""
|
| 210 |
+
from cutlass._mlir import ir
|
| 211 |
+
|
| 212 |
+
n = len(values)
|
| 213 |
+
mlir_ty = elem_type.mlir_type
|
| 214 |
+
vec_ty = ir.VectorType.get([n], mlir_ty)
|
| 215 |
+
vec = llvm.mlir_undef(vec_ty, loc=loc, ip=ip)
|
| 216 |
+
for i, v in enumerate(values):
|
| 217 |
+
vec = vector.insertelement(
|
| 218 |
+
elem_type(v).ir_value(loc=loc, ip=ip),
|
| 219 |
+
vec,
|
| 220 |
+
position=_arith.constant(T.i32(), i, loc=loc, ip=ip),
|
| 221 |
+
loc=loc,
|
| 222 |
+
ip=ip,
|
| 223 |
+
)
|
| 224 |
+
return vec
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
@dsl_user_op
|
| 228 |
+
def f32x2_to_i64(a: Float32, b: Float32, *, loc=None, ip=None) -> cutlass.Int64:
|
| 229 |
+
vec_f32x2 = vector.from_elements(
|
| 230 |
+
T.vector(2, T.f32()), (a.ir_value(), b.ir_value()), loc=loc, ip=ip
|
| 231 |
+
)
|
| 232 |
+
vec_i64x1 = vector.bitcast(T.vector(1, T.i64()), vec_f32x2)
|
| 233 |
+
res = cutlass.Int64(
|
| 234 |
+
vector.extract(vec_i64x1, dynamic_position=[], static_position=[0], loc=loc, ip=ip)
|
| 235 |
+
)
|
| 236 |
+
return res
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
@dsl_user_op
|
| 240 |
+
def i64_to_f32x2(c: cutlass.Int64, *, loc=None, ip=None) -> Tuple[Float32, Float32]:
|
| 241 |
+
vec_i64x1 = vector.from_elements(T.vector(1, T.i64()), (c.ir_value(),), loc=loc, ip=ip)
|
| 242 |
+
vec_f32x2 = vector.bitcast(T.vector(2, T.f32()), vec_i64x1)
|
| 243 |
+
res0 = Float32(
|
| 244 |
+
vector.extract(vec_f32x2, dynamic_position=[], static_position=[0], loc=loc, ip=ip)
|
| 245 |
+
)
|
| 246 |
+
res1 = Float32(
|
| 247 |
+
vector.extract(vec_f32x2, dynamic_position=[], static_position=[1], loc=loc, ip=ip)
|
| 248 |
+
)
|
| 249 |
+
return res0, res1
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
@cute.jit
|
| 253 |
+
def warp_prefix_sum(val: Int32, lane: Optional[Int32] = None) -> Int32:
|
| 254 |
+
if const_expr(lane is None):
|
| 255 |
+
lane = cute.arch.lane_idx()
|
| 256 |
+
for i in cutlass.range_constexpr(int(math.log2(cute.arch.WARP_SIZE))):
|
| 257 |
+
offset = 1 << i
|
| 258 |
+
# Very important that we set mask_and_clamp to 0
|
| 259 |
+
partial_sum = cute.arch.shuffle_sync_up(val, offset=offset, mask_and_clamp=0)
|
| 260 |
+
if lane >= offset:
|
| 261 |
+
val += partial_sum
|
| 262 |
+
return val
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
@dsl_user_op
|
| 266 |
+
def atomic_inc_i32(a: int | Int32, gmem_ptr: cute.Pointer, *, loc=None, ip=None) -> Int32:
|
| 267 |
+
from cutlass import CUDA_VERSION
|
| 268 |
+
|
| 269 |
+
# * NVVM call based on nvvm version
|
| 270 |
+
if CUDA_VERSION.major == 12 and CUDA_VERSION.minor == 9:
|
| 271 |
+
# Old API: requires explicit result type as first positional argument
|
| 272 |
+
return nvvm.atomicrmw(
|
| 273 |
+
res=T.i32(), op=nvvm.AtomicOpKind.INC, ptr=gmem_ptr.llvm_ptr, a=Int32(a).ir_value()
|
| 274 |
+
)
|
| 275 |
+
else:
|
| 276 |
+
# New API: infers result type automatically
|
| 277 |
+
return nvvm.atomicrmw(
|
| 278 |
+
op=nvvm.AtomicOpKind.INC, ptr=gmem_ptr.llvm_ptr, a=Int32(a).ir_value()
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
@dsl_user_op
|
| 283 |
+
def atomic_add_i32(a: int | Int32, gmem_ptr: cute.Pointer, *, loc=None, ip=None) -> Int32:
|
| 284 |
+
from cutlass import CUDA_VERSION
|
| 285 |
+
|
| 286 |
+
# * NVVM call based on nvvm version
|
| 287 |
+
if CUDA_VERSION.major == 12 and CUDA_VERSION.minor == 9:
|
| 288 |
+
# Old API: requires explicit result type as first positional argument
|
| 289 |
+
return nvvm.atomicrmw(
|
| 290 |
+
res=T.i32(), op=nvvm.AtomicOpKind.ADD, ptr=gmem_ptr.llvm_ptr, a=Int32(a).ir_value()
|
| 291 |
+
)
|
| 292 |
+
else:
|
| 293 |
+
# New API: infers result type automatically
|
| 294 |
+
return nvvm.atomicrmw(
|
| 295 |
+
op=nvvm.AtomicOpKind.ADD, ptr=gmem_ptr.llvm_ptr, a=Int32(a).ir_value()
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
@dsl_user_op
|
| 300 |
+
def issue_clc_query_nomulticast(
|
| 301 |
+
mbar_ptr: cute.Pointer,
|
| 302 |
+
clc_response_ptr: cute.Pointer,
|
| 303 |
+
loc=None,
|
| 304 |
+
ip=None,
|
| 305 |
+
) -> None:
|
| 306 |
+
"""
|
| 307 |
+
The clusterlaunchcontrol.try_cancel instruction requests atomically cancelling the launch
|
| 308 |
+
of a cluster that has not started running yet. It asynchronously writes an opaque response
|
| 309 |
+
to shared memory indicating whether the operation succeeded or failed. On success, the
|
| 310 |
+
opaque response contains the ctaid of the first CTA of the canceled cluster.
|
| 311 |
+
|
| 312 |
+
:param mbar_ptr: A pointer to the mbarrier address in SMEM
|
| 313 |
+
:type mbar_ptr: Pointer
|
| 314 |
+
:param clc_response_ptr: A pointer to the cluster launch control response address in SMEM
|
| 315 |
+
:type clc_response_ptr: Pointer
|
| 316 |
+
"""
|
| 317 |
+
mbar_llvm_ptr = mbar_ptr.llvm_ptr
|
| 318 |
+
clc_response_llvm_ptr = clc_response_ptr.llvm_ptr
|
| 319 |
+
nvvm.clusterlaunchcontrol_try_cancel(
|
| 320 |
+
clc_response_llvm_ptr,
|
| 321 |
+
mbar_llvm_ptr,
|
| 322 |
+
loc=loc,
|
| 323 |
+
ip=ip,
|
| 324 |
+
)
|