Instructions to use Efficient-Large-Model/Sol-H3-VAE with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use Efficient-Large-Model/Sol-H3-VAE with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("Efficient-Large-Model/Sol-H3-VAE", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
Generic sol_h3_vae package and sol_h3_vae.safetensors; GPU-specific FA4 guard moves to Sol-H3 in ComfyUI
Browse files- README.md +15 -9
- code/LICENSE.flash-attention +0 -29
- code/_spark_flash_fwd_guarded.py +0 -1246
- code/sol_h3_vae/__init__.py +4 -0
- code/{fast_h3_vae.py → sol_h3_vae/decoder.py} +26 -27
- code/{noncausal_fast3.py → sol_h3_vae/fast.py} +1 -1
- code/{noncausal_fast.py → sol_h3_vae/fast_base.py} +3 -3
- code/{fused_kernels.py → sol_h3_vae/kernels.py} +0 -0
- code/{noncausal_vit_decoder.py → sol_h3_vae/model.py} +1 -1
- code/{spark_compat.py → sol_h3_vae/template.py} +0 -0
- code/spark_decoder.py +0 -25
- code/spark_fa4.py +0 -22
- fast_h3_vae_noncausal_24l.safetensors → sol_h3_vae.safetensors +0 -0
README.md
CHANGED
|
@@ -29,10 +29,12 @@ as Sol-H3 runs it.
|
|
| 29 |
|
| 30 |
## Usage
|
| 31 |
|
| 32 |
-
Requirements: PyTorch with CUDA
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
| 36 |
|
| 37 |
```python
|
| 38 |
import sys
|
|
@@ -40,10 +42,10 @@ from huggingface_hub import hf_hub_download, snapshot_download
|
|
| 40 |
|
| 41 |
repo = snapshot_download("Efficient-Large-Model/Sol-H3-VAE")
|
| 42 |
sys.path.insert(0, f"{repo}/code")
|
| 43 |
-
from
|
| 44 |
|
| 45 |
vae_config = hf_hub_download("MiniMaxAI/MiniMax-H3", "vae/config.json")
|
| 46 |
-
decoder =
|
| 47 |
|
| 48 |
# z: normalized H3 video latents, (1, T, 24, H/16, W/16); T = 0 starts a native 5-latent block
|
| 49 |
pixels = decoder.decode(z) # (1, 3, F, H, W), the H3 VAE decoder's normalized pixel space
|
|
@@ -52,7 +54,12 @@ rgb = decoder.decode_rgb_uint8(z) # (F, H, W, 3) uint8 on the GPU
|
|
| 52 |
|
| 53 |
The decoder needs only the H3 VAE `config.json`, not the H3 VAE weights. The first call for each
|
| 54 |
latent shape compiles kernels and captures a CUDA graph (about 25 s on RTX 5090); later calls
|
| 55 |
-
reuse it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
## Limitations
|
| 58 |
|
|
@@ -64,7 +71,6 @@ reuse it. Pass `engine="ref"` for the plain PyTorch decoder (slower).
|
|
| 64 |
|
| 65 |
## License
|
| 66 |
|
| 67 |
-
The code is released under Apache-2.0
|
| 68 |
-
FlashAttention (BSD-3-Clause). The weights are distilled from the MiniMax-H3 VAE and are
|
| 69 |
subject to the
|
| 70 |
[MiniMax-H3 license](https://huggingface.co/MiniMaxAI/MiniMax-H3/blob/main/LICENSE).
|
|
|
|
| 29 |
|
| 30 |
## Usage
|
| 31 |
|
| 32 |
+
Requirements: PyTorch with CUDA and [Diffusers](https://github.com/huggingface/diffusers) with
|
| 33 |
+
MiniMax-H3 support. The fast engine (default) also needs Triton,
|
| 34 |
+
[FlashAttention-4](https://github.com/Dao-AILab/flash-attention) (`flash_attn.cute`) and
|
| 35 |
+
[QuACK](https://github.com/Dao-AILab/quack); `engine="ref"` runs in plain PyTorch without them.
|
| 36 |
+
Tested on RTX 5090 with PyTorch 2.12 (CUDA 13), Triton 3.7, Diffusers 0.40, FlashAttention-4
|
| 37 |
+
4.0.0b19 and QuACK 0.6.3.
|
| 38 |
|
| 39 |
```python
|
| 40 |
import sys
|
|
|
|
| 42 |
|
| 43 |
repo = snapshot_download("Efficient-Large-Model/Sol-H3-VAE")
|
| 44 |
sys.path.insert(0, f"{repo}/code")
|
| 45 |
+
from sol_h3_vae import SolH3VAEDecoder
|
| 46 |
|
| 47 |
vae_config = hf_hub_download("MiniMaxAI/MiniMax-H3", "vae/config.json")
|
| 48 |
+
decoder = SolH3VAEDecoder(vae_config, f"{repo}/sol_h3_vae.safetensors")
|
| 49 |
|
| 50 |
# z: normalized H3 video latents, (1, T, 24, H/16, W/16); T = 0 starts a native 5-latent block
|
| 51 |
pixels = decoder.decode(z) # (1, 3, F, H, W), the H3 VAE decoder's normalized pixel space
|
|
|
|
| 54 |
|
| 55 |
The decoder needs only the H3 VAE `config.json`, not the H3 VAE weights. The first call for each
|
| 56 |
latent shape compiles kernels and captures a CUDA graph (about 25 s on RTX 5090); later calls
|
| 57 |
+
reuse it.
|
| 58 |
+
|
| 59 |
+
On SM120 GPUs (RTX 5090, DGX Spark), FlashAttention-4 4.0.0b19 to 4.0.0b22 has a varlen bug that
|
| 60 |
+
makes the fast engine hit an illegal memory access. Apply the guard from
|
| 61 |
+
[Sol-H3 in ComfyUI](https://github.com/NVlabs/Sana/blob/sol-engine/models/minimax_h3/Sol-H3-ComfyUI/sol_h3_engine/fa4_sm120.py)
|
| 62 |
+
before the first decode, or use `engine="ref"`.
|
| 63 |
|
| 64 |
## Limitations
|
| 65 |
|
|
|
|
| 71 |
|
| 72 |
## License
|
| 73 |
|
| 74 |
+
The code is released under Apache-2.0. The weights are distilled from the MiniMax-H3 VAE and are
|
|
|
|
| 75 |
subject to the
|
| 76 |
[MiniMax-H3 license](https://huggingface.co/MiniMaxAI/MiniMax-H3/blob/main/LICENSE).
|
code/LICENSE.flash-attention
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
BSD 3-Clause License
|
| 2 |
-
|
| 3 |
-
Copyright (c) 2022, the respective contributors, as shown by the AUTHORS file.
|
| 4 |
-
All rights reserved.
|
| 5 |
-
|
| 6 |
-
Redistribution and use in source and binary forms, with or without
|
| 7 |
-
modification, are permitted provided that the following conditions are met:
|
| 8 |
-
|
| 9 |
-
* Redistributions of source code must retain the above copyright notice, this
|
| 10 |
-
list of conditions and the following disclaimer.
|
| 11 |
-
|
| 12 |
-
* Redistributions in binary form must reproduce the above copyright notice,
|
| 13 |
-
this list of conditions and the following disclaimer in the documentation
|
| 14 |
-
and/or other materials provided with the distribution.
|
| 15 |
-
|
| 16 |
-
* Neither the name of the copyright holder nor the names of its
|
| 17 |
-
contributors may be used to endorse or promote products derived from
|
| 18 |
-
this software without specific prior written permission.
|
| 19 |
-
|
| 20 |
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 21 |
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 22 |
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 23 |
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 24 |
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 25 |
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 26 |
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 27 |
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 28 |
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 29 |
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code/_spark_flash_fwd_guarded.py
DELETED
|
@@ -1,1246 +0,0 @@
|
|
| 1 |
-
# Copyright (c) 2025, Jay Shah, Ganesh Bikshandi, Ying Zhang, Vijay Thakkar, Pradeep Ramani, Tri Dao.
|
| 2 |
-
# A reimplementation of
|
| 3 |
-
# https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_fwd_kernel_sm80.h
|
| 4 |
-
# and https://github.com/Dao-AILab/flash-attention/blob/main/hopper/flash_fwd_kernel_sm90.h
|
| 5 |
-
# from Cutlass C++ to Cute-DSL.
|
| 6 |
-
# Built on Cute-DSL example: https://github.com/NVIDIA/cutlass/blob/main/examples/python/CuTeDSL/ampere/flash_attention_v2.py
|
| 7 |
-
|
| 8 |
-
import math
|
| 9 |
-
from types import SimpleNamespace
|
| 10 |
-
from typing import Type, Callable, Optional
|
| 11 |
-
from functools import partial
|
| 12 |
-
|
| 13 |
-
import cuda.bindings.driver as cuda
|
| 14 |
-
|
| 15 |
-
import cutlass
|
| 16 |
-
import cutlass.cute as cute
|
| 17 |
-
from cutlass import Float32, Int32, const_expr
|
| 18 |
-
from cutlass.cute.nvgpu import cpasync, warp
|
| 19 |
-
import cutlass.utils as utils_basic
|
| 20 |
-
from cutlass.base_dsl.arch import Arch
|
| 21 |
-
from cutlass.cutlass_dsl import BaseDSL
|
| 22 |
-
|
| 23 |
-
from quack import copy_utils
|
| 24 |
-
from quack import layout_utils
|
| 25 |
-
|
| 26 |
-
from flash_attn.cute import ampere_helpers as sm80_utils
|
| 27 |
-
from flash_attn.cute.cute_dsl_utils import assume_tensor_aligned
|
| 28 |
-
from flash_attn.cute import utils
|
| 29 |
-
from flash_attn.cute.mask import AttentionMask
|
| 30 |
-
from flash_attn.cute.softmax import Softmax, apply_score_mod_inner
|
| 31 |
-
from flash_attn.cute.seqlen_info import SeqlenInfoQK
|
| 32 |
-
from flash_attn.cute.block_info import BlockInfo
|
| 33 |
-
from flash_attn.cute.pack_gqa import PackGQA, pack_gqa_layout
|
| 34 |
-
from flash_attn.cute.named_barrier import NamedBarrierFwd
|
| 35 |
-
from flash_attn.cute.block_sparsity import BlockSparseTensors
|
| 36 |
-
from flash_attn.cute.tile_scheduler import SingleTileScheduler, SingleTileVarlenScheduler, TileSchedulerArguments
|
| 37 |
-
from flash_attn.cute.utils import AuxData
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
class FlashAttentionForwardBase:
|
| 41 |
-
|
| 42 |
-
def __init__(
|
| 43 |
-
self,
|
| 44 |
-
dtype: Type[cutlass.Numeric],
|
| 45 |
-
head_dim: int,
|
| 46 |
-
head_dim_v: Optional[int] = None,
|
| 47 |
-
qhead_per_kvhead: int = 1,
|
| 48 |
-
is_causal: bool = False,
|
| 49 |
-
is_local: bool = False,
|
| 50 |
-
pack_gqa: bool = True,
|
| 51 |
-
tile_m: int = 128,
|
| 52 |
-
tile_n: int = 128,
|
| 53 |
-
num_stages: int = 1,
|
| 54 |
-
num_threads: int = 128,
|
| 55 |
-
Q_in_regs: bool = False,
|
| 56 |
-
score_mod: Optional[cutlass.Constexpr] = None,
|
| 57 |
-
mask_mod: Optional[cutlass.Constexpr] = None,
|
| 58 |
-
has_aux_tensors: bool = False,
|
| 59 |
-
q_subtile_factor: int = 1,
|
| 60 |
-
):
|
| 61 |
-
"""Initializes the configuration for a flash attention kernel.
|
| 62 |
-
|
| 63 |
-
All contiguous dimensions must be at least 16 bytes aligned, which means that the head dimension
|
| 64 |
-
should be a multiple of 8.
|
| 65 |
-
|
| 66 |
-
:param head_dim: head dimension
|
| 67 |
-
:type head_dim: int
|
| 68 |
-
:param tile_m: m block size
|
| 69 |
-
:type tile_m: int
|
| 70 |
-
:param tile_n: n block size
|
| 71 |
-
:type tile_n: int
|
| 72 |
-
:param num_threads: number of threads
|
| 73 |
-
:type num_threads: int
|
| 74 |
-
:param is_causal: is causal
|
| 75 |
-
:param score_mod: A callable that takes the attention scores and applies a modification.
|
| 76 |
-
Callable signature: ``score_mod(scores, batch_idx, head_idx, q_idx, kv_idx, aux_tensors) -> Any``
|
| 77 |
-
:param mask_mod: A callable that takes the attention scores and returns a boolean representing whether that score should be masked.
|
| 78 |
-
Callable signature: ``mask_mod(batch_idx, head_idx, q_idx, kv_idx, aux_tensors) -> Boolean``
|
| 79 |
-
"""
|
| 80 |
-
self.dtype = dtype
|
| 81 |
-
# padding head_dim to a multiple of 16 as k_block_size
|
| 82 |
-
hdim_multiple_of = 16
|
| 83 |
-
self.tile_hdim = int(math.ceil(head_dim / hdim_multiple_of) * hdim_multiple_of)
|
| 84 |
-
head_dim_v = head_dim_v if head_dim_v is not None else head_dim
|
| 85 |
-
self.same_hdim_kv = head_dim == head_dim_v
|
| 86 |
-
self.tile_hdimv = int(math.ceil(head_dim_v / hdim_multiple_of) * hdim_multiple_of)
|
| 87 |
-
# Can save registers (and hence be faster) if we don't have to check hdim predication
|
| 88 |
-
self.check_hdim_oob = head_dim != self.tile_hdim
|
| 89 |
-
self.check_hdim_v_oob = head_dim_v != self.tile_hdimv
|
| 90 |
-
self.qhead_per_kvhead = qhead_per_kvhead
|
| 91 |
-
self.is_causal = is_causal
|
| 92 |
-
self.is_local = is_local
|
| 93 |
-
self.pack_gqa = pack_gqa
|
| 94 |
-
self.tile_m = tile_m
|
| 95 |
-
self.tile_n = tile_n
|
| 96 |
-
self.num_threads = num_threads
|
| 97 |
-
self.num_stages = num_stages
|
| 98 |
-
self.q_subtile_factor = q_subtile_factor
|
| 99 |
-
self.Q_in_regs = Q_in_regs
|
| 100 |
-
self.score_mod = score_mod
|
| 101 |
-
self.mask_mod = mask_mod
|
| 102 |
-
self.qk_acc_dtype = Float32
|
| 103 |
-
self.score_vec_size: cutlass.Constexpr = getattr(
|
| 104 |
-
score_mod, "__vec_size__", 1 if cutlass.const_expr(has_aux_tensors) else 2
|
| 105 |
-
)
|
| 106 |
-
if self.score_vec_size > 2:
|
| 107 |
-
raise ValueError(
|
| 108 |
-
f"score_mod vec_size {self.score_vec_size} not supported on Sm80/90/120 "
|
| 109 |
-
"due to accumulator thread ownership pattern."
|
| 110 |
-
)
|
| 111 |
-
self.mask_vec_size: cutlass.Constexpr = getattr(mask_mod, "__vec_size__", 1)
|
| 112 |
-
if self.mask_vec_size > 1:
|
| 113 |
-
raise ValueError(
|
| 114 |
-
f"mask_mod vec_size {self.mask_vec_size} not supported on Sm80/90/120 "
|
| 115 |
-
"due to accumulator thread ownership pattern."
|
| 116 |
-
)
|
| 117 |
-
self.arch = BaseDSL._get_dsl().get_arch_enum()
|
| 118 |
-
|
| 119 |
-
@staticmethod
|
| 120 |
-
def can_implement(
|
| 121 |
-
dtype,
|
| 122 |
-
head_dim,
|
| 123 |
-
head_dim_v,
|
| 124 |
-
tile_m,
|
| 125 |
-
tile_n,
|
| 126 |
-
num_stages,
|
| 127 |
-
num_threads,
|
| 128 |
-
is_causal,
|
| 129 |
-
Q_in_regs=False,
|
| 130 |
-
) -> bool:
|
| 131 |
-
"""Check if the kernel can be implemented with the given parameters.
|
| 132 |
-
|
| 133 |
-
:param dtype: data type
|
| 134 |
-
:type dtype: cutlass.Numeric
|
| 135 |
-
:param head_dim: head dimension
|
| 136 |
-
:type head_dim: int
|
| 137 |
-
:param tile_m: m block size
|
| 138 |
-
:type tile_m: int
|
| 139 |
-
:param tile_n: n block size
|
| 140 |
-
:type tile_n: int
|
| 141 |
-
:param num_threads: number of threads
|
| 142 |
-
:type num_threads: int
|
| 143 |
-
:param is_causal: is causal
|
| 144 |
-
:type is_causal: bool
|
| 145 |
-
|
| 146 |
-
:return: True if the kernel can be implemented, False otherwise
|
| 147 |
-
:rtype: bool
|
| 148 |
-
"""
|
| 149 |
-
if dtype not in [cutlass.Float16, cutlass.BFloat16]:
|
| 150 |
-
return False
|
| 151 |
-
if head_dim % 8 != 0:
|
| 152 |
-
return False
|
| 153 |
-
if head_dim_v % 8 != 0:
|
| 154 |
-
return False
|
| 155 |
-
if tile_n % 16 != 0:
|
| 156 |
-
return False
|
| 157 |
-
if num_threads % 32 != 0:
|
| 158 |
-
return False
|
| 159 |
-
# Check if block size setting is out of shared memory capacity
|
| 160 |
-
# Shared memory usage: Q tile + (K tile + V tile) where K and V use the same tile size
|
| 161 |
-
smem_usage_Q = tile_m * head_dim * 2
|
| 162 |
-
smem_usage_K = tile_n * head_dim * num_stages * 2
|
| 163 |
-
smem_usage_V = tile_n * head_dim_v * num_stages * 2
|
| 164 |
-
smem_usage_QV = (
|
| 165 |
-
(smem_usage_Q + smem_usage_V) if not Q_in_regs else max(smem_usage_Q, smem_usage_V)
|
| 166 |
-
)
|
| 167 |
-
smem_usage = smem_usage_QV + smem_usage_K
|
| 168 |
-
# TODO: sm86 and sm89
|
| 169 |
-
smem_capacity = utils_basic.get_smem_capacity_in_bytes("sm_80")
|
| 170 |
-
if smem_usage > smem_capacity:
|
| 171 |
-
return False
|
| 172 |
-
# Check if twice the block size is divisible by the number of threads
|
| 173 |
-
if (tile_m * 2) % num_threads != 0:
|
| 174 |
-
return False
|
| 175 |
-
return True
|
| 176 |
-
|
| 177 |
-
def _check_type(
|
| 178 |
-
self,
|
| 179 |
-
mQ_type: Type[cutlass.Numeric],
|
| 180 |
-
mK_type: Type[cutlass.Numeric],
|
| 181 |
-
mV_type: Type[cutlass.Numeric],
|
| 182 |
-
mO_type: Type[cutlass.Numeric],
|
| 183 |
-
mLSE_type: Type[cutlass.Numeric] | None,
|
| 184 |
-
mCuSeqlensQ_type: Type[cutlass.Numeric] | None,
|
| 185 |
-
mCuSeqlensK_type: Type[cutlass.Numeric] | None,
|
| 186 |
-
mSeqUsedQ_type: Type[cutlass.Numeric] | None,
|
| 187 |
-
mSeqUsedK_type: Type[cutlass.Numeric] | None,
|
| 188 |
-
):
|
| 189 |
-
# Get the data type and check if it is fp16 or bf16
|
| 190 |
-
if const_expr(not (mQ_type == mK_type == mV_type == mO_type)):
|
| 191 |
-
raise TypeError("All tensors must have the same data type")
|
| 192 |
-
if const_expr(mQ_type not in [cutlass.Float16, cutlass.BFloat16]):
|
| 193 |
-
raise TypeError("Only Float16 or BFloat16 is supported")
|
| 194 |
-
if const_expr(mLSE_type not in [None, Float32]):
|
| 195 |
-
raise TypeError("LSE tensor must be Float32")
|
| 196 |
-
if const_expr(mCuSeqlensQ_type not in [None, Int32]):
|
| 197 |
-
raise TypeError("cu_seqlens_q tensor must be Int32")
|
| 198 |
-
if const_expr(mCuSeqlensK_type not in [None, Int32]):
|
| 199 |
-
raise TypeError("cu_seqlens_k tensor must be Int32")
|
| 200 |
-
if const_expr(mSeqUsedQ_type not in [None, Int32]):
|
| 201 |
-
raise TypeError("seqused_q tensor must be Int32")
|
| 202 |
-
if const_expr(mSeqUsedK_type not in [None, Int32]):
|
| 203 |
-
raise TypeError("seqused_k tensor must be Int32")
|
| 204 |
-
assert mQ_type == self.dtype
|
| 205 |
-
|
| 206 |
-
def _setup_attributes(self):
|
| 207 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 208 |
-
# Shared memory layout: Q/K/V
|
| 209 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 210 |
-
sQ_layout_atom, sK_layout_atom, sV_layout_atom, sO_layout_atom, sP_layout_atom = (
|
| 211 |
-
self._get_smem_layout_atom()
|
| 212 |
-
)
|
| 213 |
-
self.sQ_layout = cute.tile_to_shape(
|
| 214 |
-
sQ_layout_atom,
|
| 215 |
-
(self.tile_m, self.tile_hdim),
|
| 216 |
-
(0, 1),
|
| 217 |
-
)
|
| 218 |
-
self.sK_layout = cute.tile_to_shape(
|
| 219 |
-
sK_layout_atom,
|
| 220 |
-
(self.tile_n, self.tile_hdim, self.num_stages),
|
| 221 |
-
(0, 1, 2),
|
| 222 |
-
)
|
| 223 |
-
self.sV_layout = cute.tile_to_shape(
|
| 224 |
-
sV_layout_atom,
|
| 225 |
-
(self.tile_n, self.tile_hdimv, self.num_stages),
|
| 226 |
-
(0, 1, 2),
|
| 227 |
-
)
|
| 228 |
-
self.sO_layout = cute.tile_to_shape(
|
| 229 |
-
sO_layout_atom,
|
| 230 |
-
(self.tile_m, self.tile_hdimv),
|
| 231 |
-
(0, 1),
|
| 232 |
-
)
|
| 233 |
-
if const_expr(sP_layout_atom is not None):
|
| 234 |
-
self.sP_layout = cute.tile_to_shape(
|
| 235 |
-
sP_layout_atom,
|
| 236 |
-
(self.tile_m, self.tile_n),
|
| 237 |
-
(0, 1),
|
| 238 |
-
)
|
| 239 |
-
else:
|
| 240 |
-
self.sP_layout = None
|
| 241 |
-
|
| 242 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 243 |
-
# GMEM Tiled copy:
|
| 244 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 245 |
-
# Thread layouts for copies
|
| 246 |
-
universal_copy_bits = 128
|
| 247 |
-
async_copy_elems = universal_copy_bits // self.dtype.width
|
| 248 |
-
# atom_async_copy: async copy atom for QKV load
|
| 249 |
-
atom_async_copy = cute.make_copy_atom(
|
| 250 |
-
cpasync.CopyG2SOp(cache_mode=cpasync.LoadCacheMode.GLOBAL),
|
| 251 |
-
self.dtype,
|
| 252 |
-
num_bits_per_copy=universal_copy_bits,
|
| 253 |
-
)
|
| 254 |
-
# atom_universal_copy: universal copy atom for O store
|
| 255 |
-
atom_universal_copy = cute.make_copy_atom(
|
| 256 |
-
cute.nvgpu.CopyUniversalOp(),
|
| 257 |
-
self.dtype,
|
| 258 |
-
num_bits_per_copy=universal_copy_bits,
|
| 259 |
-
)
|
| 260 |
-
# tQ_layout and tK_layout: thread layout for QK load
|
| 261 |
-
tQK_shape_dim_1 = sQ_layout_atom.outer.shape[1] // async_copy_elems
|
| 262 |
-
assert self.num_Q_load_threads % tQK_shape_dim_1 == 0, (
|
| 263 |
-
"num_threads must be divisible by tQK_shape_dim_1"
|
| 264 |
-
)
|
| 265 |
-
assert self.num_producer_threads % tQK_shape_dim_1 == 0, (
|
| 266 |
-
"num_threads must be divisible by tQK_shape_dim_1"
|
| 267 |
-
)
|
| 268 |
-
tQ_layout = cute.make_ordered_layout(
|
| 269 |
-
(self.num_Q_load_threads // tQK_shape_dim_1, tQK_shape_dim_1),
|
| 270 |
-
order=(1, 0),
|
| 271 |
-
)
|
| 272 |
-
tK_layout = cute.make_ordered_layout(
|
| 273 |
-
(self.num_producer_threads // tQK_shape_dim_1, tQK_shape_dim_1),
|
| 274 |
-
order=(1, 0),
|
| 275 |
-
)
|
| 276 |
-
# So that we don't have to check if we overshoot kBlockM when we load Q
|
| 277 |
-
assert self.tile_m % tQ_layout.shape[0] == 0
|
| 278 |
-
tV_shape_dim_1 = sV_layout_atom.outer.shape[1] // async_copy_elems
|
| 279 |
-
tV_layout = cute.make_ordered_layout(
|
| 280 |
-
(self.num_producer_threads // tV_shape_dim_1, tV_shape_dim_1),
|
| 281 |
-
order=(1, 0),
|
| 282 |
-
)
|
| 283 |
-
# TODO: need a different layout for O if O dtype is not the same as V dtype
|
| 284 |
-
# tO_layout: thread layout for O store
|
| 285 |
-
tO_layout = cute.make_ordered_layout(
|
| 286 |
-
(self.num_epilogue_threads // tV_shape_dim_1, tV_shape_dim_1),
|
| 287 |
-
order=(1, 0),
|
| 288 |
-
)
|
| 289 |
-
# So that we don't have to check if we overshoot kBlockM when we store O
|
| 290 |
-
assert self.tile_m % tO_layout.shape[0] == 0
|
| 291 |
-
|
| 292 |
-
# Value layouts for copies
|
| 293 |
-
vQKV_layout = cute.make_layout((1, async_copy_elems))
|
| 294 |
-
vO_layout = vQKV_layout
|
| 295 |
-
|
| 296 |
-
self.gmem_tiled_copy_Q = cute.make_tiled_copy_tv(atom_async_copy, tQ_layout, vQKV_layout)
|
| 297 |
-
self.gmem_tiled_copy_K = cute.make_tiled_copy_tv(atom_async_copy, tK_layout, vQKV_layout)
|
| 298 |
-
self.gmem_tiled_copy_V = cute.make_tiled_copy_tv(atom_async_copy, tV_layout, vQKV_layout)
|
| 299 |
-
# gmem_tiled_copy_O: tiled copy for O store
|
| 300 |
-
self.gmem_tiled_copy_O = cute.make_tiled_copy_tv(atom_universal_copy, tO_layout, vO_layout)
|
| 301 |
-
|
| 302 |
-
def _get_smem_layout_atom(self):
|
| 303 |
-
raise NotImplementedError()
|
| 304 |
-
|
| 305 |
-
def _get_tiled_mma(self):
|
| 306 |
-
raise NotImplementedError()
|
| 307 |
-
|
| 308 |
-
def _get_shared_storage_cls(self):
|
| 309 |
-
raise NotImplementedError()
|
| 310 |
-
|
| 311 |
-
@cute.jit
|
| 312 |
-
def __call__(
|
| 313 |
-
self,
|
| 314 |
-
mQ: cute.Tensor,
|
| 315 |
-
mK: cute.Tensor,
|
| 316 |
-
mV: cute.Tensor,
|
| 317 |
-
mO: cute.Tensor,
|
| 318 |
-
mLSE: Optional[cute.Tensor],
|
| 319 |
-
softmax_scale: Float32,
|
| 320 |
-
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 321 |
-
stream: cuda.CUstream = None,
|
| 322 |
-
):
|
| 323 |
-
"""Configures and launches the flash attention kernel.
|
| 324 |
-
|
| 325 |
-
mQ/mK/mV/mO has same data types(supports fp16 and bf16) and same layout:
|
| 326 |
-
(batch_size, seqlen_q, num_head, head_dim):(_, _, _, 1)
|
| 327 |
-
"""
|
| 328 |
-
raise NotImplementedError()
|
| 329 |
-
|
| 330 |
-
@cute.jit
|
| 331 |
-
def epilogue(
|
| 332 |
-
self,
|
| 333 |
-
acc_O: cute.Tensor,
|
| 334 |
-
lse: cute.Tensor,
|
| 335 |
-
mO: cute.Tensor,
|
| 336 |
-
mLSE: Optional[cute.Tensor],
|
| 337 |
-
sO: cute.Tensor,
|
| 338 |
-
seqlen: SeqlenInfoQK,
|
| 339 |
-
gmem_tiled_copy_O: cute.TiledCopy,
|
| 340 |
-
tma_atom_O: Optional[cute.CopyAtom],
|
| 341 |
-
tiled_mma: cute.TiledMma,
|
| 342 |
-
tidx: Int32,
|
| 343 |
-
m_block: Int32,
|
| 344 |
-
head_idx: Int32,
|
| 345 |
-
batch_idx: Int32,
|
| 346 |
-
):
|
| 347 |
-
# store acc_O
|
| 348 |
-
rO = cute.make_fragment_like(acc_O, self.dtype)
|
| 349 |
-
rO.store(acc_O.load().to(self.dtype))
|
| 350 |
-
# Make sure all threads have finished reading V
|
| 351 |
-
cute.arch.barrier(
|
| 352 |
-
barrier_id=int(NamedBarrierFwd.Epilogue), number_of_threads=self.num_epilogue_threads
|
| 353 |
-
)
|
| 354 |
-
smem_copy_atom_O = utils.get_smem_store_atom(self.arch.major * 10 + self.arch.minor, self.dtype)
|
| 355 |
-
smem_thr_copy_O = cute.make_tiled_copy_C(smem_copy_atom_O, tiled_mma).get_slice(tidx)
|
| 356 |
-
taccOrO = smem_thr_copy_O.retile(rO)
|
| 357 |
-
taccOsO = smem_thr_copy_O.partition_D(sO)
|
| 358 |
-
# taccOsO = copy_utils.partition_D_position_independent(smem_thr_copy_O, sO)
|
| 359 |
-
# copy acc O from rmem to smem with the smem copy atom
|
| 360 |
-
cute.copy(smem_copy_atom_O, taccOrO, taccOsO)
|
| 361 |
-
|
| 362 |
-
cO = cute.make_identity_tensor((self.tile_m, self.tile_hdimv))
|
| 363 |
-
pack_gqa = PackGQA(
|
| 364 |
-
self.tile_m, self.tile_hdimv, self.check_hdim_v_oob, self.qhead_per_kvhead
|
| 365 |
-
)
|
| 366 |
-
|
| 367 |
-
# Write LSE from rmem -> gmem
|
| 368 |
-
if const_expr(mLSE is not None):
|
| 369 |
-
mLSE_cur = seqlen.offset_batch_Q(mLSE, batch_idx, dim=2)[None, head_idx]
|
| 370 |
-
if const_expr(not self.pack_gqa):
|
| 371 |
-
gLSE = cute.local_tile(mLSE_cur, (self.tile_m,), (m_block,))
|
| 372 |
-
gLSE_expanded_layout = cute.append(
|
| 373 |
-
gLSE.layout, cute.make_layout((self.tile_hdimv,), stride=(0,))
|
| 374 |
-
)
|
| 375 |
-
gLSE_expanded = cute.make_tensor(gLSE.iterator, gLSE_expanded_layout)
|
| 376 |
-
thr_mma = tiled_mma.get_slice(tidx)
|
| 377 |
-
taccOgLSE = layout_utils.reshape_acc_to_mn(thr_mma.partition_C(gLSE_expanded))
|
| 378 |
-
assert cute.size(taccOgLSE, mode=[0]) == cute.size(lse)
|
| 379 |
-
taccOcO = layout_utils.reshape_acc_to_mn(thr_mma.partition_C(cO))
|
| 380 |
-
t0accOcO = layout_utils.reshape_acc_to_mn(thr_mma.get_slice(0).partition_C(cO))
|
| 381 |
-
# Only the thread corresponding to column 0 writes out the lse to gmem
|
| 382 |
-
if taccOcO[0][1] == 0:
|
| 383 |
-
for m in cutlass.range(cute.size(taccOgLSE.shape[1]), unroll_full=True):
|
| 384 |
-
if (
|
| 385 |
-
t0accOcO[m, 0][0]
|
| 386 |
-
< seqlen.seqlen_q - m_block * self.tile_m - taccOcO[0][0]
|
| 387 |
-
):
|
| 388 |
-
taccOgLSE[m, 0] = lse[m]
|
| 389 |
-
else:
|
| 390 |
-
pack_gqa.store_LSE(mLSE_cur, lse, tiled_mma, tidx, m_block, seqlen.seqlen_q)
|
| 391 |
-
|
| 392 |
-
ragged = self.use_tma_O and (seqlen.has_cu_seqlens_q or seqlen.has_seqused_q)
|
| 393 |
-
mO_cur = seqlen.offset_batch_Q(mO, batch_idx, dim=3, ragged=ragged)[None, None, head_idx]
|
| 394 |
-
# thr_mma = tiled_mma.get_slice(tidx)
|
| 395 |
-
# taccOgO = thr_mma.partition_C(gO)
|
| 396 |
-
# cute.autovec_copy(rO, taccOgO)
|
| 397 |
-
# sync to make sure all smem stores are done
|
| 398 |
-
if const_expr(self.use_tma_O):
|
| 399 |
-
# ensure smem writes are visible to TMA
|
| 400 |
-
cute.arch.fence_view_async_shared()
|
| 401 |
-
cute.arch.barrier_arrive(
|
| 402 |
-
barrier_id=int(NamedBarrierFwd.Epilogue),
|
| 403 |
-
number_of_threads=self.num_epilogue_threads + cute.arch.WARP_SIZE,
|
| 404 |
-
)
|
| 405 |
-
gO = cute.local_tile(mO_cur, (self.tile_m, self.tile_hdimv), (m_block, 0))
|
| 406 |
-
store_O, _, _ = copy_utils.tma_get_copy_fn(
|
| 407 |
-
tma_atom_O, 0, cute.make_layout(1), sO, gO, single_stage=True
|
| 408 |
-
)
|
| 409 |
-
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx())
|
| 410 |
-
if warp_idx == 4:
|
| 411 |
-
cute.arch.barrier(
|
| 412 |
-
barrier_id=int(NamedBarrierFwd.Epilogue),
|
| 413 |
-
number_of_threads=self.num_epilogue_threads + cute.arch.WARP_SIZE,
|
| 414 |
-
)
|
| 415 |
-
store_O()
|
| 416 |
-
cute.arch.cp_async_bulk_commit_group()
|
| 417 |
-
cute.arch.cp_async_bulk_wait_group(0, read=True)
|
| 418 |
-
else:
|
| 419 |
-
cute.arch.barrier(
|
| 420 |
-
barrier_id=int(NamedBarrierFwd.Epilogue),
|
| 421 |
-
number_of_threads=self.num_epilogue_threads,
|
| 422 |
-
)
|
| 423 |
-
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
| 424 |
-
tOsO = gmem_thr_copy_O.partition_S(sO)
|
| 425 |
-
tOrO = cute.make_fragment_like(tOsO, self.dtype)
|
| 426 |
-
# load acc O from smem to rmem for wider vectorization
|
| 427 |
-
cute.autovec_copy(tOsO, tOrO)
|
| 428 |
-
if const_expr(not self.pack_gqa):
|
| 429 |
-
gO = cute.local_tile(mO_cur, (self.tile_m, self.tile_hdimv), (m_block, 0))
|
| 430 |
-
tOgO = gmem_thr_copy_O.partition_D(gO)
|
| 431 |
-
tOcO = gmem_thr_copy_O.partition_S(cO)
|
| 432 |
-
t0OcO = gmem_tiled_copy_O.get_slice(0).partition_S(cO)
|
| 433 |
-
tOpO = utils.predicate_k(tOcO, limit=mO.shape[1])
|
| 434 |
-
# copy acc O from rmem to gmem
|
| 435 |
-
for rest_m in cutlass.range_constexpr(cute.size(tOrO.shape[1])):
|
| 436 |
-
if (
|
| 437 |
-
t0OcO[0, rest_m, 0][0]
|
| 438 |
-
< seqlen.seqlen_q - m_block * self.tile_m - tOcO[0][0]
|
| 439 |
-
):
|
| 440 |
-
cute.copy(
|
| 441 |
-
gmem_tiled_copy_O,
|
| 442 |
-
tOrO[None, rest_m, None],
|
| 443 |
-
tOgO[None, rest_m, None],
|
| 444 |
-
pred=tOpO[None, rest_m, None]
|
| 445 |
-
if const_expr(self.check_hdim_v_oob)
|
| 446 |
-
else None,
|
| 447 |
-
)
|
| 448 |
-
else:
|
| 449 |
-
pack_gqa.store_O(mO_cur, tOrO, gmem_tiled_copy_O, tidx, m_block, seqlen.seqlen_q)
|
| 450 |
-
|
| 451 |
-
@cute.jit
|
| 452 |
-
def advance_pipeline(self, pipeline_index):
|
| 453 |
-
return pipeline_index + 1 if pipeline_index < self.num_stages - 1 else 0
|
| 454 |
-
|
| 455 |
-
@cute.jit
|
| 456 |
-
def load_Q(
|
| 457 |
-
self,
|
| 458 |
-
gmem_thr_copy: cute.TiledCopy,
|
| 459 |
-
gQ: cute.Tensor,
|
| 460 |
-
sQ: cute.Tensor,
|
| 461 |
-
block: Int32,
|
| 462 |
-
seqlen: Int32,
|
| 463 |
-
headdim: Int32,
|
| 464 |
-
):
|
| 465 |
-
tQsQ, tQgQ = gmem_thr_copy.partition_D(sQ), gmem_thr_copy.partition_S(gQ)
|
| 466 |
-
cQ = cute.make_identity_tensor((self.tile_m, self.tile_hdim))
|
| 467 |
-
tQcQ = gmem_thr_copy.partition_S(cQ)
|
| 468 |
-
t0QcQ = gmem_thr_copy.get_slice(0).partition_S(cQ)
|
| 469 |
-
tQpQ = utils.predicate_k(tQcQ, limit=headdim)
|
| 470 |
-
for m in cutlass.range_constexpr(cute.size(tQsQ.shape[1])):
|
| 471 |
-
# Instead of using tQcQ, we using t0QcQ and subtract the offset from the limit
|
| 472 |
-
# (seqlen - block * kBlockM). This is because the entries of t0QcQ are known at compile time.
|
| 473 |
-
if t0QcQ[0, m, 0][0] < seqlen - block * self.tile_m - tQcQ[0][0]:
|
| 474 |
-
cute.copy(
|
| 475 |
-
gmem_thr_copy,
|
| 476 |
-
tQgQ[None, m, None],
|
| 477 |
-
tQsQ[None, m, None],
|
| 478 |
-
pred=tQpQ[None, m, None] if const_expr(self.check_hdim_oob) else None,
|
| 479 |
-
)
|
| 480 |
-
# We don't need to clear the sQ smem tiles since we'll only write out the valid outputs
|
| 481 |
-
|
| 482 |
-
@cute.jit
|
| 483 |
-
def load_K(
|
| 484 |
-
self,
|
| 485 |
-
gmem_tiled_copy: cute.TiledCopy,
|
| 486 |
-
tKgK: cute.Tensor,
|
| 487 |
-
tKsK: cute.Tensor,
|
| 488 |
-
tKcK: cute.Tensor,
|
| 489 |
-
t0KcK: cute.Tensor,
|
| 490 |
-
tKpK: cute.Tensor,
|
| 491 |
-
block: Int32,
|
| 492 |
-
smem_pipe_write: Int32,
|
| 493 |
-
seqlen: Int32,
|
| 494 |
-
need_predicates: cutlass.Constexpr,
|
| 495 |
-
):
|
| 496 |
-
# Do we need to check if we overshoot kBlockN when we load K?
|
| 497 |
-
is_even_n_smem_k = self.tile_n % gmem_tiled_copy.tiler_mn[0].shape == 0
|
| 498 |
-
if const_expr(need_predicates or not is_even_n_smem_k):
|
| 499 |
-
# Instead of using tKcK, we using t0KcK and subtract the offset from the limit
|
| 500 |
-
# (seqlen - block * kBlockN). This is because the entries of t0KcK are known at compile time.
|
| 501 |
-
if const_expr(is_even_n_smem_k):
|
| 502 |
-
seqlen_limit = seqlen - block * self.tile_n
|
| 503 |
-
else:
|
| 504 |
-
if const_expr(not need_predicates):
|
| 505 |
-
seqlen_limit = self.tile_n
|
| 506 |
-
else:
|
| 507 |
-
seqlen_limit = cutlass.min(seqlen - block * self.tile_n, self.tile_n)
|
| 508 |
-
seqlen_limit -= tKcK[0][0]
|
| 509 |
-
for n in cutlass.range_constexpr(cute.size(tKsK.shape[1])):
|
| 510 |
-
if t0KcK[0, n, 0][0] < seqlen_limit:
|
| 511 |
-
cute.copy(
|
| 512 |
-
gmem_tiled_copy,
|
| 513 |
-
tKgK[None, n, None, block],
|
| 514 |
-
tKsK[
|
| 515 |
-
None, n, None, smem_pipe_write if const_expr(self.num_stages > 1) else 0
|
| 516 |
-
],
|
| 517 |
-
pred=tKpK[None, n, None] if const_expr(self.check_hdim_oob) else None,
|
| 518 |
-
)
|
| 519 |
-
# We don't need to clear the sK smem tiles since we'll mask out the scores anyway.
|
| 520 |
-
else:
|
| 521 |
-
cute.copy(
|
| 522 |
-
gmem_tiled_copy,
|
| 523 |
-
tKgK[None, None, None, block],
|
| 524 |
-
tKsK[None, None, None, smem_pipe_write if const_expr(self.num_stages > 1) else 0],
|
| 525 |
-
pred=tKpK if const_expr(self.check_hdim_oob) else None,
|
| 526 |
-
)
|
| 527 |
-
|
| 528 |
-
@cute.jit
|
| 529 |
-
def load_V(
|
| 530 |
-
self,
|
| 531 |
-
gmem_tiled_copy: cute.TiledCopy,
|
| 532 |
-
tVgV: cute.Tensor,
|
| 533 |
-
tVsV: cute.Tensor,
|
| 534 |
-
tVcV: cute.Tensor,
|
| 535 |
-
t0VcV: cute.Tensor,
|
| 536 |
-
tVpV: cute.Tensor,
|
| 537 |
-
block: Int32,
|
| 538 |
-
smem_pipe_write: Int32,
|
| 539 |
-
seqlen: Int32,
|
| 540 |
-
need_predicates: cutlass.Constexpr,
|
| 541 |
-
):
|
| 542 |
-
# Do we need to check if we overshoot kBlockN when we load V?
|
| 543 |
-
is_even_n_smem_v = self.tile_n % gmem_tiled_copy.tiler_mn[0].shape == 0
|
| 544 |
-
if const_expr(need_predicates or not is_even_n_smem_v):
|
| 545 |
-
for n in cutlass.range_constexpr(cute.size(tVsV.shape[1])):
|
| 546 |
-
# If kBlockN doesn't evenly divide the tiled copy, only the last `n` needs to be checked
|
| 547 |
-
if (
|
| 548 |
-
is_even_n_smem_v
|
| 549 |
-
or n < cute.size(tVsV.shape[1]) - 1
|
| 550 |
-
or tVcV[0, n, 0][0] < self.tile_n
|
| 551 |
-
):
|
| 552 |
-
predicate = tVpV[None, n, None] if const_expr(self.check_hdim_v_oob) else None
|
| 553 |
-
if const_expr(need_predicates):
|
| 554 |
-
seqlen_limit = seqlen - block * self.tile_n - tVcV[0][0]
|
| 555 |
-
predicate_n = t0VcV[0, n, 0][0] < seqlen_limit
|
| 556 |
-
predicate = cute.make_fragment_like(tVpV[None, 0, None])
|
| 557 |
-
for k in cutlass.range_constexpr(cute.size(predicate.shape[1])):
|
| 558 |
-
for i in cutlass.range_constexpr(cute.size(predicate.shape[0])):
|
| 559 |
-
predicate[i, k] = (
|
| 560 |
-
tVpV[i, n, k] if const_expr(self.check_hdim_v_oob) else True
|
| 561 |
-
) and predicate_n
|
| 562 |
-
cute.copy(
|
| 563 |
-
gmem_tiled_copy,
|
| 564 |
-
tVgV[None, n, None, block],
|
| 565 |
-
tVsV[
|
| 566 |
-
None, n, None, smem_pipe_write if const_expr(self.num_stages > 1) else 0
|
| 567 |
-
],
|
| 568 |
-
pred=predicate,
|
| 569 |
-
)
|
| 570 |
-
else:
|
| 571 |
-
cute.copy(
|
| 572 |
-
gmem_tiled_copy,
|
| 573 |
-
tVgV[None, None, None, block],
|
| 574 |
-
tVsV[None, None, None, smem_pipe_write if const_expr(self.num_stages > 1) else 0],
|
| 575 |
-
pred=tVpV if const_expr(self.check_hdim_v_oob) else None,
|
| 576 |
-
)
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
class FlashAttentionForwardSm80(FlashAttentionForwardBase):
|
| 580 |
-
def _get_smem_layout_atom(self):
|
| 581 |
-
sQ_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, self.tile_hdim)
|
| 582 |
-
sK_layout_atom = sQ_layout_atom
|
| 583 |
-
sV_layout_atom = sm80_utils.get_smem_layout_atom(self.dtype, self.tile_hdimv)
|
| 584 |
-
sO_layout_atom = sV_layout_atom
|
| 585 |
-
sP_layout_atom = None
|
| 586 |
-
return sQ_layout_atom, sK_layout_atom, sV_layout_atom, sO_layout_atom, sP_layout_atom
|
| 587 |
-
|
| 588 |
-
def _get_tiled_mma(self):
|
| 589 |
-
tiled_mma_qk = cute.make_tiled_mma(
|
| 590 |
-
warp.MmaF16BF16Op(self.dtype, Float32, (16, 8, 16)),
|
| 591 |
-
(self.num_threads // 32, 1, 1),
|
| 592 |
-
permutation_mnk=(self.num_threads // 32 * 16, 16, 16),
|
| 593 |
-
)
|
| 594 |
-
tiled_mma_pv = cute.make_tiled_mma(
|
| 595 |
-
warp.MmaF16BF16Op(self.dtype, Float32, (16, 8, 16)),
|
| 596 |
-
(self.num_threads // 32, 1, 1),
|
| 597 |
-
permutation_mnk=(self.num_threads // 32 * 16, 16, 16),
|
| 598 |
-
)
|
| 599 |
-
return tiled_mma_qk, tiled_mma_pv
|
| 600 |
-
|
| 601 |
-
def _get_shared_storage_cls(self):
|
| 602 |
-
sQ_struct, sK_struct, sV_struct = [
|
| 603 |
-
cute.struct.Align[cute.struct.MemRange[self.dtype, cute.cosize(layout)], 1024]
|
| 604 |
-
for layout in (self.sQ_layout, self.sK_layout, self.sV_layout)
|
| 605 |
-
]
|
| 606 |
-
cosize_sQV = max(cute.cosize(self.sQ_layout), cute.cosize(self.sV_layout))
|
| 607 |
-
sQV_struct = cute.struct.Align[cute.struct.MemRange[self.dtype, cosize_sQV], 1024]
|
| 608 |
-
|
| 609 |
-
@cute.struct
|
| 610 |
-
class SharedStorageQKV:
|
| 611 |
-
sV: sV_struct
|
| 612 |
-
sQ: sQ_struct
|
| 613 |
-
sK: sK_struct
|
| 614 |
-
|
| 615 |
-
@cute.struct
|
| 616 |
-
class SharedStorageSharedQV:
|
| 617 |
-
sQ: sQV_struct
|
| 618 |
-
sK: sK_struct
|
| 619 |
-
|
| 620 |
-
return SharedStorageQKV if const_expr(not self.Q_in_regs) else SharedStorageSharedQV
|
| 621 |
-
|
| 622 |
-
@cute.jit
|
| 623 |
-
def __call__(
|
| 624 |
-
self,
|
| 625 |
-
mQ: cute.Tensor,
|
| 626 |
-
mK: cute.Tensor,
|
| 627 |
-
mV: cute.Tensor,
|
| 628 |
-
mO: cute.Tensor,
|
| 629 |
-
mLSE: Optional[cute.Tensor],
|
| 630 |
-
softmax_scale: Float32,
|
| 631 |
-
mCuSeqlensQ: Optional[cute.Tensor] = None,
|
| 632 |
-
mCuSeqlensK: Optional[cute.Tensor] = None,
|
| 633 |
-
mSeqUsedQ: Optional[cute.Tensor] = None,
|
| 634 |
-
mSeqUsedK: Optional[cute.Tensor] = None,
|
| 635 |
-
mPageTable: Optional[cute.Tensor] = None,
|
| 636 |
-
window_size_left: Int32 | int | None = None,
|
| 637 |
-
window_size_right: Int32 | int | None = None,
|
| 638 |
-
learnable_sink: Optional[cute.Tensor] = None,
|
| 639 |
-
blocksparse_tensors: Optional[BlockSparseTensors] = None,
|
| 640 |
-
aux_data: AuxData = AuxData(),
|
| 641 |
-
# Always keep stream as the last parameter (EnvStream: obtained implicitly via TVM FFI).
|
| 642 |
-
stream: cuda.CUstream = None,
|
| 643 |
-
):
|
| 644 |
-
"""Configures and launches the flash attention kernel.
|
| 645 |
-
|
| 646 |
-
mQ/mK/mV/mO has same data types(supports fp16 and bf16) and same layout:
|
| 647 |
-
(batch_size, seqlen_q, num_head, head_dim):(_, _, _, 1)
|
| 648 |
-
"""
|
| 649 |
-
assert learnable_sink is None, "Learnable sink is not supported in this kernel"
|
| 650 |
-
self._check_type(
|
| 651 |
-
*(t.element_type if t is not None else None for t in (mQ, mK, mV, mO, mLSE, mCuSeqlensQ, mCuSeqlensK, mSeqUsedQ, mSeqUsedK))
|
| 652 |
-
)
|
| 653 |
-
tiled_mma_qk, tiled_mma_pv = self._get_tiled_mma()
|
| 654 |
-
self.num_mma_threads = tiled_mma_pv.size
|
| 655 |
-
self.num_producer_threads = self.num_threads
|
| 656 |
-
self.num_Q_load_threads = self.num_threads
|
| 657 |
-
self.num_epilogue_threads = self.num_threads
|
| 658 |
-
self.use_tma_O = Arch.sm_90 <= self.arch < Arch.sm_120
|
| 659 |
-
self._setup_attributes()
|
| 660 |
-
SharedStorage = self._get_shared_storage_cls()
|
| 661 |
-
mQ, mK, mV, mO = [assume_tensor_aligned(t) for t in (mQ, mK, mV, mO)]
|
| 662 |
-
# Layout permutation: 4D non-varlen vs 3D varlen
|
| 663 |
-
QO_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensQ is None) else [0, 2, 1]
|
| 664 |
-
KV_layout_transpose = [1, 3, 2, 0] if const_expr(mCuSeqlensK is None) else [0, 2, 1]
|
| 665 |
-
mQ, mO = [
|
| 666 |
-
cute.make_tensor(t.iterator, cute.select(t.layout, mode=QO_layout_transpose))
|
| 667 |
-
for t in (mQ, mO)
|
| 668 |
-
]
|
| 669 |
-
mK, mV = [
|
| 670 |
-
cute.make_tensor(t.iterator, cute.select(t.layout, mode=KV_layout_transpose))
|
| 671 |
-
for t in (mK, mV)
|
| 672 |
-
]
|
| 673 |
-
if const_expr(mLSE is not None):
|
| 674 |
-
LSE_layout_transpose = [2, 1, 0] if const_expr(mCuSeqlensQ is None) else [1, 0]
|
| 675 |
-
mLSE = cute.make_tensor(mLSE.iterator, cute.select(mLSE.layout, mode=LSE_layout_transpose))
|
| 676 |
-
if const_expr(self.pack_gqa):
|
| 677 |
-
nheads_kv = mK.shape[2]
|
| 678 |
-
mQ = pack_gqa_layout(mQ, self.qhead_per_kvhead, nheads_kv, head_idx=2)
|
| 679 |
-
mO = pack_gqa_layout(mO, self.qhead_per_kvhead, nheads_kv, head_idx=2)
|
| 680 |
-
if const_expr(mLSE is not None):
|
| 681 |
-
mLSE = pack_gqa_layout(mLSE, self.qhead_per_kvhead, nheads_kv, head_idx=1)
|
| 682 |
-
# TileScheduler for varlen, simple grid for non-varlen
|
| 683 |
-
if const_expr(mCuSeqlensQ is not None or mSeqUsedQ is not None):
|
| 684 |
-
TileScheduler = SingleTileVarlenScheduler
|
| 685 |
-
else:
|
| 686 |
-
TileScheduler = SingleTileScheduler
|
| 687 |
-
num_batch = (
|
| 688 |
-
mCuSeqlensQ.shape[0] - 1
|
| 689 |
-
if const_expr(mCuSeqlensQ is not None)
|
| 690 |
-
else cute.size(mQ.shape[3])
|
| 691 |
-
)
|
| 692 |
-
tile_sched_args = TileSchedulerArguments(
|
| 693 |
-
num_block=cute.ceil_div(cute.size(mQ.shape[0]), self.tile_m),
|
| 694 |
-
num_head=cute.size(mQ.shape[2]),
|
| 695 |
-
num_batch=num_batch,
|
| 696 |
-
num_splits=1,
|
| 697 |
-
seqlen_k=0,
|
| 698 |
-
headdim=mQ.shape[1],
|
| 699 |
-
headdim_v=mV.shape[1],
|
| 700 |
-
total_q=cute.size(mQ.shape[0])
|
| 701 |
-
if const_expr(mCuSeqlensQ is not None)
|
| 702 |
-
else cute.size(mQ.shape[0]) * cute.size(mQ.shape[3]),
|
| 703 |
-
tile_shape_mn=(self.tile_m, self.tile_n),
|
| 704 |
-
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 705 |
-
mCuSeqlensQ=mCuSeqlensQ,
|
| 706 |
-
mSeqUsedQ=mSeqUsedQ,
|
| 707 |
-
)
|
| 708 |
-
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
|
| 709 |
-
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
|
| 710 |
-
softmax_scale_log2, softmax_scale = utils.compute_softmax_scale_log2(softmax_scale, self.score_mod)
|
| 711 |
-
fastdiv_mods = utils.compute_fastdiv_mods(mQ, mK, self.qhead_per_kvhead, self.pack_gqa, aux_data.tensors)
|
| 712 |
-
|
| 713 |
-
self.kernel(
|
| 714 |
-
mQ,
|
| 715 |
-
mK,
|
| 716 |
-
mV,
|
| 717 |
-
mO,
|
| 718 |
-
mLSE,
|
| 719 |
-
mCuSeqlensQ,
|
| 720 |
-
mCuSeqlensK,
|
| 721 |
-
mSeqUsedQ,
|
| 722 |
-
mSeqUsedK,
|
| 723 |
-
softmax_scale_log2,
|
| 724 |
-
softmax_scale,
|
| 725 |
-
window_size_left,
|
| 726 |
-
window_size_right,
|
| 727 |
-
self.sQ_layout,
|
| 728 |
-
self.sK_layout,
|
| 729 |
-
self.sV_layout,
|
| 730 |
-
self.sO_layout,
|
| 731 |
-
self.sP_layout,
|
| 732 |
-
self.gmem_tiled_copy_Q,
|
| 733 |
-
self.gmem_tiled_copy_K,
|
| 734 |
-
self.gmem_tiled_copy_V,
|
| 735 |
-
self.gmem_tiled_copy_O,
|
| 736 |
-
tiled_mma_qk,
|
| 737 |
-
tiled_mma_pv,
|
| 738 |
-
SharedStorage,
|
| 739 |
-
tile_sched_params,
|
| 740 |
-
TileScheduler,
|
| 741 |
-
aux_data,
|
| 742 |
-
fastdiv_mods,
|
| 743 |
-
).launch(
|
| 744 |
-
grid=grid_dim,
|
| 745 |
-
block=[self.num_threads, 1, 1],
|
| 746 |
-
smem=SharedStorage.size_in_bytes(),
|
| 747 |
-
stream=stream,
|
| 748 |
-
)
|
| 749 |
-
|
| 750 |
-
@cute.kernel
|
| 751 |
-
def kernel(
|
| 752 |
-
self,
|
| 753 |
-
mQ: cute.Tensor,
|
| 754 |
-
mK: cute.Tensor,
|
| 755 |
-
mV: cute.Tensor,
|
| 756 |
-
mO: cute.Tensor,
|
| 757 |
-
mLSE: Optional[cute.Tensor],
|
| 758 |
-
mCuSeqlensQ: Optional[cute.Tensor],
|
| 759 |
-
mCuSeqlensK: Optional[cute.Tensor],
|
| 760 |
-
mSeqUsedQ: Optional[cute.Tensor],
|
| 761 |
-
mSeqUsedK: Optional[cute.Tensor],
|
| 762 |
-
softmax_scale_log2: Float32,
|
| 763 |
-
softmax_scale: Optional[Float32],
|
| 764 |
-
window_size_left: Optional[Int32],
|
| 765 |
-
window_size_right: Optional[Int32],
|
| 766 |
-
sQ_layout: cute.ComposedLayout,
|
| 767 |
-
sK_layout: cute.ComposedLayout,
|
| 768 |
-
sV_layout: cute.ComposedLayout,
|
| 769 |
-
sO_layout: cute.ComposedLayout,
|
| 770 |
-
sP_layout: cute.ComposedLayout | None,
|
| 771 |
-
gmem_tiled_copy_Q: cute.TiledCopy,
|
| 772 |
-
gmem_tiled_copy_K: cute.TiledCopy,
|
| 773 |
-
gmem_tiled_copy_V: cute.TiledCopy,
|
| 774 |
-
gmem_tiled_copy_O: cute.TiledCopy,
|
| 775 |
-
tiled_mma_qk: cute.TiledMma,
|
| 776 |
-
tiled_mma_pv: cute.TiledMma,
|
| 777 |
-
SharedStorage: cutlass.Constexpr,
|
| 778 |
-
tile_sched_params,
|
| 779 |
-
TileScheduler: cutlass.Constexpr[Callable],
|
| 780 |
-
aux_data: AuxData = AuxData(),
|
| 781 |
-
fastdiv_mods=None,
|
| 782 |
-
):
|
| 783 |
-
# Thread index, block index
|
| 784 |
-
tidx, _, _ = cute.arch.thread_idx()
|
| 785 |
-
|
| 786 |
-
tile_scheduler = TileScheduler.create(tile_sched_params)
|
| 787 |
-
work_tile = tile_scheduler.initial_work_tile_info()
|
| 788 |
-
m_block, num_head, batch_size, _ = work_tile.tile_idx
|
| 789 |
-
|
| 790 |
-
# Spark fix: surplus scheduler tiles must not read sequence metadata.
|
| 791 |
-
if work_tile.is_valid_tile:
|
| 792 |
-
|
| 793 |
-
block_info = BlockInfo(
|
| 794 |
-
self.tile_m,
|
| 795 |
-
self.tile_n,
|
| 796 |
-
self.is_causal,
|
| 797 |
-
self.is_local,
|
| 798 |
-
False, # is_split_kv
|
| 799 |
-
window_size_left,
|
| 800 |
-
window_size_right,
|
| 801 |
-
qhead_per_kvhead_packgqa=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 802 |
-
)
|
| 803 |
-
seqlen = SeqlenInfoQK.create(
|
| 804 |
-
batch_idx=batch_size,
|
| 805 |
-
seqlen_q_static=mQ.shape[0] if const_expr(not self.pack_gqa) else mQ.shape[0][1],
|
| 806 |
-
seqlen_k_static=mK.shape[0],
|
| 807 |
-
mCuSeqlensQ=mCuSeqlensQ,
|
| 808 |
-
mCuSeqlensK=mCuSeqlensK,
|
| 809 |
-
mSeqUsedQ=mSeqUsedQ,
|
| 810 |
-
mSeqUsedK=mSeqUsedK,
|
| 811 |
-
)
|
| 812 |
-
n_block_min, n_block_max = block_info.get_n_block_min_max(seqlen, m_block)
|
| 813 |
-
# For varlen, wasted grid tiles (where batch_idx >= num_batch) will have
|
| 814 |
-
# seqlen_q=seqlen_k=0 and n_block_max=0. Clamp to 0 so we don't use a
|
| 815 |
-
# negative block index for K/V loads; the load/store predicates already
|
| 816 |
-
# guard all memory accesses when seqlen is 0.
|
| 817 |
-
n_block = cutlass.max(n_block_max - 1, 0)
|
| 818 |
-
|
| 819 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 820 |
-
# Get the appropriate tiles for this thread block.
|
| 821 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 822 |
-
blkQ_shape = (self.tile_m, self.tile_hdim)
|
| 823 |
-
blkK_shape = (self.tile_n, self.tile_hdim)
|
| 824 |
-
blkV_shape = (self.tile_n, self.tile_hdimv)
|
| 825 |
-
num_head_kv = num_head if const_expr(self.pack_gqa) else num_head // self.qhead_per_kvhead
|
| 826 |
-
mQ_cur = seqlen.offset_batch_Q(mQ, batch_size, dim=3)[None, None, num_head]
|
| 827 |
-
if const_expr(not seqlen.has_cu_seqlens_k):
|
| 828 |
-
mK_cur = mK[None, None, num_head_kv, batch_size]
|
| 829 |
-
mV_cur = mV[None, None, num_head_kv, batch_size]
|
| 830 |
-
else:
|
| 831 |
-
mK_cur = cute.domain_offset((seqlen.offset_k, 0), mK[None, None, num_head_kv])
|
| 832 |
-
mV_cur = cute.domain_offset((seqlen.offset_k, 0), mV[None, None, num_head_kv])
|
| 833 |
-
if const_expr(not self.pack_gqa):
|
| 834 |
-
gQ = cute.local_tile(mQ_cur, blkQ_shape, (m_block, 0))
|
| 835 |
-
gK = cute.local_tile(mK_cur, blkK_shape, (None, 0))
|
| 836 |
-
gV = cute.local_tile(mV_cur, blkV_shape, (None, 0))
|
| 837 |
-
|
| 838 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 839 |
-
# Get shared memory buffer
|
| 840 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 841 |
-
smem = cutlass.utils.SmemAllocator()
|
| 842 |
-
storage = smem.allocate(SharedStorage)
|
| 843 |
-
sQ = storage.sQ.get_tensor(sQ_layout)
|
| 844 |
-
sK = storage.sK.get_tensor(sK_layout)
|
| 845 |
-
if const_expr(not self.Q_in_regs):
|
| 846 |
-
sV = storage.sV.get_tensor(sV_layout)
|
| 847 |
-
else:
|
| 848 |
-
sV = cute.make_tensor(cute.recast_ptr(sQ.iterator, dtype=self.dtype), sV_layout)
|
| 849 |
-
# Transpose view of V to tensor with layout (head_dim_v, tile_n) for tiled mma
|
| 850 |
-
sVt = layout_utils.transpose_view(sV)
|
| 851 |
-
|
| 852 |
-
gmem_thr_copy_K = gmem_tiled_copy_K.get_slice(tidx)
|
| 853 |
-
gmem_thr_copy_V = gmem_tiled_copy_V.get_slice(tidx)
|
| 854 |
-
# (CPY_Atom, CPY_N, CPY_K, n_block)
|
| 855 |
-
tKsK, tKgK = gmem_thr_copy_K.partition_D(sK), gmem_thr_copy_K.partition_S(gK)
|
| 856 |
-
# (CPY_Atom, CPY_N, CPY_K, n_block)
|
| 857 |
-
tVsV, tVgV = gmem_thr_copy_V.partition_D(sV), gmem_thr_copy_V.partition_S(gV)
|
| 858 |
-
|
| 859 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 860 |
-
# Tile MMA compute thread partitions and allocate accumulators
|
| 861 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 862 |
-
thr_mma_qk = tiled_mma_qk.get_slice(tidx)
|
| 863 |
-
thr_mma_pv = tiled_mma_pv.get_slice(tidx)
|
| 864 |
-
tSrQ = thr_mma_qk.make_fragment_A(thr_mma_qk.partition_A(sQ))
|
| 865 |
-
tSrK = thr_mma_qk.make_fragment_B(thr_mma_qk.partition_B(sK[None, None, 0]))
|
| 866 |
-
tOrVt = thr_mma_pv.make_fragment_B(thr_mma_pv.partition_B(sVt[None, None, 0]))
|
| 867 |
-
acc_shape_O = thr_mma_pv.partition_shape_C((self.tile_m, self.tile_hdimv))
|
| 868 |
-
acc_O = cute.make_rmem_tensor(acc_shape_O, Float32)
|
| 869 |
-
acc_O.fill(0.0)
|
| 870 |
-
|
| 871 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 872 |
-
# Smem copy atom tiling
|
| 873 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 874 |
-
smem_copy_atom_QK = cute.make_copy_atom(
|
| 875 |
-
warp.LdMatrix8x8x16bOp(transpose=False, num_matrices=4),
|
| 876 |
-
self.dtype,
|
| 877 |
-
)
|
| 878 |
-
smem_copy_atom_V = cute.make_copy_atom(
|
| 879 |
-
warp.LdMatrix8x8x16bOp(transpose=True, num_matrices=4),
|
| 880 |
-
self.dtype,
|
| 881 |
-
)
|
| 882 |
-
smem_thr_copy_Q = utils.make_tiled_copy_A(smem_copy_atom_QK, tiled_mma_qk).get_slice(tidx)
|
| 883 |
-
smem_thr_copy_K = utils.make_tiled_copy_B(smem_copy_atom_QK, tiled_mma_qk).get_slice(tidx)
|
| 884 |
-
smem_thr_copy_V = utils.make_tiled_copy_B(smem_copy_atom_V, tiled_mma_pv).get_slice(tidx)
|
| 885 |
-
|
| 886 |
-
tSsQ = smem_thr_copy_Q.partition_S(sQ)
|
| 887 |
-
tSsK = smem_thr_copy_K.partition_S(sK)
|
| 888 |
-
tOsVt = smem_thr_copy_V.partition_S(sVt)
|
| 889 |
-
|
| 890 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 891 |
-
# Predicate: Mark indices that need to copy when problem_shape isn't a multiple
|
| 892 |
-
# of tile_shape
|
| 893 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 894 |
-
# Construct identity layout for KV
|
| 895 |
-
cK = cute.make_identity_tensor((self.tile_n, self.tile_hdim))
|
| 896 |
-
tKcK = gmem_thr_copy_K.partition_S(cK)
|
| 897 |
-
t0KcK = gmem_thr_copy_K.get_slice(0).partition_S(cK)
|
| 898 |
-
if const_expr(self.tile_hdim == self.tile_hdimv):
|
| 899 |
-
tVcV = tKcK
|
| 900 |
-
t0VcV = t0KcK
|
| 901 |
-
else:
|
| 902 |
-
cV = cute.make_identity_tensor((self.tile_n, self.tile_hdimv))
|
| 903 |
-
tVcV = gmem_thr_copy_V.partition_S(cV)
|
| 904 |
-
t0VcV = gmem_thr_copy_V.get_slice(0).partition_S(cV)
|
| 905 |
-
# Allocate predicate tensors for m and n, here we only allocate the tile of k, and
|
| 906 |
-
# use "if" on the mn dimension.
|
| 907 |
-
# This is to reduce register pressure and gets 2-3% performance gain.
|
| 908 |
-
tKpK = utils.predicate_k(tKcK, limit=mK.shape[1])
|
| 909 |
-
if const_expr(self.same_hdim_kv):
|
| 910 |
-
tVpV = tKpK
|
| 911 |
-
else:
|
| 912 |
-
tVpV = utils.predicate_k(tVcV, limit=mV.shape[1])
|
| 913 |
-
|
| 914 |
-
# shape: (atom_v_m * rest_m)
|
| 915 |
-
softmax = Softmax.create(
|
| 916 |
-
softmax_scale_log2,
|
| 917 |
-
num_rows=acc_O.shape[0][0] * acc_O.shape[1],
|
| 918 |
-
softmax_scale=softmax_scale,
|
| 919 |
-
)
|
| 920 |
-
softmax.reset()
|
| 921 |
-
|
| 922 |
-
# group parameters for compute_one_n_block
|
| 923 |
-
mma_params = SimpleNamespace(
|
| 924 |
-
thr_mma_qk=thr_mma_qk,
|
| 925 |
-
thr_mma_pv=thr_mma_pv,
|
| 926 |
-
tSrQ=tSrQ,
|
| 927 |
-
tSrK=tSrK,
|
| 928 |
-
tOrVt=tOrVt,
|
| 929 |
-
acc_O=acc_O,
|
| 930 |
-
)
|
| 931 |
-
smem_copy_params = SimpleNamespace(
|
| 932 |
-
smem_thr_copy_Q=smem_thr_copy_Q,
|
| 933 |
-
smem_thr_copy_K=smem_thr_copy_K,
|
| 934 |
-
smem_thr_copy_V=smem_thr_copy_V,
|
| 935 |
-
tSsQ=tSsQ,
|
| 936 |
-
tSsK=tSsK,
|
| 937 |
-
tOsVt=tOsVt,
|
| 938 |
-
)
|
| 939 |
-
load_K = partial(
|
| 940 |
-
self.load_K, gmem_tiled_copy_K, tKgK, tKsK, tKcK, t0KcK, tKpK, seqlen=seqlen.seqlen_k
|
| 941 |
-
)
|
| 942 |
-
load_V = partial(
|
| 943 |
-
self.load_V, gmem_tiled_copy_V, tVgV, tVsV, tVcV, t0VcV, tVpV, seqlen=seqlen.seqlen_k
|
| 944 |
-
)
|
| 945 |
-
|
| 946 |
-
compute_one_n_block = partial(
|
| 947 |
-
self.compute_one_n_block,
|
| 948 |
-
mma_params=mma_params,
|
| 949 |
-
smem_copy_params=smem_copy_params,
|
| 950 |
-
softmax=softmax,
|
| 951 |
-
load_K=load_K,
|
| 952 |
-
load_V=load_V,
|
| 953 |
-
score_mod=self.score_mod,
|
| 954 |
-
batch_idx=batch_size,
|
| 955 |
-
head_idx=num_head,
|
| 956 |
-
m_block=m_block,
|
| 957 |
-
aux_data=aux_data,
|
| 958 |
-
fastdiv_mods=fastdiv_mods,
|
| 959 |
-
)
|
| 960 |
-
|
| 961 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 962 |
-
# Prologue
|
| 963 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 964 |
-
# Start async loads of the last mn-tile, where we take care of the mn residue
|
| 965 |
-
gmem_thr_copy_Q = gmem_tiled_copy_Q.get_slice(tidx)
|
| 966 |
-
if const_expr(not self.pack_gqa):
|
| 967 |
-
self.load_Q(gmem_thr_copy_Q, gQ, sQ, m_block, seqlen=seqlen.seqlen_q, headdim=mQ.shape[1])
|
| 968 |
-
else:
|
| 969 |
-
pack_gqa = PackGQA(self.tile_m, self.tile_hdim, self.check_hdim_oob, self.qhead_per_kvhead)
|
| 970 |
-
pack_gqa.load_Q(mQ_cur, sQ, gmem_tiled_copy_Q, tidx, m_block, seqlen.seqlen_q)
|
| 971 |
-
cute.arch.cp_async_commit_group()
|
| 972 |
-
|
| 973 |
-
def preprocess_Q():
|
| 974 |
-
cute.arch.cp_async_wait_group(self.num_stages * 2 - 1)
|
| 975 |
-
if const_expr(self.Q_in_regs):
|
| 976 |
-
cute.arch.barrier()
|
| 977 |
-
tSrQ_copy_view = smem_thr_copy_Q.retile(tSrQ)
|
| 978 |
-
cute.copy(smem_thr_copy_Q, tSsQ, tSrQ_copy_view)
|
| 979 |
-
|
| 980 |
-
# If Q_in_regs, we load Q, then load 1 stage of K, then (optionally) rotate Q and
|
| 981 |
-
# read from smem_q to registers, then load V.
|
| 982 |
-
# If !Q_in_regs, we load Q, load all stages of K & V, then (optionally) rotate Q.
|
| 983 |
-
if const_expr(self.Q_in_regs):
|
| 984 |
-
load_K(n_block, smem_pipe_write=0, need_predicates=True)
|
| 985 |
-
cute.arch.cp_async_commit_group()
|
| 986 |
-
preprocess_Q()
|
| 987 |
-
cute.arch.barrier() # Make sure all threads have read smem_q before loading V
|
| 988 |
-
|
| 989 |
-
for stage in cutlass.range_constexpr(self.num_stages):
|
| 990 |
-
if const_expr(not self.Q_in_regs or stage > 0):
|
| 991 |
-
if stage == 0 or n_block - stage >= 0:
|
| 992 |
-
load_K(n_block - stage, smem_pipe_write=stage, need_predicates=stage == 0)
|
| 993 |
-
cute.arch.cp_async_commit_group()
|
| 994 |
-
if const_expr(stage < self.num_stages - 1):
|
| 995 |
-
if stage == 0 or n_block - stage >= 0:
|
| 996 |
-
load_V(n_block - stage, smem_pipe_write=stage, need_predicates=stage == 0)
|
| 997 |
-
cute.arch.cp_async_commit_group()
|
| 998 |
-
if const_expr(not self.Q_in_regs):
|
| 999 |
-
preprocess_Q()
|
| 1000 |
-
|
| 1001 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 1002 |
-
# Mainloop
|
| 1003 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 1004 |
-
# Start processing of the first n-block.
|
| 1005 |
-
# For performance reason, we separate out two kinds of iterations:
|
| 1006 |
-
# those that need masking on S, and those that don't.
|
| 1007 |
-
# We need masking on S for the very last block when K and V has length not multiple of tile_n.
|
| 1008 |
-
# We also need masking on S if it's causal, for the last several blocks.
|
| 1009 |
-
mask = AttentionMask(
|
| 1010 |
-
self.tile_m,
|
| 1011 |
-
self.tile_n,
|
| 1012 |
-
seqlen,
|
| 1013 |
-
window_size_left,
|
| 1014 |
-
window_size_right,
|
| 1015 |
-
self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 1016 |
-
)
|
| 1017 |
-
mask_fn = partial(
|
| 1018 |
-
mask.apply_mask,
|
| 1019 |
-
batch_idx=batch_size,
|
| 1020 |
-
head_idx=num_head,
|
| 1021 |
-
m_block=m_block,
|
| 1022 |
-
thr_mma=thr_mma_qk,
|
| 1023 |
-
mask_causal=self.is_causal,
|
| 1024 |
-
mask_local=self.is_local,
|
| 1025 |
-
aux_data=aux_data,
|
| 1026 |
-
fastdiv_mods=fastdiv_mods if const_expr(self.mask_mod is not None) else None,
|
| 1027 |
-
)
|
| 1028 |
-
|
| 1029 |
-
# First iteration with seqlen masking
|
| 1030 |
-
smem_pipe_read = Int32(0)
|
| 1031 |
-
smem_pipe_write = Int32(self.num_stages - 1)
|
| 1032 |
-
compute_one_n_block(
|
| 1033 |
-
n_block,
|
| 1034 |
-
smem_pipe_read,
|
| 1035 |
-
smem_pipe_write,
|
| 1036 |
-
is_first_n_block=True,
|
| 1037 |
-
seqlen=seqlen,
|
| 1038 |
-
mask_fn=partial(mask_fn, mask_mod=self.mask_mod, mask_seqlen=True),
|
| 1039 |
-
)
|
| 1040 |
-
smem_pipe_read = self.advance_pipeline(smem_pipe_read)
|
| 1041 |
-
smem_pipe_write = self.advance_pipeline(smem_pipe_write)
|
| 1042 |
-
# Next couple of iterations with causal masking
|
| 1043 |
-
if const_expr(self.is_causal or self.is_local):
|
| 1044 |
-
n_block_min_causal_local_mask = block_info.get_n_block_min_causal_local_mask(
|
| 1045 |
-
seqlen, m_block, n_block_min
|
| 1046 |
-
)
|
| 1047 |
-
for n_tile in cutlass.range(n_block_max - 1 - n_block_min_causal_local_mask, unroll=1):
|
| 1048 |
-
n_block = n_block_max - 2 - n_tile
|
| 1049 |
-
compute_one_n_block(
|
| 1050 |
-
n_block,
|
| 1051 |
-
smem_pipe_read,
|
| 1052 |
-
smem_pipe_write,
|
| 1053 |
-
seqlen=seqlen,
|
| 1054 |
-
mask_fn=partial(mask_fn, mask_mod=self.mask_mod, mask_seqlen=True),
|
| 1055 |
-
)
|
| 1056 |
-
smem_pipe_read = self.advance_pipeline(smem_pipe_read)
|
| 1057 |
-
smem_pipe_write = self.advance_pipeline(smem_pipe_write)
|
| 1058 |
-
# The remaining iterations have no masking
|
| 1059 |
-
for n_tile in cutlass.range(n_block, unroll=1):
|
| 1060 |
-
compute_one_n_block(
|
| 1061 |
-
n_block - n_tile - 1, smem_pipe_read, smem_pipe_write,
|
| 1062 |
-
seqlen=seqlen, is_first_n_block=False,
|
| 1063 |
-
mask_fn=partial(mask_fn, mask_mod=self.mask_mod, mask_seqlen=False)
|
| 1064 |
-
)
|
| 1065 |
-
smem_pipe_read = self.advance_pipeline(smem_pipe_read)
|
| 1066 |
-
smem_pipe_write = self.advance_pipeline(smem_pipe_write)
|
| 1067 |
-
# TODO: local
|
| 1068 |
-
|
| 1069 |
-
# normalize acc_O by row_sum and calculate the lse
|
| 1070 |
-
row_scale = softmax.finalize()
|
| 1071 |
-
softmax.rescale_O(acc_O, row_scale)
|
| 1072 |
-
|
| 1073 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 1074 |
-
# Epilogue
|
| 1075 |
-
# ///////////////////////////////////////////////////////////////////////////////
|
| 1076 |
-
# reuse sQ's data iterator
|
| 1077 |
-
sO = cute.make_tensor(sQ.iterator, sO_layout)
|
| 1078 |
-
self.epilogue(
|
| 1079 |
-
acc_O,
|
| 1080 |
-
softmax.row_sum,
|
| 1081 |
-
mO,
|
| 1082 |
-
mLSE,
|
| 1083 |
-
sO,
|
| 1084 |
-
seqlen,
|
| 1085 |
-
gmem_tiled_copy_O,
|
| 1086 |
-
None,
|
| 1087 |
-
tiled_mma_pv,
|
| 1088 |
-
tidx,
|
| 1089 |
-
m_block,
|
| 1090 |
-
num_head,
|
| 1091 |
-
batch_size,
|
| 1092 |
-
)
|
| 1093 |
-
|
| 1094 |
-
@cute.jit
|
| 1095 |
-
def compute_one_n_block(
|
| 1096 |
-
self,
|
| 1097 |
-
n_block: Int32,
|
| 1098 |
-
smem_pipe_read: Int32,
|
| 1099 |
-
smem_pipe_write: Int32,
|
| 1100 |
-
mma_params: SimpleNamespace,
|
| 1101 |
-
smem_copy_params: SimpleNamespace,
|
| 1102 |
-
softmax: Softmax,
|
| 1103 |
-
load_K: Callable,
|
| 1104 |
-
load_V: Callable,
|
| 1105 |
-
score_mod: Callable | None,
|
| 1106 |
-
batch_idx: cutlass.Int32,
|
| 1107 |
-
head_idx: cutlass.Int32,
|
| 1108 |
-
m_block: cutlass.Int32,
|
| 1109 |
-
seqlen: SeqlenInfoQK,
|
| 1110 |
-
aux_data: AuxData = AuxData(),
|
| 1111 |
-
fastdiv_mods=None,
|
| 1112 |
-
mask_fn: Optional[Callable] = None,
|
| 1113 |
-
is_first_n_block: cutlass.Constexpr = False,
|
| 1114 |
-
check_inf: cutlass.Constexpr = True,
|
| 1115 |
-
):
|
| 1116 |
-
"""Compute one n_block of S/O.
|
| 1117 |
-
|
| 1118 |
-
This function provides different variants for processing the first n block versus
|
| 1119 |
-
subsequent blocks.
|
| 1120 |
-
"""
|
| 1121 |
-
|
| 1122 |
-
def sync():
|
| 1123 |
-
cute.arch.cp_async_wait_group(self.num_stages * 2 - 2)
|
| 1124 |
-
cute.arch.barrier()
|
| 1125 |
-
|
| 1126 |
-
acc_shape_S = mma_params.thr_mma_qk.partition_shape_C((self.tile_m, self.tile_n))
|
| 1127 |
-
acc_S = cute.make_rmem_tensor(acc_shape_S, Float32)
|
| 1128 |
-
acc_S.fill(0.0)
|
| 1129 |
-
# wait for smem tile QK before mma calculation for S
|
| 1130 |
-
sync()
|
| 1131 |
-
|
| 1132 |
-
# need predicates for the first tile
|
| 1133 |
-
def load_V_next():
|
| 1134 |
-
if self.num_stages == 1 or n_block - self.num_stages + 1 >= 0:
|
| 1135 |
-
load_V(
|
| 1136 |
-
n_block - self.num_stages + 1,
|
| 1137 |
-
smem_pipe_write,
|
| 1138 |
-
need_predicates=is_first_n_block and self.num_stages == 1,
|
| 1139 |
-
)
|
| 1140 |
-
cute.arch.cp_async_commit_group()
|
| 1141 |
-
|
| 1142 |
-
load_V_next()
|
| 1143 |
-
sm80_utils.gemm(
|
| 1144 |
-
mma_params.thr_mma_qk,
|
| 1145 |
-
acc_S,
|
| 1146 |
-
mma_params.tSrQ,
|
| 1147 |
-
mma_params.tSrK,
|
| 1148 |
-
smem_copy_params.tSsQ,
|
| 1149 |
-
smem_copy_params.tSsK[
|
| 1150 |
-
None, None, None, smem_pipe_read if const_expr(self.num_stages > 1) else 0
|
| 1151 |
-
],
|
| 1152 |
-
smem_copy_params.smem_thr_copy_Q,
|
| 1153 |
-
smem_copy_params.smem_thr_copy_K,
|
| 1154 |
-
# hook_fn=load_V_next,
|
| 1155 |
-
A_in_regs=self.Q_in_regs,
|
| 1156 |
-
)
|
| 1157 |
-
if const_expr(score_mod is not None):
|
| 1158 |
-
self.apply_score_mod(
|
| 1159 |
-
mma_params.thr_mma_qk,
|
| 1160 |
-
batch_idx,
|
| 1161 |
-
head_idx,
|
| 1162 |
-
m_block,
|
| 1163 |
-
acc_S,
|
| 1164 |
-
n_block,
|
| 1165 |
-
softmax_scale=softmax.softmax_scale,
|
| 1166 |
-
seqlen=seqlen,
|
| 1167 |
-
aux_data=aux_data,
|
| 1168 |
-
fastdiv_mods=fastdiv_mods,
|
| 1169 |
-
)
|
| 1170 |
-
|
| 1171 |
-
smem_pipe_write = self.advance_pipeline(smem_pipe_write)
|
| 1172 |
-
|
| 1173 |
-
def load_K_next():
|
| 1174 |
-
if n_block - self.num_stages >= 0:
|
| 1175 |
-
load_K(n_block - self.num_stages, smem_pipe_write, need_predicates=False)
|
| 1176 |
-
cute.arch.cp_async_commit_group()
|
| 1177 |
-
|
| 1178 |
-
# wait for smem tile V for O
|
| 1179 |
-
if const_expr(self.num_stages == 1):
|
| 1180 |
-
sync()
|
| 1181 |
-
load_K_next()
|
| 1182 |
-
if const_expr(mask_fn is not None):
|
| 1183 |
-
mask_fn(acc_S, n_block=n_block)
|
| 1184 |
-
row_scale = softmax.online_softmax(acc_S, is_first=is_first_n_block, check_inf=check_inf)
|
| 1185 |
-
softmax.rescale_O(mma_params.acc_O, row_scale)
|
| 1186 |
-
rP = cute.make_fragment_like(acc_S, self.dtype)
|
| 1187 |
-
rP.store(acc_S.load().to(self.dtype))
|
| 1188 |
-
tOrP = layout_utils.reshape_acc_to_frgA(rP)
|
| 1189 |
-
if const_expr(self.num_stages > 1):
|
| 1190 |
-
sync()
|
| 1191 |
-
load_K_next()
|
| 1192 |
-
sm80_utils.gemm_rs(
|
| 1193 |
-
mma_params.thr_mma_pv,
|
| 1194 |
-
mma_params.acc_O,
|
| 1195 |
-
tOrP,
|
| 1196 |
-
mma_params.tOrVt,
|
| 1197 |
-
smem_copy_params.tOsVt[
|
| 1198 |
-
None, None, None, smem_pipe_read if const_expr(self.num_stages > 1) else 0
|
| 1199 |
-
],
|
| 1200 |
-
smem_copy_params.smem_thr_copy_V,
|
| 1201 |
-
# hook_fn=load_K_next,
|
| 1202 |
-
)
|
| 1203 |
-
# if const_expr(self.num_stages > 1):
|
| 1204 |
-
# load_K_next()
|
| 1205 |
-
@cute.jit
|
| 1206 |
-
def apply_score_mod(
|
| 1207 |
-
self,
|
| 1208 |
-
thr_mma_qk,
|
| 1209 |
-
batch_idx,
|
| 1210 |
-
head_idx,
|
| 1211 |
-
m_block,
|
| 1212 |
-
acc_S,
|
| 1213 |
-
n_block,
|
| 1214 |
-
softmax_scale,
|
| 1215 |
-
seqlen,
|
| 1216 |
-
aux_data: AuxData = AuxData(),
|
| 1217 |
-
fastdiv_mods=None,
|
| 1218 |
-
):
|
| 1219 |
-
# Prepare index tensor
|
| 1220 |
-
cS = cute.make_identity_tensor((self.tile_m, self.tile_n))
|
| 1221 |
-
cS = cute.domain_offset((m_block * self.tile_m, n_block * self.tile_n), cS)
|
| 1222 |
-
tScS = thr_mma_qk.partition_C(cS)
|
| 1223 |
-
|
| 1224 |
-
apply_score_mod_inner(
|
| 1225 |
-
acc_S,
|
| 1226 |
-
tScS,
|
| 1227 |
-
self.score_mod,
|
| 1228 |
-
batch_idx,
|
| 1229 |
-
head_idx,
|
| 1230 |
-
softmax_scale,
|
| 1231 |
-
self.score_vec_size,
|
| 1232 |
-
self.qk_acc_dtype,
|
| 1233 |
-
aux_data,
|
| 1234 |
-
fastdiv_mods,
|
| 1235 |
-
seqlen_info=seqlen,
|
| 1236 |
-
constant_q_idx=None,
|
| 1237 |
-
qhead_per_kvhead=self.qhead_per_kvhead if const_expr(self.pack_gqa) else 1,
|
| 1238 |
-
)
|
| 1239 |
-
|
| 1240 |
-
|
| 1241 |
-
# SM90 forward pass moved to flash_fwd_sm90.py; re-export for backward compatibility
|
| 1242 |
-
def __getattr__(name):
|
| 1243 |
-
if name == "FlashAttentionForwardSm90":
|
| 1244 |
-
from flash_attn.cute.flash_fwd_sm90 import FlashAttentionForwardSm90
|
| 1245 |
-
return FlashAttentionForwardSm90
|
| 1246 |
-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code/sol_h3_vae/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Sol-H3-VAE: fast noncausal decoder for MiniMax-H3 video latents."""
|
| 2 |
+
from .decoder import SolH3VAEDecoder
|
| 3 |
+
|
| 4 |
+
__all__ = ["SolH3VAEDecoder"]
|
code/{fast_h3_vae.py → sol_h3_vae/decoder.py}
RENAMED
|
@@ -1,41 +1,39 @@
|
|
| 1 |
-
"""
|
| 2 |
|
| 3 |
-
from
|
| 4 |
-
dec =
|
| 5 |
-
pixels = dec.decode(z) # z: normalized latents (
|
| 6 |
rgb_u8 = dec.decode_rgb_uint8(z) # (F, 16H, 16W, 3) uint8 on the GPU
|
| 7 |
|
| 8 |
-
* any clip length T (t = 0 must be the start of a native 5-latent block, as for the
|
| 9 |
-
|
| 10 |
-
(tested T = 3..15: frame count equal to decode_base, fast vs reference 60.7-61.4 dB). For T = 1 and 2 the released
|
| 11 |
-
decode_base raises an error; this decoder returns 1 and 5 frames by the same rule;
|
| 12 |
* engine="fast" (default): NonCausalFast3 (FlashAttention-4 varlen windows, fused Triton kernels, one CUDA graph per
|
| 13 |
-
(T, H, W)); engine="ref": the plain PyTorch NonCausalViTDecoder (any GPU with SDPA);
|
| 14 |
* the fast engine is built on first use of a shape and cached (`max_engines` most recent shapes).
|
| 15 |
"""
|
| 16 |
from __future__ import annotations
|
| 17 |
|
|
|
|
| 18 |
from collections import OrderedDict
|
| 19 |
|
| 20 |
import torch
|
| 21 |
from safetensors.torch import load_file
|
| 22 |
|
| 23 |
-
from
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
-
class
|
| 27 |
-
def __init__(self,
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
assert not
|
| 35 |
-
self.model =
|
| 36 |
-
|
| 37 |
-
vae.to("cpu")
|
| 38 |
-
torch.cuda.empty_cache()
|
| 39 |
self.engine, self.max_engines = engine, max_engines
|
| 40 |
self._engines: OrderedDict = OrderedDict()
|
| 41 |
|
|
@@ -44,7 +42,7 @@ class FastH3VAEDecoder:
|
|
| 44 |
if key in self._engines:
|
| 45 |
self._engines.move_to_end(key)
|
| 46 |
return self._engines[key]
|
| 47 |
-
from
|
| 48 |
eng = NonCausalFast3(self.model, T, H, W, cuda_graph=True)
|
| 49 |
self._engines[key] = eng
|
| 50 |
while len(self._engines) > self.max_engines:
|
|
@@ -54,7 +52,8 @@ class FastH3VAEDecoder:
|
|
| 54 |
|
| 55 |
@torch.no_grad()
|
| 56 |
def decode(self, z):
|
| 57 |
-
"""z: normalized latents (1, T, 24, H, W). Returns
|
|
|
|
| 58 |
assert z.dim() == 5 and z.shape[0] == 1 and z.shape[2] == 24, z.shape
|
| 59 |
if self.engine == "ref":
|
| 60 |
with torch.autocast("cuda", dtype=torch.bfloat16):
|
|
@@ -64,6 +63,6 @@ class FastH3VAEDecoder:
|
|
| 64 |
|
| 65 |
@torch.no_grad()
|
| 66 |
def decode_rgb_uint8(self, z):
|
| 67 |
-
"""Same as decode, then
|
| 68 |
px = self.decode(z)
|
| 69 |
-
return self.
|
|
|
|
| 1 |
+
"""Sol-H3-VAE: a 24-layer noncausal decoder for MiniMax-H3 video latents.
|
| 2 |
|
| 3 |
+
from sol_h3_vae import SolH3VAEDecoder
|
| 4 |
+
dec = SolH3VAEDecoder("MiniMax-H3/vae/config.json", "sol_h3_vae.safetensors")
|
| 5 |
+
pixels = dec.decode(z) # z: normalized latents (1, T, 24, H, W) -> (1, 3, F, 16H, 16W)
|
| 6 |
rgb_u8 = dec.decode_rgb_uint8(z) # (F, 16H, 16W, 3) uint8 on the GPU
|
| 7 |
|
| 8 |
+
* any clip length T (t = 0 must be the start of a native 5-latent block, as for the H3 VAE). The output has the same
|
| 9 |
+
frames as the H3 VAE decoder: 17 frames per full block, the first latent of every block keeps 1 of its 4 frames;
|
|
|
|
|
|
|
| 10 |
* engine="fast" (default): NonCausalFast3 (FlashAttention-4 varlen windows, fused Triton kernels, one CUDA graph per
|
| 11 |
+
(T, H, W)); engine="ref": the plain PyTorch NonCausalViTDecoder (any GPU with SDPA, no FlashAttention-4 needed);
|
| 12 |
* the fast engine is built on first use of a shape and cached (`max_engines` most recent shapes).
|
| 13 |
"""
|
| 14 |
from __future__ import annotations
|
| 15 |
|
| 16 |
+
import json
|
| 17 |
from collections import OrderedDict
|
| 18 |
|
| 19 |
import torch
|
| 20 |
from safetensors.torch import load_file
|
| 21 |
|
| 22 |
+
from .model import NonCausalViTDecoder
|
| 23 |
+
from .template import RGBProcessor, make_template
|
| 24 |
|
| 25 |
|
| 26 |
+
class SolH3VAEDecoder:
|
| 27 |
+
def __init__(self, vae_config, weights, *, engine: str = "fast", device="cuda", max_engines: int = 1):
|
| 28 |
+
"""vae_config: the H3 VAE config.json (Diffusers layout); weights: sol_h3_vae.safetensors. The checkpoint holds
|
| 29 |
+
every decoder parameter, so the H3 VAE weights are not needed."""
|
| 30 |
+
with open(vae_config) as f:
|
| 31 |
+
config = json.load(f)
|
| 32 |
+
model = NonCausalViTDecoder(make_template(config), time_norm=5)
|
| 33 |
+
model.load_state_dict(load_file(str(weights), device="cpu"), strict=True, assign=True)
|
| 34 |
+
assert not any(t.is_meta for t in list(model.parameters()) + list(model.buffers()))
|
| 35 |
+
self.model = model.to(device).eval().requires_grad_(False)
|
| 36 |
+
self.processor = RGBProcessor()
|
|
|
|
|
|
|
| 37 |
self.engine, self.max_engines = engine, max_engines
|
| 38 |
self._engines: OrderedDict = OrderedDict()
|
| 39 |
|
|
|
|
| 42 |
if key in self._engines:
|
| 43 |
self._engines.move_to_end(key)
|
| 44 |
return self._engines[key]
|
| 45 |
+
from .fast import NonCausalFast3
|
| 46 |
eng = NonCausalFast3(self.model, T, H, W, cuda_graph=True)
|
| 47 |
self._engines[key] = eng
|
| 48 |
while len(self._engines) > self.max_engines:
|
|
|
|
| 52 |
|
| 53 |
@torch.no_grad()
|
| 54 |
def decode(self, z):
|
| 55 |
+
"""z: normalized latents (1, T, 24, H, W). Returns the H3 VAE decoder's pixels (1, 3, F, 16H, 16W), fp32,
|
| 56 |
+
ImageNet-normalized like the H3 VAE output; `processor.revert_tensor` maps them to [0, 1]."""
|
| 57 |
assert z.dim() == 5 and z.shape[0] == 1 and z.shape[2] == 24, z.shape
|
| 58 |
if self.engine == "ref":
|
| 59 |
with torch.autocast("cuda", dtype=torch.bfloat16):
|
|
|
|
| 63 |
|
| 64 |
@torch.no_grad()
|
| 65 |
def decode_rgb_uint8(self, z):
|
| 66 |
+
"""Same as decode, then revert_tensor, clamp and uint8: (F, 16H, 16W, 3) on the GPU."""
|
| 67 |
px = self.decode(z)
|
| 68 |
+
return self.processor.revert_tensor(px).clamp_(0, 1).mul_(255).round_().to(torch.uint8)[0].permute(1, 2, 3, 0)
|
code/{noncausal_fast3.py → sol_h3_vae/fast.py}
RENAMED
|
@@ -10,7 +10,7 @@ import torch
|
|
| 10 |
import triton
|
| 11 |
import triton.language as tl
|
| 12 |
|
| 13 |
-
from
|
| 14 |
|
| 15 |
|
| 16 |
@triton.jit
|
|
|
|
| 10 |
import triton
|
| 11 |
import triton.language as tl
|
| 12 |
|
| 13 |
+
from .fast_base import BF16, NonCausalFast, _resid_norm_kernel, qgemm, qgemm_act, _flash_attn_fwd, _nc_qkv_post_kernel
|
| 14 |
|
| 15 |
|
| 16 |
@triton.jit
|
code/{noncausal_fast.py → sol_h3_vae/fast_base.py}
RENAMED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
tokens of the block + its global tokens;
|
| 7 |
* the 5 global rows of every block are appended to the token rows, so each linear layer is a single GEMM;
|
| 8 |
* residual add + RMSNorm, SwiGLU in the up-projection GEMM (quack), fp32 head as one bf16 GEMM on a hi/lo split
|
| 9 |
-
(
|
| 10 |
* the whole decode is one CUDA graph.
|
| 11 |
"""
|
| 12 |
from __future__ import annotations
|
|
@@ -24,8 +24,8 @@ if "cutlass" not in sys.modules:
|
|
| 24 |
from flash_attn.cute.interface import _flash_attn_fwd
|
| 25 |
from quack.gemm_interface import gemm as qgemm, gemm_act as qgemm_act
|
| 26 |
|
| 27 |
-
from
|
| 28 |
-
from
|
| 29 |
|
| 30 |
BF16 = torch.bfloat16
|
| 31 |
import triton
|
|
|
|
| 6 |
tokens of the block + its global tokens;
|
| 7 |
* the 5 global rows of every block are appended to the token rows, so each linear layer is a single GEMM;
|
| 8 |
* residual add + RMSNorm, SwiGLU in the up-projection GEMM (quack), fp32 head as one bf16 GEMM on a hi/lo split
|
| 9 |
+
(kernels.py);
|
| 10 |
* the whole decode is one CUDA graph.
|
| 11 |
"""
|
| 12 |
from __future__ import annotations
|
|
|
|
| 24 |
from flash_attn.cute.interface import _flash_attn_fwd
|
| 25 |
from quack.gemm_interface import gemm as qgemm, gemm_act as qgemm_act
|
| 26 |
|
| 27 |
+
from .kernels import _interleave, _resid_ln_split_kernel, _resid_norm_kernel
|
| 28 |
+
from .model import OUT_CH, PATCH, PATCH_T, NonCausalViTDecoder
|
| 29 |
|
| 30 |
BF16 = torch.bfloat16
|
| 31 |
import triton
|
code/{fused_kernels.py → sol_h3_vae/kernels.py}
RENAMED
|
File without changes
|
code/{noncausal_vit_decoder.py → sol_h3_vae/model.py}
RENAMED
|
@@ -23,7 +23,7 @@ import torch
|
|
| 23 |
from torch import nn
|
| 24 |
from torch.nn import functional as F
|
| 25 |
|
| 26 |
-
from
|
| 27 |
|
| 28 |
PATCH, PATCH_T, OUT_CH = 16, 4, 3
|
| 29 |
KEEP_24 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 31, 32, 35]
|
|
|
|
| 23 |
from torch import nn
|
| 24 |
from torch.nn import functional as F
|
| 25 |
|
| 26 |
+
from .template import _apply_qk_norm, _apply_rotary_pos_emb_impl
|
| 27 |
|
| 28 |
PATCH, PATCH_T, OUT_CH = 16, 4, 3
|
| 29 |
KEEP_24 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 31, 32, 35]
|
code/{spark_compat.py → sol_h3_vae/template.py}
RENAMED
|
File without changes
|
code/spark_decoder.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
"""Build the decoder from the Diffusers H3 VAE config.json alone; decoding is FastH3VAEDecoder's."""
|
| 2 |
-
import json
|
| 3 |
-
from collections import OrderedDict
|
| 4 |
-
from types import SimpleNamespace
|
| 5 |
-
|
| 6 |
-
from safetensors.torch import load_file
|
| 7 |
-
|
| 8 |
-
from fast_h3_vae import FastH3VAEDecoder
|
| 9 |
-
from noncausal_vit_decoder import NonCausalViTDecoder
|
| 10 |
-
from spark_compat import RGBProcessor, make_template
|
| 11 |
-
from spark_fa4 import enable_sm120_guard
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
class SparkFastH3VAEDecoder(FastH3VAEDecoder):
|
| 15 |
-
def __init__(self, vae_config, weights, *, engine='fast', device='cuda', max_engines=1):
|
| 16 |
-
enable_sm120_guard()
|
| 17 |
-
with open(vae_config) as f:
|
| 18 |
-
config = json.load(f)
|
| 19 |
-
model = NonCausalViTDecoder(make_template(config), time_norm=5)
|
| 20 |
-
model.load_state_dict(load_file(str(weights), device='cpu'), strict=True, assign=True)
|
| 21 |
-
assert not any(t.is_meta for t in list(model.parameters()) + list(model.buffers()))
|
| 22 |
-
self.model = model.to(device).eval().requires_grad_(False)
|
| 23 |
-
self.vae = SimpleNamespace(processor=RGBProcessor())
|
| 24 |
-
self.engine, self.max_engines = engine, max_engines
|
| 25 |
-
self._engines = OrderedDict()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
code/spark_fa4.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
"""Process-local SM120 fix for FlashAttention-4 (4.0.0b19-b22) varlen forward.
|
| 2 |
-
|
| 3 |
-
On SM120 (RTX 5090, DGX Spark) surplus scheduler work tiles could read sequence
|
| 4 |
-
metadata past the batch. _spark_flash_fwd_guarded.py is FA4's flash_fwd.py with that
|
| 5 |
-
guard added; the installed FA4 package and valid-tile computation stay unchanged.
|
| 6 |
-
"""
|
| 7 |
-
from flash_attn.cute import interface
|
| 8 |
-
from flash_attn.cute.cache_utils import JITCache
|
| 9 |
-
from flash_attn.cute.flash_fwd_sm120 import FlashAttentionForwardSm120
|
| 10 |
-
from _spark_flash_fwd_guarded import FlashAttentionForwardSm80
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
class SparkFlashAttentionForwardSm120(FlashAttentionForwardSm120):
|
| 14 |
-
kernel = FlashAttentionForwardSm80.kernel
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
def enable_sm120_guard():
|
| 18 |
-
if interface.FlashAttentionForwardSm120 is SparkFlashAttentionForwardSm120:
|
| 19 |
-
return
|
| 20 |
-
interface.FlashAttentionForwardSm120 = SparkFlashAttentionForwardSm120
|
| 21 |
-
# Never reuse an unguarded binary from an existing in-memory/disk cache.
|
| 22 |
-
interface._flash_attn_fwd.compile_cache = JITCache()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fast_h3_vae_noncausal_24l.safetensors → sol_h3_vae.safetensors
RENAMED
|
File without changes
|