Repository navigation
[feat] Add opt-in FlashInfer attention backend for Wan #1799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
klhhhhh
wants to merge
12
commits into
hao-ai-lab:main
Choose a base branch
from
klhhhhh:flashinfer-backend
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3299edb
[feat]support flashinfer attention kernel of prefill
klhhhhh aaa8060
[feat]support flashinfer attention kernel of prefill
klhhhhh a22ccd7
add flashinfer enum
klhhhhh ef759cf
check flashinfer env
klhhhhh f275bd9
add flashinfer backend in selector
klhhhhh 01e6aca
[test]add tests for flashinfer
klhhhhh 9c33250
beanchmark for test
klhhhhh 374cc54
delete benchmark
klhhhhh 29f8075
fix some bugs
klhhhhh b1f045a
rebase
klhhhhh baa64ff
[fix]: scope FlashInfer to Wan and preserve backend compatibility
klhhhhh 5d56e3e
fix pkg import
klhhhhh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """FlashInfer dense prefill attention backend. | ||
|
|
||
| This backend uses FlashInfer's single-request NHD kernel once per batch item. | ||
| That path preserves FastVideo's BSHD/SP contract and supports self-attention, | ||
| cross-attention, GQA, causal attention, and tokenizer-style padding masks. | ||
| FlashInfer's prefill API is inference-only here; training must use FLASH_ATTN or | ||
| TORCH_SDPA. | ||
|
|
||
| ``forward`` reads ``attn_mask``/``is_causal`` off ``attn_metadata`` by | ||
| attribute, not by ``isinstance(FlashInferMetadata)``: several model forwards | ||
| (e.g. HYWorld) build ``SDPAMetadata`` directly and hand it to whichever | ||
| backend the selector resolved. Any metadata dataclass with the same field | ||
| names satisfies this backend; do not add a strict type check here without | ||
| also updating those call sites. | ||
|
|
||
| A layer must list FLASHINFER explicitly in its ``supported_attention_backends`` | ||
| to use this backend — there is no implicit bridge from FLASH_ATTN support, since | ||
| this kernel's masking/GQA conventions have not been vetted per-model. | ||
| """ | ||
|
|
||
| from dataclasses import dataclass | ||
|
|
||
| import torch | ||
|
|
||
| from fastvideo.attention.backends.abstract import (AttentionBackend, AttentionImpl, AttentionMetadata, | ||
| AttentionMetadataBuilder) | ||
|
|
||
|
|
||
| @dataclass | ||
| class FlashInferMetadata(AttentionMetadata): | ||
| current_timestep: int | ||
| attn_mask: torch.Tensor | None = None | ||
| is_causal: bool = False | ||
|
|
||
|
|
||
| class FlashInferMetadataBuilder(AttentionMetadataBuilder): | ||
|
|
||
| def prepare(self): | ||
| pass | ||
|
|
||
| def build(self, current_timestep: int, attn_mask: torch.Tensor | None = None) -> FlashInferMetadata: # type: ignore | ||
| return FlashInferMetadata(current_timestep=current_timestep, attn_mask=attn_mask) | ||
|
|
||
|
|
||
| class FlashInferBackend(AttentionBackend): | ||
| accept_output_buffer: bool = True | ||
|
|
||
| @staticmethod | ||
| def get_supported_head_sizes() -> list[int]: | ||
| # FlashInfer 0.6.x's FA2 fallback is unsafe for some other dimensions. | ||
| return [64, 128, 256] | ||
|
|
||
| @staticmethod | ||
| def get_name() -> str: | ||
| return "FLASHINFER" | ||
|
|
||
| @staticmethod | ||
| def get_impl_cls() -> type["FlashInferImpl"]: | ||
| return FlashInferImpl | ||
|
|
||
| @staticmethod | ||
| def get_metadata_cls() -> type[FlashInferMetadata]: | ||
| return FlashInferMetadata | ||
|
|
||
| @staticmethod | ||
| def get_builder_cls() -> type[FlashInferMetadataBuilder]: | ||
| return FlashInferMetadataBuilder | ||
|
|
||
|
|
||
| def _mask_for_sample(attn_mask: torch.Tensor, sample: int, query_len: int, key_len: int) -> torch.Tensor: | ||
| """Convert FastVideo's padding/additive mask to FlashInfer's [Q, K] bool mask.""" | ||
| mask = attn_mask.to(dtype=torch.bool) if not attn_mask.dtype.is_floating_point else attn_mask >= 0 | ||
| if mask.dim() == 2: | ||
| if mask.shape[-1] > key_len: | ||
| raise ValueError(f"Invalid FLASHINFER mask length: expected at most {key_len}, got {mask.shape[-1]}") | ||
| key_mask = mask[sample] | ||
| if key_mask.shape[0] < key_len: | ||
| key_mask = torch.nn.functional.pad(key_mask, (key_len - key_mask.shape[0], 0), value=True) | ||
| return key_mask.unsqueeze(0).expand(query_len, -1) | ||
| if mask.dim() == 3: | ||
| mask = mask[sample] | ||
| elif mask.dim() == 4: | ||
| mask = mask[sample, 0] | ||
|
klhhhhh marked this conversation as resolved.
Resolved
|
||
| else: | ||
| raise ValueError(f"Unsupported FLASHINFER attention mask shape: {attn_mask.shape}") | ||
| if mask.shape[-2:] != (query_len, key_len): | ||
| if mask.shape[-2] == 1 and mask.shape[-1] == key_len: | ||
| mask = mask.expand(query_len, -1) | ||
| else: | ||
| raise ValueError(f"FLASHINFER mask must broadcast to [{query_len}, {key_len}], got {mask.shape}") | ||
| return mask | ||
|
|
||
|
|
||
| class FlashInferImpl(AttentionImpl): | ||
|
|
||
| def __init__(self, | ||
| num_heads: int, | ||
| head_size: int, | ||
| causal: bool, | ||
| softmax_scale: float, | ||
| num_kv_heads: int | None = None, | ||
| prefix: str = "", | ||
| **extra_impl_args) -> None: | ||
| del num_heads, head_size, num_kv_heads, prefix, extra_impl_args | ||
| self.causal = causal | ||
| self.softmax_scale = softmax_scale | ||
|
|
||
| def forward(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, | ||
| attn_metadata: FlashInferMetadata | None) -> torch.Tensor: | ||
| if torch.is_grad_enabled() and (query.requires_grad or key.requires_grad or value.requires_grad): | ||
| raise RuntimeError("FLASHINFER backend is inference-only; use FLASH_ATTN or TORCH_SDPA for training.") | ||
|
|
||
| from flashinfer.prefill import single_prefill_with_kv_cache | ||
|
|
||
| original_dtype = query.dtype | ||
| if original_dtype not in (torch.float16, torch.bfloat16): | ||
| query = query.to(torch.bfloat16) | ||
| key = key.to(torch.bfloat16) | ||
| value = value.to(torch.bfloat16) | ||
|
|
||
| mask = attn_metadata.attn_mask if attn_metadata is not None else None | ||
| causal = self.causal or bool(attn_metadata is not None and getattr(attn_metadata, "is_causal", False)) | ||
| outputs = [] | ||
| for sample in range(query.shape[0]): | ||
| custom_mask = None | ||
| if mask is not None: | ||
| custom_mask = _mask_for_sample(mask, sample, query.shape[1], key.shape[1]).to(query.device) | ||
| if causal: | ||
| causal_mask = torch.ones((query.shape[1], key.shape[1]), dtype=torch.bool, | ||
| device=query.device).tril(key.shape[1] - query.shape[1]) | ||
| custom_mask = custom_mask & causal_mask | ||
| outputs.append( | ||
| single_prefill_with_kv_cache(query[sample], | ||
| key[sample], | ||
| value[sample], | ||
| custom_mask=custom_mask, | ||
| causal=causal and custom_mask is None, | ||
| kv_layout="NHD", | ||
| sm_scale=self.softmax_scale)) | ||
| output = torch.stack(outputs) | ||
| return output.to(original_dtype) if output.dtype != original_dtype else output | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.