Skip to content

Commit 59d4e5a

Browse files
zhtmikesayakpaul
andauthored
add SP support for _flash_3_varlen_hub backend (#13809)
* add `_flash_3_varlen_hub` support for SP * make signiture clear * update * add accuracy test * fix dtype * loosen threshold because of bfloat16 * fix the break change of huggingface/kernels-community@1471f94 * fix stale error message * fix merge * revert change --------- Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
1 parent bb6b33e commit 59d4e5a

2 files changed

Lines changed: 274 additions & 34 deletions

File tree

src/diffusers/models/attention_dispatch.py

Lines changed: 253 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ class _HubKernelConfig:
340340
AttentionBackendName._FLASH_3_VARLEN_HUB: _HubKernelConfig(
341341
repo_id="kernels-community/flash-attn3",
342342
function_attr="flash_attn_varlen_func",
343+
wrapped_forward_attr="flash_attn_interface._flash_attn_forward",
344+
wrapped_backward_attr="flash_attn_interface._flash_attn_backward",
343345
version=1,
344346
),
345347
AttentionBackendName.FLASH_HUB: _HubKernelConfig(
@@ -1612,6 +1614,194 @@ def _flash_attention_3_hub_backward_op(
16121614
return grad_query, grad_key, grad_value
16131615

16141616

1617+
def _flash_attention_3_varlen_hub_forward_op(
1618+
ctx: torch.autograd.function.FunctionCtx,
1619+
query: torch.Tensor,
1620+
key: torch.Tensor,
1621+
value: torch.Tensor,
1622+
attn_mask: torch.Tensor | None = None,
1623+
dropout_p: float = 0.0,
1624+
is_causal: bool = False,
1625+
scale: float | None = None,
1626+
enable_gqa: bool = False,
1627+
return_lse: bool = False,
1628+
_save_ctx: bool = True,
1629+
_parallel_config: "ParallelConfig" | None = None,
1630+
*,
1631+
window_size: tuple[int, int] = (-1, -1),
1632+
softcap: float = 0.0,
1633+
num_splits: int = 1,
1634+
pack_gqa: bool | None = None,
1635+
deterministic: bool = False,
1636+
sm_margin: int = 0,
1637+
):
1638+
if dropout_p != 0.0:
1639+
raise ValueError("`dropout_p` is not yet supported for flash-attn 3 varlen hub kernels.")
1640+
if enable_gqa:
1641+
raise ValueError("`enable_gqa` is not yet supported for flash-attn 3 varlen hub kernels.")
1642+
1643+
config = _HUB_KERNELS_REGISTRY[AttentionBackendName._FLASH_3_VARLEN_HUB]
1644+
wrapped_forward_fn = config.wrapped_forward_fn
1645+
wrapped_backward_fn = config.wrapped_backward_fn
1646+
if wrapped_forward_fn is None or wrapped_backward_fn is None:
1647+
raise RuntimeError(
1648+
"Flash attention 3 varlen hub kernels must expose `flash_attn_interface._flash_attn_forward` and "
1649+
"`flash_attn_interface._flash_attn_backward` for context parallel execution."
1650+
)
1651+
1652+
if scale is None:
1653+
scale = query.shape[-1] ** (-0.5)
1654+
1655+
batch_size, seq_len_q, num_heads, _ = query.shape
1656+
_, seq_len_kv, _, _ = key.shape
1657+
1658+
if attn_mask is not None:
1659+
attn_mask = _normalize_attn_mask(attn_mask, batch_size, seq_len_kv)
1660+
(_, seqlens_k), (cu_seqlens_q, cu_seqlens_k), (_, max_seqlen_k) = (
1661+
_prepare_for_flash_attn_or_sage_varlen_with_mask(batch_size, seq_len_q, attn_mask, query.device)
1662+
)
1663+
indices_k = attn_mask.flatten().nonzero(as_tuple=False).flatten()
1664+
query_packed = query.flatten(0, 1)
1665+
key_packed = key.reshape(-1, *key.shape[2:])[indices_k]
1666+
value_packed = value.reshape(-1, *value.shape[2:])[indices_k]
1667+
max_seqlen_q = seq_len_q
1668+
else:
1669+
(_, seqlens_k), (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k) = (
1670+
_prepare_for_flash_attn_or_sage_varlen_without_mask(batch_size, seq_len_q, seq_len_kv, query.device)
1671+
)
1672+
query_packed = query.flatten(0, 1)
1673+
key_packed = key.flatten(0, 1)
1674+
value_packed = value.flatten(0, 1)
1675+
seqlens_k = None
1676+
1677+
out_packed, softmax_lse, *_ = wrapped_forward_fn(
1678+
query_packed,
1679+
key_packed,
1680+
value_packed,
1681+
None, # k_new
1682+
None, # v_new
1683+
None, # qv
1684+
None, # out_
1685+
cu_seqlens_q,
1686+
cu_seqlens_k,
1687+
None, # cu_seqlens_k_new
1688+
None, # seqused_q
1689+
None, # seqused_k
1690+
max_seqlen_q,
1691+
max_seqlen_k,
1692+
None, # page_table
1693+
None, # kv_batch_idx
1694+
None, # leftpad_k
1695+
None, # rotary_cos
1696+
None, # rotary_sin
1697+
None, # seqlens_rotary
1698+
None, # q_descale
1699+
None, # k_descale
1700+
None, # v_descale
1701+
scale,
1702+
causal=is_causal,
1703+
window_size_left=window_size[0],
1704+
window_size_right=window_size[1],
1705+
attention_chunk=0,
1706+
softcap=softcap,
1707+
rotary_interleaved=True,
1708+
scheduler_metadata=None,
1709+
num_splits=num_splits,
1710+
pack_gqa=pack_gqa,
1711+
sm_margin=sm_margin,
1712+
)
1713+
1714+
out = out_packed.view(batch_size, seq_len_q, *out_packed.shape[1:])
1715+
1716+
if _save_ctx:
1717+
ctx.save_for_backward(
1718+
query_packed, key_packed, value_packed, out_packed, softmax_lse, cu_seqlens_q, cu_seqlens_k
1719+
)
1720+
ctx.seqlens_k = seqlens_k # None if unmasked
1721+
ctx.indices_k = indices_k if attn_mask is not None else None
1722+
ctx.max_seqlen_q = max_seqlen_q
1723+
ctx.max_seqlen_k = max_seqlen_k
1724+
ctx.batch_size = batch_size
1725+
ctx.seq_len_q = seq_len_q
1726+
ctx.seq_len_kv = seq_len_kv
1727+
ctx.num_heads = num_heads
1728+
ctx.scale = scale
1729+
ctx.is_causal = is_causal
1730+
ctx.window_size = window_size
1731+
ctx.softcap = softcap
1732+
ctx.deterministic = deterministic
1733+
ctx.sm_margin = sm_margin
1734+
1735+
# softmax_lse in varlen mode: (num_heads, total_q) -> (batch_size, seq_len_q, num_heads)
1736+
lse_sp = softmax_lse.view(num_heads, batch_size, seq_len_q).permute(1, 2, 0).contiguous()
1737+
1738+
return (out, lse_sp) if return_lse else out
1739+
1740+
1741+
def _flash_attention_3_varlen_hub_backward_op(
1742+
ctx: torch.autograd.function.FunctionCtx,
1743+
grad_out: torch.Tensor,
1744+
*args,
1745+
**kwargs,
1746+
):
1747+
config = _HUB_KERNELS_REGISTRY[AttentionBackendName._FLASH_3_VARLEN_HUB]
1748+
wrapped_backward_fn = config.wrapped_backward_fn
1749+
if wrapped_backward_fn is None:
1750+
raise RuntimeError(
1751+
"Flash attention 3 varlen hub kernels must expose `flash_attn_interface._flash_attn_backward` "
1752+
"for context parallel execution."
1753+
)
1754+
1755+
query_packed, key_packed, value_packed, out_packed, softmax_lse, cu_seqlens_q, cu_seqlens_k = ctx.saved_tensors
1756+
1757+
grad_out_packed = grad_out.flatten(0, 1)
1758+
grad_query, grad_key, grad_value = (
1759+
torch.empty_like(query_packed),
1760+
torch.empty_like(key_packed),
1761+
torch.empty_like(value_packed),
1762+
)
1763+
1764+
wrapped_backward_fn(
1765+
grad_out_packed,
1766+
query_packed,
1767+
key_packed,
1768+
value_packed,
1769+
out_packed,
1770+
softmax_lse,
1771+
cu_seqlens_q,
1772+
cu_seqlens_k,
1773+
None,
1774+
None, # seqused_q, seqused_k
1775+
ctx.max_seqlen_q,
1776+
ctx.max_seqlen_k,
1777+
grad_query,
1778+
grad_key,
1779+
grad_value,
1780+
ctx.scale,
1781+
ctx.is_causal,
1782+
ctx.window_size[0],
1783+
ctx.window_size[1],
1784+
ctx.softcap,
1785+
ctx.deterministic,
1786+
ctx.sm_margin,
1787+
)
1788+
1789+
grad_query = grad_query.view(ctx.batch_size, ctx.seq_len_q, *grad_query.shape[1:])
1790+
1791+
if ctx.seqlens_k is not None:
1792+
grad_key = _unpad_to_padded(grad_key, ctx.indices_k, ctx.batch_size, ctx.seq_len_kv)
1793+
grad_value = _unpad_to_padded(grad_value, ctx.indices_k, ctx.batch_size, ctx.seq_len_kv)
1794+
else:
1795+
grad_key = grad_key.view(ctx.batch_size, ctx.seq_len_kv, *grad_key.shape[1:])
1796+
grad_value = grad_value.view(ctx.batch_size, ctx.seq_len_kv, *grad_value.shape[1:])
1797+
1798+
grad_query = grad_query[..., : grad_out.shape[-1]]
1799+
grad_key = grad_key[..., : grad_out.shape[-1]]
1800+
grad_value = grad_value[..., : grad_out.shape[-1]]
1801+
1802+
return grad_query, grad_key, grad_value
1803+
1804+
16151805
def _sage_attention_forward_op(
16161806
ctx: torch.autograd.function.FunctionCtx,
16171807
query: torch.Tensor,
@@ -3007,7 +3197,7 @@ def _flash_attention_3_hub(
30073197
@_AttentionBackendRegistry.register(
30083198
AttentionBackendName._FLASH_3_VARLEN_HUB,
30093199
constraints=[_check_device, _check_qkv_dtype_bf16_or_fp16, _check_shape],
3010-
supports_context_parallel=False,
3200+
supports_context_parallel=True,
30113201
)
30123202
def _flash_attention_3_varlen_hub(
30133203
query: torch.Tensor,
@@ -3019,44 +3209,74 @@ def _flash_attention_3_varlen_hub(
30193209
return_lse: bool = False,
30203210
_parallel_config: "ParallelConfig" | None = None,
30213211
) -> torch.Tensor:
3212+
if _parallel_config is not None and _parallel_config.context_parallel_config.ring_degree > 1:
3213+
raise NotImplementedError("`ring_degree > 1` is not yet supported for the _FLASH_3_VARLEN_HUB backend.")
3214+
30223215
batch_size, seq_len_q, _, _ = query.shape
30233216
_, seq_len_kv, _, _ = key.shape
30243217

3025-
if attn_mask is not None:
3026-
attn_mask = _normalize_attn_mask(attn_mask, batch_size, seq_len_kv)
3027-
(_, _), (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k) = (
3028-
_prepare_for_flash_attn_or_sage_varlen_with_mask(batch_size, seq_len_q, attn_mask, query.device)
3029-
)
3030-
indices_k = attn_mask.flatten().nonzero(as_tuple=False).flatten()
3031-
key_packed = key.reshape(-1, *key.shape[2:])[indices_k]
3032-
value_packed = value.reshape(-1, *value.shape[2:])[indices_k]
3033-
else:
3034-
(_, _), (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k) = (
3035-
_prepare_for_flash_attn_or_sage_varlen_without_mask(batch_size, seq_len_q, seq_len_kv, query.device)
3036-
)
3037-
key_packed = key.flatten(0, 1)
3038-
value_packed = value.flatten(0, 1)
3218+
if _parallel_config is None:
3219+
if attn_mask is not None:
3220+
attn_mask = _normalize_attn_mask(attn_mask, batch_size, seq_len_kv)
3221+
(_, _), (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k) = (
3222+
_prepare_for_flash_attn_or_sage_varlen_with_mask(batch_size, seq_len_q, attn_mask, query.device)
3223+
)
3224+
indices_k = attn_mask.flatten().nonzero(as_tuple=False).flatten()
3225+
key_packed = key.reshape(-1, *key.shape[2:])[indices_k]
3226+
value_packed = value.reshape(-1, *value.shape[2:])[indices_k]
3227+
else:
3228+
(_, _), (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k) = (
3229+
_prepare_for_flash_attn_or_sage_varlen_without_mask(batch_size, seq_len_q, seq_len_kv, query.device)
3230+
)
3231+
key_packed = key.flatten(0, 1)
3232+
value_packed = value.flatten(0, 1)
30393233

3040-
query_packed = query.flatten(0, 1)
3234+
query_packed = query.flatten(0, 1)
30413235

3042-
func = _HUB_KERNELS_REGISTRY[AttentionBackendName._FLASH_3_VARLEN_HUB].kernel_fn
3043-
result = func(
3044-
q=query_packed,
3045-
k=key_packed,
3046-
v=value_packed,
3047-
cu_seqlens_q=cu_seqlens_q,
3048-
cu_seqlens_k=cu_seqlens_k,
3049-
max_seqlen_q=max_seqlen_q,
3050-
max_seqlen_k=max_seqlen_k,
3051-
softmax_scale=scale,
3052-
causal=is_causal,
3053-
)
3054-
if isinstance(result, tuple):
3055-
out, lse, *_ = result
3236+
func = _HUB_KERNELS_REGISTRY[AttentionBackendName._FLASH_3_VARLEN_HUB].kernel_fn
3237+
result = func(
3238+
q=query_packed,
3239+
k=key_packed,
3240+
v=value_packed,
3241+
cu_seqlens_q=cu_seqlens_q,
3242+
cu_seqlens_k=cu_seqlens_k,
3243+
max_seqlen_q=max_seqlen_q,
3244+
max_seqlen_k=max_seqlen_k,
3245+
softmax_scale=scale,
3246+
causal=is_causal,
3247+
)
3248+
if isinstance(result, tuple):
3249+
out, lse, *_ = result
3250+
else:
3251+
out = result
3252+
lse = None
3253+
out = out.unflatten(0, (batch_size, -1))
30563254
else:
3057-
out = result
3058-
lse = None
3059-
out = out.unflatten(0, (batch_size, -1))
3255+
forward_op = functools.partial(
3256+
_flash_attention_3_varlen_hub_forward_op,
3257+
window_size=(-1, -1),
3258+
softcap=0.0,
3259+
num_splits=1,
3260+
pack_gqa=None,
3261+
deterministic=False,
3262+
sm_margin=0,
3263+
)
3264+
out = _templated_context_parallel_attention(
3265+
query,
3266+
key,
3267+
value,
3268+
attn_mask,
3269+
0.0,
3270+
is_causal,
3271+
scale,
3272+
False,
3273+
return_lse,
3274+
forward_op=forward_op,
3275+
backward_op=_flash_attention_3_varlen_hub_backward_op,
3276+
_parallel_config=_parallel_config,
3277+
)
3278+
if return_lse:
3279+
out, lse = out
30603280

30613281
return (out, lse) if return_lse else out
30623282

tests/models/testing_utils/parallelism.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,10 @@ class ContextParallelAttentionBackendsTesterMixin:
426426
"_flash_3_hub",
427427
marks=pytest.mark.skipif(not is_kernels_available(), reason="`kernels` is not available."),
428428
),
429+
pytest.param(
430+
"_flash_3_varlen_hub",
431+
marks=pytest.mark.skipif(not is_kernels_available(), reason="`kernels` is not available."),
432+
),
429433
],
430434
)
431435
@pytest.mark.parametrize("ulysses_anything", [True, False])
@@ -443,7 +447,7 @@ def test_context_parallel_attn_backend_inference(self, cp_type, attention_backen
443447
if cp_type == "ring_degree":
444448
if attention_backend == AttentionBackendName.NATIVE:
445449
pytest.skip("Skipping test because ring isn't supported with native attention backend.")
446-
elif attention_backend in ("flash_varlen_hub"):
450+
elif attention_backend in ("flash_varlen_hub", "_flash_3_varlen_hub"):
447451
pytest.skip("`ring_degree` is not yet supported for varlen attention hub kernels.")
448452

449453
if ulysses_anything and "ulysses" not in cp_type:
@@ -453,6 +457,18 @@ def test_context_parallel_attn_backend_inference(self, cp_type, attention_backen
453457
init_dict = self.get_init_dict()
454458
inputs_dict = self.get_dummy_inputs()
455459

460+
# Single-GPU reference with the same attention backend (no context parallel)
461+
model = self.model_class(**init_dict).eval().to(torch_device)
462+
if attention_backend:
463+
model.set_attention_backend(attention_backend)
464+
465+
# Copy inputs and cast model + inputs as needed
466+
ref_inputs = inputs_dict.copy()
467+
model, ref_inputs = _maybe_cast_to_bf16(attention_backend, model, ref_inputs)
468+
state_dict = {k: v.cpu() for k, v in model.state_dict().items()}
469+
with torch.no_grad():
470+
ref_output = model(**ref_inputs, return_dict=False)[0].cpu()
471+
456472
# Move all tensors to CPU for multiprocessing
457473
inputs_dict = {k: v.cpu() if isinstance(v, torch.Tensor) else v for k, v in inputs_dict.items()}
458474
cp_dict = {cp_type: world_size}
@@ -478,6 +494,7 @@ def test_context_parallel_attn_backend_inference(self, cp_type, attention_backen
478494
inputs_dict,
479495
return_dict,
480496
attention_backend,
497+
state_dict,
481498
),
482499
nprocs=world_size,
483500
join=True,
@@ -486,3 +503,6 @@ def test_context_parallel_attn_backend_inference(self, cp_type, attention_backen
486503
assert return_dict.get("status") == "success", (
487504
f"Context parallel inference failed: {return_dict.get('error', 'Unknown error')}"
488505
)
506+
507+
cp_output = torch.tensor(return_dict["output"], dtype=ref_output.dtype)
508+
torch.testing.assert_close(ref_output, cp_output, atol=1e-2, rtol=1e-2)

0 commit comments

Comments
 (0)