unsloth/scripts/sdpa_mask_backend_probe.py
Daniel Han a5928064a0 video: skip padded text tokens in HunyuanVideo-1.5 joint attention
HunyuanVideo-1.5's DiT runs a joint [video; text] self-attention and, on every
block and step, builds a dense [B,1,N,N] boolean mask so the video never attends
to the padded text. A dense bool attn_mask disables every fused SDPA kernel
(flash rejects it; cuDNN and memory-efficient fall back), so the attention runs
the slow math-style path: at the production shape (121 frames, 480p, N about 50k)
one attention call is ~421ms with the mask vs ~19ms with attn_mask=None. The text
is ~99.5% padding (a t2v prompt fills ~9 of ~1985 slots), so nearly all of that
cost is spent masking padding.

install_hunyuan_attention_trim installs an eager forward pre-hook that drops the
all-zero image stream (t2v) and trims the mllm/byt5 text streams to their
globally-valid columns, plus a null-mask attention processor that runs
attn_mask=None once no partially-padded column remains (the batch-1 /
per-guidance-branch case) and otherwise delegates to the stock dense-mask
processor. The model already zeroes and masks the padded text and discards its
attention output (only the video split feeds proj_out), so removing it is exact
for the video; the only numeric change is the SDPA kernel (masked fallback to
fused). Measured on a B200: 23.3s to 1.3s per DiT forward at 121 frames (~18x with
regional compile, 0 graph breaks); per-forward cosine 0.99998 vs stock; equal
distance to an fp32 reference (LPIPS fp32-vs-stock 0.292, fp32-vs-trim 0.307), so
it is not less accurate than the current bf16 default.

Wired auto-on for HunyuanVideo-1.5 in the video loader, before the attention
backend set so the requested kernel pins onto the new processors; a no-op for
every other family and reversible (stock dense-mask path on any anomaly). Adds
hermetic tests and the diagnostic/validation scripts.
2026-07-09 05:09:49 +00:00

65 lines
2.1 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Which SDPA backends tolerate a dense bool attn_mask, and at what cost, at Hunyuan's real
joint shape (B=1, H=16, N=50345, D=128, bf16)? Decides whether nulling the all-True mask is
the real win on the PRODUCTION cuDNN path (not just the native math fallback)."""
import time
import torch
import torch.nn.functional as F
from torch.nn.attention import SDPBackend, sdpa_kernel
B, H, N, D = 1, 16, 50345, 128
dev, dt = "cuda:0", torch.bfloat16
def mk():
return torch.randn(B, H, N, D, device=dev, dtype=dt)
def timed(fn, iters=20):
torch.cuda.synchronize()
for _ in range(3):
try:
fn()
except Exception as e: # noqa: BLE001
return f"UNSUPPORTED ({type(e).__name__})"
torch.cuda.synchronize()
t0 = time.perf_counter()
for _ in range(iters):
fn()
torch.cuda.synchronize()
return (time.perf_counter() - t0) / iters * 1e3
q, k, v = mk(), mk(), mk()
dense = torch.ones(B, 1, N, N, dtype=torch.bool, device=dev)
backends = {
"default(dispatch)": None,
"MATH": [SDPBackend.MATH],
"FLASH": [SDPBackend.FLASH_ATTENTION],
"EFFICIENT": [SDPBackend.EFFICIENT_ATTENTION],
"CUDNN": [SDPBackend.CUDNN_ATTENTION],
}
print(f"shape B={B} H={H} N={N} D={D} {dt}\n")
print(f"{'backend':<20}{'mask=dense(ms)':>18}{'mask=None(ms)':>18}")
for name, bk in backends.items():
def run_dense():
if bk is None:
return F.scaled_dot_product_attention(q, k, v, attn_mask=dense)
with sdpa_kernel(bk):
return F.scaled_dot_product_attention(q, k, v, attn_mask=dense)
def run_none():
if bk is None:
return F.scaled_dot_product_attention(q, k, v, attn_mask=None)
with sdpa_kernel(bk):
return F.scaled_dot_product_attention(q, k, v, attn_mask=None)
dms = timed(run_dense)
nms = timed(run_none)
d_s = f"{dms:.2f}" if isinstance(dms, float) else dms
n_s = f"{nms:.2f}" if isinstance(nms, float) else nms
print(f"{name:<20}{d_s:>18}{n_s:>18}")