Files
hao-ai-lab-FastVideo/fastvideo-kernel/benchmarks/bench_vsa_tile_layout.py
T

119 lines
5.1 KiB
Python

#!/usr/bin/env python3
"""
Benchmark the SM100 fused VSA64 tile layout against the legacy scatter/transpose route.
Times Wan VSA inference preprocessing through the backend
(``VideoSparseAttentionImpl.preprocess_qkv``), toggling
``FASTVIDEO_DISABLE_VSA64_FUSED_LAYOUT``:
- legacy: tile scatter into BSHD + three contiguous Q/K/V transposes + a gate view
- fused: one Triton kernel writing tiled BHSD directly
With ``--include_attn`` the timed region also covers ``forward`` (the
``video_sparse_attn`` call). Outputs of both routes are checked for bitwise
equality before timing. The fused route only activates on SM100.
"""
from __future__ import annotations
import argparse
import random
from collections.abc import Callable
import numpy as np
import torch
try:
from triton.testing import do_bench
except Exception as e: # pragma: no cover
raise ImportError("This benchmark requires triton (for triton.testing.do_bench).") from e
import fastvideo.envs as envs
from fastvideo.attention.backends.video_sparse_attn import (VideoSparseAttentionImpl,
VideoSparseAttentionMetadataBuilder)
def set_seed(seed: int = 42) -> None:
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
def parse_arguments() -> argparse.Namespace:
p = argparse.ArgumentParser(description="Benchmark the SM100 fused VSA64 tile layout")
p.add_argument("--dit_shape",
type=int,
nargs=3,
default=[21, 30, 52],
metavar=("T", "H", "W"),
help="Post-patchify token grid (default: Wan 480p, 81 frames)")
p.add_argument("--batch_size", type=int, default=1)
p.add_argument("--num_heads", type=int, default=40, help="Heads per rank")
p.add_argument("--sparsity", type=float, default=0.9)
p.add_argument("--include_attn", action="store_true", help="Also time the video_sparse_attn forward")
p.add_argument("--warmup", type=int, default=200, help="Warmup time in ms (triton do_bench)")
p.add_argument("--rep", type=int, default=2000, help="Measurement time in ms (triton do_bench)")
p.add_argument("--seed", type=int, default=42)
return p.parse_args()
def make_step(impl: VideoSparseAttentionImpl, qkvg: torch.Tensor, builder: VideoSparseAttentionMetadataBuilder,
build_kwargs: dict, fused: bool, include_attn: bool) -> Callable[[], tuple[torch.Tensor, ...]]:
metadata = builder.build(**build_kwargs)
def step() -> tuple[torch.Tensor, ...]:
# The routing gate reads the switch on every call.
with envs.FASTVIDEO_DISABLE_VSA64_FUSED_LAYOUT.override(not fused):
tiled = impl.preprocess_qkv(qkvg, metadata)
q, k, v, gate = tiled.chunk(4)
if include_attn:
return (impl.forward(q, k, v, gate, metadata), )
if metadata.fused_layout_active:
return q, k, v, gate
return (q.transpose(1, 2).contiguous(), k.transpose(1, 2).contiguous(), v.transpose(1, 2).contiguous(),
gate.transpose(1, 2))
with torch.no_grad():
step()
if fused and not metadata.fused_layout_active:
raise RuntimeError("Fused layout did not activate (requires SM100, bf16, head_dim 128, "
"and fastvideo_kernel.triton_kernels.vsa_tile_layout).")
return step
def main() -> None:
args = parse_arguments()
set_seed(args.seed)
impl = object.__new__(VideoSparseAttentionImpl)
builder = VideoSparseAttentionMetadataBuilder()
build_kwargs = dict(current_timestep=0,
raw_latent_shape=tuple(args.dit_shape),
patch_size=(1, 1, 1),
VSA_sparsity=args.sparsity,
device=torch.device("cuda"),
cache_tile_buf=True)
sequence = int(np.prod(args.dit_shape))
qkvg = torch.randn((4 * args.batch_size, sequence, args.num_heads, 128), device="cuda", dtype=torch.bfloat16)
legacy = make_step(impl, qkvg, builder, build_kwargs, fused=False, include_attn=args.include_attn)
fused = make_step(impl, qkvg, builder, build_kwargs, fused=True, include_attn=args.include_attn)
with torch.no_grad():
if not all(torch.equal(old, new) for old, new in zip(legacy(), fused(), strict=True)):
raise RuntimeError("Fused and legacy outputs differ.")
legacy_ms = do_bench(legacy, warmup=args.warmup, rep=args.rep, return_mode="median")
fused_ms = do_bench(fused, warmup=args.warmup, rep=args.rep, return_mode="median")
region = "layout+attn" if args.include_attn else "layout"
print(f"GPU: {torch.cuda.get_device_name()} | dit_shape={tuple(args.dit_shape)} seq={sequence} "
f"B={args.batch_size} H={args.num_heads} D=128 sparsity={args.sparsity}")
print(f"{region} (median): legacy {legacy_ms:.3f} ms | fused {fused_ms:.3f} ms | "
f"speedup {legacy_ms / fused_ms:.2f}x")
if __name__ == "__main__":
if not torch.cuda.is_available():
raise RuntimeError("CUDA is required for this benchmark.")
main()