Kernels:
Trusted publisher
Uploaded using `kernel-builder`.
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +8 -3
- build/torch-cuda/__init__.py +5 -11
- build/torch-cuda/_ops.py +39 -3
- build/torch-cuda/_private_for_testing.py +16 -0
- build/torch-cuda/benchmark.py +0 -268
- build/torch-cuda/blackwell_helpers.py +48 -22
- build/torch-cuda/block_info.py +69 -8
- build/torch-cuda/block_sparse_utils.py +790 -312
- build/torch-cuda/block_sparsity.py +359 -73
- build/torch-cuda/cache_utils.py +9 -16
- build/torch-cuda/compute_block_sparsity.py +330 -157
- build/torch-cuda/cu_blocks_kernel.py +181 -0
- build/torch-cuda/cute_dsl_utils.py +155 -67
- build/torch-cuda/fa_logging.py +97 -0
- build/torch-cuda/flash_attn4/__init__.py +0 -26
- build/torch-cuda/flash_bwd.py +161 -79
- build/torch-cuda/flash_bwd_mla_dk_sm100.py +1185 -0
- build/torch-cuda/flash_bwd_mla_dq_dqv_sm100.py +1297 -0
- build/torch-cuda/flash_bwd_mla_sm100.py +2135 -0
- build/torch-cuda/flash_bwd_postprocess.py +133 -35
- build/torch-cuda/flash_bwd_preprocess.py +274 -164
- build/torch-cuda/flash_bwd_sm100.py +621 -259
- build/torch-cuda/flash_bwd_sm120.py +55 -0
- build/torch-cuda/flash_bwd_sm90.py +556 -192
- build/torch-cuda/flash_fwd.py +0 -0
- build/torch-cuda/flash_fwd_combine.py +356 -310
- build/torch-cuda/flash_fwd_mla_sm100.py +0 -0
- build/torch-cuda/flash_fwd_sm100.py +0 -0
- build/torch-cuda/flash_fwd_sm120.py +61 -0
- build/torch-cuda/flash_fwd_sm90.py +1550 -0
- build/torch-cuda/interface.py +0 -0
- build/torch-cuda/mask.py +1221 -163
- build/torch-cuda/metadata.json +86 -2
- build/torch-cuda/metadata.json.sigstore +1 -0
- build/torch-cuda/mma_sm100_desc.py +2 -2
- build/torch-cuda/named_barrier.py +31 -0
- build/torch-cuda/pack_gqa.py +111 -13
- build/torch-cuda/paged_kv.py +62 -28
- build/torch-cuda/pipeline.py +198 -236
- build/torch-cuda/prepare_scheduler.py +387 -0
- build/torch-cuda/quack/__init__.py +9 -0
- build/torch-cuda/quack/activation.py +0 -568
- build/torch-cuda/quack/copy_utils.py +529 -73
- build/torch-cuda/quack/cute_dsl_utils.py +55 -29
- build/torch-cuda/quack/dsl/__init__.py +12 -0
- build/torch-cuda/quack/dsl/cute_tensor_indexing.py +139 -0
- build/torch-cuda/quack/layout_utils.py +92 -4
- build/torch-cuda/quack/rounding.py +231 -0
- build/torch-cuda/quack/sm90_utils.py +33 -1
- build/torch-cuda/quack/utils.py +201 -0
README.md
CHANGED
|
@@ -11,16 +11,21 @@ This is the repository card of kernels-community/flash-attn4 that has been pushe
|
|
| 11 |
# make sure `kernels` is installed: `pip install -U kernels`
|
| 12 |
from kernels import get_kernel
|
| 13 |
|
| 14 |
-
kernel_module = get_kernel("kernels-community/flash-attn4", version=
|
| 15 |
-
|
| 16 |
|
| 17 |
-
|
| 18 |
```
|
| 19 |
|
| 20 |
## Available functions
|
|
|
|
| 21 |
- `flash_attn_func`
|
| 22 |
- `flash_attn_varlen_func`
|
| 23 |
|
| 24 |
## Benchmarks
|
| 25 |
|
| 26 |
No benchmark available yet.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
# make sure `kernels` is installed: `pip install -U kernels`
|
| 12 |
from kernels import get_kernel
|
| 13 |
|
| 14 |
+
kernel_module = get_kernel("kernels-community/flash-attn4", version=1)
|
| 15 |
+
_private_for_testing = kernel_module._private_for_testing
|
| 16 |
|
| 17 |
+
_private_for_testing(...)
|
| 18 |
```
|
| 19 |
|
| 20 |
## Available functions
|
| 21 |
+
- `_private_for_testing`
|
| 22 |
- `flash_attn_func`
|
| 23 |
- `flash_attn_varlen_func`
|
| 24 |
|
| 25 |
## Benchmarks
|
| 26 |
|
| 27 |
No benchmark available yet.
|
| 28 |
+
|
| 29 |
+
## Source code
|
| 30 |
+
|
| 31 |
+
Source code of this kernel originally comes from https://github.com/Dao-AILab/flash-attention.git and it was repurposed for compatibility with `kernels`.
|
build/torch-cuda/__init__.py
CHANGED
|
@@ -1,24 +1,18 @@
|
|
| 1 |
"""Flash Attention CUTE (CUDA Template Engine) implementation."""
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
# Update when syncing again.
|
| 6 |
-
__version__ = "4.0.0.beta4"
|
| 7 |
-
|
| 8 |
-
import cutlass.cute as cute
|
| 9 |
|
| 10 |
from .interface import (
|
| 11 |
flash_attn_func,
|
| 12 |
flash_attn_varlen_func,
|
| 13 |
)
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
cute.compile = cute_compile_patched
|
| 19 |
-
|
| 20 |
|
| 21 |
__all__ = [
|
|
|
|
| 22 |
"flash_attn_func",
|
| 23 |
"flash_attn_varlen_func",
|
| 24 |
]
|
|
|
|
| 1 |
"""Flash Attention CUTE (CUDA Template Engine) implementation."""
|
| 2 |
|
| 3 |
+
__version__ = "4.0.0.beta30"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
from .interface import (
|
| 6 |
flash_attn_func,
|
| 7 |
flash_attn_varlen_func,
|
| 8 |
)
|
| 9 |
|
| 10 |
+
# Internal symbols the tests need to reach. Not public API -- see the module
|
| 11 |
+
# docstring.
|
| 12 |
+
from . import _private_for_testing # noqa: F401
|
|
|
|
|
|
|
| 13 |
|
| 14 |
__all__ = [
|
| 15 |
+
"_private_for_testing",
|
| 16 |
"flash_attn_func",
|
| 17 |
"flash_attn_varlen_func",
|
| 18 |
]
|
build/torch-cuda/_ops.py
CHANGED
|
@@ -1,8 +1,44 @@
|
|
| 1 |
import torch
|
| 2 |
-
ops = torch.ops._flash_attn4_06b9ab8
|
| 3 |
|
| 4 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
Prefix op by namespace.
|
| 7 |
"""
|
| 8 |
-
return f"
|
|
|
|
| 1 |
import torch
|
|
|
|
| 2 |
|
| 3 |
+
def get_backend() -> str:
|
| 4 |
+
"""Detect the backend by inspecting torch."""
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
if hasattr(torch.backends, "tpu"):
|
| 8 |
+
# torch_tpu sets torch.backends.tpu when it is imported (via
|
| 9 |
+
# torch's device-backend autoload), regardless of whether TPU
|
| 10 |
+
# hardware is present — analogous to torch.version.cuda being
|
| 11 |
+
# set on CUDA builds without a GPU.
|
| 12 |
+
return "tpu"
|
| 13 |
+
elif hasattr(torch, "neuron"):
|
| 14 |
+
# Needs to be sorted before specific Torch builds, since Neuron
|
| 15 |
+
# extension can be loaded into e.g. CUDA Torch builds.
|
| 16 |
+
return "neuron"
|
| 17 |
+
elif torch.version.cuda is not None:
|
| 18 |
+
return "cuda"
|
| 19 |
+
elif torch.version.hip is not None:
|
| 20 |
+
return "rocm"
|
| 21 |
+
elif torch.backends.mps.is_available():
|
| 22 |
+
return "metal"
|
| 23 |
+
elif hasattr(torch.version, "xpu") and torch.version.xpu is not None:
|
| 24 |
+
return "xpu"
|
| 25 |
+
else:
|
| 26 |
+
return "cpu"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _find_ops_name() -> str:
|
| 30 |
+
kernel_name = "flash_attn4"
|
| 31 |
+
unique_id = "d8eaf3c"
|
| 32 |
+
backend = get_backend()
|
| 33 |
+
return f"_{kernel_name}_{backend}_{unique_id}"
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
_OPS_NAME = _find_ops_name()
|
| 37 |
+
|
| 38 |
+
ops = getattr(torch.ops, _OPS_NAME)
|
| 39 |
+
|
| 40 |
+
def add_op_namespace_prefix(op_name: str) -> str:
|
| 41 |
"""
|
| 42 |
Prefix op by namespace.
|
| 43 |
"""
|
| 44 |
+
return f"{_OPS_NAME}::{op_name}"
|
build/torch-cuda/_private_for_testing.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Internal symbols reachable for the test suite.
|
| 2 |
+
|
| 3 |
+
Modules re-exported here are **not** public API. They carry no stability or
|
| 4 |
+
versioning guarantee and must not be used by downstream code -- they are
|
| 5 |
+
exposed only so that the tests can reach them through ``get_kernel``, which
|
| 6 |
+
loads the kernel under a generated module name.
|
| 7 |
+
|
| 8 |
+
Anything genuinely public belongs in ``__init__.py``'s ``__all__`` instead,
|
| 9 |
+
where changing it requires a kernel version bump.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from . import compute_block_sparsity # noqa: F401
|
| 13 |
+
|
| 14 |
+
__all__ = [
|
| 15 |
+
"compute_block_sparsity",
|
| 16 |
+
]
|
build/torch-cuda/benchmark.py
DELETED
|
@@ -1,268 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2023, Tri Dao.
|
| 2 |
-
"""Useful functions for writing test code."""
|
| 3 |
-
|
| 4 |
-
import torch
|
| 5 |
-
import torch.utils.benchmark as benchmark
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
def benchmark_forward(
|
| 9 |
-
fn, *inputs, repeats=10, desc="", verbose=True, amp=False, amp_dtype=torch.float16, **kwinputs
|
| 10 |
-
):
|
| 11 |
-
"""Use Pytorch Benchmark on the forward pass of an arbitrary function."""
|
| 12 |
-
if verbose:
|
| 13 |
-
print(desc, "- Forward pass")
|
| 14 |
-
|
| 15 |
-
def amp_wrapper(*inputs, **kwinputs):
|
| 16 |
-
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
|
| 17 |
-
fn(*inputs, **kwinputs)
|
| 18 |
-
|
| 19 |
-
t = benchmark.Timer(
|
| 20 |
-
stmt="fn_amp(*inputs, **kwinputs)",
|
| 21 |
-
globals={"fn_amp": amp_wrapper, "inputs": inputs, "kwinputs": kwinputs},
|
| 22 |
-
num_threads=torch.get_num_threads(),
|
| 23 |
-
)
|
| 24 |
-
m = t.timeit(repeats)
|
| 25 |
-
if verbose:
|
| 26 |
-
print(m)
|
| 27 |
-
return t, m
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def benchmark_backward(
|
| 31 |
-
fn,
|
| 32 |
-
*inputs,
|
| 33 |
-
grad=None,
|
| 34 |
-
repeats=10,
|
| 35 |
-
desc="",
|
| 36 |
-
verbose=True,
|
| 37 |
-
amp=False,
|
| 38 |
-
amp_dtype=torch.float16,
|
| 39 |
-
**kwinputs,
|
| 40 |
-
):
|
| 41 |
-
"""Use Pytorch Benchmark on the backward pass of an arbitrary function."""
|
| 42 |
-
if verbose:
|
| 43 |
-
print(desc, "- Backward pass")
|
| 44 |
-
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
|
| 45 |
-
y = fn(*inputs, **kwinputs)
|
| 46 |
-
if type(y) is tuple:
|
| 47 |
-
y = y[0]
|
| 48 |
-
if grad is None:
|
| 49 |
-
grad = torch.randn_like(y)
|
| 50 |
-
else:
|
| 51 |
-
if grad.shape != y.shape:
|
| 52 |
-
raise RuntimeError("Grad shape does not match output shape")
|
| 53 |
-
|
| 54 |
-
def f(*inputs, y, grad):
|
| 55 |
-
# Set .grad to None to avoid extra operation of gradient accumulation
|
| 56 |
-
for x in inputs:
|
| 57 |
-
if isinstance(x, torch.Tensor):
|
| 58 |
-
x.grad = None
|
| 59 |
-
y.backward(grad, retain_graph=True)
|
| 60 |
-
|
| 61 |
-
t = benchmark.Timer(
|
| 62 |
-
stmt="f(*inputs, y=y, grad=grad)",
|
| 63 |
-
globals={"f": f, "inputs": inputs, "y": y, "grad": grad},
|
| 64 |
-
num_threads=torch.get_num_threads(),
|
| 65 |
-
)
|
| 66 |
-
m = t.timeit(repeats)
|
| 67 |
-
if verbose:
|
| 68 |
-
print(m)
|
| 69 |
-
return t, m
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
def benchmark_combined(
|
| 73 |
-
fn,
|
| 74 |
-
*inputs,
|
| 75 |
-
grad=None,
|
| 76 |
-
repeats=10,
|
| 77 |
-
desc="",
|
| 78 |
-
verbose=True,
|
| 79 |
-
amp=False,
|
| 80 |
-
amp_dtype=torch.float16,
|
| 81 |
-
**kwinputs,
|
| 82 |
-
):
|
| 83 |
-
"""Use Pytorch Benchmark on the forward+backward pass of an arbitrary function."""
|
| 84 |
-
if verbose:
|
| 85 |
-
print(desc, "- Forward + Backward pass")
|
| 86 |
-
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
|
| 87 |
-
y = fn(*inputs, **kwinputs)
|
| 88 |
-
if type(y) is tuple:
|
| 89 |
-
y = y[0]
|
| 90 |
-
if grad is None:
|
| 91 |
-
grad = torch.randn_like(y)
|
| 92 |
-
else:
|
| 93 |
-
if grad.shape != y.shape:
|
| 94 |
-
raise RuntimeError("Grad shape does not match output shape")
|
| 95 |
-
|
| 96 |
-
def f(grad, *inputs, **kwinputs):
|
| 97 |
-
for x in inputs:
|
| 98 |
-
if isinstance(x, torch.Tensor):
|
| 99 |
-
x.grad = None
|
| 100 |
-
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
|
| 101 |
-
y = fn(*inputs, **kwinputs)
|
| 102 |
-
if type(y) is tuple:
|
| 103 |
-
y = y[0]
|
| 104 |
-
y.backward(grad, retain_graph=True)
|
| 105 |
-
|
| 106 |
-
t = benchmark.Timer(
|
| 107 |
-
stmt="f(grad, *inputs, **kwinputs)",
|
| 108 |
-
globals={"f": f, "fn": fn, "inputs": inputs, "grad": grad, "kwinputs": kwinputs},
|
| 109 |
-
num_threads=torch.get_num_threads(),
|
| 110 |
-
)
|
| 111 |
-
m = t.timeit(repeats)
|
| 112 |
-
if verbose:
|
| 113 |
-
print(m)
|
| 114 |
-
return t, m
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
def benchmark_fwd_bwd(
|
| 118 |
-
fn,
|
| 119 |
-
*inputs,
|
| 120 |
-
grad=None,
|
| 121 |
-
repeats=10,
|
| 122 |
-
desc="",
|
| 123 |
-
verbose=True,
|
| 124 |
-
amp=False,
|
| 125 |
-
amp_dtype=torch.float16,
|
| 126 |
-
**kwinputs,
|
| 127 |
-
):
|
| 128 |
-
"""Use Pytorch Benchmark on the forward+backward pass of an arbitrary function."""
|
| 129 |
-
return (
|
| 130 |
-
benchmark_forward(
|
| 131 |
-
fn,
|
| 132 |
-
*inputs,
|
| 133 |
-
repeats=repeats,
|
| 134 |
-
desc=desc,
|
| 135 |
-
verbose=verbose,
|
| 136 |
-
amp=amp,
|
| 137 |
-
amp_dtype=amp_dtype,
|
| 138 |
-
**kwinputs,
|
| 139 |
-
),
|
| 140 |
-
benchmark_backward(
|
| 141 |
-
fn,
|
| 142 |
-
*inputs,
|
| 143 |
-
grad=grad,
|
| 144 |
-
repeats=repeats,
|
| 145 |
-
desc=desc,
|
| 146 |
-
verbose=verbose,
|
| 147 |
-
amp=amp,
|
| 148 |
-
amp_dtype=amp_dtype,
|
| 149 |
-
**kwinputs,
|
| 150 |
-
),
|
| 151 |
-
)
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
def benchmark_all(
|
| 155 |
-
fn,
|
| 156 |
-
*inputs,
|
| 157 |
-
grad=None,
|
| 158 |
-
repeats=10,
|
| 159 |
-
desc="",
|
| 160 |
-
verbose=True,
|
| 161 |
-
amp=False,
|
| 162 |
-
amp_dtype=torch.float16,
|
| 163 |
-
**kwinputs,
|
| 164 |
-
):
|
| 165 |
-
"""Use Pytorch Benchmark on the forward+backward pass of an arbitrary function."""
|
| 166 |
-
return (
|
| 167 |
-
benchmark_forward(
|
| 168 |
-
fn,
|
| 169 |
-
*inputs,
|
| 170 |
-
repeats=repeats,
|
| 171 |
-
desc=desc,
|
| 172 |
-
verbose=verbose,
|
| 173 |
-
amp=amp,
|
| 174 |
-
amp_dtype=amp_dtype,
|
| 175 |
-
**kwinputs,
|
| 176 |
-
),
|
| 177 |
-
benchmark_backward(
|
| 178 |
-
fn,
|
| 179 |
-
*inputs,
|
| 180 |
-
grad=grad,
|
| 181 |
-
repeats=repeats,
|
| 182 |
-
desc=desc,
|
| 183 |
-
verbose=verbose,
|
| 184 |
-
amp=amp,
|
| 185 |
-
amp_dtype=amp_dtype,
|
| 186 |
-
**kwinputs,
|
| 187 |
-
),
|
| 188 |
-
benchmark_combined(
|
| 189 |
-
fn,
|
| 190 |
-
*inputs,
|
| 191 |
-
grad=grad,
|
| 192 |
-
repeats=repeats,
|
| 193 |
-
desc=desc,
|
| 194 |
-
verbose=verbose,
|
| 195 |
-
amp=amp,
|
| 196 |
-
amp_dtype=amp_dtype,
|
| 197 |
-
**kwinputs,
|
| 198 |
-
),
|
| 199 |
-
)
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
def pytorch_profiler(
|
| 203 |
-
fn,
|
| 204 |
-
*inputs,
|
| 205 |
-
trace_filename=None,
|
| 206 |
-
backward=False,
|
| 207 |
-
amp=False,
|
| 208 |
-
amp_dtype=torch.float16,
|
| 209 |
-
cpu=False,
|
| 210 |
-
verbose=True,
|
| 211 |
-
**kwinputs,
|
| 212 |
-
):
|
| 213 |
-
"""Wrap benchmark functions in Pytorch profiler to see CUDA information."""
|
| 214 |
-
if backward:
|
| 215 |
-
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
|
| 216 |
-
out = fn(*inputs, **kwinputs)
|
| 217 |
-
if type(out) is tuple:
|
| 218 |
-
out = out[0]
|
| 219 |
-
g = torch.randn_like(out)
|
| 220 |
-
for _ in range(30): # Warm up
|
| 221 |
-
if backward:
|
| 222 |
-
for x in inputs:
|
| 223 |
-
if isinstance(x, torch.Tensor):
|
| 224 |
-
x.grad = None
|
| 225 |
-
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
|
| 226 |
-
out = fn(*inputs, **kwinputs)
|
| 227 |
-
if type(out) is tuple:
|
| 228 |
-
out = out[0]
|
| 229 |
-
# Backward should be done outside autocast
|
| 230 |
-
if backward:
|
| 231 |
-
out.backward(g, retain_graph=True)
|
| 232 |
-
activities = ([torch.profiler.ProfilerActivity.CPU] if cpu else []) + [
|
| 233 |
-
torch.profiler.ProfilerActivity.CUDA
|
| 234 |
-
]
|
| 235 |
-
with torch.profiler.profile(
|
| 236 |
-
activities=activities,
|
| 237 |
-
record_shapes=True,
|
| 238 |
-
# profile_memory=True,
|
| 239 |
-
with_stack=True,
|
| 240 |
-
) as prof:
|
| 241 |
-
if backward:
|
| 242 |
-
for x in inputs:
|
| 243 |
-
if isinstance(x, torch.Tensor):
|
| 244 |
-
x.grad = None
|
| 245 |
-
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
|
| 246 |
-
out = fn(*inputs, **kwinputs)
|
| 247 |
-
if type(out) is tuple:
|
| 248 |
-
out = out[0]
|
| 249 |
-
if backward:
|
| 250 |
-
out.backward(g, retain_graph=True)
|
| 251 |
-
if verbose:
|
| 252 |
-
# print(prof.key_averages().table(sort_by="self_cuda_time_total", row_limit=50))
|
| 253 |
-
print(prof.key_averages().table(row_limit=50))
|
| 254 |
-
if trace_filename is not None:
|
| 255 |
-
prof.export_chrome_trace(trace_filename)
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
def benchmark_memory(fn, *inputs, desc="", verbose=True, **kwinputs):
|
| 259 |
-
torch.cuda.empty_cache()
|
| 260 |
-
torch.cuda.reset_peak_memory_stats()
|
| 261 |
-
torch.cuda.synchronize()
|
| 262 |
-
fn(*inputs, **kwinputs)
|
| 263 |
-
torch.cuda.synchronize()
|
| 264 |
-
mem = torch.cuda.max_memory_allocated() / ((2**20) * 1000)
|
| 265 |
-
if verbose:
|
| 266 |
-
print(f"{desc} max memory: {mem}GB")
|
| 267 |
-
torch.cuda.empty_cache()
|
| 268 |
-
return mem
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-cuda/blackwell_helpers.py
CHANGED
|
@@ -10,6 +10,26 @@ from cutlass._mlir.dialects import llvm
|
|
| 10 |
from . import mma_sm100_desc as sm100_desc
|
| 11 |
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
@cute.jit
|
| 14 |
def gemm_w_idx(
|
| 15 |
tiled_mma: cute.TiledMma,
|
|
@@ -108,6 +128,7 @@ def gemm_ptx(
|
|
| 108 |
sA_layout = sA.layout if sA is not None else None
|
| 109 |
sB_layout = sB.layout
|
| 110 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
|
|
|
| 111 |
if const_expr(not is_ts):
|
| 112 |
sA_swizzle = sA.iterator.type.swizzle_type
|
| 113 |
smem_desc_base_a: int = const_expr(
|
|
@@ -177,7 +198,7 @@ def gemm_ptx(
|
|
| 177 |
f"mov.b64 smem_desc_a, {{$1, {hex(smem_desc_a_hi)}}};\n\t"
|
| 178 |
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 179 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 180 |
-
f"tcgen05.mma.cta_group::1.kind::
|
| 181 |
"}\n",
|
| 182 |
"r,r,r,r",
|
| 183 |
has_side_effects=True,
|
|
@@ -198,7 +219,7 @@ def gemm_ptx(
|
|
| 198 |
".reg .b64 smem_desc_b;\n\t"
|
| 199 |
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 200 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 201 |
-
f"tcgen05.mma.cta_group::1.kind::
|
| 202 |
"}\n",
|
| 203 |
"r,r,r,r",
|
| 204 |
has_side_effects=True,
|
|
@@ -223,6 +244,7 @@ def gemm_ptx_loop(
|
|
| 223 |
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 224 |
sB_layout = sB.layout
|
| 225 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
|
|
|
| 226 |
if const_expr(not is_ts):
|
| 227 |
sA_swizzle = sA.iterator.type.swizzle_type
|
| 228 |
smem_desc_base_a: int = const_expr(
|
|
@@ -310,14 +332,14 @@ def gemm_ptx_loop(
|
|
| 310 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 311 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 312 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 313 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 314 |
+ "".join(
|
| 315 |
(
|
| 316 |
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 317 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 318 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 319 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 320 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 321 |
)
|
| 322 |
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 323 |
)
|
|
@@ -351,14 +373,14 @@ def gemm_ptx_loop(
|
|
| 351 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 352 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 353 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 354 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 355 |
+ "".join(
|
| 356 |
(
|
| 357 |
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 358 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 359 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 360 |
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 361 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 362 |
)
|
| 363 |
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 364 |
)
|
|
@@ -394,6 +416,7 @@ def gemm_ptx_partial(
|
|
| 394 |
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 395 |
sB_layout = sB.layout
|
| 396 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
|
|
|
| 397 |
if const_expr(not is_ts):
|
| 398 |
sA_swizzle = sA.iterator.type.swizzle_type
|
| 399 |
smem_desc_base_a: int = const_expr(
|
|
@@ -477,7 +500,7 @@ def gemm_ptx_partial(
|
|
| 477 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo_start, smem_desc_a_hi}};\n\t"
|
| 478 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 479 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 480 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 481 |
+ "".join(
|
| 482 |
(
|
| 483 |
# f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
|
@@ -486,7 +509,7 @@ def gemm_ptx_partial(
|
|
| 486 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 487 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 488 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 489 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 490 |
)
|
| 491 |
for k in range(1, cute.size(tCrA.shape[2]))
|
| 492 |
)
|
|
@@ -554,7 +577,7 @@ def gemm_ptx_partial(
|
|
| 554 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 555 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 556 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 557 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 558 |
+ "".join(
|
| 559 |
(
|
| 560 |
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
|
@@ -562,7 +585,7 @@ def gemm_ptx_partial(
|
|
| 562 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 563 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 564 |
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 565 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 566 |
)
|
| 567 |
for k in range(
|
| 568 |
1,
|
|
@@ -575,7 +598,7 @@ def gemm_ptx_partial(
|
|
| 575 |
(
|
| 576 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 577 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 578 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 579 |
)
|
| 580 |
for k in range(split_arrive_idx, cute.size(tCrA.shape[2]))
|
| 581 |
)
|
|
@@ -613,6 +636,7 @@ def gemm_ptx_partial1(
|
|
| 613 |
assert sA_layout is not None, "sA_layout must be provided when a_src is not TMEM"
|
| 614 |
assert sA_swizzle is not None, "sA_swizzle must be provided when a_src is not TMEM"
|
| 615 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
|
|
|
| 616 |
if const_expr(not is_ts):
|
| 617 |
smem_desc_base_a: int = const_expr(
|
| 618 |
sm100_desc.make_smem_desc_base(
|
|
@@ -706,14 +730,14 @@ def gemm_ptx_partial1(
|
|
| 706 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 707 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 708 |
"setp.ne.b32 p, $4, 0;\n\t"
|
| 709 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 710 |
+ "".join(
|
| 711 |
(
|
| 712 |
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 713 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 714 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 715 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 716 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 717 |
)
|
| 718 |
for k in range(1, cute.size(tCrA.shape[2]))
|
| 719 |
)
|
|
@@ -751,13 +775,13 @@ def gemm_ptx_partial1(
|
|
| 751 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 752 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 753 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 754 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 755 |
+ "".join(
|
| 756 |
(
|
| 757 |
f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 758 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 759 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 760 |
-
f"@leader_thread tcgen05.mma.cta_group::1.kind::
|
| 761 |
)
|
| 762 |
for k in range(1, cute.size(tCrA.shape[2]))
|
| 763 |
)
|
|
@@ -783,6 +807,7 @@ def gemm_ptx_precomputed(
|
|
| 783 |
mbar_phase: Optional[Int32] = None,
|
| 784 |
zero_init: bool | Boolean = False,
|
| 785 |
cta_group: int = 1,
|
|
|
|
| 786 |
) -> None:
|
| 787 |
# acc_tmem_addr += acc_offset
|
| 788 |
is_ts = const_expr(smem_desc_base_a is None)
|
|
@@ -842,7 +867,7 @@ def gemm_ptx_precomputed(
|
|
| 842 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo_start, smem_desc_a_hi}};\n\t"
|
| 843 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 844 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 845 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 846 |
+ "".join(
|
| 847 |
(
|
| 848 |
# f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
|
@@ -851,7 +876,7 @@ def gemm_ptx_precomputed(
|
|
| 851 |
f"add.s32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 852 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 853 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 854 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 855 |
)
|
| 856 |
for k in range(1, num_k_tile)
|
| 857 |
)
|
|
@@ -911,7 +936,7 @@ def gemm_ptx_precomputed(
|
|
| 911 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 912 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 913 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 914 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 915 |
+ "".join(
|
| 916 |
(
|
| 917 |
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
|
@@ -919,7 +944,7 @@ def gemm_ptx_precomputed(
|
|
| 919 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 920 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 921 |
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 922 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 923 |
)
|
| 924 |
for k in range(
|
| 925 |
1,
|
|
@@ -933,7 +958,7 @@ def gemm_ptx_precomputed(
|
|
| 933 |
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 934 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 935 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 936 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 937 |
)
|
| 938 |
for k in range(num_k_tile // 4 * 3, num_k_tile)
|
| 939 |
)
|
|
@@ -1019,6 +1044,7 @@ def gemm_ptx_precomputed_varname(
|
|
| 1019 |
smem_offset: int,
|
| 1020 |
zero_init: bool | Boolean = False,
|
| 1021 |
cta_group: int = 1,
|
|
|
|
| 1022 |
) -> None:
|
| 1023 |
is_ts = False
|
| 1024 |
num_k_tile = cute.size(tCrB_layout.shape[2])
|
|
@@ -1067,7 +1093,7 @@ def gemm_ptx_precomputed_varname(
|
|
| 1067 |
)
|
| 1068 |
+ "setp.ne.b32 p, $1, 0;\n\t"
|
| 1069 |
# 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"
|
| 1070 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 1071 |
+ "".join(
|
| 1072 |
(
|
| 1073 |
# f"mov.b64 {{smem_desc_a_lo, smem_desc_a_hi}}, {smem_var_name_prefix}_{k};\n\t"
|
|
@@ -1077,7 +1103,7 @@ def gemm_ptx_precomputed_varname(
|
|
| 1077 |
# f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 1078 |
# 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"
|
| 1079 |
# 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"
|
| 1080 |
-
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::
|
| 1081 |
)
|
| 1082 |
for k in range(1, num_k_tile)
|
| 1083 |
)
|
|
|
|
| 10 |
from . import 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 |
+
# cutlass-dsl >=4.5.2 builds plain FP8 MMAs as MmaF8F6F4Op (make_trivial_tiled_mma's
|
| 21 |
+
# _F8F6F4_TYPES branch); <4.4.x returned the now-legacy MmaFP8Op. Both map to kind::f8f6f4.
|
| 22 |
+
if isinstance(op, (tcgen05.mma.MmaFP8Op, tcgen05.mma.MmaF8F6F4Op)):
|
| 23 |
+
return "f8f6f4"
|
| 24 |
+
if isinstance(op, tcgen05.mma.MmaMXF8Op):
|
| 25 |
+
return "mxf8f6f4"
|
| 26 |
+
if isinstance(op, tcgen05.mma.MmaMXF4Op):
|
| 27 |
+
return "mxf4"
|
| 28 |
+
if isinstance(op, tcgen05.mma.MmaMXF4NVF4Op):
|
| 29 |
+
return "mxf4nvf4"
|
| 30 |
+
raise TypeError(f"Unsupported tcgen05 MMA op kind: {type(op).__name__}")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
@cute.jit
|
| 34 |
def gemm_w_idx(
|
| 35 |
tiled_mma: cute.TiledMma,
|
|
|
|
| 128 |
sA_layout = sA.layout if sA is not None else None
|
| 129 |
sB_layout = sB.layout
|
| 130 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 131 |
+
kind = _tcgen05_mma_kind(op)
|
| 132 |
if const_expr(not is_ts):
|
| 133 |
sA_swizzle = sA.iterator.type.swizzle_type
|
| 134 |
smem_desc_base_a: int = const_expr(
|
|
|
|
| 198 |
f"mov.b64 smem_desc_a, {{$1, {hex(smem_desc_a_hi)}}};\n\t"
|
| 199 |
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 200 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 201 |
+
f"tcgen05.mma.cta_group::1.kind::{kind} [$0], smem_desc_a, smem_desc_b, idesc, p;\n\t"
|
| 202 |
"}\n",
|
| 203 |
"r,r,r,r",
|
| 204 |
has_side_effects=True,
|
|
|
|
| 219 |
".reg .b64 smem_desc_b;\n\t"
|
| 220 |
f"mov.b64 smem_desc_b, {{$2, {hex(smem_desc_b_hi)}}};\n\t"
|
| 221 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 222 |
+
f"tcgen05.mma.cta_group::1.kind::{kind} [$0], [$1], smem_desc_b, {hex(idesc)}, p;\n\t"
|
| 223 |
"}\n",
|
| 224 |
"r,r,r,r",
|
| 225 |
has_side_effects=True,
|
|
|
|
| 244 |
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 245 |
sB_layout = sB.layout
|
| 246 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 247 |
+
kind = _tcgen05_mma_kind(op)
|
| 248 |
if const_expr(not is_ts):
|
| 249 |
sA_swizzle = sA.iterator.type.swizzle_type
|
| 250 |
smem_desc_base_a: int = const_expr(
|
|
|
|
| 332 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 333 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 334 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 335 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 336 |
+ "".join(
|
| 337 |
(
|
| 338 |
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 339 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 340 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 341 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 342 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 343 |
)
|
| 344 |
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 345 |
)
|
|
|
|
| 373 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 374 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 375 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 376 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 377 |
+ "".join(
|
| 378 |
(
|
| 379 |
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 380 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 381 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 382 |
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [$0], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 383 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], [tmem_a + {hex(offset_a[k])}], smem_desc_b, idesc, 1;\n\t"
|
| 384 |
)
|
| 385 |
for k in cutlass.range_constexpr(1, cute.size(tCrA.shape[2]))
|
| 386 |
)
|
|
|
|
| 416 |
sA_layout = sA.layout if sA is not None else tCrA.layout
|
| 417 |
sB_layout = sB.layout
|
| 418 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 419 |
+
kind = _tcgen05_mma_kind(op)
|
| 420 |
if const_expr(not is_ts):
|
| 421 |
sA_swizzle = sA.iterator.type.swizzle_type
|
| 422 |
smem_desc_base_a: int = const_expr(
|
|
|
|
| 500 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo_start, smem_desc_a_hi}};\n\t"
|
| 501 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 502 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 503 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 504 |
+ "".join(
|
| 505 |
(
|
| 506 |
# f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
|
|
|
| 509 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 510 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 511 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 512 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 513 |
)
|
| 514 |
for k in range(1, cute.size(tCrA.shape[2]))
|
| 515 |
)
|
|
|
|
| 577 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 578 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 579 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 580 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 581 |
+ "".join(
|
| 582 |
(
|
| 583 |
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
|
|
|
| 585 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 586 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 587 |
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 588 |
+
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"
|
| 589 |
)
|
| 590 |
for k in range(
|
| 591 |
1,
|
|
|
|
| 598 |
(
|
| 599 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 600 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 601 |
+
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"
|
| 602 |
)
|
| 603 |
for k in range(split_arrive_idx, cute.size(tCrA.shape[2]))
|
| 604 |
)
|
|
|
|
| 636 |
assert sA_layout is not None, "sA_layout must be provided when a_src is not TMEM"
|
| 637 |
assert sA_swizzle is not None, "sA_swizzle must be provided when a_src is not TMEM"
|
| 638 |
idesc: int = const_expr(sm100_desc.mma_op_to_idesc(op))
|
| 639 |
+
kind = _tcgen05_mma_kind(op)
|
| 640 |
if const_expr(not is_ts):
|
| 641 |
smem_desc_base_a: int = const_expr(
|
| 642 |
sm100_desc.make_smem_desc_base(
|
|
|
|
| 730 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 731 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 732 |
"setp.ne.b32 p, $4, 0;\n\t"
|
| 733 |
+
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"
|
| 734 |
+ "".join(
|
| 735 |
(
|
| 736 |
f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
| 737 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 738 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 739 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 740 |
+
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"
|
| 741 |
)
|
| 742 |
for k in range(1, cute.size(tCrA.shape[2]))
|
| 743 |
)
|
|
|
|
| 775 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 776 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 777 |
"setp.ne.b32 p, $3, 0;\n\t"
|
| 778 |
+
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"
|
| 779 |
+ "".join(
|
| 780 |
(
|
| 781 |
f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
| 782 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 783 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 784 |
+
f"@leader_thread tcgen05.mma.cta_group::1.kind::{kind} [$0], [tmem_a], smem_desc_b, idesc, {{$4, $5, $6, $7}}, 1;\n\t"
|
| 785 |
)
|
| 786 |
for k in range(1, cute.size(tCrA.shape[2]))
|
| 787 |
)
|
|
|
|
| 807 |
mbar_phase: Optional[Int32] = None,
|
| 808 |
zero_init: bool | Boolean = False,
|
| 809 |
cta_group: int = 1,
|
| 810 |
+
kind: str = "f16",
|
| 811 |
) -> None:
|
| 812 |
# acc_tmem_addr += acc_offset
|
| 813 |
is_ts = const_expr(smem_desc_base_a is None)
|
|
|
|
| 867 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo_start, smem_desc_a_hi}};\n\t"
|
| 868 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 869 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 870 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, {pred_str};\n\t"
|
| 871 |
+ "".join(
|
| 872 |
(
|
| 873 |
# f"add.u32 smem_desc_a_lo, smem_desc_a_lo, {hex(offset_a_diff[k - 1])};\n\t"
|
|
|
|
| 876 |
f"add.s32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 877 |
f"mov.b64 smem_desc_a, {{smem_desc_a_lo, smem_desc_a_hi}};\n\t"
|
| 878 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 879 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], smem_desc_a, smem_desc_b, idesc, 1;\n\t"
|
| 880 |
)
|
| 881 |
for k in range(1, num_k_tile)
|
| 882 |
)
|
|
|
|
| 936 |
f"mov.b32 smem_desc_b_hi, {hex(smem_desc_b_hi)};\n\t"
|
| 937 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo_start, smem_desc_b_hi}};\n\t"
|
| 938 |
"setp.ne.b32 p, $2, 0;\n\t"
|
| 939 |
+
f"@leader_thread tcgen05.mma.cta_group::{cta_group}.kind::{kind} [tmem_acc], [tmem_a], smem_desc_b, idesc, {pred_str};\n\t"
|
| 940 |
+ "".join(
|
| 941 |
(
|
| 942 |
# f"add.u32 tmem_a, tmem_a, {hex(offset_a_diff[k - 1])};\n\t"
|
|
|
|
| 944 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 945 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 946 |
# f"@leader_thread tcgen05.mma.cta_group::1.kind::f16 [tmem_acc], [tmem_a], smem_desc_b, idesc, 1;\n\t"
|
| 947 |
+
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"
|
| 948 |
)
|
| 949 |
for k in range(
|
| 950 |
1,
|
|
|
|
| 958 |
# f"add.u32 smem_desc_b_lo, smem_desc_b_lo, {hex(offset_b_diff[k - 1])};\n\t"
|
| 959 |
f"add.u32 smem_desc_b_lo, smem_desc_b_lo_start, {hex(offset_b[k])};\n\t"
|
| 960 |
f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 961 |
+
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"
|
| 962 |
)
|
| 963 |
for k in range(num_k_tile // 4 * 3, num_k_tile)
|
| 964 |
)
|
|
|
|
| 1044 |
smem_offset: int,
|
| 1045 |
zero_init: bool | Boolean = False,
|
| 1046 |
cta_group: int = 1,
|
| 1047 |
+
kind: str = "f16",
|
| 1048 |
) -> None:
|
| 1049 |
is_ts = False
|
| 1050 |
num_k_tile = cute.size(tCrB_layout.shape[2])
|
|
|
|
| 1093 |
)
|
| 1094 |
+ "setp.ne.b32 p, $1, 0;\n\t"
|
| 1095 |
# 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"
|
| 1096 |
+
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"
|
| 1097 |
+ "".join(
|
| 1098 |
(
|
| 1099 |
# f"mov.b64 {{smem_desc_a_lo, smem_desc_a_hi}}, {smem_var_name_prefix}_{k};\n\t"
|
|
|
|
| 1103 |
# f"mov.b64 smem_desc_b, {{smem_desc_b_lo, smem_desc_b_hi}};\n\t"
|
| 1104 |
# 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"
|
| 1105 |
# 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"
|
| 1106 |
+
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"
|
| 1107 |
)
|
| 1108 |
for k in range(1, num_k_tile)
|
| 1109 |
)
|
build/torch-cuda/block_info.py
CHANGED
|
@@ -6,7 +6,7 @@ import cutlass
|
|
| 6 |
import cutlass.cute as cute
|
| 7 |
from cutlass import Int32, const_expr
|
| 8 |
|
| 9 |
-
from .seqlen_info import SeqlenInfoQK
|
| 10 |
|
| 11 |
|
| 12 |
@dataclass(frozen=True)
|
|
@@ -19,14 +19,18 @@ class BlockInfo:
|
|
| 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:
|
| 29 |
-
num_splits:
|
| 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)):
|
|
@@ -45,11 +49,20 @@ class BlockInfo:
|
|
| 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 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 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
|
|
@@ -70,6 +83,37 @@ class BlockInfo:
|
|
| 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_min_causal_local_mask(
|
| 75 |
self,
|
|
@@ -106,3 +150,20 @@ class BlockInfo:
|
|
| 106 |
n_idx = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 107 |
n_idx_left = n_idx - self.window_size_left
|
| 108 |
return cutlass.max(n_block_min, cute.ceil_div(n_idx_left, self.tile_n))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
import cutlass.cute as cute
|
| 7 |
from cutlass import Int32, const_expr
|
| 8 |
|
| 9 |
+
from .seqlen_info import SeqlenInfoQK, SeqlenInfoQKNewK
|
| 10 |
|
| 11 |
|
| 12 |
@dataclass(frozen=True)
|
|
|
|
| 19 |
window_size_left: Optional[Int32] = None
|
| 20 |
window_size_right: Optional[Int32] = None
|
| 21 |
qhead_per_kvhead_packgqa: cutlass.Constexpr[int] = 1
|
| 22 |
+
num_splits: Int32 = 1
|
| 23 |
+
# If True, the scheduler packs num_splits into the top 16 bits of split_idx
|
| 24 |
+
pack_split_idx: cutlass.Constexpr[bool] = False
|
| 25 |
+
num_n_blocks_per_split: Optional[cutlass.Constexpr[Int32]] = None
|
| 26 |
|
| 27 |
@cute.jit
|
| 28 |
def get_n_block_min_max(
|
| 29 |
self,
|
| 30 |
seqlen_info: SeqlenInfoQK,
|
| 31 |
m_block: Int32,
|
| 32 |
+
split_idx: Int32 = 0,
|
| 33 |
+
num_splits: Int32 = 1,
|
| 34 |
) -> Tuple[Int32, Int32]:
|
| 35 |
n_block_max = cute.ceil_div(seqlen_info.seqlen_k, self.tile_n)
|
| 36 |
if const_expr(self.is_causal or (self.is_local and self.window_size_right is not None)):
|
|
|
|
| 49 |
n_idx_left = n_idx - self.window_size_left
|
| 50 |
n_block_min = cutlass.max(n_idx_left // self.tile_n, 0)
|
| 51 |
if cutlass.const_expr(self.is_split_kv):
|
| 52 |
+
if const_expr(self.pack_split_idx):
|
| 53 |
+
# Unpack num_splits from top 16 bits of split_idx (packed by scheduler)
|
| 54 |
+
num_splits = split_idx >> 16
|
| 55 |
+
split_idx = split_idx & 0xFFFF
|
| 56 |
+
else:
|
| 57 |
+
num_splits = self.num_splits
|
| 58 |
+
if const_expr(self.num_n_blocks_per_split is not None):
|
| 59 |
+
num_n_blocks_per_split = self.num_n_blocks_per_split
|
| 60 |
+
else:
|
| 61 |
+
num_n_blocks_per_split = (
|
| 62 |
+
Int32(0)
|
| 63 |
+
if n_block_max <= n_block_min
|
| 64 |
+
else (n_block_max - n_block_min + num_splits - 1) // num_splits
|
| 65 |
+
)
|
| 66 |
n_block_min = n_block_min + split_idx * num_n_blocks_per_split
|
| 67 |
n_block_max = cutlass.min(n_block_min + num_n_blocks_per_split, n_block_max)
|
| 68 |
return n_block_min, n_block_max
|
|
|
|
| 83 |
m_block_max = min(m_block_max, cute.ceil_div(m_idx_left, self.tile_m))
|
| 84 |
return m_block_min, m_block_max
|
| 85 |
|
| 86 |
+
@cute.jit
|
| 87 |
+
def get_n_block_k_new_min_max(
|
| 88 |
+
self,
|
| 89 |
+
seqlen_info: SeqlenInfoQKNewK,
|
| 90 |
+
m_block: Int32,
|
| 91 |
+
split_idx: Int32 = 0,
|
| 92 |
+
num_splits: Int32 = 1,
|
| 93 |
+
) -> Tuple[Int32, Int32]:
|
| 94 |
+
"""Get the block range for new K tokens (append KV).
|
| 95 |
+
|
| 96 |
+
First computes the full n_block range via get_n_block_min_max, then maps
|
| 97 |
+
those blocks into the new-K index space by subtracting seqlen_k_og.
|
| 98 |
+
"""
|
| 99 |
+
n_block_min, n_block_max = self.get_n_block_min_max(
|
| 100 |
+
seqlen_info,
|
| 101 |
+
m_block,
|
| 102 |
+
split_idx,
|
| 103 |
+
num_splits,
|
| 104 |
+
)
|
| 105 |
+
idx_k_new_min = cutlass.max(n_block_min * self.tile_n - seqlen_info.seqlen_k_og, 0)
|
| 106 |
+
idx_k_new_max = cutlass.min(
|
| 107 |
+
n_block_max * self.tile_n - seqlen_info.seqlen_k_og, seqlen_info.seqlen_k_new
|
| 108 |
+
)
|
| 109 |
+
n_block_new_min = idx_k_new_min // self.tile_n
|
| 110 |
+
n_block_new_max = (
|
| 111 |
+
cute.ceil_div(idx_k_new_max, self.tile_n)
|
| 112 |
+
if idx_k_new_max > idx_k_new_min
|
| 113 |
+
else n_block_new_min
|
| 114 |
+
)
|
| 115 |
+
return n_block_new_min, n_block_new_max
|
| 116 |
+
|
| 117 |
@cute.jit
|
| 118 |
def get_n_block_min_causal_local_mask(
|
| 119 |
self,
|
|
|
|
| 150 |
n_idx = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 151 |
n_idx_left = n_idx - self.window_size_left
|
| 152 |
return cutlass.max(n_block_min, cute.ceil_div(n_idx_left, self.tile_n))
|
| 153 |
+
|
| 154 |
+
@cute.jit
|
| 155 |
+
def get_n_block_max_for_m_block(
|
| 156 |
+
self,
|
| 157 |
+
seqlen_info: SeqlenInfoQK,
|
| 158 |
+
m_block: Int32,
|
| 159 |
+
) -> Int32:
|
| 160 |
+
n_block_max = cute.ceil_div(seqlen_info.seqlen_k, self.tile_n)
|
| 161 |
+
if const_expr(self.is_causal or self.window_size_right is not None):
|
| 162 |
+
m_idx_max = (m_block + 1) * self.tile_m
|
| 163 |
+
if const_expr(self.qhead_per_kvhead_packgqa > 1):
|
| 164 |
+
m_idx_max = cute.ceil_div(m_idx_max, self.qhead_per_kvhead_packgqa)
|
| 165 |
+
n_idx_right = m_idx_max + seqlen_info.seqlen_k - seqlen_info.seqlen_q
|
| 166 |
+
if const_expr(self.window_size_right is not None):
|
| 167 |
+
n_idx_right += self.window_size_right
|
| 168 |
+
n_block_max = min(n_block_max, cute.ceil_div(n_idx_right, self.tile_n))
|
| 169 |
+
return n_block_max
|
build/torch-cuda/block_sparse_utils.py
CHANGED
|
@@ -5,7 +5,7 @@ This module contains runtime execution functions for block-sparse attention kern
|
|
| 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
|
|
@@ -17,6 +17,80 @@ from .quack import copy_utils
|
|
| 17 |
# Import data structures from block_sparsity
|
| 18 |
from .block_sparsity import BlockSparseTensors
|
| 19 |
from .named_barrier import NamedBarrierBwd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
|
| 22 |
# NOTE [SM100 block-sparse empty tiles: mbarrier contract]
|
|
@@ -61,35 +135,51 @@ from .named_barrier import NamedBarrierBwd
|
|
| 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`, `
|
| 65 |
-
# -
|
| 66 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
load_q_with_first: cutlass.Constexpr,
|
| 76 |
first_block_preloaded: cutlass.Constexpr,
|
| 77 |
kv_producer_state,
|
| 78 |
-
load_Q,
|
| 79 |
load_K,
|
| 80 |
load_V,
|
| 81 |
pipeline_k,
|
| 82 |
pipeline_v,
|
| 83 |
-
use_tma_q: cutlass.Constexpr,
|
| 84 |
-
tma_q_bytes: cutlass.Constexpr,
|
| 85 |
intra_wg_overlap: cutlass.Constexpr,
|
|
|
|
| 86 |
):
|
| 87 |
-
"""Iterate over the sparse blocks and load K, V
|
| 88 |
-
|
| 89 |
means we need to pipeline the last V load from the partial block case,
|
| 90 |
with the loads for the full blocks. Set first_block_preloaded when the
|
| 91 |
caller has already issued the first K load for the list.
|
| 92 |
|
|
|
|
|
|
|
| 93 |
Note:
|
| 94 |
we iterate along the block_n indices in reverse.
|
| 95 |
|
|
@@ -98,44 +188,40 @@ def load_block_list(
|
|
| 98 |
|
| 99 |
"""
|
| 100 |
if block_count > 0:
|
|
|
|
| 101 |
if const_expr(not intra_wg_overlap):
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
if const_expr(load_q_with_first and use_tma_q):
|
| 109 |
-
load_Q(tma_bar_ptr=pipeline_k.producer_get_barrier(kv_producer_state))
|
| 110 |
-
|
| 111 |
-
load_K(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 112 |
-
pipeline_v.producer_acquire(kv_producer_state)
|
| 113 |
-
load_V(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 114 |
-
kv_producer_state.advance()
|
| 115 |
-
|
| 116 |
-
for offset in cutlass.range(1, block_count):
|
| 117 |
-
n_block = block_indices[block_count - 1 - offset]
|
| 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)
|
| 121 |
load_V(src_idx=n_block, producer_state=kv_producer_state)
|
| 122 |
kv_producer_state.advance()
|
| 123 |
else:
|
| 124 |
-
n_block_first =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
if const_expr(not first_block_preloaded):
|
| 126 |
-
|
| 127 |
-
tma_q_bytes if const_expr(load_q_with_first) and const_expr(use_tma_q) else 0
|
| 128 |
-
)
|
| 129 |
-
pipeline_k.producer_acquire(kv_producer_state, extra_tx_count=extra_tx)
|
| 130 |
-
|
| 131 |
-
if const_expr(load_q_with_first and use_tma_q):
|
| 132 |
-
load_Q(tma_bar_ptr=pipeline_k.producer_get_barrier(kv_producer_state))
|
| 133 |
-
|
| 134 |
load_K(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 135 |
|
| 136 |
-
for idx in cutlass.range(
|
| 137 |
-
n_block_prev =
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
kv_producer_state_prev = kv_producer_state.clone()
|
| 140 |
kv_producer_state.advance()
|
| 141 |
pipeline_k.producer_acquire(kv_producer_state)
|
|
@@ -153,10 +239,11 @@ def finish_overlap_v_load(
|
|
| 153 |
load_V,
|
| 154 |
pipeline_v,
|
| 155 |
kv_producer_state,
|
|
|
|
| 156 |
):
|
| 157 |
"""Load the final V block after overlapped K/V loads."""
|
| 158 |
if block_count > 0:
|
| 159 |
-
n_block_last = block_indices[0]
|
| 160 |
pipeline_v.producer_acquire(kv_producer_state)
|
| 161 |
load_V(src_idx=n_block_last, producer_state=kv_producer_state)
|
| 162 |
kv_producer_state.advance()
|
|
@@ -185,20 +272,21 @@ def produce_block_sparse_loads(
|
|
| 185 |
batch_idx,
|
| 186 |
head_idx,
|
| 187 |
m_block,
|
|
|
|
| 188 |
kv_producer_state,
|
| 189 |
-
load_Q,
|
| 190 |
load_K,
|
| 191 |
load_V,
|
| 192 |
pipeline_k,
|
| 193 |
pipeline_v,
|
| 194 |
-
use_tma_q: cutlass.Constexpr,
|
| 195 |
-
tma_q_bytes: cutlass.Constexpr,
|
| 196 |
intra_wg_overlap: cutlass.Constexpr,
|
| 197 |
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 198 |
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
|
|
|
| 199 |
):
|
| 200 |
"""Iterate over the mask and full block lists for a single tile.
|
| 201 |
|
|
|
|
|
|
|
| 202 |
The masked (partial) list may leave the last V load pending when intra-warp-group
|
| 203 |
overlap is enabled. The first full block must consume that pending V while
|
| 204 |
issuing its own K load on the next pipeline stage.
|
|
@@ -211,40 +299,42 @@ def produce_block_sparse_loads(
|
|
| 211 |
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 212 |
must be converted to unpacked for sparse tensor indexing.
|
| 213 |
"""
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
| 217 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 218 |
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
mask_empty = curr_mask_block_cnt == 0
|
| 230 |
full_empty = curr_full_block_cnt == 0
|
| 231 |
|
| 232 |
if mask_empty:
|
| 233 |
-
# No masked blocks: the full list owns the initial
|
| 234 |
kv_producer_state = load_block_list(
|
| 235 |
curr_full_block_idx,
|
| 236 |
curr_full_block_cnt,
|
| 237 |
-
load_q_with_first=True,
|
| 238 |
first_block_preloaded=False,
|
| 239 |
kv_producer_state=kv_producer_state,
|
| 240 |
-
load_Q=load_Q,
|
| 241 |
load_K=load_K,
|
| 242 |
load_V=load_V,
|
| 243 |
pipeline_k=pipeline_k,
|
| 244 |
pipeline_v=pipeline_v,
|
| 245 |
-
use_tma_q=use_tma_q,
|
| 246 |
-
tma_q_bytes=tma_q_bytes,
|
| 247 |
intra_wg_overlap=intra_wg_overlap,
|
|
|
|
| 248 |
)
|
| 249 |
|
| 250 |
if const_expr(intra_wg_overlap) and curr_full_block_cnt > 0:
|
|
@@ -254,24 +344,21 @@ def produce_block_sparse_loads(
|
|
| 254 |
load_V,
|
| 255 |
pipeline_v,
|
| 256 |
kv_producer_state,
|
|
|
|
| 257 |
)
|
| 258 |
else:
|
| 259 |
-
# Masked blocks present
|
| 260 |
-
# start immediately. When overlap is disabled this fully drains the list.
|
| 261 |
kv_producer_state = load_block_list(
|
| 262 |
curr_mask_block_idx,
|
| 263 |
curr_mask_block_cnt,
|
| 264 |
-
load_q_with_first=True,
|
| 265 |
first_block_preloaded=False,
|
| 266 |
kv_producer_state=kv_producer_state,
|
| 267 |
-
load_Q=load_Q,
|
| 268 |
load_K=load_K,
|
| 269 |
load_V=load_V,
|
| 270 |
pipeline_k=pipeline_k,
|
| 271 |
pipeline_v=pipeline_v,
|
| 272 |
-
use_tma_q=use_tma_q,
|
| 273 |
-
tma_q_bytes=tma_q_bytes,
|
| 274 |
intra_wg_overlap=intra_wg_overlap,
|
|
|
|
| 275 |
)
|
| 276 |
|
| 277 |
if full_empty:
|
|
@@ -282,13 +369,18 @@ def produce_block_sparse_loads(
|
|
| 282 |
load_V,
|
| 283 |
pipeline_v,
|
| 284 |
kv_producer_state,
|
|
|
|
| 285 |
)
|
| 286 |
else:
|
| 287 |
if const_expr(intra_wg_overlap):
|
| 288 |
# Bridge the masked list to the full list by overlapping the pending masked V
|
| 289 |
# with the first full K load.
|
| 290 |
-
n_block_mask_last = curr_mask_block_idx[0]
|
| 291 |
-
n_block_full_first =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
kv_producer_state_prev = kv_producer_state.clone()
|
| 293 |
kv_producer_state.advance()
|
| 294 |
pipeline_k.producer_acquire(kv_producer_state)
|
|
@@ -299,17 +391,14 @@ def produce_block_sparse_loads(
|
|
| 299 |
kv_producer_state = load_block_list(
|
| 300 |
curr_full_block_idx,
|
| 301 |
curr_full_block_cnt,
|
| 302 |
-
load_q_with_first=False,
|
| 303 |
first_block_preloaded=True,
|
| 304 |
kv_producer_state=kv_producer_state,
|
| 305 |
-
load_Q=load_Q,
|
| 306 |
load_K=load_K,
|
| 307 |
load_V=load_V,
|
| 308 |
pipeline_k=pipeline_k,
|
| 309 |
pipeline_v=pipeline_v,
|
| 310 |
-
use_tma_q=use_tma_q,
|
| 311 |
-
tma_q_bytes=tma_q_bytes,
|
| 312 |
intra_wg_overlap=intra_wg_overlap,
|
|
|
|
| 313 |
)
|
| 314 |
|
| 315 |
kv_producer_state = finish_overlap_v_load(
|
|
@@ -318,24 +407,21 @@ def produce_block_sparse_loads(
|
|
| 318 |
load_V,
|
| 319 |
pipeline_v,
|
| 320 |
kv_producer_state,
|
|
|
|
| 321 |
)
|
| 322 |
else:
|
| 323 |
-
# Non-overlap path with both lists: run the full list normally
|
| 324 |
-
# reload because the masked list already issued it).
|
| 325 |
kv_producer_state = load_block_list(
|
| 326 |
curr_full_block_idx,
|
| 327 |
curr_full_block_cnt,
|
| 328 |
-
load_q_with_first=False,
|
| 329 |
first_block_preloaded=False,
|
| 330 |
kv_producer_state=kv_producer_state,
|
| 331 |
-
load_Q=load_Q,
|
| 332 |
load_K=load_K,
|
| 333 |
load_V=load_V,
|
| 334 |
pipeline_k=pipeline_k,
|
| 335 |
pipeline_v=pipeline_v,
|
| 336 |
-
use_tma_q=use_tma_q,
|
| 337 |
-
tma_q_bytes=tma_q_bytes,
|
| 338 |
intra_wg_overlap=intra_wg_overlap,
|
|
|
|
| 339 |
)
|
| 340 |
|
| 341 |
return kv_producer_state
|
|
@@ -347,7 +433,7 @@ def consume_block_sparse_loads(
|
|
| 347 |
batch_idx,
|
| 348 |
head_idx,
|
| 349 |
m_block,
|
| 350 |
-
|
| 351 |
kv_consumer_state,
|
| 352 |
mma_pv_fn,
|
| 353 |
mma_one_n_block,
|
|
@@ -373,15 +459,20 @@ def consume_block_sparse_loads(
|
|
| 373 |
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 374 |
must be converted to unpacked for sparse tensor indexing.
|
| 375 |
"""
|
| 376 |
-
|
| 377 |
-
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx = blocksparse_tensors
|
| 378 |
-
|
| 379 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 380 |
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
|
| 386 |
processed_any = curr_mask_block_cnt + curr_full_block_cnt > 0
|
| 387 |
|
|
@@ -462,7 +553,7 @@ def consume_block_sparse_loads(
|
|
| 462 |
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 463 |
kv_consumer_state = process_first_half_block(
|
| 464 |
n_block=mask_n_block,
|
| 465 |
-
seqlen=
|
| 466 |
kv_consumer_state=kv_consumer_state,
|
| 467 |
mask_fn=partial(
|
| 468 |
mask_fn,
|
|
@@ -478,7 +569,7 @@ def consume_block_sparse_loads(
|
|
| 478 |
kv_consumer_state = mma_one_n_block(
|
| 479 |
kv_consumer_state,
|
| 480 |
n_block=mask_n_block,
|
| 481 |
-
seqlen=
|
| 482 |
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 483 |
mask_fn=partial(mask_fn, mask_mod=mask_mod, mask_seqlen=False),
|
| 484 |
)
|
|
@@ -489,7 +580,7 @@ def consume_block_sparse_loads(
|
|
| 489 |
if curr_mask_block_cnt == 0:
|
| 490 |
kv_consumer_state = process_first_half_block(
|
| 491 |
n_block=full_n_block,
|
| 492 |
-
seqlen=
|
| 493 |
kv_consumer_state=kv_consumer_state,
|
| 494 |
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 495 |
score_mod_fn=score_mod_fn,
|
|
@@ -499,7 +590,7 @@ def consume_block_sparse_loads(
|
|
| 499 |
kv_consumer_state = mma_one_n_block(
|
| 500 |
kv_consumer_state,
|
| 501 |
n_block=full_n_block,
|
| 502 |
-
seqlen=
|
| 503 |
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 504 |
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 505 |
)
|
|
@@ -509,7 +600,7 @@ def consume_block_sparse_loads(
|
|
| 509 |
kv_consumer_state = mma_one_n_block(
|
| 510 |
kv_consumer_state,
|
| 511 |
n_block=full_n_block,
|
| 512 |
-
seqlen=
|
| 513 |
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 514 |
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=False),
|
| 515 |
)
|
|
@@ -525,10 +616,20 @@ def consume_block_sparse_loads(
|
|
| 525 |
return kv_consumer_state, O_should_accumulate, processed_any
|
| 526 |
|
| 527 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
@cute.jit
|
| 529 |
def load_block_list_sm100(
|
| 530 |
block_indices: cute.Tensor,
|
| 531 |
-
|
|
|
|
| 532 |
load_q_with_first: cutlass.Constexpr,
|
| 533 |
q_stage: cutlass.Constexpr,
|
| 534 |
kv_producer_state,
|
|
@@ -536,11 +637,16 @@ def load_block_list_sm100(
|
|
| 536 |
load_K,
|
| 537 |
load_V,
|
| 538 |
pipeline_kv,
|
|
|
|
| 539 |
):
|
| 540 |
-
"""SM100
|
| 541 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 542 |
# First iteration: load Q alongside K if requested
|
| 543 |
-
n_block_first =
|
| 544 |
|
| 545 |
if const_expr(load_q_with_first):
|
| 546 |
# SM100 loads Q0 and optionally Q1
|
|
@@ -556,8 +662,8 @@ def load_block_list_sm100(
|
|
| 556 |
kv_producer_state.advance()
|
| 557 |
|
| 558 |
# Remaining blocks
|
| 559 |
-
for offset in cutlass.range(1,
|
| 560 |
-
n_block =
|
| 561 |
load_K(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
| 562 |
kv_producer_state.advance()
|
| 563 |
load_V(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
|
@@ -573,6 +679,9 @@ def produce_block_sparse_loads_sm100(
|
|
| 573 |
batch_idx,
|
| 574 |
head_idx,
|
| 575 |
m_block,
|
|
|
|
|
|
|
|
|
|
| 576 |
kv_producer_state,
|
| 577 |
load_Q,
|
| 578 |
load_K,
|
|
@@ -582,6 +691,7 @@ def produce_block_sparse_loads_sm100(
|
|
| 582 |
q_producer_phase: Int32,
|
| 583 |
qhead_per_kvhead: cutlass.Constexpr,
|
| 584 |
q_subtile_factor: cutlass.Constexpr,
|
|
|
|
| 585 |
):
|
| 586 |
"""SM100 entry point for sparse block iteration.
|
| 587 |
|
|
@@ -594,20 +704,32 @@ def produce_block_sparse_loads_sm100(
|
|
| 594 |
"""
|
| 595 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 596 |
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
|
|
|
|
|
|
| 608 |
|
| 609 |
-
|
| 610 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
|
| 612 |
q_phase_flipped = False
|
| 613 |
|
|
@@ -615,7 +737,8 @@ def produce_block_sparse_loads_sm100(
|
|
| 615 |
# No masked blocks: process full list with Q loading
|
| 616 |
kv_producer_state = load_block_list_sm100(
|
| 617 |
curr_full_block_idx,
|
| 618 |
-
|
|
|
|
| 619 |
load_q_with_first=True,
|
| 620 |
q_stage=q_stage,
|
| 621 |
kv_producer_state=kv_producer_state,
|
|
@@ -623,13 +746,15 @@ def produce_block_sparse_loads_sm100(
|
|
| 623 |
load_K=load_K,
|
| 624 |
load_V=load_V,
|
| 625 |
pipeline_kv=pipeline_kv,
|
|
|
|
| 626 |
)
|
| 627 |
q_phase_flipped = not full_empty
|
| 628 |
else:
|
| 629 |
# Process masked blocks with Q loading
|
| 630 |
kv_producer_state = load_block_list_sm100(
|
| 631 |
curr_mask_block_idx,
|
| 632 |
-
|
|
|
|
| 633 |
load_q_with_first=True,
|
| 634 |
q_stage=q_stage,
|
| 635 |
kv_producer_state=kv_producer_state,
|
|
@@ -637,6 +762,7 @@ def produce_block_sparse_loads_sm100(
|
|
| 637 |
load_K=load_K,
|
| 638 |
load_V=load_V,
|
| 639 |
pipeline_kv=pipeline_kv,
|
|
|
|
| 640 |
)
|
| 641 |
q_phase_flipped = True
|
| 642 |
|
|
@@ -644,7 +770,8 @@ def produce_block_sparse_loads_sm100(
|
|
| 644 |
# Process full blocks without Q loading
|
| 645 |
kv_producer_state = load_block_list_sm100(
|
| 646 |
curr_full_block_idx,
|
| 647 |
-
|
|
|
|
| 648 |
load_q_with_first=False,
|
| 649 |
q_stage=q_stage,
|
| 650 |
kv_producer_state=kv_producer_state,
|
|
@@ -652,6 +779,7 @@ def produce_block_sparse_loads_sm100(
|
|
| 652 |
load_K=load_K,
|
| 653 |
load_V=load_V,
|
| 654 |
pipeline_kv=pipeline_kv,
|
|
|
|
| 655 |
)
|
| 656 |
|
| 657 |
if q_phase_flipped:
|
|
@@ -666,19 +794,39 @@ def get_total_block_count(
|
|
| 666 |
batch_idx,
|
| 667 |
head_idx,
|
| 668 |
m_block,
|
|
|
|
|
|
|
| 669 |
qhead_per_kvhead: cutlass.Constexpr,
|
| 670 |
q_subtile_factor: cutlass.Constexpr,
|
|
|
|
|
|
|
| 671 |
):
|
| 672 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
|
|
|
|
|
|
|
|
|
| 682 |
|
| 683 |
|
| 684 |
@cute.jit
|
|
@@ -691,7 +839,7 @@ def handle_block_sparse_empty_tile_correction_sm100(
|
|
| 691 |
is_split_kv: cutlass.Constexpr,
|
| 692 |
learnable_sink,
|
| 693 |
mLSE,
|
| 694 |
-
|
| 695 |
m_block: Int32,
|
| 696 |
head_idx: Int32,
|
| 697 |
batch_idx: Int32,
|
|
@@ -699,7 +847,7 @@ def handle_block_sparse_empty_tile_correction_sm100(
|
|
| 699 |
sScale: cute.Tensor,
|
| 700 |
stats: list,
|
| 701 |
correction_epilogue: Callable,
|
| 702 |
-
thr_mma_pv: cute.
|
| 703 |
tOtO: cute.Tensor,
|
| 704 |
sO: cute.Tensor,
|
| 705 |
pipeline_sm_stats: cutlass.pipeline.PipelineAsync,
|
|
@@ -709,6 +857,8 @@ def handle_block_sparse_empty_tile_correction_sm100(
|
|
| 709 |
o_corr_consumer_phase: Int32,
|
| 710 |
corr_epi_producer_phase: Int32,
|
| 711 |
softmax_scale_log2: Float32,
|
|
|
|
|
|
|
| 712 |
mO_cur: Optional[cute.Tensor] = None,
|
| 713 |
gO: Optional[cute.Tensor] = None,
|
| 714 |
gmem_tiled_copy_O: Optional[cute.TiledCopy] = None,
|
|
@@ -748,10 +898,11 @@ def handle_block_sparse_empty_tile_correction_sm100(
|
|
| 748 |
if sink_val != -Float32.inf and (const_expr(not is_split_kv) or split_idx == 0):
|
| 749 |
if row_max_value == -Float32.inf:
|
| 750 |
row_max_value = sink_val * (LOG2_E / softmax_scale_log2)
|
| 751 |
-
row_sum_value =
|
| 752 |
else:
|
| 753 |
row_sum_value = row_sum_value + cute.math.exp2(
|
| 754 |
-
sink_val * LOG2_E - row_max_value * softmax_scale_log2
|
|
|
|
| 755 |
)
|
| 756 |
if tidx < m_block_size:
|
| 757 |
scale_row_idx = tidx + stage * m_block_size
|
|
@@ -768,17 +919,19 @@ def handle_block_sparse_empty_tile_correction_sm100(
|
|
| 768 |
|
| 769 |
if const_expr(gmem_tiled_copy_O is None):
|
| 770 |
pipeline_o_epi.producer_acquire_w_index_phase(stage, corr_epi_producer_phase)
|
|
|
|
|
|
|
| 771 |
correction_epilogue(
|
| 772 |
thr_mma_pv,
|
| 773 |
tOtO[None, None, None, stage],
|
| 774 |
tidx,
|
| 775 |
stage,
|
| 776 |
m_block,
|
| 777 |
-
|
| 778 |
Float32(0.0), # zero scale ensures empty tile writes zeros into staged outputs
|
| 779 |
sO[None, None, stage],
|
| 780 |
mO_cur,
|
| 781 |
-
|
| 782 |
gmem_tiled_copy_O,
|
| 783 |
)
|
| 784 |
if const_expr(gmem_tiled_copy_O is None):
|
|
@@ -794,12 +947,103 @@ def handle_block_sparse_empty_tile_correction_sm100(
|
|
| 794 |
)
|
| 795 |
|
| 796 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 797 |
@cute.jit
|
| 798 |
def softmax_block_sparse_sm100(
|
| 799 |
blocksparse_tensors: BlockSparseTensors,
|
| 800 |
batch_idx,
|
| 801 |
head_idx,
|
| 802 |
m_block,
|
|
|
|
|
|
|
|
|
|
| 803 |
softmax_step: Callable,
|
| 804 |
mask_fn: Callable,
|
| 805 |
mask_fn_none: Callable,
|
|
@@ -813,104 +1057,81 @@ def softmax_block_sparse_sm100(
|
|
| 813 |
check_m_boundary: bool,
|
| 814 |
qhead_per_kvhead: cutlass.Constexpr,
|
| 815 |
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
|
|
|
| 816 |
):
|
| 817 |
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % 4
|
| 818 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 819 |
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
|
|
|
|
|
|
| 831 |
|
| 832 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 833 |
|
| 834 |
if total_block_cnt == 0:
|
| 835 |
-
# See NOTE [SM100 block-sparse empty tiles: mbarrier contract].
|
| 836 |
-
# pipeline_sm_stats.producer_commit_w_index(stage_idx)
|
| 837 |
sm_stats_barrier.arrive_w_index(index=stage_idx * 4 + warp_idx)
|
| 838 |
else:
|
| 839 |
-
if
|
| 840 |
-
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 841 |
(
|
| 842 |
mma_si_consumer_phase,
|
| 843 |
si_corr_producer_phase,
|
| 844 |
s0_s1_sequence_phase,
|
| 845 |
-
) =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
mma_si_consumer_phase,
|
| 847 |
si_corr_producer_phase,
|
| 848 |
s0_s1_sequence_phase,
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
|
| 852 |
)
|
| 853 |
-
for i in cutlass.range(1, curr_mask_block_cnt):
|
| 854 |
-
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1 - i]
|
| 855 |
-
(
|
| 856 |
-
mma_si_consumer_phase,
|
| 857 |
-
si_corr_producer_phase,
|
| 858 |
-
s0_s1_sequence_phase,
|
| 859 |
-
) = softmax_step(
|
| 860 |
-
mma_si_consumer_phase,
|
| 861 |
-
si_corr_producer_phase,
|
| 862 |
-
s0_s1_sequence_phase,
|
| 863 |
-
mask_n_block,
|
| 864 |
-
mask_fn=partial(mask_fn, mask_seqlen=False, check_q_boundary=check_m_boundary),
|
| 865 |
-
)
|
| 866 |
|
| 867 |
-
if
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
mma_si_consumer_phase,
|
| 887 |
-
si_corr_producer_phase,
|
| 888 |
-
s0_s1_sequence_phase,
|
| 889 |
-
) = softmax_step(
|
| 890 |
-
mma_si_consumer_phase,
|
| 891 |
-
si_corr_producer_phase,
|
| 892 |
-
s0_s1_sequence_phase,
|
| 893 |
-
full_n_block,
|
| 894 |
-
is_first=False,
|
| 895 |
-
mask_fn=partial(
|
| 896 |
-
mask_fn_none, mask_seqlen=False, check_q_boundary=check_m_boundary
|
| 897 |
-
),
|
| 898 |
-
)
|
| 899 |
-
for i in cutlass.range(1, curr_full_block_cnt):
|
| 900 |
-
full_n_block = curr_full_block_idx[curr_full_block_cnt - 1 - i]
|
| 901 |
-
(
|
| 902 |
-
mma_si_consumer_phase,
|
| 903 |
-
si_corr_producer_phase,
|
| 904 |
-
s0_s1_sequence_phase,
|
| 905 |
-
) = softmax_step(
|
| 906 |
-
mma_si_consumer_phase,
|
| 907 |
-
si_corr_producer_phase,
|
| 908 |
-
s0_s1_sequence_phase,
|
| 909 |
-
full_n_block,
|
| 910 |
-
mask_fn=partial(
|
| 911 |
-
mask_fn_none, mask_seqlen=False, check_q_boundary=check_m_boundary
|
| 912 |
-
),
|
| 913 |
-
)
|
| 914 |
|
| 915 |
return (
|
| 916 |
mma_si_consumer_phase,
|
|
@@ -940,24 +1161,24 @@ def get_total_q_block_count_bwd(
|
|
| 940 |
batch_idx,
|
| 941 |
head_idx,
|
| 942 |
n_block,
|
| 943 |
-
|
| 944 |
m_block_max: int = 0,
|
| 945 |
):
|
| 946 |
"""Count total tile iterations for given n_block (KV tile) in backward."""
|
| 947 |
-
q_block_cnt, _, full_block_cnt, _ = blocksparse_tensors
|
| 948 |
total = q_block_cnt[batch_idx, head_idx, n_block]
|
| 949 |
if const_expr(full_block_cnt is not None):
|
| 950 |
total = total + full_block_cnt[batch_idx, head_idx, n_block]
|
| 951 |
-
return total *
|
| 952 |
|
| 953 |
|
| 954 |
@cute.jit
|
| 955 |
-
def
|
| 956 |
blocksparse_tensors: BlockSparseTensors,
|
| 957 |
batch_idx,
|
| 958 |
head_idx,
|
| 959 |
n_block,
|
| 960 |
-
# Pipeline states
|
| 961 |
producer_state_Q_LSE,
|
| 962 |
producer_state_dO_dPsum,
|
| 963 |
# Pipelines
|
|
@@ -983,14 +1204,20 @@ def produce_block_sparse_q_loads_bwd_sm100(
|
|
| 983 |
should_load_Q: cutlass.Constexpr,
|
| 984 |
should_load_dO: cutlass.Constexpr,
|
| 985 |
# Subtiling factor and bounds
|
| 986 |
-
|
| 987 |
m_block_max: int = 0,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 988 |
):
|
| 989 |
-
"""SM100 backward block
|
| 990 |
-
|
| 991 |
-
Returns updated (producer_state_Q_LSE, producer_state_dO_dPsum).
|
| 992 |
-
First iteration loads K/V alongside Q/dO; subsequent iterations load only Q/dO.
|
| 993 |
-
"""
|
| 994 |
(
|
| 995 |
curr_q_cnt,
|
| 996 |
curr_q_idx,
|
|
@@ -998,81 +1225,325 @@ def produce_block_sparse_q_loads_bwd_sm100(
|
|
| 998 |
curr_full_idx,
|
| 999 |
loop_count,
|
| 1000 |
) = get_block_sparse_iteration_info_bwd(
|
| 1001 |
-
blocksparse_tensors, batch_idx, head_idx, n_block,
|
| 1002 |
)
|
| 1003 |
-
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
|
| 1007 |
curr_q_cnt,
|
| 1008 |
curr_q_idx,
|
| 1009 |
curr_full_cnt,
|
| 1010 |
curr_full_idx,
|
| 1011 |
-
|
| 1012 |
-
m_block_max,
|
| 1013 |
)
|
| 1014 |
-
|
| 1015 |
if m_block_max > 0:
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
if
|
| 1019 |
-
|
| 1020 |
-
|
| 1021 |
-
|
| 1022 |
-
|
| 1023 |
-
|
| 1024 |
-
|
| 1025 |
-
|
| 1026 |
-
|
| 1027 |
-
|
| 1028 |
-
|
| 1029 |
-
|
| 1030 |
-
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
| 1031 |
-
)
|
| 1032 |
-
producer_state_Q_LSE.advance()
|
| 1033 |
-
if const_expr(should_load_dO):
|
| 1034 |
-
pipeline_dO.producer_acquire(
|
| 1035 |
-
producer_state_dO_dPsum, extra_tx_count=tma_copy_bytes_V
|
| 1036 |
)
|
| 1037 |
-
|
| 1038 |
-
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
| 1048 |
-
|
| 1049 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1050 |
if const_expr(should_load_Q):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1051 |
pipeline_Q.producer_acquire(producer_state_Q_LSE)
|
| 1052 |
-
load_Q(
|
| 1053 |
pipeline_Q.producer_commit(producer_state_Q_LSE)
|
|
|
|
| 1054 |
pipeline_LSE.producer_acquire(producer_state_Q_LSE)
|
| 1055 |
with cute.arch.elect_one():
|
| 1056 |
copy_stats(
|
| 1057 |
-
gLSE[None,
|
| 1058 |
sLSE[None, producer_state_Q_LSE.index],
|
| 1059 |
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
| 1060 |
)
|
| 1061 |
producer_state_Q_LSE.advance()
|
|
|
|
| 1062 |
if const_expr(should_load_dO):
|
| 1063 |
-
pipeline_dO.producer_acquire(
|
| 1064 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1065 |
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
|
|
|
| 1066 |
pipeline_dPsum.producer_acquire(producer_state_dO_dPsum)
|
| 1067 |
with cute.arch.elect_one():
|
| 1068 |
copy_stats(
|
| 1069 |
-
gdPsum[None,
|
| 1070 |
sdPsum[None, producer_state_dO_dPsum.index],
|
| 1071 |
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dO_dPsum),
|
| 1072 |
)
|
| 1073 |
producer_state_dO_dPsum.advance()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1074 |
|
| 1075 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1076 |
|
| 1077 |
|
| 1078 |
@cute.jit
|
|
@@ -1081,14 +1552,14 @@ def get_block_sparse_iteration_info_bwd(
|
|
| 1081 |
batch_idx,
|
| 1082 |
head_idx,
|
| 1083 |
n_block,
|
| 1084 |
-
|
| 1085 |
m_block_max: int = 0,
|
| 1086 |
):
|
| 1087 |
"""Extract block-sparse iteration info for backward pass.
|
| 1088 |
|
| 1089 |
Returns (curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count).
|
| 1090 |
"""
|
| 1091 |
-
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1092 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1093 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1094 |
|
|
@@ -1102,7 +1573,7 @@ def get_block_sparse_iteration_info_bwd(
|
|
| 1102 |
sparse_block_count = curr_q_cnt
|
| 1103 |
if const_expr(full_cnt is not None):
|
| 1104 |
sparse_block_count = sparse_block_count + curr_full_cnt
|
| 1105 |
-
total_count = sparse_block_count *
|
| 1106 |
|
| 1107 |
return curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count
|
| 1108 |
|
|
@@ -1114,7 +1585,7 @@ def get_m_block_from_iter_bwd(
|
|
| 1114 |
curr_q_idx: cute.Tensor,
|
| 1115 |
curr_full_cnt,
|
| 1116 |
curr_full_idx: Optional[cute.Tensor],
|
| 1117 |
-
|
| 1118 |
m_block_max: int = 0,
|
| 1119 |
):
|
| 1120 |
"""Derive m_block index and is_full_block flag from iteration index.
|
|
@@ -1123,8 +1594,8 @@ def get_m_block_from_iter_bwd(
|
|
| 1123 |
- m_block: The actual Q-tile block index
|
| 1124 |
- is_full_block: True if this is a full block (no mask_mod needed)
|
| 1125 |
"""
|
| 1126 |
-
sparse_iter_idx = iter_idx //
|
| 1127 |
-
subtile_offset = iter_idx %
|
| 1128 |
|
| 1129 |
sparse_m_block = Int32(0)
|
| 1130 |
is_full_block = False
|
|
@@ -1137,7 +1608,7 @@ def get_m_block_from_iter_bwd(
|
|
| 1137 |
else:
|
| 1138 |
sparse_m_block = curr_q_idx[sparse_iter_idx]
|
| 1139 |
|
| 1140 |
-
return sparse_m_block *
|
| 1141 |
|
| 1142 |
|
| 1143 |
@cute.jit
|
|
@@ -1165,6 +1636,13 @@ def _load_q_do_block_sm90(
|
|
| 1165 |
else:
|
| 1166 |
pipeline_Q.producer_acquire(producer_state_Q)
|
| 1167 |
load_Q(m_block, producer_state=producer_state_Q)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1168 |
load_LSE(m_block, producer_state=producer_state_Q)
|
| 1169 |
|
| 1170 |
producer_state_dO_cur = (
|
|
@@ -1202,7 +1680,7 @@ def produce_block_sparse_q_loads_bwd_sm90(
|
|
| 1202 |
tma_copy_bytes_K,
|
| 1203 |
tma_copy_bytes_V,
|
| 1204 |
Q_stage_eq_dO_stage: cutlass.Constexpr,
|
| 1205 |
-
|
| 1206 |
m_block_max: int,
|
| 1207 |
):
|
| 1208 |
"""SM90 backward block sparse loading with separate partial/full loops.
|
|
@@ -1212,7 +1690,7 @@ def produce_block_sparse_q_loads_bwd_sm90(
|
|
| 1212 |
|
| 1213 |
Returns updated (producer_state_Q, producer_state_dO).
|
| 1214 |
"""
|
| 1215 |
-
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1216 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1217 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1218 |
|
|
@@ -1225,10 +1703,10 @@ def produce_block_sparse_q_loads_bwd_sm90(
|
|
| 1225 |
|
| 1226 |
kv_loaded = False
|
| 1227 |
|
| 1228 |
-
for iter_idx in cutlass.range(curr_q_cnt *
|
| 1229 |
-
sparse_idx = iter_idx //
|
| 1230 |
-
subtile_offset = iter_idx %
|
| 1231 |
-
m_block = curr_q_idx[sparse_idx] *
|
| 1232 |
|
| 1233 |
if m_block < m_block_max:
|
| 1234 |
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
|
@@ -1251,10 +1729,10 @@ def produce_block_sparse_q_loads_bwd_sm90(
|
|
| 1251 |
kv_loaded = True
|
| 1252 |
|
| 1253 |
if const_expr(full_cnt is not None):
|
| 1254 |
-
for iter_idx in cutlass.range(curr_full_cnt *
|
| 1255 |
-
sparse_idx = iter_idx //
|
| 1256 |
-
subtile_offset = iter_idx %
|
| 1257 |
-
m_block = curr_full_idx[sparse_idx] *
|
| 1258 |
|
| 1259 |
if m_block < m_block_max:
|
| 1260 |
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
|
@@ -1295,9 +1773,9 @@ def consume_block_sparse_mma_bwd_sm90(
|
|
| 1295 |
thr_mma_SdP,
|
| 1296 |
score_mod_fn=None,
|
| 1297 |
score_mod_bwd_fn=None,
|
| 1298 |
-
|
| 1299 |
m_block_max: int = 0,
|
| 1300 |
-
|
| 1301 |
fastdiv_mods=(None, None),
|
| 1302 |
):
|
| 1303 |
"""SM90 backward block sparse MMA consumption with separate partial/full loops.
|
|
@@ -1307,7 +1785,7 @@ def consume_block_sparse_mma_bwd_sm90(
|
|
| 1307 |
|
| 1308 |
Returns updated (consumer_state_Q, consumer_state_dO).
|
| 1309 |
"""
|
| 1310 |
-
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1311 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1312 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1313 |
|
|
@@ -1330,7 +1808,7 @@ def consume_block_sparse_mma_bwd_sm90(
|
|
| 1330 |
mask_causal=is_causal,
|
| 1331 |
mask_local=is_local,
|
| 1332 |
mask_mod=mask_mod,
|
| 1333 |
-
|
| 1334 |
fastdiv_mods=fastdiv_mods,
|
| 1335 |
)
|
| 1336 |
|
|
@@ -1343,14 +1821,14 @@ def consume_block_sparse_mma_bwd_sm90(
|
|
| 1343 |
mask_seqlen=True,
|
| 1344 |
mask_causal=is_causal,
|
| 1345 |
mask_local=is_local,
|
| 1346 |
-
|
| 1347 |
fastdiv_mods=fastdiv_mods,
|
| 1348 |
)
|
| 1349 |
|
| 1350 |
-
for iter_idx in cutlass.range(curr_q_cnt *
|
| 1351 |
-
sparse_idx = iter_idx //
|
| 1352 |
-
subtile_offset = iter_idx %
|
| 1353 |
-
m_block = curr_q_idx[sparse_idx] *
|
| 1354 |
|
| 1355 |
if m_block < m_block_max:
|
| 1356 |
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
|
@@ -1365,10 +1843,10 @@ def consume_block_sparse_mma_bwd_sm90(
|
|
| 1365 |
dKV_accumulate = True
|
| 1366 |
|
| 1367 |
if const_expr(full_cnt is not None):
|
| 1368 |
-
for iter_idx in cutlass.range(curr_full_cnt *
|
| 1369 |
-
sparse_idx = iter_idx //
|
| 1370 |
-
subtile_offset = iter_idx %
|
| 1371 |
-
m_block = curr_full_idx[sparse_idx] *
|
| 1372 |
|
| 1373 |
if m_block < m_block_max:
|
| 1374 |
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
|
@@ -1390,18 +1868,18 @@ def _store_one_dQaccum_sm90(
|
|
| 1390 |
m_block,
|
| 1391 |
sdQaccum: cute.Tensor,
|
| 1392 |
gdQaccum: cute.Tensor,
|
| 1393 |
-
|
| 1394 |
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1395 |
tma_copy_bytes_dQ,
|
| 1396 |
):
|
| 1397 |
"""Store dQaccum for a single m_block."""
|
| 1398 |
-
for warp_group_idx in cutlass.range_constexpr(
|
| 1399 |
-
cute.arch.cp_async_bulk_wait_group(
|
| 1400 |
cute.arch.barrier_arrive(
|
| 1401 |
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1402 |
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1403 |
)
|
| 1404 |
-
for warp_group_idx in cutlass.range_constexpr(
|
| 1405 |
cute.arch.barrier(
|
| 1406 |
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1407 |
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
|
@@ -1409,7 +1887,7 @@ def _store_one_dQaccum_sm90(
|
|
| 1409 |
with cute.arch.elect_one():
|
| 1410 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1411 |
sdQaccum[None, warp_group_idx].iterator,
|
| 1412 |
-
gdQaccum[None, warp_group_idx, m_block].iterator,
|
| 1413 |
tma_copy_bytes_dQ,
|
| 1414 |
)
|
| 1415 |
cute.arch.cp_async_bulk_commit_group()
|
|
@@ -1423,9 +1901,9 @@ def dQaccum_store_block_sparse_bwd_sm90(
|
|
| 1423 |
n_block,
|
| 1424 |
sdQaccum: cute.Tensor,
|
| 1425 |
gdQaccum: cute.Tensor,
|
| 1426 |
-
|
| 1427 |
m_block_max: int,
|
| 1428 |
-
|
| 1429 |
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1430 |
tma_copy_bytes_dQ,
|
| 1431 |
):
|
|
@@ -1433,7 +1911,7 @@ def dQaccum_store_block_sparse_bwd_sm90(
|
|
| 1433 |
|
| 1434 |
Iterates partial blocks first, then full blocks, matching producer/consumer order.
|
| 1435 |
"""
|
| 1436 |
-
q_cnt, q_idx, full_cnt, full_idx = blocksparse_tensors
|
| 1437 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1438 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1439 |
|
|
@@ -1444,33 +1922,33 @@ def dQaccum_store_block_sparse_bwd_sm90(
|
|
| 1444 |
curr_full_cnt = Int32(0)
|
| 1445 |
curr_full_idx = None
|
| 1446 |
|
| 1447 |
-
for iter_idx in cutlass.range(curr_q_cnt *
|
| 1448 |
-
sparse_idx = iter_idx //
|
| 1449 |
-
subtile_offset = iter_idx %
|
| 1450 |
-
m_block = curr_q_idx[sparse_idx] *
|
| 1451 |
|
| 1452 |
if m_block < m_block_max:
|
| 1453 |
_store_one_dQaccum_sm90(
|
| 1454 |
m_block,
|
| 1455 |
sdQaccum,
|
| 1456 |
gdQaccum,
|
| 1457 |
-
|
| 1458 |
num_threads_per_warp_group,
|
| 1459 |
tma_copy_bytes_dQ,
|
| 1460 |
)
|
| 1461 |
|
| 1462 |
if const_expr(full_cnt is not None):
|
| 1463 |
-
for iter_idx in cutlass.range(curr_full_cnt *
|
| 1464 |
-
sparse_idx = iter_idx //
|
| 1465 |
-
subtile_offset = iter_idx %
|
| 1466 |
-
m_block = curr_full_idx[sparse_idx] *
|
| 1467 |
|
| 1468 |
if m_block < m_block_max:
|
| 1469 |
_store_one_dQaccum_sm90(
|
| 1470 |
m_block,
|
| 1471 |
sdQaccum,
|
| 1472 |
gdQaccum,
|
| 1473 |
-
|
| 1474 |
num_threads_per_warp_group,
|
| 1475 |
tma_copy_bytes_dQ,
|
| 1476 |
)
|
|
|
|
| 5 |
These utilities are used by CUTE DSL kernels to produce and consume block-sparse loads.
|
| 6 |
"""
|
| 7 |
|
| 8 |
+
from typing import Callable, Optional, Tuple
|
| 9 |
from functools import partial
|
| 10 |
import math
|
| 11 |
import cutlass
|
|
|
|
| 17 |
# Import data structures from block_sparsity
|
| 18 |
from .block_sparsity import BlockSparseTensors
|
| 19 |
from .named_barrier import NamedBarrierBwd
|
| 20 |
+
from .seqlen_info import SeqlenInfoQK
|
| 21 |
+
from .utils import AuxData
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@cute.jit
|
| 25 |
+
def _get_curr_blocksparse_tensors_varlen(
|
| 26 |
+
head_idx: cutlass.Int32,
|
| 27 |
+
m_block: cutlass.Int32,
|
| 28 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 29 |
+
seqlen_info: SeqlenInfoQK,
|
| 30 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 31 |
+
) -> Tuple[cutlass.Int32, cute.Tensor, cutlass.Int32, Optional[cute.Tensor]]:
|
| 32 |
+
"""Varlen path: tensors are 2D [nheads, total_m_blocks] / [nheads, total_n_blocks]."""
|
| 33 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx, *_ = blocksparse_tensors
|
| 34 |
+
curr_m_block = seqlen_info.m_block_offset + m_block
|
| 35 |
+
sparse_num_n_blocks = (
|
| 36 |
+
seqlen_info.num_n_blocks
|
| 37 |
+
if const_expr(kv_subtile_factor == 1)
|
| 38 |
+
else (seqlen_info.num_n_blocks + kv_subtile_factor - 1) // kv_subtile_factor
|
| 39 |
+
)
|
| 40 |
+
batch_block_idx_offset = (
|
| 41 |
+
seqlen_info.block_idx_offset
|
| 42 |
+
if const_expr(seqlen_info.has_cu_block_idx_offsets)
|
| 43 |
+
else seqlen_info.m_block_offset * sparse_num_n_blocks
|
| 44 |
+
)
|
| 45 |
+
curr_block_idx_offset = batch_block_idx_offset + m_block * sparse_num_n_blocks
|
| 46 |
+
curr_mask_block_cnt = mask_block_cnt[head_idx, curr_m_block]
|
| 47 |
+
curr_mask_block_idx = cute.domain_offset(curr_block_idx_offset, mask_block_idx[head_idx, None])
|
| 48 |
+
if const_expr(full_block_cnt is not None):
|
| 49 |
+
curr_full_block_cnt = full_block_cnt[head_idx, curr_m_block]
|
| 50 |
+
curr_full_block_idx = cute.domain_offset(
|
| 51 |
+
curr_block_idx_offset, full_block_idx[head_idx, None]
|
| 52 |
+
)
|
| 53 |
+
else:
|
| 54 |
+
curr_full_block_cnt = Int32(0)
|
| 55 |
+
curr_full_block_idx = None
|
| 56 |
+
return (curr_mask_block_cnt, curr_mask_block_idx, curr_full_block_cnt, curr_full_block_idx)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
@cute.jit
|
| 60 |
+
def _get_curr_blocksparse_tensors(
|
| 61 |
+
batch_idx: cutlass.Int32,
|
| 62 |
+
head_idx: cutlass.Int32,
|
| 63 |
+
m_block: cutlass.Int32,
|
| 64 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 65 |
+
) -> Tuple[cutlass.Int32, cute.Tensor, cutlass.Int32, Optional[cute.Tensor]]:
|
| 66 |
+
"""Fixed-length path: tensors are 4D [batch, nheads, m_block, n_block]."""
|
| 67 |
+
mask_block_cnt, mask_block_idx, full_block_cnt, full_block_idx, *_ = blocksparse_tensors
|
| 68 |
+
curr_mask_block_cnt = mask_block_cnt[batch_idx, head_idx, m_block]
|
| 69 |
+
curr_mask_block_idx = mask_block_idx[batch_idx, head_idx, m_block, None]
|
| 70 |
+
if const_expr(full_block_cnt is not None):
|
| 71 |
+
curr_full_block_cnt = full_block_cnt[batch_idx, head_idx, m_block]
|
| 72 |
+
curr_full_block_idx = full_block_idx[batch_idx, head_idx, m_block, None]
|
| 73 |
+
else:
|
| 74 |
+
curr_full_block_cnt = Int32(0)
|
| 75 |
+
curr_full_block_idx = None
|
| 76 |
+
return (curr_mask_block_cnt, curr_mask_block_idx, curr_full_block_cnt, curr_full_block_idx)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
@cute.jit
|
| 80 |
+
def get_curr_blocksparse_tensors(
|
| 81 |
+
batch_idx: cutlass.Int32,
|
| 82 |
+
head_idx: cutlass.Int32,
|
| 83 |
+
m_block: cutlass.Int32,
|
| 84 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 85 |
+
seqlen_info: SeqlenInfoQK,
|
| 86 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 87 |
+
) -> Tuple[cutlass.Int32, cute.Tensor, cutlass.Int32, Optional[cute.Tensor]]:
|
| 88 |
+
"""Extract head, m_block, and batch-local blocksparsity data from blocksparse_tensors"""
|
| 89 |
+
if const_expr(len(blocksparse_tensors.mask_block_cnt.shape) == 2):
|
| 90 |
+
return _get_curr_blocksparse_tensors_varlen(
|
| 91 |
+
head_idx, m_block, blocksparse_tensors, seqlen_info, kv_subtile_factor
|
| 92 |
+
)
|
| 93 |
+
return _get_curr_blocksparse_tensors(batch_idx, head_idx, m_block, blocksparse_tensors)
|
| 94 |
|
| 95 |
|
| 96 |
# NOTE [SM100 block-sparse empty tiles: mbarrier contract]
|
|
|
|
| 135 |
# to ack/advance, and arrives `mbar_P_full_O_rescaled` when MMA can proceed.
|
| 136 |
#
|
| 137 |
# Backward (SM100):
|
| 138 |
+
# - Empty KV tile: for a given sparse `n_block`, `loop_count == 0` means no Q tiles contribute.
|
| 139 |
+
# - Load helpers guard their prologue/mainloop/tail on `loop_count > 0`; MMA/softmax/relay use
|
| 140 |
+
# the same block-sparse count as `process_tile`. Empty tiles therefore skip both producer and
|
| 141 |
+
# consumer operations entirely, with no synthetic per-tile mbarrier handshake like forward.
|
| 142 |
+
# - For non-empty tiles, producer tails are part of the non-empty load contract: the load warp emits
|
| 143 |
+
# exactly the Q/dO/LSE/dPsum/Qt/Kt items that the MMA and softmax consumers wait on, then tails the
|
| 144 |
+
# advanced producer states. Empty tiles leave producer and consumer states unchanged for the next tile.
|
| 145 |
# - In the `not dKV_postprocess` path, dK/dV for empty KV tiles are explicitly written as zeros
|
| 146 |
# even when `process_tile == False` (see `flash_bwd_sm100.py` `should_zero_dKV`).
|
| 147 |
|
| 148 |
|
| 149 |
+
@cute.jit
|
| 150 |
+
def sparse_physical_n_block_forward(
|
| 151 |
+
block_indices: cute.Tensor,
|
| 152 |
+
offset,
|
| 153 |
+
kv_subtile_factor: cutlass.Constexpr[int],
|
| 154 |
+
):
|
| 155 |
+
"""Map forward physical N offsets to physical N tile indices."""
|
| 156 |
+
sparse_offset = offset // kv_subtile_factor
|
| 157 |
+
subtile_offset = offset - sparse_offset * kv_subtile_factor
|
| 158 |
+
coarse_block = block_indices[sparse_offset]
|
| 159 |
+
return coarse_block * kv_subtile_factor + subtile_offset
|
| 160 |
+
|
| 161 |
+
|
| 162 |
@cute.jit
|
| 163 |
def load_block_list(
|
| 164 |
block_indices: cute.Tensor,
|
| 165 |
block_count,
|
|
|
|
| 166 |
first_block_preloaded: cutlass.Constexpr,
|
| 167 |
kv_producer_state,
|
|
|
|
| 168 |
load_K,
|
| 169 |
load_V,
|
| 170 |
pipeline_k,
|
| 171 |
pipeline_v,
|
|
|
|
|
|
|
| 172 |
intra_wg_overlap: cutlass.Constexpr,
|
| 173 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 174 |
):
|
| 175 |
+
"""Iterate over the sparse blocks and load K, V into the pipeline.
|
| 176 |
+
For the intra_wg_overlap case, we overlap the loads of K and V. And this
|
| 177 |
means we need to pipeline the last V load from the partial block case,
|
| 178 |
with the loads for the full blocks. Set first_block_preloaded when the
|
| 179 |
caller has already issued the first K load for the list.
|
| 180 |
|
| 181 |
+
Q is loaded separately on its own mbarrier before this function is called.
|
| 182 |
+
|
| 183 |
Note:
|
| 184 |
we iterate along the block_n indices in reverse.
|
| 185 |
|
|
|
|
| 188 |
|
| 189 |
"""
|
| 190 |
if block_count > 0:
|
| 191 |
+
total_blocks = block_count * kv_subtile_factor
|
| 192 |
if const_expr(not intra_wg_overlap):
|
| 193 |
+
for offset in cutlass.range(total_blocks):
|
| 194 |
+
n_block = sparse_physical_n_block_forward(
|
| 195 |
+
block_indices,
|
| 196 |
+
total_blocks - 1 - offset,
|
| 197 |
+
kv_subtile_factor,
|
| 198 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
pipeline_k.producer_acquire(kv_producer_state)
|
| 200 |
load_K(src_idx=n_block, producer_state=kv_producer_state)
|
| 201 |
pipeline_v.producer_acquire(kv_producer_state)
|
| 202 |
load_V(src_idx=n_block, producer_state=kv_producer_state)
|
| 203 |
kv_producer_state.advance()
|
| 204 |
else:
|
| 205 |
+
n_block_first = sparse_physical_n_block_forward(
|
| 206 |
+
block_indices,
|
| 207 |
+
total_blocks - 1,
|
| 208 |
+
kv_subtile_factor,
|
| 209 |
+
)
|
| 210 |
if const_expr(not first_block_preloaded):
|
| 211 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
load_K(src_idx=n_block_first, producer_state=kv_producer_state)
|
| 213 |
|
| 214 |
+
for idx in cutlass.range(total_blocks - 1, unroll=1):
|
| 215 |
+
n_block_prev = sparse_physical_n_block_forward(
|
| 216 |
+
block_indices,
|
| 217 |
+
total_blocks - 1 - idx,
|
| 218 |
+
kv_subtile_factor,
|
| 219 |
+
)
|
| 220 |
+
n_block = sparse_physical_n_block_forward(
|
| 221 |
+
block_indices,
|
| 222 |
+
total_blocks - 1 - (idx + 1),
|
| 223 |
+
kv_subtile_factor,
|
| 224 |
+
)
|
| 225 |
kv_producer_state_prev = kv_producer_state.clone()
|
| 226 |
kv_producer_state.advance()
|
| 227 |
pipeline_k.producer_acquire(kv_producer_state)
|
|
|
|
| 239 |
load_V,
|
| 240 |
pipeline_v,
|
| 241 |
kv_producer_state,
|
| 242 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 243 |
):
|
| 244 |
"""Load the final V block after overlapped K/V loads."""
|
| 245 |
if block_count > 0:
|
| 246 |
+
n_block_last = block_indices[0] * kv_subtile_factor
|
| 247 |
pipeline_v.producer_acquire(kv_producer_state)
|
| 248 |
load_V(src_idx=n_block_last, producer_state=kv_producer_state)
|
| 249 |
kv_producer_state.advance()
|
|
|
|
| 272 |
batch_idx,
|
| 273 |
head_idx,
|
| 274 |
m_block,
|
| 275 |
+
seqlen_info: SeqlenInfoQK,
|
| 276 |
kv_producer_state,
|
|
|
|
| 277 |
load_K,
|
| 278 |
load_V,
|
| 279 |
pipeline_k,
|
| 280 |
pipeline_v,
|
|
|
|
|
|
|
| 281 |
intra_wg_overlap: cutlass.Constexpr,
|
| 282 |
qhead_per_kvhead: cutlass.Constexpr[int] = 1,
|
| 283 |
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 284 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 285 |
):
|
| 286 |
"""Iterate over the mask and full block lists for a single tile.
|
| 287 |
|
| 288 |
+
Q is loaded separately on its own mbarrier before this function is called.
|
| 289 |
+
|
| 290 |
The masked (partial) list may leave the last V load pending when intra-warp-group
|
| 291 |
overlap is enabled. The first full block must consume that pending V while
|
| 292 |
issuing its own K load on the next pipeline stage.
|
|
|
|
| 299 |
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 300 |
must be converted to unpacked for sparse tensor indexing.
|
| 301 |
"""
|
| 302 |
+
# The SM90 consumer (consume_block_sparse_loads) has no kv_subtile support yet.
|
| 303 |
+
assert kv_subtile_factor == 1, (
|
| 304 |
+
"Coarse KV blocks (kv_subtile_factor > 1) are not supported on the SM90 forward path yet."
|
| 305 |
+
)
|
| 306 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 307 |
|
| 308 |
+
(
|
| 309 |
+
curr_mask_block_cnt,
|
| 310 |
+
curr_mask_block_idx,
|
| 311 |
+
curr_full_block_cnt,
|
| 312 |
+
curr_full_block_idx,
|
| 313 |
+
) = get_curr_blocksparse_tensors(
|
| 314 |
+
batch_idx,
|
| 315 |
+
head_idx,
|
| 316 |
+
m_block_sparse,
|
| 317 |
+
blocksparse_tensors,
|
| 318 |
+
seqlen_info,
|
| 319 |
+
kv_subtile_factor,
|
| 320 |
+
)
|
| 321 |
|
| 322 |
mask_empty = curr_mask_block_cnt == 0
|
| 323 |
full_empty = curr_full_block_cnt == 0
|
| 324 |
|
| 325 |
if mask_empty:
|
| 326 |
+
# No masked blocks: the full list owns the initial K load.
|
| 327 |
kv_producer_state = load_block_list(
|
| 328 |
curr_full_block_idx,
|
| 329 |
curr_full_block_cnt,
|
|
|
|
| 330 |
first_block_preloaded=False,
|
| 331 |
kv_producer_state=kv_producer_state,
|
|
|
|
| 332 |
load_K=load_K,
|
| 333 |
load_V=load_V,
|
| 334 |
pipeline_k=pipeline_k,
|
| 335 |
pipeline_v=pipeline_v,
|
|
|
|
|
|
|
| 336 |
intra_wg_overlap=intra_wg_overlap,
|
| 337 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 338 |
)
|
| 339 |
|
| 340 |
if const_expr(intra_wg_overlap) and curr_full_block_cnt > 0:
|
|
|
|
| 344 |
load_V,
|
| 345 |
pipeline_v,
|
| 346 |
kv_producer_state,
|
| 347 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 348 |
)
|
| 349 |
else:
|
| 350 |
+
# Masked blocks present. When overlap is disabled this fully drains the list.
|
|
|
|
| 351 |
kv_producer_state = load_block_list(
|
| 352 |
curr_mask_block_idx,
|
| 353 |
curr_mask_block_cnt,
|
|
|
|
| 354 |
first_block_preloaded=False,
|
| 355 |
kv_producer_state=kv_producer_state,
|
|
|
|
| 356 |
load_K=load_K,
|
| 357 |
load_V=load_V,
|
| 358 |
pipeline_k=pipeline_k,
|
| 359 |
pipeline_v=pipeline_v,
|
|
|
|
|
|
|
| 360 |
intra_wg_overlap=intra_wg_overlap,
|
| 361 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 362 |
)
|
| 363 |
|
| 364 |
if full_empty:
|
|
|
|
| 369 |
load_V,
|
| 370 |
pipeline_v,
|
| 371 |
kv_producer_state,
|
| 372 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 373 |
)
|
| 374 |
else:
|
| 375 |
if const_expr(intra_wg_overlap):
|
| 376 |
# Bridge the masked list to the full list by overlapping the pending masked V
|
| 377 |
# with the first full K load.
|
| 378 |
+
n_block_mask_last = curr_mask_block_idx[0] * kv_subtile_factor
|
| 379 |
+
n_block_full_first = sparse_physical_n_block_forward(
|
| 380 |
+
curr_full_block_idx,
|
| 381 |
+
curr_full_block_cnt * kv_subtile_factor - 1,
|
| 382 |
+
kv_subtile_factor,
|
| 383 |
+
)
|
| 384 |
kv_producer_state_prev = kv_producer_state.clone()
|
| 385 |
kv_producer_state.advance()
|
| 386 |
pipeline_k.producer_acquire(kv_producer_state)
|
|
|
|
| 391 |
kv_producer_state = load_block_list(
|
| 392 |
curr_full_block_idx,
|
| 393 |
curr_full_block_cnt,
|
|
|
|
| 394 |
first_block_preloaded=True,
|
| 395 |
kv_producer_state=kv_producer_state,
|
|
|
|
| 396 |
load_K=load_K,
|
| 397 |
load_V=load_V,
|
| 398 |
pipeline_k=pipeline_k,
|
| 399 |
pipeline_v=pipeline_v,
|
|
|
|
|
|
|
| 400 |
intra_wg_overlap=intra_wg_overlap,
|
| 401 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 402 |
)
|
| 403 |
|
| 404 |
kv_producer_state = finish_overlap_v_load(
|
|
|
|
| 407 |
load_V,
|
| 408 |
pipeline_v,
|
| 409 |
kv_producer_state,
|
| 410 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 411 |
)
|
| 412 |
else:
|
| 413 |
+
# Non-overlap path with both lists: run the full list normally.
|
|
|
|
| 414 |
kv_producer_state = load_block_list(
|
| 415 |
curr_full_block_idx,
|
| 416 |
curr_full_block_cnt,
|
|
|
|
| 417 |
first_block_preloaded=False,
|
| 418 |
kv_producer_state=kv_producer_state,
|
|
|
|
| 419 |
load_K=load_K,
|
| 420 |
load_V=load_V,
|
| 421 |
pipeline_k=pipeline_k,
|
| 422 |
pipeline_v=pipeline_v,
|
|
|
|
|
|
|
| 423 |
intra_wg_overlap=intra_wg_overlap,
|
| 424 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 425 |
)
|
| 426 |
|
| 427 |
return kv_producer_state
|
|
|
|
| 433 |
batch_idx,
|
| 434 |
head_idx,
|
| 435 |
m_block,
|
| 436 |
+
seqlen_info,
|
| 437 |
kv_consumer_state,
|
| 438 |
mma_pv_fn,
|
| 439 |
mma_one_n_block,
|
|
|
|
| 459 |
qhead_per_kvhead: Pack-GQA factor. When > 1, m_block is in packed space and
|
| 460 |
must be converted to unpacked for sparse tensor indexing.
|
| 461 |
"""
|
|
|
|
|
|
|
|
|
|
| 462 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 463 |
|
| 464 |
+
(
|
| 465 |
+
curr_mask_block_cnt,
|
| 466 |
+
curr_mask_block_idx,
|
| 467 |
+
curr_full_block_cnt,
|
| 468 |
+
curr_full_block_idx,
|
| 469 |
+
) = get_curr_blocksparse_tensors(
|
| 470 |
+
batch_idx,
|
| 471 |
+
head_idx,
|
| 472 |
+
m_block_sparse,
|
| 473 |
+
blocksparse_tensors,
|
| 474 |
+
seqlen_info,
|
| 475 |
+
)
|
| 476 |
|
| 477 |
processed_any = curr_mask_block_cnt + curr_full_block_cnt > 0
|
| 478 |
|
|
|
|
| 553 |
mask_n_block = curr_mask_block_idx[curr_mask_block_cnt - 1]
|
| 554 |
kv_consumer_state = process_first_half_block(
|
| 555 |
n_block=mask_n_block,
|
| 556 |
+
seqlen=seqlen_info,
|
| 557 |
kv_consumer_state=kv_consumer_state,
|
| 558 |
mask_fn=partial(
|
| 559 |
mask_fn,
|
|
|
|
| 569 |
kv_consumer_state = mma_one_n_block(
|
| 570 |
kv_consumer_state,
|
| 571 |
n_block=mask_n_block,
|
| 572 |
+
seqlen=seqlen_info,
|
| 573 |
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 574 |
mask_fn=partial(mask_fn, mask_mod=mask_mod, mask_seqlen=False),
|
| 575 |
)
|
|
|
|
| 580 |
if curr_mask_block_cnt == 0:
|
| 581 |
kv_consumer_state = process_first_half_block(
|
| 582 |
n_block=full_n_block,
|
| 583 |
+
seqlen=seqlen_info,
|
| 584 |
kv_consumer_state=kv_consumer_state,
|
| 585 |
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 586 |
score_mod_fn=score_mod_fn,
|
|
|
|
| 590 |
kv_consumer_state = mma_one_n_block(
|
| 591 |
kv_consumer_state,
|
| 592 |
n_block=full_n_block,
|
| 593 |
+
seqlen=seqlen_info,
|
| 594 |
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 595 |
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=True),
|
| 596 |
)
|
|
|
|
| 600 |
kv_consumer_state = mma_one_n_block(
|
| 601 |
kv_consumer_state,
|
| 602 |
n_block=full_n_block,
|
| 603 |
+
seqlen=seqlen_info,
|
| 604 |
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 605 |
mask_fn=partial(mask_fn, mask_mod=None, mask_seqlen=False),
|
| 606 |
)
|
|
|
|
| 616 |
return kv_consumer_state, O_should_accumulate, processed_any
|
| 617 |
|
| 618 |
|
| 619 |
+
@cute.jit
|
| 620 |
+
def split_block_range(block_count, split_idx: Int32, num_splits: Int32):
|
| 621 |
+
"""Return the half-open block-list range assigned to one SplitKV partition."""
|
| 622 |
+
blocks_per_split = cute.ceil_div(block_count, num_splits)
|
| 623 |
+
block_begin = cutlass.min(split_idx * blocks_per_split, block_count)
|
| 624 |
+
block_end = cutlass.min(block_begin + blocks_per_split, block_count)
|
| 625 |
+
return block_begin, block_end
|
| 626 |
+
|
| 627 |
+
|
| 628 |
@cute.jit
|
| 629 |
def load_block_list_sm100(
|
| 630 |
block_indices: cute.Tensor,
|
| 631 |
+
block_begin,
|
| 632 |
+
block_end,
|
| 633 |
load_q_with_first: cutlass.Constexpr,
|
| 634 |
q_stage: cutlass.Constexpr,
|
| 635 |
kv_producer_state,
|
|
|
|
| 637 |
load_K,
|
| 638 |
load_V,
|
| 639 |
pipeline_kv,
|
| 640 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 641 |
):
|
| 642 |
+
"""SM100 sparse load loop over physical N tiles from coarse metadata."""
|
| 643 |
+
physical_n_block = partial(
|
| 644 |
+
sparse_physical_n_block_forward, block_indices, kv_subtile_factor=kv_subtile_factor
|
| 645 |
+
)
|
| 646 |
+
physical_block_count = block_end - block_begin
|
| 647 |
+
if physical_block_count > 0:
|
| 648 |
# First iteration: load Q alongside K if requested
|
| 649 |
+
n_block_first = physical_n_block(block_end - 1)
|
| 650 |
|
| 651 |
if const_expr(load_q_with_first):
|
| 652 |
# SM100 loads Q0 and optionally Q1
|
|
|
|
| 662 |
kv_producer_state.advance()
|
| 663 |
|
| 664 |
# Remaining blocks
|
| 665 |
+
for offset in cutlass.range(1, physical_block_count):
|
| 666 |
+
n_block = physical_n_block(block_end - 1 - offset)
|
| 667 |
load_K(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
| 668 |
kv_producer_state.advance()
|
| 669 |
load_V(block=n_block, producer_state=kv_producer_state, page_idx=None)
|
|
|
|
| 679 |
batch_idx,
|
| 680 |
head_idx,
|
| 681 |
m_block,
|
| 682 |
+
seqlen_info: SeqlenInfoQK,
|
| 683 |
+
split_idx: Int32,
|
| 684 |
+
num_splits: Int32,
|
| 685 |
kv_producer_state,
|
| 686 |
load_Q,
|
| 687 |
load_K,
|
|
|
|
| 691 |
q_producer_phase: Int32,
|
| 692 |
qhead_per_kvhead: cutlass.Constexpr,
|
| 693 |
q_subtile_factor: cutlass.Constexpr,
|
| 694 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 695 |
):
|
| 696 |
"""SM100 entry point for sparse block iteration.
|
| 697 |
|
|
|
|
| 704 |
"""
|
| 705 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 706 |
|
| 707 |
+
(
|
| 708 |
+
curr_mask_block_cnt,
|
| 709 |
+
curr_mask_block_idx,
|
| 710 |
+
curr_full_block_cnt,
|
| 711 |
+
curr_full_block_idx,
|
| 712 |
+
) = get_curr_blocksparse_tensors(
|
| 713 |
+
batch_idx,
|
| 714 |
+
head_idx,
|
| 715 |
+
m_block_sparse,
|
| 716 |
+
blocksparse_tensors,
|
| 717 |
+
seqlen_info,
|
| 718 |
+
kv_subtile_factor,
|
| 719 |
+
)
|
| 720 |
|
| 721 |
+
mask_begin, mask_end = split_block_range(
|
| 722 |
+
curr_mask_block_cnt * kv_subtile_factor,
|
| 723 |
+
split_idx,
|
| 724 |
+
num_splits,
|
| 725 |
+
)
|
| 726 |
+
full_begin, full_end = split_block_range(
|
| 727 |
+
curr_full_block_cnt * kv_subtile_factor,
|
| 728 |
+
split_idx,
|
| 729 |
+
num_splits,
|
| 730 |
+
)
|
| 731 |
+
mask_empty = mask_begin == mask_end
|
| 732 |
+
full_empty = full_begin == full_end
|
| 733 |
|
| 734 |
q_phase_flipped = False
|
| 735 |
|
|
|
|
| 737 |
# No masked blocks: process full list with Q loading
|
| 738 |
kv_producer_state = load_block_list_sm100(
|
| 739 |
curr_full_block_idx,
|
| 740 |
+
full_begin,
|
| 741 |
+
full_end,
|
| 742 |
load_q_with_first=True,
|
| 743 |
q_stage=q_stage,
|
| 744 |
kv_producer_state=kv_producer_state,
|
|
|
|
| 746 |
load_K=load_K,
|
| 747 |
load_V=load_V,
|
| 748 |
pipeline_kv=pipeline_kv,
|
| 749 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 750 |
)
|
| 751 |
q_phase_flipped = not full_empty
|
| 752 |
else:
|
| 753 |
# Process masked blocks with Q loading
|
| 754 |
kv_producer_state = load_block_list_sm100(
|
| 755 |
curr_mask_block_idx,
|
| 756 |
+
mask_begin,
|
| 757 |
+
mask_end,
|
| 758 |
load_q_with_first=True,
|
| 759 |
q_stage=q_stage,
|
| 760 |
kv_producer_state=kv_producer_state,
|
|
|
|
| 762 |
load_K=load_K,
|
| 763 |
load_V=load_V,
|
| 764 |
pipeline_kv=pipeline_kv,
|
| 765 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 766 |
)
|
| 767 |
q_phase_flipped = True
|
| 768 |
|
|
|
|
| 770 |
# Process full blocks without Q loading
|
| 771 |
kv_producer_state = load_block_list_sm100(
|
| 772 |
curr_full_block_idx,
|
| 773 |
+
full_begin,
|
| 774 |
+
full_end,
|
| 775 |
load_q_with_first=False,
|
| 776 |
q_stage=q_stage,
|
| 777 |
kv_producer_state=kv_producer_state,
|
|
|
|
| 779 |
load_K=load_K,
|
| 780 |
load_V=load_V,
|
| 781 |
pipeline_kv=pipeline_kv,
|
| 782 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 783 |
)
|
| 784 |
|
| 785 |
if q_phase_flipped:
|
|
|
|
| 794 |
batch_idx,
|
| 795 |
head_idx,
|
| 796 |
m_block,
|
| 797 |
+
split_idx: Int32,
|
| 798 |
+
num_splits: Int32,
|
| 799 |
qhead_per_kvhead: cutlass.Constexpr,
|
| 800 |
q_subtile_factor: cutlass.Constexpr,
|
| 801 |
+
seqlen_info: SeqlenInfoQK,
|
| 802 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 803 |
):
|
| 804 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 805 |
+
(
|
| 806 |
+
curr_mask_block_cnt,
|
| 807 |
+
_,
|
| 808 |
+
curr_full_block_cnt,
|
| 809 |
+
_,
|
| 810 |
+
) = get_curr_blocksparse_tensors(
|
| 811 |
+
batch_idx,
|
| 812 |
+
head_idx,
|
| 813 |
+
m_block_sparse,
|
| 814 |
+
blocksparse_tensors,
|
| 815 |
+
seqlen_info,
|
| 816 |
+
kv_subtile_factor,
|
| 817 |
+
)
|
| 818 |
|
| 819 |
+
mask_begin, mask_end = split_block_range(
|
| 820 |
+
curr_mask_block_cnt * kv_subtile_factor,
|
| 821 |
+
split_idx,
|
| 822 |
+
num_splits,
|
| 823 |
+
)
|
| 824 |
+
full_begin, full_end = split_block_range(
|
| 825 |
+
curr_full_block_cnt * kv_subtile_factor,
|
| 826 |
+
split_idx,
|
| 827 |
+
num_splits,
|
| 828 |
+
)
|
| 829 |
+
return mask_end - mask_begin + full_end - full_begin
|
| 830 |
|
| 831 |
|
| 832 |
@cute.jit
|
|
|
|
| 839 |
is_split_kv: cutlass.Constexpr,
|
| 840 |
learnable_sink,
|
| 841 |
mLSE,
|
| 842 |
+
seqlen_info,
|
| 843 |
m_block: Int32,
|
| 844 |
head_idx: Int32,
|
| 845 |
batch_idx: Int32,
|
|
|
|
| 847 |
sScale: cute.Tensor,
|
| 848 |
stats: list,
|
| 849 |
correction_epilogue: Callable,
|
| 850 |
+
thr_mma_pv: cute.ThrMma,
|
| 851 |
tOtO: cute.Tensor,
|
| 852 |
sO: cute.Tensor,
|
| 853 |
pipeline_sm_stats: cutlass.pipeline.PipelineAsync,
|
|
|
|
| 857 |
o_corr_consumer_phase: Int32,
|
| 858 |
corr_epi_producer_phase: Int32,
|
| 859 |
softmax_scale_log2: Float32,
|
| 860 |
+
max_offset: Float32,
|
| 861 |
+
max_offset_scale: Float32,
|
| 862 |
mO_cur: Optional[cute.Tensor] = None,
|
| 863 |
gO: Optional[cute.Tensor] = None,
|
| 864 |
gmem_tiled_copy_O: Optional[cute.TiledCopy] = None,
|
|
|
|
| 898 |
if sink_val != -Float32.inf and (const_expr(not is_split_kv) or split_idx == 0):
|
| 899 |
if row_max_value == -Float32.inf:
|
| 900 |
row_max_value = sink_val * (LOG2_E / softmax_scale_log2)
|
| 901 |
+
row_sum_value = max_offset_scale
|
| 902 |
else:
|
| 903 |
row_sum_value = row_sum_value + cute.math.exp2(
|
| 904 |
+
sink_val * LOG2_E - row_max_value * softmax_scale_log2 + max_offset,
|
| 905 |
+
fastmath=True,
|
| 906 |
)
|
| 907 |
if tidx < m_block_size:
|
| 908 |
scale_row_idx = tidx + stage * m_block_size
|
|
|
|
| 919 |
|
| 920 |
if const_expr(gmem_tiled_copy_O is None):
|
| 921 |
pipeline_o_epi.producer_acquire_w_index_phase(stage, corr_epi_producer_phase)
|
| 922 |
+
|
| 923 |
+
gO_stage = gO[None, None, stage] if const_expr(gO is not None) else None
|
| 924 |
correction_epilogue(
|
| 925 |
thr_mma_pv,
|
| 926 |
tOtO[None, None, None, stage],
|
| 927 |
tidx,
|
| 928 |
stage,
|
| 929 |
m_block,
|
| 930 |
+
seqlen_info.seqlen_q,
|
| 931 |
Float32(0.0), # zero scale ensures empty tile writes zeros into staged outputs
|
| 932 |
sO[None, None, stage],
|
| 933 |
mO_cur,
|
| 934 |
+
gO_stage,
|
| 935 |
gmem_tiled_copy_O,
|
| 936 |
)
|
| 937 |
if const_expr(gmem_tiled_copy_O is None):
|
|
|
|
| 947 |
)
|
| 948 |
|
| 949 |
|
| 950 |
+
@cute.jit
|
| 951 |
+
def softmax_block_sparse_sm100_list(
|
| 952 |
+
block_indices: cute.Tensor,
|
| 953 |
+
block_end,
|
| 954 |
+
split_block_cnt,
|
| 955 |
+
kv_subtile_factor: cutlass.Constexpr[int],
|
| 956 |
+
softmax_step: Callable,
|
| 957 |
+
mask_fn_base: Callable,
|
| 958 |
+
mma_si_consumer_phase: Int32,
|
| 959 |
+
si_corr_producer_phase: Int32,
|
| 960 |
+
s0_s1_sequence_phase: Int32,
|
| 961 |
+
check_m_boundary: bool,
|
| 962 |
+
is_first_block: bool,
|
| 963 |
+
allow_unmasked_inner_blocks: cutlass.Constexpr[bool],
|
| 964 |
+
):
|
| 965 |
+
"""Run one reverse sparse list while masking only the first coarse KV fragment."""
|
| 966 |
+
mask_fn_seqlen = partial(mask_fn_base, mask_seqlen=True, check_q_boundary=check_m_boundary)
|
| 967 |
+
physical_n_block = partial(
|
| 968 |
+
sparse_physical_n_block_forward, block_indices, kv_subtile_factor=kv_subtile_factor
|
| 969 |
+
)
|
| 970 |
+
|
| 971 |
+
n_block = physical_n_block(block_end - 1)
|
| 972 |
+
if is_first_block:
|
| 973 |
+
(
|
| 974 |
+
mma_si_consumer_phase,
|
| 975 |
+
si_corr_producer_phase,
|
| 976 |
+
s0_s1_sequence_phase,
|
| 977 |
+
) = softmax_step(
|
| 978 |
+
mma_si_consumer_phase,
|
| 979 |
+
si_corr_producer_phase,
|
| 980 |
+
s0_s1_sequence_phase,
|
| 981 |
+
n_block,
|
| 982 |
+
is_first=True,
|
| 983 |
+
mask_fn=mask_fn_seqlen,
|
| 984 |
+
)
|
| 985 |
+
else:
|
| 986 |
+
(
|
| 987 |
+
mma_si_consumer_phase,
|
| 988 |
+
si_corr_producer_phase,
|
| 989 |
+
s0_s1_sequence_phase,
|
| 990 |
+
) = softmax_step(
|
| 991 |
+
mma_si_consumer_phase,
|
| 992 |
+
si_corr_producer_phase,
|
| 993 |
+
s0_s1_sequence_phase,
|
| 994 |
+
n_block,
|
| 995 |
+
is_first=False,
|
| 996 |
+
mask_fn=mask_fn_seqlen,
|
| 997 |
+
)
|
| 998 |
+
|
| 999 |
+
first_fragment_count = cutlass.min(
|
| 1000 |
+
split_block_cnt,
|
| 1001 |
+
(block_end - 1) % kv_subtile_factor + 1,
|
| 1002 |
+
)
|
| 1003 |
+
for j in cutlass.range_constexpr(1, kv_subtile_factor):
|
| 1004 |
+
if j < first_fragment_count:
|
| 1005 |
+
(
|
| 1006 |
+
mma_si_consumer_phase,
|
| 1007 |
+
si_corr_producer_phase,
|
| 1008 |
+
s0_s1_sequence_phase,
|
| 1009 |
+
) = softmax_step(
|
| 1010 |
+
mma_si_consumer_phase,
|
| 1011 |
+
si_corr_producer_phase,
|
| 1012 |
+
s0_s1_sequence_phase,
|
| 1013 |
+
physical_n_block(block_end - 1 - j),
|
| 1014 |
+
mask_fn=mask_fn_seqlen,
|
| 1015 |
+
)
|
| 1016 |
+
for i in cutlass.range(first_fragment_count, split_block_cnt):
|
| 1017 |
+
(
|
| 1018 |
+
mma_si_consumer_phase,
|
| 1019 |
+
si_corr_producer_phase,
|
| 1020 |
+
s0_s1_sequence_phase,
|
| 1021 |
+
) = softmax_step(
|
| 1022 |
+
mma_si_consumer_phase,
|
| 1023 |
+
si_corr_producer_phase,
|
| 1024 |
+
s0_s1_sequence_phase,
|
| 1025 |
+
physical_n_block(block_end - 1 - i),
|
| 1026 |
+
mask_fn=None
|
| 1027 |
+
if const_expr(allow_unmasked_inner_blocks and check_m_boundary is False)
|
| 1028 |
+
else partial(
|
| 1029 |
+
mask_fn_base,
|
| 1030 |
+
mask_seqlen=False,
|
| 1031 |
+
check_q_boundary=check_m_boundary,
|
| 1032 |
+
),
|
| 1033 |
+
)
|
| 1034 |
+
|
| 1035 |
+
return mma_si_consumer_phase, si_corr_producer_phase, s0_s1_sequence_phase
|
| 1036 |
+
|
| 1037 |
+
|
| 1038 |
@cute.jit
|
| 1039 |
def softmax_block_sparse_sm100(
|
| 1040 |
blocksparse_tensors: BlockSparseTensors,
|
| 1041 |
batch_idx,
|
| 1042 |
head_idx,
|
| 1043 |
m_block,
|
| 1044 |
+
seqlen_info: SeqlenInfoQK,
|
| 1045 |
+
split_idx: Int32,
|
| 1046 |
+
num_splits: Int32,
|
| 1047 |
softmax_step: Callable,
|
| 1048 |
mask_fn: Callable,
|
| 1049 |
mask_fn_none: Callable,
|
|
|
|
| 1057 |
check_m_boundary: bool,
|
| 1058 |
qhead_per_kvhead: cutlass.Constexpr,
|
| 1059 |
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 1060 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 1061 |
):
|
| 1062 |
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % 4
|
| 1063 |
m_block_sparse = sparse_tensor_m_block(m_block, qhead_per_kvhead, q_subtile_factor)
|
| 1064 |
|
| 1065 |
+
(
|
| 1066 |
+
curr_mask_block_cnt,
|
| 1067 |
+
curr_mask_block_idx,
|
| 1068 |
+
curr_full_block_cnt,
|
| 1069 |
+
curr_full_block_idx,
|
| 1070 |
+
) = get_curr_blocksparse_tensors(
|
| 1071 |
+
batch_idx,
|
| 1072 |
+
head_idx,
|
| 1073 |
+
m_block_sparse,
|
| 1074 |
+
blocksparse_tensors,
|
| 1075 |
+
seqlen_info,
|
| 1076 |
+
kv_subtile_factor,
|
| 1077 |
+
)
|
| 1078 |
|
| 1079 |
+
mask_begin, mask_end = split_block_range(
|
| 1080 |
+
curr_mask_block_cnt * kv_subtile_factor,
|
| 1081 |
+
split_idx,
|
| 1082 |
+
num_splits,
|
| 1083 |
+
)
|
| 1084 |
+
full_begin, full_end = split_block_range(
|
| 1085 |
+
curr_full_block_cnt * kv_subtile_factor,
|
| 1086 |
+
split_idx,
|
| 1087 |
+
num_splits,
|
| 1088 |
+
)
|
| 1089 |
+
split_mask_block_cnt = mask_end - mask_begin
|
| 1090 |
+
split_full_block_cnt = full_end - full_begin
|
| 1091 |
+
total_block_cnt = split_mask_block_cnt + split_full_block_cnt
|
| 1092 |
|
| 1093 |
if total_block_cnt == 0:
|
|
|
|
|
|
|
| 1094 |
sm_stats_barrier.arrive_w_index(index=stage_idx * 4 + warp_idx)
|
| 1095 |
else:
|
| 1096 |
+
if split_mask_block_cnt > 0:
|
|
|
|
| 1097 |
(
|
| 1098 |
mma_si_consumer_phase,
|
| 1099 |
si_corr_producer_phase,
|
| 1100 |
s0_s1_sequence_phase,
|
| 1101 |
+
) = softmax_block_sparse_sm100_list(
|
| 1102 |
+
curr_mask_block_idx,
|
| 1103 |
+
mask_end,
|
| 1104 |
+
split_mask_block_cnt,
|
| 1105 |
+
kv_subtile_factor,
|
| 1106 |
+
softmax_step,
|
| 1107 |
+
mask_fn,
|
| 1108 |
mma_si_consumer_phase,
|
| 1109 |
si_corr_producer_phase,
|
| 1110 |
s0_s1_sequence_phase,
|
| 1111 |
+
check_m_boundary,
|
| 1112 |
+
True,
|
| 1113 |
+
False,
|
| 1114 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1115 |
|
| 1116 |
+
if split_full_block_cnt > 0:
|
| 1117 |
+
(
|
| 1118 |
+
mma_si_consumer_phase,
|
| 1119 |
+
si_corr_producer_phase,
|
| 1120 |
+
s0_s1_sequence_phase,
|
| 1121 |
+
) = softmax_block_sparse_sm100_list(
|
| 1122 |
+
curr_full_block_idx,
|
| 1123 |
+
full_end,
|
| 1124 |
+
split_full_block_cnt,
|
| 1125 |
+
kv_subtile_factor,
|
| 1126 |
+
softmax_step,
|
| 1127 |
+
mask_fn_none,
|
| 1128 |
+
mma_si_consumer_phase,
|
| 1129 |
+
si_corr_producer_phase,
|
| 1130 |
+
s0_s1_sequence_phase,
|
| 1131 |
+
check_m_boundary,
|
| 1132 |
+
split_mask_block_cnt == 0,
|
| 1133 |
+
True,
|
| 1134 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1135 |
|
| 1136 |
return (
|
| 1137 |
mma_si_consumer_phase,
|
|
|
|
| 1161 |
batch_idx,
|
| 1162 |
head_idx,
|
| 1163 |
n_block,
|
| 1164 |
+
q_subtile_factor: cutlass.Constexpr = 1,
|
| 1165 |
m_block_max: int = 0,
|
| 1166 |
):
|
| 1167 |
"""Count total tile iterations for given n_block (KV tile) in backward."""
|
| 1168 |
+
q_block_cnt, _, full_block_cnt, _, *_ = blocksparse_tensors
|
| 1169 |
total = q_block_cnt[batch_idx, head_idx, n_block]
|
| 1170 |
if const_expr(full_block_cnt is not None):
|
| 1171 |
total = total + full_block_cnt[batch_idx, head_idx, n_block]
|
| 1172 |
+
return total * q_subtile_factor
|
| 1173 |
|
| 1174 |
|
| 1175 |
@cute.jit
|
| 1176 |
+
def produce_block_sparse_q_loads_bwd_sm100_default(
|
| 1177 |
blocksparse_tensors: BlockSparseTensors,
|
| 1178 |
batch_idx,
|
| 1179 |
head_idx,
|
| 1180 |
n_block,
|
| 1181 |
+
# Pipeline states returned after advancing
|
| 1182 |
producer_state_Q_LSE,
|
| 1183 |
producer_state_dO_dPsum,
|
| 1184 |
# Pipelines
|
|
|
|
| 1204 |
should_load_Q: cutlass.Constexpr,
|
| 1205 |
should_load_dO: cutlass.Constexpr,
|
| 1206 |
# Subtiling factor and bounds
|
| 1207 |
+
q_subtile_factor: cutlass.Constexpr = 1,
|
| 1208 |
m_block_max: int = 0,
|
| 1209 |
+
# Optional 2CTA state for hdim <= 128
|
| 1210 |
+
use_2cta_instrs: cutlass.Constexpr = False,
|
| 1211 |
+
producer_state_Qt=None,
|
| 1212 |
+
producer_state_Kt=None,
|
| 1213 |
+
pipeline_Qt=None,
|
| 1214 |
+
pipeline_Kt=None,
|
| 1215 |
+
load_Qt=None,
|
| 1216 |
+
load_Kt=None,
|
| 1217 |
+
load_dOt=None,
|
| 1218 |
+
tma_copy_bytes_dO=0,
|
| 1219 |
):
|
| 1220 |
+
"""Produce SM100 backward block-sparse Q/dO loads for 1CTA and non-hdim192 2CTA."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1221 |
(
|
| 1222 |
curr_q_cnt,
|
| 1223 |
curr_q_idx,
|
|
|
|
| 1225 |
curr_full_idx,
|
| 1226 |
loop_count,
|
| 1227 |
) = get_block_sparse_iteration_info_bwd(
|
| 1228 |
+
blocksparse_tensors, batch_idx, head_idx, n_block, q_subtile_factor, m_block_max
|
| 1229 |
)
|
| 1230 |
+
# 2 cta peels the first loop for Qt path; so we guard the whole block if loopcount == 0
|
| 1231 |
+
if loop_count > Int32(0):
|
| 1232 |
+
first_m_block, _ = get_m_block_from_iter_bwd(
|
| 1233 |
+
Int32(0),
|
| 1234 |
curr_q_cnt,
|
| 1235 |
curr_q_idx,
|
| 1236 |
curr_full_cnt,
|
| 1237 |
curr_full_idx,
|
| 1238 |
+
q_subtile_factor=q_subtile_factor,
|
| 1239 |
+
m_block_max=m_block_max,
|
| 1240 |
)
|
| 1241 |
+
# with q_subtile > 1 we need to guard against fully OOB regions
|
| 1242 |
if m_block_max > 0:
|
| 1243 |
+
first_m_block = cutlass.min(first_m_block, m_block_max - 1)
|
| 1244 |
+
|
| 1245 |
+
if const_expr(should_load_Q):
|
| 1246 |
+
pipeline_Q.producer_acquire(producer_state_Q_LSE, extra_tx_count=tma_copy_bytes_K)
|
| 1247 |
+
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q_LSE))
|
| 1248 |
+
load_Q(first_m_block, producer_state=producer_state_Q_LSE)
|
| 1249 |
+
pipeline_Q.producer_commit(producer_state_Q_LSE)
|
| 1250 |
+
|
| 1251 |
+
pipeline_LSE.producer_acquire(producer_state_Q_LSE)
|
| 1252 |
+
with cute.arch.elect_one():
|
| 1253 |
+
copy_stats(
|
| 1254 |
+
gLSE[None, first_m_block],
|
| 1255 |
+
sLSE[None, producer_state_Q_LSE.index],
|
| 1256 |
+
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1257 |
)
|
| 1258 |
+
producer_state_Q_LSE.advance()
|
| 1259 |
+
|
| 1260 |
+
if const_expr(should_load_dO):
|
| 1261 |
+
pipeline_dO.producer_acquire(
|
| 1262 |
+
producer_state_dO_dPsum,
|
| 1263 |
+
extra_tx_count=tma_copy_bytes_V + tma_copy_bytes_dO
|
| 1264 |
+
if const_expr(load_dOt is not None)
|
| 1265 |
+
else tma_copy_bytes_V,
|
| 1266 |
+
)
|
| 1267 |
+
load_V(tma_bar_ptr=pipeline_dO.producer_get_barrier(producer_state_dO_dPsum))
|
| 1268 |
+
load_dO(first_m_block, producer_state=producer_state_dO_dPsum)
|
| 1269 |
+
if const_expr(load_dOt is not None):
|
| 1270 |
+
load_dOt(first_m_block, producer_state=producer_state_dO_dPsum)
|
| 1271 |
+
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 1272 |
+
|
| 1273 |
+
pipeline_dPsum.producer_acquire(producer_state_dO_dPsum)
|
| 1274 |
+
with cute.arch.elect_one():
|
| 1275 |
+
copy_stats(
|
| 1276 |
+
gdPsum[None, first_m_block],
|
| 1277 |
+
sdPsum[None, producer_state_dO_dPsum.index],
|
| 1278 |
+
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dO_dPsum),
|
| 1279 |
+
)
|
| 1280 |
+
producer_state_dO_dPsum.advance()
|
| 1281 |
+
|
| 1282 |
+
if const_expr(use_2cta_instrs):
|
| 1283 |
+
assert load_Kt is not None and pipeline_Kt is not None
|
| 1284 |
+
assert producer_state_Kt is not None
|
| 1285 |
+
pipeline_Kt.producer_acquire(producer_state_Kt)
|
| 1286 |
+
load_Kt(tma_bar_ptr=pipeline_Kt.producer_get_barrier(producer_state_Kt))
|
| 1287 |
+
pipeline_Kt.producer_commit(producer_state_Kt)
|
| 1288 |
+
producer_state_Kt.advance()
|
| 1289 |
+
|
| 1290 |
+
prev_m_block = first_m_block
|
| 1291 |
+
for iter_idx in cutlass.range(Int32(1), loop_count, unroll=1):
|
| 1292 |
+
m_block, _ = get_m_block_from_iter_bwd(
|
| 1293 |
+
iter_idx,
|
| 1294 |
+
curr_q_cnt,
|
| 1295 |
+
curr_q_idx,
|
| 1296 |
+
curr_full_cnt,
|
| 1297 |
+
curr_full_idx,
|
| 1298 |
+
q_subtile_factor=q_subtile_factor,
|
| 1299 |
+
m_block_max=m_block_max,
|
| 1300 |
+
)
|
| 1301 |
+
if m_block_max > 0:
|
| 1302 |
+
m_block = cutlass.min(m_block, m_block_max - 1)
|
| 1303 |
if const_expr(should_load_Q):
|
| 1304 |
+
if const_expr(load_Qt is not None):
|
| 1305 |
+
assert pipeline_Qt is not None and producer_state_Qt is not None
|
| 1306 |
+
pipeline_Qt.producer_acquire(producer_state_Qt)
|
| 1307 |
+
load_Qt(prev_m_block, producer_state=producer_state_Qt)
|
| 1308 |
+
pipeline_Qt.producer_commit(producer_state_Qt)
|
| 1309 |
+
producer_state_Qt.advance()
|
| 1310 |
+
|
| 1311 |
pipeline_Q.producer_acquire(producer_state_Q_LSE)
|
| 1312 |
+
load_Q(m_block, producer_state=producer_state_Q_LSE)
|
| 1313 |
pipeline_Q.producer_commit(producer_state_Q_LSE)
|
| 1314 |
+
|
| 1315 |
pipeline_LSE.producer_acquire(producer_state_Q_LSE)
|
| 1316 |
with cute.arch.elect_one():
|
| 1317 |
copy_stats(
|
| 1318 |
+
gLSE[None, m_block],
|
| 1319 |
sLSE[None, producer_state_Q_LSE.index],
|
| 1320 |
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_Q_LSE),
|
| 1321 |
)
|
| 1322 |
producer_state_Q_LSE.advance()
|
| 1323 |
+
|
| 1324 |
if const_expr(should_load_dO):
|
| 1325 |
+
pipeline_dO.producer_acquire(
|
| 1326 |
+
producer_state_dO_dPsum,
|
| 1327 |
+
extra_tx_count=tma_copy_bytes_dO if const_expr(load_dOt is not None) else 0,
|
| 1328 |
+
)
|
| 1329 |
+
load_dO(m_block, producer_state=producer_state_dO_dPsum)
|
| 1330 |
+
if const_expr(load_dOt is not None):
|
| 1331 |
+
load_dOt(m_block, producer_state=producer_state_dO_dPsum)
|
| 1332 |
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 1333 |
+
|
| 1334 |
pipeline_dPsum.producer_acquire(producer_state_dO_dPsum)
|
| 1335 |
with cute.arch.elect_one():
|
| 1336 |
copy_stats(
|
| 1337 |
+
gdPsum[None, m_block],
|
| 1338 |
sdPsum[None, producer_state_dO_dPsum.index],
|
| 1339 |
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dO_dPsum),
|
| 1340 |
)
|
| 1341 |
producer_state_dO_dPsum.advance()
|
| 1342 |
+
prev_m_block = m_block
|
| 1343 |
+
|
| 1344 |
+
if const_expr(should_load_Q):
|
| 1345 |
+
if const_expr(load_Qt is not None):
|
| 1346 |
+
assert pipeline_Qt is not None and producer_state_Qt is not None
|
| 1347 |
+
pipeline_Qt.producer_acquire(producer_state_Qt)
|
| 1348 |
+
load_Qt(prev_m_block, producer_state=producer_state_Qt)
|
| 1349 |
+
pipeline_Qt.producer_commit(producer_state_Qt)
|
| 1350 |
+
producer_state_Qt.advance()
|
| 1351 |
|
| 1352 |
+
pipeline_Q.producer_tail(producer_state_Q_LSE.clone())
|
| 1353 |
+
pipeline_LSE.producer_tail(producer_state_Q_LSE)
|
| 1354 |
+
if const_expr(load_Qt is not None):
|
| 1355 |
+
pipeline_Qt.producer_tail(producer_state_Qt)
|
| 1356 |
+
if const_expr(should_load_dO):
|
| 1357 |
+
pipeline_dO.producer_tail(producer_state_dO_dPsum.clone())
|
| 1358 |
+
pipeline_dPsum.producer_tail(producer_state_dO_dPsum)
|
| 1359 |
+
|
| 1360 |
+
return producer_state_Q_LSE, producer_state_dO_dPsum, producer_state_Qt, producer_state_Kt
|
| 1361 |
+
|
| 1362 |
+
|
| 1363 |
+
@cute.jit
|
| 1364 |
+
def produce_block_sparse_q_loads_bwd_sm100_2cta_hdim192(
|
| 1365 |
+
blocksparse_tensors: BlockSparseTensors,
|
| 1366 |
+
batch_idx,
|
| 1367 |
+
head_idx,
|
| 1368 |
+
n_block,
|
| 1369 |
+
# Pipeline states returned after advancing
|
| 1370 |
+
producer_state_Q_Qt,
|
| 1371 |
+
producer_state_O_Ot,
|
| 1372 |
+
producer_state_LSE,
|
| 1373 |
+
producer_state_dPsum,
|
| 1374 |
+
# Pipelines
|
| 1375 |
+
pipeline_Q,
|
| 1376 |
+
pipeline_LSE,
|
| 1377 |
+
pipeline_dO,
|
| 1378 |
+
pipeline_dPsum,
|
| 1379 |
+
pipeline_Qt,
|
| 1380 |
+
# Load functions
|
| 1381 |
+
load_K,
|
| 1382 |
+
load_V,
|
| 1383 |
+
load_Q,
|
| 1384 |
+
load_dO,
|
| 1385 |
+
load_Qt,
|
| 1386 |
+
load_Kt,
|
| 1387 |
+
load_dOt,
|
| 1388 |
+
copy_stats,
|
| 1389 |
+
# Global tensors for LSE/dPsum
|
| 1390 |
+
gLSE,
|
| 1391 |
+
sLSE,
|
| 1392 |
+
gdPsum,
|
| 1393 |
+
sdPsum,
|
| 1394 |
+
# TMA copy bytes for extra_tx_count
|
| 1395 |
+
tma_copy_bytes_K,
|
| 1396 |
+
tma_copy_bytes_V,
|
| 1397 |
+
# Subtiling factor and bounds
|
| 1398 |
+
q_subtile_factor: cutlass.Constexpr = 1,
|
| 1399 |
+
m_block_max: int = 0,
|
| 1400 |
+
):
|
| 1401 |
+
"""Produce SM100 backward block-sparse Q/dO loads for the hdim192 2CTA schedule."""
|
| 1402 |
+
(
|
| 1403 |
+
curr_q_cnt,
|
| 1404 |
+
curr_q_idx,
|
| 1405 |
+
curr_full_cnt,
|
| 1406 |
+
curr_full_idx,
|
| 1407 |
+
loop_count,
|
| 1408 |
+
) = get_block_sparse_iteration_info_bwd(
|
| 1409 |
+
blocksparse_tensors, batch_idx, head_idx, n_block, q_subtile_factor, m_block_max
|
| 1410 |
+
)
|
| 1411 |
+
|
| 1412 |
+
if loop_count > Int32(0):
|
| 1413 |
+
first_m_block, _ = get_m_block_from_iter_bwd(
|
| 1414 |
+
Int32(0),
|
| 1415 |
+
curr_q_cnt,
|
| 1416 |
+
curr_q_idx,
|
| 1417 |
+
curr_full_cnt,
|
| 1418 |
+
curr_full_idx,
|
| 1419 |
+
q_subtile_factor=q_subtile_factor,
|
| 1420 |
+
m_block_max=m_block_max,
|
| 1421 |
+
)
|
| 1422 |
+
|
| 1423 |
+
# with q_subtile > 1 we need to guard against fully OOB regions
|
| 1424 |
+
if m_block_max > 0:
|
| 1425 |
+
first_m_block = cutlass.min(first_m_block, m_block_max - 1)
|
| 1426 |
+
|
| 1427 |
+
# K & Q (for S)
|
| 1428 |
+
pipeline_Q.producer_acquire(
|
| 1429 |
+
producer_state_Q_Qt,
|
| 1430 |
+
extra_tx_count=tma_copy_bytes_K,
|
| 1431 |
+
)
|
| 1432 |
+
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q_Qt))
|
| 1433 |
+
load_Q(first_m_block, producer_state=producer_state_Q_Qt)
|
| 1434 |
+
pipeline_Q.producer_commit(producer_state_Q_Qt)
|
| 1435 |
+
producer_state_Q_Qt.advance()
|
| 1436 |
+
|
| 1437 |
+
# LSE
|
| 1438 |
+
pipeline_LSE.producer_acquire(producer_state_LSE)
|
| 1439 |
+
with cute.arch.elect_one():
|
| 1440 |
+
copy_stats(
|
| 1441 |
+
gLSE[None, first_m_block],
|
| 1442 |
+
sLSE[None, producer_state_LSE.index],
|
| 1443 |
+
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_LSE),
|
| 1444 |
+
)
|
| 1445 |
+
producer_state_LSE.advance()
|
| 1446 |
+
|
| 1447 |
+
# dOt + V, for dP.T = V @ dO.T
|
| 1448 |
+
pipeline_dO.producer_acquire(
|
| 1449 |
+
producer_state_O_Ot,
|
| 1450 |
+
extra_tx_count=tma_copy_bytes_V,
|
| 1451 |
+
)
|
| 1452 |
+
load_V(tma_bar_ptr=pipeline_dO.producer_get_barrier(producer_state_O_Ot))
|
| 1453 |
+
load_dOt(first_m_block, producer_state=producer_state_O_Ot)
|
| 1454 |
+
pipeline_dO.producer_commit(producer_state_O_Ot)
|
| 1455 |
+
producer_state_O_Ot.advance()
|
| 1456 |
+
|
| 1457 |
+
# dPsum
|
| 1458 |
+
pipeline_dPsum.producer_acquire(producer_state_dPsum)
|
| 1459 |
+
with cute.arch.elect_one():
|
| 1460 |
+
copy_stats(
|
| 1461 |
+
gdPsum[None, first_m_block],
|
| 1462 |
+
sdPsum[None, producer_state_dPsum.index],
|
| 1463 |
+
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dPsum),
|
| 1464 |
+
)
|
| 1465 |
+
producer_state_dPsum.advance()
|
| 1466 |
+
|
| 1467 |
+
# Qt, for dK = dS.T @ Q
|
| 1468 |
+
pipeline_Qt.producer_acquire(
|
| 1469 |
+
producer_state_Q_Qt,
|
| 1470 |
+
extra_tx_count=tma_copy_bytes_K,
|
| 1471 |
+
)
|
| 1472 |
+
load_Qt(first_m_block, producer_state=producer_state_Q_Qt)
|
| 1473 |
+
load_Kt(tma_bar_ptr=pipeline_Qt.producer_get_barrier(producer_state_Q_Qt))
|
| 1474 |
+
pipeline_Qt.producer_commit(producer_state_Q_Qt)
|
| 1475 |
+
producer_state_Q_Qt.advance()
|
| 1476 |
+
|
| 1477 |
+
# dO, for dV = P.T @ dO
|
| 1478 |
+
pipeline_dO.producer_acquire(producer_state_O_Ot)
|
| 1479 |
+
load_dO(first_m_block, producer_state=producer_state_O_Ot)
|
| 1480 |
+
pipeline_dO.producer_commit(producer_state_O_Ot)
|
| 1481 |
+
producer_state_O_Ot.advance()
|
| 1482 |
+
|
| 1483 |
+
# 2CTA: [lse | Q | dOt | dPsum | Qt | dO]
|
| 1484 |
+
for iter_idx in cutlass.range(Int32(1), loop_count, unroll=1):
|
| 1485 |
+
m_block, _ = get_m_block_from_iter_bwd(
|
| 1486 |
+
iter_idx,
|
| 1487 |
+
curr_q_cnt,
|
| 1488 |
+
curr_q_idx,
|
| 1489 |
+
curr_full_cnt,
|
| 1490 |
+
curr_full_idx,
|
| 1491 |
+
q_subtile_factor=q_subtile_factor,
|
| 1492 |
+
m_block_max=m_block_max,
|
| 1493 |
+
)
|
| 1494 |
+
if m_block_max > 0:
|
| 1495 |
+
m_block = cutlass.min(m_block, m_block_max - 1)
|
| 1496 |
+
|
| 1497 |
+
# LSE
|
| 1498 |
+
pipeline_LSE.producer_acquire(producer_state_LSE)
|
| 1499 |
+
with cute.arch.elect_one():
|
| 1500 |
+
copy_stats(
|
| 1501 |
+
gLSE[None, m_block],
|
| 1502 |
+
sLSE[None, producer_state_LSE.index],
|
| 1503 |
+
mbar_ptr=pipeline_LSE.producer_get_barrier(producer_state_LSE),
|
| 1504 |
+
)
|
| 1505 |
+
producer_state_LSE.advance()
|
| 1506 |
+
|
| 1507 |
+
# Q
|
| 1508 |
+
pipeline_Q.producer_acquire(producer_state_Q_Qt)
|
| 1509 |
+
load_Q(m_block, producer_state=producer_state_Q_Qt)
|
| 1510 |
+
pipeline_Q.producer_commit(producer_state_Q_Qt)
|
| 1511 |
+
producer_state_Q_Qt.advance()
|
| 1512 |
+
|
| 1513 |
+
# dPsum
|
| 1514 |
+
pipeline_dPsum.producer_acquire(producer_state_dPsum)
|
| 1515 |
+
with cute.arch.elect_one():
|
| 1516 |
+
copy_stats(
|
| 1517 |
+
gdPsum[None, m_block],
|
| 1518 |
+
sdPsum[None, producer_state_dPsum.index],
|
| 1519 |
+
mbar_ptr=pipeline_dPsum.producer_get_barrier(producer_state_dPsum),
|
| 1520 |
+
)
|
| 1521 |
+
producer_state_dPsum.advance()
|
| 1522 |
+
|
| 1523 |
+
# dOt, for dP.T = V @ dO.T
|
| 1524 |
+
pipeline_dO.producer_acquire(producer_state_O_Ot)
|
| 1525 |
+
load_dOt(m_block, producer_state=producer_state_O_Ot)
|
| 1526 |
+
pipeline_dO.producer_commit(producer_state_O_Ot)
|
| 1527 |
+
producer_state_O_Ot.advance()
|
| 1528 |
+
|
| 1529 |
+
# Qt, for dK = dS.T @ Q
|
| 1530 |
+
pipeline_Qt.producer_acquire(producer_state_Q_Qt)
|
| 1531 |
+
load_Qt(m_block, producer_state=producer_state_Q_Qt)
|
| 1532 |
+
pipeline_Qt.producer_commit(producer_state_Q_Qt)
|
| 1533 |
+
producer_state_Q_Qt.advance()
|
| 1534 |
+
|
| 1535 |
+
# dO, for dV = P.T @ dO
|
| 1536 |
+
pipeline_dO.producer_acquire(producer_state_O_Ot)
|
| 1537 |
+
load_dO(m_block, producer_state=producer_state_O_Ot)
|
| 1538 |
+
pipeline_dO.producer_commit(producer_state_O_Ot)
|
| 1539 |
+
producer_state_O_Ot.advance()
|
| 1540 |
+
|
| 1541 |
+
pipeline_Q.producer_tail(producer_state_Q_Qt)
|
| 1542 |
+
pipeline_LSE.producer_tail(producer_state_LSE)
|
| 1543 |
+
pipeline_dO.producer_tail(producer_state_O_Ot)
|
| 1544 |
+
pipeline_dPsum.producer_tail(producer_state_dPsum)
|
| 1545 |
+
|
| 1546 |
+
return producer_state_Q_Qt, producer_state_O_Ot, producer_state_LSE, producer_state_dPsum
|
| 1547 |
|
| 1548 |
|
| 1549 |
@cute.jit
|
|
|
|
| 1552 |
batch_idx,
|
| 1553 |
head_idx,
|
| 1554 |
n_block,
|
| 1555 |
+
q_subtile_factor: cutlass.Constexpr = 1,
|
| 1556 |
m_block_max: int = 0,
|
| 1557 |
):
|
| 1558 |
"""Extract block-sparse iteration info for backward pass.
|
| 1559 |
|
| 1560 |
Returns (curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count).
|
| 1561 |
"""
|
| 1562 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1563 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1564 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1565 |
|
|
|
|
| 1573 |
sparse_block_count = curr_q_cnt
|
| 1574 |
if const_expr(full_cnt is not None):
|
| 1575 |
sparse_block_count = sparse_block_count + curr_full_cnt
|
| 1576 |
+
total_count = sparse_block_count * q_subtile_factor
|
| 1577 |
|
| 1578 |
return curr_q_cnt, curr_q_idx, curr_full_cnt, curr_full_idx, total_count
|
| 1579 |
|
|
|
|
| 1585 |
curr_q_idx: cute.Tensor,
|
| 1586 |
curr_full_cnt,
|
| 1587 |
curr_full_idx: Optional[cute.Tensor],
|
| 1588 |
+
q_subtile_factor: cutlass.Constexpr = 1,
|
| 1589 |
m_block_max: int = 0,
|
| 1590 |
):
|
| 1591 |
"""Derive m_block index and is_full_block flag from iteration index.
|
|
|
|
| 1594 |
- m_block: The actual Q-tile block index
|
| 1595 |
- is_full_block: True if this is a full block (no mask_mod needed)
|
| 1596 |
"""
|
| 1597 |
+
sparse_iter_idx = iter_idx // q_subtile_factor
|
| 1598 |
+
subtile_offset = iter_idx % q_subtile_factor
|
| 1599 |
|
| 1600 |
sparse_m_block = Int32(0)
|
| 1601 |
is_full_block = False
|
|
|
|
| 1608 |
else:
|
| 1609 |
sparse_m_block = curr_q_idx[sparse_iter_idx]
|
| 1610 |
|
| 1611 |
+
return sparse_m_block * q_subtile_factor + subtile_offset, is_full_block
|
| 1612 |
|
| 1613 |
|
| 1614 |
@cute.jit
|
|
|
|
| 1636 |
else:
|
| 1637 |
pipeline_Q.producer_acquire(producer_state_Q)
|
| 1638 |
load_Q(m_block, producer_state=producer_state_Q)
|
| 1639 |
+
if load_kv:
|
| 1640 |
+
# Wait for bwd preprocess to finish writing LSE and dPsum, and zeroing dQaccum. Same
|
| 1641 |
+
# point in the load order as the dense arm's wait in flash_bwd_sm90.load(): after this
|
| 1642 |
+
# block's K/Q loads, before the first load that reads preprocess output.
|
| 1643 |
+
# load_kv is the tile's first-block marker -- the caller flips kv_loaded in the same
|
| 1644 |
+
# branch that loads a block -- so this runs once per tile, like the dense arm's.
|
| 1645 |
+
cute.arch.griddepcontrol_wait()
|
| 1646 |
load_LSE(m_block, producer_state=producer_state_Q)
|
| 1647 |
|
| 1648 |
producer_state_dO_cur = (
|
|
|
|
| 1680 |
tma_copy_bytes_K,
|
| 1681 |
tma_copy_bytes_V,
|
| 1682 |
Q_stage_eq_dO_stage: cutlass.Constexpr,
|
| 1683 |
+
q_subtile_factor: cutlass.Constexpr,
|
| 1684 |
m_block_max: int,
|
| 1685 |
):
|
| 1686 |
"""SM90 backward block sparse loading with separate partial/full loops.
|
|
|
|
| 1690 |
|
| 1691 |
Returns updated (producer_state_Q, producer_state_dO).
|
| 1692 |
"""
|
| 1693 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1694 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1695 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1696 |
|
|
|
|
| 1703 |
|
| 1704 |
kv_loaded = False
|
| 1705 |
|
| 1706 |
+
for iter_idx in cutlass.range(curr_q_cnt * q_subtile_factor, unroll=1):
|
| 1707 |
+
sparse_idx = iter_idx // q_subtile_factor
|
| 1708 |
+
subtile_offset = iter_idx % q_subtile_factor
|
| 1709 |
+
m_block = curr_q_idx[sparse_idx] * q_subtile_factor + subtile_offset
|
| 1710 |
|
| 1711 |
if m_block < m_block_max:
|
| 1712 |
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
|
|
|
| 1729 |
kv_loaded = True
|
| 1730 |
|
| 1731 |
if const_expr(full_cnt is not None):
|
| 1732 |
+
for iter_idx in cutlass.range(curr_full_cnt * q_subtile_factor, unroll=1):
|
| 1733 |
+
sparse_idx = iter_idx // q_subtile_factor
|
| 1734 |
+
subtile_offset = iter_idx % q_subtile_factor
|
| 1735 |
+
m_block = curr_full_idx[sparse_idx] * q_subtile_factor + subtile_offset
|
| 1736 |
|
| 1737 |
if m_block < m_block_max:
|
| 1738 |
producer_state_Q, producer_state_dO = _load_q_do_block_sm90(
|
|
|
|
| 1773 |
thr_mma_SdP,
|
| 1774 |
score_mod_fn=None,
|
| 1775 |
score_mod_bwd_fn=None,
|
| 1776 |
+
q_subtile_factor: cutlass.Constexpr = 1,
|
| 1777 |
m_block_max: int = 0,
|
| 1778 |
+
aux_data: AuxData = AuxData(),
|
| 1779 |
fastdiv_mods=(None, None),
|
| 1780 |
):
|
| 1781 |
"""SM90 backward block sparse MMA consumption with separate partial/full loops.
|
|
|
|
| 1785 |
|
| 1786 |
Returns updated (consumer_state_Q, consumer_state_dO).
|
| 1787 |
"""
|
| 1788 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1789 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1790 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1791 |
|
|
|
|
| 1808 |
mask_causal=is_causal,
|
| 1809 |
mask_local=is_local,
|
| 1810 |
mask_mod=mask_mod,
|
| 1811 |
+
aux_data=aux_data,
|
| 1812 |
fastdiv_mods=fastdiv_mods,
|
| 1813 |
)
|
| 1814 |
|
|
|
|
| 1821 |
mask_seqlen=True,
|
| 1822 |
mask_causal=is_causal,
|
| 1823 |
mask_local=is_local,
|
| 1824 |
+
aux_data=aux_data,
|
| 1825 |
fastdiv_mods=fastdiv_mods,
|
| 1826 |
)
|
| 1827 |
|
| 1828 |
+
for iter_idx in cutlass.range(curr_q_cnt * q_subtile_factor, unroll=1):
|
| 1829 |
+
sparse_idx = iter_idx // q_subtile_factor
|
| 1830 |
+
subtile_offset = iter_idx % q_subtile_factor
|
| 1831 |
+
m_block = curr_q_idx[sparse_idx] * q_subtile_factor + subtile_offset
|
| 1832 |
|
| 1833 |
if m_block < m_block_max:
|
| 1834 |
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
|
|
|
| 1843 |
dKV_accumulate = True
|
| 1844 |
|
| 1845 |
if const_expr(full_cnt is not None):
|
| 1846 |
+
for iter_idx in cutlass.range(curr_full_cnt * q_subtile_factor, unroll=1):
|
| 1847 |
+
sparse_idx = iter_idx // q_subtile_factor
|
| 1848 |
+
subtile_offset = iter_idx % q_subtile_factor
|
| 1849 |
+
m_block = curr_full_idx[sparse_idx] * q_subtile_factor + subtile_offset
|
| 1850 |
|
| 1851 |
if m_block < m_block_max:
|
| 1852 |
consumer_state_Q, consumer_state_dO = mma_one_m_block_fn(
|
|
|
|
| 1868 |
m_block,
|
| 1869 |
sdQaccum: cute.Tensor,
|
| 1870 |
gdQaccum: cute.Tensor,
|
| 1871 |
+
num_dQ_warp_groups: cutlass.Constexpr,
|
| 1872 |
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1873 |
tma_copy_bytes_dQ,
|
| 1874 |
):
|
| 1875 |
"""Store dQaccum for a single m_block."""
|
| 1876 |
+
for warp_group_idx in cutlass.range_constexpr(num_dQ_warp_groups):
|
| 1877 |
+
cute.arch.cp_async_bulk_wait_group(num_dQ_warp_groups - 1 - warp_group_idx, read=True)
|
| 1878 |
cute.arch.barrier_arrive(
|
| 1879 |
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1880 |
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1881 |
)
|
| 1882 |
+
for warp_group_idx in cutlass.range_constexpr(num_dQ_warp_groups):
|
| 1883 |
cute.arch.barrier(
|
| 1884 |
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1885 |
number_of_threads=num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
|
|
|
| 1887 |
with cute.arch.elect_one():
|
| 1888 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1889 |
sdQaccum[None, warp_group_idx].iterator,
|
| 1890 |
+
gdQaccum[(None, warp_group_idx), m_block].iterator,
|
| 1891 |
tma_copy_bytes_dQ,
|
| 1892 |
)
|
| 1893 |
cute.arch.cp_async_bulk_commit_group()
|
|
|
|
| 1901 |
n_block,
|
| 1902 |
sdQaccum: cute.Tensor,
|
| 1903 |
gdQaccum: cute.Tensor,
|
| 1904 |
+
q_subtile_factor: cutlass.Constexpr,
|
| 1905 |
m_block_max: int,
|
| 1906 |
+
num_dQ_warp_groups: cutlass.Constexpr,
|
| 1907 |
num_threads_per_warp_group: cutlass.Constexpr,
|
| 1908 |
tma_copy_bytes_dQ,
|
| 1909 |
):
|
|
|
|
| 1911 |
|
| 1912 |
Iterates partial blocks first, then full blocks, matching producer/consumer order.
|
| 1913 |
"""
|
| 1914 |
+
q_cnt, q_idx, full_cnt, full_idx, *_ = blocksparse_tensors
|
| 1915 |
curr_q_cnt = q_cnt[batch_idx, head_idx, n_block]
|
| 1916 |
curr_q_idx = q_idx[batch_idx, head_idx, n_block, None]
|
| 1917 |
|
|
|
|
| 1922 |
curr_full_cnt = Int32(0)
|
| 1923 |
curr_full_idx = None
|
| 1924 |
|
| 1925 |
+
for iter_idx in cutlass.range(curr_q_cnt * q_subtile_factor, unroll=1):
|
| 1926 |
+
sparse_idx = iter_idx // q_subtile_factor
|
| 1927 |
+
subtile_offset = iter_idx % q_subtile_factor
|
| 1928 |
+
m_block = curr_q_idx[sparse_idx] * q_subtile_factor + subtile_offset
|
| 1929 |
|
| 1930 |
if m_block < m_block_max:
|
| 1931 |
_store_one_dQaccum_sm90(
|
| 1932 |
m_block,
|
| 1933 |
sdQaccum,
|
| 1934 |
gdQaccum,
|
| 1935 |
+
num_dQ_warp_groups,
|
| 1936 |
num_threads_per_warp_group,
|
| 1937 |
tma_copy_bytes_dQ,
|
| 1938 |
)
|
| 1939 |
|
| 1940 |
if const_expr(full_cnt is not None):
|
| 1941 |
+
for iter_idx in cutlass.range(curr_full_cnt * q_subtile_factor, unroll=1):
|
| 1942 |
+
sparse_idx = iter_idx // q_subtile_factor
|
| 1943 |
+
subtile_offset = iter_idx % q_subtile_factor
|
| 1944 |
+
m_block = curr_full_idx[sparse_idx] * q_subtile_factor + subtile_offset
|
| 1945 |
|
| 1946 |
if m_block < m_block_max:
|
| 1947 |
_store_one_dQaccum_sm90(
|
| 1948 |
m_block,
|
| 1949 |
sdQaccum,
|
| 1950 |
gdQaccum,
|
| 1951 |
+
num_dQ_warp_groups,
|
| 1952 |
num_threads_per_warp_group,
|
| 1953 |
tma_copy_bytes_dQ,
|
| 1954 |
)
|
build/torch-cuda/block_sparsity.py
CHANGED
|
@@ -17,13 +17,23 @@ def ceildiv(a: int, b: int) -> int:
|
|
| 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 |
|
| 23 |
def __new_from_mlir_values__(self, values):
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
class BlockSparseTensorsTorch(NamedTuple):
|
|
@@ -31,7 +41,184 @@ class BlockSparseTensorsTorch(NamedTuple):
|
|
| 31 |
mask_block_idx: torch.Tensor
|
| 32 |
full_block_cnt: torch.Tensor | None = None
|
| 33 |
full_block_idx: torch.Tensor | None = None
|
|
|
|
|
|
|
| 34 |
block_size: tuple[int, int] | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
|
| 37 |
def _expand_sparsity_tensor(
|
|
@@ -61,8 +248,8 @@ def _check_and_expand_block(
|
|
| 61 |
name: str,
|
| 62 |
cnt: torch.Tensor | None,
|
| 63 |
idx: torch.Tensor | None,
|
| 64 |
-
expected_count_shape: Tuple[int,
|
| 65 |
-
expected_index_shape: Tuple[int,
|
| 66 |
context: str | None,
|
| 67 |
hint: str | Callable[[], str] | None,
|
| 68 |
) -> Tuple[torch.Tensor | None, torch.Tensor | None]:
|
|
@@ -81,12 +268,37 @@ def _check_and_expand_block(
|
|
| 81 |
expanded_cnt = _expand_sparsity_tensor(
|
| 82 |
cnt, expected_count_shape, f"{name}_block_cnt", context, hint
|
| 83 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
expanded_idx = _expand_sparsity_tensor(
|
| 85 |
idx, expected_index_shape, f"{name}_block_idx", context, hint
|
| 86 |
)
|
| 87 |
return expanded_cnt, expanded_idx
|
| 88 |
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
def get_block_sparse_expected_shapes(
|
| 91 |
batch_size: int,
|
| 92 |
num_head: int,
|
|
@@ -124,7 +336,7 @@ def infer_block_sparse_expected_shapes(
|
|
| 124 |
Expectations:
|
| 125 |
- mask_block_cnt is (B, H, M) and mask_block_idx is (B, H, M, N).
|
| 126 |
- Batch/head dims may be 1 for broadcast, or match the requested sizes.
|
| 127 |
-
- sparse_block_size_kv must
|
| 128 |
- sparse_block_size_q must be a multiple of q_stage * tile_m.
|
| 129 |
- If sparse_block_size_q is omitted and seqlen_q/num_m_blocks is ambiguous,
|
| 130 |
the caller must provide block_size to disambiguate. TODO will make this required in a future PR.
|
|
@@ -133,24 +345,23 @@ def infer_block_sparse_expected_shapes(
|
|
| 133 |
base_n_block = n_block_size
|
| 134 |
if sparse_block_size_kv is None:
|
| 135 |
sparse_block_size_kv = base_n_block
|
| 136 |
-
if sparse_block_size_kv !=
|
| 137 |
-
raise ValueError(
|
|
|
|
|
|
|
| 138 |
if tensors.mask_block_idx is None:
|
| 139 |
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 140 |
num_m_blocks = tensors.mask_block_idx.shape[2]
|
| 141 |
|
| 142 |
if sparse_block_size_q is None:
|
| 143 |
-
|
| 144 |
-
if
|
| 145 |
-
max_block_size = seqlen_q
|
| 146 |
-
else:
|
| 147 |
-
max_block_size = (seqlen_q - 1) // (num_m_blocks - 1)
|
| 148 |
-
if max_block_size != min_block_size and base_m_block != 1:
|
| 149 |
raise ValueError(
|
| 150 |
f"Block sparse tensors{context} require explicit sparse_block_size[0] "
|
| 151 |
f"to disambiguate block size for seqlen_q={seqlen_q} and num_m_blocks={num_m_blocks}."
|
| 152 |
)
|
| 153 |
-
sparse_block_size_q
|
|
|
|
| 154 |
|
| 155 |
if sparse_block_size_q % base_m_block != 0:
|
| 156 |
raise ValueError(
|
|
@@ -186,9 +397,11 @@ def infer_block_sparse_expected_shapes(
|
|
| 186 |
raise ValueError(f"Block sparse tensors{context} {dim_name} dim must be {tgt} or 1.")
|
| 187 |
if mask_block_cnt.shape[2] != mask_block_idx.shape[2]:
|
| 188 |
raise ValueError(f"Block sparse tensors{context} must share the same m-block dimension.")
|
| 189 |
-
|
|
|
|
|
|
|
| 190 |
raise ValueError(
|
| 191 |
-
f"Block sparse tensors{context} n-block dimension must be {expected_n_blocks}."
|
| 192 |
)
|
| 193 |
if expected_m_blocks != num_m_blocks:
|
| 194 |
raise ValueError(
|
|
@@ -206,17 +419,19 @@ def get_block_sparse_expected_shapes_bwd(
|
|
| 206 |
seqlen_k: int,
|
| 207 |
m_block_size: int,
|
| 208 |
n_block_size: int,
|
| 209 |
-
|
|
|
|
| 210 |
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int]]:
|
| 211 |
"""Return (expected_count_shape, expected_index_shape) for backward block sparse normalization.
|
| 212 |
|
| 213 |
Backward uses Q-direction indexing (transposed from forward), where shapes are
|
| 214 |
indexed by N-blocks first, then M-blocks. The sparse_block_size_q is determined
|
| 215 |
-
by
|
| 216 |
"""
|
| 217 |
-
sparse_block_size_q =
|
|
|
|
| 218 |
expected_m_blocks = ceildiv(seqlen_q, sparse_block_size_q)
|
| 219 |
-
expected_n_blocks = ceildiv(seqlen_k,
|
| 220 |
expected_count_shape = (batch_size, num_head, expected_n_blocks)
|
| 221 |
expected_index_shape = (batch_size, num_head, expected_n_blocks, expected_m_blocks)
|
| 222 |
return expected_count_shape, expected_index_shape
|
|
@@ -225,8 +440,8 @@ def get_block_sparse_expected_shapes_bwd(
|
|
| 225 |
def normalize_block_sparse_tensors(
|
| 226 |
tensors: BlockSparseTensorsTorch,
|
| 227 |
*,
|
| 228 |
-
expected_count_shape: Tuple[int,
|
| 229 |
-
expected_index_shape: Tuple[int,
|
| 230 |
context: str | None = None,
|
| 231 |
hint: str | Callable[[], str] | None = None,
|
| 232 |
) -> BlockSparseTensorsTorch:
|
|
@@ -257,12 +472,39 @@ def normalize_block_sparse_tensors(
|
|
| 257 |
if full_cnt is not None and mask_cnt.device != full_cnt.device:
|
| 258 |
raise ValueError("All block sparse tensors must be on the same device")
|
| 259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
return BlockSparseTensorsTorch(
|
| 261 |
mask_block_cnt=mask_cnt,
|
| 262 |
mask_block_idx=mask_idx,
|
| 263 |
full_block_cnt=full_cnt,
|
| 264 |
full_block_idx=full_idx,
|
|
|
|
|
|
|
| 265 |
block_size=tensors.block_size,
|
|
|
|
|
|
|
|
|
|
| 266 |
)
|
| 267 |
|
| 268 |
|
|
@@ -294,6 +536,8 @@ def get_block_sparse_broadcast_pattern(
|
|
| 294 |
tensors.mask_block_idx,
|
| 295 |
tensors.full_block_cnt,
|
| 296 |
tensors.full_block_idx,
|
|
|
|
|
|
|
| 297 |
):
|
| 298 |
if tensor is not None:
|
| 299 |
patterns.append(get_broadcast_dims(tensor))
|
|
@@ -302,6 +546,15 @@ def get_block_sparse_broadcast_pattern(
|
|
| 302 |
return tuple(patterns)
|
| 303 |
|
| 304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
def normalize_block_sparse_config(
|
| 306 |
tensors: BlockSparseTensorsTorch,
|
| 307 |
*,
|
|
@@ -311,40 +564,67 @@ def normalize_block_sparse_config(
|
|
| 311 |
seqlen_k: int,
|
| 312 |
block_size: tuple[int, int],
|
| 313 |
q_stage: int,
|
| 314 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
m_block_size, n_block_size = block_size
|
| 316 |
if tensors.block_size is None:
|
| 317 |
-
sparse_block_size_q, sparse_block_size_kv =
|
| 318 |
else:
|
| 319 |
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 320 |
-
if sparse_block_size_kv !=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
raise ValueError(
|
| 322 |
f"Block sparsity requires sparse_block_size[1]={n_block_size} to match tile_n."
|
| 323 |
)
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
)
|
| 338 |
-
)
|
| 339 |
normalized_tensors = normalize_block_sparse_tensors(
|
| 340 |
tensors,
|
| 341 |
expected_count_shape=expected_count_shape,
|
| 342 |
expected_index_shape=expected_index_shape,
|
| 343 |
)
|
| 344 |
-
return (
|
| 345 |
-
normalized_tensors,
|
| 346 |
-
get_block_sparse_broadcast_pattern(normalized_tensors),
|
| 347 |
-
q_subtile_factor,
|
|
|
|
| 348 |
)
|
| 349 |
|
| 350 |
|
|
@@ -356,21 +636,24 @@ def normalize_block_sparse_config_bwd(
|
|
| 356 |
seqlen_q: int,
|
| 357 |
seqlen_k: int,
|
| 358 |
block_size: tuple[int, int],
|
| 359 |
-
|
|
|
|
| 360 |
) -> tuple[BlockSparseTensorsTorch, Tuple[Tuple[bool, ...], ...] | None]:
|
| 361 |
m_block_size, n_block_size = block_size
|
| 362 |
if tensors.block_size is None:
|
| 363 |
-
sparse_block_size_q, sparse_block_size_kv =
|
| 364 |
else:
|
| 365 |
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 366 |
-
if sparse_block_size_q !=
|
| 367 |
raise ValueError(
|
| 368 |
-
f"Block sparsity expects sparse_block_size_q={
|
| 369 |
-
f"for
|
| 370 |
)
|
| 371 |
-
|
|
|
|
| 372 |
raise ValueError(
|
| 373 |
-
f"Block sparsity expects sparse_block_size[1]={
|
|
|
|
| 374 |
)
|
| 375 |
expected_count_shape, expected_index_shape = get_block_sparse_expected_shapes_bwd(
|
| 376 |
batch_size,
|
|
@@ -379,7 +662,8 @@ def normalize_block_sparse_config_bwd(
|
|
| 379 |
seqlen_k,
|
| 380 |
m_block_size,
|
| 381 |
n_block_size,
|
| 382 |
-
|
|
|
|
| 383 |
)
|
| 384 |
normalized_tensors = normalize_block_sparse_tensors(
|
| 385 |
tensors,
|
|
@@ -389,7 +673,7 @@ def normalize_block_sparse_config_bwd(
|
|
| 389 |
hint=lambda: (
|
| 390 |
f"Backward expects Q-direction block-sparse tensors (q_mask_cnt/q_mask_idx, "
|
| 391 |
f"and optionally full_q_cnt/full_q_idx). Regenerate the backward BlockMask with "
|
| 392 |
-
f"BLOCK_SIZE=({
|
| 393 |
),
|
| 394 |
)
|
| 395 |
return normalized_tensors, get_block_sparse_broadcast_pattern(normalized_tensors)
|
|
@@ -401,29 +685,27 @@ def to_cute_block_sparse_tensors(
|
|
| 401 |
"""Convert torch block sparsity tensors to CuTe tensors, optionally for tvm ffi"""
|
| 402 |
if not is_block_sparsity_enabled(tensors):
|
| 403 |
return None
|
| 404 |
-
|
| 405 |
-
mask_block_cnt,
|
| 406 |
-
mask_block_idx,
|
| 407 |
-
full_block_cnt,
|
| 408 |
-
full_block_idx,
|
| 409 |
-
*_,
|
| 410 |
-
) = tensors
|
| 411 |
-
|
| 412 |
-
(
|
| 413 |
-
mask_block_cnt_tensor,
|
| 414 |
-
mask_block_idx_tensor,
|
| 415 |
-
) = [
|
| 416 |
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 417 |
-
for t in (mask_block_cnt, mask_block_idx)
|
| 418 |
]
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 424 |
if t is not None
|
| 425 |
else None
|
| 426 |
-
for t in (
|
| 427 |
]
|
| 428 |
|
| 429 |
return BlockSparseTensors(
|
|
@@ -431,6 +713,10 @@ def to_cute_block_sparse_tensors(
|
|
| 431 |
mask_block_idx_tensor,
|
| 432 |
full_block_cnt_tensor,
|
| 433 |
full_block_idx_tensor,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 434 |
)
|
| 435 |
|
| 436 |
|
|
|
|
| 17 |
class BlockSparseTensors(NamedTuple):
|
| 18 |
mask_block_cnt: cute.Tensor
|
| 19 |
mask_block_idx: cute.Tensor
|
| 20 |
+
full_block_cnt: cute.Tensor | None = None
|
| 21 |
+
full_block_idx: cute.Tensor | None = None
|
| 22 |
+
cu_total_m_blocks: cute.Tensor | None = None
|
| 23 |
+
cu_block_idx_offsets: cute.Tensor | None = None
|
| 24 |
+
dq_write_order: cute.Tensor | None = None
|
| 25 |
+
dq_write_order_full: cute.Tensor | None = None
|
| 26 |
|
| 27 |
def __new_from_mlir_values__(self, values):
|
| 28 |
+
new_fields = []
|
| 29 |
+
idx = 0
|
| 30 |
+
for original in self:
|
| 31 |
+
if original is None:
|
| 32 |
+
new_fields.append(None)
|
| 33 |
+
else:
|
| 34 |
+
new_fields.append(values[idx])
|
| 35 |
+
idx += 1
|
| 36 |
+
return BlockSparseTensors(*new_fields)
|
| 37 |
|
| 38 |
|
| 39 |
class BlockSparseTensorsTorch(NamedTuple):
|
|
|
|
| 41 |
mask_block_idx: torch.Tensor
|
| 42 |
full_block_cnt: torch.Tensor | None = None
|
| 43 |
full_block_idx: torch.Tensor | None = None
|
| 44 |
+
cu_total_m_blocks: torch.Tensor | None = None
|
| 45 |
+
cu_block_idx_offsets: torch.Tensor | None = None
|
| 46 |
block_size: tuple[int, int] | None = None
|
| 47 |
+
dq_write_order: torch.Tensor | None = None
|
| 48 |
+
dq_write_order_full: torch.Tensor | None = None
|
| 49 |
+
spt: bool | None = None
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _ordered_to_dense_simple(
|
| 53 |
+
num_blocks: torch.Tensor,
|
| 54 |
+
indices: torch.Tensor,
|
| 55 |
+
num_cols: int,
|
| 56 |
+
) -> torch.Tensor:
|
| 57 |
+
"""Convert ordered sparse representation to dense binary matrix.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
num_blocks: [B, H, num_rows] count of valid entries per row
|
| 61 |
+
indices: [B, H, num_rows, max_entries] column indices (valid entries packed left)
|
| 62 |
+
num_cols: total number of columns
|
| 63 |
+
|
| 64 |
+
Returns:
|
| 65 |
+
dense: [B, H, num_rows, num_cols] binary int32 matrix
|
| 66 |
+
"""
|
| 67 |
+
B, H, num_rows, max_entries = indices.shape
|
| 68 |
+
device = indices.device
|
| 69 |
+
dense = torch.zeros(B, H, num_rows, num_cols + 1, dtype=torch.int32, device=device)
|
| 70 |
+
col_range = torch.arange(max_entries, device=device)
|
| 71 |
+
valid = col_range[None, None, None, :] < num_blocks[:, :, :, None]
|
| 72 |
+
safe_indices = torch.where(valid, indices.long(), num_cols)
|
| 73 |
+
row_idx = torch.arange(num_rows, device=device)[None, None, :, None].expand_as(indices)
|
| 74 |
+
b_idx = torch.arange(B, device=device)[:, None, None, None].expand_as(indices)
|
| 75 |
+
h_idx = torch.arange(H, device=device)[None, :, None, None].expand_as(indices)
|
| 76 |
+
dense[b_idx, h_idx, row_idx, safe_indices] = 1
|
| 77 |
+
return dense[:, :, :, :num_cols]
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def compute_dq_write_order(
|
| 81 |
+
fwd_mask_cnt: torch.Tensor,
|
| 82 |
+
fwd_mask_idx: torch.Tensor,
|
| 83 |
+
fwd_full_cnt: torch.Tensor | None,
|
| 84 |
+
fwd_full_idx: torch.Tensor | None,
|
| 85 |
+
bwd_mask_cnt: torch.Tensor,
|
| 86 |
+
bwd_mask_idx: torch.Tensor,
|
| 87 |
+
bwd_full_cnt: torch.Tensor | None,
|
| 88 |
+
bwd_full_idx: torch.Tensor | None,
|
| 89 |
+
spt: bool = False,
|
| 90 |
+
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
| 91 |
+
"""Compute dQ write-order metadata for deterministic block-sparse backward.
|
| 92 |
+
|
| 93 |
+
For each (n_block, i) in the backward iteration, computes the semaphore
|
| 94 |
+
lock value: the rank of n_block in the combined (partial + full) sorted
|
| 95 |
+
contributor list for the target m_block.
|
| 96 |
+
|
| 97 |
+
Lock values are assigned in ascending n_block order (or descending if spt=True)
|
| 98 |
+
to guarantee deadlock-freedom with the CTA scheduling order.
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
fwd_mask_cnt: [B, H, num_m_blocks] partial contributor counts per m_block
|
| 102 |
+
fwd_mask_idx: [B, H, num_m_blocks, max_kv] partial contributor n_block indices (ascending)
|
| 103 |
+
fwd_full_cnt: [B, H, num_m_blocks] full contributor counts per m_block (optional)
|
| 104 |
+
fwd_full_idx: [B, H, num_m_blocks, max_kv] full contributor n_block indices (optional)
|
| 105 |
+
bwd_mask_cnt: [B, H, num_n_blocks] partial iteration counts per n_block
|
| 106 |
+
bwd_mask_idx: [B, H, num_n_blocks, max_q] partial iteration m_block indices
|
| 107 |
+
bwd_full_cnt: [B, H, num_n_blocks] full iteration counts per n_block (optional)
|
| 108 |
+
bwd_full_idx: [B, H, num_n_blocks, max_q] full iteration m_block indices (optional)
|
| 109 |
+
spt: if True, reverse ordering (highest n_block gets lock_value=0)
|
| 110 |
+
|
| 111 |
+
Returns:
|
| 112 |
+
(dq_write_order, dq_write_order_full): tensors parallel to bwd_mask_idx
|
| 113 |
+
and bwd_full_idx respectively, containing lock values.
|
| 114 |
+
"""
|
| 115 |
+
device = fwd_mask_idx.device
|
| 116 |
+
B, H, num_m, max_kv_partial = fwd_mask_idx.shape
|
| 117 |
+
_, _, num_n, max_q_partial = bwd_mask_idx.shape
|
| 118 |
+
|
| 119 |
+
has_full = fwd_full_cnt is not None and fwd_full_idx is not None
|
| 120 |
+
|
| 121 |
+
dense_partial = _ordered_to_dense_simple(fwd_mask_cnt, fwd_mask_idx, num_n)
|
| 122 |
+
if has_full:
|
| 123 |
+
dense_full = _ordered_to_dense_simple(fwd_full_cnt, fwd_full_idx, num_n)
|
| 124 |
+
dense = (dense_partial + dense_full).clamp(max=1)
|
| 125 |
+
else:
|
| 126 |
+
dense = dense_partial
|
| 127 |
+
|
| 128 |
+
cumsum = dense.cumsum(dim=-1)
|
| 129 |
+
rank_table = (cumsum - dense).to(torch.int32)
|
| 130 |
+
|
| 131 |
+
if spt:
|
| 132 |
+
total_per_m = cumsum[:, :, :, -1:]
|
| 133 |
+
rank_table = (total_per_m - 1 - rank_table).to(torch.int32)
|
| 134 |
+
|
| 135 |
+
def _gather_write_order(bwd_idx, bwd_cnt):
|
| 136 |
+
b_i = torch.arange(B, device=device)[:, None, None, None].expand_as(bwd_idx)
|
| 137 |
+
h_i = torch.arange(H, device=device)[None, :, None, None].expand_as(bwd_idx)
|
| 138 |
+
n_i = torch.arange(bwd_idx.shape[2], device=device)[None, None, :, None].expand_as(bwd_idx)
|
| 139 |
+
m_vals = bwd_idx.long().clamp(0, num_m - 1)
|
| 140 |
+
return rank_table[b_i, h_i, m_vals, n_i].to(torch.int32)
|
| 141 |
+
|
| 142 |
+
dq_write_order = _gather_write_order(bwd_mask_idx, bwd_mask_cnt)
|
| 143 |
+
|
| 144 |
+
dq_write_order_full = None
|
| 145 |
+
if has_full and bwd_full_cnt is not None and bwd_full_idx is not None:
|
| 146 |
+
dq_write_order_full = _gather_write_order(bwd_full_idx, bwd_full_cnt)
|
| 147 |
+
|
| 148 |
+
return dq_write_order, dq_write_order_full
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def compute_dq_write_order_from_block_mask(
|
| 152 |
+
block_mask,
|
| 153 |
+
spt: bool = False,
|
| 154 |
+
) -> tuple[torch.Tensor, torch.Tensor | None]:
|
| 155 |
+
(
|
| 156 |
+
_seq_q,
|
| 157 |
+
_seq_k,
|
| 158 |
+
kv_mask_cnt,
|
| 159 |
+
kv_mask_idx,
|
| 160 |
+
full_kv_cnt,
|
| 161 |
+
full_kv_idx,
|
| 162 |
+
q_mask_cnt,
|
| 163 |
+
q_mask_idx,
|
| 164 |
+
full_q_cnt,
|
| 165 |
+
full_q_idx,
|
| 166 |
+
*_,
|
| 167 |
+
) = block_mask.as_tuple()
|
| 168 |
+
return compute_dq_write_order(
|
| 169 |
+
kv_mask_cnt,
|
| 170 |
+
kv_mask_idx,
|
| 171 |
+
full_kv_cnt,
|
| 172 |
+
full_kv_idx,
|
| 173 |
+
q_mask_cnt,
|
| 174 |
+
q_mask_idx,
|
| 175 |
+
full_q_cnt,
|
| 176 |
+
full_q_idx,
|
| 177 |
+
spt=spt,
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def get_sparse_q_block_size(
|
| 182 |
+
tensors: BlockSparseTensorsTorch | None,
|
| 183 |
+
seqlen_q: int,
|
| 184 |
+
) -> int | None:
|
| 185 |
+
"""Return the Q sparse block size, or None when sparsity is unset or ambiguous."""
|
| 186 |
+
if tensors is None:
|
| 187 |
+
return None
|
| 188 |
+
if tensors.block_size is not None:
|
| 189 |
+
return tensors.block_size[0]
|
| 190 |
+
num_m_blocks = tensors.mask_block_idx.shape[2]
|
| 191 |
+
min_block_size = ceildiv(seqlen_q, num_m_blocks)
|
| 192 |
+
max_block_size = seqlen_q if num_m_blocks == 1 else (seqlen_q - 1) // (num_m_blocks - 1)
|
| 193 |
+
if min_block_size != max_block_size:
|
| 194 |
+
return None
|
| 195 |
+
return min_block_size
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def get_kv_subtile_factor(
|
| 199 |
+
block_sparse_tensors: BlockSparseTensorsTorch | None,
|
| 200 |
+
n_block_size: int,
|
| 201 |
+
) -> int:
|
| 202 |
+
"""Return the number of physical KV tiles covered by one sparse KV block."""
|
| 203 |
+
if block_sparse_tensors is None or block_sparse_tensors.block_size is None:
|
| 204 |
+
return 1
|
| 205 |
+
sparse_block_size_kv = block_sparse_tensors.block_size[1]
|
| 206 |
+
if sparse_block_size_kv % n_block_size != 0:
|
| 207 |
+
raise ValueError(
|
| 208 |
+
"Block sparsity expects sparse_block_size[1] "
|
| 209 |
+
f"to be a multiple of tile_n={n_block_size}; got {sparse_block_size_kv}."
|
| 210 |
+
)
|
| 211 |
+
return sparse_block_size_kv // n_block_size
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def block_sparse_bwd_supports_2cta(
|
| 215 |
+
block_sparse_tensors: BlockSparseTensorsTorch | None,
|
| 216 |
+
n_block_size: int,
|
| 217 |
+
) -> bool:
|
| 218 |
+
"""Return whether sparse KV metadata constrains backward away from 2CTA."""
|
| 219 |
+
if block_sparse_tensors is None:
|
| 220 |
+
return True
|
| 221 |
+
return get_kv_subtile_factor(block_sparse_tensors, n_block_size) % 2 == 0
|
| 222 |
|
| 223 |
|
| 224 |
def _expand_sparsity_tensor(
|
|
|
|
| 248 |
name: str,
|
| 249 |
cnt: torch.Tensor | None,
|
| 250 |
idx: torch.Tensor | None,
|
| 251 |
+
expected_count_shape: Tuple[int, ...],
|
| 252 |
+
expected_index_shape: Tuple[int, ...],
|
| 253 |
context: str | None,
|
| 254 |
hint: str | Callable[[], str] | None,
|
| 255 |
) -> Tuple[torch.Tensor | None, torch.Tensor | None]:
|
|
|
|
| 268 |
expanded_cnt = _expand_sparsity_tensor(
|
| 269 |
cnt, expected_count_shape, f"{name}_block_cnt", context, hint
|
| 270 |
)
|
| 271 |
+
# [Note] Allow Compact block sparse indices
|
| 272 |
+
# Allow the last dimension (n_blocks) of idx to be <= expected, since
|
| 273 |
+
# FA4 only accesses indices 0..cnt-1 per query tile. This enables compact
|
| 274 |
+
# index tensors that avoid O(N^2) memory at long sequence lengths.
|
| 275 |
+
if idx.ndim == 4 and idx.shape[3] <= expected_index_shape[3]:
|
| 276 |
+
expected_index_shape = (*expected_index_shape[:3], idx.shape[3])
|
| 277 |
expanded_idx = _expand_sparsity_tensor(
|
| 278 |
idx, expected_index_shape, f"{name}_block_idx", context, hint
|
| 279 |
)
|
| 280 |
return expanded_cnt, expanded_idx
|
| 281 |
|
| 282 |
|
| 283 |
+
def _check_and_expand_metadata_tensor(
|
| 284 |
+
name: str,
|
| 285 |
+
tensor: torch.Tensor | None,
|
| 286 |
+
expected_shape: Tuple[int, ...],
|
| 287 |
+
context: str | None,
|
| 288 |
+
hint: str | Callable[[], str] | None,
|
| 289 |
+
device: torch.device,
|
| 290 |
+
) -> torch.Tensor | None:
|
| 291 |
+
if tensor is None:
|
| 292 |
+
return None
|
| 293 |
+
if tensor.dtype != torch.int32:
|
| 294 |
+
raise ValueError(f"{name} must have dtype torch.int32")
|
| 295 |
+
if tensor.device != device:
|
| 296 |
+
raise ValueError(f"{name} must be on the same device as block sparse tensors")
|
| 297 |
+
if not tensor.is_cuda:
|
| 298 |
+
raise ValueError(f"{name} must live on CUDA")
|
| 299 |
+
return _expand_sparsity_tensor(tensor, expected_shape, name, context, hint)
|
| 300 |
+
|
| 301 |
+
|
| 302 |
def get_block_sparse_expected_shapes(
|
| 303 |
batch_size: int,
|
| 304 |
num_head: int,
|
|
|
|
| 336 |
Expectations:
|
| 337 |
- mask_block_cnt is (B, H, M) and mask_block_idx is (B, H, M, N).
|
| 338 |
- Batch/head dims may be 1 for broadcast, or match the requested sizes.
|
| 339 |
+
- sparse_block_size_kv must be a multiple of tile_n.
|
| 340 |
- sparse_block_size_q must be a multiple of q_stage * tile_m.
|
| 341 |
- If sparse_block_size_q is omitted and seqlen_q/num_m_blocks is ambiguous,
|
| 342 |
the caller must provide block_size to disambiguate. TODO will make this required in a future PR.
|
|
|
|
| 345 |
base_n_block = n_block_size
|
| 346 |
if sparse_block_size_kv is None:
|
| 347 |
sparse_block_size_kv = base_n_block
|
| 348 |
+
if sparse_block_size_kv % base_n_block != 0:
|
| 349 |
+
raise ValueError(
|
| 350 |
+
f"Block sparse tensors{context} require BLOCK_SIZE_KV to be a multiple of {base_n_block}."
|
| 351 |
+
)
|
| 352 |
if tensors.mask_block_idx is None:
|
| 353 |
raise ValueError("mask_block_cnt and mask_block_idx must be provided for block sparsity.")
|
| 354 |
num_m_blocks = tensors.mask_block_idx.shape[2]
|
| 355 |
|
| 356 |
if sparse_block_size_q is None:
|
| 357 |
+
sparse_block_size_q = get_sparse_q_block_size(tensors, seqlen_q)
|
| 358 |
+
if sparse_block_size_q is None and base_m_block != 1:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
raise ValueError(
|
| 360 |
f"Block sparse tensors{context} require explicit sparse_block_size[0] "
|
| 361 |
f"to disambiguate block size for seqlen_q={seqlen_q} and num_m_blocks={num_m_blocks}."
|
| 362 |
)
|
| 363 |
+
if sparse_block_size_q is None:
|
| 364 |
+
sparse_block_size_q = ceildiv(seqlen_q, num_m_blocks)
|
| 365 |
|
| 366 |
if sparse_block_size_q % base_m_block != 0:
|
| 367 |
raise ValueError(
|
|
|
|
| 397 |
raise ValueError(f"Block sparse tensors{context} {dim_name} dim must be {tgt} or 1.")
|
| 398 |
if mask_block_cnt.shape[2] != mask_block_idx.shape[2]:
|
| 399 |
raise ValueError(f"Block sparse tensors{context} must share the same m-block dimension.")
|
| 400 |
+
# [Note] Allow Compact block sparse indices: FA4 only accesses indices 0..cnt-1
|
| 401 |
+
# per query tile, so idx.shape[3] can be <= expected_n_blocks.
|
| 402 |
+
if mask_block_idx.shape[3] > expected_n_blocks:
|
| 403 |
raise ValueError(
|
| 404 |
+
f"Block sparse tensors{context} n-block dimension must be <= {expected_n_blocks}."
|
| 405 |
)
|
| 406 |
if expected_m_blocks != num_m_blocks:
|
| 407 |
raise ValueError(
|
|
|
|
| 419 |
seqlen_k: int,
|
| 420 |
m_block_size: int,
|
| 421 |
n_block_size: int,
|
| 422 |
+
q_subtile_factor: int,
|
| 423 |
+
kv_subtile_factor: int = 1,
|
| 424 |
) -> Tuple[Tuple[int, int, int], Tuple[int, int, int, int]]:
|
| 425 |
"""Return (expected_count_shape, expected_index_shape) for backward block sparse normalization.
|
| 426 |
|
| 427 |
Backward uses Q-direction indexing (transposed from forward), where shapes are
|
| 428 |
indexed by N-blocks first, then M-blocks. The sparse_block_size_q is determined
|
| 429 |
+
by q_subtile_factor * m_block_size.
|
| 430 |
"""
|
| 431 |
+
sparse_block_size_q = q_subtile_factor * m_block_size
|
| 432 |
+
sparse_block_size_kv = kv_subtile_factor * n_block_size
|
| 433 |
expected_m_blocks = ceildiv(seqlen_q, sparse_block_size_q)
|
| 434 |
+
expected_n_blocks = ceildiv(seqlen_k, sparse_block_size_kv)
|
| 435 |
expected_count_shape = (batch_size, num_head, expected_n_blocks)
|
| 436 |
expected_index_shape = (batch_size, num_head, expected_n_blocks, expected_m_blocks)
|
| 437 |
return expected_count_shape, expected_index_shape
|
|
|
|
| 440 |
def normalize_block_sparse_tensors(
|
| 441 |
tensors: BlockSparseTensorsTorch,
|
| 442 |
*,
|
| 443 |
+
expected_count_shape: Tuple[int, ...],
|
| 444 |
+
expected_index_shape: Tuple[int, ...],
|
| 445 |
context: str | None = None,
|
| 446 |
hint: str | Callable[[], str] | None = None,
|
| 447 |
) -> BlockSparseTensorsTorch:
|
|
|
|
| 472 |
if full_cnt is not None and mask_cnt.device != full_cnt.device:
|
| 473 |
raise ValueError("All block sparse tensors must be on the same device")
|
| 474 |
|
| 475 |
+
dq_write_order = _check_and_expand_metadata_tensor(
|
| 476 |
+
"dq_write_order",
|
| 477 |
+
tensors.dq_write_order,
|
| 478 |
+
tuple(mask_idx.shape),
|
| 479 |
+
context,
|
| 480 |
+
hint,
|
| 481 |
+
mask_cnt.device,
|
| 482 |
+
)
|
| 483 |
+
dq_write_order_full = _check_and_expand_metadata_tensor(
|
| 484 |
+
"dq_write_order_full",
|
| 485 |
+
tensors.dq_write_order_full,
|
| 486 |
+
tuple(full_idx.shape) if full_idx is not None else expected_index_shape,
|
| 487 |
+
context,
|
| 488 |
+
hint,
|
| 489 |
+
mask_cnt.device,
|
| 490 |
+
)
|
| 491 |
+
spt = tensors.spt
|
| 492 |
+
if spt is not None and not isinstance(spt, bool):
|
| 493 |
+
raise ValueError("spt must be a bool when provided")
|
| 494 |
+
if spt is not None and dq_write_order is None:
|
| 495 |
+
raise ValueError("spt requires dq_write_order to be provided")
|
| 496 |
+
|
| 497 |
return BlockSparseTensorsTorch(
|
| 498 |
mask_block_cnt=mask_cnt,
|
| 499 |
mask_block_idx=mask_idx,
|
| 500 |
full_block_cnt=full_cnt,
|
| 501 |
full_block_idx=full_idx,
|
| 502 |
+
cu_total_m_blocks=tensors.cu_total_m_blocks,
|
| 503 |
+
cu_block_idx_offsets=tensors.cu_block_idx_offsets,
|
| 504 |
block_size=tensors.block_size,
|
| 505 |
+
dq_write_order=dq_write_order,
|
| 506 |
+
dq_write_order_full=dq_write_order_full,
|
| 507 |
+
spt=spt,
|
| 508 |
)
|
| 509 |
|
| 510 |
|
|
|
|
| 536 |
tensors.mask_block_idx,
|
| 537 |
tensors.full_block_cnt,
|
| 538 |
tensors.full_block_idx,
|
| 539 |
+
tensors.dq_write_order,
|
| 540 |
+
tensors.dq_write_order_full,
|
| 541 |
):
|
| 542 |
if tensor is not None:
|
| 543 |
patterns.append(get_broadcast_dims(tensor))
|
|
|
|
| 546 |
return tuple(patterns)
|
| 547 |
|
| 548 |
|
| 549 |
+
class NormalizedBlockSparseConfig(NamedTuple):
|
| 550 |
+
"""Result of validating and normalizing a user block-sparse config."""
|
| 551 |
+
|
| 552 |
+
tensors: BlockSparseTensorsTorch
|
| 553 |
+
broadcast_pattern: Tuple[Tuple[bool, ...], ...] | None
|
| 554 |
+
q_subtile_factor: int
|
| 555 |
+
kv_subtile_factor: int
|
| 556 |
+
|
| 557 |
+
|
| 558 |
def normalize_block_sparse_config(
|
| 559 |
tensors: BlockSparseTensorsTorch,
|
| 560 |
*,
|
|
|
|
| 564 |
seqlen_k: int,
|
| 565 |
block_size: tuple[int, int],
|
| 566 |
q_stage: int,
|
| 567 |
+
allow_kv_subtile: bool = False,
|
| 568 |
+
) -> NormalizedBlockSparseConfig:
|
| 569 |
+
"""Validate the block-sparse config, infer expected shapes, and normalize.
|
| 570 |
+
|
| 571 |
+
Handles both fixed-length (3D `[B, H, M]` / 4D `[B, H, M, N]`) and varlen
|
| 572 |
+
(2D `[H, total_m_blocks]` / `[H, total_n_blocks]`) layouts. Varlen is
|
| 573 |
+
detected by `tensors.cu_total_m_blocks is not None` and forces
|
| 574 |
+
`q_subtile_factor == 1` (TODO: potentially remove this restriction).
|
| 575 |
+
"""
|
| 576 |
m_block_size, n_block_size = block_size
|
| 577 |
if tensors.block_size is None:
|
| 578 |
+
sparse_block_size_q, sparse_block_size_kv = None, n_block_size
|
| 579 |
else:
|
| 580 |
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 581 |
+
if sparse_block_size_kv % n_block_size != 0:
|
| 582 |
+
raise ValueError(
|
| 583 |
+
f"Block sparsity requires sparse_block_size[1] to be a multiple of tile_n={n_block_size}."
|
| 584 |
+
)
|
| 585 |
+
kv_subtile_factor = sparse_block_size_kv // n_block_size
|
| 586 |
+
if kv_subtile_factor != 1 and not allow_kv_subtile:
|
| 587 |
raise ValueError(
|
| 588 |
f"Block sparsity requires sparse_block_size[1]={n_block_size} to match tile_n."
|
| 589 |
)
|
| 590 |
+
if tensors.cu_total_m_blocks is not None:
|
| 591 |
+
base_m_block = q_stage * m_block_size
|
| 592 |
+
if sparse_block_size_q is not None and sparse_block_size_q != base_m_block:
|
| 593 |
+
raise ValueError(
|
| 594 |
+
f"Varlen block sparsity requires sparse_block_size[0]={base_m_block} "
|
| 595 |
+
f"(= q_stage * tile_m); got {sparse_block_size_q}."
|
| 596 |
+
)
|
| 597 |
+
total_m_blocks = tensors.mask_block_cnt.shape[-1]
|
| 598 |
+
total_n_blocks = tensors.mask_block_idx.shape[-1]
|
| 599 |
+
expected_count_shape = (num_head, total_m_blocks)
|
| 600 |
+
expected_index_shape = (num_head, total_n_blocks)
|
| 601 |
+
q_subtile_factor = 1
|
| 602 |
+
else:
|
| 603 |
+
expected_count_shape, expected_index_shape, q_subtile_factor = (
|
| 604 |
+
infer_block_sparse_expected_shapes(
|
| 605 |
+
tensors,
|
| 606 |
+
batch_size=batch_size,
|
| 607 |
+
num_head=num_head,
|
| 608 |
+
seqlen_q=seqlen_q,
|
| 609 |
+
seqlen_k=seqlen_k,
|
| 610 |
+
m_block_size=m_block_size,
|
| 611 |
+
n_block_size=n_block_size,
|
| 612 |
+
q_stage=q_stage,
|
| 613 |
+
context="forward",
|
| 614 |
+
sparse_block_size_q=sparse_block_size_q,
|
| 615 |
+
sparse_block_size_kv=sparse_block_size_kv,
|
| 616 |
+
)
|
| 617 |
)
|
|
|
|
| 618 |
normalized_tensors = normalize_block_sparse_tensors(
|
| 619 |
tensors,
|
| 620 |
expected_count_shape=expected_count_shape,
|
| 621 |
expected_index_shape=expected_index_shape,
|
| 622 |
)
|
| 623 |
+
return NormalizedBlockSparseConfig(
|
| 624 |
+
tensors=normalized_tensors,
|
| 625 |
+
broadcast_pattern=get_block_sparse_broadcast_pattern(normalized_tensors),
|
| 626 |
+
q_subtile_factor=q_subtile_factor,
|
| 627 |
+
kv_subtile_factor=kv_subtile_factor,
|
| 628 |
)
|
| 629 |
|
| 630 |
|
|
|
|
| 636 |
seqlen_q: int,
|
| 637 |
seqlen_k: int,
|
| 638 |
block_size: tuple[int, int],
|
| 639 |
+
q_subtile_factor: int,
|
| 640 |
+
kv_subtile_factor: int = 1,
|
| 641 |
) -> tuple[BlockSparseTensorsTorch, Tuple[Tuple[bool, ...], ...] | None]:
|
| 642 |
m_block_size, n_block_size = block_size
|
| 643 |
if tensors.block_size is None:
|
| 644 |
+
sparse_block_size_q, sparse_block_size_kv = q_subtile_factor * m_block_size, n_block_size
|
| 645 |
else:
|
| 646 |
sparse_block_size_q, sparse_block_size_kv = tensors.block_size
|
| 647 |
+
if sparse_block_size_q != q_subtile_factor * m_block_size:
|
| 648 |
raise ValueError(
|
| 649 |
+
f"Block sparsity expects sparse_block_size_q={q_subtile_factor * m_block_size} "
|
| 650 |
+
f"for q_subtile_factor={q_subtile_factor}."
|
| 651 |
)
|
| 652 |
+
expected_sparse_block_size_kv = kv_subtile_factor * n_block_size
|
| 653 |
+
if sparse_block_size_kv != expected_sparse_block_size_kv:
|
| 654 |
raise ValueError(
|
| 655 |
+
f"Block sparsity expects sparse_block_size[1]={expected_sparse_block_size_kv} "
|
| 656 |
+
f"for kv_subtile_factor={kv_subtile_factor}."
|
| 657 |
)
|
| 658 |
expected_count_shape, expected_index_shape = get_block_sparse_expected_shapes_bwd(
|
| 659 |
batch_size,
|
|
|
|
| 662 |
seqlen_k,
|
| 663 |
m_block_size,
|
| 664 |
n_block_size,
|
| 665 |
+
q_subtile_factor,
|
| 666 |
+
kv_subtile_factor,
|
| 667 |
)
|
| 668 |
normalized_tensors = normalize_block_sparse_tensors(
|
| 669 |
tensors,
|
|
|
|
| 673 |
hint=lambda: (
|
| 674 |
f"Backward expects Q-direction block-sparse tensors (q_mask_cnt/q_mask_idx, "
|
| 675 |
f"and optionally full_q_cnt/full_q_idx). Regenerate the backward BlockMask with "
|
| 676 |
+
f"BLOCK_SIZE=({q_subtile_factor * m_block_size}, {expected_sparse_block_size_kv})."
|
| 677 |
),
|
| 678 |
)
|
| 679 |
return normalized_tensors, get_block_sparse_broadcast_pattern(normalized_tensors)
|
|
|
|
| 685 |
"""Convert torch block sparsity tensors to CuTe tensors, optionally for tvm ffi"""
|
| 686 |
if not is_block_sparsity_enabled(tensors):
|
| 687 |
return None
|
| 688 |
+
mask_block_cnt_tensor, mask_block_idx_tensor = [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 690 |
+
for t in (tensors.mask_block_cnt, tensors.mask_block_idx)
|
| 691 |
]
|
| 692 |
+
full_block_cnt_tensor, full_block_idx_tensor = [
|
| 693 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 694 |
+
if t is not None
|
| 695 |
+
else None
|
| 696 |
+
for t in (tensors.full_block_cnt, tensors.full_block_idx)
|
| 697 |
+
]
|
| 698 |
+
cu_total_m_blocks_tensor, cu_block_idx_offsets_tensor = [
|
| 699 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=0, enable_tvm_ffi=enable_tvm_ffi)
|
| 700 |
+
if t is not None
|
| 701 |
+
else None
|
| 702 |
+
for t in (tensors.cu_total_m_blocks, tensors.cu_block_idx_offsets)
|
| 703 |
+
]
|
| 704 |
+
dq_write_order_tensor, dq_write_order_full_tensor = [
|
| 705 |
to_cute_tensor(t, assumed_align=4, leading_dim=-1, enable_tvm_ffi=enable_tvm_ffi)
|
| 706 |
if t is not None
|
| 707 |
else None
|
| 708 |
+
for t in (tensors.dq_write_order, tensors.dq_write_order_full)
|
| 709 |
]
|
| 710 |
|
| 711 |
return BlockSparseTensors(
|
|
|
|
| 713 |
mask_block_idx_tensor,
|
| 714 |
full_block_cnt_tensor,
|
| 715 |
full_block_idx_tensor,
|
| 716 |
+
cu_total_m_blocks_tensor,
|
| 717 |
+
cu_block_idx_offsets_tensor,
|
| 718 |
+
dq_write_order_tensor,
|
| 719 |
+
dq_write_order_full_tensor,
|
| 720 |
)
|
| 721 |
|
| 722 |
|
build/torch-cuda/cache_utils.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
# Manage Ahead-of-Time (AOT) compiled kernels
|
| 2 |
import fcntl
|
| 3 |
import hashlib
|
| 4 |
-
import logging
|
| 5 |
import os
|
| 6 |
import pickle
|
| 7 |
import sys
|
|
@@ -18,6 +17,7 @@ import cutlass
|
|
| 18 |
import cutlass.cute as cute
|
| 19 |
import tvm_ffi
|
| 20 |
from cutlass.cutlass_dsl import JitCompiledFunction
|
|
|
|
| 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.
|
|
@@ -30,13 +30,6 @@ for _lib_path in cute.runtime.find_runtime_libraries(enable_tvm_ffi=False):
|
|
| 30 |
CompileKeyType: TypeAlias = tuple[Hashable, ...]
|
| 31 |
CallableFunction: TypeAlias = JitCompiledFunction | tvm_ffi.Function
|
| 32 |
|
| 33 |
-
logger = logging.getLogger(__name__)
|
| 34 |
-
_handler = logging.StreamHandler()
|
| 35 |
-
_handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d %(levelname)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"))
|
| 36 |
-
logger.addHandler(_handler)
|
| 37 |
-
logger.setLevel(logging.DEBUG)
|
| 38 |
-
|
| 39 |
-
|
| 40 |
# Enable cache via `FLASH_ATTENTION_CUTE_DSL_CACHE_ENABLED=1`
|
| 41 |
CUTE_DSL_CACHE_ENABLED: bool = os.getenv("FLASH_ATTENTION_CUTE_DSL_CACHE_ENABLED", "0") == "1"
|
| 42 |
|
|
@@ -222,13 +215,13 @@ class JITPersistentCache(JITCache):
|
|
| 222 |
label=sha256_hex,
|
| 223 |
):
|
| 224 |
if obj_path.exists():
|
| 225 |
-
|
| 226 |
m = cute.runtime.load_module(str(obj_path), enable_tvm_ffi=True)
|
| 227 |
fn = getattr(m, self.EXPORT_FUNCTION_PREFIX)
|
| 228 |
JITCache.__setitem__(self, key, fn)
|
| 229 |
return True
|
| 230 |
else:
|
| 231 |
-
|
| 232 |
return False
|
| 233 |
|
| 234 |
def _try_export_to_storage(self, key: CompileKeyType, fn: JitCompiledFunction) -> None:
|
|
@@ -243,14 +236,14 @@ class JITPersistentCache(JITCache):
|
|
| 243 |
obj_path = self.cache_path / f"{sha256_hex}.o"
|
| 244 |
if obj_path.exists():
|
| 245 |
# Another process already exported.
|
| 246 |
-
|
| 247 |
return
|
| 248 |
-
|
| 249 |
fn.export_to_c(
|
| 250 |
object_file_path=str(obj_path),
|
| 251 |
function_name=self.EXPORT_FUNCTION_PREFIX,
|
| 252 |
)
|
| 253 |
-
|
| 254 |
|
| 255 |
def _key_to_hash(self, key: CompileKeyType) -> str:
|
| 256 |
return hashlib.sha256(pickle.dumps(key)).hexdigest()
|
|
@@ -262,7 +255,7 @@ class JITPersistentCache(JITCache):
|
|
| 262 |
"""
|
| 263 |
Not only clear the in-memory cache. Also purge persistent compilation cache.
|
| 264 |
"""
|
| 265 |
-
|
| 266 |
super().clear()
|
| 267 |
for child in self.cache_path.iterdir():
|
| 268 |
child.unlink()
|
|
@@ -281,8 +274,8 @@ def get_jit_cache(name: str | None = None) -> JITCache:
|
|
| 281 |
path = get_cache_path() / _compute_source_fingerprint()
|
| 282 |
if name:
|
| 283 |
path = path / name
|
| 284 |
-
|
| 285 |
return JITPersistentCache(path)
|
| 286 |
else:
|
| 287 |
-
|
| 288 |
return JITCache()
|
|
|
|
| 1 |
# Manage Ahead-of-Time (AOT) compiled kernels
|
| 2 |
import fcntl
|
| 3 |
import hashlib
|
|
|
|
| 4 |
import os
|
| 5 |
import pickle
|
| 6 |
import sys
|
|
|
|
| 17 |
import cutlass.cute as cute
|
| 18 |
import tvm_ffi
|
| 19 |
from cutlass.cutlass_dsl import JitCompiledFunction
|
| 20 |
+
from .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.
|
|
|
|
| 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 |
|
|
|
|
| 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:
|
|
|
|
| 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()
|
|
|
|
| 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()
|
|
|
|
| 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/compute_block_sparsity.py
CHANGED
|
@@ -11,8 +11,22 @@ from .block_sparsity import (
|
|
| 11 |
BlockSparseTensorsTorch,
|
| 12 |
to_cute_block_sparse_tensors,
|
| 13 |
)
|
| 14 |
-
from .
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
from .seqlen_info import SeqlenInfoQK
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
class BlockSparsityKernel:
|
|
@@ -28,7 +42,6 @@ class BlockSparsityKernel:
|
|
| 28 |
|
| 29 |
TODO:
|
| 30 |
- optimize mask_mod evaluation
|
| 31 |
-
- varlen support
|
| 32 |
- transposed tensors for bwd pass
|
| 33 |
"""
|
| 34 |
|
|
@@ -52,18 +65,31 @@ class BlockSparsityKernel:
|
|
| 52 |
blocksparse_tensors: BlockSparseTensors,
|
| 53 |
seqlen_q: Int32,
|
| 54 |
seqlen_k: Int32,
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
):
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
if const_expr(self.compute_full_blocks):
|
| 60 |
-
assert
|
| 61 |
"full block tensors must be provided when computing full blocks"
|
| 62 |
)
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
if const_expr(self.use_fast_sampling):
|
| 69 |
num_threads = 5
|
|
@@ -72,46 +98,46 @@ class BlockSparsityKernel:
|
|
| 72 |
num_threads = self.tile_mn[0]
|
| 73 |
self.num_warps = (num_threads + 32 - 1) // 32
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
self.kernel(
|
| 76 |
-
|
| 77 |
-
self.mask_idx,
|
| 78 |
-
self.full_cnt,
|
| 79 |
-
self.full_idx,
|
| 80 |
-
num_n_blocks,
|
| 81 |
seqlen_q,
|
| 82 |
seqlen_k,
|
| 83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
).launch(grid=grid, block=[num_threads, 1, 1])
|
| 85 |
|
| 86 |
@cute.kernel
|
| 87 |
def kernel(
|
| 88 |
self,
|
| 89 |
-
|
| 90 |
-
mask_idx: cute.Tensor,
|
| 91 |
-
full_cnt: cute.Tensor,
|
| 92 |
-
full_idx: cute.Tensor,
|
| 93 |
-
num_n_blocks: Int32,
|
| 94 |
seqlen_q: Int32,
|
| 95 |
seqlen_k: Int32,
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
):
|
| 98 |
tidx, _, _ = cute.arch.thread_idx()
|
| 99 |
warp_idx = cute.arch.warp_idx()
|
| 100 |
lane_id = cute.arch.lane_idx()
|
| 101 |
-
m_block, head_idx, batch_idx = cute.arch.block_idx()
|
| 102 |
|
| 103 |
ssa = partial(scalar_to_ssa, dtype=Int32)
|
| 104 |
|
| 105 |
-
seqlen = SeqlenInfoQK.create(
|
| 106 |
-
batch_idx,
|
| 107 |
-
seqlen_q,
|
| 108 |
-
seqlen_k,
|
| 109 |
-
mCuSeqlensQ=None,
|
| 110 |
-
mCuSeqlensK=None,
|
| 111 |
-
mSeqUsedQ=None,
|
| 112 |
-
mSeqUsedK=None,
|
| 113 |
-
)
|
| 114 |
-
|
| 115 |
@cute.struct
|
| 116 |
class SharedStorage:
|
| 117 |
reduction_buffer_smem: cute.struct.Align[
|
|
@@ -124,132 +150,157 @@ class BlockSparsityKernel:
|
|
| 124 |
reduction_buffer = storage.reduction_buffer_smem.get_tensor(
|
| 125 |
cute.make_layout((self.num_warps, 2))
|
| 126 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
num_mask_blocks = Int32(0)
|
| 129 |
num_full_blocks = Int32(0)
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
n_base = n_block * self.tile_mn[1]
|
| 134 |
|
| 135 |
if const_expr(self.use_fast_sampling):
|
| 136 |
-
#
|
| 137 |
-
#
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
kv_idx = n_base
|
| 147 |
-
elif tidx == 1:
|
| 148 |
-
# Top-right corner
|
| 149 |
-
q_idx = m_base
|
| 150 |
-
kv_idx = cutlass.min(n_base + self.tile_mn[1] - 1, seqlen_k - 1)
|
| 151 |
-
elif tidx == 2:
|
| 152 |
-
# Bottom-left corner
|
| 153 |
-
q_idx = cutlass.min(m_base + self.tile_mn[0] - 1, seqlen_q - 1)
|
| 154 |
-
kv_idx = n_base
|
| 155 |
-
elif tidx == 3:
|
| 156 |
-
# Bottom-right corner
|
| 157 |
-
q_idx = cutlass.min(m_base + self.tile_mn[0] - 1, seqlen_q - 1)
|
| 158 |
-
kv_idx = cutlass.min(n_base + self.tile_mn[1] - 1, seqlen_k - 1)
|
| 159 |
elif tidx == 4:
|
| 160 |
-
|
| 161 |
-
q_idx = m_base + (cutlass.min(seqlen_q - m_base, self.tile_mn[0])) // 2
|
| 162 |
-
kv_idx = n_base + (cutlass.min(seqlen_k - n_base, self.tile_mn[1])) // 2
|
| 163 |
-
else:
|
| 164 |
-
thread_is_valid = Boolean(False)
|
| 165 |
|
| 166 |
-
|
| 167 |
-
|
|
|
|
| 168 |
thread_is_valid = Boolean(True)
|
| 169 |
-
q_idx_ssa = ssa(q_idx)
|
| 170 |
-
kv_idx_ssa = ssa(kv_idx)
|
| 171 |
thread_result = ssa_to_scalar(
|
| 172 |
-
|
|
|
|
| 173 |
ssa(batch_idx),
|
| 174 |
ssa(head_idx),
|
| 175 |
-
|
| 176 |
-
|
| 177 |
seqlen,
|
| 178 |
-
|
| 179 |
)
|
| 180 |
)
|
| 181 |
-
else:
|
| 182 |
-
thread_is_valid = Boolean(False)
|
| 183 |
|
| 184 |
-
# Use vote_any_sync to see if any valid thread found unmasked or masked
|
| 185 |
-
# Only count results from threads that checked valid indices
|
| 186 |
has_unmasked = cute.arch.vote_any_sync(thread_result & thread_is_valid)
|
| 187 |
-
has_masked = cute.arch.vote_any_sync(
|
| 188 |
|
| 189 |
else:
|
| 190 |
-
# Full path
|
| 191 |
-
#
|
| 192 |
thread_has_unmasked = Boolean(False)
|
| 193 |
thread_has_masked = Boolean(False)
|
| 194 |
-
thread_is_valid = Boolean(False)
|
| 195 |
-
|
| 196 |
-
# Each thread handles 1 row
|
| 197 |
-
q_idx = m_base + tidx
|
| 198 |
kv_idx = Int32(0)
|
| 199 |
-
|
| 200 |
-
thread_is_valid = Boolean(True)
|
| 201 |
-
q_idx_ssa = ssa(q_idx)
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
kv_idx_ssa = ssa(kv_idx)
|
| 207 |
-
|
| 208 |
-
# Only check elements within valid sequence bounds
|
| 209 |
-
if kv_idx < seqlen_k:
|
| 210 |
-
# Direct scalar call
|
| 211 |
mask_val = ssa_to_scalar(
|
| 212 |
-
|
|
|
|
| 213 |
ssa(batch_idx),
|
| 214 |
ssa(head_idx),
|
| 215 |
-
|
| 216 |
-
|
| 217 |
seqlen,
|
| 218 |
-
|
| 219 |
)
|
| 220 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
thread_has_unmasked = Boolean(True)
|
| 225 |
-
else:
|
| 226 |
-
thread_has_masked = Boolean(True)
|
| 227 |
-
|
| 228 |
-
# Block-level reduction to combine results across all threads
|
| 229 |
-
# Only count votes from threads that checked valid indices
|
| 230 |
-
warp_has_unmasked_mask = cute.arch.vote_any_sync(
|
| 231 |
-
thread_has_unmasked & thread_is_valid
|
| 232 |
-
)
|
| 233 |
-
warp_has_masked_mask = cute.arch.vote_any_sync(thread_has_masked & thread_is_valid)
|
| 234 |
-
|
| 235 |
-
# lane 0 writes the ballot mask to shared memory
|
| 236 |
-
lane_id = tidx % 32
|
| 237 |
if lane_id == 0:
|
| 238 |
-
|
| 239 |
-
reduction_buffer[warp_idx,
|
| 240 |
-
reduction_buffer[warp_idx, 1] = Int8(1) if warp_has_masked_mask else Int8(0)
|
| 241 |
-
|
| 242 |
cute.arch.sync_threads()
|
| 243 |
|
| 244 |
-
#
|
| 245 |
has_unmasked = Boolean(False)
|
| 246 |
has_masked = Boolean(False)
|
| 247 |
-
if
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
| 253 |
|
| 254 |
# Only thread 0 updates the output arrays (common to both paths)
|
| 255 |
if tidx == 0:
|
|
@@ -261,17 +312,23 @@ class BlockSparsityKernel:
|
|
| 261 |
is_full = Boolean(has_unmasked and (not has_masked))
|
| 262 |
|
| 263 |
if is_partial:
|
| 264 |
-
|
| 265 |
num_mask_blocks += 1
|
| 266 |
elif is_full and const_expr(self.compute_full_blocks):
|
| 267 |
-
|
| 268 |
num_full_blocks += 1
|
| 269 |
|
| 270 |
# Only thread 0 writes back the counts
|
| 271 |
if tidx == 0:
|
| 272 |
-
mask_cnt
|
| 273 |
-
if const_expr(self.
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
|
| 277 |
def compute_block_sparsity(
|
|
@@ -282,11 +339,18 @@ def compute_block_sparsity(
|
|
| 282 |
seqlen_q,
|
| 283 |
seqlen_k,
|
| 284 |
mask_mod: Callable,
|
| 285 |
-
aux_tensors: Optional[list],
|
| 286 |
device,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
compute_full_blocks: bool = True,
|
| 288 |
use_fast_sampling: bool = False,
|
| 289 |
-
) ->
|
| 290 |
"""
|
| 291 |
Computes block sparsity for a given `mask_mod`.
|
| 292 |
|
|
@@ -300,59 +364,145 @@ def compute_block_sparsity(
|
|
| 300 |
mask_mod: The `mask_mod` callable to use.
|
| 301 |
aux_tensors: A list of auxiliary tensors.
|
| 302 |
device: The device to use.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
compute_full_blocks: Whether to compute full blocks. If False, only partially-masked blocks are computed.
|
| 304 |
use_fast_sampling: Whether to use 5-point sampling (4 corners + center). This is much faster, but only suitable for masks where this check is sufficient.
|
| 305 |
|
| 306 |
Returns:
|
| 307 |
-
|
| 308 |
"""
|
| 309 |
-
|
|
|
|
|
|
|
| 310 |
use_fast_sampling = getattr(mask_mod, "use_fast_sampling", use_fast_sampling)
|
| 311 |
|
| 312 |
num_m_blocks = (seqlen_q + tile_m - 1) // tile_m
|
| 313 |
num_n_blocks = (seqlen_k + tile_n - 1) // tile_n
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 328 |
(batch_size, num_heads, num_m_blocks, num_n_blocks), device=device, dtype=torch.int32
|
| 329 |
)
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
|
| 334 |
blocksparse_tensors_torch = BlockSparseTensorsTorch(
|
| 335 |
mask_block_cnt=mask_block_cnt,
|
| 336 |
mask_block_idx=mask_block_idx,
|
| 337 |
full_block_cnt=full_block_cnt,
|
| 338 |
full_block_idx=full_block_idx,
|
|
|
|
|
|
|
| 339 |
block_size=(tile_m, tile_n),
|
| 340 |
)
|
| 341 |
|
| 342 |
mask_mod_hash = hash_callable(mask_mod)
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
| 346 |
|
| 347 |
compile_key = (
|
| 348 |
tile_m,
|
| 349 |
tile_n,
|
| 350 |
mask_mod_hash,
|
|
|
|
|
|
|
| 351 |
compute_full_blocks,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
aux_tensors is not None,
|
| 353 |
use_fast_sampling,
|
| 354 |
)
|
| 355 |
if compile_key not in compute_block_sparsity.compile_cache:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
kernel = BlockSparsityKernel(
|
| 357 |
mask_mod,
|
| 358 |
tile_mn=(tile_m, tile_n),
|
|
@@ -362,17 +512,40 @@ def compute_block_sparsity(
|
|
| 362 |
)
|
| 363 |
|
| 364 |
compute_block_sparsity.compile_cache[compile_key] = cute.compile(
|
| 365 |
-
kernel,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
)
|
| 367 |
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
-
return
|
| 376 |
|
| 377 |
|
| 378 |
compute_block_sparsity.compile_cache = {}
|
|
|
|
| 11 |
BlockSparseTensorsTorch,
|
| 12 |
to_cute_block_sparse_tensors,
|
| 13 |
)
|
| 14 |
+
from .block_sparse_utils import get_curr_blocksparse_tensors
|
| 15 |
+
from .testing import is_fake_mode
|
| 16 |
+
from .cute_dsl_utils import (
|
| 17 |
+
get_aux_tensor_metadata,
|
| 18 |
+
to_cute_aux_tensor,
|
| 19 |
+
to_cute_tensor,
|
| 20 |
+
)
|
| 21 |
+
from .utils import (
|
| 22 |
+
get_batch_from_cu_tensor,
|
| 23 |
+
hash_callable,
|
| 24 |
+
scalar_to_ssa,
|
| 25 |
+
ssa_to_scalar,
|
| 26 |
+
)
|
| 27 |
+
from .mask import call_mask_mod
|
| 28 |
from .seqlen_info import SeqlenInfoQK
|
| 29 |
+
from .utils import AuxData
|
| 30 |
|
| 31 |
|
| 32 |
class BlockSparsityKernel:
|
|
|
|
| 42 |
|
| 43 |
TODO:
|
| 44 |
- optimize mask_mod evaluation
|
|
|
|
| 45 |
- transposed tensors for bwd pass
|
| 46 |
"""
|
| 47 |
|
|
|
|
| 65 |
blocksparse_tensors: BlockSparseTensors,
|
| 66 |
seqlen_q: Int32,
|
| 67 |
seqlen_k: Int32,
|
| 68 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 69 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 70 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 71 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 72 |
+
aux_data: AuxData = AuxData(),
|
| 73 |
):
|
| 74 |
+
mask_cnt, mask_idx, full_cnt, full_idx, mCuTotalMBlocks, mCuBlockIdxOffsets, *_ = (
|
| 75 |
+
blocksparse_tensors
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
self.is_varlen_q = const_expr(mCuSeqlensQ is not None)
|
| 79 |
|
| 80 |
if const_expr(self.compute_full_blocks):
|
| 81 |
+
assert full_cnt is not None and full_idx is not None, (
|
| 82 |
"full block tensors must be provided when computing full blocks"
|
| 83 |
)
|
| 84 |
+
if const_expr(not self.is_varlen_q):
|
| 85 |
+
batch_size, num_heads, num_m_blocks, _ = mask_idx.shape
|
| 86 |
+
total_m_blocks = batch_size * num_m_blocks
|
| 87 |
+
else:
|
| 88 |
+
assert const_expr(mCuTotalMBlocks is not None), (
|
| 89 |
+
"mCuTotalMBlocks must be provided when varlen q"
|
| 90 |
+
)
|
| 91 |
+
num_heads, total_m_blocks = mask_cnt.shape # num_m_blocks is total_m_blocks
|
| 92 |
+
batch_size = mCuSeqlensQ.shape[0] - 1
|
| 93 |
|
| 94 |
if const_expr(self.use_fast_sampling):
|
| 95 |
num_threads = 5
|
|
|
|
| 98 |
num_threads = self.tile_mn[0]
|
| 99 |
self.num_warps = (num_threads + 32 - 1) // 32
|
| 100 |
|
| 101 |
+
if const_expr(not self.is_varlen_q):
|
| 102 |
+
grid = [num_m_blocks, num_heads, batch_size]
|
| 103 |
+
else:
|
| 104 |
+
grid = [total_m_blocks, num_heads, 1]
|
| 105 |
+
|
| 106 |
self.kernel(
|
| 107 |
+
blocksparse_tensors,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
seqlen_q,
|
| 109 |
seqlen_k,
|
| 110 |
+
batch_size,
|
| 111 |
+
mCuSeqlensQ,
|
| 112 |
+
mCuSeqlensK,
|
| 113 |
+
mSeqUsedQ,
|
| 114 |
+
mSeqUsedK,
|
| 115 |
+
mCuTotalMBlocks,
|
| 116 |
+
mCuBlockIdxOffsets,
|
| 117 |
+
aux_data,
|
| 118 |
).launch(grid=grid, block=[num_threads, 1, 1])
|
| 119 |
|
| 120 |
@cute.kernel
|
| 121 |
def kernel(
|
| 122 |
self,
|
| 123 |
+
blocksparse_tensors: BlockSparseTensors,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
seqlen_q: Int32,
|
| 125 |
seqlen_k: Int32,
|
| 126 |
+
batch_size: Int32,
|
| 127 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 128 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 129 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 130 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 131 |
+
mCuTotalMBlocks: Optional[cute.Tensor] = None,
|
| 132 |
+
mCuBlockIdxOffsets: Optional[cute.Tensor] = None,
|
| 133 |
+
aux_data: AuxData = AuxData(),
|
| 134 |
):
|
| 135 |
tidx, _, _ = cute.arch.thread_idx()
|
| 136 |
warp_idx = cute.arch.warp_idx()
|
| 137 |
lane_id = cute.arch.lane_idx()
|
|
|
|
| 138 |
|
| 139 |
ssa = partial(scalar_to_ssa, dtype=Int32)
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
@cute.struct
|
| 142 |
class SharedStorage:
|
| 143 |
reduction_buffer_smem: cute.struct.Align[
|
|
|
|
| 150 |
reduction_buffer = storage.reduction_buffer_smem.get_tensor(
|
| 151 |
cute.make_layout((self.num_warps, 2))
|
| 152 |
)
|
| 153 |
+
SeqlenInfoCls = partial(
|
| 154 |
+
SeqlenInfoQK.create,
|
| 155 |
+
seqlen_q_static=seqlen_q,
|
| 156 |
+
seqlen_k_static=seqlen_k,
|
| 157 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 158 |
+
mCuSeqlensK=mCuSeqlensK,
|
| 159 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 160 |
+
mSeqUsedK=mSeqUsedK,
|
| 161 |
+
mCuTotalMBlocks=mCuTotalMBlocks,
|
| 162 |
+
mCuBlockIdxOffsets=mCuBlockIdxOffsets,
|
| 163 |
+
tile_m=self.tile_mn[0],
|
| 164 |
+
tile_n=self.tile_mn[1],
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
if const_expr(not self.is_varlen_q):
|
| 168 |
+
m_block, head_idx, batch_idx = cute.arch.block_idx()
|
| 169 |
+
else:
|
| 170 |
+
global_m_block, head_idx, _ = cute.arch.block_idx()
|
| 171 |
+
batch_idx = get_batch_from_cu_tensor(global_m_block, mCuTotalMBlocks)
|
| 172 |
+
m_block = global_m_block - mCuTotalMBlocks[batch_idx]
|
| 173 |
+
|
| 174 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 175 |
+
seqlen_q = seqlen.seqlen_q
|
| 176 |
+
seqlen_k = seqlen.seqlen_k
|
| 177 |
+
global_m_block = seqlen.m_block_offset + m_block
|
| 178 |
+
|
| 179 |
+
num_n_blocks = (seqlen_k + self.tile_mn[1] - 1) // self.tile_mn[1]
|
| 180 |
+
|
| 181 |
+
_, curr_mask_idx, _, curr_full_idx = get_curr_blocksparse_tensors(
|
| 182 |
+
batch_idx, head_idx, m_block, blocksparse_tensors, seqlen
|
| 183 |
+
)
|
| 184 |
|
| 185 |
num_mask_blocks = Int32(0)
|
| 186 |
num_full_blocks = Int32(0)
|
| 187 |
|
| 188 |
+
m_base = m_block * self.tile_mn[0]
|
| 189 |
+
if const_expr(self.use_fast_sampling):
|
| 190 |
+
# Loop-invariant per-thread q_idx for the 5 sample points
|
| 191 |
+
# (tidx 0, 1: top corners; 2, 3: bottom corners; 4: center).
|
| 192 |
+
q_idx_sample = m_base
|
| 193 |
+
if tidx == 2 or tidx == 3:
|
| 194 |
+
q_idx_sample = cutlass.min(m_base + self.tile_mn[0] - 1, seqlen_q - 1)
|
| 195 |
+
elif tidx == 4:
|
| 196 |
+
q_idx_sample = m_base + cutlass.min(seqlen_q - m_base, self.tile_mn[0]) // 2
|
| 197 |
+
else:
|
| 198 |
+
q_idx_thread = m_base + tidx
|
| 199 |
+
thread_in_bounds = Boolean(tidx < self.tile_mn[0] and q_idx_thread < seqlen_q)
|
| 200 |
+
|
| 201 |
+
for n_block in cutlass.range(num_n_blocks):
|
| 202 |
n_base = n_block * self.tile_mn[1]
|
| 203 |
|
| 204 |
if const_expr(self.use_fast_sampling):
|
| 205 |
+
# 5-point sampling (4 corners + center). Interior n_blocks
|
| 206 |
+
# (n_base + tile_n <= seqlen_k) skip the OOB clamp on the right /
|
| 207 |
+
# center samples.
|
| 208 |
+
is_interior = (n_base + self.tile_mn[1]) <= seqlen_k
|
| 209 |
+
n_right = Int32(0)
|
| 210 |
+
n_mid = Int32(0)
|
| 211 |
+
if is_interior:
|
| 212 |
+
n_right = n_base + self.tile_mn[1] - 1
|
| 213 |
+
n_mid = n_base + self.tile_mn[1] // 2
|
| 214 |
+
else:
|
| 215 |
+
n_right = cutlass.min(n_base + self.tile_mn[1] - 1, seqlen_k - 1)
|
| 216 |
+
n_mid = n_base + cutlass.min(seqlen_k - n_base, self.tile_mn[1]) // 2
|
| 217 |
|
| 218 |
+
kv_idx = n_base
|
| 219 |
+
if tidx == 1 or tidx == 3:
|
| 220 |
+
kv_idx = n_right
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
elif tidx == 4:
|
| 222 |
+
kv_idx = n_mid
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
+
thread_result = Boolean(False)
|
| 225 |
+
thread_is_valid = Boolean(False)
|
| 226 |
+
if tidx < 5:
|
| 227 |
thread_is_valid = Boolean(True)
|
|
|
|
|
|
|
| 228 |
thread_result = ssa_to_scalar(
|
| 229 |
+
call_mask_mod(
|
| 230 |
+
self.mask_mod,
|
| 231 |
ssa(batch_idx),
|
| 232 |
ssa(head_idx),
|
| 233 |
+
ssa(q_idx_sample),
|
| 234 |
+
ssa(kv_idx),
|
| 235 |
seqlen,
|
| 236 |
+
aux_data,
|
| 237 |
)
|
| 238 |
)
|
|
|
|
|
|
|
| 239 |
|
|
|
|
|
|
|
| 240 |
has_unmasked = cute.arch.vote_any_sync(thread_result & thread_is_valid)
|
| 241 |
+
has_masked = cute.arch.vote_any_sync(Boolean(not thread_result) & thread_is_valid)
|
| 242 |
|
| 243 |
else:
|
| 244 |
+
# Full path. Interior blocks (n_base + tile_n <= seqlen_k) drop the
|
| 245 |
+
# per-element bound check; the boundary block (at most one) keeps it.
|
| 246 |
thread_has_unmasked = Boolean(False)
|
| 247 |
thread_has_masked = Boolean(False)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
kv_idx = Int32(0)
|
| 249 |
+
is_interior = (n_base + self.tile_mn[1]) <= seqlen_k
|
|
|
|
|
|
|
| 250 |
|
| 251 |
+
if is_interior:
|
| 252 |
+
if thread_in_bounds:
|
| 253 |
+
for c in cutlass.range(self.tile_mn[1], unroll_full=True):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
mask_val = ssa_to_scalar(
|
| 255 |
+
call_mask_mod(
|
| 256 |
+
self.mask_mod,
|
| 257 |
ssa(batch_idx),
|
| 258 |
ssa(head_idx),
|
| 259 |
+
ssa(q_idx_thread),
|
| 260 |
+
ssa(n_base + c),
|
| 261 |
seqlen,
|
| 262 |
+
aux_data,
|
| 263 |
)
|
| 264 |
)
|
| 265 |
+
thread_has_unmasked |= Boolean(mask_val)
|
| 266 |
+
thread_has_masked |= Boolean(not mask_val)
|
| 267 |
+
else:
|
| 268 |
+
if thread_in_bounds:
|
| 269 |
+
for c in cutlass.range(self.tile_mn[1], unroll_full=True):
|
| 270 |
+
kv_idx = n_base + c
|
| 271 |
+
if kv_idx < seqlen_k:
|
| 272 |
+
mask_val = ssa_to_scalar(
|
| 273 |
+
call_mask_mod(
|
| 274 |
+
self.mask_mod,
|
| 275 |
+
ssa(batch_idx),
|
| 276 |
+
ssa(head_idx),
|
| 277 |
+
ssa(q_idx_thread),
|
| 278 |
+
ssa(kv_idx),
|
| 279 |
+
seqlen,
|
| 280 |
+
aux_data,
|
| 281 |
+
)
|
| 282 |
+
)
|
| 283 |
+
thread_has_unmasked |= Boolean(mask_val)
|
| 284 |
+
thread_has_masked |= Boolean(not mask_val)
|
| 285 |
|
| 286 |
+
warp_unmasked = cute.arch.vote_any_sync(thread_has_unmasked & thread_in_bounds)
|
| 287 |
+
warp_masked = cute.arch.vote_any_sync(thread_has_masked & thread_in_bounds)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
if lane_id == 0:
|
| 289 |
+
reduction_buffer[warp_idx, 0] = Int8(1) if warp_unmasked else Int8(0)
|
| 290 |
+
reduction_buffer[warp_idx, 1] = Int8(1) if warp_masked else Int8(0)
|
|
|
|
|
|
|
| 291 |
cute.arch.sync_threads()
|
| 292 |
|
| 293 |
+
# Cross-warp OR via warp 0; thread 0 (lane 0 of warp 0) holds the result.
|
| 294 |
has_unmasked = Boolean(False)
|
| 295 |
has_masked = Boolean(False)
|
| 296 |
+
if warp_idx == 0:
|
| 297 |
+
lane_unmasked = Boolean(False)
|
| 298 |
+
lane_masked = Boolean(False)
|
| 299 |
+
if lane_id < self.num_warps:
|
| 300 |
+
lane_unmasked = reduction_buffer[lane_id, 0] != Int8(0)
|
| 301 |
+
lane_masked = reduction_buffer[lane_id, 1] != Int8(0)
|
| 302 |
+
has_unmasked = cute.arch.vote_any_sync(lane_unmasked)
|
| 303 |
+
has_masked = cute.arch.vote_any_sync(lane_masked)
|
| 304 |
|
| 305 |
# Only thread 0 updates the output arrays (common to both paths)
|
| 306 |
if tidx == 0:
|
|
|
|
| 312 |
is_full = Boolean(has_unmasked and (not has_masked))
|
| 313 |
|
| 314 |
if is_partial:
|
| 315 |
+
curr_mask_idx[num_mask_blocks] = n_block
|
| 316 |
num_mask_blocks += 1
|
| 317 |
elif is_full and const_expr(self.compute_full_blocks):
|
| 318 |
+
curr_full_idx[num_full_blocks] = n_block
|
| 319 |
num_full_blocks += 1
|
| 320 |
|
| 321 |
# Only thread 0 writes back the counts
|
| 322 |
if tidx == 0:
|
| 323 |
+
mask_cnt, _, full_cnt, *_ = blocksparse_tensors
|
| 324 |
+
if const_expr(self.is_varlen_q):
|
| 325 |
+
mask_cnt[head_idx, global_m_block] = num_mask_blocks
|
| 326 |
+
if const_expr(self.compute_full_blocks):
|
| 327 |
+
full_cnt[head_idx, global_m_block] = num_full_blocks
|
| 328 |
+
else:
|
| 329 |
+
mask_cnt[batch_idx, head_idx, m_block] = num_mask_blocks
|
| 330 |
+
if const_expr(self.compute_full_blocks):
|
| 331 |
+
full_cnt[batch_idx, head_idx, m_block] = num_full_blocks
|
| 332 |
|
| 333 |
|
| 334 |
def compute_block_sparsity(
|
|
|
|
| 339 |
seqlen_q,
|
| 340 |
seqlen_k,
|
| 341 |
mask_mod: Callable,
|
| 342 |
+
aux_tensors: Optional[list],
|
| 343 |
device,
|
| 344 |
+
aux_scalars: Optional[tuple] = None,
|
| 345 |
+
cu_seqlens_q: Optional[torch.Tensor] = None,
|
| 346 |
+
cu_seqlens_k: Optional[torch.Tensor] = None,
|
| 347 |
+
seqused_q: Optional[torch.Tensor] = None,
|
| 348 |
+
seqused_k: Optional[torch.Tensor] = None,
|
| 349 |
+
cu_total_m_blocks: Optional[torch.Tensor] = None,
|
| 350 |
+
cu_block_idx_offsets: Optional[torch.Tensor] = None,
|
| 351 |
compute_full_blocks: bool = True,
|
| 352 |
use_fast_sampling: bool = False,
|
| 353 |
+
) -> BlockSparseTensorsTorch:
|
| 354 |
"""
|
| 355 |
Computes block sparsity for a given `mask_mod`.
|
| 356 |
|
|
|
|
| 364 |
mask_mod: The `mask_mod` callable to use.
|
| 365 |
aux_tensors: A list of auxiliary tensors.
|
| 366 |
device: The device to use.
|
| 367 |
+
cu_seqlens_q: Cumulative q sequence lengths for varlen
|
| 368 |
+
cu_seqlens_k: Cumulative k sequence lengths for varlen
|
| 369 |
+
seqused_q: Per-batch effective q sequence lengths
|
| 370 |
+
seqused_k: Per-batch effective k sequence lengths
|
| 371 |
+
cu_total_m_blocks: Cumulative total m blocks tensor for varlen q
|
| 372 |
+
cu_block_idx_offsets: Cumulative offsets into the packed mask_block_idx /
|
| 373 |
+
full_block_idx tensors per batch (== cumsum of M_b * N_b).
|
| 374 |
compute_full_blocks: Whether to compute full blocks. If False, only partially-masked blocks are computed.
|
| 375 |
use_fast_sampling: Whether to use 5-point sampling (4 corners + center). This is much faster, but only suitable for masks where this check is sufficient.
|
| 376 |
|
| 377 |
Returns:
|
| 378 |
+
BlockSparseTensorsTorch
|
| 379 |
"""
|
| 380 |
+
aux_scalars = tuple(aux_scalars) if aux_scalars else None
|
| 381 |
+
|
| 382 |
+
# Check if mask_mod is marked as suitable for 5-point sampling
|
| 383 |
use_fast_sampling = getattr(mask_mod, "use_fast_sampling", use_fast_sampling)
|
| 384 |
|
| 385 |
num_m_blocks = (seqlen_q + tile_m - 1) // tile_m
|
| 386 |
num_n_blocks = (seqlen_k + tile_n - 1) // tile_n
|
| 387 |
|
| 388 |
+
if cu_seqlens_q is not None:
|
| 389 |
+
assert cu_total_m_blocks is not None, "total m blocks must be provided when varlen q"
|
| 390 |
+
total_m_blocks = cu_total_m_blocks[-1].item()
|
| 391 |
+
if cu_block_idx_offsets is None and (cu_seqlens_k is not None or seqused_k is not None):
|
| 392 |
+
# Derive cu_block_idx_offsets from per-batch K seqlens.
|
| 393 |
+
cu_block_idx_offsets_list = [0]
|
| 394 |
+
for batch_idx in range(batch_size):
|
| 395 |
+
batch_seqlen_q = cu_seqlens_q[batch_idx + 1].item() - cu_seqlens_q[batch_idx].item()
|
| 396 |
+
if cu_seqlens_k is not None:
|
| 397 |
+
batch_seqlen_k = (
|
| 398 |
+
cu_seqlens_k[batch_idx + 1].item() - cu_seqlens_k[batch_idx].item()
|
| 399 |
+
)
|
| 400 |
+
else:
|
| 401 |
+
batch_seqlen_k = seqused_k[batch_idx].item()
|
| 402 |
+
num_m_blocks_batch = (batch_seqlen_q + tile_m - 1) // tile_m
|
| 403 |
+
num_n_blocks_batch = (batch_seqlen_k + tile_n - 1) // tile_n
|
| 404 |
+
cu_block_idx_offsets_list.append(
|
| 405 |
+
cu_block_idx_offsets_list[-1] + num_m_blocks_batch * num_n_blocks_batch
|
| 406 |
+
)
|
| 407 |
+
cu_block_idx_offsets = torch.tensor(
|
| 408 |
+
cu_block_idx_offsets_list, dtype=torch.int32, device=device
|
| 409 |
+
)
|
| 410 |
+
if cu_block_idx_offsets is not None:
|
| 411 |
+
total_n_blocks = cu_block_idx_offsets[-1].item()
|
| 412 |
+
else:
|
| 413 |
+
# Uniform-K varlen-Q: every batch has the same K seqlen.
|
| 414 |
+
total_n_blocks = total_m_blocks * num_n_blocks
|
| 415 |
+
|
| 416 |
+
mask_block_cnt = torch.zeros((num_heads, total_m_blocks), device=device, dtype=torch.int32)
|
| 417 |
+
mask_block_idx = torch.zeros((num_heads, total_n_blocks), device=device, dtype=torch.int32)
|
| 418 |
+
full_block_cnt = (
|
| 419 |
+
torch.zeros((num_heads, total_m_blocks), device=device, dtype=torch.int32)
|
| 420 |
+
if compute_full_blocks
|
| 421 |
+
else None
|
| 422 |
+
)
|
| 423 |
+
full_block_idx = (
|
| 424 |
+
torch.zeros((num_heads, total_n_blocks), device=device, dtype=torch.int32)
|
| 425 |
+
if compute_full_blocks
|
| 426 |
+
else None
|
| 427 |
+
)
|
| 428 |
+
else:
|
| 429 |
+
total_m_blocks = batch_size * num_m_blocks
|
| 430 |
+
total_n_blocks = batch_size * num_m_blocks * num_n_blocks
|
| 431 |
+
|
| 432 |
+
mask_block_cnt = torch.zeros(
|
| 433 |
+
(batch_size, num_heads, num_m_blocks), device=device, dtype=torch.int32
|
| 434 |
+
)
|
| 435 |
+
mask_block_idx = torch.zeros(
|
| 436 |
(batch_size, num_heads, num_m_blocks, num_n_blocks), device=device, dtype=torch.int32
|
| 437 |
)
|
| 438 |
+
full_block_cnt = (
|
| 439 |
+
torch.zeros((batch_size, num_heads, num_m_blocks), device=device, dtype=torch.int32)
|
| 440 |
+
if compute_full_blocks
|
| 441 |
+
else None
|
| 442 |
+
)
|
| 443 |
+
full_block_idx = (
|
| 444 |
+
torch.zeros(
|
| 445 |
+
(batch_size, num_heads, num_m_blocks, num_n_blocks),
|
| 446 |
+
device=device,
|
| 447 |
+
dtype=torch.int32,
|
| 448 |
+
)
|
| 449 |
+
if compute_full_blocks
|
| 450 |
+
else None
|
| 451 |
+
)
|
| 452 |
|
| 453 |
blocksparse_tensors_torch = BlockSparseTensorsTorch(
|
| 454 |
mask_block_cnt=mask_block_cnt,
|
| 455 |
mask_block_idx=mask_block_idx,
|
| 456 |
full_block_cnt=full_block_cnt,
|
| 457 |
full_block_idx=full_block_idx,
|
| 458 |
+
cu_total_m_blocks=cu_total_m_blocks,
|
| 459 |
+
cu_block_idx_offsets=cu_block_idx_offsets,
|
| 460 |
block_size=(tile_m, tile_n),
|
| 461 |
)
|
| 462 |
|
| 463 |
mask_mod_hash = hash_callable(mask_mod)
|
| 464 |
+
if aux_tensors is not None:
|
| 465 |
+
aux_tensor_metadata = get_aux_tensor_metadata(aux_tensors)
|
| 466 |
+
else:
|
| 467 |
+
aux_tensor_metadata = None
|
| 468 |
+
aux_scalar_metadata = tuple(type(s) for s in aux_scalars) if aux_scalars is not None else None
|
| 469 |
|
| 470 |
compile_key = (
|
| 471 |
tile_m,
|
| 472 |
tile_n,
|
| 473 |
mask_mod_hash,
|
| 474 |
+
aux_tensor_metadata,
|
| 475 |
+
aux_scalar_metadata,
|
| 476 |
compute_full_blocks,
|
| 477 |
+
cu_seqlens_q is None,
|
| 478 |
+
cu_seqlens_k is None,
|
| 479 |
+
seqused_q is None,
|
| 480 |
+
seqused_k is None,
|
| 481 |
aux_tensors is not None,
|
| 482 |
use_fast_sampling,
|
| 483 |
)
|
| 484 |
if compile_key not in compute_block_sparsity.compile_cache:
|
| 485 |
+
(
|
| 486 |
+
cu_seqlens_q_tensor,
|
| 487 |
+
cu_seqlens_k_tensor,
|
| 488 |
+
seqused_q_tensor,
|
| 489 |
+
seqused_k_tensor,
|
| 490 |
+
) = [
|
| 491 |
+
to_cute_tensor(t, assumed_align=4, leading_dim=0) if t is not None else None
|
| 492 |
+
for t in (
|
| 493 |
+
cu_seqlens_q,
|
| 494 |
+
cu_seqlens_k,
|
| 495 |
+
seqused_q,
|
| 496 |
+
seqused_k,
|
| 497 |
+
)
|
| 498 |
+
]
|
| 499 |
+
blocksparse_tensors = to_cute_block_sparse_tensors(
|
| 500 |
+
blocksparse_tensors_torch, enable_tvm_ffi=True
|
| 501 |
+
)
|
| 502 |
+
if aux_tensors is not None:
|
| 503 |
+
cute_aux_tensors = [to_cute_aux_tensor(buf) for buf in aux_tensors]
|
| 504 |
+
else:
|
| 505 |
+
cute_aux_tensors = None
|
| 506 |
kernel = BlockSparsityKernel(
|
| 507 |
mask_mod,
|
| 508 |
tile_mn=(tile_m, tile_n),
|
|
|
|
| 512 |
)
|
| 513 |
|
| 514 |
compute_block_sparsity.compile_cache[compile_key] = cute.compile(
|
| 515 |
+
kernel,
|
| 516 |
+
blocksparse_tensors,
|
| 517 |
+
seqlen_q,
|
| 518 |
+
seqlen_k,
|
| 519 |
+
cu_seqlens_q_tensor,
|
| 520 |
+
cu_seqlens_k_tensor,
|
| 521 |
+
seqused_q_tensor,
|
| 522 |
+
seqused_k_tensor,
|
| 523 |
+
AuxData(cute_aux_tensors, aux_scalars),
|
| 524 |
+
options="--enable-tvm-ffi",
|
| 525 |
)
|
| 526 |
|
| 527 |
+
if not is_fake_mode():
|
| 528 |
+
compute_block_sparsity.compile_cache[compile_key](
|
| 529 |
+
(
|
| 530 |
+
blocksparse_tensors_torch.mask_block_cnt,
|
| 531 |
+
blocksparse_tensors_torch.mask_block_idx,
|
| 532 |
+
blocksparse_tensors_torch.full_block_cnt,
|
| 533 |
+
blocksparse_tensors_torch.full_block_idx,
|
| 534 |
+
blocksparse_tensors_torch.cu_total_m_blocks,
|
| 535 |
+
blocksparse_tensors_torch.cu_block_idx_offsets,
|
| 536 |
+
blocksparse_tensors_torch.dq_write_order,
|
| 537 |
+
blocksparse_tensors_torch.dq_write_order_full,
|
| 538 |
+
),
|
| 539 |
+
seqlen_q,
|
| 540 |
+
seqlen_k,
|
| 541 |
+
cu_seqlens_q,
|
| 542 |
+
cu_seqlens_k,
|
| 543 |
+
seqused_q,
|
| 544 |
+
seqused_k,
|
| 545 |
+
AuxData(aux_tensors, aux_scalars),
|
| 546 |
+
)
|
| 547 |
|
| 548 |
+
return blocksparse_tensors_torch
|
| 549 |
|
| 550 |
|
| 551 |
compute_block_sparsity.compile_cache = {}
|
build/torch-cuda/cu_blocks_kernel.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Callable, Optional
|
| 2 |
+
|
| 3 |
+
import cuda.bindings.driver as cuda
|
| 4 |
+
|
| 5 |
+
import cutlass
|
| 6 |
+
import cutlass.cute as cute
|
| 7 |
+
from cutlass import Int32, const_expr
|
| 8 |
+
|
| 9 |
+
from .utils import get_batch_from_cu_tensor
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class CuSeqlensToBlocksKernel:
|
| 13 |
+
def __init__(
|
| 14 |
+
self,
|
| 15 |
+
tile: int = 128,
|
| 16 |
+
num_threads: int = 1024,
|
| 17 |
+
seqlen_q_multiplier: int = 1,
|
| 18 |
+
):
|
| 19 |
+
self.tile = tile
|
| 20 |
+
self.num_threads = num_threads
|
| 21 |
+
assert num_threads % 32 == 0
|
| 22 |
+
self.num_warps = num_threads // cute.arch.WARP_SIZE
|
| 23 |
+
self.seqlen_q_multiplier = seqlen_q_multiplier
|
| 24 |
+
|
| 25 |
+
@cute.jit
|
| 26 |
+
def __call__(
|
| 27 |
+
self,
|
| 28 |
+
mCuBlocks: cute.Tensor,
|
| 29 |
+
mCuSplitsBlocks: Optional[cute.Tensor],
|
| 30 |
+
mCuSeqlens: Optional[cute.Tensor],
|
| 31 |
+
mSeqUsed: Optional[cute.Tensor] = None,
|
| 32 |
+
mNumSplits: Optional[cute.Tensor] = None,
|
| 33 |
+
mVirtualBatchIdx: Optional[cute.Tensor] = None,
|
| 34 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 35 |
+
stream: cuda.CUstream = None,
|
| 36 |
+
):
|
| 37 |
+
assert const_expr((mNumSplits is None) == (mCuSplitsBlocks is None))
|
| 38 |
+
assert const_expr(mCuSeqlens is not None or mSeqUsed is not None)
|
| 39 |
+
|
| 40 |
+
@cute.struct
|
| 41 |
+
class SharedStorage:
|
| 42 |
+
warp_block_count: cute.struct.MemRange[Int32, self.num_warps]
|
| 43 |
+
warp_split_count: cute.struct.MemRange[Int32, self.num_warps]
|
| 44 |
+
|
| 45 |
+
self.kernel(
|
| 46 |
+
mCuBlocks,
|
| 47 |
+
mCuSplitsBlocks,
|
| 48 |
+
mCuSeqlens,
|
| 49 |
+
mSeqUsed,
|
| 50 |
+
mNumSplits,
|
| 51 |
+
mVirtualBatchIdx,
|
| 52 |
+
SharedStorage,
|
| 53 |
+
).launch(
|
| 54 |
+
grid=[1, 1, 1],
|
| 55 |
+
block=[self.num_threads, 1, 1],
|
| 56 |
+
stream=stream,
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
@cute.kernel
|
| 60 |
+
def kernel(
|
| 61 |
+
self,
|
| 62 |
+
mCuBlocks: cute.Tensor,
|
| 63 |
+
mCuSplitsBlocks: Optional[cute.Tensor],
|
| 64 |
+
mCuSeqlens: Optional[cute.Tensor],
|
| 65 |
+
mSeqUsed: Optional[cute.Tensor],
|
| 66 |
+
mNumSplits: Optional[cute.Tensor],
|
| 67 |
+
mVirtualBatchIdx: Optional[cute.Tensor],
|
| 68 |
+
SharedStorage: cutlass.Constexpr[Callable],
|
| 69 |
+
):
|
| 70 |
+
has_splits = mNumSplits is not None
|
| 71 |
+
batch_size = mCuBlocks.shape[0] - 1
|
| 72 |
+
tidx = cute.arch.thread_idx()[0]
|
| 73 |
+
lane_idx = cute.arch.lane_idx()
|
| 74 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 75 |
+
|
| 76 |
+
smem = cutlass.utils.SmemAllocator()
|
| 77 |
+
storage = smem.allocate(SharedStorage)
|
| 78 |
+
warp_block_count = storage.warp_block_count.get_tensor(cute.make_layout(self.num_warps))
|
| 79 |
+
warp_split_count = storage.warp_split_count.get_tensor(cute.make_layout(self.num_warps))
|
| 80 |
+
|
| 81 |
+
if tidx == 0:
|
| 82 |
+
mCuBlocks[0] = 0
|
| 83 |
+
if const_expr(has_splits):
|
| 84 |
+
mCuSplitsBlocks[0] = 0
|
| 85 |
+
|
| 86 |
+
# Process the batch in chunks of num_threads, carrying the running totals in the bases.
|
| 87 |
+
base = Int32(0)
|
| 88 |
+
base_splits = Int32(0)
|
| 89 |
+
num_chunks = (batch_size + self.num_threads - 1) // self.num_threads
|
| 90 |
+
for chunk in cutlass.range(num_chunks):
|
| 91 |
+
batch_idx = chunk * self.num_threads + tidx
|
| 92 |
+
|
| 93 |
+
seqlen = Int32(0)
|
| 94 |
+
batch_splits = Int32(0)
|
| 95 |
+
if batch_idx < batch_size:
|
| 96 |
+
if const_expr(mVirtualBatchIdx is not None):
|
| 97 |
+
batch_idx = Int32(mVirtualBatchIdx[batch_idx])
|
| 98 |
+
if const_expr(mSeqUsed is not None):
|
| 99 |
+
seqlen = mSeqUsed[batch_idx]
|
| 100 |
+
else:
|
| 101 |
+
seqlen = mCuSeqlens[batch_idx + 1] - mCuSeqlens[batch_idx]
|
| 102 |
+
if const_expr(has_splits):
|
| 103 |
+
batch_splits = mNumSplits[batch_idx]
|
| 104 |
+
seqlen *= self.seqlen_q_multiplier
|
| 105 |
+
num_blocks = (seqlen + self.tile - 1) // self.tile
|
| 106 |
+
num_split_blocks = num_blocks * batch_splits
|
| 107 |
+
|
| 108 |
+
total_blocks_for_batch = num_blocks
|
| 109 |
+
total_split_blocks_for_batch = num_split_blocks
|
| 110 |
+
|
| 111 |
+
for delta in (1, 2, 4, 8, 16):
|
| 112 |
+
other = cute.arch.shuffle_sync_up(total_blocks_for_batch, delta, mask_and_clamp=0)
|
| 113 |
+
if const_expr(has_splits):
|
| 114 |
+
other_splits = cute.arch.shuffle_sync_up(
|
| 115 |
+
total_split_blocks_for_batch, delta, mask_and_clamp=0
|
| 116 |
+
)
|
| 117 |
+
if lane_idx >= delta:
|
| 118 |
+
total_split_blocks_for_batch += other_splits
|
| 119 |
+
if lane_idx >= delta:
|
| 120 |
+
total_blocks_for_batch += other
|
| 121 |
+
|
| 122 |
+
if lane_idx == 31:
|
| 123 |
+
warp_block_count[warp_idx] = total_blocks_for_batch
|
| 124 |
+
if const_expr(has_splits):
|
| 125 |
+
warp_split_count[warp_idx] = total_split_blocks_for_batch
|
| 126 |
+
|
| 127 |
+
cute.arch.sync_threads()
|
| 128 |
+
|
| 129 |
+
total_blocks_for_batch += base
|
| 130 |
+
total_split_blocks_for_batch += base_splits
|
| 131 |
+
|
| 132 |
+
for idx in cutlass.range(warp_idx):
|
| 133 |
+
total_blocks_for_batch += warp_block_count[idx]
|
| 134 |
+
if const_expr(has_splits):
|
| 135 |
+
total_split_blocks_for_batch += warp_split_count[idx]
|
| 136 |
+
|
| 137 |
+
if batch_idx < batch_size:
|
| 138 |
+
mCuBlocks[chunk * self.num_threads + tidx + 1] = total_blocks_for_batch
|
| 139 |
+
if const_expr(has_splits):
|
| 140 |
+
mCuSplitsBlocks[chunk * self.num_threads + tidx + 1] = (
|
| 141 |
+
total_split_blocks_for_batch
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
for idx in cutlass.range(self.num_warps):
|
| 145 |
+
base += warp_block_count[idx]
|
| 146 |
+
if const_expr(has_splits):
|
| 147 |
+
base_splits += warp_split_count[idx]
|
| 148 |
+
# warp_block_count / warp_split_count are reused by the next chunk.
|
| 149 |
+
cute.arch.sync_threads()
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class CuBlocksToBatchKernel:
|
| 153 |
+
"""Inverts a cumulative block count into a flat block -> batch lookup."""
|
| 154 |
+
|
| 155 |
+
def __init__(self, num_threads: int = 128):
|
| 156 |
+
self.num_threads = num_threads
|
| 157 |
+
|
| 158 |
+
@cute.jit
|
| 159 |
+
def __call__(
|
| 160 |
+
self,
|
| 161 |
+
mCuTotalBlocks: cute.Tensor,
|
| 162 |
+
mBlocksToBatchIdx: cute.Tensor,
|
| 163 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 164 |
+
stream: cuda.CUstream = None,
|
| 165 |
+
):
|
| 166 |
+
num_blocks = mBlocksToBatchIdx.shape[0]
|
| 167 |
+
self.kernel(mCuTotalBlocks, mBlocksToBatchIdx).launch(
|
| 168 |
+
grid=[(num_blocks + self.num_threads - 1) // self.num_threads, 1, 1],
|
| 169 |
+
block=[self.num_threads, 1, 1],
|
| 170 |
+
stream=stream,
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
@cute.kernel
|
| 174 |
+
def kernel(
|
| 175 |
+
self,
|
| 176 |
+
mCuTotalBlocks: cute.Tensor,
|
| 177 |
+
mBlocksToBatchIdx: cute.Tensor,
|
| 178 |
+
):
|
| 179 |
+
block_idx = cute.arch.block_idx()[0] * self.num_threads + cute.arch.thread_idx()[0]
|
| 180 |
+
if block_idx < mBlocksToBatchIdx.shape[0]:
|
| 181 |
+
mBlocksToBatchIdx[block_idx] = get_batch_from_cu_tensor(block_idx, mCuTotalBlocks)
|
build/torch-cuda/cute_dsl_utils.py
CHANGED
|
@@ -1,12 +1,11 @@
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
| 3 |
import os
|
| 4 |
-
import pathlib
|
| 5 |
from typing import Tuple
|
| 6 |
-
from functools import
|
| 7 |
-
from dataclasses import dataclass, fields
|
| 8 |
|
| 9 |
import torch
|
|
|
|
| 10 |
|
| 11 |
try:
|
| 12 |
from triton.tools.disasm import extract
|
|
@@ -15,7 +14,6 @@ except ImportError:
|
|
| 15 |
|
| 16 |
import cutlass
|
| 17 |
import cutlass.cute as cute
|
| 18 |
-
from cutlass.base_dsl.typing import JitArgument
|
| 19 |
from cutlass.cutlass_dsl import NumericMeta
|
| 20 |
from cutlass.cute.runtime import from_dlpack
|
| 21 |
|
|
@@ -30,6 +28,8 @@ torch2cute_dtype_map = {
|
|
| 30 |
torch.float16: cutlass.Float16,
|
| 31 |
torch.bfloat16: cutlass.BFloat16,
|
| 32 |
torch.float32: cutlass.Float32,
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
|
|
@@ -43,61 +43,73 @@ def get_device_capacity(device: torch.device = None) -> Tuple[int, int]:
|
|
| 43 |
return torch.cuda.get_device_capability(device)
|
| 44 |
|
| 45 |
|
| 46 |
-
@
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
return
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
def
|
| 88 |
-
"""
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
| 93 |
)
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
|
| 103 |
def assume_strides_aligned(t):
|
|
@@ -120,7 +132,22 @@ def assume_tensor_aligned(t):
|
|
| 120 |
|
| 121 |
def to_cute_tensor(t, assumed_align=16, leading_dim=-1, fully_dynamic=False, enable_tvm_ffi=True):
|
| 122 |
"""Convert torch tensor to cute tensor for TVM FFI. leading_dim=-1 defaults to t.ndim-1."""
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
if fully_dynamic:
|
| 125 |
return tensor.mark_layout_dynamic()
|
| 126 |
if leading_dim == -1:
|
|
@@ -146,15 +173,37 @@ def to_cute_aux_tensor(t, enable_tvm_ffi=True):
|
|
| 146 |
)
|
| 147 |
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
def get_aux_tensor_metadata(aux_tensors):
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
| 155 |
)
|
| 156 |
-
|
| 157 |
-
)
|
| 158 |
|
| 159 |
|
| 160 |
def get_broadcast_dims(tensor: torch.Tensor) -> Tuple[bool, ...]:
|
|
@@ -164,4 +213,43 @@ def get_broadcast_dims(tensor: torch.Tensor) -> Tuple[bool, ...]:
|
|
| 164 |
stride=0 as static, meaning kernels compiled with different broadcast
|
| 165 |
patterns are not interchangeable.
|
| 166 |
"""
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
| 3 |
import os
|
|
|
|
| 4 |
from typing import Tuple
|
| 5 |
+
from functools import lru_cache
|
|
|
|
| 6 |
|
| 7 |
import torch
|
| 8 |
+
from torch._subclasses.fake_tensor import FakeTensor
|
| 9 |
|
| 10 |
try:
|
| 11 |
from triton.tools.disasm import extract
|
|
|
|
| 14 |
|
| 15 |
import cutlass
|
| 16 |
import cutlass.cute as cute
|
|
|
|
| 17 |
from cutlass.cutlass_dsl import NumericMeta
|
| 18 |
from cutlass.cute.runtime import from_dlpack
|
| 19 |
|
|
|
|
| 28 |
torch.float16: cutlass.Float16,
|
| 29 |
torch.bfloat16: cutlass.BFloat16,
|
| 30 |
torch.float32: cutlass.Float32,
|
| 31 |
+
torch.float8_e4m3fn: cutlass.Float8E4M3FN,
|
| 32 |
+
torch.float8_e5m2: cutlass.Float8E5M2,
|
| 33 |
}
|
| 34 |
|
| 35 |
|
|
|
|
| 43 |
return torch.cuda.get_device_capability(device)
|
| 44 |
|
| 45 |
|
| 46 |
+
@lru_cache
|
| 47 |
+
def _get_device_arch_and_num_sms(device_index: int) -> tuple[int, int]:
|
| 48 |
+
properties = torch.cuda.get_device_properties(device_index)
|
| 49 |
+
return properties.major * 10 + properties.minor, properties.multi_processor_count
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def get_num_sms_for_selection(device_index: int, arch: int) -> int:
|
| 53 |
+
"""Return the SM count of the matching local GPU or cross-compilation target."""
|
| 54 |
+
override = os.getenv("FLASH_ATTENTION_NUM_SMS")
|
| 55 |
+
if override is not None:
|
| 56 |
+
num_sms = int(override)
|
| 57 |
+
if num_sms <= 0:
|
| 58 |
+
raise ValueError("FLASH_ATTENTION_NUM_SMS must be positive")
|
| 59 |
+
return num_sms
|
| 60 |
+
if torch.cuda.is_available():
|
| 61 |
+
device_arch, num_sms = _get_device_arch_and_num_sms(device_index)
|
| 62 |
+
if device_arch == arch:
|
| 63 |
+
return num_sms
|
| 64 |
+
raise RuntimeError(
|
| 65 |
+
"Cannot determine the target GPU's SM count; set FLASH_ATTENTION_NUM_SMS "
|
| 66 |
+
"when cross-compiling without a matching local GPU"
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _has_aligned_pointer(tensor: torch.Tensor, align_bytes: int) -> bool:
|
| 71 |
+
address = (
|
| 72 |
+
tensor.storage_offset() * tensor.element_size()
|
| 73 |
+
if isinstance(tensor, FakeTensor)
|
| 74 |
+
else tensor.data_ptr()
|
| 75 |
+
)
|
| 76 |
+
return address % align_bytes == 0
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def _is_aligned_layout(tensor: torch.Tensor, align_bytes: int) -> bool:
|
| 80 |
+
"""Return whether a tensor satisfies the pointer and stride ABI kernels assume."""
|
| 81 |
+
if tensor.stride(-1) != 1 or not _has_aligned_pointer(tensor, align_bytes):
|
| 82 |
+
return False
|
| 83 |
+
stride_alignment = max(1, align_bytes // tensor.element_size())
|
| 84 |
+
return all(stride == 0 or stride % stride_alignment == 0 for stride in tensor.stride()[:-1])
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def maybe_contiguous(tensor: torch.Tensor | None, align_bytes: int = 16):
|
| 88 |
+
"""Canonicalize inputs to the pointer and stride alignment kernels assume."""
|
| 89 |
+
if tensor is None:
|
| 90 |
+
return None
|
| 91 |
+
if tensor.is_contiguous():
|
| 92 |
+
return (
|
| 93 |
+
tensor
|
| 94 |
+
if _has_aligned_pointer(tensor, align_bytes)
|
| 95 |
+
else tensor.clone(memory_format=torch.contiguous_format)
|
| 96 |
)
|
| 97 |
+
if not _has_aligned_pointer(tensor, align_bytes):
|
| 98 |
+
return tensor.clone(memory_format=torch.contiguous_format)
|
| 99 |
+
return tensor if _is_aligned_layout(tensor, align_bytes) else tensor.contiguous()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def validate_output_layout(tensor: torch.Tensor, name: str, align_bytes: int) -> None:
|
| 103 |
+
"""Validate a caller-provided output or SplitKV workspace."""
|
| 104 |
+
assert 0 not in tensor.stride(), f"{name} must not have broadcast dimensions"
|
| 105 |
+
if tensor.is_contiguous():
|
| 106 |
+
assert _has_aligned_pointer(tensor, align_bytes), (
|
| 107 |
+
f"{name} must have aligned strides and a contiguous last dimension"
|
| 108 |
+
)
|
| 109 |
+
return
|
| 110 |
+
assert _is_aligned_layout(tensor, align_bytes), (
|
| 111 |
+
f"{name} must have aligned strides and a contiguous last dimension"
|
| 112 |
+
)
|
| 113 |
|
| 114 |
|
| 115 |
def assume_strides_aligned(t):
|
|
|
|
| 132 |
|
| 133 |
def to_cute_tensor(t, assumed_align=16, leading_dim=-1, fully_dynamic=False, enable_tvm_ffi=True):
|
| 134 |
"""Convert torch tensor to cute tensor for TVM FFI. leading_dim=-1 defaults to t.ndim-1."""
|
| 135 |
+
if t is None:
|
| 136 |
+
return None
|
| 137 |
+
# NOTE: torch 2.9.1 doesn't support fp8 via DLPack but 2.11.0 nightly does
|
| 138 |
+
# currently export raw bytes as uint8 and tell cutlass correct type
|
| 139 |
+
# can directly export as fp8 when torch supports it
|
| 140 |
+
if t.dtype in (torch.float8_e4m3fn, torch.float8_e5m2):
|
| 141 |
+
tensor = from_dlpack(
|
| 142 |
+
t.view(torch.uint8).detach(),
|
| 143 |
+
assumed_align=assumed_align,
|
| 144 |
+
enable_tvm_ffi=enable_tvm_ffi,
|
| 145 |
+
)
|
| 146 |
+
tensor.element_type = (
|
| 147 |
+
cutlass.Float8E4M3FN if t.dtype == torch.float8_e4m3fn else cutlass.Float8E5M2
|
| 148 |
+
)
|
| 149 |
+
else:
|
| 150 |
+
tensor = from_dlpack(t.detach(), assumed_align=assumed_align, enable_tvm_ffi=enable_tvm_ffi)
|
| 151 |
if fully_dynamic:
|
| 152 |
return tensor.mark_layout_dynamic()
|
| 153 |
if leading_dim == -1:
|
|
|
|
| 173 |
)
|
| 174 |
|
| 175 |
|
| 176 |
+
def _resolve_aux_leading_dim(tensor: torch.Tensor) -> int | None:
|
| 177 |
+
"""Pick the mode CuTe keeps as a static stride-1 leading dimension."""
|
| 178 |
+
leading_dim = getattr(tensor, "__leading_dim__", None)
|
| 179 |
+
if leading_dim is not None:
|
| 180 |
+
if tensor.ndim == 0:
|
| 181 |
+
raise ValueError("Scalar aux tensors cannot declare __leading_dim__")
|
| 182 |
+
leading_dim %= tensor.ndim
|
| 183 |
+
if tensor.stride(leading_dim) != 1:
|
| 184 |
+
raise ValueError("Aux tensor __leading_dim__ must identify a stride-1 dimension")
|
| 185 |
+
return leading_dim
|
| 186 |
+
|
| 187 |
+
unit_stride_dims = [dim for dim, stride in enumerate(tensor.stride()) if stride == 1]
|
| 188 |
+
if len(unit_stride_dims) <= 1:
|
| 189 |
+
return unit_stride_dims[0] if unit_stride_dims else None
|
| 190 |
+
nontrivial_dims = [dim for dim in unit_stride_dims if tensor.shape[dim] > 1]
|
| 191 |
+
if len(nontrivial_dims) != 1:
|
| 192 |
+
raise ValueError("Aux tensor layout has no unique stride-1 leading dimension")
|
| 193 |
+
return nontrivial_dims[0]
|
| 194 |
+
|
| 195 |
+
|
| 196 |
def get_aux_tensor_metadata(aux_tensors):
|
| 197 |
+
"""Return the static aux-tensor ABI facts that must key the compile cache."""
|
| 198 |
+
metadata = []
|
| 199 |
+
for tensor in aux_tensors:
|
| 200 |
+
leading_dim = _resolve_aux_leading_dim(tensor)
|
| 201 |
+
static_strides = tuple(
|
| 202 |
+
0 if stride == 0 else 1 if dim == leading_dim else None
|
| 203 |
+
for dim, stride in enumerate(tensor.stride())
|
| 204 |
)
|
| 205 |
+
metadata.append((tensor.dtype, getattr(tensor, "__assumed_align__", None), static_strides))
|
| 206 |
+
return tuple(metadata)
|
| 207 |
|
| 208 |
|
| 209 |
def get_broadcast_dims(tensor: torch.Tensor) -> Tuple[bool, ...]:
|
|
|
|
| 213 |
stride=0 as static, meaning kernels compiled with different broadcast
|
| 214 |
patterns are not interchangeable.
|
| 215 |
"""
|
| 216 |
+
strides = tensor.stride()
|
| 217 |
+
# Written this way for speed.
|
| 218 |
+
if 0 not in strides:
|
| 219 |
+
return (False,) * len(strides)
|
| 220 |
+
return tuple(stride == 0 for stride in strides)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# credit: monellz (https://github.com/NVIDIA/cutlass/issues/2658#issuecomment-3630564264)
|
| 224 |
+
def dump_kernel_attributes(compiled_kernel):
|
| 225 |
+
from cuda.bindings import driver
|
| 226 |
+
from cutlass.utils import HardwareInfo
|
| 227 |
+
import torch
|
| 228 |
+
|
| 229 |
+
device_id = torch.cuda.current_device()
|
| 230 |
+
hardware_info = HardwareInfo(device_id=device_id)
|
| 231 |
+
cubin_data = compiled_kernel.artifacts.CUBIN
|
| 232 |
+
assert cubin_data is not None, "cubin_data is None, need '--keep-cubin' option when compiling"
|
| 233 |
+
cuda_library = hardware_info._checkCudaErrors(
|
| 234 |
+
driver.cuLibraryLoadData(cubin_data, None, None, 0, None, None, 0)
|
| 235 |
+
)
|
| 236 |
+
kernels = hardware_info._checkCudaErrors(driver.cuLibraryEnumerateKernels(1, cuda_library))
|
| 237 |
+
kernel = hardware_info._checkCudaErrors(driver.cuKernelGetFunction(kernels[0]))
|
| 238 |
+
# more metrics: https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__EXEC.html#group__CUDA__EXEC_1g5e92a1b0d8d1b82cb00dcfb2de15961b
|
| 239 |
+
local_size_bytes = hardware_info._checkCudaErrors(
|
| 240 |
+
driver.cuFuncGetAttribute(
|
| 241 |
+
driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES,
|
| 242 |
+
kernel,
|
| 243 |
+
)
|
| 244 |
+
)
|
| 245 |
+
num_regs = hardware_info._checkCudaErrors(
|
| 246 |
+
driver.cuFuncGetAttribute(
|
| 247 |
+
driver.CUfunction_attribute.CU_FUNC_ATTRIBUTE_NUM_REGS,
|
| 248 |
+
kernel,
|
| 249 |
+
)
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
print("--- Kernel Info ---")
|
| 253 |
+
print(f"local_size_bytes: {local_size_bytes}")
|
| 254 |
+
print(f"num_regs: {num_regs}")
|
| 255 |
+
print("--- End Kernel Info ---")
|
build/torch-cuda/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("flash_attn")
|
| 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/flash_attn4/__init__.py
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
import ctypes
|
| 2 |
-
import sys
|
| 3 |
-
|
| 4 |
-
import importlib
|
| 5 |
-
from pathlib import Path
|
| 6 |
-
from types import ModuleType
|
| 7 |
-
|
| 8 |
-
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
-
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
-
# it would also be used for other imports. So, we make a module name that
|
| 11 |
-
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
-
# the path.
|
| 13 |
-
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
-
module_name = path_hash
|
| 15 |
-
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
-
if spec is None:
|
| 17 |
-
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
-
module = importlib.util.module_from_spec(spec)
|
| 19 |
-
if module is None:
|
| 20 |
-
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
-
sys.modules[module_name] = module
|
| 22 |
-
spec.loader.exec_module(module) # type: ignore
|
| 23 |
-
return module
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-cuda/flash_bwd.py
CHANGED
|
@@ -11,7 +11,7 @@ import cuda.bindings.driver as cuda
|
|
| 11 |
import cutlass
|
| 12 |
import cutlass.cute as cute
|
| 13 |
from cutlass.cute.nvgpu import cpasync, warp
|
| 14 |
-
from cutlass import
|
| 15 |
import cutlass.utils as utils_basic
|
| 16 |
|
| 17 |
from .quack import layout_utils
|
|
@@ -19,9 +19,13 @@ from . import ampere_helpers as sm80_utils
|
|
| 19 |
from .cute_dsl_utils import assume_tensor_aligned
|
| 20 |
from . import utils
|
| 21 |
from .mask import AttentionMask
|
|
|
|
| 22 |
from .seqlen_info import SeqlenInfoQK
|
|
|
|
| 23 |
from .quack.cute_dsl_utils import ParamsBase
|
| 24 |
from .tile_scheduler import SingleTileScheduler, SingleTileVarlenScheduler, TileSchedulerArguments
|
|
|
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
class FlashAttentionBackwardSm80:
|
|
@@ -38,6 +42,7 @@ class FlashAttentionBackwardSm80:
|
|
| 38 |
num_threads: int = 256,
|
| 39 |
pack_gqa: bool = False,
|
| 40 |
is_causal: bool = False,
|
|
|
|
| 41 |
SdP_swapAB: bool = False,
|
| 42 |
dKV_swapAB: bool = False,
|
| 43 |
dQ_swapAB: bool = False,
|
|
@@ -45,6 +50,8 @@ class FlashAttentionBackwardSm80:
|
|
| 45 |
AtomLayoutNdKV: int = 8,
|
| 46 |
AtomLayoutMdQ: int = 1,
|
| 47 |
V_in_regs: bool = False,
|
|
|
|
|
|
|
| 48 |
):
|
| 49 |
"""Initializes the configuration for a flash attention v2 kernel.
|
| 50 |
|
|
@@ -62,7 +69,8 @@ class FlashAttentionBackwardSm80:
|
|
| 62 |
:param is_causal: is causal
|
| 63 |
"""
|
| 64 |
self.dtype = dtype
|
| 65 |
-
# padding head_dim to a multiple of 16
|
|
|
|
| 66 |
hdim_multiple_of = 32
|
| 67 |
self.head_dim_padded = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 68 |
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
|
@@ -77,6 +85,7 @@ class FlashAttentionBackwardSm80:
|
|
| 77 |
self.num_threads = num_threads
|
| 78 |
self.pack_gqa = pack_gqa
|
| 79 |
self.is_causal = is_causal
|
|
|
|
| 80 |
self.num_stages_Q = num_stages_Q
|
| 81 |
self.num_stages_dO = num_stages_dO
|
| 82 |
self.SdP_swapAB = SdP_swapAB
|
|
@@ -89,6 +98,8 @@ class FlashAttentionBackwardSm80:
|
|
| 89 |
self.Mma_dKV_is_RS = AtomLayoutMSdP == 1 and AtomLayoutNdKV == num_mma_warps and SdP_swapAB and not dKV_swapAB
|
| 90 |
self.V_in_regs = V_in_regs
|
| 91 |
self.share_QV_smem = V_in_regs
|
|
|
|
|
|
|
| 92 |
|
| 93 |
@staticmethod
|
| 94 |
def can_implement(
|
|
@@ -161,13 +172,13 @@ class FlashAttentionBackwardSm80:
|
|
| 161 |
else:
|
| 162 |
if cutlass.const_expr(not (mdK_type == mdV_type == cutlass.Float32)):
|
| 163 |
raise TypeError("mdKaccum and mdVaccum tensors must have the data type Float32")
|
| 164 |
-
if cutlass.const_expr(
|
| 165 |
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 166 |
-
if cutlass.const_expr(
|
| 167 |
raise TypeError("LSE tensor must be Float32")
|
| 168 |
-
if cutlass.const_expr(
|
| 169 |
raise TypeError("dPsum tensor must be Float32")
|
| 170 |
-
if cutlass.const_expr(
|
| 171 |
raise TypeError("dQaccum tensor must be Float32")
|
| 172 |
if cutlass.const_expr(mCuSeqlensQ_type not in [None, cutlass.Int32]):
|
| 173 |
raise TypeError("cuSeqlensQ tensor must be Int32")
|
|
@@ -372,17 +383,24 @@ class FlashAttentionBackwardSm80:
|
|
| 372 |
mdK: cute.Tensor,
|
| 373 |
mdV: cute.Tensor,
|
| 374 |
softmax_scale: cutlass.Float32,
|
| 375 |
-
stream: cuda.CUstream,
|
| 376 |
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 377 |
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 378 |
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 379 |
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 380 |
-
softcap: Float32 | float | None = None,
|
| 381 |
window_size_left: Int32 | int | None = None,
|
| 382 |
window_size_right: Int32 | int | None = None,
|
| 383 |
mdQ_semaphore: Optional[cute.Tensor] = None,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
):
|
| 385 |
-
assert mdQ_semaphore is None
|
|
|
|
|
|
|
| 386 |
# Get the data type and check if it is fp16 or bf16
|
| 387 |
self._check_type(*(t.element_type if t is not None else None
|
| 388 |
for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV, mCuSeqlensQ, mCuSeqlensK, mSeqUsedQ, mSeqUsedK)))
|
|
@@ -417,12 +435,15 @@ class FlashAttentionBackwardSm80:
|
|
| 417 |
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if cutlass.const_expr(self.pack_gqa) else 1,
|
| 418 |
mCuSeqlensQ=mCuSeqlensK,
|
| 419 |
mSeqUsedQ=mSeqUsedK,
|
|
|
|
| 420 |
)
|
| 421 |
|
| 422 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 423 |
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 424 |
|
| 425 |
-
softmax_scale_log2 =
|
|
|
|
|
|
|
| 426 |
self.kernel(
|
| 427 |
mQ,
|
| 428 |
mK,
|
|
@@ -439,6 +460,8 @@ class FlashAttentionBackwardSm80:
|
|
| 439 |
mSeqUsedK,
|
| 440 |
softmax_scale,
|
| 441 |
softmax_scale_log2,
|
|
|
|
|
|
|
| 442 |
self.sQ_layout,
|
| 443 |
self.sK_layout,
|
| 444 |
self.sV_layout,
|
|
@@ -458,6 +481,7 @@ class FlashAttentionBackwardSm80:
|
|
| 458 |
SharedStorage,
|
| 459 |
tile_sched_params,
|
| 460 |
TileScheduler,
|
|
|
|
| 461 |
).launch(
|
| 462 |
grid=grid_dim,
|
| 463 |
block=[self.num_threads, 1, 1],
|
|
@@ -483,6 +507,8 @@ class FlashAttentionBackwardSm80:
|
|
| 483 |
mSeqUsedK: Optional[cute.Tensor],
|
| 484 |
softmax_scale: cutlass.Float32,
|
| 485 |
softmax_scale_log2: cutlass.Float32,
|
|
|
|
|
|
|
| 486 |
sQ_layout: cute.ComposedLayout,
|
| 487 |
sK_layout: cute.ComposedLayout,
|
| 488 |
sV_layout: cute.ComposedLayout,
|
|
@@ -502,6 +528,7 @@ class FlashAttentionBackwardSm80:
|
|
| 502 |
SharedStorage: cutlass.Constexpr,
|
| 503 |
tile_sched_params: ParamsBase,
|
| 504 |
TileScheduler: cutlass.Constexpr[Callable],
|
|
|
|
| 505 |
):
|
| 506 |
# Thread index, block index
|
| 507 |
tidx, _, _ = cute.arch.thread_idx()
|
|
@@ -512,15 +539,28 @@ class FlashAttentionBackwardSm80:
|
|
| 512 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 513 |
|
| 514 |
if work_tile.is_valid_tile:
|
| 515 |
-
seqlen = SeqlenInfoQK.create(
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
# TODO: return early if m_block_max == 0
|
| 525 |
|
| 526 |
# ///////////////////////////////////////////////////////////////////////////////
|
|
@@ -538,7 +578,7 @@ class FlashAttentionBackwardSm80:
|
|
| 538 |
mdPsum_cur = mdPsum[batch_idx, head_idx, None]
|
| 539 |
mdQaccum_cur = mdQaccum[batch_idx, head_idx, None]
|
| 540 |
else:
|
| 541 |
-
padded_offset_q = seqlen.
|
| 542 |
mQ_cur = cute.domain_offset((seqlen.offset_q, 0), mQ[None, head_idx, None])
|
| 543 |
mLSE_cur = cute.domain_offset((padded_offset_q,), mLSE[head_idx, None])
|
| 544 |
mdO_cur = cute.domain_offset((seqlen.offset_q, 0), mdO[None, head_idx, None])
|
|
@@ -615,8 +655,8 @@ class FlashAttentionBackwardSm80:
|
|
| 615 |
thr_mma_dq = tiled_mma_dq.get_slice(tidx)
|
| 616 |
acc_shape_dK = thr_mma_dkv.partition_shape_C((self.n_block_size, self.head_dim_padded))
|
| 617 |
acc_shape_dV = thr_mma_dkv.partition_shape_C((self.n_block_size, self.head_dim_v_padded))
|
| 618 |
-
acc_dK = cute.
|
| 619 |
-
acc_dV = cute.
|
| 620 |
acc_dK.fill(0.0)
|
| 621 |
acc_dV.fill(0.0)
|
| 622 |
|
|
@@ -755,63 +795,73 @@ class FlashAttentionBackwardSm80:
|
|
| 755 |
smem_copy_params=smem_copy_params, gmem_copy_params=gmem_copy_params,
|
| 756 |
load_Q_LSE=load_Q_LSE, load_dO_dPsum=load_dO_dPsum,
|
| 757 |
m_block_max=m_block_max,
|
|
|
|
| 758 |
softmax_scale_log2=softmax_scale_log2,
|
|
|
|
| 759 |
)
|
| 760 |
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
self.load_K(gmem_thr_copy_QK, tKgK, tKsK, n_block, seqlen=seqlen.seqlen_k,
|
| 770 |
-
headdim=d_head)
|
| 771 |
-
cute.arch.cp_async_commit_group()
|
| 772 |
-
|
| 773 |
-
if cutlass.const_expr(self.V_in_regs):
|
| 774 |
-
cute.arch.cp_async_wait_group(1)
|
| 775 |
-
cute.arch.barrier()
|
| 776 |
-
tdPrV_copy_view = smem_thr_copy_KV.retile(tdPrV)
|
| 777 |
-
cute.copy(smem_thr_copy_KV, tdPsV, tdPrV_copy_view)
|
| 778 |
-
# Sync to avoid loading Q to smem_q, which overlaps with smem_v
|
| 779 |
-
cute.arch.barrier()
|
| 780 |
-
|
| 781 |
-
m_block = m_block_min
|
| 782 |
-
assert self.num_stages_Q >= self.num_stages_dO
|
| 783 |
-
for stage in cutlass.range_constexpr(self.num_stages_Q):
|
| 784 |
-
if cutlass.const_expr(self.num_stages_Q == 1 or stage < self.num_stages_Q - 1):
|
| 785 |
-
if stage == 0 or m_block + stage < m_block_max:
|
| 786 |
-
load_Q_LSE(m_block + stage, smem_pipe_write_q=stage)
|
| 787 |
-
cute.arch.cp_async_commit_group()
|
| 788 |
-
if cutlass.const_expr(stage < self.num_stages_dO):
|
| 789 |
-
if stage == 0 or m_block + stage < m_block_max:
|
| 790 |
-
load_dO_dPsum(m_block + stage, smem_pipe_write_q=stage)
|
| 791 |
cute.arch.cp_async_commit_group()
|
|
|
|
|
|
|
|
|
|
| 792 |
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 810 |
)
|
| 811 |
-
smem_pipe_read_q =
|
| 812 |
-
smem_pipe_read_do =
|
| 813 |
-
smem_pipe_write_q =
|
| 814 |
-
smem_pipe_write_do =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
|
| 816 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 817 |
# Epilogue
|
|
@@ -842,7 +892,9 @@ class FlashAttentionBackwardSm80:
|
|
| 842 |
load_Q_LSE: Callable,
|
| 843 |
load_dO_dPsum: Callable,
|
| 844 |
m_block_max: cutlass.Int32,
|
|
|
|
| 845 |
softmax_scale_log2: cutlass.Float32,
|
|
|
|
| 846 |
mask_fn: Optional[Callable] = None,
|
| 847 |
):
|
| 848 |
def load_Q_next():
|
|
@@ -860,7 +912,7 @@ class FlashAttentionBackwardSm80:
|
|
| 860 |
acc_shape_SdP = mma_params.thr_mma_sdp.partition_shape_C(
|
| 861 |
(self.m_block_size, self.n_block_size) if cutlass.const_expr(not self.SdP_swapAB) else (self.n_block_size, self.m_block_size)
|
| 862 |
)
|
| 863 |
-
acc_S = cute.
|
| 864 |
acc_S.fill(0.0)
|
| 865 |
cute.arch.cp_async_wait_group(1 if cutlass.const_expr(self.num_stages_Q > 1) else 0)
|
| 866 |
cute.arch.barrier()
|
|
@@ -871,13 +923,30 @@ class FlashAttentionBackwardSm80:
|
|
| 871 |
smem_copy_params.smem_thr_copy_QdO, smem_copy_params.smem_thr_copy_KV,
|
| 872 |
swap_AB=self.SdP_swapAB,
|
| 873 |
)
|
|
|
|
|
|
|
| 874 |
tLSErLSE = cute.make_fragment_like(smem_copy_params.tSsLSEMma[None, 0])
|
| 875 |
cute.autovec_copy(
|
| 876 |
smem_copy_params.tSsLSEMma[None, smem_pipe_read_q if cutlass.const_expr(self.num_stages_Q > 1) else 0], tLSErLSE
|
| 877 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
if cutlass.const_expr(mask_fn is not None):
|
| 879 |
mask_fn(acc_S, m_block=m_block)
|
| 880 |
-
acc_S_mn = layout_utils.reshape_acc_to_mn(acc_S)
|
| 881 |
bidx = 0
|
| 882 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_S_mn)
|
| 883 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == 1: cute.print_tensor(tLSErLSE)
|
|
@@ -887,7 +956,7 @@ class FlashAttentionBackwardSm80:
|
|
| 887 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_S_mn)
|
| 888 |
|
| 889 |
# MMA dP
|
| 890 |
-
acc_dP = cute.
|
| 891 |
acc_dP.fill(0.0)
|
| 892 |
cute.arch.cp_async_wait_group(1 if cutlass.const_expr(self.num_stages_dO > 1) else 0)
|
| 893 |
cute.arch.barrier()
|
|
@@ -907,7 +976,20 @@ class FlashAttentionBackwardSm80:
|
|
| 907 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_dP_mn)
|
| 908 |
assert cute.size(acc_dP_mn, mode=[0]) == cute.size(tLSErdPsum)
|
| 909 |
for r in cutlass.range(cute.size(acc_dP_mn, mode=[0]), unroll_full=True):
|
| 910 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 911 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_dP_mn)
|
| 912 |
rP = cute.make_fragment_like(acc_S, self.dtype)
|
| 913 |
rP.store(acc_S.load().to(self.dtype))
|
|
@@ -944,7 +1026,7 @@ class FlashAttentionBackwardSm80:
|
|
| 944 |
acc_shape_dQ = mma_params.thr_mma_dq.partition_shape_C(
|
| 945 |
(self.m_block_size, self.head_dim_padded) if cutlass.const_expr(not self.dQ_swapAB) else (self.head_dim_padded, self.m_block_size)
|
| 946 |
)
|
| 947 |
-
acc_dQ = cute.
|
| 948 |
acc_dQ.fill(0.0)
|
| 949 |
sm80_utils.gemm(
|
| 950 |
mma_params.thr_mma_dq, acc_dQ, mma_params.tdQrdS, mma_params.tdQrK,
|
|
@@ -968,7 +1050,7 @@ class FlashAttentionBackwardSm80:
|
|
| 968 |
|
| 969 |
# MMA dK
|
| 970 |
if cutlass.const_expr(self.Mma_dKV_is_RS):
|
| 971 |
-
|
| 972 |
else:
|
| 973 |
tdKrdS = mma_params.tdKrdS
|
| 974 |
sm80_utils.gemm(
|
|
|
|
| 11 |
import cutlass
|
| 12 |
import cutlass.cute as cute
|
| 13 |
from cutlass.cute.nvgpu import cpasync, warp
|
| 14 |
+
from cutlass import Int32
|
| 15 |
import cutlass.utils as utils_basic
|
| 16 |
|
| 17 |
from .quack import layout_utils
|
|
|
|
| 19 |
from .cute_dsl_utils import assume_tensor_aligned
|
| 20 |
from . import utils
|
| 21 |
from .mask import AttentionMask
|
| 22 |
+
from .softmax import call_score_mod, call_score_mod_bwd
|
| 23 |
from .seqlen_info import SeqlenInfoQK
|
| 24 |
+
from .block_info import BlockInfo
|
| 25 |
from .quack.cute_dsl_utils import ParamsBase
|
| 26 |
from .tile_scheduler import SingleTileScheduler, SingleTileVarlenScheduler, TileSchedulerArguments
|
| 27 |
+
from .block_sparsity import BlockSparseTensors
|
| 28 |
+
from .utils import AuxData
|
| 29 |
|
| 30 |
|
| 31 |
class FlashAttentionBackwardSm80:
|
|
|
|
| 42 |
num_threads: int = 256,
|
| 43 |
pack_gqa: bool = False,
|
| 44 |
is_causal: bool = False,
|
| 45 |
+
is_local: bool = False,
|
| 46 |
SdP_swapAB: bool = False,
|
| 47 |
dKV_swapAB: bool = False,
|
| 48 |
dQ_swapAB: bool = False,
|
|
|
|
| 50 |
AtomLayoutNdKV: int = 8,
|
| 51 |
AtomLayoutMdQ: int = 1,
|
| 52 |
V_in_regs: bool = False,
|
| 53 |
+
score_mod: cutlass.Constexpr | None = None,
|
| 54 |
+
score_mod_bwd: cutlass.Constexpr | None = None,
|
| 55 |
):
|
| 56 |
"""Initializes the configuration for a flash attention v2 kernel.
|
| 57 |
|
|
|
|
| 69 |
:param is_causal: is causal
|
| 70 |
"""
|
| 71 |
self.dtype = dtype
|
| 72 |
+
# padding head_dim to a multiple of 32 (stricter than fwd's 16) due to
|
| 73 |
+
# backward kernel register layout requirements for dQ/dK/dV accumulation
|
| 74 |
hdim_multiple_of = 32
|
| 75 |
self.head_dim_padded = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 76 |
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
|
|
|
| 85 |
self.num_threads = num_threads
|
| 86 |
self.pack_gqa = pack_gqa
|
| 87 |
self.is_causal = is_causal
|
| 88 |
+
self.is_local = is_local
|
| 89 |
self.num_stages_Q = num_stages_Q
|
| 90 |
self.num_stages_dO = num_stages_dO
|
| 91 |
self.SdP_swapAB = SdP_swapAB
|
|
|
|
| 98 |
self.Mma_dKV_is_RS = AtomLayoutMSdP == 1 and AtomLayoutNdKV == num_mma_warps and SdP_swapAB and not dKV_swapAB
|
| 99 |
self.V_in_regs = V_in_regs
|
| 100 |
self.share_QV_smem = V_in_regs
|
| 101 |
+
self.score_mod = score_mod
|
| 102 |
+
self.score_mod_bwd = score_mod_bwd
|
| 103 |
|
| 104 |
@staticmethod
|
| 105 |
def can_implement(
|
|
|
|
| 172 |
else:
|
| 173 |
if cutlass.const_expr(not (mdK_type == mdV_type == cutlass.Float32)):
|
| 174 |
raise TypeError("mdKaccum and mdVaccum tensors must have the data type Float32")
|
| 175 |
+
if cutlass.const_expr(mQ_type not in [cutlass.Float16, cutlass.BFloat16]):
|
| 176 |
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 177 |
+
if cutlass.const_expr(mLSE_type not in [cutlass.Float32]):
|
| 178 |
raise TypeError("LSE tensor must be Float32")
|
| 179 |
+
if cutlass.const_expr(mdPsum_type not in [cutlass.Float32]):
|
| 180 |
raise TypeError("dPsum tensor must be Float32")
|
| 181 |
+
if cutlass.const_expr(mdQaccum_type not in [cutlass.Float32]):
|
| 182 |
raise TypeError("dQaccum tensor must be Float32")
|
| 183 |
if cutlass.const_expr(mCuSeqlensQ_type not in [None, cutlass.Int32]):
|
| 184 |
raise TypeError("cuSeqlensQ tensor must be Int32")
|
|
|
|
| 383 |
mdK: cute.Tensor,
|
| 384 |
mdV: cute.Tensor,
|
| 385 |
softmax_scale: cutlass.Float32,
|
|
|
|
| 386 |
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 387 |
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 388 |
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 389 |
mSeqUsedK: Optional[cute.Tensor] = None,
|
|
|
|
| 390 |
window_size_left: Int32 | int | None = None,
|
| 391 |
window_size_right: Int32 | int | None = None,
|
| 392 |
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 393 |
+
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 394 |
+
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 395 |
+
aux_data: AuxData = AuxData(),
|
| 396 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 397 |
+
mCuTotalMBlocks: Optional[cute.Tensor] = None,
|
| 398 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 399 |
+
stream: cuda.CUstream = None,
|
| 400 |
):
|
| 401 |
+
assert mdQ_semaphore is None and mdK_semaphore is None and mdV_semaphore is None, (
|
| 402 |
+
"determinism not supported yet for Sm80"
|
| 403 |
+
)
|
| 404 |
# Get the data type and check if it is fp16 or bf16
|
| 405 |
self._check_type(*(t.element_type if t is not None else None
|
| 406 |
for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV, mCuSeqlensQ, mCuSeqlensK, mSeqUsedQ, mSeqUsedK)))
|
|
|
|
| 435 |
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if cutlass.const_expr(self.pack_gqa) else 1,
|
| 436 |
mCuSeqlensQ=mCuSeqlensK,
|
| 437 |
mSeqUsedQ=mSeqUsedK,
|
| 438 |
+
cu_total_m_blocks_ptr=mCuTotalMBlocks,
|
| 439 |
)
|
| 440 |
|
| 441 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 442 |
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 443 |
|
| 444 |
+
softmax_scale_log2, _ = utils.compute_softmax_scale_log2(
|
| 445 |
+
softmax_scale, self.score_mod
|
| 446 |
+
)
|
| 447 |
self.kernel(
|
| 448 |
mQ,
|
| 449 |
mK,
|
|
|
|
| 460 |
mSeqUsedK,
|
| 461 |
softmax_scale,
|
| 462 |
softmax_scale_log2,
|
| 463 |
+
window_size_left,
|
| 464 |
+
window_size_right,
|
| 465 |
self.sQ_layout,
|
| 466 |
self.sK_layout,
|
| 467 |
self.sV_layout,
|
|
|
|
| 481 |
SharedStorage,
|
| 482 |
tile_sched_params,
|
| 483 |
TileScheduler,
|
| 484 |
+
aux_data,
|
| 485 |
).launch(
|
| 486 |
grid=grid_dim,
|
| 487 |
block=[self.num_threads, 1, 1],
|
|
|
|
| 507 |
mSeqUsedK: Optional[cute.Tensor],
|
| 508 |
softmax_scale: cutlass.Float32,
|
| 509 |
softmax_scale_log2: cutlass.Float32,
|
| 510 |
+
window_size_left: Optional[Int32],
|
| 511 |
+
window_size_right: Optional[Int32],
|
| 512 |
sQ_layout: cute.ComposedLayout,
|
| 513 |
sK_layout: cute.ComposedLayout,
|
| 514 |
sV_layout: cute.ComposedLayout,
|
|
|
|
| 528 |
SharedStorage: cutlass.Constexpr,
|
| 529 |
tile_sched_params: ParamsBase,
|
| 530 |
TileScheduler: cutlass.Constexpr[Callable],
|
| 531 |
+
aux_data: AuxData = AuxData(),
|
| 532 |
):
|
| 533 |
# Thread index, block index
|
| 534 |
tidx, _, _ = cute.arch.thread_idx()
|
|
|
|
| 539 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 540 |
|
| 541 |
if work_tile.is_valid_tile:
|
| 542 |
+
seqlen = SeqlenInfoQK.create(
|
| 543 |
+
batch_idx,
|
| 544 |
+
mQ.shape[1],
|
| 545 |
+
mK.shape[1],
|
| 546 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 547 |
+
mCuSeqlensK=mCuSeqlensK,
|
| 548 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 549 |
+
mSeqUsedK=mSeqUsedK,
|
| 550 |
+
tile_m=self.m_block_size,
|
| 551 |
+
tile_n=self.n_block_size,
|
| 552 |
+
)
|
| 553 |
+
|
| 554 |
+
block_info = BlockInfo(
|
| 555 |
+
self.m_block_size,
|
| 556 |
+
self.n_block_size,
|
| 557 |
+
self.is_causal,
|
| 558 |
+
self.is_local,
|
| 559 |
+
False,
|
| 560 |
+
window_size_left,
|
| 561 |
+
window_size_right,
|
| 562 |
+
)
|
| 563 |
+
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 564 |
# TODO: return early if m_block_max == 0
|
| 565 |
|
| 566 |
# ///////////////////////////////////////////////////////////////////////////////
|
|
|
|
| 578 |
mdPsum_cur = mdPsum[batch_idx, head_idx, None]
|
| 579 |
mdQaccum_cur = mdQaccum[batch_idx, head_idx, None]
|
| 580 |
else:
|
| 581 |
+
padded_offset_q = seqlen.padded_offset_q
|
| 582 |
mQ_cur = cute.domain_offset((seqlen.offset_q, 0), mQ[None, head_idx, None])
|
| 583 |
mLSE_cur = cute.domain_offset((padded_offset_q,), mLSE[head_idx, None])
|
| 584 |
mdO_cur = cute.domain_offset((seqlen.offset_q, 0), mdO[None, head_idx, None])
|
|
|
|
| 655 |
thr_mma_dq = tiled_mma_dq.get_slice(tidx)
|
| 656 |
acc_shape_dK = thr_mma_dkv.partition_shape_C((self.n_block_size, self.head_dim_padded))
|
| 657 |
acc_shape_dV = thr_mma_dkv.partition_shape_C((self.n_block_size, self.head_dim_v_padded))
|
| 658 |
+
acc_dK = cute.make_rmem_tensor(acc_shape_dK, cutlass.Float32)
|
| 659 |
+
acc_dV = cute.make_rmem_tensor(acc_shape_dV, cutlass.Float32)
|
| 660 |
acc_dK.fill(0.0)
|
| 661 |
acc_dV.fill(0.0)
|
| 662 |
|
|
|
|
| 795 |
smem_copy_params=smem_copy_params, gmem_copy_params=gmem_copy_params,
|
| 796 |
load_Q_LSE=load_Q_LSE, load_dO_dPsum=load_dO_dPsum,
|
| 797 |
m_block_max=m_block_max,
|
| 798 |
+
softmax_scale=softmax_scale,
|
| 799 |
softmax_scale_log2=softmax_scale_log2,
|
| 800 |
+
aux_data=aux_data,
|
| 801 |
)
|
| 802 |
|
| 803 |
+
if m_block_min < m_block_max:
|
| 804 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 805 |
+
# Prologue
|
| 806 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 807 |
+
# Start async loads of the last mn-tile, where we take care of the mn residue
|
| 808 |
+
self.load_V(gmem_thr_copy_VdO, tVgV, tVsV, n_block, seqlen=seqlen.seqlen_k,
|
| 809 |
+
headdim=d_head_v)
|
| 810 |
+
if cutlass.const_expr(self.V_in_regs):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 811 |
cute.arch.cp_async_commit_group()
|
| 812 |
+
self.load_K(gmem_thr_copy_QK, tKgK, tKsK, n_block, seqlen=seqlen.seqlen_k,
|
| 813 |
+
headdim=d_head)
|
| 814 |
+
cute.arch.cp_async_commit_group()
|
| 815 |
|
| 816 |
+
if cutlass.const_expr(self.V_in_regs):
|
| 817 |
+
cute.arch.cp_async_wait_group(1)
|
| 818 |
+
cute.arch.barrier()
|
| 819 |
+
tdPrV_copy_view = smem_thr_copy_KV.retile(tdPrV)
|
| 820 |
+
cute.copy(smem_thr_copy_KV, tdPsV, tdPrV_copy_view)
|
| 821 |
+
# Sync to avoid loading Q to smem_q, which overlaps with smem_v
|
| 822 |
+
cute.arch.barrier()
|
| 823 |
+
|
| 824 |
+
m_block = m_block_min
|
| 825 |
+
assert self.num_stages_Q >= self.num_stages_dO
|
| 826 |
+
for stage in cutlass.range_constexpr(self.num_stages_Q):
|
| 827 |
+
if cutlass.const_expr(self.num_stages_Q == 1 or stage < self.num_stages_Q - 1):
|
| 828 |
+
if stage == 0 or m_block + stage < m_block_max:
|
| 829 |
+
load_Q_LSE(m_block + stage, smem_pipe_write_q=stage)
|
| 830 |
+
cute.arch.cp_async_commit_group()
|
| 831 |
+
if cutlass.const_expr(stage < self.num_stages_dO):
|
| 832 |
+
if stage == 0 or m_block + stage < m_block_max:
|
| 833 |
+
load_dO_dPsum(m_block + stage, smem_pipe_write_q=stage)
|
| 834 |
+
cute.arch.cp_async_commit_group()
|
| 835 |
+
|
| 836 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 837 |
+
# Mainloop
|
| 838 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 839 |
+
# Start processing of the first n-block.
|
| 840 |
+
mask = AttentionMask(
|
| 841 |
+
self.m_block_size,
|
| 842 |
+
self.n_block_size,
|
| 843 |
+
seqlen,
|
| 844 |
+
window_size_left,
|
| 845 |
+
window_size_right,
|
| 846 |
+
)
|
| 847 |
+
mask_fn = partial(
|
| 848 |
+
mask.apply_mask, n_block=n_block, thr_mma=thr_mma_sdp,
|
| 849 |
+
batch_idx=batch_idx, head_idx=head_idx,
|
| 850 |
+
mask_seqlen=True, mask_causal=self.is_causal, mask_local=self.is_local
|
| 851 |
)
|
| 852 |
+
smem_pipe_read_q = cutlass.Int32(0)
|
| 853 |
+
smem_pipe_read_do = cutlass.Int32(0)
|
| 854 |
+
smem_pipe_write_q = cutlass.Int32(self.num_stages_Q - 1)
|
| 855 |
+
smem_pipe_write_do = cutlass.Int32(0)
|
| 856 |
+
for m_tile in cutlass.range(m_block_min, m_block_max, unroll=1):
|
| 857 |
+
compute_one_m_block(
|
| 858 |
+
m_tile, smem_pipe_read_q, smem_pipe_read_do, smem_pipe_write_q, smem_pipe_write_do,
|
| 859 |
+
mask_fn=mask_fn,
|
| 860 |
+
)
|
| 861 |
+
smem_pipe_read_q = self.advance_pipeline(smem_pipe_read_q, self.num_stages_Q)
|
| 862 |
+
smem_pipe_read_do = self.advance_pipeline(smem_pipe_read_do, self.num_stages_dO)
|
| 863 |
+
smem_pipe_write_q = self.advance_pipeline(smem_pipe_write_q, self.num_stages_Q)
|
| 864 |
+
smem_pipe_write_do = self.advance_pipeline(smem_pipe_write_do, self.num_stages_dO)
|
| 865 |
|
| 866 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 867 |
# Epilogue
|
|
|
|
| 892 |
load_Q_LSE: Callable,
|
| 893 |
load_dO_dPsum: Callable,
|
| 894 |
m_block_max: cutlass.Int32,
|
| 895 |
+
softmax_scale: cutlass.Float32,
|
| 896 |
softmax_scale_log2: cutlass.Float32,
|
| 897 |
+
aux_data: AuxData = AuxData(),
|
| 898 |
mask_fn: Optional[Callable] = None,
|
| 899 |
):
|
| 900 |
def load_Q_next():
|
|
|
|
| 912 |
acc_shape_SdP = mma_params.thr_mma_sdp.partition_shape_C(
|
| 913 |
(self.m_block_size, self.n_block_size) if cutlass.const_expr(not self.SdP_swapAB) else (self.n_block_size, self.m_block_size)
|
| 914 |
)
|
| 915 |
+
acc_S = cute.make_rmem_tensor(acc_shape_SdP, cutlass.Float32)
|
| 916 |
acc_S.fill(0.0)
|
| 917 |
cute.arch.cp_async_wait_group(1 if cutlass.const_expr(self.num_stages_Q > 1) else 0)
|
| 918 |
cute.arch.barrier()
|
|
|
|
| 923 |
smem_copy_params.smem_thr_copy_QdO, smem_copy_params.smem_thr_copy_KV,
|
| 924 |
swap_AB=self.SdP_swapAB,
|
| 925 |
)
|
| 926 |
+
acc_S_pre = cute.make_fragment_like(acc_S)
|
| 927 |
+
acc_S_pre.store(acc_S.load())
|
| 928 |
tLSErLSE = cute.make_fragment_like(smem_copy_params.tSsLSEMma[None, 0])
|
| 929 |
cute.autovec_copy(
|
| 930 |
smem_copy_params.tSsLSEMma[None, smem_pipe_read_q if cutlass.const_expr(self.num_stages_Q > 1) else 0], tLSErLSE
|
| 931 |
)
|
| 932 |
+
acc_S_mn = layout_utils.reshape_acc_to_mn(acc_S)
|
| 933 |
+
acc_S_pre_mn = layout_utils.reshape_acc_to_mn(acc_S_pre)
|
| 934 |
+
if cutlass.const_expr(self.score_mod is not None):
|
| 935 |
+
for r in cutlass.range(cute.size(acc_S_mn, mode=[0]), unroll_full=True):
|
| 936 |
+
acc_S_mn[r, None].store(
|
| 937 |
+
call_score_mod(
|
| 938 |
+
self.score_mod,
|
| 939 |
+
acc_S_mn[r, None].load() * softmax_scale,
|
| 940 |
+
0,
|
| 941 |
+
0,
|
| 942 |
+
0,
|
| 943 |
+
0,
|
| 944 |
+
None,
|
| 945 |
+
aux_data,
|
| 946 |
+
)
|
| 947 |
+
)
|
| 948 |
if cutlass.const_expr(mask_fn is not None):
|
| 949 |
mask_fn(acc_S, m_block=m_block)
|
|
|
|
| 950 |
bidx = 0
|
| 951 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_S_mn)
|
| 952 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == 1: cute.print_tensor(tLSErLSE)
|
|
|
|
| 956 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_S_mn)
|
| 957 |
|
| 958 |
# MMA dP
|
| 959 |
+
acc_dP = cute.make_rmem_tensor(acc_shape_SdP, cutlass.Float32)
|
| 960 |
acc_dP.fill(0.0)
|
| 961 |
cute.arch.cp_async_wait_group(1 if cutlass.const_expr(self.num_stages_dO > 1) else 0)
|
| 962 |
cute.arch.barrier()
|
|
|
|
| 976 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_dP_mn)
|
| 977 |
assert cute.size(acc_dP_mn, mode=[0]) == cute.size(tLSErdPsum)
|
| 978 |
for r in cutlass.range(cute.size(acc_dP_mn, mode=[0]), unroll_full=True):
|
| 979 |
+
grad_val = acc_S_mn[r, None].load() * (acc_dP_mn[r, None].load() - tLSErdPsum[r])
|
| 980 |
+
if cutlass.const_expr(self.score_mod_bwd is not None):
|
| 981 |
+
grad_val = call_score_mod_bwd(
|
| 982 |
+
self.score_mod_bwd,
|
| 983 |
+
grad_val,
|
| 984 |
+
acc_S_pre_mn[r, None].load() * softmax_scale,
|
| 985 |
+
0,
|
| 986 |
+
0,
|
| 987 |
+
0,
|
| 988 |
+
0,
|
| 989 |
+
None,
|
| 990 |
+
aux_data,
|
| 991 |
+
)
|
| 992 |
+
acc_dP_mn[r, None].store(grad_val)
|
| 993 |
# if cute.arch.thread_idx()[0] == 0 and cute.arch.block_idx()[0] == bidx: cute.print_tensor(acc_dP_mn)
|
| 994 |
rP = cute.make_fragment_like(acc_S, self.dtype)
|
| 995 |
rP.store(acc_S.load().to(self.dtype))
|
|
|
|
| 1026 |
acc_shape_dQ = mma_params.thr_mma_dq.partition_shape_C(
|
| 1027 |
(self.m_block_size, self.head_dim_padded) if cutlass.const_expr(not self.dQ_swapAB) else (self.head_dim_padded, self.m_block_size)
|
| 1028 |
)
|
| 1029 |
+
acc_dQ = cute.make_rmem_tensor(acc_shape_dQ, cutlass.Float32)
|
| 1030 |
acc_dQ.fill(0.0)
|
| 1031 |
sm80_utils.gemm(
|
| 1032 |
mma_params.thr_mma_dq, acc_dQ, mma_params.tdQrdS, mma_params.tdQrK,
|
|
|
|
| 1050 |
|
| 1051 |
# MMA dK
|
| 1052 |
if cutlass.const_expr(self.Mma_dKV_is_RS):
|
| 1053 |
+
tdKrdS = layout_utils.reshape_acc_to_frgA(rdS)
|
| 1054 |
else:
|
| 1055 |
tdKrdS = mma_params.tdKrdS
|
| 1056 |
sm80_utils.gemm(
|
build/torch-cuda/flash_bwd_mla_dk_sm100.py
ADDED
|
@@ -0,0 +1,1185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modified from CUTLASS example file, original copyright:
|
| 2 |
+
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
| 3 |
+
# SPDX-License-Identifier: BSD-3-Clause
|
| 4 |
+
|
| 5 |
+
# Redistribution and use in source and binary forms, with or without
|
| 6 |
+
# modification, are permitted provided that the following conditions are met:
|
| 7 |
+
|
| 8 |
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
| 9 |
+
# list of conditions and the following disclaimer.
|
| 10 |
+
|
| 11 |
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
| 12 |
+
# this list of conditions and the following disclaimer in the documentation
|
| 13 |
+
# and/or other materials provided with the distribution.
|
| 14 |
+
|
| 15 |
+
# 3. Neither the name of the copyright holder nor the names of its
|
| 16 |
+
# contributors may be used to endorse or promote products derived from
|
| 17 |
+
# this software without specific prior written permission.
|
| 18 |
+
|
| 19 |
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 20 |
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 21 |
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 22 |
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 23 |
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 24 |
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 25 |
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 26 |
+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 27 |
+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 28 |
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 29 |
+
|
| 30 |
+
from typing import Callable, Optional, Tuple, Union
|
| 31 |
+
import cuda.bindings.driver as cuda
|
| 32 |
+
|
| 33 |
+
import cutlass
|
| 34 |
+
from cutlass import Int32, const_expr
|
| 35 |
+
from cutlass.cutlass_dsl import dsl_user_op
|
| 36 |
+
import cutlass.cute as cute
|
| 37 |
+
import cutlass.cute.testing as testing
|
| 38 |
+
import cutlass.utils as utils
|
| 39 |
+
from cutlass.utils.gemm.sm100 import (
|
| 40 |
+
transform_partitioned_tensor_layout,
|
| 41 |
+
epilogue_smem_copy_and_partition,
|
| 42 |
+
epilogue_tmem_copy_and_partition,
|
| 43 |
+
)
|
| 44 |
+
import cutlass.pipeline as pipeline
|
| 45 |
+
from cutlass.pipeline import pipeline_init_arrive, pipeline_init_wait
|
| 46 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 47 |
+
|
| 48 |
+
from .utils import get_batch_from_cu_tensor
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@dsl_user_op
|
| 52 |
+
def elem_pointer(x: cute.Tensor, coord, *, loc=None, ip=None) -> cute.Pointer:
|
| 53 |
+
"""
|
| 54 |
+
Get a pointer to an element at the specified coordinate in a tensor.
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
x: The tensor (typically a shared memory tensor)
|
| 58 |
+
coord: The coordinate tuple, can be hierarchical like (row, (col, cluster_idx))
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
Pointer to the element at the specified coordinate
|
| 62 |
+
"""
|
| 63 |
+
return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class dKGemmKernel:
|
| 67 |
+
def __init__(
|
| 68 |
+
self,
|
| 69 |
+
topk: int,
|
| 70 |
+
heads: int,
|
| 71 |
+
dim: int,
|
| 72 |
+
varlen: bool,
|
| 73 |
+
):
|
| 74 |
+
self.varlen = varlen
|
| 75 |
+
self.topk = topk
|
| 76 |
+
self.heads = heads
|
| 77 |
+
self.dim = dim
|
| 78 |
+
# A operand dS'^T: (total_q, heads, topk), topk-major
|
| 79 |
+
self.ab_dtype = cutlass.BFloat16
|
| 80 |
+
self.a_major_mode = cute.nvgpu.OperandMajorMode.MN
|
| 81 |
+
# B operand Q: (total_q, heads, dim), dim-major
|
| 82 |
+
self.b_major_mode = cute.nvgpu.OperandMajorMode.MN
|
| 83 |
+
# Index operand I: (total_q, seqlen_k)
|
| 84 |
+
self.idx_dtype = cutlass.Int32
|
| 85 |
+
|
| 86 |
+
# Output dKaccum: (total_q, seqlen_k, dim), dim-major
|
| 87 |
+
self.c_dtype = cutlass.Float32
|
| 88 |
+
self.c_layout = utils.LayoutEnum.ROW_MAJOR
|
| 89 |
+
|
| 90 |
+
self.acc_dtype = cutlass.Float32
|
| 91 |
+
|
| 92 |
+
if self.topk % 256 == 1:
|
| 93 |
+
# The kernel schedule requires 2CTA instructions with this tile shape
|
| 94 |
+
self.cluster_shape_mn = (2, 1)
|
| 95 |
+
self.use_2cta_instrs = True
|
| 96 |
+
else:
|
| 97 |
+
self.cluster_shape_mn = (1, 1)
|
| 98 |
+
self.use_2cta_instrs = False
|
| 99 |
+
self.cta_group = tcgen05.CtaGroup.TWO if self.use_2cta_instrs else tcgen05.CtaGroup.ONE
|
| 100 |
+
self.atom_thr_size = 2 if self.use_2cta_instrs else 1
|
| 101 |
+
self.cta_tile_shape_mnk = (128, 64, 1)
|
| 102 |
+
self.mma_tiler_dK = (
|
| 103 |
+
self.cta_tile_shape_mnk[0] * self.atom_thr_size,
|
| 104 |
+
self.cta_tile_shape_mnk[1],
|
| 105 |
+
1,
|
| 106 |
+
)
|
| 107 |
+
self.arch = "sm_100"
|
| 108 |
+
|
| 109 |
+
self.occupancy = 1
|
| 110 |
+
# Set specialized warp ids
|
| 111 |
+
self.epilogue_warp_id = [0, 1, 2, 3]
|
| 112 |
+
self.mma_warp_id = 4
|
| 113 |
+
self.tma_warp_id = 5
|
| 114 |
+
self.sched_warp_id = 6
|
| 115 |
+
self.threads_per_cta = 32 * len(
|
| 116 |
+
[
|
| 117 |
+
self.mma_warp_id,
|
| 118 |
+
self.tma_warp_id,
|
| 119 |
+
self.sched_warp_id,
|
| 120 |
+
*self.epilogue_warp_id,
|
| 121 |
+
]
|
| 122 |
+
)
|
| 123 |
+
# Set barrier id for cta sync, epilogue sync and tmem ptr sync
|
| 124 |
+
self.epilog_sync_bar_id = 1
|
| 125 |
+
self.tmem_alloc_sync_bar_id = 2
|
| 126 |
+
self.I_load_bar_id = 3
|
| 127 |
+
|
| 128 |
+
@cute.jit
|
| 129 |
+
def __call__(
|
| 130 |
+
self,
|
| 131 |
+
dS: cute.Tensor, # (batch, seqlen_q, heads, topk) or (total_q, heads, topk)
|
| 132 |
+
I: cute.Tensor, # (batch, seqlen_q, heads, topk) or (total_q, heads, topk)
|
| 133 |
+
Q: cute.Tensor, # (batch, seqlen_q, heads, dim) or (total_q, heads, dim)
|
| 134 |
+
dKaccum: cute.Tensor, # (batch, seqlen_k, dim) or (total_k, dim)
|
| 135 |
+
cuSeqlensQ: Optional[cute.Tensor], # (batch + 1,)
|
| 136 |
+
cuSeqlensK: Optional[cute.Tensor], # (batch + 1,)
|
| 137 |
+
stream: cuda.CUstream,
|
| 138 |
+
):
|
| 139 |
+
if const_expr(self.ab_dtype != dS.element_type):
|
| 140 |
+
raise TypeError(f"Type must match: {self.ab_dtype} != {dS.element_type}")
|
| 141 |
+
if const_expr(self.ab_dtype != Q.element_type):
|
| 142 |
+
raise TypeError(f"Type must match: {self.ab_dtype} != {Q.element_type}")
|
| 143 |
+
if const_expr(self.c_dtype != dKaccum.element_type):
|
| 144 |
+
raise TypeError(f"Type must match: {self.c_dtype} != {dKaccum.element_type}")
|
| 145 |
+
|
| 146 |
+
if const_expr(self.varlen):
|
| 147 |
+
assert cuSeqlensQ is not None
|
| 148 |
+
assert cuSeqlensK is not None
|
| 149 |
+
|
| 150 |
+
# For non-varlen, group batch and seqlen modes into token mode
|
| 151 |
+
if const_expr(not self.varlen):
|
| 152 |
+
batch_divmod = cute.FastDivmodDivisor(dS.shape[0])
|
| 153 |
+
dS = cute.group_modes(dS, 0, 2)
|
| 154 |
+
I = cute.group_modes(I, 0, 2)
|
| 155 |
+
Q = cute.group_modes(Q, 0, 2)
|
| 156 |
+
dKaccum = cute.group_modes(dKaccum, 0, 2)
|
| 157 |
+
else:
|
| 158 |
+
batch_divmod = cute.FastDivmodDivisor(0)
|
| 159 |
+
|
| 160 |
+
# Permute everything to (_, heads, tokens) for MMA
|
| 161 |
+
dS_mkl = cute.make_tensor(dS.iterator, cute.select(dS.layout, [2, 1, 0]))
|
| 162 |
+
I_ml = cute.make_tensor(I.iterator, cute.select(I.layout, [1, 0]))
|
| 163 |
+
Q_nkl = cute.make_tensor(Q.iterator, cute.select(Q.layout, [2, 1, 0]))
|
| 164 |
+
dKaccum_nl = cute.make_tensor(dKaccum.iterator, cute.select(dKaccum.layout, [1, 0]))
|
| 165 |
+
|
| 166 |
+
# Configure tiled mma
|
| 167 |
+
self.tiled_mma_dK = utils.sm100.make_trivial_tiled_mma(
|
| 168 |
+
self.ab_dtype,
|
| 169 |
+
self.ab_dtype,
|
| 170 |
+
self.a_major_mode,
|
| 171 |
+
self.b_major_mode,
|
| 172 |
+
self.acc_dtype,
|
| 173 |
+
self.cta_group,
|
| 174 |
+
self.mma_tiler_dK[:2],
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# Compute mma/cluster/tile shapes
|
| 178 |
+
mma_inst_shape_k = cute.size(self.tiled_mma_dK.shape_mnk, mode=[2])
|
| 179 |
+
mma_inst_tile_k = 4 if self.heads == 64 else 8
|
| 180 |
+
self.mma_tiler_dK = (
|
| 181 |
+
self.mma_tiler_dK[0],
|
| 182 |
+
self.mma_tiler_dK[1],
|
| 183 |
+
mma_inst_shape_k * mma_inst_tile_k,
|
| 184 |
+
)
|
| 185 |
+
self.cta_tile_shape_mnk_dK = (
|
| 186 |
+
self.mma_tiler_dK[0] // self.atom_thr_size,
|
| 187 |
+
self.mma_tiler_dK[1],
|
| 188 |
+
self.mma_tiler_dK[2],
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
# Compute cluster layout
|
| 192 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 193 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 194 |
+
(self.atom_thr_size,),
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
# Compute number of multicast CTAs for A/B
|
| 198 |
+
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
| 199 |
+
self.num_mcast_ctas_b = cute.size(self.cluster_layout_vmnk.shape[1])
|
| 200 |
+
self.is_a_mcast = self.num_mcast_ctas_a > 1
|
| 201 |
+
self.is_b_mcast = self.num_mcast_ctas_b > 1
|
| 202 |
+
|
| 203 |
+
# Compute epilogue subtile shape for TMA store
|
| 204 |
+
# self.epi_tile_dK = utils.sm100.compute_epilogue_tile_shape(
|
| 205 |
+
# self.cta_tile_shape_mnk_dK,
|
| 206 |
+
# self.use_2cta_instrs,
|
| 207 |
+
# self.c_layout,
|
| 208 |
+
# self.c_dtype,
|
| 209 |
+
# )
|
| 210 |
+
self.epi_tile_dK = (cute.make_layout(128), cute.make_layout(32))
|
| 211 |
+
self.epi_tile_dK_width = cute.size(self.epi_tile_dK[1].shape)
|
| 212 |
+
|
| 213 |
+
self.dK_smem_layout = utils.sm100.make_smem_layout_epi(
|
| 214 |
+
self.c_dtype, self.c_layout, self.epi_tile_dK, 1
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
smem_capacity = utils.get_smem_capacity_in_bytes()
|
| 218 |
+
|
| 219 |
+
# TMEM pipeline stages
|
| 220 |
+
# 64 TMEM columns per stage, 8 stages can fit in TMEM
|
| 221 |
+
self.num_acc_stage = 8
|
| 222 |
+
self.num_tmem_alloc_cols = 512
|
| 223 |
+
|
| 224 |
+
# SMEM pipeline stages
|
| 225 |
+
# TMA load Q (persists for the whole token)
|
| 226 |
+
self.num_Q_stage = 1
|
| 227 |
+
assert const_expr(self.mma_tiler_dK[1] * self.num_Q_stage == self.dim)
|
| 228 |
+
Q_smem_layout_stage_one = utils.sm100.make_smem_layout_b(
|
| 229 |
+
self.tiled_mma_dK, self.mma_tiler_dK, self.ab_dtype, 1
|
| 230 |
+
)
|
| 231 |
+
Q_bytes = cute.size_in_bytes(self.ab_dtype, Q_smem_layout_stage_one) * self.num_Q_stage
|
| 232 |
+
|
| 233 |
+
# TMA store-reduce dK
|
| 234 |
+
self.num_c_stage = 2
|
| 235 |
+
dK_bytes_per_stage = cute.size_in_bytes(self.c_dtype, self.dK_smem_layout)
|
| 236 |
+
dK_bytes = dK_bytes_per_stage * self.num_c_stage
|
| 237 |
+
|
| 238 |
+
# cp.async load I
|
| 239 |
+
self.num_I_stage = 2
|
| 240 |
+
self.I_smem_layout_staged = cute.make_layout(
|
| 241 |
+
(self.mma_tiler_dK[0] // self.atom_thr_size, self.num_I_stage)
|
| 242 |
+
)
|
| 243 |
+
I_bytes = cute.size_in_bytes(self.idx_dtype, self.I_smem_layout_staged)
|
| 244 |
+
|
| 245 |
+
# TMA load dS
|
| 246 |
+
dS_smem_layout_stage_one = utils.sm100.make_smem_layout_a(
|
| 247 |
+
self.tiled_mma_dK, self.mma_tiler_dK, self.ab_dtype, 1
|
| 248 |
+
)
|
| 249 |
+
dS_bytes_per_stage = cute.size_in_bytes(self.ab_dtype, dS_smem_layout_stage_one)
|
| 250 |
+
|
| 251 |
+
mbar_helpers_bytes = 1024
|
| 252 |
+
|
| 253 |
+
# Increase dS stages to fill SMEM
|
| 254 |
+
self.num_dS_stage = (
|
| 255 |
+
smem_capacity // self.occupancy - (mbar_helpers_bytes + dK_bytes + Q_bytes + I_bytes)
|
| 256 |
+
) // dS_bytes_per_stage
|
| 257 |
+
dS_bytes = self.num_dS_stage * dS_bytes_per_stage
|
| 258 |
+
|
| 259 |
+
# Increase dK stages to fill remainder
|
| 260 |
+
self.num_c_stage += (
|
| 261 |
+
smem_capacity // self.occupancy
|
| 262 |
+
- (mbar_helpers_bytes + dK_bytes + dS_bytes + Q_bytes + I_bytes)
|
| 263 |
+
) // dK_bytes_per_stage
|
| 264 |
+
|
| 265 |
+
# Increase I stages to fill remainder
|
| 266 |
+
|
| 267 |
+
# Compute shared memory layout
|
| 268 |
+
self.dS_smem_layout_staged = utils.sm100.make_smem_layout_a(
|
| 269 |
+
self.tiled_mma_dK, self.mma_tiler_dK, self.ab_dtype, self.num_dS_stage
|
| 270 |
+
)
|
| 271 |
+
self.Q_smem_layout_staged = utils.sm100.make_smem_layout_b(
|
| 272 |
+
self.tiled_mma_dK, self.mma_tiler_dK, self.ab_dtype, self.num_Q_stage
|
| 273 |
+
)
|
| 274 |
+
self.dK_smem_layout_staged = utils.sm100.make_smem_layout_epi(
|
| 275 |
+
self.c_dtype, self.c_layout, self.epi_tile_dK, self.num_c_stage
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
# TMA load for dS
|
| 279 |
+
dS_op = utils.sm100.cluster_shape_to_tma_atom_A(
|
| 280 |
+
self.cluster_shape_mn, self.tiled_mma_dK.thr_id
|
| 281 |
+
)
|
| 282 |
+
self.dS_smem_layout = cute.slice_(self.dS_smem_layout_staged, (None, None, None, 0))
|
| 283 |
+
tma_atom_dS, tma_tensor_dS = cute.nvgpu.make_tiled_tma_atom_A(
|
| 284 |
+
dS_op,
|
| 285 |
+
dS_mkl,
|
| 286 |
+
self.dS_smem_layout,
|
| 287 |
+
self.mma_tiler_dK,
|
| 288 |
+
self.tiled_mma_dK,
|
| 289 |
+
self.cluster_layout_vmnk.shape,
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
# TMA load for Q
|
| 293 |
+
Q_op = utils.sm100.cluster_shape_to_tma_atom_B(
|
| 294 |
+
self.cluster_shape_mn, self.tiled_mma_dK.thr_id
|
| 295 |
+
)
|
| 296 |
+
self.Q_smem_layout = cute.slice_(self.Q_smem_layout_staged, (None, None, None, 0))
|
| 297 |
+
tma_atom_Q, tma_tensor_Q = cute.nvgpu.make_tiled_tma_atom_B(
|
| 298 |
+
Q_op,
|
| 299 |
+
Q_nkl,
|
| 300 |
+
self.Q_smem_layout,
|
| 301 |
+
self.mma_tiler_dK,
|
| 302 |
+
self.tiled_mma_dK,
|
| 303 |
+
self.cluster_layout_vmnk.shape,
|
| 304 |
+
)
|
| 305 |
+
self.Q_load_bytes = (
|
| 306 |
+
cute.size_in_bytes(self.ab_dtype, self.Q_smem_layout) * self.atom_thr_size
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
self.dS_load_bytes = (
|
| 310 |
+
cute.size_in_bytes(self.ab_dtype, self.dS_smem_layout) * self.atom_thr_size
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
# coalesced store for dKaccum: 1 warp copies 1 epi tile row
|
| 314 |
+
vector_width_dK = self.epi_tile_dK_width * self.c_dtype.width // cute.arch.WARP_SIZE
|
| 315 |
+
copy_atom_dK = cute.make_copy_atom(
|
| 316 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 317 |
+
self.c_dtype,
|
| 318 |
+
num_bits_per_copy=vector_width_dK,
|
| 319 |
+
)
|
| 320 |
+
vector_elts_dK = vector_width_dK // self.c_dtype.width
|
| 321 |
+
thr_layout_dK = cute.make_layout((cute.arch.WARP_SIZE,))
|
| 322 |
+
val_layout_dK = cute.make_layout((vector_elts_dK,))
|
| 323 |
+
tiled_copy_dK = cute.make_tiled_copy_tv(copy_atom_dK, thr_layout_dK, val_layout_dK)
|
| 324 |
+
|
| 325 |
+
# cp.async load I: 1 warp copies 32 values, 4 warps copy 128 values
|
| 326 |
+
copy_atom_I = cute.make_copy_atom(
|
| 327 |
+
cute.nvgpu.cpasync.CopyG2SOp(),
|
| 328 |
+
self.idx_dtype,
|
| 329 |
+
num_bits_per_copy=32,
|
| 330 |
+
)
|
| 331 |
+
vI = cute.make_layout((1,))
|
| 332 |
+
tI = cute.make_layout((128,))
|
| 333 |
+
tiled_copy_I = cute.make_tiled_copy_tv(copy_atom_I, tI, vI)
|
| 334 |
+
|
| 335 |
+
# Setup clc stage by default
|
| 336 |
+
self.num_clc_stage = 1
|
| 337 |
+
assert self.num_clc_stage == 1, "Only single-stage CLC pipeline is supported"
|
| 338 |
+
|
| 339 |
+
# Response size is 4B * 4 elements
|
| 340 |
+
self.num_clc_response_bytes = 16
|
| 341 |
+
|
| 342 |
+
# Compute grid size and set up tile scheduler
|
| 343 |
+
total_q = cute.size(dS_mkl.shape[2])
|
| 344 |
+
cluster_shape_mnl = (*self.cluster_shape_mn, 1)
|
| 345 |
+
num_ctas_mnl = (self.cluster_shape_mn[0] * total_q, self.cluster_shape_mn[1], 1)
|
| 346 |
+
self.tile_sched_params = utils.ClcDynamicPersistentTileSchedulerParams(
|
| 347 |
+
num_ctas_mnl, cluster_shape_mnl
|
| 348 |
+
)
|
| 349 |
+
grid = utils.ClcDynamicPersistentTileScheduler.get_grid_shape(self.tile_sched_params)
|
| 350 |
+
|
| 351 |
+
# Define shared storage for kernel
|
| 352 |
+
buffer_align_bytes = 1024
|
| 353 |
+
|
| 354 |
+
@cute.struct
|
| 355 |
+
class SharedStorage:
|
| 356 |
+
dS_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_dS_stage * 2]
|
| 357 |
+
Q_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_Q_stage * 2]
|
| 358 |
+
acc_full_mbar_ptr: cute.struct.MemRange[cutlass.Int64, self.num_acc_stage * 2]
|
| 359 |
+
tmem_dealloc_mbar: cutlass.Int64
|
| 360 |
+
tmem_holding_buf: cutlass.Int32
|
| 361 |
+
clc_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2]
|
| 362 |
+
clc_response: cute.struct.MemRange[cutlass.Int32, 4]
|
| 363 |
+
sI: cute.struct.Align[
|
| 364 |
+
cute.struct.MemRange[self.idx_dtype, cute.cosize(self.I_smem_layout_staged)],
|
| 365 |
+
buffer_align_bytes,
|
| 366 |
+
]
|
| 367 |
+
sdS: cute.struct.Align[
|
| 368 |
+
cute.struct.MemRange[self.ab_dtype, cute.cosize(self.dS_smem_layout_staged)],
|
| 369 |
+
buffer_align_bytes,
|
| 370 |
+
]
|
| 371 |
+
sQ: cute.struct.Align[
|
| 372 |
+
cute.struct.MemRange[self.ab_dtype, cute.cosize(self.Q_smem_layout_staged)],
|
| 373 |
+
buffer_align_bytes,
|
| 374 |
+
]
|
| 375 |
+
sdK: cute.struct.Align[
|
| 376 |
+
cute.struct.MemRange[self.c_dtype, cute.cosize(self.dK_smem_layout_staged)],
|
| 377 |
+
buffer_align_bytes,
|
| 378 |
+
]
|
| 379 |
+
|
| 380 |
+
# Launch the kernel synchronously
|
| 381 |
+
self.kernel(
|
| 382 |
+
self.tiled_mma_dK,
|
| 383 |
+
tma_atom_dS,
|
| 384 |
+
tma_tensor_dS,
|
| 385 |
+
tma_atom_Q,
|
| 386 |
+
tma_tensor_Q,
|
| 387 |
+
tiled_copy_dK,
|
| 388 |
+
dKaccum_nl,
|
| 389 |
+
tiled_copy_I,
|
| 390 |
+
I_ml,
|
| 391 |
+
batch_divmod,
|
| 392 |
+
cuSeqlensQ,
|
| 393 |
+
cuSeqlensK,
|
| 394 |
+
self.cluster_layout_vmnk,
|
| 395 |
+
self.dS_smem_layout_staged,
|
| 396 |
+
self.Q_smem_layout_staged,
|
| 397 |
+
self.dK_smem_layout_staged,
|
| 398 |
+
self.I_smem_layout_staged,
|
| 399 |
+
self.epi_tile_dK,
|
| 400 |
+
self.tile_sched_params,
|
| 401 |
+
SharedStorage,
|
| 402 |
+
).launch(
|
| 403 |
+
grid=grid,
|
| 404 |
+
block=[self.threads_per_cta, 1, 1],
|
| 405 |
+
cluster=(*self.cluster_shape_mn, 1),
|
| 406 |
+
stream=stream,
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
# GPU device kernel
|
| 410 |
+
@cute.kernel
|
| 411 |
+
def kernel(
|
| 412 |
+
self,
|
| 413 |
+
tiled_mma_dK: cute.TiledMma,
|
| 414 |
+
tma_atom_dS: cute.CopyAtom,
|
| 415 |
+
mdS_mkl: cute.Tensor,
|
| 416 |
+
tma_atom_Q: Optional[cute.CopyAtom],
|
| 417 |
+
mQ_nkl: Optional[cute.Tensor],
|
| 418 |
+
tiled_copy_dK: cute.TiledCopy,
|
| 419 |
+
mdKaccum_nl: Optional[cute.Tensor],
|
| 420 |
+
tiled_copy_I: cute.TiledCopy,
|
| 421 |
+
mI_ml: cute.Tensor,
|
| 422 |
+
batch_divmod: cute.FastDivmodDivisor,
|
| 423 |
+
cuSeqlensQ: Optional[cute.Tensor],
|
| 424 |
+
cuSeqlensK: Optional[cute.Tensor],
|
| 425 |
+
cluster_layout_vmnk: cute.Layout,
|
| 426 |
+
dS_smem_layout_staged: cute.ComposedLayout,
|
| 427 |
+
Q_smem_layout_staged: Union[cute.Layout, cute.ComposedLayout],
|
| 428 |
+
dK_smem_layout_staged: Union[cute.ComposedLayout, cute.Layout],
|
| 429 |
+
I_smem_layout_staged: Union[cute.Layout, cute.ComposedLayout],
|
| 430 |
+
epi_tile_dK: cute.Tile,
|
| 431 |
+
tile_sched_params: utils.ClcDynamicPersistentTileSchedulerParams,
|
| 432 |
+
SharedStorage: cutlass.Constexpr[Callable],
|
| 433 |
+
):
|
| 434 |
+
warp_idx = cute.arch.warp_idx()
|
| 435 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 436 |
+
|
| 437 |
+
#
|
| 438 |
+
# Prefetch tma desc
|
| 439 |
+
#
|
| 440 |
+
if warp_idx == self.tma_warp_id:
|
| 441 |
+
cpasync.prefetch_descriptor(tma_atom_dS)
|
| 442 |
+
cpasync.prefetch_descriptor(tma_atom_Q)
|
| 443 |
+
|
| 444 |
+
#
|
| 445 |
+
# Setup cta/thread coordinates
|
| 446 |
+
#
|
| 447 |
+
# Coords inside cluster
|
| 448 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 449 |
+
mma_tile_coord_v = bidx % self.atom_thr_size
|
| 450 |
+
is_leader_cta = mma_tile_coord_v == 0
|
| 451 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 452 |
+
is_first_cta_in_cluster = cta_rank_in_cluster == 0
|
| 453 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(cta_rank_in_cluster)
|
| 454 |
+
# Coord inside cta
|
| 455 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 456 |
+
|
| 457 |
+
#
|
| 458 |
+
# Alloc and init: a+b full/empty, accumulator full/empty, tensor memory dealloc barrier
|
| 459 |
+
#
|
| 460 |
+
|
| 461 |
+
smem = utils.SmemAllocator()
|
| 462 |
+
storage = smem.allocate(SharedStorage)
|
| 463 |
+
|
| 464 |
+
# Initialize mainloop ab_pipeline (barrier) and states
|
| 465 |
+
dS_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 466 |
+
num_tma_producer = self.num_mcast_ctas_a + self.num_mcast_ctas_b - 1
|
| 467 |
+
dS_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 468 |
+
pipeline.Agent.Thread, num_tma_producer
|
| 469 |
+
)
|
| 470 |
+
dS_producer, dS_consumer = pipeline.PipelineTmaUmma.create(
|
| 471 |
+
barrier_storage=storage.dS_full_mbar_ptr.data_ptr(),
|
| 472 |
+
num_stages=self.num_dS_stage,
|
| 473 |
+
producer_group=dS_pipeline_producer_group,
|
| 474 |
+
consumer_group=dS_pipeline_consumer_group,
|
| 475 |
+
tx_count=self.dS_load_bytes,
|
| 476 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 477 |
+
defer_sync=True,
|
| 478 |
+
).make_participants()
|
| 479 |
+
|
| 480 |
+
Q_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 481 |
+
Q_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 482 |
+
pipeline.Agent.Thread, num_tma_producer
|
| 483 |
+
)
|
| 484 |
+
Q_pipeline = pipeline.PipelineTmaUmma.create(
|
| 485 |
+
barrier_storage=storage.Q_full_mbar_ptr.data_ptr(),
|
| 486 |
+
num_stages=self.num_Q_stage,
|
| 487 |
+
producer_group=Q_pipeline_producer_group,
|
| 488 |
+
consumer_group=Q_pipeline_consumer_group,
|
| 489 |
+
tx_count=self.Q_load_bytes,
|
| 490 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 491 |
+
defer_sync=True,
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
# Initialize acc_pipeline (barrier) and states
|
| 495 |
+
acc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 496 |
+
num_acc_consumer_threads = len(self.epilogue_warp_id) * (2 if self.use_2cta_instrs else 1)
|
| 497 |
+
acc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 498 |
+
pipeline.Agent.Thread, num_acc_consumer_threads
|
| 499 |
+
)
|
| 500 |
+
acc_pipeline = pipeline.PipelineUmmaAsync.create(
|
| 501 |
+
barrier_storage=storage.acc_full_mbar_ptr.data_ptr(),
|
| 502 |
+
num_stages=self.num_acc_stage,
|
| 503 |
+
producer_group=acc_pipeline_producer_group,
|
| 504 |
+
consumer_group=acc_pipeline_consumer_group,
|
| 505 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 506 |
+
defer_sync=True,
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
# Initialize clc_pipeline (barrier) and states
|
| 510 |
+
clc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 511 |
+
cluster_size = cute.size(self.cluster_shape_mn)
|
| 512 |
+
num_clc_consumer_threads = 32 * (1 + cluster_size * (1 + len(self.epilogue_warp_id) + 1))
|
| 513 |
+
clc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 514 |
+
pipeline.Agent.Thread, num_clc_consumer_threads
|
| 515 |
+
)
|
| 516 |
+
clc_pipeline = pipeline.PipelineClcFetchAsync.create(
|
| 517 |
+
barrier_storage=storage.clc_mbar_ptr.data_ptr(),
|
| 518 |
+
num_stages=self.num_clc_stage,
|
| 519 |
+
producer_group=clc_pipeline_producer_group,
|
| 520 |
+
consumer_group=clc_pipeline_consumer_group,
|
| 521 |
+
tx_count=self.num_clc_response_bytes,
|
| 522 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 523 |
+
defer_sync=True,
|
| 524 |
+
)
|
| 525 |
+
|
| 526 |
+
tmem_alloc_barrier = pipeline.NamedBarrier(
|
| 527 |
+
barrier_id=self.tmem_alloc_sync_bar_id,
|
| 528 |
+
num_threads=32 * len((self.mma_warp_id, *self.epilogue_warp_id)),
|
| 529 |
+
)
|
| 530 |
+
# Tensor memory dealloc barrier init
|
| 531 |
+
tmem = utils.TmemAllocator(
|
| 532 |
+
storage.tmem_holding_buf.ptr,
|
| 533 |
+
barrier_for_retrieve=tmem_alloc_barrier,
|
| 534 |
+
allocator_warp_id=self.epilogue_warp_id[0],
|
| 535 |
+
is_two_cta=self.use_2cta_instrs,
|
| 536 |
+
two_cta_tmem_dealloc_mbar_ptr=storage.tmem_dealloc_mbar.ptr,
|
| 537 |
+
)
|
| 538 |
+
|
| 539 |
+
# Cluster arrive after barrier init
|
| 540 |
+
pipeline_init_arrive(cluster_shape_mn=cluster_layout_vmnk, is_relaxed=True)
|
| 541 |
+
|
| 542 |
+
# Initial clc response pointer
|
| 543 |
+
clc_response_ptr = storage.clc_response.data_ptr()
|
| 544 |
+
|
| 545 |
+
clc_consumer_state = pipeline.make_pipeline_state(
|
| 546 |
+
pipeline.PipelineUserType.Consumer, self.num_clc_stage
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
#
|
| 550 |
+
# Setup smem tensor A/B/C
|
| 551 |
+
#
|
| 552 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 553 |
+
sdS = storage.sdS.get_tensor(
|
| 554 |
+
dS_smem_layout_staged.outer,
|
| 555 |
+
swizzle=dS_smem_layout_staged.inner,
|
| 556 |
+
)
|
| 557 |
+
# (MMA_M, STAGE)
|
| 558 |
+
sI = storage.sI.get_tensor(I_smem_layout_staged)
|
| 559 |
+
|
| 560 |
+
sQ = storage.sQ.get_tensor(
|
| 561 |
+
Q_smem_layout_staged.outer,
|
| 562 |
+
swizzle=Q_smem_layout_staged.inner,
|
| 563 |
+
)
|
| 564 |
+
sdK = storage.sdK.get_tensor(
|
| 565 |
+
dK_smem_layout_staged.outer,
|
| 566 |
+
swizzle=dK_smem_layout_staged.inner,
|
| 567 |
+
)
|
| 568 |
+
|
| 569 |
+
#
|
| 570 |
+
# Compute multicast mask for A/B buffer full
|
| 571 |
+
#
|
| 572 |
+
a_full_mcast_mask = None
|
| 573 |
+
b_full_mcast_mask = None
|
| 574 |
+
if const_expr(self.is_a_mcast or self.is_b_mcast or self.use_2cta_instrs):
|
| 575 |
+
a_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 576 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 577 |
+
)
|
| 578 |
+
b_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 579 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=1
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
#
|
| 583 |
+
# Local_tile partition global tensors
|
| 584 |
+
#
|
| 585 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 586 |
+
gdS_mkl = cute.local_tile(
|
| 587 |
+
mdS_mkl, cute.slice_(self.mma_tiler_dK, (None, 0, None)), (None, None, None)
|
| 588 |
+
)
|
| 589 |
+
# (bM, RestM, RestL)
|
| 590 |
+
gI_ml = cute.local_tile(mI_ml, cute.slice_(self.mma_tiler_dK, (None, 0, 0)), (None, None))
|
| 591 |
+
# (bN, bK, RestN, RestK, RestL)
|
| 592 |
+
gQ_nkl = cute.local_tile(
|
| 593 |
+
mQ_nkl,
|
| 594 |
+
cute.slice_(self.mma_tiler_dK, (0, None, None)),
|
| 595 |
+
(None, None, None),
|
| 596 |
+
)
|
| 597 |
+
n_tile_cnt = cute.size(gQ_nkl, mode=[2])
|
| 598 |
+
# (bN, RestN, RestL)
|
| 599 |
+
gdKaccum_nl = cute.local_tile(
|
| 600 |
+
mdKaccum_nl,
|
| 601 |
+
cute.slice_(self.mma_tiler_dK, (0, None, 0)),
|
| 602 |
+
(None, None),
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
m_tile_cnt = cute.size(gdS_mkl, mode=[2])
|
| 606 |
+
k_tile_cnt = cute.size(gdS_mkl, mode=[3])
|
| 607 |
+
assert const_expr(k_tile_cnt == 1)
|
| 608 |
+
|
| 609 |
+
#
|
| 610 |
+
# Partition global tensor for TiledMMA_A/B/C
|
| 611 |
+
#
|
| 612 |
+
thr_mma_dK = tiled_mma_dK.get_slice(mma_tile_coord_v)
|
| 613 |
+
# (MMA, MMA_N, MMA_K, RestN, RestK, RestL)
|
| 614 |
+
tCgQ = thr_mma_dK.partition_B(gQ_nkl)
|
| 615 |
+
|
| 616 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 617 |
+
tCgdKaccum_fake = thr_mma_dK.partition_C(
|
| 618 |
+
cute.make_identity_tensor(
|
| 619 |
+
cute.append(cute.slice_(self.mma_tiler_dK, (None, None, 0)), 1, up_to_rank=5)
|
| 620 |
+
)
|
| 621 |
+
)
|
| 622 |
+
|
| 623 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 624 |
+
tCgdS = thr_mma_dK.partition_A(gdS_mkl)
|
| 625 |
+
|
| 626 |
+
#
|
| 627 |
+
# Partition global/shared tensor for TMA load A/B
|
| 628 |
+
#
|
| 629 |
+
# TMA load A partition_S/D
|
| 630 |
+
a_cta_layout = cute.make_layout(cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape)
|
| 631 |
+
# ((atom_v, rest_v), STAGE)
|
| 632 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 633 |
+
tAsdS, tAgdS = cpasync.tma_partition(
|
| 634 |
+
tma_atom_dS,
|
| 635 |
+
block_in_cluster_coord_vmnk[2],
|
| 636 |
+
a_cta_layout,
|
| 637 |
+
cute.group_modes(sdS, 0, 3),
|
| 638 |
+
cute.group_modes(tCgdS, 0, 3),
|
| 639 |
+
)
|
| 640 |
+
# TMA load B partition_S/D
|
| 641 |
+
b_cta_layout = cute.make_layout(cute.slice_(cluster_layout_vmnk, (0, None, 0, 0)).shape)
|
| 642 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 643 |
+
tBsQ, tBgQ = cpasync.tma_partition(
|
| 644 |
+
tma_atom_Q,
|
| 645 |
+
block_in_cluster_coord_vmnk[1],
|
| 646 |
+
b_cta_layout,
|
| 647 |
+
cute.group_modes(sQ, 0, 3),
|
| 648 |
+
cute.group_modes(tCgQ, 0, 3),
|
| 649 |
+
)
|
| 650 |
+
|
| 651 |
+
#
|
| 652 |
+
# Partition shared/tensor memory tensor for TiledMMA_A/B/C
|
| 653 |
+
#
|
| 654 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 655 |
+
tCrdS = tiled_mma_dK.make_fragment_A(sdS)
|
| 656 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 657 |
+
tCrQ = tiled_mma_dK.make_fragment_B(sQ)
|
| 658 |
+
# (MMA, MMA_M, MMA_N)
|
| 659 |
+
acc_shape_dK = tiled_mma_dK.partition_shape_C(self.mma_tiler_dK[:2])
|
| 660 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 661 |
+
tCtdK_fake = tiled_mma_dK.make_fragment_C(cute.append(acc_shape_dK, self.num_acc_stage))
|
| 662 |
+
|
| 663 |
+
#
|
| 664 |
+
# Cluster wait before tensor memory alloc
|
| 665 |
+
#
|
| 666 |
+
pipeline_init_wait(cluster_shape_mn=cluster_layout_vmnk)
|
| 667 |
+
|
| 668 |
+
#
|
| 669 |
+
# Construct the scheduler
|
| 670 |
+
#
|
| 671 |
+
tile_sched = utils.ClcDynamicPersistentTileScheduler.create(
|
| 672 |
+
tile_sched_params,
|
| 673 |
+
cute.arch.block_idx(),
|
| 674 |
+
cute.arch.grid_dim(),
|
| 675 |
+
clc_response_ptr,
|
| 676 |
+
)
|
| 677 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 678 |
+
|
| 679 |
+
#
|
| 680 |
+
# Specialized TMA load warp
|
| 681 |
+
#
|
| 682 |
+
|
| 683 |
+
if warp_idx == self.tma_warp_id:
|
| 684 |
+
Q_producer_state = pipeline.make_pipeline_state(
|
| 685 |
+
pipeline.PipelineUserType.Producer, self.num_Q_stage
|
| 686 |
+
)
|
| 687 |
+
#
|
| 688 |
+
# Persistent tile scheduling loop
|
| 689 |
+
#
|
| 690 |
+
|
| 691 |
+
dS_producer.reset()
|
| 692 |
+
peek_dS_empty_status = dS_producer.try_acquire()
|
| 693 |
+
|
| 694 |
+
while work_tile.is_valid_tile:
|
| 695 |
+
# Get tile coord from tile scheduler
|
| 696 |
+
cur_tile_coord = work_tile.tile_idx
|
| 697 |
+
total_q_coord = cur_tile_coord[0] // self.cluster_shape_mn[0]
|
| 698 |
+
|
| 699 |
+
#
|
| 700 |
+
# Slice to per mma tile index
|
| 701 |
+
#
|
| 702 |
+
# ((atom_v, rest_v), RestM, RestK)
|
| 703 |
+
tAgdS_slice = tAgdS[(None, None, None, total_q_coord)]
|
| 704 |
+
# ((atom_v, rest_v), RestN, RestK)
|
| 705 |
+
tBgQ_slice = tBgQ[(None, None, None, total_q_coord)]
|
| 706 |
+
|
| 707 |
+
#
|
| 708 |
+
# Tma load loop -- fully unrolled as all sizes are static
|
| 709 |
+
#
|
| 710 |
+
for m_tile in cutlass.range_constexpr(m_tile_cnt):
|
| 711 |
+
# Conditionally wait for AB buffer empty
|
| 712 |
+
dS_handle = dS_producer.acquire_and_advance(peek_dS_empty_status)
|
| 713 |
+
|
| 714 |
+
# TMA load dS
|
| 715 |
+
cute.copy(
|
| 716 |
+
tma_atom_dS,
|
| 717 |
+
tAgdS_slice[(None, m_tile, 0)],
|
| 718 |
+
tAsdS[(None, dS_handle.index)],
|
| 719 |
+
tma_bar_ptr=dS_handle.barrier,
|
| 720 |
+
mcast_mask=a_full_mcast_mask,
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
peek_dS_empty_status = dS_producer.try_acquire()
|
| 724 |
+
|
| 725 |
+
if m_tile == 0:
|
| 726 |
+
# TMA load Q only on first m-tile
|
| 727 |
+
for n_tile in cutlass.range_constexpr(n_tile_cnt):
|
| 728 |
+
Q_pipeline.producer_acquire(Q_producer_state)
|
| 729 |
+
Q_load_barrier = Q_pipeline.producer_get_barrier(Q_producer_state)
|
| 730 |
+
cute.copy(
|
| 731 |
+
tma_atom_Q,
|
| 732 |
+
tBgQ_slice[(None, n_tile, 0)],
|
| 733 |
+
tBsQ[(None, n_tile)],
|
| 734 |
+
tma_bar_ptr=Q_load_barrier,
|
| 735 |
+
mcast_mask=b_full_mcast_mask,
|
| 736 |
+
)
|
| 737 |
+
Q_pipeline.producer_commit(Q_producer_state)
|
| 738 |
+
Q_producer_state.advance()
|
| 739 |
+
|
| 740 |
+
#
|
| 741 |
+
# Advance to next tile
|
| 742 |
+
#
|
| 743 |
+
clc_pipeline.consumer_wait(clc_consumer_state)
|
| 744 |
+
work_tile = tile_sched.get_current_work()
|
| 745 |
+
clc_pipeline.consumer_release(clc_consumer_state)
|
| 746 |
+
clc_consumer_state.advance()
|
| 747 |
+
#
|
| 748 |
+
# Wait A/B buffer empty
|
| 749 |
+
#
|
| 750 |
+
dS_producer.tail()
|
| 751 |
+
Q_pipeline.producer_tail(Q_producer_state)
|
| 752 |
+
|
| 753 |
+
#
|
| 754 |
+
# Sched warp
|
| 755 |
+
#
|
| 756 |
+
elif warp_idx == self.sched_warp_id and is_first_cta_in_cluster:
|
| 757 |
+
#
|
| 758 |
+
# Persistent tile scheduling loop
|
| 759 |
+
#
|
| 760 |
+
clc_producer_state = pipeline.make_pipeline_state(
|
| 761 |
+
pipeline.PipelineUserType.ProducerConsumer, self.num_clc_stage
|
| 762 |
+
)
|
| 763 |
+
|
| 764 |
+
while work_tile.is_valid_tile:
|
| 765 |
+
#
|
| 766 |
+
# Advance to next tile
|
| 767 |
+
#
|
| 768 |
+
clc_pipeline.producer_acquire(clc_producer_state)
|
| 769 |
+
mbarrier_addr = clc_pipeline.producer_get_barrier(clc_producer_state)
|
| 770 |
+
tile_sched.advance_to_next_work(mbarrier_addr)
|
| 771 |
+
clc_producer_state.advance()
|
| 772 |
+
|
| 773 |
+
clc_pipeline.consumer_wait(clc_consumer_state)
|
| 774 |
+
work_tile = tile_sched.get_current_work()
|
| 775 |
+
clc_pipeline.consumer_release(clc_consumer_state)
|
| 776 |
+
clc_consumer_state.advance()
|
| 777 |
+
clc_pipeline.producer_tail(clc_producer_state)
|
| 778 |
+
|
| 779 |
+
#
|
| 780 |
+
# Specialized MMA warp
|
| 781 |
+
#
|
| 782 |
+
elif warp_idx == self.mma_warp_id:
|
| 783 |
+
#
|
| 784 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 785 |
+
#
|
| 786 |
+
tmem.wait_for_alloc()
|
| 787 |
+
tmem_ptr = tmem.retrieve_ptr(self.acc_dtype)
|
| 788 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 789 |
+
tCtdK_base = cute.make_tensor(tmem_ptr, tCtdK_fake.layout)
|
| 790 |
+
|
| 791 |
+
#
|
| 792 |
+
# Persistent tile scheduling loop
|
| 793 |
+
#
|
| 794 |
+
acc_producer_state = pipeline.make_pipeline_state(
|
| 795 |
+
pipeline.PipelineUserType.Producer, self.num_acc_stage
|
| 796 |
+
)
|
| 797 |
+
Q_consumer_state = pipeline.make_pipeline_state(
|
| 798 |
+
pipeline.PipelineUserType.Consumer, self.num_Q_stage
|
| 799 |
+
)
|
| 800 |
+
|
| 801 |
+
# Peek (try_wait) AB buffer full for k_tile = 0
|
| 802 |
+
dS_consumer.reset()
|
| 803 |
+
peek_dS_full_status = cutlass.Boolean(1)
|
| 804 |
+
if is_leader_cta:
|
| 805 |
+
peek_dS_full_status = dS_consumer.try_wait()
|
| 806 |
+
|
| 807 |
+
while work_tile.is_valid_tile:
|
| 808 |
+
# Get tile coord from tile scheduler
|
| 809 |
+
cur_tile_coord = work_tile.tile_idx
|
| 810 |
+
total_q_coord = cur_tile_coord[0] // self.cluster_shape_mn[0]
|
| 811 |
+
|
| 812 |
+
#
|
| 813 |
+
# Mma mainloop
|
| 814 |
+
#
|
| 815 |
+
Q_consumer_state_wait = Q_consumer_state.clone()
|
| 816 |
+
for m_tile in cutlass.range(m_tile_cnt):
|
| 817 |
+
if is_leader_cta:
|
| 818 |
+
# Conditionally wait for AB buffer full
|
| 819 |
+
dS_handle = dS_consumer.wait_and_advance(peek_dS_full_status)
|
| 820 |
+
for n_tile in cutlass.range_constexpr(n_tile_cnt):
|
| 821 |
+
tCtdK = tCtdK_base[(None, None, None, acc_producer_state.index)]
|
| 822 |
+
tiled_mma_dK.set(tcgen05.Field.ACCUMULATE, False)
|
| 823 |
+
acc_pipeline.producer_acquire(acc_producer_state)
|
| 824 |
+
|
| 825 |
+
if m_tile == 0:
|
| 826 |
+
Q_pipeline.consumer_wait(Q_consumer_state_wait)
|
| 827 |
+
Q_consumer_state_wait.advance()
|
| 828 |
+
|
| 829 |
+
num_kblocks = cute.size(tCrdS, mode=[2])
|
| 830 |
+
for kblk_idx in cutlass.range(num_kblocks, unroll_full=True):
|
| 831 |
+
cute.gemm(
|
| 832 |
+
tiled_mma_dK,
|
| 833 |
+
tCtdK,
|
| 834 |
+
tCrdS[(None, None, kblk_idx, dS_handle.index)],
|
| 835 |
+
tCrQ[(None, None, kblk_idx, n_tile)],
|
| 836 |
+
tCtdK,
|
| 837 |
+
)
|
| 838 |
+
# Enable accumulate on tCtdK after first kblock
|
| 839 |
+
tiled_mma_dK.set(tcgen05.Field.ACCUMULATE, True)
|
| 840 |
+
|
| 841 |
+
if m_tile == m_tile_cnt - 1:
|
| 842 |
+
Q_pipeline.consumer_release(Q_consumer_state)
|
| 843 |
+
Q_consumer_state.advance()
|
| 844 |
+
|
| 845 |
+
acc_pipeline.producer_commit(acc_producer_state)
|
| 846 |
+
acc_producer_state.advance()
|
| 847 |
+
# Async arrive AB buffer empty
|
| 848 |
+
dS_handle.release()
|
| 849 |
+
|
| 850 |
+
peek_dS_full_status = dS_consumer.try_wait()
|
| 851 |
+
else:
|
| 852 |
+
for n_tile in cutlass.range_constexpr(n_tile_cnt):
|
| 853 |
+
acc_producer_state.advance()
|
| 854 |
+
|
| 855 |
+
#
|
| 856 |
+
# Advance to next tile
|
| 857 |
+
#
|
| 858 |
+
clc_pipeline.consumer_wait(clc_consumer_state)
|
| 859 |
+
work_tile = tile_sched.get_current_work()
|
| 860 |
+
clc_pipeline.consumer_release(clc_consumer_state)
|
| 861 |
+
clc_consumer_state.advance()
|
| 862 |
+
#
|
| 863 |
+
# Wait for accumulator buffer empty
|
| 864 |
+
#
|
| 865 |
+
acc_pipeline.producer_tail(acc_producer_state)
|
| 866 |
+
|
| 867 |
+
#
|
| 868 |
+
# Specialized epilogue warps
|
| 869 |
+
#
|
| 870 |
+
elif warp_idx < self.mma_warp_id:
|
| 871 |
+
#
|
| 872 |
+
# Alloc tensor memory buffer
|
| 873 |
+
#
|
| 874 |
+
tmem.allocate(self.num_tmem_alloc_cols)
|
| 875 |
+
|
| 876 |
+
#
|
| 877 |
+
# Retrieving tensor memory ptr and make accumulator tensor
|
| 878 |
+
#
|
| 879 |
+
tmem.wait_for_alloc()
|
| 880 |
+
tmem_ptr = tmem.retrieve_ptr(self.acc_dtype)
|
| 881 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 882 |
+
tCtdK_base = cute.make_tensor(tmem_ptr, tCtdK_fake.layout)
|
| 883 |
+
|
| 884 |
+
# Both gemms share accumulator and TMA store pipelines
|
| 885 |
+
acc_consumer_state = pipeline.make_pipeline_state(
|
| 886 |
+
pipeline.PipelineUserType.Consumer, self.num_acc_stage
|
| 887 |
+
)
|
| 888 |
+
c_producer_group = pipeline.CooperativeGroup(
|
| 889 |
+
pipeline.Agent.Thread,
|
| 890 |
+
32 * len(self.epilogue_warp_id),
|
| 891 |
+
)
|
| 892 |
+
|
| 893 |
+
I_load_barrier = pipeline.NamedBarrier(
|
| 894 |
+
barrier_id=self.I_load_bar_id,
|
| 895 |
+
num_threads=32 * len(self.epilogue_warp_id),
|
| 896 |
+
)
|
| 897 |
+
|
| 898 |
+
# (EPI_TOPK, REST_TOPK, TOKENS)
|
| 899 |
+
gI_tile = cute.local_tile(
|
| 900 |
+
gI_ml,
|
| 901 |
+
(epi_tile_dK[0],),
|
| 902 |
+
(0 if is_leader_cta else 1, None, None),
|
| 903 |
+
)
|
| 904 |
+
|
| 905 |
+
thr_copy_I = tiled_copy_I.get_slice(tidx)
|
| 906 |
+
# (COPY_ATOM, EPI_TOPK, REST_TOPK, TOKENS)
|
| 907 |
+
tIgI = thr_copy_I.partition_S(gI_tile)
|
| 908 |
+
# (COPY_ATOM, EPI_TOPK, STAGE)
|
| 909 |
+
tIsI = thr_copy_I.partition_D(sI)
|
| 910 |
+
|
| 911 |
+
num_subtiles_executed = 0
|
| 912 |
+
while work_tile.is_valid_tile:
|
| 913 |
+
# Get tile coord from tile scheduler
|
| 914 |
+
cur_tile_coord = work_tile.tile_idx
|
| 915 |
+
token = cutlass.Int64(cur_tile_coord[0]) // self.cluster_shape_mn[0]
|
| 916 |
+
|
| 917 |
+
# Get batch index from token_coord and cuSeqlensQ
|
| 918 |
+
# Get seqlen_k offset from cuSeqlensK
|
| 919 |
+
if const_expr(self.varlen):
|
| 920 |
+
batch = get_batch_from_cu_tensor(token, cuSeqlensQ)
|
| 921 |
+
seqlen_k_offset = cuSeqlensK[batch]
|
| 922 |
+
else:
|
| 923 |
+
_, batch = divmod(token, batch_divmod)
|
| 924 |
+
seqlen_k_offset = Int32(0) # unused
|
| 925 |
+
|
| 926 |
+
sI_read_stage = 0
|
| 927 |
+
sI_write_stage = 0
|
| 928 |
+
|
| 929 |
+
# Prefetch load I
|
| 930 |
+
I_load_barrier.arrive_and_wait()
|
| 931 |
+
for m_tile in cutlass.range_constexpr(min(m_tile_cnt, self.num_I_stage - 1)):
|
| 932 |
+
cute.copy(
|
| 933 |
+
tiled_copy_I,
|
| 934 |
+
tIgI[(None, None, m_tile, token)],
|
| 935 |
+
tIsI[(None, None, sI_write_stage)],
|
| 936 |
+
)
|
| 937 |
+
cute.arch.cp_async_commit_group()
|
| 938 |
+
sI_write_stage = (sI_write_stage + 1) % self.num_I_stage
|
| 939 |
+
|
| 940 |
+
for m_tile in cutlass.range(m_tile_cnt):
|
| 941 |
+
I_load_barrier.arrive_and_wait()
|
| 942 |
+
# cp.async load I
|
| 943 |
+
if m_tile < m_tile_cnt - 1:
|
| 944 |
+
cute.copy(
|
| 945 |
+
tiled_copy_I,
|
| 946 |
+
tIgI[(None, None, m_tile + 1, token)],
|
| 947 |
+
tIsI[(None, None, sI_write_stage)],
|
| 948 |
+
)
|
| 949 |
+
cute.arch.cp_async_commit_group()
|
| 950 |
+
sI_write_stage = (sI_write_stage + 1) % self.num_I_stage
|
| 951 |
+
cute.arch.cp_async_wait_group(self.num_I_stage - 1)
|
| 952 |
+
I_load_barrier.arrive_and_wait()
|
| 953 |
+
|
| 954 |
+
sI_tile = sI[(None, sI_read_stage)]
|
| 955 |
+
|
| 956 |
+
for n_tile in cutlass.range_constexpr(n_tile_cnt):
|
| 957 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 958 |
+
mma_tile_coord_mnl = (
|
| 959 |
+
m_tile,
|
| 960 |
+
n_tile,
|
| 961 |
+
token,
|
| 962 |
+
)
|
| 963 |
+
acc_consumer_state = self.epilogue_scatter_reduce(
|
| 964 |
+
tidx,
|
| 965 |
+
0 if is_leader_cta else 1,
|
| 966 |
+
tiled_copy_dK,
|
| 967 |
+
tCtdK_base,
|
| 968 |
+
sdK,
|
| 969 |
+
sI_tile,
|
| 970 |
+
gdKaccum_nl,
|
| 971 |
+
tCgdKaccum_fake,
|
| 972 |
+
batch,
|
| 973 |
+
seqlen_k_offset,
|
| 974 |
+
epi_tile_dK,
|
| 975 |
+
num_subtiles_executed,
|
| 976 |
+
mma_tile_coord_mnl,
|
| 977 |
+
acc_consumer_state,
|
| 978 |
+
acc_pipeline,
|
| 979 |
+
)
|
| 980 |
+
num_subtiles_executed += self.mma_tiler_dK[1] // cute.size(epi_tile_dK[1])
|
| 981 |
+
# Advance I consumer pipeline
|
| 982 |
+
sI_read_stage = (sI_read_stage + 1) % self.num_I_stage
|
| 983 |
+
|
| 984 |
+
cute.arch.cp_async_wait_group(0)
|
| 985 |
+
#
|
| 986 |
+
# Advance to next tile
|
| 987 |
+
#
|
| 988 |
+
clc_pipeline.consumer_wait(clc_consumer_state)
|
| 989 |
+
work_tile = tile_sched.get_current_work()
|
| 990 |
+
clc_pipeline.consumer_release(clc_consumer_state)
|
| 991 |
+
clc_consumer_state.advance()
|
| 992 |
+
|
| 993 |
+
# # Wait for C store complete
|
| 994 |
+
# dK_pipeline.producer_tail()
|
| 995 |
+
#
|
| 996 |
+
# Dealloc the tensor memory buffer
|
| 997 |
+
#
|
| 998 |
+
tmem.relinquish_alloc_permit()
|
| 999 |
+
tmem.free(tmem_ptr)
|
| 1000 |
+
|
| 1001 |
+
@cute.jit
|
| 1002 |
+
def epilogue_scatter_reduce(
|
| 1003 |
+
self,
|
| 1004 |
+
epi_tidx: Int32,
|
| 1005 |
+
cta_idx: Int32,
|
| 1006 |
+
tiled_copy_c: cute.CopyAtom,
|
| 1007 |
+
tCtAcc_base: cute.Tensor,
|
| 1008 |
+
sC: cute.Tensor,
|
| 1009 |
+
sI_tile: cute.Tensor,
|
| 1010 |
+
gC_base: cute.Tensor,
|
| 1011 |
+
tCgC_fake: cute.Tensor,
|
| 1012 |
+
batch: Int32,
|
| 1013 |
+
seqlen_k_offset: Int32,
|
| 1014 |
+
epi_tile: cute.Tile,
|
| 1015 |
+
num_subtiles_executed: Int32,
|
| 1016 |
+
mma_tile_coord_mnl: Tuple[Int32, Int32, cutlass.Int64],
|
| 1017 |
+
acc_consumer_state: pipeline.PipelineState,
|
| 1018 |
+
acc_pipeline: pipeline.PipelineAsync,
|
| 1019 |
+
) -> pipeline.PipelineState:
|
| 1020 |
+
warp_idx = cute.arch.make_warp_uniform(epi_tidx // 32)
|
| 1021 |
+
|
| 1022 |
+
# Layout transformation for tCgC_base
|
| 1023 |
+
# ((MMA_ATOM_M, MMA_ATOM_N), MMA_M, MMA_N, REST_M, REST_N, REST_L)
|
| 1024 |
+
# -> ((MMA_ATOM_M, MMA_M), (MMA_ATOM_N, MMA_N), REST_M, REST_N, REST_L)
|
| 1025 |
+
tCgC_fake = transform_partitioned_tensor_layout(tCgC_fake)
|
| 1026 |
+
|
| 1027 |
+
# Layout transformation for tCtAcc_base
|
| 1028 |
+
# ((MMA_ATOM_M, MMA_ATOM_N), MMA_M, MMA_N, STAGE)
|
| 1029 |
+
# -> ((MMA_ATOM_M, MMA_M), (MMA_ATOM_N, MMA_N), STAGE)
|
| 1030 |
+
tCtAcc = transform_partitioned_tensor_layout(tCtAcc_base)
|
| 1031 |
+
|
| 1032 |
+
tiled_copy_t2r, tTR_tAcc_base, tTR_rAcc = epilogue_tmem_copy_and_partition(
|
| 1033 |
+
self,
|
| 1034 |
+
epi_tidx,
|
| 1035 |
+
tCtAcc,
|
| 1036 |
+
tCgC_fake,
|
| 1037 |
+
epi_tile,
|
| 1038 |
+
self.use_2cta_instrs,
|
| 1039 |
+
)
|
| 1040 |
+
|
| 1041 |
+
tTR_rC = cute.make_rmem_tensor(tTR_rAcc.shape, self.c_dtype)
|
| 1042 |
+
tiled_copy_r2s, tRS_rC, tRS_sC = epilogue_smem_copy_and_partition(
|
| 1043 |
+
self, tiled_copy_t2r, tTR_rC, epi_tidx, sC
|
| 1044 |
+
)
|
| 1045 |
+
|
| 1046 |
+
# (EPI_TILE_N, EPI_N, RestN, RestL)
|
| 1047 |
+
gC_epi = cute.flat_divide(gC_base, (epi_tile[1],))
|
| 1048 |
+
# (EPI_TILE_N, EPI_N, SEQLEN_K)
|
| 1049 |
+
gC_epi = gC_epi[None, None, mma_tile_coord_mnl[1], None]
|
| 1050 |
+
# (EPI_TILE_N, MMA_M, STAGE)
|
| 1051 |
+
sC_epi = cute.make_tensor(
|
| 1052 |
+
sC.iterator,
|
| 1053 |
+
cute.select(sC.layout, [1, 0, 2]),
|
| 1054 |
+
# swizzle=sC.layout.inner,
|
| 1055 |
+
)
|
| 1056 |
+
|
| 1057 |
+
thr_copy_c = tiled_copy_c.get_slice(epi_tidx % 32)
|
| 1058 |
+
# (COPY_ATOM_N, COPY_N, MMA_M, STAGE)
|
| 1059 |
+
tCsC = thr_copy_c.partition_S(sC_epi)
|
| 1060 |
+
# (COPY_ATOM_N, COPY_N)
|
| 1061 |
+
tCrC = cute.make_fragment_like(tCsC[None, None, 0, 0])
|
| 1062 |
+
# (COPY_ATOM_N, COPY_N, EPI_N, SEQLEN_K)
|
| 1063 |
+
tCgC = thr_copy_c.partition_D(gC_epi)
|
| 1064 |
+
|
| 1065 |
+
# Set tensor memory buffer for current tile
|
| 1066 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_M)
|
| 1067 |
+
tTR_tAcc = tTR_tAcc_base[(None, None, None, None, None, acc_consumer_state.index)]
|
| 1068 |
+
|
| 1069 |
+
#
|
| 1070 |
+
# Wait for accumulator buffer full
|
| 1071 |
+
#
|
| 1072 |
+
acc_pipeline.consumer_wait(acc_consumer_state)
|
| 1073 |
+
|
| 1074 |
+
tTR_tAcc = cute.group_modes(tTR_tAcc, 3, cute.rank(tTR_tAcc))
|
| 1075 |
+
|
| 1076 |
+
epilog_sync_barrier = pipeline.NamedBarrier(
|
| 1077 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 1078 |
+
num_threads=32 * len(self.epilogue_warp_id),
|
| 1079 |
+
)
|
| 1080 |
+
|
| 1081 |
+
#
|
| 1082 |
+
# Store accumulator to global memory in subtiles
|
| 1083 |
+
#
|
| 1084 |
+
subtile_cnt = cute.size(tTR_tAcc.shape, mode=[3])
|
| 1085 |
+
for subtile_idx in range(subtile_cnt):
|
| 1086 |
+
#
|
| 1087 |
+
# Load accumulator from tensor memory buffer to register
|
| 1088 |
+
#
|
| 1089 |
+
tTR_tAcc_mn = tTR_tAcc[(None, None, None, subtile_idx)]
|
| 1090 |
+
cute.copy(tiled_copy_t2r, tTR_tAcc_mn, tTR_rAcc)
|
| 1091 |
+
|
| 1092 |
+
#
|
| 1093 |
+
# Convert to C type
|
| 1094 |
+
#
|
| 1095 |
+
acc_vec = tiled_copy_r2s.retile(tTR_rAcc).load()
|
| 1096 |
+
acc_vec = acc_vec.to(self.c_dtype)
|
| 1097 |
+
tRS_rC.store(acc_vec)
|
| 1098 |
+
|
| 1099 |
+
#
|
| 1100 |
+
# Store C to shared memory
|
| 1101 |
+
#
|
| 1102 |
+
c_buffer = (num_subtiles_executed + subtile_idx) % self.num_c_stage
|
| 1103 |
+
cute.copy(tiled_copy_r2s, tRS_rC, tRS_sC[(None, None, None, c_buffer)])
|
| 1104 |
+
|
| 1105 |
+
# Fence and barrier to make sure shared memory store is visible to TMA store
|
| 1106 |
+
cute.arch.fence_proxy("async.shared", space="cta")
|
| 1107 |
+
epilog_sync_barrier.arrive_and_wait()
|
| 1108 |
+
|
| 1109 |
+
#
|
| 1110 |
+
# TMA store C to global memory (issued by lane 0 from all epi warps)
|
| 1111 |
+
#
|
| 1112 |
+
for topk_idx_in_warp in cutlass.range(32):
|
| 1113 |
+
topk_idx = topk_idx_in_warp + warp_idx * 32
|
| 1114 |
+
seqlen_k_idx_in_batch = sI_tile[topk_idx]
|
| 1115 |
+
if const_expr(self.varlen):
|
| 1116 |
+
seqlen_k_idx = seqlen_k_idx_in_batch + seqlen_k_offset
|
| 1117 |
+
else:
|
| 1118 |
+
seqlen_k_idx = (batch, seqlen_k_idx_in_batch)
|
| 1119 |
+
cute.copy(tiled_copy_c, tCsC[(None, None, topk_idx, c_buffer)], tCrC)
|
| 1120 |
+
# Skip -1 sentinel slots (invalid top-k entries)
|
| 1121 |
+
if seqlen_k_idx_in_batch >= 0:
|
| 1122 |
+
for j in cutlass.range_constexpr(cute.size(tCrC, mode=[1])):
|
| 1123 |
+
for i in cutlass.range_constexpr(cute.size(tCrC, mode=[0])):
|
| 1124 |
+
ptr = elem_pointer(tCgC, (i, j, subtile_idx, seqlen_k_idx))
|
| 1125 |
+
cute.arch.atomic_add(
|
| 1126 |
+
ptr=ptr,
|
| 1127 |
+
val=tCrC[i, j],
|
| 1128 |
+
)
|
| 1129 |
+
epilog_sync_barrier.arrive_and_wait()
|
| 1130 |
+
|
| 1131 |
+
epilog_sync_barrier.arrive_and_wait()
|
| 1132 |
+
|
| 1133 |
+
#
|
| 1134 |
+
# Async arrive accumulator buffer empty
|
| 1135 |
+
#
|
| 1136 |
+
with cute.arch.elect_one():
|
| 1137 |
+
acc_pipeline.consumer_release(acc_consumer_state)
|
| 1138 |
+
acc_consumer_state.advance()
|
| 1139 |
+
return acc_consumer_state
|
| 1140 |
+
|
| 1141 |
+
def check_can_implement(self):
|
| 1142 |
+
"""Check if parameters are valid.
|
| 1143 |
+
|
| 1144 |
+
:raises testing.CantImplementError: If the mma tiler, cluster shape, or alignments are invalid
|
| 1145 |
+
"""
|
| 1146 |
+
if self.dim != 64:
|
| 1147 |
+
raise testing.CantImplementError(f"Only dim = 64 supported for dK gemm, got {self.dim}")
|
| 1148 |
+
# Check valid MMA tile shape and cluster shape
|
| 1149 |
+
if not (
|
| 1150 |
+
(not self.use_2cta_instrs and self.mma_tiler_dK[0] in [64, 128])
|
| 1151 |
+
or (self.use_2cta_instrs and self.mma_tiler_dK[0] in [128, 256])
|
| 1152 |
+
):
|
| 1153 |
+
raise testing.CantImplementError(
|
| 1154 |
+
f"Invalid mma tiler & use_2cta_instrs: {self.mma_tiler_dK}, {self.use_2cta_instrs}"
|
| 1155 |
+
)
|
| 1156 |
+
if self.mma_tiler_dK[1] not in range(32, 257, 32):
|
| 1157 |
+
raise testing.CantImplementError(f"Invalid mma tiler N: {self.mma_tiler_dK[1]}")
|
| 1158 |
+
# Skip illegal cluster shape
|
| 1159 |
+
if self.cluster_shape_mn[0] % (2 if self.use_2cta_instrs else 1) != 0:
|
| 1160 |
+
raise testing.CantImplementError(f"Invalid cluster shape M: {self.cluster_shape_mn[0]}")
|
| 1161 |
+
# Skip invalid cluster shape
|
| 1162 |
+
is_power_of_2 = lambda x: x > 0 and (x & (x - 1)) == 0
|
| 1163 |
+
if (
|
| 1164 |
+
self.cluster_shape_mn[0] * self.cluster_shape_mn[1] > 16
|
| 1165 |
+
or self.cluster_shape_mn[0] <= 0
|
| 1166 |
+
or self.cluster_shape_mn[1] <= 0
|
| 1167 |
+
or not is_power_of_2(self.cluster_shape_mn[0])
|
| 1168 |
+
or not is_power_of_2(self.cluster_shape_mn[1])
|
| 1169 |
+
):
|
| 1170 |
+
raise testing.CantImplementError(f"Invalid cluster shape: {self.cluster_shape_mn}")
|
| 1171 |
+
|
| 1172 |
+
# Check that all tensors are 16B aligned for TMA
|
| 1173 |
+
|
| 1174 |
+
def check_contiguous_16B_alignment(dtype, num_major_elements):
|
| 1175 |
+
num_contiguous_elements = 16 * 8 // dtype.width
|
| 1176 |
+
return num_major_elements % num_contiguous_elements == 0
|
| 1177 |
+
|
| 1178 |
+
if (
|
| 1179 |
+
not check_contiguous_16B_alignment(self.ab_dtype, self.topk)
|
| 1180 |
+
or not check_contiguous_16B_alignment(self.ab_dtype, self.dim)
|
| 1181 |
+
or not check_contiguous_16B_alignment(self.c_dtype, self.dim)
|
| 1182 |
+
):
|
| 1183 |
+
raise testing.CantImplementError(
|
| 1184 |
+
f"Invalid tensor alignment: {self.ab_dtype=}, {self.c_dtype=}, {self.topk=}, {self.dim=}"
|
| 1185 |
+
)
|
build/torch-cuda/flash_bwd_mla_dq_dqv_sm100.py
ADDED
|
@@ -0,0 +1,1297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2026, Colfax International.
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
CuTe DSL implementation of dQ+dQv gemm for DSA backward.
|
| 5 |
+
Performs both dQ = dS @ K and dQv = dS @ V, where K and V are
|
| 6 |
+
gathered according to index tensor mIdxTopK.
|
| 7 |
+
|
| 8 |
+
This uses MQA with 128 heads.
|
| 9 |
+
|
| 10 |
+
Inputs:
|
| 11 |
+
- dS: [batch, seqlen_q, nheads, top_k] or [total_q, nheads, top_k]
|
| 12 |
+
- K: [batch, seqlen_k, hdim] or [total_k, hdim]
|
| 13 |
+
- V: [batch, seqlen_k, hdim_v] or [total_k, hdim_v]
|
| 14 |
+
- IdxTopK: [batch, seqlen_q, top_k] or [total_q, top_k]
|
| 15 |
+
|
| 16 |
+
Outputs:
|
| 17 |
+
- dQ: [batch, seqlen_q, nheads, hdim] or [total_q, nheads, hdim]
|
| 18 |
+
- dQv: [batch, seqlen_q, nheads, hdim_v] or [total_q, nheads, hdim_v]
|
| 19 |
+
|
| 20 |
+
All sizes are known at compile time except seqlen_q, which is the batch dimension.
|
| 21 |
+
Representative numbers are:
|
| 22 |
+
- nheads = 128
|
| 23 |
+
- hdim = 64
|
| 24 |
+
- hdim_v = 512
|
| 25 |
+
- top_k = 2048
|
| 26 |
+
|
| 27 |
+
We launch a cluster of shape (1, 2) with mma tile 128x256, so that one cluster
|
| 28 |
+
covers the full dQv mma. dS is loaded via TMA and multicast across the CTAs.
|
| 29 |
+
Cluster 0 also performs the dQ mma with tile size 128x64.
|
| 30 |
+
|
| 31 |
+
K and V are loaded via CpAsync, with logic according to CpasyncGatherKVManager.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
from functools import partial
|
| 35 |
+
from typing import Optional, Tuple, Type
|
| 36 |
+
|
| 37 |
+
import cuda.bindings.driver as cuda
|
| 38 |
+
import cutlass
|
| 39 |
+
import cutlass.cute as cute
|
| 40 |
+
import cutlass.pipeline as pipeline
|
| 41 |
+
import cutlass.utils as utils
|
| 42 |
+
from cutlass import Int32, const_expr
|
| 43 |
+
from cutlass.cute import FastDivmodDivisor
|
| 44 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 45 |
+
from cutlass.pipeline import pipeline_init_arrive, pipeline_init_wait
|
| 46 |
+
|
| 47 |
+
from .topk_gather_kv import CpasyncGatherKVManager
|
| 48 |
+
from .utils import get_batch_from_cu_tensor
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class dQdQvGemmKernel:
|
| 52 |
+
def __init__(
|
| 53 |
+
self,
|
| 54 |
+
acc_dtype: Type[cutlass.Numeric],
|
| 55 |
+
nheads: int,
|
| 56 |
+
head_dim_k: Optional[int],
|
| 57 |
+
head_dim_v: int,
|
| 58 |
+
top_k: int,
|
| 59 |
+
):
|
| 60 |
+
self.acc_dtype: Type[cutlass.Numeric] = acc_dtype
|
| 61 |
+
self.nheads = nheads
|
| 62 |
+
assert self.nheads == 128, (
|
| 63 |
+
"only 128 heads supported; will expand to include 64 heads in a future PR."
|
| 64 |
+
)
|
| 65 |
+
self.head_dim_k = head_dim_k or 0 # when head_dim_k not provided, dQ is not computed
|
| 66 |
+
self.head_dim_v = head_dim_v
|
| 67 |
+
self.top_k = top_k
|
| 68 |
+
self.tile_k = 128
|
| 69 |
+
|
| 70 |
+
self.cluster_shape_mn = (1, 2)
|
| 71 |
+
self.mma_tiler_dQ = (self.nheads, self.head_dim_k, self.tile_k)
|
| 72 |
+
self.mma_tiler_dQv = (self.nheads, self.head_dim_v // 2, self.tile_k)
|
| 73 |
+
self.num_mainloop_iters = self.top_k // self.tile_k
|
| 74 |
+
self.arch = "sm_100"
|
| 75 |
+
|
| 76 |
+
self.cta_group = tcgen05.CtaGroup.ONE
|
| 77 |
+
|
| 78 |
+
self.occupancy = 1
|
| 79 |
+
self.threads_per_warp = cute.arch.WARP_SIZE
|
| 80 |
+
|
| 81 |
+
# ---- Set specialized warp ids ----
|
| 82 |
+
self.epilogue_warp_ids = (0, 1, 2, 3)
|
| 83 |
+
self.kv_load_warp_ids = (4, 5, 6, 7)
|
| 84 |
+
self.mma_warp_id = 8
|
| 85 |
+
self.tma_warp_id = 9
|
| 86 |
+
self.sched_warp_id = 10
|
| 87 |
+
self.threads_per_cta = 32 * len(
|
| 88 |
+
(
|
| 89 |
+
self.mma_warp_id,
|
| 90 |
+
self.tma_warp_id,
|
| 91 |
+
self.sched_warp_id,
|
| 92 |
+
*self.epilogue_warp_ids,
|
| 93 |
+
*self.kv_load_warp_ids,
|
| 94 |
+
)
|
| 95 |
+
)
|
| 96 |
+
# ---- Set barrier id for cta sync, epilogue sync and tmem ptr sync ----
|
| 97 |
+
self.epilog_sync_bar_id = 1
|
| 98 |
+
self.tmem_alloc_sync_bar_id = 2
|
| 99 |
+
self.tmem_dealloc_sync_bar_id = 3
|
| 100 |
+
self.kv_load_sync_bar_id = 4
|
| 101 |
+
|
| 102 |
+
self.epilog_sync_barrier = pipeline.NamedBarrier(
|
| 103 |
+
barrier_id=self.epilog_sync_bar_id,
|
| 104 |
+
num_threads=self.threads_per_warp * len(self.epilogue_warp_ids),
|
| 105 |
+
)
|
| 106 |
+
self.kv_load_sync_barrier = pipeline.NamedBarrier(
|
| 107 |
+
barrier_id=self.kv_load_sync_bar_id,
|
| 108 |
+
num_threads=self.threads_per_warp * len(self.kv_load_warp_ids),
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
self.is_persistent = False
|
| 112 |
+
|
| 113 |
+
# ---- pipeline stages ---- TODO: tune these
|
| 114 |
+
self.num_stages_dS = 2
|
| 115 |
+
self.num_stages_KV = 2
|
| 116 |
+
self.num_stages_acc = 1
|
| 117 |
+
self.num_stages_clc = 1
|
| 118 |
+
|
| 119 |
+
# ---- register allocation ----
|
| 120 |
+
self.num_regs_KV = 224
|
| 121 |
+
self.num_regs_epi = 128
|
| 122 |
+
self.num_regs_other = 112
|
| 123 |
+
|
| 124 |
+
@cute.jit
|
| 125 |
+
def __call__(
|
| 126 |
+
self,
|
| 127 |
+
mdS: cute.Tensor,
|
| 128 |
+
mK: Optional[cute.Tensor],
|
| 129 |
+
mV: cute.Tensor,
|
| 130 |
+
mdQ: cute.Tensor,
|
| 131 |
+
mdQv: cute.Tensor,
|
| 132 |
+
mIdxTopK: cute.Tensor,
|
| 133 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 134 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 135 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 136 |
+
stream: cuda.CUstream = None,
|
| 137 |
+
):
|
| 138 |
+
self.compute_dQ = const_expr(mK is not None)
|
| 139 |
+
|
| 140 |
+
# ---- dtype info ----
|
| 141 |
+
self.ds_dtype: Type[cutlass.Numeric] = mdS.element_type
|
| 142 |
+
self.kv_dtype: Type[cutlass.Numeric] = mV.element_type
|
| 143 |
+
self.dq_dtype: Type[cutlass.Numeric] = mdQv.element_type
|
| 144 |
+
if const_expr(self.compute_dQ):
|
| 145 |
+
assert self.kv_dtype == mV.element_type
|
| 146 |
+
assert self.dq_dtype == mdQ.element_type
|
| 147 |
+
|
| 148 |
+
varlen_q = const_expr(mCuSeqlensQ is not None)
|
| 149 |
+
varlen_k = const_expr(mCuSeqlensK is not None)
|
| 150 |
+
|
| 151 |
+
# ------------------------------------------------------------------ #
|
| 152 |
+
# Reshape GMEM layouts for static strides #
|
| 153 |
+
# ------------------------------------------------------------------ #
|
| 154 |
+
seqlen_q = Int32(0) if const_expr(varlen_q) else mdS.shape[1]
|
| 155 |
+
seqlen_q_divmod = FastDivmodDivisor(seqlen_q)
|
| 156 |
+
seqlen_k = Int32(0) if const_expr(varlen_k) else mV.shape[1]
|
| 157 |
+
|
| 158 |
+
# ---- group batch and seqlen modes in nonvarlen case ----
|
| 159 |
+
def group_batch_seqlen(t: cute.Tensor, varlen: bool) -> cute.Tensor:
|
| 160 |
+
if const_expr(not varlen):
|
| 161 |
+
t = cute.make_tensor(
|
| 162 |
+
t.iterator,
|
| 163 |
+
cute.make_layout(
|
| 164 |
+
(t.shape[1], t.shape[0], *t.shape[2:]),
|
| 165 |
+
stride=(t.stride[1], t.stride[0], *t.stride[2:]),
|
| 166 |
+
),
|
| 167 |
+
)
|
| 168 |
+
t = cute.group_modes(t, 0, 2)
|
| 169 |
+
return t
|
| 170 |
+
|
| 171 |
+
mdS = group_batch_seqlen(mdS, varlen_q)
|
| 172 |
+
mdQv = group_batch_seqlen(mdQv, varlen_q)
|
| 173 |
+
mV = group_batch_seqlen(mV, varlen_k)
|
| 174 |
+
mIdxTopK = group_batch_seqlen(mIdxTopK, varlen_q)
|
| 175 |
+
if const_expr(self.compute_dQ):
|
| 176 |
+
mdQ = group_batch_seqlen(mdQ, varlen_q)
|
| 177 |
+
mK = group_batch_seqlen(mK, varlen_k)
|
| 178 |
+
|
| 179 |
+
# ---- transpose and make static modes static ----
|
| 180 |
+
def static_reshape(t: cute.Tensor, *static_shapes) -> cute.Tensor:
|
| 181 |
+
static_modes = range(1, len(t.shape))
|
| 182 |
+
return cute.make_tensor(
|
| 183 |
+
t.iterator,
|
| 184 |
+
cute.make_layout(
|
| 185 |
+
(*static_shapes, t.shape[0]),
|
| 186 |
+
stride=(*(t.stride[i] for i in static_modes), t.stride[0]),
|
| 187 |
+
),
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
mdS = static_reshape(mdS, self.nheads, self.top_k)
|
| 191 |
+
mdQv = static_reshape(mdQv, self.nheads, self.head_dim_v)
|
| 192 |
+
mV = static_reshape(mV, self.head_dim_v)
|
| 193 |
+
mIdxTopK = static_reshape(mIdxTopK, self.top_k)
|
| 194 |
+
if const_expr(self.compute_dQ):
|
| 195 |
+
mdQ = static_reshape(mdQ, self.nheads, self.head_dim_k)
|
| 196 |
+
mK = static_reshape(mK, self.head_dim_k)
|
| 197 |
+
|
| 198 |
+
# ---- layout info ----
|
| 199 |
+
self.ds_major_mode = utils.LayoutEnum.from_tensor(mdS).mma_major_mode()
|
| 200 |
+
self.kv_major_mode = utils.LayoutEnum.from_tensor(mV).mma_major_mode()
|
| 201 |
+
self.dq_layout = utils.LayoutEnum.from_tensor(mdQv)
|
| 202 |
+
if const_expr(self.compute_dQ):
|
| 203 |
+
assert self.dq_layout == utils.LayoutEnum.from_tensor(mdQ)
|
| 204 |
+
|
| 205 |
+
# ------------------------------------------------------------------ #
|
| 206 |
+
# Setup attributes that depend on kernel inputs #
|
| 207 |
+
# ------------------------------------------------------------------ #
|
| 208 |
+
tiled_mma_v = utils.sm100.make_trivial_tiled_mma(
|
| 209 |
+
self.ds_dtype,
|
| 210 |
+
self.ds_major_mode,
|
| 211 |
+
self.kv_major_mode,
|
| 212 |
+
self.acc_dtype,
|
| 213 |
+
self.cta_group,
|
| 214 |
+
self.mma_tiler_dQv[:2],
|
| 215 |
+
)
|
| 216 |
+
if const_expr(self.compute_dQ):
|
| 217 |
+
tiled_mma_k = utils.sm100.make_trivial_tiled_mma(
|
| 218 |
+
self.ds_dtype,
|
| 219 |
+
self.ds_major_mode,
|
| 220 |
+
self.kv_major_mode,
|
| 221 |
+
self.acc_dtype,
|
| 222 |
+
self.cta_group,
|
| 223 |
+
self.mma_tiler_dQ[:2],
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
self.cta_tile_shape_dQv = (
|
| 227 |
+
self.mma_tiler_dQv[0],
|
| 228 |
+
self.mma_tiler_dQv[1],
|
| 229 |
+
self.mma_tiler_dQv[2],
|
| 230 |
+
)
|
| 231 |
+
|
| 232 |
+
# ---- Compute cluster layout ----
|
| 233 |
+
self.cluster_layout_vmnk = cute.tiled_divide(
|
| 234 |
+
cute.make_layout((*self.cluster_shape_mn, 1)),
|
| 235 |
+
(tiled_mma_v.thr_id.shape,),
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
# ---- Compute number of multicast CTAs for A/B ----
|
| 239 |
+
self.num_mcast_ctas_a = cute.size(self.cluster_layout_vmnk.shape[2])
|
| 240 |
+
|
| 241 |
+
# ---- Compute epi tiles for dQ/dQv ----
|
| 242 |
+
self.epi_tile_dQv = utils.sm100.compute_epilogue_tile_shape(
|
| 243 |
+
self.cta_tile_shape_dQv,
|
| 244 |
+
False, # use_2cta_instrs
|
| 245 |
+
self.dq_layout,
|
| 246 |
+
self.dq_dtype,
|
| 247 |
+
)
|
| 248 |
+
self.epi_tile_dQ = None
|
| 249 |
+
if const_expr(self.compute_dQ):
|
| 250 |
+
self.epi_tile_dQ = utils.sm100.compute_epilogue_tile_shape(
|
| 251 |
+
self.mma_tiler_dQ,
|
| 252 |
+
False,
|
| 253 |
+
self.dq_layout,
|
| 254 |
+
self.dq_dtype,
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
# ---- Device-specific attributes ----
|
| 258 |
+
self.smem_capacity = utils.get_smem_capacity_in_bytes()
|
| 259 |
+
|
| 260 |
+
self.num_tmem_alloc_cols = 512
|
| 261 |
+
|
| 262 |
+
# ------------------------------------------------------------------ #
|
| 263 |
+
# Make SMEM layouts #
|
| 264 |
+
# ------------------------------------------------------------------ #
|
| 265 |
+
sdS_layout = utils.sm100.make_smem_layout_a(
|
| 266 |
+
tiled_mma_v,
|
| 267 |
+
self.mma_tiler_dQ,
|
| 268 |
+
self.ds_dtype,
|
| 269 |
+
self.num_stages_dS,
|
| 270 |
+
)
|
| 271 |
+
sV_layout = utils.sm100.make_smem_layout_b(
|
| 272 |
+
tiled_mma_v,
|
| 273 |
+
self.mma_tiler_dQv,
|
| 274 |
+
self.kv_dtype,
|
| 275 |
+
self.num_stages_KV,
|
| 276 |
+
)
|
| 277 |
+
sdQv_layout = utils.sm100.make_smem_layout_epi(
|
| 278 |
+
self.dq_dtype,
|
| 279 |
+
self.dq_layout,
|
| 280 |
+
self.epi_tile_dQv,
|
| 281 |
+
self.num_stages_acc,
|
| 282 |
+
)
|
| 283 |
+
sK_layout, sdQ_layout = None, None
|
| 284 |
+
if const_expr(self.compute_dQ):
|
| 285 |
+
sK_layout = utils.sm100.make_smem_layout_b(
|
| 286 |
+
tiled_mma_k,
|
| 287 |
+
self.mma_tiler_dQ,
|
| 288 |
+
self.kv_dtype,
|
| 289 |
+
self.num_stages_KV,
|
| 290 |
+
)
|
| 291 |
+
sdQ_layout = utils.sm100.make_smem_layout_epi(
|
| 292 |
+
self.dq_dtype,
|
| 293 |
+
self.dq_layout,
|
| 294 |
+
self.epi_tile_dQ,
|
| 295 |
+
self.num_stages_acc,
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
# ------------------------------------------------------------------ #
|
| 299 |
+
# Set up TMA load/stores #
|
| 300 |
+
# ------------------------------------------------------------------ #
|
| 301 |
+
atom_thr_size = cute.size(tiled_mma_v.thr_id.shape)
|
| 302 |
+
|
| 303 |
+
# ---- Setup TMA load for dS ----
|
| 304 |
+
dS_op = utils.sm100.cluster_shape_to_tma_atom_A(self.cluster_shape_mn, tiled_mma_v.thr_id)
|
| 305 |
+
dS_smem_layout = cute.slice_(sdS_layout, (None, None, None, 0))
|
| 306 |
+
tma_atom_dS, tma_tensor_dS = cute.nvgpu.make_tiled_tma_atom_A(
|
| 307 |
+
dS_op,
|
| 308 |
+
mdS,
|
| 309 |
+
dS_smem_layout,
|
| 310 |
+
self.mma_tiler_dQv,
|
| 311 |
+
tiled_mma_v,
|
| 312 |
+
self.cluster_layout_vmnk.shape,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
dS_copy_size = cute.size_in_bytes(self.ds_dtype, dS_smem_layout)
|
| 316 |
+
self.num_tma_load_bytes = dS_copy_size * atom_thr_size
|
| 317 |
+
|
| 318 |
+
# ---- Setup TMA store for dQ and dQV ----
|
| 319 |
+
dQv_epi_smem_layout = cute.select(sdQv_layout, mode=[0, 1])
|
| 320 |
+
tma_atom_dQv, tma_tensor_dQv = cpasync.make_tiled_tma_atom(
|
| 321 |
+
cpasync.CopyBulkTensorTileS2GOp(), mdQv, dQv_epi_smem_layout, self.epi_tile_dQv
|
| 322 |
+
)
|
| 323 |
+
tma_atom_dQ, tma_tensor_dQ = None, None
|
| 324 |
+
if const_expr(self.compute_dQ):
|
| 325 |
+
dQ_epi_smem_layout = cute.select(sdQ_layout, mode=[0, 1])
|
| 326 |
+
tma_atom_dQ, tma_tensor_dQ = cpasync.make_tiled_tma_atom(
|
| 327 |
+
cpasync.CopyBulkTensorTileS2GOp(), mdQ, dQ_epi_smem_layout, self.epi_tile_dQ
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
# ------------------------------------------------------------------ #
|
| 331 |
+
# Set up shared storage for SMEM #
|
| 332 |
+
# ------------------------------------------------------------------ #
|
| 333 |
+
|
| 334 |
+
self.buffer_align_bytes = 1024
|
| 335 |
+
|
| 336 |
+
sdS_size = cute.cosize(sdS_layout)
|
| 337 |
+
sK_size = cute.cosize(sK_layout) if const_expr(self.compute_dQ) else 0
|
| 338 |
+
sV_size = cute.cosize(sV_layout)
|
| 339 |
+
sdQ_size = cute.cosize(sdQ_layout) if const_expr(self.compute_dQ) else 0
|
| 340 |
+
sdQv_size = cute.cosize(sdQv_layout)
|
| 341 |
+
assert sdQ_size <= sK_size, f"require {sdQ_size=} <= {sK_size=}"
|
| 342 |
+
assert sdQv_size <= sV_size, f"require {sdQv_size=} <= {sV_size=}"
|
| 343 |
+
|
| 344 |
+
self.overlap_kv_epi = self.compute_dQ
|
| 345 |
+
if const_expr(self.overlap_kv_epi):
|
| 346 |
+
sdQ_size = 0
|
| 347 |
+
sdQv_size = 0
|
| 348 |
+
|
| 349 |
+
@cute.struct
|
| 350 |
+
class SharedStorage:
|
| 351 |
+
mbar_ptr_dS: cute.struct.MemRange[cutlass.Int64, self.num_stages_dS * 2]
|
| 352 |
+
mbar_ptr_KV: cute.struct.MemRange[cutlass.Int64, self.num_stages_KV * 2]
|
| 353 |
+
mbar_ptr_dQ_dQv: cute.struct.MemRange[cutlass.Int64, self.num_stages_acc * 2]
|
| 354 |
+
mbar_ptr_KV_cpasync: cute.struct.MemRange[cutlass.Int64, self.num_stages_KV * 2]
|
| 355 |
+
mbar_ptr_load_kv_epi: cute.struct.MemRange[cutlass.Int64, 2]
|
| 356 |
+
# Tmem holding buffer
|
| 357 |
+
tmem_dealloc_mbar: cutlass.Int64
|
| 358 |
+
tmem_holding_buf: cutlass.Int32
|
| 359 |
+
# Clc pointers
|
| 360 |
+
clc_ptr: cute.struct.Align[
|
| 361 |
+
cute.struct.MemRange[cutlass.Int64, self.num_stages_clc * 2], 16
|
| 362 |
+
]
|
| 363 |
+
clc_response_ptr: cute.struct.Align[cute.struct.MemRange[cutlass.Int32, 4], 16]
|
| 364 |
+
# Smem tensors
|
| 365 |
+
sdS: cute.struct.Align[
|
| 366 |
+
cute.struct.MemRange[self.ds_dtype, sdS_size],
|
| 367 |
+
self.buffer_align_bytes,
|
| 368 |
+
]
|
| 369 |
+
sK: cute.struct.Align[
|
| 370 |
+
cute.struct.MemRange[self.kv_dtype, sK_size],
|
| 371 |
+
self.buffer_align_bytes,
|
| 372 |
+
]
|
| 373 |
+
sV: cute.struct.Align[
|
| 374 |
+
cute.struct.MemRange[self.kv_dtype, sV_size],
|
| 375 |
+
self.buffer_align_bytes,
|
| 376 |
+
]
|
| 377 |
+
sdQ: cute.struct.Align[
|
| 378 |
+
cute.struct.MemRange[self.dq_dtype, sdQ_size],
|
| 379 |
+
self.buffer_align_bytes,
|
| 380 |
+
]
|
| 381 |
+
sdQv: cute.struct.Align[
|
| 382 |
+
cute.struct.MemRange[self.dq_dtype, sdQv_size],
|
| 383 |
+
self.buffer_align_bytes,
|
| 384 |
+
]
|
| 385 |
+
|
| 386 |
+
self.shared_storage = SharedStorage
|
| 387 |
+
|
| 388 |
+
# ---- Compute grid size ----
|
| 389 |
+
self.tile_sched_params, grid = self._compute_grid(
|
| 390 |
+
mdQv,
|
| 391 |
+
self.cta_tile_shape_dQv,
|
| 392 |
+
self.cluster_shape_mn,
|
| 393 |
+
)
|
| 394 |
+
self.num_clc_response_bytes = 16
|
| 395 |
+
# permute grid to conform to grid_dim_z <= 65536 constraint;
|
| 396 |
+
# this is undone in the kernel
|
| 397 |
+
grid = (grid[2], grid[1], grid[0])
|
| 398 |
+
|
| 399 |
+
# cute.printf("dQ/dQv grid: {}", grid)
|
| 400 |
+
# print("dQ/dQv SMEM: ", self.shared_storage.size_in_bytes())
|
| 401 |
+
# ---- Launch the kernel synchronously ----
|
| 402 |
+
self.kernel(
|
| 403 |
+
tiled_mma_k if const_expr(self.compute_dQ) else None,
|
| 404 |
+
tiled_mma_v,
|
| 405 |
+
tma_atom_dS,
|
| 406 |
+
tma_tensor_dS,
|
| 407 |
+
mK,
|
| 408 |
+
mV,
|
| 409 |
+
tma_atom_dQ,
|
| 410 |
+
tma_tensor_dQ,
|
| 411 |
+
tma_atom_dQv,
|
| 412 |
+
tma_tensor_dQv,
|
| 413 |
+
mIdxTopK,
|
| 414 |
+
mCuSeqlensQ,
|
| 415 |
+
mCuSeqlensK,
|
| 416 |
+
seqlen_q_divmod,
|
| 417 |
+
self.cluster_layout_vmnk,
|
| 418 |
+
sdS_layout,
|
| 419 |
+
sK_layout,
|
| 420 |
+
sV_layout,
|
| 421 |
+
sdQ_layout,
|
| 422 |
+
sdQv_layout,
|
| 423 |
+
self.epi_tile_dQ,
|
| 424 |
+
self.epi_tile_dQv,
|
| 425 |
+
self.tile_sched_params,
|
| 426 |
+
seqlen_k,
|
| 427 |
+
).launch(
|
| 428 |
+
grid=grid,
|
| 429 |
+
block=[self.threads_per_cta, 1, 1],
|
| 430 |
+
cluster=(*self.cluster_shape_mn, 1),
|
| 431 |
+
smem=self.shared_storage.size_in_bytes(),
|
| 432 |
+
stream=stream,
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
# GPU device kernel
|
| 436 |
+
@cute.kernel
|
| 437 |
+
def kernel(
|
| 438 |
+
self,
|
| 439 |
+
tiled_mma_k: Optional[cute.TiledMma],
|
| 440 |
+
tiled_mma_v: cute.TiledMma,
|
| 441 |
+
tma_atom_dS: cute.CopyAtom,
|
| 442 |
+
mdS: cute.Tensor,
|
| 443 |
+
mK: Optional[cute.Tensor],
|
| 444 |
+
mV: cute.Tensor,
|
| 445 |
+
tma_atom_dQ: Optional[cute.CopyAtom],
|
| 446 |
+
mdQ: Optional[cute.Tensor],
|
| 447 |
+
tma_atom_dQv: cute.CopyAtom,
|
| 448 |
+
mdQv: cute.Tensor,
|
| 449 |
+
mIdxTopK: cute.Tensor,
|
| 450 |
+
mCuSeqlensQ: cute.Tensor,
|
| 451 |
+
mCuSeqlensK: cute.Tensor,
|
| 452 |
+
seqlen_q_divmod: FastDivmodDivisor,
|
| 453 |
+
cluster_layout_vmnk: cute.Layout,
|
| 454 |
+
sdS_layout: cute.ComposedLayout,
|
| 455 |
+
sK_layout: Optional[cute.ComposedLayout],
|
| 456 |
+
sV_layout: cute.ComposedLayout,
|
| 457 |
+
sdQ_layout: Optional[cute.ComposedLayout],
|
| 458 |
+
sdQv_layout: cute.ComposedLayout,
|
| 459 |
+
epi_tile_dQ: Optional[cute.Tile],
|
| 460 |
+
epi_tile_dQv: cute.Tile,
|
| 461 |
+
tile_sched_params: utils.ClcDynamicPersistentTileSchedulerParams,
|
| 462 |
+
seqlen_k_static: Int32,
|
| 463 |
+
):
|
| 464 |
+
"""
|
| 465 |
+
GPU device kernel performing the Persistent batched GEMM computation.
|
| 466 |
+
"""
|
| 467 |
+
warp_idx = cute.arch.warp_idx()
|
| 468 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 469 |
+
|
| 470 |
+
# ------------------------------------------------------------------ #
|
| 471 |
+
# Prefetch TMA descriptors #
|
| 472 |
+
# ------------------------------------------------------------------ #
|
| 473 |
+
if warp_idx == self.tma_warp_id:
|
| 474 |
+
cpasync.prefetch_descriptor(tma_atom_dS)
|
| 475 |
+
cpasync.prefetch_descriptor(tma_atom_dQv)
|
| 476 |
+
if const_expr(self.compute_dQ):
|
| 477 |
+
cpasync.prefetch_descriptor(tma_atom_dQ)
|
| 478 |
+
|
| 479 |
+
# ------------------------------------------------------------------ #
|
| 480 |
+
# Cluster coordinates #
|
| 481 |
+
# ------------------------------------------------------------------ #
|
| 482 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 483 |
+
gridx, gridy, gridz = cute.arch.grid_dim()
|
| 484 |
+
mma_v_tile_coord_v = bidx % cute.size(tiled_mma_v.thr_id.shape)
|
| 485 |
+
if const_expr(self.compute_dQ):
|
| 486 |
+
mma_k_tile_coord_v = bidx % cute.size(tiled_mma_k.thr_id.shape)
|
| 487 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 488 |
+
block_in_cluster_coord_vmnk = cluster_layout_vmnk.get_flat_coord(cta_rank_in_cluster)
|
| 489 |
+
is_first_cta = cta_rank_in_cluster == 0
|
| 490 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 491 |
+
|
| 492 |
+
# ------------------------------------------------------------------ #
|
| 493 |
+
# Shared storage allocation #
|
| 494 |
+
# ------------------------------------------------------------------ #
|
| 495 |
+
smem = utils.SmemAllocator()
|
| 496 |
+
storage = smem.allocate(self.shared_storage)
|
| 497 |
+
|
| 498 |
+
# ------------------------------------------------------------------ #
|
| 499 |
+
# Initialize pipelines #
|
| 500 |
+
# ------------------------------------------------------------------ #
|
| 501 |
+
ThreadCooperativeGroup = partial(pipeline.CooperativeGroup, pipeline.Agent.Thread)
|
| 502 |
+
kv_commit_group = ThreadCooperativeGroup(1)
|
| 503 |
+
clc_producer_group = ThreadCooperativeGroup(1)
|
| 504 |
+
num_clc_consumer_threads = 32 * (
|
| 505 |
+
1 # sched warp on CTA0 only
|
| 506 |
+
+ cute.size(self.cluster_shape_mn)
|
| 507 |
+
* (1 + len(self.epilogue_warp_ids) + len(self.kv_load_warp_ids) + 1)
|
| 508 |
+
# tma + epi + kv_load + mma, on BOTH CTAs
|
| 509 |
+
)
|
| 510 |
+
clc_consumer_group = ThreadCooperativeGroup(num_clc_consumer_threads)
|
| 511 |
+
mma_warp = ThreadCooperativeGroup(1)
|
| 512 |
+
tma_warp = ThreadCooperativeGroup(cute.size(self.cluster_shape_mn))
|
| 513 |
+
tma_warp_local = ThreadCooperativeGroup(1)
|
| 514 |
+
epilogue_warps = ThreadCooperativeGroup(len(self.epilogue_warp_ids))
|
| 515 |
+
load_warps = ThreadCooperativeGroup(len(self.kv_load_warp_ids))
|
| 516 |
+
|
| 517 |
+
pipeline_dS = pipeline.PipelineTmaUmma.create(
|
| 518 |
+
barrier_storage=storage.mbar_ptr_dS.data_ptr(),
|
| 519 |
+
num_stages=self.num_stages_dS,
|
| 520 |
+
producer_group=mma_warp,
|
| 521 |
+
consumer_group=tma_warp,
|
| 522 |
+
tx_count=self.num_tma_load_bytes,
|
| 523 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 524 |
+
defer_sync=True,
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
pipeline_KV = pipeline.PipelineAsyncUmma.create(
|
| 528 |
+
barrier_storage=storage.mbar_ptr_KV.data_ptr(),
|
| 529 |
+
num_stages=self.num_stages_KV,
|
| 530 |
+
producer_group=kv_commit_group,
|
| 531 |
+
consumer_group=mma_warp,
|
| 532 |
+
defer_sync=True,
|
| 533 |
+
)
|
| 534 |
+
|
| 535 |
+
pipeline_dQ_dQv = pipeline.PipelineUmmaAsync.create(
|
| 536 |
+
barrier_storage=storage.mbar_ptr_dQ_dQv.data_ptr(),
|
| 537 |
+
num_stages=self.num_stages_acc,
|
| 538 |
+
producer_group=mma_warp,
|
| 539 |
+
consumer_group=epilogue_warps,
|
| 540 |
+
defer_sync=True,
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
pipeline_clc = pipeline.PipelineClcFetchAsync.create(
|
| 544 |
+
barrier_storage=storage.clc_ptr.data_ptr(),
|
| 545 |
+
num_stages=self.num_stages_clc,
|
| 546 |
+
producer_group=clc_producer_group,
|
| 547 |
+
consumer_group=clc_consumer_group,
|
| 548 |
+
tx_count=self.num_clc_response_bytes,
|
| 549 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 550 |
+
defer_sync=True,
|
| 551 |
+
)
|
| 552 |
+
|
| 553 |
+
pipeline_load_kv_epi = None
|
| 554 |
+
if const_expr(self.overlap_kv_epi):
|
| 555 |
+
pipeline_load_kv_epi = pipeline.PipelineAsync.create(
|
| 556 |
+
barrier_storage=storage.mbar_ptr_load_kv_epi.data_ptr(),
|
| 557 |
+
num_stages=1,
|
| 558 |
+
producer_group=epilogue_warps,
|
| 559 |
+
consumer_group=load_warps,
|
| 560 |
+
defer_sync=True,
|
| 561 |
+
)
|
| 562 |
+
|
| 563 |
+
# ------------------------------------------------------------------ #
|
| 564 |
+
# TMEM Allocation #
|
| 565 |
+
# ------------------------------------------------------------------ #
|
| 566 |
+
tmem_alloc_barrier = pipeline.NamedBarrier(
|
| 567 |
+
barrier_id=self.tmem_alloc_sync_bar_id,
|
| 568 |
+
num_threads=32 * len((self.mma_warp_id, *self.epilogue_warp_ids)),
|
| 569 |
+
)
|
| 570 |
+
# ---- Tensor memory dealloc barrier init ----
|
| 571 |
+
tmem = utils.TmemAllocator(
|
| 572 |
+
storage.tmem_holding_buf.ptr,
|
| 573 |
+
barrier_for_retrieve=tmem_alloc_barrier,
|
| 574 |
+
allocator_warp_id=self.epilogue_warp_ids[0],
|
| 575 |
+
is_two_cta=False,
|
| 576 |
+
two_cta_tmem_dealloc_mbar_ptr=storage.tmem_dealloc_mbar.ptr,
|
| 577 |
+
)
|
| 578 |
+
|
| 579 |
+
# ---- Cluster arrive after barrier init ----
|
| 580 |
+
pipeline_init_arrive(cluster_shape_mn=cluster_layout_vmnk, is_relaxed=True)
|
| 581 |
+
|
| 582 |
+
# ---- Initial clc response pointer ----
|
| 583 |
+
clc_response_ptr = storage.clc_response_ptr.data_ptr()
|
| 584 |
+
|
| 585 |
+
clc_consumer_state = pipeline.make_pipeline_state(
|
| 586 |
+
pipeline.PipelineUserType.Consumer, self.num_stages_clc
|
| 587 |
+
)
|
| 588 |
+
|
| 589 |
+
# ------------------------------------------------------------------ #
|
| 590 |
+
# SMEM tensors #
|
| 591 |
+
# ------------------------------------------------------------------ #
|
| 592 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 593 |
+
sdS = storage.sdS.get_tensor(sdS_layout.outer, swizzle=sdS_layout.inner)
|
| 594 |
+
if const_expr(self.compute_dQ):
|
| 595 |
+
sK = storage.sK.get_tensor(sK_layout.outer, swizzle=sK_layout.inner)
|
| 596 |
+
sV = storage.sV.get_tensor(sV_layout.outer, swizzle=sV_layout.inner)
|
| 597 |
+
# sdQ = storage.sdQ.get_tensor(sdQ_layout.outer, swizzle=sdQ_layout.inner)
|
| 598 |
+
if const_expr(self.compute_dQ):
|
| 599 |
+
sdQ = cute.make_tensor(
|
| 600 |
+
cute.recast_ptr(sK.iterator, sdQ_layout.inner, self.dq_dtype), sdQ_layout.outer
|
| 601 |
+
)
|
| 602 |
+
if const_expr(not self.compute_dQ):
|
| 603 |
+
sdQv = storage.sdQv.get_tensor(sdQv_layout.outer, swizzle=sdQv_layout.inner)
|
| 604 |
+
else:
|
| 605 |
+
sdQv = cute.make_tensor(
|
| 606 |
+
cute.recast_ptr(sV.iterator, sdQv_layout.inner, self.dq_dtype), sdQv_layout.outer
|
| 607 |
+
)
|
| 608 |
+
|
| 609 |
+
dS_full_mcast_mask = cpasync.create_tma_multicast_mask(
|
| 610 |
+
cluster_layout_vmnk, block_in_cluster_coord_vmnk, mcast_mode=2
|
| 611 |
+
)
|
| 612 |
+
|
| 613 |
+
# ------------------------------------------------------------------ #
|
| 614 |
+
# Global tile partitioning #
|
| 615 |
+
# ------------------------------------------------------------------ #
|
| 616 |
+
# (bM, bK, RestM, RestK, RestL)
|
| 617 |
+
gdS = cute.local_tile(
|
| 618 |
+
mdS, cute.slice_(self.mma_tiler_dQv, (None, 0, None)), (None, None, None)
|
| 619 |
+
)
|
| 620 |
+
# (bM, bN, RestM, RestN, RestL)
|
| 621 |
+
if const_expr(self.compute_dQ):
|
| 622 |
+
gdQ = cute.local_tile(
|
| 623 |
+
mdQ, cute.slice_(self.mma_tiler_dQ, (None, None, 0)), (None, None, None)
|
| 624 |
+
)
|
| 625 |
+
gdQv = cute.local_tile(
|
| 626 |
+
mdQv, cute.slice_(self.mma_tiler_dQv, (None, None, 0)), (None, None, None)
|
| 627 |
+
)
|
| 628 |
+
k_tile_cnt = cute.size(gdS, mode=[3])
|
| 629 |
+
|
| 630 |
+
# ------------------------------------------------------------------ #
|
| 631 |
+
# TiledMMA partitioning #
|
| 632 |
+
# ------------------------------------------------------------------ #
|
| 633 |
+
thr_mma_v = tiled_mma_v.get_slice(mma_v_tile_coord_v)
|
| 634 |
+
# (MMA, MMA_M, MMA_K, RestM, RestK, RestL)
|
| 635 |
+
tdQvgdS = thr_mma_v.partition_A(gdS)
|
| 636 |
+
# (MMA, MMA_M, MMA_N, RestM, RestN, RestL)
|
| 637 |
+
tdQvgdQv = thr_mma_v.partition_C(gdQv)
|
| 638 |
+
if const_expr(self.compute_dQ):
|
| 639 |
+
thr_mma_k = tiled_mma_k.get_slice(mma_k_tile_coord_v)
|
| 640 |
+
tdQgdQ = thr_mma_k.partition_C(gdQ)
|
| 641 |
+
|
| 642 |
+
# ------------------------------------------------------------------ #
|
| 643 |
+
# TMA partition for dS #
|
| 644 |
+
# ------------------------------------------------------------------ #
|
| 645 |
+
dS_cta_layout = cute.make_layout(cute.slice_(cluster_layout_vmnk, (0, 0, None, 0)).shape)
|
| 646 |
+
# ((atom_v, rest_v), STAGE)
|
| 647 |
+
# ((atom_v, rest_v), RestM, RestK, RestL)
|
| 648 |
+
tdSsdS, tdSgdS = cpasync.tma_partition(
|
| 649 |
+
tma_atom_dS,
|
| 650 |
+
block_in_cluster_coord_vmnk[2],
|
| 651 |
+
dS_cta_layout,
|
| 652 |
+
cute.group_modes(sdS, 0, 3),
|
| 653 |
+
cute.group_modes(tdQvgdS, 0, 3),
|
| 654 |
+
)
|
| 655 |
+
|
| 656 |
+
# ------------------------------------------------------------------ #
|
| 657 |
+
# MMA fragments #
|
| 658 |
+
# ------------------------------------------------------------------ #
|
| 659 |
+
# (MMA, MMA_M, MMA_K, STAGE)
|
| 660 |
+
tdQvrdS = tiled_mma_v.make_fragment_A(sdS)
|
| 661 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 662 |
+
tdQvrV = tiled_mma_v.make_fragment_B(sV)
|
| 663 |
+
# (MMA, MMA_M, MMA_N)
|
| 664 |
+
acc_v_shape = tiled_mma_v.partition_shape_C(self.mma_tiler_dQv[:2])
|
| 665 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 666 |
+
tdQvtAcc_fake = tiled_mma_v.make_fragment_C(cute.append(acc_v_shape, self.num_stages_acc))
|
| 667 |
+
if const_expr(self.compute_dQ):
|
| 668 |
+
# (MMA, MMA_N, MMA_K, STAGE)
|
| 669 |
+
tdQrK = tiled_mma_k.make_fragment_B(sK)
|
| 670 |
+
# (MMA, MMA_M, MMA_N)
|
| 671 |
+
acc_k_shape = tiled_mma_k.partition_shape_C(self.mma_tiler_dQ[:2])
|
| 672 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 673 |
+
tdQtAcc_fake = tiled_mma_k.make_fragment_C(
|
| 674 |
+
cute.append(acc_k_shape, self.num_stages_acc)
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
# ------------------------------------------------------------------ #
|
| 678 |
+
# Cluster wait before tensor memory alloc #
|
| 679 |
+
# ------------------------------------------------------------------ #
|
| 680 |
+
pipeline_init_wait(cluster_shape_mn=cluster_layout_vmnk)
|
| 681 |
+
|
| 682 |
+
# ------------------------------------------------------------------ #
|
| 683 |
+
# Tile Scheduler #
|
| 684 |
+
# ------------------------------------------------------------------ #
|
| 685 |
+
tile_sched = utils.ClcDynamicPersistentTileScheduler.create(
|
| 686 |
+
tile_sched_params,
|
| 687 |
+
cute.arch.block_idx(),
|
| 688 |
+
cute.arch.grid_dim(),
|
| 689 |
+
clc_response_ptr,
|
| 690 |
+
)
|
| 691 |
+
work_tile = tile_sched.initial_work_tile_info()
|
| 692 |
+
|
| 693 |
+
# ------------------------------------------------------------------ #
|
| 694 |
+
# TMA load warp #
|
| 695 |
+
# ------------------------------------------------------------------ #
|
| 696 |
+
if warp_idx == self.tma_warp_id:
|
| 697 |
+
cute.arch.setmaxregister_decrease(self.num_regs_other)
|
| 698 |
+
|
| 699 |
+
producer_state_dS = pipeline.make_pipeline_state(
|
| 700 |
+
pipeline.PipelineUserType.Producer, stages=self.num_stages_dS
|
| 701 |
+
)
|
| 702 |
+
|
| 703 |
+
while work_tile.is_valid_tile:
|
| 704 |
+
# ---- Get tile coord from tile scheduler ----
|
| 705 |
+
token, cta, _ = work_tile.tile_idx
|
| 706 |
+
|
| 707 |
+
# ((atom_v, rest_v), RestK)
|
| 708 |
+
tdSgdS_slice = tdSgdS[(None, 0, None, token)]
|
| 709 |
+
|
| 710 |
+
# ---- mainloop ----
|
| 711 |
+
for k_tile in cutlass.range(0, k_tile_cnt, 1, unroll=1):
|
| 712 |
+
pipeline_dS.producer_acquire(producer_state_dS)
|
| 713 |
+
index_dS = producer_state_dS.index
|
| 714 |
+
|
| 715 |
+
# ---- TMA load dS ----
|
| 716 |
+
cute.copy(
|
| 717 |
+
tma_atom_dS,
|
| 718 |
+
tdSgdS_slice[(None, k_tile)],
|
| 719 |
+
tdSsdS[(None, index_dS)],
|
| 720 |
+
tma_bar_ptr=pipeline_dS.producer_get_barrier(producer_state_dS),
|
| 721 |
+
mcast_mask=dS_full_mcast_mask,
|
| 722 |
+
)
|
| 723 |
+
|
| 724 |
+
producer_state_dS.advance()
|
| 725 |
+
|
| 726 |
+
# ---- Advance to next tile ----
|
| 727 |
+
pipeline_clc.consumer_wait(clc_consumer_state)
|
| 728 |
+
work_tile = tile_sched.get_current_work()
|
| 729 |
+
pipeline_clc.consumer_release(clc_consumer_state)
|
| 730 |
+
clc_consumer_state.advance()
|
| 731 |
+
|
| 732 |
+
# ---- Wait dS buffer empty ----
|
| 733 |
+
pipeline_dS.producer_tail(producer_state_dS)
|
| 734 |
+
|
| 735 |
+
# ------------------------------------------------------------------ #
|
| 736 |
+
# Clc Scheduler warp #
|
| 737 |
+
# ------------------------------------------------------------------ #
|
| 738 |
+
if warp_idx == self.sched_warp_id and is_first_cta:
|
| 739 |
+
cute.arch.setmaxregister_decrease(self.num_regs_other)
|
| 740 |
+
clc_producer_state = pipeline.make_pipeline_state(
|
| 741 |
+
pipeline.PipelineUserType.ProducerConsumer, self.num_stages_clc
|
| 742 |
+
)
|
| 743 |
+
|
| 744 |
+
while work_tile.is_valid_tile:
|
| 745 |
+
pipeline_clc.producer_acquire(clc_producer_state)
|
| 746 |
+
mbar_addr = pipeline_clc.producer_get_barrier(clc_producer_state)
|
| 747 |
+
tile_sched.advance_to_next_work(mbar_addr)
|
| 748 |
+
clc_producer_state.advance()
|
| 749 |
+
|
| 750 |
+
pipeline_clc.consumer_wait(clc_consumer_state)
|
| 751 |
+
work_tile = tile_sched.get_current_work()
|
| 752 |
+
pipeline_clc.consumer_release(clc_consumer_state)
|
| 753 |
+
clc_consumer_state.advance()
|
| 754 |
+
|
| 755 |
+
pipeline_clc.producer_tail(clc_producer_state)
|
| 756 |
+
|
| 757 |
+
# ------------------------------------------------------------------ #
|
| 758 |
+
# CpAsync KV load warps #
|
| 759 |
+
# ------------------------------------------------------------------ #
|
| 760 |
+
if warp_idx >= self.kv_load_warp_ids[0] and warp_idx <= self.kv_load_warp_ids[-1]:
|
| 761 |
+
cute.arch.setmaxregister_increase(self.num_regs_KV)
|
| 762 |
+
find_batch = partial(
|
| 763 |
+
self.find_batch_from_q, seqlen_q_divmod=seqlen_q_divmod, mCuSeqlensQ=mCuSeqlensQ
|
| 764 |
+
)
|
| 765 |
+
|
| 766 |
+
load_epi_consumer_state = pipeline.make_pipeline_state(
|
| 767 |
+
pipeline.PipelineUserType.Consumer, 1
|
| 768 |
+
)
|
| 769 |
+
producer_state_KV = pipeline.make_pipeline_state(
|
| 770 |
+
pipeline.PipelineUserType.Producer, stages=self.num_stages_KV
|
| 771 |
+
)
|
| 772 |
+
|
| 773 |
+
kv_tidx = tidx % (len(self.kv_load_warp_ids) * self.threads_per_warp)
|
| 774 |
+
kv_warp_idx = warp_idx % len(self.kv_load_warp_ids)
|
| 775 |
+
|
| 776 |
+
mV_cta = cute.domain_offset((cta_rank_in_cluster * (self.head_dim_v // 2), 0), mV)
|
| 777 |
+
|
| 778 |
+
while work_tile.is_valid_tile:
|
| 779 |
+
# ---- Get tile coord from tile scheduler ----
|
| 780 |
+
token, cta, _ = work_tile.tile_idx
|
| 781 |
+
|
| 782 |
+
batch_idx = find_batch(token)
|
| 783 |
+
k_batch_offset = (
|
| 784 |
+
mCuSeqlensK[batch_idx] if const_expr(mCuSeqlensK is not None) else Int32(0)
|
| 785 |
+
)
|
| 786 |
+
seqlen_k = (
|
| 787 |
+
mCuSeqlensK[batch_idx + 1] - k_batch_offset
|
| 788 |
+
if const_expr(mCuSeqlensK is not None)
|
| 789 |
+
else seqlen_k_static
|
| 790 |
+
)
|
| 791 |
+
if const_expr(mCuSeqlensK is not None):
|
| 792 |
+
if const_expr(self.compute_dQ):
|
| 793 |
+
mK_cur = cute.domain_offset((0, k_batch_offset), mK)[None, None]
|
| 794 |
+
mV_cur = cute.domain_offset((0, k_batch_offset), mV_cta)[None, None]
|
| 795 |
+
else:
|
| 796 |
+
if const_expr(self.compute_dQ):
|
| 797 |
+
mK_cur = cute.domain_offset((0, (0, batch_idx)), mK)[None, None]
|
| 798 |
+
mV_cur = cute.domain_offset((0, (0, batch_idx)), mV_cta)[None, None]
|
| 799 |
+
mIdxTopK_cur = mIdxTopK[None, token]
|
| 800 |
+
|
| 801 |
+
cpasync_gather_kv_manager = CpasyncGatherKVManager.create(
|
| 802 |
+
mIdxTopK_cur,
|
| 803 |
+
0,
|
| 804 |
+
kv_tidx,
|
| 805 |
+
kv_warp_idx,
|
| 806 |
+
self.top_k,
|
| 807 |
+
seqlen_k,
|
| 808 |
+
self.mma_tiler_dQv[2],
|
| 809 |
+
self.head_dim_k,
|
| 810 |
+
self.mma_tiler_dQv[1],
|
| 811 |
+
1,
|
| 812 |
+
len(self.kv_load_warp_ids) * self.threads_per_warp,
|
| 813 |
+
mV.element_type,
|
| 814 |
+
1,
|
| 815 |
+
)
|
| 816 |
+
|
| 817 |
+
# ---- K/V load mainloop ----
|
| 818 |
+
for k_tile in cutlass.range_constexpr(k_tile_cnt):
|
| 819 |
+
# ---- Load top-k index tensor ----
|
| 820 |
+
cpasync_gather_kv_manager.load_index_topk(k_tile, transpose=True)
|
| 821 |
+
|
| 822 |
+
stage = producer_state_KV.index
|
| 823 |
+
pipeline_KV.producer_acquire(producer_state_KV)
|
| 824 |
+
|
| 825 |
+
# ---- Load V (and optionally load K) ----
|
| 826 |
+
cpasync_gather_kv_manager.load_X(mV_cur, sV[None, None, None, stage], True, "V")
|
| 827 |
+
if const_expr(self.compute_dQ):
|
| 828 |
+
if is_first_cta:
|
| 829 |
+
cpasync_gather_kv_manager.load_X(
|
| 830 |
+
mK_cur, sK[None, None, None, stage], True, "K"
|
| 831 |
+
)
|
| 832 |
+
|
| 833 |
+
cute.arch.cp_async_commit_group()
|
| 834 |
+
cute.arch.cp_async_wait_group(0)
|
| 835 |
+
self.kv_load_sync_barrier.arrive_and_wait()
|
| 836 |
+
|
| 837 |
+
cute.arch.fence_proxy("async.shared", space="cta")
|
| 838 |
+
if kv_warp_idx == 0:
|
| 839 |
+
with cute.arch.elect_one():
|
| 840 |
+
pipeline_KV.producer_commit(producer_state_KV)
|
| 841 |
+
producer_state_KV.advance()
|
| 842 |
+
|
| 843 |
+
if const_expr(self.overlap_kv_epi):
|
| 844 |
+
pipeline_load_kv_epi.consumer_wait(load_epi_consumer_state)
|
| 845 |
+
with cute.arch.elect_one():
|
| 846 |
+
pipeline_load_kv_epi.consumer_release(load_epi_consumer_state)
|
| 847 |
+
load_epi_consumer_state.advance()
|
| 848 |
+
|
| 849 |
+
# ---- Advance to next tile ----
|
| 850 |
+
pipeline_clc.consumer_wait(clc_consumer_state)
|
| 851 |
+
work_tile = tile_sched.get_current_work()
|
| 852 |
+
pipeline_clc.consumer_release(clc_consumer_state)
|
| 853 |
+
clc_consumer_state.advance()
|
| 854 |
+
|
| 855 |
+
pipeline_KV.producer_tail(producer_state_KV)
|
| 856 |
+
|
| 857 |
+
# ------------------------------------------------------------------ #
|
| 858 |
+
# MMA warp #
|
| 859 |
+
# ------------------------------------------------------------------ #
|
| 860 |
+
if warp_idx == self.mma_warp_id:
|
| 861 |
+
cute.arch.setmaxregister_decrease(self.num_regs_other)
|
| 862 |
+
# --- Retrieve TMEM ptr and make accumulator tensors
|
| 863 |
+
tmem.wait_for_alloc()
|
| 864 |
+
tmem_ptr = tmem.retrieve_ptr(self.acc_dtype)
|
| 865 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 866 |
+
if const_expr(self.compute_dQ):
|
| 867 |
+
tdQtAcc_base = cute.make_tensor(tmem_ptr, tdQtAcc_fake.layout)
|
| 868 |
+
tdQvtAcc_ptr = tmem_ptr + (
|
| 869 |
+
tcgen05.find_tmem_tensor_col_offset(tdQtAcc_base)
|
| 870 |
+
if const_expr(self.compute_dQ)
|
| 871 |
+
else 0
|
| 872 |
+
)
|
| 873 |
+
tdQvtAcc_base = cute.make_tensor(tdQvtAcc_ptr, tdQvtAcc_fake.layout)
|
| 874 |
+
|
| 875 |
+
consumer_state_dS = pipeline.make_pipeline_state(
|
| 876 |
+
pipeline.PipelineUserType.Consumer, stages=self.num_stages_dS
|
| 877 |
+
)
|
| 878 |
+
consumer_state_KV = pipeline.make_pipeline_state(
|
| 879 |
+
pipeline.PipelineUserType.Consumer, stages=self.num_stages_KV
|
| 880 |
+
)
|
| 881 |
+
producer_state_dQ_dQv = pipeline.make_pipeline_state(
|
| 882 |
+
pipeline.PipelineUserType.Producer, self.num_stages_acc
|
| 883 |
+
)
|
| 884 |
+
|
| 885 |
+
while work_tile.is_valid_tile:
|
| 886 |
+
# ---- Get tile coord from tile scheduler ----
|
| 887 |
+
token, cta, _ = work_tile.tile_idx
|
| 888 |
+
|
| 889 |
+
# ---- Set tensor memory buffer for current tile ----
|
| 890 |
+
# (MMA, MMA_M, MMA_N)
|
| 891 |
+
tdQvtAcc = tdQvtAcc_base[(None, None, None, producer_state_dQ_dQv.index)]
|
| 892 |
+
if const_expr(self.compute_dQ):
|
| 893 |
+
tdQtAcc = tdQtAcc_base[(None, None, None, producer_state_dQ_dQv.index)]
|
| 894 |
+
|
| 895 |
+
# ---- Wait for accumulator buffer empty ----
|
| 896 |
+
pipeline_dQ_dQv.producer_acquire(producer_state_dQ_dQv)
|
| 897 |
+
|
| 898 |
+
# ---- Reset the ACCUMULATE field for each tile ----
|
| 899 |
+
tiled_mma_v.set(tcgen05.Field.ACCUMULATE, False)
|
| 900 |
+
if const_expr(self.compute_dQ):
|
| 901 |
+
tiled_mma_k.set(tcgen05.Field.ACCUMULATE, False)
|
| 902 |
+
|
| 903 |
+
# ---- Mma mainloop ----
|
| 904 |
+
for k_tile in cutlass.range_constexpr(k_tile_cnt):
|
| 905 |
+
pipeline_dS.consumer_wait(consumer_state_dS)
|
| 906 |
+
pipeline_KV.consumer_wait(consumer_state_KV)
|
| 907 |
+
dS_stage = consumer_state_dS.index
|
| 908 |
+
KV_stage = consumer_state_KV.index
|
| 909 |
+
|
| 910 |
+
num_kblocks = cute.size(tdQvrdS, mode=[2])
|
| 911 |
+
for kblk_idx in cutlass.range(num_kblocks, unroll_full=True):
|
| 912 |
+
# dQv += dS @ V
|
| 913 |
+
cute.gemm(
|
| 914 |
+
tiled_mma_v,
|
| 915 |
+
tdQvtAcc,
|
| 916 |
+
tdQvrdS[(None, None, kblk_idx, dS_stage)],
|
| 917 |
+
tdQvrV[(None, None, kblk_idx, KV_stage)],
|
| 918 |
+
tdQvtAcc,
|
| 919 |
+
)
|
| 920 |
+
# Enable accumulate on tdQvtAcc after first kblock
|
| 921 |
+
tiled_mma_v.set(tcgen05.Field.ACCUMULATE, True)
|
| 922 |
+
|
| 923 |
+
if const_expr(self.compute_dQ):
|
| 924 |
+
if is_first_cta:
|
| 925 |
+
# dQ += dS @ K
|
| 926 |
+
cute.gemm(
|
| 927 |
+
tiled_mma_k,
|
| 928 |
+
tdQtAcc,
|
| 929 |
+
tdQvrdS[(None, None, kblk_idx, dS_stage)],
|
| 930 |
+
tdQrK[(None, None, kblk_idx, KV_stage)],
|
| 931 |
+
tdQtAcc,
|
| 932 |
+
)
|
| 933 |
+
# Enable accumulate on tdQtAcc after first kblock
|
| 934 |
+
tiled_mma_k.set(tcgen05.Field.ACCUMULATE, True)
|
| 935 |
+
|
| 936 |
+
pipeline_dS.consumer_release(consumer_state_dS)
|
| 937 |
+
pipeline_KV.consumer_release(consumer_state_KV)
|
| 938 |
+
consumer_state_dS.advance()
|
| 939 |
+
consumer_state_KV.advance()
|
| 940 |
+
|
| 941 |
+
pipeline_dQ_dQv.producer_commit(producer_state_dQ_dQv)
|
| 942 |
+
producer_state_dQ_dQv.advance()
|
| 943 |
+
|
| 944 |
+
# ---- Advance to next tile ----
|
| 945 |
+
pipeline_clc.consumer_wait(clc_consumer_state)
|
| 946 |
+
work_tile = tile_sched.get_current_work()
|
| 947 |
+
pipeline_clc.consumer_release(clc_consumer_state)
|
| 948 |
+
clc_consumer_state.advance()
|
| 949 |
+
|
| 950 |
+
pipeline_dQ_dQv.producer_tail(producer_state_dQ_dQv)
|
| 951 |
+
|
| 952 |
+
# ------------------------------------------------------------------ #
|
| 953 |
+
# Epilogue warps #
|
| 954 |
+
# ------------------------------------------------------------------ #
|
| 955 |
+
if warp_idx >= self.epilogue_warp_ids[0] and warp_idx <= self.epilogue_warp_ids[-1]:
|
| 956 |
+
cute.arch.setmaxregister_increase(self.num_regs_epi)
|
| 957 |
+
# ---- Alloc tensor memory buffer ----
|
| 958 |
+
tmem.allocate(self.num_tmem_alloc_cols)
|
| 959 |
+
|
| 960 |
+
# ---- Retrieving tensor memory ptr and make accumulator tensor ----
|
| 961 |
+
tmem.wait_for_alloc()
|
| 962 |
+
tmem_ptr = tmem.retrieve_ptr(self.acc_dtype)
|
| 963 |
+
# (MMA, MMA_M, MMA_N, STAGE)
|
| 964 |
+
if const_expr(self.compute_dQ):
|
| 965 |
+
tdQtAcc_base = cute.make_tensor(tmem_ptr, tdQtAcc_fake.layout)
|
| 966 |
+
tdQvtAcc_ptr = tmem_ptr + (
|
| 967 |
+
tcgen05.find_tmem_tensor_col_offset(tdQtAcc_base)
|
| 968 |
+
if const_expr(self.compute_dQ)
|
| 969 |
+
else 0
|
| 970 |
+
)
|
| 971 |
+
tdQvtAcc_base = cute.make_tensor(tdQvtAcc_ptr, tdQvtAcc_fake.layout)
|
| 972 |
+
|
| 973 |
+
epi_idx = tidx
|
| 974 |
+
# print(f"tdQvtAcc_base.layout = {tdQvtAcc_base.layout}")
|
| 975 |
+
# ---- TMEM -> RMEM -> SMEM -> GMEM copies + partitions ----
|
| 976 |
+
tiled_copy_dQv_t2r, tTR_dQvtAcc_base, tTR_dQvrAcc = (
|
| 977 |
+
self.epilogue_tmem_copy_and_partition(
|
| 978 |
+
epi_idx,
|
| 979 |
+
tdQvtAcc_base,
|
| 980 |
+
tdQvgdQv,
|
| 981 |
+
epi_tile_dQv,
|
| 982 |
+
self.mma_tiler_dQv,
|
| 983 |
+
self.dq_layout,
|
| 984 |
+
self.dq_dtype,
|
| 985 |
+
)
|
| 986 |
+
)
|
| 987 |
+
# print(f"tTR_tdQvtAcc_base.layout = {tTR_dQvtAcc_base.layout}")
|
| 988 |
+
tTR_rdQv = cute.make_rmem_tensor(tTR_dQvrAcc.shape, self.dq_dtype)
|
| 989 |
+
(
|
| 990 |
+
tiled_copy_dQv_r2s,
|
| 991 |
+
tRS_rdQv,
|
| 992 |
+
tRS_sdQv,
|
| 993 |
+
) = self.epilogue_smem_copy_and_partition(
|
| 994 |
+
self.dq_layout,
|
| 995 |
+
self.dq_dtype,
|
| 996 |
+
tiled_copy_dQv_t2r,
|
| 997 |
+
tTR_rdQv,
|
| 998 |
+
epi_idx,
|
| 999 |
+
sdQv,
|
| 1000 |
+
)
|
| 1001 |
+
bSG_sdQv, bSG_gdQv_partitioned = self.epilogue_gmem_copy_and_partition(
|
| 1002 |
+
tma_atom_dQv,
|
| 1003 |
+
tdQvgdQv,
|
| 1004 |
+
epi_tile_dQv,
|
| 1005 |
+
sdQv,
|
| 1006 |
+
)
|
| 1007 |
+
if const_expr(self.compute_dQ):
|
| 1008 |
+
(tiled_copy_dQ_t2r, tTR_dQtAcc_base, tTR_dQrAcc) = (
|
| 1009 |
+
self.epilogue_tmem_copy_and_partition(
|
| 1010 |
+
epi_idx,
|
| 1011 |
+
tdQtAcc_base,
|
| 1012 |
+
tdQgdQ,
|
| 1013 |
+
epi_tile_dQ,
|
| 1014 |
+
self.mma_tiler_dQ,
|
| 1015 |
+
self.dq_layout,
|
| 1016 |
+
self.dq_dtype,
|
| 1017 |
+
)
|
| 1018 |
+
)
|
| 1019 |
+
|
| 1020 |
+
tTR_rdQ = cute.make_rmem_tensor(tTR_dQrAcc.shape, self.dq_dtype)
|
| 1021 |
+
(
|
| 1022 |
+
tiled_copy_dQ_r2s,
|
| 1023 |
+
tRS_rdQ,
|
| 1024 |
+
tRS_sdQ,
|
| 1025 |
+
) = self.epilogue_smem_copy_and_partition(
|
| 1026 |
+
self.dq_layout,
|
| 1027 |
+
self.dq_dtype,
|
| 1028 |
+
tiled_copy_dQ_t2r,
|
| 1029 |
+
tTR_rdQ,
|
| 1030 |
+
epi_idx,
|
| 1031 |
+
sdQ,
|
| 1032 |
+
)
|
| 1033 |
+
bSG_sdQ, bSG_gdQ_partitioned = self.epilogue_gmem_copy_and_partition(
|
| 1034 |
+
tma_atom_dQ,
|
| 1035 |
+
tdQgdQ,
|
| 1036 |
+
epi_tile_dQ,
|
| 1037 |
+
sdQ,
|
| 1038 |
+
)
|
| 1039 |
+
|
| 1040 |
+
acc_consumer_state = pipeline.make_pipeline_state(
|
| 1041 |
+
pipeline.PipelineUserType.Consumer, self.num_stages_acc
|
| 1042 |
+
)
|
| 1043 |
+
load_epi_producer_state = pipeline.make_pipeline_state(
|
| 1044 |
+
pipeline.PipelineUserType.Producer, 1
|
| 1045 |
+
)
|
| 1046 |
+
|
| 1047 |
+
epi_producer_group = pipeline.CooperativeGroup(
|
| 1048 |
+
pipeline.Agent.Thread,
|
| 1049 |
+
cute.arch.WARP_SIZE,
|
| 1050 |
+
)
|
| 1051 |
+
pipeline_epi = pipeline.PipelineTmaStore.create(
|
| 1052 |
+
num_stages=self.num_stages_acc,
|
| 1053 |
+
producer_group=epi_producer_group,
|
| 1054 |
+
)
|
| 1055 |
+
|
| 1056 |
+
# ---- Persistent tile scheduling loop for epilogue ----
|
| 1057 |
+
while work_tile.is_valid_tile:
|
| 1058 |
+
# ---- Get current work tile ----
|
| 1059 |
+
token, cta, bid_x = work_tile.tile_idx
|
| 1060 |
+
|
| 1061 |
+
bSG_gdQv = bSG_gdQv_partitioned[
|
| 1062 |
+
(None, None, None, bid_x, cta_rank_in_cluster, token)
|
| 1063 |
+
]
|
| 1064 |
+
tTR_dQvtAcc = tTR_dQvtAcc_base[
|
| 1065 |
+
(None, None, None, None, None, acc_consumer_state.index)
|
| 1066 |
+
]
|
| 1067 |
+
tTR_dQvtAcc = cute.group_modes(tTR_dQvtAcc, 3, cute.rank(tTR_dQvtAcc))
|
| 1068 |
+
bSG_gdQv = cute.group_modes(bSG_gdQv, 1, cute.rank(bSG_gdQv))
|
| 1069 |
+
|
| 1070 |
+
subtile_cnt_v = cute.size(tTR_dQvtAcc.shape, mode=[3])
|
| 1071 |
+
epi_subtile_counter_v = 0
|
| 1072 |
+
|
| 1073 |
+
if const_expr(self.compute_dQ):
|
| 1074 |
+
bSG_gdQ = bSG_gdQ_partitioned[(None, None, None, bid_x, cta, token)]
|
| 1075 |
+
tTR_dQtAcc = tTR_dQtAcc_base[
|
| 1076 |
+
(None, None, None, None, None, acc_consumer_state.index)
|
| 1077 |
+
]
|
| 1078 |
+
tTR_dQtAcc = cute.group_modes(tTR_dQtAcc, 3, cute.rank(tTR_dQtAcc))
|
| 1079 |
+
bSG_gdQ = cute.group_modes(bSG_gdQ, 1, cute.rank(bSG_gdQ))
|
| 1080 |
+
|
| 1081 |
+
subtile_cnt_k = cute.size(tTR_dQtAcc.shape, mode=[3])
|
| 1082 |
+
epi_subtile_counter_k = 0
|
| 1083 |
+
|
| 1084 |
+
pipeline_dQ_dQv.consumer_wait(acc_consumer_state)
|
| 1085 |
+
|
| 1086 |
+
for subtile_idx in cutlass.range(subtile_cnt_v, unroll_full=True):
|
| 1087 |
+
store_dQ = (
|
| 1088 |
+
const_expr(self.compute_dQ)
|
| 1089 |
+
and is_first_cta
|
| 1090 |
+
and (subtile_idx < subtile_cnt_k)
|
| 1091 |
+
)
|
| 1092 |
+
|
| 1093 |
+
if not store_dQ:
|
| 1094 |
+
tTR_dQvtAcc_mn = tTR_dQvtAcc[(None, None, None, subtile_idx)]
|
| 1095 |
+
cute.copy(tiled_copy_dQv_t2r, tTR_dQvtAcc_mn, tTR_dQvrAcc)
|
| 1096 |
+
cute.arch.fence_view_async_tmem_load()
|
| 1097 |
+
|
| 1098 |
+
# convert to output dtype
|
| 1099 |
+
tRS_rdQv.store(
|
| 1100 |
+
tiled_copy_dQv_r2s.retile(tTR_dQvrAcc).load().to(self.dq_dtype)
|
| 1101 |
+
)
|
| 1102 |
+
|
| 1103 |
+
epi_buffer = epi_subtile_counter_v % self.num_stages_acc
|
| 1104 |
+
cute.copy(
|
| 1105 |
+
tiled_copy_dQv_r2s, tRS_rdQv, tRS_sdQv[(None, None, None, epi_buffer)]
|
| 1106 |
+
)
|
| 1107 |
+
cute.arch.fence_proxy("async.shared", space="cta")
|
| 1108 |
+
self.epilog_sync_barrier.arrive_and_wait()
|
| 1109 |
+
|
| 1110 |
+
if warp_idx == self.epilogue_warp_ids[0]:
|
| 1111 |
+
cute.copy(
|
| 1112 |
+
tma_atom_dQv,
|
| 1113 |
+
bSG_sdQv[(None, epi_buffer)],
|
| 1114 |
+
bSG_gdQv[(None, subtile_idx)],
|
| 1115 |
+
)
|
| 1116 |
+
pipeline_epi.producer_commit()
|
| 1117 |
+
pipeline_epi.producer_acquire()
|
| 1118 |
+
|
| 1119 |
+
self.epilog_sync_barrier.arrive_and_wait()
|
| 1120 |
+
epi_subtile_counter_v += 1
|
| 1121 |
+
elif const_expr(self.compute_dQ):
|
| 1122 |
+
tTR_dQtAcc_mn = tTR_dQtAcc[(None, None, None, subtile_idx)]
|
| 1123 |
+
tTR_dQvtAcc_mn = tTR_dQvtAcc[(None, None, None, subtile_idx)]
|
| 1124 |
+
cute.copy(tiled_copy_dQ_t2r, tTR_dQtAcc_mn, tTR_dQrAcc)
|
| 1125 |
+
cute.copy(tiled_copy_dQv_t2r, tTR_dQvtAcc_mn, tTR_dQvrAcc)
|
| 1126 |
+
|
| 1127 |
+
# convert to output dtype
|
| 1128 |
+
tRS_rdQ.store(tiled_copy_dQ_r2s.retile(tTR_dQrAcc).load().to(self.dq_dtype))
|
| 1129 |
+
tRS_rdQv.store(
|
| 1130 |
+
tiled_copy_dQv_r2s.retile(tTR_dQvrAcc).load().to(self.dq_dtype)
|
| 1131 |
+
)
|
| 1132 |
+
|
| 1133 |
+
epi_buffer = epi_subtile_counter_v % self.num_stages_acc
|
| 1134 |
+
epi_buffer_k = epi_subtile_counter_k % self.num_stages_acc
|
| 1135 |
+
cute.copy(
|
| 1136 |
+
tiled_copy_dQv_r2s, tRS_rdQv, tRS_sdQv[(None, None, None, epi_buffer)]
|
| 1137 |
+
)
|
| 1138 |
+
cute.copy(
|
| 1139 |
+
tiled_copy_dQ_r2s, tRS_rdQ, tRS_sdQ[(None, None, None, epi_buffer_k)]
|
| 1140 |
+
)
|
| 1141 |
+
cute.arch.fence_proxy("async.shared", space="cta")
|
| 1142 |
+
self.epilog_sync_barrier.arrive_and_wait()
|
| 1143 |
+
|
| 1144 |
+
if warp_idx == self.epilogue_warp_ids[0]:
|
| 1145 |
+
cute.copy(
|
| 1146 |
+
tma_atom_dQv,
|
| 1147 |
+
bSG_sdQv[(None, epi_buffer)],
|
| 1148 |
+
bSG_gdQv[(None, subtile_idx)],
|
| 1149 |
+
)
|
| 1150 |
+
cute.copy(
|
| 1151 |
+
tma_atom_dQ,
|
| 1152 |
+
bSG_sdQ[(None, epi_buffer_k)],
|
| 1153 |
+
bSG_gdQ[(None, subtile_idx)],
|
| 1154 |
+
)
|
| 1155 |
+
pipeline_epi.producer_commit()
|
| 1156 |
+
pipeline_epi.producer_acquire()
|
| 1157 |
+
|
| 1158 |
+
self.epilog_sync_barrier.arrive_and_wait()
|
| 1159 |
+
epi_subtile_counter_v += 1
|
| 1160 |
+
epi_subtile_counter_k += 1
|
| 1161 |
+
|
| 1162 |
+
if const_expr(self.overlap_kv_epi):
|
| 1163 |
+
pipeline_load_kv_epi.producer_acquire(load_epi_producer_state)
|
| 1164 |
+
with cute.arch.elect_one():
|
| 1165 |
+
pipeline_load_kv_epi.producer_commit(load_epi_producer_state)
|
| 1166 |
+
load_epi_producer_state.advance()
|
| 1167 |
+
|
| 1168 |
+
with cute.arch.elect_one():
|
| 1169 |
+
pipeline_dQ_dQv.consumer_release(acc_consumer_state)
|
| 1170 |
+
acc_consumer_state.advance()
|
| 1171 |
+
|
| 1172 |
+
# ---- Advance to next tile ----
|
| 1173 |
+
pipeline_clc.consumer_wait(clc_consumer_state)
|
| 1174 |
+
work_tile = tile_sched.get_current_work()
|
| 1175 |
+
pipeline_clc.consumer_release(clc_consumer_state)
|
| 1176 |
+
clc_consumer_state.advance()
|
| 1177 |
+
|
| 1178 |
+
# ---- Dealloc the tensor memory buffer ----
|
| 1179 |
+
tmem.relinquish_alloc_permit()
|
| 1180 |
+
self.epilog_sync_barrier.arrive_and_wait()
|
| 1181 |
+
tmem.free(tmem_ptr)
|
| 1182 |
+
pipeline_epi.producer_tail()
|
| 1183 |
+
|
| 1184 |
+
def epilogue_tmem_copy_and_partition(
|
| 1185 |
+
self,
|
| 1186 |
+
tidx: cutlass.Int32,
|
| 1187 |
+
tAcc: cute.Tensor,
|
| 1188 |
+
gC_mnl: cute.Tensor,
|
| 1189 |
+
epi_tile: cute.Tile,
|
| 1190 |
+
mma_tiler_mnk,
|
| 1191 |
+
c_layout,
|
| 1192 |
+
c_dtype,
|
| 1193 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1194 |
+
copy_atom_t2r = utils.sm100.get_tmem_load_op(
|
| 1195 |
+
mma_tiler_mnk,
|
| 1196 |
+
c_layout,
|
| 1197 |
+
c_dtype,
|
| 1198 |
+
self.acc_dtype,
|
| 1199 |
+
epi_tile,
|
| 1200 |
+
use_2cta_instrs=False,
|
| 1201 |
+
)
|
| 1202 |
+
|
| 1203 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, STAGE)
|
| 1204 |
+
tAcc_epi = cute.flat_divide(tAcc[((None, None), 0, 0, None)], epi_tile)
|
| 1205 |
+
|
| 1206 |
+
# (EPI_TILE_M, EPI_TILE_N)
|
| 1207 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(copy_atom_t2r, tAcc_epi[(None, None, 0, 0, 0)])
|
| 1208 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1209 |
+
|
| 1210 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, STAGE)
|
| 1211 |
+
tTR_tAcc = thr_copy_t2r.partition_S(tAcc_epi)
|
| 1212 |
+
|
| 1213 |
+
# (EPI_TILE_M, EPI_TILE_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1214 |
+
gC_mnl_epi = cute.flat_divide(gC_mnl[((None, None), 0, 0, None, None, None)], epi_tile)
|
| 1215 |
+
# (T2R, T2R_M, T2R_N, EPI_M, EPI_N, RestM, RestN, RestL)
|
| 1216 |
+
tTR_gC = thr_copy_t2r.partition_D(gC_mnl_epi)
|
| 1217 |
+
|
| 1218 |
+
# (T2R, T2R_M, T2R_N)
|
| 1219 |
+
rAcc_shape = tTR_gC[(None, None, None, 0, 0, 0, 0, 0)].shape
|
| 1220 |
+
tTR_rAcc = cute.make_rmem_tensor(rAcc_shape, self.acc_dtype)
|
| 1221 |
+
|
| 1222 |
+
return (tiled_copy_t2r, tTR_tAcc, tTR_rAcc)
|
| 1223 |
+
|
| 1224 |
+
def epilogue_smem_copy_and_partition(
|
| 1225 |
+
self,
|
| 1226 |
+
c_layout,
|
| 1227 |
+
c_dtype: Type[cutlass.Numeric],
|
| 1228 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 1229 |
+
tTR_rC: cute.Tensor,
|
| 1230 |
+
tidx: cutlass.Int32,
|
| 1231 |
+
sC: cute.Tensor,
|
| 1232 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1233 |
+
copy_atom_r2s = utils.sm100.get_smem_store_op(
|
| 1234 |
+
c_layout, c_dtype, self.acc_dtype, tiled_copy_t2r
|
| 1235 |
+
)
|
| 1236 |
+
tiled_copy_r2s = cute.make_tiled_copy_D(copy_atom_r2s, tiled_copy_t2r)
|
| 1237 |
+
# (R2S, R2S_M, R2S_N, PIPE_D)
|
| 1238 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx)
|
| 1239 |
+
tRS_sC = thr_copy_r2s.partition_D(sC)
|
| 1240 |
+
# (R2S, R2S_M, R2S_N)
|
| 1241 |
+
tRS_rC = tiled_copy_r2s.retile(tTR_rC)
|
| 1242 |
+
return tiled_copy_r2s, tRS_rC, tRS_sC
|
| 1243 |
+
|
| 1244 |
+
def epilogue_gmem_copy_and_partition(
|
| 1245 |
+
self,
|
| 1246 |
+
tma_atom,
|
| 1247 |
+
gC,
|
| 1248 |
+
epi_tile,
|
| 1249 |
+
sC,
|
| 1250 |
+
) -> Tuple[cute.Tensor, cute.Tensor]:
|
| 1251 |
+
gC_epi = cute.flat_divide(gC[((None, None), 0, 0, None, None, None)], epi_tile)
|
| 1252 |
+
|
| 1253 |
+
sC_for_tma_partition = cute.group_modes(sC, 0, 2)
|
| 1254 |
+
|
| 1255 |
+
gC_for_tma_partition = cute.group_modes(gC_epi, 0, 2)
|
| 1256 |
+
|
| 1257 |
+
bSG_sC, bSG_gC = cpasync.tma_partition(
|
| 1258 |
+
tma_atom,
|
| 1259 |
+
0,
|
| 1260 |
+
cute.make_layout(1),
|
| 1261 |
+
sC_for_tma_partition,
|
| 1262 |
+
gC_for_tma_partition,
|
| 1263 |
+
)
|
| 1264 |
+
|
| 1265 |
+
return bSG_sC, bSG_gC
|
| 1266 |
+
|
| 1267 |
+
@cute.jit
|
| 1268 |
+
def find_batch_from_q(
|
| 1269 |
+
self,
|
| 1270 |
+
token: Int32,
|
| 1271 |
+
seqlen_q_divmod: FastDivmodDivisor,
|
| 1272 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 1273 |
+
) -> Int32:
|
| 1274 |
+
"""Find batch index from q token (binary search for varlen, divmod otherwise)"""
|
| 1275 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 1276 |
+
return get_batch_from_cu_tensor(token, mCuSeqlensQ)
|
| 1277 |
+
else:
|
| 1278 |
+
batch, _ = divmod(token, seqlen_q_divmod)
|
| 1279 |
+
return batch
|
| 1280 |
+
|
| 1281 |
+
@staticmethod
|
| 1282 |
+
def _compute_grid(c, cta_tile_shape_mnk, cluster_shape_mn):
|
| 1283 |
+
c_shape = cute.slice_(cta_tile_shape_mnk, (None, None, 0))
|
| 1284 |
+
gc = cute.zipped_divide(c, tiler=c_shape)
|
| 1285 |
+
c_logical_shape = gc[(0, (None, None, None))].shape
|
| 1286 |
+
|
| 1287 |
+
num_ctas_mnl = (
|
| 1288 |
+
cute.size(c_logical_shape[0]),
|
| 1289 |
+
cute.size(c_logical_shape[1]),
|
| 1290 |
+
cute.size(c_logical_shape[2]),
|
| 1291 |
+
)
|
| 1292 |
+
|
| 1293 |
+
tile_sched_params = utils.ClcDynamicPersistentTileSchedulerParams(
|
| 1294 |
+
num_ctas_mnl, (*cluster_shape_mn, 1)
|
| 1295 |
+
)
|
| 1296 |
+
grid = utils.ClcDynamicPersistentTileScheduler.get_grid_shape(tile_sched_params)
|
| 1297 |
+
return tile_sched_params, grid
|
build/torch-cuda/flash_bwd_mla_sm100.py
ADDED
|
@@ -0,0 +1,2135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2026, Colfax International.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from functools import partial
|
| 5 |
+
from typing import Callable, Optional
|
| 6 |
+
|
| 7 |
+
import cuda.bindings.driver as cuda
|
| 8 |
+
|
| 9 |
+
import cutlass
|
| 10 |
+
import cutlass.cute as cute
|
| 11 |
+
from cutlass import Float32, Int64, Int32, Boolean, const_expr
|
| 12 |
+
import cutlass.pipeline as pipeline
|
| 13 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05
|
| 14 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 15 |
+
from cutlass.utils import ClcDynamicPersistentTileScheduler
|
| 16 |
+
|
| 17 |
+
from .quack import copy_utils, layout_utils
|
| 18 |
+
|
| 19 |
+
from .pack_gqa import pack_gqa_layout
|
| 20 |
+
from .seqlen_info import SeqlenInfoQK
|
| 21 |
+
from .block_info import BlockInfo
|
| 22 |
+
from . import blackwell_helpers as fa_sm100_utils
|
| 23 |
+
from .tile_scheduler import (
|
| 24 |
+
SchedulerState,
|
| 25 |
+
SchedulingMode,
|
| 26 |
+
TileSchedulerArguments,
|
| 27 |
+
TileSchedulerProtocol,
|
| 28 |
+
SingleTileScheduler,
|
| 29 |
+
SingleTileLPTScheduler,
|
| 30 |
+
SingleTileVarlenScheduler,
|
| 31 |
+
ParamsBase,
|
| 32 |
+
)
|
| 33 |
+
from .fa_logging import fa_log, fa_printf
|
| 34 |
+
from .utils import smid, elem_pointer, get_batch_from_cu_tensor
|
| 35 |
+
from .copy_utils import tiled_copy_2d, atomic_add_fp32x4
|
| 36 |
+
|
| 37 |
+
from .topk_gather_kv import CpasyncGatherKVManager
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
from .named_barrier import NamedBarrierBwdSm100_MLA2CTA
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class FlashAttentionSparseMLABackwardSm100:
|
| 44 |
+
def __init__(
|
| 45 |
+
self,
|
| 46 |
+
is_causal: bool = False,
|
| 47 |
+
topk_length: int = 2048,
|
| 48 |
+
qhead_per_kvhead: int = 1,
|
| 49 |
+
nheads_kv: int = 1,
|
| 50 |
+
hdim: int = 64,
|
| 51 |
+
hdimv: int = 512,
|
| 52 |
+
has_seqused_q: bool = False,
|
| 53 |
+
disable_bitmask: bool = False,
|
| 54 |
+
use_clc_scheduler: bool = True,
|
| 55 |
+
):
|
| 56 |
+
use_cpasync_load_KV = True
|
| 57 |
+
self.is_causal = is_causal
|
| 58 |
+
self.is_local = False
|
| 59 |
+
self.pack_gqa = True
|
| 60 |
+
self.qhead_per_kvhead = qhead_per_kvhead
|
| 61 |
+
self.nheads_kv = nheads_kv
|
| 62 |
+
self.has_seqused_q = has_seqused_q
|
| 63 |
+
self.use_tma_O = True
|
| 64 |
+
self.use_cpasync_load_KV = True
|
| 65 |
+
self.use_tma_KV = False
|
| 66 |
+
self.topk_length = topk_length
|
| 67 |
+
self.is_topk_gather = True
|
| 68 |
+
assert qhead_per_kvhead == 128 or qhead_per_kvhead == 64
|
| 69 |
+
|
| 70 |
+
# user-provided option if topk indices guaranteed in bounds
|
| 71 |
+
self.disable_bitmask = disable_bitmask
|
| 72 |
+
|
| 73 |
+
# ==== tile scheduler ====
|
| 74 |
+
self.static_persistent = False
|
| 75 |
+
self.use_clc_scheduler = use_clc_scheduler
|
| 76 |
+
self.sched_stages = 1
|
| 77 |
+
self.scheduling_mode = (
|
| 78 |
+
SchedulingMode.CLC if self.use_clc_scheduler else SchedulingMode.STATIC
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
if const_expr(has_seqused_q):
|
| 82 |
+
self.TileScheduler = SingleTileVarlenScheduler
|
| 83 |
+
elif self.use_clc_scheduler:
|
| 84 |
+
self.TileScheduler = SingleTileLPTScheduler
|
| 85 |
+
else:
|
| 86 |
+
self.TileScheduler = SingleTileScheduler
|
| 87 |
+
|
| 88 |
+
fa_log(
|
| 89 |
+
1,
|
| 90 |
+
f"TileScheduler={self.TileScheduler.__name__}, scheduling_mode={self.scheduling_mode.name}",
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
# ==== thread info ====
|
| 94 |
+
self.num_softmax_threads = 128
|
| 95 |
+
self.num_epilogue_threads = 128
|
| 96 |
+
self.num_load_threads = 32
|
| 97 |
+
self.num_mma_threads = 32
|
| 98 |
+
self.num_empty_threads = 0
|
| 99 |
+
self.num_relay_threads = 32
|
| 100 |
+
self.num_cpasync_load_threads = 128
|
| 101 |
+
self.num_threads = 512
|
| 102 |
+
self.num_warps = self.num_threads // 32
|
| 103 |
+
self.softmax_warp_indices = (0, 1, 2, 3)
|
| 104 |
+
self.epilogue_warp_indices = (4, 5, 6, 7)
|
| 105 |
+
self.load_warp_id = 8
|
| 106 |
+
self.mma_warp_id = 9
|
| 107 |
+
self.clc_scheduler_warp_id = 10
|
| 108 |
+
self.relay_warp_id = 11
|
| 109 |
+
self.cpasync_load_warp_indices = (12, 13, 14, 15)
|
| 110 |
+
self.empty_warp_ids = ()
|
| 111 |
+
|
| 112 |
+
# ==== register usage ====
|
| 113 |
+
assert self.num_warps == 16
|
| 114 |
+
|
| 115 |
+
self.num_regs_load = 128
|
| 116 |
+
self.num_regs_mma = 128
|
| 117 |
+
self.num_regs_softmax = 128
|
| 118 |
+
self.num_regs_epilogue = 128
|
| 119 |
+
self.num_regs_cpasync = 128
|
| 120 |
+
self.num_regs_other = 128
|
| 121 |
+
|
| 122 |
+
# self.num_regs_load = 128 - 32
|
| 123 |
+
# self.num_regs_mma = 128 - 32
|
| 124 |
+
# self.num_regs_softmax = 128 + 32
|
| 125 |
+
# self.num_regs_epilogue = 128 + 32
|
| 126 |
+
# self.num_regs_cpasync = 128 - 32
|
| 127 |
+
# self.num_regs_other = 48
|
| 128 |
+
|
| 129 |
+
self.num_regs_per_thread = 128
|
| 130 |
+
self.num_regs_total = 512
|
| 131 |
+
|
| 132 |
+
assert (
|
| 133 |
+
self.num_regs_mma
|
| 134 |
+
+ self.num_regs_softmax
|
| 135 |
+
+ self.num_regs_epilogue
|
| 136 |
+
+ self.num_regs_cpasync
|
| 137 |
+
<= self.num_regs_total
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# ==== 2cta info ====
|
| 141 |
+
self.use_2cta_instrs = True
|
| 142 |
+
self.cta_group = tcgen05.CtaGroup.TWO
|
| 143 |
+
self.cta_group_size = 2
|
| 144 |
+
self.cluster_shape_mn = (2, 1)
|
| 145 |
+
self.cluster_shape_mnk = (2, 1, 1)
|
| 146 |
+
|
| 147 |
+
# ==== problem shape info ====
|
| 148 |
+
self.hdim = hdim # ignored
|
| 149 |
+
self.hdimv = hdimv
|
| 150 |
+
self.tile_m = qhead_per_kvhead
|
| 151 |
+
self.tile_n = 64
|
| 152 |
+
self.cta_tiler_mn = (self.tile_m // self.cta_group_size, self.tile_n)
|
| 153 |
+
self.cluster_tile_n = self.cta_group_size * self.tile_n
|
| 154 |
+
self.num_hdimv_splits = 2 # split hdimv in half for our Qv @ V^T and P @ V mmas.
|
| 155 |
+
|
| 156 |
+
self.tile_P = (self.tile_m, self.tile_n)
|
| 157 |
+
self.tile_Pt = (self.tile_n, self.tile_m)
|
| 158 |
+
self.tile_dS = (self.tile_m, self.tile_n)
|
| 159 |
+
self.tile_dSt = (self.tile_n, self.tile_m)
|
| 160 |
+
self.tile_dV = (self.tile_n, 32)
|
| 161 |
+
|
| 162 |
+
# ==== MMA info ====
|
| 163 |
+
# dP.T = V @ dO.T , N x M x dv
|
| 164 |
+
# dV += P.T @ dO , N x dv x M
|
| 165 |
+
# dV += dS.T @ Qv , N x dv x M
|
| 166 |
+
self.mma_tiler_VdO = (
|
| 167 |
+
self.cluster_tile_n,
|
| 168 |
+
self.tile_m,
|
| 169 |
+
self.hdimv // self.num_hdimv_splits,
|
| 170 |
+
)
|
| 171 |
+
self.mma_tiler_PtdOt = (
|
| 172 |
+
self.cluster_tile_n,
|
| 173 |
+
self.hdimv // self.num_hdimv_splits,
|
| 174 |
+
self.tile_m,
|
| 175 |
+
)
|
| 176 |
+
self.mma_tiler_dStQvt = (
|
| 177 |
+
self.cluster_tile_n,
|
| 178 |
+
self.hdimv // self.num_hdimv_splits,
|
| 179 |
+
self.tile_m,
|
| 180 |
+
)
|
| 181 |
+
# note: store P.T, dS.T as tile_n major (i.e., as P and dS)
|
| 182 |
+
self.major_mode_V = tcgen05.OperandMajorMode.K
|
| 183 |
+
self.major_mode_dO = tcgen05.OperandMajorMode.K
|
| 184 |
+
self.major_mode_Pt = tcgen05.OperandMajorMode.MN
|
| 185 |
+
self.major_mode_dOt = tcgen05.OperandMajorMode.MN
|
| 186 |
+
self.major_mode_dSt = tcgen05.OperandMajorMode.MN
|
| 187 |
+
self.major_mode_Qvt = tcgen05.OperandMajorMode.MN
|
| 188 |
+
self.operand_source_V = tcgen05.OperandSource.SMEM
|
| 189 |
+
self.operand_source_Pt = tcgen05.OperandSource.SMEM
|
| 190 |
+
self.operand_source_dSt = tcgen05.OperandSource.SMEM
|
| 191 |
+
|
| 192 |
+
# ==== pipeline info ====
|
| 193 |
+
# stationary: dOi
|
| 194 |
+
# mainloop:
|
| 195 |
+
# *) P, scaleP => Pt
|
| 196 |
+
# *) dSt
|
| 197 |
+
# *) Vi, i = {0, 1}
|
| 198 |
+
# *) dOti => Qvi => dVi, i = {0, 1}
|
| 199 |
+
|
| 200 |
+
# redundant names for ease-of-use
|
| 201 |
+
self.num_stages_V = 2
|
| 202 |
+
self.num_stages_dO = 2
|
| 203 |
+
self.num_stages_P = 1
|
| 204 |
+
self.num_stages_Pt = 1
|
| 205 |
+
self.num_stages_dS = 1
|
| 206 |
+
self.num_stages_dSt = 1
|
| 207 |
+
self.num_stages_dOt = 2
|
| 208 |
+
self.num_stages_Qv = 2
|
| 209 |
+
self.num_stages_Qvt = 2
|
| 210 |
+
|
| 211 |
+
self.num_stages_dP = 1
|
| 212 |
+
self.num_stages_dPt = 1
|
| 213 |
+
self.num_stages_dV = 2 # == hdimv splits, for Umma <-> Async
|
| 214 |
+
self.num_epi_stages_dV = 8 # == 2 splits x 4 slots/split
|
| 215 |
+
|
| 216 |
+
self.num_stages_scaleP = 1
|
| 217 |
+
self.num_stages_dPsum = 1
|
| 218 |
+
|
| 219 |
+
# ==== dtype info ====
|
| 220 |
+
self.dtype_acc = Float32
|
| 221 |
+
|
| 222 |
+
# ==== TMEM info ====
|
| 223 |
+
SM100_TMEM_CAPACITY_COLUMNS = 512
|
| 224 |
+
self.tmem_alloc_cols = SM100_TMEM_CAPACITY_COLUMNS
|
| 225 |
+
self.tmem_cols_dP = self.tile_m // self.cta_group_size
|
| 226 |
+
self.tmem_cols_dVi = (self.hdimv // self.num_hdimv_splits) // self.cta_group_size
|
| 227 |
+
self.tmem_offset_dV0 = 0
|
| 228 |
+
self.tmem_offset_dV1 = self.tmem_offset_dV0 + self.tmem_cols_dVi
|
| 229 |
+
self.tmem_offsets_dV = [self.tmem_offset_dV0, self.tmem_offset_dV1]
|
| 230 |
+
self.tmem_offset_dP = self.tmem_offset_dV1 + self.tmem_cols_dVi
|
| 231 |
+
self.total_tmem = self.tmem_offset_dP + self.tmem_cols_dP
|
| 232 |
+
assert self.total_tmem <= self.tmem_alloc_cols, (
|
| 233 |
+
f"Total TMEM columns allocated {self.total_tmem} exceeds capacity {self.tmem_alloc_cols}"
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
def _get_shared_storage_cls(self):
|
| 237 |
+
self.buffer_align_bytes = 1024
|
| 238 |
+
|
| 239 |
+
def smem_struct_align(dtype, staged_layout):
|
| 240 |
+
return cute.struct.Align[
|
| 241 |
+
cute.struct.MemRange[dtype, cute.cosize(staged_layout)],
|
| 242 |
+
self.buffer_align_bytes,
|
| 243 |
+
]
|
| 244 |
+
|
| 245 |
+
def mbar_struct(num_stages):
|
| 246 |
+
return cute.struct.MemRange[Int64, 2 * num_stages]
|
| 247 |
+
|
| 248 |
+
# sV, sdO, sP = sPt, sdSt = sdS, sdOt = sQvt = sdV
|
| 249 |
+
(
|
| 250 |
+
sV_struct,
|
| 251 |
+
sdO_struct,
|
| 252 |
+
sP_struct,
|
| 253 |
+
sdS_struct,
|
| 254 |
+
sQvt_struct,
|
| 255 |
+
sScaleP_struct,
|
| 256 |
+
sdPsum_struct,
|
| 257 |
+
) = (
|
| 258 |
+
smem_struct_align(dtype, layout)
|
| 259 |
+
for dtype, layout in [
|
| 260 |
+
(self.dtype, self.sV_layout_staged),
|
| 261 |
+
(self.dtype, self.sdO_layout_staged),
|
| 262 |
+
(self.dtype, self.sPt_layout_staged),
|
| 263 |
+
(self.dtype, self.sdSt_layout_staged),
|
| 264 |
+
(self.dtype, self.sQvt_layout_staged),
|
| 265 |
+
(self.dtype_scale, self.sScaleP_layout_staged),
|
| 266 |
+
(self.dtype_scale, self.sdPsum_layout_staged),
|
| 267 |
+
]
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
(
|
| 271 |
+
mbar_ptr_V_struct, # load V
|
| 272 |
+
mbar_ptr_dO_struct, # load dO
|
| 273 |
+
mbar_ptr_dOt_Qvt_struct, # load dOt => Qvt
|
| 274 |
+
mbar_ptr_dSt_struct, # store dS
|
| 275 |
+
mbar_ptr_P_struct, # load P
|
| 276 |
+
mbar_ptr_Pt_struct, # store Pt
|
| 277 |
+
mbar_ptr_dPt_struct, # dP mma
|
| 278 |
+
mbar_ptr_dV_struct, # dV mma
|
| 279 |
+
mbar_ptr_scaleP_struct, # load scaleP
|
| 280 |
+
mbar_ptr_dPsum_struct, # load dPsum
|
| 281 |
+
) = (
|
| 282 |
+
mbar_struct(n)
|
| 283 |
+
for n in [
|
| 284 |
+
self.num_stages_V,
|
| 285 |
+
self.num_stages_dO,
|
| 286 |
+
self.num_stages_Qvt,
|
| 287 |
+
self.num_stages_dSt,
|
| 288 |
+
self.num_stages_P,
|
| 289 |
+
self.num_stages_Pt,
|
| 290 |
+
self.num_stages_dPt,
|
| 291 |
+
self.num_stages_dV,
|
| 292 |
+
self.num_stages_scaleP,
|
| 293 |
+
self.num_stages_dPsum,
|
| 294 |
+
]
|
| 295 |
+
)
|
| 296 |
+
tmem_dealloc_mbar_struct = Int64
|
| 297 |
+
tmem_holding_buf_struct = Int32
|
| 298 |
+
|
| 299 |
+
self.sched_stages = 1
|
| 300 |
+
clc_response_size = self.sched_stages * 4 if self.use_clc_scheduler else 0
|
| 301 |
+
clc_mbar_size = self.sched_stages * 2 if self.use_clc_scheduler else 0
|
| 302 |
+
|
| 303 |
+
@cute.struct
|
| 304 |
+
class SharedStorage:
|
| 305 |
+
mbar_ptr_V: mbar_ptr_V_struct
|
| 306 |
+
mbar_ptr_V_cpasync: mbar_ptr_V_struct
|
| 307 |
+
mbar_ptr_dO: mbar_ptr_dO_struct
|
| 308 |
+
mbar_ptr_dOt_Qvt: mbar_ptr_dOt_Qvt_struct
|
| 309 |
+
mbar_ptr_P: mbar_ptr_P_struct
|
| 310 |
+
mbar_ptr_Pt: mbar_ptr_Pt_struct
|
| 311 |
+
mbar_ptr_dSt: mbar_ptr_dSt_struct
|
| 312 |
+
mbar_ptr_dPt: mbar_ptr_dPt_struct
|
| 313 |
+
mbar_ptr_dV: mbar_ptr_dV_struct
|
| 314 |
+
mbar_ptr_dV_epi: mbar_ptr_dV_struct
|
| 315 |
+
mbar_ptr_scaleP: mbar_ptr_scaleP_struct
|
| 316 |
+
mbar_ptr_dPsum: mbar_ptr_dPsum_struct
|
| 317 |
+
tmem_dealloc_mbar: tmem_dealloc_mbar_struct
|
| 318 |
+
tmem_holding_buf: tmem_holding_buf_struct
|
| 319 |
+
clc_mbar_ptr: cute.struct.MemRange[cutlass.Int64, clc_mbar_size]
|
| 320 |
+
clc_response: cute.struct.MemRange[Int32, clc_response_size]
|
| 321 |
+
|
| 322 |
+
sScaleP: sScaleP_struct
|
| 323 |
+
sdPsum: sdPsum_struct
|
| 324 |
+
sV: sV_struct
|
| 325 |
+
sdO: sdO_struct
|
| 326 |
+
sP: sP_struct
|
| 327 |
+
sdS: sdS_struct
|
| 328 |
+
sQv: sQvt_struct
|
| 329 |
+
|
| 330 |
+
# print("smem bytes = ", SharedStorage.size_in_bytes())
|
| 331 |
+
|
| 332 |
+
return SharedStorage
|
| 333 |
+
|
| 334 |
+
# fmt: off
|
| 335 |
+
@cute.jit
|
| 336 |
+
def __call__(
|
| 337 |
+
self,
|
| 338 |
+
mdO: cute.Tensor, # (b, s_q, h, dv) or (total_q, h, dv) if there is cu_seqlens_q
|
| 339 |
+
mV: cute.Tensor, # (b_k, s_k, h_k, dv) or (total_k, h_k, dv) if there is cu_seqlens_k
|
| 340 |
+
mQv: cute.Tensor, # == mdO
|
| 341 |
+
mP: cute.Tensor, # (b, s_q, h, topk) or (total_q, h, topk)
|
| 342 |
+
mdV: cute.Tensor, # == mV
|
| 343 |
+
mdS: cute.Tensor, # == mP
|
| 344 |
+
mIndexTopk: cute.Tensor, # (b, s_q, topk) or (total_q, topk) if there is cu_seqlens_q
|
| 345 |
+
softmax_scale: Float32,
|
| 346 |
+
mScaleP: Optional[cute.Tensor] = None, # (b, s_q, topk//128, h) or (total_q, topk//128, h)
|
| 347 |
+
mdPsum: Optional[cute.Tensor] = None, # (b, s_q, h) or (total_q, h) if there is cu_seqlens_q
|
| 348 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None, # (b + 1)
|
| 349 |
+
mCuSeqlensK: Optional[cute.Tensor] = None, # (b + 1)
|
| 350 |
+
mSeqUsedQ: Optional[cute.Tensor] = None, # (b)
|
| 351 |
+
mSeqUsedK: Optional[cute.Tensor] = None, # (b)
|
| 352 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 353 |
+
stream: cuda.CUstream = None,
|
| 354 |
+
):
|
| 355 |
+
# fmt: on
|
| 356 |
+
# ==== dtype info ====
|
| 357 |
+
self.dtype = mdO.element_type
|
| 358 |
+
self.dtype_dV = mdV.element_type
|
| 359 |
+
self.dtype_scale = Float32
|
| 360 |
+
self.dtype_index = mIndexTopk.element_type
|
| 361 |
+
assert self.dtype.width == 16
|
| 362 |
+
assert self.dtype_dV.width == 32
|
| 363 |
+
assert self.dtype_index == Int32
|
| 364 |
+
if const_expr(mScaleP is not None):
|
| 365 |
+
assert mScaleP.element_type == self.dtype_scale
|
| 366 |
+
if const_expr(mdPsum is not None):
|
| 367 |
+
assert mdPsum.element_type == self.dtype_scale
|
| 368 |
+
|
| 369 |
+
# ==== Prepare Tensors ====
|
| 370 |
+
new_stride = lambda mX: (
|
| 371 |
+
*(cute.assume(s, divby=128 // mX.element_type.width) for s in mX.stride[:-1]),
|
| 372 |
+
mX.stride[-1],
|
| 373 |
+
)
|
| 374 |
+
mQv, mV, mdV, mdO, mP, mdS, mScaleP, mdPsum = [
|
| 375 |
+
cute.make_tensor(mX.iterator, cute.make_layout(mX.shape, stride=new_stride(mX)))
|
| 376 |
+
if mX is not None
|
| 377 |
+
else None
|
| 378 |
+
for mX in (mQv, mV, mdV, mdO, mP, mdS, mScaleP, mdPsum)
|
| 379 |
+
]
|
| 380 |
+
# (b, s, h, d) -> (s, d, h, b) or
|
| 381 |
+
# (total, h, d) -> (total, d, h)
|
| 382 |
+
QO_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 2, 1]
|
| 383 |
+
KV_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensK is None) else [0, 2, 1]
|
| 384 |
+
mQv, mdO, mP, mdS = [
|
| 385 |
+
cute.make_tensor(mX.iterator, cute.select(mX.layout, mode=QO_layout_transpose))
|
| 386 |
+
if mX is not None
|
| 387 |
+
else None
|
| 388 |
+
for mX in (mQv, mdO, mP, mdS)
|
| 389 |
+
]
|
| 390 |
+
mV, mdV = [
|
| 391 |
+
cute.make_tensor(mX.iterator, cute.select(mX.layout, mode=KV_layout_transpose))
|
| 392 |
+
if mX is not None
|
| 393 |
+
else None
|
| 394 |
+
for mX in (mV, mdV)
|
| 395 |
+
]
|
| 396 |
+
|
| 397 |
+
# (b, s, topk//128, h) -> (s, topk//128, h, b) or
|
| 398 |
+
# (total, topk//128, h) -> (total, topk//128, h)
|
| 399 |
+
ScaleP_layout_transpose = [1, 2, 3, 0] if const_expr(mCuSeqlensQ is None) else [0, 1, 2]
|
| 400 |
+
mScaleP = cute.make_tensor(
|
| 401 |
+
mScaleP.iterator, cute.select(mScaleP.layout, mode=ScaleP_layout_transpose)
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
# (b, s, h) -> (s, h, b) or
|
| 405 |
+
# (total, h) -> (total, h)
|
| 406 |
+
dPsum_layout_transpose = [1, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 1]
|
| 407 |
+
mdPsum = cute.make_tensor(
|
| 408 |
+
mdPsum.iterator, cute.select(mdPsum.layout, mode=dPsum_layout_transpose)
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
# (b, s_q, topk) -> (topk, s_q, b) or (total_q, topk) -> (topk, total_q)
|
| 412 |
+
topk_layout_transpose = [2, 1, 0] if const_expr(mCuSeqlensQ is None) else [1, 0]
|
| 413 |
+
mIndexTopk = cute.make_tensor(
|
| 414 |
+
mIndexTopk.iterator, cute.select(mIndexTopk.layout, mode=topk_layout_transpose)
|
| 415 |
+
)
|
| 416 |
+
topk_length_dynamic = mIndexTopk.shape[0]
|
| 417 |
+
|
| 418 |
+
if const_expr(self.pack_gqa):
|
| 419 |
+
mQv, mdO, mP, mdS, mScaleP = [
|
| 420 |
+
pack_gqa_layout(mX, self.qhead_per_kvhead, self.nheads_kv, head_idx=2)
|
| 421 |
+
if mX is not None
|
| 422 |
+
else None
|
| 423 |
+
for mX in (mQv, mdO, mP, mdS, mScaleP)
|
| 424 |
+
]
|
| 425 |
+
if const_expr(mdPsum is not None):
|
| 426 |
+
mdPsum = pack_gqa_layout(mdPsum, self.qhead_per_kvhead, self.nheads_kv, head_idx=1)
|
| 427 |
+
|
| 428 |
+
# ((h/h_k, s_q), dv, h_k, b) -> (dv, (h/h_k, s_q), h_k, b)
|
| 429 |
+
# or ((h/h_k, total_q), dv, h_k) -> (dv, (h/h_k, total_q), h_k)
|
| 430 |
+
mma_operand_layout_transpose = (
|
| 431 |
+
[1, 0, 2, 3] if const_expr(mCuSeqlensQ is None) else [1, 0, 2]
|
| 432 |
+
)
|
| 433 |
+
mQvt, mdOt = [
|
| 434 |
+
cute.make_tensor(mX.iterator, cute.select(mX.layout, mode=mma_operand_layout_transpose))
|
| 435 |
+
for mX in (mQv, mdO)
|
| 436 |
+
]
|
| 437 |
+
|
| 438 |
+
# fmt: off
|
| 439 |
+
# ==== Prepare MMAs ====
|
| 440 |
+
# (local_var, dtype_a, major_a, major_b, mma_tiler, operand_source_a)
|
| 441 |
+
_mma_specs = [
|
| 442 |
+
("tiled_mma_VdO", self.dtype, self.major_mode_V, self.major_mode_dO, self.mma_tiler_VdO, self.operand_source_V),
|
| 443 |
+
("tiled_mma_PtdOt", self.dtype, self.major_mode_Pt, self.major_mode_dOt, self.mma_tiler_PtdOt, self.operand_source_Pt),
|
| 444 |
+
("tiled_mma_dStQvt", self.dtype, self.major_mode_dSt, self.major_mode_Qvt, self.mma_tiler_dStQvt, self.operand_source_dSt),
|
| 445 |
+
]
|
| 446 |
+
tiled_mma_VdO, tiled_mma_PtdOt, tiled_mma_dStQvt = (
|
| 447 |
+
sm100_utils.make_trivial_tiled_mma(
|
| 448 |
+
dtype_a, major_a, major_b, self.dtype_acc, self.cta_group, mma_tiler[:2], operand_source_a,
|
| 449 |
+
)
|
| 450 |
+
for _, dtype_a, major_a, major_b, mma_tiler, operand_source_a in _mma_specs
|
| 451 |
+
)
|
| 452 |
+
|
| 453 |
+
# ==== Prepare SMEM layouts and TMAs ====
|
| 454 |
+
# (attr, make_fn, tiled_mma, mma_tiler, dtype, num_stages)
|
| 455 |
+
_smem_layout_specs = [
|
| 456 |
+
("sV_layout", sm100_utils.make_smem_layout_a, tiled_mma_VdO, self.mma_tiler_VdO, self.dtype, self.num_stages_V),
|
| 457 |
+
("sdO_layout", sm100_utils.make_smem_layout_b, tiled_mma_VdO, self.mma_tiler_VdO, self.dtype, self.num_stages_dO),
|
| 458 |
+
("sPt_layout", sm100_utils.make_smem_layout_a, tiled_mma_PtdOt, self.mma_tiler_PtdOt, self.dtype, self.num_stages_Pt),
|
| 459 |
+
("sdOt_layout", sm100_utils.make_smem_layout_b, tiled_mma_PtdOt, self.mma_tiler_PtdOt, self.dtype, self.num_stages_dOt),
|
| 460 |
+
("sdSt_layout", sm100_utils.make_smem_layout_a, tiled_mma_dStQvt, self.mma_tiler_dStQvt, self.dtype, self.num_stages_dSt),
|
| 461 |
+
("sQvt_layout", sm100_utils.make_smem_layout_b, tiled_mma_dStQvt, self.mma_tiler_dStQvt, self.dtype, self.num_stages_Qvt),
|
| 462 |
+
]
|
| 463 |
+
for attr, make_fn, tiled_mma, mma_tiler, dtype, num_stages in _smem_layout_specs:
|
| 464 |
+
ab_kwarg = "a_dtype" if make_fn is sm100_utils.make_smem_layout_a else "b_dtype"
|
| 465 |
+
staged = make_fn(
|
| 466 |
+
tiled_mma=tiled_mma,
|
| 467 |
+
mma_tiler_mnk=mma_tiler,
|
| 468 |
+
num_stages=num_stages,
|
| 469 |
+
**{ab_kwarg: dtype},
|
| 470 |
+
)
|
| 471 |
+
setattr(self, f"{attr}_staged", staged)
|
| 472 |
+
setattr(self, attr, cute.select(staged, mode=[0, 1, 2]))
|
| 473 |
+
|
| 474 |
+
# Prepare additional SMEM load layouts
|
| 475 |
+
self.P_layout_major = cutlass.utils.LayoutEnum.from_tensor(mP)
|
| 476 |
+
self.sP_layout_staged = sm100_utils.make_smem_layout_epi(
|
| 477 |
+
self.dtype, self.P_layout_major, self.tile_P, self.num_stages_P
|
| 478 |
+
)
|
| 479 |
+
self.sP_layout = cute.select(self.sP_layout_staged, mode=[0, 1])
|
| 480 |
+
self.sScaleP_layout_staged = cute.make_layout((self.tile_m, self.num_stages_scaleP))
|
| 481 |
+
self.sScaleP_layout = cute.select(self.sScaleP_layout_staged, mode=[0])
|
| 482 |
+
self.sdPsum_layout_staged = cute.make_layout((self.tile_m, self.num_stages_dPsum))
|
| 483 |
+
self.sdPsum_layout = cute.select(self.sdPsum_layout_staged, mode=[0])
|
| 484 |
+
|
| 485 |
+
# ==== TMA load ====
|
| 486 |
+
for attr, dtype, layout in [
|
| 487 |
+
("tma_copy_bytes_V", self.dtype, self.sV_layout),
|
| 488 |
+
("tma_copy_bytes_dO", self.dtype, self.sdO_layout),
|
| 489 |
+
("tma_copy_bytes_dOt", self.dtype, self.sdOt_layout),
|
| 490 |
+
("tma_copy_bytes_Qvt", self.dtype, self.sQvt_layout),
|
| 491 |
+
]:
|
| 492 |
+
setattr(self, attr, cute.size_in_bytes(dtype, layout) * self.cta_group_size)
|
| 493 |
+
|
| 494 |
+
assert self.tma_copy_bytes_dOt == self.tma_copy_bytes_Qvt
|
| 495 |
+
self.tma_copy_bytes_P = cute.size_in_bytes(self.dtype, self.sP_layout)
|
| 496 |
+
self.tma_copy_bytes_scaleP = cute.size_in_bytes(self.dtype_scale, self.sScaleP_layout)
|
| 497 |
+
self.tma_copy_bytes_dPsum = cute.size_in_bytes(self.dtype_scale, self.sdPsum_layout)
|
| 498 |
+
|
| 499 |
+
tma_load_op = cpasync.CopyBulkTensorTileG2SOp(self.cta_group)
|
| 500 |
+
cta_layout_vmnk = cute.tiled_divide(
|
| 501 |
+
cute.make_layout(self.cluster_shape_mnk), (tiled_mma_VdO.thr_id.shape,)
|
| 502 |
+
)
|
| 503 |
+
cta_shape = cta_layout_vmnk.shape
|
| 504 |
+
|
| 505 |
+
def make_tma(make_fn, mX, smem_layout, mma_tiler, tiled_mma):
|
| 506 |
+
return make_fn(tma_load_op, mX, smem_layout, mma_tiler, tiled_mma, cta_shape)
|
| 507 |
+
|
| 508 |
+
A, B = cute.nvgpu.make_tiled_tma_atom_A, cute.nvgpu.make_tiled_tma_atom_B
|
| 509 |
+
|
| 510 |
+
# (atom_name, tensor_name, make_fn, m, smem_layout, mma_tiler, tiled_mma)
|
| 511 |
+
_tma_specs = [
|
| 512 |
+
("tma_atom_dO", "tma_tensor_dO", B, mdO, self.sdO_layout, self.mma_tiler_VdO, tiled_mma_VdO),
|
| 513 |
+
("tma_atom_dOt", "tma_tensor_dOt", B, mdOt, self.sdOt_layout, self.mma_tiler_PtdOt, tiled_mma_PtdOt),
|
| 514 |
+
("tma_atom_Qvt", "tma_tensor_Qvt", B, mQvt, self.sQvt_layout, self.mma_tiler_dStQvt, tiled_mma_dStQvt),
|
| 515 |
+
]
|
| 516 |
+
_tmas = {}
|
| 517 |
+
for atom_name, tensor_name, make_fn, m, smem_layout, mma_tiler, tiled_mma in _tma_specs:
|
| 518 |
+
_tmas[atom_name], _tmas[tensor_name] = (
|
| 519 |
+
make_tma(make_fn, m, smem_layout, mma_tiler, tiled_mma)
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
(tma_atom_dO, tma_tensor_dO,
|
| 523 |
+
tma_atom_dOt, tma_tensor_dOt,
|
| 524 |
+
tma_atom_Qvt, tma_tensor_Qvt) = _tmas.values()
|
| 525 |
+
|
| 526 |
+
# Make TMA load for P separately
|
| 527 |
+
tma_atom_P, tma_tensor_P = cute.nvgpu.cpasync.make_tiled_tma_atom(
|
| 528 |
+
cpasync.CopyBulkTensorTileG2SOp(),
|
| 529 |
+
mP,
|
| 530 |
+
self.sP_layout,
|
| 531 |
+
self.tile_P,
|
| 532 |
+
)
|
| 533 |
+
|
| 534 |
+
# ==== TMA store ====
|
| 535 |
+
tma_store_op = cpasync.CopyBulkTensorTileS2GOp()
|
| 536 |
+
|
| 537 |
+
self.dS_layout_major = cutlass.utils.LayoutEnum.from_tensor(mdS)
|
| 538 |
+
self.dV_layout_major = cutlass.utils.LayoutEnum.from_tensor(mdV)
|
| 539 |
+
# (tile_m, tile_n, dS_stages) = (nheads, 64, dS_stage)
|
| 540 |
+
sdS_layout_staged = sm100_utils.make_smem_layout_epi(
|
| 541 |
+
self.dtype, self.dS_layout_major, self.tile_dS, self.num_stages_dSt
|
| 542 |
+
)
|
| 543 |
+
# (tile_n, 32, dV_epi_stages) = (64, 32, 4 x 2)
|
| 544 |
+
sdV_layout_staged = sm100_utils.make_smem_layout_epi(
|
| 545 |
+
self.dtype_dV, self.dV_layout_major, self.tile_dV, self.num_epi_stages_dV
|
| 546 |
+
)
|
| 547 |
+
tma_atom_dS, tma_tensor_dS = cpasync.make_tiled_tma_atom(
|
| 548 |
+
tma_store_op, mdS, cute.select(sdS_layout_staged, mode=[0, 1]), self.tile_dS
|
| 549 |
+
)
|
| 550 |
+
# fmt: on
|
| 551 |
+
|
| 552 |
+
# ==== Allocate shared memory ====
|
| 553 |
+
SharedStorage = self._get_shared_storage_cls()
|
| 554 |
+
|
| 555 |
+
# ==== Tile scheduler ====
|
| 556 |
+
TileScheduler = self.TileScheduler
|
| 557 |
+
|
| 558 |
+
fa_printf(1, "mdO = {}", mdO.layout)
|
| 559 |
+
batch_size_for_sched = cute.size(mdO.shape[3]) if const_expr(mCuSeqlensQ is None) else 1
|
| 560 |
+
tile_sched_args = TileSchedulerArguments(
|
| 561 |
+
num_block=cute.ceil_div(cute.size(mdO.shape[0]), self.tile_m),
|
| 562 |
+
num_head=cute.size(mdO.shape[2]),
|
| 563 |
+
num_batch=batch_size_for_sched,
|
| 564 |
+
num_splits=1,
|
| 565 |
+
seqlen_k=cute.size(mV.shape[0]),
|
| 566 |
+
headdim=self.hdim,
|
| 567 |
+
headdim_v=self.hdimv,
|
| 568 |
+
total_q=cute.size(mdO.shape[0])
|
| 569 |
+
if const_expr(mCuSeqlensQ is not None)
|
| 570 |
+
else cute.size(mdO.shape[0]) * cute.size(mdO.shape[3]),
|
| 571 |
+
tile_shape_mn=self.cta_tiler_mn,
|
| 572 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 573 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 574 |
+
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 575 |
+
element_size=self.dtype.width // 8,
|
| 576 |
+
is_persistent=self.static_persistent,
|
| 577 |
+
lpt=False,
|
| 578 |
+
is_split_kv=False,
|
| 579 |
+
cluster_shape_mn=self.cluster_shape_mn,
|
| 580 |
+
use_cluster_idx=True,
|
| 581 |
+
)
|
| 582 |
+
tile_sched_params = TileScheduler.to_underlying_arguments(
|
| 583 |
+
tile_sched_args, scheduling_mode=self.scheduling_mode
|
| 584 |
+
)
|
| 585 |
+
self.tile_scheduler_cls = TileScheduler
|
| 586 |
+
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 587 |
+
fa_printf(1, "grid = {}", grid_dim)
|
| 588 |
+
|
| 589 |
+
# ==== Named Barrier ====
|
| 590 |
+
self.cpasync_barrier = cutlass.pipeline.NamedBarrier(
|
| 591 |
+
barrier_id=int(NamedBarrierBwdSm100_MLA2CTA.Cpasync),
|
| 592 |
+
num_threads=self.num_cpasync_load_threads,
|
| 593 |
+
)
|
| 594 |
+
self.softmax_barrier = cutlass.pipeline.NamedBarrier(
|
| 595 |
+
barrier_id=int(NamedBarrierBwdSm100_MLA2CTA.Softmax),
|
| 596 |
+
num_threads=self.num_softmax_threads,
|
| 597 |
+
)
|
| 598 |
+
self.epi_barrier = cutlass.pipeline.NamedBarrier(
|
| 599 |
+
barrier_id=int(NamedBarrierBwdSm100_MLA2CTA.Epilogue),
|
| 600 |
+
num_threads=self.num_epilogue_threads,
|
| 601 |
+
)
|
| 602 |
+
|
| 603 |
+
LOG2_E = math.log2(math.e)
|
| 604 |
+
softmax_scale_log2 = softmax_scale * LOG2_E
|
| 605 |
+
|
| 606 |
+
# ==== Launch kernel ====
|
| 607 |
+
block_dim = (self.num_threads, 1, 1)
|
| 608 |
+
self.kernel(
|
| 609 |
+
mV,
|
| 610 |
+
mdV,
|
| 611 |
+
tma_tensor_dO,
|
| 612 |
+
tma_tensor_dOt,
|
| 613 |
+
tma_tensor_Qvt,
|
| 614 |
+
tma_tensor_P,
|
| 615 |
+
tma_tensor_dS,
|
| 616 |
+
mScaleP,
|
| 617 |
+
mdPsum,
|
| 618 |
+
mCuSeqlensQ,
|
| 619 |
+
mCuSeqlensK,
|
| 620 |
+
mSeqUsedQ,
|
| 621 |
+
mSeqUsedK,
|
| 622 |
+
mIndexTopk,
|
| 623 |
+
tma_atom_dO,
|
| 624 |
+
tma_atom_dOt,
|
| 625 |
+
tma_atom_Qvt,
|
| 626 |
+
tma_atom_P,
|
| 627 |
+
tma_atom_dS,
|
| 628 |
+
self.sV_layout_staged,
|
| 629 |
+
self.sdO_layout_staged,
|
| 630 |
+
self.sdOt_layout_staged,
|
| 631 |
+
self.sQvt_layout_staged,
|
| 632 |
+
self.sP_layout_staged, # load P
|
| 633 |
+
sdS_layout_staged, # store dS
|
| 634 |
+
sdV_layout_staged,
|
| 635 |
+
self.sPt_layout_staged, # mma Pt
|
| 636 |
+
self.sdSt_layout_staged, # mma dSt
|
| 637 |
+
self.sScaleP_layout_staged,
|
| 638 |
+
self.sdPsum_layout_staged,
|
| 639 |
+
tiled_mma_VdO,
|
| 640 |
+
tiled_mma_PtdOt,
|
| 641 |
+
tiled_mma_dStQvt,
|
| 642 |
+
softmax_scale,
|
| 643 |
+
softmax_scale_log2,
|
| 644 |
+
topk_length_dynamic,
|
| 645 |
+
tile_sched_params,
|
| 646 |
+
SharedStorage,
|
| 647 |
+
).launch(
|
| 648 |
+
grid=grid_dim,
|
| 649 |
+
block=block_dim,
|
| 650 |
+
cluster=self.cluster_shape_mnk,
|
| 651 |
+
smem=SharedStorage.size_in_bytes(),
|
| 652 |
+
stream=stream,
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
@cute.kernel
|
| 656 |
+
def kernel(
|
| 657 |
+
self,
|
| 658 |
+
mV: cute.Tensor,
|
| 659 |
+
mdV: cute.Tensor,
|
| 660 |
+
mdO: cute.Tensor,
|
| 661 |
+
mdOt: cute.Tensor,
|
| 662 |
+
mQvt: cute.Tensor,
|
| 663 |
+
mP: cute.Tensor,
|
| 664 |
+
mdS: cute.Tensor,
|
| 665 |
+
mScaleP: Optional[cute.Tensor],
|
| 666 |
+
mdPsum: Optional[cute.Tensor],
|
| 667 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 668 |
+
mCuSeqlensK: Optional[cute.Tensor],
|
| 669 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 670 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 671 |
+
mIndexTopk: Optional[cute.Tensor],
|
| 672 |
+
tma_atom_dO: cute.CopyAtom,
|
| 673 |
+
tma_atom_dOt: cute.CopyAtom,
|
| 674 |
+
tma_atom_Qvt: cute.CopyAtom,
|
| 675 |
+
tma_atom_P: cute.CopyAtom,
|
| 676 |
+
tma_atom_dS: cute.CopyAtom,
|
| 677 |
+
sV_layout_staged: cute.ComposedLayout,
|
| 678 |
+
sdO_layout_staged: cute.ComposedLayout,
|
| 679 |
+
sdOt_layout_staged: cute.ComposedLayout,
|
| 680 |
+
sQvt_layout_staged: cute.ComposedLayout,
|
| 681 |
+
sP_layout_staged: cute.ComposedLayout,
|
| 682 |
+
sdS_layout_staged: cute.ComposedLayout,
|
| 683 |
+
sdV_layout_staged: cute.ComposedLayout,
|
| 684 |
+
sPt_layout_staged: cute.ComposedLayout,
|
| 685 |
+
sdSt_layout_staged: cute.ComposedLayout,
|
| 686 |
+
sScaleP_layout_staged: cute.Layout,
|
| 687 |
+
sdPsum_layout_staged: cute.Layout,
|
| 688 |
+
tiled_mma_VdO: cute.TiledMma,
|
| 689 |
+
tiled_mma_PtdOt: cute.TiledMma,
|
| 690 |
+
tiled_mma_dStQvt: cute.TiledMma,
|
| 691 |
+
softmax_scale: Float32,
|
| 692 |
+
softmax_scale_log2: Float32,
|
| 693 |
+
topk_length_dynamic: Optional[Int32],
|
| 694 |
+
tile_sched_params: ParamsBase,
|
| 695 |
+
SharedStorage: cutlass.Constexpr[Callable],
|
| 696 |
+
):
|
| 697 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 698 |
+
cta_layout_vmnk = cute.tiled_divide(
|
| 699 |
+
cute.make_layout(self.cluster_shape_mnk), (tiled_mma_VdO.thr_id.shape,)
|
| 700 |
+
)
|
| 701 |
+
mma_tile_coord_v = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 702 |
+
is_leader_cta = mma_tile_coord_v == 0
|
| 703 |
+
|
| 704 |
+
# ==== Allocate SMEM ====
|
| 705 |
+
smem = cutlass.utils.SmemAllocator()
|
| 706 |
+
storage = smem.allocate(SharedStorage)
|
| 707 |
+
|
| 708 |
+
# ==== Prepare TMEM allocator ====
|
| 709 |
+
tmem_alloc_barrier = pipeline.NamedBarrier(
|
| 710 |
+
barrier_id=int(NamedBarrierBwdSm100_MLA2CTA.TmemPtr),
|
| 711 |
+
num_threads=self.num_mma_threads + self.num_softmax_threads + self.num_epilogue_threads,
|
| 712 |
+
)
|
| 713 |
+
tmem = cutlass.utils.TmemAllocator(
|
| 714 |
+
storage.tmem_holding_buf.ptr,
|
| 715 |
+
barrier_for_retrieve=tmem_alloc_barrier,
|
| 716 |
+
allocator_warp_id=self.mma_warp_id,
|
| 717 |
+
is_two_cta=self.use_2cta_instrs,
|
| 718 |
+
two_cta_tmem_dealloc_mbar_ptr=storage.tmem_dealloc_mbar.ptr,
|
| 719 |
+
)
|
| 720 |
+
|
| 721 |
+
# ==== Prefetch TMA descriptors ====
|
| 722 |
+
if warp_idx == self.load_warp_id:
|
| 723 |
+
cpasync.prefetch_descriptor(tma_atom_dO)
|
| 724 |
+
cpasync.prefetch_descriptor(tma_atom_dOt)
|
| 725 |
+
cpasync.prefetch_descriptor(tma_atom_Qvt)
|
| 726 |
+
cpasync.prefetch_descriptor(tma_atom_P)
|
| 727 |
+
cpasync.prefetch_descriptor(tma_atom_dS)
|
| 728 |
+
|
| 729 |
+
# ==== Construct pipelines ====
|
| 730 |
+
tma_warp = pipeline.CooperativeGroup(pipeline.Agent.Thread, 1)
|
| 731 |
+
mma_warp = pipeline.CooperativeGroup(pipeline.Agent.Thread, 1)
|
| 732 |
+
sm_warps = pipeline.CooperativeGroup(pipeline.Agent.Thread, self.num_softmax_threads // 32)
|
| 733 |
+
store_warp = pipeline.CooperativeGroup(pipeline.Agent.Thread, 1)
|
| 734 |
+
sm_threads = pipeline.CooperativeGroup(pipeline.Agent.Thread, self.num_softmax_threads)
|
| 735 |
+
epi_threads = pipeline.CooperativeGroup(pipeline.Agent.Thread, self.num_epilogue_threads)
|
| 736 |
+
sm_threads_cluster = pipeline.CooperativeGroup(
|
| 737 |
+
pipeline.Agent.Thread, self.num_softmax_threads * self.cta_group_size
|
| 738 |
+
)
|
| 739 |
+
epi_threads_cluster = pipeline.CooperativeGroup(
|
| 740 |
+
pipeline.Agent.Thread, self.num_epilogue_threads * self.cta_group_size
|
| 741 |
+
)
|
| 742 |
+
cpasync_load_threads = pipeline.CooperativeGroup(
|
| 743 |
+
pipeline.Agent.Thread, self.num_cpasync_load_threads
|
| 744 |
+
)
|
| 745 |
+
relay_warps_cluster = pipeline.CooperativeGroup(pipeline.Agent.Thread, self.cta_group_size)
|
| 746 |
+
relay_threads = pipeline.CooperativeGroup(pipeline.Agent.Thread, self.num_relay_threads)
|
| 747 |
+
|
| 748 |
+
TmaUmma = pipeline.PipelineTmaUmma
|
| 749 |
+
TmaAsync = pipeline.PipelineTmaAsync
|
| 750 |
+
AsyncUmma = pipeline.PipelineAsyncUmma
|
| 751 |
+
UmmaAsync = pipeline.PipelineUmmaAsync
|
| 752 |
+
Async = pipeline.PipelineAsync
|
| 753 |
+
|
| 754 |
+
def make_pipeline(cls, mbar_ptr, num_stages, producer, consumer, tx_count=None):
|
| 755 |
+
return cls.create(
|
| 756 |
+
barrier_storage=mbar_ptr.data_ptr(),
|
| 757 |
+
num_stages=num_stages,
|
| 758 |
+
producer_group=producer,
|
| 759 |
+
consumer_group=consumer,
|
| 760 |
+
defer_sync=True,
|
| 761 |
+
**(
|
| 762 |
+
{"cta_layout_vmnk": cta_layout_vmnk}
|
| 763 |
+
if cls is not Async and cls is not TmaAsync
|
| 764 |
+
else {}
|
| 765 |
+
),
|
| 766 |
+
**({"tx_count": tx_count} if tx_count is not None else {}),
|
| 767 |
+
)
|
| 768 |
+
|
| 769 |
+
# Unconditional pipelines
|
| 770 |
+
# fmt: off
|
| 771 |
+
# TmaUmma: dO, dOt & Qvt
|
| 772 |
+
pipeline_dO = make_pipeline(TmaUmma, storage.mbar_ptr_dO, self.num_stages_dO, tma_warp, mma_warp, self.tma_copy_bytes_dO)
|
| 773 |
+
pipeline_dOt_Qvt = make_pipeline(TmaUmma, storage.mbar_ptr_dOt_Qvt, self.num_stages_Qvt, tma_warp, mma_warp, self.tma_copy_bytes_dOt)
|
| 774 |
+
# TmaAsync: P, scaleP
|
| 775 |
+
pipeline_P = make_pipeline(TmaAsync, storage.mbar_ptr_P, self.num_stages_P, tma_warp, sm_warps, self.tma_copy_bytes_P)
|
| 776 |
+
pipeline_scaleP = make_pipeline(TmaAsync, storage.mbar_ptr_scaleP, self.num_stages_scaleP, tma_warp, sm_warps, self.tma_copy_bytes_scaleP)
|
| 777 |
+
pipeline_dPsum = make_pipeline(TmaAsync, storage.mbar_ptr_dPsum, self.num_stages_dPsum, tma_warp, sm_warps, self.tma_copy_bytes_dPsum)
|
| 778 |
+
# AsyncUmma: Pt => dV mma, dSt => dV mma
|
| 779 |
+
pipeline_Pt = make_pipeline(AsyncUmma, storage.mbar_ptr_Pt, self.num_stages_Pt, sm_threads_cluster, mma_warp)
|
| 780 |
+
pipeline_dSt = make_pipeline(AsyncUmma, storage.mbar_ptr_dSt, self.num_stages_dSt, sm_threads_cluster, mma_warp)
|
| 781 |
+
# UmmaAsync: dPt, dV
|
| 782 |
+
pipeline_dPt = make_pipeline(UmmaAsync, storage.mbar_ptr_dPt, self.num_stages_dPt, mma_warp, sm_threads_cluster)
|
| 783 |
+
pipeline_dV = make_pipeline(UmmaAsync, storage.mbar_ptr_dV, self.num_stages_dV, mma_warp, epi_threads_cluster)
|
| 784 |
+
# Async: dV_epi
|
| 785 |
+
pipeline_dV_epi = make_pipeline(Async, storage.mbar_ptr_dV_epi, self.num_stages_dV, tma_warp, store_warp)
|
| 786 |
+
|
| 787 |
+
pipeline_V = make_pipeline(AsyncUmma, storage.mbar_ptr_V, self.num_stages_V, relay_warps_cluster, mma_warp)
|
| 788 |
+
pipeline_V_cpasync = make_pipeline(Async, storage.mbar_ptr_V_cpasync, self.num_stages_V, cpasync_load_threads, relay_threads)
|
| 789 |
+
# fmt: on
|
| 790 |
+
|
| 791 |
+
pipeline.pipeline_init_arrive(cluster_shape_mn=cta_layout_vmnk, is_relaxed=True)
|
| 792 |
+
|
| 793 |
+
# ==== Get SMEM tensors ====
|
| 794 |
+
# fmt: off
|
| 795 |
+
sV, sdO, sP, sPt, sdOt, sdS, sdSt, sQvt = (
|
| 796 |
+
store.get_tensor(layout.outer, swizzle=layout.inner)
|
| 797 |
+
for store, layout in [
|
| 798 |
+
(storage.sV, sV_layout_staged),
|
| 799 |
+
(storage.sdO, sdO_layout_staged),
|
| 800 |
+
(storage.sP, sP_layout_staged), # P & Pt overlap
|
| 801 |
+
(storage.sP, sPt_layout_staged), # P & Pt overlap
|
| 802 |
+
(storage.sQv, sdOt_layout_staged), # {dOt, Qvt, dV} overlap
|
| 803 |
+
(storage.sdS, sdS_layout_staged), # dS & dSt overlap
|
| 804 |
+
(storage.sdS, sdSt_layout_staged), # dS & dSt overlap
|
| 805 |
+
(storage.sQv, sQvt_layout_staged), # {dOt, Qvt, dV} overlap
|
| 806 |
+
]
|
| 807 |
+
)
|
| 808 |
+
sdV = cute.make_tensor(
|
| 809 |
+
cute.recast_ptr(sdOt.iterator, sdV_layout_staged.inner, self.dtype_acc), sdV_layout_staged.outer
|
| 810 |
+
)
|
| 811 |
+
assert cute.cosize(sdV) * self.dtype_acc.width // self.dtype.width == cute.cosize(sdOt)
|
| 812 |
+
|
| 813 |
+
sScaleP = storage.sScaleP.get_tensor(sScaleP_layout_staged)
|
| 814 |
+
sdPsum = storage.sdPsum.get_tensor(sdPsum_layout_staged)
|
| 815 |
+
# fmt: on
|
| 816 |
+
|
| 817 |
+
# ==== Get thread MMAs and accumulator fragments ====
|
| 818 |
+
thr_mma_VdO = tiled_mma_VdO.get_slice(mma_tile_coord_v)
|
| 819 |
+
thr_mma_PtdOt = tiled_mma_PtdOt.get_slice(mma_tile_coord_v)
|
| 820 |
+
thr_mma_dStQvt = tiled_mma_dStQvt.get_slice(mma_tile_coord_v)
|
| 821 |
+
|
| 822 |
+
acc_shape_dPt = thr_mma_VdO.partition_shape_C(self.mma_tiler_VdO[:2])
|
| 823 |
+
acc_shape_dVi = thr_mma_PtdOt.partition_shape_C(self.mma_tiler_PtdOt[:2])
|
| 824 |
+
tdPtdP_fake = thr_mma_VdO.make_fragment_C(acc_shape_dPt)
|
| 825 |
+
tdVtdV0_fake = thr_mma_PtdOt.make_fragment_C(acc_shape_dVi)
|
| 826 |
+
tdVtdV1_fake = thr_mma_PtdOt.make_fragment_C(acc_shape_dVi)
|
| 827 |
+
# tdPtdP = cute.make_tensor(tdPtdP.iterator + self.tmem_offset_dP, tdPtdP.layout)
|
| 828 |
+
# tdVtdV0 = cute.make_tensor(tdVtdV0.iterator + self.tmem_offset_dV0, tdVtdV0.layout)
|
| 829 |
+
# tdVtdV1 = cute.make_tensor(tdVtdV1.iterator + self.tmem_offset_dV1, tdVtdV1.layout)
|
| 830 |
+
|
| 831 |
+
block_info = BlockInfo(
|
| 832 |
+
self.tile_m * self.cta_group_size,
|
| 833 |
+
self.tile_n,
|
| 834 |
+
is_causal=self.is_causal,
|
| 835 |
+
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 836 |
+
)
|
| 837 |
+
SeqlenInfoCls = partial(
|
| 838 |
+
SeqlenInfoQK.create,
|
| 839 |
+
seqlen_q_static=mdO.shape[0] if const_expr(not self.pack_gqa) else mdO.shape[0][1],
|
| 840 |
+
seqlen_k_static=mV.shape[0],
|
| 841 |
+
tile_m=self.tile_m,
|
| 842 |
+
tile_n=self.tile_n,
|
| 843 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 844 |
+
mCuSeqlensK=mCuSeqlensK,
|
| 845 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 846 |
+
mSeqUsedK=mSeqUsedK,
|
| 847 |
+
)
|
| 848 |
+
|
| 849 |
+
if const_expr(self.use_clc_scheduler):
|
| 850 |
+
clc_response_ptr = storage.clc_response.data_ptr()
|
| 851 |
+
clc_mbar_ptr = storage.clc_mbar_ptr.data_ptr()
|
| 852 |
+
|
| 853 |
+
clc_pipeline_producer_group = pipeline.CooperativeGroup(pipeline.Agent.Thread)
|
| 854 |
+
num_clc_consumer_warps_per_cta = self.num_threads // cute.arch.WARP_SIZE
|
| 855 |
+
num_clc_consumer_warps = num_clc_consumer_warps_per_cta * self.cta_group_size
|
| 856 |
+
clc_pipeline_consumer_group = pipeline.CooperativeGroup(
|
| 857 |
+
pipeline.Agent.Thread, cute.arch.WARP_SIZE * num_clc_consumer_warps
|
| 858 |
+
)
|
| 859 |
+
sched_ctx = SchedulerState.create_clc(
|
| 860 |
+
hw_scheduler=ClcDynamicPersistentTileScheduler.create(
|
| 861 |
+
self.tile_scheduler_cls.clc_problem_shape(tile_sched_params),
|
| 862 |
+
cute.arch.block_idx(),
|
| 863 |
+
cute.arch.grid_dim(),
|
| 864 |
+
clc_response_ptr,
|
| 865 |
+
),
|
| 866 |
+
pipeline=pipeline.PipelineClcFetchAsync.create(
|
| 867 |
+
barrier_storage=clc_mbar_ptr,
|
| 868 |
+
num_stages=self.sched_stages,
|
| 869 |
+
producer_group=clc_pipeline_producer_group,
|
| 870 |
+
consumer_group=clc_pipeline_consumer_group,
|
| 871 |
+
tx_count=16,
|
| 872 |
+
cta_layout_vmnk=cta_layout_vmnk,
|
| 873 |
+
),
|
| 874 |
+
consumer_state=pipeline.make_pipeline_state(
|
| 875 |
+
pipeline.PipelineUserType.Consumer, self.sched_stages
|
| 876 |
+
),
|
| 877 |
+
producer_state=pipeline.make_pipeline_state(
|
| 878 |
+
pipeline.PipelineUserType.Producer, self.sched_stages
|
| 879 |
+
),
|
| 880 |
+
)
|
| 881 |
+
tile_scheduler = self.tile_scheduler_cls.create(tile_sched_params, ctx=sched_ctx)
|
| 882 |
+
else:
|
| 883 |
+
tile_scheduler = self.tile_scheduler_cls.create(tile_sched_params)
|
| 884 |
+
assert isinstance(tile_scheduler, TileSchedulerProtocol), (
|
| 885 |
+
f"tile_scheduler is not a TileSchedulerProtocol: {type(tile_scheduler)}"
|
| 886 |
+
)
|
| 887 |
+
|
| 888 |
+
pipeline.pipeline_init_wait(cluster_shape_mn=cta_layout_vmnk)
|
| 889 |
+
|
| 890 |
+
if const_expr(self.use_clc_scheduler):
|
| 891 |
+
if warp_idx == self.clc_scheduler_warp_id:
|
| 892 |
+
cute.arch.setmaxregister_decrease(self.num_regs_other)
|
| 893 |
+
if is_leader_cta:
|
| 894 |
+
self.clc_scheduler_warp(tile_scheduler)
|
| 895 |
+
else:
|
| 896 |
+
self.empty_warp(tile_scheduler)
|
| 897 |
+
for i in cutlass.range_constexpr(len(self.empty_warp_ids)):
|
| 898 |
+
if warp_idx == self.empty_warp_ids[i] and warp_idx != self.clc_scheduler_warp_id:
|
| 899 |
+
cute.arch.setmaxregister_decrease(self.num_regs_other)
|
| 900 |
+
self.empty_warp(tile_scheduler)
|
| 901 |
+
else:
|
| 902 |
+
for i in cutlass.range_constexpr(len(self.empty_warp_ids)):
|
| 903 |
+
if warp_idx == self.empty_warp_ids[i]:
|
| 904 |
+
cute.arch.setmaxregister_decrease(self.num_regs_other)
|
| 905 |
+
|
| 906 |
+
if const_expr(self.use_cpasync_load_KV):
|
| 907 |
+
if warp_idx == self.relay_warp_id:
|
| 908 |
+
if const_expr(self.num_regs_load < self.num_regs_per_thread):
|
| 909 |
+
cute.arch.setmaxregister_decrease(self.num_regs_load)
|
| 910 |
+
self.relay(
|
| 911 |
+
pipeline_V,
|
| 912 |
+
pipeline_V_cpasync,
|
| 913 |
+
topk_length_dynamic,
|
| 914 |
+
block_info,
|
| 915 |
+
SeqlenInfoCls,
|
| 916 |
+
tile_scheduler=tile_scheduler,
|
| 917 |
+
)
|
| 918 |
+
|
| 919 |
+
if warp_idx in self.cpasync_load_warp_indices:
|
| 920 |
+
if const_expr(self.num_regs_cpasync < self.num_regs_per_thread):
|
| 921 |
+
cute.arch.setmaxregister_decrease(self.num_regs_cpasync)
|
| 922 |
+
self.load_cpasync(
|
| 923 |
+
mIndexTopk,
|
| 924 |
+
mV,
|
| 925 |
+
sV,
|
| 926 |
+
pipeline_V,
|
| 927 |
+
pipeline_V_cpasync,
|
| 928 |
+
topk_length_dynamic,
|
| 929 |
+
block_info,
|
| 930 |
+
SeqlenInfoCls,
|
| 931 |
+
mCuSeqlensQ,
|
| 932 |
+
tile_scheduler=tile_scheduler,
|
| 933 |
+
)
|
| 934 |
+
|
| 935 |
+
if warp_idx == self.load_warp_id:
|
| 936 |
+
if const_expr(self.num_regs_load < self.num_regs_per_thread):
|
| 937 |
+
cute.arch.setmaxregister_decrease(self.num_regs_load)
|
| 938 |
+
self.load(
|
| 939 |
+
mdO,
|
| 940 |
+
mP,
|
| 941 |
+
mdOt,
|
| 942 |
+
mQvt,
|
| 943 |
+
mScaleP,
|
| 944 |
+
mdPsum,
|
| 945 |
+
sdO,
|
| 946 |
+
sP,
|
| 947 |
+
sdOt,
|
| 948 |
+
sQvt,
|
| 949 |
+
sScaleP,
|
| 950 |
+
sdPsum,
|
| 951 |
+
tma_atom_dO,
|
| 952 |
+
tma_atom_P,
|
| 953 |
+
tma_atom_dOt,
|
| 954 |
+
tma_atom_Qvt,
|
| 955 |
+
pipeline_dO,
|
| 956 |
+
pipeline_P,
|
| 957 |
+
pipeline_dOt_Qvt,
|
| 958 |
+
pipeline_Pt,
|
| 959 |
+
pipeline_dV_epi,
|
| 960 |
+
pipeline_scaleP,
|
| 961 |
+
pipeline_dPsum,
|
| 962 |
+
thr_mma_VdO,
|
| 963 |
+
thr_mma_PtdOt,
|
| 964 |
+
thr_mma_dStQvt,
|
| 965 |
+
topk_length_dynamic,
|
| 966 |
+
block_info,
|
| 967 |
+
SeqlenInfoCls,
|
| 968 |
+
mCuSeqlensQ,
|
| 969 |
+
tile_scheduler=tile_scheduler,
|
| 970 |
+
)
|
| 971 |
+
|
| 972 |
+
if warp_idx == self.mma_warp_id:
|
| 973 |
+
if const_expr(self.num_regs_mma < self.num_regs_per_thread):
|
| 974 |
+
cute.arch.setmaxregister_decrease(self.num_regs_mma)
|
| 975 |
+
# ==== Allocate TMEM ====
|
| 976 |
+
tmem.allocate(self.tmem_alloc_cols)
|
| 977 |
+
tmem.wait_for_alloc()
|
| 978 |
+
tmem_ptr = tmem.retrieve_ptr(self.dtype_acc)
|
| 979 |
+
tdPtdP = cute.make_tensor(tmem_ptr + self.tmem_offset_dP, tdPtdP_fake.layout)
|
| 980 |
+
tdVtdV0 = cute.make_tensor(tmem_ptr + self.tmem_offset_dV0, tdVtdV0_fake.layout)
|
| 981 |
+
tdVtdV1 = cute.make_tensor(tmem_ptr + self.tmem_offset_dV1, tdVtdV1_fake.layout)
|
| 982 |
+
self.mma(
|
| 983 |
+
sV,
|
| 984 |
+
sdO,
|
| 985 |
+
sPt,
|
| 986 |
+
sdOt,
|
| 987 |
+
sdSt,
|
| 988 |
+
sQvt,
|
| 989 |
+
tdPtdP,
|
| 990 |
+
tdVtdV0,
|
| 991 |
+
tdVtdV1,
|
| 992 |
+
tiled_mma_VdO,
|
| 993 |
+
tiled_mma_PtdOt,
|
| 994 |
+
tiled_mma_dStQvt,
|
| 995 |
+
pipeline_V,
|
| 996 |
+
pipeline_dO,
|
| 997 |
+
pipeline_dPt,
|
| 998 |
+
pipeline_Pt,
|
| 999 |
+
pipeline_dOt_Qvt,
|
| 1000 |
+
pipeline_dSt,
|
| 1001 |
+
pipeline_dV,
|
| 1002 |
+
is_leader_cta,
|
| 1003 |
+
topk_length_dynamic,
|
| 1004 |
+
block_info,
|
| 1005 |
+
SeqlenInfoCls,
|
| 1006 |
+
mCuSeqlensQ,
|
| 1007 |
+
tile_scheduler=tile_scheduler,
|
| 1008 |
+
)
|
| 1009 |
+
tmem.relinquish_alloc_permit()
|
| 1010 |
+
tmem_alloc_barrier.arrive_and_wait()
|
| 1011 |
+
tmem.free(tmem_ptr)
|
| 1012 |
+
|
| 1013 |
+
if warp_idx in self.softmax_warp_indices:
|
| 1014 |
+
cute.arch.setmaxregister_increase(self.num_regs_softmax)
|
| 1015 |
+
tmem.wait_for_alloc()
|
| 1016 |
+
tmem_ptr = tmem.retrieve_ptr(self.dtype_acc)
|
| 1017 |
+
tdPtdP = cute.make_tensor(tmem_ptr + self.tmem_offset_dP, tdPtdP_fake.layout)
|
| 1018 |
+
self.compute_loop(
|
| 1019 |
+
softmax_scale,
|
| 1020 |
+
softmax_scale_log2,
|
| 1021 |
+
thr_mma_VdO,
|
| 1022 |
+
tdPtdP,
|
| 1023 |
+
sP,
|
| 1024 |
+
sdS,
|
| 1025 |
+
sScaleP,
|
| 1026 |
+
sdPsum,
|
| 1027 |
+
mdS,
|
| 1028 |
+
tma_atom_dS,
|
| 1029 |
+
pipeline_P,
|
| 1030 |
+
pipeline_Pt,
|
| 1031 |
+
pipeline_dPt,
|
| 1032 |
+
pipeline_dSt,
|
| 1033 |
+
pipeline_scaleP,
|
| 1034 |
+
pipeline_dPsum,
|
| 1035 |
+
topk_length_dynamic,
|
| 1036 |
+
block_info,
|
| 1037 |
+
SeqlenInfoCls,
|
| 1038 |
+
mCuSeqlensQ,
|
| 1039 |
+
tile_scheduler=tile_scheduler,
|
| 1040 |
+
)
|
| 1041 |
+
tmem_alloc_barrier.arrive()
|
| 1042 |
+
|
| 1043 |
+
if warp_idx in self.epilogue_warp_indices:
|
| 1044 |
+
if const_expr(self.num_regs_epilogue < self.num_regs_per_thread):
|
| 1045 |
+
cute.arch.setmaxregister_decrease(self.num_regs_epilogue)
|
| 1046 |
+
elif const_expr(self.num_regs_epilogue > self.num_regs_per_thread):
|
| 1047 |
+
cute.arch.setmaxregister_increase(self.num_regs_epilogue)
|
| 1048 |
+
|
| 1049 |
+
tmem.wait_for_alloc()
|
| 1050 |
+
tmem_ptr = tmem.retrieve_ptr(self.dtype_acc)
|
| 1051 |
+
tdVtdV0 = cute.make_tensor(tmem_ptr + self.tmem_offset_dV0, tdVtdV0_fake.layout)
|
| 1052 |
+
tdVtdV1 = cute.make_tensor(tmem_ptr + self.tmem_offset_dV1, tdVtdV1_fake.layout)
|
| 1053 |
+
self.dVacc_store(
|
| 1054 |
+
mIndexTopk,
|
| 1055 |
+
mdV,
|
| 1056 |
+
sdV,
|
| 1057 |
+
tdVtdV0,
|
| 1058 |
+
tdVtdV1,
|
| 1059 |
+
thr_mma_PtdOt,
|
| 1060 |
+
pipeline_dV,
|
| 1061 |
+
pipeline_dV_epi,
|
| 1062 |
+
topk_length_dynamic,
|
| 1063 |
+
block_info,
|
| 1064 |
+
SeqlenInfoCls,
|
| 1065 |
+
mCuSeqlensQ,
|
| 1066 |
+
tile_scheduler=tile_scheduler,
|
| 1067 |
+
)
|
| 1068 |
+
tmem_alloc_barrier.arrive()
|
| 1069 |
+
|
| 1070 |
+
@cute.jit
|
| 1071 |
+
def clc_scheduler_warp(
|
| 1072 |
+
self,
|
| 1073 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1074 |
+
):
|
| 1075 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1076 |
+
while work_tile.is_valid_tile:
|
| 1077 |
+
tile_scheduler.prefetch_next_work()
|
| 1078 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
| 1079 |
+
cta_m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1080 |
+
if cute.arch.thread_idx()[0] == self.clc_scheduler_warp_id * cute.arch.WARP_SIZE:
|
| 1081 |
+
fa_printf(
|
| 1082 |
+
3,
|
| 1083 |
+
"[CLC] query sm={} cta={} (m_blk={},h={},b={},s={}) valid={}\n",
|
| 1084 |
+
smid(),
|
| 1085 |
+
cute.arch.block_idx()[0],
|
| 1086 |
+
work_tile.tile_idx[0],
|
| 1087 |
+
work_tile.tile_idx[1],
|
| 1088 |
+
work_tile.tile_idx[2],
|
| 1089 |
+
work_tile.tile_idx[3],
|
| 1090 |
+
work_tile.is_valid_tile,
|
| 1091 |
+
)
|
| 1092 |
+
tile_scheduler.producer_tail()
|
| 1093 |
+
|
| 1094 |
+
@cute.jit
|
| 1095 |
+
def empty_warp(
|
| 1096 |
+
self,
|
| 1097 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1098 |
+
):
|
| 1099 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1100 |
+
while work_tile.is_valid_tile:
|
| 1101 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
| 1102 |
+
|
| 1103 |
+
@cute.jit
|
| 1104 |
+
def relay(
|
| 1105 |
+
self,
|
| 1106 |
+
pipeline_V: pipeline.PipelineAsyncUmma,
|
| 1107 |
+
pipeline_V_cpasync: pipeline.PipelineAsync,
|
| 1108 |
+
topk_length_dynamic: Optional[Int32],
|
| 1109 |
+
block_info: BlockInfo,
|
| 1110 |
+
SeqlenInfoCls: Callable,
|
| 1111 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1112 |
+
):
|
| 1113 |
+
# ==== Make pipeline states ====
|
| 1114 |
+
# pipeline_V producer
|
| 1115 |
+
# pipeline_V_cpasync consumer
|
| 1116 |
+
producer_state_V = pipeline.make_pipeline_state(
|
| 1117 |
+
pipeline.PipelineUserType.Producer, stages=self.num_stages_V
|
| 1118 |
+
)
|
| 1119 |
+
consumer_state_V = pipeline.make_pipeline_state(
|
| 1120 |
+
pipeline.PipelineUserType.Consumer, stages=self.num_stages_V
|
| 1121 |
+
)
|
| 1122 |
+
relay_V_fn = partial(self.relay_inner, pipeline_V_cpasync, pipeline_V)
|
| 1123 |
+
|
| 1124 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1125 |
+
while work_tile.is_valid_tile:
|
| 1126 |
+
# m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1127 |
+
# seqlen = SeqlenInfoCls(batch_idx)
|
| 1128 |
+
|
| 1129 |
+
num_n_block_groups = self.topk_length // self.cluster_tile_n
|
| 1130 |
+
# num_n_block_groups = topk_length_dynamic // self.cluster_tile_n
|
| 1131 |
+
|
| 1132 |
+
# ==== Mainloop ====
|
| 1133 |
+
for _ in cutlass.range(num_n_block_groups, unroll=1):
|
| 1134 |
+
for _ in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1135 |
+
consumer_state_V, producer_state_V = relay_V_fn(
|
| 1136 |
+
consumer_state_V, producer_state_V
|
| 1137 |
+
)
|
| 1138 |
+
|
| 1139 |
+
# Advance to next tile
|
| 1140 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
| 1141 |
+
|
| 1142 |
+
pipeline_V.producer_tail(producer_state_V)
|
| 1143 |
+
|
| 1144 |
+
@cute.jit
|
| 1145 |
+
def relay_inner(
|
| 1146 |
+
self,
|
| 1147 |
+
pipeline_cpasync: pipeline.PipelineAsync,
|
| 1148 |
+
pipeline_mma: pipeline.PipelineAsyncUmma,
|
| 1149 |
+
consumer_state: pipeline.PipelineState,
|
| 1150 |
+
producer_state: pipeline.PipelineState,
|
| 1151 |
+
):
|
| 1152 |
+
pipeline_cpasync.consumer_wait(consumer_state)
|
| 1153 |
+
with cute.arch.elect_one():
|
| 1154 |
+
pipeline_mma.producer_commit(producer_state)
|
| 1155 |
+
consumer_state.advance()
|
| 1156 |
+
producer_state.advance()
|
| 1157 |
+
return consumer_state, producer_state
|
| 1158 |
+
|
| 1159 |
+
@cute.jit
|
| 1160 |
+
def load_cpasync(
|
| 1161 |
+
self,
|
| 1162 |
+
mIndexTopk: cute.Tensor,
|
| 1163 |
+
mV: cute.Tensor,
|
| 1164 |
+
sV: cute.Tensor,
|
| 1165 |
+
pipeline_V: pipeline.PipelineAsyncUmma,
|
| 1166 |
+
pipeline_V_cpasync: pipeline.PipelineAsync,
|
| 1167 |
+
topk_length_dynamic: Optional[Int32],
|
| 1168 |
+
block_info: BlockInfo,
|
| 1169 |
+
SeqlenInfoCls: Callable,
|
| 1170 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 1171 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1172 |
+
):
|
| 1173 |
+
# ==== cpasync load warpgroup ====
|
| 1174 |
+
# Description: loads tiles of V from gmem to smem using cpasync
|
| 1175 |
+
# produces: V
|
| 1176 |
+
# consumes: -
|
| 1177 |
+
|
| 1178 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 1179 |
+
tidx = cute.arch.thread_idx()[0] % self.num_cpasync_load_threads
|
| 1180 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % (
|
| 1181 |
+
self.num_cpasync_load_threads // 32
|
| 1182 |
+
)
|
| 1183 |
+
|
| 1184 |
+
# ==== Make pipeline states ====
|
| 1185 |
+
# producer: acquire PipelineAsyncUmma <- mma
|
| 1186 |
+
# producer: commit PipelineAsync -> relay
|
| 1187 |
+
producer_state_V = pipeline.make_pipeline_state(
|
| 1188 |
+
pipeline.PipelineUserType.Producer, stages=self.num_stages_V
|
| 1189 |
+
)
|
| 1190 |
+
|
| 1191 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1192 |
+
while work_tile.is_valid_tile:
|
| 1193 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1194 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 1195 |
+
batch_idx = get_batch_from_cu_tensor(m_block, mCuSeqlensQ)
|
| 1196 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 1197 |
+
|
| 1198 |
+
num_n_block_groups = self.topk_length // self.cluster_tile_n
|
| 1199 |
+
# num_n_block_groups = topk_length_dynamic // self.cluster_tile_n
|
| 1200 |
+
|
| 1201 |
+
if const_expr(seqlen.has_cu_seqlens_q):
|
| 1202 |
+
# m_block means absolute m_idx
|
| 1203 |
+
mIndexTopk_cur = mIndexTopk[None, m_block]
|
| 1204 |
+
else:
|
| 1205 |
+
mIndexTopk_cur = mIndexTopk[None, m_block, batch_idx]
|
| 1206 |
+
|
| 1207 |
+
if const_expr(self.is_causal):
|
| 1208 |
+
m_local_idx = (
|
| 1209 |
+
m_block - seqlen.offset_q if const_expr(seqlen.has_cu_seqlens_q) else m_block
|
| 1210 |
+
)
|
| 1211 |
+
seqlen_k_limit = m_local_idx + 1 + seqlen.seqlen_k - seqlen.seqlen_q
|
| 1212 |
+
else:
|
| 1213 |
+
seqlen_k_limit = seqlen.seqlen_k
|
| 1214 |
+
cpasync_gather_kv_manager = CpasyncGatherKVManager.create(
|
| 1215 |
+
mIndexTopk_cur,
|
| 1216 |
+
cta_rank_in_cluster,
|
| 1217 |
+
tidx,
|
| 1218 |
+
warp_idx,
|
| 1219 |
+
self.topk_length,
|
| 1220 |
+
seqlen_k_limit,
|
| 1221 |
+
self.cluster_tile_n,
|
| 1222 |
+
self.hdim,
|
| 1223 |
+
self.hdimv,
|
| 1224 |
+
self.num_hdimv_splits,
|
| 1225 |
+
self.num_cpasync_load_threads,
|
| 1226 |
+
mV.element_type,
|
| 1227 |
+
self.cta_group_size,
|
| 1228 |
+
self.cpasync_barrier,
|
| 1229 |
+
self.disable_bitmask,
|
| 1230 |
+
)
|
| 1231 |
+
|
| 1232 |
+
# (seqlen_k, hdimv)
|
| 1233 |
+
mV_cur = seqlen.offset_batch_K(mV, batch_idx, dim=3)[None, None, head_idx]
|
| 1234 |
+
|
| 1235 |
+
load_V = partial(
|
| 1236 |
+
self.cpasync_gather_load_KV,
|
| 1237 |
+
cpasync_gather_kv_manager,
|
| 1238 |
+
pipeline_V,
|
| 1239 |
+
pipeline_V_cpasync,
|
| 1240 |
+
sV,
|
| 1241 |
+
False,
|
| 1242 |
+
"V",
|
| 1243 |
+
mV_cur,
|
| 1244 |
+
)
|
| 1245 |
+
|
| 1246 |
+
# ==== Mainloop ====
|
| 1247 |
+
for n_block_group in cutlass.range(num_n_block_groups, unroll=1):
|
| 1248 |
+
cpasync_gather_kv_manager.load_index_topk(n_block_group, transpose=False)
|
| 1249 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1250 |
+
producer_state_V = load_V(producer_state_V, d_offset=split * self.hdimv // 2)
|
| 1251 |
+
|
| 1252 |
+
# Advance to next tile
|
| 1253 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
| 1254 |
+
|
| 1255 |
+
pipeline_V.producer_tail(producer_state_V)
|
| 1256 |
+
|
| 1257 |
+
@cute.jit
|
| 1258 |
+
def cpasync_gather_load_KV(
|
| 1259 |
+
self,
|
| 1260 |
+
cpasync_gather_kv_manager: CpasyncGatherKVManager,
|
| 1261 |
+
pipeline_mma: pipeline.PipelineAsyncUmma,
|
| 1262 |
+
pipeline_cpasync: pipeline.PipelineAsync,
|
| 1263 |
+
sX: cute.Tensor,
|
| 1264 |
+
transpose: bool,
|
| 1265 |
+
K_or_V: str,
|
| 1266 |
+
mX: cute.Tensor,
|
| 1267 |
+
producer_state: pipeline.PipelineState,
|
| 1268 |
+
d_offset: int = 0,
|
| 1269 |
+
):
|
| 1270 |
+
stage = producer_state.index
|
| 1271 |
+
pipeline_mma.producer_acquire(producer_state)
|
| 1272 |
+
cpasync_gather_kv_manager.load_X(
|
| 1273 |
+
mX, sX[None, None, None, stage], transpose, K_or_V, d_offset
|
| 1274 |
+
)
|
| 1275 |
+
cute.arch.cp_async_commit_group()
|
| 1276 |
+
pipeline_cpasync.sync_object_full.arrive_cp_async_mbarrier(stage)
|
| 1277 |
+
producer_state.advance()
|
| 1278 |
+
return producer_state
|
| 1279 |
+
|
| 1280 |
+
@cute.jit
|
| 1281 |
+
def load(
|
| 1282 |
+
self,
|
| 1283 |
+
mdO: cute.Tensor,
|
| 1284 |
+
mP: cute.Tensor,
|
| 1285 |
+
mdOt: cute.Tensor,
|
| 1286 |
+
mQvt: cute.Tensor,
|
| 1287 |
+
mScaleP: Optional[cute.Tensor],
|
| 1288 |
+
mdPsum: Optional[cute.Tensor],
|
| 1289 |
+
sdO: cute.Tensor,
|
| 1290 |
+
sP: cute.Tensor,
|
| 1291 |
+
sdOt: cute.Tensor,
|
| 1292 |
+
sQvt: cute.Tensor,
|
| 1293 |
+
sScaleP: cute.Tensor,
|
| 1294 |
+
sdPsum: cute.Tensor,
|
| 1295 |
+
tma_atom_dO: cute.CopyAtom,
|
| 1296 |
+
tma_atom_P: cute.CopyAtom,
|
| 1297 |
+
tma_atom_dOt: cute.CopyAtom,
|
| 1298 |
+
tma_atom_Qvt: cute.CopyAtom,
|
| 1299 |
+
pipeline_dO: pipeline.PipelineAsync, # TmaUmma
|
| 1300 |
+
pipeline_P: pipeline.PipelineAsync, # TmaAsync
|
| 1301 |
+
pipeline_dOt_Qvt: pipeline.PipelineAsync, # TmaUmma
|
| 1302 |
+
pipeline_Pt: pipeline.PipelineAsync, # AsyncUmma
|
| 1303 |
+
pipeline_dV_epi: pipeline.PipelineAsync, # Async
|
| 1304 |
+
pipeline_scaleP: pipeline.PipelineAsync, # TmaAsync
|
| 1305 |
+
pipeline_dPsum: pipeline.PipelineAsync, # TmaAsync
|
| 1306 |
+
thr_mma_VdO: cute.ThrMma,
|
| 1307 |
+
thr_mma_PtdOt: cute.ThrMma,
|
| 1308 |
+
thr_mma_dStQvt: cute.ThrMma,
|
| 1309 |
+
topk_length_dynamic: Optional[Int32],
|
| 1310 |
+
block_info: BlockInfo,
|
| 1311 |
+
SeqlenInfoCls: Callable,
|
| 1312 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 1313 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1314 |
+
):
|
| 1315 |
+
# ==== Load warp ====
|
| 1316 |
+
# Description: loads tiles of dO, P, dOt, Qvt from gmem to smem using TMA
|
| 1317 |
+
# produces: dO, P, dOt, Qvt
|
| 1318 |
+
# consumes: -
|
| 1319 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 1320 |
+
lane_idx = cute.arch.lane_idx()
|
| 1321 |
+
|
| 1322 |
+
# ==== Make pipeline states ====
|
| 1323 |
+
Producer = pipeline.PipelineUserType.Producer
|
| 1324 |
+
producer_state_dO = pipeline.make_pipeline_state(Producer, stages=self.num_stages_dO)
|
| 1325 |
+
producer_state_P = pipeline.make_pipeline_state(Producer, stages=self.num_stages_P)
|
| 1326 |
+
producer_state_dOt_Qvt = pipeline.make_pipeline_state(Producer, stages=self.num_stages_dOt)
|
| 1327 |
+
producer_state_dV_epi = pipeline.make_pipeline_state(Producer, stages=self.num_stages_dV)
|
| 1328 |
+
producer_state_scaleP = pipeline.make_pipeline_state(
|
| 1329 |
+
Producer, stages=self.num_stages_scaleP
|
| 1330 |
+
)
|
| 1331 |
+
producer_state_dPsum = pipeline.make_pipeline_state(Producer, stages=self.num_stages_dPsum)
|
| 1332 |
+
|
| 1333 |
+
copy_atom_stats = cute.make_copy_atom(cpasync.CopyBulkG2SOp(), Float32)
|
| 1334 |
+
|
| 1335 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1336 |
+
while work_tile.is_valid_tile:
|
| 1337 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1338 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 1339 |
+
batch_idx = get_batch_from_cu_tensor(m_block, mCuSeqlensQ)
|
| 1340 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 1341 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 1342 |
+
m_block -= seqlen.offset_q
|
| 1343 |
+
num_n_block_groups = self.topk_length // self.cluster_tile_n
|
| 1344 |
+
# num_n_block_groups = topk_length_dynamic // self.cluster_tile_n
|
| 1345 |
+
|
| 1346 |
+
# ==== Partition GMEM tensors ====
|
| 1347 |
+
# (seqlen_q, topk or hdimv)
|
| 1348 |
+
mP_cur = seqlen.offset_batch_Q(mP, batch_idx, dim=3)[None, None, head_idx]
|
| 1349 |
+
mdO_cur = seqlen.offset_batch_Q(mdO, batch_idx, dim=3)[None, None, head_idx]
|
| 1350 |
+
|
| 1351 |
+
# (hdimv, seqlen_q)
|
| 1352 |
+
offset = (
|
| 1353 |
+
(0, seqlen.offset_q) if const_expr(not self.pack_gqa) else (0, (0, seqlen.offset_q))
|
| 1354 |
+
)
|
| 1355 |
+
if const_expr(not seqlen.has_cu_seqlens_q):
|
| 1356 |
+
mdOt_cur = mdOt[None, None, head_idx, batch_idx]
|
| 1357 |
+
mQvt_cur = mQvt[None, None, head_idx, batch_idx]
|
| 1358 |
+
else:
|
| 1359 |
+
mdOt_cur = cute.domain_offset(offset, mdOt[None, None, head_idx])
|
| 1360 |
+
mQvt_cur = cute.domain_offset(offset, mQvt[None, None, head_idx])
|
| 1361 |
+
|
| 1362 |
+
gScaleP = None
|
| 1363 |
+
if const_expr(mScaleP is not None):
|
| 1364 |
+
mScaleP_cur = seqlen.offset_batch_Q(mScaleP, batch_idx, dim=3)[None, None, head_idx]
|
| 1365 |
+
# (tile_m, topk//128)
|
| 1366 |
+
gScaleP = cute.local_tile(mScaleP_cur, (self.tile_m,), (m_block, None))
|
| 1367 |
+
gdPsum = None
|
| 1368 |
+
if const_expr(mdPsum is not None):
|
| 1369 |
+
mdPsum_cur = seqlen.offset_batch_Q(mdPsum, batch_idx, dim=2)[None, head_idx]
|
| 1370 |
+
# (tile_m)
|
| 1371 |
+
gdPsum = cute.local_tile(mdPsum_cur, (self.tile_m,), (m_block,))
|
| 1372 |
+
|
| 1373 |
+
# (tile_m, tile_n, n_blocks)
|
| 1374 |
+
gP = cute.local_tile(
|
| 1375 |
+
mP_cur,
|
| 1376 |
+
(self.tile_m, self.tile_n),
|
| 1377 |
+
(m_block, None),
|
| 1378 |
+
)
|
| 1379 |
+
# (tile_m, hdimv//2, 2)
|
| 1380 |
+
gdO = cute.local_tile(
|
| 1381 |
+
mdO_cur,
|
| 1382 |
+
(self.mma_tiler_VdO[1], self.mma_tiler_VdO[2]),
|
| 1383 |
+
(m_block, None),
|
| 1384 |
+
)
|
| 1385 |
+
# (hdimv//2, tile_m, 2)
|
| 1386 |
+
gdOt = cute.local_tile(
|
| 1387 |
+
mdOt_cur,
|
| 1388 |
+
(self.mma_tiler_PtdOt[1], self.mma_tiler_PtdOt[2]),
|
| 1389 |
+
(None, m_block),
|
| 1390 |
+
)
|
| 1391 |
+
gQvt = cute.local_tile(
|
| 1392 |
+
mQvt_cur,
|
| 1393 |
+
(self.mma_tiler_dStQvt[1], self.mma_tiler_dStQvt[2]),
|
| 1394 |
+
(None, m_block),
|
| 1395 |
+
)
|
| 1396 |
+
|
| 1397 |
+
tdPgdO = thr_mma_VdO.partition_B(gdO)
|
| 1398 |
+
tdVgdOt = thr_mma_PtdOt.partition_B(gdOt)
|
| 1399 |
+
tdVgQvt = thr_mma_dStQvt.partition_B(gQvt)
|
| 1400 |
+
|
| 1401 |
+
# (V, REST)
|
| 1402 |
+
tPsP, tPgP = cpasync.tma_partition(
|
| 1403 |
+
atom=tma_atom_P,
|
| 1404 |
+
cta_coord=0,
|
| 1405 |
+
cta_layout=cute.make_layout(1),
|
| 1406 |
+
smem_tensor=cute.group_modes(sP, 0, 2),
|
| 1407 |
+
gmem_tensor=cute.group_modes(gP, 0, 2),
|
| 1408 |
+
)
|
| 1409 |
+
tdOsdO, tdOgdO = cpasync.tma_partition(
|
| 1410 |
+
atom=tma_atom_dO,
|
| 1411 |
+
cta_coord=0,
|
| 1412 |
+
cta_layout=cute.make_layout(1),
|
| 1413 |
+
smem_tensor=cute.group_modes(sdO, 0, 3),
|
| 1414 |
+
gmem_tensor=cute.group_modes(tdPgdO, 0, 3),
|
| 1415 |
+
)
|
| 1416 |
+
tdOtsdOt, tdOtgdOt = cpasync.tma_partition(
|
| 1417 |
+
atom=tma_atom_dOt,
|
| 1418 |
+
cta_coord=0,
|
| 1419 |
+
cta_layout=cute.make_layout(1),
|
| 1420 |
+
smem_tensor=cute.group_modes(sdOt, 0, 3),
|
| 1421 |
+
gmem_tensor=cute.group_modes(tdVgdOt, 0, 3),
|
| 1422 |
+
)
|
| 1423 |
+
tQvtsQvt, tQvtgQvt = cpasync.tma_partition(
|
| 1424 |
+
atom=tma_atom_Qvt,
|
| 1425 |
+
cta_coord=0,
|
| 1426 |
+
cta_layout=cute.make_layout(1),
|
| 1427 |
+
smem_tensor=cute.group_modes(sQvt, 0, 3),
|
| 1428 |
+
gmem_tensor=cute.group_modes(tdVgQvt, 0, 3),
|
| 1429 |
+
)
|
| 1430 |
+
|
| 1431 |
+
load_P = partial(self.load_inner, tma_atom_P, tPgP, tPsP, pipeline_P)
|
| 1432 |
+
load_dO = partial(self.load_inner, tma_atom_dO, tdOgdO, tdOsdO, pipeline_dO)
|
| 1433 |
+
load_dOt = partial(self.load_inner, tma_atom_dOt, tdOtgdOt, tdOtsdOt, pipeline_dOt_Qvt)
|
| 1434 |
+
load_Qvt = partial(self.load_inner, tma_atom_Qvt, tQvtgQvt, tQvtsQvt, pipeline_dOt_Qvt)
|
| 1435 |
+
load_scaleP = partial(
|
| 1436 |
+
self.load_inner, copy_atom_stats, gScaleP, sScaleP, pipeline_scaleP, bulk_copy=True
|
| 1437 |
+
)
|
| 1438 |
+
load_dPsum = partial(
|
| 1439 |
+
self.load_inner, copy_atom_stats, gdPsum, sdPsum, pipeline_dPsum, bulk_copy=True
|
| 1440 |
+
)
|
| 1441 |
+
|
| 1442 |
+
# ==== Load stationary operands ====
|
| 1443 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1444 |
+
producer_state_dO = load_dO(producer_state_dO, block=split)
|
| 1445 |
+
|
| 1446 |
+
producer_state_dPsum = load_dPsum(producer_state_dPsum)
|
| 1447 |
+
|
| 1448 |
+
# ==== Mainloop ====
|
| 1449 |
+
for n_block_group in cutlass.range(num_n_block_groups, unroll=1):
|
| 1450 |
+
n_block = 2 * n_block_group + cta_rank_in_cluster
|
| 1451 |
+
# load ScaleP
|
| 1452 |
+
if const_expr(mScaleP is not None):
|
| 1453 |
+
producer_state_scaleP = load_scaleP(producer_state_scaleP, block=n_block_group)
|
| 1454 |
+
pipeline_Pt.producer_acquire(producer_state_P)
|
| 1455 |
+
producer_state_P = load_P(producer_state_P, block=n_block)
|
| 1456 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1457 |
+
pipeline_dV_epi.producer_acquire(producer_state_dV_epi)
|
| 1458 |
+
producer_state_dV_epi.advance()
|
| 1459 |
+
producer_state_dOt_Qvt = load_dOt(producer_state_dOt_Qvt, block=split)
|
| 1460 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1461 |
+
producer_state_dOt_Qvt = load_Qvt(producer_state_dOt_Qvt, block=split)
|
| 1462 |
+
|
| 1463 |
+
# Advance to next tile
|
| 1464 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
| 1465 |
+
|
| 1466 |
+
pipeline_P.producer_tail(producer_state_P)
|
| 1467 |
+
pipeline_dO.producer_tail(producer_state_dO)
|
| 1468 |
+
pipeline_dOt_Qvt.producer_tail(producer_state_dOt_Qvt)
|
| 1469 |
+
|
| 1470 |
+
@cute.jit
|
| 1471 |
+
def load_inner(
|
| 1472 |
+
self,
|
| 1473 |
+
copy_atom: cute.CopyAtom,
|
| 1474 |
+
tXgX: cute.Tensor,
|
| 1475 |
+
tXsX: cute.Tensor,
|
| 1476 |
+
load_pipeline: pipeline.PipelineAsync,
|
| 1477 |
+
producer_state: pipeline.PipelineState,
|
| 1478 |
+
block: Optional[Int32] = None,
|
| 1479 |
+
bulk_copy: bool = False,
|
| 1480 |
+
):
|
| 1481 |
+
if const_expr(block is not None):
|
| 1482 |
+
tXgX = tXgX[(None, block)]
|
| 1483 |
+
if const_expr(cute.rank(tXsX) != 1):
|
| 1484 |
+
assert cute.rank(tXsX) == 2, f"wrong rank for tXsX, got {cute.rank(tXsX)}"
|
| 1485 |
+
stage = producer_state.index
|
| 1486 |
+
tXsX = tXsX[(None, stage)]
|
| 1487 |
+
|
| 1488 |
+
load_pipeline.producer_acquire(producer_state)
|
| 1489 |
+
mbar_ptr = load_pipeline.producer_get_barrier(producer_state)
|
| 1490 |
+
if const_expr(bulk_copy):
|
| 1491 |
+
with cute.arch.elect_one():
|
| 1492 |
+
cute.copy(copy_atom, tXgX, tXsX, mbar_ptr=mbar_ptr)
|
| 1493 |
+
else:
|
| 1494 |
+
cute.copy(copy_atom, tXgX, tXsX, tma_bar_ptr=mbar_ptr)
|
| 1495 |
+
producer_state.advance()
|
| 1496 |
+
return producer_state
|
| 1497 |
+
|
| 1498 |
+
@cute.jit
|
| 1499 |
+
def mma(
|
| 1500 |
+
self,
|
| 1501 |
+
sV: cute.Tensor,
|
| 1502 |
+
sdO: cute.Tensor,
|
| 1503 |
+
sPt: cute.Tensor,
|
| 1504 |
+
sdOt: cute.Tensor,
|
| 1505 |
+
sdSt: cute.Tensor,
|
| 1506 |
+
sQvt: cute.Tensor,
|
| 1507 |
+
tdPtdP: cute.Tensor,
|
| 1508 |
+
tdVtdV0: cute.Tensor,
|
| 1509 |
+
tdVtdV1: cute.Tensor,
|
| 1510 |
+
tiled_mma_VdO: cute.TiledMma,
|
| 1511 |
+
tiled_mma_PtdOt: cute.TiledMma,
|
| 1512 |
+
tiled_mma_dStQvt: cute.TiledMma,
|
| 1513 |
+
pipeline_V: pipeline.PipelineAsync, # AsyncUmma
|
| 1514 |
+
pipeline_dO: pipeline.PipelineAsync, # TmaUmma
|
| 1515 |
+
pipeline_dPt: pipeline.PipelineAsync, # UmmaAsync
|
| 1516 |
+
pipeline_Pt: pipeline.PipelineAsync, # AsyncUmma
|
| 1517 |
+
pipeline_dOt_Qvt: pipeline.PipelineAsync, # TmaUmma
|
| 1518 |
+
pipeline_dSt: pipeline.PipelineAsync, # AsyncUmma
|
| 1519 |
+
pipeline_dV: pipeline.PipelineAsync, # UmmaAsync
|
| 1520 |
+
is_leader_cta: Boolean,
|
| 1521 |
+
topk_length_dynamic: Optional[Int32],
|
| 1522 |
+
block_info: BlockInfo,
|
| 1523 |
+
SeqlenInfoCls: Callable,
|
| 1524 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 1525 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1526 |
+
):
|
| 1527 |
+
# ==== mma warp ====
|
| 1528 |
+
# Description: Computes dP = V @ dO^T, dV = P^T @ dO, and dV += dS^T @ Qv
|
| 1529 |
+
# i.e. dP = gemm(V, dO), dV += gemm(P.T, dO.T), dV += gemm(dS.T, Qv.T)
|
| 1530 |
+
# Produces: dP, dV
|
| 1531 |
+
# Consumes: V, dO, P.T, dO.T, dS.T, Qv.T
|
| 1532 |
+
lane_idx = cute.arch.lane_idx()
|
| 1533 |
+
|
| 1534 |
+
tdVtdVs = [tdVtdV0, tdVtdV1]
|
| 1535 |
+
|
| 1536 |
+
# Set accumulate = True for dS^T@Qv since we are accumulating on the P^T@dO result
|
| 1537 |
+
tiled_mma_dStQvt.set(tcgen05.Field.ACCUMULATE, True)
|
| 1538 |
+
|
| 1539 |
+
# Operands for dP=V@dO^T
|
| 1540 |
+
tdPrV = tiled_mma_VdO.make_fragment_A(sV)
|
| 1541 |
+
tdPrdO = tiled_mma_VdO.make_fragment_B(sdO)
|
| 1542 |
+
|
| 1543 |
+
# Operands for dVi=P^T@dOi
|
| 1544 |
+
tdVrPt = tiled_mma_PtdOt.make_fragment_A(sPt)
|
| 1545 |
+
tdVrdOt = tiled_mma_PtdOt.make_fragment_B(sdOt)
|
| 1546 |
+
|
| 1547 |
+
# Operands for dVi+=dS^T@Qvi
|
| 1548 |
+
tdVrdSt = tiled_mma_dStQvt.make_fragment_A(sdSt)
|
| 1549 |
+
tdVrQvt = tiled_mma_dStQvt.make_fragment_B(sQvt)
|
| 1550 |
+
|
| 1551 |
+
use_ptx_gemm_VdO = False
|
| 1552 |
+
use_ptx_gemm_PtdOt = False
|
| 1553 |
+
use_ptx_gemm_dStQvt = False
|
| 1554 |
+
|
| 1555 |
+
# GEMM functions
|
| 1556 |
+
if const_expr(use_ptx_gemm_VdO):
|
| 1557 |
+
gemm_VdO = partial(
|
| 1558 |
+
fa_sm100_utils.gemm_ptx_partial,
|
| 1559 |
+
tiled_mma_VdO.op,
|
| 1560 |
+
self.tmem_offset_dP,
|
| 1561 |
+
zero_init=True,
|
| 1562 |
+
cta_group=self.cta_group_size,
|
| 1563 |
+
)
|
| 1564 |
+
else:
|
| 1565 |
+
gemm_VdO = partial(
|
| 1566 |
+
fa_sm100_utils.gemm,
|
| 1567 |
+
tiled_mma_VdO,
|
| 1568 |
+
tdPtdP,
|
| 1569 |
+
)
|
| 1570 |
+
if const_expr(use_ptx_gemm_PtdOt):
|
| 1571 |
+
gemm_PtdOt = [
|
| 1572 |
+
partial(
|
| 1573 |
+
fa_sm100_utils.gemm_ptx_partial,
|
| 1574 |
+
tiled_mma_PtdOt.op,
|
| 1575 |
+
self.tmem_offsets_dV[split],
|
| 1576 |
+
zero_init=True,
|
| 1577 |
+
cta_group=self.cta_group_size,
|
| 1578 |
+
)
|
| 1579 |
+
for split in range(self.num_hdimv_splits)
|
| 1580 |
+
]
|
| 1581 |
+
else:
|
| 1582 |
+
gemm_PtdOt = [
|
| 1583 |
+
partial(
|
| 1584 |
+
fa_sm100_utils.gemm,
|
| 1585 |
+
tiled_mma_PtdOt,
|
| 1586 |
+
tdVtdVs[split],
|
| 1587 |
+
zero_init=True,
|
| 1588 |
+
)
|
| 1589 |
+
for split in range(self.num_hdimv_splits)
|
| 1590 |
+
]
|
| 1591 |
+
if const_expr(use_ptx_gemm_dStQvt):
|
| 1592 |
+
gemm_dStQvt = [
|
| 1593 |
+
partial(
|
| 1594 |
+
fa_sm100_utils.gemm_ptx_partial,
|
| 1595 |
+
tiled_mma_dStQvt.op,
|
| 1596 |
+
self.tmem_offsets_dV[split],
|
| 1597 |
+
zero_init=False,
|
| 1598 |
+
cta_group=self.cta_group_size,
|
| 1599 |
+
)
|
| 1600 |
+
for split in range(self.num_hdimv_splits)
|
| 1601 |
+
]
|
| 1602 |
+
else:
|
| 1603 |
+
gemm_dStQvt = [
|
| 1604 |
+
partial(
|
| 1605 |
+
fa_sm100_utils.gemm,
|
| 1606 |
+
tiled_mma_dStQvt,
|
| 1607 |
+
tdVtdVs[split],
|
| 1608 |
+
zero_init=False,
|
| 1609 |
+
)
|
| 1610 |
+
for split in range(self.num_hdimv_splits)
|
| 1611 |
+
]
|
| 1612 |
+
|
| 1613 |
+
Consumer, Producer = pipeline.PipelineUserType.Consumer, pipeline.PipelineUserType.Producer
|
| 1614 |
+
consumer_state_V = pipeline.make_pipeline_state(Consumer, stages=self.num_stages_V)
|
| 1615 |
+
consumer_state_dO = pipeline.make_pipeline_state(Consumer, stages=self.num_stages_dO)
|
| 1616 |
+
consumer_state_Pt = pipeline.make_pipeline_state(Consumer, stages=self.num_stages_Pt)
|
| 1617 |
+
consumer_state_dOt_Qvt = pipeline.make_pipeline_state(Consumer, stages=self.num_stages_dOt)
|
| 1618 |
+
consumer_state_dSt = pipeline.make_pipeline_state(Consumer, stages=self.num_stages_dSt)
|
| 1619 |
+
producer_state_dPt = pipeline.make_pipeline_state(Producer, stages=self.num_stages_dPt)
|
| 1620 |
+
producer_state_dV = pipeline.make_pipeline_state(Producer, stages=self.num_stages_dV)
|
| 1621 |
+
|
| 1622 |
+
mma_VdO = partial(
|
| 1623 |
+
self.mma_inner, gemm_VdO, pipeline_V, tdPrV, sV, tdPrdO, sdO, swap_AB_stage=True, use_ptx=use_ptx_gemm_VdO
|
| 1624 |
+
)
|
| 1625 |
+
mma_PtdOt = partial(
|
| 1626 |
+
self.mma_inner, gemm_PtdOt, pipeline_dOt_Qvt, tdVrPt, sPt, tdVrdOt, sdOt, use_ptx=use_ptx_gemm_PtdOt
|
| 1627 |
+
)
|
| 1628 |
+
mma_dStQvt = partial(
|
| 1629 |
+
self.mma_inner, gemm_dStQvt, pipeline_dOt_Qvt, tdVrdSt, sdSt, tdVrQvt, sQvt, use_ptx=use_ptx_gemm_dStQvt
|
| 1630 |
+
)
|
| 1631 |
+
|
| 1632 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1633 |
+
while work_tile.is_valid_tile:
|
| 1634 |
+
# m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1635 |
+
# if const_expr(mCuSeqlensQ is not None):
|
| 1636 |
+
# batch_idx = get_batch_from_cu_tensor(m_block, mCuSeqlensQ)
|
| 1637 |
+
# seqlen = SeqlenInfoCls(batch_idx)
|
| 1638 |
+
# num_n_block_groups = self.topk_length // self.cluster_tile_n
|
| 1639 |
+
num_n_block_groups = topk_length_dynamic // self.cluster_tile_n
|
| 1640 |
+
|
| 1641 |
+
if is_leader_cta:
|
| 1642 |
+
# ==== Prologue ====
|
| 1643 |
+
consumer_wait_state_dO = consumer_state_dO.clone()
|
| 1644 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1645 |
+
pipeline_dO.consumer_wait(consumer_wait_state_dO)
|
| 1646 |
+
consumer_wait_state_dO.advance()
|
| 1647 |
+
|
| 1648 |
+
# ==== Mainloop ====
|
| 1649 |
+
for _ in cutlass.range(num_n_block_groups, unroll=1):
|
| 1650 |
+
# 1. dP = V @ dO^T
|
| 1651 |
+
# mma inner waits for V
|
| 1652 |
+
pipeline_dPt.producer_acquire(producer_state_dPt)
|
| 1653 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1654 |
+
consumer_state_V = mma_VdO(
|
| 1655 |
+
consumer_state_V, a_stage=split, zero_init=split == 0
|
| 1656 |
+
)
|
| 1657 |
+
pipeline_dPt.producer_commit(producer_state_dPt)
|
| 1658 |
+
producer_state_dPt.advance()
|
| 1659 |
+
|
| 1660 |
+
# 2. dV = P^T @ dO
|
| 1661 |
+
# mma inner waits for dOt
|
| 1662 |
+
pipeline_Pt.consumer_wait(consumer_state_Pt)
|
| 1663 |
+
producer_acquire_state_dV = producer_state_dV.clone()
|
| 1664 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1665 |
+
pipeline_dV.producer_acquire(producer_acquire_state_dV)
|
| 1666 |
+
producer_acquire_state_dV.advance()
|
| 1667 |
+
consumer_state_dOt_Qvt = mma_PtdOt(consumer_state_dOt_Qvt, acc_stage=split)
|
| 1668 |
+
pipeline_Pt.consumer_release(consumer_state_Pt)
|
| 1669 |
+
consumer_state_Pt.advance()
|
| 1670 |
+
|
| 1671 |
+
# 3. dV += dS^T @ Qv
|
| 1672 |
+
# mma inner waits for Qvt
|
| 1673 |
+
pipeline_dSt.consumer_wait(consumer_state_dSt)
|
| 1674 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1675 |
+
consumer_state_dOt_Qvt = mma_dStQvt(consumer_state_dOt_Qvt, acc_stage=split)
|
| 1676 |
+
pipeline_dV.producer_commit(producer_state_dV)
|
| 1677 |
+
producer_state_dV.advance()
|
| 1678 |
+
pipeline_dSt.consumer_release(consumer_state_dSt)
|
| 1679 |
+
consumer_state_dSt.advance()
|
| 1680 |
+
|
| 1681 |
+
# ==== Epilogue ====
|
| 1682 |
+
for _ in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 1683 |
+
pipeline_dO.consumer_release(consumer_state_dO)
|
| 1684 |
+
consumer_state_dO.advance()
|
| 1685 |
+
|
| 1686 |
+
# Advance to next tile
|
| 1687 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
| 1688 |
+
|
| 1689 |
+
pipeline_dPt.producer_tail(producer_state_dPt)
|
| 1690 |
+
pipeline_dV.producer_tail(producer_state_dV)
|
| 1691 |
+
|
| 1692 |
+
@cute.jit
|
| 1693 |
+
def mma_inner(
|
| 1694 |
+
self,
|
| 1695 |
+
gemm,
|
| 1696 |
+
load_pipeline,
|
| 1697 |
+
tCrA,
|
| 1698 |
+
sA,
|
| 1699 |
+
tCrB,
|
| 1700 |
+
sB,
|
| 1701 |
+
consumer_state: pipeline.PipelineState,
|
| 1702 |
+
acc_stage: Optional[Int32] = None,
|
| 1703 |
+
a_stage: Int32 = 0,
|
| 1704 |
+
zero_init: Optional[bool] = None,
|
| 1705 |
+
swap_AB_stage: bool = False,
|
| 1706 |
+
use_ptx: bool = True,
|
| 1707 |
+
):
|
| 1708 |
+
if const_expr(acc_stage is not None):
|
| 1709 |
+
gemm = gemm[acc_stage]
|
| 1710 |
+
|
| 1711 |
+
smem_stage = consumer_state.index
|
| 1712 |
+
|
| 1713 |
+
if const_expr(not swap_AB_stage):
|
| 1714 |
+
a_stage = a_stage
|
| 1715 |
+
b_stage = smem_stage
|
| 1716 |
+
else:
|
| 1717 |
+
a_stage = smem_stage
|
| 1718 |
+
b_stage = a_stage
|
| 1719 |
+
|
| 1720 |
+
tCrA_cur = tCrA[None, None, None, a_stage]
|
| 1721 |
+
sA_cur = sA[None, None, None, a_stage]
|
| 1722 |
+
tCrB_cur = tCrB[None, None, None, b_stage]
|
| 1723 |
+
sB_cur = sB[None, None, None, b_stage]
|
| 1724 |
+
|
| 1725 |
+
kwargs = dict(tCrA=tCrA_cur, tCrB=tCrB_cur)
|
| 1726 |
+
if const_expr(use_ptx):
|
| 1727 |
+
kwargs |= dict(sA=sA_cur, sB=sB_cur)
|
| 1728 |
+
if const_expr(zero_init is not None):
|
| 1729 |
+
kwargs["zero_init"] = zero_init
|
| 1730 |
+
|
| 1731 |
+
load_pipeline.consumer_wait(consumer_state)
|
| 1732 |
+
gemm(**kwargs)
|
| 1733 |
+
load_pipeline.consumer_release(consumer_state)
|
| 1734 |
+
consumer_state.advance()
|
| 1735 |
+
return consumer_state
|
| 1736 |
+
|
| 1737 |
+
@cute.jit
|
| 1738 |
+
def compute_loop(
|
| 1739 |
+
self,
|
| 1740 |
+
softmax_scale: Float32,
|
| 1741 |
+
softmax_scale_log2: Float32,
|
| 1742 |
+
thr_mma_VdO: cute.ThrMma,
|
| 1743 |
+
tdPtdP: cute.Tensor,
|
| 1744 |
+
sP: cute.Tensor,
|
| 1745 |
+
sdS: cute.Tensor,
|
| 1746 |
+
sScaleP: cute.Tensor,
|
| 1747 |
+
sdPsum: cute.Tensor,
|
| 1748 |
+
mdS: cute.Tensor,
|
| 1749 |
+
tma_atom_dS: cute.CopyAtom,
|
| 1750 |
+
pipeline_P: pipeline.PipelineAsync, # TmaAsync
|
| 1751 |
+
pipeline_Pt: pipeline.PipelineAsync, # AsyncUmma
|
| 1752 |
+
pipeline_dPt: pipeline.PipelineAsync, # UmmaAsync
|
| 1753 |
+
pipeline_dSt: pipeline.PipelineAsync, # AsyncUmma
|
| 1754 |
+
pipeline_scaleP: pipeline.PipelineAsync, # TmaAsync
|
| 1755 |
+
pipeline_dPsum: pipeline.PipelineAsync, # TmaAsync
|
| 1756 |
+
topk_length_dynamic: Optional[Int32],
|
| 1757 |
+
block_info: BlockInfo,
|
| 1758 |
+
SeqlenInfoCls: Callable,
|
| 1759 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 1760 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1761 |
+
):
|
| 1762 |
+
tidx = cute.arch.thread_idx()[0] % self.num_softmax_threads
|
| 1763 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % (
|
| 1764 |
+
self.num_softmax_threads // 32
|
| 1765 |
+
)
|
| 1766 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 1767 |
+
leader_warp = warp_idx == 0
|
| 1768 |
+
|
| 1769 |
+
# 256b // 32 = 8 values, 128 mqa // 2 => tmem_rep = 8
|
| 1770 |
+
tmem_rep = self.tile_m // self.cta_group_size // 8
|
| 1771 |
+
copy_atom_t2r = cute.make_copy_atom(
|
| 1772 |
+
tcgen05.copy.Ld16x256bOp(tcgen05.copy.Repetition(tmem_rep)),
|
| 1773 |
+
self.dtype_acc,
|
| 1774 |
+
)
|
| 1775 |
+
# ((64,(64,2)),1,1):((65536,(1,4194304 = 65536*64)),0,0)
|
| 1776 |
+
tdPtdP = tdPtdP[(None, None), 0, 0]
|
| 1777 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(copy_atom_t2r, tdPtdP)
|
| 1778 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 1779 |
+
# (T2R, T2R_M, T2R_N)
|
| 1780 |
+
# (((64,16),1),2,1):(((1,65536),0),1048576,0)>, 1048576/65536 = 16
|
| 1781 |
+
tdPtdP_t2r = thr_copy_t2r.partition_S(tdPtdP)
|
| 1782 |
+
|
| 1783 |
+
cdP = cute.make_identity_tensor(self.mma_tiler_VdO[:2]) # (128, 128)
|
| 1784 |
+
tdPcdP = thr_mma_VdO.partition_C(cdP)[(None, None), 0, 0] # (64,128):(1@0,1@1)
|
| 1785 |
+
# (((2,2,8),1),2,1):(((1@1,8@0,8@1),0),16@0,0)
|
| 1786 |
+
tdPcdP_t2r = thr_copy_t2r.partition_D(tdPcdP)
|
| 1787 |
+
assert tdPcdP_t2r.shape[0][1] == 1, f"unexpected tdPcdP_t2r shape, got {tdPcdP_t2r.shape}"
|
| 1788 |
+
|
| 1789 |
+
smem_load_op = cute.nvgpu.warp.LdMatrix8x8x16bOp(True, 4) # ldsm x num_matrices = ldsm x 4
|
| 1790 |
+
smem_store_op = cute.nvgpu.warp.StMatrix8x8x16bOp(True, 4) # stsm x num_matrices = stsm x 4
|
| 1791 |
+
smem_load_atom = cute.make_copy_atom(smem_load_op, self.dtype)
|
| 1792 |
+
smem_store_atom = cute.make_copy_atom(smem_store_op, self.dtype)
|
| 1793 |
+
tiled_copy_r2s = cute.make_tiled_copy_D(smem_store_atom, tiled_copy_t2r)
|
| 1794 |
+
tiled_copy_s2r = cute.make_tiled_copy_D(smem_load_atom, tiled_copy_t2r)
|
| 1795 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx)
|
| 1796 |
+
thr_copy_s2r = tiled_copy_s2r.get_slice(tidx)
|
| 1797 |
+
|
| 1798 |
+
sPt_load_layout = cute.make_ordered_layout(
|
| 1799 |
+
self.tile_Pt + (self.num_stages_P,), order=(1, 0, 2)
|
| 1800 |
+
)
|
| 1801 |
+
# (tile_n, tile_m, stages_P)
|
| 1802 |
+
sPt = cute.composition(sP, sPt_load_layout)
|
| 1803 |
+
sdSt = cute.composition(sdS, sPt_load_layout)
|
| 1804 |
+
|
| 1805 |
+
# (R2S, R2S_M, R2S_N, PIPE_D)
|
| 1806 |
+
# ((8,4),2,1,1):((1,1024),16,0,0)
|
| 1807 |
+
tSR_sPt = thr_copy_s2r.partition_S(sPt)
|
| 1808 |
+
tRS_sdSt = thr_copy_r2s.partition_D(sdSt)
|
| 1809 |
+
|
| 1810 |
+
# ((2,2),(2,8,1),stage):((0,0),(1,8,0),_)
|
| 1811 |
+
tPsScaleP_nm = self.broadcast_tensor_nm_view(sScaleP, thr_mma_VdO, thr_copy_t2r)
|
| 1812 |
+
tdPsdPsum_nm = self.broadcast_tensor_nm_view(sdPsum, thr_mma_VdO, thr_copy_t2r)
|
| 1813 |
+
|
| 1814 |
+
Consumer, Producer = pipeline.PipelineUserType.Consumer, pipeline.PipelineUserType.Producer
|
| 1815 |
+
|
| 1816 |
+
consumer_state_P = pipeline.make_pipeline_state(Consumer, stages=self.num_stages_P)
|
| 1817 |
+
consumer_state_dPt = pipeline.make_pipeline_state(Consumer, stages=1)
|
| 1818 |
+
consumer_state_scaleP = pipeline.make_pipeline_state(
|
| 1819 |
+
Consumer, stages=self.num_stages_scaleP
|
| 1820 |
+
)
|
| 1821 |
+
consumer_state_dPsum = pipeline.make_pipeline_state(Consumer, stages=self.num_stages_dPsum)
|
| 1822 |
+
|
| 1823 |
+
producer_state_Pt = pipeline.make_pipeline_state(Producer, stages=self.num_stages_Pt)
|
| 1824 |
+
producer_state_dSt = pipeline.make_pipeline_state(Producer, stages=self.num_stages_dSt)
|
| 1825 |
+
|
| 1826 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1827 |
+
while work_tile.is_valid_tile:
|
| 1828 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1829 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 1830 |
+
batch_idx = get_batch_from_cu_tensor(m_block, mCuSeqlensQ)
|
| 1831 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 1832 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 1833 |
+
m_block -= seqlen.offset_q
|
| 1834 |
+
num_n_block_groups = self.topk_length // self.cluster_tile_n
|
| 1835 |
+
# num_n_block_groups = topk_length_dynamic // self.cluster_tile_n
|
| 1836 |
+
|
| 1837 |
+
mdS_cur = seqlen.offset_batch_Q(mdS, batch_idx, dim=3)[None, None, head_idx]
|
| 1838 |
+
|
| 1839 |
+
gdS = cute.local_tile(mdS_cur, (self.tile_m, self.tile_n), (m_block, None))
|
| 1840 |
+
store_dS, _, _ = copy_utils.tma_get_copy_fn(
|
| 1841 |
+
tma_atom_dS,
|
| 1842 |
+
0,
|
| 1843 |
+
cute.make_layout(1),
|
| 1844 |
+
sdS,
|
| 1845 |
+
gdS,
|
| 1846 |
+
)
|
| 1847 |
+
|
| 1848 |
+
pipeline_dPsum.consumer_wait(consumer_state_dPsum)
|
| 1849 |
+
|
| 1850 |
+
tdPsdPsum_cur = tdPsdPsum_nm[0, None, consumer_state_dPsum.index]
|
| 1851 |
+
tdPrdPsum_cur_f32 = cute.make_rmem_tensor(tdPsdPsum_cur.shape, dtype=self.dtype_scale)
|
| 1852 |
+
cute.autovec_copy(tdPsdPsum_cur, tdPrdPsum_cur_f32)
|
| 1853 |
+
|
| 1854 |
+
for n_block_group in cutlass.range(num_n_block_groups, unroll=1):
|
| 1855 |
+
n_block = 2 * n_block_group + cta_rank_in_cluster
|
| 1856 |
+
|
| 1857 |
+
pipeline_P.consumer_wait(consumer_state_P)
|
| 1858 |
+
# todo: ablate wait -> try_wait
|
| 1859 |
+
pipeline_scaleP.consumer_wait(consumer_state_scaleP)
|
| 1860 |
+
|
| 1861 |
+
# (((2,2,8),1),2,1):(((1,2,4),0),32,0)
|
| 1862 |
+
rPt = cute.make_rmem_tensor(tdPcdP_t2r.shape, self.dtype)
|
| 1863 |
+
# (S2R, S2R_M, S2R_N)
|
| 1864 |
+
rPt_copy_view = tiled_copy_s2r.retile(rPt)
|
| 1865 |
+
tSR_sPt_cur = tSR_sPt[None, None, None, consumer_state_P.index]
|
| 1866 |
+
cute.copy(tiled_copy_s2r, tSR_sPt_cur, rPt_copy_view)
|
| 1867 |
+
|
| 1868 |
+
# ((2,2),(2,8,1)):((2,32),(1,4,0))
|
| 1869 |
+
rP_nm = layout_utils.reshape_acc_to_mn(rPt[(None, 0), None, None])
|
| 1870 |
+
|
| 1871 |
+
tPsScaleP_cur = tPsScaleP_nm[0, None, consumer_state_scaleP.index]
|
| 1872 |
+
tPrScaleP_cur_f32 = cute.make_rmem_tensor(
|
| 1873 |
+
tPsScaleP_cur.shape, dtype=self.dtype_scale
|
| 1874 |
+
)
|
| 1875 |
+
tPrScaleP_cur = cute.make_rmem_tensor(tPsScaleP_cur.shape, dtype=self.dtype)
|
| 1876 |
+
cute.autovec_copy(tPsScaleP_cur, tPrScaleP_cur_f32)
|
| 1877 |
+
tPrScaleP_cur.store(tPrScaleP_cur_f32.load().to(self.dtype))
|
| 1878 |
+
|
| 1879 |
+
# scale P
|
| 1880 |
+
for n in cutlass.range_constexpr(cute.size(rP_nm.shape[0])):
|
| 1881 |
+
rP_cur = rP_nm[n, None]
|
| 1882 |
+
rP_cur.store(rP_cur.load() * tPrScaleP_cur.load())
|
| 1883 |
+
cute.arch.sync_warp()
|
| 1884 |
+
|
| 1885 |
+
cute.copy(tiled_copy_r2s, rPt_copy_view, tSR_sPt_cur)
|
| 1886 |
+
cute.arch.fence_view_async_shared()
|
| 1887 |
+
self.softmax_barrier.arrive_and_wait()
|
| 1888 |
+
|
| 1889 |
+
pipeline_scaleP.consumer_release(consumer_state_scaleP)
|
| 1890 |
+
consumer_state_scaleP.advance()
|
| 1891 |
+
|
| 1892 |
+
pipeline_Pt.producer_commit(producer_state_Pt)
|
| 1893 |
+
producer_state_Pt.advance()
|
| 1894 |
+
|
| 1895 |
+
# note: mma also signals Pt free, signal acquired in tma warp
|
| 1896 |
+
pipeline_P.consumer_release(consumer_state_P)
|
| 1897 |
+
consumer_state_P.advance()
|
| 1898 |
+
|
| 1899 |
+
pipeline_dPt.consumer_wait(consumer_state_dPt)
|
| 1900 |
+
|
| 1901 |
+
# (((2,2,8),1),2,1):(((1,2,4),0),32,0)
|
| 1902 |
+
tdPrdP_t2r = cute.make_rmem_tensor(tdPcdP_t2r.shape, self.dtype_acc)
|
| 1903 |
+
cute.copy(tiled_copy_t2r, tdPtdP_t2r, tdPrdP_t2r)
|
| 1904 |
+
cute.arch.fence_view_async_tmem_load()
|
| 1905 |
+
self.softmax_barrier.arrive_and_wait()
|
| 1906 |
+
|
| 1907 |
+
pipeline_dPt.consumer_release(consumer_state_dPt)
|
| 1908 |
+
consumer_state_dPt.advance()
|
| 1909 |
+
|
| 1910 |
+
# dS = P o (dP - dPsum)
|
| 1911 |
+
rdP_nm = layout_utils.reshape_acc_to_mn(tdPrdP_t2r[(None, 0), None, None])
|
| 1912 |
+
for n in cutlass.range_constexpr(cute.size(rdP_nm.shape[0])):
|
| 1913 |
+
rdP_cur = rdP_nm[n, None]
|
| 1914 |
+
rdP_cur.store(rdP_cur.load() - tdPrdPsum_cur_f32.load())
|
| 1915 |
+
|
| 1916 |
+
rPt.store(rPt.load() * (tdPrdP_t2r.load() * softmax_scale).to(self.dtype))
|
| 1917 |
+
|
| 1918 |
+
# wait for tma store to free dSt buffer
|
| 1919 |
+
if leader_warp:
|
| 1920 |
+
cute.arch.cp_async_bulk_wait_group(1 - self.num_stages_dSt, read=True)
|
| 1921 |
+
self.softmax_barrier.arrive_and_wait()
|
| 1922 |
+
|
| 1923 |
+
# note: dS guaranteed free as mma operand
|
| 1924 |
+
pipeline_dSt.producer_acquire(producer_state_dSt)
|
| 1925 |
+
|
| 1926 |
+
tRS_sdSt_cur = tRS_sdSt[None, None, None, producer_state_dSt.index]
|
| 1927 |
+
cute.copy(tiled_copy_r2s, rPt_copy_view, tRS_sdSt_cur)
|
| 1928 |
+
|
| 1929 |
+
cute.arch.fence_view_async_shared()
|
| 1930 |
+
self.softmax_barrier.arrive_and_wait()
|
| 1931 |
+
pipeline_dSt.producer_commit(producer_state_dSt)
|
| 1932 |
+
|
| 1933 |
+
# tma store
|
| 1934 |
+
if leader_warp:
|
| 1935 |
+
store_dS(src_idx=producer_state_dSt.index, dst_idx=n_block)
|
| 1936 |
+
cute.arch.cp_async_bulk_commit_group()
|
| 1937 |
+
|
| 1938 |
+
producer_state_dSt.advance()
|
| 1939 |
+
|
| 1940 |
+
pipeline_dPsum.consumer_release(consumer_state_dPsum)
|
| 1941 |
+
consumer_state_dPsum.advance()
|
| 1942 |
+
|
| 1943 |
+
# Advance to next tile
|
| 1944 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
| 1945 |
+
|
| 1946 |
+
# producer tails
|
| 1947 |
+
|
| 1948 |
+
@cute.jit
|
| 1949 |
+
def broadcast_tensor_nm_view(
|
| 1950 |
+
self,
|
| 1951 |
+
sX: cute.Tensor, # (tile_m, num_stages)
|
| 1952 |
+
thr_mma: cute.ThrMma,
|
| 1953 |
+
thr_copy_t2r: cute.ThrCopy,
|
| 1954 |
+
):
|
| 1955 |
+
assert cute.size(sX.shape[0]) == self.tile_m
|
| 1956 |
+
num_stages = sX.shape[1] if const_expr(cute.rank(sX) > 1) else 1
|
| 1957 |
+
sX_2D_cluster = cute.make_tensor(
|
| 1958 |
+
sX.iterator,
|
| 1959 |
+
cute.make_layout(
|
| 1960 |
+
(self.tile_m, self.cluster_tile_n, num_stages),
|
| 1961 |
+
stride=(1, 0, self.tile_m),
|
| 1962 |
+
),
|
| 1963 |
+
)
|
| 1964 |
+
sXt_2D_cluster = layout_utils.transpose_view(sX_2D_cluster)
|
| 1965 |
+
sXt_2D = thr_mma.partition_C(sXt_2D_cluster)[(None, None), 0, 0, None]
|
| 1966 |
+
tXsXt_2D = thr_copy_t2r.partition_D(sXt_2D)[(None, 0), None, None, None]
|
| 1967 |
+
tXsXt_nm = layout_utils.make_acc_tensor_mn_view(tXsXt_2D)
|
| 1968 |
+
return tXsXt_nm
|
| 1969 |
+
|
| 1970 |
+
@cute.jit
|
| 1971 |
+
def dVacc_store(
|
| 1972 |
+
self,
|
| 1973 |
+
mIndexTopk: cute.Tensor,
|
| 1974 |
+
mdV: cute.Tensor,
|
| 1975 |
+
sdV: cute.Tensor,
|
| 1976 |
+
tdVtdV0: cute.Tensor,
|
| 1977 |
+
tdVtdV1: cute.Tensor,
|
| 1978 |
+
thr_mma_PtdOt: cute.ThrMma,
|
| 1979 |
+
pipeline_dV: pipeline.PipelineAsync, # UmmaAsync
|
| 1980 |
+
pipeline_dV_epi: pipeline.PipelineAsync, # Async
|
| 1981 |
+
topk_length_dynamic: Optional[Int32],
|
| 1982 |
+
block_info: BlockInfo,
|
| 1983 |
+
SeqlenInfoCls: Callable,
|
| 1984 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 1985 |
+
tile_scheduler: TileSchedulerProtocol,
|
| 1986 |
+
):
|
| 1987 |
+
# ==== dVaccum store warpgroup ====
|
| 1988 |
+
# produces: -
|
| 1989 |
+
# consumes: dV
|
| 1990 |
+
|
| 1991 |
+
tdVtdV0 = tdVtdV0[(None, None), 0, 0]
|
| 1992 |
+
tdVtdV1 = tdVtdV1[(None, None), 0, 0]
|
| 1993 |
+
|
| 1994 |
+
num_epi_warps = self.num_epilogue_threads // 32
|
| 1995 |
+
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 1996 |
+
tidx = cute.arch.thread_idx()[0] % self.num_epilogue_threads
|
| 1997 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % num_epi_warps
|
| 1998 |
+
leader_warp = warp_idx == 0
|
| 1999 |
+
wg_half = warp_idx // 2
|
| 2000 |
+
|
| 2001 |
+
consumer_state_dV = pipeline.make_pipeline_state(
|
| 2002 |
+
pipeline.PipelineUserType.Consumer, stages=self.num_stages_dV
|
| 2003 |
+
)
|
| 2004 |
+
|
| 2005 |
+
copy_atom_t2r = cute.make_copy_atom(
|
| 2006 |
+
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(32)),
|
| 2007 |
+
self.dtype_acc,
|
| 2008 |
+
)
|
| 2009 |
+
tiled_copy_t2r = tcgen05.make_tmem_copy(copy_atom_t2r, tdVtdV0)
|
| 2010 |
+
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 2011 |
+
tdVtdV0_t2r = thr_copy_t2r.partition_S(tdVtdV0)
|
| 2012 |
+
tdVtdV1_t2r = thr_copy_t2r.partition_S(tdVtdV1)
|
| 2013 |
+
tdVtdVs_t2r = [tdVtdV0_t2r, tdVtdV1_t2r]
|
| 2014 |
+
|
| 2015 |
+
cdVmma = cute.make_identity_tensor(self.mma_tiler_PtdOt[:2])
|
| 2016 |
+
tdVcdVmma = thr_mma_PtdOt.partition_C(cdVmma)[(None, None), 0, 0]
|
| 2017 |
+
tdVcdVmma_t2r = thr_copy_t2r.partition_D(tdVcdVmma)
|
| 2018 |
+
|
| 2019 |
+
# 64 threads x 4 values to tile over tile_dV = (64, 32)
|
| 2020 |
+
tiled_copy_r2s = tiled_copy_2d(self.dtype_acc, 4, 64)
|
| 2021 |
+
thr_copy_r2s = tiled_copy_r2s.get_slice(tidx % 64)
|
| 2022 |
+
|
| 2023 |
+
# ((4,1),1,8,(1,8)):((1,0),0,4,(0,2048))
|
| 2024 |
+
tRS_sdV = thr_copy_r2s.partition_D(sdV)
|
| 2025 |
+
|
| 2026 |
+
tiled_copy_s2r = copy_utils.tiled_copy_2d(self.dtype_acc, 8, self.num_epilogue_threads, 4)
|
| 2027 |
+
thr_copy_s2r = tiled_copy_s2r.get_slice(tidx)
|
| 2028 |
+
# (V, M, N, STAGE)
|
| 2029 |
+
tSR_sdV = thr_copy_s2r.partition_S(sdV)
|
| 2030 |
+
|
| 2031 |
+
cdV = cute.make_identity_tensor(cute.product_each(sdV.shape[:2]))
|
| 2032 |
+
# (V, M, N)
|
| 2033 |
+
tdVcdV = thr_copy_s2r.partition_S(cdV)
|
| 2034 |
+
|
| 2035 |
+
gmem_rows_per_thread = cute.size(tSR_sdV.shape[1])
|
| 2036 |
+
|
| 2037 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 2038 |
+
while work_tile.is_valid_tile:
|
| 2039 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 2040 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 2041 |
+
batch_idx = get_batch_from_cu_tensor(m_block, mCuSeqlensQ)
|
| 2042 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 2043 |
+
num_n_block_groups = self.topk_length // self.cluster_tile_n
|
| 2044 |
+
# num_n_block_groups = topk_length_dynamic // self.cluster_tile_n
|
| 2045 |
+
|
| 2046 |
+
# (seqlen_k, hdimv)
|
| 2047 |
+
mdV_cur = seqlen.offset_batch_K(mdV, batch_idx, dim=3)[None, None, head_idx]
|
| 2048 |
+
|
| 2049 |
+
# (topk, dv)
|
| 2050 |
+
if const_expr(seqlen.has_cu_seqlens_q):
|
| 2051 |
+
# m_block means absolute m_idx
|
| 2052 |
+
mIndexTopk_cur = mIndexTopk[None, m_block]
|
| 2053 |
+
else:
|
| 2054 |
+
mIndexTopk_cur = mIndexTopk[None, m_block, batch_idx]
|
| 2055 |
+
|
| 2056 |
+
# ==== Mainloop ====
|
| 2057 |
+
for n_block_group in cutlass.range(num_n_block_groups, unroll=1):
|
| 2058 |
+
n_block = 2 * n_block_group + cta_rank_in_cluster
|
| 2059 |
+
|
| 2060 |
+
rIdxTopK = cute.make_rmem_tensor((gmem_rows_per_thread,), dtype=self.dtype_index)
|
| 2061 |
+
for j in cutlass.range_constexpr(gmem_rows_per_thread):
|
| 2062 |
+
n_idx = n_block * self.tile_n + tdVcdV[0, j, 0][0]
|
| 2063 |
+
rIdxTopK[j] = mIndexTopk_cur[n_idx]
|
| 2064 |
+
|
| 2065 |
+
for split in cutlass.range_constexpr(self.num_hdimv_splits):
|
| 2066 |
+
tdVtdV_t2r = tdVtdVs_t2r[split]
|
| 2067 |
+
|
| 2068 |
+
pipeline_dV.consumer_wait(consumer_state_dV)
|
| 2069 |
+
|
| 2070 |
+
# TODO: record meaning of hard-coded values
|
| 2071 |
+
num_cols_per_store = self.tile_dV[1] * 2
|
| 2072 |
+
num_epi_subtiles = (self.hdimv // self.num_hdimv_splits) // num_cols_per_store
|
| 2073 |
+
assert num_cols_per_store == 64
|
| 2074 |
+
assert num_epi_subtiles == 4
|
| 2075 |
+
assert cute.size(tdVtdV_t2r.shape[2]) == num_epi_subtiles
|
| 2076 |
+
|
| 2077 |
+
tdVrdV_cur_shape = tdVcdVmma_t2r[None, None, 0].shape
|
| 2078 |
+
tRS_rdV_cur_shape = tRS_sdV[None, None, None, 0].shape
|
| 2079 |
+
assert cute.size(tdVrdV_cur_shape) == cute.size(tRS_rdV_cur_shape)
|
| 2080 |
+
|
| 2081 |
+
tdVrdV_out_shape = tSR_sdV[None, None, None, 0].shape + (2,)
|
| 2082 |
+
|
| 2083 |
+
for i in cutlass.range_constexpr(num_epi_subtiles):
|
| 2084 |
+
tdVrdV_cur = cute.make_rmem_tensor(tdVrdV_cur_shape, self.dtype_acc)
|
| 2085 |
+
cute.copy(tiled_copy_t2r, tdVtdV_t2r[None, None, i], tdVrdV_cur)
|
| 2086 |
+
|
| 2087 |
+
tRS_rdV_cur = cute.make_tensor(tdVrdV_cur.iterator, tRS_rdV_cur_shape)
|
| 2088 |
+
|
| 2089 |
+
stage = 4 * split + 2 * wg_half + (i % 2)
|
| 2090 |
+
cute.copy(tiled_copy_r2s, tRS_rdV_cur, tRS_sdV[None, None, None, stage])
|
| 2091 |
+
cute.arch.fence_view_async_shared()
|
| 2092 |
+
self.epi_barrier.arrive_and_wait()
|
| 2093 |
+
|
| 2094 |
+
tSR_rdV = cute.make_rmem_tensor(tdVrdV_out_shape, dtype=self.dtype_acc)
|
| 2095 |
+
|
| 2096 |
+
for w in cutlass.range_constexpr(2):
|
| 2097 |
+
stage_out = 4 * split + 2 * w + (i % 2)
|
| 2098 |
+
cute.copy(
|
| 2099 |
+
tiled_copy_s2r,
|
| 2100 |
+
tSR_sdV[None, None, None, stage_out],
|
| 2101 |
+
tSR_rdV[None, None, None, w],
|
| 2102 |
+
)
|
| 2103 |
+
|
| 2104 |
+
for j in cutlass.range_constexpr(gmem_rows_per_thread):
|
| 2105 |
+
gmem_n_idx = rIdxTopK[j]
|
| 2106 |
+
# Skip -1 sentinel slots (invalid top-k entries)
|
| 2107 |
+
if gmem_n_idx >= 0:
|
| 2108 |
+
for w in cutlass.range_constexpr(2):
|
| 2109 |
+
dv_offset = (
|
| 2110 |
+
self.hdimv // self.num_hdimv_splits * split # 256 * split
|
| 2111 |
+
+ (self.hdimv // self.num_hdimv_splits // 2) * w # 128 * w
|
| 2112 |
+
+ 32 * i
|
| 2113 |
+
)
|
| 2114 |
+
dv_offset += tdVcdV[0, j, 0][1]
|
| 2115 |
+
gmem_coord = (gmem_n_idx, dv_offset)
|
| 2116 |
+
dV_gmem_ptr = elem_pointer(mdV_cur, gmem_coord)
|
| 2117 |
+
|
| 2118 |
+
a = tSR_rdV[0, j, 0, w]
|
| 2119 |
+
b = tSR_rdV[1, j, 0, w]
|
| 2120 |
+
c = tSR_rdV[2, j, 0, w]
|
| 2121 |
+
d = tSR_rdV[3, j, 0, w]
|
| 2122 |
+
atomic_add_fp32x4(a, b, c, d, dV_gmem_ptr)
|
| 2123 |
+
|
| 2124 |
+
cute.arch.fence_view_async_tmem_load()
|
| 2125 |
+
self.epi_barrier.arrive_and_wait()
|
| 2126 |
+
pipeline_dV.consumer_release(consumer_state_dV)
|
| 2127 |
+
|
| 2128 |
+
if leader_warp:
|
| 2129 |
+
with cute.arch.elect_one():
|
| 2130 |
+
pipeline_dV_epi.consumer_release(consumer_state_dV)
|
| 2131 |
+
|
| 2132 |
+
consumer_state_dV.advance()
|
| 2133 |
+
|
| 2134 |
+
# Advance to next tile
|
| 2135 |
+
work_tile = tile_scheduler.advance_to_next_work()
|
build/torch-cuda/flash_bwd_postprocess.py
CHANGED
|
@@ -2,7 +2,8 @@
|
|
| 2 |
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_bwd_postprocess_kernel.h
|
| 3 |
# from Cutlass C++ to Cute-DSL.
|
| 4 |
import math
|
| 5 |
-
|
|
|
|
| 6 |
|
| 7 |
import cuda.bindings.driver as cuda
|
| 8 |
|
|
@@ -31,39 +32,49 @@ from .tile_scheduler import (
|
|
| 31 |
)
|
| 32 |
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
class FlashAttentionBackwardPostprocess:
|
| 35 |
def __init__(
|
| 36 |
self,
|
| 37 |
dtype: Type[cutlass.Numeric],
|
| 38 |
head_dim: int,
|
| 39 |
-
arch:
|
| 40 |
tile_m: int = 128,
|
| 41 |
num_threads: int = 256,
|
| 42 |
AtomLayoutMdQ: int = 1,
|
| 43 |
dQ_swapAB: bool = False,
|
| 44 |
use_2cta_instrs: bool = False,
|
| 45 |
cluster_size: int = 1, # for varlen offsets
|
|
|
|
| 46 |
):
|
| 47 |
"""
|
| 48 |
:param head_dim: head dimension
|
| 49 |
:type head_dim: int
|
| 50 |
:param tile_m: m block size
|
| 51 |
:type tile_m: int
|
|
|
|
| 52 |
"""
|
| 53 |
self.dtype = dtype
|
| 54 |
self.tile_m = tile_m
|
| 55 |
-
assert arch // 10 in [8, 9, 10, 11], (
|
| 56 |
-
"Only Ampere (8.x), Hopper (9.x), and Blackwell (10.x, 11.x) are supported"
|
| 57 |
)
|
| 58 |
self.arch = arch
|
| 59 |
-
# padding head_dim to a multiple of 32 as k_block_size
|
| 60 |
-
hdim_multiple_of = 32
|
| 61 |
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 62 |
self.check_hdim_oob = head_dim != self.tile_hdim
|
| 63 |
self.num_threads = num_threads
|
| 64 |
self.AtomLayoutMdQ = AtomLayoutMdQ
|
| 65 |
self.dQ_swapAB = dQ_swapAB
|
| 66 |
-
self.use_2cta_instrs = use_2cta_instrs and arch
|
| 67 |
self.cluster_size = cluster_size
|
| 68 |
|
| 69 |
@staticmethod
|
|
@@ -89,7 +100,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 89 |
return True
|
| 90 |
|
| 91 |
def _get_tiled_mma(self):
|
| 92 |
-
if const_expr(self.arch
|
| 93 |
num_mma_warps = self.num_threads // 32
|
| 94 |
atom_layout_dQ = (
|
| 95 |
(self.AtomLayoutMdQ, num_mma_warps // self.AtomLayoutMdQ, 1)
|
|
@@ -101,9 +112,9 @@ class FlashAttentionBackwardPostprocess:
|
|
| 101 |
atom_layout_dQ,
|
| 102 |
permutation_mnk=(atom_layout_dQ[0] * 16, atom_layout_dQ[1] * 16, 16),
|
| 103 |
)
|
| 104 |
-
elif const_expr(self.arch ==
|
| 105 |
-
|
| 106 |
-
atom_layout_dQ = (self.AtomLayoutMdQ,
|
| 107 |
tiler_mn_dQ = (self.tile_m // atom_layout_dQ[0], self.tile_hdim // atom_layout_dQ[1])
|
| 108 |
tiled_mma = sm90_utils_basic.make_trivial_tiled_mma(
|
| 109 |
self.dtype,
|
|
@@ -125,7 +136,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 125 |
cta_group,
|
| 126 |
(self.tile_m, self.tile_hdim),
|
| 127 |
)
|
| 128 |
-
if const_expr(self.arch in [
|
| 129 |
assert self.num_threads == tiled_mma.size
|
| 130 |
return tiled_mma
|
| 131 |
|
|
@@ -148,22 +159,22 @@ class FlashAttentionBackwardPostprocess:
|
|
| 148 |
cute.make_layout(self.num_threads),
|
| 149 |
cute.make_layout(async_copy_elems_accum),
|
| 150 |
)
|
| 151 |
-
num_s2r_copy_elems = 1 if const_expr(self.arch
|
| 152 |
-
if const_expr(self.arch
|
| 153 |
self.s2r_tiled_copy_dQaccum = copy_utils.tiled_copy_1d(
|
| 154 |
Float32, self.num_threads, num_s2r_copy_elems
|
| 155 |
)
|
| 156 |
self.sdQaccum_layout = cute.make_layout(self.tile_m * self.tile_hdim)
|
| 157 |
-
elif const_expr(self.arch ==
|
| 158 |
num_threads_per_warp_group = 128
|
| 159 |
-
|
| 160 |
self.s2r_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 161 |
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 162 |
-
cute.make_layout((num_threads_per_warp_group,
|
| 163 |
cute.make_layout(128 // Float32.width), # val_layout
|
| 164 |
)
|
| 165 |
self.sdQaccum_layout = cute.make_layout(
|
| 166 |
-
(self.tile_m * self.tile_hdim //
|
| 167 |
)
|
| 168 |
else:
|
| 169 |
self.dQ_reduce_ncol = 32
|
|
@@ -188,14 +199,18 @@ class FlashAttentionBackwardPostprocess:
|
|
| 188 |
# then setting kBlockKSmem to 32 will cause "Static shape_div failure".
|
| 189 |
# We want to treat it as 64 x 48, so kBlockKSmem should be 16.
|
| 190 |
mma_shape_n = self.tiled_mma.get_tile_size(1)
|
| 191 |
-
if const_expr(self.arch
|
| 192 |
sdQ_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, mma_shape_n)
|
| 193 |
self.sdQ_layout = cute.tile_to_shape(
|
| 194 |
sdQ_layout_atom, (self.tile_m, self.tile_hdim), (0, 1)
|
| 195 |
)
|
| 196 |
-
elif const_expr(self.arch ==
|
|
|
|
| 197 |
self.sdQ_layout = sm90_utils.make_smem_layout(
|
| 198 |
-
self.dtype,
|
|
|
|
|
|
|
|
|
|
| 199 |
)
|
| 200 |
else:
|
| 201 |
# TODO: this is hard-coded for hdim 128
|
|
@@ -211,7 +226,10 @@ class FlashAttentionBackwardPostprocess:
|
|
| 211 |
scale: cutlass.Float32,
|
| 212 |
mCuSeqlensQ: Optional[cute.Tensor],
|
| 213 |
mSeqUsedQ: Optional[cute.Tensor],
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
| 215 |
):
|
| 216 |
# Get the data type and check if it is fp16 or bf16
|
| 217 |
if const_expr(mdQ.element_type not in [cutlass.Float16, cutlass.BFloat16]):
|
|
@@ -219,6 +237,19 @@ class FlashAttentionBackwardPostprocess:
|
|
| 219 |
if const_expr(mdQaccum is not None):
|
| 220 |
if const_expr(mdQaccum.element_type not in [cutlass.Float32]):
|
| 221 |
raise TypeError("dQaccum tensor must be Float32")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
|
| 223 |
mdQaccum, mdQ = [assume_tensor_aligned(t) for t in (mdQaccum, mdQ)]
|
| 224 |
|
|
@@ -253,6 +284,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 253 |
tile_shape_mn=(self.tile_m, 1),
|
| 254 |
mCuSeqlensQ=mCuSeqlensQ,
|
| 255 |
mSeqUsedQ=mSeqUsedQ,
|
|
|
|
| 256 |
)
|
| 257 |
|
| 258 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
|
@@ -264,6 +296,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 264 |
mdQ,
|
| 265 |
mCuSeqlensQ,
|
| 266 |
mSeqUsedQ,
|
|
|
|
| 267 |
scale,
|
| 268 |
self.tiled_mma,
|
| 269 |
self.dQ_swapAB,
|
|
@@ -288,6 +321,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 288 |
mdQ: cute.Tensor,
|
| 289 |
mCuSeqlensQ: Optional[cute.Tensor],
|
| 290 |
mSeqUsedQ: Optional[cute.Tensor],
|
|
|
|
| 291 |
scale: cutlass.Float32,
|
| 292 |
tiled_mma: cute.TiledMma,
|
| 293 |
dQ_swapAB: cutlass.Constexpr,
|
|
@@ -305,7 +339,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 305 |
smem = cutlass.utils.SmemAllocator()
|
| 306 |
sdQaccum = smem.allocate_tensor(cutlass.Float32, sdQaccum_layout, byte_alignment=1024)
|
| 307 |
sdQaccum_flat = cute.make_tensor(sdQaccum.iterator, cute.make_layout(cute.size(sdQaccum)))
|
| 308 |
-
if const_expr(self.arch in [
|
| 309 |
sdQ = cute.make_tensor(cute.recast_ptr(sdQaccum.iterator, dtype=self.dtype), sdQ_layout)
|
| 310 |
else:
|
| 311 |
# extra stage dimension
|
|
@@ -323,6 +357,73 @@ class FlashAttentionBackwardPostprocess:
|
|
| 323 |
|
| 324 |
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
if work_tile.is_valid_tile:
|
| 327 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 328 |
# Get the appropriate tiles for this thread block.
|
|
@@ -343,10 +444,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 343 |
mdQaccum_cur = mdQaccum[batch_idx, head_idx, None]
|
| 344 |
head_dim = mdQ.shape[3]
|
| 345 |
else:
|
| 346 |
-
|
| 347 |
-
padded_offset_q = seqlen.padded_offset_q
|
| 348 |
-
else:
|
| 349 |
-
padded_offset_q = seqlen.offset_q + batch_idx * self.tile_m
|
| 350 |
mdQ_cur = cute.domain_offset((seqlen.offset_q, 0), mdQ[None, head_idx, None])
|
| 351 |
mdQaccum_cur = cute.domain_offset(
|
| 352 |
(padded_offset_q * self.tile_hdim,), mdQaccum[head_idx, None]
|
|
@@ -371,7 +469,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 371 |
seqlen_q = seqlen.seqlen_q
|
| 372 |
seqlen_q_rounded = cute.round_up(seqlen_q, self.tile_m)
|
| 373 |
|
| 374 |
-
if const_expr(self.arch
|
| 375 |
# 2-CTA: remap dQaccum layout into TMEM view before writing sdQ
|
| 376 |
num_reduce_threads = self.num_threads
|
| 377 |
thr_mma_dsk = tiled_mma.get_slice(tidx)
|
|
@@ -394,7 +492,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 394 |
g2s_thr_copy = tiled_copy_accum.get_slice(tidx)
|
| 395 |
|
| 396 |
# S -> R
|
| 397 |
-
tdQrdQ_fp32 = cute.
|
| 398 |
tdQrdQ_s2r = cute.make_tensor(tdQrdQ_fp32.iterator, tdQrdQ_fp32.shape)
|
| 399 |
|
| 400 |
smem_copy_atom = sm100_utils_basic.get_smem_store_op(
|
|
@@ -406,7 +504,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 406 |
tiler_mn=tiled_tmem_ld.tiler_mn,
|
| 407 |
)
|
| 408 |
tdQsdQ_r2s = thr_tmem_ld.partition_D(thr_mma_dsk.partition_C(sdQ))
|
| 409 |
-
tdQrdQ_r2s = cute.
|
| 410 |
|
| 411 |
num_stages = cute.size(tdQrdQ_fp32, mode=[1])
|
| 412 |
stage_stride = self.dQ_reduce_ncol
|
|
@@ -502,11 +600,11 @@ class FlashAttentionBackwardPostprocess:
|
|
| 502 |
tile_shape = (self.tile_m, self.tile_hdim)
|
| 503 |
acc = None
|
| 504 |
tiled_copy_t2r = None
|
| 505 |
-
if const_expr(self.arch in [
|
| 506 |
acc_shape = tiled_mma.partition_shape_C(
|
| 507 |
tile_shape if const_expr(not dQ_swapAB) else tile_shape[::-1]
|
| 508 |
)
|
| 509 |
-
acc = cute.
|
| 510 |
assert cute.size(acc) == cute.size(tdQsdQaccum)
|
| 511 |
else:
|
| 512 |
thr_mma = tiled_mma.get_slice(0) # 1-CTA
|
|
@@ -522,7 +620,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 522 |
tiled_copy_t2r = tcgen05.make_tmem_copy(tmem_load_atom, tdQtdQ)
|
| 523 |
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 524 |
tdQrdQ_t2r_shape = thr_copy_t2r.partition_D(tdQcdQ).shape
|
| 525 |
-
acc = cute.
|
| 526 |
tdQrdQaccum = cute.make_tensor(acc.iterator, cute.make_layout(tdQsdQaccum.shape))
|
| 527 |
cute.autovec_copy(tdQsdQaccum, tdQrdQaccum)
|
| 528 |
# Convert tdQrdQaccum from fp32 to fp16/bf16
|
|
@@ -531,7 +629,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 531 |
|
| 532 |
# Step 3: Copy dQ from register to smem
|
| 533 |
cute.arch.barrier() # make sure all threads have finished loading dQaccum
|
| 534 |
-
if const_expr(self.arch in [
|
| 535 |
copy_atom_r2s_dQ = utils.get_smem_store_atom(
|
| 536 |
self.arch, self.dtype, transpose=self.dQ_swapAB
|
| 537 |
)
|
|
@@ -553,7 +651,7 @@ class FlashAttentionBackwardPostprocess:
|
|
| 553 |
)
|
| 554 |
thr_copy_r2s_dQ = tiled_copy_r2s_dQ.get_slice(tidx)
|
| 555 |
cdQ = cute.make_identity_tensor((self.tile_m, self.tile_hdim))
|
| 556 |
-
if const_expr(self.arch in [
|
| 557 |
taccdQrdQ = thr_copy_r2s_dQ.retile(rdQ)
|
| 558 |
else:
|
| 559 |
taccdQcdQ_shape = thr_copy_r2s_dQ.partition_S(cdQ).shape
|
|
|
|
| 2 |
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_bwd_postprocess_kernel.h
|
| 3 |
# from Cutlass C++ to Cute-DSL.
|
| 4 |
import math
|
| 5 |
+
import operator
|
| 6 |
+
from typing import Callable, NamedTuple, Optional, Type
|
| 7 |
|
| 8 |
import cuda.bindings.driver as cuda
|
| 9 |
|
|
|
|
| 32 |
)
|
| 33 |
|
| 34 |
|
| 35 |
+
class LearnableSinkBwdTensors(NamedTuple):
|
| 36 |
+
dpsum: cute.Tensor
|
| 37 |
+
lse: cute.Tensor
|
| 38 |
+
sink: cute.Tensor
|
| 39 |
+
dsink: cute.Tensor
|
| 40 |
+
|
| 41 |
+
def __new_from_mlir_values__(self, values):
|
| 42 |
+
return LearnableSinkBwdTensors(*values)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
class FlashAttentionBackwardPostprocess:
|
| 46 |
def __init__(
|
| 47 |
self,
|
| 48 |
dtype: Type[cutlass.Numeric],
|
| 49 |
head_dim: int,
|
| 50 |
+
arch: int,
|
| 51 |
tile_m: int = 128,
|
| 52 |
num_threads: int = 256,
|
| 53 |
AtomLayoutMdQ: int = 1,
|
| 54 |
dQ_swapAB: bool = False,
|
| 55 |
use_2cta_instrs: bool = False,
|
| 56 |
cluster_size: int = 1, # for varlen offsets
|
| 57 |
+
hdim_multiple_of: int = 32,
|
| 58 |
):
|
| 59 |
"""
|
| 60 |
:param head_dim: head dimension
|
| 61 |
:type head_dim: int
|
| 62 |
:param tile_m: m block size
|
| 63 |
:type tile_m: int
|
| 64 |
+
:param hdim_multiple_of: accumulator alignment shared with the main backward kernel.
|
| 65 |
"""
|
| 66 |
self.dtype = dtype
|
| 67 |
self.tile_m = tile_m
|
| 68 |
+
assert arch // 10 in [8, 9, 10, 11, 12], (
|
| 69 |
+
"Only Ampere (8.x), Hopper (9.x), and Blackwell (10.x, 11.x, 12.x) are supported"
|
| 70 |
)
|
| 71 |
self.arch = arch
|
|
|
|
|
|
|
| 72 |
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 73 |
self.check_hdim_oob = head_dim != self.tile_hdim
|
| 74 |
self.num_threads = num_threads
|
| 75 |
self.AtomLayoutMdQ = AtomLayoutMdQ
|
| 76 |
self.dQ_swapAB = dQ_swapAB
|
| 77 |
+
self.use_2cta_instrs = use_2cta_instrs and arch // 10 in [10, 11] and head_dim != 64
|
| 78 |
self.cluster_size = cluster_size
|
| 79 |
|
| 80 |
@staticmethod
|
|
|
|
| 100 |
return True
|
| 101 |
|
| 102 |
def _get_tiled_mma(self):
|
| 103 |
+
if const_expr(self.arch // 10 in [8, 12]):
|
| 104 |
num_mma_warps = self.num_threads // 32
|
| 105 |
atom_layout_dQ = (
|
| 106 |
(self.AtomLayoutMdQ, num_mma_warps // self.AtomLayoutMdQ, 1)
|
|
|
|
| 112 |
atom_layout_dQ,
|
| 113 |
permutation_mnk=(atom_layout_dQ[0] * 16, atom_layout_dQ[1] * 16, 16),
|
| 114 |
)
|
| 115 |
+
elif const_expr(self.arch // 10 == 9):
|
| 116 |
+
num_wg_mma = self.num_threads // 128
|
| 117 |
+
atom_layout_dQ = (self.AtomLayoutMdQ, num_wg_mma // self.AtomLayoutMdQ)
|
| 118 |
tiler_mn_dQ = (self.tile_m // atom_layout_dQ[0], self.tile_hdim // atom_layout_dQ[1])
|
| 119 |
tiled_mma = sm90_utils_basic.make_trivial_tiled_mma(
|
| 120 |
self.dtype,
|
|
|
|
| 136 |
cta_group,
|
| 137 |
(self.tile_m, self.tile_hdim),
|
| 138 |
)
|
| 139 |
+
if const_expr(self.arch // 10 in [8, 9, 12]):
|
| 140 |
assert self.num_threads == tiled_mma.size
|
| 141 |
return tiled_mma
|
| 142 |
|
|
|
|
| 159 |
cute.make_layout(self.num_threads),
|
| 160 |
cute.make_layout(async_copy_elems_accum),
|
| 161 |
)
|
| 162 |
+
num_s2r_copy_elems = 1 if const_expr(self.arch // 10 in [8, 12]) else 4
|
| 163 |
+
if const_expr(self.arch // 10 in [8, 12]):
|
| 164 |
self.s2r_tiled_copy_dQaccum = copy_utils.tiled_copy_1d(
|
| 165 |
Float32, self.num_threads, num_s2r_copy_elems
|
| 166 |
)
|
| 167 |
self.sdQaccum_layout = cute.make_layout(self.tile_m * self.tile_hdim)
|
| 168 |
+
elif const_expr(self.arch // 10 == 9):
|
| 169 |
num_threads_per_warp_group = 128
|
| 170 |
+
num_wg_mma = self.num_threads // 128
|
| 171 |
self.s2r_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 172 |
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 173 |
+
cute.make_layout((num_threads_per_warp_group, num_wg_mma)), # thr_layout
|
| 174 |
cute.make_layout(128 // Float32.width), # val_layout
|
| 175 |
)
|
| 176 |
self.sdQaccum_layout = cute.make_layout(
|
| 177 |
+
(self.tile_m * self.tile_hdim // num_wg_mma, num_wg_mma)
|
| 178 |
)
|
| 179 |
else:
|
| 180 |
self.dQ_reduce_ncol = 32
|
|
|
|
| 199 |
# then setting kBlockKSmem to 32 will cause "Static shape_div failure".
|
| 200 |
# We want to treat it as 64 x 48, so kBlockKSmem should be 16.
|
| 201 |
mma_shape_n = self.tiled_mma.get_tile_size(1)
|
| 202 |
+
if const_expr(self.arch // 10 in [8, 12]):
|
| 203 |
sdQ_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, mma_shape_n)
|
| 204 |
self.sdQ_layout = cute.tile_to_shape(
|
| 205 |
sdQ_layout_atom, (self.tile_m, self.tile_hdim), (0, 1)
|
| 206 |
)
|
| 207 |
+
elif const_expr(self.arch // 10 == 9):
|
| 208 |
+
wg_d_dQ = num_wg_mma // self.AtomLayoutMdQ
|
| 209 |
self.sdQ_layout = sm90_utils.make_smem_layout(
|
| 210 |
+
self.dtype,
|
| 211 |
+
LayoutEnum.ROW_MAJOR,
|
| 212 |
+
(self.tile_m, self.tile_hdim),
|
| 213 |
+
major_mode_size=self.tile_hdim // wg_d_dQ,
|
| 214 |
)
|
| 215 |
else:
|
| 216 |
# TODO: this is hard-coded for hdim 128
|
|
|
|
| 226 |
scale: cutlass.Float32,
|
| 227 |
mCuSeqlensQ: Optional[cute.Tensor],
|
| 228 |
mSeqUsedQ: Optional[cute.Tensor],
|
| 229 |
+
sink_tensors: LearnableSinkBwdTensors | None,
|
| 230 |
+
mCuTotalMBlocks: Optional[cute.Tensor] = None,
|
| 231 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 232 |
+
stream: cuda.CUstream = None,
|
| 233 |
):
|
| 234 |
# Get the data type and check if it is fp16 or bf16
|
| 235 |
if const_expr(mdQ.element_type not in [cutlass.Float16, cutlass.BFloat16]):
|
|
|
|
| 237 |
if const_expr(mdQaccum is not None):
|
| 238 |
if const_expr(mdQaccum.element_type not in [cutlass.Float32]):
|
| 239 |
raise TypeError("dQaccum tensor must be Float32")
|
| 240 |
+
if const_expr(sink_tensors is not None):
|
| 241 |
+
mdPsum, mLSE, mLearnableSink, mdSink = sink_tensors
|
| 242 |
+
if const_expr(
|
| 243 |
+
mLearnableSink.element_type
|
| 244 |
+
not in [cutlass.Float16, cutlass.BFloat16, cutlass.Float32]
|
| 245 |
+
):
|
| 246 |
+
raise TypeError("Learnable sink tensor must be Float16, BFloat16, or Float32")
|
| 247 |
+
if const_expr(mdPsum.element_type not in [cutlass.Float32]):
|
| 248 |
+
raise TypeError("dPsum must be Float32")
|
| 249 |
+
if const_expr(mLSE.element_type not in [cutlass.Float32]):
|
| 250 |
+
raise TypeError("LSE must be Float32")
|
| 251 |
+
if const_expr(mdSink.element_type != mLearnableSink.element_type):
|
| 252 |
+
raise TypeError("dSink must have the learnable sink dtype")
|
| 253 |
|
| 254 |
mdQaccum, mdQ = [assume_tensor_aligned(t) for t in (mdQaccum, mdQ)]
|
| 255 |
|
|
|
|
| 284 |
tile_shape_mn=(self.tile_m, 1),
|
| 285 |
mCuSeqlensQ=mCuSeqlensQ,
|
| 286 |
mSeqUsedQ=mSeqUsedQ,
|
| 287 |
+
cu_total_m_blocks_ptr=mCuTotalMBlocks,
|
| 288 |
)
|
| 289 |
|
| 290 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
|
|
|
| 296 |
mdQ,
|
| 297 |
mCuSeqlensQ,
|
| 298 |
mSeqUsedQ,
|
| 299 |
+
sink_tensors,
|
| 300 |
scale,
|
| 301 |
self.tiled_mma,
|
| 302 |
self.dQ_swapAB,
|
|
|
|
| 321 |
mdQ: cute.Tensor,
|
| 322 |
mCuSeqlensQ: Optional[cute.Tensor],
|
| 323 |
mSeqUsedQ: Optional[cute.Tensor],
|
| 324 |
+
sink_tensors: LearnableSinkBwdTensors | None,
|
| 325 |
scale: cutlass.Float32,
|
| 326 |
tiled_mma: cute.TiledMma,
|
| 327 |
dQ_swapAB: cutlass.Constexpr,
|
|
|
|
| 339 |
smem = cutlass.utils.SmemAllocator()
|
| 340 |
sdQaccum = smem.allocate_tensor(cutlass.Float32, sdQaccum_layout, byte_alignment=1024)
|
| 341 |
sdQaccum_flat = cute.make_tensor(sdQaccum.iterator, cute.make_layout(cute.size(sdQaccum)))
|
| 342 |
+
if const_expr(self.arch // 10 in [8, 9, 12]):
|
| 343 |
sdQ = cute.make_tensor(cute.recast_ptr(sdQaccum.iterator, dtype=self.dtype), sdQ_layout)
|
| 344 |
else:
|
| 345 |
# extra stage dimension
|
|
|
|
| 357 |
|
| 358 |
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 359 |
|
| 360 |
+
# Reuse one existing dQ postprocess CTA per head to reduce dSink from
|
| 361 |
+
# the per-row dPsum and LSE written by backward preprocess. This avoids
|
| 362 |
+
# both a global atomic accumulator and a separate zero-initialization.
|
| 363 |
+
if const_expr(sink_tensors is not None):
|
| 364 |
+
mdPsum, mLSE, mLearnableSink, mdSink = sink_tensors
|
| 365 |
+
block_x, block_y, block_z = cute.arch.block_idx()
|
| 366 |
+
sink_head_idx = head_idx if const_expr(mCuSeqlensQ is None) else block_x
|
| 367 |
+
# Varlen uses block_x to select one CTA per head. block_y and block_z
|
| 368 |
+
# are currently always zero, but check them defensively.
|
| 369 |
+
reduce_sink = (
|
| 370 |
+
m_block == 0 and batch_idx == 0
|
| 371 |
+
if const_expr(mCuSeqlensQ is None)
|
| 372 |
+
else block_x < mdSink.shape[0] and block_y == 0 and block_z == 0
|
| 373 |
+
)
|
| 374 |
+
if reduce_sink:
|
| 375 |
+
sink_sum = Float32(0.0)
|
| 376 |
+
num_batch = (
|
| 377 |
+
mdQ.shape[0] if const_expr(mCuSeqlensQ is None) else mCuSeqlensQ.shape[0] - 1
|
| 378 |
+
)
|
| 379 |
+
sink_val = Float32(mLearnableSink[sink_head_idx])
|
| 380 |
+
sink_batch = 0
|
| 381 |
+
while sink_batch < num_batch:
|
| 382 |
+
sink_seqlen = SeqlenInfoQK.create(
|
| 383 |
+
sink_batch,
|
| 384 |
+
mdQ.shape[1],
|
| 385 |
+
0,
|
| 386 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 387 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 388 |
+
tile_m=self.tile_m * self.cluster_size,
|
| 389 |
+
)
|
| 390 |
+
sink_row = tidx
|
| 391 |
+
while sink_row < sink_seqlen.seqlen_q:
|
| 392 |
+
if const_expr(mCuSeqlensQ is None):
|
| 393 |
+
dpsum_val = mdPsum[sink_batch, sink_head_idx, sink_row]
|
| 394 |
+
lse_val = mLSE[sink_batch, sink_head_idx, sink_row]
|
| 395 |
+
else:
|
| 396 |
+
dpsum_val = mdPsum[
|
| 397 |
+
sink_head_idx, sink_seqlen.padded_offset_q + sink_row
|
| 398 |
+
]
|
| 399 |
+
lse_val = mLSE[sink_head_idx, sink_seqlen.offset_q + sink_row]
|
| 400 |
+
lse_val = Float32(lse_val)
|
| 401 |
+
sink_prob = (
|
| 402 |
+
Float32(1.0)
|
| 403 |
+
if lse_val == -Float32.inf
|
| 404 |
+
else cute.math.exp2(
|
| 405 |
+
(sink_val - lse_val) * utils.LOG2_E,
|
| 406 |
+
fastmath=True,
|
| 407 |
+
)
|
| 408 |
+
)
|
| 409 |
+
sink_sum += -sink_prob * Float32(dpsum_val)
|
| 410 |
+
sink_row += self.num_threads
|
| 411 |
+
sink_batch += 1
|
| 412 |
+
|
| 413 |
+
sink_sum = utils.warp_reduce(sink_sum, operator.add)
|
| 414 |
+
lane_idx = cute.arch.lane_idx()
|
| 415 |
+
warp_idx = tidx // cute.arch.WARP_SIZE
|
| 416 |
+
num_warps = self.num_threads // cute.arch.WARP_SIZE
|
| 417 |
+
if lane_idx == 0:
|
| 418 |
+
sdQaccum_flat[warp_idx] = sink_sum
|
| 419 |
+
cute.arch.sync_threads()
|
| 420 |
+
if warp_idx == 0:
|
| 421 |
+
sink_sum = sdQaccum_flat[lane_idx] if lane_idx < num_warps else Float32(0.0)
|
| 422 |
+
sink_sum = utils.warp_reduce(sink_sum, operator.add)
|
| 423 |
+
if lane_idx == 0:
|
| 424 |
+
mdSink[sink_head_idx] = sink_sum.to(mdSink.element_type)
|
| 425 |
+
cute.arch.sync_threads()
|
| 426 |
+
|
| 427 |
if work_tile.is_valid_tile:
|
| 428 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 429 |
# Get the appropriate tiles for this thread block.
|
|
|
|
| 444 |
mdQaccum_cur = mdQaccum[batch_idx, head_idx, None]
|
| 445 |
head_dim = mdQ.shape[3]
|
| 446 |
else:
|
| 447 |
+
padded_offset_q = seqlen.padded_offset_q
|
|
|
|
|
|
|
|
|
|
| 448 |
mdQ_cur = cute.domain_offset((seqlen.offset_q, 0), mdQ[None, head_idx, None])
|
| 449 |
mdQaccum_cur = cute.domain_offset(
|
| 450 |
(padded_offset_q * self.tile_hdim,), mdQaccum[head_idx, None]
|
|
|
|
| 469 |
seqlen_q = seqlen.seqlen_q
|
| 470 |
seqlen_q_rounded = cute.round_up(seqlen_q, self.tile_m)
|
| 471 |
|
| 472 |
+
if const_expr(self.arch // 10 in [10, 11] and self.use_2cta_instrs):
|
| 473 |
# 2-CTA: remap dQaccum layout into TMEM view before writing sdQ
|
| 474 |
num_reduce_threads = self.num_threads
|
| 475 |
thr_mma_dsk = tiled_mma.get_slice(tidx)
|
|
|
|
| 492 |
g2s_thr_copy = tiled_copy_accum.get_slice(tidx)
|
| 493 |
|
| 494 |
# S -> R
|
| 495 |
+
tdQrdQ_fp32 = cute.make_rmem_tensor(tdQrdQ.shape, cutlass.Float32)
|
| 496 |
tdQrdQ_s2r = cute.make_tensor(tdQrdQ_fp32.iterator, tdQrdQ_fp32.shape)
|
| 497 |
|
| 498 |
smem_copy_atom = sm100_utils_basic.get_smem_store_op(
|
|
|
|
| 504 |
tiler_mn=tiled_tmem_ld.tiler_mn,
|
| 505 |
)
|
| 506 |
tdQsdQ_r2s = thr_tmem_ld.partition_D(thr_mma_dsk.partition_C(sdQ))
|
| 507 |
+
tdQrdQ_r2s = cute.make_rmem_tensor(tdQsdQ_r2s.shape, self.dtype)
|
| 508 |
|
| 509 |
num_stages = cute.size(tdQrdQ_fp32, mode=[1])
|
| 510 |
stage_stride = self.dQ_reduce_ncol
|
|
|
|
| 600 |
tile_shape = (self.tile_m, self.tile_hdim)
|
| 601 |
acc = None
|
| 602 |
tiled_copy_t2r = None
|
| 603 |
+
if const_expr(self.arch // 10 in [8, 9, 12]):
|
| 604 |
acc_shape = tiled_mma.partition_shape_C(
|
| 605 |
tile_shape if const_expr(not dQ_swapAB) else tile_shape[::-1]
|
| 606 |
)
|
| 607 |
+
acc = cute.make_rmem_tensor(acc_shape, cutlass.Float32)
|
| 608 |
assert cute.size(acc) == cute.size(tdQsdQaccum)
|
| 609 |
else:
|
| 610 |
thr_mma = tiled_mma.get_slice(0) # 1-CTA
|
|
|
|
| 620 |
tiled_copy_t2r = tcgen05.make_tmem_copy(tmem_load_atom, tdQtdQ)
|
| 621 |
thr_copy_t2r = tiled_copy_t2r.get_slice(tidx)
|
| 622 |
tdQrdQ_t2r_shape = thr_copy_t2r.partition_D(tdQcdQ).shape
|
| 623 |
+
acc = cute.make_rmem_tensor(tdQrdQ_t2r_shape, Float32)
|
| 624 |
tdQrdQaccum = cute.make_tensor(acc.iterator, cute.make_layout(tdQsdQaccum.shape))
|
| 625 |
cute.autovec_copy(tdQsdQaccum, tdQrdQaccum)
|
| 626 |
# Convert tdQrdQaccum from fp32 to fp16/bf16
|
|
|
|
| 629 |
|
| 630 |
# Step 3: Copy dQ from register to smem
|
| 631 |
cute.arch.barrier() # make sure all threads have finished loading dQaccum
|
| 632 |
+
if const_expr(self.arch // 10 in [8, 9, 12]):
|
| 633 |
copy_atom_r2s_dQ = utils.get_smem_store_atom(
|
| 634 |
self.arch, self.dtype, transpose=self.dQ_swapAB
|
| 635 |
)
|
|
|
|
| 651 |
)
|
| 652 |
thr_copy_r2s_dQ = tiled_copy_r2s_dQ.get_slice(tidx)
|
| 653 |
cdQ = cute.make_identity_tensor((self.tile_m, self.tile_hdim))
|
| 654 |
+
if const_expr(self.arch // 10 in [8, 9, 12]):
|
| 655 |
taccdQrdQ = thr_copy_r2s_dQ.retile(rdQ)
|
| 656 |
else:
|
| 657 |
taccdQcdQ_shape = thr_copy_r2s_dQ.partition_S(cdQ).shape
|
build/torch-cuda/flash_bwd_preprocess.py
CHANGED
|
@@ -1,27 +1,38 @@
|
|
| 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_bwd_preprocess_kernel.h
|
| 3 |
# from Cutlass C++ to Cute-DSL.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import math
|
| 5 |
import operator
|
| 6 |
-
from typing import Callable, Type, Optional
|
| 7 |
|
| 8 |
import cuda.bindings.driver as cuda
|
| 9 |
|
| 10 |
import cutlass
|
| 11 |
import cutlass.cute as cute
|
| 12 |
-
from cutlass import Float32
|
|
|
|
| 13 |
|
| 14 |
-
from .quack import copy_utils
|
| 15 |
|
| 16 |
from . import utils
|
| 17 |
-
from .
|
| 18 |
-
from .seqlen_info import SeqlenInfoQK
|
| 19 |
from .quack.cute_dsl_utils import ParamsBase
|
| 20 |
from .tile_scheduler import (
|
| 21 |
SingleTileScheduler,
|
| 22 |
SingleTileVarlenScheduler,
|
| 23 |
TileSchedulerArguments,
|
| 24 |
)
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
class FlashAttentionBackwardPreprocess:
|
|
@@ -30,9 +41,14 @@ class FlashAttentionBackwardPreprocess:
|
|
| 30 |
dtype: Type[cutlass.Numeric],
|
| 31 |
head_dim: int,
|
| 32 |
head_dim_v: int,
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
):
|
| 37 |
"""
|
| 38 |
All contiguous dimensions must be at least 16 bytes aligned which indicates the head dimension
|
|
@@ -40,31 +56,35 @@ class FlashAttentionBackwardPreprocess:
|
|
| 40 |
|
| 41 |
:param head_dim: head dimension
|
| 42 |
:type head_dim: int
|
| 43 |
-
:param
|
| 44 |
-
:type
|
| 45 |
:param num_threads: number of threads
|
| 46 |
:type num_threads: int
|
|
|
|
| 47 |
"""
|
|
|
|
| 48 |
self.dtype = dtype
|
| 49 |
-
self.
|
| 50 |
-
self.arch = arch
|
| 51 |
-
# padding head_dim to a multiple of 32 as k_block_size
|
| 52 |
-
hdim_multiple_of = 32
|
| 53 |
self.head_dim_padded = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 54 |
self.head_dim_v_padded = int(math.ceil(head_dim_v / hdim_multiple_of) * hdim_multiple_of)
|
| 55 |
self.check_hdim_v_oob = head_dim_v != self.head_dim_v_padded
|
| 56 |
self.num_threads = num_threads
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
@staticmethod
|
| 59 |
-
def can_implement(dtype, head_dim,
|
| 60 |
"""Check if the kernel can be implemented with the given parameters.
|
| 61 |
|
| 62 |
:param dtype: data type
|
| 63 |
:type dtype: cutlass.Numeric
|
| 64 |
:param head_dim: head dimension
|
| 65 |
:type head_dim: int
|
| 66 |
-
:param
|
| 67 |
-
:type
|
| 68 |
:param num_threads: number of threads
|
| 69 |
:type num_threads: int
|
| 70 |
|
|
@@ -77,7 +97,7 @@ class FlashAttentionBackwardPreprocess:
|
|
| 77 |
return False
|
| 78 |
if num_threads % 32 != 0:
|
| 79 |
return False
|
| 80 |
-
if num_threads <
|
| 81 |
return False
|
| 82 |
return True
|
| 83 |
|
|
@@ -105,7 +125,7 @@ class FlashAttentionBackwardPreprocess:
|
|
| 105 |
universal_copy_bits = 128
|
| 106 |
num_copy_elems_dQaccum = universal_copy_bits // Float32.width
|
| 107 |
assert (
|
| 108 |
-
self.
|
| 109 |
) % self.num_threads == 0
|
| 110 |
self.gmem_tiled_copy_dQaccum = copy_utils.tiled_copy_1d(
|
| 111 |
Float32, self.num_threads, num_copy_elems_dQaccum
|
|
@@ -114,72 +134,147 @@ class FlashAttentionBackwardPreprocess:
|
|
| 114 |
@cute.jit
|
| 115 |
def __call__(
|
| 116 |
self,
|
| 117 |
-
mO: cute.Tensor,
|
| 118 |
-
mdO: cute.Tensor,
|
| 119 |
-
|
| 120 |
-
mLSE: Optional[cute.Tensor],
|
| 121 |
-
mLSElog2: Optional[cute.Tensor],
|
|
|
|
| 122 |
mdQaccum: Optional[cute.Tensor],
|
| 123 |
-
mCuSeqlensQ: Optional[cute.Tensor],
|
| 124 |
-
mSeqUsedQ: Optional[cute.Tensor],
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
):
|
| 127 |
# Get the data type and check if it is fp16 or bf16
|
| 128 |
-
if
|
| 129 |
raise TypeError("All tensors must have the same data type")
|
| 130 |
-
if
|
| 131 |
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 132 |
-
if
|
| 133 |
-
raise TypeError("
|
| 134 |
-
if
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
| 136 |
raise TypeError("dQaccum tensor must be Float32")
|
| 137 |
-
if
|
| 138 |
-
|
| 139 |
-
if cutlass.const_expr(mLSE.element_type not in [Float32]):
|
| 140 |
raise TypeError("LSE tensor must be Float32")
|
| 141 |
-
|
|
|
|
| 142 |
raise TypeError("LSElog2 tensor must be Float32")
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
self._setup_attributes()
|
| 147 |
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
TileScheduler = SingleTileVarlenScheduler
|
| 150 |
-
num_head = mO.shape[
|
| 151 |
num_batch = mCuSeqlensQ.shape[0] - 1
|
| 152 |
else:
|
| 153 |
TileScheduler = SingleTileScheduler
|
| 154 |
num_head = mO.shape[2]
|
| 155 |
-
num_batch = mO.shape[
|
| 156 |
|
| 157 |
tile_sched_args = TileSchedulerArguments(
|
| 158 |
-
num_block=cute.ceil_div(mO.shape[
|
| 159 |
num_head=num_head,
|
| 160 |
num_batch=num_batch,
|
| 161 |
num_splits=1,
|
| 162 |
seqlen_k=0,
|
| 163 |
headdim=0,
|
| 164 |
-
headdim_v=mO.shape[
|
| 165 |
-
total_q=mO.shape[0]
|
| 166 |
-
|
|
|
|
|
|
|
| 167 |
mCuSeqlensQ=mCuSeqlensQ,
|
| 168 |
mSeqUsedQ=mSeqUsedQ,
|
|
|
|
|
|
|
| 169 |
)
|
| 170 |
|
| 171 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 172 |
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 173 |
|
|
|
|
|
|
|
|
|
|
| 174 |
self.kernel(
|
| 175 |
mO,
|
| 176 |
mdO,
|
| 177 |
-
|
| 178 |
mLSE,
|
| 179 |
mLSElog2,
|
| 180 |
mdQaccum,
|
| 181 |
mCuSeqlensQ,
|
| 182 |
mSeqUsedQ,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
self.gmem_tiled_copy_O,
|
| 184 |
self.gmem_tiled_copy_dQaccum,
|
| 185 |
tile_sched_params,
|
|
@@ -188,6 +283,7 @@ class FlashAttentionBackwardPreprocess:
|
|
| 188 |
grid=grid_dim,
|
| 189 |
block=[self.num_threads, 1, 1],
|
| 190 |
stream=stream,
|
|
|
|
| 191 |
)
|
| 192 |
|
| 193 |
@cute.kernel
|
|
@@ -195,12 +291,16 @@ class FlashAttentionBackwardPreprocess:
|
|
| 195 |
self,
|
| 196 |
mO: cute.Tensor,
|
| 197 |
mdO: cute.Tensor,
|
| 198 |
-
|
| 199 |
mLSE: Optional[cute.Tensor],
|
| 200 |
mLSElog2: Optional[cute.Tensor],
|
| 201 |
mdQaccum: Optional[cute.Tensor],
|
| 202 |
mCuSeqlensQ: Optional[cute.Tensor],
|
| 203 |
mSeqUsedQ: Optional[cute.Tensor],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
gmem_tiled_copy_O: cute.TiledCopy,
|
| 205 |
gmem_tiled_copy_dQaccum: cute.TiledCopy,
|
| 206 |
tile_sched_params: ParamsBase,
|
|
@@ -213,149 +313,159 @@ class FlashAttentionBackwardPreprocess:
|
|
| 213 |
work_tile = tile_scheduler.initial_work_tile_info()
|
| 214 |
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
if work_tile.is_valid_tile:
|
| 217 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 218 |
# Get the appropriate tiles for this thread block.
|
| 219 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
)
|
|
|
|
|
|
|
| 229 |
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
mdO_cur = cute.domain_offset((seqlen.offset_q, 0), mdO[None, head_idx, None])
|
| 238 |
-
|
| 239 |
-
padded_offset_q = seqlen.offset_q + batch_idx * self.m_block_size
|
| 240 |
-
if cutlass.const_expr(self.arch >= 90):
|
| 241 |
-
padded_offset_q = padded_offset_q // self.m_block_size * self.m_block_size
|
| 242 |
-
mdPsum_cur = cute.domain_offset((padded_offset_q,), mdPsum[head_idx, None])
|
| 243 |
-
headdim_v = mO.shape[2]
|
| 244 |
-
|
| 245 |
-
blkOdO_shape = (self.m_block_size, self.head_dim_v_padded)
|
| 246 |
-
# (m_block_size, head_dim_v)
|
| 247 |
-
gO = cute.local_tile(mO_cur, blkOdO_shape, (m_block, 0))
|
| 248 |
-
gdO = cute.local_tile(mdO_cur, blkOdO_shape, (m_block, 0))
|
| 249 |
|
|
|
|
|
|
|
|
|
|
| 250 |
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 251 |
# (CPY_Atom, CPY_M, CPY_K)
|
| 252 |
tOgO = gmem_thr_copy_O.partition_S(gO)
|
| 253 |
tOgdO = gmem_thr_copy_O.partition_S(gdO)
|
| 254 |
-
|
| 255 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 256 |
-
# Predicate: Mark indices that need to copy when problem_shape isn't a multiple
|
| 257 |
-
# of tile_shape
|
| 258 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 259 |
-
# Construct identity layout for KV
|
| 260 |
-
cO = cute.make_identity_tensor((self.m_block_size, self.head_dim_v_padded))
|
| 261 |
tOcO = gmem_thr_copy_O.partition_S(cO)
|
| 262 |
t0OcO = gmem_thr_copy_O.get_slice(0).partition_S(cO)
|
| 263 |
-
tOpO =
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
gLSE = cute.local_tile(mLSE_cur, (self.m_block_size,), (m_block,))
|
| 276 |
-
lse = Float32.inf
|
| 277 |
-
if tidx < seqlen_q - m_block * self.m_block_size:
|
| 278 |
-
lse = gLSE[tidx]
|
| 279 |
-
|
| 280 |
-
tOrO = cute.make_fragment_like(tOgO)
|
| 281 |
-
tOrdO = cute.make_fragment_like(tOgdO)
|
| 282 |
-
assert cute.size(tOgO, mode=[0]) == cute.size(tOgdO, mode=[0])
|
| 283 |
-
assert cute.size(tOgO, mode=[1]) == cute.size(tOgdO, mode=[1])
|
| 284 |
-
assert cute.size(tOgO, mode=[2]) == cute.size(tOgdO, mode=[2])
|
| 285 |
for m in cutlass.range(cute.size(tOrO.shape[1]), unroll_full=True):
|
| 286 |
-
# Instead of using tOcO, we using t0OcO and subtract the offset from the limit
|
| 287 |
-
#
|
| 288 |
-
if t0OcO[0, m, 0][0] <
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
tOrdO[None, m, None],
|
| 301 |
-
pred=tOpdO[None, m, None]
|
| 302 |
-
if cutlass.const_expr(self.check_hdim_v_oob)
|
| 303 |
-
else None,
|
| 304 |
-
)
|
| 305 |
# Sum across the "k" dimension
|
| 306 |
-
|
| 307 |
cute.ReductionOp.ADD, init_val=0.0, reduction_profile=(0, None, 1)
|
| 308 |
)
|
| 309 |
threads_per_row = gmem_tiled_copy_O.layout_src_tv_tiled[0].shape[0]
|
| 310 |
assert cute.arch.WARP_SIZE % threads_per_row == 0
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
#
|
| 316 |
-
|
| 317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
if tOcO[0, 0, 0][1] == 0:
|
| 319 |
-
for m in cutlass.range(cute.size(
|
| 320 |
row = tOcO[0, m, 0][0]
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
# Clear dQaccum
|
| 324 |
-
if
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
# by padded_offset_q * self.head_dim_padded keeps alignment
|
| 334 |
-
# since statically divisible by 4
|
| 335 |
-
|
| 336 |
-
mdQaccum_cur_ptr = cute.make_ptr(
|
| 337 |
-
dtype=mdQaccum_cur.element_type,
|
| 338 |
-
value=mdQaccum_cur.iterator.toint(),
|
| 339 |
-
mem_space=mdQaccum_cur.iterator.memspace,
|
| 340 |
-
assumed_align=mdQaccum.iterator.alignment,
|
| 341 |
-
)
|
| 342 |
-
mdQaccum_cur = cute.make_tensor(mdQaccum_cur_ptr, mdQaccum_cur.layout)
|
| 343 |
-
|
| 344 |
-
blkdQaccum_shape = (self.m_block_size * self.head_dim_padded,)
|
| 345 |
gdQaccum = cute.local_tile(mdQaccum_cur, blkdQaccum_shape, (m_block,))
|
| 346 |
gmem_thr_copy_dQaccum = gmem_tiled_copy_dQaccum.get_slice(tidx)
|
| 347 |
tdQgdQaccum = gmem_thr_copy_dQaccum.partition_S(gdQaccum)
|
| 348 |
-
zero = cute.
|
| 349 |
zero.fill(0.0)
|
| 350 |
cute.copy(gmem_tiled_copy_dQaccum, zero, tdQgdQaccum)
|
| 351 |
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
gLSElog2 = cute.local_tile(mLSElog2_cur, (self.
|
| 359 |
LOG2_E = math.log2(math.e)
|
| 360 |
-
if tidx < seqlen_q_rounded - m_block * self.
|
| 361 |
-
gLSElog2[tidx] =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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_bwd_preprocess_kernel.h
|
| 3 |
# from Cutlass C++ to Cute-DSL.
|
| 4 |
+
#
|
| 5 |
+
# Computes D_i = (dO_i * O_i).sum(dim=-1), optionally adjusted for LSE gradient:
|
| 6 |
+
# D'_i = D_i - dLSE_i
|
| 7 |
+
# This works because in the backward pass:
|
| 8 |
+
# dS_ij = P_ij * (dP_ij - D_i) [standard]
|
| 9 |
+
# When LSE is differentiable, d(loss)/d(S_ij) gets an extra term dLSE_i * P_ij
|
| 10 |
+
# (since d(LSE_i)/d(S_ij) = P_ij), giving:
|
| 11 |
+
# dS_ij = P_ij * (dP_ij - D_i) + dLSE_i * P_ij
|
| 12 |
+
# = P_ij * (dP_ij - (D_i - dLSE_i))
|
| 13 |
+
# So the main backward kernel is unchanged; we just replace D with D' = D - dLSE here.
|
| 14 |
import math
|
| 15 |
import operator
|
| 16 |
+
from typing import Callable, Type, Optional
|
| 17 |
|
| 18 |
import cuda.bindings.driver as cuda
|
| 19 |
|
| 20 |
import cutlass
|
| 21 |
import cutlass.cute as cute
|
| 22 |
+
from cutlass import Float32, const_expr
|
| 23 |
+
from cutlass.cutlass_dsl import Arch, BaseDSL
|
| 24 |
|
| 25 |
+
from .quack import copy_utils, layout_utils
|
| 26 |
|
| 27 |
from . import utils
|
| 28 |
+
from .seqlen_info import SeqlenInfo
|
|
|
|
| 29 |
from .quack.cute_dsl_utils import ParamsBase
|
| 30 |
from .tile_scheduler import (
|
| 31 |
SingleTileScheduler,
|
| 32 |
SingleTileVarlenScheduler,
|
| 33 |
TileSchedulerArguments,
|
| 34 |
)
|
| 35 |
+
from .pack_gqa import pack_gqa_layout
|
| 36 |
|
| 37 |
|
| 38 |
class FlashAttentionBackwardPreprocess:
|
|
|
|
| 41 |
dtype: Type[cutlass.Numeric],
|
| 42 |
head_dim: int,
|
| 43 |
head_dim_v: int,
|
| 44 |
+
tile_m: int = 128,
|
| 45 |
+
num_threads: int = 256,
|
| 46 |
+
use_padded_offsets: bool = True,
|
| 47 |
+
nheads_major: bool = False,
|
| 48 |
+
pack_gqa: bool = False,
|
| 49 |
+
qhead_per_kvhead: int = 1,
|
| 50 |
+
nheads_kv: int = 1,
|
| 51 |
+
hdim_multiple_of: int = 32,
|
| 52 |
):
|
| 53 |
"""
|
| 54 |
All contiguous dimensions must be at least 16 bytes aligned which indicates the head dimension
|
|
|
|
| 56 |
|
| 57 |
:param head_dim: head dimension
|
| 58 |
:type head_dim: int
|
| 59 |
+
:param tile_m: m block size
|
| 60 |
+
:type tile_m: int
|
| 61 |
:param num_threads: number of threads
|
| 62 |
:type num_threads: int
|
| 63 |
+
:param hdim_multiple_of: accumulator alignment shared with the main backward kernel.
|
| 64 |
"""
|
| 65 |
+
self.use_pdl = BaseDSL._get_dsl().get_arch_enum() >= Arch.sm_90a
|
| 66 |
self.dtype = dtype
|
| 67 |
+
self.tile_m = tile_m
|
|
|
|
|
|
|
|
|
|
| 68 |
self.head_dim_padded = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 69 |
self.head_dim_v_padded = int(math.ceil(head_dim_v / hdim_multiple_of) * hdim_multiple_of)
|
| 70 |
self.check_hdim_v_oob = head_dim_v != self.head_dim_v_padded
|
| 71 |
self.num_threads = num_threads
|
| 72 |
+
self.use_padded_offsets = use_padded_offsets
|
| 73 |
+
self.nheads_major = nheads_major
|
| 74 |
+
self.pack_gqa = pack_gqa
|
| 75 |
+
self.qhead_per_kvhead = qhead_per_kvhead
|
| 76 |
+
self.nheads_kv = nheads_kv
|
| 77 |
|
| 78 |
@staticmethod
|
| 79 |
+
def can_implement(dtype, head_dim, tile_m, num_threads) -> bool:
|
| 80 |
"""Check if the kernel can be implemented with the given parameters.
|
| 81 |
|
| 82 |
:param dtype: data type
|
| 83 |
:type dtype: cutlass.Numeric
|
| 84 |
:param head_dim: head dimension
|
| 85 |
:type head_dim: int
|
| 86 |
+
:param tile_m: m block size
|
| 87 |
+
:type tile_m: int
|
| 88 |
:param num_threads: number of threads
|
| 89 |
:type num_threads: int
|
| 90 |
|
|
|
|
| 97 |
return False
|
| 98 |
if num_threads % 32 != 0:
|
| 99 |
return False
|
| 100 |
+
if num_threads < tile_m: # For multiplying lse with log2
|
| 101 |
return False
|
| 102 |
return True
|
| 103 |
|
|
|
|
| 125 |
universal_copy_bits = 128
|
| 126 |
num_copy_elems_dQaccum = universal_copy_bits // Float32.width
|
| 127 |
assert (
|
| 128 |
+
self.tile_m * self.head_dim_padded // num_copy_elems_dQaccum
|
| 129 |
) % self.num_threads == 0
|
| 130 |
self.gmem_tiled_copy_dQaccum = copy_utils.tiled_copy_1d(
|
| 131 |
Float32, self.num_threads, num_copy_elems_dQaccum
|
|
|
|
| 134 |
@cute.jit
|
| 135 |
def __call__(
|
| 136 |
self,
|
| 137 |
+
mO: cute.Tensor, # (batch, seqlen, nheads, head_dim_v) or (total_q, nheads, head_dim_v)
|
| 138 |
+
mdO: cute.Tensor, # same shape as mO
|
| 139 |
+
mPdPsum: cute.Tensor, # (batch, nheads, seqlen_padded) or (nheads, total_q_padded)
|
| 140 |
+
mLSE: Optional[cute.Tensor], # (batch, nheads, seqlen) or (nheads, total_q)
|
| 141 |
+
mLSElog2: Optional[cute.Tensor], # same shape as mPdPsum
|
| 142 |
+
# (batch, nheads, seqlen_padded * head_dim_v) or (nheads, total_q_padded * head_dim_v)
|
| 143 |
mdQaccum: Optional[cute.Tensor],
|
| 144 |
+
mCuSeqlensQ: Optional[cute.Tensor], # (batch + 1,)
|
| 145 |
+
mSeqUsedQ: Optional[cute.Tensor], # (batch,)
|
| 146 |
+
mdLSE: Optional[cute.Tensor], # (batch, nheads, seqlen) or (nheads, total_q)
|
| 147 |
+
mRowMax: Optional[cute.Tensor], # (b, s, n, h) or (t, n, h)
|
| 148 |
+
mScaleP: Optional[cute.Tensor], # == mRowMax
|
| 149 |
+
softmax_scale: Float32,
|
| 150 |
+
mCuTotalMBlocks: Optional[cute.Tensor] = None,
|
| 151 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 152 |
+
stream: cuda.CUstream = None,
|
| 153 |
):
|
| 154 |
# Get the data type and check if it is fp16 or bf16
|
| 155 |
+
if const_expr(not (mO.element_type == mdO.element_type)):
|
| 156 |
raise TypeError("All tensors must have the same data type")
|
| 157 |
+
if const_expr(mO.element_type not in [cutlass.Float16, cutlass.BFloat16]):
|
| 158 |
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 159 |
+
if const_expr(mPdPsum.element_type not in [Float32]):
|
| 160 |
+
raise TypeError("PdPsum tensor must be Float32")
|
| 161 |
+
if const_expr(mdQaccum is not None):
|
| 162 |
+
assert self.nheads_major is False
|
| 163 |
+
assert self.pack_gqa is False
|
| 164 |
+
assert self.use_padded_offsets is True
|
| 165 |
+
if const_expr(mdQaccum.element_type not in [Float32]):
|
| 166 |
raise TypeError("dQaccum tensor must be Float32")
|
| 167 |
+
if const_expr(mLSE is not None):
|
| 168 |
+
if const_expr(mLSE.element_type not in [Float32]):
|
|
|
|
| 169 |
raise TypeError("LSE tensor must be Float32")
|
| 170 |
+
if const_expr(mLSElog2 is not None):
|
| 171 |
+
if const_expr(mLSElog2.element_type not in [Float32]):
|
| 172 |
raise TypeError("LSElog2 tensor must be Float32")
|
| 173 |
+
if const_expr(mdLSE is not None):
|
| 174 |
+
if const_expr(mdLSE.element_type not in [Float32]):
|
| 175 |
+
raise TypeError("dLSE tensor must be Float32")
|
| 176 |
+
if const_expr(mScaleP is not None):
|
| 177 |
+
assert self.nheads_major is True
|
| 178 |
+
assert self.pack_gqa is True
|
| 179 |
+
assert mRowMax is not None
|
| 180 |
+
if const_expr(mScaleP.element_type not in [Float32]):
|
| 181 |
+
raise TypeError("ScaleP tensor must be Float32")
|
| 182 |
+
if const_expr(mRowMax.element_type not in [Float32]):
|
| 183 |
+
raise TypeError("RowMax tensor must be Float32")
|
| 184 |
|
| 185 |
self._setup_attributes()
|
| 186 |
|
| 187 |
+
# (b, s, h, d) -> (s, d, h, b) or
|
| 188 |
+
# (total, h, d) -> (total, d, h)
|
| 189 |
+
QO_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 2, 1]
|
| 190 |
+
mO, mdO = [
|
| 191 |
+
cute.make_tensor(mX.iterator, cute.select(mX.layout, mode=QO_layout_transpose))
|
| 192 |
+
for mX in (mO, mdO)
|
| 193 |
+
]
|
| 194 |
+
|
| 195 |
+
if const_expr(not self.nheads_major):
|
| 196 |
+
# (batch, nheads, seqlen) -> (seqlen, nheads, batch) or
|
| 197 |
+
# (nheads, total_q) -> (total_q, nheads)
|
| 198 |
+
transpose = [2, 1, 0] if const_expr(mCuSeqlensQ is None) else [1, 0]
|
| 199 |
+
else:
|
| 200 |
+
# (batch, seqlen, nheads) -> (seqlen, nheads, batch) or
|
| 201 |
+
# (total_q, nheads) -> (total_q, nheads)
|
| 202 |
+
transpose = [1, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 1]
|
| 203 |
+
mPdPsum, mLSE, mLSElog2, mdLSE, mdQaccum = [
|
| 204 |
+
layout_utils.select(mX, transpose) if mX is not None else None
|
| 205 |
+
for mX in (mPdPsum, mLSE, mLSElog2, mdLSE, mdQaccum)
|
| 206 |
+
]
|
| 207 |
+
|
| 208 |
+
# (b, s, n, h) => (s, n, h, b) or
|
| 209 |
+
# (total, n, h) == (total, n, h)
|
| 210 |
+
rowmax_layout_transpose = [1, 2, 3, 0] if const_expr(mCuSeqlensQ is None) else [0, 1, 2]
|
| 211 |
+
if const_expr(mRowMax is not None):
|
| 212 |
+
mRowMax = layout_utils.select(mRowMax, rowmax_layout_transpose)
|
| 213 |
+
if const_expr(mScaleP is not None):
|
| 214 |
+
mScaleP = layout_utils.select(mScaleP, rowmax_layout_transpose)
|
| 215 |
+
|
| 216 |
+
# pack gqa
|
| 217 |
+
if const_expr(self.pack_gqa):
|
| 218 |
+
mO, mdO, mRowMax, mScaleP = [
|
| 219 |
+
pack_gqa_layout(mX, self.qhead_per_kvhead, self.nheads_kv, head_idx=2)
|
| 220 |
+
if mX is not None
|
| 221 |
+
else None
|
| 222 |
+
for mX in (mO, mdO, mRowMax, mScaleP)
|
| 223 |
+
]
|
| 224 |
+
mPdPsum, mLSE, mLSElog2, mdLSE = [
|
| 225 |
+
pack_gqa_layout(mX, self.qhead_per_kvhead, self.nheads_kv, head_idx=1)
|
| 226 |
+
if mX is not None
|
| 227 |
+
else None
|
| 228 |
+
for mX in (mPdPsum, mLSE, mLSElog2, mdLSE)
|
| 229 |
+
]
|
| 230 |
+
|
| 231 |
+
# mO: (s, d, h, b) or (total, d, h)
|
| 232 |
+
if const_expr(mCuSeqlensQ is not None):
|
| 233 |
TileScheduler = SingleTileVarlenScheduler
|
| 234 |
+
num_head = mO.shape[2]
|
| 235 |
num_batch = mCuSeqlensQ.shape[0] - 1
|
| 236 |
else:
|
| 237 |
TileScheduler = SingleTileScheduler
|
| 238 |
num_head = mO.shape[2]
|
| 239 |
+
num_batch = mO.shape[3]
|
| 240 |
|
| 241 |
tile_sched_args = TileSchedulerArguments(
|
| 242 |
+
num_block=cute.ceil_div(mO.shape[0], self.tile_m),
|
| 243 |
num_head=num_head,
|
| 244 |
num_batch=num_batch,
|
| 245 |
num_splits=1,
|
| 246 |
seqlen_k=0,
|
| 247 |
headdim=0,
|
| 248 |
+
headdim_v=mO.shape[1],
|
| 249 |
+
total_q=cute.size(mO.shape[0])
|
| 250 |
+
if const_expr(mCuSeqlensQ is not None)
|
| 251 |
+
else cute.size(mO.shape[0]) * cute.size(mO.shape[3]),
|
| 252 |
+
tile_shape_mn=(self.tile_m, 1),
|
| 253 |
mCuSeqlensQ=mCuSeqlensQ,
|
| 254 |
mSeqUsedQ=mSeqUsedQ,
|
| 255 |
+
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 256 |
+
cu_total_m_blocks_ptr=mCuTotalMBlocks,
|
| 257 |
)
|
| 258 |
|
| 259 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 260 |
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 261 |
|
| 262 |
+
LOG2_E = math.log2(math.e)
|
| 263 |
+
softmax_scale_log2 = softmax_scale * LOG2_E
|
| 264 |
+
|
| 265 |
self.kernel(
|
| 266 |
mO,
|
| 267 |
mdO,
|
| 268 |
+
mPdPsum,
|
| 269 |
mLSE,
|
| 270 |
mLSElog2,
|
| 271 |
mdQaccum,
|
| 272 |
mCuSeqlensQ,
|
| 273 |
mSeqUsedQ,
|
| 274 |
+
mdLSE,
|
| 275 |
+
mRowMax,
|
| 276 |
+
mScaleP,
|
| 277 |
+
softmax_scale_log2,
|
| 278 |
self.gmem_tiled_copy_O,
|
| 279 |
self.gmem_tiled_copy_dQaccum,
|
| 280 |
tile_sched_params,
|
|
|
|
| 283 |
grid=grid_dim,
|
| 284 |
block=[self.num_threads, 1, 1],
|
| 285 |
stream=stream,
|
| 286 |
+
use_pdl=self.use_pdl,
|
| 287 |
)
|
| 288 |
|
| 289 |
@cute.kernel
|
|
|
|
| 291 |
self,
|
| 292 |
mO: cute.Tensor,
|
| 293 |
mdO: cute.Tensor,
|
| 294 |
+
mPdPsum: cute.Tensor,
|
| 295 |
mLSE: Optional[cute.Tensor],
|
| 296 |
mLSElog2: Optional[cute.Tensor],
|
| 297 |
mdQaccum: Optional[cute.Tensor],
|
| 298 |
mCuSeqlensQ: Optional[cute.Tensor],
|
| 299 |
mSeqUsedQ: Optional[cute.Tensor],
|
| 300 |
+
mdLSE: Optional[cute.Tensor],
|
| 301 |
+
mRowMax: Optional[cute.Tensor],
|
| 302 |
+
mScaleP: Optional[cute.Tensor],
|
| 303 |
+
softmax_scale_log2: Float32,
|
| 304 |
gmem_tiled_copy_O: cute.TiledCopy,
|
| 305 |
gmem_tiled_copy_dQaccum: cute.TiledCopy,
|
| 306 |
tile_sched_params: ParamsBase,
|
|
|
|
| 313 |
work_tile = tile_scheduler.initial_work_tile_info()
|
| 314 |
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 315 |
|
| 316 |
+
# This kernel is launched with use_pdl=True, so the GPU may start executing it in
|
| 317 |
+
# "prologue" mode while the previous stream kernel is still running. We must wait
|
| 318 |
+
# before touching any upstream GMEM outputs (mO, mdO, mLSE); otherwise we risk
|
| 319 |
+
# reading a partially-written dout, which silently corrupts dpsum = sum(O * dO) and
|
| 320 |
+
# propagates to dQ/dK via dS = P * (dP - dpsum).
|
| 321 |
+
if const_expr(self.use_pdl):
|
| 322 |
+
cute.arch.griddepcontrol_wait()
|
| 323 |
+
|
| 324 |
if work_tile.is_valid_tile:
|
| 325 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 326 |
# Get the appropriate tiles for this thread block.
|
| 327 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 328 |
+
seqlen_static = mO.shape[0] if const_expr(not self.pack_gqa) else mO.shape[0][1]
|
| 329 |
+
seqlen = SeqlenInfo.create(
|
| 330 |
+
batch_idx, seqlen_static, mCuSeqlensQ, mSeqUsedQ, tile=self.tile_m
|
| 331 |
+
)
|
| 332 |
+
# (seqlen, dv)
|
| 333 |
+
mO_cur, mdO_cur = [
|
| 334 |
+
seqlen.offset_batch(mX, batch_idx, dim=3)[None, None, head_idx] for mX in (mO, mdO)
|
| 335 |
+
]
|
| 336 |
+
mPdPsum_cur = seqlen.offset_batch(
|
| 337 |
+
mPdPsum, batch_idx, dim=2, padded=self.use_padded_offsets
|
| 338 |
+
)[None, head_idx]
|
| 339 |
+
headdim_v = mO_cur.shape[1]
|
| 340 |
+
seqlen_q = (
|
| 341 |
+
seqlen.seqlen
|
| 342 |
+
if const_expr(not self.pack_gqa)
|
| 343 |
+
else seqlen.seqlen * self.qhead_per_kvhead
|
| 344 |
)
|
| 345 |
+
seqlen_q_rounded = cute.round_up(seqlen_q, self.tile_m)
|
| 346 |
+
seqlen_limit = seqlen_q - m_block * self.tile_m
|
| 347 |
|
| 348 |
+
lse = None
|
| 349 |
+
if const_expr(mLSE is not None):
|
| 350 |
+
mLSE_cur = seqlen.offset_batch(mLSE, batch_idx, dim=2)[None, head_idx]
|
| 351 |
+
gLSE = cute.local_tile(mLSE_cur, (self.tile_m,), (m_block,))
|
| 352 |
+
lse = Float32.inf
|
| 353 |
+
if tidx < seqlen_limit:
|
| 354 |
+
lse = gLSE[tidx]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
+
blk_shape = (self.tile_m, self.head_dim_v_padded)
|
| 357 |
+
gO = cute.local_tile(mO_cur, blk_shape, (m_block, 0))
|
| 358 |
+
gdO = cute.local_tile(mdO_cur, blk_shape, (m_block, 0))
|
| 359 |
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 360 |
# (CPY_Atom, CPY_M, CPY_K)
|
| 361 |
tOgO = gmem_thr_copy_O.partition_S(gO)
|
| 362 |
tOgdO = gmem_thr_copy_O.partition_S(gdO)
|
| 363 |
+
cO = cute.make_identity_tensor(blk_shape)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
tOcO = gmem_thr_copy_O.partition_S(cO)
|
| 365 |
t0OcO = gmem_thr_copy_O.get_slice(0).partition_S(cO)
|
| 366 |
+
tOpO = None
|
| 367 |
+
if const_expr(self.check_hdim_v_oob):
|
| 368 |
+
tOpO = copy_utils.predicate_k(tOcO, limit=headdim_v)
|
| 369 |
+
# Each copy uses the predicate sliced to the current m (see loop below).
|
| 370 |
+
copy = copy_utils.copy
|
| 371 |
+
|
| 372 |
+
tOrO = cute.make_rmem_tensor_like(tOgO)
|
| 373 |
+
tOrdO = cute.make_rmem_tensor_like(tOgdO)
|
| 374 |
+
if const_expr(self.check_hdim_v_oob):
|
| 375 |
+
tOrO.fill(0.0)
|
| 376 |
+
tOrdO.fill(0.0)
|
| 377 |
+
assert tOgO.shape == tOgdO.shape
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
for m in cutlass.range(cute.size(tOrO.shape[1]), unroll_full=True):
|
| 379 |
+
# Instead of using tOcO, we using t0OcO and subtract the offset from the limit.
|
| 380 |
+
# This is bc the entries of t0OcO are known at compile time.
|
| 381 |
+
if t0OcO[0, m, 0][0] < seqlen_limit - tOcO[0][0]:
|
| 382 |
+
# The predicate carries a CPY_M mode (broadcast over m); slice it to the
|
| 383 |
+
# current m so its shape matches the m-sliced copy source. Otherwise the
|
| 384 |
+
# vectorized copy atom's predicate-shape verification fails when head_dim_v
|
| 385 |
+
# is not a multiple of the copy-atom width (e.g. 72, 104).
|
| 386 |
+
tOpO_cur = tOpO[None, m, None] if const_expr(tOpO is not None) else None
|
| 387 |
+
copy(tOgO[None, m, None], tOrO[None, m, None], pred=tOpO_cur)
|
| 388 |
+
copy(tOgdO[None, m, None], tOrdO[None, m, None], pred=tOpO_cur)
|
| 389 |
+
# O and dO loads are done; signal that the next kernel can start.
|
| 390 |
+
# Correctness is ensured by griddepcontrol_wait() in bwd_sm90 before it reads our outputs.
|
| 391 |
+
if const_expr(self.use_pdl):
|
| 392 |
+
cute.arch.griddepcontrol_launch_dependents()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
# Sum across the "k" dimension
|
| 394 |
+
pdpsum = (tOrO.load().to(Float32) * tOrdO.load().to(Float32)).reduce(
|
| 395 |
cute.ReductionOp.ADD, init_val=0.0, reduction_profile=(0, None, 1)
|
| 396 |
)
|
| 397 |
threads_per_row = gmem_tiled_copy_O.layout_src_tv_tiled[0].shape[0]
|
| 398 |
assert cute.arch.WARP_SIZE % threads_per_row == 0
|
| 399 |
+
pdpsum = utils.warp_reduce(pdpsum, operator.add, width=threads_per_row)
|
| 400 |
+
PdP_sum = cute.make_rmem_tensor(cute.size(tOrO, mode=[1]), Float32)
|
| 401 |
+
PdP_sum.store(pdpsum)
|
| 402 |
+
|
| 403 |
+
# If dLSE is provided, compute D' = D - dLSE (see module docstring for derivation).
|
| 404 |
+
gdLSE = None
|
| 405 |
+
if const_expr(mdLSE is not None):
|
| 406 |
+
mdLSE_cur = seqlen.offset_batch(mdLSE, batch_idx, dim=2)[None, head_idx]
|
| 407 |
+
gdLSE = cute.local_tile(mdLSE_cur, (self.tile_m,), (m_block,))
|
| 408 |
+
|
| 409 |
+
# Write PdPsum from rmem -> gmem
|
| 410 |
+
gPdPsum = cute.local_tile(mPdPsum_cur, (self.tile_m,), (m_block,))
|
| 411 |
+
# Only the thread corresponding to column 0 writes out the PdPsum to gmem
|
| 412 |
if tOcO[0, 0, 0][1] == 0:
|
| 413 |
+
for m in cutlass.range(cute.size(PdP_sum), unroll_full=True):
|
| 414 |
row = tOcO[0, m, 0][0]
|
| 415 |
+
PdPsum_val = 0.0
|
| 416 |
+
if row < seqlen_limit:
|
| 417 |
+
PdPsum_val = PdP_sum[m]
|
| 418 |
+
if const_expr(mdLSE is not None):
|
| 419 |
+
PdPsum_val -= gdLSE[row]
|
| 420 |
+
gPdPsum[row] = PdPsum_val
|
| 421 |
|
| 422 |
# Clear dQaccum
|
| 423 |
+
if const_expr(mdQaccum is not None):
|
| 424 |
+
mdQaccum_cur = seqlen.offset_batch(
|
| 425 |
+
mdQaccum,
|
| 426 |
+
batch_idx,
|
| 427 |
+
dim=2,
|
| 428 |
+
padded=self.use_padded_offsets,
|
| 429 |
+
multiple=self.head_dim_padded,
|
| 430 |
+
)[None, head_idx]
|
| 431 |
+
blkdQaccum_shape = (self.tile_m * self.head_dim_padded,)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
gdQaccum = cute.local_tile(mdQaccum_cur, blkdQaccum_shape, (m_block,))
|
| 433 |
gmem_thr_copy_dQaccum = gmem_tiled_copy_dQaccum.get_slice(tidx)
|
| 434 |
tdQgdQaccum = gmem_thr_copy_dQaccum.partition_S(gdQaccum)
|
| 435 |
+
zero = cute.make_rmem_tensor_like(tdQgdQaccum)
|
| 436 |
zero.fill(0.0)
|
| 437 |
cute.copy(gmem_tiled_copy_dQaccum, zero, tdQgdQaccum)
|
| 438 |
|
| 439 |
+
LOG2_E = math.log2(math.e)
|
| 440 |
+
lse_log2 = lse * LOG2_E if lse != -Float32.inf else 0.0
|
| 441 |
+
if const_expr(mLSElog2 is not None):
|
| 442 |
+
mLSElog2_cur = seqlen.offset_batch(
|
| 443 |
+
mLSElog2, batch_idx, dim=2, padded=self.use_padded_offsets
|
| 444 |
+
)[None, head_idx]
|
| 445 |
+
gLSElog2 = cute.local_tile(mLSElog2_cur, (self.tile_m,), (m_block,))
|
| 446 |
LOG2_E = math.log2(math.e)
|
| 447 |
+
if tidx < seqlen_q_rounded - m_block * self.tile_m:
|
| 448 |
+
gLSElog2[tidx] = lse_log2
|
| 449 |
+
|
| 450 |
+
if const_expr(mRowMax is not None):
|
| 451 |
+
assert mLSE is not None
|
| 452 |
+
# (s, n)
|
| 453 |
+
mRowMax_cur, mScaleP_cur = [
|
| 454 |
+
seqlen.offset_batch(mX, batch_idx, dim=3)[None, None, head_idx]
|
| 455 |
+
for mX in (mRowMax, mScaleP)
|
| 456 |
+
]
|
| 457 |
+
# (tile_m, n)
|
| 458 |
+
gRowMax, gScaleP = [
|
| 459 |
+
cute.local_tile(mX, (self.tile_m,), (m_block, None))
|
| 460 |
+
for mX in (mRowMax_cur, mScaleP_cur)
|
| 461 |
+
]
|
| 462 |
+
|
| 463 |
+
assert self.tile_m <= self.num_threads
|
| 464 |
+
if const_expr(self.tile_m == self.num_threads) or tidx < self.tile_m:
|
| 465 |
+
for n in cutlass.range(gRowMax.shape[1], unroll=4):
|
| 466 |
+
row_max = gRowMax[tidx, n]
|
| 467 |
+
scale = 0.0
|
| 468 |
+
if row_max != -Float32.inf and lse != -Float32.inf:
|
| 469 |
+
scale = softmax_scale_log2 * row_max - lse_log2
|
| 470 |
+
scale = cute.math.exp2(scale, fastmath=True)
|
| 471 |
+
gScaleP[tidx, n] = scale
|
build/torch-cuda/flash_bwd_sm100.py
CHANGED
|
@@ -14,7 +14,6 @@ from cutlass.cute.nvgpu import cpasync, tcgen05
|
|
| 14 |
import cutlass.utils.blackwell_helpers as sm100_utils_basic
|
| 15 |
from cutlass.pipeline import PipelineAsync
|
| 16 |
|
| 17 |
-
from .quack import activation
|
| 18 |
from .quack import layout_utils
|
| 19 |
from . import utils
|
| 20 |
from .cute_dsl_utils import assume_tensor_aligned
|
|
@@ -36,14 +35,35 @@ from . import barrier
|
|
| 36 |
from .named_barrier import NamedBarrierBwdSm100
|
| 37 |
from .softmax import apply_score_mod_inner, apply_score_mod_bwd_inner
|
| 38 |
from .block_sparsity import BlockSparseTensors
|
|
|
|
| 39 |
from .block_sparse_utils import (
|
| 40 |
get_total_q_block_count_bwd,
|
| 41 |
get_block_sparse_iteration_info_bwd,
|
| 42 |
get_m_block_from_iter_bwd,
|
| 43 |
-
|
|
|
|
| 44 |
)
|
| 45 |
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
class FlashAttentionBackwardSm100:
|
| 48 |
arch = 100
|
| 49 |
|
|
@@ -58,22 +78,28 @@ class FlashAttentionBackwardSm100:
|
|
| 58 |
tile_n: int = 128,
|
| 59 |
is_persistent: bool = False,
|
| 60 |
deterministic: bool = False,
|
|
|
|
| 61 |
cluster_size: int = 1,
|
| 62 |
use_2cta_instrs: bool = False,
|
| 63 |
score_mod: cutlass.Constexpr | None = None,
|
| 64 |
score_mod_bwd: cutlass.Constexpr | None = None,
|
| 65 |
mask_mod: cutlass.Constexpr | None = None,
|
| 66 |
has_aux_tensors: cutlass.Constexpr = False,
|
| 67 |
-
|
|
|
|
| 68 |
):
|
| 69 |
-
#
|
| 70 |
-
|
|
|
|
|
|
|
| 71 |
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 72 |
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
| 73 |
self.same_hdim_kv = head_dim == head_dim_v
|
| 74 |
self.tile_hdimv = int(math.ceil(head_dim_v / hdim_multiple_of) * hdim_multiple_of)
|
| 75 |
self.check_hdim_oob = head_dim != self.tile_hdim
|
| 76 |
self.check_hdim_v_oob = head_dim_v != self.tile_hdimv
|
|
|
|
|
|
|
| 77 |
|
| 78 |
self.tile_m = tile_m
|
| 79 |
self.tile_n = tile_n
|
|
@@ -81,14 +107,7 @@ class FlashAttentionBackwardSm100:
|
|
| 81 |
assert self.tile_hdim <= 128 or (self.tile_hdim == 192 and self.tile_hdimv == 128)
|
| 82 |
assert self.tile_hdimv <= 128
|
| 83 |
|
| 84 |
-
self.use_2cta_instrs = bool(
|
| 85 |
-
use_2cta_instrs
|
| 86 |
-
and cluster_size == 2
|
| 87 |
-
and not is_local
|
| 88 |
-
and score_mod is None
|
| 89 |
-
and score_mod_bwd is None
|
| 90 |
-
and mask_mod is None
|
| 91 |
-
)
|
| 92 |
self.cta_group_size = 2 if self.use_2cta_instrs else 1
|
| 93 |
|
| 94 |
assert self.tile_hdim != 192 or self.use_2cta_instrs, "Must use 2CTA for hdim 192"
|
|
@@ -111,19 +130,25 @@ class FlashAttentionBackwardSm100:
|
|
| 111 |
|
| 112 |
assert cluster_size in (1, 2), "Only cluster_size=1 or 2 is supported"
|
| 113 |
self.cluster_shape_mn = (cluster_size, 1)
|
|
|
|
|
|
|
|
|
|
| 114 |
self.is_persistent = is_persistent
|
| 115 |
self.is_causal = is_causal
|
| 116 |
self.is_local = is_local
|
| 117 |
self.qhead_per_kvhead = qhead_per_kvhead
|
| 118 |
self.pack_gqa = False
|
| 119 |
self.deterministic = deterministic
|
|
|
|
| 120 |
|
| 121 |
# Score mod and mask mod support
|
| 122 |
self.score_mod = score_mod
|
| 123 |
self.score_mod_bwd = score_mod_bwd
|
| 124 |
self.mask_mod = mask_mod
|
| 125 |
self.has_aux_tensors = has_aux_tensors
|
| 126 |
-
self.
|
|
|
|
|
|
|
| 127 |
# For score_mod, use vec_size=1 (like forward) to handle per-element indices
|
| 128 |
if cutlass.const_expr(has_aux_tensors):
|
| 129 |
self.vec_size: cutlass.Constexpr = 1
|
|
@@ -137,6 +162,16 @@ class FlashAttentionBackwardSm100:
|
|
| 137 |
# Generally slower to use store dS in smem for dK, and doesn't work for 2cta
|
| 138 |
self.use_smem_dS_for_mma_dK = False
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
self.reduce_warp_ids = (0, 1, 2, 3)
|
| 141 |
self.compute_warp_ids = (4, 5, 6, 7, 8, 9, 10, 11)
|
| 142 |
self.mma_warp_id = 12
|
|
@@ -191,7 +226,6 @@ class FlashAttentionBackwardSm100:
|
|
| 191 |
self.tmem_dQ_offset = 512 - self.tile_hdim // 2
|
| 192 |
else:
|
| 193 |
self.tmem_S_offset = 0
|
| 194 |
-
self.tmem_P_offset = 0 # overlap with S
|
| 195 |
self.tmem_dV_offset = self.tmem_S_offset + self.tile_n
|
| 196 |
self.tmem_dP_offset = self.tmem_dV_offset + self.tile_hdimv
|
| 197 |
self.tmem_dQ_offset = (
|
|
@@ -200,7 +234,14 @@ class FlashAttentionBackwardSm100:
|
|
| 200 |
else self.tmem_dP_offset
|
| 201 |
)
|
| 202 |
self.tmem_dK_offset = self.tmem_dP_offset + self.tile_m
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
if (not is_causal and not is_local) or deterministic:
|
| 206 |
self.num_regs_reduce = 136 if self.use_2cta_instrs else 152
|
|
@@ -238,6 +279,7 @@ class FlashAttentionBackwardSm100:
|
|
| 238 |
self.Q_stage = 1 if self.use_2cta_instrs else 2
|
| 239 |
self.dO_stage = 1
|
| 240 |
self.single_stage = 1
|
|
|
|
| 241 |
# LSE_stage = Q_stage and dPsum_stage = dO_stage
|
| 242 |
self.sdKVaccum_stage = 2
|
| 243 |
# number of tma reduce adds per dQacc mma
|
|
@@ -453,20 +495,21 @@ class FlashAttentionBackwardSm100:
|
|
| 453 |
mdK: cute.Tensor,
|
| 454 |
mdV: cute.Tensor,
|
| 455 |
softmax_scale: Float32,
|
| 456 |
-
stream: cuda.CUstream,
|
| 457 |
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 458 |
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 459 |
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 460 |
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 461 |
-
softcap: Float32 | float | None = None,
|
| 462 |
window_size_left: Int32 | int | None = None,
|
| 463 |
window_size_right: Int32 | int | None = None,
|
| 464 |
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 465 |
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 466 |
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 467 |
-
|
| 468 |
# Block-sparse tensors (Q direction - for iterating m_blocks per n_block):
|
| 469 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
|
|
|
|
|
|
|
|
|
| 470 |
):
|
| 471 |
self.q_dtype = mQ.element_type
|
| 472 |
self.k_dtype = mK.element_type
|
|
@@ -489,7 +532,9 @@ class FlashAttentionBackwardSm100:
|
|
| 489 |
assert self.dk_dtype.width == 32, "Must accumulate dK in float precision for GQA"
|
| 490 |
assert self.dv_dtype.width == 32, "Must accumulate dV in float precision for GQA"
|
| 491 |
|
| 492 |
-
mdQaccum, mdK, mdV
|
|
|
|
|
|
|
| 493 |
|
| 494 |
# (b, s, n, h) --> (s, h, n, b) or (t, n, h) -> (t, h, n)
|
| 495 |
QO_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 2, 1]
|
|
@@ -706,7 +751,11 @@ class FlashAttentionBackwardSm100:
|
|
| 706 |
TileScheduler = SingleTileLPTBwdScheduler
|
| 707 |
else:
|
| 708 |
TileScheduler = SingleTileScheduler
|
| 709 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 710 |
tile_sched_args = TileSchedulerArguments(
|
| 711 |
cute.ceil_div(cute.size(mK.shape[0]), self.cta_tiler[0]), # num_blocks
|
| 712 |
cute.size(mQ.shape[2]), # num_heads = num_query_heads
|
|
@@ -727,6 +776,7 @@ class FlashAttentionBackwardSm100:
|
|
| 727 |
qhead_per_kvhead_packgqa=1, # pack_gqa disabled for bwd
|
| 728 |
element_size=self.k_dtype.width // 8,
|
| 729 |
is_persistent=self.is_persistent, # persistent mode not tested
|
|
|
|
| 730 |
lpt=self.spt,
|
| 731 |
head_swizzle=self.deterministic,
|
| 732 |
)
|
|
@@ -782,7 +832,7 @@ class FlashAttentionBackwardSm100:
|
|
| 782 |
cutlass.Int64, self.dQaccum_reduce_stage // 2
|
| 783 |
]
|
| 784 |
tmem_holding_buf: Int32
|
| 785 |
-
|
| 786 |
|
| 787 |
# 2-CTA
|
| 788 |
Qt_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.Q_stage]
|
|
@@ -852,6 +902,7 @@ class FlashAttentionBackwardSm100:
|
|
| 852 |
S_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.single_stage]
|
| 853 |
dP_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.single_stage]
|
| 854 |
dS_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.single_stage]
|
|
|
|
| 855 |
dKV_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.sdKVaccum_stage]
|
| 856 |
dQ_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2]
|
| 857 |
dQ_cluster_full_mbar_ptr: cute.struct.MemRange[
|
|
@@ -861,7 +912,7 @@ class FlashAttentionBackwardSm100:
|
|
| 861 |
cutlass.Int64, self.dQaccum_reduce_stage // 2
|
| 862 |
]
|
| 863 |
tmem_holding_buf: Int32
|
| 864 |
-
|
| 865 |
|
| 866 |
sQ: cute.struct.Align[
|
| 867 |
cute.struct.MemRange[cute.Uint8, sQ_alloc_bytes],
|
|
@@ -912,7 +963,7 @@ class FlashAttentionBackwardSm100:
|
|
| 912 |
window_size_right = Int32(window_size_right)
|
| 913 |
|
| 914 |
fastdiv_mods = None
|
| 915 |
-
if const_expr(
|
| 916 |
seqlen_q = cute.size(mQ.shape[0]) // (
|
| 917 |
self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1
|
| 918 |
)
|
|
@@ -921,21 +972,17 @@ class FlashAttentionBackwardSm100:
|
|
| 921 |
seqlen_k_divmod = FastDivmodDivisor(seqlen_k)
|
| 922 |
fastdiv_mods = (seqlen_q_divmod, seqlen_k_divmod)
|
| 923 |
self.use_block_sparsity = cutlass.const_expr(blocksparse_tensors is not None)
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
assert
|
| 927 |
-
"2-CTA
|
| 928 |
-
"Please create kernel with use_2cta_instrs=False for block sparse attention."
|
| 929 |
-
)
|
| 930 |
-
assert window_size_left is None and window_size_right is None, (
|
| 931 |
-
"2-CTA mode does not support window attention. "
|
| 932 |
-
"Please create kernel with use_2cta_instrs=False for window attention."
|
| 933 |
)
|
|
|
|
| 934 |
# 2-CTA: 231424 and 1-CTA: 232448
|
| 935 |
# print("SMEM: ", self.shared_storage.size_in_bytes())
|
| 936 |
-
if const_expr(self.use_block_sparsity
|
| 937 |
assert all(x is None for x in (mCuSeqlensQ, mCuSeqlensK, mSeqUsedQ, mSeqUsedK)), (
|
| 938 |
-
"Variable sequence length is not supported yet for blocksparse
|
| 939 |
)
|
| 940 |
|
| 941 |
self.kernel(
|
|
@@ -997,7 +1044,7 @@ class FlashAttentionBackwardSm100:
|
|
| 997 |
window_size_left,
|
| 998 |
window_size_right,
|
| 999 |
tile_sched_params,
|
| 1000 |
-
|
| 1001 |
fastdiv_mods,
|
| 1002 |
blocksparse_tensors,
|
| 1003 |
).launch(
|
|
@@ -1009,6 +1056,16 @@ class FlashAttentionBackwardSm100:
|
|
| 1009 |
min_blocks_per_mp=1,
|
| 1010 |
)
|
| 1011 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1012 |
@cute.kernel
|
| 1013 |
def kernel(
|
| 1014 |
self,
|
|
@@ -1070,7 +1127,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1070 |
window_size_left: Optional[Int32],
|
| 1071 |
window_size_right: Optional[Int32],
|
| 1072 |
tile_sched_params: ParamsBase,
|
| 1073 |
-
|
| 1074 |
fastdiv_mods=(None, None),
|
| 1075 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 1076 |
):
|
|
@@ -1146,11 +1203,11 @@ class FlashAttentionBackwardSm100:
|
|
| 1146 |
* len((self.mma_warp_id, *self.compute_warp_ids, *self.reduce_warp_ids)),
|
| 1147 |
)
|
| 1148 |
tmem = cutlass.utils.TmemAllocator(
|
| 1149 |
-
storage.tmem_holding_buf,
|
| 1150 |
barrier_for_retrieve=tmem_alloc_barrier,
|
| 1151 |
allocator_warp_id=self.mma_warp_id,
|
| 1152 |
is_two_cta=self.use_2cta_instrs,
|
| 1153 |
-
two_cta_tmem_dealloc_mbar_ptr=storage.
|
| 1154 |
)
|
| 1155 |
|
| 1156 |
# UMMA producers and AsyncThread consumers
|
|
@@ -1160,6 +1217,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1160 |
pipeline_consumer_group_MMA_AsyncThread = cutlass.pipeline.CooperativeGroup(
|
| 1161 |
cutlass.pipeline.Agent.Thread, len(self.compute_warp_ids) * self.cta_group_size
|
| 1162 |
)
|
|
|
|
| 1163 |
pipeline_S_P = cutlass.pipeline.PipelineUmmaAsync.create(
|
| 1164 |
num_stages=1,
|
| 1165 |
producer_group=pipeline_producer_group_MMA_AsyncThread,
|
|
@@ -1209,6 +1267,16 @@ class FlashAttentionBackwardSm100:
|
|
| 1209 |
barrier_storage=storage.dS_mbar_ptr.data_ptr(),
|
| 1210 |
cta_layout_vmnk=cluster_layout_vmnk,
|
| 1211 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1212 |
|
| 1213 |
# TMA producer and UMMA consumers
|
| 1214 |
pipeline_producer_group = cutlass.pipeline.CooperativeGroup(
|
|
@@ -1412,14 +1480,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1412 |
)
|
| 1413 |
TileSchedulerCls = partial(self.tile_scheduler_cls.create, tile_sched_params)
|
| 1414 |
|
| 1415 |
-
AttentionMaskCls =
|
| 1416 |
-
AttentionMask,
|
| 1417 |
-
self.tile_m,
|
| 1418 |
-
self.tile_n * self.cta_group_size,
|
| 1419 |
-
swap_AB=True,
|
| 1420 |
-
window_size_left=window_size_left,
|
| 1421 |
-
window_size_right=window_size_right,
|
| 1422 |
-
)
|
| 1423 |
# EMPTY
|
| 1424 |
# (15)
|
| 1425 |
if warp_idx == self.empty_warp_id:
|
|
@@ -1440,6 +1501,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1440 |
block_info,
|
| 1441 |
SeqlenInfoCls,
|
| 1442 |
TileSchedulerCls,
|
|
|
|
| 1443 |
)
|
| 1444 |
|
| 1445 |
# LOAD
|
|
@@ -1536,6 +1598,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1536 |
pipeline_dKV,
|
| 1537 |
pipeline_dP,
|
| 1538 |
pipeline_dQ,
|
|
|
|
| 1539 |
block_info,
|
| 1540 |
SeqlenInfoCls,
|
| 1541 |
TileSchedulerCls,
|
|
@@ -1574,6 +1637,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1574 |
pipeline_dS,
|
| 1575 |
pipeline_dKV,
|
| 1576 |
pipeline_dP,
|
|
|
|
| 1577 |
dS_cluster_empty_mbar_ptr,
|
| 1578 |
dS_cluster_full_mbar_ptr,
|
| 1579 |
dQaccum_empty_mbar_ptr,
|
|
@@ -1592,7 +1656,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1592 |
tiled_copy_r2s_dKV,
|
| 1593 |
mdK_semaphore,
|
| 1594 |
mdV_semaphore,
|
| 1595 |
-
|
| 1596 |
fastdiv_mods,
|
| 1597 |
blocksparse_tensors,
|
| 1598 |
)
|
|
@@ -1631,6 +1695,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1631 |
block_info: BlockInfo,
|
| 1632 |
SeqlenInfoCls: Callable,
|
| 1633 |
TileSchedulerCls: Callable,
|
|
|
|
| 1634 |
):
|
| 1635 |
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 1636 |
dS_cluster_phase = Int32(0)
|
|
@@ -1648,9 +1713,20 @@ class FlashAttentionBackwardSm100:
|
|
| 1648 |
process_tile = (
|
| 1649 |
const_expr(not self.is_local and not self.is_varlen_q) or m_block_min < m_block_max
|
| 1650 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1651 |
|
| 1652 |
if process_tile:
|
| 1653 |
-
num_iters = m_block_max - m_block_min
|
| 1654 |
for _ in cutlass.range(num_iters, unroll=1):
|
| 1655 |
# Wait for dS_xchg from peer CTA
|
| 1656 |
cute.arch.mbarrier_wait(dS_cluster_full_mbar_ptr, phase=dS_cluster_phase)
|
|
@@ -1668,11 +1744,11 @@ class FlashAttentionBackwardSm100:
|
|
| 1668 |
@cute.jit
|
| 1669 |
def load(
|
| 1670 |
self,
|
| 1671 |
-
thr_mma_S: cute.
|
| 1672 |
-
thr_mma_dP: cute.
|
| 1673 |
-
thr_mma_dV: cute.
|
| 1674 |
-
thr_mma_dK: cute.
|
| 1675 |
-
thr_mma_dQ: cute.
|
| 1676 |
mQ: cute.Tensor,
|
| 1677 |
mK: cute.Tensor,
|
| 1678 |
mKt: Optional[cute.Tensor],
|
|
@@ -1756,6 +1832,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1756 |
)
|
| 1757 |
head_idx_kv = head_idx // self.qhead_per_kvhead
|
| 1758 |
n_block_cta_group = n_block // self.cta_group_size
|
|
|
|
| 1759 |
|
| 1760 |
# GMEM tensors (varlen-aware)
|
| 1761 |
mQ_cur = seqlen.offset_batch_Q(mQ, batch_idx, dim=3)[None, None, head_idx]
|
|
@@ -1835,6 +1912,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1835 |
single_stage=True,
|
| 1836 |
)
|
| 1837 |
|
|
|
|
| 1838 |
if const_expr(tma_atom_dOt is not None):
|
| 1839 |
gdOt = cute.local_tile(
|
| 1840 |
mdOt_cur, cute.select(self.mma_tiler_vdo, mode=[1, 2]), (None, 0)
|
|
@@ -1864,6 +1942,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1864 |
load_dO = copy_utils.tma_producer_copy_fn(load_dO, pipeline_dO)
|
| 1865 |
|
| 1866 |
# (4) dK += dS.T @ Q (2-CTA: needs separate Qt load)
|
|
|
|
| 1867 |
if const_expr(tma_atom_Qt is not None):
|
| 1868 |
gQt = cute.local_tile(
|
| 1869 |
mQt_cur, cute.select(self.mma_tiler_dsq, mode=[1, 2]), (0, None)
|
|
@@ -1880,6 +1959,7 @@ class FlashAttentionBackwardSm100:
|
|
| 1880 |
load_Qt = copy_utils.tma_producer_copy_fn(load_Qt, pipeline_Qt)
|
| 1881 |
|
| 1882 |
# (5) dQ = dS @ K
|
|
|
|
| 1883 |
if const_expr(self.use_2cta_instrs):
|
| 1884 |
gKt = cute.local_tile(
|
| 1885 |
mKt_cur, cute.select(self.mma_tiler_dsk, mode=[1, 2]), (0, n_block_cta_group)
|
|
@@ -1904,59 +1984,18 @@ class FlashAttentionBackwardSm100:
|
|
| 1904 |
# gdPsum = cute.logical_divide(gdPsum, (64,))[(None, block_in_cluster_coord_vmnk[1]), None]
|
| 1905 |
# copy_stats = partial(cute.copy, copy_atom_stats, mcast_mask=q_do_mcast_mask)
|
| 1906 |
|
| 1907 |
-
|
| 1908 |
-
if const_expr(self.use_block_sparsity):
|
| 1909 |
-
total_m_block_cnt = get_total_q_block_count_bwd(
|
| 1910 |
-
blocksparse_tensors,
|
| 1911 |
-
batch_idx,
|
| 1912 |
-
head_idx,
|
| 1913 |
-
n_block,
|
| 1914 |
-
subtile_factor=self.subtile_factor,
|
| 1915 |
-
m_block_max=m_block_max,
|
| 1916 |
-
)
|
| 1917 |
-
process_tile = total_m_block_cnt > Int32(0)
|
| 1918 |
-
else:
|
| 1919 |
process_tile = (
|
| 1920 |
const_expr(not self.is_local and not self.is_varlen_q)
|
| 1921 |
or m_block_min < m_block_max
|
| 1922 |
)
|
| 1923 |
|
| 1924 |
-
|
| 1925 |
-
if const_expr(self.use_block_sparsity):
|
| 1926 |
-
producer_state_Q_LSE, producer_state_dO_dPsum = (
|
| 1927 |
-
produce_block_sparse_q_loads_bwd_sm100(
|
| 1928 |
-
blocksparse_tensors,
|
| 1929 |
-
batch_idx,
|
| 1930 |
-
head_idx,
|
| 1931 |
-
n_block,
|
| 1932 |
-
producer_state_Q_LSE,
|
| 1933 |
-
producer_state_dO_dPsum,
|
| 1934 |
-
pipeline_Q,
|
| 1935 |
-
pipeline_LSE,
|
| 1936 |
-
pipeline_dO,
|
| 1937 |
-
pipeline_dPsum,
|
| 1938 |
-
load_K,
|
| 1939 |
-
load_V,
|
| 1940 |
-
load_Q,
|
| 1941 |
-
load_dO,
|
| 1942 |
-
copy_stats,
|
| 1943 |
-
gLSE,
|
| 1944 |
-
sLSE,
|
| 1945 |
-
gdPsum,
|
| 1946 |
-
sdPsum,
|
| 1947 |
-
self.tma_copy_bytes["K"],
|
| 1948 |
-
self.tma_copy_bytes["V"],
|
| 1949 |
-
should_load_Q=should_load_Q,
|
| 1950 |
-
should_load_dO=should_load_dO,
|
| 1951 |
-
subtile_factor=self.subtile_factor,
|
| 1952 |
-
m_block_max=m_block_max,
|
| 1953 |
-
)
|
| 1954 |
-
)
|
| 1955 |
-
else:
|
| 1956 |
first_m_block = m_block_min
|
| 1957 |
if const_expr(self.use_2cta_instrs and self.tile_hdim == 192):
|
| 1958 |
#### Prologue ####
|
| 1959 |
assert should_load_Q and should_load_dO
|
|
|
|
| 1960 |
# K & Q (for S)
|
| 1961 |
pipeline_Q.producer_acquire(
|
| 1962 |
producer_state_Q_Qt,
|
|
@@ -2060,6 +2099,10 @@ class FlashAttentionBackwardSm100:
|
|
| 2060 |
pipeline_dO.producer_commit(producer_state_O_Ot)
|
| 2061 |
producer_state_O_Ot.advance()
|
| 2062 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2063 |
else:
|
| 2064 |
#### Prologue ####
|
| 2065 |
if const_expr(should_load_Q):
|
|
@@ -2089,7 +2132,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2089 |
pipeline_dO.producer_acquire(
|
| 2090 |
producer_state_dO_dPsum,
|
| 2091 |
extra_tx_count=self.tma_copy_bytes["V"] + self.tma_copy_bytes["dO"]
|
| 2092 |
-
if const_expr(
|
| 2093 |
else self.tma_copy_bytes["V"],
|
| 2094 |
)
|
| 2095 |
load_V(
|
|
@@ -2098,7 +2141,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2098 |
)
|
| 2099 |
)
|
| 2100 |
load_dO(first_m_block, producer_state=producer_state_dO_dPsum)
|
| 2101 |
-
if const_expr(
|
| 2102 |
load_dOt(first_m_block, producer_state=producer_state_dO_dPsum)
|
| 2103 |
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 2104 |
|
|
@@ -2122,7 +2165,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2122 |
#### Main Loop ####
|
| 2123 |
for m_block in cutlass.range(m_block_min + 1, m_block_max, unroll=1):
|
| 2124 |
if const_expr(should_load_Q):
|
| 2125 |
-
if const_expr(
|
| 2126 |
pipeline_Qt.producer_acquire(producer_state_Qt)
|
| 2127 |
load_Qt(m_block - 1, producer_state=producer_state_Qt)
|
| 2128 |
pipeline_Qt.producer_commit(producer_state_Qt)
|
|
@@ -2149,11 +2192,11 @@ class FlashAttentionBackwardSm100:
|
|
| 2149 |
pipeline_dO.producer_acquire(
|
| 2150 |
producer_state_dO_dPsum,
|
| 2151 |
extra_tx_count=self.tma_copy_bytes["dO"]
|
| 2152 |
-
if const_expr(
|
| 2153 |
else 0,
|
| 2154 |
)
|
| 2155 |
load_dO(m_block, producer_state=producer_state_dO_dPsum)
|
| 2156 |
-
if const_expr(
|
| 2157 |
load_dOt(m_block, producer_state=producer_state_dO_dPsum)
|
| 2158 |
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 2159 |
|
|
@@ -2171,27 +2214,104 @@ class FlashAttentionBackwardSm100:
|
|
| 2171 |
|
| 2172 |
#### Tail ####
|
| 2173 |
if const_expr(should_load_Q):
|
| 2174 |
-
if const_expr(
|
| 2175 |
pipeline_Qt.producer_acquire(producer_state_Qt)
|
| 2176 |
load_Qt(m_block_max - 1, producer_state=producer_state_Qt)
|
| 2177 |
pipeline_Qt.producer_commit(producer_state_Qt)
|
| 2178 |
producer_state_Qt.advance()
|
| 2179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2180 |
if const_expr(self.use_2cta_instrs and self.tile_hdim == 192):
|
| 2181 |
-
|
| 2182 |
-
|
| 2183 |
-
|
| 2184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2185 |
else:
|
| 2186 |
-
|
| 2187 |
-
|
| 2188 |
-
|
| 2189 |
-
|
| 2190 |
-
|
| 2191 |
-
|
| 2192 |
-
|
| 2193 |
-
|
| 2194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2195 |
tile_scheduler.prefetch_next_work()
|
| 2196 |
tile_scheduler.advance_to_next_work()
|
| 2197 |
work_tile = tile_scheduler.get_current_work()
|
|
@@ -2232,6 +2352,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2232 |
pipeline_dKV: PipelineAsync,
|
| 2233 |
pipeline_dP: PipelineAsync,
|
| 2234 |
pipeline_dQ: PipelineAsync,
|
|
|
|
| 2235 |
block_info: BlockInfo,
|
| 2236 |
SeqlenInfoCls: Callable,
|
| 2237 |
TileSchedulerCls: Callable,
|
|
@@ -2343,6 +2464,10 @@ class FlashAttentionBackwardSm100:
|
|
| 2343 |
)
|
| 2344 |
producer_phase_acc = Int32(1) # For S & P, dP, dQ
|
| 2345 |
producer_phase_dQ = Int32(1) # 2-CTA: separate phase for dQ pipeline
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2346 |
consumer_state_dS = cutlass.pipeline.make_pipeline_state(
|
| 2347 |
cutlass.pipeline.PipelineUserType.Consumer, 1
|
| 2348 |
)
|
|
@@ -2366,8 +2491,8 @@ class FlashAttentionBackwardSm100:
|
|
| 2366 |
blocksparse_tensors,
|
| 2367 |
batch_idx,
|
| 2368 |
head_idx,
|
| 2369 |
-
n_block,
|
| 2370 |
-
|
| 2371 |
m_block_max=m_block_max,
|
| 2372 |
)
|
| 2373 |
process_tile = block_iter_count > Int32(0)
|
|
@@ -2392,7 +2517,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2392 |
# 4. dV = P.T @ dO
|
| 2393 |
# 5. dQ = dS @ K
|
| 2394 |
|
| 2395 |
-
main_loop_iters =
|
| 2396 |
|
| 2397 |
# empty waits
|
| 2398 |
# pipeline_S_P.sync_object_empty.wait(0, producer_phase_acc)
|
|
@@ -2582,20 +2707,26 @@ class FlashAttentionBackwardSm100:
|
|
| 2582 |
else:
|
| 2583 |
if is_leader_cta and process_tile:
|
| 2584 |
accumulate_dK = False
|
|
|
|
| 2585 |
# -----------------------------------------------------------
|
| 2586 |
###### Prologue
|
| 2587 |
# -----------------------------------------------------------
|
| 2588 |
-
# 1. S =
|
| 2589 |
-
# 2. dP = V @
|
| 2590 |
-
# 3. dV
|
| 2591 |
|
| 2592 |
-
# 1) S = K @ Q
|
| 2593 |
handle_Q = pipeline_Q_consumer.wait_and_advance()
|
| 2594 |
-
|
|
|
|
|
|
|
|
|
|
| 2595 |
mma_qk_fn(B_idx=handle_Q.index)
|
| 2596 |
pipeline_S_P.sync_object_full.arrive(0, pipeline_S_P.producer_mask, cta_group)
|
|
|
|
|
|
|
| 2597 |
|
| 2598 |
-
# 2) dP = V @
|
| 2599 |
pipeline_dO.consumer_wait(consumer_state_dO)
|
| 2600 |
pipeline_dP.sync_object_empty.wait(0, producer_phase_acc)
|
| 2601 |
pipeline_dQ.sync_object_empty.wait(0, producer_phase_acc)
|
|
@@ -2604,19 +2735,22 @@ class FlashAttentionBackwardSm100:
|
|
| 2604 |
|
| 2605 |
producer_phase_acc ^= 1
|
| 2606 |
# 3) dV = P.T @ dO
|
| 2607 |
-
|
| 2608 |
-
|
| 2609 |
-
|
| 2610 |
-
|
|
|
|
| 2611 |
|
| 2612 |
# -----------------------------------------------------------
|
| 2613 |
###### MAIN LOOP
|
| 2614 |
# -----------------------------------------------------------
|
| 2615 |
-
#
|
| 2616 |
-
#
|
| 2617 |
-
#
|
| 2618 |
-
#
|
| 2619 |
-
#
|
|
|
|
|
|
|
| 2620 |
|
| 2621 |
# For block sparsity, we use block_iter_count; for dense, use m_block range
|
| 2622 |
# MMA doesn't need actual m_block indices, just the iteration count
|
|
@@ -2630,10 +2764,25 @@ class FlashAttentionBackwardSm100:
|
|
| 2630 |
for _ in cutlass.range(main_loop_iters, unroll=1):
|
| 2631 |
# (1) S.T = K @ Q.T
|
| 2632 |
handle_Q_next = pipeline_Q_consumer.wait_and_advance()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2633 |
mma_qk_fn(B_idx=handle_Q_next.index)
|
| 2634 |
pipeline_S_P.sync_object_full.arrive(
|
| 2635 |
0, pipeline_S_P.producer_mask, cta_group
|
| 2636 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2637 |
|
| 2638 |
# (2) dK += dS.T @ Q
|
| 2639 |
pipeline_dS.consumer_wait(consumer_state_dS)
|
|
@@ -2647,22 +2796,34 @@ class FlashAttentionBackwardSm100:
|
|
| 2647 |
pipeline_dS.consumer_release(consumer_state_dS)
|
| 2648 |
consumer_state_dS.advance()
|
| 2649 |
|
| 2650 |
-
# (4) dP = V @ dO.T
|
| 2651 |
pipeline_dO.consumer_wait(consumer_state_dO)
|
| 2652 |
pipeline_dQ.sync_object_empty.wait(0, producer_phase_acc)
|
| 2653 |
mma_dov_fn(B_idx=consumer_state_dO.index)
|
| 2654 |
pipeline_dP.sync_object_full.arrive(0, pipeline_dP.producer_mask, cta_group)
|
| 2655 |
|
| 2656 |
-
# (5) dV += P.T @ dO
|
| 2657 |
producer_phase_acc ^= 1
|
| 2658 |
-
|
| 2659 |
-
|
| 2660 |
-
|
| 2661 |
-
|
|
|
|
| 2662 |
|
| 2663 |
handle_Q = handle_Q_next
|
| 2664 |
|
| 2665 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2666 |
|
| 2667 |
# signal to the epilogue that dV is ready
|
| 2668 |
# pipeline_dKV.producer_acquire(producer_state_dKV)
|
|
@@ -2753,13 +2914,17 @@ class FlashAttentionBackwardSm100:
|
|
| 2753 |
n_block,
|
| 2754 |
softmax_scale,
|
| 2755 |
seqlen_info,
|
| 2756 |
-
|
| 2757 |
fastdiv_mods=(None, None),
|
| 2758 |
):
|
| 2759 |
"""Apply forward score modification for SM100 backward pass."""
|
| 2760 |
-
# In bwd, S is computed as K @ Q.T so dimensions are (tile_n, tile_m)
|
| 2761 |
-
|
| 2762 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2763 |
tScS = thr_mma_S.partition_C(cS)
|
| 2764 |
tScS_idx = thr_copy_t2r.partition_D(tScS)
|
| 2765 |
|
|
@@ -2772,7 +2937,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2772 |
softmax_scale,
|
| 2773 |
self.vec_size,
|
| 2774 |
self.qk_acc_dtype,
|
| 2775 |
-
|
| 2776 |
fastdiv_mods,
|
| 2777 |
seqlen_info,
|
| 2778 |
constant_q_idx=None,
|
|
@@ -2790,7 +2955,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2790 |
head_idx,
|
| 2791 |
softmax_scale,
|
| 2792 |
seqlen_info,
|
| 2793 |
-
|
| 2794 |
fastdiv_mods=(None, None),
|
| 2795 |
):
|
| 2796 |
"""Apply backward score modification (joint graph) for SM100."""
|
|
@@ -2804,7 +2969,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2804 |
softmax_scale,
|
| 2805 |
self.vec_size,
|
| 2806 |
self.qk_acc_dtype,
|
| 2807 |
-
|
| 2808 |
fastdiv_mods,
|
| 2809 |
seqlen_info,
|
| 2810 |
constant_q_idx=None,
|
|
@@ -2815,10 +2980,10 @@ class FlashAttentionBackwardSm100:
|
|
| 2815 |
@cute.jit
|
| 2816 |
def compute_loop(
|
| 2817 |
self,
|
| 2818 |
-
thr_mma_S: cute.
|
| 2819 |
-
thr_mma_dP: cute.
|
| 2820 |
-
thr_mma_dV: cute.
|
| 2821 |
-
thr_mma_dK: cute.
|
| 2822 |
tStS: cute.Tensor,
|
| 2823 |
tdPtdP: cute.Tensor,
|
| 2824 |
tdVtdV: cute.Tensor,
|
|
@@ -2835,6 +3000,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2835 |
pipeline_dS: PipelineAsync,
|
| 2836 |
pipeline_dKV: PipelineAsync,
|
| 2837 |
pipeline_dP: PipelineAsync,
|
|
|
|
| 2838 |
dS_cluster_empty_mbar_ptr: cute.Pointer,
|
| 2839 |
dS_cluster_full_mbar_ptr: cute.Pointer,
|
| 2840 |
dQaccum_empty_mbar_ptr: cute.Pointer,
|
|
@@ -2853,7 +3019,7 @@ class FlashAttentionBackwardSm100:
|
|
| 2853 |
tiled_copy_r2s_dKV: Optional[cute.TiledCopy],
|
| 2854 |
mdK_semaphore: Optional[cute.Tensor],
|
| 2855 |
mdV_semaphore: Optional[cute.Tensor],
|
| 2856 |
-
|
| 2857 |
fastdiv_mods=(None, None),
|
| 2858 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 2859 |
):
|
|
@@ -2888,13 +3054,17 @@ class FlashAttentionBackwardSm100:
|
|
| 2888 |
|
| 2889 |
tileP_f32_like = self.cta_tiler[1] // 32 * self.v_dtype.width
|
| 2890 |
# tStS has shape ((128, 128), 1, 1), tStP has shape ((128, 64), 1, 1)
|
| 2891 |
-
# tP overlap with tS
|
| 2892 |
tStP = cute.composition(tStS, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
| 2893 |
-
|
|
|
|
|
|
|
|
|
|
| 2894 |
tScS = thr_mma_S.partition_C(cute.make_identity_tensor(self.mma_tiler_kq[:2]))
|
| 2895 |
tScP = cute.composition(tScS, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
| 2896 |
-
# tdS overlap with tdP
|
| 2897 |
tdPtdS = cute.composition(tdPtdP, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
|
|
|
|
|
|
|
|
|
| 2898 |
tdPcdP = thr_mma_dP.partition_C(cute.make_identity_tensor(self.mma_tiler_vdo[:2]))
|
| 2899 |
tdPcdS = cute.composition(tdPcdP, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
| 2900 |
|
|
@@ -2953,6 +3123,12 @@ class FlashAttentionBackwardSm100:
|
|
| 2953 |
cutlass.pipeline.PipelineUserType.Consumer, 1
|
| 2954 |
)
|
| 2955 |
# consumer_phase_S_P_dP = Int32(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2956 |
producer_state_dS = pipeline.make_pipeline_state( # Our impl has shortcut for stage==1
|
| 2957 |
cutlass.pipeline.PipelineUserType.Producer, 1
|
| 2958 |
)
|
|
@@ -2971,31 +3147,58 @@ class FlashAttentionBackwardSm100:
|
|
| 2971 |
while work_tile.is_valid_tile:
|
| 2972 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 2973 |
seqlen = SeqlenInfoCls(batch_idx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2974 |
m_block_min, m_block_max = block_info.get_m_block_min_max(
|
| 2975 |
seqlen, n_block // self.cluster_shape_mnk[0]
|
| 2976 |
)
|
| 2977 |
mask = AttentionMaskCls(seqlen)
|
| 2978 |
-
|
| 2979 |
# TODO: condition mask_seqlen
|
| 2980 |
mask_fn = partial(
|
| 2981 |
mask.apply_mask_sm100_transposed,
|
| 2982 |
tScS_t2r=tScS_t2r,
|
| 2983 |
t0ScS_t2r=t0ScS_t2r,
|
| 2984 |
-
n_block=
|
| 2985 |
mask_seqlen=True,
|
| 2986 |
mask_causal=self.is_causal,
|
| 2987 |
mask_local=self.is_local,
|
| 2988 |
mask_mod=self.mask_mod,
|
| 2989 |
batch_idx=batch_idx,
|
| 2990 |
head_idx=head_idx,
|
| 2991 |
-
|
| 2992 |
fastdiv_mods=fastdiv_mods,
|
| 2993 |
)
|
| 2994 |
|
| 2995 |
# prefetch_LSE = not self.is_causal
|
| 2996 |
prefetch_LSE = False
|
| 2997 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2998 |
if const_expr(self.use_block_sparsity):
|
|
|
|
| 2999 |
(
|
| 3000 |
curr_q_cnt,
|
| 3001 |
curr_q_idx,
|
|
@@ -3006,22 +3209,19 @@ class FlashAttentionBackwardSm100:
|
|
| 3006 |
blocksparse_tensors,
|
| 3007 |
batch_idx,
|
| 3008 |
head_idx,
|
| 3009 |
-
n_block,
|
| 3010 |
-
|
| 3011 |
m_block_max=m_block_max,
|
| 3012 |
)
|
| 3013 |
process_tile = loop_count > Int32(0)
|
| 3014 |
-
else:
|
| 3015 |
-
process_tile = (
|
| 3016 |
-
const_expr(not self.is_local and not self.is_varlen_q)
|
| 3017 |
-
or m_block_min < m_block_max
|
| 3018 |
-
)
|
| 3019 |
-
loop_count = m_block_max - m_block_min
|
| 3020 |
|
| 3021 |
# Mainloop
|
| 3022 |
# Block sparsity: iterate over sparse m_block count and derive actual m_block
|
| 3023 |
# from Q_IDX/FULL_Q_IDX tensors. Dense: iterate m_block_min..m_block_max directly.
|
| 3024 |
for iter_idx in cutlass.range(loop_count, unroll=1):
|
|
|
|
|
|
|
|
|
|
| 3025 |
if const_expr(self.use_block_sparsity):
|
| 3026 |
m_block, is_full_block = get_m_block_from_iter_bwd(
|
| 3027 |
iter_idx,
|
|
@@ -3029,27 +3229,34 @@ class FlashAttentionBackwardSm100:
|
|
| 3029 |
curr_q_idx,
|
| 3030 |
curr_full_cnt,
|
| 3031 |
curr_full_idx,
|
| 3032 |
-
|
| 3033 |
m_block_max=m_block_max,
|
| 3034 |
)
|
| 3035 |
m_block_oob = m_block >= m_block_max
|
| 3036 |
-
else:
|
| 3037 |
-
m_block = m_block_min + iter_idx
|
| 3038 |
-
m_block_oob = False
|
| 3039 |
-
is_full_block = False
|
| 3040 |
# Prefetch 1 stage of LSE
|
| 3041 |
pipeline_LSE.consumer_wait(consumer_state_LSE)
|
| 3042 |
-
tSrLSE_s2r = cute.
|
| 3043 |
if const_expr(prefetch_LSE and not self.shuffle_LSE):
|
| 3044 |
cute.autovec_copy(tSsLSE[None, 0, 0, 0, consumer_state_LSE.index], tSrLSE_s2r)
|
| 3045 |
|
| 3046 |
-
|
|
|
|
|
|
|
|
|
|
| 3047 |
# pipeline_S_P.sync_object_full.wait(0, consumer_phase_S_P_dP)
|
| 3048 |
#### TMEM->RMEM (Load S from TMEM)
|
| 3049 |
-
tSrS_t2r = cute.
|
| 3050 |
cute.copy(thr_copy_t2r, tStS_t2r, tSrS_t2r)
|
| 3051 |
|
| 3052 |
-
if const_expr(self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3053 |
# Signal S tmem load completion using pipeline_S_P when hdim 192
|
| 3054 |
# dP is overlapped with S
|
| 3055 |
cute.arch.fence_view_async_tmem_load()
|
|
@@ -3080,7 +3287,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3080 |
n_block,
|
| 3081 |
softmax_scale,
|
| 3082 |
seqlen,
|
| 3083 |
-
|
| 3084 |
fastdiv_mods,
|
| 3085 |
)
|
| 3086 |
|
|
@@ -3097,7 +3304,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3097 |
#### P = exp(S - LSE)
|
| 3098 |
# ---------------------------------------------
|
| 3099 |
lane_idx = cute.arch.lane_idx()
|
| 3100 |
-
tSrP_r2t_f32 = cute.
|
| 3101 |
tSrP_r2t = cute.recast_tensor(tSrP_r2t_f32, self.q_dtype)
|
| 3102 |
for stage in cutlass.range_constexpr(num_stages):
|
| 3103 |
tSrS_cur = tSrS_t2r[None, stage, 0, 0]
|
|
@@ -3125,10 +3332,14 @@ class FlashAttentionBackwardSm100:
|
|
| 3125 |
tSrS_cur[2 * v + 1] = cute.math.exp2(tSrS_cur[2 * v + 1], fastmath=True)
|
| 3126 |
utils.cvt_f16(tSrS_cur, tSrP_r2t[None, stage, 0, 0])
|
| 3127 |
if const_expr(stage == 0):
|
| 3128 |
-
|
| 3129 |
-
|
| 3130 |
-
|
| 3131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3132 |
cute.copy(
|
| 3133 |
thr_copy_r2t,
|
| 3134 |
tSrP_r2t_f32[None, stage, None, None],
|
|
@@ -3137,12 +3348,22 @@ class FlashAttentionBackwardSm100:
|
|
| 3137 |
|
| 3138 |
cute.arch.fence_view_async_tmem_store()
|
| 3139 |
cute.arch.fence_view_async_shared()
|
| 3140 |
-
|
| 3141 |
-
if const_expr(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3142 |
# Signal tmem store P completion with pipeline_S_P
|
| 3143 |
with cute.arch.elect_one():
|
| 3144 |
pipeline_S_P.consumer_release(consumer_state_S_P_dP)
|
| 3145 |
# pipeline_S_P.sync_object_empty.arrive(0, pipeline_S_P.consumer_mask)
|
|
|
|
|
|
|
| 3146 |
pipeline_LSE.consumer_release(consumer_state_LSE)
|
| 3147 |
consumer_state_LSE.advance()
|
| 3148 |
# ---------------------------------------------
|
|
@@ -3157,10 +3378,12 @@ class FlashAttentionBackwardSm100:
|
|
| 3157 |
|
| 3158 |
##### dS.T = P.T * (dP.T - Psum)
|
| 3159 |
for stage in cutlass.range_constexpr(num_stages):
|
| 3160 |
-
tdPrdP_t2r = cute.
|
| 3161 |
cute.copy(thr_copy_t2r, tdPtdP_t2r[None, stage, None, None], tdPrdP_t2r)
|
| 3162 |
cute.arch.fence_view_async_tmem_load()
|
| 3163 |
-
self.
|
|
|
|
|
|
|
| 3164 |
tdPrdP_cur = tdPrdP_t2r[None, 0, 0]
|
| 3165 |
tSrS_cur = tSrS_t2r[None, stage, 0, 0]
|
| 3166 |
tSsdPsum_cur = tSsdPsum[None, stage, 0, 0, consumer_state_dPsum.index]
|
|
@@ -3177,10 +3400,8 @@ class FlashAttentionBackwardSm100:
|
|
| 3177 |
utils.shuffle_sync(tSrdPsum, offset=2 * v),
|
| 3178 |
utils.shuffle_sync(tSrdPsum, offset=2 * v + 1),
|
| 3179 |
)
|
| 3180 |
-
tdPrdP_cur[2 * v], tdPrdP_cur[2 * v + 1] = (
|
| 3181 |
-
|
| 3182 |
-
(tdPrdP_cur[2 * v], tdPrdP_cur[2 * v + 1]), dPsum_pair
|
| 3183 |
-
)
|
| 3184 |
)
|
| 3185 |
tdPrdP_cur[2 * v], tdPrdP_cur[2 * v + 1] = cute.arch.mul_packed_f32x2(
|
| 3186 |
(tSrS_cur[2 * v], tSrS_cur[2 * v + 1]),
|
|
@@ -3189,9 +3410,12 @@ class FlashAttentionBackwardSm100:
|
|
| 3189 |
|
| 3190 |
if const_expr(self.score_mod_bwd is not None):
|
| 3191 |
tSrS_pre_cur = tSrS_pre[None, stage, 0, 0]
|
| 3192 |
-
|
|
|
|
|
|
|
| 3193 |
cS_bwd = cute.domain_offset(
|
| 3194 |
-
(
|
|
|
|
| 3195 |
)
|
| 3196 |
tScS_bwd = thr_mma_S.partition_C(cS_bwd)
|
| 3197 |
tScS_idx_bwd = thr_copy_t2r.partition_D(tScS_bwd)
|
|
@@ -3204,7 +3428,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3204 |
head_idx,
|
| 3205 |
softmax_scale,
|
| 3206 |
seqlen,
|
| 3207 |
-
|
| 3208 |
fastdiv_mods,
|
| 3209 |
)
|
| 3210 |
# Zero out OOB positions (kv_idx >= seqlen_k) after score_mod_bwd
|
|
@@ -3252,7 +3476,13 @@ class FlashAttentionBackwardSm100:
|
|
| 3252 |
cute.autovec_copy(tdPrdS_xchg, tRS_sdS_xchg[None, 0])
|
| 3253 |
|
| 3254 |
cute.arch.fence_view_async_shared()
|
| 3255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3256 |
pipeline_dPsum.consumer_release(consumer_state_dPsum)
|
| 3257 |
consumer_state_dPsum.advance()
|
| 3258 |
# when 2cta hdim 128, pipeline_dS also signals S tmem load completion so is deferred
|
|
@@ -3413,12 +3643,67 @@ class FlashAttentionBackwardSm100:
|
|
| 3413 |
tile_scheduler.advance_to_next_work()
|
| 3414 |
work_tile = tile_scheduler.get_current_work()
|
| 3415 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3416 |
@cute.jit
|
| 3417 |
def dQacc_reduce(
|
| 3418 |
self,
|
| 3419 |
mdQaccum: cute.Tensor,
|
| 3420 |
sdQaccum: cute.Tensor,
|
| 3421 |
-
thr_mma_dQ: cute.
|
| 3422 |
tdQtdQ: cute.Tensor,
|
| 3423 |
pipeline_dQ: PipelineAsync,
|
| 3424 |
dQaccum_empty_mbar_ptr: Optional[cute.Pointer],
|
|
@@ -3470,6 +3755,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3470 |
while work_tile.is_valid_tile:
|
| 3471 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 3472 |
n_block_cta_group = n_block // self.cta_group_size # for 2cta
|
|
|
|
| 3473 |
seqlen = SeqlenInfoCls(batch_idx)
|
| 3474 |
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block_cta_group)
|
| 3475 |
if const_expr(not seqlen.has_cu_seqlens_q):
|
|
@@ -3485,13 +3771,43 @@ class FlashAttentionBackwardSm100:
|
|
| 3485 |
)
|
| 3486 |
|
| 3487 |
if const_expr(self.deterministic):
|
|
|
|
| 3488 |
mdQ_semaphore_cur = mdQ_semaphore[None, None, head_idx, batch_idx]
|
| 3489 |
|
| 3490 |
-
|
| 3491 |
-
delay_semaphore_release = not self.tile_hdim == 192
|
| 3492 |
|
| 3493 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3494 |
if const_expr(self.use_block_sparsity):
|
|
|
|
| 3495 |
(
|
| 3496 |
curr_q_cnt,
|
| 3497 |
curr_q_idx,
|
|
@@ -3502,22 +3818,30 @@ class FlashAttentionBackwardSm100:
|
|
| 3502 |
blocksparse_tensors,
|
| 3503 |
batch_idx,
|
| 3504 |
head_idx,
|
| 3505 |
-
|
| 3506 |
-
|
| 3507 |
m_block_max=m_block_max,
|
| 3508 |
)
|
| 3509 |
process_tile = loop_count > Int32(0)
|
| 3510 |
-
|
| 3511 |
-
|
| 3512 |
-
|
| 3513 |
-
|
| 3514 |
-
|
| 3515 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3516 |
|
| 3517 |
# dQacc_reduce mainloop
|
| 3518 |
# Block sparsity: iterate over sparse m_block count and derive actual m_block
|
| 3519 |
# from Q_IDX/FULL_Q_IDX tensors. Dense: iterate m_block_min..m_block_max directly.
|
| 3520 |
for iter_idx in cutlass.range(loop_count, unroll=1):
|
|
|
|
|
|
|
| 3521 |
if const_expr(self.use_block_sparsity):
|
| 3522 |
m_block, _ = get_m_block_from_iter_bwd(
|
| 3523 |
iter_idx,
|
|
@@ -3525,16 +3849,13 @@ class FlashAttentionBackwardSm100:
|
|
| 3525 |
curr_q_idx,
|
| 3526 |
curr_full_cnt,
|
| 3527 |
curr_full_idx,
|
| 3528 |
-
|
| 3529 |
m_block_max=m_block_max,
|
| 3530 |
)
|
| 3531 |
-
|
| 3532 |
-
m_block = cutlass.min(m_block, m_block_max - 1)
|
| 3533 |
-
else:
|
| 3534 |
-
m_block = m_block_min + iter_idx
|
| 3535 |
pipeline_dQ.consumer_wait(dQ_consumer_state)
|
| 3536 |
# TMEM -> RMEM
|
| 3537 |
-
tdQrdQ_t2r = cute.
|
| 3538 |
cute.copy(thr_copy_t2r, tdQtdQ_t2r, tdQrdQ_t2r)
|
| 3539 |
cute.arch.fence_view_async_tmem_load()
|
| 3540 |
cute.arch.sync_warp()
|
|
@@ -3542,6 +3863,8 @@ class FlashAttentionBackwardSm100:
|
|
| 3542 |
pipeline_dQ.consumer_release(dQ_consumer_state)
|
| 3543 |
dQ_consumer_state.advance()
|
| 3544 |
|
|
|
|
|
|
|
| 3545 |
gdQaccum_cur = gdQaccum[None, None, m_block]
|
| 3546 |
|
| 3547 |
tdQrdQ_shape = (
|
|
@@ -3559,22 +3882,27 @@ class FlashAttentionBackwardSm100:
|
|
| 3559 |
cute.arch.fence_view_async_shared()
|
| 3560 |
# semaphore acquire
|
| 3561 |
if const_expr(self.deterministic and stage == 0):
|
| 3562 |
-
if
|
| 3563 |
-
|
| 3564 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3565 |
)
|
| 3566 |
-
lock_value = n_block_max_for_m_block - 1 - n_block_cta_group
|
| 3567 |
-
else:
|
| 3568 |
-
lock_value = n_block_cta_group
|
| 3569 |
-
barrier.wait_eq(
|
| 3570 |
-
mdQ_semaphore_cur[(m_block, None)].iterator,
|
| 3571 |
-
tidx,
|
| 3572 |
-
cta_rank_in_cluster,
|
| 3573 |
-
lock_value,
|
| 3574 |
-
)
|
| 3575 |
self.reduce_sync_barrier.arrive_and_wait()
|
| 3576 |
# Copy from shared memory to global memory
|
| 3577 |
-
if is_tma_warp:
|
| 3578 |
with cute.arch.elect_one():
|
| 3579 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 3580 |
sdQaccum[None, smem_idx].iterator,
|
|
@@ -3583,20 +3911,12 @@ class FlashAttentionBackwardSm100:
|
|
| 3583 |
)
|
| 3584 |
cute.arch.cp_async_bulk_commit_group()
|
| 3585 |
cute.arch.cp_async_bulk_wait_group(self.sdQaccum_stage - 1, read=read_flag)
|
|
|
|
|
|
|
|
|
|
| 3586 |
self.reduce_sync_barrier.arrive_and_wait()
|
| 3587 |
dQ_tma_store_producer_state.advance()
|
| 3588 |
-
|
| 3589 |
-
# tdQgdQ = thr_copy_dQaccum_r2s.partition_D(gdQaccum[None, stage, m_block])
|
| 3590 |
-
# assert cute.size(tdQrdQ_r2s) == cute.size(tdQgdQ)
|
| 3591 |
-
# for i in cutlass.range(cute.size(tdQrdQ_r2s) // 4, unroll_full=True):
|
| 3592 |
-
# copy_utils.atomic_add_fp32x4(
|
| 3593 |
-
# tdQrdQ_r2s[4 * i],
|
| 3594 |
-
# tdQrdQ_r2s[4 * i + 1],
|
| 3595 |
-
# tdQrdQ_r2s[4 * i + 2],
|
| 3596 |
-
# tdQrdQ_r2s[4 * i + 3],
|
| 3597 |
-
# utils.elem_pointer(tdQgdQ, 4 * i),
|
| 3598 |
-
# )
|
| 3599 |
-
# semaphore release for prior m_block
|
| 3600 |
if const_expr(self.deterministic and stage == 0 and delay_semaphore_release):
|
| 3601 |
if m_block > m_block_min:
|
| 3602 |
barrier.arrive_inc(
|
|
@@ -3618,12 +3938,16 @@ class FlashAttentionBackwardSm100:
|
|
| 3618 |
# NOTE: arrive_inc calls red_release which issues membar
|
| 3619 |
if const_expr(self.deterministic and not delay_semaphore_release):
|
| 3620 |
if const_expr(self.sdQaccum_stage > 1 and not self.tile_hdim == 192):
|
| 3621 |
-
if is_tma_warp:
|
| 3622 |
cute.arch.cp_async_bulk_wait_group(0, read=read_flag)
|
| 3623 |
self.reduce_sync_barrier.arrive_and_wait()
|
| 3624 |
-
|
| 3625 |
-
|
| 3626 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3627 |
|
| 3628 |
if process_tile:
|
| 3629 |
if is_tma_warp:
|
|
@@ -3639,7 +3963,10 @@ class FlashAttentionBackwardSm100:
|
|
| 3639 |
)
|
| 3640 |
|
| 3641 |
if const_expr(
|
| 3642 |
-
self.deterministic
|
|
|
|
|
|
|
|
|
|
| 3643 |
):
|
| 3644 |
m_block_global_max = cute.ceil_div(seqlen.seqlen_q, self.tile_m)
|
| 3645 |
for m_block in cutlass.range(m_block_max, m_block_global_max, unroll=1):
|
|
@@ -3650,6 +3977,9 @@ class FlashAttentionBackwardSm100:
|
|
| 3650 |
tile_scheduler.advance_to_next_work()
|
| 3651 |
work_tile = tile_scheduler.get_current_work()
|
| 3652 |
|
|
|
|
|
|
|
|
|
|
| 3653 |
@cute.jit
|
| 3654 |
def epilogue_dKV(
|
| 3655 |
self,
|
|
@@ -3659,8 +3989,8 @@ class FlashAttentionBackwardSm100:
|
|
| 3659 |
head_idx: Int32,
|
| 3660 |
n_block: Int32,
|
| 3661 |
seqlen,
|
| 3662 |
-
thr_mma_dV: cute.
|
| 3663 |
-
thr_mma_dK: cute.
|
| 3664 |
tdVtdV: cute.Tensor,
|
| 3665 |
tdKtdK: cute.Tensor,
|
| 3666 |
mdV: cute.Tensor,
|
|
@@ -3678,13 +4008,22 @@ class FlashAttentionBackwardSm100:
|
|
| 3678 |
mdV_cur = seqlen.offset_batch_K(mdV, batch_idx, dim=3)[None, None, head_idx]
|
| 3679 |
mdK_cur = seqlen.offset_batch_K(mdK, batch_idx, dim=3)[None, None, head_idx]
|
| 3680 |
|
| 3681 |
-
|
| 3682 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3683 |
)
|
| 3684 |
# dV
|
| 3685 |
pipeline_dKV.consumer_wait(consumer_state_dKV)
|
| 3686 |
|
| 3687 |
-
tiled_tmem_ld_dV = tcgen05.make_tmem_copy(
|
| 3688 |
thr_tmem_ld_dV = tiled_tmem_ld_dV.get_slice(tidx)
|
| 3689 |
|
| 3690 |
tdVtdV_t2r_p = thr_tmem_ld_dV.partition_S(tdVtdV)
|
|
@@ -3696,7 +4035,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3696 |
|
| 3697 |
tdVcdV_t2r_p = thr_tmem_ld_dV.partition_D(tdVcdV_tensor)
|
| 3698 |
tdVcdV_t2r = self.split_wg(tdVcdV_t2r_p, wg_idx, num_wg)
|
| 3699 |
-
tdVrdV_t2r = cute.
|
| 3700 |
|
| 3701 |
cute.copy(thr_tmem_ld_dV, tdVtdV_t2r, tdVrdV_t2r)
|
| 3702 |
cute.arch.fence_view_async_tmem_load()
|
|
@@ -3713,7 +4052,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3713 |
tiler_mn=tiled_tmem_ld_dV.tiler_mn,
|
| 3714 |
)
|
| 3715 |
|
| 3716 |
-
tdVrdV_r2s = cute.
|
| 3717 |
for i in cutlass.range_constexpr(cute.size(tdVrdV_t2r, mode=[1])):
|
| 3718 |
dV_vec = tdVrdV_t2r[(None, i, 0, 0)].load()
|
| 3719 |
tdVrdV_r2s[(None, i, 0, 0)].store(dV_vec.to(self.dv_dtype))
|
|
@@ -3725,8 +4064,11 @@ class FlashAttentionBackwardSm100:
|
|
| 3725 |
tdVgdV_r2g_p = thr_tmem_ld_dV.partition_D(tdVgdV)
|
| 3726 |
tdVgdV_r2g = self.split_wg(tdVgdV_r2g_p, wg_idx, num_wg)
|
| 3727 |
|
|
|
|
|
|
|
|
|
|
| 3728 |
if tidx < seqlen.seqlen_k - self.tile_n * n_block:
|
| 3729 |
-
cute.copy(tiled_gmem_store_dV, tdVrdV_r2s, tdVgdV_r2g)
|
| 3730 |
|
| 3731 |
cute.arch.sync_warp()
|
| 3732 |
with cute.arch.elect_one():
|
|
@@ -3736,7 +4078,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3736 |
# dK
|
| 3737 |
pipeline_dKV.consumer_wait(consumer_state_dKV)
|
| 3738 |
|
| 3739 |
-
tiled_tmem_ld_dK = tcgen05.make_tmem_copy(
|
| 3740 |
thr_tmem_ld_dK = tiled_tmem_ld_dK.get_slice(tidx)
|
| 3741 |
|
| 3742 |
tdKtdK_t2r_p = thr_tmem_ld_dK.partition_S(tdKtdK)
|
|
@@ -3748,7 +4090,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3748 |
|
| 3749 |
tdKcdK_t2r_p = thr_tmem_ld_dK.partition_D(tdKcdK_tensor)
|
| 3750 |
tdKcdK_t2r = self.split_wg(tdKcdK_t2r_p, wg_idx, num_wg)
|
| 3751 |
-
tdKrdK_t2r = cute.
|
| 3752 |
|
| 3753 |
cute.copy(tiled_tmem_ld_dK, tdKtdK_t2r, tdKrdK_t2r)
|
| 3754 |
cute.arch.fence_view_async_tmem_load()
|
|
@@ -3766,7 +4108,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3766 |
tiler_mn=tiled_tmem_ld_dK.tiler_mn,
|
| 3767 |
)
|
| 3768 |
|
| 3769 |
-
tdKrdK_r2s = cute.
|
| 3770 |
|
| 3771 |
for i in cutlass.range_constexpr(cute.size(tdKrdK_t2r, mode=[1])):
|
| 3772 |
dK_vec = tdKrdK_t2r[(None, i, 0, 0)].load() * softmax_scale
|
|
@@ -3779,14 +4121,33 @@ class FlashAttentionBackwardSm100:
|
|
| 3779 |
tdKgdK_r2g_p = thr_tmem_ld_dK.partition_D(tdKgdK)
|
| 3780 |
tdKgdK_r2g = self.split_wg(tdKgdK_r2g_p, wg_idx, num_wg)
|
| 3781 |
|
|
|
|
|
|
|
|
|
|
| 3782 |
if tidx < seqlen.seqlen_k - self.tile_n * n_block:
|
| 3783 |
-
cute.copy(tiled_gmem_store_dK, tdKrdK_r2s, tdKgdK_r2g)
|
| 3784 |
|
| 3785 |
cute.arch.sync_warp()
|
| 3786 |
with cute.arch.elect_one():
|
| 3787 |
pipeline_dKV.consumer_release(consumer_state_dKV)
|
| 3788 |
return consumer_state_dKV
|
| 3789 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3790 |
@cute.jit
|
| 3791 |
def epilogue_dK_or_dV_tma(
|
| 3792 |
self,
|
|
@@ -3795,7 +4156,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3795 |
head_idx: Int32,
|
| 3796 |
n_block: Int32,
|
| 3797 |
seqlen,
|
| 3798 |
-
thr_mma: cute.
|
| 3799 |
tdKVtdKV: cute.Tensor,
|
| 3800 |
mdKV: cute.Tensor,
|
| 3801 |
sdKV: cute.Tensor,
|
|
@@ -3861,6 +4222,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3861 |
|
| 3862 |
deterministic_KV = self.deterministic and self.qhead_per_kvhead > 1
|
| 3863 |
if const_expr(deterministic_KV):
|
|
|
|
| 3864 |
mdKV_semaphore_cur = mdKV_semaphore[n_block, None, head_idx_kv, batch_idx]
|
| 3865 |
|
| 3866 |
if const_expr(not self.dKV_postprocess):
|
|
@@ -3913,7 +4275,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3913 |
if const_expr(num_epi_stages > 1):
|
| 3914 |
tdKVcdKV_t2r = tdKVcdKV_t2r[None, epi_stage]
|
| 3915 |
|
| 3916 |
-
tdKVrdKV_t2r = cute.
|
| 3917 |
|
| 3918 |
assert cute.size(tdKVrdKV_t2r) == cute.size(tdKVtdKV_t2r) // cute.arch.WARP_SIZE, (
|
| 3919 |
"RMEM<->TMEM fragment size mismatch"
|
|
@@ -3929,7 +4291,7 @@ class FlashAttentionBackwardSm100:
|
|
| 3929 |
tdKVrdKV_t2r[2 * i], tdKVrdKV_t2r[2 * i + 1] = cute.arch.mul_packed_f32x2(
|
| 3930 |
(tdKVrdKV_t2r[2 * i], tdKVrdKV_t2r[2 * i + 1]), (scale, scale)
|
| 3931 |
)
|
| 3932 |
-
tdKVrdKV = cute.
|
| 3933 |
tdKVrdKV.store(tdKVrdKV_t2r.load().to(dtype))
|
| 3934 |
|
| 3935 |
# RMEM -> SMEM -- copy, fence and barrier
|
|
|
|
| 14 |
import cutlass.utils.blackwell_helpers as sm100_utils_basic
|
| 15 |
from cutlass.pipeline import PipelineAsync
|
| 16 |
|
|
|
|
| 17 |
from .quack import layout_utils
|
| 18 |
from . import utils
|
| 19 |
from .cute_dsl_utils import assume_tensor_aligned
|
|
|
|
| 35 |
from .named_barrier import NamedBarrierBwdSm100
|
| 36 |
from .softmax import apply_score_mod_inner, apply_score_mod_bwd_inner
|
| 37 |
from .block_sparsity import BlockSparseTensors
|
| 38 |
+
from .utils import AuxData
|
| 39 |
from .block_sparse_utils import (
|
| 40 |
get_total_q_block_count_bwd,
|
| 41 |
get_block_sparse_iteration_info_bwd,
|
| 42 |
get_m_block_from_iter_bwd,
|
| 43 |
+
produce_block_sparse_q_loads_bwd_sm100_2cta_hdim192,
|
| 44 |
+
produce_block_sparse_q_loads_bwd_sm100_default,
|
| 45 |
)
|
| 46 |
|
| 47 |
|
| 48 |
+
# NOTE [hdim64 dedicated P/dS TMEM slots]
|
| 49 |
+
# The 1-CTA hdim64 backward uses TMEM columns [0, 384), leaving room for two 64-column
|
| 50 |
+
# slots. With split_P_dS, P and dS live there instead of aliasing S and dP:
|
| 51 |
+
# * S is released as soon as the compute warps have loaded it, so the MMA warp issues
|
| 52 |
+
# QK_{t+1} while the softmax of tile t is still running; dV += P.T @ dO for tile t
|
| 53 |
+
# then trails QK_{t+1} and is gated by its own pipeline_P.
|
| 54 |
+
# * The compute-wide barriers that ordered the P-over-S and dS-over-dP overwrites are
|
| 55 |
+
# gone. They were cross-warp hazards: a warp's P (dS) columns overlap S (dP) lanes
|
| 56 |
+
# that another warp still has to load.
|
| 57 |
+
# * The remaining compute-wide barriers before pipeline arrivals become sync_warp: every
|
| 58 |
+
# mbarrier signalled from the compute warps expects one arrival per warp, so the
|
| 59 |
+
# mbarrier itself is the cross-warp join. This is 1-CTA only: at 2-CTA the barrier
|
| 60 |
+
# before the dS commit also orders every warp's sdS_xchg write ahead of the
|
| 61 |
+
# single-thread DSMEM copy to the peer CTA.
|
| 62 |
+
# The S handshake keeps its own phase/state (producer_phase_S, consumer_state_S) so its
|
| 63 |
+
# parity never depends on where the other pipelines flip. Perf evidence in PR #2804; the
|
| 64 |
+
# aliased layout is what every other shape runs.
|
| 65 |
+
|
| 66 |
+
|
| 67 |
class FlashAttentionBackwardSm100:
|
| 68 |
arch = 100
|
| 69 |
|
|
|
|
| 78 |
tile_n: int = 128,
|
| 79 |
is_persistent: bool = False,
|
| 80 |
deterministic: bool = False,
|
| 81 |
+
spt: Optional[bool] = None,
|
| 82 |
cluster_size: int = 1,
|
| 83 |
use_2cta_instrs: bool = False,
|
| 84 |
score_mod: cutlass.Constexpr | None = None,
|
| 85 |
score_mod_bwd: cutlass.Constexpr | None = None,
|
| 86 |
mask_mod: cutlass.Constexpr | None = None,
|
| 87 |
has_aux_tensors: cutlass.Constexpr = False,
|
| 88 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 89 |
+
kv_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 90 |
):
|
| 91 |
+
# Pad head_dim to a multiple of 32 (k_block_size). Must stay a multiple of 32 so
|
| 92 |
+
# that the dQ accumulator reduce (dQ_reduce_ncol up to 32) tiles evenly and matches
|
| 93 |
+
# both the preprocess kernel and the interface's head_dim_rounded (also mult of 32).
|
| 94 |
+
hdim_multiple_of = 32
|
| 95 |
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 96 |
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
| 97 |
self.same_hdim_kv = head_dim == head_dim_v
|
| 98 |
self.tile_hdimv = int(math.ceil(head_dim_v / hdim_multiple_of) * hdim_multiple_of)
|
| 99 |
self.check_hdim_oob = head_dim != self.tile_hdim
|
| 100 |
self.check_hdim_v_oob = head_dim_v != self.tile_hdimv
|
| 101 |
+
self.head_dim = head_dim
|
| 102 |
+
self.head_dim_v = head_dim_v
|
| 103 |
|
| 104 |
self.tile_m = tile_m
|
| 105 |
self.tile_n = tile_n
|
|
|
|
| 107 |
assert self.tile_hdim <= 128 or (self.tile_hdim == 192 and self.tile_hdimv == 128)
|
| 108 |
assert self.tile_hdimv <= 128
|
| 109 |
|
| 110 |
+
self.use_2cta_instrs = bool(use_2cta_instrs and cluster_size == 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
self.cta_group_size = 2 if self.use_2cta_instrs else 1
|
| 112 |
|
| 113 |
assert self.tile_hdim != 192 or self.use_2cta_instrs, "Must use 2CTA for hdim 192"
|
|
|
|
| 130 |
|
| 131 |
assert cluster_size in (1, 2), "Only cluster_size=1 or 2 is supported"
|
| 132 |
self.cluster_shape_mn = (cluster_size, 1)
|
| 133 |
+
# The MMA warp's producer_phase_acc (shared by the S/P, dP and dQ empty waits in the
|
| 134 |
+
# aliased layout) is only consistent across one work tile per CTA.
|
| 135 |
+
assert not is_persistent, "persistent tile scheduling is not supported"
|
| 136 |
self.is_persistent = is_persistent
|
| 137 |
self.is_causal = is_causal
|
| 138 |
self.is_local = is_local
|
| 139 |
self.qhead_per_kvhead = qhead_per_kvhead
|
| 140 |
self.pack_gqa = False
|
| 141 |
self.deterministic = deterministic
|
| 142 |
+
self.spt_override = spt
|
| 143 |
|
| 144 |
# Score mod and mask mod support
|
| 145 |
self.score_mod = score_mod
|
| 146 |
self.score_mod_bwd = score_mod_bwd
|
| 147 |
self.mask_mod = mask_mod
|
| 148 |
self.has_aux_tensors = has_aux_tensors
|
| 149 |
+
self.q_subtile_factor = q_subtile_factor
|
| 150 |
+
self.kv_subtile_factor = kv_subtile_factor
|
| 151 |
+
assert self.kv_subtile_factor == 1 or self.kv_subtile_factor % self.cta_group_size == 0
|
| 152 |
# For score_mod, use vec_size=1 (like forward) to handle per-element indices
|
| 153 |
if cutlass.const_expr(has_aux_tensors):
|
| 154 |
self.vec_size: cutlass.Constexpr = 1
|
|
|
|
| 162 |
# Generally slower to use store dS in smem for dK, and doesn't work for 2cta
|
| 163 |
self.use_smem_dS_for_mma_dK = False
|
| 164 |
|
| 165 |
+
# See NOTE [hdim64 dedicated P/dS TMEM slots]
|
| 166 |
+
self.split_P_dS = (
|
| 167 |
+
cluster_size == 1
|
| 168 |
+
and self.tile_m == 128
|
| 169 |
+
and self.tile_n == 128
|
| 170 |
+
and self.tile_hdim == 64
|
| 171 |
+
and self.tile_hdimv == 64
|
| 172 |
+
and not self.use_smem_dS_for_mma_dK
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
self.reduce_warp_ids = (0, 1, 2, 3)
|
| 176 |
self.compute_warp_ids = (4, 5, 6, 7, 8, 9, 10, 11)
|
| 177 |
self.mma_warp_id = 12
|
|
|
|
| 226 |
self.tmem_dQ_offset = 512 - self.tile_hdim // 2
|
| 227 |
else:
|
| 228 |
self.tmem_S_offset = 0
|
|
|
|
| 229 |
self.tmem_dV_offset = self.tmem_S_offset + self.tile_n
|
| 230 |
self.tmem_dP_offset = self.tmem_dV_offset + self.tile_hdimv
|
| 231 |
self.tmem_dQ_offset = (
|
|
|
|
| 234 |
else self.tmem_dP_offset
|
| 235 |
)
|
| 236 |
self.tmem_dK_offset = self.tmem_dP_offset + self.tile_m
|
| 237 |
+
if self.split_P_dS:
|
| 238 |
+
# P/dS are packed 16-bit over tile_m columns -> tile_m // 2 fp32 columns
|
| 239 |
+
self.tmem_P_offset = self.tmem_dK_offset + self.tile_hdim
|
| 240 |
+
self.tmem_dS_offset = self.tmem_P_offset + self.tile_m // 2
|
| 241 |
+
assert self.tmem_dS_offset + self.tile_m // 2 <= self.tmem_alloc_cols
|
| 242 |
+
else:
|
| 243 |
+
self.tmem_P_offset = self.tmem_S_offset # overlap with S
|
| 244 |
+
self.tmem_dS_offset = self.tmem_dP_offset # overlap with dP
|
| 245 |
|
| 246 |
if (not is_causal and not is_local) or deterministic:
|
| 247 |
self.num_regs_reduce = 136 if self.use_2cta_instrs else 152
|
|
|
|
| 279 |
self.Q_stage = 1 if self.use_2cta_instrs else 2
|
| 280 |
self.dO_stage = 1
|
| 281 |
self.single_stage = 1
|
| 282 |
+
self.P_stage = 1 if self.split_P_dS else 0 # dedicated P pipeline, else P aliases S
|
| 283 |
# LSE_stage = Q_stage and dPsum_stage = dO_stage
|
| 284 |
self.sdKVaccum_stage = 2
|
| 285 |
# number of tma reduce adds per dQacc mma
|
|
|
|
| 495 |
mdK: cute.Tensor,
|
| 496 |
mdV: cute.Tensor,
|
| 497 |
softmax_scale: Float32,
|
|
|
|
| 498 |
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 499 |
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 500 |
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 501 |
mSeqUsedK: Optional[cute.Tensor] = None,
|
|
|
|
| 502 |
window_size_left: Int32 | int | None = None,
|
| 503 |
window_size_right: Int32 | int | None = None,
|
| 504 |
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 505 |
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 506 |
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 507 |
+
aux_data: AuxData = AuxData(),
|
| 508 |
# Block-sparse tensors (Q direction - for iterating m_blocks per n_block):
|
| 509 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 510 |
+
mCuTotalMBlocks: Optional[cute.Tensor] = None,
|
| 511 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 512 |
+
stream: cuda.CUstream = None,
|
| 513 |
):
|
| 514 |
self.q_dtype = mQ.element_type
|
| 515 |
self.k_dtype = mK.element_type
|
|
|
|
| 532 |
assert self.dk_dtype.width == 32, "Must accumulate dK in float precision for GQA"
|
| 533 |
assert self.dv_dtype.width == 32, "Must accumulate dV in float precision for GQA"
|
| 534 |
|
| 535 |
+
mdQaccum, mdK, mdV, mLSE, mdPsum = [
|
| 536 |
+
assume_tensor_aligned(t) for t in (mdQaccum, mdK, mdV, mLSE, mdPsum)
|
| 537 |
+
]
|
| 538 |
|
| 539 |
# (b, s, n, h) --> (s, h, n, b) or (t, n, h) -> (t, h, n)
|
| 540 |
QO_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 2, 1]
|
|
|
|
| 751 |
TileScheduler = SingleTileLPTBwdScheduler
|
| 752 |
else:
|
| 753 |
TileScheduler = SingleTileScheduler
|
| 754 |
+
if const_expr(self.spt_override is None):
|
| 755 |
+
self.spt = (self.is_causal or self.is_local) and self.deterministic
|
| 756 |
+
else:
|
| 757 |
+
assert self.spt_override is not None
|
| 758 |
+
self.spt = self.spt_override and self.deterministic
|
| 759 |
tile_sched_args = TileSchedulerArguments(
|
| 760 |
cute.ceil_div(cute.size(mK.shape[0]), self.cta_tiler[0]), # num_blocks
|
| 761 |
cute.size(mQ.shape[2]), # num_heads = num_query_heads
|
|
|
|
| 776 |
qhead_per_kvhead_packgqa=1, # pack_gqa disabled for bwd
|
| 777 |
element_size=self.k_dtype.width // 8,
|
| 778 |
is_persistent=self.is_persistent, # persistent mode not tested
|
| 779 |
+
cu_total_m_blocks_ptr=mCuTotalMBlocks,
|
| 780 |
lpt=self.spt,
|
| 781 |
head_swizzle=self.deterministic,
|
| 782 |
)
|
|
|
|
| 832 |
cutlass.Int64, self.dQaccum_reduce_stage // 2
|
| 833 |
]
|
| 834 |
tmem_holding_buf: Int32
|
| 835 |
+
tmem_dealloc_mbar: cutlass.Int64
|
| 836 |
|
| 837 |
# 2-CTA
|
| 838 |
Qt_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.Q_stage]
|
|
|
|
| 902 |
S_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.single_stage]
|
| 903 |
dP_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.single_stage]
|
| 904 |
dS_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.single_stage]
|
| 905 |
+
P_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.P_stage]
|
| 906 |
dKV_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2 * self.sdKVaccum_stage]
|
| 907 |
dQ_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2]
|
| 908 |
dQ_cluster_full_mbar_ptr: cute.struct.MemRange[
|
|
|
|
| 912 |
cutlass.Int64, self.dQaccum_reduce_stage // 2
|
| 913 |
]
|
| 914 |
tmem_holding_buf: Int32
|
| 915 |
+
tmem_dealloc_mbar: Int64
|
| 916 |
|
| 917 |
sQ: cute.struct.Align[
|
| 918 |
cute.struct.MemRange[cute.Uint8, sQ_alloc_bytes],
|
|
|
|
| 963 |
window_size_right = Int32(window_size_right)
|
| 964 |
|
| 965 |
fastdiv_mods = None
|
| 966 |
+
if const_expr(aux_data.tensors is not None):
|
| 967 |
seqlen_q = cute.size(mQ.shape[0]) // (
|
| 968 |
self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1
|
| 969 |
)
|
|
|
|
| 972 |
seqlen_k_divmod = FastDivmodDivisor(seqlen_k)
|
| 973 |
fastdiv_mods = (seqlen_q_divmod, seqlen_k_divmod)
|
| 974 |
self.use_block_sparsity = cutlass.const_expr(blocksparse_tensors is not None)
|
| 975 |
+
if const_expr(self.use_block_sparsity and self.use_2cta_instrs):
|
| 976 |
+
# Both CTAs of a cluster must map to the same sparse KV column or they deadlock.
|
| 977 |
+
assert self.kv_subtile_factor % self.cta_group_size == 0, (
|
| 978 |
+
"2-CTA block-sparse backward requires kv_subtile_factor % cta_group_size == 0"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 979 |
)
|
| 980 |
+
|
| 981 |
# 2-CTA: 231424 and 1-CTA: 232448
|
| 982 |
# print("SMEM: ", self.shared_storage.size_in_bytes())
|
| 983 |
+
if const_expr(self.use_block_sparsity):
|
| 984 |
assert all(x is None for x in (mCuSeqlensQ, mCuSeqlensK, mSeqUsedQ, mSeqUsedK)), (
|
| 985 |
+
"Variable sequence length is not supported yet for blocksparse in bwd"
|
| 986 |
)
|
| 987 |
|
| 988 |
self.kernel(
|
|
|
|
| 1044 |
window_size_left,
|
| 1045 |
window_size_right,
|
| 1046 |
tile_sched_params,
|
| 1047 |
+
aux_data,
|
| 1048 |
fastdiv_mods,
|
| 1049 |
blocksparse_tensors,
|
| 1050 |
).launch(
|
|
|
|
| 1056 |
min_blocks_per_mp=1,
|
| 1057 |
)
|
| 1058 |
|
| 1059 |
+
def _generate_attention_mask_cls(self, window_size_left, window_size_right):
|
| 1060 |
+
return partial(
|
| 1061 |
+
AttentionMask,
|
| 1062 |
+
self.tile_m,
|
| 1063 |
+
self.tile_n * self.cta_group_size,
|
| 1064 |
+
swap_AB=True,
|
| 1065 |
+
window_size_left=window_size_left,
|
| 1066 |
+
window_size_right=window_size_right,
|
| 1067 |
+
)
|
| 1068 |
+
|
| 1069 |
@cute.kernel
|
| 1070 |
def kernel(
|
| 1071 |
self,
|
|
|
|
| 1127 |
window_size_left: Optional[Int32],
|
| 1128 |
window_size_right: Optional[Int32],
|
| 1129 |
tile_sched_params: ParamsBase,
|
| 1130 |
+
aux_data: AuxData = AuxData(),
|
| 1131 |
fastdiv_mods=(None, None),
|
| 1132 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 1133 |
):
|
|
|
|
| 1203 |
* len((self.mma_warp_id, *self.compute_warp_ids, *self.reduce_warp_ids)),
|
| 1204 |
)
|
| 1205 |
tmem = cutlass.utils.TmemAllocator(
|
| 1206 |
+
storage.tmem_holding_buf.ptr,
|
| 1207 |
barrier_for_retrieve=tmem_alloc_barrier,
|
| 1208 |
allocator_warp_id=self.mma_warp_id,
|
| 1209 |
is_two_cta=self.use_2cta_instrs,
|
| 1210 |
+
two_cta_tmem_dealloc_mbar_ptr=storage.tmem_dealloc_mbar.ptr,
|
| 1211 |
)
|
| 1212 |
|
| 1213 |
# UMMA producers and AsyncThread consumers
|
|
|
|
| 1217 |
pipeline_consumer_group_MMA_AsyncThread = cutlass.pipeline.CooperativeGroup(
|
| 1218 |
cutlass.pipeline.Agent.Thread, len(self.compute_warp_ids) * self.cta_group_size
|
| 1219 |
)
|
| 1220 |
+
# With split_P_dS, this pipeline guards only the S TMEM buffer (P has pipeline_P).
|
| 1221 |
pipeline_S_P = cutlass.pipeline.PipelineUmmaAsync.create(
|
| 1222 |
num_stages=1,
|
| 1223 |
producer_group=pipeline_producer_group_MMA_AsyncThread,
|
|
|
|
| 1267 |
barrier_storage=storage.dS_mbar_ptr.data_ptr(),
|
| 1268 |
cta_layout_vmnk=cluster_layout_vmnk,
|
| 1269 |
)
|
| 1270 |
+
if const_expr(self.split_P_dS):
|
| 1271 |
+
pipeline_P = cutlass.pipeline.PipelineAsyncUmma.create(
|
| 1272 |
+
num_stages=1,
|
| 1273 |
+
producer_group=pipeline_PdS_producer_group,
|
| 1274 |
+
consumer_group=pipeline_PdS_consumer_group,
|
| 1275 |
+
barrier_storage=storage.P_mbar_ptr.data_ptr(),
|
| 1276 |
+
cta_layout_vmnk=cluster_layout_vmnk,
|
| 1277 |
+
)
|
| 1278 |
+
else:
|
| 1279 |
+
pipeline_P = None
|
| 1280 |
|
| 1281 |
# TMA producer and UMMA consumers
|
| 1282 |
pipeline_producer_group = cutlass.pipeline.CooperativeGroup(
|
|
|
|
| 1480 |
)
|
| 1481 |
TileSchedulerCls = partial(self.tile_scheduler_cls.create, tile_sched_params)
|
| 1482 |
|
| 1483 |
+
AttentionMaskCls = self._generate_attention_mask_cls(window_size_left, window_size_right)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1484 |
# EMPTY
|
| 1485 |
# (15)
|
| 1486 |
if warp_idx == self.empty_warp_id:
|
|
|
|
| 1501 |
block_info,
|
| 1502 |
SeqlenInfoCls,
|
| 1503 |
TileSchedulerCls,
|
| 1504 |
+
blocksparse_tensors,
|
| 1505 |
)
|
| 1506 |
|
| 1507 |
# LOAD
|
|
|
|
| 1598 |
pipeline_dKV,
|
| 1599 |
pipeline_dP,
|
| 1600 |
pipeline_dQ,
|
| 1601 |
+
pipeline_P,
|
| 1602 |
block_info,
|
| 1603 |
SeqlenInfoCls,
|
| 1604 |
TileSchedulerCls,
|
|
|
|
| 1637 |
pipeline_dS,
|
| 1638 |
pipeline_dKV,
|
| 1639 |
pipeline_dP,
|
| 1640 |
+
pipeline_P,
|
| 1641 |
dS_cluster_empty_mbar_ptr,
|
| 1642 |
dS_cluster_full_mbar_ptr,
|
| 1643 |
dQaccum_empty_mbar_ptr,
|
|
|
|
| 1656 |
tiled_copy_r2s_dKV,
|
| 1657 |
mdK_semaphore,
|
| 1658 |
mdV_semaphore,
|
| 1659 |
+
aux_data,
|
| 1660 |
fastdiv_mods,
|
| 1661 |
blocksparse_tensors,
|
| 1662 |
)
|
|
|
|
| 1695 |
block_info: BlockInfo,
|
| 1696 |
SeqlenInfoCls: Callable,
|
| 1697 |
TileSchedulerCls: Callable,
|
| 1698 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 1699 |
):
|
| 1700 |
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
|
| 1701 |
dS_cluster_phase = Int32(0)
|
|
|
|
| 1713 |
process_tile = (
|
| 1714 |
const_expr(not self.is_local and not self.is_varlen_q) or m_block_min < m_block_max
|
| 1715 |
)
|
| 1716 |
+
num_iters = m_block_max - m_block_min
|
| 1717 |
+
if const_expr(self.use_block_sparsity):
|
| 1718 |
+
assert blocksparse_tensors is not None
|
| 1719 |
+
num_iters = get_total_q_block_count_bwd(
|
| 1720 |
+
blocksparse_tensors,
|
| 1721 |
+
batch_idx,
|
| 1722 |
+
head_idx,
|
| 1723 |
+
n_block // self.kv_subtile_factor,
|
| 1724 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 1725 |
+
m_block_max=m_block_max,
|
| 1726 |
+
)
|
| 1727 |
+
process_tile = num_iters > Int32(0)
|
| 1728 |
|
| 1729 |
if process_tile:
|
|
|
|
| 1730 |
for _ in cutlass.range(num_iters, unroll=1):
|
| 1731 |
# Wait for dS_xchg from peer CTA
|
| 1732 |
cute.arch.mbarrier_wait(dS_cluster_full_mbar_ptr, phase=dS_cluster_phase)
|
|
|
|
| 1744 |
@cute.jit
|
| 1745 |
def load(
|
| 1746 |
self,
|
| 1747 |
+
thr_mma_S: cute.ThrMma,
|
| 1748 |
+
thr_mma_dP: cute.ThrMma,
|
| 1749 |
+
thr_mma_dV: cute.ThrMma,
|
| 1750 |
+
thr_mma_dK: cute.ThrMma,
|
| 1751 |
+
thr_mma_dQ: cute.ThrMma,
|
| 1752 |
mQ: cute.Tensor,
|
| 1753 |
mK: cute.Tensor,
|
| 1754 |
mKt: Optional[cute.Tensor],
|
|
|
|
| 1832 |
)
|
| 1833 |
head_idx_kv = head_idx // self.qhead_per_kvhead
|
| 1834 |
n_block_cta_group = n_block // self.cta_group_size
|
| 1835 |
+
n_block_sparse = n_block // self.kv_subtile_factor
|
| 1836 |
|
| 1837 |
# GMEM tensors (varlen-aware)
|
| 1838 |
mQ_cur = seqlen.offset_batch_Q(mQ, batch_idx, dim=3)[None, None, head_idx]
|
|
|
|
| 1912 |
single_stage=True,
|
| 1913 |
)
|
| 1914 |
|
| 1915 |
+
load_dOt = None
|
| 1916 |
if const_expr(tma_atom_dOt is not None):
|
| 1917 |
gdOt = cute.local_tile(
|
| 1918 |
mdOt_cur, cute.select(self.mma_tiler_vdo, mode=[1, 2]), (None, 0)
|
|
|
|
| 1942 |
load_dO = copy_utils.tma_producer_copy_fn(load_dO, pipeline_dO)
|
| 1943 |
|
| 1944 |
# (4) dK += dS.T @ Q (2-CTA: needs separate Qt load)
|
| 1945 |
+
load_Qt = None
|
| 1946 |
if const_expr(tma_atom_Qt is not None):
|
| 1947 |
gQt = cute.local_tile(
|
| 1948 |
mQt_cur, cute.select(self.mma_tiler_dsq, mode=[1, 2]), (0, None)
|
|
|
|
| 1959 |
load_Qt = copy_utils.tma_producer_copy_fn(load_Qt, pipeline_Qt)
|
| 1960 |
|
| 1961 |
# (5) dQ = dS @ K
|
| 1962 |
+
load_Kt = None
|
| 1963 |
if const_expr(self.use_2cta_instrs):
|
| 1964 |
gKt = cute.local_tile(
|
| 1965 |
mKt_cur, cute.select(self.mma_tiler_dsk, mode=[1, 2]), (0, n_block_cta_group)
|
|
|
|
| 1984 |
# gdPsum = cute.logical_divide(gdPsum, (64,))[(None, block_in_cluster_coord_vmnk[1]), None]
|
| 1985 |
# copy_stats = partial(cute.copy, copy_atom_stats, mcast_mask=q_do_mcast_mask)
|
| 1986 |
|
| 1987 |
+
if const_expr(not self.use_block_sparsity):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1988 |
process_tile = (
|
| 1989 |
const_expr(not self.is_local and not self.is_varlen_q)
|
| 1990 |
or m_block_min < m_block_max
|
| 1991 |
)
|
| 1992 |
|
| 1993 |
+
if process_tile:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1994 |
first_m_block = m_block_min
|
| 1995 |
if const_expr(self.use_2cta_instrs and self.tile_hdim == 192):
|
| 1996 |
#### Prologue ####
|
| 1997 |
assert should_load_Q and should_load_dO
|
| 1998 |
+
assert load_dOt is not None and load_Qt is not None
|
| 1999 |
# K & Q (for S)
|
| 2000 |
pipeline_Q.producer_acquire(
|
| 2001 |
producer_state_Q_Qt,
|
|
|
|
| 2099 |
pipeline_dO.producer_commit(producer_state_O_Ot)
|
| 2100 |
producer_state_O_Ot.advance()
|
| 2101 |
|
| 2102 |
+
pipeline_Q.producer_tail(producer_state_Q_Qt)
|
| 2103 |
+
pipeline_LSE.producer_tail(producer_state_LSE)
|
| 2104 |
+
pipeline_dO.producer_tail(producer_state_O_Ot)
|
| 2105 |
+
pipeline_dPsum.producer_tail(producer_state_dPsum)
|
| 2106 |
else:
|
| 2107 |
#### Prologue ####
|
| 2108 |
if const_expr(should_load_Q):
|
|
|
|
| 2132 |
pipeline_dO.producer_acquire(
|
| 2133 |
producer_state_dO_dPsum,
|
| 2134 |
extra_tx_count=self.tma_copy_bytes["V"] + self.tma_copy_bytes["dO"]
|
| 2135 |
+
if const_expr(load_dOt is not None)
|
| 2136 |
else self.tma_copy_bytes["V"],
|
| 2137 |
)
|
| 2138 |
load_V(
|
|
|
|
| 2141 |
)
|
| 2142 |
)
|
| 2143 |
load_dO(first_m_block, producer_state=producer_state_dO_dPsum)
|
| 2144 |
+
if const_expr(load_dOt is not None):
|
| 2145 |
load_dOt(first_m_block, producer_state=producer_state_dO_dPsum)
|
| 2146 |
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 2147 |
|
|
|
|
| 2165 |
#### Main Loop ####
|
| 2166 |
for m_block in cutlass.range(m_block_min + 1, m_block_max, unroll=1):
|
| 2167 |
if const_expr(should_load_Q):
|
| 2168 |
+
if const_expr(load_Qt is not None):
|
| 2169 |
pipeline_Qt.producer_acquire(producer_state_Qt)
|
| 2170 |
load_Qt(m_block - 1, producer_state=producer_state_Qt)
|
| 2171 |
pipeline_Qt.producer_commit(producer_state_Qt)
|
|
|
|
| 2192 |
pipeline_dO.producer_acquire(
|
| 2193 |
producer_state_dO_dPsum,
|
| 2194 |
extra_tx_count=self.tma_copy_bytes["dO"]
|
| 2195 |
+
if const_expr(load_dOt is not None)
|
| 2196 |
else 0,
|
| 2197 |
)
|
| 2198 |
load_dO(m_block, producer_state=producer_state_dO_dPsum)
|
| 2199 |
+
if const_expr(load_dOt is not None):
|
| 2200 |
load_dOt(m_block, producer_state=producer_state_dO_dPsum)
|
| 2201 |
pipeline_dO.producer_commit(producer_state_dO_dPsum)
|
| 2202 |
|
|
|
|
| 2214 |
|
| 2215 |
#### Tail ####
|
| 2216 |
if const_expr(should_load_Q):
|
| 2217 |
+
if const_expr(load_Qt is not None):
|
| 2218 |
pipeline_Qt.producer_acquire(producer_state_Qt)
|
| 2219 |
load_Qt(m_block_max - 1, producer_state=producer_state_Qt)
|
| 2220 |
pipeline_Qt.producer_commit(producer_state_Qt)
|
| 2221 |
producer_state_Qt.advance()
|
| 2222 |
|
| 2223 |
+
pipeline_Q.producer_tail(producer_state_Q_LSE.clone())
|
| 2224 |
+
pipeline_LSE.producer_tail(producer_state_Q_LSE)
|
| 2225 |
+
if const_expr(load_Qt is not None):
|
| 2226 |
+
pipeline_Qt.producer_tail(producer_state_Qt)
|
| 2227 |
+
if const_expr(should_load_dO):
|
| 2228 |
+
pipeline_dO.producer_tail(producer_state_dO_dPsum.clone())
|
| 2229 |
+
pipeline_dPsum.producer_tail(producer_state_dO_dPsum)
|
| 2230 |
+
|
| 2231 |
+
else:
|
| 2232 |
+
assert blocksparse_tensors is not None
|
| 2233 |
if const_expr(self.use_2cta_instrs and self.tile_hdim == 192):
|
| 2234 |
+
assert should_load_Q and should_load_dO
|
| 2235 |
+
assert load_dOt is not None and load_Qt is not None
|
| 2236 |
+
assert load_Kt is not None and pipeline_Qt is not None
|
| 2237 |
+
(
|
| 2238 |
+
producer_state_Q_Qt,
|
| 2239 |
+
producer_state_O_Ot,
|
| 2240 |
+
producer_state_LSE,
|
| 2241 |
+
producer_state_dPsum,
|
| 2242 |
+
) = produce_block_sparse_q_loads_bwd_sm100_2cta_hdim192(
|
| 2243 |
+
blocksparse_tensors,
|
| 2244 |
+
batch_idx,
|
| 2245 |
+
head_idx,
|
| 2246 |
+
n_block_sparse,
|
| 2247 |
+
producer_state_Q_Qt,
|
| 2248 |
+
producer_state_O_Ot,
|
| 2249 |
+
producer_state_LSE,
|
| 2250 |
+
producer_state_dPsum,
|
| 2251 |
+
pipeline_Q,
|
| 2252 |
+
pipeline_LSE,
|
| 2253 |
+
pipeline_dO,
|
| 2254 |
+
pipeline_dPsum,
|
| 2255 |
+
pipeline_Qt,
|
| 2256 |
+
load_K,
|
| 2257 |
+
load_V,
|
| 2258 |
+
load_Q,
|
| 2259 |
+
load_dO,
|
| 2260 |
+
load_Qt,
|
| 2261 |
+
load_Kt,
|
| 2262 |
+
load_dOt,
|
| 2263 |
+
copy_stats,
|
| 2264 |
+
gLSE,
|
| 2265 |
+
sLSE,
|
| 2266 |
+
gdPsum,
|
| 2267 |
+
sdPsum,
|
| 2268 |
+
self.tma_copy_bytes["K"],
|
| 2269 |
+
self.tma_copy_bytes["V"],
|
| 2270 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 2271 |
+
m_block_max=m_block_max,
|
| 2272 |
+
)
|
| 2273 |
else:
|
| 2274 |
+
(
|
| 2275 |
+
producer_state_Q_LSE,
|
| 2276 |
+
producer_state_dO_dPsum,
|
| 2277 |
+
producer_state_Qt,
|
| 2278 |
+
producer_state_Kt,
|
| 2279 |
+
) = produce_block_sparse_q_loads_bwd_sm100_default(
|
| 2280 |
+
blocksparse_tensors,
|
| 2281 |
+
batch_idx,
|
| 2282 |
+
head_idx,
|
| 2283 |
+
n_block_sparse,
|
| 2284 |
+
producer_state_Q_LSE,
|
| 2285 |
+
producer_state_dO_dPsum,
|
| 2286 |
+
pipeline_Q,
|
| 2287 |
+
pipeline_LSE,
|
| 2288 |
+
pipeline_dO,
|
| 2289 |
+
pipeline_dPsum,
|
| 2290 |
+
load_K,
|
| 2291 |
+
load_V,
|
| 2292 |
+
load_Q,
|
| 2293 |
+
load_dO,
|
| 2294 |
+
copy_stats,
|
| 2295 |
+
gLSE,
|
| 2296 |
+
sLSE,
|
| 2297 |
+
gdPsum,
|
| 2298 |
+
sdPsum,
|
| 2299 |
+
self.tma_copy_bytes["K"],
|
| 2300 |
+
self.tma_copy_bytes["V"],
|
| 2301 |
+
should_load_Q,
|
| 2302 |
+
should_load_dO,
|
| 2303 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 2304 |
+
m_block_max=m_block_max,
|
| 2305 |
+
use_2cta_instrs=self.use_2cta_instrs,
|
| 2306 |
+
producer_state_Qt=producer_state_Qt,
|
| 2307 |
+
producer_state_Kt=producer_state_Kt,
|
| 2308 |
+
pipeline_Qt=pipeline_Qt,
|
| 2309 |
+
pipeline_Kt=pipeline_Kt,
|
| 2310 |
+
load_Qt=load_Qt,
|
| 2311 |
+
load_Kt=load_Kt,
|
| 2312 |
+
load_dOt=load_dOt,
|
| 2313 |
+
tma_copy_bytes_dO=self.tma_copy_bytes["dO"],
|
| 2314 |
+
)
|
| 2315 |
tile_scheduler.prefetch_next_work()
|
| 2316 |
tile_scheduler.advance_to_next_work()
|
| 2317 |
work_tile = tile_scheduler.get_current_work()
|
|
|
|
| 2352 |
pipeline_dKV: PipelineAsync,
|
| 2353 |
pipeline_dP: PipelineAsync,
|
| 2354 |
pipeline_dQ: PipelineAsync,
|
| 2355 |
+
pipeline_P: Optional[PipelineAsync],
|
| 2356 |
block_info: BlockInfo,
|
| 2357 |
SeqlenInfoCls: Callable,
|
| 2358 |
TileSchedulerCls: Callable,
|
|
|
|
| 2464 |
)
|
| 2465 |
producer_phase_acc = Int32(1) # For S & P, dP, dQ
|
| 2466 |
producer_phase_dQ = Int32(1) # 2-CTA: separate phase for dQ pipeline
|
| 2467 |
+
producer_phase_S = Int32(1) # split_P_dS: separate phase for the S handshake
|
| 2468 |
+
consumer_state_P = cutlass.pipeline.make_pipeline_state(
|
| 2469 |
+
cutlass.pipeline.PipelineUserType.Consumer, 1
|
| 2470 |
+
)
|
| 2471 |
consumer_state_dS = cutlass.pipeline.make_pipeline_state(
|
| 2472 |
cutlass.pipeline.PipelineUserType.Consumer, 1
|
| 2473 |
)
|
|
|
|
| 2491 |
blocksparse_tensors,
|
| 2492 |
batch_idx,
|
| 2493 |
head_idx,
|
| 2494 |
+
n_block // self.kv_subtile_factor,
|
| 2495 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 2496 |
m_block_max=m_block_max,
|
| 2497 |
)
|
| 2498 |
process_tile = block_iter_count > Int32(0)
|
|
|
|
| 2517 |
# 4. dV = P.T @ dO
|
| 2518 |
# 5. dQ = dS @ K
|
| 2519 |
|
| 2520 |
+
main_loop_iters = block_iter_count
|
| 2521 |
|
| 2522 |
# empty waits
|
| 2523 |
# pipeline_S_P.sync_object_empty.wait(0, producer_phase_acc)
|
|
|
|
| 2707 |
else:
|
| 2708 |
if is_leader_cta and process_tile:
|
| 2709 |
accumulate_dK = False
|
| 2710 |
+
accumulate_dV = False
|
| 2711 |
# -----------------------------------------------------------
|
| 2712 |
###### Prologue
|
| 2713 |
# -----------------------------------------------------------
|
| 2714 |
+
# 1. S.T = K @ Q.T
|
| 2715 |
+
# 2. dP.T = V @ dO.T
|
| 2716 |
+
# 3. dV = P.T @ dO (split_P_dS: issued in the main loop, after QK_1)
|
| 2717 |
|
| 2718 |
+
# 1) S.T = K @ Q.T
|
| 2719 |
handle_Q = pipeline_Q_consumer.wait_and_advance()
|
| 2720 |
+
if const_expr(self.split_P_dS):
|
| 2721 |
+
pipeline_S_P.sync_object_empty.wait(0, producer_phase_S)
|
| 2722 |
+
else:
|
| 2723 |
+
pipeline_S_P.sync_object_empty.wait(0, producer_phase_acc)
|
| 2724 |
mma_qk_fn(B_idx=handle_Q.index)
|
| 2725 |
pipeline_S_P.sync_object_full.arrive(0, pipeline_S_P.producer_mask, cta_group)
|
| 2726 |
+
if const_expr(self.split_P_dS):
|
| 2727 |
+
producer_phase_S ^= 1
|
| 2728 |
|
| 2729 |
+
# 2) dP.T = V @ dO.T
|
| 2730 |
pipeline_dO.consumer_wait(consumer_state_dO)
|
| 2731 |
pipeline_dP.sync_object_empty.wait(0, producer_phase_acc)
|
| 2732 |
pipeline_dQ.sync_object_empty.wait(0, producer_phase_acc)
|
|
|
|
| 2735 |
|
| 2736 |
producer_phase_acc ^= 1
|
| 2737 |
# 3) dV = P.T @ dO
|
| 2738 |
+
if const_expr(not self.split_P_dS):
|
| 2739 |
+
pipeline_S_P.sync_object_empty.wait(0, producer_phase_acc)
|
| 2740 |
+
mma_pdo_fn(B_idx=consumer_state_dO.index, zero_init=True)
|
| 2741 |
+
pipeline_dO.consumer_release(consumer_state_dO)
|
| 2742 |
+
consumer_state_dO.advance()
|
| 2743 |
|
| 2744 |
# -----------------------------------------------------------
|
| 2745 |
###### MAIN LOOP
|
| 2746 |
# -----------------------------------------------------------
|
| 2747 |
+
# Per trip t (the CTA owns a KV tile, so S and dP come out transposed):
|
| 2748 |
+
# 1. S.T = K @ Q.T (tile t+1)
|
| 2749 |
+
# 1b. dV += P.T @ dO (tile t; split_P_dS only -- otherwise at 5)
|
| 2750 |
+
# 2. dK += dS.T @ Q
|
| 2751 |
+
# 3. dQ = dS @ K
|
| 2752 |
+
# 4. dP.T = V @ dO.T (tile t+1)
|
| 2753 |
+
# 5. dV += P.T @ dO (tile t+1; aliased layout only)
|
| 2754 |
|
| 2755 |
# For block sparsity, we use block_iter_count; for dense, use m_block range
|
| 2756 |
# MMA doesn't need actual m_block indices, just the iteration count
|
|
|
|
| 2764 |
for _ in cutlass.range(main_loop_iters, unroll=1):
|
| 2765 |
# (1) S.T = K @ Q.T
|
| 2766 |
handle_Q_next = pipeline_Q_consumer.wait_and_advance()
|
| 2767 |
+
if const_expr(self.split_P_dS):
|
| 2768 |
+
# S empty means "S_t loaded", not "P_t stored", so this QK
|
| 2769 |
+
# overlaps the softmax of tile t.
|
| 2770 |
+
pipeline_S_P.sync_object_empty.wait(0, producer_phase_S)
|
| 2771 |
mma_qk_fn(B_idx=handle_Q_next.index)
|
| 2772 |
pipeline_S_P.sync_object_full.arrive(
|
| 2773 |
0, pipeline_S_P.producer_mask, cta_group
|
| 2774 |
)
|
| 2775 |
+
if const_expr(self.split_P_dS):
|
| 2776 |
+
producer_phase_S ^= 1
|
| 2777 |
+
# (1b) dV += P_t.T @ dO_t, issued after QK_{t+1} so QK never
|
| 2778 |
+
# waits behind the P store.
|
| 2779 |
+
pipeline_P.consumer_wait(consumer_state_P)
|
| 2780 |
+
mma_pdo_fn(B_idx=consumer_state_dO.index, zero_init=not accumulate_dV)
|
| 2781 |
+
accumulate_dV = True
|
| 2782 |
+
pipeline_P.consumer_release(consumer_state_P)
|
| 2783 |
+
consumer_state_P.advance()
|
| 2784 |
+
pipeline_dO.consumer_release(consumer_state_dO)
|
| 2785 |
+
consumer_state_dO.advance()
|
| 2786 |
|
| 2787 |
# (2) dK += dS.T @ Q
|
| 2788 |
pipeline_dS.consumer_wait(consumer_state_dS)
|
|
|
|
| 2796 |
pipeline_dS.consumer_release(consumer_state_dS)
|
| 2797 |
consumer_state_dS.advance()
|
| 2798 |
|
| 2799 |
+
# (4) dP.T = V @ dO.T
|
| 2800 |
pipeline_dO.consumer_wait(consumer_state_dO)
|
| 2801 |
pipeline_dQ.sync_object_empty.wait(0, producer_phase_acc)
|
| 2802 |
mma_dov_fn(B_idx=consumer_state_dO.index)
|
| 2803 |
pipeline_dP.sync_object_full.arrive(0, pipeline_dP.producer_mask, cta_group)
|
| 2804 |
|
| 2805 |
+
# (5) dV += P.T @ dO (split_P_dS: done at (1b) of the next trip)
|
| 2806 |
producer_phase_acc ^= 1
|
| 2807 |
+
if const_expr(not self.split_P_dS):
|
| 2808 |
+
pipeline_S_P.sync_object_empty.wait(0, producer_phase_acc)
|
| 2809 |
+
mma_pdo_fn(B_idx=consumer_state_dO.index, zero_init=False)
|
| 2810 |
+
pipeline_dO.consumer_release(consumer_state_dO)
|
| 2811 |
+
consumer_state_dO.advance()
|
| 2812 |
|
| 2813 |
handle_Q = handle_Q_next
|
| 2814 |
|
| 2815 |
+
if const_expr(self.split_P_dS):
|
| 2816 |
+
# the last tile's dV += P.T @ dO; there is no further QK to overlap
|
| 2817 |
+
pipeline_P.consumer_wait(consumer_state_P)
|
| 2818 |
+
mma_pdo_fn(B_idx=consumer_state_dO.index, zero_init=not accumulate_dV)
|
| 2819 |
+
pipeline_P.consumer_release(consumer_state_P)
|
| 2820 |
+
consumer_state_P.advance()
|
| 2821 |
+
pipeline_dO.consumer_release(consumer_state_dO)
|
| 2822 |
+
consumer_state_dO.advance()
|
| 2823 |
+
else:
|
| 2824 |
+
pipeline_S_P.sync_object_full.arrive(
|
| 2825 |
+
0, pipeline_S_P.producer_mask, cta_group
|
| 2826 |
+
)
|
| 2827 |
|
| 2828 |
# signal to the epilogue that dV is ready
|
| 2829 |
# pipeline_dKV.producer_acquire(producer_state_dKV)
|
|
|
|
| 2914 |
n_block,
|
| 2915 |
softmax_scale,
|
| 2916 |
seqlen_info,
|
| 2917 |
+
aux_data: AuxData = AuxData(),
|
| 2918 |
fastdiv_mods=(None, None),
|
| 2919 |
):
|
| 2920 |
"""Apply forward score modification for SM100 backward pass."""
|
| 2921 |
+
# In bwd, S is computed as K @ Q.T so dimensions are (tile_n, tile_m).
|
| 2922 |
+
# With 2CTA, partition_C must see the full cluster tile so each CTA
|
| 2923 |
+
# gets its own half of the tile.
|
| 2924 |
+
cluster_tile_n = self.tile_n * self.cta_group_size
|
| 2925 |
+
cluster_n_block = n_block // self.cta_group_size
|
| 2926 |
+
cS = cute.make_identity_tensor((cluster_tile_n, self.tile_m))
|
| 2927 |
+
cS = cute.domain_offset((cluster_n_block * cluster_tile_n, m_block * self.tile_m), cS)
|
| 2928 |
tScS = thr_mma_S.partition_C(cS)
|
| 2929 |
tScS_idx = thr_copy_t2r.partition_D(tScS)
|
| 2930 |
|
|
|
|
| 2937 |
softmax_scale,
|
| 2938 |
self.vec_size,
|
| 2939 |
self.qk_acc_dtype,
|
| 2940 |
+
aux_data,
|
| 2941 |
fastdiv_mods,
|
| 2942 |
seqlen_info,
|
| 2943 |
constant_q_idx=None,
|
|
|
|
| 2955 |
head_idx,
|
| 2956 |
softmax_scale,
|
| 2957 |
seqlen_info,
|
| 2958 |
+
aux_data: AuxData = AuxData(),
|
| 2959 |
fastdiv_mods=(None, None),
|
| 2960 |
):
|
| 2961 |
"""Apply backward score modification (joint graph) for SM100."""
|
|
|
|
| 2969 |
softmax_scale,
|
| 2970 |
self.vec_size,
|
| 2971 |
self.qk_acc_dtype,
|
| 2972 |
+
aux_data,
|
| 2973 |
fastdiv_mods,
|
| 2974 |
seqlen_info,
|
| 2975 |
constant_q_idx=None,
|
|
|
|
| 2980 |
@cute.jit
|
| 2981 |
def compute_loop(
|
| 2982 |
self,
|
| 2983 |
+
thr_mma_S: cute.ThrMma,
|
| 2984 |
+
thr_mma_dP: cute.ThrMma,
|
| 2985 |
+
thr_mma_dV: cute.ThrMma,
|
| 2986 |
+
thr_mma_dK: cute.ThrMma,
|
| 2987 |
tStS: cute.Tensor,
|
| 2988 |
tdPtdP: cute.Tensor,
|
| 2989 |
tdVtdV: cute.Tensor,
|
|
|
|
| 3000 |
pipeline_dS: PipelineAsync,
|
| 3001 |
pipeline_dKV: PipelineAsync,
|
| 3002 |
pipeline_dP: PipelineAsync,
|
| 3003 |
+
pipeline_P: Optional[PipelineAsync],
|
| 3004 |
dS_cluster_empty_mbar_ptr: cute.Pointer,
|
| 3005 |
dS_cluster_full_mbar_ptr: cute.Pointer,
|
| 3006 |
dQaccum_empty_mbar_ptr: cute.Pointer,
|
|
|
|
| 3019 |
tiled_copy_r2s_dKV: Optional[cute.TiledCopy],
|
| 3020 |
mdK_semaphore: Optional[cute.Tensor],
|
| 3021 |
mdV_semaphore: Optional[cute.Tensor],
|
| 3022 |
+
aux_data: AuxData = AuxData(),
|
| 3023 |
fastdiv_mods=(None, None),
|
| 3024 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 3025 |
):
|
|
|
|
| 3054 |
|
| 3055 |
tileP_f32_like = self.cta_tiler[1] // 32 * self.v_dtype.width
|
| 3056 |
# tStS has shape ((128, 128), 1, 1), tStP has shape ((128, 64), 1, 1)
|
|
|
|
| 3057 |
tStP = cute.composition(tStS, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
| 3058 |
+
# Rebase the iterator explicitly (P may alias S), otherwise the tmem address is wrong
|
| 3059 |
+
tStP = cute.make_tensor(
|
| 3060 |
+
tStS.iterator + (self.tmem_P_offset - self.tmem_S_offset), tStP.layout
|
| 3061 |
+
)
|
| 3062 |
tScS = thr_mma_S.partition_C(cute.make_identity_tensor(self.mma_tiler_kq[:2]))
|
| 3063 |
tScP = cute.composition(tScS, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
|
|
|
| 3064 |
tdPtdS = cute.composition(tdPtdP, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
| 3065 |
+
tdPtdS = cute.make_tensor(
|
| 3066 |
+
tdPtdP.iterator + (self.tmem_dS_offset - self.tmem_dP_offset), tdPtdS.layout
|
| 3067 |
+
)
|
| 3068 |
tdPcdP = thr_mma_dP.partition_C(cute.make_identity_tensor(self.mma_tiler_vdo[:2]))
|
| 3069 |
tdPcdS = cute.composition(tdPcdP, (cute.make_layout((self.tile_n, tileP_f32_like)), 1, 1))
|
| 3070 |
|
|
|
|
| 3123 |
cutlass.pipeline.PipelineUserType.Consumer, 1
|
| 3124 |
)
|
| 3125 |
# consumer_phase_S_P_dP = Int32(0)
|
| 3126 |
+
consumer_state_S = pipeline.make_pipeline_state(
|
| 3127 |
+
cutlass.pipeline.PipelineUserType.Consumer, 1
|
| 3128 |
+
)
|
| 3129 |
+
producer_state_P = pipeline.make_pipeline_state(
|
| 3130 |
+
cutlass.pipeline.PipelineUserType.Producer, 1
|
| 3131 |
+
)
|
| 3132 |
producer_state_dS = pipeline.make_pipeline_state( # Our impl has shortcut for stage==1
|
| 3133 |
cutlass.pipeline.PipelineUserType.Producer, 1
|
| 3134 |
)
|
|
|
|
| 3147 |
while work_tile.is_valid_tile:
|
| 3148 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 3149 |
seqlen = SeqlenInfoCls(batch_idx)
|
| 3150 |
+
|
| 3151 |
+
recompute_fastdiv_mods_q = const_expr(
|
| 3152 |
+
aux_data.tensors is not None and (seqlen.has_cu_seqlens_q or seqlen.has_seqused_q)
|
| 3153 |
+
)
|
| 3154 |
+
recompute_fastdiv_mods_k = const_expr(
|
| 3155 |
+
aux_data.tensors is not None and (seqlen.has_cu_seqlens_k or seqlen.has_seqused_k)
|
| 3156 |
+
)
|
| 3157 |
+
|
| 3158 |
+
if const_expr(fastdiv_mods is not None and fastdiv_mods[0] is not None):
|
| 3159 |
+
seqlen_q_divmod, seqlen_k_divmod = fastdiv_mods
|
| 3160 |
+
fastdiv_mods = (
|
| 3161 |
+
seqlen_q_divmod
|
| 3162 |
+
if not recompute_fastdiv_mods_q
|
| 3163 |
+
else FastDivmodDivisor(seqlen.seqlen_q),
|
| 3164 |
+
seqlen_k_divmod
|
| 3165 |
+
if not recompute_fastdiv_mods_k
|
| 3166 |
+
else FastDivmodDivisor(seqlen.seqlen_k),
|
| 3167 |
+
)
|
| 3168 |
m_block_min, m_block_max = block_info.get_m_block_min_max(
|
| 3169 |
seqlen, n_block // self.cluster_shape_mnk[0]
|
| 3170 |
)
|
| 3171 |
mask = AttentionMaskCls(seqlen)
|
| 3172 |
+
cluster_n_block = n_block // self.cta_group_size
|
| 3173 |
# TODO: condition mask_seqlen
|
| 3174 |
mask_fn = partial(
|
| 3175 |
mask.apply_mask_sm100_transposed,
|
| 3176 |
tScS_t2r=tScS_t2r,
|
| 3177 |
t0ScS_t2r=t0ScS_t2r,
|
| 3178 |
+
n_block=cluster_n_block,
|
| 3179 |
mask_seqlen=True,
|
| 3180 |
mask_causal=self.is_causal,
|
| 3181 |
mask_local=self.is_local,
|
| 3182 |
mask_mod=self.mask_mod,
|
| 3183 |
batch_idx=batch_idx,
|
| 3184 |
head_idx=head_idx,
|
| 3185 |
+
aux_data=aux_data,
|
| 3186 |
fastdiv_mods=fastdiv_mods,
|
| 3187 |
)
|
| 3188 |
|
| 3189 |
# prefetch_LSE = not self.is_causal
|
| 3190 |
prefetch_LSE = False
|
| 3191 |
+
|
| 3192 |
+
curr_q_cnt = Int32(0)
|
| 3193 |
+
curr_q_idx = None
|
| 3194 |
+
curr_full_cnt = Int32(0)
|
| 3195 |
+
curr_full_idx = None
|
| 3196 |
+
loop_count = m_block_max - m_block_min
|
| 3197 |
+
process_tile = (
|
| 3198 |
+
const_expr(not self.is_local and not self.is_varlen_q) or m_block_min < m_block_max
|
| 3199 |
+
)
|
| 3200 |
if const_expr(self.use_block_sparsity):
|
| 3201 |
+
assert blocksparse_tensors is not None
|
| 3202 |
(
|
| 3203 |
curr_q_cnt,
|
| 3204 |
curr_q_idx,
|
|
|
|
| 3209 |
blocksparse_tensors,
|
| 3210 |
batch_idx,
|
| 3211 |
head_idx,
|
| 3212 |
+
n_block // self.kv_subtile_factor,
|
| 3213 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 3214 |
m_block_max=m_block_max,
|
| 3215 |
)
|
| 3216 |
process_tile = loop_count > Int32(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3217 |
|
| 3218 |
# Mainloop
|
| 3219 |
# Block sparsity: iterate over sparse m_block count and derive actual m_block
|
| 3220 |
# from Q_IDX/FULL_Q_IDX tensors. Dense: iterate m_block_min..m_block_max directly.
|
| 3221 |
for iter_idx in cutlass.range(loop_count, unroll=1):
|
| 3222 |
+
m_block = m_block_min + iter_idx
|
| 3223 |
+
m_block_oob = False
|
| 3224 |
+
is_full_block = False
|
| 3225 |
if const_expr(self.use_block_sparsity):
|
| 3226 |
m_block, is_full_block = get_m_block_from_iter_bwd(
|
| 3227 |
iter_idx,
|
|
|
|
| 3229 |
curr_q_idx,
|
| 3230 |
curr_full_cnt,
|
| 3231 |
curr_full_idx,
|
| 3232 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 3233 |
m_block_max=m_block_max,
|
| 3234 |
)
|
| 3235 |
m_block_oob = m_block >= m_block_max
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3236 |
# Prefetch 1 stage of LSE
|
| 3237 |
pipeline_LSE.consumer_wait(consumer_state_LSE)
|
| 3238 |
+
tSrLSE_s2r = cute.make_rmem_tensor(tScS_t2r[None, 0, 0, 0].shape, Float32)
|
| 3239 |
if const_expr(prefetch_LSE and not self.shuffle_LSE):
|
| 3240 |
cute.autovec_copy(tSsLSE[None, 0, 0, 0, consumer_state_LSE.index], tSrLSE_s2r)
|
| 3241 |
|
| 3242 |
+
if const_expr(self.split_P_dS):
|
| 3243 |
+
pipeline_S_P.consumer_wait(consumer_state_S)
|
| 3244 |
+
else:
|
| 3245 |
+
pipeline_S_P.consumer_wait(consumer_state_S_P_dP)
|
| 3246 |
# pipeline_S_P.sync_object_full.wait(0, consumer_phase_S_P_dP)
|
| 3247 |
#### TMEM->RMEM (Load S from TMEM)
|
| 3248 |
+
tSrS_t2r = cute.make_rmem_tensor(tScS_t2r.shape, Float32)
|
| 3249 |
cute.copy(thr_copy_t2r, tStS_t2r, tSrS_t2r)
|
| 3250 |
|
| 3251 |
+
if const_expr(self.split_P_dS):
|
| 3252 |
+
# S is in registers: release it before the softmax so the MMA warp can
|
| 3253 |
+
# issue the next QK into the slot
|
| 3254 |
+
cute.arch.fence_view_async_tmem_load()
|
| 3255 |
+
cute.arch.sync_warp()
|
| 3256 |
+
with cute.arch.elect_one():
|
| 3257 |
+
pipeline_S_P.consumer_release(consumer_state_S)
|
| 3258 |
+
consumer_state_S.advance()
|
| 3259 |
+
elif const_expr(self.tile_hdim == 192):
|
| 3260 |
# Signal S tmem load completion using pipeline_S_P when hdim 192
|
| 3261 |
# dP is overlapped with S
|
| 3262 |
cute.arch.fence_view_async_tmem_load()
|
|
|
|
| 3287 |
n_block,
|
| 3288 |
softmax_scale,
|
| 3289 |
seqlen,
|
| 3290 |
+
aux_data,
|
| 3291 |
fastdiv_mods,
|
| 3292 |
)
|
| 3293 |
|
|
|
|
| 3304 |
#### P = exp(S - LSE)
|
| 3305 |
# ---------------------------------------------
|
| 3306 |
lane_idx = cute.arch.lane_idx()
|
| 3307 |
+
tSrP_r2t_f32 = cute.make_rmem_tensor(tScP_r2t.shape, Float32) # 64
|
| 3308 |
tSrP_r2t = cute.recast_tensor(tSrP_r2t_f32, self.q_dtype)
|
| 3309 |
for stage in cutlass.range_constexpr(num_stages):
|
| 3310 |
tSrS_cur = tSrS_t2r[None, stage, 0, 0]
|
|
|
|
| 3332 |
tSrS_cur[2 * v + 1] = cute.math.exp2(tSrS_cur[2 * v + 1], fastmath=True)
|
| 3333 |
utils.cvt_f16(tSrS_cur, tSrP_r2t[None, stage, 0, 0])
|
| 3334 |
if const_expr(stage == 0):
|
| 3335 |
+
if const_expr(self.split_P_dS):
|
| 3336 |
+
# wait until the previous pdo UMMA has consumed the P slot
|
| 3337 |
+
pipeline_P.producer_acquire(producer_state_P)
|
| 3338 |
+
else:
|
| 3339 |
+
cute.arch.fence_view_async_tmem_load()
|
| 3340 |
+
# P overwrites S lanes another warp may still be loading, so
|
| 3341 |
+
# every warp must have loaded S before any stores P.
|
| 3342 |
+
self.compute_sync_barrier.arrive_and_wait()
|
| 3343 |
cute.copy(
|
| 3344 |
thr_copy_r2t,
|
| 3345 |
tSrP_r2t_f32[None, stage, None, None],
|
|
|
|
| 3348 |
|
| 3349 |
cute.arch.fence_view_async_tmem_store()
|
| 3350 |
cute.arch.fence_view_async_shared()
|
| 3351 |
+
# The P (or S_P) and LSE mbarriers expect one arrival per compute warp
|
| 3352 |
+
if const_expr(self.split_P_dS):
|
| 3353 |
+
cute.arch.sync_warp()
|
| 3354 |
+
else:
|
| 3355 |
+
self.compute_sync_barrier.arrive_and_wait()
|
| 3356 |
+
if const_expr(self.split_P_dS):
|
| 3357 |
+
with cute.arch.elect_one():
|
| 3358 |
+
pipeline_P.producer_commit(producer_state_P)
|
| 3359 |
+
producer_state_P.advance()
|
| 3360 |
+
elif const_expr(not self.tile_hdim == 192):
|
| 3361 |
# Signal tmem store P completion with pipeline_S_P
|
| 3362 |
with cute.arch.elect_one():
|
| 3363 |
pipeline_S_P.consumer_release(consumer_state_S_P_dP)
|
| 3364 |
# pipeline_S_P.sync_object_empty.arrive(0, pipeline_S_P.consumer_mask)
|
| 3365 |
+
# Normally we'd need syncwarp here since only 1 thread will signal in
|
| 3366 |
+
# consumer_release, but we already have the barrier or sync_warp before this
|
| 3367 |
pipeline_LSE.consumer_release(consumer_state_LSE)
|
| 3368 |
consumer_state_LSE.advance()
|
| 3369 |
# ---------------------------------------------
|
|
|
|
| 3378 |
|
| 3379 |
##### dS.T = P.T * (dP.T - Psum)
|
| 3380 |
for stage in cutlass.range_constexpr(num_stages):
|
| 3381 |
+
tdPrdP_t2r = cute.make_rmem_tensor(tScS_t2r[None, 0, None, None].shape, Float32)
|
| 3382 |
cute.copy(thr_copy_t2r, tdPtdP_t2r[None, stage, None, None], tdPrdP_t2r)
|
| 3383 |
cute.arch.fence_view_async_tmem_load()
|
| 3384 |
+
if const_expr(not self.split_P_dS):
|
| 3385 |
+
# dS overwrites dP with the same cross-warp lane sharing as P over S
|
| 3386 |
+
self.compute_sync_barrier.arrive_and_wait()
|
| 3387 |
tdPrdP_cur = tdPrdP_t2r[None, 0, 0]
|
| 3388 |
tSrS_cur = tSrS_t2r[None, stage, 0, 0]
|
| 3389 |
tSsdPsum_cur = tSsdPsum[None, stage, 0, 0, consumer_state_dPsum.index]
|
|
|
|
| 3400 |
utils.shuffle_sync(tSrdPsum, offset=2 * v),
|
| 3401 |
utils.shuffle_sync(tSrdPsum, offset=2 * v + 1),
|
| 3402 |
)
|
| 3403 |
+
tdPrdP_cur[2 * v], tdPrdP_cur[2 * v + 1] = cute.arch.sub_packed_f32x2(
|
| 3404 |
+
(tdPrdP_cur[2 * v], tdPrdP_cur[2 * v + 1]), dPsum_pair
|
|
|
|
|
|
|
| 3405 |
)
|
| 3406 |
tdPrdP_cur[2 * v], tdPrdP_cur[2 * v + 1] = cute.arch.mul_packed_f32x2(
|
| 3407 |
(tSrS_cur[2 * v], tSrS_cur[2 * v + 1]),
|
|
|
|
| 3410 |
|
| 3411 |
if const_expr(self.score_mod_bwd is not None):
|
| 3412 |
tSrS_pre_cur = tSrS_pre[None, stage, 0, 0]
|
| 3413 |
+
cluster_tile_n = self.tile_n * self.cta_group_size
|
| 3414 |
+
cluster_n_block = n_block // self.cta_group_size
|
| 3415 |
+
cS_bwd = cute.make_identity_tensor((cluster_tile_n, self.tile_m))
|
| 3416 |
cS_bwd = cute.domain_offset(
|
| 3417 |
+
(cluster_n_block * cluster_tile_n, m_block * self.tile_m),
|
| 3418 |
+
cS_bwd,
|
| 3419 |
)
|
| 3420 |
tScS_bwd = thr_mma_S.partition_C(cS_bwd)
|
| 3421 |
tScS_idx_bwd = thr_copy_t2r.partition_D(tScS_bwd)
|
|
|
|
| 3428 |
head_idx,
|
| 3429 |
softmax_scale,
|
| 3430 |
seqlen,
|
| 3431 |
+
aux_data,
|
| 3432 |
fastdiv_mods,
|
| 3433 |
)
|
| 3434 |
# Zero out OOB positions (kv_idx >= seqlen_k) after score_mod_bwd
|
|
|
|
| 3476 |
cute.autovec_copy(tdPrdS_xchg, tRS_sdS_xchg[None, 0])
|
| 3477 |
|
| 3478 |
cute.arch.fence_view_async_shared()
|
| 3479 |
+
# The dS and dPsum mbarriers expect one arrival per compute warp
|
| 3480 |
+
if const_expr(self.split_P_dS):
|
| 3481 |
+
cute.arch.sync_warp()
|
| 3482 |
+
else:
|
| 3483 |
+
self.compute_sync_barrier.arrive_and_wait()
|
| 3484 |
+
# Normally we'd need syncwarp here since only 1 thread will signal in
|
| 3485 |
+
# consumer_release, but we already have the barrier or sync_warp before this
|
| 3486 |
pipeline_dPsum.consumer_release(consumer_state_dPsum)
|
| 3487 |
consumer_state_dPsum.advance()
|
| 3488 |
# when 2cta hdim 128, pipeline_dS also signals S tmem load completion so is deferred
|
|
|
|
| 3643 |
tile_scheduler.advance_to_next_work()
|
| 3644 |
work_tile = tile_scheduler.get_current_work()
|
| 3645 |
|
| 3646 |
+
@cute.jit
|
| 3647 |
+
def _dq_semaphore_lock_value(
|
| 3648 |
+
self,
|
| 3649 |
+
iter_idx: Int32,
|
| 3650 |
+
curr_q_cnt: Int32,
|
| 3651 |
+
curr_dq_write_order: Optional[cute.Tensor],
|
| 3652 |
+
curr_dq_write_order_full: Optional[cute.Tensor],
|
| 3653 |
+
blocksparse_tensors: Optional[BlockSparseTensors],
|
| 3654 |
+
block_info: BlockInfo,
|
| 3655 |
+
seqlen,
|
| 3656 |
+
m_block: Int32,
|
| 3657 |
+
n_block: Int32,
|
| 3658 |
+
) -> Int32:
|
| 3659 |
+
lock_value = n_block
|
| 3660 |
+
if const_expr(self.spt):
|
| 3661 |
+
n_block_max_for_m_block = block_info.get_n_block_max_for_m_block(seqlen, m_block)
|
| 3662 |
+
lock_value = n_block_max_for_m_block - 1 - n_block
|
| 3663 |
+
if const_expr(self.use_block_sparsity):
|
| 3664 |
+
assert blocksparse_tensors is not None
|
| 3665 |
+
if const_expr(blocksparse_tensors.dq_write_order is not None):
|
| 3666 |
+
sparse_iter = iter_idx // self.q_subtile_factor
|
| 3667 |
+
if sparse_iter < curr_q_cnt:
|
| 3668 |
+
assert curr_dq_write_order is not None
|
| 3669 |
+
lock_value = curr_dq_write_order[sparse_iter]
|
| 3670 |
+
else:
|
| 3671 |
+
assert curr_dq_write_order_full is not None
|
| 3672 |
+
lock_value = curr_dq_write_order_full[sparse_iter - curr_q_cnt]
|
| 3673 |
+
if const_expr(self.kv_subtile_factor > self.cta_group_size):
|
| 3674 |
+
groups_per_sparse_block = self.kv_subtile_factor // self.cta_group_size
|
| 3675 |
+
local_group = n_block % groups_per_sparse_block
|
| 3676 |
+
if const_expr(self.spt):
|
| 3677 |
+
# [NOTE] KV_subtile determ + spt
|
| 3678 |
+
# dq_write_order stores one rank per sparse block; each physical tile
|
| 3679 |
+
# derives its slot as rank * groups_per_sparse_block + (n_block % groups_per_sparse_block).
|
| 3680 |
+
# W/ kv_subtile the tail sparse column can have a physical tile that is
|
| 3681 |
+
# never scheduled; e.g. kv_tile = 128, seqlen = 1023, KV_Block = 384 -> 3
|
| 3682 |
+
# sparse blocks. The last block is covered in [768, 896), [896, 1024) and
|
| 3683 |
+
# then [1024, 1152) which no CTA ever runs. Since the highest tile gets
|
| 3684 |
+
# the lowest lock value under spt, we would hang!
|
| 3685 |
+
# In this case we locally reverse, [N+2, N+1, N*] where N* is not scheduled
|
| 3686 |
+
# -> [N+1, N, N+2*]. Ahh but won't the vacant N+2 slot stall the next sparse
|
| 3687 |
+
# block's CTAs? It will, so the writer holding N+1 bumps the increment by 2
|
| 3688 |
+
# instead of 1 (i.e. 1 + #unscheduled) :)
|
| 3689 |
+
total_groups = cute.ceil_div(
|
| 3690 |
+
seqlen.seqlen_k, self.tile_n * self.cta_group_size
|
| 3691 |
+
)
|
| 3692 |
+
groups_in_own_block = cutlass.min(
|
| 3693 |
+
groups_per_sparse_block,
|
| 3694 |
+
total_groups
|
| 3695 |
+
- (n_block // groups_per_sparse_block) * groups_per_sparse_block,
|
| 3696 |
+
)
|
| 3697 |
+
local_group = groups_in_own_block - 1 - local_group
|
| 3698 |
+
lock_value = lock_value * groups_per_sparse_block + local_group
|
| 3699 |
+
return lock_value
|
| 3700 |
+
|
| 3701 |
@cute.jit
|
| 3702 |
def dQacc_reduce(
|
| 3703 |
self,
|
| 3704 |
mdQaccum: cute.Tensor,
|
| 3705 |
sdQaccum: cute.Tensor,
|
| 3706 |
+
thr_mma_dQ: cute.ThrMma,
|
| 3707 |
tdQtdQ: cute.Tensor,
|
| 3708 |
pipeline_dQ: PipelineAsync,
|
| 3709 |
dQaccum_empty_mbar_ptr: Optional[cute.Pointer],
|
|
|
|
| 3755 |
while work_tile.is_valid_tile:
|
| 3756 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 3757 |
n_block_cta_group = n_block // self.cta_group_size # for 2cta
|
| 3758 |
+
n_block_sparse = n_block // self.kv_subtile_factor
|
| 3759 |
seqlen = SeqlenInfoCls(batch_idx)
|
| 3760 |
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block_cta_group)
|
| 3761 |
if const_expr(not seqlen.has_cu_seqlens_q):
|
|
|
|
| 3771 |
)
|
| 3772 |
|
| 3773 |
if const_expr(self.deterministic):
|
| 3774 |
+
assert mdQ_semaphore is not None
|
| 3775 |
mdQ_semaphore_cur = mdQ_semaphore[None, None, head_idx, batch_idx]
|
| 3776 |
|
| 3777 |
+
delay_semaphore_release = not self.tile_hdim == 192 and not self.use_block_sparsity
|
|
|
|
| 3778 |
|
| 3779 |
+
dq_sem_release_inc = Int32(1)
|
| 3780 |
+
if const_expr(
|
| 3781 |
+
self.deterministic
|
| 3782 |
+
and self.use_block_sparsity
|
| 3783 |
+
and self.spt
|
| 3784 |
+
and self.kv_subtile_factor > self.cta_group_size
|
| 3785 |
+
):
|
| 3786 |
+
# A truncated tail block's last writer releases the missing increments,
|
| 3787 |
+
# see: [NOTE] KV_subtile determ + spt
|
| 3788 |
+
groups_per_sparse_block = self.kv_subtile_factor // self.cta_group_size
|
| 3789 |
+
total_groups = cute.ceil_div(seqlen.seqlen_k, self.tile_n * self.cta_group_size)
|
| 3790 |
+
tail_sparse_block_idx = (total_groups - 1) // groups_per_sparse_block
|
| 3791 |
+
groups_in_tail = total_groups - tail_sparse_block_idx * groups_per_sparse_block
|
| 3792 |
+
is_tail_bridge_group = (
|
| 3793 |
+
n_block_cta_group // groups_per_sparse_block == tail_sparse_block_idx
|
| 3794 |
+
and n_block_cta_group % groups_per_sparse_block == 0
|
| 3795 |
+
)
|
| 3796 |
+
if is_tail_bridge_group:
|
| 3797 |
+
dq_sem_release_inc = Int32(1) + groups_per_sparse_block - groups_in_tail
|
| 3798 |
+
|
| 3799 |
+
curr_q_cnt = Int32(0)
|
| 3800 |
+
curr_q_idx = None
|
| 3801 |
+
curr_full_cnt = Int32(0)
|
| 3802 |
+
curr_full_idx = None
|
| 3803 |
+
curr_dq_write_order = None
|
| 3804 |
+
curr_dq_write_order_full = None
|
| 3805 |
+
loop_count = m_block_max - m_block_min
|
| 3806 |
+
process_tile = (
|
| 3807 |
+
const_expr(not self.is_local and not self.is_varlen_q) or m_block_min < m_block_max
|
| 3808 |
+
)
|
| 3809 |
if const_expr(self.use_block_sparsity):
|
| 3810 |
+
assert blocksparse_tensors is not None
|
| 3811 |
(
|
| 3812 |
curr_q_cnt,
|
| 3813 |
curr_q_idx,
|
|
|
|
| 3818 |
blocksparse_tensors,
|
| 3819 |
batch_idx,
|
| 3820 |
head_idx,
|
| 3821 |
+
n_block_sparse,
|
| 3822 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 3823 |
m_block_max=m_block_max,
|
| 3824 |
)
|
| 3825 |
process_tile = loop_count > Int32(0)
|
| 3826 |
+
if const_expr(self.deterministic and self.use_block_sparsity):
|
| 3827 |
+
assert blocksparse_tensors is not None
|
| 3828 |
+
if const_expr(blocksparse_tensors.dq_write_order is not None):
|
| 3829 |
+
assert blocksparse_tensors.dq_write_order is not None
|
| 3830 |
+
curr_dq_write_order = blocksparse_tensors.dq_write_order[
|
| 3831 |
+
batch_idx, head_idx, n_block_sparse, None
|
| 3832 |
+
]
|
| 3833 |
+
if const_expr(blocksparse_tensors.dq_write_order_full is not None):
|
| 3834 |
+
assert blocksparse_tensors.dq_write_order_full is not None
|
| 3835 |
+
curr_dq_write_order_full = blocksparse_tensors.dq_write_order_full[
|
| 3836 |
+
batch_idx, head_idx, n_block_sparse, None
|
| 3837 |
+
]
|
| 3838 |
|
| 3839 |
# dQacc_reduce mainloop
|
| 3840 |
# Block sparsity: iterate over sparse m_block count and derive actual m_block
|
| 3841 |
# from Q_IDX/FULL_Q_IDX tensors. Dense: iterate m_block_min..m_block_max directly.
|
| 3842 |
for iter_idx in cutlass.range(loop_count, unroll=1):
|
| 3843 |
+
m_block = m_block_min + iter_idx
|
| 3844 |
+
m_block_oob_upper = False
|
| 3845 |
if const_expr(self.use_block_sparsity):
|
| 3846 |
m_block, _ = get_m_block_from_iter_bwd(
|
| 3847 |
iter_idx,
|
|
|
|
| 3849 |
curr_q_idx,
|
| 3850 |
curr_full_cnt,
|
| 3851 |
curr_full_idx,
|
| 3852 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 3853 |
m_block_max=m_block_max,
|
| 3854 |
)
|
| 3855 |
+
m_block_oob_upper = m_block >= m_block_max
|
|
|
|
|
|
|
|
|
|
| 3856 |
pipeline_dQ.consumer_wait(dQ_consumer_state)
|
| 3857 |
# TMEM -> RMEM
|
| 3858 |
+
tdQrdQ_t2r = cute.make_rmem_tensor(tdQrdQ_t2r_shape, Float32)
|
| 3859 |
cute.copy(thr_copy_t2r, tdQtdQ_t2r, tdQrdQ_t2r)
|
| 3860 |
cute.arch.fence_view_async_tmem_load()
|
| 3861 |
cute.arch.sync_warp()
|
|
|
|
| 3863 |
pipeline_dQ.consumer_release(dQ_consumer_state)
|
| 3864 |
dQ_consumer_state.advance()
|
| 3865 |
|
| 3866 |
+
if m_block_max > 0:
|
| 3867 |
+
m_block = cutlass.min(m_block, m_block_max - 1)
|
| 3868 |
gdQaccum_cur = gdQaccum[None, None, m_block]
|
| 3869 |
|
| 3870 |
tdQrdQ_shape = (
|
|
|
|
| 3882 |
cute.arch.fence_view_async_shared()
|
| 3883 |
# semaphore acquire
|
| 3884 |
if const_expr(self.deterministic and stage == 0):
|
| 3885 |
+
if not m_block_oob_upper:
|
| 3886 |
+
lock_value = self._dq_semaphore_lock_value(
|
| 3887 |
+
iter_idx,
|
| 3888 |
+
curr_q_cnt,
|
| 3889 |
+
curr_dq_write_order,
|
| 3890 |
+
curr_dq_write_order_full,
|
| 3891 |
+
blocksparse_tensors,
|
| 3892 |
+
block_info,
|
| 3893 |
+
seqlen,
|
| 3894 |
+
m_block,
|
| 3895 |
+
n_block_cta_group,
|
| 3896 |
+
)
|
| 3897 |
+
barrier.wait_eq(
|
| 3898 |
+
mdQ_semaphore_cur[(m_block, None)].iterator,
|
| 3899 |
+
tidx,
|
| 3900 |
+
cta_rank_in_cluster,
|
| 3901 |
+
lock_value,
|
| 3902 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3903 |
self.reduce_sync_barrier.arrive_and_wait()
|
| 3904 |
# Copy from shared memory to global memory
|
| 3905 |
+
if is_tma_warp and not m_block_oob_upper:
|
| 3906 |
with cute.arch.elect_one():
|
| 3907 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 3908 |
sdQaccum[None, smem_idx].iterator,
|
|
|
|
| 3911 |
)
|
| 3912 |
cute.arch.cp_async_bulk_commit_group()
|
| 3913 |
cute.arch.cp_async_bulk_wait_group(self.sdQaccum_stage - 1, read=read_flag)
|
| 3914 |
+
elif is_tma_warp:
|
| 3915 |
+
# Drain pending TMA stores so SMEM buffers are safe to reuse
|
| 3916 |
+
cute.arch.cp_async_bulk_wait_group(0, read=read_flag)
|
| 3917 |
self.reduce_sync_barrier.arrive_and_wait()
|
| 3918 |
dQ_tma_store_producer_state.advance()
|
| 3919 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3920 |
if const_expr(self.deterministic and stage == 0 and delay_semaphore_release):
|
| 3921 |
if m_block > m_block_min:
|
| 3922 |
barrier.arrive_inc(
|
|
|
|
| 3938 |
# NOTE: arrive_inc calls red_release which issues membar
|
| 3939 |
if const_expr(self.deterministic and not delay_semaphore_release):
|
| 3940 |
if const_expr(self.sdQaccum_stage > 1 and not self.tile_hdim == 192):
|
| 3941 |
+
if is_tma_warp and not m_block_oob_upper:
|
| 3942 |
cute.arch.cp_async_bulk_wait_group(0, read=read_flag)
|
| 3943 |
self.reduce_sync_barrier.arrive_and_wait()
|
| 3944 |
+
if not m_block_oob_upper:
|
| 3945 |
+
barrier.arrive_inc(
|
| 3946 |
+
mdQ_semaphore_cur[m_block, None].iterator,
|
| 3947 |
+
tidx,
|
| 3948 |
+
cta_rank_in_cluster,
|
| 3949 |
+
dq_sem_release_inc,
|
| 3950 |
+
)
|
| 3951 |
|
| 3952 |
if process_tile:
|
| 3953 |
if is_tma_warp:
|
|
|
|
| 3963 |
)
|
| 3964 |
|
| 3965 |
if const_expr(
|
| 3966 |
+
self.deterministic
|
| 3967 |
+
and not self.spt
|
| 3968 |
+
and not self.use_block_sparsity
|
| 3969 |
+
and block_info.window_size_left is not None
|
| 3970 |
):
|
| 3971 |
m_block_global_max = cute.ceil_div(seqlen.seqlen_q, self.tile_m)
|
| 3972 |
for m_block in cutlass.range(m_block_max, m_block_global_max, unroll=1):
|
|
|
|
| 3977 |
tile_scheduler.advance_to_next_work()
|
| 3978 |
work_tile = tile_scheduler.get_current_work()
|
| 3979 |
|
| 3980 |
+
if const_expr(not self.deterministic):
|
| 3981 |
+
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
| 3982 |
+
|
| 3983 |
@cute.jit
|
| 3984 |
def epilogue_dKV(
|
| 3985 |
self,
|
|
|
|
| 3989 |
head_idx: Int32,
|
| 3990 |
n_block: Int32,
|
| 3991 |
seqlen,
|
| 3992 |
+
thr_mma_dV: cute.ThrMma,
|
| 3993 |
+
thr_mma_dK: cute.ThrMma,
|
| 3994 |
tdVtdV: cute.Tensor,
|
| 3995 |
tdKtdK: cute.Tensor,
|
| 3996 |
mdV: cute.Tensor,
|
|
|
|
| 4008 |
mdV_cur = seqlen.offset_batch_K(mdV, batch_idx, dim=3)[None, None, head_idx]
|
| 4009 |
mdK_cur = seqlen.offset_batch_K(mdK, batch_idx, dim=3)[None, None, head_idx]
|
| 4010 |
|
| 4011 |
+
# The gmem store below has no head-dim OOB protection from TMA, so when head_dim is
|
| 4012 |
+
# not a multiple of the tile we predicate each 128-bit store vector (8 bf16 columns).
|
| 4013 |
+
# The store's predicate granularity is one tmem-load repetition, so shrink the
|
| 4014 |
+
# repetition to 8 columns for that case; 16 keeps fewer tmem loads otherwise.
|
| 4015 |
+
tmem_load_atom_dV = cute.make_copy_atom(
|
| 4016 |
+
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(8 if self.check_hdim_v_oob else 16)),
|
| 4017 |
+
Float32,
|
| 4018 |
+
)
|
| 4019 |
+
tmem_load_atom_dK = cute.make_copy_atom(
|
| 4020 |
+
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(8 if self.check_hdim_oob else 16)),
|
| 4021 |
+
Float32,
|
| 4022 |
)
|
| 4023 |
# dV
|
| 4024 |
pipeline_dKV.consumer_wait(consumer_state_dKV)
|
| 4025 |
|
| 4026 |
+
tiled_tmem_ld_dV = tcgen05.make_tmem_copy(tmem_load_atom_dV, tdVtdV)
|
| 4027 |
thr_tmem_ld_dV = tiled_tmem_ld_dV.get_slice(tidx)
|
| 4028 |
|
| 4029 |
tdVtdV_t2r_p = thr_tmem_ld_dV.partition_S(tdVtdV)
|
|
|
|
| 4035 |
|
| 4036 |
tdVcdV_t2r_p = thr_tmem_ld_dV.partition_D(tdVcdV_tensor)
|
| 4037 |
tdVcdV_t2r = self.split_wg(tdVcdV_t2r_p, wg_idx, num_wg)
|
| 4038 |
+
tdVrdV_t2r = cute.make_rmem_tensor(tdVcdV_t2r.shape, Float32)
|
| 4039 |
|
| 4040 |
cute.copy(thr_tmem_ld_dV, tdVtdV_t2r, tdVrdV_t2r)
|
| 4041 |
cute.arch.fence_view_async_tmem_load()
|
|
|
|
| 4052 |
tiler_mn=tiled_tmem_ld_dV.tiler_mn,
|
| 4053 |
)
|
| 4054 |
|
| 4055 |
+
tdVrdV_r2s = cute.make_rmem_tensor(tdVrdV_t2r.shape, self.dv_dtype)
|
| 4056 |
for i in cutlass.range_constexpr(cute.size(tdVrdV_t2r, mode=[1])):
|
| 4057 |
dV_vec = tdVrdV_t2r[(None, i, 0, 0)].load()
|
| 4058 |
tdVrdV_r2s[(None, i, 0, 0)].store(dV_vec.to(self.dv_dtype))
|
|
|
|
| 4064 |
tdVgdV_r2g_p = thr_tmem_ld_dV.partition_D(tdVgdV)
|
| 4065 |
tdVgdV_r2g = self.split_wg(tdVgdV_r2g_p, wg_idx, num_wg)
|
| 4066 |
|
| 4067 |
+
tdVpdV = None
|
| 4068 |
+
if const_expr(self.check_hdim_v_oob):
|
| 4069 |
+
tdVpdV = self.predicate_hdim(tdVcdV_t2r, self.head_dim_v)
|
| 4070 |
if tidx < seqlen.seqlen_k - self.tile_n * n_block:
|
| 4071 |
+
cute.copy(tiled_gmem_store_dV, tdVrdV_r2s, tdVgdV_r2g, pred=tdVpdV)
|
| 4072 |
|
| 4073 |
cute.arch.sync_warp()
|
| 4074 |
with cute.arch.elect_one():
|
|
|
|
| 4078 |
# dK
|
| 4079 |
pipeline_dKV.consumer_wait(consumer_state_dKV)
|
| 4080 |
|
| 4081 |
+
tiled_tmem_ld_dK = tcgen05.make_tmem_copy(tmem_load_atom_dK, tdKtdK)
|
| 4082 |
thr_tmem_ld_dK = tiled_tmem_ld_dK.get_slice(tidx)
|
| 4083 |
|
| 4084 |
tdKtdK_t2r_p = thr_tmem_ld_dK.partition_S(tdKtdK)
|
|
|
|
| 4090 |
|
| 4091 |
tdKcdK_t2r_p = thr_tmem_ld_dK.partition_D(tdKcdK_tensor)
|
| 4092 |
tdKcdK_t2r = self.split_wg(tdKcdK_t2r_p, wg_idx, num_wg)
|
| 4093 |
+
tdKrdK_t2r = cute.make_rmem_tensor(tdKcdK_t2r.shape, Float32)
|
| 4094 |
|
| 4095 |
cute.copy(tiled_tmem_ld_dK, tdKtdK_t2r, tdKrdK_t2r)
|
| 4096 |
cute.arch.fence_view_async_tmem_load()
|
|
|
|
| 4108 |
tiler_mn=tiled_tmem_ld_dK.tiler_mn,
|
| 4109 |
)
|
| 4110 |
|
| 4111 |
+
tdKrdK_r2s = cute.make_rmem_tensor(tdKrdK_t2r.shape, self.dk_dtype)
|
| 4112 |
|
| 4113 |
for i in cutlass.range_constexpr(cute.size(tdKrdK_t2r, mode=[1])):
|
| 4114 |
dK_vec = tdKrdK_t2r[(None, i, 0, 0)].load() * softmax_scale
|
|
|
|
| 4121 |
tdKgdK_r2g_p = thr_tmem_ld_dK.partition_D(tdKgdK)
|
| 4122 |
tdKgdK_r2g = self.split_wg(tdKgdK_r2g_p, wg_idx, num_wg)
|
| 4123 |
|
| 4124 |
+
tdKpdK = None
|
| 4125 |
+
if const_expr(self.check_hdim_oob):
|
| 4126 |
+
tdKpdK = self.predicate_hdim(tdKcdK_t2r, self.head_dim)
|
| 4127 |
if tidx < seqlen.seqlen_k - self.tile_n * n_block:
|
| 4128 |
+
cute.copy(tiled_gmem_store_dK, tdKrdK_r2s, tdKgdK_r2g, pred=tdKpdK)
|
| 4129 |
|
| 4130 |
cute.arch.sync_warp()
|
| 4131 |
with cute.arch.elect_one():
|
| 4132 |
pipeline_dKV.consumer_release(consumer_state_dKV)
|
| 4133 |
return consumer_state_dKV
|
| 4134 |
|
| 4135 |
+
@cute.jit
|
| 4136 |
+
def predicate_hdim(self, tcoord: cute.Tensor, limit: cutlass.Constexpr[int]) -> cute.Tensor:
|
| 4137 |
+
"""Per-store-vector head-dim predicate for the non-TMA dK/dV epilogue store.
|
| 4138 |
+
|
| 4139 |
+
``tcoord`` is the (tile_n, hdim) identity tensor partitioned like the register data,
|
| 4140 |
+
shape ((rep, 1), chunks, 1, 1) with one contiguous ``rep``-wide column chunk per copy
|
| 4141 |
+
vector; the predicate drops the vector mode and marks each chunk whose first column
|
| 4142 |
+
is inside ``limit``. Valid because ``limit`` is a multiple of ``rep``.
|
| 4143 |
+
"""
|
| 4144 |
+
tpred = cute.make_rmem_tensor(
|
| 4145 |
+
cute.make_layout((1, cute.size(tcoord, mode=[1]), 1, 1)), cutlass.Boolean
|
| 4146 |
+
)
|
| 4147 |
+
for i in cutlass.range_constexpr(cute.size(tcoord, mode=[1])):
|
| 4148 |
+
tpred[0, i, 0, 0] = tcoord[0, i, 0, 0][1] < limit
|
| 4149 |
+
return tpred
|
| 4150 |
+
|
| 4151 |
@cute.jit
|
| 4152 |
def epilogue_dK_or_dV_tma(
|
| 4153 |
self,
|
|
|
|
| 4156 |
head_idx: Int32,
|
| 4157 |
n_block: Int32,
|
| 4158 |
seqlen,
|
| 4159 |
+
thr_mma: cute.ThrMma,
|
| 4160 |
tdKVtdKV: cute.Tensor,
|
| 4161 |
mdKV: cute.Tensor,
|
| 4162 |
sdKV: cute.Tensor,
|
|
|
|
| 4222 |
|
| 4223 |
deterministic_KV = self.deterministic and self.qhead_per_kvhead > 1
|
| 4224 |
if const_expr(deterministic_KV):
|
| 4225 |
+
assert mdKV_semaphore is not None
|
| 4226 |
mdKV_semaphore_cur = mdKV_semaphore[n_block, None, head_idx_kv, batch_idx]
|
| 4227 |
|
| 4228 |
if const_expr(not self.dKV_postprocess):
|
|
|
|
| 4275 |
if const_expr(num_epi_stages > 1):
|
| 4276 |
tdKVcdKV_t2r = tdKVcdKV_t2r[None, epi_stage]
|
| 4277 |
|
| 4278 |
+
tdKVrdKV_t2r = cute.make_rmem_tensor(tdKVcdKV_t2r.shape, Float32)
|
| 4279 |
|
| 4280 |
assert cute.size(tdKVrdKV_t2r) == cute.size(tdKVtdKV_t2r) // cute.arch.WARP_SIZE, (
|
| 4281 |
"RMEM<->TMEM fragment size mismatch"
|
|
|
|
| 4291 |
tdKVrdKV_t2r[2 * i], tdKVrdKV_t2r[2 * i + 1] = cute.arch.mul_packed_f32x2(
|
| 4292 |
(tdKVrdKV_t2r[2 * i], tdKVrdKV_t2r[2 * i + 1]), (scale, scale)
|
| 4293 |
)
|
| 4294 |
+
tdKVrdKV = cute.make_rmem_tensor(tdKVrdKV_t2r.shape, dtype) # (32 columns)
|
| 4295 |
tdKVrdKV.store(tdKVrdKV_t2r.load().to(dtype))
|
| 4296 |
|
| 4297 |
# RMEM -> SMEM -- copy, fence and barrier
|
build/torch-cuda/flash_bwd_sm120.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# SM120 (Blackwell GeForce / DGX Spark) backward pass.
|
| 3 |
+
#
|
| 4 |
+
# SM120 uses the same SM80-era MMA instructions (mma.sync.aligned.m16n8k16) but has
|
| 5 |
+
# a smaller shared memory capacity (99 KB vs 163 KB on SM80). This module subclasses
|
| 6 |
+
# FlashAttentionBackwardSm80 and overrides the SMEM capacity check accordingly.
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.utils as utils_basic
|
| 10 |
+
|
| 11 |
+
from .flash_bwd import FlashAttentionBackwardSm80
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class FlashAttentionBackwardSm120(FlashAttentionBackwardSm80):
|
| 15 |
+
@staticmethod
|
| 16 |
+
def can_implement(
|
| 17 |
+
dtype,
|
| 18 |
+
head_dim,
|
| 19 |
+
head_dim_v,
|
| 20 |
+
m_block_size,
|
| 21 |
+
n_block_size,
|
| 22 |
+
num_stages_Q,
|
| 23 |
+
num_stages_dO,
|
| 24 |
+
num_threads,
|
| 25 |
+
is_causal,
|
| 26 |
+
V_in_regs=False,
|
| 27 |
+
) -> bool:
|
| 28 |
+
"""Check if the kernel can be implemented on SM120.
|
| 29 |
+
|
| 30 |
+
Same logic as SM80 but uses SM120's shared memory capacity (99 KB).
|
| 31 |
+
"""
|
| 32 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16]:
|
| 33 |
+
return False
|
| 34 |
+
if head_dim % 8 != 0:
|
| 35 |
+
return False
|
| 36 |
+
if head_dim_v % 8 != 0:
|
| 37 |
+
return False
|
| 38 |
+
if n_block_size % 16 != 0:
|
| 39 |
+
return False
|
| 40 |
+
if num_threads % 32 != 0:
|
| 41 |
+
return False
|
| 42 |
+
# Shared memory usage: Q tile + dO tile + K tile + V tile
|
| 43 |
+
smem_usage_Q = m_block_size * head_dim * num_stages_Q * 2
|
| 44 |
+
smem_usage_dO = m_block_size * head_dim_v * num_stages_dO * 2
|
| 45 |
+
smem_usage_K = n_block_size * head_dim * 2
|
| 46 |
+
smem_usage_V = n_block_size * head_dim_v * 2
|
| 47 |
+
smem_usage_QV = (
|
| 48 |
+
(smem_usage_Q + smem_usage_V) if not V_in_regs else max(smem_usage_Q, smem_usage_V)
|
| 49 |
+
)
|
| 50 |
+
smem_usage = smem_usage_QV + smem_usage_dO + smem_usage_K
|
| 51 |
+
# SM120 has 99 KB shared memory (vs 163 KB on SM80)
|
| 52 |
+
smem_capacity = utils_basic.get_smem_capacity_in_bytes("sm_120")
|
| 53 |
+
if smem_usage > smem_capacity:
|
| 54 |
+
return False
|
| 55 |
+
return True
|
build/torch-cuda/flash_bwd_sm90.py
CHANGED
|
@@ -24,10 +24,17 @@ from .seqlen_info import SeqlenInfoQK
|
|
| 24 |
from .block_info import BlockInfo
|
| 25 |
from . import pipeline
|
| 26 |
from .quack.cute_dsl_utils import ParamsBase
|
| 27 |
-
from .tile_scheduler import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
from .named_barrier import NamedBarrierBwd
|
| 29 |
from .softmax import apply_score_mod_inner, apply_score_mod_bwd_inner
|
| 30 |
from .block_sparsity import BlockSparseTensors
|
|
|
|
| 31 |
from .block_sparse_utils import (
|
| 32 |
get_total_q_block_count_bwd,
|
| 33 |
produce_block_sparse_q_loads_bwd_sm90,
|
|
@@ -46,6 +53,8 @@ class FlashAttentionBackwardSm90:
|
|
| 46 |
head_dim_v: Optional[int] = None,
|
| 47 |
qhead_per_kvhead: int = 1,
|
| 48 |
is_causal: bool = False,
|
|
|
|
|
|
|
| 49 |
tile_m: int = 64,
|
| 50 |
tile_n: int = 128,
|
| 51 |
Q_stage: int = 2,
|
|
@@ -63,11 +72,12 @@ class FlashAttentionBackwardSm90:
|
|
| 63 |
score_mod_bwd: cutlass.Constexpr | None = None,
|
| 64 |
mask_mod: cutlass.Constexpr | None = None,
|
| 65 |
has_aux_tensors: cutlass.Constexpr = False,
|
| 66 |
-
|
|
|
|
| 67 |
):
|
| 68 |
self.dtype = dtype
|
| 69 |
-
#
|
| 70 |
-
hdim_multiple_of = 16
|
| 71 |
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 72 |
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
| 73 |
self.same_hdim_kv = head_dim == head_dim_v
|
|
@@ -77,7 +87,8 @@ class FlashAttentionBackwardSm90:
|
|
| 77 |
self.check_hdim_v_oob = head_dim_v != self.tile_hdimv
|
| 78 |
self.qhead_per_kvhead = qhead_per_kvhead
|
| 79 |
self.is_causal = is_causal
|
| 80 |
-
self.is_local =
|
|
|
|
| 81 |
self.tile_m = tile_m
|
| 82 |
self.tile_n = tile_n
|
| 83 |
self.num_threads = num_threads
|
|
@@ -92,23 +103,23 @@ class FlashAttentionBackwardSm90:
|
|
| 92 |
self.AtomLayoutMSdP = AtomLayoutMSdP
|
| 93 |
self.AtomLayoutNdKV = AtomLayoutNdKV
|
| 94 |
self.AtomLayoutMdQ = AtomLayoutMdQ
|
| 95 |
-
self.
|
| 96 |
self.mma_dkv_is_rs = (
|
| 97 |
AtomLayoutMSdP == 1
|
| 98 |
-
and AtomLayoutNdKV == self.
|
| 99 |
and SdP_swapAB
|
| 100 |
and not dKV_swapAB
|
| 101 |
)
|
| 102 |
self.V_in_regs = V_in_regs
|
|
|
|
| 103 |
if qhead_per_kvhead > 1:
|
| 104 |
assert self.same_hdim_kv, "GQA backward requires head_dim == head_dim_v"
|
| 105 |
-
assert self.
|
| 106 |
# These are tuned for speed
|
| 107 |
# Do we keep the LSE and dPsum in each thread, or split them across 8 threads that share
|
| 108 |
# them and then shuffle to get the value whenever we need? This can reduce register
|
| 109 |
# pressure when SdP_swapAB, where each thread needs to keep statistics for (kBlockM / 4)
|
| 110 |
# rows. If !SdP_swapAB, each thread only needs to keep statistics for 2 rows.
|
| 111 |
-
# TODO: impl these for hdim 64
|
| 112 |
self.shuffle_LSE = self.SdP_swapAB and self.tile_hdim <= 64
|
| 113 |
self.shuffle_dPsum = self.SdP_swapAB and self.tile_hdim <= 64
|
| 114 |
|
|
@@ -118,12 +129,18 @@ class FlashAttentionBackwardSm90:
|
|
| 118 |
self.score_mod_bwd = score_mod_bwd
|
| 119 |
self.mask_mod = mask_mod
|
| 120 |
self.has_aux_tensors = has_aux_tensors
|
| 121 |
-
self.
|
| 122 |
if cutlass.const_expr(has_aux_tensors):
|
| 123 |
self.vec_size: cutlass.Constexpr = 1
|
| 124 |
else:
|
| 125 |
self.vec_size: cutlass.Constexpr = 4
|
| 126 |
self.qk_acc_dtype = Float32
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
@staticmethod
|
| 129 |
def can_implement(
|
|
@@ -182,32 +199,64 @@ class FlashAttentionBackwardSm90:
|
|
| 182 |
assert mQ_type == self.dtype
|
| 183 |
|
| 184 |
def _setup_attributes(self):
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
]
|
| 194 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
self.sdQaccum_layout = cute.make_layout(
|
| 196 |
-
(self.tile_m * self.tile_hdim // self.
|
| 197 |
)
|
| 198 |
# dQaccum R->S
|
| 199 |
self.r2s_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 200 |
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 201 |
# thr_layout
|
| 202 |
-
cute.make_layout((self.num_threads_per_warp_group, self.
|
| 203 |
cute.make_layout(128 // Float32.width), # val_layout
|
| 204 |
)
|
| 205 |
# dKVaccum for GQA epilogue - reuses sV+sK memory recast as f32
|
| 206 |
# TODO: assert that sVaccum and sKaccum don't overflow smem
|
| 207 |
|
| 208 |
def _get_tiled_mma(self):
|
|
|
|
| 209 |
# S = Q @ K.T, dP = dO @ V.T
|
| 210 |
-
atom_layout_SdP = (self.AtomLayoutMSdP, self.
|
| 211 |
tiler_mn_SdP = (self.tile_m // atom_layout_SdP[0], self.tile_n // atom_layout_SdP[1])
|
| 212 |
tiled_mma_SdP = sm90_utils_basic.make_trivial_tiled_mma(
|
| 213 |
self.dtype,
|
|
@@ -215,12 +264,11 @@ class FlashAttentionBackwardSm90:
|
|
| 215 |
warpgroup.OperandMajorMode.K,
|
| 216 |
warpgroup.OperandMajorMode.K,
|
| 217 |
Float32,
|
| 218 |
-
atom_layout_mnk=(atom_layout_SdP
|
| 219 |
-
|
| 220 |
-
tiler_mn=tiler_mn_SdP if not self.SdP_swapAB else tiler_mn_SdP[::-1],
|
| 221 |
)
|
| 222 |
# dV = P.T @ dO, dK = dS.T @ Q
|
| 223 |
-
atom_layout_dKV = (self.AtomLayoutNdKV, self.
|
| 224 |
tiler_mn_dK = (self.tile_n // atom_layout_dKV[0], self.tile_hdim // atom_layout_dKV[1])
|
| 225 |
tiler_mn_dV = (self.tile_n // atom_layout_dKV[0], self.tile_hdimv // atom_layout_dKV[1])
|
| 226 |
tiled_mma_dK, tiled_mma_dV = [
|
|
@@ -232,9 +280,8 @@ class FlashAttentionBackwardSm90:
|
|
| 232 |
else warpgroup.OperandMajorMode.K,
|
| 233 |
warpgroup.OperandMajorMode.MN,
|
| 234 |
Float32,
|
| 235 |
-
atom_layout_mnk=(atom_layout_dKV
|
| 236 |
-
|
| 237 |
-
tiler_mn=tiler_mn_d if not self.dKV_swapAB else tiler_mn_d[::-1],
|
| 238 |
a_source=warpgroup.OperandSource.RMEM
|
| 239 |
if self.mma_dkv_is_rs
|
| 240 |
else warpgroup.OperandSource.SMEM,
|
|
@@ -242,7 +289,8 @@ class FlashAttentionBackwardSm90:
|
|
| 242 |
for tiler_mn_d in (tiler_mn_dK, tiler_mn_dV)
|
| 243 |
]
|
| 244 |
# dQ = dS @ K
|
| 245 |
-
|
|
|
|
| 246 |
tiler_mn_dQ = (self.tile_m // atom_layout_dQ[0], self.tile_hdim // atom_layout_dQ[1])
|
| 247 |
tiled_mma_dQ = sm90_utils_basic.make_trivial_tiled_mma(
|
| 248 |
self.dtype,
|
|
@@ -250,8 +298,8 @@ class FlashAttentionBackwardSm90:
|
|
| 250 |
warpgroup.OperandMajorMode.K if not self.dQ_swapAB else warpgroup.OperandMajorMode.MN,
|
| 251 |
warpgroup.OperandMajorMode.MN if not self.dQ_swapAB else warpgroup.OperandMajorMode.K,
|
| 252 |
Float32,
|
| 253 |
-
atom_layout_mnk=(atom_layout_dQ
|
| 254 |
-
tiler_mn=tiler_mn_dQ if not self.dQ_swapAB else tiler_mn_dQ[
|
| 255 |
)
|
| 256 |
return tiled_mma_SdP, tiled_mma_dK, tiled_mma_dV, tiled_mma_dQ
|
| 257 |
|
|
@@ -305,23 +353,25 @@ class FlashAttentionBackwardSm90:
|
|
| 305 |
mdK: cute.Tensor,
|
| 306 |
mdV: cute.Tensor,
|
| 307 |
softmax_scale: Float32,
|
| 308 |
-
stream: cuda.CUstream,
|
| 309 |
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 310 |
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 311 |
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 312 |
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 313 |
-
softcap: Float32 | float | None = None,
|
| 314 |
window_size_left: Int32 | int | None = None,
|
| 315 |
window_size_right: Int32 | int | None = None,
|
| 316 |
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 317 |
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 318 |
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 319 |
-
|
| 320 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
|
|
|
|
|
|
|
|
|
| 321 |
):
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
|
|
|
| 325 |
|
| 326 |
self._check_type(
|
| 327 |
*(
|
|
@@ -330,23 +380,45 @@ class FlashAttentionBackwardSm90:
|
|
| 330 |
)
|
| 331 |
)
|
| 332 |
|
|
|
|
|
|
|
| 333 |
mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV = [
|
| 334 |
assume_tensor_aligned(t) for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV)
|
| 335 |
]
|
| 336 |
|
| 337 |
-
|
| 338 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
if const_expr(self.qhead_per_kvhead == 1):
|
| 340 |
-
mdK, mdV = [
|
| 341 |
else:
|
| 342 |
-
|
|
|
|
| 343 |
mdK, mdV = [layout_utils.select(t, accum_transpose) for t in (mdK, mdV)]
|
| 344 |
-
|
|
|
|
| 345 |
mLSE, mdPsum, mdQaccum = [
|
| 346 |
layout_utils.select(t, LSE_dPsum_dQaccum_transpose) for t in (mLSE, mdPsum, mdQaccum)
|
| 347 |
]
|
| 348 |
|
| 349 |
tiled_mma_SdP, tiled_mma_dK, tiled_mma_dV, tiled_mma_dQ = self._get_tiled_mma()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
self.num_mma_threads = tiled_mma_SdP.size
|
| 352 |
assert self.num_mma_threads + 128 == self.num_threads
|
|
@@ -354,10 +426,25 @@ class FlashAttentionBackwardSm90:
|
|
| 354 |
self.num_threads_per_warp_group = 128
|
| 355 |
self.num_producer_threads = 32
|
| 356 |
|
| 357 |
-
self.
|
| 358 |
-
self.
|
| 359 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
|
| 362 |
self._setup_attributes()
|
| 363 |
SharedStorage = self._get_shared_storage_cls()
|
|
@@ -374,7 +461,7 @@ class FlashAttentionBackwardSm90:
|
|
| 374 |
self.tma_copy_bytes["LSE"] = self.tile_m * Float32.width // 8
|
| 375 |
self.tma_copy_bytes["dPsum"] = self.tile_m * Float32.width // 8
|
| 376 |
self.tma_copy_bytes["dQ"] = (
|
| 377 |
-
self.tile_m * self.tile_hdim * Float32.width // 8 // self.
|
| 378 |
)
|
| 379 |
self.tma_copy_bytes["dKacc"] = self.tile_n * self.tile_hdim * Float32.width // 8
|
| 380 |
self.tma_copy_bytes["dVacc"] = self.tile_n * self.tile_hdimv * Float32.width // 8
|
|
@@ -404,38 +491,60 @@ class FlashAttentionBackwardSm90:
|
|
| 404 |
(self.tile_m, self.tile_hdimv),
|
| 405 |
)
|
| 406 |
if const_expr(self.qhead_per_kvhead == 1):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
tma_atom_dK, tma_tensor_dK = cpasync.make_tiled_tma_atom(
|
| 408 |
cpasync.CopyBulkTensorTileS2GOp(),
|
| 409 |
-
|
| 410 |
cute.select(self.sK_layout, mode=[0, 1]),
|
| 411 |
(self.tile_n, self.tile_hdim),
|
| 412 |
)
|
| 413 |
tma_atom_dV, tma_tensor_dV = cpasync.make_tiled_tma_atom(
|
| 414 |
cpasync.CopyBulkTensorTileS2GOp(),
|
| 415 |
-
|
| 416 |
cute.select(self.sV_layout, mode=[0, 1]),
|
| 417 |
(self.tile_n, self.tile_hdimv),
|
| 418 |
)
|
| 419 |
else:
|
| 420 |
tma_atom_dK = tma_atom_dV = tma_tensor_dK = tma_tensor_dV = None
|
| 421 |
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
tile_sched_args = TileSchedulerArguments(
|
| 424 |
cute.ceil_div(cute.size(mK.shape[0]), self.tile_n),
|
| 425 |
cute.size(mQ.shape[2]),
|
| 426 |
-
cute.size(
|
|
|
|
|
|
|
| 427 |
1, # num_splits
|
| 428 |
-
cute.size(
|
| 429 |
-
mQ.shape[1],
|
| 430 |
-
mV.shape[1],
|
| 431 |
-
total_q=cute.size(
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
|
|
|
|
|
|
| 435 |
qhead_per_kvhead_packgqa=1,
|
| 436 |
element_size=self.dtype.width // 8,
|
| 437 |
is_persistent=False,
|
| 438 |
-
lpt=
|
|
|
|
|
|
|
| 439 |
)
|
| 440 |
|
| 441 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
|
@@ -448,7 +557,7 @@ class FlashAttentionBackwardSm90:
|
|
| 448 |
softmax_scale_log2 = LOG2_E
|
| 449 |
|
| 450 |
fastdiv_mods = None
|
| 451 |
-
if const_expr(
|
| 452 |
seqlen_q = cute.size(mQ.shape[0])
|
| 453 |
seqlen_k = cute.size(mK.shape[0])
|
| 454 |
seqlen_q_divmod = FastDivmodDivisor(seqlen_q)
|
|
@@ -461,6 +570,11 @@ class FlashAttentionBackwardSm90:
|
|
| 461 |
|
| 462 |
self.use_block_sparsity = cutlass.const_expr(blocksparse_tensors is not None)
|
| 463 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
self.kernel(
|
| 465 |
tma_tensor_Q,
|
| 466 |
tma_tensor_K,
|
|
@@ -477,6 +591,10 @@ class FlashAttentionBackwardSm90:
|
|
| 477 |
mLSE,
|
| 478 |
mdPsum,
|
| 479 |
mdQaccum,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
self.sQ_layout,
|
| 481 |
self.sK_layout,
|
| 482 |
self.sV_layout,
|
|
@@ -493,15 +611,21 @@ class FlashAttentionBackwardSm90:
|
|
| 493 |
tile_sched_params,
|
| 494 |
TileScheduler,
|
| 495 |
SharedStorage,
|
| 496 |
-
|
| 497 |
fastdiv_mods,
|
| 498 |
blocksparse_tensors,
|
| 499 |
qhead_per_kvhead_divmod,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
).launch(
|
| 501 |
grid=grid_dim,
|
| 502 |
block=[self.num_threads, 1, 1],
|
| 503 |
stream=stream,
|
| 504 |
min_blocks_per_mp=1,
|
|
|
|
| 505 |
)
|
| 506 |
|
| 507 |
@cute.kernel
|
|
@@ -522,6 +646,10 @@ class FlashAttentionBackwardSm90:
|
|
| 522 |
mLSE: cute.Tensor,
|
| 523 |
mdPsum: cute.Tensor,
|
| 524 |
mdQaccum: cute.Tensor,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
sQ_layout: cute.ComposedLayout,
|
| 526 |
sK_layout: cute.ComposedLayout,
|
| 527 |
sV_layout: cute.ComposedLayout,
|
|
@@ -538,19 +666,23 @@ class FlashAttentionBackwardSm90:
|
|
| 538 |
tile_sched_params: ParamsBase,
|
| 539 |
TileScheduler: cutlass.Constexpr[Callable],
|
| 540 |
SharedStorage: cutlass.Constexpr[Callable],
|
| 541 |
-
|
| 542 |
fastdiv_mods=(None, None),
|
| 543 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 544 |
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 545 |
):
|
| 546 |
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 547 |
|
| 548 |
# prefetch TMA descriptors
|
| 549 |
if warp_idx == 0:
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
cpasync.prefetch_descriptor(tma_atom_dO)
|
| 554 |
|
| 555 |
smem = cutlass.utils.SmemAllocator()
|
| 556 |
storage = smem.allocate(SharedStorage)
|
|
@@ -604,25 +736,27 @@ class FlashAttentionBackwardSm90:
|
|
| 604 |
self.is_causal,
|
| 605 |
self.is_local,
|
| 606 |
False, # is_split_kv
|
| 607 |
-
|
| 608 |
-
|
| 609 |
qhead_per_kvhead_packgqa=1,
|
| 610 |
)
|
| 611 |
SeqlenInfoCls = partial(
|
| 612 |
SeqlenInfoQK.create,
|
| 613 |
seqlen_q_static=mQ.shape[0],
|
| 614 |
seqlen_k_static=mK.shape[0],
|
| 615 |
-
mCuSeqlensQ=
|
| 616 |
-
mCuSeqlensK=
|
| 617 |
-
mSeqUsedQ=
|
| 618 |
-
mSeqUsedK=
|
|
|
|
|
|
|
| 619 |
)
|
| 620 |
AttentionMaskCls = partial(
|
| 621 |
AttentionMask,
|
| 622 |
self.tile_m,
|
| 623 |
self.tile_n,
|
| 624 |
-
window_size_left=
|
| 625 |
-
window_size_right=
|
| 626 |
swap_AB=self.SdP_swapAB,
|
| 627 |
)
|
| 628 |
TileSchedulerCls = partial(TileScheduler.create, tile_sched_params)
|
|
@@ -663,18 +797,20 @@ class FlashAttentionBackwardSm90:
|
|
| 663 |
TileSchedulerCls,
|
| 664 |
SeqlenInfoCls,
|
| 665 |
blocksparse_tensors,
|
|
|
|
| 666 |
)
|
| 667 |
else:
|
| 668 |
-
cute.arch.setmaxregister_increase(self.num_mma_regs)
|
| 669 |
tidx, _, _ = cute.arch.thread_idx()
|
| 670 |
tidx = tidx - 128
|
| 671 |
-
|
| 672 |
tiled_mma_SdP,
|
| 673 |
tiled_mma_dK,
|
| 674 |
tiled_mma_dV,
|
| 675 |
tiled_mma_dQ,
|
| 676 |
mdK,
|
| 677 |
mdV,
|
|
|
|
|
|
|
| 678 |
mdQaccum,
|
| 679 |
sQ,
|
| 680 |
sK,
|
|
@@ -697,11 +833,24 @@ class FlashAttentionBackwardSm90:
|
|
| 697 |
SeqlenInfoCls,
|
| 698 |
AttentionMaskCls,
|
| 699 |
TileSchedulerCls,
|
| 700 |
-
|
| 701 |
fastdiv_mods,
|
| 702 |
blocksparse_tensors,
|
| 703 |
qhead_per_kvhead_divmod,
|
| 704 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
|
| 706 |
@cute.jit
|
| 707 |
def load(
|
|
@@ -749,18 +898,22 @@ class FlashAttentionBackwardSm90:
|
|
| 749 |
if const_expr(self.qhead_per_kvhead == 1)
|
| 750 |
else head_idx // qhead_per_kvhead_divmod
|
| 751 |
)
|
| 752 |
-
mK_cur = mK[None, None, head_idx_kv
|
|
|
|
| 753 |
gK = cute.local_tile(mK_cur, (self.tile_n, self.tile_hdim), (n_block, 0))
|
| 754 |
-
mV_cur = mV[None, None, head_idx_kv, batch_idx]
|
| 755 |
gV = cute.local_tile(mV_cur, (self.tile_n, self.tile_hdimv), (n_block, 0))
|
| 756 |
|
| 757 |
-
mQ_cur = mQ[None, None, head_idx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
gQ = cute.local_tile(mQ_cur, (self.tile_m, self.tile_hdim), (None, 0))
|
| 759 |
-
mdO_cur = mdO[None, None, head_idx, batch_idx]
|
| 760 |
gdO = cute.local_tile(mdO_cur, (self.tile_m, self.tile_hdimv), (None, 0))
|
| 761 |
-
mLSE_cur = mLSE[None, head_idx, batch_idx]
|
| 762 |
gLSE = cute.local_tile(mLSE_cur, (self.tile_m,), (None,))
|
| 763 |
-
mdPsum_cur = mdPsum[None, head_idx, batch_idx]
|
| 764 |
gdPsum = cute.local_tile(mdPsum_cur, (self.tile_m,), (None,))
|
| 765 |
|
| 766 |
load_K, _, _ = copy_utils.tma_get_copy_fn(
|
|
@@ -786,14 +939,17 @@ class FlashAttentionBackwardSm90:
|
|
| 786 |
|
| 787 |
if const_expr(not self.use_block_sparsity):
|
| 788 |
total_m_block_cnt = m_block_max - m_block_min
|
| 789 |
-
process_tile =
|
|
|
|
|
|
|
|
|
|
| 790 |
else:
|
| 791 |
total_m_block_cnt = get_total_q_block_count_bwd(
|
| 792 |
blocksparse_tensors,
|
| 793 |
batch_idx,
|
| 794 |
head_idx,
|
| 795 |
n_block,
|
| 796 |
-
|
| 797 |
m_block_max=m_block_max,
|
| 798 |
)
|
| 799 |
process_tile = total_m_block_cnt > Int32(0)
|
|
@@ -806,6 +962,8 @@ class FlashAttentionBackwardSm90:
|
|
| 806 |
)
|
| 807 |
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q))
|
| 808 |
load_Q(first_m_block, producer_state=producer_state_Q)
|
|
|
|
|
|
|
| 809 |
load_LSE(first_m_block, producer_state=producer_state_Q)
|
| 810 |
producer_state_dO_cur = (
|
| 811 |
producer_state_dO
|
|
@@ -836,6 +994,8 @@ class FlashAttentionBackwardSm90:
|
|
| 836 |
producer_state_Q.advance()
|
| 837 |
producer_state_dO.advance()
|
| 838 |
else:
|
|
|
|
|
|
|
| 839 |
producer_state_Q, producer_state_dO = produce_block_sparse_q_loads_bwd_sm90(
|
| 840 |
blocksparse_tensors,
|
| 841 |
batch_idx,
|
|
@@ -854,7 +1014,7 @@ class FlashAttentionBackwardSm90:
|
|
| 854 |
self.tma_copy_bytes["K"],
|
| 855 |
self.tma_copy_bytes["V"],
|
| 856 |
Q_stage_eq_dO_stage=(self.Q_stage == self.dO_stage),
|
| 857 |
-
|
| 858 |
m_block_max=m_block_max,
|
| 859 |
)
|
| 860 |
|
|
@@ -866,14 +1026,14 @@ class FlashAttentionBackwardSm90:
|
|
| 866 |
def apply_score_mod(
|
| 867 |
self,
|
| 868 |
acc_S: cute.Tensor,
|
| 869 |
-
thr_mma_SdP: cute.
|
| 870 |
batch_idx,
|
| 871 |
head_idx,
|
| 872 |
m_block,
|
| 873 |
n_block,
|
| 874 |
softmax_scale,
|
| 875 |
seqlen_info: SeqlenInfoQK,
|
| 876 |
-
|
| 877 |
fastdiv_mods=(None, None),
|
| 878 |
):
|
| 879 |
# [NOTE] SdP_swapAB: swapAB transposes the tile, so use (n, m) indexing
|
|
@@ -897,7 +1057,7 @@ class FlashAttentionBackwardSm90:
|
|
| 897 |
softmax_scale,
|
| 898 |
self.vec_size,
|
| 899 |
self.qk_acc_dtype,
|
| 900 |
-
|
| 901 |
fastdiv_mods,
|
| 902 |
seqlen_info,
|
| 903 |
constant_q_idx=None,
|
|
@@ -910,14 +1070,14 @@ class FlashAttentionBackwardSm90:
|
|
| 910 |
self,
|
| 911 |
grad_tensor: cute.Tensor,
|
| 912 |
score_tensor: cute.Tensor,
|
| 913 |
-
thr_mma_SdP: cute.
|
| 914 |
batch_idx,
|
| 915 |
head_idx,
|
| 916 |
m_block,
|
| 917 |
n_block,
|
| 918 |
softmax_scale,
|
| 919 |
seqlen_info: SeqlenInfoQK,
|
| 920 |
-
|
| 921 |
fastdiv_mods=(None, None),
|
| 922 |
):
|
| 923 |
cS = cute.make_identity_tensor(
|
|
@@ -941,7 +1101,7 @@ class FlashAttentionBackwardSm90:
|
|
| 941 |
softmax_scale,
|
| 942 |
self.vec_size,
|
| 943 |
self.qk_acc_dtype,
|
| 944 |
-
|
| 945 |
fastdiv_mods,
|
| 946 |
seqlen_info,
|
| 947 |
constant_q_idx=None,
|
|
@@ -958,6 +1118,8 @@ class FlashAttentionBackwardSm90:
|
|
| 958 |
tiled_mma_dQ: cute.TiledMma,
|
| 959 |
mdK: cute.Tensor,
|
| 960 |
mdV: cute.Tensor,
|
|
|
|
|
|
|
| 961 |
mdQaccum: cute.Tensor,
|
| 962 |
sQ: cute.Tensor,
|
| 963 |
sK: cute.Tensor,
|
|
@@ -980,20 +1142,24 @@ class FlashAttentionBackwardSm90:
|
|
| 980 |
SeqlenInfoCls: Callable,
|
| 981 |
AttentionMaskCls: Callable,
|
| 982 |
TileSchedulerCls: Callable,
|
| 983 |
-
|
| 984 |
fastdiv_mods=(None, None),
|
| 985 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 986 |
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
|
|
|
| 987 |
):
|
| 988 |
warp_group_idx = cute.arch.make_warp_uniform(tidx // self.num_threads_per_warp_group)
|
| 989 |
warp_group_thread_layout = cute.make_layout(
|
| 990 |
-
self.
|
| 991 |
)
|
| 992 |
thr_mma_SdP = tiled_mma_SdP.get_slice(tidx)
|
| 993 |
wg_mma_SdP = tiled_mma_SdP.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 994 |
wg_mma_dK = tiled_mma_dK.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 995 |
wg_mma_dV = tiled_mma_dV.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 996 |
-
wg_mma_dQ =
|
|
|
|
|
|
|
|
|
|
| 997 |
# S = Q @ K.T
|
| 998 |
shape_mnk_S = (self.tile_m, self.tile_n, self.tile_hdim)
|
| 999 |
_, tSrQ, tSrK = sm90_utils.partition_fragment_ABC(
|
|
@@ -1039,23 +1205,41 @@ class FlashAttentionBackwardSm90:
|
|
| 1039 |
# dQ = dS @ K
|
| 1040 |
sKt = layout_utils.transpose_view(sK)
|
| 1041 |
shape_mnk_dQ = (self.tile_m, self.tile_hdim, self.tile_n)
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1048 |
|
| 1049 |
-
# Smem copy atom tiling
|
| 1050 |
copy_P_r2s = None
|
|
|
|
| 1051 |
if const_expr(sP is not None):
|
| 1052 |
sP_cpy = sP if const_expr(not self.SdP_swapAB) else sPt
|
| 1053 |
copy_P_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1054 |
-
tiled_mma_SdP,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1055 |
)
|
| 1056 |
sdS_cpy = sdS if const_expr(not self.SdP_swapAB) else sdSt
|
| 1057 |
copy_dS_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1058 |
-
tiled_mma_SdP,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1059 |
)
|
| 1060 |
|
| 1061 |
tLSEsLSE = layout_utils.mma_partition_C_vec(
|
|
@@ -1064,27 +1248,25 @@ class FlashAttentionBackwardSm90:
|
|
| 1064 |
tLSEsdPsum = layout_utils.mma_partition_C_vec(
|
| 1065 |
sdPsum, thr_mma_SdP, expand_shape=self.tile_n, is_colvec=not self.SdP_swapAB
|
| 1066 |
)
|
| 1067 |
-
|
| 1068 |
-
|
| 1069 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1070 |
|
| 1071 |
PdS_barrier = cutlass.pipeline.NamedBarrier(
|
| 1072 |
barrier_id=int(NamedBarrierBwd.PdS), num_threads=self.num_mma_threads
|
| 1073 |
)
|
| 1074 |
-
score_mod_fn = partial(
|
| 1075 |
-
self.apply_score_mod,
|
| 1076 |
-
thr_mma_SdP=thr_mma_SdP,
|
| 1077 |
-
softmax_scale=softmax_scale,
|
| 1078 |
-
aux_tensors=aux_tensors,
|
| 1079 |
-
fastdiv_mods=fastdiv_mods,
|
| 1080 |
-
)
|
| 1081 |
-
score_mod_bwd_fn = partial(
|
| 1082 |
-
self.apply_score_mod_bwd,
|
| 1083 |
-
thr_mma_SdP=thr_mma_SdP,
|
| 1084 |
-
softmax_scale=softmax_scale,
|
| 1085 |
-
aux_tensors=aux_tensors,
|
| 1086 |
-
fastdiv_mods=fastdiv_mods,
|
| 1087 |
-
)
|
| 1088 |
|
| 1089 |
mma_one_m_block_all = partial(
|
| 1090 |
self.mma_one_m_block,
|
|
@@ -1105,6 +1287,7 @@ class FlashAttentionBackwardSm90:
|
|
| 1105 |
PdS_barrier=PdS_barrier,
|
| 1106 |
# acc_dV=acc_dV,
|
| 1107 |
# acc_dK=acc_dK,
|
|
|
|
| 1108 |
)
|
| 1109 |
|
| 1110 |
consumer_state_Q = cutlass.pipeline.make_pipeline_state(
|
|
@@ -1118,7 +1301,40 @@ class FlashAttentionBackwardSm90:
|
|
| 1118 |
while work_tile.is_valid_tile:
|
| 1119 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1120 |
seqlen = SeqlenInfoCls(batch_idx)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1121 |
mask = AttentionMaskCls(seqlen)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1122 |
score_mod_fn_cur = partial(
|
| 1123 |
score_mod_fn,
|
| 1124 |
batch_idx=batch_idx,
|
|
@@ -1136,14 +1352,17 @@ class FlashAttentionBackwardSm90:
|
|
| 1136 |
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 1137 |
|
| 1138 |
if const_expr(not self.use_block_sparsity):
|
| 1139 |
-
process_tile =
|
|
|
|
|
|
|
|
|
|
| 1140 |
else:
|
| 1141 |
total_m_block_cnt = get_total_q_block_count_bwd(
|
| 1142 |
blocksparse_tensors,
|
| 1143 |
batch_idx,
|
| 1144 |
head_idx,
|
| 1145 |
n_block,
|
| 1146 |
-
|
| 1147 |
m_block_max=m_block_max,
|
| 1148 |
)
|
| 1149 |
process_tile = total_m_block_cnt > Int32(0)
|
|
@@ -1160,7 +1379,7 @@ class FlashAttentionBackwardSm90:
|
|
| 1160 |
mask_causal=self.is_causal,
|
| 1161 |
mask_local=self.is_local,
|
| 1162 |
mask_mod=self.mask_mod,
|
| 1163 |
-
|
| 1164 |
fastdiv_mods=fastdiv_mods,
|
| 1165 |
)
|
| 1166 |
dKV_accumulate = False
|
|
@@ -1191,9 +1410,9 @@ class FlashAttentionBackwardSm90:
|
|
| 1191 |
thr_mma_SdP=thr_mma_SdP,
|
| 1192 |
score_mod_fn=score_mod_fn_cur,
|
| 1193 |
score_mod_bwd_fn=score_mod_bwd_fn_cur,
|
| 1194 |
-
|
| 1195 |
m_block_max=m_block_max,
|
| 1196 |
-
|
| 1197 |
fastdiv_mods=fastdiv_mods,
|
| 1198 |
)
|
| 1199 |
|
|
@@ -1216,10 +1435,12 @@ class FlashAttentionBackwardSm90:
|
|
| 1216 |
head_idx,
|
| 1217 |
batch_idx,
|
| 1218 |
qhead_per_kvhead_divmod,
|
|
|
|
|
|
|
| 1219 |
)
|
| 1220 |
else:
|
| 1221 |
-
#
|
| 1222 |
-
if const_expr(self.use_block_sparsity):
|
| 1223 |
acc_dK.fill(0.0)
|
| 1224 |
acc_dV.fill(0.0)
|
| 1225 |
self.epilogue_dKV(
|
|
@@ -1239,6 +1460,8 @@ class FlashAttentionBackwardSm90:
|
|
| 1239 |
head_idx,
|
| 1240 |
batch_idx,
|
| 1241 |
qhead_per_kvhead_divmod,
|
|
|
|
|
|
|
| 1242 |
)
|
| 1243 |
|
| 1244 |
tile_scheduler.advance_to_next_work()
|
|
@@ -1248,6 +1471,22 @@ class FlashAttentionBackwardSm90:
|
|
| 1248 |
if warp_idx == 4:
|
| 1249 |
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
| 1250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1251 |
@cute.jit
|
| 1252 |
def mma_one_m_block(
|
| 1253 |
self,
|
|
@@ -1266,16 +1505,17 @@ class FlashAttentionBackwardSm90:
|
|
| 1266 |
pipeline_dO: cutlass.pipeline.PipelineAsync,
|
| 1267 |
tLSEsLSE: cute.Tensor,
|
| 1268 |
tLSEsdPsum: cute.Tensor,
|
| 1269 |
-
tdQsdQaccum: cute.Tensor,
|
| 1270 |
softmax_scale_log2: Float32,
|
| 1271 |
PdS_barrier: cutlass.pipeline.NamedBarrier,
|
|
|
|
| 1272 |
mask_fn: Optional[Callable] = None,
|
| 1273 |
score_mod_fn: Optional[Callable] = None,
|
| 1274 |
score_mod_bwd_fn: Optional[Callable] = None,
|
| 1275 |
dKV_accumulate: Boolean = True,
|
| 1276 |
):
|
| 1277 |
consumer_state_dO_cur = (
|
| 1278 |
-
|
| 1279 |
)
|
| 1280 |
smem_idx_Q = consumer_state_Q.index
|
| 1281 |
smem_idx_dO = consumer_state_dO_cur.index if const_expr(self.dO_stage > 1) else 0
|
|
@@ -1283,6 +1523,7 @@ class FlashAttentionBackwardSm90:
|
|
| 1283 |
# (1) [GEMM 1] S = Q @ K^T
|
| 1284 |
pipeline_Q.consumer_wait(consumer_state_Q, pipeline_Q.consumer_try_wait(consumer_state_Q))
|
| 1285 |
acc_S = mma_qk_fn(A_idx=smem_idx_Q, wg_wait=-1)
|
|
|
|
| 1286 |
tLSErLSE = copy_utils.load_s2r(tLSEsLSE[None, smem_idx_Q])
|
| 1287 |
# (2) [GEMM 2] dP = dO @ V.T
|
| 1288 |
pipeline_dO.consumer_wait(
|
|
@@ -1301,10 +1542,12 @@ class FlashAttentionBackwardSm90:
|
|
| 1301 |
if cutlass.const_expr(mask_fn is not None):
|
| 1302 |
mask_fn(acc_S, m_block=m_block)
|
| 1303 |
acc_S_mn = layout_utils.reshape_acc_to_mn(acc_S, transpose=self.SdP_swapAB)
|
|
|
|
| 1304 |
for r in cutlass.range_constexpr(cute.size(acc_S_mn, mode=[0])):
|
|
|
|
| 1305 |
for c in cutlass.range(cute.size(acc_S_mn, mode=[1]), unroll_full=True):
|
| 1306 |
acc_S_mn[r, c] = cute.math.exp2(
|
| 1307 |
-
acc_S_mn[r, c] * softmax_scale_log2 -
|
| 1308 |
)
|
| 1309 |
tLSErdPsum = copy_utils.load_s2r(tLSEsdPsum[None, smem_idx_dO])
|
| 1310 |
|
|
@@ -1321,8 +1564,9 @@ class FlashAttentionBackwardSm90:
|
|
| 1321 |
warpgroup.wait_group(0)
|
| 1322 |
acc_dP_mn = layout_utils.reshape_acc_to_mn(acc_dP, transpose=self.SdP_swapAB)
|
| 1323 |
for r in cutlass.range_constexpr(cute.size(acc_dP_mn, mode=[0])):
|
|
|
|
| 1324 |
for c in cutlass.range(cute.size(acc_dP_mn, mode=[1]), unroll_full=True):
|
| 1325 |
-
acc_dP_mn[r, c] = acc_S_mn[r, c] * (acc_dP_mn[r, c] -
|
| 1326 |
|
| 1327 |
if const_expr(self.score_mod_bwd is not None):
|
| 1328 |
score_mod_bwd_fn(acc_dP, acc_S_pre, m_block=m_block)
|
|
@@ -1354,36 +1598,50 @@ class FlashAttentionBackwardSm90:
|
|
| 1354 |
# smem fence to make sure sdS is written before it's read by WGMMA
|
| 1355 |
cute.arch.fence_view_async_shared()
|
| 1356 |
PdS_barrier.arrive_and_wait()
|
| 1357 |
-
# (6) [GEMM 4] dQ = dS @ K
|
| 1358 |
-
acc_dQ = mma_dsk_fn(A_idx=smem_idx_PdS, wg_wait=1)
|
| 1359 |
-
# if cute.arch.thread_idx()[0] == 128: cute.print_tensor(acc_dV)
|
| 1360 |
-
pipeline_dO.consumer_release(consumer_state_dO_cur) # release dO as dV mma is done
|
| 1361 |
|
| 1362 |
-
|
| 1363 |
-
|
| 1364 |
-
|
| 1365 |
-
|
| 1366 |
-
)
|
| 1367 |
-
else:
|
| 1368 |
-
mma_dsq_fn(tCrA=tdKrdS, B_idx=smem_idx_Q, zero_init=not dKV_accumulate, wg_wait=1)
|
| 1369 |
-
# if cute.arch.thread_idx()[0] == 128: cute.print_tensor(acc_dQ)
|
| 1370 |
|
| 1371 |
-
|
| 1372 |
-
|
| 1373 |
-
|
| 1374 |
-
|
| 1375 |
-
|
| 1376 |
-
|
| 1377 |
-
|
| 1378 |
-
cute.arch.barrier_arrive(
|
| 1379 |
-
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1380 |
-
number_of_threads=self.num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1381 |
-
)
|
| 1382 |
|
| 1383 |
-
|
| 1384 |
-
|
| 1385 |
-
|
| 1386 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1387 |
|
| 1388 |
consumer_state_Q.advance()
|
| 1389 |
consumer_state_dO.advance()
|
|
@@ -1408,6 +1666,8 @@ class FlashAttentionBackwardSm90:
|
|
| 1408 |
head_idx: Int32,
|
| 1409 |
batch_idx: Int32,
|
| 1410 |
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
|
|
|
|
|
|
| 1411 |
):
|
| 1412 |
epi_barrier = cutlass.pipeline.NamedBarrier(
|
| 1413 |
barrier_id=int(NamedBarrierBwd.Epilogue), num_threads=self.num_mma_threads
|
|
@@ -1415,8 +1675,12 @@ class FlashAttentionBackwardSm90:
|
|
| 1415 |
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 1416 |
|
| 1417 |
if const_expr(self.qhead_per_kvhead == 1):
|
| 1418 |
-
|
| 1419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1420 |
gdK = cute.local_tile(mdK_cur, (self.tile_n, self.tile_hdim), (n_block, 0))
|
| 1421 |
gdV = cute.local_tile(mdV_cur, (self.tile_n, self.tile_hdimv), (n_block, 0))
|
| 1422 |
store_dK, _, _ = copy_utils.tma_get_copy_fn(
|
|
@@ -1428,10 +1692,18 @@ class FlashAttentionBackwardSm90:
|
|
| 1428 |
sdV = sV if const_expr(not self.dKV_swapAB) else layout_utils.transpose_view(sV)
|
| 1429 |
sdK = sK if const_expr(not self.dKV_swapAB) else layout_utils.transpose_view(sK)
|
| 1430 |
copy_dV_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1431 |
-
tiled_mma_dV,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1432 |
)
|
| 1433 |
copy_dK_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1434 |
-
tiled_mma_dK,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1435 |
)
|
| 1436 |
cute.arch.cp_async_bulk_wait_group(1, read=True)
|
| 1437 |
epi_barrier.arrive_and_wait()
|
|
@@ -1450,15 +1722,26 @@ class FlashAttentionBackwardSm90:
|
|
| 1450 |
store_dK()
|
| 1451 |
cute.arch.cp_async_bulk_commit_group()
|
| 1452 |
else:
|
| 1453 |
-
|
| 1454 |
-
|
| 1455 |
-
|
| 1456 |
-
|
|
|
|
| 1457 |
head_idx_kv = head_idx // qhead_per_kvhead_divmod
|
| 1458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1459 |
gdKaccum_ = cute.local_tile(mdKaccum_cur, (self.tile_n * self.tile_hdim,), (n_block,))
|
| 1460 |
gdKaccum = cute.flat_divide(gdKaccum_, (sdKaccum_shape0,))
|
| 1461 |
-
mdVaccum_cur = mdV[None, head_idx_kv, batch_idx]
|
| 1462 |
gdVaccum_ = cute.local_tile(mdVaccum_cur, (self.tile_n * self.tile_hdimv,), (n_block,))
|
| 1463 |
gdVaccum = cute.flat_divide(gdVaccum_, (sdVaccum_shape0,))
|
| 1464 |
# These two overlap each other
|
|
@@ -1467,14 +1750,17 @@ class FlashAttentionBackwardSm90:
|
|
| 1467 |
sdVaccum = cute.make_tensor(sVaccum_ptr, sdVaccum_layout)
|
| 1468 |
tiled_copy_dKVaccum_r2s = cute.make_tiled_copy_tv(
|
| 1469 |
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 1470 |
-
cute.make_layout((self.num_threads_per_warp_group, self.
|
| 1471 |
cute.make_layout(128 // Float32.width),
|
| 1472 |
)
|
| 1473 |
thr_copy_dKVaccum_r2s = tiled_copy_dKVaccum_r2s.get_slice(tidx)
|
| 1474 |
tdKsdKaccum = thr_copy_dKVaccum_r2s.partition_D(sdKaccum)
|
| 1475 |
tdVsdVaccum = thr_copy_dKVaccum_r2s.partition_D(sdVaccum)
|
| 1476 |
|
| 1477 |
-
|
|
|
|
|
|
|
|
|
|
| 1478 |
epi_barrier.arrive_and_wait()
|
| 1479 |
tdKrdKaccum_flat = cute.make_tensor(acc_dK.iterator, tdKsdKaccum.shape)
|
| 1480 |
cute.autovec_copy(tdKrdKaccum_flat, tdKsdKaccum)
|
|
@@ -1482,15 +1768,18 @@ class FlashAttentionBackwardSm90:
|
|
| 1482 |
epi_barrier.arrive_and_wait()
|
| 1483 |
if warp_idx == 4:
|
| 1484 |
with cute.arch.elect_one():
|
| 1485 |
-
for wg_idx in cutlass.range_constexpr(self.
|
| 1486 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1487 |
sdKaccum[None, wg_idx].iterator,
|
| 1488 |
gdKaccum[None, wg_idx].iterator,
|
| 1489 |
-
self.tma_copy_bytes["dKacc"] // self.
|
| 1490 |
)
|
| 1491 |
cute.arch.cp_async_bulk_commit_group()
|
| 1492 |
|
| 1493 |
-
cute.arch.cp_async_bulk_wait_group(0, read=
|
|
|
|
|
|
|
|
|
|
| 1494 |
epi_barrier.arrive_and_wait()
|
| 1495 |
tdVrdVaccum_flat = cute.make_tensor(acc_dV.iterator, tdVsdVaccum.shape)
|
| 1496 |
cute.autovec_copy(tdVrdVaccum_flat, tdVsdVaccum)
|
|
@@ -1498,13 +1787,16 @@ class FlashAttentionBackwardSm90:
|
|
| 1498 |
epi_barrier.arrive_and_wait()
|
| 1499 |
if warp_idx == 4:
|
| 1500 |
with cute.arch.elect_one():
|
| 1501 |
-
for wg_idx in cutlass.range_constexpr(self.
|
| 1502 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1503 |
sdVaccum[None, wg_idx].iterator,
|
| 1504 |
gdVaccum[None, wg_idx].iterator,
|
| 1505 |
-
self.tma_copy_bytes["dVacc"] // self.
|
| 1506 |
)
|
| 1507 |
cute.arch.cp_async_bulk_commit_group()
|
|
|
|
|
|
|
|
|
|
| 1508 |
|
| 1509 |
@cute.jit
|
| 1510 |
def dQaccum_store(
|
|
@@ -1515,21 +1807,45 @@ class FlashAttentionBackwardSm90:
|
|
| 1515 |
TileSchedulerCls: cutlass.Constexpr[Callable],
|
| 1516 |
SeqlenInfoCls: cutlass.Constexpr[Callable],
|
| 1517 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
|
|
|
| 1518 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1519 |
tile_scheduler = TileSchedulerCls()
|
| 1520 |
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1521 |
while work_tile.is_valid_tile:
|
| 1522 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1523 |
seqlen = SeqlenInfoCls(batch_idx)
|
| 1524 |
-
|
| 1525 |
-
|
| 1526 |
-
|
| 1527 |
-
|
| 1528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1529 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1530 |
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 1531 |
if const_expr(not self.use_block_sparsity):
|
| 1532 |
-
process_tile =
|
|
|
|
|
|
|
|
|
|
| 1533 |
loop_count = m_block_max - m_block_min
|
| 1534 |
else:
|
| 1535 |
total_block_cnt = get_total_q_block_count_bwd(
|
|
@@ -1537,7 +1853,7 @@ class FlashAttentionBackwardSm90:
|
|
| 1537 |
batch_idx,
|
| 1538 |
head_idx,
|
| 1539 |
n_block,
|
| 1540 |
-
|
| 1541 |
m_block_max=m_block_max,
|
| 1542 |
)
|
| 1543 |
process_tile = total_block_cnt > Int32(0)
|
|
@@ -1548,17 +1864,36 @@ class FlashAttentionBackwardSm90:
|
|
| 1548 |
m_block = m_block_min + iter_idx
|
| 1549 |
m_block_safe = m_block
|
| 1550 |
|
| 1551 |
-
|
| 1552 |
-
|
| 1553 |
-
|
| 1554 |
-
|
|
|
|
|
|
|
|
|
|
| 1555 |
cute.arch.barrier_arrive(
|
| 1556 |
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1557 |
number_of_threads=self.num_threads_per_warp_group
|
| 1558 |
+ cute.arch.WARP_SIZE,
|
| 1559 |
)
|
| 1560 |
|
| 1561 |
-
for
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1562 |
cute.arch.barrier(
|
| 1563 |
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1564 |
number_of_threads=self.num_threads_per_warp_group
|
|
@@ -1567,11 +1902,24 @@ class FlashAttentionBackwardSm90:
|
|
| 1567 |
with cute.arch.elect_one():
|
| 1568 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1569 |
sdQaccum[None, warp_group_idx].iterator,
|
| 1570 |
-
gdQaccum[None, warp_group_idx, m_block_safe].iterator,
|
| 1571 |
self.tma_copy_bytes["dQ"],
|
| 1572 |
)
|
| 1573 |
cute.arch.cp_async_bulk_commit_group()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1574 |
else:
|
|
|
|
|
|
|
|
|
|
| 1575 |
dQaccum_store_block_sparse_bwd_sm90(
|
| 1576 |
blocksparse_tensors,
|
| 1577 |
batch_idx,
|
|
@@ -1579,13 +1927,29 @@ class FlashAttentionBackwardSm90:
|
|
| 1579 |
n_block,
|
| 1580 |
sdQaccum,
|
| 1581 |
gdQaccum,
|
| 1582 |
-
|
| 1583 |
m_block_max=m_block_max,
|
| 1584 |
-
|
| 1585 |
num_threads_per_warp_group=self.num_threads_per_warp_group,
|
| 1586 |
tma_copy_bytes_dQ=self.tma_copy_bytes["dQ"],
|
| 1587 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1588 |
tile_scheduler.advance_to_next_work()
|
| 1589 |
work_tile = tile_scheduler.get_current_work()
|
| 1590 |
|
| 1591 |
-
|
|
|
|
|
|
| 24 |
from .block_info import BlockInfo
|
| 25 |
from . import pipeline
|
| 26 |
from .quack.cute_dsl_utils import ParamsBase
|
| 27 |
+
from .tile_scheduler import (
|
| 28 |
+
TileSchedulerArguments,
|
| 29 |
+
SingleTileScheduler,
|
| 30 |
+
SingleTileLPTBwdScheduler,
|
| 31 |
+
SingleTileVarlenScheduler,
|
| 32 |
+
)
|
| 33 |
+
from . import barrier
|
| 34 |
from .named_barrier import NamedBarrierBwd
|
| 35 |
from .softmax import apply_score_mod_inner, apply_score_mod_bwd_inner
|
| 36 |
from .block_sparsity import BlockSparseTensors
|
| 37 |
+
from .utils import AuxData
|
| 38 |
from .block_sparse_utils import (
|
| 39 |
get_total_q_block_count_bwd,
|
| 40 |
produce_block_sparse_q_loads_bwd_sm90,
|
|
|
|
| 53 |
head_dim_v: Optional[int] = None,
|
| 54 |
qhead_per_kvhead: int = 1,
|
| 55 |
is_causal: bool = False,
|
| 56 |
+
is_local: bool = False,
|
| 57 |
+
deterministic: bool = False,
|
| 58 |
tile_m: int = 64,
|
| 59 |
tile_n: int = 128,
|
| 60 |
Q_stage: int = 2,
|
|
|
|
| 72 |
score_mod_bwd: cutlass.Constexpr | None = None,
|
| 73 |
mask_mod: cutlass.Constexpr | None = None,
|
| 74 |
has_aux_tensors: cutlass.Constexpr = False,
|
| 75 |
+
q_subtile_factor: cutlass.Constexpr[int] = 1,
|
| 76 |
+
dQ_single_wg: bool = False,
|
| 77 |
):
|
| 78 |
self.dtype = dtype
|
| 79 |
+
# Swapped dK/dV accumulators put head_dim on WGMMA's 64-row M axis.
|
| 80 |
+
hdim_multiple_of = 64 if dKV_swapAB else 16
|
| 81 |
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 82 |
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
| 83 |
self.same_hdim_kv = head_dim == head_dim_v
|
|
|
|
| 87 |
self.check_hdim_v_oob = head_dim_v != self.tile_hdimv
|
| 88 |
self.qhead_per_kvhead = qhead_per_kvhead
|
| 89 |
self.is_causal = is_causal
|
| 90 |
+
self.is_local = is_local
|
| 91 |
+
self.deterministic = deterministic
|
| 92 |
self.tile_m = tile_m
|
| 93 |
self.tile_n = tile_n
|
| 94 |
self.num_threads = num_threads
|
|
|
|
| 103 |
self.AtomLayoutMSdP = AtomLayoutMSdP
|
| 104 |
self.AtomLayoutNdKV = AtomLayoutNdKV
|
| 105 |
self.AtomLayoutMdQ = AtomLayoutMdQ
|
| 106 |
+
self.num_wg_mma = (self.num_threads // 128) - 1
|
| 107 |
self.mma_dkv_is_rs = (
|
| 108 |
AtomLayoutMSdP == 1
|
| 109 |
+
and AtomLayoutNdKV == self.num_wg_mma
|
| 110 |
and SdP_swapAB
|
| 111 |
and not dKV_swapAB
|
| 112 |
)
|
| 113 |
self.V_in_regs = V_in_regs
|
| 114 |
+
# May be overridden in __call__ for varlen inputs.
|
| 115 |
if qhead_per_kvhead > 1:
|
| 116 |
assert self.same_hdim_kv, "GQA backward requires head_dim == head_dim_v"
|
| 117 |
+
assert self.num_wg_mma == 2, "GQA backward assumes 2 warp groups"
|
| 118 |
# These are tuned for speed
|
| 119 |
# Do we keep the LSE and dPsum in each thread, or split them across 8 threads that share
|
| 120 |
# them and then shuffle to get the value whenever we need? This can reduce register
|
| 121 |
# pressure when SdP_swapAB, where each thread needs to keep statistics for (kBlockM / 4)
|
| 122 |
# rows. If !SdP_swapAB, each thread only needs to keep statistics for 2 rows.
|
|
|
|
| 123 |
self.shuffle_LSE = self.SdP_swapAB and self.tile_hdim <= 64
|
| 124 |
self.shuffle_dPsum = self.SdP_swapAB and self.tile_hdim <= 64
|
| 125 |
|
|
|
|
| 129 |
self.score_mod_bwd = score_mod_bwd
|
| 130 |
self.mask_mod = mask_mod
|
| 131 |
self.has_aux_tensors = has_aux_tensors
|
| 132 |
+
self.q_subtile_factor = q_subtile_factor
|
| 133 |
if cutlass.const_expr(has_aux_tensors):
|
| 134 |
self.vec_size: cutlass.Constexpr = 1
|
| 135 |
else:
|
| 136 |
self.vec_size: cutlass.Constexpr = 4
|
| 137 |
self.qk_acc_dtype = Float32
|
| 138 |
+
# dQ_single_wg: WG0 computes the full dQ GEMM, WG1 skips it.
|
| 139 |
+
# Only valid for 2 MMA warp groups.
|
| 140 |
+
# Credit: Ben Spector
|
| 141 |
+
if dQ_single_wg:
|
| 142 |
+
assert self.num_wg_mma == 2, "dQ_single_wg only supports 2 warp groups"
|
| 143 |
+
self.num_wg_dQ = 1 if dQ_single_wg else self.num_wg_mma
|
| 144 |
|
| 145 |
@staticmethod
|
| 146 |
def can_implement(
|
|
|
|
| 199 |
assert mQ_type == self.dtype
|
| 200 |
|
| 201 |
def _setup_attributes(self):
|
| 202 |
+
# We need to accommodate both Q and Q^T (and dO and dO^T) in shared memory.
|
| 203 |
+
# Q & dO are used in the SdP Mma and Q^T and dO^T are used in the dKV Mma.
|
| 204 |
+
# The M dimension (tile_m) doesn't matter for the layout, only the K dimension
|
| 205 |
+
wg_d_dKV = self.num_wg_mma // self.AtomLayoutNdKV
|
| 206 |
+
self.sQ_layout, self.sdO_layout = [
|
| 207 |
+
# Need to set major_mode_size (mms) to accommodate Q and Q.T
|
| 208 |
+
sm90_utils.make_smem_layout(
|
| 209 |
+
self.dtype,
|
| 210 |
+
LayoutEnum.ROW_MAJOR,
|
| 211 |
+
shape,
|
| 212 |
+
stage,
|
| 213 |
+
major_mode_size=mms,
|
| 214 |
+
)
|
| 215 |
+
for shape, stage, mms in [
|
| 216 |
+
((self.tile_m, self.tile_hdim), self.Q_stage, self.tile_hdim // wg_d_dKV),
|
| 217 |
+
((self.tile_m, self.tile_hdimv), self.dO_stage, self.tile_hdim // wg_d_dKV),
|
| 218 |
]
|
| 219 |
]
|
| 220 |
+
wg_d_dQ = self.num_wg_dQ // self.AtomLayoutMdQ
|
| 221 |
+
# Accomodate both K and K.T
|
| 222 |
+
self.sK_layout = sm90_utils.make_smem_layout(
|
| 223 |
+
self.dtype,
|
| 224 |
+
LayoutEnum.ROW_MAJOR,
|
| 225 |
+
(self.tile_n, self.tile_hdim),
|
| 226 |
+
stage=None,
|
| 227 |
+
major_mode_size=self.tile_hdim // wg_d_dQ,
|
| 228 |
+
)
|
| 229 |
+
# There's only V, no V.T, so layout is normal
|
| 230 |
+
self.sV_layout = sm90_utils.make_smem_layout(
|
| 231 |
+
self.dtype, LayoutEnum.ROW_MAJOR, (self.tile_n, self.tile_hdimv), None
|
| 232 |
+
)
|
| 233 |
+
# Accomodate both S and S.T
|
| 234 |
+
wg_n_SdP = self.num_wg_mma // self.AtomLayoutMSdP
|
| 235 |
+
wg_n_dKV = self.AtomLayoutNdKV
|
| 236 |
+
self.sPdS_layout = sm90_utils.make_smem_layout(
|
| 237 |
+
self.dtype,
|
| 238 |
+
LayoutEnum.ROW_MAJOR,
|
| 239 |
+
(self.tile_m, self.tile_n),
|
| 240 |
+
stage=self.PdS_stage,
|
| 241 |
+
major_mode_size=math.gcd(self.tile_n // wg_n_SdP, self.tile_n // wg_n_dKV),
|
| 242 |
+
)
|
| 243 |
self.sdQaccum_layout = cute.make_layout(
|
| 244 |
+
(self.tile_m * self.tile_hdim // self.num_wg_dQ, self.num_wg_dQ)
|
| 245 |
)
|
| 246 |
# dQaccum R->S
|
| 247 |
self.r2s_tiled_copy_dQaccum = cute.make_tiled_copy_tv(
|
| 248 |
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 249 |
# thr_layout
|
| 250 |
+
cute.make_layout((self.num_threads_per_warp_group, self.num_wg_dQ)),
|
| 251 |
cute.make_layout(128 // Float32.width), # val_layout
|
| 252 |
)
|
| 253 |
# dKVaccum for GQA epilogue - reuses sV+sK memory recast as f32
|
| 254 |
# TODO: assert that sVaccum and sKaccum don't overflow smem
|
| 255 |
|
| 256 |
def _get_tiled_mma(self):
|
| 257 |
+
maybe_swap_mn = lambda shape, swap: (shape[1], shape[0], *shape[2:]) if swap else shape
|
| 258 |
# S = Q @ K.T, dP = dO @ V.T
|
| 259 |
+
atom_layout_SdP = (self.AtomLayoutMSdP, self.num_wg_mma // self.AtomLayoutMSdP, 1)
|
| 260 |
tiler_mn_SdP = (self.tile_m // atom_layout_SdP[0], self.tile_n // atom_layout_SdP[1])
|
| 261 |
tiled_mma_SdP = sm90_utils_basic.make_trivial_tiled_mma(
|
| 262 |
self.dtype,
|
|
|
|
| 264 |
warpgroup.OperandMajorMode.K,
|
| 265 |
warpgroup.OperandMajorMode.K,
|
| 266 |
Float32,
|
| 267 |
+
atom_layout_mnk=maybe_swap_mn(atom_layout_SdP, self.SdP_swapAB),
|
| 268 |
+
tiler_mn=(64, tiler_mn_SdP[1] if not self.SdP_swapAB else tiler_mn_SdP[0]),
|
|
|
|
| 269 |
)
|
| 270 |
# dV = P.T @ dO, dK = dS.T @ Q
|
| 271 |
+
atom_layout_dKV = (self.AtomLayoutNdKV, self.num_wg_mma // self.AtomLayoutNdKV, 1)
|
| 272 |
tiler_mn_dK = (self.tile_n // atom_layout_dKV[0], self.tile_hdim // atom_layout_dKV[1])
|
| 273 |
tiler_mn_dV = (self.tile_n // atom_layout_dKV[0], self.tile_hdimv // atom_layout_dKV[1])
|
| 274 |
tiled_mma_dK, tiled_mma_dV = [
|
|
|
|
| 280 |
else warpgroup.OperandMajorMode.K,
|
| 281 |
warpgroup.OperandMajorMode.MN,
|
| 282 |
Float32,
|
| 283 |
+
atom_layout_mnk=maybe_swap_mn(atom_layout_dKV, self.dKV_swapAB),
|
| 284 |
+
tiler_mn=(64, tiler_mn_d[1] if not self.dKV_swapAB else tiler_mn_d[0]),
|
|
|
|
| 285 |
a_source=warpgroup.OperandSource.RMEM
|
| 286 |
if self.mma_dkv_is_rs
|
| 287 |
else warpgroup.OperandSource.SMEM,
|
|
|
|
| 289 |
for tiler_mn_d in (tiler_mn_dK, tiler_mn_dV)
|
| 290 |
]
|
| 291 |
# dQ = dS @ K
|
| 292 |
+
assert self.num_wg_dQ % self.AtomLayoutMdQ == 0
|
| 293 |
+
atom_layout_dQ = (self.AtomLayoutMdQ, self.num_wg_dQ // self.AtomLayoutMdQ, 1)
|
| 294 |
tiler_mn_dQ = (self.tile_m // atom_layout_dQ[0], self.tile_hdim // atom_layout_dQ[1])
|
| 295 |
tiled_mma_dQ = sm90_utils_basic.make_trivial_tiled_mma(
|
| 296 |
self.dtype,
|
|
|
|
| 298 |
warpgroup.OperandMajorMode.K if not self.dQ_swapAB else warpgroup.OperandMajorMode.MN,
|
| 299 |
warpgroup.OperandMajorMode.MN if not self.dQ_swapAB else warpgroup.OperandMajorMode.K,
|
| 300 |
Float32,
|
| 301 |
+
atom_layout_mnk=maybe_swap_mn(atom_layout_dQ, self.dQ_swapAB),
|
| 302 |
+
tiler_mn=(64, tiler_mn_dQ[1] if not self.dQ_swapAB else tiler_mn_dQ[0]),
|
| 303 |
)
|
| 304 |
return tiled_mma_SdP, tiled_mma_dK, tiled_mma_dV, tiled_mma_dQ
|
| 305 |
|
|
|
|
| 353 |
mdK: cute.Tensor,
|
| 354 |
mdV: cute.Tensor,
|
| 355 |
softmax_scale: Float32,
|
|
|
|
| 356 |
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 357 |
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 358 |
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 359 |
mSeqUsedK: Optional[cute.Tensor] = None,
|
|
|
|
| 360 |
window_size_left: Int32 | int | None = None,
|
| 361 |
window_size_right: Int32 | int | None = None,
|
| 362 |
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 363 |
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 364 |
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 365 |
+
aux_data: AuxData = AuxData(),
|
| 366 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 367 |
+
mCuTotalMBlocks: Optional[cute.Tensor] = None,
|
| 368 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 369 |
+
stream: cuda.CUstream = None,
|
| 370 |
):
|
| 371 |
+
# For GQA (qhead_per_kvhead > 1), multiple Q heads accumulate into the same dK/dV,
|
| 372 |
+
# so we need the float32 accum path + postprocess.
|
| 373 |
+
# For varlen_k with qhead_per_kvhead == 1, we use ragged TMA tensors.
|
| 374 |
+
self.varlen_k = mCuSeqlensK is not None or mSeqUsedK is not None
|
| 375 |
|
| 376 |
self._check_type(
|
| 377 |
*(
|
|
|
|
| 380 |
)
|
| 381 |
)
|
| 382 |
|
| 383 |
+
self.is_varlen_q = mCuSeqlensQ is not None or mSeqUsedQ is not None
|
| 384 |
+
|
| 385 |
mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV = [
|
| 386 |
assume_tensor_aligned(t) for t in (mQ, mK, mV, mdO, mLSE, mdPsum, mdQaccum, mdK, mdV)
|
| 387 |
]
|
| 388 |
|
| 389 |
+
# Non-varlen inputs are (b, s, n, h), varlen inputs are (s, n, h).
|
| 390 |
+
# We convert both to a seqlen-major view with head-dim second.
|
| 391 |
+
# Each tensor may have different rank when Q is padded (seqused_q) but K/V are unpadded (cu_seqlens_k).
|
| 392 |
+
def _qkv_transpose(t):
|
| 393 |
+
return layout_utils.select(t, [1, 3, 2, 0] if cute.rank(t.shape) == 4 else [0, 2, 1])
|
| 394 |
+
|
| 395 |
+
mQ, mK, mV, mdO = [_qkv_transpose(t) for t in (mQ, mK, mV, mdO)]
|
| 396 |
if const_expr(self.qhead_per_kvhead == 1):
|
| 397 |
+
mdK, mdV = [_qkv_transpose(t) for t in (mdK, mdV)]
|
| 398 |
else:
|
| 399 |
+
# Accum tensors are (b, n, s*h) for non-varlen and (n, s*h) for varlen.
|
| 400 |
+
accum_transpose = [2, 1, 0] if cute.rank(mdK.shape) == 3 else [1, 0]
|
| 401 |
mdK, mdV = [layout_utils.select(t, accum_transpose) for t in (mdK, mdV)]
|
| 402 |
+
# Non-varlen stats are (b, n, s), varlen stats are (n, s).
|
| 403 |
+
LSE_dPsum_dQaccum_transpose = [2, 1, 0] if cute.rank(mLSE.shape) == 3 else [1, 0]
|
| 404 |
mLSE, mdPsum, mdQaccum = [
|
| 405 |
layout_utils.select(t, LSE_dPsum_dQaccum_transpose) for t in (mLSE, mdPsum, mdQaccum)
|
| 406 |
]
|
| 407 |
|
| 408 |
tiled_mma_SdP, tiled_mma_dK, tiled_mma_dV, tiled_mma_dQ = self._get_tiled_mma()
|
| 409 |
+
# (batch, num_head, num_m_blocks, cluster_size) -> (num_m_blocks, cluster_size, num_head, batch)
|
| 410 |
+
if const_expr(self.deterministic):
|
| 411 |
+
assert mdQ_semaphore is not None
|
| 412 |
+
mdQ_semaphore = layout_utils.select(mdQ_semaphore, mode=[2, 3, 1, 0])
|
| 413 |
+
if const_expr(self.deterministic and self.qhead_per_kvhead > 1):
|
| 414 |
+
assert mdK_semaphore is not None
|
| 415 |
+
assert mdV_semaphore is not None
|
| 416 |
+
mdK_semaphore, mdV_semaphore = [
|
| 417 |
+
layout_utils.select(t, mode=[2, 3, 1, 0]) for t in (mdK_semaphore, mdV_semaphore)
|
| 418 |
+
]
|
| 419 |
+
else:
|
| 420 |
+
mdK_semaphore = None
|
| 421 |
+
mdV_semaphore = None
|
| 422 |
|
| 423 |
self.num_mma_threads = tiled_mma_SdP.size
|
| 424 |
assert self.num_mma_threads + 128 == self.num_threads
|
|
|
|
| 426 |
self.num_threads_per_warp_group = 128
|
| 427 |
self.num_producer_threads = 32
|
| 428 |
|
| 429 |
+
REG_LIMIT = 504 if self.num_wg_mma == 2 else 512
|
| 430 |
+
if const_expr(self.num_wg_mma == 2):
|
| 431 |
+
if const_expr(self.num_wg_dQ == 1):
|
| 432 |
+
self.num_mma_regs_wg0 = 256
|
| 433 |
+
self.num_mma_regs_wg1 = 224
|
| 434 |
+
else:
|
| 435 |
+
self.num_mma_regs_wg0 = 240
|
| 436 |
+
self.num_mma_regs_wg1 = 240
|
| 437 |
+
self.num_mma_regs = self.num_mma_regs_wg0 # for backward compat
|
| 438 |
+
self.num_producer_regs = 24
|
| 439 |
+
assert (
|
| 440 |
+
self.num_mma_regs_wg0 + self.num_mma_regs_wg1 + self.num_producer_regs <= REG_LIMIT
|
| 441 |
+
)
|
| 442 |
+
else: # 3 warp groups
|
| 443 |
+
self.num_mma_regs_wg0 = 160
|
| 444 |
+
self.num_mma_regs_wg1 = 160
|
| 445 |
+
self.num_mma_regs = 160
|
| 446 |
+
self.num_producer_regs = 32
|
| 447 |
+
assert self.num_mma_regs_wg0 * self.num_wg_mma + self.num_producer_regs <= REG_LIMIT
|
| 448 |
|
| 449 |
self._setup_attributes()
|
| 450 |
SharedStorage = self._get_shared_storage_cls()
|
|
|
|
| 461 |
self.tma_copy_bytes["LSE"] = self.tile_m * Float32.width // 8
|
| 462 |
self.tma_copy_bytes["dPsum"] = self.tile_m * Float32.width // 8
|
| 463 |
self.tma_copy_bytes["dQ"] = (
|
| 464 |
+
self.tile_m * self.tile_hdim * Float32.width // 8 // self.num_wg_dQ
|
| 465 |
)
|
| 466 |
self.tma_copy_bytes["dKacc"] = self.tile_n * self.tile_hdim * Float32.width // 8
|
| 467 |
self.tma_copy_bytes["dVacc"] = self.tile_n * self.tile_hdimv * Float32.width // 8
|
|
|
|
| 491 |
(self.tile_m, self.tile_hdimv),
|
| 492 |
)
|
| 493 |
if const_expr(self.qhead_per_kvhead == 1):
|
| 494 |
+
mdK_tma = (
|
| 495 |
+
copy_utils.create_ragged_tensor_for_tma(mdK, ragged_dim=0, ptr_shift=True)
|
| 496 |
+
if self.varlen_k
|
| 497 |
+
else mdK
|
| 498 |
+
)
|
| 499 |
+
mdV_tma = (
|
| 500 |
+
copy_utils.create_ragged_tensor_for_tma(mdV, ragged_dim=0, ptr_shift=True)
|
| 501 |
+
if self.varlen_k
|
| 502 |
+
else mdV
|
| 503 |
+
)
|
| 504 |
tma_atom_dK, tma_tensor_dK = cpasync.make_tiled_tma_atom(
|
| 505 |
cpasync.CopyBulkTensorTileS2GOp(),
|
| 506 |
+
mdK_tma,
|
| 507 |
cute.select(self.sK_layout, mode=[0, 1]),
|
| 508 |
(self.tile_n, self.tile_hdim),
|
| 509 |
)
|
| 510 |
tma_atom_dV, tma_tensor_dV = cpasync.make_tiled_tma_atom(
|
| 511 |
cpasync.CopyBulkTensorTileS2GOp(),
|
| 512 |
+
mdV_tma,
|
| 513 |
cute.select(self.sV_layout, mode=[0, 1]),
|
| 514 |
(self.tile_n, self.tile_hdimv),
|
| 515 |
)
|
| 516 |
else:
|
| 517 |
tma_atom_dK = tma_atom_dV = tma_tensor_dK = tma_tensor_dV = None
|
| 518 |
|
| 519 |
+
if const_expr(mCuSeqlensK is not None or mSeqUsedK is not None):
|
| 520 |
+
TileScheduler = SingleTileVarlenScheduler
|
| 521 |
+
elif const_expr(self.deterministic):
|
| 522 |
+
TileScheduler = SingleTileLPTBwdScheduler
|
| 523 |
+
else:
|
| 524 |
+
TileScheduler = SingleTileScheduler
|
| 525 |
+
self.spt = (self.is_causal or self.is_local) and self.deterministic
|
| 526 |
tile_sched_args = TileSchedulerArguments(
|
| 527 |
cute.ceil_div(cute.size(mK.shape[0]), self.tile_n),
|
| 528 |
cute.size(mQ.shape[2]),
|
| 529 |
+
cute.size(mK.shape[3])
|
| 530 |
+
if const_expr(mCuSeqlensK is None)
|
| 531 |
+
else cute.size(mCuSeqlensK.shape[0] - 1), # num_batch
|
| 532 |
1, # num_splits
|
| 533 |
+
cute.size(mQ.shape[0]), # pass seqlen_q or total_q for seqlen_k
|
| 534 |
+
mQ.shape[1], # headdim
|
| 535 |
+
mV.shape[1], # headdim_v
|
| 536 |
+
total_q=cute.size(mK.shape[0])
|
| 537 |
+
if const_expr(mCuSeqlensK is not None)
|
| 538 |
+
else cute.size(mK.shape[0]) * cute.size(mK.shape[3]),
|
| 539 |
+
tile_shape_mn=(self.tile_n, self.tile_m), # Swapping the role of Q & K
|
| 540 |
+
mCuSeqlensQ=mCuSeqlensK,
|
| 541 |
+
mSeqUsedQ=mSeqUsedK,
|
| 542 |
qhead_per_kvhead_packgqa=1,
|
| 543 |
element_size=self.dtype.width // 8,
|
| 544 |
is_persistent=False,
|
| 545 |
+
lpt=self.spt,
|
| 546 |
+
head_swizzle=self.deterministic,
|
| 547 |
+
cu_total_m_blocks_ptr=mCuTotalMBlocks,
|
| 548 |
)
|
| 549 |
|
| 550 |
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
|
|
|
| 557 |
softmax_scale_log2 = LOG2_E
|
| 558 |
|
| 559 |
fastdiv_mods = None
|
| 560 |
+
if const_expr(aux_data.tensors is not None):
|
| 561 |
seqlen_q = cute.size(mQ.shape[0])
|
| 562 |
seqlen_k = cute.size(mK.shape[0])
|
| 563 |
seqlen_q_divmod = FastDivmodDivisor(seqlen_q)
|
|
|
|
| 570 |
|
| 571 |
self.use_block_sparsity = cutlass.const_expr(blocksparse_tensors is not None)
|
| 572 |
|
| 573 |
+
if const_expr(window_size_left is not None):
|
| 574 |
+
window_size_left = Int32(window_size_left)
|
| 575 |
+
if const_expr(window_size_right is not None):
|
| 576 |
+
window_size_right = Int32(window_size_right)
|
| 577 |
+
|
| 578 |
self.kernel(
|
| 579 |
tma_tensor_Q,
|
| 580 |
tma_tensor_K,
|
|
|
|
| 591 |
mLSE,
|
| 592 |
mdPsum,
|
| 593 |
mdQaccum,
|
| 594 |
+
mCuSeqlensQ,
|
| 595 |
+
mCuSeqlensK,
|
| 596 |
+
mSeqUsedQ,
|
| 597 |
+
mSeqUsedK,
|
| 598 |
self.sQ_layout,
|
| 599 |
self.sK_layout,
|
| 600 |
self.sV_layout,
|
|
|
|
| 611 |
tile_sched_params,
|
| 612 |
TileScheduler,
|
| 613 |
SharedStorage,
|
| 614 |
+
aux_data,
|
| 615 |
fastdiv_mods,
|
| 616 |
blocksparse_tensors,
|
| 617 |
qhead_per_kvhead_divmod,
|
| 618 |
+
mdQ_semaphore,
|
| 619 |
+
mdK_semaphore,
|
| 620 |
+
mdV_semaphore,
|
| 621 |
+
window_size_left,
|
| 622 |
+
window_size_right,
|
| 623 |
).launch(
|
| 624 |
grid=grid_dim,
|
| 625 |
block=[self.num_threads, 1, 1],
|
| 626 |
stream=stream,
|
| 627 |
min_blocks_per_mp=1,
|
| 628 |
+
use_pdl=True,
|
| 629 |
)
|
| 630 |
|
| 631 |
@cute.kernel
|
|
|
|
| 646 |
mLSE: cute.Tensor,
|
| 647 |
mdPsum: cute.Tensor,
|
| 648 |
mdQaccum: cute.Tensor,
|
| 649 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 650 |
+
mCuSeqlensK: Optional[cute.Tensor],
|
| 651 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 652 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 653 |
sQ_layout: cute.ComposedLayout,
|
| 654 |
sK_layout: cute.ComposedLayout,
|
| 655 |
sV_layout: cute.ComposedLayout,
|
|
|
|
| 666 |
tile_sched_params: ParamsBase,
|
| 667 |
TileScheduler: cutlass.Constexpr[Callable],
|
| 668 |
SharedStorage: cutlass.Constexpr[Callable],
|
| 669 |
+
aux_data: AuxData = AuxData(),
|
| 670 |
fastdiv_mods=(None, None),
|
| 671 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 672 |
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
| 673 |
+
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 674 |
+
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 675 |
+
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 676 |
+
window_size_left: Optional[Int32] = None,
|
| 677 |
+
window_size_right: Optional[Int32] = None,
|
| 678 |
):
|
| 679 |
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 680 |
|
| 681 |
# prefetch TMA descriptors
|
| 682 |
if warp_idx == 0:
|
| 683 |
+
for atom in [tma_atom_Q, tma_atom_K, tma_atom_V, tma_atom_dO, tma_atom_dK, tma_atom_dV]:
|
| 684 |
+
if const_expr(atom is not None):
|
| 685 |
+
cpasync.prefetch_descriptor(atom)
|
|
|
|
| 686 |
|
| 687 |
smem = cutlass.utils.SmemAllocator()
|
| 688 |
storage = smem.allocate(SharedStorage)
|
|
|
|
| 736 |
self.is_causal,
|
| 737 |
self.is_local,
|
| 738 |
False, # is_split_kv
|
| 739 |
+
window_size_left,
|
| 740 |
+
window_size_right,
|
| 741 |
qhead_per_kvhead_packgqa=1,
|
| 742 |
)
|
| 743 |
SeqlenInfoCls = partial(
|
| 744 |
SeqlenInfoQK.create,
|
| 745 |
seqlen_q_static=mQ.shape[0],
|
| 746 |
seqlen_k_static=mK.shape[0],
|
| 747 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 748 |
+
mCuSeqlensK=mCuSeqlensK,
|
| 749 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 750 |
+
mSeqUsedK=mSeqUsedK,
|
| 751 |
+
tile_m=self.tile_m,
|
| 752 |
+
tile_n=self.tile_n,
|
| 753 |
)
|
| 754 |
AttentionMaskCls = partial(
|
| 755 |
AttentionMask,
|
| 756 |
self.tile_m,
|
| 757 |
self.tile_n,
|
| 758 |
+
window_size_left=window_size_left,
|
| 759 |
+
window_size_right=window_size_right,
|
| 760 |
swap_AB=self.SdP_swapAB,
|
| 761 |
)
|
| 762 |
TileSchedulerCls = partial(TileScheduler.create, tile_sched_params)
|
|
|
|
| 797 |
TileSchedulerCls,
|
| 798 |
SeqlenInfoCls,
|
| 799 |
blocksparse_tensors,
|
| 800 |
+
mdQ_semaphore,
|
| 801 |
)
|
| 802 |
else:
|
|
|
|
| 803 |
tidx, _, _ = cute.arch.thread_idx()
|
| 804 |
tidx = tidx - 128
|
| 805 |
+
mma_args = (
|
| 806 |
tiled_mma_SdP,
|
| 807 |
tiled_mma_dK,
|
| 808 |
tiled_mma_dV,
|
| 809 |
tiled_mma_dQ,
|
| 810 |
mdK,
|
| 811 |
mdV,
|
| 812 |
+
mdK_semaphore,
|
| 813 |
+
mdV_semaphore,
|
| 814 |
mdQaccum,
|
| 815 |
sQ,
|
| 816 |
sK,
|
|
|
|
| 833 |
SeqlenInfoCls,
|
| 834 |
AttentionMaskCls,
|
| 835 |
TileSchedulerCls,
|
| 836 |
+
aux_data,
|
| 837 |
fastdiv_mods,
|
| 838 |
blocksparse_tensors,
|
| 839 |
qhead_per_kvhead_divmod,
|
| 840 |
)
|
| 841 |
+
if const_expr(self.num_wg_dQ == self.num_wg_mma):
|
| 842 |
+
# Both WGs compute dQ
|
| 843 |
+
cute.arch.setmaxregister_increase(self.num_mma_regs_wg0)
|
| 844 |
+
self.mma(*mma_args, is_dQ_wg=True)
|
| 845 |
+
else:
|
| 846 |
+
# WG0 computes dQ, WG1 skips it
|
| 847 |
+
warp_idx_in_mma = cute.arch.make_warp_uniform(cute.arch.warp_idx()) - 4
|
| 848 |
+
if warp_idx_in_mma < 4:
|
| 849 |
+
cute.arch.setmaxregister_increase(self.num_mma_regs_wg0)
|
| 850 |
+
self.mma(*mma_args, is_dQ_wg=True)
|
| 851 |
+
else:
|
| 852 |
+
cute.arch.setmaxregister_increase(self.num_mma_regs_wg1)
|
| 853 |
+
self.mma(*mma_args, is_dQ_wg=False)
|
| 854 |
|
| 855 |
@cute.jit
|
| 856 |
def load(
|
|
|
|
| 898 |
if const_expr(self.qhead_per_kvhead == 1)
|
| 899 |
else head_idx // qhead_per_kvhead_divmod
|
| 900 |
)
|
| 901 |
+
mK_cur = seqlen.offset_batch_K(mK, batch_idx, dim=3)[None, None, head_idx_kv]
|
| 902 |
+
mV_cur = seqlen.offset_batch_K(mV, batch_idx, dim=3)[None, None, head_idx_kv]
|
| 903 |
gK = cute.local_tile(mK_cur, (self.tile_n, self.tile_hdim), (n_block, 0))
|
|
|
|
| 904 |
gV = cute.local_tile(mV_cur, (self.tile_n, self.tile_hdimv), (n_block, 0))
|
| 905 |
|
| 906 |
+
mQ_cur = seqlen.offset_batch_Q(mQ, batch_idx, dim=3)[None, None, head_idx]
|
| 907 |
+
mLSE_cur = seqlen.offset_batch_Q(mLSE, batch_idx, dim=2, padded=True)[
|
| 908 |
+
None, head_idx
|
| 909 |
+
]
|
| 910 |
+
mdO_cur = seqlen.offset_batch_Q(mdO, batch_idx, dim=3)[None, None, head_idx]
|
| 911 |
+
mdPsum_cur = seqlen.offset_batch_Q(mdPsum, batch_idx, dim=2, padded=True)[
|
| 912 |
+
None, head_idx
|
| 913 |
+
]
|
| 914 |
gQ = cute.local_tile(mQ_cur, (self.tile_m, self.tile_hdim), (None, 0))
|
|
|
|
| 915 |
gdO = cute.local_tile(mdO_cur, (self.tile_m, self.tile_hdimv), (None, 0))
|
|
|
|
| 916 |
gLSE = cute.local_tile(mLSE_cur, (self.tile_m,), (None,))
|
|
|
|
| 917 |
gdPsum = cute.local_tile(mdPsum_cur, (self.tile_m,), (None,))
|
| 918 |
|
| 919 |
load_K, _, _ = copy_utils.tma_get_copy_fn(
|
|
|
|
| 939 |
|
| 940 |
if const_expr(not self.use_block_sparsity):
|
| 941 |
total_m_block_cnt = m_block_max - m_block_min
|
| 942 |
+
process_tile = (
|
| 943 |
+
const_expr(not self.is_local and not self.is_varlen_q)
|
| 944 |
+
or m_block_min < m_block_max
|
| 945 |
+
)
|
| 946 |
else:
|
| 947 |
total_m_block_cnt = get_total_q_block_count_bwd(
|
| 948 |
blocksparse_tensors,
|
| 949 |
batch_idx,
|
| 950 |
head_idx,
|
| 951 |
n_block,
|
| 952 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 953 |
m_block_max=m_block_max,
|
| 954 |
)
|
| 955 |
process_tile = total_m_block_cnt > Int32(0)
|
|
|
|
| 962 |
)
|
| 963 |
load_K(tma_bar_ptr=pipeline_Q.producer_get_barrier(producer_state_Q))
|
| 964 |
load_Q(first_m_block, producer_state=producer_state_Q)
|
| 965 |
+
# Wait for bwd preprocess to finish writing LSE and dPsum
|
| 966 |
+
cute.arch.griddepcontrol_wait()
|
| 967 |
load_LSE(first_m_block, producer_state=producer_state_Q)
|
| 968 |
producer_state_dO_cur = (
|
| 969 |
producer_state_dO
|
|
|
|
| 994 |
producer_state_Q.advance()
|
| 995 |
producer_state_dO.advance()
|
| 996 |
else:
|
| 997 |
+
# The wait for bwd preprocess that the dense arm above does inline sits
|
| 998 |
+
# in _load_q_do_block_sm90, at the same point in the load order.
|
| 999 |
producer_state_Q, producer_state_dO = produce_block_sparse_q_loads_bwd_sm90(
|
| 1000 |
blocksparse_tensors,
|
| 1001 |
batch_idx,
|
|
|
|
| 1014 |
self.tma_copy_bytes["K"],
|
| 1015 |
self.tma_copy_bytes["V"],
|
| 1016 |
Q_stage_eq_dO_stage=(self.Q_stage == self.dO_stage),
|
| 1017 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 1018 |
m_block_max=m_block_max,
|
| 1019 |
)
|
| 1020 |
|
|
|
|
| 1026 |
def apply_score_mod(
|
| 1027 |
self,
|
| 1028 |
acc_S: cute.Tensor,
|
| 1029 |
+
thr_mma_SdP: cute.ThrMma,
|
| 1030 |
batch_idx,
|
| 1031 |
head_idx,
|
| 1032 |
m_block,
|
| 1033 |
n_block,
|
| 1034 |
softmax_scale,
|
| 1035 |
seqlen_info: SeqlenInfoQK,
|
| 1036 |
+
aux_data: AuxData = AuxData(),
|
| 1037 |
fastdiv_mods=(None, None),
|
| 1038 |
):
|
| 1039 |
# [NOTE] SdP_swapAB: swapAB transposes the tile, so use (n, m) indexing
|
|
|
|
| 1057 |
softmax_scale,
|
| 1058 |
self.vec_size,
|
| 1059 |
self.qk_acc_dtype,
|
| 1060 |
+
aux_data,
|
| 1061 |
fastdiv_mods,
|
| 1062 |
seqlen_info,
|
| 1063 |
constant_q_idx=None,
|
|
|
|
| 1070 |
self,
|
| 1071 |
grad_tensor: cute.Tensor,
|
| 1072 |
score_tensor: cute.Tensor,
|
| 1073 |
+
thr_mma_SdP: cute.ThrMma,
|
| 1074 |
batch_idx,
|
| 1075 |
head_idx,
|
| 1076 |
m_block,
|
| 1077 |
n_block,
|
| 1078 |
softmax_scale,
|
| 1079 |
seqlen_info: SeqlenInfoQK,
|
| 1080 |
+
aux_data: AuxData = AuxData(),
|
| 1081 |
fastdiv_mods=(None, None),
|
| 1082 |
):
|
| 1083 |
cS = cute.make_identity_tensor(
|
|
|
|
| 1101 |
softmax_scale,
|
| 1102 |
self.vec_size,
|
| 1103 |
self.qk_acc_dtype,
|
| 1104 |
+
aux_data,
|
| 1105 |
fastdiv_mods,
|
| 1106 |
seqlen_info,
|
| 1107 |
constant_q_idx=None,
|
|
|
|
| 1118 |
tiled_mma_dQ: cute.TiledMma,
|
| 1119 |
mdK: cute.Tensor,
|
| 1120 |
mdV: cute.Tensor,
|
| 1121 |
+
mdK_semaphore: Optional[cute.Tensor],
|
| 1122 |
+
mdV_semaphore: Optional[cute.Tensor],
|
| 1123 |
mdQaccum: cute.Tensor,
|
| 1124 |
sQ: cute.Tensor,
|
| 1125 |
sK: cute.Tensor,
|
|
|
|
| 1142 |
SeqlenInfoCls: Callable,
|
| 1143 |
AttentionMaskCls: Callable,
|
| 1144 |
TileSchedulerCls: Callable,
|
| 1145 |
+
aux_data: AuxData = AuxData(),
|
| 1146 |
fastdiv_mods=(None, None),
|
| 1147 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 1148 |
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
| 1149 |
+
is_dQ_wg: cutlass.Constexpr[bool] = True,
|
| 1150 |
):
|
| 1151 |
warp_group_idx = cute.arch.make_warp_uniform(tidx // self.num_threads_per_warp_group)
|
| 1152 |
warp_group_thread_layout = cute.make_layout(
|
| 1153 |
+
self.num_wg_mma, stride=self.num_threads_per_warp_group
|
| 1154 |
)
|
| 1155 |
thr_mma_SdP = tiled_mma_SdP.get_slice(tidx)
|
| 1156 |
wg_mma_SdP = tiled_mma_SdP.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 1157 |
wg_mma_dK = tiled_mma_dK.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 1158 |
wg_mma_dV = tiled_mma_dV.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 1159 |
+
wg_mma_dQ = None
|
| 1160 |
+
if const_expr(is_dQ_wg):
|
| 1161 |
+
wg_idx_dQ = warp_group_idx if const_expr(self.num_wg_dQ > 1) else 0
|
| 1162 |
+
wg_mma_dQ = tiled_mma_dQ.get_slice(warp_group_thread_layout(wg_idx_dQ))
|
| 1163 |
# S = Q @ K.T
|
| 1164 |
shape_mnk_S = (self.tile_m, self.tile_n, self.tile_hdim)
|
| 1165 |
_, tSrQ, tSrK = sm90_utils.partition_fragment_ABC(
|
|
|
|
| 1205 |
# dQ = dS @ K
|
| 1206 |
sKt = layout_utils.transpose_view(sK)
|
| 1207 |
shape_mnk_dQ = (self.tile_m, self.tile_hdim, self.tile_n)
|
| 1208 |
+
mma_dsk_fn = None
|
| 1209 |
+
if const_expr(is_dQ_wg):
|
| 1210 |
+
_, tdQrdS, tdQrKt = sm90_utils.partition_fragment_ABC(
|
| 1211 |
+
wg_mma_dQ, shape_mnk_dQ, sdS, sKt, swap_AB=self.dQ_swapAB
|
| 1212 |
+
)
|
| 1213 |
+
mma_dsk_fn = partial(
|
| 1214 |
+
gemm_zero_init,
|
| 1215 |
+
tiled_mma_dQ,
|
| 1216 |
+
shape_mnk_dQ[:2],
|
| 1217 |
+
tdQrdS,
|
| 1218 |
+
tdQrKt,
|
| 1219 |
+
swap_AB=self.dQ_swapAB,
|
| 1220 |
+
)
|
| 1221 |
|
| 1222 |
+
# Smem copy atom tiling for P/dS R2S
|
| 1223 |
copy_P_r2s = None
|
| 1224 |
+
mms_PdS = self.tile_n // (self.num_wg_mma // self.AtomLayoutMSdP)
|
| 1225 |
if const_expr(sP is not None):
|
| 1226 |
sP_cpy = sP if const_expr(not self.SdP_swapAB) else sPt
|
| 1227 |
copy_P_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1228 |
+
tiled_mma_SdP,
|
| 1229 |
+
sP_cpy,
|
| 1230 |
+
tidx,
|
| 1231 |
+
transpose=self.SdP_swapAB,
|
| 1232 |
+
position_independent=True,
|
| 1233 |
+
major_mode_size=mms_PdS,
|
| 1234 |
)
|
| 1235 |
sdS_cpy = sdS if const_expr(not self.SdP_swapAB) else sdSt
|
| 1236 |
copy_dS_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1237 |
+
tiled_mma_SdP,
|
| 1238 |
+
sdS_cpy,
|
| 1239 |
+
tidx,
|
| 1240 |
+
transpose=self.SdP_swapAB,
|
| 1241 |
+
position_independent=True,
|
| 1242 |
+
major_mode_size=mms_PdS,
|
| 1243 |
)
|
| 1244 |
|
| 1245 |
tLSEsLSE = layout_utils.mma_partition_C_vec(
|
|
|
|
| 1248 |
tLSEsdPsum = layout_utils.mma_partition_C_vec(
|
| 1249 |
sdPsum, thr_mma_SdP, expand_shape=self.tile_n, is_colvec=not self.SdP_swapAB
|
| 1250 |
)
|
| 1251 |
+
# When shuffle=True, rows are distributed across 8 quads (4 threads each) within a warp.
|
| 1252 |
+
# Each thread loads only ceil(num_rows/8) values;
|
| 1253 |
+
shfl_copy = copy_utils.tiled_copy_1d(sLSE.element_type, num_threads=8, num_copy_elems=2)
|
| 1254 |
+
if const_expr(self.shuffle_LSE):
|
| 1255 |
+
tLSEsLSE = shfl_copy.get_slice(cute.arch.lane_idx() // 4).partition_S(tLSEsLSE)
|
| 1256 |
+
# ((2, 1), 1, 2) -> (((2, 1), 1), 2)
|
| 1257 |
+
tLSEsLSE = cute.group_modes(tLSEsLSE, 0, 2)
|
| 1258 |
+
if const_expr(self.shuffle_dPsum):
|
| 1259 |
+
tLSEsdPsum = shfl_copy.get_slice(cute.arch.lane_idx() // 4).partition_S(tLSEsdPsum)
|
| 1260 |
+
tLSEsdPsum = cute.group_modes(tLSEsdPsum, 0, 2)
|
| 1261 |
+
|
| 1262 |
+
tdQsdQaccum = None
|
| 1263 |
+
if const_expr(is_dQ_wg):
|
| 1264 |
+
smem_thr_copy_dQaccum = r2s_tiled_copy_dQaccum.get_slice(tidx)
|
| 1265 |
+
tdQsdQaccum = smem_thr_copy_dQaccum.partition_D(sdQaccum)
|
| 1266 |
|
| 1267 |
PdS_barrier = cutlass.pipeline.NamedBarrier(
|
| 1268 |
barrier_id=int(NamedBarrierBwd.PdS), num_threads=self.num_mma_threads
|
| 1269 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1270 |
|
| 1271 |
mma_one_m_block_all = partial(
|
| 1272 |
self.mma_one_m_block,
|
|
|
|
| 1287 |
PdS_barrier=PdS_barrier,
|
| 1288 |
# acc_dV=acc_dV,
|
| 1289 |
# acc_dK=acc_dK,
|
| 1290 |
+
is_dQ_wg=is_dQ_wg,
|
| 1291 |
)
|
| 1292 |
|
| 1293 |
consumer_state_Q = cutlass.pipeline.make_pipeline_state(
|
|
|
|
| 1301 |
while work_tile.is_valid_tile:
|
| 1302 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1303 |
seqlen = SeqlenInfoCls(batch_idx)
|
| 1304 |
+
|
| 1305 |
+
recompute_fastdiv_mods_q = const_expr(
|
| 1306 |
+
aux_data.tensors is not None and (seqlen.has_cu_seqlens_q or seqlen.has_seqused_q)
|
| 1307 |
+
)
|
| 1308 |
+
recompute_fastdiv_mods_k = const_expr(
|
| 1309 |
+
aux_data.tensors is not None and (seqlen.has_cu_seqlens_k or seqlen.has_seqused_k)
|
| 1310 |
+
)
|
| 1311 |
+
|
| 1312 |
+
if const_expr(fastdiv_mods is not None and fastdiv_mods[0] is not None):
|
| 1313 |
+
seqlen_q_divmod, seqlen_k_divmod = fastdiv_mods
|
| 1314 |
+
fastdiv_mods = (
|
| 1315 |
+
seqlen_q_divmod
|
| 1316 |
+
if not recompute_fastdiv_mods_q
|
| 1317 |
+
else FastDivmodDivisor(seqlen.seqlen_q),
|
| 1318 |
+
seqlen_k_divmod
|
| 1319 |
+
if not recompute_fastdiv_mods_k
|
| 1320 |
+
else FastDivmodDivisor(seqlen.seqlen_k),
|
| 1321 |
+
)
|
| 1322 |
+
|
| 1323 |
mask = AttentionMaskCls(seqlen)
|
| 1324 |
+
score_mod_fn = partial(
|
| 1325 |
+
self.apply_score_mod,
|
| 1326 |
+
thr_mma_SdP=thr_mma_SdP,
|
| 1327 |
+
softmax_scale=softmax_scale,
|
| 1328 |
+
aux_data=aux_data,
|
| 1329 |
+
fastdiv_mods=fastdiv_mods,
|
| 1330 |
+
)
|
| 1331 |
+
score_mod_bwd_fn = partial(
|
| 1332 |
+
self.apply_score_mod_bwd,
|
| 1333 |
+
thr_mma_SdP=thr_mma_SdP,
|
| 1334 |
+
softmax_scale=softmax_scale,
|
| 1335 |
+
aux_data=aux_data,
|
| 1336 |
+
fastdiv_mods=fastdiv_mods,
|
| 1337 |
+
)
|
| 1338 |
score_mod_fn_cur = partial(
|
| 1339 |
score_mod_fn,
|
| 1340 |
batch_idx=batch_idx,
|
|
|
|
| 1352 |
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 1353 |
|
| 1354 |
if const_expr(not self.use_block_sparsity):
|
| 1355 |
+
process_tile = (
|
| 1356 |
+
const_expr(not self.is_local and not self.is_varlen_q)
|
| 1357 |
+
or m_block_min < m_block_max
|
| 1358 |
+
)
|
| 1359 |
else:
|
| 1360 |
total_m_block_cnt = get_total_q_block_count_bwd(
|
| 1361 |
blocksparse_tensors,
|
| 1362 |
batch_idx,
|
| 1363 |
head_idx,
|
| 1364 |
n_block,
|
| 1365 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 1366 |
m_block_max=m_block_max,
|
| 1367 |
)
|
| 1368 |
process_tile = total_m_block_cnt > Int32(0)
|
|
|
|
| 1379 |
mask_causal=self.is_causal,
|
| 1380 |
mask_local=self.is_local,
|
| 1381 |
mask_mod=self.mask_mod,
|
| 1382 |
+
aux_data=aux_data,
|
| 1383 |
fastdiv_mods=fastdiv_mods,
|
| 1384 |
)
|
| 1385 |
dKV_accumulate = False
|
|
|
|
| 1410 |
thr_mma_SdP=thr_mma_SdP,
|
| 1411 |
score_mod_fn=score_mod_fn_cur,
|
| 1412 |
score_mod_bwd_fn=score_mod_bwd_fn_cur,
|
| 1413 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 1414 |
m_block_max=m_block_max,
|
| 1415 |
+
aux_data=aux_data,
|
| 1416 |
fastdiv_mods=fastdiv_mods,
|
| 1417 |
)
|
| 1418 |
|
|
|
|
| 1435 |
head_idx,
|
| 1436 |
batch_idx,
|
| 1437 |
qhead_per_kvhead_divmod,
|
| 1438 |
+
mdK_semaphore,
|
| 1439 |
+
mdV_semaphore,
|
| 1440 |
)
|
| 1441 |
else:
|
| 1442 |
+
# KV tile with zero Q blocks produces no dK/dV; write zeros.
|
| 1443 |
+
if const_expr(self.use_block_sparsity or self.is_local or self.is_varlen_q):
|
| 1444 |
acc_dK.fill(0.0)
|
| 1445 |
acc_dV.fill(0.0)
|
| 1446 |
self.epilogue_dKV(
|
|
|
|
| 1460 |
head_idx,
|
| 1461 |
batch_idx,
|
| 1462 |
qhead_per_kvhead_divmod,
|
| 1463 |
+
mdK_semaphore,
|
| 1464 |
+
mdV_semaphore,
|
| 1465 |
)
|
| 1466 |
|
| 1467 |
tile_scheduler.advance_to_next_work()
|
|
|
|
| 1471 |
if warp_idx == 4:
|
| 1472 |
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
| 1473 |
|
| 1474 |
+
@staticmethod
|
| 1475 |
+
@cute.jit
|
| 1476 |
+
def _get_stat(tSrS: cute.Tensor, row: Int32, lane: Int32, shuffle: bool) -> Float32:
|
| 1477 |
+
"""Retrieve the statistic for a given accumulator row.
|
| 1478 |
+
|
| 1479 |
+
When shuffle=False, direct register indexing.
|
| 1480 |
+
When shuffle=True, warp shuffle from the thread group that holds the value.
|
| 1481 |
+
"""
|
| 1482 |
+
if const_expr(not shuffle):
|
| 1483 |
+
return tSrS[row]
|
| 1484 |
+
# tSrS: (((2, 1), 1), 1)), distributed across 8 threads in the warp
|
| 1485 |
+
vecsize = cute.size(tSrS, mode=[0, 0]) # 2
|
| 1486 |
+
idx0, off, idx1 = cute.idx2crd(row, (vecsize, 8, cute.shape(tSrS, mode=[0, 1])))
|
| 1487 |
+
# register index: 0, 1, 0, 1, ..., 2, 3, 2, 3, ...
|
| 1488 |
+
return utils.shuffle_sync(tSrS[idx0 + idx1 * vecsize], offset=off * 4 + (lane % 4))
|
| 1489 |
+
|
| 1490 |
@cute.jit
|
| 1491 |
def mma_one_m_block(
|
| 1492 |
self,
|
|
|
|
| 1505 |
pipeline_dO: cutlass.pipeline.PipelineAsync,
|
| 1506 |
tLSEsLSE: cute.Tensor,
|
| 1507 |
tLSEsdPsum: cute.Tensor,
|
| 1508 |
+
tdQsdQaccum: Optional[cute.Tensor],
|
| 1509 |
softmax_scale_log2: Float32,
|
| 1510 |
PdS_barrier: cutlass.pipeline.NamedBarrier,
|
| 1511 |
+
is_dQ_wg: cutlass.Constexpr[bool] = True,
|
| 1512 |
mask_fn: Optional[Callable] = None,
|
| 1513 |
score_mod_fn: Optional[Callable] = None,
|
| 1514 |
score_mod_bwd_fn: Optional[Callable] = None,
|
| 1515 |
dKV_accumulate: Boolean = True,
|
| 1516 |
):
|
| 1517 |
consumer_state_dO_cur = (
|
| 1518 |
+
consumer_state_Q if const_expr(self.Q_stage == self.dO_stage) else consumer_state_dO
|
| 1519 |
)
|
| 1520 |
smem_idx_Q = consumer_state_Q.index
|
| 1521 |
smem_idx_dO = consumer_state_dO_cur.index if const_expr(self.dO_stage > 1) else 0
|
|
|
|
| 1523 |
# (1) [GEMM 1] S = Q @ K^T
|
| 1524 |
pipeline_Q.consumer_wait(consumer_state_Q, pipeline_Q.consumer_try_wait(consumer_state_Q))
|
| 1525 |
acc_S = mma_qk_fn(A_idx=smem_idx_Q, wg_wait=-1)
|
| 1526 |
+
# If shuffle_LSE, OOB reads are OK since sLSE is already padded
|
| 1527 |
tLSErLSE = copy_utils.load_s2r(tLSEsLSE[None, smem_idx_Q])
|
| 1528 |
# (2) [GEMM 2] dP = dO @ V.T
|
| 1529 |
pipeline_dO.consumer_wait(
|
|
|
|
| 1542 |
if cutlass.const_expr(mask_fn is not None):
|
| 1543 |
mask_fn(acc_S, m_block=m_block)
|
| 1544 |
acc_S_mn = layout_utils.reshape_acc_to_mn(acc_S, transpose=self.SdP_swapAB)
|
| 1545 |
+
lane_idx = cute.arch.lane_idx()
|
| 1546 |
for r in cutlass.range_constexpr(cute.size(acc_S_mn, mode=[0])):
|
| 1547 |
+
lse_val = self._get_stat(tLSErLSE, r, lane_idx, shuffle=self.shuffle_LSE)
|
| 1548 |
for c in cutlass.range(cute.size(acc_S_mn, mode=[1]), unroll_full=True):
|
| 1549 |
acc_S_mn[r, c] = cute.math.exp2(
|
| 1550 |
+
acc_S_mn[r, c] * softmax_scale_log2 - lse_val, fastmath=True
|
| 1551 |
)
|
| 1552 |
tLSErdPsum = copy_utils.load_s2r(tLSEsdPsum[None, smem_idx_dO])
|
| 1553 |
|
|
|
|
| 1564 |
warpgroup.wait_group(0)
|
| 1565 |
acc_dP_mn = layout_utils.reshape_acc_to_mn(acc_dP, transpose=self.SdP_swapAB)
|
| 1566 |
for r in cutlass.range_constexpr(cute.size(acc_dP_mn, mode=[0])):
|
| 1567 |
+
dpsum_val = self._get_stat(tLSErdPsum, r, lane_idx, shuffle=self.shuffle_dPsum)
|
| 1568 |
for c in cutlass.range(cute.size(acc_dP_mn, mode=[1]), unroll_full=True):
|
| 1569 |
+
acc_dP_mn[r, c] = acc_S_mn[r, c] * (acc_dP_mn[r, c] - dpsum_val)
|
| 1570 |
|
| 1571 |
if const_expr(self.score_mod_bwd is not None):
|
| 1572 |
score_mod_bwd_fn(acc_dP, acc_S_pre, m_block=m_block)
|
|
|
|
| 1598 |
# smem fence to make sure sdS is written before it's read by WGMMA
|
| 1599 |
cute.arch.fence_view_async_shared()
|
| 1600 |
PdS_barrier.arrive_and_wait()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1601 |
|
| 1602 |
+
if const_expr(is_dQ_wg):
|
| 1603 |
+
# (6) [GEMM 4] dQ = dS @ K
|
| 1604 |
+
acc_dQ = mma_dsk_fn(A_idx=smem_idx_PdS, wg_wait=1)
|
| 1605 |
+
pipeline_dO.consumer_release(consumer_state_dO_cur) # release dO as dV mma is done
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1606 |
|
| 1607 |
+
# (7) [GEMM 5] dK += dS.T @ Q
|
| 1608 |
+
if const_expr(not self.mma_dkv_is_rs):
|
| 1609 |
+
mma_dsq_fn(
|
| 1610 |
+
A_idx=smem_idx_PdS, B_idx=smem_idx_Q, zero_init=not dKV_accumulate, wg_wait=1
|
| 1611 |
+
)
|
| 1612 |
+
else:
|
| 1613 |
+
mma_dsq_fn(tCrA=tdKrdS, B_idx=smem_idx_Q, zero_init=not dKV_accumulate, wg_wait=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1614 |
|
| 1615 |
+
# dQ R2S: wait for dQaccum_store to free the smem buffer, then write dQ to smem
|
| 1616 |
+
# When dQ_single_wg, only WG0 enters here so warp_group_idx == 0
|
| 1617 |
+
cute.arch.barrier(
|
| 1618 |
+
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1619 |
+
number_of_threads=self.num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1620 |
+
)
|
| 1621 |
+
tdQrdQaccum_flat = cute.make_tensor(
|
| 1622 |
+
acc_dQ.iterator, cute.make_layout(tdQsdQaccum.shape)
|
| 1623 |
+
)
|
| 1624 |
+
cute.autovec_copy(tdQrdQaccum_flat, tdQsdQaccum)
|
| 1625 |
+
cute.arch.fence_view_async_shared()
|
| 1626 |
+
cute.arch.barrier_arrive(
|
| 1627 |
+
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1628 |
+
number_of_threads=self.num_threads_per_warp_group + cute.arch.WARP_SIZE,
|
| 1629 |
+
)
|
| 1630 |
+
|
| 1631 |
+
warpgroup.wait_group(0)
|
| 1632 |
+
pipeline_Q.consumer_release(consumer_state_Q)
|
| 1633 |
+
else:
|
| 1634 |
+
# dQ_single_wg: WG1 skips dQ, only does dV wait + dK
|
| 1635 |
+
# (7) [GEMM 5] dK += dS.T @ Q
|
| 1636 |
+
if const_expr(not self.mma_dkv_is_rs):
|
| 1637 |
+
mma_dsq_fn(
|
| 1638 |
+
A_idx=smem_idx_PdS, B_idx=smem_idx_Q, zero_init=not dKV_accumulate, wg_wait=1
|
| 1639 |
+
)
|
| 1640 |
+
else:
|
| 1641 |
+
mma_dsq_fn(tCrA=tdKrdS, B_idx=smem_idx_Q, zero_init=not dKV_accumulate, wg_wait=1)
|
| 1642 |
+
pipeline_dO.consumer_release(consumer_state_dO_cur)
|
| 1643 |
+
warpgroup.wait_group(0)
|
| 1644 |
+
pipeline_Q.consumer_release(consumer_state_Q)
|
| 1645 |
|
| 1646 |
consumer_state_Q.advance()
|
| 1647 |
consumer_state_dO.advance()
|
|
|
|
| 1666 |
head_idx: Int32,
|
| 1667 |
batch_idx: Int32,
|
| 1668 |
qhead_per_kvhead_divmod: Optional[FastDivmodDivisor] = None,
|
| 1669 |
+
mdK_semaphore: Optional[cute.Tensor] = None,
|
| 1670 |
+
mdV_semaphore: Optional[cute.Tensor] = None,
|
| 1671 |
):
|
| 1672 |
epi_barrier = cutlass.pipeline.NamedBarrier(
|
| 1673 |
barrier_id=int(NamedBarrierBwd.Epilogue), num_threads=self.num_mma_threads
|
|
|
|
| 1675 |
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 1676 |
|
| 1677 |
if const_expr(self.qhead_per_kvhead == 1):
|
| 1678 |
+
mdK_cur = seqlen.offset_batch_K(mdK, batch_idx, dim=3, ragged=self.varlen_k)[
|
| 1679 |
+
None, None, head_idx
|
| 1680 |
+
]
|
| 1681 |
+
mdV_cur = seqlen.offset_batch_K(mdV, batch_idx, dim=3, ragged=self.varlen_k)[
|
| 1682 |
+
None, None, head_idx
|
| 1683 |
+
]
|
| 1684 |
gdK = cute.local_tile(mdK_cur, (self.tile_n, self.tile_hdim), (n_block, 0))
|
| 1685 |
gdV = cute.local_tile(mdV_cur, (self.tile_n, self.tile_hdimv), (n_block, 0))
|
| 1686 |
store_dK, _, _ = copy_utils.tma_get_copy_fn(
|
|
|
|
| 1692 |
sdV = sV if const_expr(not self.dKV_swapAB) else layout_utils.transpose_view(sV)
|
| 1693 |
sdK = sK if const_expr(not self.dKV_swapAB) else layout_utils.transpose_view(sK)
|
| 1694 |
copy_dV_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1695 |
+
tiled_mma_dV,
|
| 1696 |
+
sdV,
|
| 1697 |
+
tidx,
|
| 1698 |
+
transpose=self.dKV_swapAB,
|
| 1699 |
+
position_independent=True,
|
| 1700 |
)
|
| 1701 |
copy_dK_r2s, _, _ = copy_utils.get_smem_store_C(
|
| 1702 |
+
tiled_mma_dK,
|
| 1703 |
+
sdK,
|
| 1704 |
+
tidx,
|
| 1705 |
+
transpose=self.dKV_swapAB,
|
| 1706 |
+
position_independent=True,
|
| 1707 |
)
|
| 1708 |
cute.arch.cp_async_bulk_wait_group(1, read=True)
|
| 1709 |
epi_barrier.arrive_and_wait()
|
|
|
|
| 1722 |
store_dK()
|
| 1723 |
cute.arch.cp_async_bulk_commit_group()
|
| 1724 |
else:
|
| 1725 |
+
deterministic_KV = self.deterministic and self.qhead_per_kvhead > 1
|
| 1726 |
+
sdKaccum_shape0 = self.tile_n * self.tile_hdim // self.num_wg_mma
|
| 1727 |
+
sdVaccum_shape0 = self.tile_n * self.tile_hdimv // self.num_wg_mma
|
| 1728 |
+
sdKaccum_layout = cute.make_layout((sdKaccum_shape0, self.num_wg_mma))
|
| 1729 |
+
sdVaccum_layout = cute.make_layout((sdVaccum_shape0, self.num_wg_mma))
|
| 1730 |
head_idx_kv = head_idx // qhead_per_kvhead_divmod
|
| 1731 |
+
if const_expr(deterministic_KV):
|
| 1732 |
+
assert mdK_semaphore is not None
|
| 1733 |
+
assert mdV_semaphore is not None
|
| 1734 |
+
mdK_semaphore_cur = mdK_semaphore[n_block, None, head_idx_kv, batch_idx]
|
| 1735 |
+
mdV_semaphore_cur = mdV_semaphore[n_block, None, head_idx_kv, batch_idx]
|
| 1736 |
+
lock_value = head_idx % self.qhead_per_kvhead
|
| 1737 |
+
mdKaccum_cur = seqlen.offset_batch_K(
|
| 1738 |
+
mdK, batch_idx, dim=2, padded=True, multiple=self.tile_hdim
|
| 1739 |
+
)[None, head_idx_kv]
|
| 1740 |
+
mdVaccum_cur = seqlen.offset_batch_K(
|
| 1741 |
+
mdV, batch_idx, dim=2, padded=True, multiple=self.tile_hdimv
|
| 1742 |
+
)[None, head_idx_kv]
|
| 1743 |
gdKaccum_ = cute.local_tile(mdKaccum_cur, (self.tile_n * self.tile_hdim,), (n_block,))
|
| 1744 |
gdKaccum = cute.flat_divide(gdKaccum_, (sdKaccum_shape0,))
|
|
|
|
| 1745 |
gdVaccum_ = cute.local_tile(mdVaccum_cur, (self.tile_n * self.tile_hdimv,), (n_block,))
|
| 1746 |
gdVaccum = cute.flat_divide(gdVaccum_, (sdVaccum_shape0,))
|
| 1747 |
# These two overlap each other
|
|
|
|
| 1750 |
sdVaccum = cute.make_tensor(sVaccum_ptr, sdVaccum_layout)
|
| 1751 |
tiled_copy_dKVaccum_r2s = cute.make_tiled_copy_tv(
|
| 1752 |
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Float32, num_bits_per_copy=128),
|
| 1753 |
+
cute.make_layout((self.num_threads_per_warp_group, self.num_wg_mma)),
|
| 1754 |
cute.make_layout(128 // Float32.width),
|
| 1755 |
)
|
| 1756 |
thr_copy_dKVaccum_r2s = tiled_copy_dKVaccum_r2s.get_slice(tidx)
|
| 1757 |
tdKsdKaccum = thr_copy_dKVaccum_r2s.partition_D(sdKaccum)
|
| 1758 |
tdVsdVaccum = thr_copy_dKVaccum_r2s.partition_D(sdVaccum)
|
| 1759 |
|
| 1760 |
+
read_flag = const_expr(not deterministic_KV)
|
| 1761 |
+
cute.arch.cp_async_bulk_wait_group(0, read=read_flag)
|
| 1762 |
+
if const_expr(deterministic_KV):
|
| 1763 |
+
barrier.wait_eq(mdK_semaphore_cur.iterator, tidx, 0, lock_value)
|
| 1764 |
epi_barrier.arrive_and_wait()
|
| 1765 |
tdKrdKaccum_flat = cute.make_tensor(acc_dK.iterator, tdKsdKaccum.shape)
|
| 1766 |
cute.autovec_copy(tdKrdKaccum_flat, tdKsdKaccum)
|
|
|
|
| 1768 |
epi_barrier.arrive_and_wait()
|
| 1769 |
if warp_idx == 4:
|
| 1770 |
with cute.arch.elect_one():
|
| 1771 |
+
for wg_idx in cutlass.range_constexpr(self.num_wg_mma):
|
| 1772 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1773 |
sdKaccum[None, wg_idx].iterator,
|
| 1774 |
gdKaccum[None, wg_idx].iterator,
|
| 1775 |
+
self.tma_copy_bytes["dKacc"] // self.num_wg_mma,
|
| 1776 |
)
|
| 1777 |
cute.arch.cp_async_bulk_commit_group()
|
| 1778 |
|
| 1779 |
+
cute.arch.cp_async_bulk_wait_group(0, read=read_flag)
|
| 1780 |
+
if const_expr(deterministic_KV):
|
| 1781 |
+
barrier.arrive_inc(mdK_semaphore_cur.iterator, tidx, 0, 1)
|
| 1782 |
+
barrier.wait_eq(mdV_semaphore_cur.iterator, tidx, 0, lock_value)
|
| 1783 |
epi_barrier.arrive_and_wait()
|
| 1784 |
tdVrdVaccum_flat = cute.make_tensor(acc_dV.iterator, tdVsdVaccum.shape)
|
| 1785 |
cute.autovec_copy(tdVrdVaccum_flat, tdVsdVaccum)
|
|
|
|
| 1787 |
epi_barrier.arrive_and_wait()
|
| 1788 |
if warp_idx == 4:
|
| 1789 |
with cute.arch.elect_one():
|
| 1790 |
+
for wg_idx in cutlass.range_constexpr(self.num_wg_mma):
|
| 1791 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1792 |
sdVaccum[None, wg_idx].iterator,
|
| 1793 |
gdVaccum[None, wg_idx].iterator,
|
| 1794 |
+
self.tma_copy_bytes["dVacc"] // self.num_wg_mma,
|
| 1795 |
)
|
| 1796 |
cute.arch.cp_async_bulk_commit_group()
|
| 1797 |
+
if const_expr(deterministic_KV):
|
| 1798 |
+
cute.arch.cp_async_bulk_wait_group(0, read=read_flag)
|
| 1799 |
+
barrier.arrive_inc(mdV_semaphore_cur.iterator, tidx, 0, 1)
|
| 1800 |
|
| 1801 |
@cute.jit
|
| 1802 |
def dQaccum_store(
|
|
|
|
| 1807 |
TileSchedulerCls: cutlass.Constexpr[Callable],
|
| 1808 |
SeqlenInfoCls: cutlass.Constexpr[Callable],
|
| 1809 |
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 1810 |
+
mdQ_semaphore: Optional[cute.Tensor] = None,
|
| 1811 |
):
|
| 1812 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 1813 |
+
# warp-local thread index (dQaccum_store runs on warp 1, global tidx 32-63)
|
| 1814 |
+
warp_local_tidx = tidx % cute.arch.WARP_SIZE
|
| 1815 |
+
read_flag = const_expr(not self.deterministic)
|
| 1816 |
+
|
| 1817 |
tile_scheduler = TileSchedulerCls()
|
| 1818 |
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1819 |
while work_tile.is_valid_tile:
|
| 1820 |
n_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1821 |
seqlen = SeqlenInfoCls(batch_idx)
|
| 1822 |
+
if const_expr(not seqlen.has_cu_seqlens_q):
|
| 1823 |
+
mdQaccum_cur = mdQaccum[None, head_idx, batch_idx]
|
| 1824 |
+
else:
|
| 1825 |
+
mdQaccum_cur = cute.domain_offset(
|
| 1826 |
+
(seqlen.padded_offset_q * self.tile_hdim,), mdQaccum[None, head_idx]
|
| 1827 |
+
)
|
| 1828 |
+
# ((M * K / num_wg_dQ, num_wg_dQ), num_m_blocks)
|
| 1829 |
+
gdQaccum = cute.local_tile(
|
| 1830 |
+
mdQaccum_cur,
|
| 1831 |
+
(
|
| 1832 |
+
cute.make_layout(
|
| 1833 |
+
(self.tile_m * self.tile_hdim // self.num_wg_dQ, self.num_wg_dQ)
|
| 1834 |
+
),
|
| 1835 |
+
),
|
| 1836 |
+
(None,),
|
| 1837 |
)
|
| 1838 |
+
|
| 1839 |
+
if const_expr(mdQ_semaphore is not None):
|
| 1840 |
+
# mdQ_semaphore is (num_m_blocks, cluster_size, num_head, batch) after transpose
|
| 1841 |
+
mdQ_semaphore_cur = mdQ_semaphore[None, None, head_idx, batch_idx]
|
| 1842 |
+
|
| 1843 |
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
|
| 1844 |
if const_expr(not self.use_block_sparsity):
|
| 1845 |
+
process_tile = (
|
| 1846 |
+
const_expr(not self.is_local and not self.is_varlen_q)
|
| 1847 |
+
or m_block_min < m_block_max
|
| 1848 |
+
)
|
| 1849 |
loop_count = m_block_max - m_block_min
|
| 1850 |
else:
|
| 1851 |
total_block_cnt = get_total_q_block_count_bwd(
|
|
|
|
| 1853 |
batch_idx,
|
| 1854 |
head_idx,
|
| 1855 |
n_block,
|
| 1856 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 1857 |
m_block_max=m_block_max,
|
| 1858 |
)
|
| 1859 |
process_tile = total_block_cnt > Int32(0)
|
|
|
|
| 1864 |
m_block = m_block_min + iter_idx
|
| 1865 |
m_block_safe = m_block
|
| 1866 |
|
| 1867 |
+
num_dQ_chunks = self.num_wg_dQ
|
| 1868 |
+
for warp_group_idx in cutlass.range_constexpr(num_dQ_chunks):
|
| 1869 |
+
if const_expr(not self.deterministic):
|
| 1870 |
+
# If deterministic, we already waited at the end of the prev iter
|
| 1871 |
+
cute.arch.cp_async_bulk_wait_group(
|
| 1872 |
+
num_dQ_chunks - 1 - warp_group_idx, read=read_flag
|
| 1873 |
+
)
|
| 1874 |
cute.arch.barrier_arrive(
|
| 1875 |
barrier_id=int(NamedBarrierBwd.dQEmptyWG0) + warp_group_idx,
|
| 1876 |
number_of_threads=self.num_threads_per_warp_group
|
| 1877 |
+ cute.arch.WARP_SIZE,
|
| 1878 |
)
|
| 1879 |
|
| 1880 |
+
# Semaphore acquire: wait for prior n_blocks to finish writing this m_block
|
| 1881 |
+
if const_expr(self.deterministic):
|
| 1882 |
+
if const_expr(self.spt):
|
| 1883 |
+
_, n_block_max_for_m_block = block_info.get_n_block_min_max(
|
| 1884 |
+
seqlen, m_block_safe
|
| 1885 |
+
)
|
| 1886 |
+
lock_value = n_block_max_for_m_block - 1 - n_block
|
| 1887 |
+
else:
|
| 1888 |
+
lock_value = n_block
|
| 1889 |
+
barrier.wait_eq(
|
| 1890 |
+
mdQ_semaphore_cur[(m_block_safe, None)].iterator,
|
| 1891 |
+
warp_local_tidx,
|
| 1892 |
+
0, # flag_offset
|
| 1893 |
+
lock_value,
|
| 1894 |
+
)
|
| 1895 |
+
|
| 1896 |
+
for warp_group_idx in cutlass.range_constexpr(num_dQ_chunks):
|
| 1897 |
cute.arch.barrier(
|
| 1898 |
barrier_id=int(NamedBarrierBwd.dQFullWG0) + warp_group_idx,
|
| 1899 |
number_of_threads=self.num_threads_per_warp_group
|
|
|
|
| 1902 |
with cute.arch.elect_one():
|
| 1903 |
copy_utils.cpasync_reduce_bulk_add_f32(
|
| 1904 |
sdQaccum[None, warp_group_idx].iterator,
|
| 1905 |
+
gdQaccum[(None, warp_group_idx), m_block_safe].iterator,
|
| 1906 |
self.tma_copy_bytes["dQ"],
|
| 1907 |
)
|
| 1908 |
cute.arch.cp_async_bulk_commit_group()
|
| 1909 |
+
|
| 1910 |
+
# Semaphore release: signal that this n_block is done with this m_block
|
| 1911 |
+
if const_expr(self.deterministic):
|
| 1912 |
+
cute.arch.cp_async_bulk_wait_group(0, read=read_flag)
|
| 1913 |
+
barrier.arrive_inc(
|
| 1914 |
+
mdQ_semaphore_cur[(m_block_safe, None)].iterator,
|
| 1915 |
+
warp_local_tidx,
|
| 1916 |
+
0, # flag_offset
|
| 1917 |
+
1,
|
| 1918 |
+
)
|
| 1919 |
else:
|
| 1920 |
+
assert not self.deterministic, (
|
| 1921 |
+
"Deterministic not implemented for block-sparse backward"
|
| 1922 |
+
)
|
| 1923 |
dQaccum_store_block_sparse_bwd_sm90(
|
| 1924 |
blocksparse_tensors,
|
| 1925 |
batch_idx,
|
|
|
|
| 1927 |
n_block,
|
| 1928 |
sdQaccum,
|
| 1929 |
gdQaccum,
|
| 1930 |
+
q_subtile_factor=self.q_subtile_factor,
|
| 1931 |
m_block_max=m_block_max,
|
| 1932 |
+
num_dQ_warp_groups=self.num_wg_dQ,
|
| 1933 |
num_threads_per_warp_group=self.num_threads_per_warp_group,
|
| 1934 |
tma_copy_bytes_dQ=self.tma_copy_bytes["dQ"],
|
| 1935 |
)
|
| 1936 |
+
|
| 1937 |
+
# For local masking + deterministic (non-spt): signal remaining m_blocks
|
| 1938 |
+
# that this n_block won't visit, so they don't deadlock waiting.
|
| 1939 |
+
if const_expr(
|
| 1940 |
+
self.deterministic and not self.spt and block_info.window_size_left is not None
|
| 1941 |
+
):
|
| 1942 |
+
m_block_global_max = cute.ceil_div(seqlen.seqlen_q, self.tile_m)
|
| 1943 |
+
for m_block in cutlass.range(m_block_max, m_block_global_max, unroll=1):
|
| 1944 |
+
barrier.arrive_inc(
|
| 1945 |
+
mdQ_semaphore_cur[(m_block, None)].iterator,
|
| 1946 |
+
warp_local_tidx,
|
| 1947 |
+
0, # flag_offset
|
| 1948 |
+
1,
|
| 1949 |
+
)
|
| 1950 |
+
|
| 1951 |
tile_scheduler.advance_to_next_work()
|
| 1952 |
work_tile = tile_scheduler.get_current_work()
|
| 1953 |
|
| 1954 |
+
if const_expr(not self.deterministic):
|
| 1955 |
+
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
build/torch-cuda/flash_fwd.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/torch-cuda/flash_fwd_combine.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 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
|
|
@@ -10,11 +10,18 @@ import cuda.bindings.driver as cuda
|
|
| 10 |
import cutlass
|
| 11 |
import cutlass.cute as cute
|
| 12 |
from cutlass.cute.nvgpu import cpasync
|
| 13 |
-
from cutlass import Float32, Int32, const_expr
|
|
|
|
|
|
|
| 14 |
|
| 15 |
from . import utils
|
| 16 |
from .cute_dsl_utils import assume_tensor_aligned
|
| 17 |
from .seqlen_info import SeqlenInfo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
from cutlass.cute import FastDivmodDivisor
|
| 19 |
|
| 20 |
|
|
@@ -24,7 +31,8 @@ class FlashAttentionForwardCombine:
|
|
| 24 |
dtype: Type[cutlass.Numeric],
|
| 25 |
dtype_partial: Type[cutlass.Numeric],
|
| 26 |
head_dim: int,
|
| 27 |
-
|
|
|
|
| 28 |
k_block_size: int = 64,
|
| 29 |
log_max_splits: int = 4,
|
| 30 |
num_threads: int = 256,
|
|
@@ -36,7 +44,8 @@ class FlashAttentionForwardCombine:
|
|
| 36 |
:param dtype: output data type
|
| 37 |
:param dtype_partial: partial accumulation data type
|
| 38 |
:param head_dim: head dimension
|
| 39 |
-
:param
|
|
|
|
| 40 |
:param k_block_size: k block size
|
| 41 |
:param log_max_splits: log2 of maximum splits
|
| 42 |
:param num_threads: number of threads
|
|
@@ -46,7 +55,8 @@ class FlashAttentionForwardCombine:
|
|
| 46 |
self.dtype = dtype
|
| 47 |
self.dtype_partial = dtype_partial
|
| 48 |
self.head_dim = head_dim
|
| 49 |
-
self.
|
|
|
|
| 50 |
self.k_block_size = k_block_size
|
| 51 |
self.max_splits = 1 << log_max_splits
|
| 52 |
self.num_threads = num_threads
|
|
@@ -58,7 +68,7 @@ class FlashAttentionForwardCombine:
|
|
| 58 |
dtype,
|
| 59 |
dtype_partial,
|
| 60 |
head_dim,
|
| 61 |
-
|
| 62 |
k_block_size,
|
| 63 |
log_max_splits,
|
| 64 |
num_threads,
|
|
@@ -72,12 +82,12 @@ class FlashAttentionForwardCombine:
|
|
| 72 |
return False
|
| 73 |
if num_threads % 32 != 0:
|
| 74 |
return False
|
| 75 |
-
if
|
| 76 |
return False
|
| 77 |
max_splits = 1 << log_max_splits
|
| 78 |
if max_splits > 256:
|
| 79 |
return False
|
| 80 |
-
if (
|
| 81 |
return False
|
| 82 |
return True
|
| 83 |
|
|
@@ -124,15 +134,11 @@ class FlashAttentionForwardCombine:
|
|
| 124 |
lse_copy_bits = Float32.width # 1 element per copy, width is in bits
|
| 125 |
m_block_smem = (
|
| 126 |
128
|
| 127 |
-
if self.
|
| 128 |
else (
|
| 129 |
64
|
| 130 |
-
if self.
|
| 131 |
-
else (
|
| 132 |
-
32
|
| 133 |
-
if self.m_block_size % 32 == 0
|
| 134 |
-
else (16 if self.m_block_size % 16 == 0 else 8)
|
| 135 |
-
)
|
| 136 |
)
|
| 137 |
)
|
| 138 |
gmem_threads_per_row_lse = m_block_smem
|
|
@@ -183,12 +189,12 @@ class FlashAttentionForwardCombine:
|
|
| 183 |
smem_lse_swizzle, 0, cute.make_ordered_layout((8, m_block_smem), order=(1, 0))
|
| 184 |
)
|
| 185 |
self.smem_layout_lse = cute.tile_to_shape(
|
| 186 |
-
smem_layout_atom_lse, (self.max_splits, self.
|
| 187 |
)
|
| 188 |
|
| 189 |
# O partial shared memory layout (simple layout for pipeline stages)
|
| 190 |
self.smem_layout_o = cute.make_ordered_layout(
|
| 191 |
-
(self.
|
| 192 |
)
|
| 193 |
|
| 194 |
@cute.jit
|
|
@@ -201,7 +207,9 @@ class FlashAttentionForwardCombine:
|
|
| 201 |
cu_seqlens: Optional[cute.Tensor] = None,
|
| 202 |
seqused: Optional[cute.Tensor] = None,
|
| 203 |
num_splits_dynamic_ptr: Optional[cute.Tensor] = None,
|
|
|
|
| 204 |
semaphore_to_reset: Optional[cute.Tensor] = None,
|
|
|
|
| 205 |
stream: cuda.CUstream = None,
|
| 206 |
):
|
| 207 |
# Type checking
|
|
@@ -269,7 +277,7 @@ class FlashAttentionForwardCombine:
|
|
| 269 |
sLSE: cute.struct.Align[
|
| 270 |
cute.struct.MemRange[Float32, cute.cosize(self.smem_layout_lse)], 128
|
| 271 |
]
|
| 272 |
-
sMaxValidSplit: cute.struct.Align[cute.struct.MemRange[Int32, self.
|
| 273 |
sO: cute.struct.Align[
|
| 274 |
cute.struct.MemRange[self.dtype_partial, cute.cosize(self.smem_layout_o)], 128
|
| 275 |
]
|
|
@@ -289,11 +297,29 @@ class FlashAttentionForwardCombine:
|
|
| 289 |
seqlen_divmod = FastDivmodDivisor(seqlen)
|
| 290 |
head_divmod = FastDivmodDivisor(num_head)
|
| 291 |
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
)
|
|
|
|
|
|
|
| 297 |
|
| 298 |
self.kernel(
|
| 299 |
mO_partial,
|
|
@@ -303,6 +329,7 @@ class FlashAttentionForwardCombine:
|
|
| 303 |
cu_seqlens,
|
| 304 |
seqused,
|
| 305 |
num_splits_dynamic_ptr,
|
|
|
|
| 306 |
semaphore_to_reset,
|
| 307 |
SharedStorage,
|
| 308 |
self.smem_layout_lse,
|
|
@@ -314,6 +341,8 @@ class FlashAttentionForwardCombine:
|
|
| 314 |
seqlen_divmod,
|
| 315 |
head_divmod,
|
| 316 |
varlen,
|
|
|
|
|
|
|
| 317 |
).launch(
|
| 318 |
grid=grid_dim,
|
| 319 |
block=[self.num_threads, 1, 1],
|
|
@@ -331,6 +360,7 @@ class FlashAttentionForwardCombine:
|
|
| 331 |
cu_seqlens: Optional[cute.Tensor],
|
| 332 |
seqused: Optional[cute.Tensor],
|
| 333 |
num_splits_dynamic_ptr: Optional[cute.Tensor],
|
|
|
|
| 334 |
semaphore_to_reset: Optional[cute.Tensor],
|
| 335 |
SharedStorage: cutlass.Constexpr,
|
| 336 |
smem_layout_lse: cute.Layout | cute.ComposedLayout,
|
|
@@ -342,10 +372,21 @@ class FlashAttentionForwardCombine:
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 351 |
# Get shared memory buffer
|
|
@@ -353,310 +394,315 @@ class FlashAttentionForwardCombine:
|
|
| 353 |
smem = cutlass.utils.SmemAllocator()
|
| 354 |
storage = smem.allocate(SharedStorage)
|
| 355 |
sLSE = storage.sLSE.get_tensor(smem_layout_lse)
|
| 356 |
-
sMaxValidSplit = storage.sMaxValidSplit.get_tensor((self.
|
| 357 |
sO = storage.sO.get_tensor(smem_layout_o)
|
| 358 |
|
| 359 |
-
# Handle semaphore reset
|
| 360 |
if const_expr(semaphore_to_reset is not None):
|
|
|
|
| 361 |
if (
|
| 362 |
tidx == 0
|
| 363 |
-
and
|
| 364 |
-
and
|
| 365 |
-
and
|
| 366 |
):
|
|
|
|
| 367 |
semaphore_to_reset[0] = 0
|
| 368 |
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
# Extract number of heads (head index will be determined dynamically)
|
| 385 |
-
num_head = mO_partial.shape[3]
|
| 386 |
-
max_idx = seqlen * num_head
|
| 387 |
-
|
| 388 |
-
# Early exit for single split if dynamic
|
| 389 |
-
if (const_expr(num_splits_dynamic_ptr is None) or num_splits > 1) and (
|
| 390 |
-
const_expr(not varlen) or m_block * self.m_block_size < max_idx
|
| 391 |
-
):
|
| 392 |
-
# ===============================
|
| 393 |
-
# Step 1: Load LSE_partial from gmem to shared memory
|
| 394 |
-
# ===============================
|
| 395 |
-
|
| 396 |
-
if const_expr(cu_seqlens is None):
|
| 397 |
-
mLSE_partial_cur = mLSE_partial[None, None, None, batch_idx]
|
| 398 |
-
else:
|
| 399 |
-
mLSE_partial_cur = cute.domain_offset((offset, 0, 0), mLSE_partial)
|
| 400 |
-
mLSE_partial_copy = cute.tiled_divide(mLSE_partial_cur, (1,))
|
| 401 |
-
|
| 402 |
-
gmem_thr_copy_LSE = gmem_tiled_copy_LSE.get_slice(tidx)
|
| 403 |
-
tLSEsLSE = gmem_thr_copy_LSE.partition_D(sLSE)
|
| 404 |
-
|
| 405 |
-
# Create identity tensor for coordinate tracking
|
| 406 |
-
cLSE = cute.make_identity_tensor((self.max_splits, self.m_block_size))
|
| 407 |
-
tLSEcLSE = gmem_thr_copy_LSE.partition_S(cLSE)
|
| 408 |
-
|
| 409 |
-
# Load LSE partial values
|
| 410 |
-
for m in cutlass.range(cute.size(tLSEcLSE, mode=[2]), unroll_full=True):
|
| 411 |
-
mi = tLSEcLSE[0, 0, m][1] # Get m coordinate
|
| 412 |
-
idx = m_block * self.m_block_size + mi
|
| 413 |
-
if idx < max_idx:
|
| 414 |
-
# Calculate actual sequence position and head using FastDivmodDivisor
|
| 415 |
-
if const_expr(not varlen):
|
| 416 |
-
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 417 |
-
else:
|
| 418 |
-
head_idx = idx // seqlen
|
| 419 |
-
m_idx = idx - head_idx * seqlen
|
| 420 |
-
mLSE_partial_cur_copy = mLSE_partial_copy[None, m_idx, None, head_idx]
|
| 421 |
-
for s in cutlass.range(cute.size(tLSEcLSE, mode=[1]), unroll_full=True):
|
| 422 |
-
si = tLSEcLSE[0, s, 0][0] # Get split coordinate
|
| 423 |
-
if si < num_splits:
|
| 424 |
-
cute.copy(
|
| 425 |
-
gmem_thr_copy_LSE,
|
| 426 |
-
mLSE_partial_cur_copy[None, si],
|
| 427 |
-
tLSEsLSE[None, s, m],
|
| 428 |
-
)
|
| 429 |
-
else:
|
| 430 |
-
tLSEsLSE[None, s, m].fill(-Float32.inf)
|
| 431 |
-
# Don't need to zero out the rest of the LSEs, as we will not write the output to gmem
|
| 432 |
-
cute.arch.cp_async_commit_group()
|
| 433 |
-
|
| 434 |
-
# ===============================
|
| 435 |
-
# Step 2: Load O_partial for pipeline stages
|
| 436 |
-
# ===============================
|
| 437 |
-
|
| 438 |
-
gmem_thr_copy_O_partial = gmem_tiled_copy_O_partial.get_slice(tidx)
|
| 439 |
-
cO = cute.make_identity_tensor((self.m_block_size, self.k_block_size))
|
| 440 |
-
tOcO = gmem_thr_copy_O_partial.partition_D(cO)
|
| 441 |
-
tOsO_partial = gmem_thr_copy_O_partial.partition_D(sO)
|
| 442 |
-
if const_expr(cu_seqlens is None):
|
| 443 |
-
mO_partial_cur = mO_partial[None, None, None, None, batch_idx]
|
| 444 |
-
else:
|
| 445 |
-
mO_partial_cur = cute.domain_offset((offset, 0, 0, 0), mO_partial)
|
| 446 |
-
|
| 447 |
-
# Precompute these values to avoid recomputing them in the loop
|
| 448 |
-
num_rows = const_expr(cute.size(tOcO, mode=[1]))
|
| 449 |
-
tOmidx = cute.make_fragment(num_rows, cutlass.Int32)
|
| 450 |
-
tOhidx = cute.make_fragment(num_rows, cutlass.Int32)
|
| 451 |
-
tOrOptr = cute.make_fragment(num_rows, cutlass.Int64)
|
| 452 |
-
for m in cutlass.range(num_rows, unroll_full=True):
|
| 453 |
-
mi = tOcO[0, m, 0][0] # m coordinate
|
| 454 |
-
idx = m_block * self.m_block_size + mi
|
| 455 |
-
if const_expr(not varlen):
|
| 456 |
-
tOhidx[m], tOmidx[m] = divmod(idx, seqlen_divmod)
|
| 457 |
-
else:
|
| 458 |
-
tOhidx[m] = idx // seqlen
|
| 459 |
-
tOmidx[m] = idx - tOhidx[m] * seqlen
|
| 460 |
-
tOrOptr[m] = utils.elem_pointer(
|
| 461 |
-
mO_partial_cur, (tOmidx[m], k_block * self.k_block_size, 0, tOhidx[m])
|
| 462 |
-
).toint()
|
| 463 |
-
if idx >= max_idx:
|
| 464 |
-
tOhidx[m] = -1
|
| 465 |
-
|
| 466 |
-
tOpO = cute.make_fragment(cute.size(tOcO, [2]), cutlass.Boolean)
|
| 467 |
-
if const_expr(not self.is_even_k):
|
| 468 |
-
for k in cutlass.range(cute.size(tOpO), unroll_full=True):
|
| 469 |
-
tOpO[k] = tOcO[0, 0, k][1] < mO_partial.shape[1] - k_block * self.k_block_size
|
| 470 |
-
# if cute.arch.thread_idx()[0] == 0 and k_block == 1: cute.print_tensor(tOpO)
|
| 471 |
-
|
| 472 |
-
load_O_partial = partial(
|
| 473 |
-
self.load_O_partial,
|
| 474 |
-
gmem_tiled_copy_O_partial,
|
| 475 |
-
tOrOptr,
|
| 476 |
-
tOsO_partial,
|
| 477 |
-
tOhidx,
|
| 478 |
-
tOpO,
|
| 479 |
-
tOcO,
|
| 480 |
-
mO_partial_cur.layout,
|
| 481 |
)
|
|
|
|
| 482 |
|
| 483 |
-
#
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
cute.arch.cp_async_commit_group()
|
| 488 |
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
ts2rrLSE = cute.make_fragment_like(ts2rsLSE)
|
| 505 |
-
cute.copy(s2r_tiled_copy_LSE, ts2rsLSE, ts2rrLSE)
|
| 506 |
-
|
| 507 |
-
# ===============================
|
| 508 |
-
# Step 4: Compute final LSE along split dimension
|
| 509 |
-
# ===============================
|
| 510 |
-
|
| 511 |
-
lse_sum = cute.make_fragment(cute.size(ts2rrLSE, mode=[2]), Float32)
|
| 512 |
-
ts2rcLSE = s2r_thr_copy_LSE.partition_D(cLSE)
|
| 513 |
-
# We compute the max valid split for each row to short-circuit the computation later
|
| 514 |
-
max_valid_split = cute.make_fragment(cute.size(ts2rrLSE, mode=[2]), Int32)
|
| 515 |
-
assert cute.size(ts2rrLSE, mode=[0]) == 1
|
| 516 |
-
# Compute max, scales, and final LSE for each row
|
| 517 |
-
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 518 |
-
# Find max LSE value across splits
|
| 519 |
-
threads_per_col = const_expr(self.smem_threads_per_col_lse)
|
| 520 |
-
lse_max = cute.arch.warp_reduction_max(
|
| 521 |
-
ts2rrLSE[None, None, m]
|
| 522 |
-
.load()
|
| 523 |
-
.reduce(cute.ReductionOp.MAX, init_val=-Float32.inf, reduction_profile=0),
|
| 524 |
-
threads_in_group=threads_per_col,
|
| 525 |
-
)
|
| 526 |
-
# if cute.arch.thread_idx()[0] == 0: cute.printf(lse_max)
|
| 527 |
-
# Find max valid split index
|
| 528 |
-
max_valid_idx = -1
|
| 529 |
-
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 530 |
-
if ts2rrLSE[0, s, m] != -Float32.inf:
|
| 531 |
-
max_valid_idx = ts2rcLSE[0, s, 0][0] # Get split coordinate
|
| 532 |
-
# if cute.arch.thread_idx()[0] < 32: cute.printf(max_valid_idx)
|
| 533 |
-
max_valid_split[m] = cute.arch.warp_reduction_max(
|
| 534 |
-
max_valid_idx, threads_in_group=threads_per_col
|
| 535 |
-
)
|
| 536 |
-
# Compute exp scales and sum
|
| 537 |
-
lse_max_cur = (
|
| 538 |
-
0.0 if lse_max == -Float32.inf else lse_max
|
| 539 |
-
) # In case all local LSEs are -inf
|
| 540 |
-
LOG2_E = math.log2(math.e)
|
| 541 |
-
lse_sum_cur = 0.0
|
| 542 |
-
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 543 |
-
scale = cute.math.exp2(
|
| 544 |
-
ts2rrLSE[0, s, m] * LOG2_E - (lse_max_cur * LOG2_E), fastmath=True
|
| 545 |
-
)
|
| 546 |
-
lse_sum_cur += scale
|
| 547 |
-
ts2rrLSE[0, s, m] = scale # Store scale for later use
|
| 548 |
-
lse_sum_cur = cute.arch.warp_reduction_sum(
|
| 549 |
-
lse_sum_cur, threads_in_group=threads_per_col
|
| 550 |
-
)
|
| 551 |
-
lse_sum[m] = cute.math.log(lse_sum_cur, fastmath=True) + lse_max
|
| 552 |
-
# Normalize scales
|
| 553 |
-
inv_sum = (
|
| 554 |
-
0.0 if (lse_sum_cur == 0.0 or lse_sum_cur != lse_sum_cur) else 1.0 / lse_sum_cur
|
| 555 |
-
)
|
| 556 |
-
ts2rrLSE[None, None, m].store(ts2rrLSE[None, None, m].load() * inv_sum)
|
| 557 |
-
# Store the scales exp(lse - lse_logsum) back to smem
|
| 558 |
-
cute.copy(s2r_tiled_copy_LSE, ts2rrLSE, ts2rsLSE)
|
| 559 |
-
|
| 560 |
-
# Store max valid split to smem
|
| 561 |
-
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 562 |
-
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 563 |
-
mi = ts2rcLSE[0, 0, m][1]
|
| 564 |
-
if mi < self.m_block_size:
|
| 565 |
-
sMaxValidSplit[mi] = max_valid_split[m]
|
| 566 |
-
|
| 567 |
-
# ===============================
|
| 568 |
-
# Step 5: Store final LSE to gmem
|
| 569 |
-
# ===============================
|
| 570 |
-
|
| 571 |
-
if const_expr(mLSE is not None):
|
| 572 |
-
if const_expr(cu_seqlens is None):
|
| 573 |
-
mLSE_cur = mLSE[None, None, batch_idx]
|
| 574 |
-
else:
|
| 575 |
-
mLSE_cur = cute.domain_offset((offset, 0), mLSE)
|
| 576 |
-
if k_block == 0: # Only first k_block writes LSE when mLSE is provided
|
| 577 |
-
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 578 |
-
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 579 |
-
mi = ts2rcLSE[0, 0, m][1]
|
| 580 |
-
idx = m_block * self.m_block_size + mi
|
| 581 |
-
if idx < max_idx:
|
| 582 |
-
if const_expr(not varlen):
|
| 583 |
-
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 584 |
-
else:
|
| 585 |
-
head_idx = idx // seqlen
|
| 586 |
-
m_idx = idx - head_idx * seqlen
|
| 587 |
-
mLSE_cur[m_idx, head_idx] = lse_sum[m]
|
| 588 |
-
|
| 589 |
-
# ===============================
|
| 590 |
-
# Step 6: Read O_partial and accumulate final O
|
| 591 |
-
# ===============================
|
| 592 |
-
|
| 593 |
-
cute.arch.sync_threads()
|
| 594 |
-
|
| 595 |
-
# Get max valid split for this thread
|
| 596 |
-
thr_max_valid_split = sMaxValidSplit[tOcO[0, 0, 0][0]]
|
| 597 |
-
for m in cutlass.range(1, cute.size(tOcO, mode=[1])):
|
| 598 |
-
thr_max_valid_split = max(thr_max_valid_split, sMaxValidSplit[tOcO[0, m, 0][0]])
|
| 599 |
-
|
| 600 |
-
tOrO_partial = cute.make_fragment_like(tOsO_partial[None, None, None, 0])
|
| 601 |
-
tOrO = cute.make_fragment_like(tOrO_partial, Float32)
|
| 602 |
-
tOrO.fill(0.0)
|
| 603 |
-
|
| 604 |
-
stage_load = self.stages - 1
|
| 605 |
-
stage_compute = 0
|
| 606 |
-
|
| 607 |
-
# Main accumulation loop
|
| 608 |
-
for s in cutlass.range(thr_max_valid_split + 1, unroll=4):
|
| 609 |
-
# Get scales for this split
|
| 610 |
-
scale = cute.make_fragment(num_rows, Float32)
|
| 611 |
for m in cutlass.range(num_rows, unroll_full=True):
|
| 612 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
|
| 614 |
-
# Load
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
|
|
|
|
|
|
|
|
|
| 620 |
|
| 621 |
-
# Wait for
|
| 622 |
cute.arch.cp_async_wait_group(self.stages - 1)
|
| 623 |
-
|
| 624 |
-
#
|
| 625 |
-
cute.
|
| 626 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
|
| 628 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
for m in cutlass.range(num_rows, unroll_full=True):
|
| 630 |
-
if tOhidx[m] >= 0
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
+ scale[m] * tOrO_partial[None, m, None].load().to(Float32)
|
| 634 |
)
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
rO = cute.make_fragment_like(tOrO, self.dtype)
|
| 641 |
-
rO.store(tOrO.load().to(self.dtype))
|
| 642 |
-
if const_expr(cu_seqlens is None):
|
| 643 |
-
mO_cur = mO[None, None, None, batch_idx]
|
| 644 |
-
else:
|
| 645 |
-
mO_cur = cute.domain_offset((offset, 0, 0), mO)
|
| 646 |
-
mO_cur = utils.domain_offset_aligned((0, k_block * self.k_block_size, 0), mO_cur)
|
| 647 |
-
elems_per_store = const_expr(cute.size(gmem_tiled_copy_O.layout_tv_tiled[1]))
|
| 648 |
-
# mO_cur_copy = cute.tiled_divide(mO_cur, (1, elems_per_store,))
|
| 649 |
-
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 650 |
-
# Write final results
|
| 651 |
-
for m in cutlass.range(num_rows, unroll_full=True):
|
| 652 |
-
if tOhidx[m] >= 0:
|
| 653 |
-
mO_cur_copy = cute.tiled_divide(
|
| 654 |
-
mO_cur[tOmidx[m], None, tOhidx[m]], (elems_per_store,)
|
| 655 |
-
)
|
| 656 |
-
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 657 |
-
k_idx = tOcO[0, 0, k][1] // elems_per_store
|
| 658 |
-
if const_expr(self.is_even_k) or tOpO[k]:
|
| 659 |
-
cute.copy(gmem_thr_copy_O, rO[None, m, k], mO_cur_copy[None, k_idx])
|
| 660 |
|
| 661 |
@cute.jit
|
| 662 |
def load_O_partial(
|
|
@@ -665,7 +711,7 @@ class FlashAttentionForwardCombine:
|
|
| 665 |
tOrOptr: cute.Tensor,
|
| 666 |
tOsO_partial: cute.Tensor,
|
| 667 |
tOhidx: cute.Tensor,
|
| 668 |
-
tOpO: cute.Tensor,
|
| 669 |
tOcO: cute.Tensor,
|
| 670 |
mO_cur_partial_layout: cute.Layout,
|
| 671 |
split: Int32,
|
|
@@ -684,7 +730,7 @@ class FlashAttentionForwardCombine:
|
|
| 684 |
mO_partial_cur_copy = cute.tiled_divide(mO_partial_cur, (elems_per_load,))
|
| 685 |
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 686 |
k_idx = tOcO[0, 0, k][1] // elems_per_load
|
| 687 |
-
if const_expr(
|
| 688 |
cute.copy(
|
| 689 |
gmem_tiled_copy_O_partial,
|
| 690 |
mO_partial_cur_copy[None, k_idx, split],
|
|
|
|
| 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 Callable, Type, Optional
|
| 6 |
from functools import partial
|
| 7 |
|
| 8 |
import cuda.bindings.driver as cuda
|
|
|
|
| 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 .quack.cute_dsl_utils import ParamsBase
|
| 16 |
|
| 17 |
from . import utils
|
| 18 |
from .cute_dsl_utils import assume_tensor_aligned
|
| 19 |
from .seqlen_info import SeqlenInfo
|
| 20 |
+
from .tile_scheduler import (
|
| 21 |
+
SingleTileScheduler,
|
| 22 |
+
SingleTileVarlenScheduler,
|
| 23 |
+
TileSchedulerArguments,
|
| 24 |
+
)
|
| 25 |
from cutlass.cute import FastDivmodDivisor
|
| 26 |
|
| 27 |
|
|
|
|
| 31 |
dtype: Type[cutlass.Numeric],
|
| 32 |
dtype_partial: Type[cutlass.Numeric],
|
| 33 |
head_dim: int,
|
| 34 |
+
num_head: int,
|
| 35 |
+
tile_m: int = 8,
|
| 36 |
k_block_size: int = 64,
|
| 37 |
log_max_splits: int = 4,
|
| 38 |
num_threads: int = 256,
|
|
|
|
| 44 |
:param dtype: output data type
|
| 45 |
:param dtype_partial: partial accumulation data type
|
| 46 |
:param head_dim: head dimension
|
| 47 |
+
:param num_head: number of heads
|
| 48 |
+
:param tile_m: m block size
|
| 49 |
:param k_block_size: k block size
|
| 50 |
:param log_max_splits: log2 of maximum splits
|
| 51 |
:param num_threads: number of threads
|
|
|
|
| 55 |
self.dtype = dtype
|
| 56 |
self.dtype_partial = dtype_partial
|
| 57 |
self.head_dim = head_dim
|
| 58 |
+
self.num_head = num_head
|
| 59 |
+
self.tile_m = tile_m
|
| 60 |
self.k_block_size = k_block_size
|
| 61 |
self.max_splits = 1 << log_max_splits
|
| 62 |
self.num_threads = num_threads
|
|
|
|
| 68 |
dtype,
|
| 69 |
dtype_partial,
|
| 70 |
head_dim,
|
| 71 |
+
tile_m,
|
| 72 |
k_block_size,
|
| 73 |
log_max_splits,
|
| 74 |
num_threads,
|
|
|
|
| 82 |
return False
|
| 83 |
if num_threads % 32 != 0:
|
| 84 |
return False
|
| 85 |
+
if tile_m % 8 != 0:
|
| 86 |
return False
|
| 87 |
max_splits = 1 << log_max_splits
|
| 88 |
if max_splits > 256:
|
| 89 |
return False
|
| 90 |
+
if (tile_m * max_splits) % num_threads != 0:
|
| 91 |
return False
|
| 92 |
return True
|
| 93 |
|
|
|
|
| 134 |
lse_copy_bits = Float32.width # 1 element per copy, width is in bits
|
| 135 |
m_block_smem = (
|
| 136 |
128
|
| 137 |
+
if self.tile_m % 128 == 0
|
| 138 |
else (
|
| 139 |
64
|
| 140 |
+
if self.tile_m % 64 == 0
|
| 141 |
+
else (32 if self.tile_m % 32 == 0 else (16 if self.tile_m % 16 == 0 else 8))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
)
|
| 143 |
)
|
| 144 |
gmem_threads_per_row_lse = m_block_smem
|
|
|
|
| 189 |
smem_lse_swizzle, 0, cute.make_ordered_layout((8, m_block_smem), order=(1, 0))
|
| 190 |
)
|
| 191 |
self.smem_layout_lse = cute.tile_to_shape(
|
| 192 |
+
smem_layout_atom_lse, (self.max_splits, self.tile_m), (0, 1)
|
| 193 |
)
|
| 194 |
|
| 195 |
# O partial shared memory layout (simple layout for pipeline stages)
|
| 196 |
self.smem_layout_o = cute.make_ordered_layout(
|
| 197 |
+
(self.tile_m, self.k_block_size, self.stages), order=(1, 0, 2)
|
| 198 |
)
|
| 199 |
|
| 200 |
@cute.jit
|
|
|
|
| 207 |
cu_seqlens: Optional[cute.Tensor] = None,
|
| 208 |
seqused: Optional[cute.Tensor] = None,
|
| 209 |
num_splits_dynamic_ptr: Optional[cute.Tensor] = None,
|
| 210 |
+
virtual_batch_idx: Optional[cute.Tensor] = None,
|
| 211 |
semaphore_to_reset: Optional[cute.Tensor] = None,
|
| 212 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 213 |
stream: cuda.CUstream = None,
|
| 214 |
):
|
| 215 |
# Type checking
|
|
|
|
| 277 |
sLSE: cute.struct.Align[
|
| 278 |
cute.struct.MemRange[Float32, cute.cosize(self.smem_layout_lse)], 128
|
| 279 |
]
|
| 280 |
+
sMaxValidSplit: cute.struct.Align[cute.struct.MemRange[Int32, self.tile_m], 128]
|
| 281 |
sO: cute.struct.Align[
|
| 282 |
cute.struct.MemRange[self.dtype_partial, cute.cosize(self.smem_layout_o)], 128
|
| 283 |
]
|
|
|
|
| 297 |
seqlen_divmod = FastDivmodDivisor(seqlen)
|
| 298 |
head_divmod = FastDivmodDivisor(num_head)
|
| 299 |
|
| 300 |
+
if const_expr(varlen):
|
| 301 |
+
TileScheduler = SingleTileVarlenScheduler
|
| 302 |
+
else:
|
| 303 |
+
TileScheduler = SingleTileScheduler
|
| 304 |
+
tile_sched_args = TileSchedulerArguments(
|
| 305 |
+
num_block=cute.ceil_div(seqlen * num_head, self.tile_m),
|
| 306 |
+
num_head=cute.ceil_div(self.head_dim, self.k_block_size),
|
| 307 |
+
num_batch=batch_size,
|
| 308 |
+
num_splits=1,
|
| 309 |
+
seqlen_k=1,
|
| 310 |
+
headdim=1,
|
| 311 |
+
headdim_v=1,
|
| 312 |
+
total_q=mO_partial.shape[0] * num_head
|
| 313 |
+
if const_expr(cu_seqlens is not None)
|
| 314 |
+
else seqlen * batch_size * num_head,
|
| 315 |
+
tile_shape_mn=(self.tile_m, self.tile_m),
|
| 316 |
+
mCuSeqlensQ=cu_seqlens,
|
| 317 |
+
mSeqUsedQ=seqused,
|
| 318 |
+
qhead_per_kvhead_packgqa=self.num_head,
|
| 319 |
+
virtual_batch_idx_ptr=virtual_batch_idx,
|
| 320 |
)
|
| 321 |
+
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 322 |
+
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 323 |
|
| 324 |
self.kernel(
|
| 325 |
mO_partial,
|
|
|
|
| 329 |
cu_seqlens,
|
| 330 |
seqused,
|
| 331 |
num_splits_dynamic_ptr,
|
| 332 |
+
virtual_batch_idx,
|
| 333 |
semaphore_to_reset,
|
| 334 |
SharedStorage,
|
| 335 |
self.smem_layout_lse,
|
|
|
|
| 341 |
seqlen_divmod,
|
| 342 |
head_divmod,
|
| 343 |
varlen,
|
| 344 |
+
tile_sched_params,
|
| 345 |
+
TileScheduler,
|
| 346 |
).launch(
|
| 347 |
grid=grid_dim,
|
| 348 |
block=[self.num_threads, 1, 1],
|
|
|
|
| 360 |
cu_seqlens: Optional[cute.Tensor],
|
| 361 |
seqused: Optional[cute.Tensor],
|
| 362 |
num_splits_dynamic_ptr: Optional[cute.Tensor],
|
| 363 |
+
virtual_batch_idx: Optional[cute.Tensor],
|
| 364 |
semaphore_to_reset: Optional[cute.Tensor],
|
| 365 |
SharedStorage: cutlass.Constexpr,
|
| 366 |
smem_layout_lse: cute.Layout | cute.ComposedLayout,
|
|
|
|
| 372 |
seqlen_divmod: FastDivmodDivisor,
|
| 373 |
head_divmod: FastDivmodDivisor,
|
| 374 |
varlen: cutlass.Constexpr[bool],
|
| 375 |
+
tile_sched_params: ParamsBase,
|
| 376 |
+
TileScheduler: cutlass.Constexpr[Callable],
|
| 377 |
):
|
| 378 |
# Thread and block indices
|
| 379 |
tidx, _, _ = cute.arch.thread_idx()
|
| 380 |
+
tile_scheduler = TileScheduler.create(tile_sched_params)
|
| 381 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 382 |
+
m_block, k_block, maybe_virtual_batch, _ = work_tile.tile_idx
|
| 383 |
+
|
| 384 |
+
# Map virtual batch index to real batch index (for persistent tile schedulers)
|
| 385 |
+
batch_idx = (
|
| 386 |
+
virtual_batch_idx[maybe_virtual_batch]
|
| 387 |
+
if const_expr(virtual_batch_idx is not None and not varlen)
|
| 388 |
+
else maybe_virtual_batch
|
| 389 |
+
)
|
| 390 |
|
| 391 |
# ///////////////////////////////////////////////////////////////////////////////
|
| 392 |
# Get shared memory buffer
|
|
|
|
| 394 |
smem = cutlass.utils.SmemAllocator()
|
| 395 |
storage = smem.allocate(SharedStorage)
|
| 396 |
sLSE = storage.sLSE.get_tensor(smem_layout_lse)
|
| 397 |
+
sMaxValidSplit = storage.sMaxValidSplit.get_tensor((self.tile_m,))
|
| 398 |
sO = storage.sO.get_tensor(smem_layout_o)
|
| 399 |
|
| 400 |
+
# Handle semaphore reset — wait for dependent grids first
|
| 401 |
if const_expr(semaphore_to_reset is not None):
|
| 402 |
+
bidx, bidy, bidz = cute.arch.block_idx()
|
| 403 |
if (
|
| 404 |
tidx == 0
|
| 405 |
+
and bidx == cute.arch.grid_dim()[0] - 1
|
| 406 |
+
and bidy == cute.arch.grid_dim()[1] - 1
|
| 407 |
+
and bidz == cute.arch.grid_dim()[2] - 1
|
| 408 |
):
|
| 409 |
+
cute.arch.griddepcontrol_wait()
|
| 410 |
semaphore_to_reset[0] = 0
|
| 411 |
|
| 412 |
+
if work_tile.is_valid_tile:
|
| 413 |
+
# Get number of splits (use maybe_virtual_batch for per-batch-slot splits)
|
| 414 |
+
num_splits = (
|
| 415 |
+
num_splits_dynamic_ptr[maybe_virtual_batch]
|
| 416 |
+
if const_expr(num_splits_dynamic_ptr is not None)
|
| 417 |
+
else mLSE_partial.shape[1]
|
| 418 |
+
)
|
| 419 |
+
# Handle variable length sequences using SeqlenInfo
|
| 420 |
+
seqlen_info = SeqlenInfo.create(
|
| 421 |
+
batch_idx=batch_idx,
|
| 422 |
+
seqlen_static=mO_partial.shape[0],
|
| 423 |
+
cu_seqlens=cu_seqlens,
|
| 424 |
+
seqused=seqused,
|
| 425 |
+
# Don't need to pass in tile size since we won't use offset_padded
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 426 |
)
|
| 427 |
+
seqlen, offset = seqlen_info.seqlen, seqlen_info.offset
|
| 428 |
|
| 429 |
+
# Extract number of heads (head index will be determined dynamically)
|
| 430 |
+
num_head = mO_partial.shape[3]
|
| 431 |
+
max_idx = seqlen * num_head
|
| 432 |
+
|
| 433 |
+
# TODO: early exit for single split if dynamic — for now always merge so the
|
| 434 |
+
# num_splits_dynamic == 1 case still writes mO from mO_partial[0].
|
| 435 |
+
if (const_expr(num_splits_dynamic_ptr is None) or num_splits > 0) and (
|
| 436 |
+
const_expr(not varlen) or m_block * self.tile_m < max_idx
|
| 437 |
+
):
|
| 438 |
+
# Wait for dependent grids (e.g., the main attention kernel that produces O_partial/LSE_partial)
|
| 439 |
+
cute.arch.griddepcontrol_wait()
|
| 440 |
+
|
| 441 |
+
# ===============================
|
| 442 |
+
# Step 1: Load LSE_partial from gmem to shared memory
|
| 443 |
+
# ===============================
|
| 444 |
+
|
| 445 |
+
mLSE_partial_cur = seqlen_info.offset_batch(mLSE_partial, batch_idx, dim=3)
|
| 446 |
+
mLSE_partial_copy = cute.tiled_divide(mLSE_partial_cur, (1,))
|
| 447 |
+
gmem_thr_copy_LSE = gmem_tiled_copy_LSE.get_slice(tidx)
|
| 448 |
+
tLSEsLSE = gmem_thr_copy_LSE.partition_D(sLSE)
|
| 449 |
+
# Create identity tensor for coordinate tracking
|
| 450 |
+
cLSE = cute.make_identity_tensor((self.max_splits, self.tile_m))
|
| 451 |
+
tLSEcLSE = gmem_thr_copy_LSE.partition_S(cLSE)
|
| 452 |
+
|
| 453 |
+
# Load LSE partial values
|
| 454 |
+
for m in cutlass.range(cute.size(tLSEcLSE, mode=[2]), unroll_full=True):
|
| 455 |
+
mi = tLSEcLSE[0, 0, m][1] # Get m coordinate
|
| 456 |
+
idx = m_block * self.tile_m + mi
|
| 457 |
+
if idx < max_idx:
|
| 458 |
+
# Calculate actual sequence position and head using FastDivmodDivisor
|
| 459 |
+
if const_expr(not varlen):
|
| 460 |
+
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 461 |
+
else:
|
| 462 |
+
head_idx = idx // seqlen
|
| 463 |
+
m_idx = idx - head_idx * seqlen
|
| 464 |
+
mLSE_partial_cur_copy = mLSE_partial_copy[None, m_idx, None, head_idx]
|
| 465 |
+
for s in cutlass.range(cute.size(tLSEcLSE, mode=[1]), unroll_full=True):
|
| 466 |
+
si = tLSEcLSE[0, s, 0][0] # Get split coordinate
|
| 467 |
+
if si < num_splits:
|
| 468 |
+
cute.copy(
|
| 469 |
+
gmem_thr_copy_LSE,
|
| 470 |
+
mLSE_partial_cur_copy[None, si],
|
| 471 |
+
tLSEsLSE[None, s, m],
|
| 472 |
+
)
|
| 473 |
+
else:
|
| 474 |
+
tLSEsLSE[None, s, m].fill(-Float32.inf)
|
| 475 |
+
# Don't need to zero out the rest of the LSEs, as we will not write the output to gmem
|
| 476 |
cute.arch.cp_async_commit_group()
|
| 477 |
|
| 478 |
+
# ===============================
|
| 479 |
+
# Step 2: Load O_partial for pipeline stages
|
| 480 |
+
# ===============================
|
| 481 |
+
|
| 482 |
+
gmem_thr_copy_O_partial = gmem_tiled_copy_O_partial.get_slice(tidx)
|
| 483 |
+
cO = cute.make_identity_tensor((self.tile_m, self.k_block_size))
|
| 484 |
+
tOcO = gmem_thr_copy_O_partial.partition_D(cO)
|
| 485 |
+
tOsO_partial = gmem_thr_copy_O_partial.partition_D(sO)
|
| 486 |
+
mO_partial_cur = seqlen_info.offset_batch(mO_partial, batch_idx, dim=4)
|
| 487 |
+
|
| 488 |
+
# Precompute these values to avoid recomputing them in the loop
|
| 489 |
+
num_rows = const_expr(cute.size(tOcO, mode=[1]))
|
| 490 |
+
tOmidx = cute.make_rmem_tensor(num_rows, cutlass.Int32)
|
| 491 |
+
tOhidx = cute.make_rmem_tensor(num_rows, cutlass.Int32)
|
| 492 |
+
tOrOptr = cute.make_rmem_tensor(num_rows, cutlass.Int64)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
for m in cutlass.range(num_rows, unroll_full=True):
|
| 494 |
+
mi = tOcO[0, m, 0][0] # m coordinate
|
| 495 |
+
idx = m_block * self.tile_m + mi
|
| 496 |
+
if const_expr(not varlen):
|
| 497 |
+
tOhidx[m], tOmidx[m] = divmod(idx, seqlen_divmod)
|
| 498 |
+
else:
|
| 499 |
+
tOhidx[m] = idx // seqlen
|
| 500 |
+
tOmidx[m] = idx - tOhidx[m] * seqlen
|
| 501 |
+
tOrOptr[m] = utils.elem_pointer(
|
| 502 |
+
mO_partial_cur, (tOmidx[m], k_block * self.k_block_size, 0, tOhidx[m])
|
| 503 |
+
).toint()
|
| 504 |
+
if idx >= max_idx:
|
| 505 |
+
tOhidx[m] = -1
|
| 506 |
+
|
| 507 |
+
tOpO = None
|
| 508 |
+
if const_expr(not self.is_even_k):
|
| 509 |
+
tOpO = cute.make_rmem_tensor(cute.size(tOcO, mode=[2]), Boolean)
|
| 510 |
+
for k in cutlass.range(cute.size(tOpO), unroll_full=True):
|
| 511 |
+
tOpO[k] = (
|
| 512 |
+
tOcO[0, 0, k][1] < mO_partial.shape[1] - k_block * self.k_block_size
|
| 513 |
+
)
|
| 514 |
+
# if cute.arch.thread_idx()[0] == 0 and k_block == 1: cute.print_tensor(tOpO)
|
| 515 |
+
|
| 516 |
+
load_O_partial = partial(
|
| 517 |
+
self.load_O_partial,
|
| 518 |
+
gmem_tiled_copy_O_partial,
|
| 519 |
+
tOrOptr,
|
| 520 |
+
tOsO_partial,
|
| 521 |
+
tOhidx,
|
| 522 |
+
tOpO,
|
| 523 |
+
tOcO,
|
| 524 |
+
mO_partial_cur.layout,
|
| 525 |
+
)
|
| 526 |
|
| 527 |
+
# Load first few stages of O_partial
|
| 528 |
+
for stage in cutlass.range(self.stages - 1, unroll_full=True):
|
| 529 |
+
if stage < num_splits:
|
| 530 |
+
load_O_partial(stage, stage)
|
| 531 |
+
cute.arch.cp_async_commit_group()
|
| 532 |
+
|
| 533 |
+
# ===============================
|
| 534 |
+
# Step 3: Load and transpose LSE from smem to registers
|
| 535 |
+
# ===============================
|
| 536 |
|
| 537 |
+
# Wait for LSE and initial O partial stages to complete
|
| 538 |
cute.arch.cp_async_wait_group(self.stages - 1)
|
| 539 |
+
cute.arch.sync_threads()
|
| 540 |
+
# if cute.arch.thread_idx()[0] == 0:
|
| 541 |
+
# # cute.print_tensor(sLSE)
|
| 542 |
+
# for i in range(64):
|
| 543 |
+
# cute.printf("sLSE[%d, 0] = %f", i, sLSE[i, 0])
|
| 544 |
+
# cute.arch.sync_threads()
|
| 545 |
+
|
| 546 |
+
s2r_thr_copy_LSE = s2r_tiled_copy_LSE.get_slice(tidx)
|
| 547 |
+
ts2rsLSE = s2r_thr_copy_LSE.partition_S(sLSE)
|
| 548 |
+
ts2rrLSE = cute.make_rmem_tensor_like(ts2rsLSE)
|
| 549 |
+
cute.copy(s2r_tiled_copy_LSE, ts2rsLSE, ts2rrLSE)
|
| 550 |
+
|
| 551 |
+
# ===============================
|
| 552 |
+
# Step 4: Compute final LSE along split dimension
|
| 553 |
+
# ===============================
|
| 554 |
+
|
| 555 |
+
lse_sum = cute.make_rmem_tensor(cute.size(ts2rrLSE, mode=[2]), Float32)
|
| 556 |
+
ts2rcLSE = s2r_thr_copy_LSE.partition_D(cLSE)
|
| 557 |
+
# We compute the max valid split for each row to short-circuit the computation later
|
| 558 |
+
max_valid_split = cute.make_rmem_tensor(cute.size(ts2rrLSE, mode=[2]), Int32)
|
| 559 |
+
assert cute.size(ts2rrLSE, mode=[0]) == 1
|
| 560 |
+
# Compute max, scales, and final LSE for each row
|
| 561 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 562 |
+
# Find max LSE value across splits
|
| 563 |
+
threads_per_col = const_expr(self.smem_threads_per_col_lse)
|
| 564 |
+
lse_max = cute.arch.warp_reduction_max(
|
| 565 |
+
ts2rrLSE[None, None, m]
|
| 566 |
+
.load()
|
| 567 |
+
.reduce(cute.ReductionOp.MAX, init_val=-Float32.inf, reduction_profile=0),
|
| 568 |
+
threads_in_group=threads_per_col,
|
| 569 |
+
)
|
| 570 |
+
# if cute.arch.thread_idx()[0] == 0: cute.printf(lse_max)
|
| 571 |
+
# Find max valid split index
|
| 572 |
+
max_valid_idx = -1
|
| 573 |
+
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 574 |
+
if ts2rrLSE[0, s, m] != -Float32.inf:
|
| 575 |
+
max_valid_idx = ts2rcLSE[0, s, 0][0] # Get split coordinate
|
| 576 |
+
# if cute.arch.thread_idx()[0] < 32: cute.printf(max_valid_idx)
|
| 577 |
+
max_valid_split[m] = cute.arch.warp_reduction_max(
|
| 578 |
+
max_valid_idx, threads_in_group=threads_per_col
|
| 579 |
+
)
|
| 580 |
+
# Compute exp scales and sum
|
| 581 |
+
lse_max_cur = (
|
| 582 |
+
0.0 if lse_max == -Float32.inf else lse_max
|
| 583 |
+
) # In case all local LSEs are -inf
|
| 584 |
+
LOG2_E = math.log2(math.e)
|
| 585 |
+
lse_sum_cur = 0.0
|
| 586 |
+
for s in cutlass.range(cute.size(ts2rrLSE, mode=[1]), unroll_full=True):
|
| 587 |
+
scale = cute.math.exp2(
|
| 588 |
+
ts2rrLSE[0, s, m] * LOG2_E - (lse_max_cur * LOG2_E), fastmath=True
|
| 589 |
+
)
|
| 590 |
+
lse_sum_cur += scale
|
| 591 |
+
ts2rrLSE[0, s, m] = scale # Store scale for later use
|
| 592 |
+
lse_sum_cur = cute.arch.warp_reduction_sum(
|
| 593 |
+
lse_sum_cur, threads_in_group=threads_per_col
|
| 594 |
+
)
|
| 595 |
+
lse_sum[m] = cute.math.log(lse_sum_cur, fastmath=True) + lse_max
|
| 596 |
+
# Normalize scales
|
| 597 |
+
inv_sum = (
|
| 598 |
+
0.0
|
| 599 |
+
if (lse_sum_cur == 0.0 or lse_sum_cur != lse_sum_cur)
|
| 600 |
+
else 1.0 / lse_sum_cur
|
| 601 |
+
)
|
| 602 |
+
ts2rrLSE[None, None, m].store(ts2rrLSE[None, None, m].load() * inv_sum)
|
| 603 |
+
# Store the scales exp(lse - lse_logsum) back to smem
|
| 604 |
+
cute.copy(s2r_tiled_copy_LSE, ts2rrLSE, ts2rsLSE)
|
| 605 |
+
|
| 606 |
+
# Store max valid split to smem
|
| 607 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 608 |
+
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 609 |
+
mi = ts2rcLSE[0, 0, m][1]
|
| 610 |
+
if mi < self.tile_m:
|
| 611 |
+
sMaxValidSplit[mi] = max_valid_split[m]
|
| 612 |
+
|
| 613 |
+
# ===============================
|
| 614 |
+
# Step 5: Store final LSE to gmem
|
| 615 |
+
# ===============================
|
| 616 |
+
|
| 617 |
+
if const_expr(mLSE is not None):
|
| 618 |
+
if const_expr(cu_seqlens is None):
|
| 619 |
+
mLSE_cur = mLSE[None, None, batch_idx]
|
| 620 |
+
else:
|
| 621 |
+
mLSE_cur = cute.domain_offset((offset, 0), mLSE)
|
| 622 |
+
if k_block == 0: # Only first k_block writes LSE when mLSE is provided
|
| 623 |
+
for m in cutlass.range(cute.size(ts2rrLSE, mode=[2]), unroll_full=True):
|
| 624 |
+
if ts2rcLSE[0, 0, m][0] == 0: # Only thread responsible for s=0 writes
|
| 625 |
+
mi = ts2rcLSE[0, 0, m][1]
|
| 626 |
+
idx = m_block * self.tile_m + mi
|
| 627 |
+
if idx < max_idx:
|
| 628 |
+
if const_expr(not varlen):
|
| 629 |
+
head_idx, m_idx = divmod(idx, seqlen_divmod)
|
| 630 |
+
else:
|
| 631 |
+
head_idx = idx // seqlen
|
| 632 |
+
m_idx = idx - head_idx * seqlen
|
| 633 |
+
mLSE_cur[m_idx, head_idx] = lse_sum[m]
|
| 634 |
+
|
| 635 |
+
# ===============================
|
| 636 |
+
# Step 6: Read O_partial and accumulate final O
|
| 637 |
+
# ===============================
|
| 638 |
+
|
| 639 |
+
cute.arch.sync_threads()
|
| 640 |
+
|
| 641 |
+
# Get max valid split for this thread
|
| 642 |
+
thr_max_valid_split = sMaxValidSplit[tOcO[0, 0, 0][0]]
|
| 643 |
+
for m in cutlass.range(1, cute.size(tOcO, mode=[1]), unroll_full=True):
|
| 644 |
+
thr_max_valid_split = max(thr_max_valid_split, sMaxValidSplit[tOcO[0, m, 0][0]])
|
| 645 |
+
|
| 646 |
+
tOrO_partial = cute.make_rmem_tensor_like(tOsO_partial[None, None, None, 0])
|
| 647 |
+
tOrO = cute.make_rmem_tensor_like(tOrO_partial, Float32)
|
| 648 |
+
tOrO.fill(0.0)
|
| 649 |
+
|
| 650 |
+
stage_load = self.stages - 1
|
| 651 |
+
stage_compute = 0
|
| 652 |
+
|
| 653 |
+
# Main accumulation loop
|
| 654 |
+
for s in cutlass.range(thr_max_valid_split + 1, unroll=4):
|
| 655 |
+
# Get scales for this split
|
| 656 |
+
scale = cute.make_rmem_tensor(num_rows, Float32)
|
| 657 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 658 |
+
scale[m] = sLSE[s, tOcO[0, m, 0][0]] # Get scale from smem
|
| 659 |
+
|
| 660 |
+
# Load next stage if needed
|
| 661 |
+
split_to_load = s + self.stages - 1
|
| 662 |
+
if split_to_load <= thr_max_valid_split:
|
| 663 |
+
load_O_partial(split_to_load, stage_load)
|
| 664 |
+
cute.arch.cp_async_commit_group()
|
| 665 |
+
stage_load = 0 if stage_load == self.stages - 1 else stage_load + 1
|
| 666 |
+
|
| 667 |
+
# Wait for the current stage to be ready
|
| 668 |
+
cute.arch.cp_async_wait_group(self.stages - 1)
|
| 669 |
+
# We don't need __syncthreads() because each thread is just reading its own data from smem
|
| 670 |
+
# Copy from smem to registers
|
| 671 |
+
cute.autovec_copy(tOsO_partial[None, None, None, stage_compute], tOrO_partial)
|
| 672 |
+
stage_compute = 0 if stage_compute == self.stages - 1 else stage_compute + 1
|
| 673 |
+
|
| 674 |
+
# Accumulate scaled partial results
|
| 675 |
+
for m in cutlass.range(num_rows, unroll_full=True):
|
| 676 |
+
if tOhidx[m] >= 0 and scale[m] > 0.0:
|
| 677 |
+
tOrO[None, m, None].store(
|
| 678 |
+
tOrO[None, m, None].load()
|
| 679 |
+
+ scale[m] * tOrO_partial[None, m, None].load().to(Float32)
|
| 680 |
+
)
|
| 681 |
|
| 682 |
+
# ===============================
|
| 683 |
+
# Step 7: Write final O to gmem
|
| 684 |
+
# ===============================
|
| 685 |
+
|
| 686 |
+
rO = cute.make_rmem_tensor_like(tOrO, self.dtype)
|
| 687 |
+
rO.store(tOrO.load().to(self.dtype))
|
| 688 |
+
if const_expr(cu_seqlens is None):
|
| 689 |
+
mO_cur = mO[None, None, None, batch_idx]
|
| 690 |
+
else:
|
| 691 |
+
mO_cur = cute.domain_offset((offset, 0, 0), mO)
|
| 692 |
+
mO_cur = utils.domain_offset_aligned((0, k_block * self.k_block_size, 0), mO_cur)
|
| 693 |
+
elems_per_store = const_expr(cute.size(gmem_tiled_copy_O.layout_tv_tiled[1]))
|
| 694 |
+
# mO_cur_copy = cute.tiled_divide(mO_cur, (1, elems_per_store,))
|
| 695 |
+
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 696 |
+
# Write final results
|
| 697 |
for m in cutlass.range(num_rows, unroll_full=True):
|
| 698 |
+
if tOhidx[m] >= 0:
|
| 699 |
+
mO_cur_copy = cute.tiled_divide(
|
| 700 |
+
mO_cur[tOmidx[m], None, tOhidx[m]], (elems_per_store,)
|
|
|
|
| 701 |
)
|
| 702 |
+
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 703 |
+
k_idx = tOcO[0, 0, k][1] // elems_per_store
|
| 704 |
+
if const_expr(self.is_even_k) or tOpO[k]:
|
| 705 |
+
cute.copy(gmem_thr_copy_O, rO[None, m, k], mO_cur_copy[None, k_idx])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 706 |
|
| 707 |
@cute.jit
|
| 708 |
def load_O_partial(
|
|
|
|
| 711 |
tOrOptr: cute.Tensor,
|
| 712 |
tOsO_partial: cute.Tensor,
|
| 713 |
tOhidx: cute.Tensor,
|
| 714 |
+
tOpO: Optional[cute.Tensor],
|
| 715 |
tOcO: cute.Tensor,
|
| 716 |
mO_cur_partial_layout: cute.Layout,
|
| 717 |
split: Int32,
|
|
|
|
| 730 |
mO_partial_cur_copy = cute.tiled_divide(mO_partial_cur, (elems_per_load,))
|
| 731 |
for k in cutlass.range(cute.size(tOcO, mode=[2]), unroll_full=True):
|
| 732 |
k_idx = tOcO[0, 0, k][1] // elems_per_load
|
| 733 |
+
if const_expr(tOpO is None) or tOpO[k]:
|
| 734 |
cute.copy(
|
| 735 |
gmem_tiled_copy_O_partial,
|
| 736 |
mO_partial_cur_copy[None, k_idx, split],
|
build/torch-cuda/flash_fwd_mla_sm100.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/torch-cuda/flash_fwd_sm100.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/torch-cuda/flash_fwd_sm120.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# SM120 (Blackwell GeForce / DGX Spark) forward pass.
|
| 3 |
+
#
|
| 4 |
+
# SM120 uses the same SM80-era MMA instructions (mma.sync.aligned.m16n8k16) but has
|
| 5 |
+
# a smaller shared memory capacity (99 KB vs 163 KB on SM80). This module subclasses
|
| 6 |
+
# FlashAttentionForwardSm80 and overrides the SMEM capacity check accordingly.
|
| 7 |
+
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.utils as utils_basic
|
| 10 |
+
from cutlass.base_dsl.arch import Arch
|
| 11 |
+
|
| 12 |
+
from .flash_fwd import FlashAttentionForwardSm80
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class FlashAttentionForwardSm120(FlashAttentionForwardSm80):
|
| 16 |
+
def __init__(self, *args, **kwargs):
|
| 17 |
+
"""Force SM80 code paths while the DSL still targets the resident SM120 GPU."""
|
| 18 |
+
super().__init__(*args, **kwargs)
|
| 19 |
+
self.arch = Arch.sm_80
|
| 20 |
+
|
| 21 |
+
@staticmethod
|
| 22 |
+
def can_implement(
|
| 23 |
+
dtype,
|
| 24 |
+
head_dim,
|
| 25 |
+
head_dim_v,
|
| 26 |
+
tile_m,
|
| 27 |
+
tile_n,
|
| 28 |
+
num_stages,
|
| 29 |
+
num_threads,
|
| 30 |
+
is_causal,
|
| 31 |
+
Q_in_regs=False,
|
| 32 |
+
) -> bool:
|
| 33 |
+
"""Check if the kernel can be implemented on SM120.
|
| 34 |
+
|
| 35 |
+
Same logic as SM80 but uses SM120's shared memory capacity (99 KB).
|
| 36 |
+
"""
|
| 37 |
+
if dtype not in [cutlass.Float16, cutlass.BFloat16]:
|
| 38 |
+
return False
|
| 39 |
+
if head_dim % 8 != 0:
|
| 40 |
+
return False
|
| 41 |
+
if head_dim_v % 8 != 0:
|
| 42 |
+
return False
|
| 43 |
+
if tile_n % 16 != 0:
|
| 44 |
+
return False
|
| 45 |
+
if num_threads % 32 != 0:
|
| 46 |
+
return False
|
| 47 |
+
# Shared memory usage: Q tile + (K tile + V tile)
|
| 48 |
+
smem_usage_Q = tile_m * head_dim * 2
|
| 49 |
+
smem_usage_K = tile_n * head_dim * num_stages * 2
|
| 50 |
+
smem_usage_V = tile_n * head_dim_v * num_stages * 2
|
| 51 |
+
smem_usage_QV = (
|
| 52 |
+
(smem_usage_Q + smem_usage_V) if not Q_in_regs else max(smem_usage_Q, smem_usage_V)
|
| 53 |
+
)
|
| 54 |
+
smem_usage = smem_usage_QV + smem_usage_K
|
| 55 |
+
# SM120 has 99 KB shared memory (vs 163 KB on SM80)
|
| 56 |
+
smem_capacity = utils_basic.get_smem_capacity_in_bytes("sm_120")
|
| 57 |
+
if smem_usage > smem_capacity:
|
| 58 |
+
return False
|
| 59 |
+
if (tile_m * 2) % num_threads != 0:
|
| 60 |
+
return False
|
| 61 |
+
return True
|
build/torch-cuda/flash_fwd_sm90.py
ADDED
|
@@ -0,0 +1,1550 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
+
# SM90 (Hopper) forward pass for flash attention, extracted from flash_fwd.py.
|
| 3 |
+
|
| 4 |
+
from types import SimpleNamespace
|
| 5 |
+
from typing import Callable, Literal, 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 import Float32, Int32, const_expr
|
| 13 |
+
from cutlass.cute.nvgpu import cpasync, warpgroup
|
| 14 |
+
from cutlass.utils import LayoutEnum
|
| 15 |
+
import cutlass.utils.hopper_helpers as sm90_utils_basic
|
| 16 |
+
from cutlass import pipeline
|
| 17 |
+
from cutlass.pipeline import pipeline_init_arrive, pipeline_init_wait
|
| 18 |
+
from cutlass.base_dsl.arch import Arch
|
| 19 |
+
|
| 20 |
+
from .quack import copy_utils
|
| 21 |
+
from .quack import layout_utils
|
| 22 |
+
from .quack import sm90_utils
|
| 23 |
+
|
| 24 |
+
from .cute_dsl_utils import assume_tensor_aligned
|
| 25 |
+
from . import utils
|
| 26 |
+
from .mask import AttentionMask
|
| 27 |
+
from .softmax import Softmax, apply_score_mod_inner
|
| 28 |
+
from .seqlen_info import SeqlenInfoQK
|
| 29 |
+
from .block_info import BlockInfo
|
| 30 |
+
from .block_sparsity import BlockSparseTensors
|
| 31 |
+
from .block_sparse_utils import (
|
| 32 |
+
produce_block_sparse_loads,
|
| 33 |
+
consume_block_sparse_loads,
|
| 34 |
+
)
|
| 35 |
+
from . import pipeline as pipeline_custom
|
| 36 |
+
from .pack_gqa import PackGQA, pack_gqa_layout, make_packgqa_tiled_tma_atom
|
| 37 |
+
from .paged_kv import PagedKVManager
|
| 38 |
+
from .named_barrier import NamedBarrierFwd
|
| 39 |
+
from .quack.cute_dsl_utils import ParamsBase
|
| 40 |
+
from .tile_scheduler import (
|
| 41 |
+
TileSchedulerArguments,
|
| 42 |
+
SingleTileScheduler,
|
| 43 |
+
SingleTileLPTScheduler,
|
| 44 |
+
SingleTileVarlenScheduler,
|
| 45 |
+
)
|
| 46 |
+
from cutlass.cute import FastDivmodDivisor
|
| 47 |
+
|
| 48 |
+
from .flash_fwd import FlashAttentionForwardBase
|
| 49 |
+
from .utils import AuxData
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class FlashAttentionForwardSm90(FlashAttentionForwardBase):
|
| 53 |
+
def __init__(
|
| 54 |
+
self,
|
| 55 |
+
*args,
|
| 56 |
+
intra_wg_overlap: bool = True,
|
| 57 |
+
mma_pv_is_rs: bool = True,
|
| 58 |
+
paged_kv_non_tma: bool = False,
|
| 59 |
+
**kwargs,
|
| 60 |
+
):
|
| 61 |
+
super().__init__(*args, **kwargs)
|
| 62 |
+
self.intra_wg_overlap = intra_wg_overlap
|
| 63 |
+
self.mma_pv_is_rs = mma_pv_is_rs
|
| 64 |
+
self.buffer_align_bytes = 1024
|
| 65 |
+
self.use_tma_KV = not paged_kv_non_tma
|
| 66 |
+
assert self.use_tma_KV or not (self.check_hdim_oob or self.check_hdim_v_oob), (
|
| 67 |
+
"Paged KV does not support irregular head dim"
|
| 68 |
+
)
|
| 69 |
+
self.cluster_shape_mn = (1, 1)
|
| 70 |
+
assert self.arch.is_family_of(Arch.sm_90a), "Only SM 9.x is supported"
|
| 71 |
+
|
| 72 |
+
def _get_smem_layout_atom(self):
|
| 73 |
+
sQ_layout_atom = warpgroup.make_smem_layout_atom(
|
| 74 |
+
sm90_utils_basic.get_smem_layout_atom(LayoutEnum.ROW_MAJOR, self.dtype, self.tile_hdim),
|
| 75 |
+
self.dtype,
|
| 76 |
+
)
|
| 77 |
+
sK_layout_atom = sQ_layout_atom
|
| 78 |
+
sV_layout_atom = warpgroup.make_smem_layout_atom(
|
| 79 |
+
sm90_utils_basic.get_smem_layout_atom(
|
| 80 |
+
LayoutEnum.ROW_MAJOR, self.dtype, self.tile_hdimv
|
| 81 |
+
),
|
| 82 |
+
self.dtype,
|
| 83 |
+
)
|
| 84 |
+
sO_layout_atom = sV_layout_atom
|
| 85 |
+
if not self.mma_pv_is_rs:
|
| 86 |
+
sP_layout_atom = warpgroup.make_smem_layout_atom(
|
| 87 |
+
sm90_utils_basic.get_smem_layout_atom(
|
| 88 |
+
LayoutEnum.ROW_MAJOR, self.dtype, self.tile_n
|
| 89 |
+
),
|
| 90 |
+
self.dtype,
|
| 91 |
+
)
|
| 92 |
+
else:
|
| 93 |
+
sP_layout_atom = None
|
| 94 |
+
return sQ_layout_atom, sK_layout_atom, sV_layout_atom, sO_layout_atom, sP_layout_atom
|
| 95 |
+
|
| 96 |
+
def _get_tiled_mma(self):
|
| 97 |
+
tiled_mma_qk = sm90_utils_basic.make_trivial_tiled_mma(
|
| 98 |
+
self.dtype,
|
| 99 |
+
self.dtype,
|
| 100 |
+
warpgroup.OperandMajorMode.K,
|
| 101 |
+
warpgroup.OperandMajorMode.K,
|
| 102 |
+
Float32,
|
| 103 |
+
atom_layout_mnk=(self.tile_m // 64, 1, 1),
|
| 104 |
+
tiler_mn=(64, self.tile_n),
|
| 105 |
+
)
|
| 106 |
+
tiled_mma_pv = sm90_utils_basic.make_trivial_tiled_mma(
|
| 107 |
+
self.dtype,
|
| 108 |
+
self.dtype,
|
| 109 |
+
warpgroup.OperandMajorMode.K,
|
| 110 |
+
warpgroup.OperandMajorMode.MN,
|
| 111 |
+
Float32,
|
| 112 |
+
atom_layout_mnk=(self.tile_m // 64, 1, 1), # Might need (1, 2, 1) for hdim 512
|
| 113 |
+
tiler_mn=(64, self.tile_hdimv),
|
| 114 |
+
a_source=warpgroup.OperandSource.RMEM
|
| 115 |
+
if self.mma_pv_is_rs
|
| 116 |
+
else warpgroup.OperandSource.SMEM,
|
| 117 |
+
)
|
| 118 |
+
return tiled_mma_qk, tiled_mma_pv
|
| 119 |
+
|
| 120 |
+
def _get_shared_storage_cls(self):
|
| 121 |
+
sQ_struct, sK_struct, sV_struct = [
|
| 122 |
+
cute.struct.Align[
|
| 123 |
+
cute.struct.MemRange[self.dtype, cute.cosize(layout)], self.buffer_align_bytes
|
| 124 |
+
]
|
| 125 |
+
for layout in (self.sQ_layout, self.sK_layout, self.sV_layout)
|
| 126 |
+
]
|
| 127 |
+
cosize_sQV = max(cute.cosize(self.sQ_layout), cute.cosize(self.sV_layout))
|
| 128 |
+
sQV_struct = cute.struct.Align[cute.struct.MemRange[self.dtype, cosize_sQV], 1024]
|
| 129 |
+
cosize_sP = cute.cosize(self.sP_layout) if const_expr(self.sP_layout is not None) else 0
|
| 130 |
+
sP_struct = cute.struct.Align[cute.struct.MemRange[self.dtype, cosize_sP], 1024]
|
| 131 |
+
# 1 stage * 2 for Q pipeline (full + empty), self.num_stages*2 for K, self.num_stages*2 for V,
|
| 132 |
+
mbar_ptr_Q_struct = cute.struct.MemRange[cutlass.Int64, 1 * 2]
|
| 133 |
+
mbar_ptr_K_struct = cute.struct.MemRange[cutlass.Int64, self.num_stages * 2]
|
| 134 |
+
mbar_ptr_V_struct = cute.struct.MemRange[cutlass.Int64, self.num_stages * 2]
|
| 135 |
+
|
| 136 |
+
@cute.struct
|
| 137 |
+
class SharedStorageQKV:
|
| 138 |
+
mbar_ptr_Q: mbar_ptr_Q_struct
|
| 139 |
+
mbar_ptr_K: mbar_ptr_K_struct
|
| 140 |
+
mbar_ptr_V: mbar_ptr_V_struct
|
| 141 |
+
sV: sV_struct
|
| 142 |
+
sQ: sQ_struct
|
| 143 |
+
sK: sK_struct
|
| 144 |
+
sP: sP_struct
|
| 145 |
+
|
| 146 |
+
@cute.struct
|
| 147 |
+
class SharedStorageSharedQV:
|
| 148 |
+
mbar_ptr_Q: mbar_ptr_Q_struct
|
| 149 |
+
mbar_ptr_K: mbar_ptr_K_struct
|
| 150 |
+
mbar_ptr_V: mbar_ptr_V_struct
|
| 151 |
+
sQ: sQV_struct
|
| 152 |
+
sK: sK_struct
|
| 153 |
+
sP: sP_struct
|
| 154 |
+
|
| 155 |
+
return SharedStorageQKV if const_expr(not self.Q_in_regs) else SharedStorageSharedQV
|
| 156 |
+
|
| 157 |
+
@cute.jit
|
| 158 |
+
def __call__(
|
| 159 |
+
self,
|
| 160 |
+
mQ: cute.Tensor, # (b, s_q, h, d) or (total_q, h, d) if there is cu_seqlens_q
|
| 161 |
+
mK: cute.Tensor, # (b_k, s_k, h_k, d) or (total_k, h_k, d) if there is cu_seqlens_k or (num_pages, page_size, h_k, d) if there is page_table
|
| 162 |
+
mV: cute.Tensor, # (b_k, s_k, h_k, dv) or (total_k, h_k, dv) if there is cu_seqlens_k or (num_pages, page_size, h_k, dv) if there is page_table
|
| 163 |
+
mO: cute.Tensor, # (b, s_q, h, dv) or (total_q, h, dv) if there is cu_seqlens_q
|
| 164 |
+
mLSE: Optional[cute.Tensor],
|
| 165 |
+
softmax_scale: Float32,
|
| 166 |
+
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 167 |
+
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 168 |
+
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 169 |
+
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 170 |
+
mPageTable: Optional[cute.Tensor] = None, # (b_k, max_num_pages_per_seq)
|
| 171 |
+
window_size_left: Int32 | int | None = None,
|
| 172 |
+
window_size_right: Int32 | int | None = None,
|
| 173 |
+
learnable_sink: Optional[cute.Tensor] = None,
|
| 174 |
+
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 175 |
+
aux_data: AuxData = AuxData(),
|
| 176 |
+
mCuTotalMBlocks: Optional[cute.Tensor] = None,
|
| 177 |
+
mCuTotalSplitsMBlocks: Optional[cute.Tensor] = None,
|
| 178 |
+
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 179 |
+
stream: cuda.CUstream = None,
|
| 180 |
+
):
|
| 181 |
+
"""Configures and launches the flash attention kernel.
|
| 182 |
+
|
| 183 |
+
mQ/mK/mV/mO has same data types(supports fp16 and bf16) and same layout:
|
| 184 |
+
(batch_size, seqlen_q, num_head, head_dim):(_, _, _, 1)
|
| 185 |
+
"""
|
| 186 |
+
|
| 187 |
+
self._check_type(
|
| 188 |
+
*(
|
| 189 |
+
t.element_type if t is not None else None
|
| 190 |
+
for t in (mQ, mK, mV, mO, mLSE, mCuSeqlensQ, mCuSeqlensK, mSeqUsedQ, mSeqUsedK)
|
| 191 |
+
)
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
self.varlen_q = mCuSeqlensQ is not None or mSeqUsedQ is not None
|
| 195 |
+
|
| 196 |
+
mQ, mK, mV, mO = [assume_tensor_aligned(t) for t in (mQ, mK, mV, mO)]
|
| 197 |
+
QO_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 2, 1]
|
| 198 |
+
mQ, mO = [layout_utils.select(t, QO_layout_transpose) for t in (mQ, mO)]
|
| 199 |
+
KV_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensK is None) else [0, 2, 1]
|
| 200 |
+
mK, mV = [layout_utils.select(t, KV_layout_transpose) for t in (mK, mV)]
|
| 201 |
+
LSE_layout_transpose = [2, 1, 0] if const_expr(mCuSeqlensQ is None) else [1, 0]
|
| 202 |
+
mLSE = (
|
| 203 |
+
layout_utils.select(mLSE, LSE_layout_transpose)
|
| 204 |
+
if const_expr(mLSE is not None)
|
| 205 |
+
else None
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
tiled_mma_qk, tiled_mma_pv = self._get_tiled_mma()
|
| 209 |
+
self.num_mma_threads = tiled_mma_qk.size
|
| 210 |
+
self.num_threads_per_warp_group = 128
|
| 211 |
+
self.num_wg_mma = self.num_mma_threads // self.num_threads_per_warp_group
|
| 212 |
+
assert self.num_wg_mma in [1, 2, 3]
|
| 213 |
+
self.num_threads = self.num_threads_per_warp_group * (self.num_wg_mma + 1)
|
| 214 |
+
self.num_producer_threads = 32
|
| 215 |
+
self.num_Q_load_threads = self.num_threads_per_warp_group # If not TMA_Q
|
| 216 |
+
self.num_epilogue_threads = self.num_mma_threads
|
| 217 |
+
self.num_mma_regs, self.num_producer_regs = {1: (256, 56), 2: (240, 24), 3: (160, 32)}[
|
| 218 |
+
self.num_wg_mma
|
| 219 |
+
]
|
| 220 |
+
self.use_block_sparsity = cutlass.const_expr(blocksparse_tensors is not None)
|
| 221 |
+
|
| 222 |
+
self.use_scheduler_barrier = (
|
| 223 |
+
(self.num_wg_mma >= 2 and self.tile_hdim <= 128)
|
| 224 |
+
if const_expr(self.intra_wg_overlap)
|
| 225 |
+
else (self.num_wg_mma == 2)
|
| 226 |
+
)
|
| 227 |
+
self.use_tma_Q = self.arch >= Arch.sm_90 and not (
|
| 228 |
+
self.pack_gqa and self.tile_m % self.qhead_per_kvhead != 0
|
| 229 |
+
)
|
| 230 |
+
self.use_tma_O = self.use_tma_Q
|
| 231 |
+
# Producer needs more registers when doing cp.async Q or KV loads
|
| 232 |
+
if const_expr(self.num_wg_mma == 2 and (not self.use_tma_Q or not self.use_tma_KV)):
|
| 233 |
+
self.num_mma_regs, self.num_producer_regs = 224, 40
|
| 234 |
+
self.rescale_O_before_gemm = self.tile_hdimv > 128 and self.intra_wg_overlap
|
| 235 |
+
self._setup_attributes()
|
| 236 |
+
# TODO: we prob don't need most of what's in _setup_attributes
|
| 237 |
+
self.sQ_layout, self.sK_layout, self.sV_layout, self.sO_layout = [
|
| 238 |
+
sm90_utils.make_smem_layout(mX.element_type, LayoutEnum.ROW_MAJOR, shape, stage)
|
| 239 |
+
for mX, shape, stage in [
|
| 240 |
+
(mQ, (self.tile_m, self.tile_hdim), None),
|
| 241 |
+
(mK, (self.tile_n, self.tile_hdim), self.num_stages),
|
| 242 |
+
(mV, (self.tile_n, self.tile_hdimv), self.num_stages),
|
| 243 |
+
(mO, (self.tile_m, self.tile_hdimv), None),
|
| 244 |
+
]
|
| 245 |
+
]
|
| 246 |
+
self.sP_layout = None
|
| 247 |
+
if const_expr(not self.mma_pv_is_rs):
|
| 248 |
+
self.sP_layout = sm90_utils.make_smem_layout(
|
| 249 |
+
mV.element_type, LayoutEnum.ROW_MAJOR, (self.tile_m, self.tile_n)
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
SharedStorage = self._get_shared_storage_cls()
|
| 253 |
+
|
| 254 |
+
mQ_og, mO_og = mQ, mO
|
| 255 |
+
if const_expr(self.pack_gqa):
|
| 256 |
+
nheads_kv = mK.shape[2]
|
| 257 |
+
mQ = pack_gqa_layout(mQ, self.qhead_per_kvhead, nheads_kv, head_idx=2)
|
| 258 |
+
mO = pack_gqa_layout(mO, self.qhead_per_kvhead, nheads_kv, head_idx=2)
|
| 259 |
+
if const_expr(mLSE is not None):
|
| 260 |
+
mLSE = pack_gqa_layout(mLSE, self.qhead_per_kvhead, nheads_kv, head_idx=1)
|
| 261 |
+
|
| 262 |
+
# TMA
|
| 263 |
+
gmem_tiled_copy_Q = cpasync.CopyBulkTensorTileG2SOp()
|
| 264 |
+
gmem_tiled_copy_KV = cpasync.CopyBulkTensorTileG2SOp() # Might multicast
|
| 265 |
+
gmem_tiled_copy_O = cpasync.CopyBulkTensorTileS2GOp()
|
| 266 |
+
self.tma_copy_bytes = {
|
| 267 |
+
name: cute.size_in_bytes(mX.element_type, cute.select(layout, mode=[0, 1]))
|
| 268 |
+
for name, mX, layout in [
|
| 269 |
+
("Q", mQ, self.sQ_layout),
|
| 270 |
+
("K", mK, self.sK_layout),
|
| 271 |
+
("V", mV, self.sV_layout),
|
| 272 |
+
]
|
| 273 |
+
}
|
| 274 |
+
make_tiled_tma_atom_fn = (
|
| 275 |
+
partial(make_packgqa_tiled_tma_atom, qhead_per_kvhead=self.qhead_per_kvhead, head_idx=2)
|
| 276 |
+
if const_expr(self.pack_gqa)
|
| 277 |
+
else cpasync.make_tiled_tma_atom
|
| 278 |
+
)
|
| 279 |
+
tma_atom_Q, tma_tensor_Q = None, None
|
| 280 |
+
if const_expr(self.use_tma_Q):
|
| 281 |
+
tma_atom_Q, tma_tensor_Q = make_tiled_tma_atom_fn(
|
| 282 |
+
gmem_tiled_copy_Q,
|
| 283 |
+
mQ_og if const_expr(self.pack_gqa) else mQ,
|
| 284 |
+
self.sQ_layout,
|
| 285 |
+
(self.tile_m, self.tile_hdim), # No mcast
|
| 286 |
+
)
|
| 287 |
+
tma_atom_K, tma_tensor_K = None, None
|
| 288 |
+
tma_atom_V, tma_tensor_V = None, None
|
| 289 |
+
if const_expr(self.use_tma_KV):
|
| 290 |
+
tma_atom_K, tma_tensor_K = cpasync.make_tiled_tma_atom(
|
| 291 |
+
gmem_tiled_copy_KV,
|
| 292 |
+
mK,
|
| 293 |
+
cute.select(self.sK_layout, mode=[0, 1]),
|
| 294 |
+
(self.tile_n, self.tile_hdim),
|
| 295 |
+
1, # No mcast for now
|
| 296 |
+
)
|
| 297 |
+
tma_atom_V, tma_tensor_V = cpasync.make_tiled_tma_atom(
|
| 298 |
+
gmem_tiled_copy_KV,
|
| 299 |
+
mV,
|
| 300 |
+
cute.select(self.sV_layout, mode=[0, 1]),
|
| 301 |
+
(self.tile_n, self.tile_hdimv),
|
| 302 |
+
1, # No mcast for now
|
| 303 |
+
)
|
| 304 |
+
tma_atom_O, tma_tensor_O = None, None
|
| 305 |
+
if const_expr(self.use_tma_O):
|
| 306 |
+
mO_tma = mO_og if const_expr(self.pack_gqa) else mO
|
| 307 |
+
if const_expr(self.varlen_q):
|
| 308 |
+
mO_tma = copy_utils.create_ragged_tensor_for_tma(
|
| 309 |
+
mO_tma, ragged_dim=0, ptr_shift=True
|
| 310 |
+
)
|
| 311 |
+
tma_atom_O, tma_tensor_O = make_tiled_tma_atom_fn(
|
| 312 |
+
gmem_tiled_copy_O,
|
| 313 |
+
mO_tma,
|
| 314 |
+
self.sO_layout,
|
| 315 |
+
(self.tile_m, self.tile_hdimv), # No mcast
|
| 316 |
+
)
|
| 317 |
+
if const_expr(mCuSeqlensQ is not None or mSeqUsedQ is not None):
|
| 318 |
+
# TODO: dispatch to DynamicPersistentVarlenScheduler when appropriate
|
| 319 |
+
TileScheduler = SingleTileVarlenScheduler
|
| 320 |
+
else:
|
| 321 |
+
TileScheduler = (
|
| 322 |
+
SingleTileScheduler
|
| 323 |
+
if const_expr(not self.is_causal or self.is_local)
|
| 324 |
+
else SingleTileLPTScheduler
|
| 325 |
+
)
|
| 326 |
+
tile_sched_args = TileSchedulerArguments(
|
| 327 |
+
cute.ceil_div(cute.size(mQ.shape[0]), self.tile_m),
|
| 328 |
+
cute.size(mQ.shape[2]),
|
| 329 |
+
cute.size(mQ.shape[3])
|
| 330 |
+
if const_expr(mCuSeqlensQ is None)
|
| 331 |
+
else cute.size(mCuSeqlensQ.shape[0] - 1),
|
| 332 |
+
1, # num_splits
|
| 333 |
+
cute.size(mK.shape[0])
|
| 334 |
+
if const_expr(mPageTable is None)
|
| 335 |
+
else mK.shape[0] * mPageTable.shape[1],
|
| 336 |
+
mQ.shape[1],
|
| 337 |
+
mV.shape[1],
|
| 338 |
+
total_q=cute.size(mQ.shape[0])
|
| 339 |
+
if const_expr(mCuSeqlensQ is not None)
|
| 340 |
+
else cute.size(mQ.shape[0]) * cute.size(mQ.shape[3]),
|
| 341 |
+
tile_shape_mn=(self.tile_m, self.tile_n),
|
| 342 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 343 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 344 |
+
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 345 |
+
element_size=self.dtype.width // 8,
|
| 346 |
+
is_persistent=False,
|
| 347 |
+
lpt=self.is_causal or self.is_local,
|
| 348 |
+
cu_total_m_blocks_ptr=mCuTotalMBlocks,
|
| 349 |
+
cu_total_splits_m_blocks_ptr=mCuTotalSplitsMBlocks,
|
| 350 |
+
)
|
| 351 |
+
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 352 |
+
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 353 |
+
softmax_scale_log2, softmax_scale = utils.compute_softmax_scale_log2(
|
| 354 |
+
softmax_scale, self.score_mod
|
| 355 |
+
)
|
| 356 |
+
window_size_left = Int32(window_size_left) if window_size_left is not None else None
|
| 357 |
+
window_size_right = Int32(window_size_right) if window_size_right is not None else None
|
| 358 |
+
fastdiv_mods = utils.compute_fastdiv_mods(
|
| 359 |
+
mQ, mK, self.qhead_per_kvhead, self.pack_gqa, aux_data.tensors, mPageTable
|
| 360 |
+
)
|
| 361 |
+
|
| 362 |
+
self.kernel(
|
| 363 |
+
tma_tensor_Q if const_expr(self.use_tma_Q) else mQ,
|
| 364 |
+
tma_tensor_K if const_expr(self.use_tma_KV) else mK,
|
| 365 |
+
tma_tensor_V if const_expr(self.use_tma_KV) else mV,
|
| 366 |
+
tma_tensor_O if const_expr(self.use_tma_O) else mO,
|
| 367 |
+
mLSE,
|
| 368 |
+
mCuSeqlensQ,
|
| 369 |
+
mCuSeqlensK,
|
| 370 |
+
mSeqUsedQ,
|
| 371 |
+
mSeqUsedK,
|
| 372 |
+
mPageTable,
|
| 373 |
+
tma_atom_Q,
|
| 374 |
+
tma_atom_K,
|
| 375 |
+
tma_atom_V,
|
| 376 |
+
tma_atom_O,
|
| 377 |
+
softmax_scale_log2,
|
| 378 |
+
softmax_scale,
|
| 379 |
+
window_size_left,
|
| 380 |
+
window_size_right,
|
| 381 |
+
learnable_sink,
|
| 382 |
+
blocksparse_tensors,
|
| 383 |
+
self.sQ_layout,
|
| 384 |
+
self.sK_layout,
|
| 385 |
+
self.sV_layout,
|
| 386 |
+
self.sO_layout,
|
| 387 |
+
self.sP_layout,
|
| 388 |
+
self.gmem_tiled_copy_Q,
|
| 389 |
+
self.gmem_tiled_copy_K,
|
| 390 |
+
self.gmem_tiled_copy_V,
|
| 391 |
+
self.gmem_tiled_copy_O,
|
| 392 |
+
tiled_mma_qk,
|
| 393 |
+
tiled_mma_pv,
|
| 394 |
+
tile_sched_params,
|
| 395 |
+
TileScheduler,
|
| 396 |
+
SharedStorage,
|
| 397 |
+
aux_data,
|
| 398 |
+
fastdiv_mods,
|
| 399 |
+
).launch(
|
| 400 |
+
grid=grid_dim,
|
| 401 |
+
block=[self.num_threads, 1, 1],
|
| 402 |
+
stream=stream,
|
| 403 |
+
min_blocks_per_mp=1,
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
@cute.kernel
|
| 407 |
+
def kernel(
|
| 408 |
+
self,
|
| 409 |
+
mQ: cute.Tensor,
|
| 410 |
+
mK: cute.Tensor,
|
| 411 |
+
mV: cute.Tensor,
|
| 412 |
+
mO: cute.Tensor,
|
| 413 |
+
mLSE: Optional[cute.Tensor],
|
| 414 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 415 |
+
mCuSeqlensK: Optional[cute.Tensor],
|
| 416 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 417 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 418 |
+
mPageTable: Optional[cute.Tensor],
|
| 419 |
+
tma_atom_Q: Optional[cute.CopyAtom],
|
| 420 |
+
tma_atom_K: Optional[cute.CopyAtom],
|
| 421 |
+
tma_atom_V: Optional[cute.CopyAtom],
|
| 422 |
+
tma_atom_O: Optional[cute.CopyAtom],
|
| 423 |
+
softmax_scale_log2: Float32,
|
| 424 |
+
softmax_scale: Optional[Float32],
|
| 425 |
+
window_size_left: Optional[Int32],
|
| 426 |
+
window_size_right: Optional[Int32],
|
| 427 |
+
learnable_sink: Optional[cute.Tensor],
|
| 428 |
+
blocksparse_tensors: Optional[BlockSparseTensors],
|
| 429 |
+
sQ_layout: cute.ComposedLayout,
|
| 430 |
+
sK_layout: cute.ComposedLayout,
|
| 431 |
+
sV_layout: cute.ComposedLayout,
|
| 432 |
+
sO_layout: cute.ComposedLayout,
|
| 433 |
+
sP_layout: cute.ComposedLayout | None,
|
| 434 |
+
gmem_tiled_copy_Q: cute.TiledCopy,
|
| 435 |
+
gmem_tiled_copy_K: cute.TiledCopy,
|
| 436 |
+
gmem_tiled_copy_V: cute.TiledCopy,
|
| 437 |
+
gmem_tiled_copy_O: cute.TiledCopy,
|
| 438 |
+
tiled_mma_qk: cute.TiledMma,
|
| 439 |
+
tiled_mma_pv: cute.TiledMma,
|
| 440 |
+
tile_sched_params: ParamsBase,
|
| 441 |
+
TileScheduler: cutlass.Constexpr[Callable],
|
| 442 |
+
SharedStorage: cutlass.Constexpr[Callable],
|
| 443 |
+
aux_data: AuxData = AuxData(),
|
| 444 |
+
fastdiv_mods=None,
|
| 445 |
+
):
|
| 446 |
+
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 447 |
+
# Prefetch tma descriptor
|
| 448 |
+
if warp_idx == 0:
|
| 449 |
+
for tma_atom in (tma_atom_Q, tma_atom_K, tma_atom_V, tma_atom_O):
|
| 450 |
+
if const_expr(tma_atom is not None):
|
| 451 |
+
cpasync.prefetch_descriptor(tma_atom)
|
| 452 |
+
|
| 453 |
+
smem = cutlass.utils.SmemAllocator()
|
| 454 |
+
storage = smem.allocate(SharedStorage)
|
| 455 |
+
|
| 456 |
+
# Mbarrier / pipeline init
|
| 457 |
+
mbar_ptr_Q = storage.mbar_ptr_Q.data_ptr()
|
| 458 |
+
|
| 459 |
+
ThreadCooperativeGroup = partial(pipeline.CooperativeGroup, pipeline.Agent.Thread)
|
| 460 |
+
tma_warp = ThreadCooperativeGroup(1)
|
| 461 |
+
load_threads = ThreadCooperativeGroup(self.num_threads_per_warp_group)
|
| 462 |
+
mma_warps = ThreadCooperativeGroup(self.num_mma_threads // cute.arch.WARP_SIZE)
|
| 463 |
+
if const_expr(self.use_tma_Q):
|
| 464 |
+
pipeline_q = pipeline_custom.PipelineTmaAsync.create(
|
| 465 |
+
barrier_storage=mbar_ptr_Q,
|
| 466 |
+
num_stages=1,
|
| 467 |
+
producer_group=tma_warp,
|
| 468 |
+
consumer_group=mma_warps,
|
| 469 |
+
tx_count=self.tma_copy_bytes["Q"],
|
| 470 |
+
defer_sync=True,
|
| 471 |
+
)
|
| 472 |
+
else:
|
| 473 |
+
pipeline_q = pipeline_custom.PipelineCpAsync.create(
|
| 474 |
+
barrier_storage=mbar_ptr_Q,
|
| 475 |
+
num_stages=1,
|
| 476 |
+
producer_group=load_threads,
|
| 477 |
+
consumer_group=mma_warps,
|
| 478 |
+
defer_sync=True,
|
| 479 |
+
elect_one_release=True,
|
| 480 |
+
syncwarp_before_release=False,
|
| 481 |
+
)
|
| 482 |
+
|
| 483 |
+
if const_expr(self.use_tma_KV):
|
| 484 |
+
pipeline_k = pipeline_custom.PipelineTmaAsync.create(
|
| 485 |
+
barrier_storage=storage.mbar_ptr_K.data_ptr(),
|
| 486 |
+
num_stages=self.num_stages,
|
| 487 |
+
producer_group=tma_warp,
|
| 488 |
+
consumer_group=mma_warps,
|
| 489 |
+
tx_count=self.tma_copy_bytes["K"],
|
| 490 |
+
defer_sync=True,
|
| 491 |
+
)
|
| 492 |
+
pipeline_v = pipeline_custom.PipelineTmaAsync.create(
|
| 493 |
+
barrier_storage=storage.mbar_ptr_V.data_ptr(),
|
| 494 |
+
num_stages=self.num_stages,
|
| 495 |
+
producer_group=tma_warp,
|
| 496 |
+
consumer_group=mma_warps,
|
| 497 |
+
tx_count=self.tma_copy_bytes["V"],
|
| 498 |
+
defer_sync=True,
|
| 499 |
+
)
|
| 500 |
+
else:
|
| 501 |
+
pipeline_k = pipeline_custom.PipelineCpAsync.create(
|
| 502 |
+
barrier_storage=storage.mbar_ptr_K.data_ptr(),
|
| 503 |
+
num_stages=self.num_stages,
|
| 504 |
+
producer_group=load_threads,
|
| 505 |
+
consumer_group=mma_warps,
|
| 506 |
+
defer_sync=True,
|
| 507 |
+
elect_one_release=True,
|
| 508 |
+
syncwarp_before_release=False,
|
| 509 |
+
)
|
| 510 |
+
pipeline_v = pipeline_custom.PipelineCpAsync.create(
|
| 511 |
+
barrier_storage=storage.mbar_ptr_V.data_ptr(),
|
| 512 |
+
num_stages=self.num_stages,
|
| 513 |
+
producer_group=load_threads,
|
| 514 |
+
consumer_group=mma_warps,
|
| 515 |
+
defer_sync=True,
|
| 516 |
+
elect_one_release=True,
|
| 517 |
+
syncwarp_before_release=False,
|
| 518 |
+
)
|
| 519 |
+
|
| 520 |
+
# Cluster arrive after barrier init
|
| 521 |
+
pipeline_init_arrive(cluster_shape_mn=self.cluster_shape_mn, is_relaxed=True)
|
| 522 |
+
|
| 523 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 524 |
+
# Get shared memory buffer
|
| 525 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 526 |
+
sQ = storage.sQ.get_tensor(sQ_layout.outer, swizzle=sQ_layout.inner)
|
| 527 |
+
sK = storage.sK.get_tensor(sK_layout.outer, swizzle=sK_layout.inner)
|
| 528 |
+
if const_expr(not self.Q_in_regs):
|
| 529 |
+
sV = storage.sV.get_tensor(sV_layout.outer, swizzle=sV_layout.inner)
|
| 530 |
+
else:
|
| 531 |
+
sV = storage.sQ.get_tensor(
|
| 532 |
+
sV_layout.outer, swizzle=sV_layout.inner, dtype=mV.element_type
|
| 533 |
+
)
|
| 534 |
+
# Transpose view of V to tensor with layout (head_dim_v, tile_n) for tiled mma
|
| 535 |
+
sVt = layout_utils.transpose_view(sV)
|
| 536 |
+
sP = None
|
| 537 |
+
if const_expr(sP_layout is not None):
|
| 538 |
+
sP = storage.sP.get_tensor(sP_layout.outer, swizzle=sP_layout.inner)
|
| 539 |
+
# reuse sQ's data iterator
|
| 540 |
+
sO = storage.sQ.get_tensor(sO_layout.outer, swizzle=sO_layout.inner, dtype=self.dtype)
|
| 541 |
+
|
| 542 |
+
block_info = BlockInfo(
|
| 543 |
+
self.tile_m,
|
| 544 |
+
self.tile_n,
|
| 545 |
+
self.is_causal,
|
| 546 |
+
self.is_local,
|
| 547 |
+
False, # is_split_kv
|
| 548 |
+
window_size_left,
|
| 549 |
+
window_size_right,
|
| 550 |
+
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 551 |
+
)
|
| 552 |
+
SeqlenInfoCls = partial(
|
| 553 |
+
SeqlenInfoQK.create,
|
| 554 |
+
seqlen_q_static=mQ.shape[0] if const_expr(not self.pack_gqa) else mQ.shape[0][1],
|
| 555 |
+
seqlen_k_static=mK.shape[0]
|
| 556 |
+
if const_expr(mPageTable is None)
|
| 557 |
+
else mK.shape[0] * mPageTable.shape[1],
|
| 558 |
+
mCuSeqlensQ=mCuSeqlensQ,
|
| 559 |
+
mCuSeqlensK=mCuSeqlensK,
|
| 560 |
+
mSeqUsedQ=mSeqUsedQ,
|
| 561 |
+
mSeqUsedK=mSeqUsedK,
|
| 562 |
+
mCuTotalMBlocks=(
|
| 563 |
+
blocksparse_tensors.cu_total_m_blocks if blocksparse_tensors is not None else None
|
| 564 |
+
),
|
| 565 |
+
mCuBlockIdxOffsets=(
|
| 566 |
+
blocksparse_tensors.cu_block_idx_offsets
|
| 567 |
+
if blocksparse_tensors is not None
|
| 568 |
+
else None
|
| 569 |
+
),
|
| 570 |
+
# Don't need to pass in tile_mn because we won't access offset_padded
|
| 571 |
+
)
|
| 572 |
+
AttentionMaskCls = partial(
|
| 573 |
+
AttentionMask,
|
| 574 |
+
self.tile_m,
|
| 575 |
+
self.tile_n,
|
| 576 |
+
window_size_left=window_size_left,
|
| 577 |
+
window_size_right=window_size_right,
|
| 578 |
+
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 579 |
+
)
|
| 580 |
+
TileSchedulerCls = partial(TileScheduler.create, tile_sched_params)
|
| 581 |
+
|
| 582 |
+
# Cluster wait before starting
|
| 583 |
+
pipeline_init_wait(cluster_shape_mn=self.cluster_shape_mn)
|
| 584 |
+
|
| 585 |
+
if warp_idx < 4: # Producer
|
| 586 |
+
cute.arch.setmaxregister_decrease(self.num_producer_regs)
|
| 587 |
+
self.load(
|
| 588 |
+
mQ,
|
| 589 |
+
mK,
|
| 590 |
+
mV,
|
| 591 |
+
sQ,
|
| 592 |
+
sK,
|
| 593 |
+
sV,
|
| 594 |
+
tma_atom_Q,
|
| 595 |
+
tma_atom_K,
|
| 596 |
+
tma_atom_V,
|
| 597 |
+
pipeline_k,
|
| 598 |
+
pipeline_v,
|
| 599 |
+
pipeline_q,
|
| 600 |
+
gmem_tiled_copy_Q,
|
| 601 |
+
mPageTable,
|
| 602 |
+
blocksparse_tensors,
|
| 603 |
+
block_info,
|
| 604 |
+
SeqlenInfoCls,
|
| 605 |
+
TileSchedulerCls,
|
| 606 |
+
)
|
| 607 |
+
|
| 608 |
+
else: # Consumer
|
| 609 |
+
cute.arch.setmaxregister_increase(self.num_mma_regs)
|
| 610 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 611 |
+
# Tile MMA compute thread partitions and allocate accumulators
|
| 612 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 613 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 614 |
+
tidx = tidx - 128
|
| 615 |
+
self.mma(
|
| 616 |
+
tiled_mma_qk,
|
| 617 |
+
tiled_mma_pv,
|
| 618 |
+
mO,
|
| 619 |
+
mLSE,
|
| 620 |
+
sQ,
|
| 621 |
+
sK,
|
| 622 |
+
sVt,
|
| 623 |
+
sP,
|
| 624 |
+
sO,
|
| 625 |
+
learnable_sink,
|
| 626 |
+
pipeline_k,
|
| 627 |
+
pipeline_v,
|
| 628 |
+
pipeline_q,
|
| 629 |
+
gmem_tiled_copy_O,
|
| 630 |
+
tma_atom_O,
|
| 631 |
+
tidx,
|
| 632 |
+
softmax_scale_log2,
|
| 633 |
+
softmax_scale,
|
| 634 |
+
block_info,
|
| 635 |
+
SeqlenInfoCls,
|
| 636 |
+
AttentionMaskCls,
|
| 637 |
+
TileSchedulerCls,
|
| 638 |
+
blocksparse_tensors,
|
| 639 |
+
aux_data,
|
| 640 |
+
fastdiv_mods,
|
| 641 |
+
)
|
| 642 |
+
|
| 643 |
+
@cute.jit
|
| 644 |
+
def load(
|
| 645 |
+
self,
|
| 646 |
+
mQ: cute.Tensor,
|
| 647 |
+
mK: cute.Tensor,
|
| 648 |
+
mV: cute.Tensor,
|
| 649 |
+
sQ: cute.Tensor,
|
| 650 |
+
sK: cute.Tensor,
|
| 651 |
+
sV: cute.Tensor,
|
| 652 |
+
tma_atom_Q: Optional[cute.CopyAtom],
|
| 653 |
+
tma_atom_K: Optional[cute.CopyAtom],
|
| 654 |
+
tma_atom_V: Optional[cute.CopyAtom],
|
| 655 |
+
pipeline_k: pipeline.PipelineAsync,
|
| 656 |
+
pipeline_v: pipeline.PipelineAsync,
|
| 657 |
+
pipeline_q: pipeline.PipelineAsync,
|
| 658 |
+
gmem_tiled_copy_Q: cute.TiledCopy,
|
| 659 |
+
mPageTable: Optional[cute.Tensor],
|
| 660 |
+
blocksparse_tensors: Optional[BlockSparseTensors],
|
| 661 |
+
block_info: BlockInfo,
|
| 662 |
+
SeqlenInfoCls: Callable,
|
| 663 |
+
TileSchedulerCls: Callable,
|
| 664 |
+
):
|
| 665 |
+
warp_idx_in_wg = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % 4
|
| 666 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 667 |
+
|
| 668 |
+
# TMA: only warp 0 loads. cp_async: all warps load.
|
| 669 |
+
# When not use_tma_Q, all 128 producer threads participate in Q loading.
|
| 670 |
+
is_load_warp = warp_idx_in_wg == 0 or const_expr(not self.use_tma_KV or not self.use_tma_Q)
|
| 671 |
+
# KV loading restricted to warp 0 for TMA, all warps for non-TMA KV
|
| 672 |
+
is_kv_load_warp = warp_idx_in_wg == 0 or const_expr(not self.use_tma_KV)
|
| 673 |
+
|
| 674 |
+
if is_load_warp:
|
| 675 |
+
q_producer_phase = Int32(1)
|
| 676 |
+
kv_producer_state = pipeline.make_pipeline_state(
|
| 677 |
+
pipeline.PipelineUserType.Producer, self.num_stages
|
| 678 |
+
)
|
| 679 |
+
tile_scheduler = TileSchedulerCls()
|
| 680 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 681 |
+
while work_tile.is_valid_tile:
|
| 682 |
+
# if work_tile.is_valid_tile:
|
| 683 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 684 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 685 |
+
mQ_cur = seqlen.offset_batch_Q(mQ, batch_idx, dim=3)[None, None, head_idx]
|
| 686 |
+
head_idx_kv = (
|
| 687 |
+
head_idx // self.qhead_per_kvhead if const_expr(not self.pack_gqa) else head_idx
|
| 688 |
+
)
|
| 689 |
+
|
| 690 |
+
load_Q = None
|
| 691 |
+
if const_expr(self.use_tma_Q):
|
| 692 |
+
gQ = cute.local_tile(mQ_cur, (self.tile_m, self.tile_hdim), (m_block, 0))
|
| 693 |
+
load_Q, _, _ = copy_utils.tma_get_copy_fn(
|
| 694 |
+
tma_atom_Q, 0, cute.make_layout(1), gQ, sQ, single_stage=True
|
| 695 |
+
)
|
| 696 |
+
|
| 697 |
+
paged_kv_manager = None
|
| 698 |
+
tma_load_K_fn = None
|
| 699 |
+
tma_load_V_fn = None
|
| 700 |
+
if const_expr(self.use_tma_KV):
|
| 701 |
+
# === TMA path (non-paged and paged with page_size == n_block_size) ===
|
| 702 |
+
if const_expr(mPageTable is not None):
|
| 703 |
+
# Paged TMA: keep page dimension indexable
|
| 704 |
+
mK_cur = mK[None, None, head_idx_kv, None]
|
| 705 |
+
mV_cur = mV[None, None, head_idx_kv, None]
|
| 706 |
+
gK = cute.local_tile(mK_cur, (self.tile_n, self.tile_hdim), (0, 0, None))
|
| 707 |
+
gV = cute.local_tile(mV_cur, (self.tile_n, self.tile_hdimv), (0, 0, None))
|
| 708 |
+
else:
|
| 709 |
+
# Non-paged TMA
|
| 710 |
+
mK_cur = seqlen.offset_batch_K(mK, batch_idx, dim=3)[
|
| 711 |
+
None, None, head_idx_kv
|
| 712 |
+
]
|
| 713 |
+
mV_cur = seqlen.offset_batch_K(mV, batch_idx, dim=3)[
|
| 714 |
+
None, None, head_idx_kv
|
| 715 |
+
]
|
| 716 |
+
gK = cute.local_tile(mK_cur, (self.tile_n, self.tile_hdim), (None, 0))
|
| 717 |
+
gV = cute.local_tile(mV_cur, (self.tile_n, self.tile_hdimv), (None, 0))
|
| 718 |
+
# TODO: mcast
|
| 719 |
+
tma_load_K_fn, _, _ = copy_utils.tma_get_copy_fn(
|
| 720 |
+
tma_atom_K, 0, cute.make_layout(1), gK, sK
|
| 721 |
+
)
|
| 722 |
+
tma_load_K_fn = copy_utils.tma_producer_copy_fn(tma_load_K_fn, pipeline_k)
|
| 723 |
+
tma_load_V_fn, _, _ = copy_utils.tma_get_copy_fn(
|
| 724 |
+
tma_atom_V, 0, cute.make_layout(1), gV, sV
|
| 725 |
+
)
|
| 726 |
+
tma_load_V_fn = copy_utils.tma_producer_copy_fn(tma_load_V_fn, pipeline_v)
|
| 727 |
+
else:
|
| 728 |
+
# === cp_async path (paged KV with page_size != n_block_size) ===
|
| 729 |
+
paged_kv_manager = PagedKVManager.create(
|
| 730 |
+
mPageTable,
|
| 731 |
+
mK,
|
| 732 |
+
mV,
|
| 733 |
+
FastDivmodDivisor(mK.shape[0]),
|
| 734 |
+
batch_idx,
|
| 735 |
+
head_idx_kv,
|
| 736 |
+
tidx,
|
| 737 |
+
seqlen.seqlen_k,
|
| 738 |
+
0, # leftpad_k
|
| 739 |
+
self.tile_n,
|
| 740 |
+
self.tile_hdim,
|
| 741 |
+
self.tile_hdimv,
|
| 742 |
+
self.num_threads_per_warp_group,
|
| 743 |
+
mK.element_type,
|
| 744 |
+
arch=self.arch.major * 10 + self.arch.minor,
|
| 745 |
+
)
|
| 746 |
+
|
| 747 |
+
load_K = partial(
|
| 748 |
+
self.load_KV,
|
| 749 |
+
tma_load_K_fn,
|
| 750 |
+
paged_kv_manager,
|
| 751 |
+
sK,
|
| 752 |
+
pipeline_kv=pipeline_k,
|
| 753 |
+
K_or_V="K",
|
| 754 |
+
)
|
| 755 |
+
load_V = partial(
|
| 756 |
+
self.load_KV,
|
| 757 |
+
tma_load_V_fn,
|
| 758 |
+
paged_kv_manager,
|
| 759 |
+
sV,
|
| 760 |
+
pipeline_kv=pipeline_v,
|
| 761 |
+
K_or_V="V",
|
| 762 |
+
)
|
| 763 |
+
|
| 764 |
+
pack_gqa = None
|
| 765 |
+
if const_expr(not self.use_tma_Q):
|
| 766 |
+
pack_gqa = PackGQA(
|
| 767 |
+
self.tile_m, self.tile_hdim, self.check_hdim_oob, self.qhead_per_kvhead
|
| 768 |
+
)
|
| 769 |
+
|
| 770 |
+
if const_expr(not self.use_block_sparsity):
|
| 771 |
+
n_block_min, n_block_max = block_info.get_n_block_min_max(seqlen, m_block)
|
| 772 |
+
# if cute.arch.thread_idx()[0] == 0:
|
| 773 |
+
# cute.printf("m_block = %d, n_block_min: %d, n_block_max: %d", m_block, n_block_min, n_block_max)
|
| 774 |
+
# Clamp n_block to 0 when n_block_max == 0 (can happen with causal
|
| 775 |
+
# + pack_gqa when seqlen_k < tile_n). TMA handles n_block=-1
|
| 776 |
+
# gracefully (fills zeros), but cp.async would crash on
|
| 777 |
+
# out-of-bounds page table access.
|
| 778 |
+
n_block = (
|
| 779 |
+
n_block_max - 1
|
| 780 |
+
if const_expr(self.use_tma_KV)
|
| 781 |
+
else cutlass.max(n_block_max - 1, 0)
|
| 782 |
+
)
|
| 783 |
+
page_idx = (
|
| 784 |
+
mPageTable[batch_idx, n_block]
|
| 785 |
+
if const_expr(mPageTable is not None and self.use_tma_KV)
|
| 786 |
+
else None
|
| 787 |
+
)
|
| 788 |
+
|
| 789 |
+
# First iteration: load K on pipeline_k, Q on pipeline_q
|
| 790 |
+
if is_kv_load_warp:
|
| 791 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 792 |
+
if const_expr(not self.use_tma_KV):
|
| 793 |
+
paged_kv_manager.load_page_table(n_block)
|
| 794 |
+
load_K(block=n_block, producer_state=kv_producer_state, page_idx=page_idx)
|
| 795 |
+
if const_expr(self.use_tma_Q):
|
| 796 |
+
if warp_idx_in_wg == 0:
|
| 797 |
+
pipeline_q.producer_acquire_w_index_phase(0, q_producer_phase)
|
| 798 |
+
load_Q(tma_bar_ptr=pipeline_q.sync_object_full.get_barrier(0))
|
| 799 |
+
q_producer_phase ^= 1
|
| 800 |
+
else:
|
| 801 |
+
pipeline_q.producer_acquire_w_index_phase(0, q_producer_phase)
|
| 802 |
+
pack_gqa.load_Q(
|
| 803 |
+
mQ_cur, sQ, gmem_tiled_copy_Q, tidx, m_block, seqlen.seqlen_q
|
| 804 |
+
)
|
| 805 |
+
cute.arch.cp_async_commit_group()
|
| 806 |
+
pipeline_q.producer_commit_w_index(0)
|
| 807 |
+
q_producer_phase ^= 1
|
| 808 |
+
|
| 809 |
+
if is_kv_load_warp:
|
| 810 |
+
if const_expr(not self.intra_wg_overlap or not self.use_tma_KV):
|
| 811 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 812 |
+
load_V(
|
| 813 |
+
block=n_block, producer_state=kv_producer_state, page_idx=page_idx
|
| 814 |
+
)
|
| 815 |
+
kv_producer_state.advance()
|
| 816 |
+
for i in cutlass.range(n_block_max - 1 - n_block_min, unroll=1):
|
| 817 |
+
n_block = n_block_max - 1 - i - 1
|
| 818 |
+
page_idx = (
|
| 819 |
+
mPageTable[batch_idx, n_block]
|
| 820 |
+
if const_expr(mPageTable is not None and self.use_tma_KV)
|
| 821 |
+
else None
|
| 822 |
+
)
|
| 823 |
+
if const_expr(not self.use_tma_KV):
|
| 824 |
+
paged_kv_manager.load_page_table(n_block)
|
| 825 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 826 |
+
load_K(
|
| 827 |
+
block=n_block,
|
| 828 |
+
producer_state=kv_producer_state,
|
| 829 |
+
page_idx=page_idx,
|
| 830 |
+
)
|
| 831 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 832 |
+
load_V(
|
| 833 |
+
block=n_block,
|
| 834 |
+
producer_state=kv_producer_state,
|
| 835 |
+
page_idx=page_idx,
|
| 836 |
+
)
|
| 837 |
+
kv_producer_state.advance()
|
| 838 |
+
else:
|
| 839 |
+
for i in cutlass.range(n_block_max - 1 - n_block_min, unroll=1):
|
| 840 |
+
n_block_prev = n_block_max - i - 1
|
| 841 |
+
n_block = n_block_prev - 1
|
| 842 |
+
page_idx = (
|
| 843 |
+
mPageTable[batch_idx, n_block]
|
| 844 |
+
if const_expr(mPageTable is not None)
|
| 845 |
+
else None
|
| 846 |
+
)
|
| 847 |
+
page_idx_prev = (
|
| 848 |
+
mPageTable[batch_idx, n_block_prev]
|
| 849 |
+
if const_expr(mPageTable is not None)
|
| 850 |
+
else None
|
| 851 |
+
)
|
| 852 |
+
kv_producer_state_prev = kv_producer_state.clone()
|
| 853 |
+
kv_producer_state.advance()
|
| 854 |
+
pipeline_k.producer_acquire(kv_producer_state)
|
| 855 |
+
load_K(
|
| 856 |
+
block=n_block,
|
| 857 |
+
producer_state=kv_producer_state,
|
| 858 |
+
page_idx=page_idx,
|
| 859 |
+
)
|
| 860 |
+
pipeline_v.producer_acquire(kv_producer_state_prev)
|
| 861 |
+
load_V(
|
| 862 |
+
block=n_block_prev,
|
| 863 |
+
producer_state=kv_producer_state_prev,
|
| 864 |
+
page_idx=page_idx_prev,
|
| 865 |
+
)
|
| 866 |
+
n_block = n_block_min
|
| 867 |
+
page_idx = (
|
| 868 |
+
mPageTable[batch_idx, n_block]
|
| 869 |
+
if const_expr(mPageTable is not None)
|
| 870 |
+
else None
|
| 871 |
+
)
|
| 872 |
+
pipeline_v.producer_acquire(kv_producer_state)
|
| 873 |
+
load_V(
|
| 874 |
+
block=n_block, producer_state=kv_producer_state, page_idx=page_idx
|
| 875 |
+
)
|
| 876 |
+
kv_producer_state.advance()
|
| 877 |
+
else:
|
| 878 |
+
# Block sparsity: use TMA closures directly (not paged)
|
| 879 |
+
# Load Q on pipeline_q, separate from K/V pipeline
|
| 880 |
+
if const_expr(self.use_tma_Q):
|
| 881 |
+
if warp_idx_in_wg == 0:
|
| 882 |
+
pipeline_q.producer_acquire_w_index_phase(0, q_producer_phase)
|
| 883 |
+
load_Q(tma_bar_ptr=pipeline_q.sync_object_full.get_barrier(0))
|
| 884 |
+
q_producer_phase ^= 1
|
| 885 |
+
else:
|
| 886 |
+
pipeline_q.producer_acquire_w_index_phase(0, q_producer_phase)
|
| 887 |
+
pack_gqa.load_Q(
|
| 888 |
+
mQ_cur, sQ, gmem_tiled_copy_Q, tidx, m_block, seqlen.seqlen_q
|
| 889 |
+
)
|
| 890 |
+
cute.arch.cp_async_commit_group()
|
| 891 |
+
pipeline_q.producer_commit_w_index(0)
|
| 892 |
+
q_producer_phase ^= 1
|
| 893 |
+
if is_kv_load_warp:
|
| 894 |
+
kv_producer_state = produce_block_sparse_loads(
|
| 895 |
+
blocksparse_tensors,
|
| 896 |
+
batch_idx,
|
| 897 |
+
head_idx,
|
| 898 |
+
m_block,
|
| 899 |
+
seqlen,
|
| 900 |
+
kv_producer_state,
|
| 901 |
+
tma_load_K_fn,
|
| 902 |
+
tma_load_V_fn,
|
| 903 |
+
pipeline_k,
|
| 904 |
+
pipeline_v,
|
| 905 |
+
self.intra_wg_overlap,
|
| 906 |
+
self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 907 |
+
self.q_subtile_factor,
|
| 908 |
+
)
|
| 909 |
+
|
| 910 |
+
tile_scheduler.prefetch_next_work()
|
| 911 |
+
tile_scheduler.advance_to_next_work()
|
| 912 |
+
work_tile = tile_scheduler.get_current_work()
|
| 913 |
+
# End of persistent scheduler loop
|
| 914 |
+
|
| 915 |
+
# Producer tail is only useful for cluster to avoid early exit of blocks.
|
| 916 |
+
# We only need producer_tail on V since that's the last that's loaded, we don't
|
| 917 |
+
# need it for Q (no cluster) and K.
|
| 918 |
+
if is_kv_load_warp:
|
| 919 |
+
pipeline_v.producer_tail(kv_producer_state)
|
| 920 |
+
|
| 921 |
+
@cute.jit
|
| 922 |
+
def load_KV(
|
| 923 |
+
self,
|
| 924 |
+
tma_load_fn: Optional[Callable],
|
| 925 |
+
paged_kv_manager: Optional[PagedKVManager],
|
| 926 |
+
sX: cute.Tensor,
|
| 927 |
+
block: Int32,
|
| 928 |
+
pipeline_kv: pipeline.PipelineAsync,
|
| 929 |
+
producer_state: pipeline.PipelineState,
|
| 930 |
+
K_or_V: Literal["K", "V"],
|
| 931 |
+
page_idx: Optional[Int32] = None,
|
| 932 |
+
):
|
| 933 |
+
if const_expr(self.use_tma_KV):
|
| 934 |
+
src_idx = block if const_expr(page_idx is None) else page_idx
|
| 935 |
+
tma_load_fn(src_idx=src_idx, producer_state=producer_state)
|
| 936 |
+
else:
|
| 937 |
+
paged_kv_manager.load_KV(block, sX[None, None, producer_state.index], K_or_V)
|
| 938 |
+
cute.arch.cp_async_commit_group()
|
| 939 |
+
pipeline_kv.producer_commit(producer_state)
|
| 940 |
+
|
| 941 |
+
@cute.jit
|
| 942 |
+
def mma(
|
| 943 |
+
self,
|
| 944 |
+
tiled_mma_qk: cute.TiledMma,
|
| 945 |
+
tiled_mma_pv: cute.TiledMma,
|
| 946 |
+
mO: cute.Tensor,
|
| 947 |
+
mLSE: Optional[cute.Tensor],
|
| 948 |
+
sQ: cute.Tensor,
|
| 949 |
+
sK: cute.Tensor,
|
| 950 |
+
sVt: cute.Tensor,
|
| 951 |
+
sP: Optional[cute.Tensor],
|
| 952 |
+
sO: cute.Tensor,
|
| 953 |
+
learnable_sink: Optional[cute.Tensor],
|
| 954 |
+
pipeline_k: pipeline.PipelineAsync,
|
| 955 |
+
pipeline_v: pipeline.PipelineAsync,
|
| 956 |
+
pipeline_q: pipeline.PipelineAsync,
|
| 957 |
+
gmem_tiled_copy_O: cute.TiledCopy,
|
| 958 |
+
tma_atom_O: Optional[cute.CopyAtom],
|
| 959 |
+
tidx: Int32,
|
| 960 |
+
softmax_scale_log2: Float32,
|
| 961 |
+
softmax_scale: Optional[Float32],
|
| 962 |
+
block_info: BlockInfo,
|
| 963 |
+
SeqlenInfoCls: Callable,
|
| 964 |
+
AttentionMaskCls: Callable,
|
| 965 |
+
TileSchedulerCls: Callable,
|
| 966 |
+
blocksparse_tensors: Optional[BlockSparseTensors],
|
| 967 |
+
aux_data: AuxData = AuxData(),
|
| 968 |
+
fastdiv_mods=None,
|
| 969 |
+
):
|
| 970 |
+
aux_tensors = aux_data.tensors
|
| 971 |
+
warp_group_idx = cute.arch.make_warp_uniform(tidx // self.num_threads_per_warp_group)
|
| 972 |
+
warp_group_thread_layout = cute.make_layout(
|
| 973 |
+
self.num_wg_mma, stride=self.num_threads_per_warp_group
|
| 974 |
+
)
|
| 975 |
+
thr_mma_qk = tiled_mma_qk.get_slice(tidx)
|
| 976 |
+
wg_mma_qk = tiled_mma_qk.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 977 |
+
wg_mma_pv = tiled_mma_pv.get_slice(warp_group_thread_layout(warp_group_idx))
|
| 978 |
+
_, tSrQ, tSrK = sm90_utils.partition_fragment_ABC(
|
| 979 |
+
wg_mma_qk, (self.tile_m, self.tile_n, self.tile_hdim), sQ, sK
|
| 980 |
+
)
|
| 981 |
+
mma_qk_fn = partial(
|
| 982 |
+
sm90_utils.gemm_zero_init, tiled_mma_qk, (self.tile_m, self.tile_n), tSrQ, tSrK
|
| 983 |
+
)
|
| 984 |
+
acc_O, tOrP, tOrVt = sm90_utils.partition_fragment_ABC(
|
| 985 |
+
wg_mma_pv, (self.tile_m, self.tile_hdimv, self.tile_n), sP, sVt
|
| 986 |
+
)
|
| 987 |
+
mma_pv_fn = partial(sm90_utils.gemm_w_idx, tiled_mma_pv, acc_O, tOrP, tOrVt)
|
| 988 |
+
|
| 989 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 990 |
+
# Smem copy atom tiling
|
| 991 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 992 |
+
smem_copy_atom_P = utils.get_smem_store_atom(
|
| 993 |
+
self.arch.major * 10 + self.arch.minor, self.dtype
|
| 994 |
+
)
|
| 995 |
+
smem_thr_copy_P = cute.make_tiled_copy_C(smem_copy_atom_P, tiled_mma_qk).get_slice(tidx)
|
| 996 |
+
tPsP = smem_thr_copy_P.partition_D(sP) if const_expr(sP is not None) else None
|
| 997 |
+
smem_copy_params = SimpleNamespace(smem_thr_copy_P=smem_thr_copy_P, tPsP=tPsP)
|
| 998 |
+
|
| 999 |
+
self.mma_init()
|
| 1000 |
+
|
| 1001 |
+
q_consumer_phase = Int32(0)
|
| 1002 |
+
kv_consumer_state = pipeline.make_pipeline_state(
|
| 1003 |
+
pipeline.PipelineUserType.Consumer, self.num_stages
|
| 1004 |
+
)
|
| 1005 |
+
|
| 1006 |
+
tile_scheduler = TileSchedulerCls()
|
| 1007 |
+
work_tile = tile_scheduler.initial_work_tile_info()
|
| 1008 |
+
softmax = Softmax.create(
|
| 1009 |
+
softmax_scale_log2,
|
| 1010 |
+
num_rows=acc_O.shape[0][0] * acc_O.shape[1],
|
| 1011 |
+
softmax_scale=softmax_scale,
|
| 1012 |
+
)
|
| 1013 |
+
|
| 1014 |
+
# For RescaleOBeforeGemm: persistent scores_scale across iterations
|
| 1015 |
+
scores_scale = None
|
| 1016 |
+
if const_expr(self.rescale_O_before_gemm):
|
| 1017 |
+
scores_scale = cute.make_rmem_tensor_like(softmax.row_max, Float32)
|
| 1018 |
+
|
| 1019 |
+
mma_one_n_block_all = partial(
|
| 1020 |
+
self.mma_one_n_block_intrawg_overlap
|
| 1021 |
+
if const_expr(self.intra_wg_overlap)
|
| 1022 |
+
else self.mma_one_n_block,
|
| 1023 |
+
mma_qk_fn=mma_qk_fn,
|
| 1024 |
+
pipeline_k=pipeline_k,
|
| 1025 |
+
pipeline_v=pipeline_v,
|
| 1026 |
+
acc_O=acc_O,
|
| 1027 |
+
tOrP=tOrP,
|
| 1028 |
+
smem_copy_params=smem_copy_params,
|
| 1029 |
+
check_inf=True,
|
| 1030 |
+
scores_scale=scores_scale,
|
| 1031 |
+
)
|
| 1032 |
+
|
| 1033 |
+
process_first_half_block = partial(
|
| 1034 |
+
self.first_half_block_overlap,
|
| 1035 |
+
mma_qk_fn=mma_qk_fn,
|
| 1036 |
+
pipeline_k=pipeline_k,
|
| 1037 |
+
tOrP=tOrP,
|
| 1038 |
+
smem_copy_params=smem_copy_params,
|
| 1039 |
+
scores_scale=scores_scale,
|
| 1040 |
+
softmax=softmax,
|
| 1041 |
+
acc_O=acc_O,
|
| 1042 |
+
)
|
| 1043 |
+
process_last_half_block = partial(
|
| 1044 |
+
self.last_half_block_overlap,
|
| 1045 |
+
pipeline_v=pipeline_v,
|
| 1046 |
+
mma_pv_fn=mma_pv_fn,
|
| 1047 |
+
scores_scale=scores_scale,
|
| 1048 |
+
softmax=softmax,
|
| 1049 |
+
acc_O=acc_O,
|
| 1050 |
+
)
|
| 1051 |
+
while work_tile.is_valid_tile:
|
| 1052 |
+
# if work_tile.is_valid_tile:
|
| 1053 |
+
|
| 1054 |
+
# shape: (atom_v_m * rest_m)
|
| 1055 |
+
m_block, head_idx, batch_idx, _ = work_tile.tile_idx
|
| 1056 |
+
seqlen = SeqlenInfoCls(batch_idx)
|
| 1057 |
+
|
| 1058 |
+
# Recompute fastdiv_mods if necessary for varlen with aux_tensors
|
| 1059 |
+
recompute_fastdiv_mods_q = cutlass.const_expr(
|
| 1060 |
+
aux_tensors is not None and (seqlen.has_cu_seqlens_q or seqlen.has_seqused_q)
|
| 1061 |
+
)
|
| 1062 |
+
recompute_fastdiv_mods_k = cutlass.const_expr(
|
| 1063 |
+
aux_tensors is not None and (seqlen.has_cu_seqlens_k or seqlen.has_seqused_k)
|
| 1064 |
+
)
|
| 1065 |
+
if cutlass.const_expr(fastdiv_mods is not None):
|
| 1066 |
+
seqlen_q_divmod, seqlen_k_divmod = fastdiv_mods
|
| 1067 |
+
fastdiv_mods = (
|
| 1068 |
+
seqlen_q_divmod
|
| 1069 |
+
if not recompute_fastdiv_mods_q
|
| 1070 |
+
else FastDivmodDivisor(seqlen.seqlen_q),
|
| 1071 |
+
seqlen_k_divmod
|
| 1072 |
+
if not recompute_fastdiv_mods_k
|
| 1073 |
+
else FastDivmodDivisor(seqlen.seqlen_k),
|
| 1074 |
+
)
|
| 1075 |
+
|
| 1076 |
+
mask = AttentionMaskCls(seqlen)
|
| 1077 |
+
mask_fn = partial(
|
| 1078 |
+
mask.apply_mask,
|
| 1079 |
+
batch_idx=batch_idx,
|
| 1080 |
+
head_idx=head_idx,
|
| 1081 |
+
m_block=m_block,
|
| 1082 |
+
thr_mma=thr_mma_qk,
|
| 1083 |
+
mask_causal=self.is_causal,
|
| 1084 |
+
mask_local=self.is_local,
|
| 1085 |
+
aux_data=aux_data,
|
| 1086 |
+
fastdiv_mods=fastdiv_mods,
|
| 1087 |
+
)
|
| 1088 |
+
score_mod_fn = None
|
| 1089 |
+
if const_expr(self.score_mod is not None):
|
| 1090 |
+
score_mod_fn = partial(
|
| 1091 |
+
self.apply_score_mod,
|
| 1092 |
+
thr_mma_qk,
|
| 1093 |
+
batch_idx,
|
| 1094 |
+
head_idx,
|
| 1095 |
+
m_block,
|
| 1096 |
+
softmax_scale=softmax_scale,
|
| 1097 |
+
aux_data=aux_data,
|
| 1098 |
+
fastdiv_mods=fastdiv_mods,
|
| 1099 |
+
)
|
| 1100 |
+
mma_one_n_block = partial(
|
| 1101 |
+
mma_one_n_block_all, seqlen=seqlen, softmax=softmax, score_mod_fn=score_mod_fn
|
| 1102 |
+
)
|
| 1103 |
+
n_block_min, n_block_max = block_info.get_n_block_min_max(seqlen, m_block)
|
| 1104 |
+
pipeline_q.consumer_wait_w_index_phase(0, q_consumer_phase)
|
| 1105 |
+
# For performance reason, we separate out two kinds of iterations:
|
| 1106 |
+
# those that need masking on S, and those that don't.
|
| 1107 |
+
# We need masking on S for the very last block when K and V has length not multiple of tile_n.
|
| 1108 |
+
# We also need masking on S if it's causal, for the last several blocks.
|
| 1109 |
+
# softmax.reset() # Don't need reset as we explicitly call softmax w is_first=True
|
| 1110 |
+
O_should_accumulate = False
|
| 1111 |
+
|
| 1112 |
+
# ==========================================
|
| 1113 |
+
# MAINLOOP
|
| 1114 |
+
# ==========================================
|
| 1115 |
+
if const_expr(not self.use_block_sparsity):
|
| 1116 |
+
# ==========================================
|
| 1117 |
+
# No block-sparsity (original path)
|
| 1118 |
+
# ==========================================
|
| 1119 |
+
# First iteration with seqlen masking
|
| 1120 |
+
if const_expr(self.intra_wg_overlap):
|
| 1121 |
+
kv_consumer_state = process_first_half_block(
|
| 1122 |
+
n_block=n_block_max - 1,
|
| 1123 |
+
seqlen=seqlen,
|
| 1124 |
+
kv_consumer_state=kv_consumer_state,
|
| 1125 |
+
mask_fn=partial(mask_fn, mask_mod=self.mask_mod),
|
| 1126 |
+
score_mod_fn=score_mod_fn,
|
| 1127 |
+
is_first_block=True,
|
| 1128 |
+
)
|
| 1129 |
+
else:
|
| 1130 |
+
self.warp_scheduler_barrier_sync()
|
| 1131 |
+
kv_consumer_state = mma_one_n_block(
|
| 1132 |
+
kv_consumer_state,
|
| 1133 |
+
n_block=n_block_max - 1,
|
| 1134 |
+
seqlen=seqlen,
|
| 1135 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=True),
|
| 1136 |
+
is_first_n_block=True,
|
| 1137 |
+
mask_fn=partial(mask_fn, mask_mod=self.mask_mod, mask_seqlen=True),
|
| 1138 |
+
)
|
| 1139 |
+
O_should_accumulate = True
|
| 1140 |
+
# if cute.arch.thread_idx()[0] == 128: cute.printf("m_block = {}, n_block_max = {}, n_block_min = {}", m_block, n_block_max, n_block_min)
|
| 1141 |
+
n_block_max -= 1
|
| 1142 |
+
# Next couple of iterations with causal masking
|
| 1143 |
+
if const_expr(self.is_causal or self.is_local):
|
| 1144 |
+
n_block_min_causal_local_mask = block_info.get_n_block_min_causal_local_mask(
|
| 1145 |
+
seqlen, m_block, n_block_min
|
| 1146 |
+
)
|
| 1147 |
+
# if cute.arch.thread_idx()[0] == 128: cute.printf("n_block_min_causal_local_mask = {}", n_block_min_causal_local_mask)
|
| 1148 |
+
for n_tile in cutlass.range(
|
| 1149 |
+
n_block_max - n_block_min_causal_local_mask, unroll=1
|
| 1150 |
+
):
|
| 1151 |
+
kv_consumer_state = mma_one_n_block(
|
| 1152 |
+
kv_consumer_state,
|
| 1153 |
+
n_block=n_block_max - 1 - n_tile,
|
| 1154 |
+
seqlen=seqlen,
|
| 1155 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 1156 |
+
mask_fn=partial(mask_fn, mask_mod=self.mask_mod, mask_seqlen=False),
|
| 1157 |
+
)
|
| 1158 |
+
O_should_accumulate = True
|
| 1159 |
+
n_block_max = cutlass.min(n_block_max, n_block_min_causal_local_mask)
|
| 1160 |
+
# The remaining iterations have no masking
|
| 1161 |
+
n_block_min_before_local_mask = block_info.get_n_block_min_before_local_mask(
|
| 1162 |
+
seqlen, m_block, n_block_min
|
| 1163 |
+
)
|
| 1164 |
+
# if cute.arch.thread_idx()[0] == 128: cute.printf("n_block_min_before_local_mask = {}, n_block_min = {}", n_block_min_before_local_mask, n_block_min)
|
| 1165 |
+
for n_tile in cutlass.range(n_block_max - n_block_min_before_local_mask, unroll=1):
|
| 1166 |
+
kv_consumer_state = mma_one_n_block(
|
| 1167 |
+
kv_consumer_state,
|
| 1168 |
+
n_block=n_block_max - 1 - n_tile,
|
| 1169 |
+
seqlen=seqlen,
|
| 1170 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 1171 |
+
mask_fn=partial(mask_fn, mask_mod=self.mask_mod, mask_seqlen=False),
|
| 1172 |
+
)
|
| 1173 |
+
O_should_accumulate = True
|
| 1174 |
+
# Separate iterations with local masking on the left
|
| 1175 |
+
if const_expr(self.is_local and block_info.window_size_left is not None):
|
| 1176 |
+
n_block_max = cutlass.min(n_block_max, n_block_min_before_local_mask)
|
| 1177 |
+
for n_tile in cutlass.range(n_block_max - n_block_min, unroll=1):
|
| 1178 |
+
kv_consumer_state = mma_one_n_block(
|
| 1179 |
+
kv_consumer_state,
|
| 1180 |
+
n_block=n_block_max - 1 - n_tile,
|
| 1181 |
+
seqlen=seqlen,
|
| 1182 |
+
mma_pv_fn=partial(mma_pv_fn, zero_init=not O_should_accumulate),
|
| 1183 |
+
mask_fn=partial(mask_fn, mask_mod=self.mask_mod, mask_seqlen=False),
|
| 1184 |
+
)
|
| 1185 |
+
O_should_accumulate = True
|
| 1186 |
+
# Release Q pipeline so the producer can load the next tile's Q
|
| 1187 |
+
pipeline_q.consumer_release_w_index(0)
|
| 1188 |
+
# Last "half" iteration
|
| 1189 |
+
if const_expr(self.intra_wg_overlap):
|
| 1190 |
+
kv_consumer_state = process_last_half_block(
|
| 1191 |
+
kv_consumer_state=kv_consumer_state,
|
| 1192 |
+
zero_init=not O_should_accumulate,
|
| 1193 |
+
)
|
| 1194 |
+
O_should_accumulate = True
|
| 1195 |
+
else:
|
| 1196 |
+
self.warp_scheduler_barrier_arrive()
|
| 1197 |
+
|
| 1198 |
+
else:
|
| 1199 |
+
# ==========================================
|
| 1200 |
+
# Block sparsity
|
| 1201 |
+
# ==========================================
|
| 1202 |
+
kv_consumer_state, O_should_accumulate, processed_any = consume_block_sparse_loads(
|
| 1203 |
+
blocksparse_tensors,
|
| 1204 |
+
batch_idx,
|
| 1205 |
+
head_idx,
|
| 1206 |
+
m_block,
|
| 1207 |
+
seqlen,
|
| 1208 |
+
kv_consumer_state,
|
| 1209 |
+
mma_pv_fn,
|
| 1210 |
+
mma_one_n_block,
|
| 1211 |
+
process_first_half_block,
|
| 1212 |
+
process_last_half_block,
|
| 1213 |
+
mask_fn,
|
| 1214 |
+
score_mod_fn,
|
| 1215 |
+
O_should_accumulate,
|
| 1216 |
+
self.mask_mod,
|
| 1217 |
+
fastdiv_mods,
|
| 1218 |
+
self.intra_wg_overlap,
|
| 1219 |
+
self.warp_scheduler_barrier_sync,
|
| 1220 |
+
self.warp_scheduler_barrier_arrive,
|
| 1221 |
+
self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 1222 |
+
self.q_subtile_factor,
|
| 1223 |
+
)
|
| 1224 |
+
|
| 1225 |
+
# Release Q pipeline so the producer can load the next tile's Q
|
| 1226 |
+
pipeline_q.consumer_release_w_index(0)
|
| 1227 |
+
|
| 1228 |
+
# Handle empty case (when no blocks to process)
|
| 1229 |
+
if not processed_any:
|
| 1230 |
+
softmax.reset()
|
| 1231 |
+
acc_O.fill(0.0)
|
| 1232 |
+
|
| 1233 |
+
q_consumer_phase ^= 1
|
| 1234 |
+
|
| 1235 |
+
sink_val = None
|
| 1236 |
+
if const_expr(learnable_sink is not None):
|
| 1237 |
+
if const_expr(not self.pack_gqa):
|
| 1238 |
+
sink_val = Float32(learnable_sink[head_idx])
|
| 1239 |
+
else: # Each thread might have a different sink value due to different q_head
|
| 1240 |
+
sink_val = cute.make_rmem_tensor_like(softmax.row_max, Float32)
|
| 1241 |
+
cS = cute.make_identity_tensor((self.tile_m, self.tile_n))
|
| 1242 |
+
tScS_mn = layout_utils.reshape_acc_to_mn(thr_mma_qk.partition_C(cS))
|
| 1243 |
+
for r in cutlass.range(cute.size(sink_val), unroll_full=True):
|
| 1244 |
+
row = m_block * self.tile_m + tScS_mn[r][0]
|
| 1245 |
+
q_head_idx = row % self.qhead_per_kvhead + head_idx * self.qhead_per_kvhead
|
| 1246 |
+
sink_val[r] = Float32(learnable_sink[q_head_idx])
|
| 1247 |
+
|
| 1248 |
+
# normalize acc_O by row_sum and calculate the lse
|
| 1249 |
+
row_scale = softmax.finalize(sink_val=sink_val)
|
| 1250 |
+
softmax.rescale_O(acc_O, row_scale)
|
| 1251 |
+
|
| 1252 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1253 |
+
# Epilogue
|
| 1254 |
+
# ///////////////////////////////////////////////////////////////////////////////
|
| 1255 |
+
self.epilogue(
|
| 1256 |
+
acc_O,
|
| 1257 |
+
softmax.row_sum,
|
| 1258 |
+
mO,
|
| 1259 |
+
mLSE,
|
| 1260 |
+
sO,
|
| 1261 |
+
seqlen,
|
| 1262 |
+
gmem_tiled_copy_O,
|
| 1263 |
+
tma_atom_O,
|
| 1264 |
+
tiled_mma_pv,
|
| 1265 |
+
tidx,
|
| 1266 |
+
m_block,
|
| 1267 |
+
head_idx,
|
| 1268 |
+
batch_idx,
|
| 1269 |
+
)
|
| 1270 |
+
|
| 1271 |
+
tile_scheduler.advance_to_next_work()
|
| 1272 |
+
work_tile = tile_scheduler.get_current_work()
|
| 1273 |
+
|
| 1274 |
+
@cute.jit
|
| 1275 |
+
def first_half_block_overlap(
|
| 1276 |
+
self,
|
| 1277 |
+
n_block: Int32,
|
| 1278 |
+
mma_qk_fn: Callable,
|
| 1279 |
+
kv_consumer_state,
|
| 1280 |
+
pipeline_k,
|
| 1281 |
+
tOrP: cute.Tensor,
|
| 1282 |
+
smem_copy_params: SimpleNamespace,
|
| 1283 |
+
softmax: Softmax,
|
| 1284 |
+
seqlen: SeqlenInfoQK,
|
| 1285 |
+
scores_scale: Optional[cute.Tensor] = None,
|
| 1286 |
+
acc_O: Optional[cute.Tensor] = None,
|
| 1287 |
+
mask_fn: Callable = None,
|
| 1288 |
+
score_mod_fn: Optional[Callable] = None,
|
| 1289 |
+
is_first_block: bool = False,
|
| 1290 |
+
):
|
| 1291 |
+
"""Processes the first half block when using intra-warpgroup-overlap"""
|
| 1292 |
+
|
| 1293 |
+
pipeline_k.consumer_wait(kv_consumer_state, pipeline_k.consumer_try_wait(kv_consumer_state))
|
| 1294 |
+
acc_S = mma_qk_fn(B_idx=kv_consumer_state.index, wg_wait=0)
|
| 1295 |
+
pipeline_k.consumer_release(kv_consumer_state)
|
| 1296 |
+
|
| 1297 |
+
# Apply score modification if present
|
| 1298 |
+
if const_expr(score_mod_fn is not None):
|
| 1299 |
+
score_mod_fn(acc_S, n_block=n_block, seqlen=seqlen)
|
| 1300 |
+
|
| 1301 |
+
# Apply mask; mask_seqlen always True for first block
|
| 1302 |
+
# Caveat: if full block further right than mask block, seqlen masking is redundant;
|
| 1303 |
+
# however, masking is being applied anyway, so essentially no perf hit
|
| 1304 |
+
mask_fn(acc_S, n_block=n_block, mask_seqlen=True)
|
| 1305 |
+
|
| 1306 |
+
row_scale = softmax.online_softmax(acc_S, is_first=is_first_block)
|
| 1307 |
+
|
| 1308 |
+
tOrP_acc = layout_utils.reshape_acc_to_frgA(acc_S)
|
| 1309 |
+
tOrP_cur = (
|
| 1310 |
+
tOrP
|
| 1311 |
+
if const_expr(self.mma_pv_is_rs)
|
| 1312 |
+
else cute.make_rmem_tensor_like(tOrP_acc, self.dtype)
|
| 1313 |
+
)
|
| 1314 |
+
tOrP_cur.store(tOrP_acc.load().to(self.dtype))
|
| 1315 |
+
|
| 1316 |
+
if const_expr(not self.mma_pv_is_rs):
|
| 1317 |
+
tPrP = smem_copy_params.smem_thr_copy_P.retile(tOrP_cur)
|
| 1318 |
+
cute.copy(smem_copy_params.smem_thr_copy_P, tPrP, smem_copy_params.tPsP)
|
| 1319 |
+
# Fence and barrier to make smem store visible to WGMMA
|
| 1320 |
+
cute.arch.fence_view_async_shared()
|
| 1321 |
+
cute.arch.sync_warp()
|
| 1322 |
+
|
| 1323 |
+
# For RescaleOBeforeGemm: initialize acc_O
|
| 1324 |
+
if const_expr(self.rescale_O_before_gemm):
|
| 1325 |
+
acc_O.fill(0.0)
|
| 1326 |
+
scores_scale.store(row_scale.load())
|
| 1327 |
+
|
| 1328 |
+
return kv_consumer_state
|
| 1329 |
+
|
| 1330 |
+
@cute.jit
|
| 1331 |
+
def last_half_block_overlap(
|
| 1332 |
+
self,
|
| 1333 |
+
kv_consumer_state,
|
| 1334 |
+
pipeline_v,
|
| 1335 |
+
mma_pv_fn: Callable,
|
| 1336 |
+
zero_init: bool,
|
| 1337 |
+
scores_scale: Optional[cute.Tensor] = None,
|
| 1338 |
+
softmax: Optional[Softmax] = None,
|
| 1339 |
+
acc_O: Optional[cute.Tensor] = None,
|
| 1340 |
+
):
|
| 1341 |
+
"""Processes the final PV GEMM when using intra-warpgroup-overlap"""
|
| 1342 |
+
|
| 1343 |
+
# For RescaleOBeforeGemm: rescale O before the final PV GEMM
|
| 1344 |
+
if const_expr(self.rescale_O_before_gemm):
|
| 1345 |
+
softmax.rescale_O(acc_O, scores_scale)
|
| 1346 |
+
|
| 1347 |
+
pipeline_v.consumer_wait(kv_consumer_state, pipeline_v.consumer_try_wait(kv_consumer_state))
|
| 1348 |
+
mma_pv_fn(B_idx=kv_consumer_state.index, zero_init=zero_init, wg_wait=0)
|
| 1349 |
+
pipeline_v.consumer_release(kv_consumer_state)
|
| 1350 |
+
kv_consumer_state.advance()
|
| 1351 |
+
return kv_consumer_state
|
| 1352 |
+
|
| 1353 |
+
@cute.jit
|
| 1354 |
+
def mma_one_n_block(
|
| 1355 |
+
self,
|
| 1356 |
+
smem_pipe_read: pipeline.PipelineState | pipeline_custom.PipelineStateSimple,
|
| 1357 |
+
n_block: Int32,
|
| 1358 |
+
mma_qk_fn: Callable,
|
| 1359 |
+
mma_pv_fn: Callable,
|
| 1360 |
+
pipeline_k: pipeline.PipelineAsync,
|
| 1361 |
+
pipeline_v: pipeline.PipelineAsync,
|
| 1362 |
+
acc_O: cute.Tensor,
|
| 1363 |
+
tOrP: cute.Tensor,
|
| 1364 |
+
smem_copy_params: SimpleNamespace,
|
| 1365 |
+
softmax: Softmax,
|
| 1366 |
+
seqlen: SeqlenInfoQK,
|
| 1367 |
+
scores_scale: Optional[cute.Tensor] = None, # not used
|
| 1368 |
+
score_mod_fn: Optional[Callable] = None,
|
| 1369 |
+
mask_fn: Optional[Callable] = None,
|
| 1370 |
+
is_first_n_block: cutlass.Constexpr = False,
|
| 1371 |
+
check_inf: cutlass.Constexpr = True,
|
| 1372 |
+
):
|
| 1373 |
+
pipeline_k.consumer_wait(smem_pipe_read, pipeline_k.consumer_try_wait(smem_pipe_read))
|
| 1374 |
+
# S = Q @ K.T
|
| 1375 |
+
acc_S = mma_qk_fn(B_idx=smem_pipe_read.index, wg_wait=-1)
|
| 1376 |
+
self.warp_scheduler_barrier_arrive()
|
| 1377 |
+
warpgroup.wait_group(0)
|
| 1378 |
+
pipeline_k.consumer_release(smem_pipe_read)
|
| 1379 |
+
|
| 1380 |
+
# handle score mods and masking
|
| 1381 |
+
if const_expr(score_mod_fn is not None):
|
| 1382 |
+
score_mod_fn(acc_S, n_block=n_block, seqlen=seqlen)
|
| 1383 |
+
if const_expr(mask_fn is not None):
|
| 1384 |
+
mask_fn(acc_S=acc_S, n_block=n_block)
|
| 1385 |
+
|
| 1386 |
+
row_scale = softmax.online_softmax(acc_S, is_first=is_first_n_block, check_inf=check_inf)
|
| 1387 |
+
# if cute.arch.thread_idx()[0] == 0: cute.print_tensor(layout_utils.reshape_acc_to_mn(acc_S))
|
| 1388 |
+
tOrP_acc = layout_utils.reshape_acc_to_frgA(acc_S)
|
| 1389 |
+
tOrP_cur = (
|
| 1390 |
+
tOrP
|
| 1391 |
+
if const_expr(self.mma_pv_is_rs)
|
| 1392 |
+
else cute.make_rmem_tensor_like(tOrP_acc, self.dtype)
|
| 1393 |
+
)
|
| 1394 |
+
# tOrP.store(tOrP_acc.load().to(self.dtype))
|
| 1395 |
+
# the "to(self.dtype)" conversion fails to vectorize for block sizes other
|
| 1396 |
+
# than 128 x 128, i.e. it calls convert on 1 fp32 element at a time instead of
|
| 1397 |
+
# 2 elements. So we just call ptx directly.
|
| 1398 |
+
utils.cvt_f16(tOrP_acc, tOrP_cur)
|
| 1399 |
+
if const_expr(not self.mma_pv_is_rs):
|
| 1400 |
+
tPrP = smem_copy_params.smem_thr_copy_P.retile(tOrP_cur)
|
| 1401 |
+
cute.copy(smem_copy_params.smem_thr_copy_P, tPrP, smem_copy_params.tPsP)
|
| 1402 |
+
softmax.rescale_O(acc_O, row_scale)
|
| 1403 |
+
if const_expr(not self.mma_pv_is_rs):
|
| 1404 |
+
# Fence and barrier to make sure smem store is visible to WGMMA
|
| 1405 |
+
cute.arch.fence_view_async_shared()
|
| 1406 |
+
cute.arch.sync_warp() # Only need syncwarp since each warp is using its own P values for MmaPV
|
| 1407 |
+
pipeline_v.consumer_wait(smem_pipe_read, pipeline_v.consumer_try_wait(smem_pipe_read))
|
| 1408 |
+
self.warp_scheduler_barrier_sync()
|
| 1409 |
+
# O += P @ V
|
| 1410 |
+
mma_pv_fn(B_idx=smem_pipe_read.index, wg_wait=0)
|
| 1411 |
+
pipeline_v.consumer_release(smem_pipe_read)
|
| 1412 |
+
smem_pipe_read.advance()
|
| 1413 |
+
return smem_pipe_read
|
| 1414 |
+
|
| 1415 |
+
@cute.jit
|
| 1416 |
+
def mma_one_n_block_intrawg_overlap(
|
| 1417 |
+
self,
|
| 1418 |
+
smem_pipe_read: pipeline.PipelineState | pipeline_custom.PipelineStateSimple,
|
| 1419 |
+
n_block: Int32,
|
| 1420 |
+
mma_qk_fn: Callable,
|
| 1421 |
+
mma_pv_fn: Callable,
|
| 1422 |
+
pipeline_k: pipeline.PipelineAsync,
|
| 1423 |
+
pipeline_v: pipeline.PipelineAsync,
|
| 1424 |
+
acc_O: cute.Tensor,
|
| 1425 |
+
tOrP: cute.Tensor,
|
| 1426 |
+
smem_copy_params: SimpleNamespace,
|
| 1427 |
+
softmax: Softmax,
|
| 1428 |
+
seqlen: SeqlenInfoQK,
|
| 1429 |
+
scores_scale: Optional[cute.Tensor] = None,
|
| 1430 |
+
score_mod_fn: Optional[Callable] = None,
|
| 1431 |
+
mask_fn: Optional[Callable] = None,
|
| 1432 |
+
check_inf: cutlass.Constexpr = True,
|
| 1433 |
+
):
|
| 1434 |
+
smem_pipe_read_v = smem_pipe_read.clone()
|
| 1435 |
+
smem_pipe_read.advance()
|
| 1436 |
+
pipeline_k.consumer_wait(smem_pipe_read, pipeline_k.consumer_try_wait(smem_pipe_read))
|
| 1437 |
+
self.warp_scheduler_barrier_sync()
|
| 1438 |
+
# S = Q @ K.T
|
| 1439 |
+
acc_S = mma_qk_fn(B_idx=smem_pipe_read.index, wg_wait=-1)
|
| 1440 |
+
# RescaleOBeforeGemm: rescale O while QK GEMM is in flight, before PV GEMM
|
| 1441 |
+
if const_expr(self.rescale_O_before_gemm):
|
| 1442 |
+
softmax.rescale_O(acc_O, scores_scale)
|
| 1443 |
+
pipeline_v.consumer_wait(smem_pipe_read_v, pipeline_v.consumer_try_wait(smem_pipe_read_v))
|
| 1444 |
+
# O += P @ V
|
| 1445 |
+
mma_pv_fn(B_idx=smem_pipe_read_v.index, wg_wait=-1)
|
| 1446 |
+
self.warp_scheduler_barrier_arrive()
|
| 1447 |
+
warpgroup.wait_group(1)
|
| 1448 |
+
pipeline_k.consumer_release(smem_pipe_read)
|
| 1449 |
+
|
| 1450 |
+
# handle score mods and masking
|
| 1451 |
+
if const_expr(score_mod_fn is not None):
|
| 1452 |
+
score_mod_fn(acc_S, n_block=n_block, seqlen=seqlen)
|
| 1453 |
+
if const_expr(mask_fn is not None):
|
| 1454 |
+
mask_fn(acc_S=acc_S, n_block=n_block)
|
| 1455 |
+
# if cute.arch.thread_idx()[0] == 128: cute.print_tensor(layout_utils.reshape_acc_to_mn(acc_S))
|
| 1456 |
+
|
| 1457 |
+
row_scale = softmax.online_softmax(acc_S, check_inf=check_inf)
|
| 1458 |
+
warpgroup.wait_group(0)
|
| 1459 |
+
pipeline_v.consumer_release(smem_pipe_read_v)
|
| 1460 |
+
tOrP_acc = layout_utils.reshape_acc_to_frgA(acc_S)
|
| 1461 |
+
tOrP_cur = (
|
| 1462 |
+
tOrP
|
| 1463 |
+
if const_expr(self.mma_pv_is_rs)
|
| 1464 |
+
else cute.make_rmem_tensor_like(tOrP_acc, self.dtype)
|
| 1465 |
+
)
|
| 1466 |
+
# tOrP_cur.store(tOrP_acc.load().to(self.dtype))
|
| 1467 |
+
# the "to(self.dtype)" conversion fails to vectorize for block sizes other
|
| 1468 |
+
# than 128 x 128, i.e. it calls convert on 1 fp32 element at a time instead of
|
| 1469 |
+
# 2 elements. So we just call ptx directly.
|
| 1470 |
+
utils.cvt_f16(tOrP_acc, tOrP_cur)
|
| 1471 |
+
if const_expr(not self.mma_pv_is_rs):
|
| 1472 |
+
tPrP = smem_copy_params.smem_thr_copy_P.retile(tOrP_cur)
|
| 1473 |
+
cute.copy(smem_copy_params.smem_thr_copy_P, tPrP, smem_copy_params.tPsP)
|
| 1474 |
+
if const_expr(not self.rescale_O_before_gemm):
|
| 1475 |
+
softmax.rescale_O(acc_O, row_scale)
|
| 1476 |
+
if const_expr(self.rescale_O_before_gemm):
|
| 1477 |
+
scores_scale.store(row_scale.load())
|
| 1478 |
+
if const_expr(not self.mma_pv_is_rs):
|
| 1479 |
+
# Fence and barrier to make sure smem store is visible to WGMMA
|
| 1480 |
+
cute.arch.fence_view_async_shared()
|
| 1481 |
+
cute.arch.sync_warp() # Only need syncwarp since each warp is using its own P values for MmaPV
|
| 1482 |
+
return smem_pipe_read
|
| 1483 |
+
|
| 1484 |
+
@cute.jit
|
| 1485 |
+
def mma_init(self):
|
| 1486 |
+
warp_group_idx = utils.canonical_warp_group_idx(sync=False)
|
| 1487 |
+
if const_expr(self.use_scheduler_barrier):
|
| 1488 |
+
if warp_group_idx == 1:
|
| 1489 |
+
cute.arch.barrier_arrive(
|
| 1490 |
+
barrier_id=int(NamedBarrierFwd.WarpSchedulerWG1),
|
| 1491 |
+
number_of_threads=2 * self.num_threads_per_warp_group,
|
| 1492 |
+
)
|
| 1493 |
+
|
| 1494 |
+
@cute.jit
|
| 1495 |
+
def apply_score_mod(
|
| 1496 |
+
self,
|
| 1497 |
+
thr_mma_qk,
|
| 1498 |
+
batch_idx,
|
| 1499 |
+
head_idx,
|
| 1500 |
+
m_block,
|
| 1501 |
+
acc_S,
|
| 1502 |
+
n_block,
|
| 1503 |
+
softmax_scale,
|
| 1504 |
+
seqlen,
|
| 1505 |
+
aux_data: AuxData = AuxData(),
|
| 1506 |
+
fastdiv_mods=None,
|
| 1507 |
+
):
|
| 1508 |
+
# Prepare index tensor
|
| 1509 |
+
cS = cute.make_identity_tensor((self.tile_m, self.tile_n))
|
| 1510 |
+
cS = cute.domain_offset((m_block * self.tile_m, n_block * self.tile_n), cS)
|
| 1511 |
+
tScS = thr_mma_qk.partition_C(cS)
|
| 1512 |
+
|
| 1513 |
+
apply_score_mod_inner(
|
| 1514 |
+
acc_S,
|
| 1515 |
+
tScS,
|
| 1516 |
+
self.score_mod,
|
| 1517 |
+
batch_idx,
|
| 1518 |
+
head_idx,
|
| 1519 |
+
softmax_scale,
|
| 1520 |
+
self.score_vec_size,
|
| 1521 |
+
self.qk_acc_dtype,
|
| 1522 |
+
aux_data,
|
| 1523 |
+
fastdiv_mods,
|
| 1524 |
+
seqlen_info=seqlen,
|
| 1525 |
+
constant_q_idx=None,
|
| 1526 |
+
qhead_per_kvhead=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 1527 |
+
)
|
| 1528 |
+
|
| 1529 |
+
def warp_scheduler_barrier_sync(self):
|
| 1530 |
+
if const_expr(self.use_scheduler_barrier):
|
| 1531 |
+
cute.arch.barrier(
|
| 1532 |
+
barrier_id=int(NamedBarrierFwd.WarpSchedulerWG1)
|
| 1533 |
+
- 1
|
| 1534 |
+
+ utils.canonical_warp_group_idx(sync=False),
|
| 1535 |
+
number_of_threads=2 * self.num_threads_per_warp_group,
|
| 1536 |
+
)
|
| 1537 |
+
|
| 1538 |
+
def warp_scheduler_barrier_arrive(self):
|
| 1539 |
+
if const_expr(self.use_scheduler_barrier):
|
| 1540 |
+
assert self.num_wg_mma in [2, 3]
|
| 1541 |
+
cur_wg = utils.canonical_warp_group_idx(sync=False) - 1
|
| 1542 |
+
if const_expr(self.num_wg_mma == 2):
|
| 1543 |
+
next_wg = 1 - cur_wg
|
| 1544 |
+
else:
|
| 1545 |
+
t = cur_wg + 1
|
| 1546 |
+
next_wg = t % self.num_wg_mma
|
| 1547 |
+
cute.arch.barrier_arrive(
|
| 1548 |
+
barrier_id=int(NamedBarrierFwd.WarpSchedulerWG1) + next_wg,
|
| 1549 |
+
number_of_threads=2 * self.num_threads_per_warp_group,
|
| 1550 |
+
)
|
build/torch-cuda/interface.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
build/torch-cuda/mask.py
CHANGED
|
@@ -1,108 +1,157 @@
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
| 3 |
-
from typing import Optional, Callable
|
| 4 |
from dataclasses import dataclass
|
|
|
|
| 5 |
|
| 6 |
import cutlass
|
| 7 |
import cutlass.cute as cute
|
| 8 |
-
from cutlass import Float32, Int32, const_expr
|
|
|
|
| 9 |
|
| 10 |
from .quack import layout_utils
|
| 11 |
-
from . import utils
|
|
|
|
| 12 |
from .seqlen_info import SeqlenInfoQK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
@cute.jit
|
| 16 |
-
def
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
| 25 |
ncol = const_expr(cute.size(X.shape[cute.rank(X) - 1]) if not rank1 else cute.size(X.shape))
|
| 26 |
-
#
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
# 0 -> 0b00...00, 1 -> 0b00...01, ..., 31 -> 0b01...11, 32 -> 0b11...11
|
| 31 |
-
mask = (1 << col_limit_right_s) - 1
|
| 32 |
# This needs to be range_constexpr, o/w the compiler can't generate the R2P instruction
|
| 33 |
-
for i in cutlass.range_constexpr(min(
|
| 34 |
-
in_bound = cutlass.Boolean(mask & (1 << i))
|
| 35 |
-
c = s *
|
| 36 |
if const_expr(rank1):
|
| 37 |
X[c] = X[c] if in_bound else -Float32.inf
|
| 38 |
-
# This is the equivalent of:
|
| 39 |
-
# X[s * 24 + i] = X[s * 24 + i] if col_limit_right_s <= i else -Float32.inf
|
| 40 |
else:
|
| 41 |
for r in cutlass.range_constexpr(cute.size(X.shape[0])):
|
| 42 |
X[r, c] = X[r, c] if in_bound else -Float32.inf
|
| 43 |
|
| 44 |
|
| 45 |
@cute.jit
|
| 46 |
-
def
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
row_limit_top % (num_rep * num_wg), num_rep
|
| 55 |
-
)
|
| 56 |
-
ncol = cute.size(X.shape)
|
| 57 |
-
# Ideally we'd move by 32 instead of 24, but mask >> i isn't correct for i == 31
|
| 58 |
-
for s in cutlass.range_constexpr(cute.ceil_div(ncol, 24)):
|
| 59 |
-
row_limit_top_s = max(row_limit_top_transformed - s * 24, 0)
|
| 60 |
-
# 0 -> 0b00...00, 1 -> 0b00...01, ..., 31 -> 0b01...11, 32 -> 0b11...11
|
| 61 |
-
mask = (1 << row_limit_top_s) - 1
|
| 62 |
-
# This needs to be range_constexpr, o/w the compiler can't generate the R2P instruction
|
| 63 |
-
for i in cutlass.range_constexpr(min(24, ncol - s * 24)):
|
| 64 |
-
out_bound = cutlass.Boolean(mask & (1 << i))
|
| 65 |
-
c = s * 24 + i
|
| 66 |
-
X[c] = -Float32.inf if out_bound else X[c]
|
| 67 |
-
# tidx = cute.arch.thread_idx()[0] % 256
|
| 68 |
-
# if tidx == 128:
|
| 69 |
-
# cute.printf("tidx = {}, s = {}, i = {}, row_limit_top = {}, row_limit_top_s = {}, mask = {}, out_bound = {}", tidx, s, i, row_limit_top, row_limit_top_s, mask, out_bound)
|
| 70 |
|
| 71 |
|
| 72 |
@cute.jit
|
| 73 |
-
def
|
| 74 |
-
|
| 75 |
-
col_limit_left: Int32, # Inclusive lower bound
|
| 76 |
-
col_limit_right: Int32, # Exclusive upper bound
|
| 77 |
-
) -> None:
|
| 78 |
-
"""
|
| 79 |
-
Dual-bound masking using two bitmasks for SM100, following mask_r2p.
|
| 80 |
-
Masks elements where: NOT (col_limit_left <= col < col_limit_right)
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
-
# bits (right-1)..left are 1
|
| 98 |
-
mask_right = (1 << right_s) - 1
|
| 99 |
-
mask_left = (1 << left_s) - 1
|
| 100 |
-
mask_range = mask_right & ~mask_left
|
| 101 |
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
X[c] = X[c] if in_bound else -Float32.inf
|
| 107 |
|
| 108 |
|
|
@@ -137,7 +186,7 @@ class AttentionMask:
|
|
| 137 |
mask_causal: cutlass.Constexpr[bool],
|
| 138 |
mask_local: cutlass.Constexpr[bool] = False,
|
| 139 |
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 140 |
-
|
| 141 |
fastdiv_mods=(None, None),
|
| 142 |
) -> None:
|
| 143 |
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
|
@@ -161,8 +210,7 @@ class AttentionMask:
|
|
| 161 |
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n - thr_col_offset
|
| 162 |
if const_expr(not mask_causal and not mask_local and mask_mod is None):
|
| 163 |
if const_expr(mask_seqlen):
|
| 164 |
-
|
| 165 |
-
r2p = const_expr(False and not self.swap_AB)
|
| 166 |
if const_expr(not r2p):
|
| 167 |
# traverse column index.
|
| 168 |
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
|
@@ -170,7 +218,8 @@ class AttentionMask:
|
|
| 170 |
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 171 |
acc_S_mn[r, c] = -Float32.inf if oob else acc_S_mn[r, c]
|
| 172 |
else:
|
| 173 |
-
|
|
|
|
| 174 |
|
| 175 |
elif const_expr(
|
| 176 |
not mask_causal and not mask_local and mask_mod is not None
|
|
@@ -183,7 +232,7 @@ class AttentionMask:
|
|
| 183 |
and fastdiv_mods[1] is not None
|
| 184 |
)
|
| 185 |
wrap_aux_indices = const_expr(
|
| 186 |
-
has_fastdiv and mask_seqlen and const_expr(
|
| 187 |
)
|
| 188 |
|
| 189 |
for r in cutlass.range_constexpr(nrow):
|
|
@@ -212,13 +261,14 @@ class AttentionMask:
|
|
| 212 |
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32)
|
| 213 |
q_idx_ssa = utils.scalar_to_ssa(row_for_mod, cutlass.Int32)
|
| 214 |
kv_idx_ssa = utils.scalar_to_ssa(col_for_mod, cutlass.Int32)
|
| 215 |
-
mask_value =
|
|
|
|
| 216 |
batch_idx_ssa,
|
| 217 |
head_idx_ssa,
|
| 218 |
q_idx_ssa,
|
| 219 |
kv_idx_ssa,
|
| 220 |
self.seqlen_info,
|
| 221 |
-
|
| 222 |
)
|
| 223 |
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 224 |
if const_expr(mask_seqlen):
|
|
@@ -272,7 +322,12 @@ class AttentionMask:
|
|
| 272 |
else acc_S_mn[r, c]
|
| 273 |
)
|
| 274 |
else:
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
else: # Local
|
| 277 |
local_row_offset_right = (
|
| 278 |
causal_row_offset + self.window_size_right
|
|
@@ -284,6 +339,7 @@ class AttentionMask:
|
|
| 284 |
if const_expr(self.window_size_left is not None)
|
| 285 |
else None
|
| 286 |
)
|
|
|
|
| 287 |
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 288 |
if const_expr(self.qhead_per_kvhead_packgqa == 1):
|
| 289 |
row_idx = tScS_mn[r, 0][0] + m_block * self.tile_m
|
|
@@ -302,13 +358,22 @@ class AttentionMask:
|
|
| 302 |
if const_expr(self.window_size_left is not None)
|
| 303 |
else 0
|
| 304 |
)
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
else: # swap_AB
|
| 313 |
assert self.qhead_per_kvhead_packgqa == 1
|
| 314 |
thr_row_offset = tScS_mn[0][ROW]
|
|
@@ -338,11 +403,18 @@ class AttentionMask:
|
|
| 338 |
# column, by setting row limit to be self.tile_m.
|
| 339 |
row_limit_top = (
|
| 340 |
self.tile_m
|
| 341 |
-
if col0 >= seqlenk_col_limit
|
| 342 |
-
else
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
)
|
| 344 |
-
# TODO: do we need col_limit_sink?
|
| 345 |
-
row_limit_bot = col0 - causal_row_offset + self.window_size_left
|
| 346 |
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 347 |
row_idx = t0ScS_mn[r, 0][ROW]
|
| 348 |
acc_S_mn[r, c] = (
|
|
@@ -351,6 +423,194 @@ class AttentionMask:
|
|
| 351 |
else acc_S_mn[r, c]
|
| 352 |
)
|
| 353 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
@cute.jit
|
| 355 |
def apply_mask_sm100(
|
| 356 |
self,
|
|
@@ -365,10 +625,13 @@ class AttentionMask:
|
|
| 365 |
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 366 |
batch_idx: Int32 = None,
|
| 367 |
head_idx: Int32 = None,
|
| 368 |
-
|
| 369 |
fastdiv_mods=(None, None),
|
| 370 |
head_divmod=None,
|
|
|
|
| 371 |
check_q_boundary: bool = False,
|
|
|
|
|
|
|
| 372 |
) -> None:
|
| 373 |
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 374 |
acc_shape = (self.tile_m, self.tile_n)
|
|
@@ -382,8 +645,18 @@ class AttentionMask:
|
|
| 382 |
if n_block < 0:
|
| 383 |
n_block = 0
|
| 384 |
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n
|
| 385 |
-
|
| 386 |
-
if const_expr(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
if const_expr(mask_seqlen):
|
| 388 |
if const_expr(not r2p):
|
| 389 |
for i in cutlass.range(cute.size(tScS_t2r.shape), unroll_full=True):
|
|
@@ -392,65 +665,58 @@ class AttentionMask:
|
|
| 392 |
# For some reason the 2 lines above generate really bad SASS
|
| 393 |
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= seqlenk_col_limit else acc_S[i]
|
| 394 |
else:
|
| 395 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
elif const_expr(not mask_causal and not mask_local and mask_mod is not None):
|
| 398 |
-
#
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
)
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
head_idx_ssa,
|
| 435 |
-
mask_row_ssa,
|
| 436 |
-
kv_idx_ssa,
|
| 437 |
-
self.seqlen_info,
|
| 438 |
-
aux_tensors,
|
| 439 |
)
|
| 440 |
-
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 441 |
-
acc_S[i] = acc_S[i] if cond else -Float32.inf
|
| 442 |
-
if const_expr(mask_seqlen):
|
| 443 |
-
acc_S[i] = -Float32.inf if global_col >= self.seqlen_k else acc_S[i]
|
| 444 |
-
if check_q_boundary:
|
| 445 |
-
acc_S[i] = -Float32.inf if mask_row >= self.seqlen_q else acc_S[i]
|
| 446 |
|
| 447 |
else: # Causal or local
|
| 448 |
-
causal_row_offset =
|
| 449 |
row_idx = tScS_t2r[0][0] + m_block * self.tile_m
|
| 450 |
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 451 |
row_idx = row_idx // self.qhead_per_kvhead_packgqa
|
| 452 |
if const_expr(mask_causal):
|
| 453 |
-
col_limit_right = row_idx + causal_row_offset
|
| 454 |
if const_expr(mask_seqlen):
|
| 455 |
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 456 |
# if cute.arch.thread_idx()[0] % 32 == 0:
|
|
@@ -460,15 +726,19 @@ class AttentionMask:
|
|
| 460 |
for i in cutlass.range(ncol, unroll_full=True):
|
| 461 |
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= col_limit_right else acc_S[i]
|
| 462 |
else:
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
else:
|
| 465 |
local_row_offset_right = (
|
| 466 |
-
causal_row_offset + self.window_size_right
|
| 467 |
if const_expr(self.window_size_right is not None)
|
| 468 |
else None
|
| 469 |
)
|
| 470 |
local_row_offset_left = (
|
| 471 |
-
causal_row_offset -
|
| 472 |
if const_expr(self.window_size_left is not None)
|
| 473 |
else None
|
| 474 |
)
|
|
@@ -493,8 +763,15 @@ class AttentionMask:
|
|
| 493 |
else acc_S[i]
|
| 494 |
)
|
| 495 |
else:
|
| 496 |
-
#
|
| 497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
|
| 499 |
@cute.jit
|
| 500 |
def apply_mask_sm100_transposed(
|
|
@@ -510,7 +787,7 @@ class AttentionMask:
|
|
| 510 |
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 511 |
batch_idx: Int32 = None,
|
| 512 |
head_idx: Int32 = None,
|
| 513 |
-
|
| 514 |
fastdiv_mods=(None, None),
|
| 515 |
is_full_block: bool = False,
|
| 516 |
check_m_boundary: bool = True,
|
|
@@ -518,7 +795,7 @@ class AttentionMask:
|
|
| 518 |
"""
|
| 519 |
Backward pass: mask S = K @ Q.T where n_block tiles seqlen_k and m_block tiles seqlen_q.
|
| 520 |
|
| 521 |
-
Coordinate
|
| 522 |
- ROW corresponds to Q (m_block)
|
| 523 |
- COL corresponds to KV (n_block)
|
| 524 |
|
|
@@ -568,7 +845,7 @@ class AttentionMask:
|
|
| 568 |
and fastdiv_mods[1] is not None
|
| 569 |
)
|
| 570 |
wrap_aux_indices = const_expr(
|
| 571 |
-
has_fastdiv and mask_seqlen and const_expr(
|
| 572 |
)
|
| 573 |
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 574 |
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32)
|
|
@@ -589,13 +866,14 @@ class AttentionMask:
|
|
| 589 |
q_idx_ssa = utils.scalar_to_ssa(q_idx_for_mod, cutlass.Int32)
|
| 590 |
kv_idx_ssa = utils.scalar_to_ssa(kv_idx_for_mod, cutlass.Int32)
|
| 591 |
|
| 592 |
-
mask_value =
|
|
|
|
| 593 |
batch_idx_ssa,
|
| 594 |
head_idx_ssa,
|
| 595 |
q_idx_ssa,
|
| 596 |
kv_idx_ssa,
|
| 597 |
self.seqlen_info,
|
| 598 |
-
|
| 599 |
)
|
| 600 |
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 601 |
acc_S[i] = acc_S[i] if cond else -cutlass.Float32.inf
|
|
@@ -634,7 +912,13 @@ class AttentionMask:
|
|
| 634 |
)
|
| 635 |
else:
|
| 636 |
num_rep = cute.size(tScS_t2r, mode=[0]) # 16 or 32
|
| 637 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
else:
|
| 639 |
if const_expr(self.window_size_right is not None):
|
| 640 |
row_limit_top = causal_offset - self.window_size_right
|
|
@@ -645,9 +929,783 @@ class AttentionMask:
|
|
| 645 |
if const_expr(mask_seqlen):
|
| 646 |
if seqlenk_col_limit <= 0:
|
| 647 |
row_limit_top = self.tile_m
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
local_mask
|
| 653 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 .quack import layout_utils
|
| 13 |
+
from . import utils as utils
|
| 14 |
+
from .block_info import BlockInfo
|
| 15 |
from .seqlen_info import SeqlenInfoQK
|
| 16 |
+
from .utils import AuxData
|
| 17 |
+
|
| 18 |
+
MaskGenFn: TypeAlias = Callable[[int], Uint32]
|
| 19 |
+
MASK_R2P_CHUNK_SIZE: int = 32
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@cute.jit
|
| 23 |
+
def call_mask_mod(
|
| 24 |
+
mask_mod: cutlass.Constexpr,
|
| 25 |
+
batch_idx,
|
| 26 |
+
head_idx,
|
| 27 |
+
q_idx,
|
| 28 |
+
kv_idx,
|
| 29 |
+
seqlen_info,
|
| 30 |
+
aux_data: AuxData,
|
| 31 |
+
):
|
| 32 |
+
# Compatibility shim for pre-aux_scalars mask_mod callables.
|
| 33 |
+
if const_expr(aux_data.scalars is not None):
|
| 34 |
+
return mask_mod(
|
| 35 |
+
batch_idx,
|
| 36 |
+
head_idx,
|
| 37 |
+
q_idx,
|
| 38 |
+
kv_idx,
|
| 39 |
+
seqlen_info,
|
| 40 |
+
aux_data.tensors,
|
| 41 |
+
aux_data.scalars,
|
| 42 |
+
)
|
| 43 |
+
return mask_mod(
|
| 44 |
+
batch_idx,
|
| 45 |
+
head_idx,
|
| 46 |
+
q_idx,
|
| 47 |
+
kv_idx,
|
| 48 |
+
seqlen_info,
|
| 49 |
+
aux_data.tensors,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@cute.jit
|
| 54 |
+
def r2p_bitmask_below(limit: Int32, s: int) -> Uint32:
|
| 55 |
+
"""32-bit R2P bitmask keeping positions < limit (exclusive upper bound).
|
| 56 |
+
|
| 57 |
+
Positions 0..limit-1 in chunk `s` get bit=1 (keep), the rest bit=0 (mask).
|
| 58 |
+
Uses inline PTX to avoid shift-by-type-width UB.
|
| 59 |
+
"""
|
| 60 |
+
m = max((s + 1) * MASK_R2P_CHUNK_SIZE - limit, 0)
|
| 61 |
+
return utils.shr_u32(Uint32(0xFFFFFFFF), Uint32(m))
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@cute.jit
|
| 65 |
+
def r2p_bitmask_above(limit: Int32, s: int) -> Uint32:
|
| 66 |
+
"""32-bit R2P bitmask keeping positions >= limit (inclusive lower bound).
|
| 67 |
+
|
| 68 |
+
Positions limit..31 in chunk `s` get bit=1 (keep), the rest bit=0 (mask).
|
| 69 |
+
Uses inline PTX to avoid shift-by-type-width UB.
|
| 70 |
+
"""
|
| 71 |
+
n = max(limit - s * MASK_R2P_CHUNK_SIZE, 0)
|
| 72 |
+
return utils.shl_u32(Uint32(0xFFFFFFFF), Uint32(n))
|
| 73 |
|
| 74 |
|
| 75 |
@cute.jit
|
| 76 |
+
def mask_r2p_lambda(
|
| 77 |
+
X: cute.Tensor,
|
| 78 |
+
mask_gen_fn: cutlass.Constexpr[MaskGenFn],
|
| 79 |
+
rank1: bool = False,
|
| 80 |
+
) -> None:
|
| 81 |
+
"""Apply R2P masking with a custom bitmask generator.
|
| 82 |
+
|
| 83 |
+
mask_gen_fn(chunk_idx: constexpr int) -> Uint32:
|
| 84 |
+
Returns a 32-bit bitmask for the chunk. Bit i set means column
|
| 85 |
+
chunk_idx * chunk_size + i is KEPT; bit i clear means masked to -inf.
|
| 86 |
+
"""
|
| 87 |
ncol = const_expr(cute.size(X.shape[cute.rank(X) - 1]) if not rank1 else cute.size(X.shape))
|
| 88 |
+
# 32-column chunks. The mask_gen_fn returns a Uint32 bitmask (1=keep).
|
| 89 |
+
CHUNK_SIZE = MASK_R2P_CHUNK_SIZE
|
| 90 |
+
for s in cutlass.range_constexpr(cute.ceil_div(ncol, CHUNK_SIZE)):
|
| 91 |
+
mask = mask_gen_fn(s)
|
|
|
|
|
|
|
| 92 |
# This needs to be range_constexpr, o/w the compiler can't generate the R2P instruction
|
| 93 |
+
for i in cutlass.range_constexpr(min(CHUNK_SIZE, ncol - s * CHUNK_SIZE)):
|
| 94 |
+
in_bound = cutlass.Boolean(mask & (Uint32(1) << i))
|
| 95 |
+
c = s * CHUNK_SIZE + i
|
| 96 |
if const_expr(rank1):
|
| 97 |
X[c] = X[c] if in_bound else -Float32.inf
|
|
|
|
|
|
|
| 98 |
else:
|
| 99 |
for r in cutlass.range_constexpr(cute.size(X.shape[0])):
|
| 100 |
X[r, c] = X[r, c] if in_bound else -Float32.inf
|
| 101 |
|
| 102 |
|
| 103 |
@cute.jit
|
| 104 |
+
def sm90_col_to_r2p_idx(col_limit: Int32) -> Int32:
|
| 105 |
+
"""Transform SM90 MMA column coordinate to R2P element index.
|
| 106 |
+
|
| 107 |
+
SM90 MMA accumulator column indices are non-contiguous: 0, 1, 8, 9, 16, 17, ...
|
| 108 |
+
Element indices are contiguous: 0, 1, 2, 3, 4, 5, ...
|
| 109 |
+
This converts a column-space threshold to element-space for r2p_bitmask_below/above.
|
| 110 |
+
"""
|
| 111 |
+
return col_limit // 8 * 2 + min(col_limit % 8, 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
|
| 114 |
@cute.jit
|
| 115 |
+
def row_to_r2p_idx(x: Int32, num_rep: int, num_wg: int) -> Int32:
|
| 116 |
+
"""Convert a row coordinate to an R2P element index in the warp-group interleaved layout.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
+
In the SM100 backward pass, 2 warp groups share TMEM. The TMEM load atom
|
| 119 |
+
distributes rows in an interleaved pattern: elements 0..num_rep-1 map to
|
| 120 |
+
rows 0..num_rep-1 (warp group 0), elements num_rep..2*num_rep-1 map to
|
| 121 |
+
rows num_rep*num_wg..num_rep*num_wg+num_rep-1 (warp group 1), and so on.
|
| 122 |
+
Row-coordinate thresholds (causal limits, window bounds, uih_len) must be
|
| 123 |
+
converted to element indices before use with r2p_bitmask_above/below.
|
| 124 |
|
| 125 |
+
Rows not owned by this thread (in the gap between warp groups) are clamped
|
| 126 |
+
to the boundary element index, which is safe because R2P thresholds are
|
| 127 |
+
monotonic.
|
| 128 |
|
| 129 |
+
Example with num_rep=16, num_wg=2:
|
| 130 |
+
row 0 -> elem 0, row 15 -> elem 15,
|
| 131 |
+
row 16 -> elem 16 (clamped), row 31 -> elem 16 (clamped),
|
| 132 |
+
row 32 -> elem 16, row 33 -> elem 17, row 47 -> elem 31.
|
| 133 |
+
"""
|
| 134 |
+
return x // (num_rep * num_wg) * num_rep + min(x % (num_rep * num_wg), num_rep)
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
+
@cute.jit
|
| 138 |
+
def apply_packed_mask_chunk(
|
| 139 |
+
X: cute.Tensor,
|
| 140 |
+
chunk_idx: cutlass.Constexpr[int],
|
| 141 |
+
mask: Uint32,
|
| 142 |
+
) -> None:
|
| 143 |
+
"""Apply one 32-bit keep mask to one 32-column chunk.
|
| 144 |
+
|
| 145 |
+
The one-iteration chunk loop keeps the same lowering pattern as mask_r2p_lambda.
|
| 146 |
+
"""
|
| 147 |
+
ncol = const_expr(cute.size(X.shape))
|
| 148 |
+
col_base = chunk_idx * MASK_R2P_CHUNK_SIZE
|
| 149 |
+
for s in cutlass.range_constexpr(1):
|
| 150 |
+
for i in cutlass.range_constexpr(
|
| 151 |
+
min(MASK_R2P_CHUNK_SIZE, ncol - col_base - s * MASK_R2P_CHUNK_SIZE)
|
| 152 |
+
):
|
| 153 |
+
in_bound = cutlass.Boolean(mask & (Uint32(1) << i))
|
| 154 |
+
c = col_base + s * MASK_R2P_CHUNK_SIZE + i
|
| 155 |
X[c] = X[c] if in_bound else -Float32.inf
|
| 156 |
|
| 157 |
|
|
|
|
| 186 |
mask_causal: cutlass.Constexpr[bool],
|
| 187 |
mask_local: cutlass.Constexpr[bool] = False,
|
| 188 |
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 189 |
+
aux_data: AuxData = AuxData(),
|
| 190 |
fastdiv_mods=(None, None),
|
| 191 |
) -> None:
|
| 192 |
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
|
|
|
| 210 |
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n - thr_col_offset
|
| 211 |
if const_expr(not mask_causal and not mask_local and mask_mod is None):
|
| 212 |
if const_expr(mask_seqlen):
|
| 213 |
+
r2p = const_expr(not self.swap_AB)
|
|
|
|
| 214 |
if const_expr(not r2p):
|
| 215 |
# traverse column index.
|
| 216 |
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
|
|
|
| 218 |
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 219 |
acc_S_mn[r, c] = -Float32.inf if oob else acc_S_mn[r, c]
|
| 220 |
else:
|
| 221 |
+
seqlenk_col_limit_r2p = sm90_col_to_r2p_idx(seqlenk_col_limit)
|
| 222 |
+
mask_r2p_lambda(acc_S_mn, lambda s: r2p_bitmask_below(seqlenk_col_limit_r2p, s))
|
| 223 |
|
| 224 |
elif const_expr(
|
| 225 |
not mask_causal and not mask_local and mask_mod is not None
|
|
|
|
| 232 |
and fastdiv_mods[1] is not None
|
| 233 |
)
|
| 234 |
wrap_aux_indices = const_expr(
|
| 235 |
+
has_fastdiv and mask_seqlen and const_expr(aux_data.tensors is not None)
|
| 236 |
)
|
| 237 |
|
| 238 |
for r in cutlass.range_constexpr(nrow):
|
|
|
|
| 261 |
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32)
|
| 262 |
q_idx_ssa = utils.scalar_to_ssa(row_for_mod, cutlass.Int32)
|
| 263 |
kv_idx_ssa = utils.scalar_to_ssa(col_for_mod, cutlass.Int32)
|
| 264 |
+
mask_value = call_mask_mod(
|
| 265 |
+
mask_mod,
|
| 266 |
batch_idx_ssa,
|
| 267 |
head_idx_ssa,
|
| 268 |
q_idx_ssa,
|
| 269 |
kv_idx_ssa,
|
| 270 |
self.seqlen_info,
|
| 271 |
+
aux_data,
|
| 272 |
)
|
| 273 |
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 274 |
if const_expr(mask_seqlen):
|
|
|
|
| 322 |
else acc_S_mn[r, c]
|
| 323 |
)
|
| 324 |
else:
|
| 325 |
+
col_limit_r2p = sm90_col_to_r2p_idx(col_limit_right)
|
| 326 |
+
mask_r2p_lambda(
|
| 327 |
+
acc_S_mn[r, None],
|
| 328 |
+
lambda s: r2p_bitmask_below(col_limit_r2p, s),
|
| 329 |
+
rank1=True,
|
| 330 |
+
)
|
| 331 |
else: # Local
|
| 332 |
local_row_offset_right = (
|
| 333 |
causal_row_offset + self.window_size_right
|
|
|
|
| 339 |
if const_expr(self.window_size_left is not None)
|
| 340 |
else None
|
| 341 |
)
|
| 342 |
+
r2p_local = const_expr(not self.swap_AB)
|
| 343 |
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 344 |
if const_expr(self.qhead_per_kvhead_packgqa == 1):
|
| 345 |
row_idx = tScS_mn[r, 0][0] + m_block * self.tile_m
|
|
|
|
| 358 |
if const_expr(self.window_size_left is not None)
|
| 359 |
else 0
|
| 360 |
)
|
| 361 |
+
if const_expr(not r2p_local):
|
| 362 |
+
# traverse column index.
|
| 363 |
+
for c in cutlass.range(cute.size(tScS_mn.shape[1]), unroll_full=True):
|
| 364 |
+
col_idx = t0ScS_mn[0, c][1]
|
| 365 |
+
if col_idx >= col_limit_right or col_idx < col_limit_left:
|
| 366 |
+
acc_S_mn[r, c] = -Float32.inf
|
| 367 |
+
else:
|
| 368 |
+
col_limit_right_r2p = sm90_col_to_r2p_idx(col_limit_right)
|
| 369 |
+
col_limit_left_r2p = sm90_col_to_r2p_idx(col_limit_left)
|
| 370 |
+
|
| 371 |
+
def mask_gen_fn(s: int) -> Uint32:
|
| 372 |
+
return r2p_bitmask_below(
|
| 373 |
+
col_limit_right_r2p, s
|
| 374 |
+
) & r2p_bitmask_above(col_limit_left_r2p, s)
|
| 375 |
+
|
| 376 |
+
mask_r2p_lambda(acc_S_mn[r, None], mask_gen_fn, rank1=True)
|
| 377 |
else: # swap_AB
|
| 378 |
assert self.qhead_per_kvhead_packgqa == 1
|
| 379 |
thr_row_offset = tScS_mn[0][ROW]
|
|
|
|
| 403 |
# column, by setting row limit to be self.tile_m.
|
| 404 |
row_limit_top = (
|
| 405 |
self.tile_m
|
| 406 |
+
if col0 >= seqlenk_col_limit and mask_seqlen
|
| 407 |
+
else (
|
| 408 |
+
col0 - causal_row_offset - self.window_size_right
|
| 409 |
+
if const_expr(self.window_size_right is not None)
|
| 410 |
+
else 0
|
| 411 |
+
)
|
| 412 |
+
)
|
| 413 |
+
row_limit_bot = (
|
| 414 |
+
col0 - causal_row_offset + self.window_size_left
|
| 415 |
+
if const_expr(self.window_size_left is not None)
|
| 416 |
+
else self.tile_m
|
| 417 |
)
|
|
|
|
|
|
|
| 418 |
for r in cutlass.range(cute.size(tScS_mn.shape[0]), unroll_full=True):
|
| 419 |
row_idx = t0ScS_mn[r, 0][ROW]
|
| 420 |
acc_S_mn[r, c] = (
|
|
|
|
| 423 |
else acc_S_mn[r, c]
|
| 424 |
)
|
| 425 |
|
| 426 |
+
@cute.jit
|
| 427 |
+
def apply_mask_mod_sm100_scalar(
|
| 428 |
+
self,
|
| 429 |
+
acc_S: cute.Tensor,
|
| 430 |
+
tScS_t2r: cute.Tensor,
|
| 431 |
+
m_block: Int32,
|
| 432 |
+
n_block: Int32,
|
| 433 |
+
mask_seqlen: cutlass.Constexpr[bool],
|
| 434 |
+
mask_mod: cutlass.Constexpr[Callable],
|
| 435 |
+
batch_idx: Int32,
|
| 436 |
+
head_idx: Int32,
|
| 437 |
+
aux_data: AuxData = AuxData(),
|
| 438 |
+
fastdiv_mods=(None, None),
|
| 439 |
+
head_divmod=None,
|
| 440 |
+
check_q_boundary: bool = False,
|
| 441 |
+
) -> None:
|
| 442 |
+
"""Apply a scalar FlexAttention mask_mod to an SM100 accumulator fragment.
|
| 443 |
+
|
| 444 |
+
Each accumulator lane calls mask_mod once with logical (batch, head, q, kv)
|
| 445 |
+
indices. Pack-GQA rows are converted back to logical q/head indices before
|
| 446 |
+
the call. When aux tensors are present, indices are wrapped with fastdiv so
|
| 447 |
+
mask_mod never reads outside the per-example auxiliary storage.
|
| 448 |
+
"""
|
| 449 |
+
has_fastdiv = const_expr(
|
| 450 |
+
fastdiv_mods is not None and fastdiv_mods[0] is not None and fastdiv_mods[1] is not None
|
| 451 |
+
)
|
| 452 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 453 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 454 |
+
|
| 455 |
+
for i in cutlass.range(ncol, unroll_full=True):
|
| 456 |
+
row_coord = tScS_t2r[i][0] if not self.swap_AB else tScS_t2r[i][1]
|
| 457 |
+
col_coord = tScS_t2r[i][1] if not self.swap_AB else tScS_t2r[i][0]
|
| 458 |
+
global_row = row_coord + m_block * self.tile_m
|
| 459 |
+
global_col = col_coord + n_block * self.tile_n
|
| 460 |
+
|
| 461 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 462 |
+
assert head_divmod is not None
|
| 463 |
+
mask_row, head_offset = divmod(global_row, head_divmod)
|
| 464 |
+
head_idx_for_mod = head_idx * self.qhead_per_kvhead_packgqa + head_offset
|
| 465 |
+
else:
|
| 466 |
+
head_idx_for_mod = head_idx
|
| 467 |
+
mask_row = global_row
|
| 468 |
+
|
| 469 |
+
mask_row_for_mod = mask_row
|
| 470 |
+
if const_expr(has_fastdiv and aux_data.tensors is not None):
|
| 471 |
+
if check_q_boundary:
|
| 472 |
+
_, mask_row_for_mod = divmod(mask_row, fastdiv_mods[0])
|
| 473 |
+
global_col_for_mod = global_col
|
| 474 |
+
if const_expr(has_fastdiv and mask_seqlen and aux_data.tensors is not None):
|
| 475 |
+
_, global_col_for_mod = divmod(global_col, fastdiv_mods[1])
|
| 476 |
+
|
| 477 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32)
|
| 478 |
+
mask_row_ssa = utils.scalar_to_ssa(mask_row_for_mod, cutlass.Int32)
|
| 479 |
+
kv_idx_ssa = utils.scalar_to_ssa(global_col_for_mod, cutlass.Int32)
|
| 480 |
+
mask_value = call_mask_mod(
|
| 481 |
+
mask_mod,
|
| 482 |
+
batch_idx_ssa,
|
| 483 |
+
head_idx_ssa,
|
| 484 |
+
mask_row_ssa,
|
| 485 |
+
kv_idx_ssa,
|
| 486 |
+
self.seqlen_info,
|
| 487 |
+
aux_data,
|
| 488 |
+
)
|
| 489 |
+
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 490 |
+
acc_S[i] = acc_S[i] if cond else -Float32.inf
|
| 491 |
+
if const_expr(mask_seqlen):
|
| 492 |
+
acc_S[i] = -Float32.inf if global_col >= self.seqlen_k else acc_S[i]
|
| 493 |
+
if check_q_boundary:
|
| 494 |
+
acc_S[i] = -Float32.inf if mask_row >= self.seqlen_q else acc_S[i]
|
| 495 |
+
|
| 496 |
+
@cute.jit
|
| 497 |
+
def apply_mask_mod_sm100_vector(
|
| 498 |
+
self,
|
| 499 |
+
acc_S: cute.Tensor,
|
| 500 |
+
tScS_t2r: cute.Tensor,
|
| 501 |
+
m_block: Int32,
|
| 502 |
+
n_block: Int32,
|
| 503 |
+
mask_seqlen: cutlass.Constexpr[bool],
|
| 504 |
+
mask_mod: cutlass.Constexpr[Callable],
|
| 505 |
+
batch_idx: Int32,
|
| 506 |
+
head_idx: Int32,
|
| 507 |
+
vec_size: cutlass.Constexpr[int],
|
| 508 |
+
aux_data: AuxData = AuxData(),
|
| 509 |
+
fastdiv_mods=(None, None),
|
| 510 |
+
head_divmod=None,
|
| 511 |
+
check_q_boundary: bool = False,
|
| 512 |
+
) -> None:
|
| 513 |
+
"""Apply a vectorized FlexAttention mask_mod to an SM100 fragment.
|
| 514 |
+
|
| 515 |
+
mask_mod receives vec_size adjacent KV indices for one logical q row and
|
| 516 |
+
returns bit-packed Uint32 keep masks. Low bits correspond to lower KV
|
| 517 |
+
indices. The packed masks are combined with sequence-boundary checks, then
|
| 518 |
+
applied in 32-column chunks so the final masking lowers to R2P.
|
| 519 |
+
"""
|
| 520 |
+
has_fastdiv = const_expr(
|
| 521 |
+
fastdiv_mods is not None and fastdiv_mods[0] is not None and fastdiv_mods[1] is not None
|
| 522 |
+
)
|
| 523 |
+
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 524 |
+
ncol = const_expr(cute.size(tScS_t2r.shape))
|
| 525 |
+
mask_vals_per_apply = const_expr(max(1, vec_size // 32))
|
| 526 |
+
calls_per_apply = const_expr(max(1, 32 // vec_size))
|
| 527 |
+
n_calls = const_expr(cute.ceil_div(ncol, vec_size))
|
| 528 |
+
mask_vals = cute.make_rmem_tensor(mask_vals_per_apply, dtype=cutlass.Uint32)
|
| 529 |
+
|
| 530 |
+
# Accumulate enough vector mask_mod calls to produce 32-bit chunks that
|
| 531 |
+
# apply_packed_mask_chunk can lower to R2P.
|
| 532 |
+
for s in cutlass.range_constexpr(n_calls):
|
| 533 |
+
if const_expr(s % calls_per_apply == 0):
|
| 534 |
+
for c in cutlass.range_constexpr(mask_vals_per_apply):
|
| 535 |
+
mask_vals[c] = cutlass.Uint32(0)
|
| 536 |
+
i = s * vec_size
|
| 537 |
+
row_coord = tScS_t2r[i][0] if not self.swap_AB else tScS_t2r[i][1]
|
| 538 |
+
col_coord = tScS_t2r[i][1] if not self.swap_AB else tScS_t2r[i][0]
|
| 539 |
+
global_row = row_coord + m_block * self.tile_m
|
| 540 |
+
global_col = col_coord + n_block * self.tile_n
|
| 541 |
+
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 542 |
+
assert head_divmod is not None
|
| 543 |
+
mask_row, head_offset = divmod(global_row, head_divmod)
|
| 544 |
+
head_idx_for_mod = head_idx * self.qhead_per_kvhead_packgqa + head_offset
|
| 545 |
+
else:
|
| 546 |
+
head_idx_for_mod = head_idx
|
| 547 |
+
mask_row = global_row
|
| 548 |
+
mask_row_for_mod = mask_row
|
| 549 |
+
if const_expr(has_fastdiv and aux_data.tensors is not None):
|
| 550 |
+
if check_q_boundary:
|
| 551 |
+
_, mask_row_for_mod = divmod(mask_row, fastdiv_mods[0])
|
| 552 |
+
|
| 553 |
+
head_idx_ssa = utils.scalar_to_ssa(head_idx_for_mod, cutlass.Int32).broadcast_to(
|
| 554 |
+
(vec_size,)
|
| 555 |
+
)
|
| 556 |
+
mask_row_ssa = utils.scalar_to_ssa(mask_row_for_mod, cutlass.Int32).broadcast_to(
|
| 557 |
+
(vec_size,)
|
| 558 |
+
)
|
| 559 |
+
batch_idx_ssa_call = batch_idx_ssa.broadcast_to((vec_size,))
|
| 560 |
+
kv_idx_vec = cute.make_rmem_tensor(vec_size, cutlass.Int32)
|
| 561 |
+
|
| 562 |
+
# Build the per-lane KV indices for this vectorized mask_mod call.
|
| 563 |
+
for j in cutlass.range_constexpr(min(vec_size, ncol - i)):
|
| 564 |
+
col_j_coord = tScS_t2r[i + j][1] if not self.swap_AB else tScS_t2r[i + j][0]
|
| 565 |
+
col_j_global = col_j_coord + n_block * self.tile_n
|
| 566 |
+
col_j_for_mod = col_j_global
|
| 567 |
+
if const_expr(has_fastdiv and mask_seqlen and aux_data.tensors is not None):
|
| 568 |
+
_, col_j_for_mod = divmod(col_j_global, fastdiv_mods[1])
|
| 569 |
+
kv_idx_vec[j] = col_j_for_mod
|
| 570 |
+
kv_idx_ssa = kv_idx_vec.load()
|
| 571 |
+
|
| 572 |
+
# mask_value is already bit-packed by the vectorized mask_mod.
|
| 573 |
+
mask_value = call_mask_mod(
|
| 574 |
+
mask_mod,
|
| 575 |
+
batch_idx_ssa_call,
|
| 576 |
+
head_idx_ssa,
|
| 577 |
+
mask_row_ssa,
|
| 578 |
+
kv_idx_ssa,
|
| 579 |
+
self.seqlen_info,
|
| 580 |
+
aux_data,
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
# For vec_size < 32, multiple mask_mod calls fill one R2P chunk.
|
| 584 |
+
bit_offset = const_expr((s % calls_per_apply) * vec_size)
|
| 585 |
+
seqlen_thresh_call = (
|
| 586 |
+
self.seqlen_k - global_col if const_expr(mask_seqlen) else cutlass.Int32(0)
|
| 587 |
+
)
|
| 588 |
+
q_in_bounds = mask_row < self.seqlen_q if check_q_boundary else cutlass.Boolean(True)
|
| 589 |
+
for c in cutlass.range_constexpr(mask_vals_per_apply):
|
| 590 |
+
mask_val = mask_value[c]
|
| 591 |
+
if const_expr(vec_size < 32):
|
| 592 |
+
lane_keep = utils.shr_u32(
|
| 593 |
+
cutlass.Uint32(0xFFFFFFFF),
|
| 594 |
+
cutlass.Uint32(32 - vec_size),
|
| 595 |
+
)
|
| 596 |
+
mask_val = mask_val & lane_keep
|
| 597 |
+
if const_expr(mask_seqlen):
|
| 598 |
+
mask_val = mask_val & r2p_bitmask_below(seqlen_thresh_call, c)
|
| 599 |
+
if check_q_boundary:
|
| 600 |
+
mask_val = mask_val if q_in_bounds else cutlass.Uint32(0)
|
| 601 |
+
mask_vals[c] = mask_vals[c] | (mask_val << bit_offset)
|
| 602 |
+
|
| 603 |
+
# Apply only when the 32-bit chunk is complete, or at the tile tail.
|
| 604 |
+
is_last_in_apply = const_expr(s % calls_per_apply == calls_per_apply - 1)
|
| 605 |
+
is_last_overall = const_expr(s == n_calls - 1)
|
| 606 |
+
if const_expr(is_last_in_apply or is_last_overall):
|
| 607 |
+
apply_idx = s // calls_per_apply
|
| 608 |
+
for c in cutlass.range_constexpr(mask_vals_per_apply):
|
| 609 |
+
chunk_idx = apply_idx * mask_vals_per_apply + c
|
| 610 |
+
# Skip packed chunks that start past the accumulator fragment.
|
| 611 |
+
if const_expr(chunk_idx * 32 < ncol):
|
| 612 |
+
apply_packed_mask_chunk(acc_S, chunk_idx, mask_vals[c])
|
| 613 |
+
|
| 614 |
@cute.jit
|
| 615 |
def apply_mask_sm100(
|
| 616 |
self,
|
|
|
|
| 625 |
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 626 |
batch_idx: Int32 = None,
|
| 627 |
head_idx: Int32 = None,
|
| 628 |
+
aux_data: AuxData = AuxData(),
|
| 629 |
fastdiv_mods=(None, None),
|
| 630 |
head_divmod=None,
|
| 631 |
+
vec_size: cutlass.Constexpr[int] = 1,
|
| 632 |
check_q_boundary: bool = False,
|
| 633 |
+
r2p: bool = True,
|
| 634 |
+
rBitmask: Optional[cute.Tensor] = None,
|
| 635 |
) -> None:
|
| 636 |
assert not (mask_causal and mask_local), "mask_causal and mask_local cannot be both True"
|
| 637 |
acc_shape = (self.tile_m, self.tile_n)
|
|
|
|
| 645 |
if n_block < 0:
|
| 646 |
n_block = 0
|
| 647 |
seqlenk_col_limit = self.seqlen_k - n_block * self.tile_n
|
| 648 |
+
|
| 649 |
+
if const_expr(rBitmask is not None):
|
| 650 |
+
ncol_packed = const_expr(cute.size(rBitmask.shape[0]))
|
| 651 |
+
for i in cutlass.range_constexpr(ncol_packed):
|
| 652 |
+
col_start = 32 * i # mask is bit-packed into uint32
|
| 653 |
+
curr_mask_val = rBitmask[i]
|
| 654 |
+
for j in cutlass.range_constexpr(32):
|
| 655 |
+
curr_col = col_start + j
|
| 656 |
+
mask = (curr_mask_val >> j) & 1
|
| 657 |
+
acc_S[curr_col] = acc_S[curr_col] if cutlass.Boolean(mask) else -Float32.inf
|
| 658 |
+
|
| 659 |
+
elif const_expr(not mask_causal and not mask_local and mask_mod is None):
|
| 660 |
if const_expr(mask_seqlen):
|
| 661 |
if const_expr(not r2p):
|
| 662 |
for i in cutlass.range(cute.size(tScS_t2r.shape), unroll_full=True):
|
|
|
|
| 665 |
# For some reason the 2 lines above generate really bad SASS
|
| 666 |
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= seqlenk_col_limit else acc_S[i]
|
| 667 |
else:
|
| 668 |
+
mask_r2p_lambda(
|
| 669 |
+
acc_S,
|
| 670 |
+
lambda s: r2p_bitmask_below(seqlenk_col_limit, s),
|
| 671 |
+
rank1=True,
|
| 672 |
+
)
|
| 673 |
|
| 674 |
elif const_expr(not mask_causal and not mask_local and mask_mod is not None):
|
| 675 |
+
# FlexAttention mask_mod vectorization is gated on `mask_mod.__vec_size__`.
|
| 676 |
+
# vec_size == 1 returns a scalar Boolean. vec_size > 1 returns packed
|
| 677 |
+
# Uint32 mask fragments: one word per 32 evaluated columns.
|
| 678 |
+
assert vec_size % 32 == 0 or 32 % vec_size == 0, (
|
| 679 |
+
"vec_size must divide 32 or be a multiple of 32"
|
| 680 |
)
|
| 681 |
+
if const_expr(vec_size == 1):
|
| 682 |
+
self.apply_mask_mod_sm100_scalar(
|
| 683 |
+
acc_S,
|
| 684 |
+
tScS_t2r,
|
| 685 |
+
m_block,
|
| 686 |
+
n_block,
|
| 687 |
+
mask_seqlen,
|
| 688 |
+
mask_mod,
|
| 689 |
+
batch_idx,
|
| 690 |
+
head_idx,
|
| 691 |
+
aux_data,
|
| 692 |
+
fastdiv_mods,
|
| 693 |
+
head_divmod,
|
| 694 |
+
check_q_boundary,
|
| 695 |
+
)
|
| 696 |
+
else:
|
| 697 |
+
self.apply_mask_mod_sm100_vector(
|
| 698 |
+
acc_S,
|
| 699 |
+
tScS_t2r,
|
| 700 |
+
m_block,
|
| 701 |
+
n_block,
|
| 702 |
+
mask_seqlen,
|
| 703 |
+
mask_mod,
|
| 704 |
+
batch_idx,
|
| 705 |
+
head_idx,
|
| 706 |
+
vec_size,
|
| 707 |
+
aux_data,
|
| 708 |
+
fastdiv_mods,
|
| 709 |
+
head_divmod,
|
| 710 |
+
check_q_boundary,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 711 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
|
| 713 |
else: # Causal or local
|
| 714 |
+
causal_row_offset = self.seqlen_k - n_block * self.tile_n - self.seqlen_q
|
| 715 |
row_idx = tScS_t2r[0][0] + m_block * self.tile_m
|
| 716 |
if const_expr(self.qhead_per_kvhead_packgqa != 1):
|
| 717 |
row_idx = row_idx // self.qhead_per_kvhead_packgqa
|
| 718 |
if const_expr(mask_causal):
|
| 719 |
+
col_limit_right = row_idx + causal_row_offset + 1
|
| 720 |
if const_expr(mask_seqlen):
|
| 721 |
col_limit_right = cutlass.min(col_limit_right, seqlenk_col_limit)
|
| 722 |
# if cute.arch.thread_idx()[0] % 32 == 0:
|
|
|
|
| 726 |
for i in cutlass.range(ncol, unroll_full=True):
|
| 727 |
acc_S[i] = -Float32.inf if tScS_t2r[i][1] >= col_limit_right else acc_S[i]
|
| 728 |
else:
|
| 729 |
+
mask_r2p_lambda(
|
| 730 |
+
acc_S,
|
| 731 |
+
lambda s: r2p_bitmask_below(col_limit_right, s),
|
| 732 |
+
rank1=True,
|
| 733 |
+
)
|
| 734 |
else:
|
| 735 |
local_row_offset_right = (
|
| 736 |
+
causal_row_offset + 1 + self.window_size_right
|
| 737 |
if const_expr(self.window_size_right is not None)
|
| 738 |
else None
|
| 739 |
)
|
| 740 |
local_row_offset_left = (
|
| 741 |
+
causal_row_offset - self.window_size_left
|
| 742 |
if const_expr(self.window_size_left is not None)
|
| 743 |
else None
|
| 744 |
)
|
|
|
|
| 763 |
else acc_S[i]
|
| 764 |
)
|
| 765 |
else:
|
| 766 |
+
# Dual-bound R2P masking for SM100.
|
| 767 |
+
# Masks elements where: NOT (col_limit_left <= col < col_limit_right)
|
| 768 |
+
|
| 769 |
+
def mask_gen_fn(s: int) -> Uint32:
|
| 770 |
+
return r2p_bitmask_below(col_limit_right, s) & r2p_bitmask_above(
|
| 771 |
+
col_limit_left, s
|
| 772 |
+
)
|
| 773 |
+
|
| 774 |
+
mask_r2p_lambda(acc_S, mask_gen_fn, rank1=True)
|
| 775 |
|
| 776 |
@cute.jit
|
| 777 |
def apply_mask_sm100_transposed(
|
|
|
|
| 787 |
mask_mod: cutlass.Constexpr[Optional[Callable]] = None,
|
| 788 |
batch_idx: Int32 = None,
|
| 789 |
head_idx: Int32 = None,
|
| 790 |
+
aux_data: AuxData = AuxData(),
|
| 791 |
fastdiv_mods=(None, None),
|
| 792 |
is_full_block: bool = False,
|
| 793 |
check_m_boundary: bool = True,
|
|
|
|
| 795 |
"""
|
| 796 |
Backward pass: mask S = K @ Q.T where n_block tiles seqlen_k and m_block tiles seqlen_q.
|
| 797 |
|
| 798 |
+
Coordinate convention:
|
| 799 |
- ROW corresponds to Q (m_block)
|
| 800 |
- COL corresponds to KV (n_block)
|
| 801 |
|
|
|
|
| 845 |
and fastdiv_mods[1] is not None
|
| 846 |
)
|
| 847 |
wrap_aux_indices = const_expr(
|
| 848 |
+
has_fastdiv and mask_seqlen and const_expr(aux_data.tensors is not None)
|
| 849 |
)
|
| 850 |
batch_idx_ssa = utils.scalar_to_ssa(batch_idx, cutlass.Int32)
|
| 851 |
head_idx_ssa = utils.scalar_to_ssa(head_idx, cutlass.Int32)
|
|
|
|
| 866 |
q_idx_ssa = utils.scalar_to_ssa(q_idx_for_mod, cutlass.Int32)
|
| 867 |
kv_idx_ssa = utils.scalar_to_ssa(kv_idx_for_mod, cutlass.Int32)
|
| 868 |
|
| 869 |
+
mask_value = call_mask_mod(
|
| 870 |
+
mask_mod,
|
| 871 |
batch_idx_ssa,
|
| 872 |
head_idx_ssa,
|
| 873 |
q_idx_ssa,
|
| 874 |
kv_idx_ssa,
|
| 875 |
self.seqlen_info,
|
| 876 |
+
aux_data,
|
| 877 |
)
|
| 878 |
cond = cutlass.Boolean(utils.ssa_to_scalar(mask_value))
|
| 879 |
acc_S[i] = acc_S[i] if cond else -cutlass.Float32.inf
|
|
|
|
| 912 |
)
|
| 913 |
else:
|
| 914 |
num_rep = cute.size(tScS_t2r, mode=[0]) # 16 or 32
|
| 915 |
+
num_wg = 2
|
| 916 |
+
row_limit = row_to_r2p_idx(row_limit_top, num_rep, num_wg)
|
| 917 |
+
mask_r2p_lambda(
|
| 918 |
+
acc_S,
|
| 919 |
+
lambda s: r2p_bitmask_above(row_limit, s),
|
| 920 |
+
rank1=True,
|
| 921 |
+
)
|
| 922 |
else:
|
| 923 |
if const_expr(self.window_size_right is not None):
|
| 924 |
row_limit_top = causal_offset - self.window_size_right
|
|
|
|
| 929 |
if const_expr(mask_seqlen):
|
| 930 |
if seqlenk_col_limit <= 0:
|
| 931 |
row_limit_top = self.tile_m
|
| 932 |
+
r2p = True
|
| 933 |
+
if const_expr(not r2p):
|
| 934 |
+
for i in cutlass.range(cute.size(acc_S.shape), unroll_full=True):
|
| 935 |
+
row_idx = t0ScS_t2r[i][ROW]
|
| 936 |
+
local_mask = row_idx < row_limit_top
|
| 937 |
+
if const_expr(self.window_size_left is not None):
|
| 938 |
+
local_mask |= row_idx > row_limit_bot
|
| 939 |
+
acc_S[i] = -cutlass.Float32.inf if local_mask else acc_S[i]
|
| 940 |
+
else:
|
| 941 |
+
|
| 942 |
+
def mask_gen_fn(s: int) -> Uint32:
|
| 943 |
+
num_rep = cute.size(tScS_t2r, mode=[0])
|
| 944 |
+
num_wg = 2
|
| 945 |
+
|
| 946 |
+
row_limit = row_to_r2p_idx(row_limit_top, num_rep, num_wg)
|
| 947 |
+
mask = r2p_bitmask_above(row_limit, s)
|
| 948 |
+
|
| 949 |
+
if const_expr(self.window_size_left is not None):
|
| 950 |
+
row_limit_bottom = row_to_r2p_idx(row_limit_bot + 1, num_rep, num_wg)
|
| 951 |
+
mask = mask & r2p_bitmask_below(row_limit_bottom, s)
|
| 952 |
+
|
| 953 |
+
return mask
|
| 954 |
+
|
| 955 |
+
mask_r2p_lambda(
|
| 956 |
+
acc_S,
|
| 957 |
+
mask_gen_fn,
|
| 958 |
+
rank1=True,
|
| 959 |
+
)
|
| 960 |
+
|
| 961 |
+
|
| 962 |
+
# -----------------------------------------------------------------------------
|
| 963 |
+
# SM100 FMHA fused-mask policy layer (separate from generic mask primitives).
|
| 964 |
+
# -----------------------------------------------------------------------------
|
| 965 |
+
|
| 966 |
+
|
| 967 |
+
class Sm100MaskEnum(enum.Enum):
|
| 968 |
+
"""Enumeration of mask types for FMHA operations.
|
| 969 |
+
|
| 970 |
+
- RESIDUAL_MASK: Residual mask for handling variable sequence lengths
|
| 971 |
+
- WINDOW_MASK: Window mask for attention which also includes causal and no mask
|
| 972 |
+
- WINDOW_MASK_INFERENCE: Same as the window mask, but has the limitation that the end of q is aligned with the end of k
|
| 973 |
+
- WINDOW_MASK_BWD: Window mask for backward pass
|
| 974 |
+
- 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
|
| 975 |
+
"""
|
| 976 |
+
|
| 977 |
+
NO_MASK = enum.auto()
|
| 978 |
+
RESIDUAL_MASK = enum.auto()
|
| 979 |
+
CAUSAL_MASK = enum.auto()
|
| 980 |
+
WINDOW_MASK = enum.auto()
|
| 981 |
+
WINDOW_MASK_INFERENCE = enum.auto()
|
| 982 |
+
# Deprecated the following types
|
| 983 |
+
WINDOW_MASK_BWD = enum.auto()
|
| 984 |
+
WINDOW_MASK_BWD_INFERENCE = enum.auto()
|
| 985 |
+
RESIDUAL_MASK_BWD = enum.auto()
|
| 986 |
+
|
| 987 |
+
|
| 988 |
+
class Sm100FusedMask:
|
| 989 |
+
"""A fused mask implementation for FMHA operations.
|
| 990 |
+
|
| 991 |
+
This class handles different types of attention masks including no mask,
|
| 992 |
+
residual mask for variable sequence lengths, and causal mask for
|
| 993 |
+
autoregressive attention patterns.
|
| 994 |
+
|
| 995 |
+
The class provides methods to:
|
| 996 |
+
- Calculate trip counts for different mask types
|
| 997 |
+
- Apply masks to attention scores
|
| 998 |
+
- Handle masked and unmasked trip calculations
|
| 999 |
+
"""
|
| 1000 |
+
|
| 1001 |
+
def get_trip_count(
|
| 1002 |
+
mask_type: Sm100MaskEnum,
|
| 1003 |
+
blk_coord: cute.Coord,
|
| 1004 |
+
tile_shape: cute.Shape,
|
| 1005 |
+
seqlen_q: Int32,
|
| 1006 |
+
seqlen_k: Int32,
|
| 1007 |
+
window_size_left: Optional[Int32] = None,
|
| 1008 |
+
window_size_right: Optional[Int32] = None,
|
| 1009 |
+
) -> Int32:
|
| 1010 |
+
"""
|
| 1011 |
+
Calculate the number of trips needed for the current block.
|
| 1012 |
+
|
| 1013 |
+
The trip count depends on the mask type and the block coordinates.
|
| 1014 |
+
For causal masks, it considers the autoregressive constraint.
|
| 1015 |
+
|
| 1016 |
+
:param mask_type: Type of mask to use
|
| 1017 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1018 |
+
:param blk_coord: Block coordinates.
|
| 1019 |
+
:type blk_coord: cute.Coord
|
| 1020 |
+
:param tile_shape: Shape of the tile.
|
| 1021 |
+
:type tile_shape: cute.Shape
|
| 1022 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1023 |
+
:type seqlen_q: Int32
|
| 1024 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1025 |
+
:type seqlen_k: Int32
|
| 1026 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1027 |
+
:type window_size_left: Optional[Int32]
|
| 1028 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1029 |
+
:type window_size_right: Optional[Int32]
|
| 1030 |
+
|
| 1031 |
+
:return: Number of trips needed.
|
| 1032 |
+
:rtype: Int32
|
| 1033 |
+
"""
|
| 1034 |
+
result = 0
|
| 1035 |
+
offset = 0
|
| 1036 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 1037 |
+
offset = seqlen_k - seqlen_q
|
| 1038 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 1039 |
+
offset = seqlen_q - seqlen_k
|
| 1040 |
+
if cutlass.const_expr(mask_type == Sm100MaskEnum.RESIDUAL_MASK):
|
| 1041 |
+
result = cute.ceil_div(seqlen_k, tile_shape[1])
|
| 1042 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.RESIDUAL_MASK_BWD):
|
| 1043 |
+
result = cute.ceil_div(seqlen_q, tile_shape[0])
|
| 1044 |
+
if cutlass.const_expr(
|
| 1045 |
+
mask_type == Sm100MaskEnum.WINDOW_MASK
|
| 1046 |
+
or mask_type == Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1047 |
+
):
|
| 1048 |
+
if cutlass.const_expr(window_size_right is None):
|
| 1049 |
+
result = cute.ceil_div(seqlen_k, tile_shape[1])
|
| 1050 |
+
else:
|
| 1051 |
+
max_idx_q = (blk_coord[0] + 1) * tile_shape[0]
|
| 1052 |
+
idx_k = max_idx_q + offset + window_size_right
|
| 1053 |
+
tmp_blocks_k = cute.ceil_div(idx_k, tile_shape[1])
|
| 1054 |
+
max_blocks_k = cute.ceil_div(seqlen_k, tile_shape[1])
|
| 1055 |
+
result = dsl_min(max_blocks_k, tmp_blocks_k)
|
| 1056 |
+
if cutlass.const_expr(
|
| 1057 |
+
mask_type == Sm100MaskEnum.WINDOW_MASK_BWD
|
| 1058 |
+
or mask_type == Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 1059 |
+
):
|
| 1060 |
+
if cutlass.const_expr(window_size_left is None):
|
| 1061 |
+
result = cute.ceil_div(seqlen_q, tile_shape[0])
|
| 1062 |
+
else:
|
| 1063 |
+
max_idx_k = (blk_coord[1] + 1) * tile_shape[1]
|
| 1064 |
+
idx_k = max_idx_k + offset + window_size_left
|
| 1065 |
+
tmp_blocks_q = cute.ceil_div(idx_k, tile_shape[0])
|
| 1066 |
+
max_blocks_q = cute.ceil_div(seqlen_q, tile_shape[0])
|
| 1067 |
+
result = dsl_min(max_blocks_q, tmp_blocks_q)
|
| 1068 |
+
start_block = Sm100FusedMask.get_trip_start(
|
| 1069 |
+
mask_type,
|
| 1070 |
+
blk_coord,
|
| 1071 |
+
tile_shape,
|
| 1072 |
+
seqlen_q,
|
| 1073 |
+
seqlen_k,
|
| 1074 |
+
window_size_left,
|
| 1075 |
+
window_size_right,
|
| 1076 |
+
)
|
| 1077 |
+
result = result - start_block
|
| 1078 |
+
return result
|
| 1079 |
+
|
| 1080 |
+
@cute.jit
|
| 1081 |
+
def get_trip_start_count_via_block_info(
|
| 1082 |
+
blk_coord: cute.Coord,
|
| 1083 |
+
tile_shape: cute.Shape,
|
| 1084 |
+
seqlen_q: Int32,
|
| 1085 |
+
seqlen_k: Int32,
|
| 1086 |
+
is_causal: cutlass.Constexpr[bool] = False,
|
| 1087 |
+
is_local: cutlass.Constexpr[bool] = False,
|
| 1088 |
+
window_size_left: Optional[Int32] = None,
|
| 1089 |
+
window_size_right: Optional[Int32] = None,
|
| 1090 |
+
) -> Tuple[Int32, Int32]:
|
| 1091 |
+
block_info = BlockInfo(
|
| 1092 |
+
tile_m=tile_shape[0],
|
| 1093 |
+
tile_n=tile_shape[1],
|
| 1094 |
+
is_causal=is_causal,
|
| 1095 |
+
is_local=is_local and not is_causal,
|
| 1096 |
+
window_size_left=window_size_left,
|
| 1097 |
+
window_size_right=window_size_right,
|
| 1098 |
+
)
|
| 1099 |
+
|
| 1100 |
+
seqlen_info = SeqlenInfoQK(
|
| 1101 |
+
offset_q=Int32(0),
|
| 1102 |
+
offset_k=Int32(0),
|
| 1103 |
+
padded_offset_q=Int32(0),
|
| 1104 |
+
padded_offset_k=Int32(0),
|
| 1105 |
+
seqlen_q=seqlen_q,
|
| 1106 |
+
seqlen_k=seqlen_k,
|
| 1107 |
+
m_block_offset=Int32(0),
|
| 1108 |
+
block_idx_offset=Int32(0),
|
| 1109 |
+
num_n_blocks=cute.ceil_div(seqlen_k, tile_shape[1]),
|
| 1110 |
+
has_cu_seqlens_q=False,
|
| 1111 |
+
has_cu_seqlens_k=False,
|
| 1112 |
+
has_seqused_q=False,
|
| 1113 |
+
has_seqused_k=False,
|
| 1114 |
+
has_cu_block_idx_offsets=False,
|
| 1115 |
+
)
|
| 1116 |
+
n_block_min, n_block_max = block_info.get_n_block_min_max(seqlen_info, blk_coord[0])
|
| 1117 |
+
return n_block_min, n_block_max - n_block_min
|
| 1118 |
+
|
| 1119 |
+
@cute.jit
|
| 1120 |
+
def get_trip_mask_bounds_via_block_info(
|
| 1121 |
+
blk_coord: cute.Coord,
|
| 1122 |
+
tile_shape: cute.Shape,
|
| 1123 |
+
seqlen_q: Int32,
|
| 1124 |
+
seqlen_k: Int32,
|
| 1125 |
+
is_causal: cutlass.Constexpr[bool] = False,
|
| 1126 |
+
is_local: cutlass.Constexpr[bool] = False,
|
| 1127 |
+
window_size_left: Optional[Int32] = None,
|
| 1128 |
+
window_size_right: Optional[Int32] = None,
|
| 1129 |
+
) -> Tuple[Int32, Int32]:
|
| 1130 |
+
"""Return SM100-style mask boundaries for dense iteration.
|
| 1131 |
+
|
| 1132 |
+
Returns:
|
| 1133 |
+
- n_block_min_causal_local_mask: right-side masked region start
|
| 1134 |
+
- n_block_min_before_local_mask: start of fully unmasked middle region
|
| 1135 |
+
"""
|
| 1136 |
+
block_info = BlockInfo(
|
| 1137 |
+
tile_m=tile_shape[0],
|
| 1138 |
+
tile_n=tile_shape[1],
|
| 1139 |
+
is_causal=is_causal,
|
| 1140 |
+
is_local=is_local and not is_causal,
|
| 1141 |
+
window_size_left=window_size_left,
|
| 1142 |
+
window_size_right=window_size_right,
|
| 1143 |
+
)
|
| 1144 |
+
seqlen_info = SeqlenInfoQK(
|
| 1145 |
+
offset_q=Int32(0),
|
| 1146 |
+
offset_k=Int32(0),
|
| 1147 |
+
padded_offset_q=Int32(0),
|
| 1148 |
+
padded_offset_k=Int32(0),
|
| 1149 |
+
seqlen_q=seqlen_q,
|
| 1150 |
+
seqlen_k=seqlen_k,
|
| 1151 |
+
m_block_offset=Int32(0),
|
| 1152 |
+
block_idx_offset=Int32(0),
|
| 1153 |
+
num_n_blocks=cute.ceil_div(seqlen_k, tile_shape[1]),
|
| 1154 |
+
has_cu_seqlens_q=False,
|
| 1155 |
+
has_cu_seqlens_k=False,
|
| 1156 |
+
has_seqused_q=False,
|
| 1157 |
+
has_seqused_k=False,
|
| 1158 |
+
has_cu_block_idx_offsets=False,
|
| 1159 |
+
)
|
| 1160 |
+
n_block_min, _ = block_info.get_n_block_min_max(seqlen_info, blk_coord[0])
|
| 1161 |
+
n_block_min_causal_local_mask = block_info.get_n_block_min_causal_local_mask(
|
| 1162 |
+
seqlen_info, blk_coord[0], n_block_min
|
| 1163 |
+
)
|
| 1164 |
+
n_block_min_before_local_mask = block_info.get_n_block_min_before_local_mask(
|
| 1165 |
+
seqlen_info, blk_coord[0], n_block_min
|
| 1166 |
+
)
|
| 1167 |
+
return n_block_min_causal_local_mask, n_block_min_before_local_mask
|
| 1168 |
+
|
| 1169 |
+
@cute.jit
|
| 1170 |
+
def get_trip_start(
|
| 1171 |
+
mask_type: Sm100MaskEnum,
|
| 1172 |
+
blk_coord: cute.Coord,
|
| 1173 |
+
tile_shape: cute.Shape,
|
| 1174 |
+
seqlen_q: Int32,
|
| 1175 |
+
seqlen_k: Int32,
|
| 1176 |
+
window_size_left: Optional[Int32] = None,
|
| 1177 |
+
window_size_right: Optional[Int32] = None,
|
| 1178 |
+
) -> Int32:
|
| 1179 |
+
"""
|
| 1180 |
+
Get the start of the trip for the current block.
|
| 1181 |
+
|
| 1182 |
+
:param mask_type: Type of mask to use
|
| 1183 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1184 |
+
:param blk_coord: Block coordinates.
|
| 1185 |
+
:type blk_coord: cute.Coord
|
| 1186 |
+
:param tile_shape: Shape of the tile.
|
| 1187 |
+
:type tile_shape: cute.Shape
|
| 1188 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1189 |
+
:type seqlen_q: Int32
|
| 1190 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1191 |
+
:type seqlen_k: Int32
|
| 1192 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1193 |
+
:type window_size_left: Optional[Int32]
|
| 1194 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1195 |
+
:type window_size_right: Optional[Int32]
|
| 1196 |
+
"""
|
| 1197 |
+
result = 0
|
| 1198 |
+
offset = 0
|
| 1199 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 1200 |
+
offset = seqlen_k - seqlen_q
|
| 1201 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 1202 |
+
offset = seqlen_q - seqlen_k
|
| 1203 |
+
if cutlass.const_expr(
|
| 1204 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK
|
| 1205 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1206 |
+
):
|
| 1207 |
+
if cutlass.const_expr(window_size_left is not None):
|
| 1208 |
+
min_idx_q = blk_coord[0] * tile_shape[0]
|
| 1209 |
+
idx_k = min_idx_q + offset - window_size_left
|
| 1210 |
+
tmp_blocks_k = idx_k // tile_shape[1]
|
| 1211 |
+
result = max(tmp_blocks_k, result)
|
| 1212 |
+
if cutlass.const_expr(
|
| 1213 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK_BWD
|
| 1214 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 1215 |
+
):
|
| 1216 |
+
if cutlass.const_expr(window_size_right is not None):
|
| 1217 |
+
min_idx_k = blk_coord[1] * tile_shape[1]
|
| 1218 |
+
idx_q = min_idx_k + offset - window_size_right
|
| 1219 |
+
tmp_blocks_q = idx_q // tile_shape[0]
|
| 1220 |
+
result = max(tmp_blocks_q, result)
|
| 1221 |
+
return result
|
| 1222 |
+
|
| 1223 |
+
@cute.jit
|
| 1224 |
+
def get_leading_mask_id(
|
| 1225 |
+
mask_type: Sm100MaskEnum,
|
| 1226 |
+
blk_coord: cute.Coord,
|
| 1227 |
+
tile_shape: cute.Shape,
|
| 1228 |
+
seqlen_q: Int32,
|
| 1229 |
+
seqlen_k: Int32,
|
| 1230 |
+
window_size_left: Optional[Int32] = None,
|
| 1231 |
+
window_size_right: Optional[Int32] = None,
|
| 1232 |
+
) -> Tuple[Int32, Int32]:
|
| 1233 |
+
"""
|
| 1234 |
+
Get the begin and end tile idx for the leading mask.
|
| 1235 |
+
|
| 1236 |
+
:param mask_type: Type of mask to use
|
| 1237 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1238 |
+
:param blk_coord: Block coordinates.
|
| 1239 |
+
:type blk_coord: cute.Coord
|
| 1240 |
+
:param tile_shape: Shape of the tile.
|
| 1241 |
+
:type tile_shape: cute.Shape
|
| 1242 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1243 |
+
:type seqlen_q: Int32
|
| 1244 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1245 |
+
:type seqlen_k: Int32
|
| 1246 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1247 |
+
:type window_size_left: Optional[Int32]
|
| 1248 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1249 |
+
:type window_size_right: Optional[Int32]
|
| 1250 |
+
|
| 1251 |
+
:return: Tuple of (begin, end) tile idx for the leading mask.
|
| 1252 |
+
:rtype: Tuple[Int32, Int32]
|
| 1253 |
+
"""
|
| 1254 |
+
offset = 0
|
| 1255 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 1256 |
+
offset = seqlen_k - seqlen_q
|
| 1257 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 1258 |
+
offset = seqlen_q - seqlen_k
|
| 1259 |
+
leading_mask_begin = Sm100FusedMask.get_trip_start(
|
| 1260 |
+
mask_type,
|
| 1261 |
+
blk_coord,
|
| 1262 |
+
tile_shape,
|
| 1263 |
+
seqlen_q,
|
| 1264 |
+
seqlen_k,
|
| 1265 |
+
window_size_left,
|
| 1266 |
+
window_size_right,
|
| 1267 |
+
)
|
| 1268 |
+
trip_count = Sm100FusedMask.get_trip_count(
|
| 1269 |
+
mask_type,
|
| 1270 |
+
blk_coord,
|
| 1271 |
+
tile_shape,
|
| 1272 |
+
seqlen_q,
|
| 1273 |
+
seqlen_k,
|
| 1274 |
+
window_size_left,
|
| 1275 |
+
window_size_right,
|
| 1276 |
+
)
|
| 1277 |
+
|
| 1278 |
+
leading_mask_end = leading_mask_begin
|
| 1279 |
+
if cutlass.const_expr(
|
| 1280 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK
|
| 1281 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1282 |
+
):
|
| 1283 |
+
if cutlass.const_expr(window_size_left is not None):
|
| 1284 |
+
min_idx_q = (blk_coord[0] + 1) * tile_shape[0] + offset - window_size_left
|
| 1285 |
+
leading_mask_end = dsl_min(
|
| 1286 |
+
cute.ceil_div(min_idx_q, tile_shape[1]) - 1,
|
| 1287 |
+
trip_count + leading_mask_begin - 1,
|
| 1288 |
+
)
|
| 1289 |
+
else:
|
| 1290 |
+
leading_mask_end = leading_mask_begin - 1
|
| 1291 |
+
elif cutlass.const_expr(
|
| 1292 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK_BWD
|
| 1293 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 1294 |
+
):
|
| 1295 |
+
if cutlass.const_expr(window_size_right is not None):
|
| 1296 |
+
min_idx_k = (blk_coord[1] + 1) * tile_shape[1] + offset - window_size_right
|
| 1297 |
+
leading_mask_end = cute.ceil_div(min_idx_k, tile_shape[0]) - 1
|
| 1298 |
+
else:
|
| 1299 |
+
leading_mask_end = leading_mask_begin - 1
|
| 1300 |
+
return leading_mask_begin, leading_mask_end
|
| 1301 |
+
|
| 1302 |
+
@cute.jit
|
| 1303 |
+
def get_trailing_mask_id(
|
| 1304 |
+
mask_type: Sm100MaskEnum,
|
| 1305 |
+
blk_coord: cute.Coord,
|
| 1306 |
+
tile_shape: cute.Shape,
|
| 1307 |
+
seqlen_q: Int32,
|
| 1308 |
+
seqlen_k: Int32,
|
| 1309 |
+
window_size_left: Optional[Int32] = None,
|
| 1310 |
+
window_size_right: Optional[Int32] = None,
|
| 1311 |
+
) -> Tuple[Optional[Int32], Optional[Int32]]:
|
| 1312 |
+
"""
|
| 1313 |
+
Get the begin and end tile idx for the trailing mask.
|
| 1314 |
+
|
| 1315 |
+
:param mask_type: Type of mask to use
|
| 1316 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1317 |
+
:param blk_coord: Block coordinates.
|
| 1318 |
+
:type blk_coord: cute.Coord
|
| 1319 |
+
:param tile_shape: Shape of the tile.
|
| 1320 |
+
:type tile_shape: cute.Shape
|
| 1321 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1322 |
+
:type seqlen_q: Int32
|
| 1323 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1324 |
+
:type seqlen_k: Int32
|
| 1325 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1326 |
+
:type window_size_left: Optional[Int32]
|
| 1327 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1328 |
+
:type window_size_right: Optional[Int32]
|
| 1329 |
+
|
| 1330 |
+
:return: Tuple of (begin, end) tile idx for the trailing mask.
|
| 1331 |
+
:rtype: Tuple[Int32, Int32]
|
| 1332 |
+
"""
|
| 1333 |
+
offset = 0
|
| 1334 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE):
|
| 1335 |
+
offset = seqlen_k - seqlen_q
|
| 1336 |
+
if cutlass.const_expr(mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE):
|
| 1337 |
+
offset = seqlen_q - seqlen_k
|
| 1338 |
+
trip_start = Sm100FusedMask.get_trip_start(
|
| 1339 |
+
mask_type,
|
| 1340 |
+
blk_coord,
|
| 1341 |
+
tile_shape,
|
| 1342 |
+
seqlen_q,
|
| 1343 |
+
seqlen_k,
|
| 1344 |
+
window_size_left,
|
| 1345 |
+
window_size_right,
|
| 1346 |
+
)
|
| 1347 |
+
trip_count = Sm100FusedMask.get_trip_count(
|
| 1348 |
+
mask_type,
|
| 1349 |
+
blk_coord,
|
| 1350 |
+
tile_shape,
|
| 1351 |
+
seqlen_q,
|
| 1352 |
+
seqlen_k,
|
| 1353 |
+
window_size_left,
|
| 1354 |
+
window_size_right,
|
| 1355 |
+
)
|
| 1356 |
+
|
| 1357 |
+
trailing_mask_begin, trailing_mask_end = None, None
|
| 1358 |
+
if cutlass.const_expr(
|
| 1359 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK
|
| 1360 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1361 |
+
):
|
| 1362 |
+
if cutlass.const_expr(window_size_right is not None):
|
| 1363 |
+
min_idx_q = blk_coord[0] * tile_shape[0] + offset + window_size_right
|
| 1364 |
+
trailing_mask_begin = dsl_min(
|
| 1365 |
+
min_idx_q // tile_shape[1], trip_count + trip_start - 1
|
| 1366 |
+
)
|
| 1367 |
+
trailing_mask_end = trip_count + trip_start - 1
|
| 1368 |
+
else:
|
| 1369 |
+
# last tile, we always apply mask on it regardless whether it's a residual tile
|
| 1370 |
+
trailing_mask_begin = trip_count + trip_start - 1
|
| 1371 |
+
trailing_mask_end = trip_count + trip_start - 1
|
| 1372 |
+
else:
|
| 1373 |
+
if cutlass.const_expr(window_size_left is not None):
|
| 1374 |
+
min_idx_k = blk_coord[1] * tile_shape[1] + offset + window_size_left + 1
|
| 1375 |
+
max_idx_k = (blk_coord[1] + 1) * tile_shape[1] + offset + window_size_left
|
| 1376 |
+
trailing_mask_begin = dsl_min(
|
| 1377 |
+
cute.ceil_div(min_idx_k, tile_shape[0]) - 1,
|
| 1378 |
+
trip_count + trip_start - 1,
|
| 1379 |
+
)
|
| 1380 |
+
trailing_mask_end = dsl_min(
|
| 1381 |
+
cute.ceil_div(max_idx_k, tile_shape[0]) - 1,
|
| 1382 |
+
trip_count + trip_start - 1,
|
| 1383 |
+
)
|
| 1384 |
+
else:
|
| 1385 |
+
# last tile, we always apply mask on it regardless whether it's a residual tile
|
| 1386 |
+
trailing_mask_begin = trip_count + trip_start - 1
|
| 1387 |
+
trailing_mask_end = trip_count + trip_start - 1
|
| 1388 |
+
|
| 1389 |
+
return trailing_mask_begin, trailing_mask_end
|
| 1390 |
+
|
| 1391 |
+
@cute.jit
|
| 1392 |
+
def get_masked_leading_count(
|
| 1393 |
+
mask_type: Sm100MaskEnum,
|
| 1394 |
+
blk_coord: cute.Coord,
|
| 1395 |
+
tile_shape: cute.Shape,
|
| 1396 |
+
seqlen_q: Int32,
|
| 1397 |
+
seqlen_k: Int32,
|
| 1398 |
+
window_size_left: Optional[Int32] = None,
|
| 1399 |
+
window_size_right: Optional[Int32] = None,
|
| 1400 |
+
) -> Int32:
|
| 1401 |
+
"""
|
| 1402 |
+
Calculate the number of masked trips for the leading mask.
|
| 1403 |
+
|
| 1404 |
+
This is used for blocks that need special handling due to masking.
|
| 1405 |
+
|
| 1406 |
+
:param mask_type: Type of mask to use
|
| 1407 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1408 |
+
:param blk_coord: Block coordinates.
|
| 1409 |
+
:type blk_coord: cute.Coord
|
| 1410 |
+
:param tile_shape: Shape of the tile.
|
| 1411 |
+
:type tile_shape: cute.Shape
|
| 1412 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1413 |
+
:type seqlen_q: Int32
|
| 1414 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1415 |
+
:type seqlen_k: Int32
|
| 1416 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1417 |
+
:type window_size_left: Optional[Int32]
|
| 1418 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1419 |
+
:type window_size_right: Optional[Int32]
|
| 1420 |
+
|
| 1421 |
+
:return: Number of masked trips.
|
| 1422 |
+
:rtype: Int32
|
| 1423 |
+
"""
|
| 1424 |
+
result = 0
|
| 1425 |
+
if cutlass.const_expr(
|
| 1426 |
+
mask_type is not Sm100MaskEnum.RESIDUAL_MASK
|
| 1427 |
+
and mask_type is not Sm100MaskEnum.RESIDUAL_MASK_BWD
|
| 1428 |
+
):
|
| 1429 |
+
if cutlass.const_expr(window_size_left is not None or window_size_right is not None):
|
| 1430 |
+
leading_mask_begin, leading_mask_end = Sm100FusedMask.get_leading_mask_id(
|
| 1431 |
+
mask_type,
|
| 1432 |
+
blk_coord,
|
| 1433 |
+
tile_shape,
|
| 1434 |
+
seqlen_q,
|
| 1435 |
+
seqlen_k,
|
| 1436 |
+
window_size_left,
|
| 1437 |
+
window_size_right,
|
| 1438 |
+
)
|
| 1439 |
+
result = max(leading_mask_end - leading_mask_begin + 1, 0)
|
| 1440 |
+
|
| 1441 |
+
return result
|
| 1442 |
+
|
| 1443 |
+
@cute.jit
|
| 1444 |
+
def get_masked_trailing_count(
|
| 1445 |
+
mask_type: Sm100MaskEnum,
|
| 1446 |
+
blk_coord: cute.Coord,
|
| 1447 |
+
tile_shape: cute.Shape,
|
| 1448 |
+
seqlen_q: Int32,
|
| 1449 |
+
seqlen_k: Int32,
|
| 1450 |
+
window_size_left: Optional[Int32] = None,
|
| 1451 |
+
window_size_right: Optional[Int32] = None,
|
| 1452 |
+
rem_count: Optional[Int32] = 0,
|
| 1453 |
+
) -> Int32:
|
| 1454 |
+
"""
|
| 1455 |
+
Calculate the number of masked trips for the trailing mask.
|
| 1456 |
+
|
| 1457 |
+
This is used for blocks that need special handling due to masking.
|
| 1458 |
+
|
| 1459 |
+
:param mask_type: Type of mask to use
|
| 1460 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1461 |
+
:param blk_coord: Block coordinates.
|
| 1462 |
+
:type blk_coord: cute.Coord
|
| 1463 |
+
:param tile_shape: Shape of the tile.
|
| 1464 |
+
:type tile_shape: cute.Shape
|
| 1465 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1466 |
+
:type seqlen_q: Int32
|
| 1467 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1468 |
+
:type seqlen_k: Int32
|
| 1469 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1470 |
+
:type window_size_left: Optional[Int32]
|
| 1471 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1472 |
+
:type window_size_right: Optional[Int32]
|
| 1473 |
+
:param rem_count: Remaining count from previous calculations.
|
| 1474 |
+
:type rem_count: Int32
|
| 1475 |
+
|
| 1476 |
+
:return: Number of masked trips.
|
| 1477 |
+
:rtype: Int32
|
| 1478 |
+
"""
|
| 1479 |
+
result = 0
|
| 1480 |
+
|
| 1481 |
+
if cutlass.const_expr(
|
| 1482 |
+
mask_type is not Sm100MaskEnum.RESIDUAL_MASK
|
| 1483 |
+
and mask_type is not Sm100MaskEnum.RESIDUAL_MASK_BWD
|
| 1484 |
+
):
|
| 1485 |
+
if cutlass.const_expr(window_size_left is not None or window_size_right is not None):
|
| 1486 |
+
trailing_mask_begin, trailing_mask_end = Sm100FusedMask.get_trailing_mask_id(
|
| 1487 |
+
mask_type,
|
| 1488 |
+
blk_coord,
|
| 1489 |
+
tile_shape,
|
| 1490 |
+
seqlen_q,
|
| 1491 |
+
seqlen_k,
|
| 1492 |
+
window_size_left,
|
| 1493 |
+
window_size_right,
|
| 1494 |
+
)
|
| 1495 |
+
leading_mask_begin, leading_mask_end = Sm100FusedMask.get_leading_mask_id(
|
| 1496 |
+
mask_type,
|
| 1497 |
+
blk_coord,
|
| 1498 |
+
tile_shape,
|
| 1499 |
+
seqlen_q,
|
| 1500 |
+
seqlen_k,
|
| 1501 |
+
window_size_left,
|
| 1502 |
+
window_size_right,
|
| 1503 |
+
)
|
| 1504 |
+
if cutlass.const_expr(
|
| 1505 |
+
trailing_mask_begin is not None and trailing_mask_end is not None
|
| 1506 |
+
):
|
| 1507 |
+
if trailing_mask_begin <= leading_mask_end:
|
| 1508 |
+
result = max(trailing_mask_end - leading_mask_end, 0)
|
| 1509 |
+
else:
|
| 1510 |
+
result = max(trailing_mask_end - trailing_mask_begin + 1, 0)
|
| 1511 |
+
else:
|
| 1512 |
+
if seqlen_k % tile_shape[1] != 0:
|
| 1513 |
+
result = 1
|
| 1514 |
+
else:
|
| 1515 |
+
result = 0
|
| 1516 |
+
|
| 1517 |
+
return result + rem_count
|
| 1518 |
+
|
| 1519 |
+
@cute.jit
|
| 1520 |
+
def get_unmasked_trip_count(
|
| 1521 |
+
mask_type: Sm100MaskEnum,
|
| 1522 |
+
blk_coord: cute.Coord,
|
| 1523 |
+
tile_shape: cute.Shape,
|
| 1524 |
+
seqlen_q: Int32,
|
| 1525 |
+
seqlen_k: Int32,
|
| 1526 |
+
window_size_left: Optional[Int32] = None,
|
| 1527 |
+
window_size_right: Optional[Int32] = None,
|
| 1528 |
+
) -> Int32:
|
| 1529 |
+
"""
|
| 1530 |
+
Calculate the number of unmasked trips for the current block.
|
| 1531 |
+
|
| 1532 |
+
This represents the number of trips that don't require special
|
| 1533 |
+
masking treatment.
|
| 1534 |
+
|
| 1535 |
+
:param mask_type: Type of mask to use
|
| 1536 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1537 |
+
:param blk_coord: Block coordinates.
|
| 1538 |
+
:type blk_coord: cute.Coord
|
| 1539 |
+
:param tile_shape: Shape of the tile.
|
| 1540 |
+
:type tile_shape: cute.Shape
|
| 1541 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1542 |
+
:type seqlen_q: Int32
|
| 1543 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1544 |
+
:type seqlen_k: Int32
|
| 1545 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1546 |
+
:type window_size_left: Optional[Int32]
|
| 1547 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1548 |
+
:type window_size_right: Optional[Int32]
|
| 1549 |
+
|
| 1550 |
+
:return: Number of unmasked trips.
|
| 1551 |
+
:rtype: Int32
|
| 1552 |
+
"""
|
| 1553 |
+
result = (
|
| 1554 |
+
Sm100FusedMask.get_trip_count(
|
| 1555 |
+
mask_type,
|
| 1556 |
+
blk_coord,
|
| 1557 |
+
tile_shape,
|
| 1558 |
+
seqlen_q,
|
| 1559 |
+
seqlen_k,
|
| 1560 |
+
window_size_left,
|
| 1561 |
+
window_size_right,
|
| 1562 |
+
)
|
| 1563 |
+
- Sm100FusedMask.get_masked_leading_count(
|
| 1564 |
+
mask_type,
|
| 1565 |
+
blk_coord,
|
| 1566 |
+
tile_shape,
|
| 1567 |
+
seqlen_q,
|
| 1568 |
+
seqlen_k,
|
| 1569 |
+
window_size_left,
|
| 1570 |
+
window_size_right,
|
| 1571 |
+
)
|
| 1572 |
+
- Sm100FusedMask.get_masked_trailing_count(
|
| 1573 |
+
mask_type,
|
| 1574 |
+
blk_coord,
|
| 1575 |
+
tile_shape,
|
| 1576 |
+
seqlen_q,
|
| 1577 |
+
seqlen_k,
|
| 1578 |
+
window_size_left,
|
| 1579 |
+
window_size_right,
|
| 1580 |
+
0,
|
| 1581 |
+
)
|
| 1582 |
+
)
|
| 1583 |
+
return result
|
| 1584 |
+
|
| 1585 |
+
@cute.jit
|
| 1586 |
+
def apply_mask(
|
| 1587 |
+
mask_type: Sm100MaskEnum,
|
| 1588 |
+
acc_qk: cute.Tensor,
|
| 1589 |
+
index_qk: cute.Tensor,
|
| 1590 |
+
seqlen_q: Int32,
|
| 1591 |
+
seqlen_k: Int32,
|
| 1592 |
+
window_size_left: Optional[int] = None,
|
| 1593 |
+
window_size_right: Optional[int] = None,
|
| 1594 |
+
index_transform: cutlass.Constexpr = lambda index_q, index_k: (
|
| 1595 |
+
index_q,
|
| 1596 |
+
index_k,
|
| 1597 |
+
),
|
| 1598 |
+
):
|
| 1599 |
+
"""
|
| 1600 |
+
Apply the appropriate mask to the attention scores.
|
| 1601 |
+
|
| 1602 |
+
This method modifies the attention scores (acc_qk) based on the mask type
|
| 1603 |
+
and the positions in the index tensor.
|
| 1604 |
+
|
| 1605 |
+
:param mask_type: Type of mask to use
|
| 1606 |
+
:type mask_type: utils.Sm100MaskEnum
|
| 1607 |
+
:param acc_qk: Accumulated QK attention scores tensor.
|
| 1608 |
+
:type acc_qk: cute.Tensor
|
| 1609 |
+
:param index_qk: Index tensor containing position information.
|
| 1610 |
+
:type index_qk: cute.Tensor
|
| 1611 |
+
:param seqlen_k: Key sequence length for attention computation.
|
| 1612 |
+
:type seqlen_k: Int32
|
| 1613 |
+
:param seqlen_q: Query sequence length for attention computation.
|
| 1614 |
+
:type seqlen_q: Optional[int]
|
| 1615 |
+
:param window_size_left: Left-side sliding window size for attention masking.
|
| 1616 |
+
:type window_size_left: Optional[int]
|
| 1617 |
+
:param window_size_right: Right-side sliding window size for attention masking.
|
| 1618 |
+
:type window_size_right: Optional[int]
|
| 1619 |
+
"""
|
| 1620 |
+
offset = 0
|
| 1621 |
+
# NOTE: causal masking in this repo aligns the *end* of Q with the *end* of K
|
| 1622 |
+
# when seqlen_k != seqlen_q (same as the test/reference implementation):
|
| 1623 |
+
# k_index <= q_index + (seqlen_k - seqlen_q) + window_right
|
| 1624 |
+
# In our kernels, causal is represented by (window_left is None, window_right is not None).
|
| 1625 |
+
if cutlass.const_expr(window_size_left is None and window_size_right is not None):
|
| 1626 |
+
offset = seqlen_k - seqlen_q
|
| 1627 |
+
elif cutlass.const_expr(
|
| 1628 |
+
mask_type is Sm100MaskEnum.WINDOW_MASK_INFERENCE
|
| 1629 |
+
or mask_type is Sm100MaskEnum.WINDOW_MASK_BWD_INFERENCE
|
| 1630 |
+
):
|
| 1631 |
+
offset = seqlen_k - seqlen_q
|
| 1632 |
+
for i in cutlass.range_constexpr(cute.size(acc_qk), unroll_full=True):
|
| 1633 |
+
index_q, index_k = index_transform(*index_qk[i])
|
| 1634 |
+
if cutlass.const_expr(window_size_left is not None or window_size_right is not None):
|
| 1635 |
+
if cutlass.const_expr(window_size_left is None):
|
| 1636 |
+
if index_q + offset + window_size_right < index_k:
|
| 1637 |
+
acc_qk[i] = -Float32.inf
|
| 1638 |
+
if index_k >= seqlen_k or index_q >= seqlen_q: # residual mask
|
| 1639 |
+
acc_qk[i] = -Float32.inf
|
| 1640 |
+
elif cutlass.const_expr(window_size_right is None):
|
| 1641 |
+
if index_q + offset - window_size_left > index_k:
|
| 1642 |
+
acc_qk[i] = -Float32.inf
|
| 1643 |
+
if index_k >= seqlen_k or index_q >= seqlen_q: # residual mask
|
| 1644 |
+
acc_qk[i] = -Float32.inf
|
| 1645 |
+
else:
|
| 1646 |
+
max_K_index = dsl_min(index_q + offset + window_size_right, seqlen_k)
|
| 1647 |
+
min_K_index = max(0, index_q + offset - window_size_left)
|
| 1648 |
+
if index_k > max_K_index or index_k < min_K_index:
|
| 1649 |
+
acc_qk[i] = -Float32.inf
|
| 1650 |
+
if index_k >= seqlen_k or index_q >= seqlen_q: # residual mask
|
| 1651 |
+
acc_qk[i] = -Float32.inf
|
| 1652 |
+
|
| 1653 |
+
if cutlass.const_expr(
|
| 1654 |
+
mask_type == Sm100MaskEnum.RESIDUAL_MASK
|
| 1655 |
+
or mask_type == Sm100MaskEnum.RESIDUAL_MASK_BWD
|
| 1656 |
+
):
|
| 1657 |
+
if index_k >= seqlen_k or index_q >= seqlen_q:
|
| 1658 |
+
acc_qk[i] = -Float32.inf
|
| 1659 |
+
|
| 1660 |
+
@cute.jit
|
| 1661 |
+
def apply_mask_via_causal_local(
|
| 1662 |
+
acc_qk: cute.Tensor,
|
| 1663 |
+
index_qk: cute.Tensor,
|
| 1664 |
+
seqlen_q: Int32,
|
| 1665 |
+
seqlen_k: Int32,
|
| 1666 |
+
apply_semantic_window: cutlass.Constexpr[bool] = True,
|
| 1667 |
+
is_causal: cutlass.Constexpr[bool] = False,
|
| 1668 |
+
is_local: cutlass.Constexpr[bool] = False,
|
| 1669 |
+
window_size_left: Optional[int] = None,
|
| 1670 |
+
window_size_right: Optional[int] = None,
|
| 1671 |
+
index_transform: cutlass.Constexpr = lambda index_q, index_k: (
|
| 1672 |
+
index_q,
|
| 1673 |
+
index_k,
|
| 1674 |
+
),
|
| 1675 |
+
):
|
| 1676 |
+
"""Apply forward mask without mask_type.
|
| 1677 |
+
|
| 1678 |
+
- If apply_semantic_window=True, apply causal/local window constraints.
|
| 1679 |
+
- Always apply residual OOB masking (index_k>=seqlen_k or index_q>=seqlen_q).
|
| 1680 |
+
"""
|
| 1681 |
+
offset = 0
|
| 1682 |
+
if cutlass.const_expr(apply_semantic_window):
|
| 1683 |
+
# Match WINDOW_MASK_INFERENCE semantics: end-align Q/K when lengths differ.
|
| 1684 |
+
offset = seqlen_k - seqlen_q
|
| 1685 |
+
for i in cutlass.range_constexpr(cute.size(acc_qk), unroll_full=True):
|
| 1686 |
+
index_q, index_k = index_transform(*index_qk[i])
|
| 1687 |
+
if cutlass.const_expr(apply_semantic_window):
|
| 1688 |
+
if cutlass.const_expr(is_causal and not is_local):
|
| 1689 |
+
# Pure causal; tolerate both external forms:
|
| 1690 |
+
# - (None, None) from interface
|
| 1691 |
+
# - (None, 0) from fused-mask-style callers
|
| 1692 |
+
right = 0 if const_expr(window_size_right is None) else window_size_right
|
| 1693 |
+
if index_q + offset + right < index_k:
|
| 1694 |
+
acc_qk[i] = -Float32.inf
|
| 1695 |
+
elif cutlass.const_expr(
|
| 1696 |
+
is_local or window_size_left is not None or window_size_right is not None
|
| 1697 |
+
):
|
| 1698 |
+
if cutlass.const_expr(window_size_left is None):
|
| 1699 |
+
if index_q + offset + window_size_right < index_k:
|
| 1700 |
+
acc_qk[i] = -Float32.inf
|
| 1701 |
+
elif cutlass.const_expr(window_size_right is None):
|
| 1702 |
+
if index_q + offset - window_size_left > index_k:
|
| 1703 |
+
acc_qk[i] = -Float32.inf
|
| 1704 |
+
else:
|
| 1705 |
+
max_K_index = dsl_min(index_q + offset + window_size_right, seqlen_k)
|
| 1706 |
+
min_K_index = max(0, index_q + offset - window_size_left)
|
| 1707 |
+
if index_k > max_K_index or index_k < min_K_index:
|
| 1708 |
+
acc_qk[i] = -Float32.inf
|
| 1709 |
+
# Residual mask is always needed for boundary protection.
|
| 1710 |
+
if index_k >= seqlen_k or index_q >= seqlen_q:
|
| 1711 |
+
acc_qk[i] = -Float32.inf
|
build/torch-cuda/metadata.json
CHANGED
|
@@ -1,9 +1,93 @@
|
|
| 1 |
{
|
| 2 |
-
"
|
|
|
|
|
|
|
|
|
|
| 3 |
"license": "BSD-3-Clause",
|
|
|
|
| 4 |
"python-depends": [
|
| 5 |
"einops",
|
| 6 |
"tvm-ffi",
|
| 7 |
"nvidia-cutlass-dsl"
|
| 8 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"name": "flash-attn4",
|
| 3 |
+
"id": "_flash_attn4_cuda_d8eaf3c",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"kernels-minver": "0.14.0",
|
| 6 |
"license": "BSD-3-Clause",
|
| 7 |
+
"upstream": "https://github.com/Dao-AILab/flash-attention.git",
|
| 8 |
"python-depends": [
|
| 9 |
"einops",
|
| 10 |
"tvm-ffi",
|
| 11 |
"nvidia-cutlass-dsl"
|
| 12 |
+
],
|
| 13 |
+
"kernel-depends": [],
|
| 14 |
+
"backend": {
|
| 15 |
+
"type": "cuda"
|
| 16 |
+
},
|
| 17 |
+
"digest": {
|
| 18 |
+
"algorithm": "sha256",
|
| 19 |
+
"files": {
|
| 20 |
+
"__init__.py": "9nhmToMqhkzqb88qdOhfpDICvN3LHSZhEM8HXa7VlcU=",
|
| 21 |
+
"_ops.py": "PVG5218SwxBt8nM52gD8xBjus4sHeK7/H9pZkvPRxCc=",
|
| 22 |
+
"_private_for_testing.py": "QXVDjtjb5NEXpgkZ93UyWJgHbrhi757Jlc8lkkTKETw=",
|
| 23 |
+
"ampere_helpers.py": "ytrBcKfaN54nw20EQ2BIDXEM+ojQgtrSud7NWOSEcRE=",
|
| 24 |
+
"barrier.py": "dnr4B6p1IbjRXubJU09HRaetIiSw2iuBbMScy56mwRk=",
|
| 25 |
+
"blackwell_helpers.py": "1G7e3qNjDRq24hiED6qbAJaN+9Lt+VfKRdbBSQBlP5U=",
|
| 26 |
+
"block_info.py": "c0GOkiVrwE1jkpNJFT+RPO1xdRD5ZM6RImIsvyLWnLc=",
|
| 27 |
+
"block_sparse_utils.py": "Y4Puv8qFy3MmuN1wY7/SvPHiOkKdZC5ItN3JJhwGqvo=",
|
| 28 |
+
"block_sparsity.py": "0PG41ePOKP/KS1j6eIo9pkMp7a7vrdCAysgmU/Eul+I=",
|
| 29 |
+
"cache_utils.py": "LbHAKc04S2NK2yEvmR6aB8yZuCeT2MCOrVv5lvEzQhA=",
|
| 30 |
+
"compute_block_sparsity.py": "p2+8YK5TnSM+G7g02pkrQxllSjOg/Lb4hbgNsKsttrM=",
|
| 31 |
+
"copy_utils.py": "D1Y+7rZlRgLRVB9yAre4g4bE29vSStawglg01l5QjAk=",
|
| 32 |
+
"cu_blocks_kernel.py": "oZ/Lf41VMM5o6vzUuDvd/dwDyNm7hqFfHkeC/gnSKW8=",
|
| 33 |
+
"cute_dsl_ptxas.py": "IfBnTJ9amdfDOQkuSdWCLTh7CkZziIvs/xrAc8taxhk=",
|
| 34 |
+
"cute_dsl_utils.py": "6ko8k13DHTgblp8bhmMeqkTJ4izAlr0S0TDIGiofkrs=",
|
| 35 |
+
"fa_logging.py": "vHHt+ksct69kt43vCeeQ/0xv5kq0/Pjj2WmfROjTwKA=",
|
| 36 |
+
"fast_math.py": "fl+CK/TQujaWdVjzrpoUCLQQy3JsMSIs1hcDVp0b5zg=",
|
| 37 |
+
"flash_bwd.py": "TRhiH4s/hQpkpQAKPt8lRMpg/92QqYUIMCok60L9gUo=",
|
| 38 |
+
"flash_bwd_mla_dk_sm100.py": "nPKlnayjEyxu0T5JqrXjojn5wzBQmwW7O0RmekOu6Q8=",
|
| 39 |
+
"flash_bwd_mla_dq_dqv_sm100.py": "WM/DT6DLexdrSs4EWskTENaRwN9VdGbmbEOVqY1ITgU=",
|
| 40 |
+
"flash_bwd_mla_sm100.py": "k6mUzSMehuxfkOxnhOHQEh0VoWWu0Wqczqs0BoCPNyY=",
|
| 41 |
+
"flash_bwd_postprocess.py": "N3dE8x0gau28oJMZDaRnOtQ+Eu8dJdG32lo5c4RrDRw=",
|
| 42 |
+
"flash_bwd_preprocess.py": "0lZNAuKMo4hZb5EF1IjtoMvR5FSR0Iq1MTDKWOD25Gk=",
|
| 43 |
+
"flash_bwd_sm100.py": "32wNaSsj3PLh7FqTTgKan3Mwe9iXrKFZOXwjXFkiEFw=",
|
| 44 |
+
"flash_bwd_sm120.py": "pYu0Z1Yt1TGkLrGUDQbHj7e7plUvtCW9lE5kJhM+TeM=",
|
| 45 |
+
"flash_bwd_sm90.py": "DvV+aDvDjC50LnEXdXUizK6+maRzsjFmcGO+h8bLOGc=",
|
| 46 |
+
"flash_fwd.py": "CJCCeosdamts+KoMuX2wYzCTK8d9veU4W9xDd8Qrw3Q=",
|
| 47 |
+
"flash_fwd_combine.py": "BGXmjQjrNksA0+TpgF3Trh6KlmejTiSQD5TMbgTWujM=",
|
| 48 |
+
"flash_fwd_mla_sm100.py": "wsXfpdNvwO4bw4Ol38H5t9n57N+E5Ln8gpkNiazXA4E=",
|
| 49 |
+
"flash_fwd_sm100.py": "8kqCPNeGuI56BDiSIXdfFZalVLz8SWmGpkHlfvYwmXg=",
|
| 50 |
+
"flash_fwd_sm120.py": "RGmi1PkGYo48xqAWYimrIb7XrokTOKZYYQx5XkxDKco=",
|
| 51 |
+
"flash_fwd_sm90.py": "zmqsNatHicpxW+dSRnPn2gNMt+TaI8HX+VEOr2hlyns=",
|
| 52 |
+
"interface.py": "WSarPGybE0wvH3K6tYnGrldr/z2A7TQojBcd7Dz7iWc=",
|
| 53 |
+
"mask.py": "45XBbPI1GDqaoT0k3Nq83gKu61ZOGrK+R+mWzWXWw+k=",
|
| 54 |
+
"mma_sm100_desc.py": "rG7Ciy8aS3l8ORp4B7iPg7QwRS1BgmLD36ew81FuOfk=",
|
| 55 |
+
"named_barrier.py": "H4D5a2kKKIjuEwHTd2ZdtunddLQC+A6qhn049ylNgTs=",
|
| 56 |
+
"pack_gqa.py": "Oqx6zBC5MrUYnj5E/tTBMRuDOUV5kCu0slYHy6jDvDg=",
|
| 57 |
+
"paged_kv.py": "aCfJSKrg3NnhitFjG9FbShvP+PuAt+gQNz01AjgCtao=",
|
| 58 |
+
"pipeline.py": "hJvOkccr4KXHRYl54QyCkPReBSU6g2AOMPuVQMYe1Wo=",
|
| 59 |
+
"prepare_scheduler.py": "83KHAGQBk7uz7WdmCwV3/k7gEbhiUYEc3VVMkx+LwiU=",
|
| 60 |
+
"quack/__init__.py": "m24hqJucTK14gZS18kAZhF29vDzEWaplx6wp6XN4T70=",
|
| 61 |
+
"quack/compile_utils.py": "qJ3oTsDlbAiddrJHtEO7LPYVqn/s+neNfiw+/KvfXZU=",
|
| 62 |
+
"quack/copy_utils.py": "48qGZz3ql2hwxAH+rr8g7hJoiy2drx4YRnXjb3TcEJo=",
|
| 63 |
+
"quack/cute_dsl_utils.py": "4jBC37wrqgJU/2Y3HFVbi46rQBkade3+ymPcHIxAYQk=",
|
| 64 |
+
"quack/dsl/__init__.py": "7mF8m94hdEj3jrzX0sk0LBszrP7WDhdcUJd4lNDOpGs=",
|
| 65 |
+
"quack/dsl/cute_tensor_indexing.py": "1QnPrnekkECXkgHC5NWY6JxyDmTTf5kgClCIYwxMfG0=",
|
| 66 |
+
"quack/layout_utils.py": "MZncP1XzRhg+PShPbamPQ5Tq8U8ot2FtFH5uSeyJYZQ=",
|
| 67 |
+
"quack/rounding.py": "jI6fP/qCjSMh9s8PtjPDYfTPs200C3xx9zKll1hlodU=",
|
| 68 |
+
"quack/sm90_utils.py": "QaLZ3MLlp9dKOD+VwdhxmqEstPdq8CVGaNwffbwspNw=",
|
| 69 |
+
"quack/utils.py": "/FsIscjaxmOSj/g6frh4gLgB6iuIZ6e3EeGNunKWBuw=",
|
| 70 |
+
"seqlen_info.py": "ljfpUQcu9fILvJfyYYGkM85kiXjmNP1p748eivq0dwk=",
|
| 71 |
+
"sm100_hd256_2cta_fmha_backward.py": "BFD5O/rIJi5X//5ecE/h+zQHhb9HLI8TmpZHHhcVmns=",
|
| 72 |
+
"sm100_hd256_2cta_fmha_backward_dkdvkernel.py": "EJiDHIFGQ8A63CHSzFrDF5kigtWNZMqsvXk/28zVWfI=",
|
| 73 |
+
"sm100_hd256_2cta_fmha_backward_dqkernel.py": "SKQ8lUEl1P4ZcsHiMaGBqQJFL2o0oZTJhF8oOx6iXDs=",
|
| 74 |
+
"sm100_hd256_2cta_fmha_forward.py": "aJ0icWUbC3D2iLlj8SDXb+eaM0/U4mljFlIaH8iEZd4=",
|
| 75 |
+
"softmax.py": "eCMvLFCXjlrgm0s24D5rrdbFFRMyah1ESWjB3BEgLu4=",
|
| 76 |
+
"testing.py": "M0K0R1EmZm5b/wUDFs0qWGhS98gusj/SEXJHDMQKFf8=",
|
| 77 |
+
"tile_scheduler.py": "ELRFy6rI+J+8Z2fMDLxYrCiuUMSaBLa4h10dIe27bmA=",
|
| 78 |
+
"topk_gather_kv.py": "WchMfXog4eIIIOhTKu0vE5k3dfn41jVqrl9wtafJvRw=",
|
| 79 |
+
"utils.py": "j+SX9mJpWWA998oBdZgS28MoIV69XwTRN0mVA6Hcha8="
|
| 80 |
+
}
|
| 81 |
+
},
|
| 82 |
+
"provenance": {
|
| 83 |
+
"kernel-builder": {
|
| 84 |
+
"version": "0.17.0-dev0",
|
| 85 |
+
"commit": "a7f0afdb29a6a3372b1d47180cc0c182454c5e3b",
|
| 86 |
+
"dirty": false
|
| 87 |
+
},
|
| 88 |
+
"kernel": {
|
| 89 |
+
"commit": "d8eaf3cbe010f4795d0b0c4b0103862387ccdc97",
|
| 90 |
+
"dirty": false
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
}
|
build/torch-cuda/metadata.json.sigstore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHLTCCBrKgAwIBAgIUC8FD02MzDpPjLuXMiJ3QOc8FBi4wCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYxMDA5MDU0NzQyWhcNMjYxMDA5MDU1NzQyWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvU7iFTJAhfZ8mmvV3pYwcuyy5onUd8y8YkK3oDJrIYFKZZeP51FT0kntoP0n4AGSPRV4Ro9wrRR7jPlxS/0VyKOCBdEwggXNMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSMEGDAWgBTf0+nPViQRlvmo2OkoVaLGLhhkPzBrBgNVHREBAf8EYTBfhl1odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkvLmdpdGh1Yi93b3JrZmxvd3MvYnVpbGQueWFtbEByZWZzL2hlYWRzL21haW4wOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAfBgorBgEEAYO/MAECBBF3b3JrZmxvd19kaXNwYXRjaDA2BgorBgEEAYO/MAEDBChkOGVhZjNjYmUwMTBmNDc5NWQwYjBjNGIwMTAzODYyMzg3Y2NkYzk3MBMGCisGAQQBg78wAQQEBUJ1aWxkMCsGCisGAQQBg78wAQUEHWh1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MB0GCisGAQQBg78wAQYED3JlZnMvaGVhZHMvbWFpbjA7BgorBgEEAYO/MAEIBC0MK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wbQYKKwYBBAGDvzABCQRfDF1odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkvLmdpdGh1Yi93b3JrZmxvd3MvYnVpbGQueWFtbEByZWZzL2hlYWRzL21haW4wOAYKKwYBBAGDvzABCgQqDChkOGVhZjNjYmUwMTBmNDc5NWQwYjBjNGIwMTAzODYyMzg3Y2NkYzk3MBsGCisGAQQBg78wAQsEDQwLc2VsZi1ob3N0ZWQwQAYKKwYBBAGDvzABDAQyDDBodHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkwOAYKKwYBBAGDvzABDQQqDChkOGVhZjNjYmUwMTBmNDc5NWQwYjBjNGIwMTAzODYyMzg3Y2NkYzk3MB8GCisGAQQBg78wAQ4EEQwPcmVmcy9oZWFkcy9tYWluMBoGCisGAQQBg78wAQ8EDAwKMTA3MTQ3NTUyOTAuBgorBgEEAYO/MAEQBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZTAYBgorBgEEAYO/MAERBAoMCDI1NzIwNzQzMG0GCisGAQQBg78wARIEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wARMEKgwoZDhlYWYzY2JlMDEwZjQ3OTVkMGIwYzRiMDEwMzg2MjM4N2NjZGM5NzAhBgorBgEEAYO/MAEUBBMMEXdvcmtmbG93X2Rpc3BhdGNoMGQGCisGAQQBg78wARUEVgxUaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5L2FjdGlvbnMvcnVucy8zNzg5MDA3MjU2Mi9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwGcHVibGljMEYGCisGAQQBg78wARgEOAw2cmVwbzpodWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTpyZWY6cmVmcy9oZWFkcy9tYWluMIGKBgorBgEEAdZ5AgQCBHwEegB4AHYA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGhHzP5igAABAMARzBFAiEAq9xS/SQssYBHs+y3wB9piTHTBtP5LMlf5JId4nIfA3sCIH5m7oZGU8V7XUlOnv+dvMJ3KQo46r1zQBPQkI9jcu5gMAoGCCqGSM49BAMDA2kAMGYCMQCrjTEdjE3f7UqDCOh3sVTk2MU9H7IJ2OkZwrbUwupS/xvK0ni6p5gl2AdH0pLBfkkCMQDkblT245iYWckRzNjXOOIUi4lNnrNhu5fdRtd9Z58/K5sQp21Z61tVdGfkOPF8UF4="},"tlogEntries":[{"logIndex":"3158296362","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1791524864","inclusionPromise":{"signedEntryTimestamp":"MEUCIHdcmCseFb7Vr5yOOIYHwnu8qbD12aVWYFJAvrIFWQlQAiEAiI/g9IvQbhN7/BHfD7fKpMlkheGsalXj3sjCz0fV9Eg="},"inclusionProof":{"logIndex":"3036392100","rootHash":"teeCGBfEA5uGpfYT9SeWxOIWLaZFezcGvaKdKW6ZXHk=","treeSize":"3036392181","hashes":["KyrJTiRKZ75+pT8Sx+ulXcJDqdovWH9dj1tSGpuz7bI=","GHJ44yU03czeAoYe92xG9t5dcLSj8DC6P93R9ir+YGw=","+MpZb3IeTXUQ1VXFXZgfVFzCvOX0MDxGanUQMFngYWw=","R62P9HI0DerDEF/J2NPUG+pRooZZjr87tb/6a02VAZs=","witl6lVGRqWEukI7TPfEwEterD0x0U/N+vLFrEgM3d0=","//9Wrtt/km05V/Uz1gBzHmw1++3e5qzCpZycY0id0hM=","5qY+JootET5QYakLD1VSLaWLYsBV5CNsny3SMmLgDck=","gnsD5AdzyM8v81+WoK8S73uW+oNypt2c3v4gw07kL6c=","wDpbIRy5hWWa+M5aB2/ZontGTidhd0Opkg9pOLRfJLc=","TDNBgrWNhMz/ZLmNeiUQjXJE6NabHjQEsHCekqo5DS8=","78RhvRg8R4WwHxVvzWwFGNaJwKZGwJSN7GYO7kn5vP8=","F5tQx3q6DM2bQD3L/G2KJ9rAjZ0VMzlAmuw1Urrltpg=","KbyZDOTON24dtLPkz0tlGLB0OsswC4djFUNHxHjMipI=","VPmQZaiIPP+jGVmSWjYHBv/V9OCTZbwibPFCdg+AHeQ=","GSGvSu0XsdO6cBgqJwZWqKj8191ujOCFNrbeVOnHXfU=","bDbLklSZE3cNPdbgkYjsldgt1tp4oyGy0Ey74MEDzVs=","kbqJ2Mh+r6zBJA5FYtUICq/XJ22GIh0GC+s04H3SZOs=","QsIovxEueg218cKeugrO+eGZOkloq5eRks0NcVdhADs=","QJUdtMG5fqTebI7bWTn0N8FLr9gz+BNgQr/7OsNQa1w=","hafZfJdzUX/yyCr3Anv7Qq1DWVaWxotaclNRcl/O9SQ=","NlOclldmKPA1tMKIskqHAwuQDYHp31YwpK+OOMrMccc=","qxzHanAzz57SDdmJe0B7bJK72NTIBbwEMGBBKvDOROw=","xH/DCseLHr9eKoYT8qsORZK7zVdEGYWHuVtsVrD95wY="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n3036392181\nteeCGBfEA5uGpfYT9SeWxOIWLaZFezcGvaKdKW6ZXHk=\n\n— rekor.sigstore.dev wNI9ajBFAiEApHSDrn9feEHa8pVsxt21mKS1GsE0GwW/yWXtEDWTr8UCIAt1IfpaBYiybTSBozWZJk38aPVtTQZe2Uk7H1KNyATZ\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJjODljNjJjNDZlMmNlNmY2MzNkMjM3NWIwMGY5ZDU2ZmUxZDM3MTY5MGI0YjEyZDIxODQ3ODliODU4ZTZhYjk2In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUNVM1VGTmxzVkxDSVloNVFRanlicHhranp2YU1VM0J6MTk1T2hTZTk1ZTh3SWdVK0dVdDZGK1c4Wll4ZjBQUXFqU001bVhWeXlrSDQrZmZLZnVIS1F5cmRFPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaE1WRU5EUW5KTFowRjNTVUpCWjBsVlF6aEdSREF5VFhwRWNGQnFUSFZZVFdsS00xRlBZemhHUW1rMGQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFFUVRWTlJGVXdUbnBSZVZkb1kwNU5hbGw0VFVSQk5VMUVWVEZPZWxGNVYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVYyVlRkcFJsUktRV2htV2podGJYWldNM0JaZDJOMWVYazFiMjVWWkRoNU9GbHJTek1LYjBSS2NrbFpSa3RhV21WUU5URkdWREJyYm5SdlVEQnVORUZIVTFCU1ZqUlNiemwzY2xKU04ycFFiSGhUTHpCV2VVdFBRMEprUlhkbloxaE9UVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFuSkNaMDVXU0ZKRlFrRm1PRVZaVkVKbWFHd3hiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWMllUSldlV0p0Vm5OamVURnFZakl4ZEdSWE5YQmtTR3QyVEcxa2NHUkhhREZaYVRrellqTktjZ3BhYlhoMlpETk5kbGx1Vm5CaVIxRjFaVmRHZEdKRlFubGFWMXA2VERKb2JGbFhVbnBNTWpGb1lWYzBkMDlSV1V0TGQxbENRa0ZIUkhaNlFVSkJVVkZ5Q21GSVVqQmpTRTAyVEhrNU1HSXlkR3hpYVRWb1dUTlNjR0l5TlhwTWJXUndaRWRvTVZsdVZucGFXRXBxWWpJMU1GcFhOVEJNYlU1MllsUkJaa0puYjNJS1FtZEZSVUZaVHk5TlFVVkRRa0pHTTJJelNuSmFiWGgyWkRFNWEyRllUbmRaV0ZKcVlVUkJNa0puYjNKQ1owVkZRVmxQTDAxQlJVUkNRMmhyVDBkV2FBcGFhazVxV1cxVmQwMVVRbTFPUkdNMVRsZFJkMWxxUW1wT1IwbDNUVlJCZWs5RVdYbE5lbWN6V1RKT2ExbDZhek5OUWsxSFEybHpSMEZSVVVKbk56aDNDa0ZSVVVWQ1ZVb3hZVmQ0YTAxRGMwZERhWE5IUVZGUlFtYzNPSGRCVVZWRlNGZG9NVm95WkhCaWJXUnRXVmRPYkV3eWRHeGpiVFZzWWtoTmRGa3lPWFFLWWxoV2RXRllValZOUWpCSFEybHpSMEZSVVVKbk56aDNRVkZaUlVRelNteGFiazEyWVVkV2FGcElUWFppVjBad1ltcEJOMEpuYjNKQ1owVkZRVmxQTHdwTlFVVkpRa013VFVzeWFEQmtTRUo2VDJrNGRtUkhPWEphVnpSMVdWZE9NR0ZYT1hWamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcUNtSXlNSGRpVVZsTFMzZFpRa0pCUjBSMmVrRkNRMUZTWmtSR01XOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaFNGWnVXakpzZFZveVdtZ0tXVEpWZG1FeVZubGliVlp6WTNreGFtSXlNWFJrVnpWd1pFaHJka3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsbHVWbkJpUjFGMVpWZEdkQXBpUlVKNVdsZGFla3d5YUd4WlYxSjZUREl4YUdGWE5IZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1EyZFJjVVJEYUd0UFIxWm9XbXBPYWxsdFZYZE5WRUp0Q2s1RVl6Vk9WMUYzV1dwQ2FrNUhTWGROVkVGNlQwUlplVTE2WnpOWk1rNXJXWHByTTAxQ2MwZERhWE5IUVZGUlFtYzNPSGRCVVhORlJGRjNUR015Vm5NS1dta3hiMkl6VGpCYVYxRjNVVUZaUzB0M1dVSkNRVWRFZG5wQlFrUkJVWGxFUkVKdlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWVVoV2JncGFNbXgxV2pKYWFGa3lWWFpoTWxaNVltMVdjMk41TVdwaU1qRjBaRmMxY0dSSWEzZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JGRlJjVVJEYUd0UFIxWm9DbHBxVG1wWmJWVjNUVlJDYlU1RVl6Vk9WMUYzV1dwQ2FrNUhTWGROVkVGNlQwUlplVTE2WnpOWk1rNXJXWHByTTAxQ09FZERhWE5IUVZGUlFtYzNPSGNLUVZFMFJVVlJkMUJqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxQ2IwZERhWE5IUVZGUlFtYzNPSGRCVVRoRlJFRjNTMDFVUVROTlZGRXpUbFJWZVFwUFZFRjFRbWR2Y2tKblJVVkJXVTh2VFVGRlVVSkRRVTFJYldnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemx2WkZka2JtRlhOVzVhYlVacUNscFVRVmxDWjI5eVFtZEZSVUZaVHk5TlFVVlNRa0Z2VFVORVNURk9la2wzVG5wUmVrMUhNRWREYVhOSFFWRlJRbWMzT0hkQlVrbEZXSGQ0WkdGSVVqQUtZMGhOTmt4NU9XNWhXRkp2WkZkSmRWa3lPWFJNTW1neFdqSmtjR0p0WkcxWlYwNXNUREowYkdOdE5XeGlTRTEwV1RJNWRHSllWblZoV0ZJMVRIazFiZ3BoV0ZKdlpGZEpkbVF5T1hsaE1scHpZak5rZWt3eVNqRmhWM2hyVEc1c2FHSlhlRUZqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxRVowZERhWE5IQ2tGUlVVSm5OemgzUVZKTlJVdG5kMjlhUkdoc1dWZFplbGt5U214TlJFVjNXbXBSTTA5VVZtdE5SMGwzV1hwU2FVMUVSWGROZW1jeVRXcE5ORTR5VG1vS1drZE5OVTU2UVdoQ1oyOXlRbWRGUlVGWlR5OU5RVVZWUWtKTlRVVllaSFpqYlhSdFlrYzVNMWd5VW5Cak0wSm9aRWRPYjAxSFVVZERhWE5IUVZGUlFncG5OemgzUVZKVlJWWm5lRlZoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlV3eVJtcGtSMngyWW01TmRtTnVWblZqZVRoNlRucG5OVTFFUVROTmFsVXlUV2s1YUdSSVVteGlXRUl3WTNrNGVFMUNXVWNLUTJselIwRlJVVUpuTnpoM1FWSlpSVU5CZDBkalNGWnBZa2RzYWsxRldVZERhWE5IUVZGUlFtYzNPSGRCVW1kRlQwRjNNbU50Vm5kaWVuQnZaRmRrYmdwaFZ6VnVXbTFHYWxwVE9YSmFXRXAxV2xkNGVreFhUblppVnpFeFltMXNNR1ZVY0hsYVYxazJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOU1VkTENrSm5iM0pDWjBWRlFXUmFOVUZuVVVOQ1NIZEZaV2RDTkVGSVdVRXpWREIzWVhOaVNFVlVTbXBIVWpSamJWZGpNMEZ4U2t0WWNtcGxVRXN6TDJnMGNIa0taME00Y0Rkdk5FRkJRVWRvU0hwUU5XbG5RVUZDUVUxQlVucENSa0ZwUlVGeE9YaFRMMU5SYzNOWlFraHpLM2t6ZDBJNWNHbFVTRlJDZEZBMVRFMXNaZ28xU2tsa05HNUpaa0V6YzBOSlNEVnROMjlhUjFVNFZqZFlWV3hQYm5ZclpIWk5Tak5MVVc4ME5uSXhlbEZDVUZGclNUbHFZM1UxWjAxQmIwZERRM0ZIQ2xOTk5EbENRVTFFUVRKclFVMUhXVU5OVVVOeWFsUkZaR3BGTTJZM1ZYRkVRMDlvTTNOV1ZHc3lUVlU1U0RkSlNqSlBhMXAzY21KVmQzVndVeTk0ZGtzS01HNXBObkExWjJ3eVFXUklNSEJNUW1acmEwTk5VVVJyWW14VU1qUTFhVmxYWTJ0U2VrNXFXRTlQU1ZWcE5HeE9ibkpPYUhVMVptUlNkR1E1V2pVNEx3cExOWE5SY0RJeFdqWXhkRlprUjJaclQxQkdPRlZHTkQwS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9fX19"}],"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyTADAgEAMIICwAYJKoZIhvcNAQcCoIICsTCCAq0CAQMxDTALBglghkgBZQMEAgEwgbcGCyqGSIb3DQEJEAEEoIGnBIGkMIGhAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQg4k983K1coZlkayD036+cJpsVAAkEZEsAMpxiUQTrwEACFDkWZsJr2b7zVR04BWQQhoVadpRtGA8yMDI2MTAwOTA1NDc0MlowAwIBAaAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgADGCAdswggHXAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUOhNULwyQYe68wUMvy4qOiyojiwwwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYxMDA5MDU0NzQyWjAvBgkqhkiG9w0BCQQxIgQgLObLq7NGK87U9ONi2A7Ln6dRup/atfMcOdqLVbkL/G4wgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIIX5J7wHq2LKw7RDVsEO/IGyxog/2nq55thw2dE6zQW3MFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAoGCCqGSM49BAMCBGcwZQIxAIcZfruFVmPc3PiaAgWG2V/Lf7wzIFR8Zzf04nbAW4f9QWNBAschM6N1+f5egCBMbgIwO4DVCem+xvxWkNErxMg+VRttorrtEnVgXG9eqCuTc0xyNiLSbTkH2iLHCFVwFz7Y"}]}},"messageSignature":{"messageDigest":{"algorithm":"SHA2_256","digest":"yJxixG4s5vYz0jdbAPnVb+HTcWkLSxLSGEeJuFjmq5Y="},"signature":"MEUCIQCU3UFNlsVLCIYh5QQjybpxkjzvaMU3Bz195OhSe95e8wIgU+GUt6F+W8ZYxf0PQqjSM5mXVyykH4+ffKfuHKQyrdE="}}
|
build/torch-cuda/mma_sm100_desc.py
CHANGED
|
@@ -83,9 +83,9 @@ def to_UMMA_format(cutlass_type) -> int:
|
|
| 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.
|
| 87 |
return MXF8F6F4Format.E4M3
|
| 88 |
-
if cutlass_type is cutlass.
|
| 89 |
return MXF8F6F4Format.E5M2
|
| 90 |
raise TypeError(f"Unsupported CUTLASS scalar type for A/B: {cutlass_type!r}")
|
| 91 |
|
|
|
|
| 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 |
|
build/torch-cuda/named_barrier.py
CHANGED
|
@@ -12,6 +12,19 @@ class NamedBarrierFwd(enum.IntEnum):
|
|
| 12 |
PEmpty = enum.auto()
|
| 13 |
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
class NamedBarrierBwd(enum.IntEnum):
|
| 16 |
Epilogue = enum.auto()
|
| 17 |
WarpSchedulerWG1 = enum.auto()
|
|
@@ -20,8 +33,10 @@ class NamedBarrierBwd(enum.IntEnum):
|
|
| 20 |
PdS = enum.auto()
|
| 21 |
dQFullWG0 = enum.auto()
|
| 22 |
dQFullWG1 = enum.auto()
|
|
|
|
| 23 |
dQEmptyWG0 = enum.auto()
|
| 24 |
dQEmptyWG1 = enum.auto()
|
|
|
|
| 25 |
|
| 26 |
|
| 27 |
class NamedBarrierBwdSm100(enum.IntEnum):
|
|
@@ -30,3 +45,19 @@ class NamedBarrierBwdSm100(enum.IntEnum):
|
|
| 30 |
Compute = enum.auto()
|
| 31 |
dQaccReduce = enum.auto()
|
| 32 |
TmemPtr = 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()
|
|
|
|
| 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):
|
|
|
|
| 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()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class NamedBarrierBwdSm100_MLA2CTA(enum.IntEnum):
|
| 60 |
+
Epilogue = enum.auto()
|
| 61 |
+
TmemPtr = enum.auto()
|
| 62 |
+
Cpasync = enum.auto()
|
| 63 |
+
Softmax = enum.auto()
|
build/torch-cuda/pack_gqa.py
CHANGED
|
@@ -1,25 +1,123 @@
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
|
|
|
|
|
|
| 3 |
|
| 4 |
import cutlass
|
| 5 |
import cutlass.cute as cute
|
|
|
|
|
|
|
| 6 |
|
| 7 |
from .quack import layout_utils
|
| 8 |
-
from . import utils
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
class PackGQA:
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
check_hdim_oob: cutlass.Constexpr[bool],
|
| 17 |
-
qhead_per_kvhead: cutlass.Constexpr[bool],
|
| 18 |
-
):
|
| 19 |
-
self.m_block_size = m_block_size
|
| 20 |
-
self.head_dim_padded = head_dim_padded
|
| 21 |
-
self.check_hdim_oob = check_hdim_oob
|
| 22 |
-
self.qhead_per_kvhead = qhead_per_kvhead
|
| 23 |
|
| 24 |
@cute.jit
|
| 25 |
def compute_ptr(
|
|
@@ -32,7 +130,7 @@ class PackGQA:
|
|
| 32 |
num_threads: cutlass.Constexpr[int],
|
| 33 |
):
|
| 34 |
num_ptr_per_thread = cute.ceil_div(cute.size(cRows), threads_per_row)
|
| 35 |
-
tPrPtr = cute.
|
| 36 |
for i in cutlass.range_constexpr(num_ptr_per_thread):
|
| 37 |
row = i * num_threads + cRows[tidx % threads_per_row][0]
|
| 38 |
idx = block * self.m_block_size + row
|
|
|
|
| 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 .quack import layout_utils
|
| 12 |
+
from . import 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(
|
|
|
|
| 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_rmem_tensor(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
|
build/torch-cuda/paged_kv.py
CHANGED
|
@@ -28,6 +28,9 @@ class PagedKVManager(ParamsBase):
|
|
| 28 |
head_dim_padded: cutlass.Constexpr[Int32]
|
| 29 |
head_dim_v_padded: cutlass.Constexpr[Int32]
|
| 30 |
|
|
|
|
|
|
|
|
|
|
| 31 |
gmem_threads_per_row: cutlass.Constexpr[Int32]
|
| 32 |
page_entry_per_thread: Int32
|
| 33 |
async_copy_elems: Int32
|
|
@@ -55,7 +58,11 @@ class PagedKVManager(ParamsBase):
|
|
| 55 |
head_dim_v_padded: cutlass.Constexpr[Int32],
|
| 56 |
num_threads: cutlass.Constexpr[Int32],
|
| 57 |
dtype: Type[cutlass.Numeric],
|
|
|
|
| 58 |
):
|
|
|
|
|
|
|
|
|
|
| 59 |
universal_copy_bits = 128
|
| 60 |
async_copy_elems = universal_copy_bits // dtype.width
|
| 61 |
dtype_bytes = dtype.width // 8
|
|
@@ -79,7 +86,8 @@ class PagedKVManager(ParamsBase):
|
|
| 79 |
val_layout = cute.make_layout((1, async_copy_elems))
|
| 80 |
gmem_tiled_copy_KV = cute.make_tiled_copy_tv(atom_async_copy, thr_layout, val_layout)
|
| 81 |
gmem_thr_copy_KV = gmem_tiled_copy_KV.get_slice(thread_idx)
|
| 82 |
-
|
|
|
|
| 83 |
|
| 84 |
tPrPage = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
| 85 |
tPrPageOffset = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
|
@@ -97,7 +105,9 @@ class PagedKVManager(ParamsBase):
|
|
| 97 |
else:
|
| 98 |
cV = cute.make_identity_tensor((n_block_size, head_dim_v_padded))
|
| 99 |
tVcV = gmem_thr_copy_KV.partition_S(cV)
|
| 100 |
-
|
|
|
|
|
|
|
| 101 |
|
| 102 |
return PagedKVManager(
|
| 103 |
mPageTable,
|
|
@@ -111,6 +121,8 @@ class PagedKVManager(ParamsBase):
|
|
| 111 |
num_threads,
|
| 112 |
head_dim_padded,
|
| 113 |
head_dim_v_padded,
|
|
|
|
|
|
|
| 114 |
gmem_threads_per_row,
|
| 115 |
page_entry_per_thread,
|
| 116 |
async_copy_elems,
|
|
@@ -144,24 +156,24 @@ class PagedKVManager(ParamsBase):
|
|
| 144 |
self.tPrPageOffset[i] = page_offset
|
| 145 |
|
| 146 |
@cute.jit
|
| 147 |
-
def compute_X_ptr(self, K_or_V: str):
|
| 148 |
tPrXPtr = cute.make_rmem_tensor((self.page_entry_per_thread,), cutlass.Int64)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
for i in cutlass.range(self.page_entry_per_thread, unroll=1):
|
| 150 |
page = self.tPrPage[i]
|
| 151 |
page_offset = self.tPrPageOffset[i]
|
| 152 |
-
if const_expr(
|
| 153 |
-
tPrXPtr[i] = utils.elem_pointer(
|
| 154 |
else:
|
| 155 |
-
tPrXPtr[i] = utils.elem_pointer(
|
| 156 |
return tPrXPtr
|
| 157 |
|
| 158 |
@cute.jit
|
| 159 |
-
def
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
tPrXPtr = self.compute_X_ptr(K_or_V)
|
| 163 |
-
|
| 164 |
-
# Finesse sX layout to be (M, N).
|
| 165 |
sX_pi = cute.make_tensor(
|
| 166 |
sX.iterator,
|
| 167 |
cute.make_layout(
|
|
@@ -169,10 +181,45 @@ class PagedKVManager(ParamsBase):
|
|
| 169 |
stride=(sX.stride[0][0], (sX.stride[0][1], sX.stride[2])),
|
| 170 |
),
|
| 171 |
)
|
| 172 |
-
|
| 173 |
if const_expr(K_or_V == "V"):
|
| 174 |
-
# Need to transpose V
|
| 175 |
sX_pi = cute.make_tensor(sX_pi.iterator, cute.select(sX_pi.layout, mode=[1, 0]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
head_dim = self.head_dim_v_padded if const_expr(K_or_V == "V") else self.head_dim_padded
|
| 178 |
cX = cute.make_identity_tensor((self.n_block_size, head_dim))
|
|
@@ -198,17 +245,4 @@ class PagedKVManager(ParamsBase):
|
|
| 198 |
)
|
| 199 |
mX_paged_cur = cute.make_tensor(x_gmem_ptr, cute.make_layout((head_dim,)))
|
| 200 |
mX_paged_cur_copy = cute.tiled_divide(mX_paged_cur, (self.async_copy_elems,))
|
| 201 |
-
|
| 202 |
-
for k in cutlass.range_constexpr(cute.size(tXsX, mode=[2])):
|
| 203 |
-
ki = tXcX[0, 0, k][1] // self.async_copy_elems
|
| 204 |
-
mX_paged_cur_copy_ki = mX_paged_cur_copy[None, ki]
|
| 205 |
-
tXsX_k = tXsX[None, m, k]
|
| 206 |
-
mX_paged_cur_copy_ki = cute.make_tensor(
|
| 207 |
-
mX_paged_cur_copy_ki.iterator, tXsX_k.layout
|
| 208 |
-
)
|
| 209 |
-
cute.copy(
|
| 210 |
-
self.gmem_tiled_copy_KV,
|
| 211 |
-
mX_paged_cur_copy_ki,
|
| 212 |
-
tXsX_k,
|
| 213 |
-
pred=should_load,
|
| 214 |
-
)
|
|
|
|
| 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
|
|
|
|
| 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
|
|
|
|
| 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 |
+
# Include the final partially populated wave of rows.
|
| 90 |
+
page_entry_per_thread = (n_block_size + num_threads - 1) // num_threads
|
| 91 |
|
| 92 |
tPrPage = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
| 93 |
tPrPageOffset = cute.make_rmem_tensor((page_entry_per_thread,), Int32)
|
|
|
|
| 105 |
else:
|
| 106 |
cV = cute.make_identity_tensor((n_block_size, head_dim_v_padded))
|
| 107 |
tVcV = gmem_thr_copy_KV.partition_S(cV)
|
| 108 |
+
# When V is transposed in gmem, dv is shape[0]; otherwise dv is shape[1] (same as K)
|
| 109 |
+
V_limit = cute.size(mV_paged.shape[0 if v_gmem_transposed else 1])
|
| 110 |
+
tVpV = utils.predicate_k(tVcV, limit=V_limit)
|
| 111 |
|
| 112 |
return PagedKVManager(
|
| 113 |
mPageTable,
|
|
|
|
| 121 |
num_threads,
|
| 122 |
head_dim_padded,
|
| 123 |
head_dim_v_padded,
|
| 124 |
+
arch,
|
| 125 |
+
v_gmem_transposed,
|
| 126 |
gmem_threads_per_row,
|
| 127 |
page_entry_per_thread,
|
| 128 |
async_copy_elems,
|
|
|
|
| 156 |
self.tPrPageOffset[i] = page_offset
|
| 157 |
|
| 158 |
@cute.jit
|
| 159 |
+
def compute_X_ptr(self, K_or_V: str, d_offset: int = 0):
|
| 160 |
tPrXPtr = cute.make_rmem_tensor((self.page_entry_per_thread,), cutlass.Int64)
|
| 161 |
+
mX = self.mK_paged if const_expr(K_or_V == "K") else self.mV_paged
|
| 162 |
+
# K is always (page_size, d, num_pages). V matches K when not transposed,
|
| 163 |
+
# but is (dv, page_size, num_pages) when transposed (SM100).
|
| 164 |
+
transposed = const_expr(K_or_V == "V" and self.v_gmem_transposed)
|
| 165 |
for i in cutlass.range(self.page_entry_per_thread, unroll=1):
|
| 166 |
page = self.tPrPage[i]
|
| 167 |
page_offset = self.tPrPageOffset[i]
|
| 168 |
+
if const_expr(transposed):
|
| 169 |
+
tPrXPtr[i] = utils.elem_pointer(mX, (d_offset, page_offset, page)).toint()
|
| 170 |
else:
|
| 171 |
+
tPrXPtr[i] = utils.elem_pointer(mX, (page_offset, d_offset, page)).toint()
|
| 172 |
return tPrXPtr
|
| 173 |
|
| 174 |
@cute.jit
|
| 175 |
+
def _flatten_smem_sm100(self, sX: cute.Tensor, K_or_V: str):
|
| 176 |
+
"""Flatten SM100 smem ((a,b), cta_split, k) to (a,(b,k)); transpose V to (d,page_size)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
sX_pi = cute.make_tensor(
|
| 178 |
sX.iterator,
|
| 179 |
cute.make_layout(
|
|
|
|
| 181 |
stride=(sX.stride[0][0], (sX.stride[0][1], sX.stride[2])),
|
| 182 |
),
|
| 183 |
)
|
|
|
|
| 184 |
if const_expr(K_or_V == "V"):
|
|
|
|
| 185 |
sX_pi = cute.make_tensor(sX_pi.iterator, cute.select(sX_pi.layout, mode=[1, 0]))
|
| 186 |
+
return sX_pi
|
| 187 |
+
|
| 188 |
+
@cute.jit
|
| 189 |
+
def _copy_row_async(
|
| 190 |
+
self,
|
| 191 |
+
tXsX: cute.Tensor,
|
| 192 |
+
tXcX: cute.Tensor,
|
| 193 |
+
mX_paged_cur_copy: cute.Tensor,
|
| 194 |
+
m: Int32,
|
| 195 |
+
should_load: cute.Tensor,
|
| 196 |
+
):
|
| 197 |
+
"""Issue cp.async copies for one row across all k-tiles."""
|
| 198 |
+
for k in cutlass.range_constexpr(cute.size(tXsX, mode=[2])):
|
| 199 |
+
ki = tXcX[0, 0, k][1] // self.async_copy_elems
|
| 200 |
+
mX_paged_cur_copy_ki = mX_paged_cur_copy[None, ki]
|
| 201 |
+
tXsX_k = tXsX[None, m, k]
|
| 202 |
+
mX_paged_cur_copy_ki = cute.make_tensor(mX_paged_cur_copy_ki.iterator, tXsX_k.layout)
|
| 203 |
+
cute.copy(
|
| 204 |
+
self.gmem_tiled_copy_KV,
|
| 205 |
+
mX_paged_cur_copy_ki,
|
| 206 |
+
tXsX_k,
|
| 207 |
+
pred=should_load,
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
@cute.jit
|
| 211 |
+
def load_KV(self, n_block: Int32, sX: cute.Tensor, K_or_V: str):
|
| 212 |
+
assert K_or_V in ("K", "V")
|
| 213 |
+
|
| 214 |
+
tPrXPtr = self.compute_X_ptr(K_or_V)
|
| 215 |
+
|
| 216 |
+
if const_expr(self.arch == 90):
|
| 217 |
+
# SM90: sX is already stage-sliced by caller (sK[None, None, stage]).
|
| 218 |
+
# Flatten hierarchical modes to get (n_block_size, head_dim).
|
| 219 |
+
sX_pi = cute.group_modes(sX, 0, 1)
|
| 220 |
+
# SM90 does NOT transpose V here (it's transposed via utils.transpose_view before MMA)
|
| 221 |
+
else:
|
| 222 |
+
sX_pi = self._flatten_smem_sm100(sX, K_or_V)
|
| 223 |
|
| 224 |
head_dim = self.head_dim_v_padded if const_expr(K_or_V == "V") else self.head_dim_padded
|
| 225 |
cX = cute.make_identity_tensor((self.n_block_size, head_dim))
|
|
|
|
| 245 |
)
|
| 246 |
mX_paged_cur = cute.make_tensor(x_gmem_ptr, cute.make_layout((head_dim,)))
|
| 247 |
mX_paged_cur_copy = cute.tiled_divide(mX_paged_cur, (self.async_copy_elems,))
|
| 248 |
+
self._copy_row_async(tXsX, tXcX, mX_paged_cur_copy, m, should_load)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-cuda/pipeline.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
| 3 |
-
# import math
|
| 4 |
from typing import Optional
|
| 5 |
from dataclasses import dataclass
|
| 6 |
|
|
@@ -11,12 +10,31 @@ from cutlass.pipeline import PipelineState
|
|
| 11 |
from cutlass.pipeline import PipelineUserType
|
| 12 |
from cutlass.pipeline import NamedBarrier as NamedBarrierOg
|
| 13 |
from cutlass.pipeline import PipelineAsync as PipelineAsyncOg
|
|
|
|
| 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 |
class PipelineStateSimple:
|
| 21 |
"""
|
| 22 |
Pipeline state contains an index and phase bit corresponding to the current position in the circular buffer.
|
|
@@ -25,9 +43,6 @@ class PipelineStateSimple:
|
|
| 25 |
"""
|
| 26 |
|
| 27 |
def __init__(self, stages: int, phase_index: Int32):
|
| 28 |
-
# assert stages < 2**16
|
| 29 |
-
# self._log_stages = int(math.log2(stages))
|
| 30 |
-
# assert 1 << self._log_stages == stages, "Number of stages must be a power of 2."
|
| 31 |
self._stages = stages
|
| 32 |
self._phase_index = phase_index
|
| 33 |
|
|
@@ -36,13 +51,10 @@ class PipelineStateSimple:
|
|
| 36 |
|
| 37 |
@property
|
| 38 |
def stages(self) -> int:
|
| 39 |
-
# return 1 << self._log_stages
|
| 40 |
return self._stages
|
| 41 |
|
| 42 |
@property
|
| 43 |
def index(self) -> Int32:
|
| 44 |
-
# return self._phase_index & 0xFFFF
|
| 45 |
-
# return self._phase_index & ((1 << self._log_stages) - 1)
|
| 46 |
if const_expr(self._stages == 1):
|
| 47 |
return Int32(0)
|
| 48 |
else:
|
|
@@ -50,11 +62,8 @@ class PipelineStateSimple:
|
|
| 50 |
|
| 51 |
@property
|
| 52 |
def phase(self) -> Int32:
|
| 53 |
-
# return self._phase_index >> 16
|
| 54 |
# PTX docs say that the phase parity needs to be 0 or 1, so by right we need to
|
| 55 |
# take modulo 2. But in practice just passing the phase in without modulo works fine.
|
| 56 |
-
# return (self._phase_index >> self._log_stages) % 2
|
| 57 |
-
# return self._phase_index >> self._log_stages
|
| 58 |
if const_expr(self._stages == 1):
|
| 59 |
return self._phase_index
|
| 60 |
else:
|
|
@@ -66,21 +75,6 @@ class PipelineStateSimple:
|
|
| 66 |
else:
|
| 67 |
self._phase_index += 1
|
| 68 |
|
| 69 |
-
# def then_body(phase_index):
|
| 70 |
-
# # XOR the phase bit and set the index to 0
|
| 71 |
-
# return (phase_index & 0xFFFF0000) ^ (1 << 16)
|
| 72 |
-
|
| 73 |
-
# def else_body(phase_index):
|
| 74 |
-
# return phase_index
|
| 75 |
-
|
| 76 |
-
# self._phase_index = if_generate(
|
| 77 |
-
# (self._phase_index & 0xFFFF) == self.stages,
|
| 78 |
-
# then_body,
|
| 79 |
-
# else_body,
|
| 80 |
-
# [self._phase_index],
|
| 81 |
-
# [Int32],
|
| 82 |
-
# )
|
| 83 |
-
|
| 84 |
def __extract_mlir_values__(self):
|
| 85 |
phase_index = self._phase_index
|
| 86 |
return [phase_index.ir_value()]
|
|
@@ -94,7 +88,6 @@ def make_pipeline_state(type: PipelineUserType, stages: int):
|
|
| 94 |
Creates a pipeline state. Producers are assumed to start with an empty buffer and have a flipped phase bit of 1.
|
| 95 |
"""
|
| 96 |
if type is PipelineUserType.Producer:
|
| 97 |
-
# return PipelineStateSimple(stages, Int32(1 << 16))
|
| 98 |
return PipelineStateSimple(stages, Int32(stages))
|
| 99 |
elif type is PipelineUserType.Consumer:
|
| 100 |
return PipelineStateSimple(stages, Int32(0))
|
|
@@ -102,14 +95,73 @@ def make_pipeline_state(type: PipelineUserType, stages: int):
|
|
| 102 |
assert False, "Error: invalid PipelineUserType specified for make_pipeline_state."
|
| 103 |
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
@dataclass(frozen=True)
|
| 106 |
class NamedBarrier(NamedBarrierOg):
|
| 107 |
-
|
| 108 |
-
def create(*args, **kwargs):
|
| 109 |
-
obj = NamedBarrierOg.create(*args, **kwargs)
|
| 110 |
-
# Can't assign to __class__ directly since the dataclass is frozen
|
| 111 |
-
object.__setattr__(obj, "__class__", NamedBarrier)
|
| 112 |
-
return obj
|
| 113 |
|
| 114 |
@dsl_user_op
|
| 115 |
def arrive_w_index(self, index: Int32, *, loc=None, ip=None) -> None:
|
|
@@ -134,72 +186,121 @@ class NamedBarrier(NamedBarrierOg):
|
|
| 134 |
)
|
| 135 |
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
@dataclass(frozen=True)
|
| 138 |
-
class PipelineAsync(PipelineAsyncOg):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
@staticmethod
|
| 140 |
-
def create(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
obj = PipelineAsyncOg.create(*args, **kwargs)
|
| 142 |
-
# Can't assign to __class__ directly since the dataclass is frozen
|
| 143 |
-
# obj.__class__ = PipelineAsync
|
| 144 |
object.__setattr__(obj, "__class__", PipelineAsync)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
return obj
|
| 146 |
|
| 147 |
@dsl_user_op
|
| 148 |
-
def
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
if_generate(
|
| 158 |
-
try_acquire_token is None or try_acquire_token == 0,
|
| 159 |
-
lambda: self.sync_object_empty.wait(index, phase, loc=loc, ip=ip),
|
| 160 |
-
loc=loc,
|
| 161 |
-
ip=ip,
|
| 162 |
)
|
| 163 |
|
| 164 |
@dsl_user_op
|
| 165 |
-
def
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
*,
|
| 175 |
-
loc=None,
|
| 176 |
-
ip=None,
|
| 177 |
-
):
|
| 178 |
-
if_generate(
|
| 179 |
-
try_wait_token is None or try_wait_token == 0,
|
| 180 |
-
lambda: self.sync_object_full.wait(index, phase, loc=loc, ip=ip),
|
| 181 |
-
loc=loc,
|
| 182 |
-
ip=ip,
|
| 183 |
)
|
| 184 |
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
| 188 |
|
| 189 |
|
| 190 |
@dataclass(frozen=True)
|
| 191 |
-
class
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
"""
|
| 195 |
|
| 196 |
@staticmethod
|
| 197 |
-
def create(
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
return obj
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
@dsl_user_op
|
| 204 |
def producer_acquire(
|
| 205 |
self,
|
|
@@ -226,19 +327,15 @@ class PipelineTmaAsync(PipelineTmaAsyncOg):
|
|
| 226 |
self.sync_object_full.arrive_and_expect_tx(state.index, tx_count, loc=loc, ip=ip)
|
| 227 |
|
| 228 |
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
"""
|
| 234 |
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
# obj.__class__ = PipelineTmaUmma
|
| 240 |
-
object.__setattr__(obj, "__class__", PipelineTmaUmma)
|
| 241 |
-
return obj
|
| 242 |
|
| 243 |
@dsl_user_op
|
| 244 |
def producer_acquire(
|
|
@@ -279,162 +376,27 @@ class PipelineTmaUmma(PipelineTmaUmmaOg):
|
|
| 279 |
ip=ip,
|
| 280 |
)
|
| 281 |
|
| 282 |
-
@dsl_user_op
|
| 283 |
-
def producer_acquire_w_index_phase(
|
| 284 |
-
self,
|
| 285 |
-
index: Int32,
|
| 286 |
-
phase: Int32,
|
| 287 |
-
try_acquire_token: Optional[Boolean] = None,
|
| 288 |
-
*,
|
| 289 |
-
loc=None,
|
| 290 |
-
ip=None,
|
| 291 |
-
):
|
| 292 |
-
"""
|
| 293 |
-
TMA producer commit conditionally waits on buffer empty and sets the transaction barrier for leader threadblocks.
|
| 294 |
-
"""
|
| 295 |
-
if_generate(
|
| 296 |
-
try_acquire_token is None or try_acquire_token == 0,
|
| 297 |
-
lambda: self.sync_object_empty.wait(index, phase, loc=loc, ip=ip),
|
| 298 |
-
loc=loc,
|
| 299 |
-
ip=ip,
|
| 300 |
-
)
|
| 301 |
-
if_generate(
|
| 302 |
-
self.is_leader_cta,
|
| 303 |
-
lambda: self.sync_object_full.arrive(index, self.producer_mask, loc=loc, ip=ip),
|
| 304 |
-
loc=loc,
|
| 305 |
-
ip=ip,
|
| 306 |
-
)
|
| 307 |
|
| 308 |
-
|
| 309 |
-
def consumer_wait_w_index_phase(
|
| 310 |
-
self,
|
| 311 |
-
index: Int32,
|
| 312 |
-
phase: Int32,
|
| 313 |
-
try_wait_token: Optional[Boolean] = None,
|
| 314 |
-
*,
|
| 315 |
-
loc=None,
|
| 316 |
-
ip=None,
|
| 317 |
-
):
|
| 318 |
-
if_generate(
|
| 319 |
-
try_wait_token is None or try_wait_token == 0,
|
| 320 |
-
lambda: self.sync_object_full.wait(index, phase, loc=loc, ip=ip),
|
| 321 |
-
loc=loc,
|
| 322 |
-
ip=ip,
|
| 323 |
-
)
|
| 324 |
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
"""
|
| 328 |
-
UMMA consumer release buffer empty, cta_group needs to be provided.
|
| 329 |
-
"""
|
| 330 |
-
self.sync_object_empty.arrive(index, self.consumer_mask, self.cta_group, loc=loc, ip=ip)
|
| 331 |
|
| 332 |
|
| 333 |
@dataclass(frozen=True)
|
| 334 |
-
class PipelineUmmaAsync(PipelineUmmaAsyncOg):
|
| 335 |
-
|
| 336 |
-
def create(*args, **kwargs):
|
| 337 |
-
obj = PipelineUmmaAsyncOg.create(*args, **kwargs)
|
| 338 |
-
# Can't assign to __class__ directly since the dataclass is frozen
|
| 339 |
-
object.__setattr__(obj, "__class__", PipelineUmmaAsync)
|
| 340 |
-
return obj
|
| 341 |
|
| 342 |
-
@dsl_user_op
|
| 343 |
-
def producer_acquire_w_index_phase(
|
| 344 |
-
self,
|
| 345 |
-
index: Int32,
|
| 346 |
-
phase: Int32,
|
| 347 |
-
try_acquire_token: Optional[Boolean] = None,
|
| 348 |
-
*,
|
| 349 |
-
loc=None,
|
| 350 |
-
ip=None,
|
| 351 |
-
):
|
| 352 |
-
if_generate(
|
| 353 |
-
try_acquire_token is None or try_acquire_token == 0,
|
| 354 |
-
lambda: self.sync_object_empty.wait(index, phase, loc=loc, ip=ip),
|
| 355 |
-
loc=loc,
|
| 356 |
-
ip=ip,
|
| 357 |
-
)
|
| 358 |
|
| 359 |
-
|
| 360 |
-
def producer_commit_w_index(self, index: Int32, *, loc=None, ip=None):
|
| 361 |
-
"""
|
| 362 |
-
UMMA producer commit buffer full, cta_group needs to be provided.
|
| 363 |
-
"""
|
| 364 |
-
self.sync_object_full.arrive(index, self.producer_mask, self.cta_group, loc=loc, ip=ip)
|
| 365 |
|
| 366 |
-
@dsl_user_op
|
| 367 |
-
def consumer_wait_w_index_phase(
|
| 368 |
-
self,
|
| 369 |
-
index: Int32,
|
| 370 |
-
phase: Int32,
|
| 371 |
-
try_wait_token: Optional[Boolean] = None,
|
| 372 |
-
*,
|
| 373 |
-
loc=None,
|
| 374 |
-
ip=None,
|
| 375 |
-
):
|
| 376 |
-
if_generate(
|
| 377 |
-
try_wait_token is None or try_wait_token == 0,
|
| 378 |
-
lambda: self.sync_object_full.wait(index, phase, loc=loc, ip=ip),
|
| 379 |
-
loc=loc,
|
| 380 |
-
ip=ip,
|
| 381 |
-
)
|
| 382 |
|
| 383 |
-
|
| 384 |
-
def consumer_release_w_index(self, index: Int32, *, loc=None, ip=None):
|
| 385 |
-
self.sync_object_empty.arrive(index, self.consumer_mask, loc=loc, ip=ip)
|
| 386 |
|
| 387 |
|
| 388 |
@dataclass(frozen=True)
|
| 389 |
-
class PipelineAsyncUmma(PipelineAsyncUmmaOg):
|
| 390 |
-
|
| 391 |
-
def create(*args, **kwargs):
|
| 392 |
-
obj = PipelineAsyncUmmaOg.create(*args, **kwargs)
|
| 393 |
-
# Can't assign to __class__ directly since the dataclass is frozen
|
| 394 |
-
object.__setattr__(obj, "__class__", PipelineAsyncUmma)
|
| 395 |
-
return obj
|
| 396 |
|
| 397 |
-
@dsl_user_op
|
| 398 |
-
def producer_acquire_w_index_phase(
|
| 399 |
-
self,
|
| 400 |
-
index: Int32,
|
| 401 |
-
phase: Int32,
|
| 402 |
-
try_acquire_token: Optional[Boolean] = None,
|
| 403 |
-
*,
|
| 404 |
-
loc=None,
|
| 405 |
-
ip=None,
|
| 406 |
-
):
|
| 407 |
-
if_generate(
|
| 408 |
-
try_acquire_token is None or try_acquire_token == 0,
|
| 409 |
-
lambda: self.sync_object_empty.wait(index, phase, loc=loc, ip=ip),
|
| 410 |
-
loc=loc,
|
| 411 |
-
ip=ip,
|
| 412 |
-
)
|
| 413 |
-
|
| 414 |
-
@dsl_user_op
|
| 415 |
-
def producer_commit_w_index(self, index: Int32, *, loc=None, ip=None):
|
| 416 |
-
self.sync_object_full.arrive(index, self.producer_mask, loc=loc, ip=ip)
|
| 417 |
-
|
| 418 |
-
@dsl_user_op
|
| 419 |
-
def consumer_wait_w_index_phase(
|
| 420 |
-
self,
|
| 421 |
-
index: Int32,
|
| 422 |
-
phase: Int32,
|
| 423 |
-
try_wait_token: Optional[Boolean] = None,
|
| 424 |
-
*,
|
| 425 |
-
loc=None,
|
| 426 |
-
ip=None,
|
| 427 |
-
):
|
| 428 |
-
if_generate(
|
| 429 |
-
try_wait_token is None or try_wait_token == 0,
|
| 430 |
-
lambda: self.sync_object_full.wait(index, phase, loc=loc, ip=ip),
|
| 431 |
-
loc=loc,
|
| 432 |
-
ip=ip,
|
| 433 |
-
)
|
| 434 |
|
| 435 |
-
|
| 436 |
-
def consumer_release_w_index(self, index: Int32, *, loc=None, ip=None):
|
| 437 |
-
"""
|
| 438 |
-
UMMA consumer release buffer empty, cta_group needs to be provided.
|
| 439 |
-
"""
|
| 440 |
-
self.sync_object_empty.arrive(index, self.consumer_mask, self.cta_group, loc=loc, ip=ip)
|
|
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
|
|
|
| 3 |
from typing import Optional
|
| 4 |
from dataclasses import dataclass
|
| 5 |
|
|
|
|
| 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.
|
|
|
|
| 43 |
"""
|
| 44 |
|
| 45 |
def __init__(self, stages: int, phase_index: Int32):
|
|
|
|
|
|
|
|
|
|
| 46 |
self._stages = stages
|
| 47 |
self._phase_index = phase_index
|
| 48 |
|
|
|
|
| 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:
|
|
|
|
| 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:
|
|
|
|
| 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()]
|
|
|
|
| 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))
|
|
|
|
| 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:
|
|
|
|
| 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,
|
|
|
|
| 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(
|
|
|
|
| 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/prepare_scheduler.py
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# A reimplementation of https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_prepare_scheduler.cu
|
| 2 |
+
# from CUTLASS C++ to Cute-DSL.
|
| 3 |
+
|
| 4 |
+
from typing import Tuple, Optional, NamedTuple
|
| 5 |
+
import operator
|
| 6 |
+
import torch
|
| 7 |
+
import cuda.bindings.driver as cuda
|
| 8 |
+
import cutlass
|
| 9 |
+
import cutlass.cute as cute
|
| 10 |
+
from cutlass import Int32, const_expr, Float32
|
| 11 |
+
from cutlass.cute import FastDivmodDivisor
|
| 12 |
+
from . import utils as utils
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class SchedulerMetadataTensorsTorch(NamedTuple):
|
| 16 |
+
"""Class to store scheduler metadata for varlen"""
|
| 17 |
+
|
| 18 |
+
# tensors of shape (batch)
|
| 19 |
+
num_m_blocks_ptr: Optional[torch.Tensor]
|
| 20 |
+
num_splits_dynamic_ptr: Optional[torch.Tensor]
|
| 21 |
+
virtual_batch_idx_ptr: Optional[torch.Tensor]
|
| 22 |
+
num_nheads_in_l2_ptr: Optional[torch.Tensor]
|
| 23 |
+
# tensor of shape (1)
|
| 24 |
+
tile_count_semaphore: Optional[torch.Tensor]
|
| 25 |
+
# tensors of shape (batch + 1)
|
| 26 |
+
# cu_total_m_blocks[b+1] = sum_{i<=b} num_m_blocks[i]
|
| 27 |
+
# cu_total_splits_m_blocks[b+1] = sum_{i<=b} num_m_blocks[i] * num_splits_dynamic[i]
|
| 28 |
+
cu_total_m_blocks: Optional[torch.Tensor] = None
|
| 29 |
+
cu_total_splits_m_blocks: Optional[torch.Tensor] = None
|
| 30 |
+
blocks_to_batch_idx: Optional[torch.Tensor] = None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class FlashPrepareScheduler:
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
num_warps: int,
|
| 37 |
+
tile_m: int,
|
| 38 |
+
tile_n: int,
|
| 39 |
+
nheads: int,
|
| 40 |
+
nheads_kv: int,
|
| 41 |
+
headdim: int,
|
| 42 |
+
headdim_v: Optional[int] = None,
|
| 43 |
+
is_causal: bool = False,
|
| 44 |
+
packgqa: bool = False,
|
| 45 |
+
sort: bool = False,
|
| 46 |
+
zfill_padded_output: bool = False,
|
| 47 |
+
):
|
| 48 |
+
self.num_warps = num_warps
|
| 49 |
+
self.is_causal = is_causal
|
| 50 |
+
self.packgqa = packgqa
|
| 51 |
+
assert not sort, "LPT batch sort not yet implemented"
|
| 52 |
+
self.sort = sort
|
| 53 |
+
self.num_threads_per_warp = 32
|
| 54 |
+
self.tile_m = tile_m
|
| 55 |
+
self.tile_n = tile_n
|
| 56 |
+
self.d = headdim
|
| 57 |
+
self.dv = headdim_v if headdim_v is not None else headdim
|
| 58 |
+
self.k_num_batch_per_warp = 31
|
| 59 |
+
self.k_smem_size = 1
|
| 60 |
+
self.zfill_padded_output = zfill_padded_output
|
| 61 |
+
|
| 62 |
+
# for pack gqa, query heads per kv head is combined with seqlen_q
|
| 63 |
+
self.nheads_computed = nheads if not self.packgqa else nheads_kv
|
| 64 |
+
|
| 65 |
+
# L2 cache calculations
|
| 66 |
+
self.qhead_per_khead = nheads // nheads_kv
|
| 67 |
+
self.size_l2_divisor = (
|
| 68 |
+
1
|
| 69 |
+
if self.qhead_per_khead == 1
|
| 70 |
+
else (
|
| 71 |
+
2
|
| 72 |
+
if self.qhead_per_khead <= 2
|
| 73 |
+
else (4 if self.qhead_per_khead <= 4 else (8 if self.qhead_per_khead <= 8 else 16))
|
| 74 |
+
)
|
| 75 |
+
)
|
| 76 |
+
self.size_l2 = (32 * 1024 * 1024) // self.size_l2_divisor
|
| 77 |
+
element_size = 2
|
| 78 |
+
self.size_one_kvblock = self.tile_n * (self.d + self.dv) * element_size
|
| 79 |
+
self.max_kvblocks_in_l2 = (
|
| 80 |
+
self.size_l2 + self.size_one_kvblock - 1
|
| 81 |
+
) // self.size_one_kvblock
|
| 82 |
+
|
| 83 |
+
@staticmethod
|
| 84 |
+
def get_grid_shape(num_batch: int) -> Tuple[int, int, int]:
|
| 85 |
+
num_ctas = (num_batch + (31 * 32 - 1)) // (31 * 32)
|
| 86 |
+
return (num_ctas, 1, 1)
|
| 87 |
+
|
| 88 |
+
@cute.jit
|
| 89 |
+
def __call__(
|
| 90 |
+
self,
|
| 91 |
+
seqlen_q_static: int,
|
| 92 |
+
seqlen_k_static: int,
|
| 93 |
+
seqlen_k_new_static: int,
|
| 94 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 95 |
+
mCuSeqlensK: Optional[cute.Tensor],
|
| 96 |
+
mCuSeqlensKNew: Optional[cute.Tensor],
|
| 97 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 98 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 99 |
+
mLeftPadK: Optional[cute.Tensor],
|
| 100 |
+
num_batch: int,
|
| 101 |
+
num_splits_static: int,
|
| 102 |
+
tile_count_semaphore: Optional[cute.Tensor],
|
| 103 |
+
num_m_blocks_ptr: Optional[cute.Tensor],
|
| 104 |
+
num_splits_dynamic_ptr: Optional[cute.Tensor],
|
| 105 |
+
virtual_batch_idx_ptr: Optional[cute.Tensor],
|
| 106 |
+
num_nheads_in_l2_ptr: Optional[cute.Tensor],
|
| 107 |
+
n_blocks_per_split: Optional[int], # overrides heuristic
|
| 108 |
+
stream: cuda.CUstream,
|
| 109 |
+
):
|
| 110 |
+
tile_m_divmod = FastDivmodDivisor(self.tile_m)
|
| 111 |
+
tile_n_divmod = FastDivmodDivisor(self.tile_n)
|
| 112 |
+
|
| 113 |
+
@cute.struct
|
| 114 |
+
class SharedStorage:
|
| 115 |
+
total_blocks_smem: cute.struct.MemRange[Int32, self.k_smem_size]
|
| 116 |
+
|
| 117 |
+
self.shared_storage = SharedStorage
|
| 118 |
+
|
| 119 |
+
block = (32 * self.num_warps, 1, 1)
|
| 120 |
+
grid = self.get_grid_shape(num_batch)
|
| 121 |
+
|
| 122 |
+
hardware_info = cutlass.utils.HardwareInfo()
|
| 123 |
+
num_sm = hardware_info.get_device_multiprocessor_count()
|
| 124 |
+
|
| 125 |
+
self.kernel(
|
| 126 |
+
seqlen_q_static,
|
| 127 |
+
seqlen_k_static,
|
| 128 |
+
seqlen_k_new_static,
|
| 129 |
+
mCuSeqlensQ,
|
| 130 |
+
mCuSeqlensK,
|
| 131 |
+
mCuSeqlensKNew,
|
| 132 |
+
mSeqUsedQ,
|
| 133 |
+
mSeqUsedK,
|
| 134 |
+
mLeftPadK,
|
| 135 |
+
num_batch,
|
| 136 |
+
num_sm,
|
| 137 |
+
num_splits_static,
|
| 138 |
+
tile_m_divmod,
|
| 139 |
+
tile_n_divmod,
|
| 140 |
+
tile_count_semaphore,
|
| 141 |
+
num_m_blocks_ptr,
|
| 142 |
+
num_splits_dynamic_ptr,
|
| 143 |
+
virtual_batch_idx_ptr,
|
| 144 |
+
num_nheads_in_l2_ptr,
|
| 145 |
+
n_blocks_per_split,
|
| 146 |
+
).launch(
|
| 147 |
+
grid=grid,
|
| 148 |
+
block=block,
|
| 149 |
+
stream=stream,
|
| 150 |
+
smem=self.shared_storage.size_in_bytes(),
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
@cute.kernel
|
| 154 |
+
def kernel(
|
| 155 |
+
self,
|
| 156 |
+
seqlen_q_static: Int32,
|
| 157 |
+
seqlen_k_static: Int32,
|
| 158 |
+
seqlen_k_new_static: Int32,
|
| 159 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 160 |
+
mCuSeqlensK: Optional[cute.Tensor],
|
| 161 |
+
mCuSeqlensKNew: Optional[cute.Tensor],
|
| 162 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 163 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 164 |
+
mLeftPadK: Optional[cute.Tensor],
|
| 165 |
+
num_batch: Int32,
|
| 166 |
+
num_sm: Int32,
|
| 167 |
+
num_splits_static: Int32,
|
| 168 |
+
tile_m_divmod: FastDivmodDivisor,
|
| 169 |
+
tile_n_divmod: FastDivmodDivisor,
|
| 170 |
+
tile_count_semaphore: Optional[cute.Tensor],
|
| 171 |
+
num_m_blocks_ptr: Optional[cute.Tensor],
|
| 172 |
+
num_splits_dynamic_ptr: Optional[cute.Tensor],
|
| 173 |
+
virtual_batch_idx_ptr: Optional[cute.Tensor],
|
| 174 |
+
num_nheads_in_l2_ptr: Optional[cute.Tensor],
|
| 175 |
+
n_blocks_per_split: Optional[Int32],
|
| 176 |
+
):
|
| 177 |
+
bidx, _, _ = cute.arch.block_idx()
|
| 178 |
+
tidx, _, _ = cute.arch.thread_idx()
|
| 179 |
+
grid_dimx, _, _ = cute.arch.grid_dim()
|
| 180 |
+
warp_idx = cute.arch.warp_idx()
|
| 181 |
+
lane_idx = cute.arch.lane_idx()
|
| 182 |
+
|
| 183 |
+
smem = cutlass.utils.SmemAllocator()
|
| 184 |
+
storage = smem.allocate(self.shared_storage)
|
| 185 |
+
total_blocks_smem = storage.total_blocks_smem.get_tensor((1,))
|
| 186 |
+
|
| 187 |
+
if tidx == 0:
|
| 188 |
+
total_blocks_smem[0] = Int32(0)
|
| 189 |
+
cute.arch.sync_threads()
|
| 190 |
+
|
| 191 |
+
if const_expr(tile_count_semaphore is not None):
|
| 192 |
+
if tidx == 0:
|
| 193 |
+
tile_count_semaphore[0] = Int32(0)
|
| 194 |
+
|
| 195 |
+
batch_cta_idx_offset = bidx * self.k_num_batch_per_warp * self.num_warps
|
| 196 |
+
bidb_start = batch_cta_idx_offset + self.k_num_batch_per_warp * warp_idx
|
| 197 |
+
batch_idx = lane_idx + bidb_start
|
| 198 |
+
|
| 199 |
+
num_m_blocks, _ = self.get_num_m_blocks_and_seqlen(
|
| 200 |
+
lane_idx,
|
| 201 |
+
batch_idx,
|
| 202 |
+
mSeqUsedQ,
|
| 203 |
+
mCuSeqlensQ,
|
| 204 |
+
seqlen_q_static,
|
| 205 |
+
tile_m_divmod,
|
| 206 |
+
num_batch,
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
num_n_blocks = self.get_num_n_blocks(
|
| 210 |
+
lane_idx,
|
| 211 |
+
batch_idx,
|
| 212 |
+
mSeqUsedK,
|
| 213 |
+
mCuSeqlensK,
|
| 214 |
+
mCuSeqlensKNew,
|
| 215 |
+
seqlen_k_static,
|
| 216 |
+
seqlen_k_new_static,
|
| 217 |
+
mLeftPadK,
|
| 218 |
+
tile_n_divmod,
|
| 219 |
+
num_batch,
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
num_splits_dynamic = Int32(1)
|
| 223 |
+
if const_expr(n_blocks_per_split is not None):
|
| 224 |
+
num_splits_dynamic = cutlass.min(
|
| 225 |
+
cute.ceil_div(num_n_blocks, n_blocks_per_split), num_splits_static
|
| 226 |
+
)
|
| 227 |
+
if const_expr(self.zfill_padded_output):
|
| 228 |
+
num_splits_dynamic = cutlass.max(num_splits_dynamic, Int32(1))
|
| 229 |
+
if num_splits_dynamic > 0:
|
| 230 |
+
num_n_blocks = cute.ceil_div(num_n_blocks, num_splits_dynamic)
|
| 231 |
+
else:
|
| 232 |
+
if grid_dimx > 1 or num_splits_static == 1:
|
| 233 |
+
num_splits_dynamic = Int32(1)
|
| 234 |
+
else:
|
| 235 |
+
total_blocks = num_m_blocks * num_n_blocks
|
| 236 |
+
total_blocks = utils.warp_reduce(total_blocks, operator.add)
|
| 237 |
+
if lane_idx == 0:
|
| 238 |
+
utils.atomic_add_i32(total_blocks, total_blocks_smem.iterator)
|
| 239 |
+
|
| 240 |
+
cute.arch.sync_threads()
|
| 241 |
+
|
| 242 |
+
total_blocks = total_blocks_smem[0]
|
| 243 |
+
|
| 244 |
+
sm_margin = max(Float32(num_sm) / 128 + 0.001, 1.1) # e.g. 148/128 = 1.15625
|
| 245 |
+
blocks_per_sm = cutlass.max(
|
| 246 |
+
Int32(
|
| 247 |
+
(
|
| 248 |
+
Float32(total_blocks)
|
| 249 |
+
* sm_margin
|
| 250 |
+
* Float32(self.nheads_computed)
|
| 251 |
+
/ Float32(num_sm)
|
| 252 |
+
)
|
| 253 |
+
),
|
| 254 |
+
Int32(1),
|
| 255 |
+
)
|
| 256 |
+
# blocks_per_sm = cute.ceil_div(total_blocks * self.nheads_computed, num_sm)
|
| 257 |
+
num_splits_dynamic = cutlass.min(
|
| 258 |
+
cute.ceil_div(num_n_blocks, blocks_per_sm), num_splits_static
|
| 259 |
+
)
|
| 260 |
+
if const_expr(self.zfill_padded_output):
|
| 261 |
+
num_splits_dynamic = cutlass.max(num_splits_dynamic, Int32(1))
|
| 262 |
+
if num_splits_dynamic > 0:
|
| 263 |
+
num_n_blocks = cute.ceil_div(num_n_blocks, num_splits_dynamic)
|
| 264 |
+
|
| 265 |
+
if const_expr(self.sort):
|
| 266 |
+
# TODO: Implement sort logic
|
| 267 |
+
pass
|
| 268 |
+
|
| 269 |
+
if batch_idx < num_batch and lane_idx < self.k_num_batch_per_warp:
|
| 270 |
+
if const_expr(num_m_blocks_ptr is not None):
|
| 271 |
+
num_m_blocks_ptr[batch_idx] = num_m_blocks
|
| 272 |
+
if const_expr(num_splits_dynamic_ptr is not None):
|
| 273 |
+
num_splits_dynamic_ptr[batch_idx] = num_splits_dynamic
|
| 274 |
+
if const_expr(num_nheads_in_l2_ptr is not None):
|
| 275 |
+
nheads_in_l2 = self.get_num_nheads_in_l2(num_n_blocks)
|
| 276 |
+
num_nheads_in_l2_ptr[batch_idx] = nheads_in_l2
|
| 277 |
+
|
| 278 |
+
@cute.jit
|
| 279 |
+
def get_num_m_blocks_and_seqlen(
|
| 280 |
+
self,
|
| 281 |
+
lane_idx: Int32,
|
| 282 |
+
batch_idx: Int32,
|
| 283 |
+
mSeqUsedQ: Optional[cute.Tensor],
|
| 284 |
+
mCuSeqlensQ: Optional[cute.Tensor],
|
| 285 |
+
seqlen_q_static: Int32,
|
| 286 |
+
tile_m_divmod: FastDivmodDivisor,
|
| 287 |
+
num_batch: Int32,
|
| 288 |
+
):
|
| 289 |
+
seqlen = Int32(0)
|
| 290 |
+
if const_expr(mSeqUsedQ is not None):
|
| 291 |
+
seqlen = mSeqUsedQ[batch_idx] if batch_idx < num_batch else Int32(0)
|
| 292 |
+
elif const_expr(mCuSeqlensQ is not None):
|
| 293 |
+
# Since k_num_batch_per_warp = 31, lane 31 never processes batches
|
| 294 |
+
# So shuffle_down is safe: lane 30 gets lane 31's value (which is 0)
|
| 295 |
+
# Only access cu_seqlens if batch_idx is valid (0 to num_batch inclusive)
|
| 296 |
+
cur_cu_seqlen = Int32(0)
|
| 297 |
+
if batch_idx <= num_batch:
|
| 298 |
+
cur_cu_seqlen = mCuSeqlensQ[batch_idx]
|
| 299 |
+
next_cu_seqlen = cute.arch.shuffle_sync_down(cur_cu_seqlen, offset=1)
|
| 300 |
+
seqlen = next_cu_seqlen - cur_cu_seqlen
|
| 301 |
+
else:
|
| 302 |
+
seqlen = seqlen_q_static
|
| 303 |
+
|
| 304 |
+
seqlen_for_blocks = seqlen
|
| 305 |
+
if const_expr(self.packgqa):
|
| 306 |
+
seqlen_for_blocks = seqlen * self.qhead_per_khead
|
| 307 |
+
num_m_blocks = (
|
| 308 |
+
(seqlen_for_blocks + self.tile_m - 1) // tile_m_divmod
|
| 309 |
+
if batch_idx < num_batch and lane_idx < self.k_num_batch_per_warp
|
| 310 |
+
else Int32(0)
|
| 311 |
+
)
|
| 312 |
+
return (num_m_blocks, seqlen)
|
| 313 |
+
|
| 314 |
+
@cute.jit
|
| 315 |
+
def get_num_n_blocks(
|
| 316 |
+
self,
|
| 317 |
+
lane_idx: Int32,
|
| 318 |
+
batch_idx: Int32,
|
| 319 |
+
mSeqUsedK: Optional[cute.Tensor],
|
| 320 |
+
mCuSeqlensK: Optional[cute.Tensor],
|
| 321 |
+
mCuSeqlensKNew: Optional[cute.Tensor],
|
| 322 |
+
seqlen_k_static: Int32,
|
| 323 |
+
seqlen_k_new_static: Int32,
|
| 324 |
+
mLeftPadK: Optional[cute.Tensor],
|
| 325 |
+
tile_n_divmod: FastDivmodDivisor,
|
| 326 |
+
num_batch: Int32,
|
| 327 |
+
):
|
| 328 |
+
leftpad_k = (
|
| 329 |
+
mLeftPadK[batch_idx]
|
| 330 |
+
if const_expr(mLeftPadK is not None) and batch_idx < num_batch
|
| 331 |
+
else Int32(0)
|
| 332 |
+
)
|
| 333 |
+
seqlen = Int32(0)
|
| 334 |
+
if const_expr(mSeqUsedK is not None):
|
| 335 |
+
seqlen = mSeqUsedK[batch_idx] if batch_idx < num_batch else Int32(0)
|
| 336 |
+
elif const_expr(mCuSeqlensK is not None):
|
| 337 |
+
# Since k_num_batch_per_warp = 31, lane 31 never processes batches
|
| 338 |
+
# So shuffle_down is safe: lane 30 gets lane 31's value (which is 0)
|
| 339 |
+
# Only access cu_seqlens if batch_idx is valid (0 to num_batch inclusive)
|
| 340 |
+
cur_cu_seqlen = Int32(0)
|
| 341 |
+
if batch_idx <= num_batch:
|
| 342 |
+
cur_cu_seqlen = mCuSeqlensK[batch_idx]
|
| 343 |
+
next_cu_seqlen = cute.arch.shuffle_sync_down(cur_cu_seqlen, offset=1)
|
| 344 |
+
seqlen = next_cu_seqlen - cur_cu_seqlen
|
| 345 |
+
else:
|
| 346 |
+
seqlen = seqlen_k_static
|
| 347 |
+
|
| 348 |
+
seqlen_new = Int32(0)
|
| 349 |
+
if const_expr(mCuSeqlensKNew is not None):
|
| 350 |
+
# Since k_num_batch_per_warp = 31, lane 31 never processes batches
|
| 351 |
+
# So shuffle_down is safe: lane 30 gets lane 31's value (which is 0)
|
| 352 |
+
# Only access cu_seqlens if batch_idx is valid (0 to num_batch inclusive)
|
| 353 |
+
cur_cu_seqlen_new = Int32(0)
|
| 354 |
+
if batch_idx <= num_batch:
|
| 355 |
+
cur_cu_seqlen_new = mCuSeqlensKNew[batch_idx]
|
| 356 |
+
next_cu_seqlen_new = cute.arch.shuffle_sync_down(cur_cu_seqlen_new, offset=1)
|
| 357 |
+
seqlen_new = next_cu_seqlen_new - cur_cu_seqlen_new
|
| 358 |
+
else:
|
| 359 |
+
seqlen_new = seqlen_k_new_static
|
| 360 |
+
seqlen = seqlen - leftpad_k + seqlen_new
|
| 361 |
+
return (
|
| 362 |
+
(seqlen + self.tile_n - 1) // tile_n_divmod
|
| 363 |
+
if batch_idx < num_batch and lane_idx < self.k_num_batch_per_warp
|
| 364 |
+
else Int32(0)
|
| 365 |
+
)
|
| 366 |
+
|
| 367 |
+
@cute.jit
|
| 368 |
+
def get_num_nheads_in_l2(
|
| 369 |
+
self,
|
| 370 |
+
num_n_blocks: Int32,
|
| 371 |
+
):
|
| 372 |
+
max_kvblocks_in_l2 = self.max_kvblocks_in_l2
|
| 373 |
+
qhead_per_khead = self.qhead_per_khead
|
| 374 |
+
nheads_in_l2 = Int32(16)
|
| 375 |
+
if num_n_blocks * Int32(16) <= max_kvblocks_in_l2:
|
| 376 |
+
nheads_in_l2 = Int32(16)
|
| 377 |
+
elif num_n_blocks * Int32(8) <= max_kvblocks_in_l2:
|
| 378 |
+
nheads_in_l2 = Int32(8)
|
| 379 |
+
elif num_n_blocks * Int32(4) <= max_kvblocks_in_l2:
|
| 380 |
+
nheads_in_l2 = Int32(4)
|
| 381 |
+
elif num_n_blocks * Int32(2) <= max_kvblocks_in_l2:
|
| 382 |
+
nheads_in_l2 = Int32(2)
|
| 383 |
+
else:
|
| 384 |
+
nheads_in_l2 = Int32(1)
|
| 385 |
+
if const_expr(not self.packgqa):
|
| 386 |
+
nheads_in_l2 *= qhead_per_khead
|
| 387 |
+
return cutlass.min(nheads_in_l2, self.nheads_computed)
|
build/torch-cuda/quack/__init__.py
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Vendored subset of quack (https://github.com/Dao-AILab/quack).
|
| 2 |
+
|
| 3 |
+
Importing `.dsl` installs quack's CuTe tensor indexing patch. It must happen
|
| 4 |
+
here, exactly as upstream's `quack/__init__.py` does it, because
|
| 5 |
+
`quack.copy_utils` indexes tensors with `...` and CuTe does not support that
|
| 6 |
+
without the patch.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from . import dsl # noqa: F401
|
build/torch-cuda/quack/activation.py
DELETED
|
@@ -1,568 +0,0 @@
|
|
| 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/copy_utils.py
CHANGED
|
@@ -1,22 +1,118 @@
|
|
| 1 |
-
# Copyright (c) 2025,
|
| 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.
|
|
|
|
| 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 |
|
| 19 |
Sm100MmaPeerBitMask = 0xFEFFFFFF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
|
| 22 |
@dsl_user_op
|
|
@@ -41,6 +137,30 @@ def cvt_copy(
|
|
| 41 |
cute.copy(tiled_copy, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 42 |
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
@dsl_user_op
|
| 45 |
def load_s2r(src: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
| 46 |
dst = cute.make_rmem_tensor_like(src, src.element_type, loc=loc, ip=ip)
|
|
@@ -48,6 +168,13 @@ def load_s2r(src: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
|
| 48 |
return dst
|
| 49 |
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
@dsl_user_op
|
| 52 |
def load_s2r_retile(
|
| 53 |
tiled_copy: cute.TiledCopy,
|
|
@@ -68,11 +195,19 @@ def load_s2r_retile(
|
|
| 68 |
|
| 69 |
@dsl_user_op
|
| 70 |
def load_t2r(
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
) -> cute.Tensor:
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
return dst
|
| 77 |
|
| 78 |
|
|
@@ -294,18 +429,14 @@ def as_position_independent_swizzle_tensor(tensor: cute.Tensor) -> cute.Tensor:
|
|
| 294 |
return cute.make_tensor(cute.recast_ptr(tensor.iterator, dtype=tensor.element_type), new_layout)
|
| 295 |
|
| 296 |
|
| 297 |
-
def partition_D_position_independent(
|
| 298 |
-
thr_copy: cute.core.ThrCopy, tensor: cute.Tensor
|
| 299 |
-
) -> cute.Tensor:
|
| 300 |
return cute.make_tensor(
|
| 301 |
swizzle_ptr(thr_copy.partition_D(tensor).iterator),
|
| 302 |
thr_copy.partition_D(as_position_independent_swizzle_tensor(tensor)).layout,
|
| 303 |
)
|
| 304 |
|
| 305 |
|
| 306 |
-
def partition_S_position_independent(
|
| 307 |
-
thr_copy: cute.core.ThrCopy, tensor: cute.Tensor
|
| 308 |
-
) -> cute.Tensor:
|
| 309 |
return cute.make_tensor(
|
| 310 |
swizzle_ptr(thr_copy.partition_S(tensor).iterator),
|
| 311 |
thr_copy.partition_S(as_position_independent_swizzle_tensor(tensor)).layout,
|
|
@@ -346,12 +477,12 @@ def sm90_get_smem_load_op(
|
|
| 346 |
|
| 347 |
|
| 348 |
def get_smem_store_atom(
|
| 349 |
-
arch: cutlass.Constexpr[int],
|
| 350 |
element_type: Type[cute.Numeric],
|
| 351 |
transpose: bool = False,
|
| 352 |
major_mode_size: Optional[int] = None,
|
| 353 |
) -> cute.CopyAtom:
|
| 354 |
-
|
|
|
|
| 355 |
return cute.make_copy_atom(
|
| 356 |
cute.nvgpu.CopyUniversalOp(),
|
| 357 |
element_type,
|
|
@@ -370,12 +501,12 @@ def get_smem_store_atom(
|
|
| 370 |
|
| 371 |
|
| 372 |
def get_smem_load_atom(
|
| 373 |
-
arch: cutlass.Constexpr[int],
|
| 374 |
element_type: Type[cute.Numeric],
|
| 375 |
transpose: bool = False,
|
| 376 |
major_mode_size: Optional[int] = None,
|
| 377 |
) -> cute.CopyAtom:
|
| 378 |
-
|
|
|
|
| 379 |
return cute.make_copy_atom(
|
| 380 |
cute.nvgpu.CopyUniversalOp(),
|
| 381 |
element_type,
|
|
@@ -394,26 +525,35 @@ def get_smem_load_atom(
|
|
| 394 |
|
| 395 |
|
| 396 |
def get_smem_store_C(
|
| 397 |
-
tiled_mma: cute.TiledMma,
|
| 398 |
sC: cute.Tensor,
|
| 399 |
tidx: Int32,
|
| 400 |
-
arch: int,
|
| 401 |
transpose: bool = False,
|
| 402 |
position_independent=False,
|
| 403 |
major_mode_size: Optional[int] = None,
|
| 404 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 405 |
dtype = sC.element_type
|
| 406 |
-
|
| 407 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 408 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 409 |
if const_expr(not position_independent):
|
| 410 |
tRS_sC = thr_copy.partition_D(sC)
|
| 411 |
else:
|
| 412 |
tRS_sC = partition_D_position_independent(thr_copy, sC)
|
| 413 |
|
| 414 |
-
def copy_fn(src: cute.Tensor, dst_idx: Optional[Int32] = None, **new_kwargs):
|
| 415 |
-
dst_tensor = tRS_sC if const_expr(dst_idx is None) else tRS_sC[
|
| 416 |
cvt_copy(tiled_copy, src, dst_tensor, retile=True, **new_kwargs)
|
|
|
|
|
|
|
| 417 |
|
| 418 |
return copy_fn, thr_copy, tRS_sC
|
| 419 |
|
|
@@ -422,19 +562,18 @@ def get_smem_load_C(
|
|
| 422 |
tiled_mma: cute.TiledMma,
|
| 423 |
sC: cute.Tensor,
|
| 424 |
tidx: Int32,
|
| 425 |
-
arch: int,
|
| 426 |
transpose: bool = False,
|
| 427 |
position_independent=False,
|
| 428 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 429 |
dtype = sC.element_type
|
| 430 |
-
copy_atom = get_smem_load_atom(
|
| 431 |
tiled_copy = cute.make_tiled_copy_C(copy_atom, tiled_mma)
|
| 432 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 433 |
if const_expr(not position_independent):
|
| 434 |
tSR_sC = thr_copy.partition_S(sC)
|
| 435 |
else:
|
| 436 |
tSR_sC = partition_S_position_independent(thr_copy, sC)
|
| 437 |
-
copy_atom_RS = get_smem_store_atom(
|
| 438 |
thr_copy_RS = cute.make_tiled_copy_C(copy_atom_RS, tiled_mma).get_slice(tidx)
|
| 439 |
tRS_shape = thr_copy_RS.partition_S(cute.make_identity_tensor(sC.shape[:2])).shape
|
| 440 |
|
|
@@ -448,10 +587,18 @@ def get_smem_load_C(
|
|
| 448 |
def epilog_smem_copy_atom(
|
| 449 |
tiled_mma: cute.TiledMma, epi_tile: cute.Shape, transpose: bool = False
|
| 450 |
) -> cute.TiledCopy:
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
tiled_copy_C_atom = cute.make_tiled_copy_C_atom(copy_atom_C, tiled_mma)
|
| 456 |
return tiled_copy_C_atom
|
| 457 |
|
|
@@ -461,13 +608,12 @@ def get_smem_store_epi(
|
|
| 461 |
epi_tile: cute.Shape,
|
| 462 |
sC: Optional[cute.Tensor],
|
| 463 |
tidx: Int32,
|
| 464 |
-
arch: int,
|
| 465 |
transpose: bool = False,
|
| 466 |
position_independent=False,
|
| 467 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 468 |
dtype = sC.element_type if const_expr(sC is not None) else cutlass.Float16
|
|
|
|
| 469 |
tiled_copy_C_atom = epilog_smem_copy_atom(tiled_mma, epi_tile)
|
| 470 |
-
copy_atom = get_smem_store_atom(arch, dtype, transpose)
|
| 471 |
tiled_copy = cute.make_tiled_copy_S(copy_atom, tiled_copy_C_atom)
|
| 472 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 473 |
tRS_sC = None
|
|
@@ -488,11 +634,11 @@ def get_smem_store_epi(
|
|
| 488 |
|
| 489 |
|
| 490 |
def get_smem_store_A(
|
| 491 |
-
tiled_mma: cute.TiledMma, sA: cute.Tensor, tidx: Int32,
|
| 492 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 493 |
dtype = sA.element_type
|
| 494 |
-
transpose = tiled_mma.op.a_major_mode ==
|
| 495 |
-
copy_atom = get_smem_store_atom(
|
| 496 |
tiled_copy = cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 497 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 498 |
if const_expr(not position_independent):
|
|
@@ -510,13 +656,12 @@ def get_smem_load_A(
|
|
| 510 |
tiled_mma: cute.TiledMma,
|
| 511 |
sA: cute.Tensor,
|
| 512 |
tidx: Int32,
|
| 513 |
-
arch: int,
|
| 514 |
with_dst_tensor: bool = False,
|
| 515 |
position_independent=False,
|
| 516 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 517 |
dtype = sA.element_type
|
| 518 |
-
transpose = tiled_mma.op.a_major_mode ==
|
| 519 |
-
copy_atom = get_smem_load_atom(
|
| 520 |
tiled_copy = cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 521 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 522 |
if const_expr(not position_independent):
|
|
@@ -545,19 +690,16 @@ def cpasync_reduce_bulk_add_f32(
|
|
| 545 |
loc=None,
|
| 546 |
ip=None,
|
| 547 |
):
|
| 548 |
-
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip)
|
| 549 |
# cache_hint = cutlass.Int64(0x14F0000000000000) # EVICT_LAST
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
[gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes)
|
| 553 |
-
"cp.reduce.async.bulk.global.shared::cta.bulk_group.add.f32 [$0], [$1], $2;",
|
| 554 |
-
"l,r,r",
|
| 555 |
# [gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value(), cache_hint.ir_value()],
|
| 556 |
# "cp.reduce.async.bulk.global.shared::cta.bulk_group.L2::cache_hint.add.f32 [$0], [$1], $2, $3;",
|
| 557 |
# "l,r,r,l",
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 561 |
)
|
| 562 |
|
| 563 |
|
|
@@ -648,33 +790,34 @@ def tma_gather4_load(
|
|
| 648 |
"""
|
| 649 |
if len(row_indices) != 4:
|
| 650 |
raise ValueError(f"gather4 requires exactly 4 row indices, got {len(row_indices)}")
|
| 651 |
-
col_val = Int32(col_idx)
|
| 652 |
-
row_vals = [Int32(row_idx)
|
| 653 |
# Convert pointers to integer addresses
|
| 654 |
-
desc_addr = tma_desc_ptr.toint(loc=loc, ip=ip)
|
| 655 |
-
dst_addr = dst_smem_ptr.toint(loc=loc, ip=ip)
|
| 656 |
mbar_addr = mbarrier_ptr.toint(loc=loc, ip=ip)
|
| 657 |
if num_cta > 1:
|
| 658 |
# Executed by both CTAs. Set peer bit to 0 so that the
|
| 659 |
# transaction bytes will update CTA0's barrier.
|
| 660 |
mbar_addr = mbar_addr & Sm100MmaPeerBitMask
|
| 661 |
-
mbar_addr =
|
| 662 |
# Handle multicast_mask - may already be ir.Value or Python int
|
| 663 |
multicast_mask_val = None
|
| 664 |
if multicast_mask is not None:
|
| 665 |
-
multicast_mask_val = Int16(multicast_mask)
|
| 666 |
assert multicast_mask_val is None, "multicast is not supported yet"
|
| 667 |
# Emit inline PTX for TMA gather4
|
| 668 |
# PTX: cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4.mbarrier::complete_tx::bytes
|
| 669 |
# [dstMem], [tensorMap, {col, row0, row1, row2, row3}], [smem_bar];
|
| 670 |
ptx = (
|
| 671 |
-
|
| 672 |
-
"
|
|
|
|
| 673 |
)
|
| 674 |
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
[
|
| 678 |
dst_addr,
|
| 679 |
desc_addr,
|
| 680 |
col_val,
|
|
@@ -684,11 +827,6 @@ def tma_gather4_load(
|
|
| 684 |
row_vals[3],
|
| 685 |
mbar_addr,
|
| 686 |
],
|
| 687 |
-
ptx,
|
| 688 |
-
"r,l,r,r,r,r,r,r", # constraints: register, long, 6x register
|
| 689 |
-
has_side_effects=True,
|
| 690 |
-
is_align_stack=False,
|
| 691 |
-
asm_dialect=llvm.AsmDialect.AD_ATT,
|
| 692 |
loc=loc,
|
| 693 |
ip=ip,
|
| 694 |
)
|
|
@@ -775,6 +913,161 @@ def tma_get_copy_fn(
|
|
| 775 |
return (copy_tma if const_expr(not single_stage) else copy_tma_single_stage), s, g
|
| 776 |
|
| 777 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
def tma_producer_copy_fn(copy: Callable, pipeline: cutlass.pipeline.PipelineAsync):
|
| 779 |
def copy_fn(src_idx, producer_state: cutlass.pipeline.PipelineState, **new_kwargs):
|
| 780 |
copy(
|
|
@@ -787,6 +1080,18 @@ def tma_producer_copy_fn(copy: Callable, pipeline: cutlass.pipeline.PipelineAsyn
|
|
| 787 |
return copy_fn
|
| 788 |
|
| 789 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 790 |
@cute.jit
|
| 791 |
def gather_m_get_copy_fn(
|
| 792 |
thr_copy_A: cute.ThrCopy,
|
|
@@ -796,17 +1101,17 @@ def gather_m_get_copy_fn(
|
|
| 796 |
limit_m: Int32,
|
| 797 |
limit_k: Int32,
|
| 798 |
) -> Callable:
|
| 799 |
-
|
| 800 |
-
tAsA =
|
| 801 |
# k-major
|
| 802 |
assert tAsA.shape[2] == 1
|
| 803 |
tAsA = cute.group_modes(cute.slice_(tAsA, (None, None, 0, None)), 0, 2)
|
| 804 |
|
| 805 |
-
is_even_m_smem =
|
| 806 |
if const_expr(not is_even_m_smem):
|
| 807 |
-
limit_m = min(limit_m,
|
| 808 |
elems_per_load = cute.size(tAsA.shape[0][0])
|
| 809 |
-
cA = cute.make_identity_tensor(
|
| 810 |
tAcA = thr_copy_A.partition_S(cA)
|
| 811 |
t0AcA = thr_copy_A.get_slice(0).partition_S(cA)
|
| 812 |
# Instead of comparing tAcA to limit_m, we instead compare t0AcA to limit_m - tAcA[0][0]
|
|
@@ -828,13 +1133,13 @@ def gather_m_get_copy_fn(
|
|
| 828 |
else:
|
| 829 |
m_idx[m] = 0 # It's ok to load row 0 in the case of OOB
|
| 830 |
|
| 831 |
-
mA_k = cute.logical_divide(mA, (None,
|
| 832 |
|
| 833 |
-
def copy_fn(src_idx, dst_idx, pred: bool = False):
|
| 834 |
tApA_k = None
|
| 835 |
if const_expr(pred):
|
| 836 |
tApA_k = cute.make_rmem_tensor(cols_per_thread, Boolean)
|
| 837 |
-
limit_k_cur = limit_k - src_idx *
|
| 838 |
for k in cutlass.range(cols_per_thread, unroll_full=True):
|
| 839 |
tApA_k[k] = t0AcA[0, 0, k][1] < limit_k_cur
|
| 840 |
mA_cur = mA_k[None, (None, src_idx)]
|
|
@@ -997,11 +1302,162 @@ def gather_m_get_tma_copy_fn(
|
|
| 997 |
tma_gather4_load_fn = partial(tma_gather4_load, tma_desc_ptr, num_cta=cta_group)
|
| 998 |
|
| 999 |
def copy_fn(src_idx, dst_idx, tma_bar_ptr: cute.Pointer):
|
|
|
|
| 1000 |
col_idx = tile_K * src_idx
|
| 1001 |
for m in cutlass.range(cute.size(tSR_rAIdx, mode=[1]), unroll_full=True):
|
| 1002 |
row_indices = [tSR_rAIdx[v, m] for v in range(4)]
|
| 1003 |
-
smem_ptr =
|
| 1004 |
with cute.arch.elect_one():
|
| 1005 |
tma_gather4_load_fn(smem_ptr, tma_bar_ptr, col_idx, row_indices)
|
| 1006 |
|
| 1007 |
return copy_fn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025-2026, QuACK team.
|
| 2 |
|
| 3 |
+
from typing import Any, Optional, Type, Tuple, Callable, Sequence
|
| 4 |
from functools import partial
|
| 5 |
|
| 6 |
import cutlass
|
| 7 |
import cutlass.cute as cute
|
| 8 |
+
import cutlass.utils.blackwell_helpers as sm100_utils
|
| 9 |
|
| 10 |
from cutlass import Int32, Int16, Boolean, const_expr
|
| 11 |
+
from cutlass.base_dsl.arch import Arch
|
| 12 |
+
from cutlass.cute.nvgpu import cpasync, tcgen05, warp
|
| 13 |
from cutlass.cute.nvgpu.tcgen05.mma import CtaGroup # noqa
|
| 14 |
from cutlass.cutlass_dsl import dsl_user_op
|
| 15 |
+
from cutlass.utils import LayoutEnum
|
| 16 |
import cutlass.pipeline
|
|
|
|
| 17 |
from cutlass._mlir import ir
|
| 18 |
from cutlass._mlir.dialects import cute_nvgpu as _cute_nvgpu_ir
|
| 19 |
|
| 20 |
+
from . import layout_utils
|
| 21 |
+
from .utils import make_vector
|
| 22 |
+
|
| 23 |
|
| 24 |
Sm100MmaPeerBitMask = 0xFEFFFFFF
|
| 25 |
+
_TCGEN05_TMEM_OPS = (
|
| 26 |
+
tcgen05.Ld16x128bOp,
|
| 27 |
+
tcgen05.Ld16x256bOp,
|
| 28 |
+
tcgen05.Ld16x32bx2Op,
|
| 29 |
+
tcgen05.Ld16x64bOp,
|
| 30 |
+
tcgen05.Ld32x32bOp,
|
| 31 |
+
tcgen05.LdRed16x32bx2Op,
|
| 32 |
+
tcgen05.LdRed32x32bOp,
|
| 33 |
+
tcgen05.St16x128bOp,
|
| 34 |
+
tcgen05.St16x256bOp,
|
| 35 |
+
tcgen05.St16x32bx2Op,
|
| 36 |
+
tcgen05.St16x64bOp,
|
| 37 |
+
tcgen05.St32x32bOp,
|
| 38 |
+
)
|
| 39 |
+
_TCGEN05_TMEM_STORE_OPS = (
|
| 40 |
+
tcgen05.St16x128bOp,
|
| 41 |
+
tcgen05.St16x256bOp,
|
| 42 |
+
tcgen05.St16x32bx2Op,
|
| 43 |
+
tcgen05.St16x64bOp,
|
| 44 |
+
tcgen05.St32x32bOp,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def tmem_store_atom_from_load_atom(
|
| 49 |
+
copy_atom_t2r: Any,
|
| 50 |
+
src_dtype: Type[cutlass.Numeric],
|
| 51 |
+
dst_dtype: Type[cutlass.Numeric],
|
| 52 |
+
) -> cute.CopyAtom:
|
| 53 |
+
"""Return the matching tcgen05 R2T store atom for a selected T2R load atom.
|
| 54 |
+
|
| 55 |
+
`src_dtype` is the register fragment dtype loaded by T2R; `dst_dtype` is
|
| 56 |
+
the TMEM element dtype to store. Ratio 1 uses CUTLASS's operation-family
|
| 57 |
+
mapping directly. Ratio 2 is intentionally narrow: we allow the current
|
| 58 |
+
Ld32x32b path by halving repeat, and the widest 16dp path by halving the
|
| 59 |
+
vector width. Narrower 16dp cross-family mappings are not mirrored by
|
| 60 |
+
CUTLASS's same-family helper, so they assert until validated.
|
| 61 |
+
|
| 62 |
+
C++ CuTe's operation-family mapping is `cute::TMEM::tmem_load_to_store`:
|
| 63 |
+
https://github.com/NVIDIA/cutlass/blob/main/include/cute/atom/copy_traits_sm100.hpp#L3274
|
| 64 |
+
"""
|
| 65 |
+
load_op = copy_atom_t2r.op if const_expr(hasattr(copy_atom_t2r, "op")) else copy_atom_t2r
|
| 66 |
+
if const_expr(hasattr(load_op, "op")):
|
| 67 |
+
load_op = load_op.op
|
| 68 |
+
assert src_dtype.width >= dst_dtype.width, "TMEM R2T helper only supports narrowing stores"
|
| 69 |
+
assert src_dtype.width % dst_dtype.width == 0, "TMEM source/destination widths must divide"
|
| 70 |
+
ratio = src_dtype.width // dst_dtype.width
|
| 71 |
+
assert ratio in (1, 2), "TMEM R2T helper only supports src/dst width ratio 1 or 2"
|
| 72 |
+
repeat = load_op.repeat
|
| 73 |
+
unpack = tcgen05.Unpack.NONE
|
| 74 |
+
if const_expr(getattr(load_op, "pack", None) == tcgen05.Pack.PACK_16b_IN_32b):
|
| 75 |
+
unpack = tcgen05.Unpack.UNPACK_32b_IN_16b
|
| 76 |
+
if const_expr(isinstance(load_op, tcgen05.Ld16x64bOp)):
|
| 77 |
+
assert ratio == 1, "No validated ratio-2 store mapping for Ld16x64bOp"
|
| 78 |
+
store_op = tcgen05.St16x64bOp(repeat, unpack)
|
| 79 |
+
elif const_expr(isinstance(load_op, tcgen05.Ld16x128bOp)):
|
| 80 |
+
assert ratio == 1, "No validated ratio-2 store mapping for Ld16x128bOp"
|
| 81 |
+
store_op = tcgen05.St16x128bOp(repeat, unpack)
|
| 82 |
+
elif const_expr(isinstance(load_op, tcgen05.Ld16x256bOp)):
|
| 83 |
+
store_op = (
|
| 84 |
+
tcgen05.St16x256bOp(repeat, unpack)
|
| 85 |
+
if const_expr(ratio == 1)
|
| 86 |
+
else tcgen05.St16x128bOp(repeat, unpack)
|
| 87 |
+
)
|
| 88 |
+
elif const_expr(isinstance(load_op, tcgen05.Ld16x32bx2Op)):
|
| 89 |
+
assert ratio == 1, "No validated ratio-2 store mapping for Ld16x32bx2Op"
|
| 90 |
+
store_op = tcgen05.St16x32bx2Op(repeat, unpack)
|
| 91 |
+
elif const_expr(isinstance(load_op, tcgen05.Ld32x32bOp)):
|
| 92 |
+
if const_expr(ratio == 2):
|
| 93 |
+
assert repeat.value % 2 == 0, "Ld32x32b ratio-2 store needs even repeat"
|
| 94 |
+
repeat = tcgen05.Repetition(repeat.value // 2)
|
| 95 |
+
store_op = tcgen05.St32x32bOp(repeat, unpack)
|
| 96 |
+
else:
|
| 97 |
+
raise TypeError(f"Unsupported TMEM load op for store conversion: {type(load_op)}")
|
| 98 |
+
return cute.make_copy_atom(store_op, dst_dtype)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _tmem_copy_reg_tv_layout(tiled_copy: cute.TiledCopy):
|
| 102 |
+
"""Return the register-side TV layout for a tcgen05 tmem copy."""
|
| 103 |
+
op = tiled_copy.op
|
| 104 |
+
if const_expr(hasattr(op, "op")):
|
| 105 |
+
op = op.op
|
| 106 |
+
if const_expr(isinstance(op, _TCGEN05_TMEM_OPS)):
|
| 107 |
+
# TMEM stores read from registers; all other TMEM copy ops here write
|
| 108 |
+
# registers, including LdRed* reductions that upstream is_tmem_load
|
| 109 |
+
# intentionally does not classify as plain loads.
|
| 110 |
+
return (
|
| 111 |
+
tiled_copy.layout_src_tv_tiled
|
| 112 |
+
if const_expr(isinstance(op, _TCGEN05_TMEM_STORE_OPS))
|
| 113 |
+
else tiled_copy.layout_dst_tv_tiled
|
| 114 |
+
)
|
| 115 |
+
raise TypeError(f"Cannot infer tmem copy direction from tiled_copy.op={op}")
|
| 116 |
|
| 117 |
|
| 118 |
@dsl_user_op
|
|
|
|
| 137 |
cute.copy(tiled_copy, src, dst, pred=pred, loc=loc, ip=ip, **kwargs)
|
| 138 |
|
| 139 |
|
| 140 |
+
@dsl_user_op
|
| 141 |
+
def sr_cvt_copy(
|
| 142 |
+
tiled_copy: cute.TiledCopy,
|
| 143 |
+
src: cute.Tensor,
|
| 144 |
+
dst: cute.Tensor,
|
| 145 |
+
seed: Int32,
|
| 146 |
+
tidx: Int32,
|
| 147 |
+
*,
|
| 148 |
+
loc=None,
|
| 149 |
+
ip=None,
|
| 150 |
+
) -> None:
|
| 151 |
+
"""Like cvt_copy but uses stochastic rounding for FP32 -> BF16 conversion."""
|
| 152 |
+
assert isinstance(src.iterator, cute.Pointer) and src.memspace == cute.AddressSpace.rmem
|
| 153 |
+
from .rounding import convert_f32_to_bf16_sr
|
| 154 |
+
from cutlass.cute.tensor import TensorSSA
|
| 155 |
+
|
| 156 |
+
src_cvt = cute.make_rmem_tensor_like(src, dst.element_type)
|
| 157 |
+
src_vec = src.load()
|
| 158 |
+
raw_vec = convert_f32_to_bf16_sr(src_vec, seed, tidx, loc=loc, ip=ip)
|
| 159 |
+
src_cvt.store(TensorSSA(raw_vec, src_vec.shape, dst.element_type))
|
| 160 |
+
src = src_cvt
|
| 161 |
+
cute.copy(tiled_copy, src, dst, loc=loc, ip=ip)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
@dsl_user_op
|
| 165 |
def load_s2r(src: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
| 166 |
dst = cute.make_rmem_tensor_like(src, src.element_type, loc=loc, ip=ip)
|
|
|
|
| 168 |
return dst
|
| 169 |
|
| 170 |
|
| 171 |
+
@dsl_user_op
|
| 172 |
+
def contiguous(src: cute.Tensor, *, loc=None, ip=None) -> cute.Tensor:
|
| 173 |
+
dst = cute.make_rmem_tensor(src.shape, src.element_type, loc=loc, ip=ip)
|
| 174 |
+
cute.autovec_copy(src, dst, loc=loc, ip=ip)
|
| 175 |
+
return dst
|
| 176 |
+
|
| 177 |
+
|
| 178 |
@dsl_user_op
|
| 179 |
def load_s2r_retile(
|
| 180 |
tiled_copy: cute.TiledCopy,
|
|
|
|
| 195 |
|
| 196 |
@dsl_user_op
|
| 197 |
def load_t2r(
|
| 198 |
+
tiled_copy: cute.TiledCopy,
|
| 199 |
+
src: cute.Tensor,
|
| 200 |
+
*,
|
| 201 |
+
loc=None,
|
| 202 |
+
ip=None,
|
| 203 |
) -> cute.Tensor:
|
| 204 |
+
"""Load one tmem tile partition into rmem, deriving the rmem shape from `src`.
|
| 205 |
+
|
| 206 |
+
`src` should already be indexed to the tile being copied, with any
|
| 207 |
+
stage/subtile modes removed.
|
| 208 |
+
"""
|
| 209 |
+
dst = tmem_reg_frag(tiled_copy, src, loc=loc, ip=ip)
|
| 210 |
+
cute.copy(tiled_copy, src, dst, loc=loc, ip=ip)
|
| 211 |
return dst
|
| 212 |
|
| 213 |
|
|
|
|
| 429 |
return cute.make_tensor(cute.recast_ptr(tensor.iterator, dtype=tensor.element_type), new_layout)
|
| 430 |
|
| 431 |
|
| 432 |
+
def partition_D_position_independent(thr_copy: cute.ThrCopy, tensor: cute.Tensor) -> cute.Tensor:
|
|
|
|
|
|
|
| 433 |
return cute.make_tensor(
|
| 434 |
swizzle_ptr(thr_copy.partition_D(tensor).iterator),
|
| 435 |
thr_copy.partition_D(as_position_independent_swizzle_tensor(tensor)).layout,
|
| 436 |
)
|
| 437 |
|
| 438 |
|
| 439 |
+
def partition_S_position_independent(thr_copy: cute.ThrCopy, tensor: cute.Tensor) -> cute.Tensor:
|
|
|
|
|
|
|
| 440 |
return cute.make_tensor(
|
| 441 |
swizzle_ptr(thr_copy.partition_S(tensor).iterator),
|
| 442 |
thr_copy.partition_S(as_position_independent_swizzle_tensor(tensor)).layout,
|
|
|
|
| 477 |
|
| 478 |
|
| 479 |
def get_smem_store_atom(
|
|
|
|
| 480 |
element_type: Type[cute.Numeric],
|
| 481 |
transpose: bool = False,
|
| 482 |
major_mode_size: Optional[int] = None,
|
| 483 |
) -> cute.CopyAtom:
|
| 484 |
+
arch = cutlass.base_dsl.BaseDSL._get_dsl().get_arch_enum()
|
| 485 |
+
if const_expr(arch < Arch.sm_90 or element_type.width != 16):
|
| 486 |
return cute.make_copy_atom(
|
| 487 |
cute.nvgpu.CopyUniversalOp(),
|
| 488 |
element_type,
|
|
|
|
| 501 |
|
| 502 |
|
| 503 |
def get_smem_load_atom(
|
|
|
|
| 504 |
element_type: Type[cute.Numeric],
|
| 505 |
transpose: bool = False,
|
| 506 |
major_mode_size: Optional[int] = None,
|
| 507 |
) -> cute.CopyAtom:
|
| 508 |
+
arch = cutlass.base_dsl.BaseDSL._get_dsl().get_arch_enum()
|
| 509 |
+
if const_expr(arch < Arch.sm_90 or element_type.width != 16):
|
| 510 |
return cute.make_copy_atom(
|
| 511 |
cute.nvgpu.CopyUniversalOp(),
|
| 512 |
element_type,
|
|
|
|
| 525 |
|
| 526 |
|
| 527 |
def get_smem_store_C(
|
| 528 |
+
tiled_mma: cute.TiledMma | cute.TiledCopy,
|
| 529 |
sC: cute.Tensor,
|
| 530 |
tidx: Int32,
|
|
|
|
| 531 |
transpose: bool = False,
|
| 532 |
position_independent=False,
|
| 533 |
major_mode_size: Optional[int] = None,
|
| 534 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 535 |
dtype = sC.element_type
|
| 536 |
+
if const_expr(isinstance(tiled_mma, cute.TiledCopy)):
|
| 537 |
+
tiled_copy_t2r = tiled_mma
|
| 538 |
+
layout = LayoutEnum.COL_MAJOR if const_expr(transpose) else LayoutEnum.ROW_MAJOR
|
| 539 |
+
copy_atom = sm100_utils.get_smem_store_op(
|
| 540 |
+
layout, dtype, tiled_copy_t2r.value_type, tiled_copy_t2r
|
| 541 |
+
)
|
| 542 |
+
tiled_copy = cute.make_tiled_copy_D(copy_atom, tiled_copy_t2r)
|
| 543 |
+
else:
|
| 544 |
+
copy_atom = get_smem_store_atom(dtype, transpose, major_mode_size=major_mode_size)
|
| 545 |
+
tiled_copy = cute.make_tiled_copy_C(copy_atom, tiled_mma)
|
| 546 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 547 |
if const_expr(not position_independent):
|
| 548 |
tRS_sC = thr_copy.partition_D(sC)
|
| 549 |
else:
|
| 550 |
tRS_sC = partition_D_position_independent(thr_copy, sC)
|
| 551 |
|
| 552 |
+
def copy_fn(src: cute.Tensor, dst_idx: Optional[Int32] = None, fence=False, **new_kwargs):
|
| 553 |
+
dst_tensor = tRS_sC if const_expr(dst_idx is None) else tRS_sC[..., dst_idx]
|
| 554 |
cvt_copy(tiled_copy, src, dst_tensor, retile=True, **new_kwargs)
|
| 555 |
+
if const_expr(fence):
|
| 556 |
+
cute.arch.fence_view_async_shared()
|
| 557 |
|
| 558 |
return copy_fn, thr_copy, tRS_sC
|
| 559 |
|
|
|
|
| 562 |
tiled_mma: cute.TiledMma,
|
| 563 |
sC: cute.Tensor,
|
| 564 |
tidx: Int32,
|
|
|
|
| 565 |
transpose: bool = False,
|
| 566 |
position_independent=False,
|
| 567 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 568 |
dtype = sC.element_type
|
| 569 |
+
copy_atom = get_smem_load_atom(dtype, transpose)
|
| 570 |
tiled_copy = cute.make_tiled_copy_C(copy_atom, tiled_mma)
|
| 571 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 572 |
if const_expr(not position_independent):
|
| 573 |
tSR_sC = thr_copy.partition_S(sC)
|
| 574 |
else:
|
| 575 |
tSR_sC = partition_S_position_independent(thr_copy, sC)
|
| 576 |
+
copy_atom_RS = get_smem_store_atom(dtype, transpose)
|
| 577 |
thr_copy_RS = cute.make_tiled_copy_C(copy_atom_RS, tiled_mma).get_slice(tidx)
|
| 578 |
tRS_shape = thr_copy_RS.partition_S(cute.make_identity_tensor(sC.shape[:2])).shape
|
| 579 |
|
|
|
|
| 587 |
def epilog_smem_copy_atom(
|
| 588 |
tiled_mma: cute.TiledMma, epi_tile: cute.Shape, transpose: bool = False
|
| 589 |
) -> cute.TiledCopy:
|
| 590 |
+
arch = cutlass.base_dsl.BaseDSL._get_dsl().get_arch_enum()
|
| 591 |
+
if const_expr(arch < Arch.sm_90):
|
| 592 |
+
copy_atom_C = cute.make_copy_atom(
|
| 593 |
+
cute.nvgpu.CopyUniversalOp(),
|
| 594 |
+
cutlass.Float16, # this is just to get the right source layout
|
| 595 |
+
num_bits_per_copy=(2 if not transpose else 1) * cutlass.Float16.width,
|
| 596 |
+
)
|
| 597 |
+
else:
|
| 598 |
+
copy_atom_C = cute.make_copy_atom(
|
| 599 |
+
warp.StMatrix8x8x16bOp(transpose, num_matrices=4 if epi_tile[1] % 16 == 0 else 2),
|
| 600 |
+
cutlass.Float16, # this is just to get the right source layout
|
| 601 |
+
)
|
| 602 |
tiled_copy_C_atom = cute.make_tiled_copy_C_atom(copy_atom_C, tiled_mma)
|
| 603 |
return tiled_copy_C_atom
|
| 604 |
|
|
|
|
| 608 |
epi_tile: cute.Shape,
|
| 609 |
sC: Optional[cute.Tensor],
|
| 610 |
tidx: Int32,
|
|
|
|
| 611 |
transpose: bool = False,
|
| 612 |
position_independent=False,
|
| 613 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 614 |
dtype = sC.element_type if const_expr(sC is not None) else cutlass.Float16
|
| 615 |
+
copy_atom = get_smem_store_atom(dtype, transpose)
|
| 616 |
tiled_copy_C_atom = epilog_smem_copy_atom(tiled_mma, epi_tile)
|
|
|
|
| 617 |
tiled_copy = cute.make_tiled_copy_S(copy_atom, tiled_copy_C_atom)
|
| 618 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 619 |
tRS_sC = None
|
|
|
|
| 634 |
|
| 635 |
|
| 636 |
def get_smem_store_A(
|
| 637 |
+
tiled_mma: cute.TiledMma, sA: cute.Tensor, tidx: Int32, position_independent=False
|
| 638 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 639 |
dtype = sA.element_type
|
| 640 |
+
transpose = tiled_mma.op.a_major_mode == cute.nvgpu.OperandMajorMode.MN
|
| 641 |
+
copy_atom = get_smem_store_atom(dtype, transpose)
|
| 642 |
tiled_copy = cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 643 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 644 |
if const_expr(not position_independent):
|
|
|
|
| 656 |
tiled_mma: cute.TiledMma,
|
| 657 |
sA: cute.Tensor,
|
| 658 |
tidx: Int32,
|
|
|
|
| 659 |
with_dst_tensor: bool = False,
|
| 660 |
position_independent=False,
|
| 661 |
) -> Tuple[Callable, cute.TiledCopy, cute.Tensor]:
|
| 662 |
dtype = sA.element_type
|
| 663 |
+
transpose = tiled_mma.op.a_major_mode == cute.nvgpu.OperandMajorMode.MN
|
| 664 |
+
copy_atom = get_smem_load_atom(dtype, transpose)
|
| 665 |
tiled_copy = cute.make_tiled_copy_A(copy_atom, tiled_mma)
|
| 666 |
thr_copy = tiled_copy.get_slice(tidx)
|
| 667 |
if const_expr(not position_independent):
|
|
|
|
| 690 |
loc=None,
|
| 691 |
ip=None,
|
| 692 |
):
|
| 693 |
+
smem_ptr_i32 = smem_ptr.toint(loc=loc, ip=ip)
|
| 694 |
# cache_hint = cutlass.Int64(0x14F0000000000000) # EVICT_LAST
|
| 695 |
+
cute.arch.inline_ptx(
|
| 696 |
+
"cp.reduce.async.bulk.global.shared::cta.bulk_group.add.f32 [{$r0}], [{$r1}], {$r2};",
|
| 697 |
+
read_only_args=[gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes)],
|
|
|
|
|
|
|
| 698 |
# [gmem_ptr.llvm_ptr, smem_ptr_i32, Int32(store_bytes).ir_value(), cache_hint.ir_value()],
|
| 699 |
# "cp.reduce.async.bulk.global.shared::cta.bulk_group.L2::cache_hint.add.f32 [$0], [$1], $2, $3;",
|
| 700 |
# "l,r,r,l",
|
| 701 |
+
loc=loc,
|
| 702 |
+
ip=ip,
|
|
|
|
| 703 |
)
|
| 704 |
|
| 705 |
|
|
|
|
| 790 |
"""
|
| 791 |
if len(row_indices) != 4:
|
| 792 |
raise ValueError(f"gather4 requires exactly 4 row indices, got {len(row_indices)}")
|
| 793 |
+
col_val = Int32(col_idx)
|
| 794 |
+
row_vals = [Int32(row_idx) for row_idx in row_indices]
|
| 795 |
# Convert pointers to integer addresses
|
| 796 |
+
desc_addr = tma_desc_ptr.toint(loc=loc, ip=ip)
|
| 797 |
+
dst_addr = dst_smem_ptr.toint(loc=loc, ip=ip)
|
| 798 |
mbar_addr = mbarrier_ptr.toint(loc=loc, ip=ip)
|
| 799 |
if num_cta > 1:
|
| 800 |
# Executed by both CTAs. Set peer bit to 0 so that the
|
| 801 |
# transaction bytes will update CTA0's barrier.
|
| 802 |
mbar_addr = mbar_addr & Sm100MmaPeerBitMask
|
| 803 |
+
mbar_addr = Int32(mbar_addr)
|
| 804 |
# Handle multicast_mask - may already be ir.Value or Python int
|
| 805 |
multicast_mask_val = None
|
| 806 |
if multicast_mask is not None:
|
| 807 |
+
multicast_mask_val = Int16(multicast_mask)
|
| 808 |
assert multicast_mask_val is None, "multicast is not supported yet"
|
| 809 |
# Emit inline PTX for TMA gather4
|
| 810 |
# PTX: cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4.mbarrier::complete_tx::bytes
|
| 811 |
# [dstMem], [tensorMap, {col, row0, row1, row2, row3}], [smem_bar];
|
| 812 |
ptx = (
|
| 813 |
+
"cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4.mbarrier::complete_tx::bytes."
|
| 814 |
+
f"cta_group::{num_cta} "
|
| 815 |
+
"[{$r0}], [{$r1}, {{$r2}, {$r3}, {$r4}, {$r5}, {$r6}}], [{$r7}];"
|
| 816 |
)
|
| 817 |
|
| 818 |
+
cute.arch.inline_ptx(
|
| 819 |
+
ptx,
|
| 820 |
+
read_only_args=[
|
| 821 |
dst_addr,
|
| 822 |
desc_addr,
|
| 823 |
col_val,
|
|
|
|
| 827 |
row_vals[3],
|
| 828 |
mbar_addr,
|
| 829 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 830 |
loc=loc,
|
| 831 |
ip=ip,
|
| 832 |
)
|
|
|
|
| 913 |
return (copy_tma if const_expr(not single_stage) else copy_tma_single_stage), s, g
|
| 914 |
|
| 915 |
|
| 916 |
+
def s2t_get_copy_fn(
|
| 917 |
+
src_tensor: cute.Tensor,
|
| 918 |
+
dst_tensor: cute.Tensor,
|
| 919 |
+
cta_group: tcgen05.CtaGroup,
|
| 920 |
+
) -> Callable:
|
| 921 |
+
"""
|
| 922 |
+
Make tiledCopy for smem to tmem load, then return a copy function over stages.
|
| 923 |
+
|
| 924 |
+
:param src_tensor: The source tensor in smem
|
| 925 |
+
:param dst_tensor: The destination tensor in tmem
|
| 926 |
+
"""
|
| 927 |
+
assert src_tensor.element_type == dst_tensor.element_type
|
| 928 |
+
# (MMA, MMA_MN, MMA_K, STAGE)
|
| 929 |
+
src_compact = cute.filter_zeros(src_tensor)
|
| 930 |
+
# (MMA, MMA_MN, MMA_K)
|
| 931 |
+
dst_compact = cute.filter_zeros(dst_tensor)
|
| 932 |
+
# Make S2T CopyAtom and tiledCopy.
|
| 933 |
+
copy_atom = cute.make_copy_atom(tcgen05.Cp4x32x128bOp(cta_group), dst_tensor.element_type)
|
| 934 |
+
tiled_copy = tcgen05.make_s2t_copy(copy_atom, dst_compact)
|
| 935 |
+
thr_copy = tiled_copy.get_slice(0)
|
| 936 |
+
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K, STAGE)
|
| 937 |
+
src_partition = tcgen05.get_s2t_smem_desc_tensor(tiled_copy, thr_copy.partition_S(src_compact))
|
| 938 |
+
# ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K)
|
| 939 |
+
dst_partition = thr_copy.partition_D(dst_compact)
|
| 940 |
+
|
| 941 |
+
@dsl_user_op
|
| 942 |
+
def copy_s2t(stage_idx, *, loc=None, ip=None, **new_kwargs):
|
| 943 |
+
# Stage slice of partitioned source tensor: ((ATOM_V, REST_V), Rest_Tiler, MMA_MN, MMA_K)
|
| 944 |
+
stage_coord = (None, None, None, None, stage_idx)
|
| 945 |
+
cute.copy(
|
| 946 |
+
tiled_copy, src_partition[stage_coord], dst_partition, loc=loc, ip=ip, **new_kwargs
|
| 947 |
+
)
|
| 948 |
+
|
| 949 |
+
return copy_s2t
|
| 950 |
+
|
| 951 |
+
|
| 952 |
+
# tcgen05 TMEM <-> RMEM helpers (t2r loads / r2t stores).
|
| 953 |
+
#
|
| 954 |
+
# The register-side fragment of a tmem copy is derivable by layout algebra,
|
| 955 |
+
# with no reference to the original (pre-partition) tile tensor:
|
| 956 |
+
# - the per-thread register VALUE shape is mode 1 of the tiled copy's
|
| 957 |
+
# register-side TV layout (`layout_dst_tv_tiled` for loads,
|
| 958 |
+
# `layout_src_tv_tiled` for stores). The tmem-side partition can't supply
|
| 959 |
+
# it: tmem partitioning is warp-collective, so its value mode counts tmem
|
| 960 |
+
# cells across the whole warp, not per-thread register elements.
|
| 961 |
+
# - the tile-iteration modes are shared between partition_S and partition_D
|
| 962 |
+
# (same tiler over the same tile extent), so they can be read off whichever
|
| 963 |
+
# side was already partitioned.
|
| 964 |
+
# This kills the make-a-fake/identity-tensor-and-partition_D dance previously
|
| 965 |
+
# needed at every t2r site.
|
| 966 |
+
|
| 967 |
+
|
| 968 |
+
def tmem_reg_frag(
|
| 969 |
+
tiled_copy: cute.TiledCopy,
|
| 970 |
+
partitioned: cute.Tensor,
|
| 971 |
+
num_extra_modes: int = 0,
|
| 972 |
+
dtype: Optional[Type[cutlass.Numeric]] = None,
|
| 973 |
+
*,
|
| 974 |
+
loc=None,
|
| 975 |
+
ip=None,
|
| 976 |
+
) -> cute.Tensor:
|
| 977 |
+
"""Allocate the per-thread register fragment for ONE tile of a tcgen05
|
| 978 |
+
tmem copy, given any partitioned view of it (tmem or otherwise).
|
| 979 |
+
|
| 980 |
+
`partitioned` is (V, iter..., extra...) as produced by partition_S/_D;
|
| 981 |
+
the trailing `num_extra_modes` modes (stage, epi-subtile, ...) are
|
| 982 |
+
excluded from the fragment and indexed at copy time instead. `dtype`
|
| 983 |
+
defaults to `partitioned.element_type`.
|
| 984 |
+
The register side is inferred from the tcgen05 load/store op: destination
|
| 985 |
+
for t2r loads, source for r2t stores."""
|
| 986 |
+
tv = _tmem_copy_reg_tv_layout(tiled_copy)
|
| 987 |
+
val_shape = tv.shape[1]
|
| 988 |
+
rank = cute.rank(partitioned.shape)
|
| 989 |
+
iters = tuple(partitioned.shape[i] for i in range(1, rank - num_extra_modes))
|
| 990 |
+
frag_dtype = partitioned.element_type if const_expr(dtype is None) else dtype
|
| 991 |
+
return cute.make_rmem_tensor((val_shape, *iters), frag_dtype, loc=loc, ip=ip)
|
| 992 |
+
|
| 993 |
+
|
| 994 |
+
def r2s_partition_from_t2r(
|
| 995 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 996 |
+
s: cute.Tensor,
|
| 997 |
+
tidx: Int32,
|
| 998 |
+
transpose: bool = False,
|
| 999 |
+
position_independent=False,
|
| 1000 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
|
| 1001 |
+
"""SMEM-store (r2s) side chained off a tmem-load tiled copy: the r2s copy
|
| 1002 |
+
inherits the t2r copy's per-thread value ownership via make_tiled_copy_D,
|
| 1003 |
+
so the loaded fragment can be stored (post-conversion) without a shuffle.
|
| 1004 |
+
By default the store atom is selected like SM100 GEMM epilogues:
|
| 1005 |
+
`get_smem_store_op(layout, dst_dtype, tiled_copy_t2r.value_type, tiled_copy_t2r)`,
|
| 1006 |
+
so the stmatrix shape follows the tmem-load atom. `transpose=True` maps to
|
| 1007 |
+
COL_MAJOR, otherwise ROW_MAJOR.
|
| 1008 |
+
|
| 1009 |
+
`s` is the staged SMEM tile; its trailing stage mode is excluded from the
|
| 1010 |
+
register fragment. `position_independent=True` partitions through a
|
| 1011 |
+
position-independent swizzle view, matching `get_smem_store_C`.
|
| 1012 |
+
Returns `(tiled_copy, tRS_r, tRS_s)`; store via
|
| 1013 |
+
`cute.copy(tiled_copy, tRS_r, tRS_s[..., idx])`."""
|
| 1014 |
+
dtype = s.element_type
|
| 1015 |
+
layout = LayoutEnum.COL_MAJOR if const_expr(transpose) else LayoutEnum.ROW_MAJOR
|
| 1016 |
+
copy_atom = sm100_utils.get_smem_store_op(
|
| 1017 |
+
layout, dtype, tiled_copy_t2r.value_type, tiled_copy_t2r
|
| 1018 |
+
)
|
| 1019 |
+
tiled_copy = cute.make_tiled_copy_D(copy_atom, tiled_copy_t2r)
|
| 1020 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 1021 |
+
if const_expr(not position_independent):
|
| 1022 |
+
tRS_s = thr_copy.partition_D(s)
|
| 1023 |
+
else:
|
| 1024 |
+
tRS_s = partition_D_position_independent(thr_copy, s)
|
| 1025 |
+
rank = cute.rank(tRS_s.shape)
|
| 1026 |
+
frag_shape = tuple(tRS_s.shape[i] for i in range(rank - 1))
|
| 1027 |
+
tRS_r = cute.make_rmem_tensor(frag_shape, dtype)
|
| 1028 |
+
return tiled_copy, tRS_r, tRS_s
|
| 1029 |
+
|
| 1030 |
+
|
| 1031 |
+
def s2r_partition_from_t2r(
|
| 1032 |
+
tiled_copy_t2r: cute.TiledCopy,
|
| 1033 |
+
s: cute.Tensor,
|
| 1034 |
+
tidx: Int32,
|
| 1035 |
+
r_layout: cute.Layout,
|
| 1036 |
+
copy_atom: Optional[cute.CopyAtom] = None,
|
| 1037 |
+
transpose: bool = False,
|
| 1038 |
+
position_independent=False,
|
| 1039 |
+
) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor, cute.Tensor]:
|
| 1040 |
+
"""SMEM-load (s2r) counterpart of `r2s_partition_from_t2r` (ldmatrix vs
|
| 1041 |
+
stmatrix), for reading an epilogue input that was TMA-staged into an
|
| 1042 |
+
epi-tile SMEM buffer (e.g. C in gemm, z in ssd) into registers
|
| 1043 |
+
element-aligned with the t2r fragments. The register fragment is
|
| 1044 |
+
allocated with `r_layout` (pass the r2s fragment's layout) so its linear
|
| 1045 |
+
element order matches the t2r/r2s fragments; `tSR_r` is its retiled view
|
| 1046 |
+
for the collective copy. Per-warp SMEM footprints of this load and the
|
| 1047 |
+
chained r2s store coincide, so reusing one buffer for input then output
|
| 1048 |
+
is warp-local (no inter-warp hazard).
|
| 1049 |
+
|
| 1050 |
+
`position_independent=True` partitions through a position-independent
|
| 1051 |
+
swizzle view, matching `get_smem_load_C`.
|
| 1052 |
+
|
| 1053 |
+
Returns `(tiled_copy, tRS_r, tSR_r, tSR_s)`; load via
|
| 1054 |
+
`cute.copy(tiled_copy, tSR_s[..., idx], tSR_r)` then read `tRS_r`."""
|
| 1055 |
+
dtype = s.element_type
|
| 1056 |
+
if const_expr(copy_atom is None):
|
| 1057 |
+
copy_atom = cute.make_copy_atom(
|
| 1058 |
+
warp.LdMatrix8x8x16bOp(transpose=transpose, num_matrices=4), dtype
|
| 1059 |
+
)
|
| 1060 |
+
tiled_copy = cute.make_tiled_copy_D(copy_atom, tiled_copy_t2r)
|
| 1061 |
+
thr_copy = tiled_copy.get_slice(tidx)
|
| 1062 |
+
if const_expr(not position_independent):
|
| 1063 |
+
tSR_s = thr_copy.partition_S(s)
|
| 1064 |
+
else:
|
| 1065 |
+
tSR_s = partition_S_position_independent(thr_copy, s)
|
| 1066 |
+
tRS_r = cute.make_rmem_tensor(r_layout, dtype)
|
| 1067 |
+
tSR_r = tiled_copy.retile(tRS_r)
|
| 1068 |
+
return tiled_copy, tRS_r, tSR_r, tSR_s
|
| 1069 |
+
|
| 1070 |
+
|
| 1071 |
def tma_producer_copy_fn(copy: Callable, pipeline: cutlass.pipeline.PipelineAsync):
|
| 1072 |
def copy_fn(src_idx, producer_state: cutlass.pipeline.PipelineState, **new_kwargs):
|
| 1073 |
copy(
|
|
|
|
| 1080 |
return copy_fn
|
| 1081 |
|
| 1082 |
|
| 1083 |
+
def chain_tma_producer_copy_fns(copy_fns: Sequence[Optional[Callable]]):
|
| 1084 |
+
if not any(fn is not None for fn in copy_fns):
|
| 1085 |
+
return None
|
| 1086 |
+
|
| 1087 |
+
def copy_fn(src_idx, producer_state: cutlass.pipeline.PipelineState, **new_kwargs):
|
| 1088 |
+
for fn in copy_fns:
|
| 1089 |
+
if const_expr(fn is not None):
|
| 1090 |
+
fn(src_idx=src_idx, producer_state=producer_state, **new_kwargs)
|
| 1091 |
+
|
| 1092 |
+
return copy_fn
|
| 1093 |
+
|
| 1094 |
+
|
| 1095 |
@cute.jit
|
| 1096 |
def gather_m_get_copy_fn(
|
| 1097 |
thr_copy_A: cute.ThrCopy,
|
|
|
|
| 1101 |
limit_m: Int32,
|
| 1102 |
limit_k: Int32,
|
| 1103 |
) -> Callable:
|
| 1104 |
+
tile_M, tile_K = cute.size(sA, mode=[0]), cute.size(sA, mode=[1])
|
| 1105 |
+
tAsA = partition_D_position_independent(thr_copy_A, sA)
|
| 1106 |
# k-major
|
| 1107 |
assert tAsA.shape[2] == 1
|
| 1108 |
tAsA = cute.group_modes(cute.slice_(tAsA, (None, None, 0, None)), 0, 2)
|
| 1109 |
|
| 1110 |
+
is_even_m_smem = tile_M % thr_copy_A.tiler_mn[0].shape == 0
|
| 1111 |
if const_expr(not is_even_m_smem):
|
| 1112 |
+
limit_m = min(limit_m, tile_M)
|
| 1113 |
elems_per_load = cute.size(tAsA.shape[0][0])
|
| 1114 |
+
cA = cute.make_identity_tensor((tile_M, tile_K))
|
| 1115 |
tAcA = thr_copy_A.partition_S(cA)
|
| 1116 |
t0AcA = thr_copy_A.get_slice(0).partition_S(cA)
|
| 1117 |
# Instead of comparing tAcA to limit_m, we instead compare t0AcA to limit_m - tAcA[0][0]
|
|
|
|
| 1133 |
else:
|
| 1134 |
m_idx[m] = 0 # It's ok to load row 0 in the case of OOB
|
| 1135 |
|
| 1136 |
+
mA_k = cute.logical_divide(mA, (None, tile_K))
|
| 1137 |
|
| 1138 |
+
def copy_fn(src_idx, dst_idx, pred: cutlass.Constexpr[bool] = False):
|
| 1139 |
tApA_k = None
|
| 1140 |
if const_expr(pred):
|
| 1141 |
tApA_k = cute.make_rmem_tensor(cols_per_thread, Boolean)
|
| 1142 |
+
limit_k_cur = limit_k - src_idx * tile_K
|
| 1143 |
for k in cutlass.range(cols_per_thread, unroll_full=True):
|
| 1144 |
tApA_k[k] = t0AcA[0, 0, k][1] < limit_k_cur
|
| 1145 |
mA_cur = mA_k[None, (None, src_idx)]
|
|
|
|
| 1302 |
tma_gather4_load_fn = partial(tma_gather4_load, tma_desc_ptr, num_cta=cta_group)
|
| 1303 |
|
| 1304 |
def copy_fn(src_idx, dst_idx, tma_bar_ptr: cute.Pointer):
|
| 1305 |
+
tSR_sA_cur = tSR_sA[None, None, None, dst_idx]
|
| 1306 |
col_idx = tile_K * src_idx
|
| 1307 |
for m in cutlass.range(cute.size(tSR_rAIdx, mode=[1]), unroll_full=True):
|
| 1308 |
row_indices = [tSR_rAIdx[v, m] for v in range(4)]
|
| 1309 |
+
smem_ptr = tSR_sA_cur[None, m, None].iterator
|
| 1310 |
with cute.arch.elect_one():
|
| 1311 |
tma_gather4_load_fn(smem_ptr, tma_bar_ptr, col_idx, row_indices)
|
| 1312 |
|
| 1313 |
return copy_fn
|
| 1314 |
+
|
| 1315 |
+
|
| 1316 |
+
@cute.jit
|
| 1317 |
+
def gather_k_get_tma_copy_fn(
|
| 1318 |
+
tma_atom: cute.CopyAtom,
|
| 1319 |
+
sA: cute.Tensor, # ((4, tile_K/4), (tile_M,), STAGE) — K-grouped load layout
|
| 1320 |
+
sAIdx: cute.Tensor, # (tile_K, a_prefetch_stage) — K indices in smem
|
| 1321 |
+
col_idx: Int32, # M offset in global tensor (contiguous dim for M-major)
|
| 1322 |
+
warp_idx: Int32,
|
| 1323 |
+
num_warps: int,
|
| 1324 |
+
num_cta: int = 1,
|
| 1325 |
+
) -> Tuple[Callable, Callable]:
|
| 1326 |
+
"""Build a copy function for TMA gather4 in K dimension (M-major A).
|
| 1327 |
+
|
| 1328 |
+
Each gather4 instruction loads 4 K-columns × tile_M contiguous M-elements.
|
| 1329 |
+
col_idx is the absolute M position in the global tensor.
|
| 1330 |
+
K indices come from sAIdx (prefetched to smem by the scheduler warp).
|
| 1331 |
+
|
| 1332 |
+
Returns copy_fn(src_idx, dst_idx, tma_bar_ptr) which:
|
| 1333 |
+
Issues gather4 calls with those K indices as row_indices
|
| 1334 |
+
"""
|
| 1335 |
+
tile_K = cute.size(sAIdx, mode=[0])
|
| 1336 |
+
assert tile_K % 4 == 0
|
| 1337 |
+
cta_group = num_cta
|
| 1338 |
+
|
| 1339 |
+
# Tiled copy for loading K indices from smem to registers (4 per vector, across warps)
|
| 1340 |
+
copy_AIdx_s2r = cute.make_tiled_copy_tv(
|
| 1341 |
+
cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), Int32, num_bits_per_copy=128),
|
| 1342 |
+
cute.make_layout(num_warps), # thr_layout
|
| 1343 |
+
cute.make_layout(4), # val_layout — 4 K indices per gather4
|
| 1344 |
+
)
|
| 1345 |
+
warp_idx = cute.arch.make_warp_uniform(warp_idx)
|
| 1346 |
+
warp_copy_AIdx_s2r = copy_AIdx_s2r.get_slice(warp_idx)
|
| 1347 |
+
tSR_sAIdx = warp_copy_AIdx_s2r.partition_S(sAIdx) # (((4,1),4,4))
|
| 1348 |
+
# ((4,1),4,(64,2),(1,4)):((64,0),1024,(1,4096),(0,8192))
|
| 1349 |
+
tSR_sA = warp_copy_AIdx_s2r.partition_S(layout_utils.transpose_view(sA))
|
| 1350 |
+
tma_desc_ptr = get_tma_desc_addr(tma_atom)
|
| 1351 |
+
tma_gather4_load_fn = partial(tma_gather4_load, tma_desc_ptr, num_cta=cta_group)
|
| 1352 |
+
|
| 1353 |
+
def prefetch_from_smem_fn(
|
| 1354 |
+
a_prefetch_pipeline,
|
| 1355 |
+
src_idx,
|
| 1356 |
+
dst_idx,
|
| 1357 |
+
a_prefetch_consumer_state,
|
| 1358 |
+
) -> cute.Tensor:
|
| 1359 |
+
a_prefetch_pipeline.consumer_wait(a_prefetch_consumer_state)
|
| 1360 |
+
tSR_rAIdx = load_s2r(tSR_sAIdx[None, None, dst_idx])
|
| 1361 |
+
cute.arch.sync_warp()
|
| 1362 |
+
with cute.arch.elect_one():
|
| 1363 |
+
a_prefetch_pipeline.consumer_release(a_prefetch_consumer_state)
|
| 1364 |
+
return tSR_rAIdx
|
| 1365 |
+
|
| 1366 |
+
def copy_fn(src_idx, dst_idx, tSR_rAIdx, tma_bar_ptr: cute.Pointer):
|
| 1367 |
+
# Issue gather4: col_idx = M position, row_indices = 4 K positions
|
| 1368 |
+
tSR_sA_cur = tSR_sA[None, None, None, dst_idx]
|
| 1369 |
+
gather_dim = cute.size(tSR_sA_cur, mode=[2, 0]) # Typically 64
|
| 1370 |
+
for k in cutlass.range(cute.size(tSR_rAIdx, mode=[1]), unroll_full=True):
|
| 1371 |
+
row_indices = [tSR_rAIdx[v, k] for v in range(4)]
|
| 1372 |
+
for m in cutlass.range(cute.size(tSR_sA_cur, mode=[2, 1]), unroll_full=True):
|
| 1373 |
+
smem_ptr = tSR_sA_cur[None, k, (None, m)].iterator
|
| 1374 |
+
with cute.arch.elect_one():
|
| 1375 |
+
tma_gather4_load_fn(
|
| 1376 |
+
smem_ptr, tma_bar_ptr, col_idx + m * gather_dim, row_indices
|
| 1377 |
+
)
|
| 1378 |
+
|
| 1379 |
+
return copy_fn, prefetch_from_smem_fn
|
| 1380 |
+
|
| 1381 |
+
|
| 1382 |
+
# ---------------------------------------------------------------------------
|
| 1383 |
+
# Store helpers
|
| 1384 |
+
# ---------------------------------------------------------------------------
|
| 1385 |
+
|
| 1386 |
+
|
| 1387 |
+
@dsl_user_op
|
| 1388 |
+
@cute.jit
|
| 1389 |
+
def store(
|
| 1390 |
+
ptr: cute.Pointer,
|
| 1391 |
+
val,
|
| 1392 |
+
pred: Optional[Boolean] = None,
|
| 1393 |
+
cop: cutlass.Constexpr = None,
|
| 1394 |
+
*,
|
| 1395 |
+
loc=None,
|
| 1396 |
+
ip=None,
|
| 1397 |
+
):
|
| 1398 |
+
"""Store a scalar value via cute.arch.store.
|
| 1399 |
+
|
| 1400 |
+
ptr: cute.Pointer (any address space).
|
| 1401 |
+
val: DSL Numeric value.
|
| 1402 |
+
pred: None → unconditional. DSL Boolean → skipped when pred == 0.
|
| 1403 |
+
cop: Cache operator — "wb" (default), "cg", "cs" (streaming), "wt".
|
| 1404 |
+
"""
|
| 1405 |
+
if const_expr(pred is None):
|
| 1406 |
+
cute.arch.store(ptr.llvm_ptr, type(val)(val), cop=cop, loc=loc, ip=ip)
|
| 1407 |
+
else:
|
| 1408 |
+
if pred:
|
| 1409 |
+
cute.arch.store(ptr.llvm_ptr, type(val)(val), cop=cop, loc=loc, ip=ip)
|
| 1410 |
+
|
| 1411 |
+
|
| 1412 |
+
@dsl_user_op
|
| 1413 |
+
@cute.jit
|
| 1414 |
+
def store_v2(
|
| 1415 |
+
ptr: cute.Pointer,
|
| 1416 |
+
v0,
|
| 1417 |
+
v1,
|
| 1418 |
+
pred: Optional[Boolean] = None,
|
| 1419 |
+
cop: cutlass.Constexpr = None,
|
| 1420 |
+
*,
|
| 1421 |
+
loc=None,
|
| 1422 |
+
ip=None,
|
| 1423 |
+
):
|
| 1424 |
+
"""Vectorized store of 2 elements via cute.arch.store.
|
| 1425 |
+
|
| 1426 |
+
Packs v0, v1 into an MLIR <2 x T> vector.
|
| 1427 |
+
ptr: cute.Pointer (any address space, must be aligned for vector width).
|
| 1428 |
+
cop: Cache operator — "wb" (default), "cg", "cs" (streaming), "wt".
|
| 1429 |
+
"""
|
| 1430 |
+
vec = make_vector(type(v0), v0, v1, loc=loc, ip=ip)
|
| 1431 |
+
if const_expr(pred is None):
|
| 1432 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
| 1433 |
+
else:
|
| 1434 |
+
if pred:
|
| 1435 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
| 1436 |
+
|
| 1437 |
+
|
| 1438 |
+
@dsl_user_op
|
| 1439 |
+
@cute.jit
|
| 1440 |
+
def store_v4(
|
| 1441 |
+
ptr: cute.Pointer,
|
| 1442 |
+
v0,
|
| 1443 |
+
v1,
|
| 1444 |
+
v2,
|
| 1445 |
+
v3,
|
| 1446 |
+
pred: Optional[Boolean] = None,
|
| 1447 |
+
cop: cutlass.Constexpr = None,
|
| 1448 |
+
*,
|
| 1449 |
+
loc=None,
|
| 1450 |
+
ip=None,
|
| 1451 |
+
):
|
| 1452 |
+
"""Vectorized store of 4 elements via cute.arch.store.
|
| 1453 |
+
|
| 1454 |
+
Packs v0–v3 into an MLIR <4 x T> vector.
|
| 1455 |
+
ptr: cute.Pointer (any address space, must be aligned for vector width).
|
| 1456 |
+
cop: Cache operator — "wb" (default), "cg", "cs" (streaming), "wt".
|
| 1457 |
+
"""
|
| 1458 |
+
vec = make_vector(type(v0), v0, v1, v2, v3, loc=loc, ip=ip)
|
| 1459 |
+
if const_expr(pred is None):
|
| 1460 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
| 1461 |
+
else:
|
| 1462 |
+
if pred:
|
| 1463 |
+
cute.arch.store(ptr.llvm_ptr, vec, cop=cop, loc=loc, ip=ip)
|
build/torch-cuda/quack/cute_dsl_utils.py
CHANGED
|
@@ -4,6 +4,9 @@ from typing import Tuple, get_origin
|
|
| 4 |
from functools import lru_cache
|
| 5 |
from dataclasses import dataclass, fields
|
| 6 |
|
|
|
|
|
|
|
|
|
|
| 7 |
import torch
|
| 8 |
|
| 9 |
try:
|
|
@@ -14,7 +17,6 @@ except ImportError:
|
|
| 14 |
import cutlass
|
| 15 |
import cutlass.cute as cute
|
| 16 |
from cutlass import Int32, Int64, Float16, BFloat16, Float32
|
| 17 |
-
from cutlass.base_dsl.typing import JitArgument
|
| 18 |
from cutlass.base_dsl.tvm_ffi_builder import spec
|
| 19 |
from cutlass.cutlass_dsl import NumericMeta
|
| 20 |
|
|
@@ -61,15 +63,64 @@ torch2cute_dtype_map = {
|
|
| 61 |
|
| 62 |
|
| 63 |
@lru_cache
|
| 64 |
-
def
|
| 65 |
-
return cutlass.utils.HardwareInfo().
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
|
| 68 |
@lru_cache
|
| 69 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
return torch.cuda.get_device_capability(device)
|
| 71 |
|
| 72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
def _partition_fields(obj):
|
| 74 |
"""Split dataclass fields into (constexpr_dict, non_constexpr_dict) by type."""
|
| 75 |
all_fields = {field.name: getattr(obj, field.name) for field in fields(obj)}
|
|
@@ -138,28 +189,3 @@ class ParamsBase:
|
|
| 138 |
return values
|
| 139 |
|
| 140 |
__new_from_mlir_values__ = _new_from_mlir_values
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
@dataclass
|
| 144 |
-
class ArgumentsBase(JitArgument):
|
| 145 |
-
def __c_pointers__(self):
|
| 146 |
-
_, non_constexpr_fields = _partition_fields(self)
|
| 147 |
-
c_ptrs = []
|
| 148 |
-
for obj in non_constexpr_fields.values():
|
| 149 |
-
if hasattr(obj, "__c_pointers__"):
|
| 150 |
-
c_ptrs.extend(obj.__c_pointers__())
|
| 151 |
-
return c_ptrs
|
| 152 |
-
|
| 153 |
-
def __get_mlir_types__(self):
|
| 154 |
-
_, non_constexpr_fields = _partition_fields(self)
|
| 155 |
-
types, self._values_pos = [], []
|
| 156 |
-
for obj in non_constexpr_fields.values():
|
| 157 |
-
if hasattr(obj, "__get_mlir_types__"):
|
| 158 |
-
obj_types = obj.__get_mlir_types__()
|
| 159 |
-
types.extend(obj_types)
|
| 160 |
-
self._values_pos.append(len(obj_types))
|
| 161 |
-
else:
|
| 162 |
-
self._values_pos.append(0)
|
| 163 |
-
return types
|
| 164 |
-
|
| 165 |
-
__new_from_mlir_values__ = _new_from_mlir_values
|
|
|
|
| 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:
|
|
|
|
| 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 |
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
@lru_cache
|
| 66 |
+
def get_device_multiprocessor_count(device_id: int = 0) -> int:
|
| 67 |
+
return cutlass.utils.HardwareInfo(device_id).get_device_multiprocessor_count()
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@lru_cache
|
| 71 |
+
def get_max_active_clusters(
|
| 72 |
+
cluster_size: int,
|
| 73 |
+
device_capacity: Tuple[int, int] | None = None,
|
| 74 |
+
device_id: int = 0,
|
| 75 |
+
) -> int:
|
| 76 |
+
if device_capacity is None:
|
| 77 |
+
device_capacity = get_device_capacity()
|
| 78 |
+
if device_capacity[0] < 9:
|
| 79 |
+
if cluster_size != 1:
|
| 80 |
+
raise ValueError("SM8x kernels do not support CTA clusters; cluster_size must be 1")
|
| 81 |
+
return get_device_multiprocessor_count(device_id)
|
| 82 |
+
return cutlass.utils.HardwareInfo(device_id).get_max_active_clusters(cluster_size=cluster_size)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _parse_arch_str(arch_str: str) -> Tuple[int, int]:
|
| 86 |
+
"""Parse arch string (e.g. 'sm_90', 'sm90', '90', 'sm_100a') to (major, minor) tuple."""
|
| 87 |
+
match = re.match(r"^(?:sm_?)?(\d+)(\d)([af]?)$", arch_str.strip(), re.IGNORECASE)
|
| 88 |
+
if not match:
|
| 89 |
+
raise ValueError(f"Invalid QUACK_ARCH format: {arch_str!r} (expected e.g. '90', 'sm_90')")
|
| 90 |
+
major, minor, _ = match.groups()
|
| 91 |
+
return int(major), int(minor)
|
| 92 |
|
| 93 |
|
| 94 |
@lru_cache
|
| 95 |
+
def _get_device_capacity_cached(device: torch.device = None) -> Tuple[int, int]:
|
| 96 |
+
"""Return (major, minor) device capability.
|
| 97 |
+
|
| 98 |
+
Override with QUACK_ARCH (e.g. 'sm_90' or '90') for CPU-only compilation
|
| 99 |
+
without a GPU present.
|
| 100 |
+
"""
|
| 101 |
+
arch_override = os.environ.get("QUACK_ARCH")
|
| 102 |
+
if arch_override is not None:
|
| 103 |
+
return _parse_arch_str(arch_override)
|
| 104 |
return torch.cuda.get_device_capability(device)
|
| 105 |
|
| 106 |
|
| 107 |
+
def get_device_capacity(
|
| 108 |
+
device: torch.device | torch.Tensor | None = None,
|
| 109 |
+
) -> Tuple[int, int]:
|
| 110 |
+
"""Return (major, minor) device capability.
|
| 111 |
+
|
| 112 |
+
Override with QUACK_ARCH (e.g. 'sm_90' or '90') for CPU-only compilation
|
| 113 |
+
without a GPU present.
|
| 114 |
+
|
| 115 |
+
Accepts either a ``torch.device`` or a tensor and canonicalizes to the
|
| 116 |
+
underlying device before consulting the cached helper. This avoids leaking
|
| 117 |
+
tensors through the LRU cache key.
|
| 118 |
+
"""
|
| 119 |
+
if isinstance(device, torch.Tensor):
|
| 120 |
+
device = device.device
|
| 121 |
+
return _get_device_capacity_cached(device)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
def _partition_fields(obj):
|
| 125 |
"""Split dataclass fields into (constexpr_dict, non_constexpr_dict) by type."""
|
| 126 |
all_fields = {field.name: getattr(obj, field.name) for field in fields(obj)}
|
|
|
|
| 189 |
return values
|
| 190 |
|
| 191 |
__new_from_mlir_values__ = _new_from_mlir_values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/torch-cuda/quack/dsl/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CuTe DSL helpers and integration hooks.
|
| 2 |
+
|
| 3 |
+
Importing this package installs quack's Pythonic CuTe tensor indexing, which
|
| 4 |
+
monkey-patches CuTe's tensor classes process-wide so that `...` and `:` work in
|
| 5 |
+
`__getitem__`/`__setitem__`. `quack.copy_utils` relies on that sugar (e.g.
|
| 6 |
+
`tRS_sC[..., dst_idx]`), and the sm90 backward kernel reaches it.
|
| 7 |
+
|
| 8 |
+
Upstream's `quack.dsl` also exports `cute_op` from `torch_library_op`; nothing
|
| 9 |
+
vendored here uses it, so it is not copied.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from . import cute_tensor_indexing # noqa: F401
|
build/torch-cuda/quack/dsl/cute_tensor_indexing.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2026, Tri Dao.
|
| 2 |
+
"""Small compatibility layer for more Pythonic CuTe tensor indexing.
|
| 3 |
+
|
| 4 |
+
CuTe uses ``None`` as its underscore/full-mode slice marker, e.g. ``A[i, None]``.
|
| 5 |
+
This module teaches CuTe tensors the equivalent Python spelling ``:`` and expands
|
| 6 |
+
``...`` to the right number of full-mode slices.
|
| 7 |
+
|
| 8 |
+
Importing this module intentionally mutates CuTe's tensor classes process-wide.
|
| 9 |
+
The original methods are retained as ``_quack_original_getitem`` and
|
| 10 |
+
``_quack_original_setitem`` for debugging or manual rollback.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
from typing import Any
|
| 16 |
+
|
| 17 |
+
from cutlass.cutlass_dsl import dsl_user_op
|
| 18 |
+
import cutlass.cute.tensor as _cute_tensor
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
_ORIGINAL_GETITEM_ATTR = "_quack_original_getitem"
|
| 22 |
+
_ORIGINAL_SETITEM_ATTR = "_quack_original_setitem"
|
| 23 |
+
_PATCHED_ATTR = "_quack_extended_indexing"
|
| 24 |
+
_PATCHED_SETITEM_ATTR = f"{_PATCHED_ATTR}_setitem"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _is_full_slice(idx: Any) -> bool:
|
| 28 |
+
return isinstance(idx, slice) and idx.start is None and idx.stop is None and idx.step is None
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _index_uses_ellipsis(idx: Any) -> bool:
|
| 32 |
+
if idx is Ellipsis:
|
| 33 |
+
return True
|
| 34 |
+
if isinstance(idx, tuple):
|
| 35 |
+
return any(_index_uses_ellipsis(item) for item in idx)
|
| 36 |
+
return False
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _shape_rank(shape: Any, idx: Any = None) -> int:
|
| 40 |
+
if shape is None:
|
| 41 |
+
suffix = f" in {idx!r}" if idx is not None else ""
|
| 42 |
+
raise ValueError(f"tensor shape is required to expand ellipsis{suffix}")
|
| 43 |
+
return _cute_tensor.rank(shape)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _shape_mode(shape: Any, mode: int) -> Any:
|
| 47 |
+
if isinstance(shape, tuple) and mode < len(shape):
|
| 48 |
+
return shape[mode]
|
| 49 |
+
return None
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _canonicalize_cute_tensor_index(idx: Any, tensor_shape: Any = None) -> Any:
|
| 53 |
+
"""Convert Python indexing sugar to CuTe's coordinate convention.
|
| 54 |
+
|
| 55 |
+
``:`` becomes ``None`` (CuTe's full-mode/underscore marker) and ``...`` expands
|
| 56 |
+
within the current hierarchy level using ``tensor_shape``. Other slices like
|
| 57 |
+
``1:4`` are intentionally rejected because CuTe tensor slicing only supports
|
| 58 |
+
keeping an entire mode or selecting a single coordinate.
|
| 59 |
+
"""
|
| 60 |
+
if idx is Ellipsis:
|
| 61 |
+
return (None,) * _shape_rank(tensor_shape, idx)
|
| 62 |
+
if _is_full_slice(idx):
|
| 63 |
+
return None
|
| 64 |
+
if isinstance(idx, slice):
|
| 65 |
+
raise ValueError(f"CuTe Tensor indexing only supports full slices ':', got {idx!r}")
|
| 66 |
+
if not isinstance(idx, tuple):
|
| 67 |
+
return idx
|
| 68 |
+
|
| 69 |
+
ellipsis_count = sum(item is Ellipsis for item in idx)
|
| 70 |
+
if ellipsis_count > 1:
|
| 71 |
+
raise ValueError("CuTe Tensor indexing supports at most one ellipsis per tuple level")
|
| 72 |
+
|
| 73 |
+
explicit_modes = len(idx) - ellipsis_count
|
| 74 |
+
fill_modes = 0
|
| 75 |
+
if ellipsis_count:
|
| 76 |
+
tensor_rank = _shape_rank(tensor_shape, idx)
|
| 77 |
+
fill_modes = tensor_rank - explicit_modes
|
| 78 |
+
if fill_modes < 0:
|
| 79 |
+
raise ValueError(
|
| 80 |
+
f"ellipsis cannot expand index {idx!r} for rank-{tensor_rank} CuTe Tensor mode"
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
result: list[Any] = []
|
| 84 |
+
mode = 0
|
| 85 |
+
for item in idx:
|
| 86 |
+
if item is Ellipsis:
|
| 87 |
+
result.extend([None] * fill_modes)
|
| 88 |
+
mode += fill_modes
|
| 89 |
+
else:
|
| 90 |
+
result.append(_canonicalize_cute_tensor_index(item, _shape_mode(tensor_shape, mode)))
|
| 91 |
+
mode += 1
|
| 92 |
+
return tuple(result)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def _make_getitem(original_getitem: Any) -> Any:
|
| 96 |
+
@dsl_user_op
|
| 97 |
+
def _getitem(self: Any, idx: Any, *, loc: Any = None, ip: Any = None) -> Any:
|
| 98 |
+
tensor_shape = self.shape if _index_uses_ellipsis(idx) else None
|
| 99 |
+
idx = _canonicalize_cute_tensor_index(idx, tensor_shape)
|
| 100 |
+
return original_getitem(self, idx, loc=loc, ip=ip)
|
| 101 |
+
|
| 102 |
+
return _getitem
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _make_setitem(original_setitem: Any) -> Any:
|
| 106 |
+
@dsl_user_op
|
| 107 |
+
def _setitem(self: Any, idx: Any, data: Any, *, loc: Any = None, ip: Any = None) -> Any:
|
| 108 |
+
tensor_shape = self.shape if _index_uses_ellipsis(idx) else None
|
| 109 |
+
idx = _canonicalize_cute_tensor_index(idx, tensor_shape)
|
| 110 |
+
return original_setitem(self, idx, data, loc=loc, ip=ip)
|
| 111 |
+
|
| 112 |
+
return _setitem
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def patch_cute_tensor_indexing() -> None:
|
| 116 |
+
"""Monkey patch CuTe Tensor indexing with ``:``, ``...`` sugar.
|
| 117 |
+
|
| 118 |
+
The patch is idempotent and keeps the original CuTe implementation for all
|
| 119 |
+
canonical coordinates, so existing ``A[i, j, None]`` code continues to behave
|
| 120 |
+
exactly as before. It is a process-wide mutation of CuTe's tensor classes.
|
| 121 |
+
"""
|
| 122 |
+
for cls in (_cute_tensor._Tensor, _cute_tensor.TensorSSA):
|
| 123 |
+
if _PATCHED_ATTR not in cls.__dict__:
|
| 124 |
+
setattr(cls, _ORIGINAL_GETITEM_ATTR, cls.__getitem__)
|
| 125 |
+
cls.__getitem__ = _make_getitem(cls.__getitem__) # type: ignore[method-assign]
|
| 126 |
+
setattr(cls, _PATCHED_ATTR, True)
|
| 127 |
+
|
| 128 |
+
# TensorSSA has no upstream __setitem__, so only _Tensor needs the store path patched.
|
| 129 |
+
tensor_cls = _cute_tensor._Tensor
|
| 130 |
+
if _PATCHED_SETITEM_ATTR not in tensor_cls.__dict__:
|
| 131 |
+
setattr(tensor_cls, _ORIGINAL_SETITEM_ATTR, tensor_cls.__setitem__)
|
| 132 |
+
tensor_cls.__setitem__ = _make_setitem(tensor_cls.__setitem__) # type: ignore[method-assign]
|
| 133 |
+
setattr(tensor_cls, _PATCHED_SETITEM_ATTR, True)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
patch_cute_tensor_indexing()
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
__all__ = ["patch_cute_tensor_indexing"]
|
build/torch-cuda/quack/layout_utils.py
CHANGED
|
@@ -18,6 +18,19 @@ 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:])
|
|
@@ -217,15 +230,16 @@ def convert_layout_acc_frgA(acc_layout: cute.Layout) -> cute.Layout:
|
|
| 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 |
),
|
|
@@ -264,7 +278,7 @@ def convert_layout_zero_stride(
|
|
| 264 |
|
| 265 |
|
| 266 |
def mma_partition_C_vec(
|
| 267 |
-
sVec: cute.Tensor, thr_mma: cute.
|
| 268 |
) -> cute.Tensor:
|
| 269 |
assert cute.rank(sVec) == 2
|
| 270 |
assert sVec.stride[0] == 1
|
|
@@ -281,7 +295,7 @@ def mma_partition_C_vec(
|
|
| 281 |
|
| 282 |
|
| 283 |
def mma_partition_A_vec(
|
| 284 |
-
sVec: cute.Tensor, thr_mma: cute.
|
| 285 |
) -> cute.Tensor:
|
| 286 |
assert cute.rank(sVec) == 2
|
| 287 |
assert sVec.stride[0] == 1
|
|
@@ -295,3 +309,77 @@ def mma_partition_A_vec(
|
|
| 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]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
return cute.make_tensor(a.iterator, cute.select(a.layout, mode))
|
| 19 |
|
| 20 |
|
| 21 |
+
def concat_to_interleave(a: cute.Tensor, dim: int) -> cute.Tensor:
|
| 22 |
+
"""Reshape a concat [first_half; second_half] layout to interleaved along `dim`.
|
| 23 |
+
|
| 24 |
+
Splits dimension `dim` (size 2N) into hierarchical (2, N) so that elements
|
| 25 |
+
from the first half and second half alternate: [first_0, second_0, first_1, ...].
|
| 26 |
+
Used to convert gated MLP weight layout from concat [gate; up] to interleaved.
|
| 27 |
+
"""
|
| 28 |
+
half = cute.size(a, mode=[dim]) // 2
|
| 29 |
+
shape = (*a.shape[:dim], (2, half), *a.shape[dim + 1 :])
|
| 30 |
+
stride = (*a.stride[:dim], (half * a.stride[dim], a.stride[dim]), *a.stride[dim + 1 :])
|
| 31 |
+
return cute.make_tensor(a.iterator, cute.make_layout(shape, stride=stride))
|
| 32 |
+
|
| 33 |
+
|
| 34 |
def expand(a: cute.Tensor, dim: int, size: Int32 | int) -> cute.Tensor:
|
| 35 |
shape = (*a.shape[:dim], size, *a.shape[dim:])
|
| 36 |
stride = (*a.layout.stride[:dim], 0, *a.layout.stride[dim:])
|
|
|
|
| 230 |
)
|
| 231 |
else: # Sm80
|
| 232 |
# (4, MMA_M, MMA_N) -> (4, MMA_M, (2, MMA_N / 2))
|
| 233 |
+
assert acc_layout.shape[2] % 2 == 0
|
| 234 |
l = cute.logical_divide(acc_layout, (None, None, 2))
|
| 235 |
rA_mma_view = cute.make_layout(
|
| 236 |
(
|
| 237 |
+
(l.shape[0][0], l.shape[0][1], l.shape[2][0]),
|
| 238 |
l.shape[1],
|
| 239 |
l.shape[2][1],
|
| 240 |
),
|
| 241 |
stride=(
|
| 242 |
+
(l.stride[0][0], l.stride[0][1], l.stride[2][0]),
|
| 243 |
l.stride[1],
|
| 244 |
l.stride[2][1],
|
| 245 |
),
|
|
|
|
| 278 |
|
| 279 |
|
| 280 |
def mma_partition_C_vec(
|
| 281 |
+
sVec: cute.Tensor, thr_mma: cute.ThrMma, expand_shape: int, is_colvec: bool
|
| 282 |
) -> cute.Tensor:
|
| 283 |
assert cute.rank(sVec) == 2
|
| 284 |
assert sVec.stride[0] == 1
|
|
|
|
| 295 |
|
| 296 |
|
| 297 |
def mma_partition_A_vec(
|
| 298 |
+
sVec: cute.Tensor, thr_mma: cute.ThrMma, expand_shape: int, is_colvec: bool
|
| 299 |
) -> cute.Tensor:
|
| 300 |
assert cute.rank(sVec) == 2
|
| 301 |
assert sVec.stride[0] == 1
|
|
|
|
| 309 |
sVec_mma = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride))
|
| 310 |
tC_sVec = make_acc_tensor_mn_view(thr_mma.partition_A(sVec_mma))
|
| 311 |
return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def copy_partition_S_vec(
|
| 315 |
+
sVec: cute.Tensor, thr_copy: cute.ThrCopy, expand_shape: int, is_colvec: bool
|
| 316 |
+
) -> cute.Tensor:
|
| 317 |
+
assert cute.rank(sVec) == 2
|
| 318 |
+
assert sVec.stride[0] == 1
|
| 319 |
+
stage = sVec.shape[1]
|
| 320 |
+
shape = (
|
| 321 |
+
(sVec.shape[0], expand_shape, stage)
|
| 322 |
+
if const_expr(is_colvec)
|
| 323 |
+
else (expand_shape, sVec.shape[0], stage)
|
| 324 |
+
)
|
| 325 |
+
stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1])
|
| 326 |
+
sVec_thr = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride))
|
| 327 |
+
tC_sVec = reshape_acc_to_mn(thr_copy.partition_S(sVec_thr))
|
| 328 |
+
return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def copy_partition_D_vec(
|
| 332 |
+
sVec: cute.Tensor, thr_copy: cute.ThrCopy, expand_shape: int, is_colvec: bool
|
| 333 |
+
) -> cute.Tensor:
|
| 334 |
+
assert cute.rank(sVec) == 2
|
| 335 |
+
assert sVec.stride[0] == 1
|
| 336 |
+
stage = sVec.shape[1]
|
| 337 |
+
shape = (
|
| 338 |
+
(sVec.shape[0], expand_shape, stage)
|
| 339 |
+
if const_expr(is_colvec)
|
| 340 |
+
else (expand_shape, sVec.shape[0], stage)
|
| 341 |
+
)
|
| 342 |
+
stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1])
|
| 343 |
+
sVec_thr = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride))
|
| 344 |
+
tC_sVec = reshape_acc_to_mn(thr_copy.partition_D(sVec_thr))
|
| 345 |
+
return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def tile_atom_to_shape_SF_strided(
|
| 349 |
+
shape: cute.Shape,
|
| 350 |
+
sf_vec_size: int,
|
| 351 |
+
sf_strides,
|
| 352 |
+
) -> cute.Layout:
|
| 353 |
+
"""Build an SFA/SFB layout matching `shape` (A or B operand shape) but
|
| 354 |
+
honoring the scale tensor's actual strides instead of hardcoded packed
|
| 355 |
+
ones.
|
| 356 |
+
|
| 357 |
+
Mirrors `cutlass.utils.blockscaled_layout.tile_atom_to_shape_SF(shape,
|
| 358 |
+
sf_vec_size)`, except outer-mode strides come from `sf_strides` (pass
|
| 359 |
+
`mSFA.stride` / `mSFB.stride` directly). The inner 512-B atom
|
| 360 |
+
`((32, 4), (sf_vec_size, 4)) : ((16, 4), (0, 1))` is hardware-fixed.
|
| 361 |
+
|
| 362 |
+
Implementation uses `cute.blocked_product(atom, outer)`; `blocked_product`
|
| 363 |
+
scales the outer layout's strides by `cosize(atom) == 512`, so we divide
|
| 364 |
+
the byte strides by 512 (one tile) before handing them in.
|
| 365 |
+
|
| 366 |
+
Args:
|
| 367 |
+
shape: A/B operand shape. Rank-3 `(m/n, k, l)` or rank-2
|
| 368 |
+
`(total_mn, k)` (varlen_m).
|
| 369 |
+
sf_vec_size: Scale factor vector size (16 or 32).
|
| 370 |
+
sf_strides: Strides of the scale tensor, which has logical shape
|
| 371 |
+
`(L, rmn, rk, 512)` (rank 4). Only `sf_strides[0..2]` are used:
|
| 372 |
+
`sf_strides[1]` as the rmn stride, `sf_strides[2]` as the rk
|
| 373 |
+
stride, and `sf_strides[0]` as the L stride (only for rank-3
|
| 374 |
+
`shape`).
|
| 375 |
+
"""
|
| 376 |
+
from cutlass.utils.blockscaled_layout import BlockScaledBasicChunk
|
| 377 |
+
|
| 378 |
+
atom = BlockScaledBasicChunk(sf_vec_size).layout
|
| 379 |
+
rmn = cute.ceil_div(shape[0], 128)
|
| 380 |
+
rk = cute.ceil_div(shape[1], sf_vec_size * 4)
|
| 381 |
+
outer = cute.make_layout((rmn, rk), stride=(sf_strides[1] // 512, sf_strides[2] // 512))
|
| 382 |
+
sf_layout = cute.blocked_product(atom, outer)
|
| 383 |
+
if const_expr(len(shape) == 3):
|
| 384 |
+
sf_layout = cute.append(sf_layout, cute.make_layout(shape[2], stride=sf_strides[0]))
|
| 385 |
+
return sf_layout
|
build/torch-cuda/quack/rounding.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025-2026, Vijay Thakkar, Tri Dao.
|
| 2 |
+
"""Rounding mode control and stochastic rounding primitives for GEMM epilogues.
|
| 3 |
+
|
| 4 |
+
Provides a RoundingMode enum for configuring how epilogues downconvert the
|
| 5 |
+
accumulator dtype (typically FP32) to the output dtype before storing to gmem.
|
| 6 |
+
Stochastic rounding (RS) uses the hardware cvt.rs.satfinite.bf16x2.f32 PTX
|
| 7 |
+
instruction and is only supported on Blackwell (SM100+) GPUs.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from enum import IntEnum
|
| 11 |
+
|
| 12 |
+
import cutlass
|
| 13 |
+
import cutlass.cute as cute
|
| 14 |
+
from cutlass import Float32, Uint32
|
| 15 |
+
from cutlass._mlir import ir
|
| 16 |
+
from cutlass._mlir.dialects import llvm, vector
|
| 17 |
+
from cutlass.cutlass_dsl import dsl_user_op, Int32, T
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class RoundingMode(IntEnum):
|
| 21 |
+
"""Rounding modes for epilogue dtype downconversion.
|
| 22 |
+
|
| 23 |
+
RN — Round to nearest even (default hardware behavior)
|
| 24 |
+
RS — Stochastic rounding (SM100+ only, BF16 output only)
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
RN = 0
|
| 28 |
+
RS = 1
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
# Odd strides used to derive distinct Philox counters across output tiles and subtiles.
|
| 32 |
+
EPILOGUE_SR_SEED_M_STRIDE = 65537
|
| 33 |
+
EPILOGUE_SR_SEED_N_STRIDE = 257
|
| 34 |
+
EPILOGUE_SR_SEED_BATCH_STRIDE = 17
|
| 35 |
+
EPILOGUE_SR_SEED_SUBTILE_STRIDE = 7
|
| 36 |
+
EPILOGUE_SR_SEED_AUX_OUT_SALT = 0x9E3779B1
|
| 37 |
+
|
| 38 |
+
PHILOX_N_ROUNDS_DEFAULT = 7
|
| 39 |
+
|
| 40 |
+
PHILOX_ROUND_A = 0xD2511F53
|
| 41 |
+
PHILOX_ROUND_B = 0xCD9E8D57
|
| 42 |
+
PHILOX_KEY_A = 0x9E3779B9
|
| 43 |
+
PHILOX_KEY_B = 0xBB67AE85
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@dsl_user_op
|
| 47 |
+
def epilogue_sr_seed(
|
| 48 |
+
base_seed: Int32,
|
| 49 |
+
tile_coord_mnkl: cute.Coord,
|
| 50 |
+
subtile_idx,
|
| 51 |
+
*,
|
| 52 |
+
loc=None,
|
| 53 |
+
ip=None,
|
| 54 |
+
) -> Int32:
|
| 55 |
+
return base_seed + (
|
| 56 |
+
tile_coord_mnkl[0] * EPILOGUE_SR_SEED_M_STRIDE
|
| 57 |
+
+ tile_coord_mnkl[1] * EPILOGUE_SR_SEED_N_STRIDE
|
| 58 |
+
+ tile_coord_mnkl[3] * EPILOGUE_SR_SEED_BATCH_STRIDE
|
| 59 |
+
+ subtile_idx * EPILOGUE_SR_SEED_SUBTILE_STRIDE
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@dsl_user_op
|
| 64 |
+
def epilogue_aux_out_sr_seed(
|
| 65 |
+
base_seed: Int32,
|
| 66 |
+
tile_coord_mnkl: cute.Coord,
|
| 67 |
+
subtile_idx,
|
| 68 |
+
*,
|
| 69 |
+
loc=None,
|
| 70 |
+
ip=None,
|
| 71 |
+
) -> Int32:
|
| 72 |
+
return epilogue_sr_seed(
|
| 73 |
+
base_seed + EPILOGUE_SR_SEED_AUX_OUT_SALT,
|
| 74 |
+
tile_coord_mnkl,
|
| 75 |
+
subtile_idx,
|
| 76 |
+
loc=loc,
|
| 77 |
+
ip=ip,
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@dsl_user_op
|
| 82 |
+
def mul_wide_u32(a: Uint32, b: Uint32, *, loc=None, ip=None) -> tuple:
|
| 83 |
+
"""Unsigned 32b x 32b -> 64 wide multiply via PTX `mul.wide.u32`.
|
| 84 |
+
|
| 85 |
+
Returns (hi, lo) as a pair of Uint32 values.
|
| 86 |
+
"""
|
| 87 |
+
prod = cute.arch.mul_wide(Uint32(a), Uint32(b), loc=loc, ip=ip)
|
| 88 |
+
# ptxas folds the shift and to(Uint32) into the same IMAD.WIDE.U32 register pair
|
| 89 |
+
# that the previous inline PTX mov.b64 split produced.
|
| 90 |
+
hi = (prod >> 32).to(Uint32, loc=loc, ip=ip)
|
| 91 |
+
lo = prod.to(Uint32, loc=loc, ip=ip)
|
| 92 |
+
return hi, lo
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@dsl_user_op
|
| 96 |
+
def cvt_f32x2_bf16x2_rs(
|
| 97 |
+
a: Float32,
|
| 98 |
+
b: Float32,
|
| 99 |
+
rand_bits: Uint32,
|
| 100 |
+
*,
|
| 101 |
+
loc=None,
|
| 102 |
+
ip=None,
|
| 103 |
+
) -> cutlass.Int32:
|
| 104 |
+
"""Convert 2 FP32 values to packed BF16x2 using stochastic rounding.
|
| 105 |
+
|
| 106 |
+
Uses Blackwell PTX instruction: cvt.rs.satfinite.bf16x2.f32 dst, src_hi, src_lo, rand
|
| 107 |
+
"""
|
| 108 |
+
return cutlass.Int32(
|
| 109 |
+
llvm.inline_asm(
|
| 110 |
+
T.i32(),
|
| 111 |
+
[
|
| 112 |
+
Float32(a).ir_value(loc=loc, ip=ip),
|
| 113 |
+
Float32(b).ir_value(loc=loc, ip=ip),
|
| 114 |
+
Uint32(rand_bits).ir_value(loc=loc, ip=ip),
|
| 115 |
+
],
|
| 116 |
+
"cvt.rs.satfinite.bf16x2.f32 $0, $2, $1, $3;",
|
| 117 |
+
"=r,f,f,r",
|
| 118 |
+
has_side_effects=False,
|
| 119 |
+
is_align_stack=False,
|
| 120 |
+
)
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
@dsl_user_op
|
| 125 |
+
def philox(
|
| 126 |
+
counter: Uint32,
|
| 127 |
+
key: Uint32,
|
| 128 |
+
n_rounds: int = PHILOX_N_ROUNDS_DEFAULT,
|
| 129 |
+
*,
|
| 130 |
+
loc=None,
|
| 131 |
+
ip=None,
|
| 132 |
+
) -> tuple:
|
| 133 |
+
"""Philox 4x32b counter-based random number generator.
|
| 134 |
+
|
| 135 |
+
Given a 32b counter and a 32b key, returns four pseudo-random uint32 words
|
| 136 |
+
produced by running n_rounds of the Philox 4x32 bijection. Each round
|
| 137 |
+
performs two wide 32x32->64 multiplies with the Philox constants.
|
| 138 |
+
"""
|
| 139 |
+
c0 = Uint32(counter)
|
| 140 |
+
c1 = Uint32(0)
|
| 141 |
+
c2 = Uint32(0)
|
| 142 |
+
c3 = Uint32(0)
|
| 143 |
+
k0 = Uint32(key)
|
| 144 |
+
k1 = Uint32(0)
|
| 145 |
+
|
| 146 |
+
round_a = Uint32(PHILOX_ROUND_A)
|
| 147 |
+
round_b = Uint32(PHILOX_ROUND_B)
|
| 148 |
+
key_a = Uint32(PHILOX_KEY_A)
|
| 149 |
+
key_b = Uint32(PHILOX_KEY_B)
|
| 150 |
+
|
| 151 |
+
for _ in range(n_rounds):
|
| 152 |
+
hi_b, lo_b = mul_wide_u32(c2, round_b, loc=loc, ip=ip)
|
| 153 |
+
hi_a, lo_a = mul_wide_u32(c0, round_a, loc=loc, ip=ip)
|
| 154 |
+
c0 = hi_b ^ c1 ^ k0
|
| 155 |
+
c2 = hi_a ^ c3 ^ k1
|
| 156 |
+
c1 = lo_b
|
| 157 |
+
c3 = lo_a
|
| 158 |
+
k0 = k0 + key_a
|
| 159 |
+
k1 = k1 + key_b
|
| 160 |
+
|
| 161 |
+
return c0, c1, c2, c3
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@dsl_user_op
|
| 165 |
+
def convert_f32_to_bf16_sr(
|
| 166 |
+
src_vec,
|
| 167 |
+
seed: Int32,
|
| 168 |
+
tid: Int32,
|
| 169 |
+
*,
|
| 170 |
+
loc=None,
|
| 171 |
+
ip=None,
|
| 172 |
+
):
|
| 173 |
+
"""Convert an MLIR FP32 vector to BF16 with stochastic rounding.
|
| 174 |
+
|
| 175 |
+
Processes elements in pairs using Philox PRNG for entropy and the hardware
|
| 176 |
+
cvt.rs.satfinite.bf16x2.f32 instruction.
|
| 177 |
+
"""
|
| 178 |
+
src_vec_type = ir.VectorType(src_vec.type)
|
| 179 |
+
num_elems = src_vec_type.shape[0]
|
| 180 |
+
assert num_elems % 2 == 0, f"requires even number of elements, got {num_elems}"
|
| 181 |
+
num_pairs = num_elems // 2
|
| 182 |
+
assert num_pairs % 4 == 0, (
|
| 183 |
+
f"num_pairs must be divisible by 4 for stochastic rounding, got {num_pairs}"
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
dst_mlir_type = cutlass.BFloat16.mlir_type
|
| 187 |
+
dst_vec_type = ir.VectorType.get([num_elems], dst_mlir_type, loc=loc)
|
| 188 |
+
|
| 189 |
+
i32_vec_type = ir.VectorType.get([num_pairs], Int32.mlir_type, loc=loc)
|
| 190 |
+
i32_vec = llvm.mlir_undef(i32_vec_type, loc=loc, ip=ip)
|
| 191 |
+
|
| 192 |
+
for pair_idx in range(num_pairs):
|
| 193 |
+
lo_idx = pair_idx * 2
|
| 194 |
+
hi_idx = pair_idx * 2 + 1
|
| 195 |
+
|
| 196 |
+
src_lo = vector.extract(
|
| 197 |
+
src_vec,
|
| 198 |
+
dynamic_position=[],
|
| 199 |
+
static_position=[lo_idx],
|
| 200 |
+
loc=loc,
|
| 201 |
+
ip=ip,
|
| 202 |
+
)
|
| 203 |
+
src_hi = vector.extract(
|
| 204 |
+
src_vec,
|
| 205 |
+
dynamic_position=[],
|
| 206 |
+
static_position=[hi_idx],
|
| 207 |
+
loc=loc,
|
| 208 |
+
ip=ip,
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
group_idx = pair_idx // 4
|
| 212 |
+
intra_idx = pair_idx % 4
|
| 213 |
+
if intra_idx == 0:
|
| 214 |
+
counter = cutlass.Uint32(group_idx << 16) | cutlass.Uint32(tid)
|
| 215 |
+
rand_batch = philox(counter, cutlass.Uint32(seed))
|
| 216 |
+
|
| 217 |
+
entropy = rand_batch[intra_idx]
|
| 218 |
+
packed_i32 = cvt_f32x2_bf16x2_rs(Float32(src_lo), Float32(src_hi), entropy, loc=loc, ip=ip)
|
| 219 |
+
|
| 220 |
+
packed_i32_val = cutlass.Int32(packed_i32).ir_value(loc=loc, ip=ip)
|
| 221 |
+
i32_vec = vector.insert(
|
| 222 |
+
packed_i32_val,
|
| 223 |
+
i32_vec,
|
| 224 |
+
dynamic_position=[],
|
| 225 |
+
static_position=[pair_idx],
|
| 226 |
+
loc=loc,
|
| 227 |
+
ip=ip,
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
dst_vec = llvm.bitcast(dst_vec_type, i32_vec, loc=loc, ip=ip)
|
| 231 |
+
return dst_vec
|
build/torch-cuda/quack/sm90_utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
| 3 |
-
from typing import Type, Union, Optional
|
| 4 |
|
| 5 |
import cutlass
|
| 6 |
import cutlass.cute as cute
|
|
@@ -42,6 +42,38 @@ def make_smem_layout(
|
|
| 42 |
make_smem_layout_epi = make_smem_layout
|
| 43 |
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
@dsl_user_op
|
| 46 |
def partition_for_epilogue(
|
| 47 |
cT: cute.Tensor,
|
|
|
|
| 1 |
# Copyright (c) 2025, Tri Dao.
|
| 2 |
|
| 3 |
+
from typing import Literal, Type, Union, Optional
|
| 4 |
|
| 5 |
import cutlass
|
| 6 |
import cutlass.cute as cute
|
|
|
|
| 42 |
make_smem_layout_epi = make_smem_layout
|
| 43 |
|
| 44 |
|
| 45 |
+
def make_tiled_mma(
|
| 46 |
+
a_dtype: Type[Numeric],
|
| 47 |
+
a_major: Literal["K", "MN"],
|
| 48 |
+
b_major: Literal["K", "MN"],
|
| 49 |
+
tiler_n: int,
|
| 50 |
+
source: Literal["SS", "RS"] = "SS",
|
| 51 |
+
atom_layout_mnk: tuple = (1, 1, 1),
|
| 52 |
+
swap_AB: bool = False,
|
| 53 |
+
b_dtype: Optional[Type[Numeric]] = None,
|
| 54 |
+
acc_dtype: Type[Numeric] = Float32,
|
| 55 |
+
) -> cute.TiledMma:
|
| 56 |
+
"""`b_dtype` defaults to `a_dtype`; pass it for mixed-precision MMAs (e.g. fp8).
|
| 57 |
+
`acc_dtype` defaults to Float32."""
|
| 58 |
+
if b_dtype is None:
|
| 59 |
+
b_dtype = a_dtype
|
| 60 |
+
mode = {"K": cute.nvgpu.OperandMajorMode.K, "MN": cute.nvgpu.OperandMajorMode.MN}
|
| 61 |
+
a_mode, b_mode = mode[a_major], mode[b_major]
|
| 62 |
+
if swap_AB:
|
| 63 |
+
a_mode, b_mode = b_mode, a_mode
|
| 64 |
+
a_source = warpgroup.OperandSource.RMEM if source == "RS" else warpgroup.OperandSource.SMEM
|
| 65 |
+
return sm90_utils_og.make_trivial_tiled_mma(
|
| 66 |
+
a_dtype,
|
| 67 |
+
b_dtype,
|
| 68 |
+
a_mode,
|
| 69 |
+
b_mode,
|
| 70 |
+
acc_dtype,
|
| 71 |
+
atom_layout_mnk=atom_layout_mnk,
|
| 72 |
+
tiler_mn=(64, tiler_n),
|
| 73 |
+
a_source=a_source,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
@dsl_user_op
|
| 78 |
def partition_for_epilogue(
|
| 79 |
cT: cute.Tensor,
|
build/torch-cuda/quack/utils.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao.
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional, Tuple
|
| 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 llvm, vector
|
| 11 |
+
from cutlass.cutlass_dsl import T, dsl_user_op
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dsl_user_op
|
| 15 |
+
def elem_pointer(x: cute.Tensor, coord: cute.Coord, *, loc=None, ip=None) -> cute.Pointer:
|
| 16 |
+
return x.iterator + cute.crd2idx(coord, x.layout, loc=loc, ip=ip)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@cute.jit
|
| 20 |
+
def load_scalar_or_pointer(x, dtype=Float32):
|
| 21 |
+
if const_expr(isinstance(x, cute.Pointer)):
|
| 22 |
+
return dtype(cute.make_tensor(x, cute.make_layout(1))[0])
|
| 23 |
+
else:
|
| 24 |
+
return x
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@dsl_user_op
|
| 28 |
+
def set_block_rank(
|
| 29 |
+
smem_ptr: cute.Pointer, peer_cta_rank_in_cluster: Int32, *, loc=None, ip=None
|
| 30 |
+
) -> Int32:
|
| 31 |
+
"""Map the given smem pointer to the address at another CTA rank in the cluster."""
|
| 32 |
+
dsmem_ptr = cute.arch.map_dsmem_ptr(smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip)
|
| 33 |
+
return Int32(dsmem_ptr.toint(loc=loc, ip=ip))
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@dsl_user_op
|
| 37 |
+
def store_shared_remote(
|
| 38 |
+
val: float | Float32 | Int32 | cutlass.Int64,
|
| 39 |
+
smem_ptr: cute.Pointer,
|
| 40 |
+
mbar_ptr: cute.Pointer,
|
| 41 |
+
peer_cta_rank_in_cluster: cute.typing.Int,
|
| 42 |
+
*,
|
| 43 |
+
loc=None,
|
| 44 |
+
ip=None,
|
| 45 |
+
) -> None:
|
| 46 |
+
remote_smem_ptr_i32 = set_block_rank(smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip)
|
| 47 |
+
remote_mbar_ptr_i32 = set_block_rank(mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip)
|
| 48 |
+
if const_expr(isinstance(val, float)):
|
| 49 |
+
val = Float32(val)
|
| 50 |
+
assert isinstance(val, (Float32, Int32, cutlass.Int64)), "val must be Float32, Int32, or Int64"
|
| 51 |
+
suffix = {Float32: "f32", Int32: "s32", cutlass.Int64: "s64"}[type(val)]
|
| 52 |
+
cute.arch.inline_ptx(
|
| 53 |
+
f"st.async.shared::cluster.mbarrier::complete_tx::bytes.{suffix} "
|
| 54 |
+
"[{$r0}], {$r1}, [{$r2}];",
|
| 55 |
+
read_only_args=[remote_smem_ptr_i32, val, remote_mbar_ptr_i32],
|
| 56 |
+
loc=loc,
|
| 57 |
+
ip=ip,
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@dsl_user_op
|
| 62 |
+
def store_shared_remote_x4(
|
| 63 |
+
val0: Float32 | Int32,
|
| 64 |
+
val1: Float32 | Int32,
|
| 65 |
+
val2: Float32 | Int32,
|
| 66 |
+
val3: Float32 | Int32,
|
| 67 |
+
smem_ptr: cute.Pointer,
|
| 68 |
+
mbar_ptr: cute.Pointer,
|
| 69 |
+
peer_cta_rank_in_cluster: cute.typing.Int,
|
| 70 |
+
*,
|
| 71 |
+
loc=None,
|
| 72 |
+
ip=None,
|
| 73 |
+
) -> None:
|
| 74 |
+
remote_smem_ptr_i32 = set_block_rank(smem_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip)
|
| 75 |
+
remote_mbar_ptr_i32 = set_block_rank(mbar_ptr, peer_cta_rank_in_cluster, loc=loc, ip=ip)
|
| 76 |
+
assert isinstance(val0, (Float32, Int32)), "val must be Float32, or Int32"
|
| 77 |
+
dtype = Float32 if isinstance(val0, Float32) else Int32
|
| 78 |
+
suffix = {Float32: "f32", Int32: "s32"}[dtype]
|
| 79 |
+
cute.arch.inline_ptx(
|
| 80 |
+
"{\n\t"
|
| 81 |
+
f".reg .v4 .{suffix} abcd;\n\t"
|
| 82 |
+
f"mov.{suffix} abcd.x, {{$r2}};\n\t"
|
| 83 |
+
f"mov.{suffix} abcd.y, {{$r3}};\n\t"
|
| 84 |
+
f"mov.{suffix} abcd.z, {{$r4}};\n\t"
|
| 85 |
+
f"mov.{suffix} abcd.w, {{$r5}};\n\t"
|
| 86 |
+
f"st.async.shared::cluster.mbarrier::complete_tx::bytes.v4.{suffix} "
|
| 87 |
+
"[{$r0}], abcd, [{$r1}];\n\t"
|
| 88 |
+
"}\n",
|
| 89 |
+
read_only_args=[
|
| 90 |
+
remote_smem_ptr_i32,
|
| 91 |
+
remote_mbar_ptr_i32,
|
| 92 |
+
dtype(val0),
|
| 93 |
+
dtype(val1),
|
| 94 |
+
dtype(val2),
|
| 95 |
+
dtype(val3),
|
| 96 |
+
],
|
| 97 |
+
loc=loc,
|
| 98 |
+
ip=ip,
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@cute.jit
|
| 103 |
+
def fill_oob(tXsX: cute.Tensor, tXpX: Optional[cute.Tensor], fill_value: cute.Numeric) -> None:
|
| 104 |
+
"""Fill out-of-bounds values in shared memory tensor.
|
| 105 |
+
|
| 106 |
+
Args:
|
| 107 |
+
tXsX: Shared memory tensor to fill
|
| 108 |
+
tXpX: Predicate tensor indicating valid elements
|
| 109 |
+
fill_value: Value to fill OOB locations with
|
| 110 |
+
"""
|
| 111 |
+
tXrX_fill = cute.make_rmem_tensor_like(tXsX[(None, 0), None, 0])
|
| 112 |
+
tXrX_fill.fill(fill_value)
|
| 113 |
+
for rest_v in cutlass.range_constexpr(tXsX.shape[0][1]):
|
| 114 |
+
for rest_k in cutlass.range_constexpr(tXsX.shape[2]):
|
| 115 |
+
if const_expr(tXpX is not None):
|
| 116 |
+
if not tXpX[rest_v, 0, rest_k]:
|
| 117 |
+
cute.autovec_copy(tXrX_fill, tXsX[(None, rest_v), None, rest_k])
|
| 118 |
+
else:
|
| 119 |
+
cute.autovec_copy(tXrX_fill, tXsX[(None, rest_v), None, rest_k])
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
# ---------------------------------------------------------------------------
|
| 123 |
+
# General-purpose DSL store / vector helpers
|
| 124 |
+
# ---------------------------------------------------------------------------
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
@dsl_user_op
|
| 128 |
+
def make_vector(elem_type, *values, loc=None, ip=None):
|
| 129 |
+
"""Build an MLIR vector <N x elem_type> from N scalar DSL values.
|
| 130 |
+
|
| 131 |
+
Example: make_vector(cutlass.Uint32, v0, v1) -> <2 x i32> MLIR vector
|
| 132 |
+
"""
|
| 133 |
+
from cutlass._mlir import ir
|
| 134 |
+
|
| 135 |
+
n = len(values)
|
| 136 |
+
mlir_ty = elem_type.mlir_type
|
| 137 |
+
vec_ty = ir.VectorType.get([n], mlir_ty)
|
| 138 |
+
vec = llvm.mlir_undef(vec_ty, loc=loc, ip=ip)
|
| 139 |
+
for i, v in enumerate(values):
|
| 140 |
+
vec = vector.insert(
|
| 141 |
+
elem_type(v).ir_value(loc=loc, ip=ip),
|
| 142 |
+
vec,
|
| 143 |
+
dynamic_position=[],
|
| 144 |
+
static_position=[i],
|
| 145 |
+
loc=loc,
|
| 146 |
+
ip=ip,
|
| 147 |
+
)
|
| 148 |
+
return vec
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@dsl_user_op
|
| 152 |
+
def f32x2_to_i64(a: Float32, b: Float32, *, loc=None, ip=None) -> cutlass.Int64:
|
| 153 |
+
vec_f32x2 = vector.from_elements(
|
| 154 |
+
T.vector(2, T.f32()), (a.ir_value(), b.ir_value()), loc=loc, ip=ip
|
| 155 |
+
)
|
| 156 |
+
vec_i64x1 = vector.bitcast(T.vector(1, T.i64()), vec_f32x2)
|
| 157 |
+
res = cutlass.Int64(
|
| 158 |
+
vector.extract(vec_i64x1, dynamic_position=[], static_position=[0], loc=loc, ip=ip)
|
| 159 |
+
)
|
| 160 |
+
return res
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
@dsl_user_op
|
| 164 |
+
def i64_to_f32x2(c: cutlass.Int64, *, loc=None, ip=None) -> Tuple[Float32, Float32]:
|
| 165 |
+
vec_i64x1 = vector.from_elements(T.vector(1, T.i64()), (c.ir_value(),), loc=loc, ip=ip)
|
| 166 |
+
vec_f32x2 = vector.bitcast(T.vector(2, T.f32()), vec_i64x1)
|
| 167 |
+
res0 = Float32(
|
| 168 |
+
vector.extract(vec_f32x2, dynamic_position=[], static_position=[0], loc=loc, ip=ip)
|
| 169 |
+
)
|
| 170 |
+
res1 = Float32(
|
| 171 |
+
vector.extract(vec_f32x2, dynamic_position=[], static_position=[1], loc=loc, ip=ip)
|
| 172 |
+
)
|
| 173 |
+
return res0, res1
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
@cute.jit
|
| 177 |
+
def warp_prefix_sum(val: Int32, lane: Optional[Int32] = None) -> Int32:
|
| 178 |
+
if const_expr(lane is None):
|
| 179 |
+
lane = cute.arch.lane_idx()
|
| 180 |
+
for i in cutlass.range_constexpr(int(math.log2(cute.arch.WARP_SIZE))):
|
| 181 |
+
offset = 1 << i
|
| 182 |
+
# Very important that we set mask_and_clamp to 0
|
| 183 |
+
partial_sum = cute.arch.shuffle_sync_up(val, offset=offset, mask_and_clamp=0)
|
| 184 |
+
if lane >= offset:
|
| 185 |
+
val += partial_sum
|
| 186 |
+
return val
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
@dsl_user_op
|
| 190 |
+
def domain_offset_aligned(
|
| 191 |
+
coord: cute.Coord, tensor: cute.Tensor, *, loc=None, ip=None
|
| 192 |
+
) -> cute.Tensor:
|
| 193 |
+
assert isinstance(tensor.iterator, cute.Pointer)
|
| 194 |
+
# We assume that applying the offset does not change the pointer alignment
|
| 195 |
+
new_ptr = cute.make_ptr(
|
| 196 |
+
tensor.element_type,
|
| 197 |
+
elem_pointer(tensor, coord).toint(),
|
| 198 |
+
tensor.memspace,
|
| 199 |
+
assumed_align=tensor.iterator.alignment,
|
| 200 |
+
)
|
| 201 |
+
return cute.make_tensor(new_ptr, tensor.layout)
|