diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml
index dbbc1714a..8550c254e 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -129,11 +129,7 @@ steps:
queue: "default"
- path:
- "fastvideo/**"
- - "csrc/attn/video_sparse_attn/**"
- - "csrc/attn/video_sparse_attn/tk/**"
- - "csrc/attn/video_sparse_attn/setup.py"
- - "csrc/attn/video_sparse_attn/config_vsa.py"
- - "csrc/attn/video_sparse_attn/vsa.cpp"
+ - "fastvideo-kernel/**"
- "pyproject.toml"
- "docker/Dockerfile.python3.12"
config:
@@ -145,10 +141,7 @@ steps:
queue: "default"
- path:
- "fastvideo/**"
- - "csrc/attn/sliding_tile_attn/**"
- - "csrc/attn/sliding_tile_attn/setup.py"
- - "csrc/attn/sliding_tile_attn/config_sta.py"
- - "csrc/attn/sliding_tile_attn/st_attn.cpp"
+ - "fastvideo-kernel/**"
- "pyproject.toml"
- "docker/Dockerfile.python3.12"
config:
@@ -159,10 +152,7 @@ steps:
agents:
queue: "default"
- path:
- - "csrc/attn/sliding_tile_attn/**"
- - "csrc/attn/sliding_tile_attn/setup.py"
- - "csrc/attn/sliding_tile_attn/config_sta.py"
- - "csrc/attn/sliding_tile_attn/st_attn.cpp"
+ - "fastvideo-kernel/**"
- "pyproject.toml"
- "docker/Dockerfile.python3.12"
config:
@@ -173,12 +163,7 @@ steps:
agents:
queue: "default"
- path:
- - "csrc/attn/video_sparse_attn/**"
- - "csrc/attn/video_sparse_attn/tk/**"
- - "csrc/attn/tests/test_vsa.py"
- - "csrc/attn/video_sparse_attn/setup.py"
- - "csrc/attn/video_sparse_attn/config_vsa.py"
- - "csrc/attn/video_sparse_attn/vsa.cpp"
+ - "fastvideo-kernel/**"
- "pyproject.toml"
- "docker/Dockerfile.python3.12"
config:
@@ -189,7 +174,7 @@ steps:
agents:
queue: "default"
- path:
- - "csrc/attn/vmoba_attn/**"
+ - "fastvideo-kernel/**"
- "pyproject.toml"
- "docker/Dockerfile.python3.12"
config:
@@ -200,7 +185,7 @@ steps:
agents:
queue: "default"
- path:
- - "csrc/attn/vmoba_attn/vmoba/**"
+ - "fastvideo-kernel/**"
- "fastvideo/attention/backends/vmoba.py"
- "pyproject.toml"
- "docker/Dockerfile.python3.12"
diff --git a/.github/workflows/fastvideo-kernel-publish.yml b/.github/workflows/fastvideo-kernel-publish.yml
index 4a1e4fa44..973a900e8 100644
--- a/.github/workflows/fastvideo-kernel-publish.yml
+++ b/.github/workflows/fastvideo-kernel-publish.yml
@@ -5,7 +5,7 @@ on:
branches:
- main
paths:
- - "csrc/fastvideo_kernel/pyproject.toml"
+ - "fastvideo-kernel/pyproject.toml"
workflow_dispatch:
jobs:
@@ -23,13 +23,15 @@ jobs:
- name: Check if version changed
id: check-version
run: |
- cd csrc/fastvideo_kernel
+ cd fastvideo-kernel
# Get current commit's version from pyproject.toml
- NEW_VERSION=$(grep -oP 'version\s*=\s*"\K[^"]+' pyproject.toml)
+ # Use ^ to match start of line to avoid matching minimum-version
+ NEW_VERSION=$(grep -oP '^version\s*=\s*"\K[^"]+' pyproject.toml)
echo "New version: $NEW_VERSION"
# Get previous version from git history
- OLD_VERSION=$(git show HEAD~1:./pyproject.toml | grep -oP 'version\s*=\s*"\K[^"]+' || echo "0.0.0")
+ # Note: git show expects path relative to repo root
+ OLD_VERSION=$(git show HEAD~1:fastvideo-kernel/pyproject.toml | grep -oP '^version\s*=\s*"\K[^"]+' || echo "0.0.0")
echo "Old version: $OLD_VERSION"
if [ "$NEW_VERSION" != "$OLD_VERSION" ]; then
@@ -51,15 +53,18 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-22.04]
- python-version: ['3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.10', '3.11', '3.12']
torch-cuda:
- - torch-version: '2.5.1'
- cuda-version: '12.4.1'
- torch-cuda-short: 'cu124'
- - torch-version: '2.6.0'
- cuda-version: '12.6.3'
- torch-cuda-short: 'cu126'
- - torch-version: '2.7.1'
+ # - torch-version: '2.5.1'
+ # cuda-version: '12.4.1'
+ # torch-cuda-short: 'cu124'
+ # - torch-version: '2.6.0'
+ # cuda-version: '12.6.3'
+ # torch-cuda-short: 'cu126'
+ # - torch-version: '2.7.1'
+ # cuda-version: '12.8.0'
+ # torch-cuda-short: 'cu128'
+ - torch-version: '2.9.1'
cuda-version: '12.8.0'
torch-cuda-short: 'cu128'
@@ -138,30 +143,30 @@ jobs:
run: |
export PYTHONPATH=$GITHUB_WORKSPACE:$PYTHONPATH
- pip install setuptools ninja packaging wheel triton
+ pip install setuptools ninja packaging wheel triton scikit-build-core cmake build
- cd csrc/fastvideo_kernel
+ cd fastvideo-kernel
git submodule update --init --recursive # Ensure ThunderKittens submodule is initialized
- python setup.py bdist_wheel --dist-dir=dist
-
- - name: Rename wheel file
- run: |
- cd csrc/fastvideo_kernel
-
- CUDA_SHORT_VERSION=$(echo ${{ matrix.torch-cuda.cuda-version }} | cut -d. -f1,2 | sed 's/\.//g')
- TORCH_SHORT_VERSION=$(echo ${{ matrix.torch-cuda.torch-version }} | cut -d. -f1,2)
- # Get the correct version format
- tmpname=cu${CUDA_SHORT_VERSION}torch${TORCH_SHORT_VERSION}
- wheel_name=$(ls dist/*whl | xargs -n 1 basename | sed "s/-/+$tmpname-/2")
- # Rename with version information
- ls dist/*whl |xargs -I {} mv {} dist/${wheel_name}
- echo "wheel_name=${wheel_name}" >> $GITHUB_ENV
+ export TORCH_CUDA_ARCH_LIST="9.0a"
+
+ # Build standard wheel (no local version suffix) for PyPI
+ python -m build --wheel --outdir dist
+
+ # Fix the wheel to be manylinux compliant
+ pip install auditwheel
+ # Target manylinux_2_35 (Ubuntu 22.04 native)
+ auditwheel repair dist/*.whl --plat manylinux_2_35_x86_64 -w fixed_dist
+ # Move fixed wheels back to dist for upload consistency
+ rm dist/*.whl
+ mv fixed_dist/*.whl dist/
- name: Upload wheel artifact
+ # Only upload if it's the "main" CUDA version we want on PyPI
+ # We upload all to artifacts for inspection/GH releases, but give them distinct artifact names
uses: actions/upload-artifact@v4
with:
- name: ${{ env.wheel_name }}-py${{ matrix.python-version }}
- path: csrc/fastvideo_kernel/dist/*.whl
+ name: fastvideo_kernel-py${{ matrix.python-version }}-${{ matrix.torch-cuda.torch-cuda-short }}-torch${{ matrix.torch-cuda.torch-version }}
+ path: fastvideo-kernel/dist/*.whl
retention-days: 90
publish_package:
@@ -179,58 +184,22 @@ jobs:
with:
python-version: '3.10'
- - name: Install CUDA 12.4.1
- uses: Jimver/cuda-toolkit@v0.2.21
- id: cuda-toolkit
+ - name: Download PyPI wheels
+ uses: actions/download-artifact@v4
with:
- cuda: 12.4.1
- linux-local-args: '["--toolkit"]'
- method: 'network'
- sub-packages: '["nvcc"]'
-
- - name: Install dependencies (GCC, Clang, CUDA Paths, Git)
- run: |
- sudo apt update
- sudo apt install -y git patchelf gcc-11 g++-11 clang-11
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11
-
- # Allow Git to Access Safe Directory
- git config --global --add safe.directory /__w/FastVideo/FastVideo
-
- # Set CUDA environment variables
- export CUDA_HOME=/usr/local/cuda-12.4.1
- export PATH=${CUDA_HOME}/bin:${PATH}
- export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH
-
- # Verify installation
- gcc --version
- g++ --version
- clang-11 --version
- nvcc --version
-
- - name: Install PyTorch 2.5.1+cu12.4.1
- run: |
- pip install --upgrade pip
- pip install typing-extensions==4.12.2
- export TORCH_CUDA_VERSION=124
- pip install --no-cache-dir torch==2.5.1 --index-url https://download.pytorch.org/whl/cu${TORCH_CUDA_VERSION}
- nvcc --version
- python --version
- python -c "import torch; print('PyTorch:', torch.__version__)"
- python -c "import torch; print('CUDA:', torch.version.cuda)"
- python -c "from torch.utils import cpp_extension; print (cpp_extension.CUDA_HOME)"
+ path: fastvideo-kernel/dist/
+ pattern: 'fastvideo_kernel-py*'
+ merge-multiple: true
- name: Build source distribution
run: |
- export PYTHONPATH=$GITHUB_WORKSPACE:$PYTHONPATH
+ pip install build scikit-build-core cmake ninja
- pip install setuptools ninja packaging wheel triton
-
- cd csrc/fastvideo_kernel
- git submodule update --init --recursive
- python setup.py sdist --dist-dir=dist
+ cd fastvideo-kernel
+ # We don't need full CUDA/Torch to just package the source (sdist)
+ python -m build --sdist --outdir dist
- name: Publish release distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
- packages-dir: csrc/fastvideo_kernel/dist/
+ packages-dir: fastvideo-kernel/dist/
diff --git a/.gitmodules b/.gitmodules
index bdb04686d..c51738164 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,7 +1,3 @@
-[submodule "csrc/attn/video_sparse_attn/tk"]
- path = csrc/attn/video_sparse_attn/tk
- url = https://github.com/HazyResearch/ThunderKittens.git
-
-[submodule "csrc/attn/sliding_tile_attn/tk"]
- path = csrc/attn/sliding_tile_attn/tk
+[submodule "fastvideo-kernel/include/tk"]
+ path = fastvideo-kernel/include/tk
url = https://github.com/HazyResearch/ThunderKittens.git
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 1eeb53425..db9151d38 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,7 +4,7 @@ default_stages:
exclude: |
(?x)(
fastvideo/third_party/.*|
- csrc/.*|
+ fastvideo-kernel/.*|
assets/.*|
tests/.*|
demo/.*|
diff --git a/csrc/attn/README.md b/csrc/attn/README.md
deleted file mode 100644
index 461e5bde5..000000000
--- a/csrc/attn/README.md
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-# Attention Kernel Used in FastVideo
-
-
-
-## Video Sparse Attention (VSA)
-
-### Installation
-We support H100 (via TK) and any other GPU (via triton) for VSA.
-```bash
-git submodule update --init --recursive
-python setup_vsa.py install
-```
-
-
-If you encounter error during installation, try below:
-Install C++20 for ThunderKittens:
-```bash
-sudo apt update
-sudo apt install gcc-11 g++-11
-
-sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11
-
-sudo apt update
-sudo apt install clang-11
-```
-(If you use CUDA12.8)
-```bash
-export CUDA_HOME=/usr/local/cuda-12.8
-export PATH=${CUDA_HOME}/bin:${PATH}
-export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH
-```
-
-### Verify if you have successfully installed
-
-```bash
-# test numerical
-python tests/test_vsa.py
-# (For H100) test speed
-python benchmarks/bench_vsa_hopper.py
-```
-bench_vsa_hopper.py should print something like this:
-```bash
-Using topk=76 kv blocks per q block (out of 768 total kv blocks)
-
-=== BLOCK SPARSE ATTENTION BENCHMARK ===
-Block Sparse Forward - TFLOPS: 5622.26
-Block Sparse Backward - TFLOPS: 3865.68
-```
-
-
-## Sliding Tile Attention (STA)
-We only support H100 for STA.
-```bash
-git submodule update --init --recursive
-python setup_sta.py install
-```
-
-
-
-
-### Usage
-End-2-end inference with FastVideo:
-```bash
-bash scripts/inference/v1_inference_wan_STA.sh
-```
-
-If you want to use sliding tile attention in your custom model:
-```python
-from st_attn import sliding_tile_attention
-# assuming video size (T, H, W) = (30, 48, 80), text tokens = 256 with padding.
-# q, k, v: [batch_size, num_heads, seq_length, head_dim], seq_length = T*H*W + 256
-# a tile is a cube of size (6, 8, 8)
-# window_size in tiles: [(window_t, window_h, window_w), (..)...]. For example, window size (3, 3, 3) means a query can attend to (3x6, 3x8, 3x8) = (18, 24, 24) tokens out of the total 30x48x80 video.
-# text_length: int ranging from 0 to 256
-# If your attention contains text token (Hunyuan)
-out = sliding_tile_attention(q, k, v, window_size, text_length)
-# If your attention does not contain text token (StepVideo)
-out = sliding_tile_attention(q, k, v, window_size, 0, False)
-```
-
-
-### Test
-```bash
-python tests/test_sta.py # test STA
-python tests/test_vsa.py # test VSA
-```
-### Benchmark
-```bash
-python benchmarks/bench_sta.py
-```
-
-
-### How Does STA Work?
-We give a demo for 2D STA with window size (6,6) operating on a (10, 10) image.
-
-
-https://github.com/user-attachments/assets/f3b6dd79-7b43-4b60-a0fa-3d6495ec5747
-
-## Why is STA Fast?
-2D/3D Sliding Window Attention (SWA) creates many mixed blocks in the attention map. Even though mixed blocks have less output value,a mixed block is significantly slower than a dense block due to the GPU-unfriendly masking operation.
-
-STA removes mixed blocks.
-
-
-
-

-
-
-## Acknowledgement
-
-We learned or reuse code from FlexAtteniton, NATEN, and ThunderKittens.
diff --git a/csrc/attn/benchmarks/bench_sta.py b/csrc/attn/benchmarks/bench_sta.py
deleted file mode 100644
index 8e0c8afc1..000000000
--- a/csrc/attn/benchmarks/bench_sta.py
+++ /dev/null
@@ -1,145 +0,0 @@
-import os
-from collections import defaultdict
-
-import matplotlib.pyplot as plt
-import numpy as np
-import torch
-from st_attn import sliding_tile_attention
-from triton.testing import do_bench
-
-
-def flops(batch, seqlen, nheads, headdim, causal, mode="fwd"):
- assert mode in ["fwd", "bwd", "fwd_bwd"]
- f = 4 * batch * seqlen**2 * nheads * headdim // (2 if causal else 1)
- return f if mode == "fwd" else (2.5 * f if mode == "bwd" else 3.5 * f)
-
-
-def compute_TFLOPS(flops, ms):
- flops = flops / 1e12
- ms = ms / 1e3
- return flops / ms
-
-
-def benchmark_attention(configurations):
- results = {'fwd': defaultdict(list), 'bwd': defaultdict(list)}
-
- for B, H, N, D, causal, dit_seq_shape, window_size in configurations:
- print("=" * 60)
- print(f"Timing forward and backward pass for B={B}, H={H}, N={N}, D={D}, causal={causal}")
-
- q = torch.randn(B, H, N, D, dtype=torch.bfloat16, device='cuda', requires_grad=False).contiguous()
- k = torch.randn(B, H, N, D, dtype=torch.bfloat16, device='cuda', requires_grad=False).contiguous()
- v = torch.randn(B, H, N, D, dtype=torch.bfloat16, device='cuda', requires_grad=False).contiguous()
-
- # grad_output = torch.randn_like(q, requires_grad=False).contiguous()
- # qg = torch.zeros_like(q, requires_grad=False, dtype=torch.float).contiguous()
- # kg = torch.zeros_like(k, requires_grad=False, dtype=torch.float).contiguous()
- # vg = torch.zeros_like(v, requires_grad=False, dtype=torch.float).contiguous()
-
-
- # # Warmup for forward pass
- # for _ in range(10):
- # o = sliding_tile_attention(q, k, v, [[3, 6, 10]] * 24, 0, False, dit_seq_shape)
-
- # # Time the forward pass
- # for i in range(10):
- # start_events_fwd[i].record()
- # o = sliding_tile_attention(q, k, v, [[3, 6, 10]] * 24, 0, False, dit_seq_shape)
- # end_events_fwd[i].record()
- ms = do_bench(lambda: sliding_tile_attention(q, k, v, [window_size] * 24, 0, False, dit_seq_shape))
-
- # times_fwd = [s.elapsed_time(e) for s, e in zip(start_events_fwd, end_events_fwd)]
- # time_us_fwd = np.mean(times_fwd) * 1000
-
- tflops_fwd = compute_TFLOPS(flops(B, N, H, D, causal, 'fwd'), ms)
- results['fwd'][(D, causal)].append((N, tflops_fwd))
-
- print(f"Average time for forward pass (ms): {ms:.2f}")
- print(f"Average TFLOPS: {tflops_fwd}")
- print("-" * 60)
-
- # torch.cuda.empty_cache()
- # torch.cuda.synchronize()
-
- # # Prepare for timing backward pass
- # start_events_bwd = [torch.cuda.Event(enable_timing=True) for _ in range(10)]
- # end_events_bwd = [torch.cuda.Event(enable_timing=True) for _ in range(10)]
-
- # # Warmup for backward pass
- # for _ in range(10):
- # qg, kg, vg = tk.mha_backward(q, k, v, o, l_vec, grad_output, causal)
-
- # # Time the backward pass
- # for i in range(10):
- # start_events_bwd[i].record()
- # qg, kg, vg = tk.mha_backward(q, k, v, o, l_vec, grad_output, causal)
- # end_events_bwd[i].record()
-
- # torch.cuda.synchronize()
- # times_bwd = [s.elapsed_time(e) for s, e in zip(start_events_bwd, end_events_bwd)]
- # time_us_bwd = np.mean(times_bwd) * 1000
-
- # tflops_bwd = compute_TFLOPS(flops(B, N, H, D, causal, 'bwd'), ms)
- # results['bwd'][(D, causal)].append((N, tflops_bwd))
-
- # print(f"Average time for backward pass(ms): {ms:.2f}")
- # print(f"Average TFLOPS: {tflops_bwd}")
- # print("=" * 60)
-
- return results
-
-
-def plot_results(results):
- os.makedirs('benchmark_results', exist_ok=True)
- for mode in ['fwd', 'bwd']:
- for (D, causal), values in results[mode].items():
- seq_lens = [x[0] for x in values]
- tflops = [x[1] for x in values]
-
- plt.figure(figsize=(10, 6))
- bars = plt.bar(range(len(seq_lens)), tflops, tick_label=seq_lens)
- plt.xlabel('Sequence Length')
- plt.ylabel('TFLOPS')
- plt.title(f'{mode.upper()} Pass - Head Dim: {D}, Causal: {causal}')
- plt.grid(True)
-
- # Adding the numerical y value on top of each bar
- for bar in bars:
- yval = bar.get_height()
- plt.text(bar.get_x() + bar.get_width() / 2, yval, round(yval, 2), ha='center', va='bottom')
-
- filename = f'benchmark_results/{mode}_D{D}_causal{causal}.png'
- plt.savefig(filename)
- plt.close()
-
-
-# Example list of configurations to test
-configurations = [
- (2, 24, 69120, 128, False, '18x48x80', [3, 6, 10]),
- (2, 24, 69120, 128, True, '18x48x80', [3, 6, 10]),
- (2, 24, 82944, 128, False, '36x48x48', [3, 3, 6]), # Stepvideo
- (2, 24, 82944, 128, True, '36x48x48', [3, 3, 6]),
- # (16, 16, 768*16, 128, False),
- # (16, 16, 768*2, 128, False),
- # (16, 16, 768*4, 128, False),
- # (16, 16, 768*8, 128, False),
- # (16, 16, 768*16, 128, False),
- # (16, 16, 768, 128, True),
- # (16, 16, 768*2, 128, True),
- # (16, 16, 768*4, 128, True),
- # (16, 16, 768*8, 128, True),
- # (16, 16, 768*16, 128, True),
- # (16, 32, 768, 64, False),
- # (16, 32, 768*2, 64, False),
- # (16, 32, 768*4, 64, False),
- # (16, 32, 768*8, 64, False),
- # (16, 32, 768*16, 64, False),
- # (16, 32, 768, 64, True),
- # (16, 32, 768*2, 64, True),
- # (16, 32, 768*4, 64, True),
- # (16, 32, 768*8, 64, True),
- # (16, 32, 768*16, 64, True),
-]
-
-results = benchmark_attention(configurations)
-# plot_results(results)
diff --git a/csrc/attn/benchmarks/bench_vsa_hopper.py b/csrc/attn/benchmarks/bench_vsa_hopper.py
deleted file mode 100644
index 893067376..000000000
--- a/csrc/attn/benchmarks/bench_vsa_hopper.py
+++ /dev/null
@@ -1,224 +0,0 @@
-import torch
-import argparse
-from triton.testing import do_bench
-from vsa import block_sparse_fwd, block_sparse_bwd
-from vsa import BLOCK_M, BLOCK_N
-import triton
-import numpy as np
-import random
-
-def set_seed(seed: int = 42):
- # Python random module
- random.seed(seed)
-
- # NumPy
- np.random.seed(seed)
-
- # PyTorch
- torch.manual_seed(seed)
- torch.cuda.manual_seed(seed)
- torch.cuda.manual_seed_all(seed) # if using multi-GPU
-
-def parse_arguments():
- parser = argparse.ArgumentParser(description='Benchmark Block Sparse Attention')
- parser.add_argument('--batch_size', type=int, default=1, help='Batch size')
- parser.add_argument('--num_heads', type=int, default=12, help='Number of heads')
- parser.add_argument('--head_dim', type=int, default=128, help='Head dimension')
- parser.add_argument('--topk', type=int, default=None, help='Number of kv blocks each q block attends to')
- parser.add_argument('--seq_lengths', type=int, nargs='+', default=[49152], help='Sequence lengths to benchmark')
- return parser.parse_args()
-
-def create_input_tensors(batch, head, seq_len, headdim):
- """Create random input tensors for attention."""
- q = torch.randn(batch, head, seq_len, headdim, dtype=torch.bfloat16, device="cuda")
- k = torch.randn(batch, head, seq_len, headdim, dtype=torch.bfloat16, device="cuda")
- v = torch.randn(batch, head, seq_len, headdim, dtype=torch.bfloat16, device="cuda")
- return q, k, v
-
-def generate_block_sparse_pattern(bs, h, num_q_blocks, num_kv_blocks, k, device="cuda"):
- """
- Generate a block sparse pattern where each q block attends to exactly k kv blocks.
-
- Args:
- bs: batch size
- h: number of heads
- num_q_blocks: number of query blocks
- num_kv_blocks: number of key-value blocks
- k: number of kv blocks each q block attends to
- device: device to create tensors on
-
- Returns:
- q2k_block_sparse_index: [bs, h, num_q_blocks, k]
- Contains the indices of kv blocks that each q block attends to.
- q2k_block_sparse_num: [bs, h, num_q_blocks]
- Contains the number of kv blocks that each q block attends to (all equal to k).
- k2q_block_sparse_index: [bs, h, num_kv_blocks, num_q_blocks]
- Contains the indices of q blocks that attend to each kv block.
- k2q_block_sparse_num: [bs, h, num_kv_blocks]
- Contains the number of q blocks that attend to each kv block.
- block_sparse_mask: [bs, h, num_q_blocks, num_kv_blocks]
- Binary mask where 1 indicates attention connection.
- """
- # Ensure k is not larger than num_kv_blocks
- k = min(k, num_kv_blocks)
-
- # Create random scores for sampling
- scores = torch.rand(bs, h, num_q_blocks, num_kv_blocks, device=device)
-
- # Get top-k indices for each q block
- _, q2k_block_sparse_index = torch.topk(scores, k, dim=-1)
- q2k_block_sparse_index = q2k_block_sparse_index.to(torch.int32)
-
- # sort q2k_block_sparse_index
- q2k_block_sparse_index, _ = torch.sort(q2k_block_sparse_index, dim=-1)
-
- # All q blocks attend to exactly k kv blocks
- q2k_block_sparse_num = torch.full((bs, h, num_q_blocks), k, dtype=torch.int32, device=device)
-
- # Create the corresponding mask
- block_sparse_mask = torch.zeros(bs, h, num_q_blocks, num_kv_blocks, dtype=torch.bool, device=device)
-
- # Fill in the mask based on the indices
- for b in range(bs):
- for head in range(h):
- for q_idx in range(num_q_blocks):
- kv_indices = q2k_block_sparse_index[b, head, q_idx]
- block_sparse_mask[b, head, q_idx, kv_indices] = True
-
- # Create the reverse mapping (k2q)
- # First, initialize lists to collect q indices for each kv block
- k2q_indices_list = [[[] for _ in range(num_kv_blocks)] for _ in range(bs * h)]
-
- # Populate the lists based on q2k mapping
- for b in range(bs):
- for head in range(h):
- flat_idx = b * h + head
- for q_idx in range(num_q_blocks):
- kv_indices = q2k_block_sparse_index[b, head, q_idx].tolist()
- for kv_idx in kv_indices:
- k2q_indices_list[flat_idx][kv_idx].append(q_idx)
-
- # Find the maximum number of q blocks that attend to any kv block
- max_q_per_kv = 0
- for flat_idx in range(bs * h):
- for kv_idx in range(num_kv_blocks):
- max_q_per_kv = max(max_q_per_kv, len(k2q_indices_list[flat_idx][kv_idx]))
-
- # Create tensors for k2q mapping
- k2q_block_sparse_index = torch.full((bs, h, num_kv_blocks, max_q_per_kv), -1,
- dtype=torch.int32, device=device)
- k2q_block_sparse_num = torch.zeros((bs, h, num_kv_blocks),
- dtype=torch.int32, device=device)
-
- # Fill the tensors
- for b in range(bs):
- for head in range(h):
- flat_idx = b * h + head
- for kv_idx in range(num_kv_blocks):
- q_indices = k2q_indices_list[flat_idx][kv_idx]
- num_q = len(q_indices)
- k2q_block_sparse_num[b, head, kv_idx] = num_q
- if num_q > 0:
- k2q_block_sparse_index[b, head, kv_idx, :num_q] = torch.tensor(
- q_indices, dtype=torch.int32, device=device)
-
- return q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, block_sparse_mask
-
-def benchmark_block_sparse_attention(q, k, v, q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, flops):
- """Benchmark block sparse attention forward and backward passes."""
- print("\n=== BLOCK SPARSE ATTENTION BENCHMARK ===")
-
- # Forward pass
- # Warm-up run
- variable_block_sizes = torch.ones(q2k_block_sparse_index.shape[2], device=q.device).int() * BLOCK_M
- o, l_vec = block_sparse_fwd(q, k, v, q2k_block_sparse_index, q2k_block_sparse_num, variable_block_sizes)
- torch.cuda.synchronize()
-
- # Benchmark forward
- fwd_time = do_bench(
- lambda: block_sparse_fwd(q, k, v, q2k_block_sparse_index, q2k_block_sparse_num, variable_block_sizes),
- warmup=5,
- rep=20,
- quantiles=None
- )
-
- sparse_tflops = flops / fwd_time * 1e-12 * 1e3
- print(f"Block Sparse Forward - TFLOPS: {sparse_tflops:.2f}")
-
- # Backward pass
- grad_output = torch.randn_like(o)
-
- # Warm-up runs
- for _ in range(5):
- block_sparse_bwd(q, k, v, o, l_vec, grad_output, k2q_block_sparse_index, k2q_block_sparse_num, variable_block_sizes)
- torch.cuda.synchronize()
-
- # Benchmark backward
- bwd_time = do_bench(
- lambda: block_sparse_bwd(q, k, v, o, l_vec, grad_output, k2q_block_sparse_index, k2q_block_sparse_num, variable_block_sizes),
- warmup=5,
- rep=20,
- quantiles=None
- )
- bwd_flops = 2.5 * flops # Approximation
-
- sparse_bwd_tflops = bwd_flops / bwd_time * 1e-12 * 1e3
- print(f"Block Sparse Backward - TFLOPS: {sparse_bwd_tflops:.2f}")
-
- return sparse_tflops, sparse_bwd_tflops
-
-def main():
- args = parse_arguments()
-
- set_seed(42)
-
- # Extract parameters
- batch = args.batch_size
- head = args.num_heads
- headdim = args.head_dim
-
- print(f"Block Sparse Attention Benchmark")
- print(f"batch: {batch}, head: {head}, headdim: {headdim}")
-
- # Test with different sequence lengths
- for seq_len in args.seq_lengths:
- # Skip very long sequences if they might cause OOM
- if seq_len > 16384 and batch > 1:
- continue
-
- print("="*100)
- print(f"\nSequence length: {seq_len}")
-
- # Calculate theoretical FLOPs for attention
- flops = 4 * batch * head * headdim * seq_len * seq_len
-
- # Create input tensors
- q, k, v = create_input_tensors(batch, head, seq_len, headdim)
-
- # Setup block sparse parameters
- num_q_blocks = seq_len // BLOCK_M
- num_kv_blocks = seq_len // BLOCK_N
-
- # Determine k value (number of kv blocks per q block)
- topk = args.topk
- if topk is None:
- topk = num_kv_blocks // 10 # Default to ~90% sparsity if k is not specified
- topk = max(1, topk)
- print(f"Using topk={topk} kv blocks per q block (out of {num_kv_blocks} total kv blocks)")
-
- # Generate block sparse pattern
- q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, _ = generate_block_sparse_pattern(
- batch, head, num_q_blocks, num_kv_blocks, topk, device="cuda")
-
- # Benchmark block sparse attention
- sparse_fwd, sparse_bwd = benchmark_block_sparse_attention(
- q, k, v, q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, flops
- )
-
- # Print results
- print("\n=== PERFORMANCE RESULTS ===")
- print(f"Block Sparse Forward - TFLOPS: {sparse_fwd:.2f}")
- print(f"Block Sparse Backward - TFLOPS: {sparse_bwd:.2f}")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/csrc/attn/benchmarks/bench_vsa_triton.py b/csrc/attn/benchmarks/bench_vsa_triton.py
deleted file mode 100644
index 125b2677c..000000000
--- a/csrc/attn/benchmarks/bench_vsa_triton.py
+++ /dev/null
@@ -1,217 +0,0 @@
-import torch
-import argparse
-import triton.testing
-from vsa import block_sparse_attn
-from vsa import BLOCK_M, BLOCK_N
-
-import numpy as np
-import random
-
-def set_seed(seed: int = 42):
- # Python random module
- random.seed(seed)
-
- # NumPy
- np.random.seed(seed)
-
- # PyTorch
- torch.manual_seed(seed)
- torch.cuda.manual_seed(seed)
- torch.cuda.manual_seed_all(seed) # if using multi-GPU
-
-def parse_arguments():
- parser = argparse.ArgumentParser(description='Benchmark Block Sparse Attention')
- parser.add_argument('--batch_size', type=int, default=1, help='Batch size')
- parser.add_argument('--num_heads', type=int, default=12, help='Number of heads')
- parser.add_argument('--head_dim', type=int, default=64, help='Head dimension')
- parser.add_argument('--topk', type=int, default=None, help='Number of kv blocks each q block attends to')
- parser.add_argument('--seq_lengths', type=int, nargs='+', default=[49152], help='Sequence lengths to benchmark')
- return parser.parse_args()
-
-def create_input_tensors(batch, head, seq_len, headdim):
- """Create random input tensors for attention."""
- q = torch.randn(batch, head, seq_len, headdim, dtype=torch.bfloat16, device="cuda")
- k = torch.randn(batch, head, seq_len, headdim, dtype=torch.bfloat16, device="cuda")
- v = torch.randn(batch, head, seq_len, headdim, dtype=torch.bfloat16, device="cuda")
- return q, k, v
-
-def generate_block_sparse_pattern(bs, h, num_q_blocks, num_kv_blocks, k, device="cuda"):
- """
- Generate a block sparse pattern where each q block attends to exactly k kv blocks.
-
- Args:
- bs: batch size
- h: number of heads
- num_q_blocks: number of query blocks
- num_kv_blocks: number of key-value blocks
- k: number of kv blocks each q block attends to
- device: device to create tensors on
-
- Returns:
- q2k_block_sparse_index: [bs, h, num_q_blocks, k]
- Contains the indices of kv blocks that each q block attends to.
- q2k_block_sparse_num: [bs, h, num_q_blocks]
- Contains the number of kv blocks that each q block attends to (all equal to k).
- k2q_block_sparse_index: [bs, h, num_kv_blocks, num_q_blocks]
- Contains the indices of q blocks that attend to each kv block.
- k2q_block_sparse_num: [bs, h, num_kv_blocks]
- Contains the number of q blocks that attend to each kv block.
- block_sparse_mask: [bs, h, num_q_blocks, num_kv_blocks]
- Binary mask where 1 indicates attention connection.
- """
- # Ensure k is not larger than num_kv_blocks
- k = min(k, num_kv_blocks)
-
- # Create random scores for sampling
- scores = torch.rand(bs, h, num_q_blocks, num_kv_blocks, device=device)
-
- # Get top-k indices for each q block
- _, q2k_block_sparse_index = torch.topk(scores, k, dim=-1)
- q2k_block_sparse_index = q2k_block_sparse_index.to(torch.int32)
-
- # sort q2k_block_sparse_index
- q2k_block_sparse_index, _ = torch.sort(q2k_block_sparse_index, dim=-1)
-
- # All q blocks attend to exactly k kv blocks
- q2k_block_sparse_num = torch.full((bs, h, num_q_blocks), k, dtype=torch.int32, device=device)
-
- # Create the corresponding mask
- block_sparse_mask = torch.zeros(bs, h, num_q_blocks, num_kv_blocks, dtype=torch.bool, device=device)
-
- # Fill in the mask based on the indices
- for b in range(bs):
- for head in range(h):
- for q_idx in range(num_q_blocks):
- kv_indices = q2k_block_sparse_index[b, head, q_idx]
- block_sparse_mask[b, head, q_idx, kv_indices] = True
-
- # Create the reverse mapping (k2q)
- # First, initialize lists to collect q indices for each kv block
- k2q_indices_list = [[[] for _ in range(num_kv_blocks)] for _ in range(bs * h)]
-
- # Populate the lists based on q2k mapping
- for b in range(bs):
- for head in range(h):
- flat_idx = b * h + head
- for q_idx in range(num_q_blocks):
- kv_indices = q2k_block_sparse_index[b, head, q_idx].tolist()
- for kv_idx in kv_indices:
- k2q_indices_list[flat_idx][kv_idx].append(q_idx)
-
- # Find the maximum number of q blocks that attend to any kv block
- max_q_per_kv = 0
- for flat_idx in range(bs * h):
- for kv_idx in range(num_kv_blocks):
- max_q_per_kv = max(max_q_per_kv, len(k2q_indices_list[flat_idx][kv_idx]))
-
- # Create tensors for k2q mapping
- k2q_block_sparse_index = torch.full((bs, h, num_kv_blocks, max_q_per_kv), -1,
- dtype=torch.int32, device=device)
- k2q_block_sparse_num = torch.zeros((bs, h, num_kv_blocks),
- dtype=torch.int32, device=device)
-
- # Fill the tensors
- for b in range(bs):
- for head in range(h):
- flat_idx = b * h + head
- for kv_idx in range(num_kv_blocks):
- q_indices = k2q_indices_list[flat_idx][kv_idx]
- num_q = len(q_indices)
- k2q_block_sparse_num[b, head, kv_idx] = num_q
- if num_q > 0:
- k2q_block_sparse_index[b, head, kv_idx, :num_q] = torch.tensor(
- q_indices, dtype=torch.int32, device=device)
-
- return q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, block_sparse_mask
-
-def benchmark_block_sparse_attention(q, k, v, q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, flops):
- """Benchmark block sparse attention forward+backward pass."""
- print("\n=== BLOCK SPARSE ATTENTION FORWARD+BACKWARD BENCHMARK ===")
-
- # Combined forward+backward pass
- # Warm-up run
- q_fwd = q.clone().requires_grad_(True)
- k_fwd = k.clone().requires_grad_(True)
- v_fwd = v.clone().requires_grad_(True)
- o = block_sparse_attn(q_fwd, k_fwd, v_fwd, q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num)
- grad_output = torch.randn_like(o)
- o.backward(grad_output)
- torch.cuda.synchronize()
-
- # Benchmark forward+backward
- def forward_backward_fn():
- q_fwd = q.clone().requires_grad_(True)
- k_fwd = k.clone().requires_grad_(True)
- v_fwd = v.clone().requires_grad_(True)
- o = block_sparse_attn(q_fwd, k_fwd, v_fwd, q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num)
- grad_output = torch.randn_like(o)
- o.backward(grad_output)
-
- total_time = triton.testing.do_bench(
- forward_backward_fn,
- warmup=25,
- rep=100,
- return_mode='mean'
- )
-
- # Total flops for forward + backward (forward + 2.5x backward approximation)
- total_flops = flops + 2.5 * flops # 3.5x the forward flops
- sparse_tflops = total_flops / total_time * 1e-12 * 1e3
- print(f"Block Sparse Forward+Backward - TFLOPS: {sparse_tflops:.2f}")
-
- return sparse_tflops
-
-def main():
- args = parse_arguments()
-
- set_seed(42)
-
- # Extract parameters
- batch = args.batch_size
- head = args.num_heads
- headdim = args.head_dim
-
- print(f"Block Sparse Attention Benchmark")
- print(f"batch: {batch}, head: {head}, headdim: {headdim}")
-
- # Test with different sequence lengths
- for seq_len in args.seq_lengths:
- # Skip very long sequences if they might cause OOM
- if seq_len > 16384 and batch > 1:
- continue
-
- print("="*100)
- print(f"\nSequence length: {seq_len}")
-
- # Calculate theoretical FLOPs for attention
- flops = 4 * batch * head * headdim * seq_len * seq_len
-
- # Create input tensors
- q, k, v = create_input_tensors(batch, head, seq_len, headdim)
-
- # Setup block sparse parameters
- num_q_blocks = seq_len // BLOCK_M
- num_kv_blocks = seq_len // BLOCK_N
-
- # Determine k value (number of kv blocks per q block)
- topk = args.topk
- if topk is None:
- topk = num_kv_blocks // 10 # Default to ~90% sparsity if k is not specified
- topk = max(1, topk)
- print(f"Using topk={topk} kv blocks per q block (out of {num_kv_blocks} total kv blocks)")
-
- # Generate block sparse pattern
- q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, _ = generate_block_sparse_pattern(
- batch, head, num_q_blocks, num_kv_blocks, topk, device="cuda")
-
- # Benchmark block sparse attention
- sparse_fwd = benchmark_block_sparse_attention(
- q, k, v, q2k_block_sparse_index, q2k_block_sparse_num, k2q_block_sparse_index, k2q_block_sparse_num, flops
- )
-
- # Print results
- print("\n=== PERFORMANCE RESULTS ===")
- print(f"Block Sparse Forward+Backward - TFLOPS: {sparse_fwd:.2f}")
-
-if __name__ == "__main__":
- main()
\ No newline at end of file
diff --git a/csrc/attn/sliding_tile_attn/MANIFEST.in b/csrc/attn/sliding_tile_attn/MANIFEST.in
deleted file mode 100644
index cc38220c0..000000000
--- a/csrc/attn/sliding_tile_attn/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-recursive-include tk *
-include config_sta.py
diff --git a/csrc/attn/sliding_tile_attn/README.md b/csrc/attn/sliding_tile_attn/README.md
deleted file mode 100644
index b543d35b7..000000000
--- a/csrc/attn/sliding_tile_attn/README.md
+++ /dev/null
@@ -1,103 +0,0 @@
-
-# Attention Kernel Used in FastVideo
-
-## Sliding Tile Attention (STA)
-We support H100 (via TK) and any other GPU (via triton) for STA.
-
-### Installation
-```bash
-pip install st_attn
-```
-
-Install from source:
-
-```bash
-git submodule update --init --recursive
-python setup.py install
-```
-
-If you want to skip the compilation of the TK kernel and only use the Triton version, try below:
-
-```bash
-SKIP_SM90_EXT=1 python setup.py install
-or
-SKIP_SM90_EXT=1 pip install --no-build-isolation .
-```
-
-If you encounter error during installation, try below:
-Install C++20 for ThunderKittens:
-```bash
-sudo apt update
-sudo apt install gcc-11 g++-11
-
-sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11
-
-sudo apt update
-sudo apt install clang-11
-```
-(If you use CUDA12.8)
-```bash
-export CUDA_HOME=/usr/local/cuda-12.8
-export PATH=${CUDA_HOME}/bin:${PATH}
-export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH
-```
-
-### Usage
-End-2-end inference with FastVideo:
-```bash
-bash scripts/inference/v1_inference_wan_STA.sh
-```
-
-If you want to use sliding tile attention in your custom model:
-```python
-from st_attn import sliding_tile_attention
-# assuming video size (T, H, W) = (30, 48, 80), text tokens = 256 with padding.
-# q, k, v: [batch_size, num_heads, seq_length, head_dim], seq_length = T*H*W + 256
-# a tile is a cube of size (6, 8, 8)
-# window_size in tiles: [(window_t, window_h, window_w), (..)...]. For example, window size (3, 3, 3) means a query can attend to (3x6, 3x8, 3x8) = (18, 24, 24) tokens out of the total 30x48x80 video.
-# text_length: int ranging from 0 to 256
-# If your attention contains text token (Hunyuan)
-out = sliding_tile_attention(q, k, v, window_size, text_length)
-# If your attention does not contain text token (StepVideo)
-out = sliding_tile_attention(q, k, v, window_size, 0, False)
-```
-
-
-### Test
-```bash
-python ../tests/test_sta.py # test STA
-```
-### Benchmark
-```bash
-python ../benchmarks/bench_sta.py
-```
-
-
-### How Does STA Work?
-We give a demo for 2D STA with window size (6,6) operating on a (10, 10) image.
-
-
-https://github.com/user-attachments/assets/f3b6dd79-7b43-4b60-a0fa-3d6495ec5747
-
-
-## STA Configuration Logic
-Here is a diagram of how the window is configured and passed through the FastVideo pipeline:
-
-
-

-
-
-
-## Why is STA Fast?
-2D/3D Sliding Window Attention (SWA) creates many mixed blocks in the attention map. Even though mixed blocks have less output value,a mixed block is significantly slower than a dense block due to the GPU-unfriendly masking operation.
-
-STA removes mixed blocks.
-
-
-
-

-
-
-## Acknowledgement
-
-We learned or reuse code from FlexAtteniton, NATEN, and ThunderKittens.
diff --git a/csrc/attn/sliding_tile_attn/config_sta.py b/csrc/attn/sliding_tile_attn/config_sta.py
deleted file mode 100644
index f3008bcd3..000000000
--- a/csrc/attn/sliding_tile_attn/config_sta.py
+++ /dev/null
@@ -1,15 +0,0 @@
-### ADD TO THIS TO REGISTER NEW KERNELS
-sources = {
- 'st_attn': {
- 'source_files': {
- 'h100': 'st_attn/st_attn_h100.cu' # define these source files for each GPU target desired.
- }
- }
-}
-
-### WHICH KERNELS DO WE WANT TO BUILD?
-# (oftentimes during development work you don't need to redefine them all.)
-kernels = ['st_attn']
-
-### WHICH GPU TARGET DO WE WANT TO BUILD FOR?
-target = 'h100'
diff --git a/csrc/attn/sliding_tile_attn/setup.py b/csrc/attn/sliding_tile_attn/setup.py
deleted file mode 100644
index fc55f38d7..000000000
--- a/csrc/attn/sliding_tile_attn/setup.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import os
-import subprocess
-
-from config_sta import kernels, sources, target
-from setuptools import find_packages, setup
-from torch.utils.cpp_extension import BuildExtension, CUDAExtension
-
-target = target.lower()
-
-# Package metadata
-PACKAGE_NAME = "st_attn"
-VERSION = "0.0.6"
-AUTHOR = "Hao AI Lab"
-DESCRIPTION = "Sliding Tile Atteniton Kernel Used in FastVideo"
-URL = "https://github.com/hao-ai-lab/FastVideo/tree/main/csrc/sliding_tile_attention"
-
-# Set environment variables
-tk_root = os.getenv('THUNDERKITTENS_ROOT', os.path.abspath(os.path.join(os.getcwd(), 'tk/')))
-python_include = subprocess.check_output(['python', '-c',
- "import sysconfig; print(sysconfig.get_path('include'))"]).decode().strip()
-torch_include = subprocess.check_output([
- 'python', '-c',
- "import torch; from torch.utils.cpp_extension import include_paths; print(' '.join(['-I' + p for p in include_paths()]))"
-]).decode().strip()
-print('st_attn root:', tk_root)
-print('Python include:', python_include)
-print('Torch include directories:', torch_include)
-
-# CUDA flags
-cuda_flags = [
- '-DNDEBUG', '-Xcompiler=-Wno-psabi', '-Xcompiler=-fno-strict-aliasing', '--expt-extended-lambda',
- '--expt-relaxed-constexpr', '-forward-unknown-to-host-compiler', '--use_fast_math', '-std=c++20', '-O3',
- '-Xnvlink=--verbose', '-Xptxas=--verbose', '-Xptxas=--warn-on-spills', f'-I{tk_root}/include',
- f'-I{tk_root}/prototype', f'-I{python_include}', '-DTORCH_COMPILE'
-] + torch_include.split()
-cpp_flags = ['-std=c++20', '-O3']
-
-if target == 'h100':
- cuda_flags.append('-DKITTENS_HOPPER')
- cuda_flags.append('-arch=sm_90a')
-else:
- raise ValueError(f'Target {target} not supported')
-
-source_files = ['st_attn.cpp']
-for k in kernels:
- if target not in sources[k]['source_files']:
- raise KeyError(f'Target {target} not found in source files for kernel {k}')
- if isinstance(sources[k]['source_files'][target], list):
- source_files.extend(sources[k]['source_files'][target])
- else:
- source_files.append(sources[k]['source_files'][target])
- cpp_flags.append(f'-DTK_COMPILE_{k.replace(" ", "_").upper()}')
-
-ext_modules = []
-
-if os.environ.get("SKIP_SM90_EXT", "0") != "1":
- ext_modules.append(
- CUDAExtension('st_attn_cuda',
- sources=source_files,
- extra_compile_args={
- 'cxx': cpp_flags,
- 'nvcc': cuda_flags
- },
- libraries=['cuda'])
- )
-else:
- print("ENV SKIP_SM90_EXT=1, skip st_attn_cuda compile")
-
-setup(name=PACKAGE_NAME,
- version=VERSION,
- author=AUTHOR,
- description=DESCRIPTION,
- url=URL,
- packages=find_packages(),
- ext_modules=ext_modules,
- cmdclass={'build_ext': BuildExtension},
- classifiers=[
- "Programming Language :: Python :: 3",
- "Environment :: GPU :: NVIDIA CUDA :: 12",
- "License :: OSI Approved :: Apache Software License",
- ],
- python_requires='>=3.10',
- install_requires=["torch>=2.5.0"])
diff --git a/csrc/attn/sliding_tile_attn/st_attn.cpp b/csrc/attn/sliding_tile_attn/st_attn.cpp
deleted file mode 100644
index 41a63c17f..000000000
--- a/csrc/attn/sliding_tile_attn/st_attn.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-
-#ifdef TK_COMPILE_ST_ATTN
-extern torch::Tensor sta_forward(
- torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor o, int kernel_t_size, int kernel_w_size, int kernel_h_size, int text_length, bool process_text, bool has_text, int kernel_aspect_ratio_flag
-);
-#endif
-
-
-PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
- m.doc() = "Sliding Block Attention Kernels"; // optional module docstring
-
-#ifdef TK_COMPILE_ST_ATTN
- m.def("sta_fwd", torch::wrap_pybind_function(sta_forward), "sliding tile attention, assuming tile size is (6,8,8)");
-#endif
-}
diff --git a/csrc/attn/sliding_tile_attn/st_attn/__init__.py b/csrc/attn/sliding_tile_attn/st_attn/__init__.py
deleted file mode 100644
index 89fb94bf8..000000000
--- a/csrc/attn/sliding_tile_attn/st_attn/__init__.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import math
-
-import torch
-from torch.utils.checkpoint import detach_variable
-try:
- from st_attn_cuda import sta_fwd
-except ImportError:
- sta_fwd = None
-
-try:
- from st_attn.st_attn_triton import sliding_tile_attention_triton
-except ImportError:
- sliding_tile_attention_triton = None
-
-def sliding_tile_attention_SM90(q_all, k_all, v_all, window_size, text_length, has_text=True, dit_seq_shape='30x48x80'):
- seq_length = q_all.shape[2]
- dit_seq_shape_mapping = {
- '30x48x80':1,
- '36x48x48':2,
- '18x48x80':3,
- }
- if has_text:
- assert q_all.shape[
- 2] >= 115200 and q_all.shape[2] <= 115456, f"Unsupported {dit_seq_shape}, current shape is {q_all.shape}, only support '30x48x80' for HunyuanVideo"
- assert q_all.shape[1] == len(window_size), "Number of heads must match the number of window sizes"
- target_size = math.ceil(seq_length / 384) * 384
- pad_size = target_size - seq_length
- if pad_size > 0:
- q_all = torch.cat([q_all, q_all[:, :, -pad_size:]], dim=2)
- k_all = torch.cat([k_all, k_all[:, :, -pad_size:]], dim=2)
- v_all = torch.cat([v_all, v_all[:, :, -pad_size:]], dim=2)
- else:
- if dit_seq_shape == '36x48x48': # Stepvideo 204x768x68
- assert q_all.shape[2] == 82944
- elif dit_seq_shape == '18x48x80': # Wan 69x768x1280
- assert q_all.shape[2] == 69120
- else:
- raise ValueError(f"Unsupported {dit_seq_shape}, current shape is {q_all.shape}, only support '36x48x48' for Stepvideo and '18x48x80' for Wan")
-
- kernel_aspect_ratio_flag = dit_seq_shape_mapping[dit_seq_shape]
- hidden_states = torch.empty_like(q_all)
- # This for loop is ugly. but it is actually quite efficient. The sequence dimension alone can already oversubscribe SMs
- for head_index, (t_kernel, h_kernel, w_kernel) in enumerate(window_size):
- for batch in range(q_all.shape[0]):
- q_head, k_head, v_head, o_head = (q_all[batch:batch + 1, head_index:head_index + 1],
- k_all[batch:batch + 1,
- head_index:head_index + 1], v_all[batch:batch + 1,
- head_index:head_index + 1],
- hidden_states[batch:batch + 1, head_index:head_index + 1])
-
- _ = sta_fwd(q_head, k_head, v_head, o_head, t_kernel, h_kernel, w_kernel, text_length, False, has_text, kernel_aspect_ratio_flag)
- if has_text:
- _ = sta_fwd(q_all, k_all, v_all, hidden_states, 3, 3, 3, text_length, True, True, kernel_aspect_ratio_flag)
- return hidden_states[:, :, :seq_length]
-
-def sliding_tile_attention(q_all, k_all, v_all, window_size, text_length, has_text=True, dit_seq_shape='30x48x80'):
- major, minor = torch.cuda.get_device_capability(q_all.device)
- if major == 9 and minor == 0 and sta_fwd is not None:
- return sliding_tile_attention_SM90(q_all, k_all, v_all, window_size, text_length, has_text, dit_seq_shape)
- elif sliding_tile_attention_triton is not None:
- return sliding_tile_attention_triton(q_all, k_all, v_all, window_size, text_length, has_text, dit_seq_shape)
- else:
- raise ImportError("No suitable sliding tile attention implementation found.")
diff --git a/csrc/attn/sliding_tile_attn/st_attn/st_attn_h100.cu b/csrc/attn/sliding_tile_attn/st_attn/st_attn_h100.cu
deleted file mode 100644
index 208dc6ecc..000000000
--- a/csrc/attn/sliding_tile_attn/st_attn/st_attn_h100.cu
+++ /dev/null
@@ -1,841 +0,0 @@
-// # Define TORCH_COMPILE macro
-
-#include "kittens.cuh"
-#include
-#include
-#include
-#include
-
-// #define CLAMP(value, min, max) ((value) < (min) ? (min) : ((value) > (max) ? (max) : (value)))
-__device__ __forceinline__ int clamp_int(int value, int min, int max) {
- return (value < min) ? min : ((value > max) ? max : value);
-}
-// #define ABS(x) ((x) < 0 ? -(x) : (x))
-__device__ __forceinline__ int abs_int(int value) {
- return (value < 0) ? -value : value;
-}
-
-
-constexpr int CONSUMER_WARPGROUPS = (3);
-constexpr int PRODUCER_WARPGROUPS = (1);
-constexpr int NUM_WARPGROUPS = (CONSUMER_WARPGROUPS+PRODUCER_WARPGROUPS);
-constexpr int NUM_WORKERS = (NUM_WARPGROUPS*kittens::WARPGROUP_WARPS);
-
-using namespace kittens;
-namespace cg = cooperative_groups;
-
-template struct fwd_attend_ker_tile_dims {};
-template<> struct fwd_attend_ker_tile_dims<64> {
- constexpr static int tile_width = (64);
- constexpr static int qo_height = (4*16);
- constexpr static int kv_height = (8*16);
- constexpr static int stages = (4);
-};
-template<> struct fwd_attend_ker_tile_dims<128> {
- constexpr static int tile_width = (128);
- constexpr static int qo_height = (4*16);
- constexpr static int kv_height = (8*16);
- constexpr static int stages = (2);
-};
-
-template struct fwd_globals {
- using q_tile = st_bf::qo_height, fwd_attend_ker_tile_dims::tile_width>;
- using k_tile = st_bf::kv_height, fwd_attend_ker_tile_dims::tile_width>;
- using v_tile = st_bf::kv_height, fwd_attend_ker_tile_dims::tile_width>;
- using l_col_vec = col_vec::qo_height, fwd_attend_ker_tile_dims::tile_width>>;
- using o_tile = st_bf::qo_height, fwd_attend_ker_tile_dims::tile_width>;
-
- using q_gl = gl;
- using k_gl = gl;
- using v_gl = gl;
- using l_gl = gl;
- using o_gl = gl;
-
- q_gl q;
- k_gl k;
- v_gl v;
- l_gl l;
- o_gl o;
-
- const int N;
- const int text_L;
- const int hr;
-};
-
-
-template
-__global__ __launch_bounds__((NUM_WORKERS)*kittens::WARP_THREADS, 1)
-void fwd_attend_ker(const __grid_constant__ fwd_globals g) {
- extern __shared__ int __shm[];
- tma_swizzle_allocator al((int*)&__shm[0]);
- int warpid = kittens::warpid(), warpgroupid = warpid/kittens::WARPGROUP_WARPS;
-
- using K = fwd_attend_ker_tile_dims;
-
- using q_tile = st_bf;
- using k_tile = st_bf;
- using v_tile = st_bf;
- using l_col_vec = col_vec>;
- using o_tile = st_bf;
-
- q_tile (&q_smem)[CONSUMER_WARPGROUPS] = al.allocate();
- k_tile (&k_smem)[K::stages] = al.allocate();
- v_tile (&v_smem)[K::stages] = al.allocate();
- l_col_vec (&l_smem)[CONSUMER_WARPGROUPS] = al.allocate();
- auto (*o_smem) = reinterpret_cast(q_smem);
- int img_kv_blocks;
- int kv_blocks = g.N / (K::kv_height);
- if constexpr (text_kv) {
- img_kv_blocks = kv_blocks - 3;
- } else {
- img_kv_blocks = kv_blocks;
- }
- int kv_head_idx = blockIdx.y / g.hr;
- int seq_idx;
- if constexpr (text_q) {
- seq_idx = CT * CH * CW * 6.0 + blockIdx.x * CONSUMER_WARPGROUPS;
- } else {
- seq_idx = blockIdx.x * CONSUMER_WARPGROUPS;
- }
- __shared__ kittens::semaphore qsmem_semaphore, k_smem_arrived[K::stages], v_smem_arrived[K::stages], compute_done[K::stages];
- if (threadIdx.x == 0) {
- init_semaphore(qsmem_semaphore, 0, 1);
- for(int j = 0; j < K::stages; j++) {
- init_semaphore(k_smem_arrived[j], 0, 1);
- init_semaphore(v_smem_arrived[j], 0, 1);
- init_semaphore(compute_done[j], CONSUMER_WARPGROUPS, 0);
- }
-
- tma::expect_bytes(qsmem_semaphore, sizeof(q_smem));
-
- for (int wg = 0; wg < CONSUMER_WARPGROUPS; wg++) {
- coord q_tile_idx = {blockIdx.z, blockIdx.y, (seq_idx) + wg, 0};
- tma::load_async(q_smem[wg], g.q, q_tile_idx, qsmem_semaphore);
- }
-
- if constexpr (text_q){
- for (int j = 0; j < K::stages - 1; j++) {
- coord kv_tile_idx = {blockIdx.z, kv_head_idx, j, 0};
- tma::expect_bytes(k_smem_arrived[j], sizeof(k_tile));
- tma::load_async(k_smem[j], g.k, kv_tile_idx, k_smem_arrived[j]);
- tma::expect_bytes(v_smem_arrived[j], sizeof(v_tile));
- tma::load_async(v_smem[j], g.v, kv_tile_idx, v_smem_arrived[j]);
- }
- } else {
- int qt = seq_idx / 6 / (CH * CW);
- int qh = (seq_idx / 6) % (CH * CW) / CW;
- int qw = (seq_idx / 6) % CW;
- qt = clamp_int(qt, DT, CT-DT-1);
- qh = clamp_int(qh, DH, CH-DH-1);
- qw = clamp_int(qw, DW, CW-DW-1);
- int count = 0;
- int j = 0;
- while (count < K::stages - 1) {
- int kt = j / 3 / (CH * CW);
- int kh = (j / 3) % (CH * CW) / CW;
- int kw = (j / 3) % CW;
- bool mask = (abs_int(qt - kt) <= DT) && (abs_int(qh - kh) <= DH) && (abs_int(qw - kw) <= DW);
- if (mask){
- coord kv_tile_idx = {blockIdx.z, kv_head_idx, j, 0};
- tma::expect_bytes(k_smem_arrived[count], sizeof(k_tile));
- tma::load_async(k_smem[count], g.k, kv_tile_idx, k_smem_arrived[count]);
- tma::expect_bytes(v_smem_arrived[count], sizeof(v_tile));
- tma::load_async(v_smem[count], g.v, kv_tile_idx, v_smem_arrived[count]);
- count += 1;
- }
- j += 1;
- }
- }
- }
- __syncthreads();
-
- int pipe_idx = K::stages - 1;
-
- if(warpgroupid == NUM_WARPGROUPS-1) {
- warpgroup::decrease_registers<32>();
-
- int kv_iters;
- if constexpr (is_causal) {
- kv_iters = (seq_idx * (K::qo_height/kittens::TILE_ROW_DIM)) - 1 + (CONSUMER_WARPGROUPS * (K::qo_height/kittens::TILE_ROW_DIM));
- kv_iters = ((kv_iters / (K::kv_height/kittens::TILE_ROW_DIM)) == 0) ? (0) : ((kv_iters / (K::kv_height/kittens::TILE_ROW_DIM)) - 1);
- }
- else { kv_iters = kv_blocks-2;}
-
- if(warpid == NUM_WORKERS-4) {
- if constexpr (text_q){
- for (auto kv_idx = pipe_idx - 1; kv_idx <= kv_iters; kv_idx++) {
- coord kv_tile_idx = {blockIdx.z, kv_head_idx, kv_idx + 1, 0};
- tma::expect_bytes(k_smem_arrived[(kv_idx+1)%K::stages], sizeof(k_tile));
- tma::load_async(k_smem[(kv_idx+1)%K::stages], g.k, kv_tile_idx, k_smem_arrived[(kv_idx+1)%K::stages]);
- tma::expect_bytes(v_smem_arrived[(kv_idx+1)%K::stages], sizeof(v_tile));
- tma::load_async(v_smem[(kv_idx+1)%K::stages], g.v, kv_tile_idx, v_smem_arrived[(kv_idx+1)%K::stages]);
- kittens::wait(compute_done[(kv_idx)%K::stages], (kv_idx/K::stages)%2);
- }
- } else {
- int qt = seq_idx / 6 / (CH * CW);
- int qh = (seq_idx / 6) % (CH * CW) / CW;
- int qw = (seq_idx / 6) % CW;
- qt = clamp_int(qt, DT, CT-DT-1);
- qh = clamp_int(qh, DH, CH-DH-1);
- qw = clamp_int(qw, DW, CW-DW-1);
- int k_t_min = clamp_int(qt-DT, 0, CT-1);
- int k_t_max = clamp_int(qt+DT, 0, CT-1);
- int k_h_min = clamp_int(qh-DH, 0, CH-1);
- int k_h_max = clamp_int(qh+DH, 0, CH-1);
- int k_w_min = clamp_int(qw-DW, 0, CW-1);
- int k_w_max = clamp_int(qw+DW, 0, CW-1);
- int count = 0;
- for (int kt = k_t_min; kt <= k_t_max; kt++) {
- for (int kh = k_h_min; kh <= k_h_max; kh++) {
- for (int kw = k_w_min; kw <= k_w_max; kw++) {
- for (int j = 0; j <= 2; j++){
- if (count >= K::stages - 1) {
- int index = ((kt * (CH * CW)) + (kh * CW) + kw) * 3 + j;
- coord kv_tile_idx = {blockIdx.z, kv_head_idx, index, 0};
- tma::expect_bytes(k_smem_arrived[count%K::stages], sizeof(k_tile));
- tma::load_async(k_smem[count%K::stages], g.k, kv_tile_idx, k_smem_arrived[count%K::stages]);
- tma::expect_bytes(v_smem_arrived[count%K::stages], sizeof(v_tile));
- tma::load_async(v_smem[count%K::stages], g.v, kv_tile_idx, v_smem_arrived[count%K::stages]);
- kittens::wait(compute_done[(count - 1)%K::stages], ((count - 1)/K::stages)%2);
- count += 1;
- } else {
- count += 1;
- }
- }
- }
- }
- }
- // for text
- for (int index = img_kv_blocks; index < kv_blocks; index++) {
- coord kv_tile_idx = {blockIdx.z, kv_head_idx, index, 0};
- tma::expect_bytes(k_smem_arrived[count%K::stages], sizeof(k_tile));
- tma::load_async(k_smem[count%K::stages], g.k, kv_tile_idx, k_smem_arrived[count%K::stages]);
- tma::expect_bytes(v_smem_arrived[count%K::stages], sizeof(v_tile));
- tma::load_async(v_smem[count%K::stages], g.v, kv_tile_idx, v_smem_arrived[count%K::stages]);
- kittens::wait(compute_done[(count - 1)%K::stages], ((count - 1)/K::stages)%2);
- count += 1;
- }
- }
-
-
- }
- }
- else {
- warpgroup::increase_registers<160>();
-
- rt_fl<16, K::kv_height> att_block;
- rt_bf<16, K::kv_height> att_block_mma;
- rt_fl<16, K::tile_width> o_reg;
-
- col_vec> max_vec, norm_vec, max_vec_last_scaled, max_vec_scaled;
-
- neg_infty(max_vec);
- zero(norm_vec);
- zero(o_reg);
-
- int kv_iters;
- if constexpr (is_causal) {
- kv_iters = (seq_idx * 4) - 1 + (CONSUMER_WARPGROUPS * 4);
- kv_iters = (kv_iters/8);
- }
- else if constexpr (text_q){
- // the last three kv blocks are for text, we process them separately
- kv_iters = img_kv_blocks - 1;
- } else {
- kv_iters = clamp_int(DT*2+1, 1, CT) * clamp_int(DH*2+1, 1, CH) * clamp_int(DW*2+1, 1, CW) * 3 - 1 ;
- }
-
- kittens::wait(qsmem_semaphore, 0);
- for (auto kv_idx = 0; kv_idx <= kv_iters; kv_idx++) {
-
- kittens::wait(k_smem_arrived[(kv_idx)%K::stages], (kv_idx/K::stages)%2);
- warpgroup::mm_ABt(att_block, q_smem[warpgroupid], k_smem[(kv_idx)%K::stages]);
-
- copy(max_vec_last_scaled, max_vec);
- if constexpr (D == 64) { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.125f); }
- else { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.08838834764f); }
-
- warpgroup::mma_async_wait();
-
- row_max(max_vec, att_block, max_vec);
-
- if constexpr (D == 64) {
- mul(att_block, att_block, 1.44269504089f*0.125f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.125f);
- }
- else {
- mul(att_block, att_block, 1.44269504089f*0.08838834764f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.08838834764f);
- }
-
- sub_row(att_block, att_block, max_vec_scaled);
- exp2(att_block, att_block);
- sub(max_vec_last_scaled, max_vec_last_scaled, max_vec_scaled);
- exp2(max_vec_last_scaled, max_vec_last_scaled);
- mul(norm_vec, norm_vec, max_vec_last_scaled);
- row_sum(norm_vec, att_block, norm_vec);
- add(att_block, att_block, 0.f);
- copy(att_block_mma, att_block);
- mul_row(o_reg, o_reg, max_vec_last_scaled);
-
- kittens::wait(v_smem_arrived[(kv_idx)%K::stages], (kv_idx/K::stages)%2);
-
- warpgroup::mma_AB(o_reg, att_block_mma, v_smem[(kv_idx)%K::stages]);
- warpgroup::mma_async_wait();
-
- if(warpgroup::laneid() == 0) arrive(compute_done[(kv_idx)%K::stages], 1);
- }
- // the last three kv blocks are for text, we process them separately
- if constexpr(text_kv) {
- for (auto kv_idx = kv_iters + 1; kv_idx <= kv_iters + 3; kv_idx++) {
-
- kittens::wait(k_smem_arrived[(kv_idx)%K::stages], (kv_idx/K::stages)%2);
- warpgroup::mm_ABt(att_block, q_smem[warpgroupid], k_smem[(kv_idx)%K::stages]);
-
- copy(max_vec_last_scaled, max_vec);
- if constexpr (D == 64) { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.125f); }
- else { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.08838834764f); }
-
- warpgroup::mma_async_wait();
- // apply non-pad mask
- int offset = g.text_L - (kv_idx - (kv_iters + 1)) * K::kv_height;
- // printf("k_idx_start: %d, k_idx_end: %d, text_end: %d, offset: %d\n", k_idx_start, k_idx_end, text_end, offset);
- right_fill(att_block, att_block, offset, base_types::constants::neg_infty());
-
-
- row_max(max_vec, att_block, max_vec);
-
- if constexpr (D == 64) {
- mul(att_block, att_block, 1.44269504089f*0.125f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.125f);
- }
- else {
- mul(att_block, att_block, 1.44269504089f*0.08838834764f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.08838834764f);
- }
-
- sub_row(att_block, att_block, max_vec_scaled);
- exp2(att_block, att_block);
- sub(max_vec_last_scaled, max_vec_last_scaled, max_vec_scaled);
- exp2(max_vec_last_scaled, max_vec_last_scaled);
- mul(norm_vec, norm_vec, max_vec_last_scaled);
- row_sum(norm_vec, att_block, norm_vec);
- add(att_block, att_block, 0.f);
- copy(att_block_mma, att_block);
- mul_row(o_reg, o_reg, max_vec_last_scaled);
-
- kittens::wait(v_smem_arrived[(kv_idx)%K::stages], (kv_idx/K::stages)%2);
-
- warpgroup::mma_AB(o_reg, att_block_mma, v_smem[(kv_idx)%K::stages]);
- warpgroup::mma_async_wait();
-
- if(warpgroup::laneid() == 0) arrive(compute_done[(kv_idx)%K::stages], 1);
- }
- }
-
- div_row(o_reg, o_reg, norm_vec);
- warpgroup::store(o_smem[warpgroupid], o_reg);
- warpgroup::sync(warpgroupid+4);
-
- if (warpid % 4 == 0) {
- coord o_tile_idx = {blockIdx.z, blockIdx.y, (seq_idx) + warpgroupid, 0};
- tma::store_async(g.o, o_smem[warpgroupid], o_tile_idx);
- }
-
- mul(max_vec_scaled, max_vec_scaled, 0.69314718056f);
- log(norm_vec, norm_vec);
- add(norm_vec, norm_vec, max_vec_scaled);
-
- if constexpr (D == 64) { mul(norm_vec, norm_vec, -8.0f); }
- else { mul(norm_vec, norm_vec, -11.313708499f); }
-
- warpgroup::store(l_smem[warpgroupid], norm_vec);
- warpgroup::sync(warpgroupid+4);
-
- if (warpid % 4 == 0) {
- coord tile_idx = {blockIdx.z, blockIdx.y, 0, (seq_idx) + warpgroupid};
- tma::store_async(g.l, l_smem[warpgroupid], tile_idx);
- }
- tma::store_async_wait();
- }
-}
-
-
-
-#include "pyutils/torch_helpers.cuh"
-#include
-#include
-
-torch::Tensor
-sta_forward(torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor o, int kernel_t_size, int kernel_h_size, int kernel_w_size, int text_length, bool process_text, bool has_text, int kernel_aspect_ratio_flag)
-{
- CHECK_INPUT(q);
- CHECK_INPUT(k);
- CHECK_INPUT(v);
-
- auto batch = q.size(0);
- auto seq_len = q.size(2);
- auto head_dim = q.size(3);
- auto qo_heads = q.size(1);
- auto kv_heads = k.size(1);
-
- // check to see that these dimensions match for all inputs
- TORCH_CHECK(q.size(0) == batch, "Q batch dimension - idx 0 - must match for all inputs");
- TORCH_CHECK(k.size(0) == batch, "K batch dimension - idx 0 - must match for all inputs");
- TORCH_CHECK(v.size(0) == batch, "V batch dimension - idx 0 - must match for all inputs");
-
- TORCH_CHECK(q.size(2) == seq_len, "Q sequence length dimension - idx 2 - must match for all inputs");
- TORCH_CHECK(k.size(2) == seq_len, "K sequence length dimension - idx 2 - must match for all inputs");
- TORCH_CHECK(v.size(2) == seq_len, "V sequence length dimension - idx 2 - must match for all inputs");
-
- TORCH_CHECK(q.size(3) == head_dim, "Q head dimension - idx 3 - must match for all non-vector inputs");
- TORCH_CHECK(k.size(3) == head_dim, "K head dimension - idx 3 - must match for all non-vector inputs");
- TORCH_CHECK(v.size(3) == head_dim, "V head dimension - idx 3 - must match for all non-vector inputs");
-
- TORCH_CHECK(qo_heads >= kv_heads, "QO heads must be greater than or equal to KV heads");
- TORCH_CHECK(qo_heads % kv_heads == 0, "QO heads must be divisible by KV heads");
- TORCH_CHECK(q.size(1) == qo_heads, "QO head dimension - idx 1 - must match for all inputs");
- TORCH_CHECK(k.size(1) == kv_heads, "KV head dimension - idx 1 - must match for all inputs");
- TORCH_CHECK(v.size(1) == kv_heads, "KV head dimension - idx 1 - must match for all inputs");
-
- auto hr = qo_heads / kv_heads;
-
- c10::BFloat16* q_ptr = q.data_ptr();
- c10::BFloat16* k_ptr = k.data_ptr();
- c10::BFloat16* v_ptr = v.data_ptr();
-
- bf16* d_q = reinterpret_cast(q_ptr);
- bf16* d_k = reinterpret_cast(k_ptr);
- bf16* d_v = reinterpret_cast(v_ptr);
-
-
-
- torch::Tensor l_vec = torch::empty({static_cast(batch),
- static_cast(qo_heads),
- static_cast(seq_len),
- static_cast(1)},
- torch::TensorOptions().dtype(torch::kFloat).device(q.device()).memory_format(at::MemoryFormat::Contiguous));
-
-
- bf16* o_ptr = reinterpret_cast(o.data_ptr());
- bf16* d_o = reinterpret_cast(o_ptr);
-
- float* l_ptr = reinterpret_cast(l_vec.data_ptr());
- float* d_l = reinterpret_cast(l_ptr);
-
- //cudadevicesynchronize();
- const c10::cuda::OptionalCUDAGuard device_guard(q.device());
- const cudaStream_t stream = at::cuda::getCurrentCUDAStream().stream();
-
-
- if (head_dim == 128) {
- using q_tile = st_bf::qo_height, fwd_attend_ker_tile_dims<128>::tile_width>;
- using k_tile = st_bf::kv_height, fwd_attend_ker_tile_dims<128>::tile_width>;
- using v_tile = st_bf::kv_height, fwd_attend_ker_tile_dims<128>::tile_width>;
- using l_col_vec = col_vec::qo_height, fwd_attend_ker_tile_dims<128>::tile_width>>;
- using o_tile = st_bf::qo_height, fwd_attend_ker_tile_dims<128>::tile_width>;
-
- using q_global = gl;
- using k_global = gl;
- using v_global = gl;
- using l_global = gl;
- using o_global = gl;
-
- using globals = fwd_globals<128>;
-
- q_global qg_arg{d_q, static_cast(batch), static_cast(qo_heads), static_cast(seq_len), 128U};
- k_global kg_arg{d_k, static_cast(batch), static_cast(kv_heads), static_cast(seq_len), 128U};
- v_global vg_arg{d_v, static_cast(batch), static_cast(kv_heads), static_cast(seq_len), 128U};
- l_global lg_arg{d_l, static_cast(batch), static_cast(qo_heads), 1U, static_cast(seq_len)};
- o_global og_arg{d_o, static_cast(batch), static_cast(qo_heads), static_cast(seq_len), 128U};
-
- globals g{qg_arg, kg_arg, vg_arg, lg_arg, og_arg, static_cast(seq_len), static_cast(text_length), static_cast(hr)};
-
- constexpr int mem_size = kittens::MAX_SHARED_MEMORY;
- int threads = NUM_WORKERS * kittens::WARP_THREADS;
- if (has_text) {
- // TORCH_CHECK(seq_len % (CONSUMER_WARPGROUPS*kittens::TILE_DIM*4) == 0, "sequence length must be divisible by 192");
- dim3 grid_image(seq_len/(CONSUMER_WARPGROUPS*kittens::TILE_ROW_DIM*4)-2, qo_heads, batch);
- dim3 grid_text(2, qo_heads, batch);
- if (!process_text) {
- if (kernel_t_size == 3 && kernel_h_size == 3 && kernel_w_size == 3) {
-
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 1, 1, 1, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 1, 1, 1, 5, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 3 && kernel_h_size == 3 && kernel_w_size == 5) {
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 1, 1, 2, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true,1, 1, 2, 5, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 5 && kernel_h_size == 3 && kernel_w_size == 3) {
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 1, 1, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 2, 1, 1, 5, 6, 10><<>>(g);
-
- }else if (kernel_t_size ==3 && kernel_h_size == 5 && kernel_w_size == 5){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 1, 2, 2, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 1, 2, 2, 5, 6, 10><<>>(g);
-
- } else if (kernel_t_size ==5 && kernel_h_size == 6 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 3, 0, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 2, 3, 0, 5, 6, 10><<>>(g);
-
- } else if (kernel_t_size ==5 && kernel_h_size == 3 && kernel_w_size == 5){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 1, 2, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 2, 1, 2, 5, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 5 && kernel_h_size == 5 && kernel_w_size == 5){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 2, 2, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 2, 2, 2, 5, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 5 && kernel_h_size == 5 && kernel_w_size == 7){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 2, 3, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 2, 2, 3, 5, 6, 10><<>>(g);
- } else if (kernel_t_size == 5 && kernel_h_size == 6 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 3, 5, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 2, 3, 5, 5, 6, 10><<>>(g);
- } else if (kernel_t_size == 5 && kernel_h_size == 1 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 0, 0, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 2, 0, 0, 5, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 6 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 0, 3, 5, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true, 0, 3, 5, 5, 6, 10><<>>(g);
- } else if (kernel_t_size == 5 && kernel_h_size == 1 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, true, 2, 0, 5, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, true,2, 0, 5, 5, 6, 10><<>>(g);
- } else {
- // print error
- std::cout << "Invalid kernel size" << std::endl;
- //print kernel size
- std::cout << "Kernel size: " << kernel_t_size << " " << kernel_h_size << " " << kernel_w_size << std::endl;
- }
- } else {
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, true, true, 1, 1, 1, 5, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, true, true, 1, 1, 1, 5, 6, 10><<>>(g);
- }
-
- } else {
- dim3 grid_image(seq_len/(CONSUMER_WARPGROUPS*kittens::TILE_ROW_DIM*4), qo_heads, batch);
- if (kernel_aspect_ratio_flag == 2){
- if (kernel_t_size == 3 && kernel_h_size == 3 && kernel_w_size == 3) {
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 1, 1, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 1, 1, 6, 6, 6><<>>(g);
-
- } else if (kernel_t_size == 3 && kernel_h_size == 3 && kernel_w_size == 6) {
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 1, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false,1, 1, 3, 6, 6, 6><<>>(g);
-
- } else if (kernel_t_size == 6 && kernel_h_size == 3 && kernel_w_size == 3) {
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 1, 1, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 1, 1, 6, 6, 6><<>>(g);
-
- } else if (kernel_t_size ==3 && kernel_h_size == 6 && kernel_w_size == 6){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 3, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 3, 3, 6, 6, 6><<>>(g);
-
- }else if (kernel_t_size ==3 && kernel_h_size == 6 && kernel_w_size == 3){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 3, 1, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 3, 1, 6, 6, 6><<>>(g);
-
- } else if (kernel_t_size ==6 && kernel_h_size == 3 && kernel_w_size == 6){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 1, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 1, 3, 6, 6, 6><<>>(g);
-
- } else if (kernel_t_size == 6 && kernel_h_size == 6 && kernel_w_size == 6){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 3, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 3, 3, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 6 && kernel_h_size == 1 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 0, 0, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 0, 0, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 6 && kernel_h_size == 1 && kernel_w_size == 6){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 0, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 0, 3, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 6 && kernel_h_size == 6 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 3, 0, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 3, 0, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 6 && kernel_w_size == 6){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 3, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 3, 3, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 1 && kernel_w_size == 6){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 0, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 0, 3, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 6 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 3, 0, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 3, 0, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 6 && kernel_h_size == 6 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 3, 0, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 3, 0, 6, 6, 6><<>>(g);
- } else if (kernel_t_size == 6 && kernel_h_size == 1 && kernel_w_size == 6){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 3, 0, 3, 6, 6, 6>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 3, 0, 3, 6, 6, 6><<>>(g);
- } else {
- // print error
- std::cout << "Invalid kernel size" << std::endl;
- //print kernel size
- std::cout << "Kernel size: " << kernel_t_size << " " << kernel_h_size << " " << kernel_w_size << std::endl;
- }
- }
- else if (kernel_aspect_ratio_flag == 3) {
- if (kernel_t_size == 3 && kernel_h_size == 3 && kernel_w_size == 3) {
-
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 1, 1, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 1, 1, 3, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 3 && kernel_h_size == 3 && kernel_w_size == 5) {
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 1, 2, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false,1, 1, 2, 3, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 3 && kernel_h_size == 5 && kernel_w_size == 5){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 2, 2, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 2, 2, 3, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 3 && kernel_h_size == 6 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 3, 0, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 3, 0, 3, 6, 10><<>>(g);
-
- } else if (kernel_t_size == 3 && kernel_h_size == 5 && kernel_w_size == 7){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 2, 3, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 2, 3, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 3 && kernel_h_size == 5 && kernel_w_size == 9){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 2, 4, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 2, 4, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 3 && kernel_h_size == 6 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 3, 5, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 3, 5, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 3 && kernel_h_size == 6 && kernel_w_size == 3){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 3, 1, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 3, 1, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 3 && kernel_h_size == 1 && kernel_w_size == 1){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 0, 0, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 1, 0, 0, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 6 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 3, 5, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 3, 5, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 5 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 2, 5, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 2, 5, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 6 && kernel_w_size == 7){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 3, 3, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 3, 3, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 5 && kernel_w_size == 7){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 2, 3, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 2, 3, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 5 && kernel_w_size == 9){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 2, 4, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false, 0, 2, 4, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 3 && kernel_h_size == 1 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 0, 5, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false,1, 0, 5, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 3 && kernel_h_size == 3 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 1, 1, 5, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false,1, 1, 5, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 3 && kernel_w_size == 10){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 1, 5, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false,0, 1, 5, 3, 6, 10><<>>(g);
- } else if (kernel_t_size == 1 && kernel_h_size == 6 && kernel_w_size == 5){
- cudaFuncSetAttribute(
- fwd_attend_ker<128, false, false, false, 0, 3, 2, 3, 6, 10>,
- cudaFuncAttributeMaxDynamicSharedMemorySize,
- mem_size
- );
- fwd_attend_ker<128, false, false, false,0, 3, 2, 3, 6, 10><<>>(g);
- } else {
- // print error
- std::cout << "Invalid kernel size" << std::endl;
- //print kernel size
- std::cout << "Kernel size: " << kernel_t_size << " " << kernel_h_size << " " << kernel_w_size << std::endl;
- }
- }
-
- else {
- std::cout << "Unsupported kernel_aspect_ratio_flag: " << kernel_aspect_ratio_flag << std::endl;
- }
-
- }
- CHECK_CUDA_ERROR(cudaGetLastError());
- // cudaStreamSynchronize(stream);
- }
-
- return o;
- //cudadevicesynchronize();
-}
-
diff --git a/csrc/attn/tests/flex_sta_ref.py b/csrc/attn/tests/flex_sta_ref.py
deleted file mode 100644
index d7d9528a6..000000000
--- a/csrc/attn/tests/flex_sta_ref.py
+++ /dev/null
@@ -1,71 +0,0 @@
-from typing import Tuple
-
-import torch
-from torch import BoolTensor, IntTensor
-from torch.nn.attention.flex_attention import create_block_mask
-
-# Peiyuan: This is neccesay. Dont know why. see https://github.com/pytorch/pytorch/issues/135028
-torch._inductor.config.realize_opcount_threshold = 100
-
-
-def generate_sta_mask(canvas_twh, kernel_twh, tile_twh, text_length):
- """Generates a 3D NATTEN attention mask with a given kernel size.
-
- Args:
- canvas_t: The time dimension of the canvas.
- canvas_h: The height of the canvas.
- canvas_w: The width of the canvas.
- kernel_t: The time dimension of the kernel.
- kernel_h: The height of the kernel.
- kernel_w: The width of the kernel.
- """
- canvas_t, canvas_h, canvas_w = canvas_twh
- kernel_t, kernel_h, kernel_w = kernel_twh
- tile_t_size, tile_h_size, tile_w_size = tile_twh
- total_tile_size = tile_t_size * tile_h_size * tile_w_size
- canvas_tile_t, canvas_tile_h, canvas_tile_w = canvas_t // tile_t_size, canvas_h // tile_h_size, canvas_w // tile_w_size
- img_seq_len = canvas_t * canvas_h * canvas_w
-
- def get_tile_t_x_y(idx: IntTensor) -> Tuple[IntTensor, IntTensor, IntTensor]:
- tile_id = idx // total_tile_size
- tile_t = tile_id // (canvas_tile_h * canvas_tile_w)
- tile_h = (tile_id % (canvas_tile_h * canvas_tile_w)) // canvas_tile_w
- tile_w = tile_id % canvas_tile_w
- return tile_t, tile_h, tile_w
-
- def sta_mask_3d(
- b: IntTensor,
- h: IntTensor,
- q_idx: IntTensor,
- kv_idx: IntTensor,
- ) -> BoolTensor:
- q_t_tile, q_x_tile, q_y_tile = get_tile_t_x_y(q_idx)
- kv_t_tile, kv_x_tile, kv_y_tile = get_tile_t_x_y(kv_idx)
- # kernel nominally attempts to center itself on the query, but kernel center
- # is clamped to a fixed distance (kernel half-length) from the canvas edge
- kernel_center_t = q_t_tile.clamp(kernel_t // 2, (canvas_tile_t - 1) - kernel_t // 2)
- kernel_center_x = q_x_tile.clamp(kernel_h // 2, (canvas_tile_h - 1) - kernel_h // 2)
- kernel_center_y = q_y_tile.clamp(kernel_w // 2, (canvas_tile_w - 1) - kernel_w // 2)
- time_mask = (kernel_center_t - kv_t_tile).abs() <= kernel_t // 2
- hori_mask = (kernel_center_x - kv_x_tile).abs() <= kernel_h // 2
- vert_mask = (kernel_center_y - kv_y_tile).abs() <= kernel_w // 2
- image_mask = (q_idx < img_seq_len) & (kv_idx < img_seq_len)
- image_to_text_mask = (q_idx < img_seq_len) & (kv_idx >= img_seq_len) & (kv_idx < img_seq_len + text_length)
- text_to_all_mask = (q_idx >= img_seq_len) & (kv_idx < img_seq_len + text_length)
- return (image_mask & time_mask & hori_mask & vert_mask) | image_to_text_mask | text_to_all_mask
-
- sta_mask_3d.__name__ = f"natten_3d_c{canvas_t}x{canvas_w}x{canvas_h}_k{kernel_t}x{kernel_w}x{kernel_h}"
- return sta_mask_3d
-
-
-def get_sliding_tile_attention_mask(kernel_size, tile_size, img_size, text_length, device, text_max_len=256):
- img_seq_len = img_size[0] * img_size[1] * img_size[2]
- image_mask = generate_sta_mask(img_size, kernel_size, tile_size, text_length)
- mask = create_block_mask(image_mask,
- B=None,
- H=None,
- Q_LEN=img_seq_len + text_max_len,
- KV_LEN=img_seq_len + text_max_len,
- device=device,
- _compile=True)
- return mask
diff --git a/csrc/attn/tests/test_sta.py b/csrc/attn/tests/test_sta.py
deleted file mode 100644
index 699e56cd6..000000000
--- a/csrc/attn/tests/test_sta.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import torch
-from flex_sta_ref import get_sliding_tile_attention_mask
-from st_attn import sliding_tile_attention
-from torch.nn.attention.flex_attention import flex_attention
-# from flash_attn_interface import flash_attn_func
-from tqdm import tqdm
-
-flex_attention = torch.compile(flex_attention, dynamic=False)
-
-
-def flex_test(Q, K, V, kernel_size):
- mask = get_sliding_tile_attention_mask(kernel_size, (6, 8, 8), (18, 48, 80), 0, 'cuda', 0)
- output = flex_attention(Q, K, V, block_mask=mask)
-
- return output
-
-
-def h100_fwd_kernel_test(Q, K, V, kernel_size):
- o = sliding_tile_attention(Q, K, V, [kernel_size] * 24, 0, False, '18x48x80')
- return o
-
-
-def generate_tensor(shape, mean, std, dtype, device):
- tensor = torch.randn(shape, dtype=dtype, device=device)
-
- magnitude = torch.norm(tensor, dim=-1, keepdim=True)
- scaled_tensor = tensor * (torch.randn(magnitude.shape, dtype=dtype, device=device) * std + mean) / magnitude
-
- return scaled_tensor.contiguous()
-
-
-def check_correctness(b, h, n, d, causal, mean, std, num_iterations=50, error_mode='all'):
- results = {
- 'TK vs FLEX': {
- 'sum_diff': 0,
- 'sum_abs': 0,
- 'max_diff': 0
- },
- }
- kernel_size_ls = [(3, 3, 5), (3, 1, 10)]
- from tqdm import tqdm
- for kernel_size in tqdm(kernel_size_ls):
- for _ in range(num_iterations):
- torch.manual_seed(0)
-
- Q = generate_tensor((b, h, n, d), mean, std, torch.bfloat16, 'cuda')
- K = generate_tensor((b, h, n, d), mean, std, torch.bfloat16, 'cuda')
- V = generate_tensor((b, h, n, d), mean, std, torch.bfloat16, 'cuda')
- tk_o = h100_fwd_kernel_test(Q, K, V, kernel_size)
- pt_o = flex_test(Q, K, V, kernel_size)
-
- diff = pt_o - tk_o
- abs_diff = torch.abs(diff)
- results['TK vs FLEX']['sum_diff'] += torch.sum(abs_diff).item()
- results['TK vs FLEX']['max_diff'] = max(results['TK vs FLEX']['max_diff'], torch.max(abs_diff).item())
-
- torch.cuda.empty_cache()
- print("kernel_size", kernel_size)
- print("max_diff", torch.max(abs_diff).item())
- print(
- "avg_diff",
- torch.sum(abs_diff).item() / (b * h * n * d *
- (1 if error_mode == 'output' else 3 if error_mode == 'backward' else 4)))
-
- total_elements = b * h * n * d * num_iterations * (1 if error_mode == 'output' else
- 3 if error_mode == 'backward' else 4) * len(kernel_size_ls)
- for name, data in results.items():
- avg_diff = data['sum_diff'] / total_elements
- max_diff = data['max_diff']
- results[name] = {'avg_diff': avg_diff, 'max_diff': max_diff}
-
- return results
-
-
-# Example usage
-b, h, d = 2, 24, 128
-n = 69120 # Sequence length
-causal = False
-mean = 1e-1
-std = 10
-
-# Run correctness check directly
-results = check_correctness(b, h, n, d, causal, mean, std, error_mode='output')
-assert results['TK vs FLEX']['avg_diff'] < 3e-6, f"Average difference: {results['TK vs FLEX']['avg_diff']} is too large"
-assert results['TK vs FLEX']['max_diff'] < 4e-2, f"Maximum difference: {results['TK vs FLEX']['max_diff']} is too large"
-print(f"Average difference: {results['TK vs FLEX']['avg_diff']}")
-print(f"Maximum difference: {results['TK vs FLEX']['max_diff']}")
diff --git a/csrc/attn/tests/test_vsa.py b/csrc/attn/tests/test_vsa.py
deleted file mode 100644
index e2da3ec58..000000000
--- a/csrc/attn/tests/test_vsa.py
+++ /dev/null
@@ -1,245 +0,0 @@
-import torch
-import sys
-import os
-import numpy as np
-from tqdm import tqdm
-
-# Add the parent directory to the path to import block_sparse_attn
-sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
-from tests.utils import generate_block_sparse_mask_for_function, create_full_mask_from_block_mask
-from vsa import block_sparse_attn
-
-BLOCK_M = 64
-BLOCK_N = 64
-
-def pytorch_test(Q, K, V, block_sparse_mask, dO):
- q_ = Q.clone().float().requires_grad_()
- k_ = K.clone().float().requires_grad_()
- v_ = V.clone().float().requires_grad_()
-
- QK = torch.matmul(q_, k_.transpose(-2, -1))
- QK /= (q_.size(-1) ** 0.5)
- QK = QK.masked_fill(~block_sparse_mask.unsqueeze(0), float('-inf'))
-
- QK = torch.nn.functional.softmax(QK, dim=-1)
- output = torch.matmul(QK, v_)
-
- dO_ = dO
- output.backward(dO_)
- return (
- output.to(torch.bfloat16),
- q_.grad.to(torch.bfloat16),
- k_.grad.to(torch.bfloat16),
- v_.grad.to(torch.bfloat16),
- )
-
-
-def block_sparse_kernel_test(Q, K, V, block_sparse_mask, variable_block_sizes, q_non_pad_index, kv_non_pad_index, q_num_blocks, kv_num_blocks, dO):
- Q = Q.detach().requires_grad_()
- K = K.detach().requires_grad_()
- V = V.detach().requires_grad_()
-
- q_padded = vsa_pad(Q, q_non_pad_index, q_num_blocks, BLOCK_M)
- k_padded = vsa_pad(K, kv_non_pad_index, kv_num_blocks, BLOCK_M)
- v_padded = vsa_pad(V, kv_non_pad_index, kv_num_blocks, BLOCK_M)
- output, _= block_sparse_attn(q_padded, k_padded, v_padded, block_sparse_mask, variable_block_sizes)
- output = output[:, :, q_non_pad_index, :]
- output.backward(dO)
- return output, Q.grad, K.grad, V.grad
-
-
-def get_non_pad_index(
- vid_len: torch.LongTensor,
- n_win: int,
- win_size: int,
-):
- device = vid_len.device
- starts_pad = torch.arange(n_win, device=device) * win_size
- index_pad = starts_pad[:, None] + torch.arange(win_size, device=device)[None, :]
- index_mask = torch.arange(win_size, device=device)[None, :] < vid_len[:, None]
-
- return index_pad[index_mask]
-
-def generate_tensor(shape, dtype, device):
- tensor = torch.randn(shape, dtype=dtype, device=device)
- return tensor
-
-def generate_variable_block_sizes(num_blocks, min_size=16, max_size=64, device="cuda"):
- return torch.randint(min_size, max_size + 1, (num_blocks,), device=device, dtype=torch.int32)
-
-
-def vsa_pad(x, non_pad_index, num_blocks, block_size):
- padded_x = torch.zeros((1, x.shape[1], num_blocks * BLOCK_M, x.shape[3]), device=x.device, dtype=x.dtype)
- padded_x[:, :, non_pad_index, :] = x
- return padded_x
-
-def check_correctness(h, d, num_blocks, k, num_iterations=20, error_mode='all'):
- results = {
- 'gO': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- 'gQ': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- 'gK': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- 'gV': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- }
-
- device = "cuda" if torch.cuda.is_available() else "cpu"
- variable_block_sizes = generate_variable_block_sizes(num_blocks, device=device)
- S = int(variable_block_sizes.sum().item())
- padded_S = num_blocks * BLOCK_M
- non_pad_index = get_non_pad_index(variable_block_sizes, num_blocks, BLOCK_M)
- block_mask = generate_block_sparse_mask_for_function(h, num_blocks, num_blocks, k, device)
- full_mask = create_full_mask_from_block_mask(block_mask, variable_block_sizes, variable_block_sizes, device)
- for _ in range(num_iterations):
- Q = generate_tensor((1, h, S, d), torch.bfloat16, device)
- K = generate_tensor((1, h, S, d), torch.bfloat16, device)
- V = generate_tensor((1, h, S, d), torch.bfloat16, device)
- dO = generate_tensor((1, h, S, d), torch.bfloat16, device)
-
- # print(Q.shape, K.shape, V.shape, dO.shape)
-
- # dO_padded = torch.zeros_like(dO_padded)
- # dO_padded[:, :, non_pad_index, :] = dO
-
- pt_o, pt_qg, pt_kg, pt_vg = pytorch_test(Q, K, V, full_mask, dO)
- bs_o, bs_qg, bs_kg, bs_vg = block_sparse_kernel_test(Q, K, V, block_mask.unsqueeze(0), variable_block_sizes, non_pad_index, non_pad_index, num_blocks, num_blocks, dO)
- for name, (pt, bs) in zip(['gQ', 'gK', 'gV', 'gO'], [(pt_qg, bs_qg), (pt_kg, bs_kg), (pt_vg, bs_vg), (pt_o, bs_o)]):
- if bs is not None:
- diff = pt - bs
- abs_diff = torch.abs(diff)
- results[name]['sum_diff'] += torch.sum(abs_diff).item()
- results[name]['sum_abs'] += torch.sum(torch.abs(pt)).item()
- rel_max_diff = torch.max(abs_diff) / torch.mean(torch.abs(pt))
- results[name]['max_diff'] = max(results[name]['max_diff'], rel_max_diff.item())
- if torch.cuda.is_available():
- torch.cuda.empty_cache()
-
- total_elements = h * S * d * num_iterations
- for name, data in results.items():
- avg_diff = data['sum_diff'] / total_elements
- max_diff = data['max_diff']
- results[name] = {'avg_diff': avg_diff, 'max_diff': max_diff}
-
- return results
-
-def check_correctness_qkdiff(h, d, num_q_blocks, num_kv_blocks, k, num_iterations=20, error_mode='all'):
- results = {
- 'gO': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- 'gQ': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- 'gK': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- 'gV': {'sum_diff': 0.0, 'sum_abs': 0.0, 'max_diff': 0.0},
- }
-
- device = "cuda" if torch.cuda.is_available() else "cpu"
- q_variable_block_sizes = generate_variable_block_sizes(num_q_blocks, device=device)
- kv_variable_block_sizes = generate_variable_block_sizes(num_kv_blocks, device=device)
-
- S_q = int(q_variable_block_sizes.sum().item())
- S_kv = int(kv_variable_block_sizes.sum().item())
-
- q_non_pad_index = get_non_pad_index(q_variable_block_sizes, num_q_blocks, BLOCK_M)
- kv_non_pad_index = get_non_pad_index(kv_variable_block_sizes, num_kv_blocks, BLOCK_M)
-
- block_mask = generate_block_sparse_mask_for_function(h, num_q_blocks, num_kv_blocks, k, device)
- full_mask = create_full_mask_from_block_mask(block_mask, q_variable_block_sizes, kv_variable_block_sizes, device)
-
- for _ in range(num_iterations):
- Q = generate_tensor((1, h, S_q, d), torch.bfloat16, device)
- K = generate_tensor((1, h, S_kv, d), torch.bfloat16, device)
- V = generate_tensor((1, h, S_kv, d), torch.bfloat16, device)
- dO = generate_tensor((1, h, S_q, d), torch.bfloat16, device)
-
- # print(Q.shape, K.shape, V.shape, dO.shape)
-
- pt_o, pt_qg, pt_kg, pt_vg = pytorch_test(Q, K, V, full_mask, dO)
- bs_o, bs_qg, bs_kg, bs_vg = block_sparse_kernel_test(Q, K, V, block_mask.unsqueeze(0), kv_variable_block_sizes, q_non_pad_index, kv_non_pad_index, num_q_blocks, num_kv_blocks, dO)
-
- for name, (pt, bs) in zip(['gQ', 'gK', 'gV', 'gO'], [(pt_qg, bs_qg), (pt_kg, bs_kg), (pt_vg, bs_vg), (pt_o, bs_o)]):
- if bs is not None:
- diff = pt - bs
- abs_diff = torch.abs(diff)
- results[name]['sum_diff'] += torch.sum(abs_diff).item()
- results[name]['sum_abs'] += torch.sum(torch.abs(pt)).item()
- rel_max_diff = torch.max(abs_diff) / torch.mean(torch.abs(pt))
- results[name]['max_diff'] = max(results[name]['max_diff'], rel_max_diff.item())
- if torch.cuda.is_available():
- torch.cuda.empty_cache()
-
- total_elements_q = h * S_q * d * num_iterations
- total_elements_kv = h * S_kv * d * num_iterations
-
- for name, data in results.items():
- total_elements = total_elements_q if name in ['gQ', 'gO'] else total_elements_kv
- avg_diff = data['sum_diff'] / total_elements
- max_diff = data['max_diff']
- results[name] = {'avg_diff': avg_diff, 'max_diff': max_diff}
-
- return results
-
-def generate_error_graphs(h, d, error_mode='all'):
- test_configs = [
- {"num_blocks": 16, "k": 2, "description": "Small sequence"},
- {"num_blocks": 32, "k": 4, "description": "Medium sequence"},
- {"num_blocks": 53, "k": 6, "description": "Large sequence"},
- ]
-
- print(f"\nError Analysis for h={h}, d={d}, mode={error_mode}")
- print("=" * 150)
- print(f"{'Config':<20} {'Blocks':<8} {'K':<4} "
- f"{'gQ Avg':<12} {'Rel gQ Max':<12} "
- f"{'gK Avg':<12} {'Rel gK Max':<12} "
- f"{'gV Avg':<12} {'Rel gV Max':<12} "
- f"{'gO Avg':<12} {'Rel gO Max':<12}")
- print("-" * 150)
-
- for config in test_configs:
- num_blocks = config["num_blocks"]
- k = config["k"]
- description = config["description"]
- results = check_correctness(h, d, num_blocks, k, error_mode=error_mode)
- print(f"{description:<20} {num_blocks:<8} {k:<4} "
- f"{results['gQ']['avg_diff']:<12.6e} {results['gQ']['max_diff']:<12.6e} "
- f"{results['gK']['avg_diff']:<12.6e} {results['gK']['max_diff']:<12.6e} "
- f"{results['gV']['avg_diff']:<12.6e} {results['gV']['max_diff']:<12.6e} "
- f"{results['gO']['avg_diff']:<12.6e} {results['gO']['max_diff']:<12.6e}")
-
- print("-" * 150)
-
-def generate_error_graphs_qkdiff(h, d, error_mode='all'):
- test_configs = [
- {"num_q_blocks": 16, "num_kv_blocks": 32, "k": 2, "description": "Small Q, Med KV"},
- {"num_q_blocks": 32, "num_kv_blocks": 16, "k": 4, "description": "Med Q, Small KV"},
- {"num_q_blocks": 53, "num_kv_blocks": 32, "k": 6, "description": "Large Q, Med KV"},
- {"num_q_blocks": 16, "num_kv_blocks": 48, "k": 2, "description": "Small Q, Large KV"},
- {"num_q_blocks": 48, "num_kv_blocks": 16, "k": 2, "description": "Large Q, Small KV"},
- ]
-
- print(f"\nError Analysis (QK Diff) for h={h}, d={d}, mode={error_mode}")
- print("=" * 150)
- print(f"{'Config':<20} {'Q Blks':<8} {'KV Blks':<8} {'K':<4} "
- f"{'gQ Avg':<12} {'Rel gQ Max':<12} "
- f"{'gK Avg':<12} {'Rel gK Max':<12} "
- f"{'gV Avg':<12} {'Rel gV Max':<12} "
- f"{'gO Avg':<12} {'Rel gO Max':<12}")
- print("-" * 150)
-
- for config in test_configs:
- num_q_blocks = config["num_q_blocks"]
- num_kv_blocks = config["num_kv_blocks"]
- k = config["k"]
- description = config["description"]
- results = check_correctness_qkdiff(h, d, num_q_blocks, num_kv_blocks, k, error_mode=error_mode)
- print(f"{description:<20} {num_q_blocks:<8} {num_kv_blocks:<8} {k:<4} "
- f"{results['gQ']['avg_diff']:<12.6e} {results['gQ']['max_diff']:<12.6e} "
- f"{results['gK']['avg_diff']:<12.6e} {results['gK']['max_diff']:<12.6e} "
- f"{results['gV']['avg_diff']:<12.6e} {results['gV']['max_diff']:<12.6e} "
- f"{results['gO']['avg_diff']:<12.6e} {results['gO']['max_diff']:<12.6e}")
-
- print("-" * 150)
-
-if __name__ == "__main__":
- h, d = 16, 128
- print("Block Sparse Attention with Variable Block Sizes Analysis")
- print("=" * 60)
- for mode in ['backward']:
- generate_error_graphs(h, d, error_mode=mode)
- generate_error_graphs_qkdiff(h, d, error_mode=mode)
- print("\nAnalysis completed for all modes.")
\ No newline at end of file
diff --git a/csrc/attn/tests/test_vsa_forward.py b/csrc/attn/tests/test_vsa_forward.py
deleted file mode 100644
index d20055094..000000000
--- a/csrc/attn/tests/test_vsa_forward.py
+++ /dev/null
@@ -1,236 +0,0 @@
-import os
-import sys
-from typing import Tuple
-
-import torch
-
-# Make sure we can import from the project root (`vsa`, `tests.utils`, etc.)
-CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
-PROJECT_ROOT = os.path.dirname(CURRENT_DIR)
-if PROJECT_ROOT not in sys.path:
- sys.path.append(PROJECT_ROOT)
-if CURRENT_DIR not in sys.path:
- sys.path.append(CURRENT_DIR)
-
-from tests.utils import (
- generate_block_sparse_mask_for_function,
- create_full_mask_from_block_mask,
-)
-from vsa import block_sparse_attn, BLOCK_M
-import test_vsa as ref # reuse helper functions from backward test
-
-
-def pytorch_forward(
- Q: torch.Tensor,
- K: torch.Tensor,
- V: torch.Tensor,
- block_sparse_mask: torch.Tensor,
-) -> torch.Tensor:
- """
- Dense PyTorch reference forward:
- - Q: [1, h, S_q, d]
- - K,V: [1, h, S_kv, d]
- - block_sparse_mask: [h, S_q, S_kv] bool
- """
- q = Q.clone().float()
- k = K.clone().float()
- v = V.clone().float()
-
- attn = torch.matmul(q, k.transpose(-2, -1)) # [1, h, S_q, S_kv]
- attn = attn / (q.size(-1) ** 0.5)
- attn = attn.masked_fill(~block_sparse_mask.unsqueeze(0), float("-inf"))
- attn = torch.nn.functional.softmax(attn, dim=-1)
- out = torch.matmul(attn, v) # [1, h, S_q, d]
- return out.to(torch.bfloat16)
-
-
-def block_sparse_forward_test(
- Q: torch.Tensor,
- K: torch.Tensor,
- V: torch.Tensor,
- block_sparse_mask: torch.Tensor,
- variable_block_sizes: torch.Tensor,
- q_non_pad_index: torch.Tensor,
- kv_non_pad_index: torch.Tensor,
- q_num_blocks: int,
- kv_num_blocks: int,
-) -> torch.Tensor:
- """
- Forward-only wrapper around `block_sparse_attn`, mirroring `block_sparse_kernel_test`
- but without any backward / grad logic.
- """
- Q = Q.detach()
- K = K.detach()
- V = V.detach()
-
- q_padded = ref.vsa_pad(Q, q_non_pad_index, q_num_blocks, BLOCK_M)
- k_padded = ref.vsa_pad(K, kv_non_pad_index, kv_num_blocks, BLOCK_M)
- v_padded = ref.vsa_pad(V, kv_non_pad_index, kv_num_blocks, BLOCK_M)
-
- out_padded, _ = block_sparse_attn(
- q_padded, k_padded, v_padded, block_sparse_mask, variable_block_sizes
- )
- # Remove padding on the query side
- out = out_padded[:, :, q_non_pad_index, :]
- return out
-
-
-def run_forward_equal_qk(
- h: int = 16,
- d: int = 128,
- num_blocks: int = 16,
- k: int = 2,
- num_iterations: int = 5,
-) -> Tuple[float, float]:
- """
- Forward-only correctness test for the case S_q == S_kv.
- Mirrors `check_correctness` but only compares forward outputs.
- """
- assert torch.cuda.is_available(), "VSA kernels require CUDA"
- device = "cuda"
-
- variable_block_sizes = ref.generate_variable_block_sizes(
- num_blocks, device=device
- )
- S = int(variable_block_sizes.sum().item())
- non_pad_index = ref.get_non_pad_index(
- variable_block_sizes, num_blocks, BLOCK_M
- )
-
- block_mask = generate_block_sparse_mask_for_function(
- h, num_blocks, num_blocks, k, device
- )
- full_mask = create_full_mask_from_block_mask(
- block_mask, variable_block_sizes, variable_block_sizes, device
- )
- print(f"[qkequal] h: {h}, d: {d}, num_blocks: {num_blocks}, k: {k}")
- print(f"[qkequal] variable_block_sizes: {variable_block_sizes}, non_pad_index: {non_pad_index.shape}, block_mask: {block_mask.shape}, full_mask: {full_mask.shape}")
- sum_diff = 0.0
- sum_abs = 0.0
- max_rel_diff = 0.0
-
- for i in range(num_iterations):
- Q = ref.generate_tensor((1, h, S, d), torch.bfloat16, device)
- K = ref.generate_tensor((1, h, S, d), torch.bfloat16, device)
- V = ref.generate_tensor((1, h, S, d), torch.bfloat16, device)
-
- if i == 0: print(f"[qkequal] Q: {Q.shape}, K: {K.shape}, V: {V.shape}, full_mask: {full_mask.shape}")
- if i == 0: print(f"[qkequal] block_mask: {block_mask.shape}")
-
- pt_o = pytorch_forward(Q, K, V, full_mask)
- bs_o = block_sparse_forward_test(
- Q,
- K,
- V,
- block_mask.unsqueeze(0),
- variable_block_sizes,
- non_pad_index,
- non_pad_index,
- num_blocks,
- num_blocks,
- )
-
- diff = (pt_o - bs_o).abs()
- sum_diff += diff.sum().item()
- sum_abs += pt_o.abs().sum().item()
- rel_max = diff.max() / (pt_o.abs().mean() + 1e-6)
- max_rel_diff = max(max_rel_diff, rel_max.item())
-
- total_elems = h * S * d * num_iterations
- avg_abs_err = sum_diff / total_elems
- return avg_abs_err, max_rel_diff
-
-
-def run_forward_qk_diff(
- h: int = 16,
- d: int = 128,
- num_q_blocks: int = 16,
- num_kv_blocks: int = 32,
- k: int = 2,
- num_iterations: int = 5,
-) -> Tuple[float, float]:
- """
- Forward-only correctness test for the case S_q != S_kv.
-
- NOTE:
- - The Triton backend supports different Q/KV logical lengths via padding.
- - The SM90 (H100) CUDA backend currently assumes the same number of blocks
- for Q and KV, so we skip this test there.
- """
- assert torch.cuda.is_available(), "VSA kernels require CUDA"
-
- device = "cuda"
-
- q_variable_block_sizes = ref.generate_variable_block_sizes(
- num_q_blocks, device=device
- )
- kv_variable_block_sizes = ref.generate_variable_block_sizes(
- num_kv_blocks, device=device
- )
-
- S_q = int(q_variable_block_sizes.sum().item())
- S_kv = int(kv_variable_block_sizes.sum().item())
-
- q_non_pad_index = ref.get_non_pad_index(
- q_variable_block_sizes, num_q_blocks, BLOCK_M
- )
- kv_non_pad_index = ref.get_non_pad_index(
- kv_variable_block_sizes, num_kv_blocks, BLOCK_M
- )
-
- block_mask = generate_block_sparse_mask_for_function(
- h, num_q_blocks, num_kv_blocks, k, device
- )
- full_mask = create_full_mask_from_block_mask(
- block_mask, q_variable_block_sizes, kv_variable_block_sizes, device
- )
-
- sum_diff = 0.0
- sum_abs = 0.0
- max_rel_diff = 0.0
-
- for _ in range(num_iterations):
- Q = ref.generate_tensor((1, h, S_q, d), torch.bfloat16, device)
- K = ref.generate_tensor((1, h, S_kv, d), torch.bfloat16, device)
- V = ref.generate_tensor((1, h, S_kv, d), torch.bfloat16, device)
-
- pt_o = pytorch_forward(Q, K, V, full_mask)
- bs_o = block_sparse_forward_test(
- Q,
- K,
- V,
- block_mask.unsqueeze(0),
- kv_variable_block_sizes,
- q_non_pad_index,
- kv_non_pad_index,
- num_q_blocks,
- num_kv_blocks,
- )
-
- diff = (pt_o - bs_o).abs()
- sum_diff += diff.sum().item()
- sum_abs += pt_o.abs().sum().item()
- rel_max = diff.max() / (pt_o.abs().mean() + 1e-6)
- max_rel_diff = max(max_rel_diff, rel_max.item())
-
- total_elems = h * S_q * d * num_iterations
- avg_abs_err = sum_diff / total_elems
- return avg_abs_err, max_rel_diff
-
-
-if __name__ == "__main__":
- h, d = 16, 128
- print("Forward Block Sparse Attention Check (QK Equal)")
- print("=" * 80)
- avg_err_eq, max_rel_eq = run_forward_equal_qk(h, d, num_blocks=32, k=2)
- print(f"QK equal: avg |ΔO| = {avg_err_eq:.6e}, max rel ΔO = {max_rel_eq:.6e}")
-
- print("\nForward Block Sparse Attention Check (QK Different)")
- print("=" * 80)
- avg_err_diff, max_rel_diff = run_forward_qk_diff(
- h, d, num_q_blocks=32, num_kv_blocks=48, k=2
- )
- print(
- f"QK diff: avg |ΔO| = {avg_err_diff:.6e}, max rel ΔO = {max_rel_diff:.6e}"
- )
-
diff --git a/csrc/attn/tests/utils.py b/csrc/attn/tests/utils.py
deleted file mode 100644
index 3d9f15f45..000000000
--- a/csrc/attn/tests/utils.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import torch
-
-def generate_block_sparse_mask_for_function(h, num_q_blocks, num_kv_blocks, k, device="cuda"):
- """
- Generate block sparse mask of shape [h, num_q_blocks, num_kv_blocks].
-
- Args:
- h: number of heads
- num_q_blocks: number of query blocks
- num_kv_blocks: number of key/value blocks
- k: number of kv blocks each q block attends to
- device: device to create tensors on
-
- Returns:
- block_sparse_mask: [h, num_q_blocks, num_kv_blocks] bool tensor
- """
- k = min(k, num_kv_blocks)
- scores = torch.rand(h, num_q_blocks, num_kv_blocks, device=device)
- _, indices = torch.topk(scores, k, dim=-1)
- block_sparse_mask = torch.zeros(h, num_q_blocks, num_kv_blocks, dtype=torch.bool, device=device)
-
- block_sparse_mask = block_sparse_mask.scatter_(2, indices, 1).bool()
- return block_sparse_mask
-
-
-def create_full_mask_from_block_mask(block_sparse_mask, q_variable_block_sizes,
- kv_variable_block_sizes, device="cuda"):
- """
- Convert block-level sparse mask to full attention mask.
-
- Args:
- block_sparse_mask: [h, num_q_blocks, num_kv_blocks] bool tensor
- q_variable_block_sizes: [num_q_blocks] tensor
- kv_variable_block_sizes: [num_kv_blocks] tensor
- device: device to create tensors on
-
- Returns:
- full_mask: [h, S_q, S_kv] bool tensor where S = total sequence length
- """
- h, num_q_blocks, num_kv_blocks = block_sparse_mask.shape
- total_q_seq_len = q_variable_block_sizes.sum().item()
- total_kv_seq_len = kv_variable_block_sizes.sum().item()
-
- q_cumsum = torch.cat([torch.tensor([0], device=device), q_variable_block_sizes.cumsum(dim=0)[:-1]])
- kv_cumsum = torch.cat([torch.tensor([0], device=device), kv_variable_block_sizes.cumsum(dim=0)[:-1]])
-
- full_mask = torch.zeros(h, total_q_seq_len, total_kv_seq_len, dtype=torch.bool, device=device)
-
- for head in range(h):
- for q_block in range(num_q_blocks):
- q_start = q_cumsum[q_block]
- q_end = q_start + q_variable_block_sizes[q_block]
-
- for kv_block in range(num_kv_blocks):
- if block_sparse_mask[head, q_block, kv_block]:
- kv_start = kv_cumsum[kv_block]
- kv_end = kv_start + kv_variable_block_sizes[kv_block]
- full_mask[head, q_start:q_end, kv_start:kv_end] = True
-
- return full_mask
\ No newline at end of file
diff --git a/csrc/attn/video_sparse_attn/MANIFEST.in b/csrc/attn/video_sparse_attn/MANIFEST.in
deleted file mode 100644
index de701fcb9..000000000
--- a/csrc/attn/video_sparse_attn/MANIFEST.in
+++ /dev/null
@@ -1,2 +0,0 @@
-recursive-include tk *
-include config_vsa.py
diff --git a/csrc/attn/video_sparse_attn/README.md b/csrc/attn/video_sparse_attn/README.md
deleted file mode 100644
index c4a027592..000000000
--- a/csrc/attn/video_sparse_attn/README.md
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-# Attention Kernel Used in FastVideo
-
-## Video Sparse Attention (VSA)
-
-### Installation
-We support H100 (via TK) and any other GPU (via triton) for VSA.
-
-```bash
-pip install vsa
-```
-
-Install from source:
-
-```bash
-git submodule update --init --recursive
-python setup.py install
-```
-
-
-If you encounter error during installation, try below:
-Install C++20 for ThunderKittens:
-```bash
-sudo apt update
-sudo apt install gcc-11 g++-11
-
-sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100 --slave /usr/bin/g++ g++ /usr/bin/g++-11
-
-sudo apt update
-sudo apt install clang-11
-```
-(If you use CUDA12.8)
-```bash
-export CUDA_HOME=/usr/local/cuda-12.8
-export PATH=${CUDA_HOME}/bin:${PATH}
-export LD_LIBRARY_PATH=${CUDA_HOME}/lib64:$LD_LIBRARY_PATH
-```
-
-### Verify if you have successfully installed
-
-```bash
-# test numerical
-python ../tests/test_vsa.py
-# (For H100) test speed
-python ../benchmarks/bench_vsa_hopper.py
-```
-
-bench_vsa_hopper.py should print something like this:
-
-```bash
-Using topk=76 kv blocks per q block (out of 768 total kv blocks)
-
-=== BLOCK SPARSE ATTENTION BENCHMARK ===
-Block Sparse Forward - TFLOPS: 5622.26
-Block Sparse Backward - TFLOPS: 3865.68
-```
-
-## Acknowledgement
-
-We learned or reuse code from FlexAtteniton, NATEN, and ThunderKittens.
diff --git a/csrc/attn/video_sparse_attn/config_vsa.py b/csrc/attn/video_sparse_attn/config_vsa.py
deleted file mode 100644
index 40e725426..000000000
--- a/csrc/attn/video_sparse_attn/config_vsa.py
+++ /dev/null
@@ -1,15 +0,0 @@
-### ADD TO THIS TO REGISTER NEW KERNELS
-sources = {
- 'block_sparse': {
- 'source_files': {
- 'h100': 'vsa/block_sparse_h100.cu'
- }
- }
-}
-
-### WHICH KERNELS DO WE WANT TO BUILD?
-# (oftentimes during development work you don't need to redefine them all.)
-kernels = ['block_sparse']
-
-### WHICH GPU TARGET DO WE WANT TO BUILD FOR?
-target = 'h100'
diff --git a/csrc/attn/video_sparse_attn/setup.py b/csrc/attn/video_sparse_attn/setup.py
deleted file mode 100644
index f59303f3c..000000000
--- a/csrc/attn/video_sparse_attn/setup.py
+++ /dev/null
@@ -1,81 +0,0 @@
-import os
-import subprocess
-
-from config_vsa import kernels, sources, target
-from setuptools import find_packages, setup
-from torch.utils.cpp_extension import BuildExtension, CUDAExtension
-
-target = target.lower()
-
-# Package metadata
-PACKAGE_NAME = "vsa"
-VERSION = "0.0.3"
-AUTHOR = "Hao AI Lab"
-DESCRIPTION = "Video Sparse Attention Kernel Used in FastVideo"
-URL = "https://github.com/hao-ai-lab/FastVideo/tree/main/csrc/attn/video_sparse_attn"
-
-# Set environment variables
-tk_root = os.getenv('THUNDERKITTENS_ROOT', os.path.abspath(os.path.join(os.getcwd(), 'tk/')))
-python_include = subprocess.check_output(['python', '-c',
- "import sysconfig; print(sysconfig.get_path('include'))"]).decode().strip()
-torch_include = subprocess.check_output([
- 'python', '-c',
- "import torch; from torch.utils.cpp_extension import include_paths; print(' '.join(['-I' + p for p in include_paths()]))"
-]).decode().strip()
-print('vsa root:', tk_root)
-print('Python include:', python_include)
-print('Torch include directories:', torch_include)
-
-# CUDA flags
-cuda_flags = [
- '-DNDEBUG', '-Xcompiler=-Wno-psabi', '-Xcompiler=-fno-strict-aliasing', '--expt-extended-lambda',
- '--expt-relaxed-constexpr', '-forward-unknown-to-host-compiler', '--use_fast_math', '-std=c++20', '-O3',
- '-Xnvlink=--verbose', '-Xptxas=--verbose', '-Xptxas=--warn-on-spills', f'-I{tk_root}/include',
- f'-I{tk_root}/prototype', f'-I{python_include}', '-DTORCH_COMPILE'
-] + torch_include.split()
-cpp_flags = ['-std=c++20', '-O3']
-
-if target == 'h100':
- cuda_flags.append('-DKITTENS_HOPPER')
- cuda_flags.append('-arch=sm_90a')
-else:
- raise ValueError(f'Target {target} not supported')
-
-source_files = ['vsa.cpp']
-for k in kernels:
- if target not in sources[k]['source_files']:
- raise KeyError(f'Target {target} not found in source files for kernel {k}')
- if isinstance(sources[k]['source_files'][target], list):
- source_files.extend(sources[k]['source_files'][target])
- else:
- source_files.append(sources[k]['source_files'][target])
- cpp_flags.append(f'-DTK_COMPILE_{k.replace(" ", "_").upper()}')
-
-
-ext_modules = [
- CUDAExtension('vsa_cuda',
- sources=source_files,
- extra_compile_args={
- 'cxx': cpp_flags,
- 'nvcc': cuda_flags
- },
- libraries=['cuda'])
-]
-
-
-
-setup(name=PACKAGE_NAME,
- version=VERSION,
- author=AUTHOR,
- description=DESCRIPTION,
- url=URL,
- packages=find_packages(),
- ext_modules=ext_modules,
- cmdclass={'build_ext': BuildExtension},
- classifiers=[
- "Programming Language :: Python :: 3",
- "Environment :: GPU :: NVIDIA CUDA :: 12",
- "License :: OSI Approved :: Apache Software License",
- ],
- python_requires='>=3.10',
- install_requires=["torch>=2.5.0"])
diff --git a/csrc/attn/video_sparse_attn/tk b/csrc/attn/video_sparse_attn/tk
deleted file mode 160000
index 6c27e28c8..000000000
--- a/csrc/attn/video_sparse_attn/tk
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6c27e28c8115d1839d9eeeb530913c184a75fc87
diff --git a/csrc/attn/video_sparse_attn/vsa.cpp b/csrc/attn/video_sparse_attn/vsa.cpp
deleted file mode 100644
index 8661103a0..000000000
--- a/csrc/attn/video_sparse_attn/vsa.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-
-#ifdef TK_COMPILE_BLOCK_SPARSE
-extern std::vector block_sparse_attention_forward(
- torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor q2k_block_sparse_index, torch::Tensor q2k_block_sparse_num, torch::Tensor block_size
-);
-extern std::vector block_sparse_attention_backward(
- torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor o, torch::Tensor l_vec, torch::Tensor og, torch::Tensor k2q_block_sparse_index, torch::Tensor k2q_block_sparse_num, torch::Tensor block_size
-);
-#endif
-
-
-PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
- m.doc() = "Video Sparse Attention Kernels"; // optional module docstring
-
-#ifdef TK_COMPILE_BLOCK_SPARSE
- m.def("block_sparse_fwd", torch::wrap_pybind_function(block_sparse_attention_forward), "block sparse attention");
- m.def("block_sparse_bwd", torch::wrap_pybind_function(block_sparse_attention_backward), "block sparse attention backward");
-#endif
-}
diff --git a/csrc/attn/video_sparse_attn/vsa/__init__.py b/csrc/attn/video_sparse_attn/vsa/__init__.py
deleted file mode 100644
index 930e981c7..000000000
--- a/csrc/attn/video_sparse_attn/vsa/__init__.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import torch
-from typing import Tuple
-block_sparse_attn=None
-import torch
-major, minor = torch.cuda.get_device_capability(0)
-if major == 9 and minor == 0:# check if H100
- from vsa_cuda import block_sparse_fwd, block_sparse_bwd
- from vsa.block_sparse_wrapper import block_sparse_attn_SM90
- block_sparse_attn = block_sparse_attn_SM90
-else:
- from vsa.block_sparse_wrapper import block_sparse_attn_triton
- block_sparse_fwd = None
- block_sparse_bwd = None
- block_sparse_attn = block_sparse_attn_triton
-
-BLOCK_M = 64
-BLOCK_N = 64
-
-
-def torch_attention(q, k, v) -> Tuple[torch.Tensor, torch.Tensor]:
- QK = torch.matmul(q, k.transpose(-2, -1))
- QK /= (q.size(-1)**0.5)
-
- # Causal mask removed since causal is always false
-
- QK = torch.nn.functional.softmax(QK, dim=-1)
- output = torch.matmul(QK, v)
- return output, QK
-
-
-def video_sparse_attn(q, k, v, variable_block_sizes, topk, block_size, compress_attn_weight=None):
- """
- q: [batch_size, num_heads, seq_len, head_dim]
- k: [batch_size, num_heads, seq_len, head_dim]
- v: [batch_size, num_heads, seq_len, head_dim]
- topk: int
- block_size: int or tuple of 3 ints
- video_shape: tuple of (T, H, W)
- compress_attn_weight: [batch_size, num_heads, seq_len, head_dim]
- select_attn_weight: [batch_size, num_heads, seq_len, head_dim]
- NOTE: We assume q, k, v is zero padded!!
- V1 of sparse attention. Include compress attn and sparse attn branch, use average pooling to compress.
- Assume q, k, v is flattened in this way: [batch_size, num_heads, T//block_size[0], H//block_size[1], W//block_size[2], block_size[0], block_size[1], block_size[2]]
- """
-
- if isinstance(block_size, int):
- block_size = (block_size, block_size, block_size)
-
- block_elements = block_size[0] * block_size[1] * block_size[2]
- assert block_elements == 64
- assert q.shape[2] % block_elements == 0
- batch_size, num_heads, seq_len, head_dim = q.shape
- # compress attn
- q_compress = (q.view(batch_size, num_heads, seq_len // block_elements,
- block_elements, head_dim).float().sum(dim=3) / variable_block_sizes.view(1, 1, -1, 1)).to(q.dtype)
- k_compress = (k.view(batch_size, num_heads, seq_len // block_elements,
- block_elements, head_dim).float().sum(dim=3) / variable_block_sizes.view(1, 1, -1, 1)).to(k.dtype)
- v_compress = (v.view(batch_size, num_heads, seq_len // block_elements,
- block_elements, head_dim).float().sum(dim=3) / variable_block_sizes.view(1, 1, -1, 1)).to(v.dtype)
-
- output_compress, block_attn_score = torch_attention(q_compress, k_compress,
- v_compress)
-
- output_compress = output_compress.view(batch_size, num_heads,
- seq_len // block_elements, 1,
- head_dim)
- output_compress = output_compress.repeat(1, 1, 1, block_elements,
- 1).view(batch_size, num_heads,
- seq_len, head_dim)
-
- topK_indices = torch.topk(block_attn_score, topk, dim=-1).indices
- block_mask = torch.zeros_like(block_attn_score, dtype=torch.bool).scatter_(-1, topK_indices, True)
- output_select, _ = block_sparse_attn(q, k, v, block_mask, variable_block_sizes)
-
- if compress_attn_weight is not None:
- final_output = output_compress * compress_attn_weight + output_select
- else:
- final_output = output_compress + output_select
- return final_output
-
diff --git a/csrc/attn/video_sparse_attn/vsa/block_sparse_attn_triton.py b/csrc/attn/video_sparse_attn/vsa/block_sparse_attn_triton.py
deleted file mode 100644
index 3e2c89b6e..000000000
--- a/csrc/attn/video_sparse_attn/vsa/block_sparse_attn_triton.py
+++ /dev/null
@@ -1,450 +0,0 @@
-"""
-Fused Attention
-===============
-
-This is a Triton implementation of the Flash Attention v2 algorithm from Tri Dao
-(https://tridao.me/publications/flash2/flash2.pdf)
-
-Credits: OpenAI kernel team
-"""
-
-import pytest
-import torch
-import triton
-import triton.language as tl
-
-# ──────────────────────────── SPARSE ADDITION BEGIN ───────────────────────────
-import math # small utility needed by the sparse wrapper
-# ──────────────────────────── SPARSE ADDITION END ─────────────────────────────
-
-
-# We don't run auto-tuning every time to keep the tutorial fast. Keeping
-# the code below and commenting out the equivalent parameters is convenient for
-# re-tuning.
-configs = [
- triton.Config({'BLOCK_M': BM, 'BLOCK_N': BN}, num_stages=s, num_warps=w) \
- for BM in [64]\
- for BN in [64]\
- for s in [3, 4, 7]\
- for w in [4, 8]\
-]
-
-# ──────────────────────────── SPARSE ADDITION BEGIN ───────────────────────────
-@triton.autotune(configs, key=["N_CTX", "HEAD_DIM"])
-@triton.jit
-def _attn_fwd_sparse(Q, K, V, sm_scale, #
- q2k_index, q2k_num, max_kv_blks, #
- variable_block_sizes,
- M, Out, #
- stride_qz, stride_qh, stride_qm, stride_qk,
- stride_kz, stride_kh, stride_kn, stride_kk,
- stride_vz, stride_vh, stride_vk, stride_vn,
- stride_oz, stride_oh, stride_om, stride_on,
- Z, H, N_CTX, #
- HEAD_DIM: tl.constexpr, #
- BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr,
- STAGE: tl.constexpr):
- """
- 64×64 **block-sparse** forward kernel. Back-prop kernels remain dense
- (32×64 and 64×32) – memory footprint unchanged.
- """
-
- # ----- program-id mapping -----
- q_blk = tl.program_id(0) # Q-tile index
- off_hz = tl.program_id(1) # fused (batch, head)
- b = off_hz // H
- h = off_hz % H
- q_tiles = N_CTX // BLOCK_M
- meta_base = ((b * H + h) * q_tiles + q_blk)
-
- kv_blocks = tl.load(q2k_num + meta_base) # int32
- kv_ptr = q2k_index + meta_base * max_kv_blks # ptr to list
-
- # ----- base pointers -----
- qvk_off = (b.to(tl.int64) * stride_qz +
- h.to(tl.int64) * stride_qh)
-
- Q_ptr = tl.make_block_ptr(
- base=Q + qvk_off, shape=(N_CTX, HEAD_DIM),
- strides=(stride_qm, stride_qk),
- offsets=(q_blk * BLOCK_M, 0),
- block_shape=(BLOCK_M, HEAD_DIM), order=(1, 0))
-
- K_base = tl.make_block_ptr(
- base=K + qvk_off, shape=(HEAD_DIM, N_CTX),
- strides=(stride_kk, stride_kn),
- offsets=(0, 0),
- block_shape=(HEAD_DIM, BLOCK_N), order=(0, 1))
-
- v_order: tl.constexpr = (0, 1) if V.dtype.element_ty == tl.float8e5 else (1, 0)
- V_base = tl.make_block_ptr(
- base=V + qvk_off, shape=(N_CTX, HEAD_DIM),
- strides=(stride_vk, stride_vn),
- offsets=(0, 0),
- block_shape=(BLOCK_N, HEAD_DIM), order=v_order)
-
- O_ptr = tl.make_block_ptr(
- base=Out + qvk_off, shape=(N_CTX, HEAD_DIM),
- strides=(stride_om, stride_on),
- offsets=(q_blk * BLOCK_M, 0),
- block_shape=(BLOCK_M, HEAD_DIM), order=(1, 0))
-
- # ----- accumulators -----
- offs_m = q_blk * BLOCK_M + tl.arange(0, BLOCK_M)
- m_i = tl.full([BLOCK_M], -float("inf"), tl.float32)
- l_i = tl.zeros([BLOCK_M], dtype=tl.float32) + 1.0
- acc = tl.zeros([BLOCK_M, HEAD_DIM], dtype=tl.float32)
- qk_scale = sm_scale * 1.44269504 # 1/ln2
- q = tl.load(Q_ptr)
-
- # ----- sparse loop over valid K/V tiles -----
- for i in range(0, kv_blocks):
- kv_idx = tl.load(kv_ptr + i).to(tl.int32)
- block_size = tl.load(variable_block_sizes + kv_idx)
- K_ptr = tl.advance(K_base, (0, kv_idx * BLOCK_N))
- V_ptr = tl.advance(V_base, (kv_idx * BLOCK_N, 0))
-
- k = tl.load(K_ptr)
- qk = tl.dot(q, k)
- # mask out invalid columns
- mask = tl.arange(0, BLOCK_N) < block_size
- qk = tl.where(mask[None, :], qk, -float("inf"))
-
- m_ij = tl.maximum(m_i, tl.max(qk, 1) * qk_scale)
- p = tl.math.exp2(qk * qk_scale - m_ij[:, None])
- l_ij = tl.sum(p, 1)
-
- alpha = tl.math.exp2(m_i - m_ij)
- l_i = l_i * alpha + l_ij
- acc = acc * alpha[:, None]
-
- v = tl.load(V_ptr)
- acc = tl.dot(p.to(tl.bfloat16), v, acc)
- m_i = m_ij
-
- # ----- epilogue -----
- m_i += tl.math.log2(l_i)
- acc = acc / l_i[:, None]
- tl.store(M + off_hz * N_CTX + offs_m, m_i)
- tl.store(O_ptr, acc.to(Out.type.element_ty))
-# ──────────────────────────── SPARSE ADDITION END ─────────────────────────────
-
-
-
-
-@triton.jit
-def _attn_bwd_preprocess(O, DO, #
- Delta, #
- Z, H, N_CTX, #
- BLOCK_M: tl.constexpr, HEAD_DIM: tl.constexpr #
- ):
- off_m = tl.program_id(0) * BLOCK_M + tl.arange(0, BLOCK_M)
- off_hz = tl.program_id(1)
- off_n = tl.arange(0, HEAD_DIM)
- # load
- o = tl.load(O + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM + off_n[None, :])
- do = tl.load(DO + off_hz * HEAD_DIM * N_CTX + off_m[:, None] * HEAD_DIM + off_n[None, :]).to(tl.float32)
- delta = tl.sum(o * do, axis=1)
- # write-back
- tl.store(Delta + off_hz * N_CTX + off_m, delta)
-
-
-# The main inner-loop logic for computing dK and dV.
-@triton.jit
-def _attn_bwd_dkdv(dk, dv, #
- Q, k, v, sm_scale, #
- DO, #
- M, D, #
- k2q_index, k2q_num, max_q_blks,
- variable_block_sizes,
- # shared by Q/K/V/DO.
- stride_tok, stride_d, #
- H, N_CTX, BLOCK_M1: tl.constexpr, #
- BLOCK_N1: tl.constexpr, #
- HEAD_DIM: tl.constexpr, #
- # Filled in by the wrapper.
- start_n, start_m, num_steps):
- offs_m = start_m + tl.arange(0, BLOCK_M1)
- offs_n = start_n + tl.arange(0, BLOCK_N1)
- offs_k = tl.arange(0, HEAD_DIM)
- qT_ptrs = Q + offs_m[None, :] * stride_tok + offs_k[:, None] * stride_d
- do_ptrs = DO + offs_m[:, None] * stride_tok + offs_k[None, :] * stride_d
- # BLOCK_N1 must be a multiple of BLOCK_M1, otherwise the code wouldn't work.
- tl.static_assert(BLOCK_N1 % BLOCK_M1 == 0)
- step_m = BLOCK_M1
- kv_blk = tl.program_id(0) # Q-tile index
- off_hz = tl.program_id(2) # fused (batch, head)
- b = off_hz // H
- h = off_hz % H
- q_tiles = N_CTX // BLOCK_N1
- meta_base = ((b * H + h) * q_tiles + kv_blk)
-
- q_blocks = tl.load(k2q_num + meta_base) # int32
- q_ptr = k2q_index + meta_base * max_q_blks # ptr to list
- block_size = tl.load(variable_block_sizes + kv_blk)
-
-
-
- for blk_idx in range(q_blocks*2):
- block_sparse_offset = (tl.load(q_ptr + blk_idx//2).to(tl.int32)*2 + blk_idx%2) *step_m
- qT = tl.load(qT_ptrs + block_sparse_offset * stride_tok)
- # Load m before computing qk to reduce pipeline stall.
- offs_m = start_m + block_sparse_offset + tl.arange(0, BLOCK_M1)
- m = tl.load(M + offs_m)
- qkT = tl.dot(k, qT)
- pT = tl.math.exp2(qkT - m[None, :])
- mask = tl.arange(0, BLOCK_N1) < block_size
- pT = tl.where(mask[:, None], pT, 0.0)
-
- do = tl.load(do_ptrs + block_sparse_offset * stride_tok)
- # Compute dV.
- ppT = pT
- ppT = ppT.to(tl.bfloat16)
- dv += tl.dot(ppT, do)
- # D (= delta) is pre-divided by ds_scale.
- Di = tl.load(D + offs_m)
- # Compute dP and dS.
- dpT = tl.dot(v, tl.trans(do)).to(tl.float32)
- dsT = pT * (dpT - Di[None, :])
- dsT = dsT.to(tl.bfloat16)
- dk += tl.dot(dsT, tl.trans(qT))
- # Increment pointers.
- return dk, dv
-
-
-
-# the main inner-loop logic for computing dQ
-@triton.jit
-def _attn_bwd_dq(dq, q, K, V, #
- do, m, D,
- # shared by Q/K/V/DO.
- q2k_index, q2k_num, max_kv_blks,
- variable_block_sizes,
- stride_tok, stride_d, #
- H, N_CTX, #
- BLOCK_M2: tl.constexpr, #
- BLOCK_N2: tl.constexpr, #
- HEAD_DIM: tl.constexpr,
- # Filled in by the wrapper.
- start_m, start_n, num_steps):
- offs_m = start_m + tl.arange(0, BLOCK_M2)
- offs_n = start_n + tl.arange(0, BLOCK_N2)
- offs_k = tl.arange(0, HEAD_DIM)
- kT_ptrs = K + offs_n[None, :] * stride_tok + offs_k[:, None] * stride_d
- vT_ptrs = V + offs_n[None, :] * stride_tok + offs_k[:, None] * stride_d
- # D (= delta) is pre-divided by ds_scale.
- Di = tl.load(D + offs_m)
- # BLOCK_M2 must be a multiple of BLOCK_N2, otherwise the code wouldn't work.
- tl.static_assert(BLOCK_M2 % BLOCK_N2 == 0)
- step_n = BLOCK_N2
-
- q_blk = tl.program_id(0) # Q-tile index
- off_hz = tl.program_id(2) # fused (batch, head)
- b = off_hz // H
- h = off_hz % H
- q_tiles = N_CTX // BLOCK_M2
- meta_base = ((b * H + h) * q_tiles + q_blk)
-
- kv_blocks = tl.load(q2k_num + meta_base) # int32
- kv_ptr = q2k_index + meta_base * max_kv_blks # ptr to list
-
-
- for blk_idx in range(kv_blocks*2):
- kv_idx = tl.load(kv_ptr + blk_idx//2).to(tl.int32)
- block_size = tl.load(variable_block_sizes + kv_idx) - (blk_idx % 2) * step_n
- block_sparse_offset = (kv_idx*2 + blk_idx%2) * step_n * stride_tok
- kT = tl.load(kT_ptrs + block_sparse_offset)
- vT = tl.load(vT_ptrs + block_sparse_offset)
- qk = tl.dot(q, kT)
- p = tl.math.exp2(qk - m)
- mask = tl.arange(0, BLOCK_N2) < block_size.to(tl.int32)
- p = tl.where(mask[None, :], p , 0.0)
- # Compute dP and dS.
- dp = tl.dot(do, vT).to(tl.float32)
- ds = p * (dp - Di[:, None])
- ds = ds.to(tl.bfloat16)
- # Compute dQ.
- # NOTE: We need to de-scale dq in the end, because kT was pre-scaled.
- dq += tl.dot(ds, tl.trans(kT))
- # Increment pointers.
- return dq
-
-
-
-@triton.jit
-def _attn_bwd(Q, K, V, sm_scale, #
- DO, #
- DQ, DK, DV, #
- M, D,
- q2k_index, q2k_num, max_kv_blks,
- k2q_index, k2q_num, max_q_blks,
- variable_block_sizes,
- # shared by Q/K/V/DO.
- stride_z, stride_h, stride_tok, stride_d, #
- H, N_CTX, #
- BLOCK_M1: tl.constexpr, #
- BLOCK_N1: tl.constexpr, #
- BLOCK_M2: tl.constexpr, #
- BLOCK_N2: tl.constexpr, #
- HEAD_DIM: tl.constexpr):
- LN2 = 0.6931471824645996 # = ln(2)
-
- bhid = tl.program_id(2)
- off_chz = (bhid * N_CTX).to(tl.int64)
- adj = (stride_h * (bhid % H) + stride_z * (bhid // H)).to(tl.int64)
- pid = tl.program_id(0)
-
- # offset pointers for batch/head
- Q += adj
- K += adj
- V += adj
- DO += adj
- DQ += adj
- DK += adj
- DV += adj
- M += off_chz
- D += off_chz
-
- # load scales
- offs_k = tl.arange(0, HEAD_DIM)
-
- start_n = pid * BLOCK_N1
- start_m = 0
-
- offs_n = start_n + tl.arange(0, BLOCK_N1)
-
- dv = tl.zeros([BLOCK_N1, HEAD_DIM], dtype=tl.float32)
- dk = tl.zeros([BLOCK_N1, HEAD_DIM], dtype=tl.float32)
-
- # load K and V: they stay in SRAM throughout the inner loop.
- k = tl.load(K + offs_n[:, None] * stride_tok + offs_k[None, :] * stride_d)
- v = tl.load(V + offs_n[:, None] * stride_tok + offs_k[None, :] * stride_d)
-
-
- num_steps = N_CTX // BLOCK_M1
-
- dk, dv = _attn_bwd_dkdv( #
- dk, dv, #
- Q, k, v, sm_scale, #
- DO, #
- M, D, #
- k2q_index, k2q_num, max_q_blks,
- variable_block_sizes,
- stride_tok, stride_d, #
- H, N_CTX, #
- BLOCK_M1, BLOCK_N1, HEAD_DIM, #
- start_n, start_m, num_steps #
- )
-
- dv_ptrs = DV + offs_n[:, None] * stride_tok + offs_k[None, :] * stride_d
- tl.store(dv_ptrs, dv)
-
- # Write back dK.
- dk *= sm_scale
- dk_ptrs = DK + offs_n[:, None] * stride_tok + offs_k[None, :] * stride_d
- tl.store(dk_ptrs, dk)
-
- # THIS BLOCK DOES DQ:
- start_m = pid * BLOCK_M2
- end_n = 0
-
- offs_m = start_m + tl.arange(0, BLOCK_M2)
-
- q = tl.load(Q + offs_m[:, None] * stride_tok + offs_k[None, :] * stride_d)
- dq = tl.zeros([BLOCK_M2, HEAD_DIM], dtype=tl.float32)
- do = tl.load(DO + offs_m[:, None] * stride_tok + offs_k[None, :] * stride_d)
-
- m = tl.load(M + offs_m)
- m = m[:, None]
-
- num_steps = N_CTX // BLOCK_N2
- dq = _attn_bwd_dq(dq, q, K, V, #
- do, m, D, #
- q2k_index, q2k_num, max_kv_blks,
- variable_block_sizes,
- stride_tok, stride_d, #
- H, N_CTX, #
- BLOCK_M2, BLOCK_N2, HEAD_DIM, #
- start_m, end_n, num_steps #
- )
- # Write back dQ.
- dq_ptrs = DQ + offs_m[:, None] * stride_tok + offs_k[None, :] * stride_d
- dq *= LN2
- tl.store(dq_ptrs, dq)
-
-
-
-
-
-# ──────────────────────────── SPARSE ADDITION BEGIN ───────────────────────────
-def triton_block_sparse_attn_forward(q, k, v, q2k_index, q2k_num, variable_block_sizes):
- B, H, T, D = q.shape
- sm_scale = 1.0 / math.sqrt(D)
- max_kv_blks = q2k_index.shape[-1]
- assert T % 64 == 0, f"T must be a multiple of 64, but got {T}"
- assert T // 64 == q2k_num.shape[-1], f"shape mismatch, T // 64 = {T // 64}, q2k_num.shape[-2] = {q2k_num.shape[-2]}"
- o = torch.empty_like(q)
- M = torch.empty((B, H, T), dtype=torch.float32, device=q.device)
-
- grid = lambda _: (triton.cdiv(T, 64), B * H, 1)
- _attn_fwd_sparse[grid](
- q, k, v, sm_scale,
- q2k_index, q2k_num, max_kv_blks,
- variable_block_sizes,
- M, o,
- q.stride(0), q.stride(1), q.stride(2), q.stride(3),
- k.stride(0), k.stride(1), k.stride(2), k.stride(3),
- v.stride(0), v.stride(1), v.stride(2), v.stride(3),
- o.stride(0), o.stride(1), o.stride(2), o.stride(3),
- B, H, T,
- HEAD_DIM=D, STAGE=3
- )
-
- return o, M
-
-def triton_block_sparse_attn_backward(do, q, k, v, o, M, q2k_index, q2k_num, k2q_index, k2q_num, variable_block_sizes):
- assert do.is_contiguous()
- assert q.stride() == k.stride() == v.stride() == o.stride() == do.stride()
-
- B, H, T, D = q.shape
- sm_scale = 1.0 / math.sqrt(D)
- dq = torch.empty_like(q)
- dk = torch.empty_like(k)
- dv = torch.empty_like(v)
- BATCH, N_HEAD, N_CTX = q.shape[:3]
- BLOCK_M1, BLOCK_N1, BLOCK_M2, BLOCK_N2 = 32, 64, 64, 32
- RCP_LN2 = 1.4426950408889634 # = 1.0 / ln(2)
- arg_k = k
- arg_k = arg_k * (sm_scale * RCP_LN2)
- PRE_BLOCK = 64
- assert N_CTX % PRE_BLOCK == 0
- pre_grid = (N_CTX // PRE_BLOCK, BATCH * N_HEAD)
- delta = torch.empty_like(M)
- _attn_bwd_preprocess[pre_grid](
- o, do, #
- delta, #
- BATCH, N_HEAD, N_CTX, #
- BLOCK_M=PRE_BLOCK, HEAD_DIM=D #
- )
-
-
- max_q_blks = k2q_index.shape[-1]
- max_kv_blks = q2k_index.shape[-1]
-
- grid = (N_CTX // BLOCK_N1, 1, BATCH * N_HEAD)
- _attn_bwd[grid](
- q, arg_k, v, sm_scale, do, dq, dk, dv, #
- M, delta, #
- q2k_index, q2k_num, max_kv_blks,
- k2q_index, k2q_num, max_q_blks,
- variable_block_sizes,
- q.stride(0), q.stride(1), q.stride(2), q.stride(3), #
- N_HEAD, N_CTX, #
- BLOCK_M1=BLOCK_M1, BLOCK_N1=BLOCK_N1, #
- BLOCK_M2=BLOCK_M2, BLOCK_N2=BLOCK_N2, #
- HEAD_DIM=D #
- )
-
- return dq, dk, dv
-
-
diff --git a/csrc/attn/video_sparse_attn/vsa/block_sparse_h100.cu b/csrc/attn/video_sparse_attn/vsa/block_sparse_h100.cu
deleted file mode 100644
index f33d2719e..000000000
--- a/csrc/attn/video_sparse_attn/vsa/block_sparse_h100.cu
+++ /dev/null
@@ -1,1190 +0,0 @@
-// # Define TORCH_COMPILE macro
-
-#include "kittens.cuh"
-#include
-#include
-#include
-
-
-using namespace kittens;
-namespace cg = cooperative_groups;
-constexpr int BLOCK_M = 64;
-constexpr int BLOCK_N = 64;
-template struct fwd_attend_ker_tile_dims {};
-template<> struct fwd_attend_ker_tile_dims<64> {
- constexpr static int tile_width = (64);
- constexpr static int qo_height = (4*16);
- constexpr static int kv_height = (4*16);
-};
-template<> struct fwd_attend_ker_tile_dims<128> {
- constexpr static int tile_width = (128);
- constexpr static int qo_height = (4*16);
- constexpr static int kv_height = (4*16);
-};
-template struct fwd_globals {
- using q_tile = st_bf::qo_height, fwd_attend_ker_tile_dims::tile_width>;
- using k_tile = st_bf::kv_height, fwd_attend_ker_tile_dims::tile_width>;
- using v_tile = st_bf::kv_height, fwd_attend_ker_tile_dims::tile_width>;
- using l_col_vec = col_vec::qo_height, fwd_attend_ker_tile_dims::tile_width>>;
- using o_tile = st_bf::qo_height, fwd_attend_ker_tile_dims::tile_width>;
-
- using q_gl = gl;
- using k_gl = gl;
- using v_gl = gl;
- using l_gl = gl;
- using o_gl = gl;
-
- q_gl q;
- k_gl k;
- v_gl v;
- l_gl l;
- o_gl o;
-
- const int N;
- const int hr;
- const int max_kv_blocks_per_q;
-
- int32_t *__restrict__ q2k_block_sparse_index;
- int32_t *__restrict__ q2k_block_sparse_num;
- int32_t *__restrict__ block_size;
-};
-
-
-template
-__global__ __launch_bounds__(128, 4)
-void fwd_attend_ker(const __grid_constant__ fwd_globals g) { // use block size of 64
- extern __shared__ int __shm[];
- tma_swizzle_allocator al((int*)&__shm[0]);
-
- using K = fwd_attend_ker_tile_dims;
-
- using q_tile = st_bf<64, K::tile_width>;
- using k_tile = st_bf<64, K::tile_width>;
- using v_tile = st_bf<64, K::tile_width>;
- using l_col_vec = col_vec>;
- using o_tile = st_bf<64, K::tile_width>;
-
- q_tile (&q_smem)[1] = al.allocate();
-
- k_tile (&k_smem)[1] = al.allocate();
-
- v_tile (&v_smem)[1] = al.allocate();
-
- l_col_vec (&l_smem)[1] = al.allocate();
-
- auto (*o_smem) = reinterpret_cast(q_smem);
-
- int kv_head_idx = blockIdx.y / g.hr;
- int seq_idx = blockIdx.x;
-
- int32_t* q2k_block_sparse_index_ptr = g.q2k_block_sparse_index + blockIdx.z * gridDim.y * gridDim.x * g.max_kv_blocks_per_q + blockIdx.y * gridDim.x * g.max_kv_blocks_per_q + blockIdx.x * g.max_kv_blocks_per_q;
- int32_t* q2k_block_sparse_num_ptr = g.q2k_block_sparse_num + blockIdx.z * gridDim.y * gridDim.x + blockIdx.y * gridDim.x + blockIdx.x;
- int32_t kv_blocks = q2k_block_sparse_num_ptr[0];
- __shared__ kittens::semaphore qsmem_semaphore, k_smem_arrived, v_smem_arrived;
- if (threadIdx.x == 0) {
- int32_t kv_block_index = q2k_block_sparse_index_ptr[0];
-
- init_semaphore(qsmem_semaphore, 0, 1);
- init_semaphore(k_smem_arrived, 0, 1);
- init_semaphore(v_smem_arrived, 0, 1);
-
- // preload q block
- coord q_tile_idx = {blockIdx.z, blockIdx.y, seq_idx, 0};
- tma::expect_bytes(qsmem_semaphore, sizeof(q_smem));
- tma::load_async(q_smem[0], g.q, q_tile_idx, qsmem_semaphore);
-
- // preload the zeroth block of kv
- tma::expect_bytes(k_smem_arrived, sizeof(k_tile));
- coord k_tile_idx = {blockIdx.z, kv_head_idx, kv_block_index, 0};
- tma::load_async(k_smem[0], g.k, k_tile_idx, k_smem_arrived);
-
- tma::expect_bytes(v_smem_arrived, sizeof(v_tile));
- coord v_tile_idx = {blockIdx.z, kv_head_idx, kv_block_index, 0};
- tma::load_async(v_smem[0], g.v, v_tile_idx, v_smem_arrived);
- }
- __syncthreads();
-
- rt_fl<16, 64> att_block;
- rt_bf<16, 64> att_block_mma;
- rt_fl<16, K::tile_width> o_reg;
-
- col_vec> max_vec, norm_vec, max_vec_last_scaled, max_vec_scaled;
-
- neg_infty(max_vec);
- zero(norm_vec);
- zero(o_reg);
-
- // wait for q block
- wait(qsmem_semaphore, 0);
-
- for (int kv_idx = 0; kv_idx < kv_blocks - 1; kv_idx++) {
- // preload kv index
- int32_t kv_block_index = q2k_block_sparse_index_ptr[kv_idx + 1];
-
- // wait k
- wait(k_smem_arrived, kv_idx % 2);
-
- // compute QK^T
- warpgroup::mm_ABt(att_block, q_smem[0], k_smem[0]);
-
- copy(max_vec_last_scaled, max_vec);
- if constexpr (D == 64) { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.125f); }
- else { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.08838834764f); }
-
- warpgroup::mma_async_wait();
-
- // load K
- if (threadIdx.x == 0) {
- tma::expect_bytes(k_smem_arrived, sizeof(k_tile));
- coord k_tile_idx = {blockIdx.z, kv_head_idx, kv_block_index, 0};
- tma::load_async(k_smem[0], g.k, k_tile_idx, k_smem_arrived);
- }
-
- // exp
- right_fill(att_block, att_block, g.block_size[q2k_block_sparse_index_ptr[kv_idx]], base_types::constants::neg_infty());
- row_max(max_vec, att_block, max_vec);
-
- if constexpr (D == 64) {
- mul(att_block, att_block, 1.44269504089f*0.125f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.125f);
- }
- else {
- mul(att_block, att_block, 1.44269504089f*0.08838834764f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.08838834764f);
- }
-
- sub_row(att_block, att_block, max_vec_scaled);
- exp2(att_block, att_block);
- sub(max_vec_last_scaled, max_vec_last_scaled, max_vec_scaled);
- exp2(max_vec_last_scaled, max_vec_last_scaled);
- mul(norm_vec, norm_vec, max_vec_last_scaled);
- row_sum(norm_vec, att_block, norm_vec);
- add(att_block, att_block, 0.f);
- copy(att_block_mma, att_block);
- mul_row(o_reg, o_reg, max_vec_last_scaled);
-
- // wait v
- wait(v_smem_arrived, kv_idx % 2);
-
- // compute SV
- warpgroup::mma_AB(o_reg, att_block_mma, v_smem[0]);
- warpgroup::mma_async_wait();
-
- // load V
- if (threadIdx.x == 0) {
- tma::expect_bytes(v_smem_arrived, sizeof(v_tile));
- coord v_tile_idx = {blockIdx.z, kv_head_idx, kv_block_index, 0};
- tma::load_async(v_smem[0], g.v, v_tile_idx, v_smem_arrived);
- }
- }
-
- // last iter
- {
- int kv_idx = kv_blocks - 1;
- // wait k
- wait(k_smem_arrived, kv_idx % 2);
-
- // compute QK^T
- warpgroup::mm_ABt(att_block, q_smem[0], k_smem[0]);
-
- copy(max_vec_last_scaled, max_vec);
- if constexpr (D == 64) { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.125f); }
- else { mul(max_vec_last_scaled, max_vec_last_scaled, 1.44269504089f*0.08838834764f); }
-
- warpgroup::mma_async_wait();
-
- // exp
- right_fill(att_block, att_block, g.block_size[q2k_block_sparse_index_ptr[kv_idx]], base_types::constants::neg_infty());
-
- row_max(max_vec, att_block, max_vec);
-
- if constexpr (D == 64) {
- mul(att_block, att_block, 1.44269504089f*0.125f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.125f);
- }
- else {
- mul(att_block, att_block, 1.44269504089f*0.08838834764f);
- mul(max_vec_scaled, max_vec, 1.44269504089f*0.08838834764f);
- }
-
- sub_row(att_block, att_block, max_vec_scaled);
- exp2(att_block, att_block);
- sub(max_vec_last_scaled, max_vec_last_scaled, max_vec_scaled);
- exp2(max_vec_last_scaled, max_vec_last_scaled);
- mul(norm_vec, norm_vec, max_vec_last_scaled);
- row_sum(norm_vec, att_block, norm_vec);
- add(att_block, att_block, 0.f);
- copy(att_block_mma, att_block);
- mul_row(o_reg, o_reg, max_vec_last_scaled);
-
- // wait v
- wait(v_smem_arrived, kv_idx % 2);
-
- // compute SV
- warpgroup::mma_AB(o_reg, att_block_mma, v_smem[0]);
- warpgroup::mma_async_wait();
- }
-
- div_row(o_reg, o_reg, norm_vec);
- warpgroup::store(o_smem[0], o_reg);
- __syncthreads();
-
- // TK store_async internally calls syncwarp so we need to route on warp level
- if (threadIdx.x / 32 == 0) {
- coord o_tile_idx = {blockIdx.z, blockIdx.y, seq_idx, 0};
- tma::store_async(g.o, o_smem[0], o_tile_idx);
- }
-
- mul(max_vec_scaled, max_vec_scaled, 0.69314718056f);
- log(norm_vec, norm_vec);
- add(norm_vec, norm_vec, max_vec_scaled);
-
- if constexpr (D == 64) { mul(norm_vec, norm_vec, -8.0f); }
- else { mul(norm_vec, norm_vec, -11.313708499f); }
-
- warpgroup::store(l_smem[0], norm_vec);
- __syncthreads();
-
- if (threadIdx.x / 32 == 0) {
- coord tile_idx = {blockIdx.z, blockIdx.y, 0, seq_idx};
- tma::store_async(g.l, l_smem[0], tile_idx);
- }
- tma::store_async_wait();
-}
-
-// ---------------------------------------------------------------------------------------------------
-// ----------------------------------- Backward preparation kernel -----------------------------------
-// ---------------------------------------------------------------------------------------------------
-
-template
-struct bwd_prep_globals {
- using og_tile = st_bf<4*16, D>;
- using o_tile = st_bf<4*16, D>;
- using d_tile = col_vec>;
-
- using og_gl = gl;
- using o_gl = gl;
- using d_gl = gl;
-
- og_gl og;
- o_gl o;
- d_gl d;
-};
-
-constexpr int PREP_NUM_WARPS = (1);
-template
-__global__ __launch_bounds__(PREP_NUM_WARPS*kittens::WARP_THREADS, (D == 64) ? 6 / PREP_NUM_WARPS : 3 / PREP_NUM_WARPS)
-void bwd_attend_prep_ker(const __grid_constant__ bwd_prep_globals g) {
- extern __shared__ int __shm[];
- tma_swizzle_allocator al((int*)&__shm[0]);
-
- int warpid = kittens::warpid();
-
- using og_tile = st_bf<4*16, D>;
- using o_tile = st_bf<4*16, D>;
- using d_tile = col_vec>;
-
- og_tile (&og_smem)[PREP_NUM_WARPS] = al.allocate();
- o_tile (&o_smem) [PREP_NUM_WARPS] = al.allocate();
- d_tile (&d_smem) [PREP_NUM_WARPS] = al.allocate();
-
- rt_fl<4*16, D> og_reg, o_reg;
- col_vec> d_reg;
-
- __shared__ kittens::semaphore smem_semaphore;
-
- if (threadIdx.x == 0) {
- init_semaphore(smem_semaphore, 0, 1);
- tma::expect_bytes(smem_semaphore, sizeof(og_smem[0]) * PREP_NUM_WARPS * 2);
- }
- __syncthreads();
-
- if (warpid == 0) {
- for (int w = 0; w < PREP_NUM_WARPS; w++) {
- coord tile_idx = {blockIdx.z, blockIdx.y, (blockIdx.x * PREP_NUM_WARPS) + w, 0};
- tma::load_async(o_smem[w], g.o, tile_idx, smem_semaphore);
- tma::load_async(og_smem[w], g.og, tile_idx, smem_semaphore);
- }
- }
-
- wait(smem_semaphore, 0);
- load(o_reg, o_smem[warpid]);
- load(og_reg, og_smem[warpid]);
- mul(og_reg, og_reg, o_reg);
- row_sum(d_reg, og_reg);
- store(d_smem[warpid], d_reg);
- __syncthreads();
-
- if (warpid == 0) {
- for (int w = 0; w < PREP_NUM_WARPS; w++) {
- coord tile_idx = {blockIdx.z, blockIdx.y, 0, (blockIdx.x * PREP_NUM_WARPS) + w};
- tma::store_async(g.d, d_smem[w], tile_idx);
- }
- }
- tma::store_async_wait();
-}
-
-template struct bwd_attend_ker_tile_dims {};
-template<> struct bwd_attend_ker_tile_dims<64> {
- constexpr static int tile_width = (64);
- constexpr static int tile_h = (4*16);
- constexpr static int tile_h_qo = (4*16);
-};
-template<> struct bwd_attend_ker_tile_dims<128> {
- constexpr static int tile_width = (128);
- constexpr static int tile_h = (4*16);
- constexpr static int tile_h_qo = (4*16);
-};
-
-template
-struct bwd_globals {
- using G = bwd_attend_ker_tile_dims;
-
- using q_tile = st_bf;
- using k_tile = st_bf;
- using v_tile = st_bf;
- using og_tile = st_bf;
- using qg_tile = st_fl;
- using kg_tile = st_fl;
- using vg_tile = st_fl;
- using l_tile = row_vec>;
- using d_tile = row_vec>;
-
- using q_gl = gl;
- using k_gl = gl;
- using v_gl = gl;
-
- using og_gl = gl;
-
- using qg_gl = gl;
- using kg_gl = gl;
- using vg_gl = gl;
-
- using l_gl = gl;
- using d_gl = gl;
-
- q_gl q;
- k_gl k;
- v_gl v;
- og_gl og;
- qg_gl qg;
- kg_gl kg;
- vg_gl vg;
- l_gl l;
- d_gl d;
-
- const int N;
- const int hr;
- const int max_q_blocks_per_kv;
-
- int32_t *__restrict__ k2q_block_sparse_index;
- int32_t *__restrict__ k2q_block_sparse_num;
- int32_t *__restrict__ block_size;
-};
-
-__device__ static inline void
-stream_tile(auto ®_tile, auto &smem_vec, int tic) {
- #pragma unroll
- for(int i = 0; i < 4; i++) {
- int base_col = 16*i + 2*(kittens::laneid()%4);
- reg_tile.tiles[0][i].data[0] = *(float2*)&smem_vec[tic][base_col + 0];
- reg_tile.tiles[0][i].data[1] = *(float2*)&smem_vec[tic][base_col + 0];
- reg_tile.tiles[0][i].data[2] = *(float2*)&smem_vec[tic][base_col + 8];
- reg_tile.tiles[0][i].data[3] = *(float2*)&smem_vec[tic][base_col + 8];
- }
-}
-
-__device__ static inline void
-stream_sub_tile(auto ®_tile, auto &smem_vec, int tic) {
- #pragma unroll
- for(int i = 0; i < 4; i++) {
- int base_col = 16*i + 2*(laneid()%4);
- reg_tile.tiles[0][i].data[0] = base_ops::sub::template op(reg_tile.tiles[0][i].data[0], *(float2*)&smem_vec[tic][base_col + 0]);
- reg_tile.tiles[0][i].data[1] = base_ops::sub::template op(reg_tile.tiles[0][i].data[1], *(float2*)&smem_vec[tic][base_col + 0]);
- reg_tile.tiles[0][i].data[2] = base_ops::sub::template op(reg_tile.tiles[0][i].data[2], *(float2*)&smem_vec[tic][base_col + 8]);
- reg_tile.tiles[0][i].data[3] = base_ops::sub::template op(reg_tile.tiles[0][i].data[3], *(float2*)&smem_vec[tic][base_col + 8]);
- }
-}
-
-template
-__global__ __launch_bounds__(128, (D == 64)? 3 : 2)
-void bwd_attend_ker(const __grid_constant__ bwd_globals g) {
- extern __shared__ int __shm[];
- tma_swizzle_allocator al((int*)&__shm[0]);
-
- const int N = g.N, hr = g.hr;
- using G = bwd_attend_ker_tile_dims;
-
- using kg_tile = st_fl;
- using vg_tile = st_fl;
- using k_tile = st_bf;
- using v_tile = st_bf;
- using q_tile = st_bf;
- using og_tile = st_bf;
- using qg_tile = st_fl;
- using l_tile = row_vec>;
- using d_tile = row_vec>;
- using attn_tile = st_bf;
-
- k_tile (&k_smem) [1] = al.allocate();
- v_tile (&v_smem) [1] = al.allocate();
-
- q_tile (&q_smem) [1] = al.allocate();
- og_tile (&og_smem)[1] = al.allocate();
- qg_tile (&qg_smem) = al.allocate();
-
- l_tile (&l_smem)[1] = al.allocate();
- d_tile (&d_smem)[1] = al.allocate