[Feature] Flash Attention to Support Attention Mask (#760)
Signed-off-by: Didan Deng <33117903+wtomin@users.noreply.github.com>
This commit is contained in:
1 parent
35f994e255
commit
43ec7f701b
6 files changed
+566
-12
No files matched your search
@@ -0,0 +1,290 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
"""
|
||||
Test script for FlashAttention backend with padding handling.
|
||||
|
||||
This script tests two main scenarios:
|
||||
1. Case 1: Comparing padded vs unpadded inputs for batch_size=1
|
||||
2. Case 2: Comparing FlashAttention and SDPA backends for batch_size=2 with padding
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from vllm_omni.diffusion.attention.backends.abstract import AttentionMetadata
|
||||
from vllm_omni.diffusion.attention.backends.flash_attn import FlashAttentionImpl
|
||||
from vllm_omni.diffusion.attention.backends.sdpa import SDPAImpl
|
||||
|
||||
|
||||
def create_attention_mask(batch_size: int, seq_len: int, valid_len: int, device: torch.device) -> torch.Tensor:
|
||||
"""
|
||||
Create attention mask where first valid_len tokens are valid (1) and rest are padding (0).
|
||||
|
||||
Args:
|
||||
batch_size: Batch size
|
||||
seq_len: Total sequence length (including padding)
|
||||
valid_len: Number of valid (non-padded) tokens
|
||||
|
||||
Returns:
|
||||
Attention mask of shape (batch_size, seq_len)
|
||||
"""
|
||||
mask = torch.zeros(batch_size, seq_len, dtype=torch.bool, device=device)
|
||||
mask[:, :valid_len] = True
|
||||
return mask
|
||||
|
||||
|
||||
def pad_tensor(tensor: torch.Tensor, target_seq_len: int, pad_value: float = 0.0) -> torch.Tensor:
|
||||
"""
|
||||
Pad tensor along sequence dimension (dim=1).
|
||||
|
||||
Args:
|
||||
tensor: Input tensor of shape (batch_size, seq_len, num_heads, head_dim)
|
||||
target_seq_len: Target sequence length after padding
|
||||
pad_value: Value to use for padding
|
||||
|
||||
Returns:
|
||||
Padded tensor of shape (batch_size, target_seq_len, num_heads, head_dim)
|
||||
"""
|
||||
batch_size, seq_len, num_heads, head_dim = tensor.shape
|
||||
if target_seq_len <= seq_len:
|
||||
return tensor
|
||||
|
||||
padding = torch.full(
|
||||
(batch_size, target_seq_len - seq_len, num_heads, head_dim), pad_value, dtype=tensor.dtype, device=tensor.device
|
||||
)
|
||||
return torch.cat([tensor, padding], dim=1)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="FlashAttention requires CUDA")
|
||||
def test_padding_equivalence():
|
||||
"""
|
||||
Case 1: Test that padded and unpadded inputs produce similar outputs.
|
||||
|
||||
- Input A: batch_size=1, hidden_states (1, 48), encoder_hidden_states (1, 16)
|
||||
Concatenated length: 64, NO attention_mask
|
||||
- Input B: Same data but padded: hidden_states (1, 58), encoder_hidden_states (1, 26)
|
||||
Concatenated length: 84, WITH attention_mask
|
||||
|
||||
Expected: Output A and Output B should be very close.
|
||||
"""
|
||||
device = torch.device("cuda")
|
||||
dtype = torch.bfloat16
|
||||
|
||||
# Configuration
|
||||
batch_size = 1
|
||||
hidden_seq_len = 48
|
||||
encoder_seq_len = 16
|
||||
pad_length = 10
|
||||
num_heads = 8
|
||||
head_dim = 64
|
||||
|
||||
# Initialize FlashAttention
|
||||
fa_impl = FlashAttentionImpl(
|
||||
num_heads=num_heads, head_size=head_dim, softmax_scale=1.0 / (head_dim**0.5), causal=False
|
||||
)
|
||||
|
||||
# Create base tensors with random values (same for both A and B)
|
||||
torch.manual_seed(42)
|
||||
hidden_states_base = torch.randn(batch_size, hidden_seq_len, num_heads, head_dim, device=device, dtype=dtype)
|
||||
encoder_hidden_states_base = torch.randn(
|
||||
batch_size, encoder_seq_len, num_heads, head_dim, device=device, dtype=dtype
|
||||
)
|
||||
|
||||
# ========== Input A: Unpadded, no attention mask ==========
|
||||
query_a = torch.cat([hidden_states_base, encoder_hidden_states_base], dim=1)
|
||||
key_a = query_a.clone()
|
||||
value_a = query_a.clone()
|
||||
|
||||
attn_metadata_a = AttentionMetadata(attn_mask=None)
|
||||
|
||||
output_a = fa_impl.forward(query=query_a, key=key_a, value=value_a, attn_metadata=attn_metadata_a)
|
||||
|
||||
# ========== Input B: Padded with attention mask ==========
|
||||
hidden_states_padded = pad_tensor(hidden_states_base, hidden_seq_len + pad_length)
|
||||
encoder_hidden_states_padded = pad_tensor(encoder_hidden_states_base, encoder_seq_len + pad_length)
|
||||
|
||||
query_b = torch.cat([hidden_states_padded, encoder_hidden_states_padded], dim=1)
|
||||
key_b = query_b.clone()
|
||||
value_b = query_b.clone()
|
||||
|
||||
# Create attention mask
|
||||
attn_mask_b = torch.cat(
|
||||
[
|
||||
create_attention_mask(batch_size, hidden_seq_len + pad_length, hidden_seq_len, device),
|
||||
create_attention_mask(batch_size, encoder_seq_len + pad_length, encoder_seq_len, device),
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
|
||||
attn_metadata_b = AttentionMetadata(attn_mask=attn_mask_b)
|
||||
|
||||
output_b = fa_impl.forward(query=query_b, key=key_b, value=value_b, attn_metadata=attn_metadata_b)
|
||||
|
||||
# Extract non-padded portion from output_b
|
||||
output_b_unpadded = torch.cat(
|
||||
[
|
||||
output_b[:, :hidden_seq_len, :, :],
|
||||
output_b[:, hidden_seq_len + pad_length : hidden_seq_len + pad_length + encoder_seq_len, :, :],
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
|
||||
# Compare outputs
|
||||
max_diff = torch.max(torch.abs(output_a - output_b_unpadded)).item()
|
||||
mean_diff = torch.mean(torch.abs(output_a - output_b_unpadded)).item()
|
||||
|
||||
print("\n=== Case 1: Padding Equivalence Test ===")
|
||||
print(f"Output A shape: {output_a.shape}")
|
||||
print(f"Output B shape: {output_b.shape}")
|
||||
print(f"Output B unpadded shape: {output_b_unpadded.shape}")
|
||||
print(f"Max absolute difference: {max_diff:.6f}")
|
||||
print(f"Mean absolute difference: {mean_diff:.6f}")
|
||||
|
||||
# Assert that outputs are close
|
||||
# Using higher tolerance for bfloat16
|
||||
assert max_diff < 0.1, f"Max difference {max_diff} exceeds threshold 0.1"
|
||||
assert mean_diff < 0.01, f"Mean difference {mean_diff} exceeds threshold 0.01"
|
||||
|
||||
print("✓ Case 1 PASSED: Padded and unpadded outputs are very close!")
|
||||
|
||||
|
||||
@pytest.mark.skipif(not torch.cuda.is_available(), reason="FlashAttention requires CUDA")
|
||||
def test_fa_vs_sdpa():
|
||||
"""
|
||||
Case 2: Compare FlashAttention and SDPA backends with padding.
|
||||
|
||||
- batch_size=2
|
||||
- hidden_states: (2, 48) padded to (2, 58)
|
||||
- encoder_hidden_states: (2, 16) padded to (2, 26)
|
||||
- Concatenated length: 84
|
||||
- Compare FA and SDPA outputs
|
||||
|
||||
Expected: FA and SDPA outputs should be very close.
|
||||
"""
|
||||
device = torch.device("cuda")
|
||||
dtype = torch.bfloat16
|
||||
|
||||
# Configuration
|
||||
batch_size = 2
|
||||
hidden_seq_len = 48
|
||||
encoder_seq_len = 16
|
||||
pad_length = 10
|
||||
num_heads = 8
|
||||
head_dim = 64
|
||||
|
||||
# Initialize both backends
|
||||
fa_impl = FlashAttentionImpl(
|
||||
num_heads=num_heads, head_size=head_dim, softmax_scale=1.0 / (head_dim**0.5), causal=False
|
||||
)
|
||||
|
||||
sdpa_impl = SDPAImpl(num_heads=num_heads, head_size=head_dim, softmax_scale=1.0 / (head_dim**0.5), causal=False)
|
||||
|
||||
# Create base tensors
|
||||
torch.manual_seed(123)
|
||||
hidden_states_base = torch.randn(batch_size, hidden_seq_len, num_heads, head_dim, device=device, dtype=dtype)
|
||||
encoder_hidden_states_base = torch.randn(
|
||||
batch_size, encoder_seq_len, num_heads, head_dim, device=device, dtype=dtype
|
||||
)
|
||||
|
||||
# Pad tensors
|
||||
hidden_states_padded = pad_tensor(hidden_states_base, hidden_seq_len + pad_length)
|
||||
encoder_hidden_states_padded = pad_tensor(encoder_hidden_states_base, encoder_seq_len + pad_length)
|
||||
|
||||
# Concatenate
|
||||
query = torch.cat([hidden_states_padded, encoder_hidden_states_padded], dim=1)
|
||||
key = query.clone()
|
||||
value = query.clone()
|
||||
|
||||
# Create attention mask
|
||||
attn_mask = torch.cat(
|
||||
[
|
||||
create_attention_mask(batch_size, hidden_seq_len + pad_length, hidden_seq_len, device),
|
||||
create_attention_mask(batch_size, encoder_seq_len + pad_length, encoder_seq_len, device),
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
|
||||
attn_metadata = AttentionMetadata(attn_mask=attn_mask)
|
||||
|
||||
# Run FlashAttention
|
||||
output_fa = fa_impl.forward(query=query.clone(), key=key.clone(), value=value.clone(), attn_metadata=attn_metadata)
|
||||
|
||||
# Run SDPA
|
||||
# SDPA expects 4D attention mask: (batch_size, 1, seq_len, seq_len) or (batch_size, seq_len)
|
||||
# For causal=False, we need to convert 2D mask to 4D
|
||||
if attn_mask is not None:
|
||||
# Expand mask for SDPA: (batch_size, seq_len) -> (batch_size, 1, 1, seq_len)
|
||||
attn_mask_4d = attn_mask.unsqueeze(1).unsqueeze(2)
|
||||
# Convert bool to float: True -> 0.0, False -> -inf
|
||||
attn_mask_float = torch.zeros_like(attn_mask_4d, dtype=dtype)
|
||||
attn_mask_float.masked_fill_(~attn_mask_4d, float("-inf"))
|
||||
attn_metadata_sdpa = AttentionMetadata(attn_mask=attn_mask_float)
|
||||
else:
|
||||
attn_metadata_sdpa = AttentionMetadata(attn_mask=None)
|
||||
|
||||
output_sdpa = sdpa_impl.forward(
|
||||
query=query.clone(), key=key.clone(), value=value.clone(), attn_metadata=attn_metadata_sdpa
|
||||
)
|
||||
|
||||
# Compare outputs (only compare valid regions)
|
||||
output_fa_valid = torch.cat(
|
||||
[
|
||||
output_fa[:, :hidden_seq_len, :, :],
|
||||
output_fa[:, hidden_seq_len + pad_length : hidden_seq_len + pad_length + encoder_seq_len, :, :],
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
output_sdpa_valid = torch.cat(
|
||||
[
|
||||
output_sdpa[:, :hidden_seq_len, :, :],
|
||||
output_sdpa[:, hidden_seq_len + pad_length : hidden_seq_len + pad_length + encoder_seq_len, :, :],
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
|
||||
max_diff = torch.max(torch.abs(output_fa_valid - output_sdpa_valid)).item()
|
||||
mean_diff = torch.mean(torch.abs(output_fa_valid - output_sdpa_valid)).item()
|
||||
|
||||
print("\n=== Case 2: FA vs SDPA Comparison ===")
|
||||
print(f"Batch size: {batch_size}")
|
||||
print(f"FA output shape: {output_fa.shape}")
|
||||
print(f"SDPA output shape: {output_sdpa.shape}")
|
||||
print(f"Max absolute difference (valid region): {max_diff:.6f}")
|
||||
print(f"Mean absolute difference (valid region): {mean_diff:.6f}")
|
||||
|
||||
# Assert that outputs are close
|
||||
# Using higher tolerance for bfloat16 and different implementations
|
||||
assert max_diff < 0.01, f"Max difference {max_diff} exceeds threshold 0.01"
|
||||
assert mean_diff < 0.001, f"Mean difference {mean_diff} exceeds threshold 0.001"
|
||||
|
||||
print("✓ Case 2 PASSED: FA and SDPA outputs are very close!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Running FlashAttention Padding Tests...")
|
||||
print("=" * 60)
|
||||
|
||||
# Try to run CUDA tests
|
||||
if torch.cuda.is_available():
|
||||
try:
|
||||
print("\n[Running Case 1: Padding Equivalence for FA]")
|
||||
test_padding_equivalence()
|
||||
except Exception as e:
|
||||
print(f"✗ Case 1 failed: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
try:
|
||||
print("\n[Running Case 2: FA vs SDPA]")
|
||||
test_fa_vs_sdpa()
|
||||
except Exception as e:
|
||||
print(f"✗ Case 2 failed: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
else:
|
||||
raise RuntimeError("CUDA is not available")
|
||||
print("\n" + "=" * 60)
|
||||
print("Test suite completed!")
|
||||
@@ -9,13 +9,14 @@ from vllm_omni.diffusion.attention.backends.abstract import (
|
||||
AttentionImpl,
|
||||
AttentionMetadata,
|
||||
)
|
||||
from vllm_omni.diffusion.attention.backends.utils.fa import _pad_input, _unpad_input, _upad_input
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
try:
|
||||
# only tested with flash_attn v3
|
||||
# from flash_attn_interface import flash_attn_func as flash_attn_3_func # not available in flash-attn 2.8.1
|
||||
from flash_attn import flash_attn_func # can be FA2 or FA3
|
||||
from flash_attn import flash_attn_func, flash_attn_varlen_func # can be FA2 or FA3
|
||||
except ImportError:
|
||||
logger.warning(
|
||||
"FlashAttentionBackend is not available. You may install flash-attn "
|
||||
@@ -63,12 +64,51 @@ class FlashAttentionImpl(AttentionImpl):
|
||||
value: torch.Tensor,
|
||||
attn_metadata: AttentionMetadata = None,
|
||||
) -> torch.Tensor:
|
||||
# TODO: flash_attn_func does not support attn_mask.
|
||||
out: torch.Tensor = flash_attn_func(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
causal=self.causal,
|
||||
softmax_scale=self.softmax_scale,
|
||||
)
|
||||
"""
|
||||
Flash attention implementation.
|
||||
|
||||
Args:
|
||||
query: (batch_size, seq_len, num_heads, head_dim)
|
||||
key: (batch_size, seq_len, num_heads, head_dim)
|
||||
value: (batch_size, seq_len, num_heads, head_dim)
|
||||
attn_metadata: AttentionMetadata. Attention mask is supported as attn_metadata.attn_mask
|
||||
|
||||
Returns:
|
||||
(batch_size, seq_len, num_heads, head_dim)
|
||||
"""
|
||||
query_length = query.size(1)
|
||||
attention_mask = attn_metadata.attn_mask if attn_metadata is not None else None
|
||||
# Contains at least one padding token in the sequence
|
||||
if attention_mask is not None and torch.any(~attention_mask):
|
||||
assert attention_mask.ndim == 2, "attention_mask must be 2D, (batch_size, seq_len)"
|
||||
q, k, v, indices_q, (cu_seq_lens_q, cu_seq_lens_k), (max_length_q, max_length_k) = _upad_input(
|
||||
query, key, value, attention_mask, query_length, _unpad_input
|
||||
)
|
||||
|
||||
out_unpad = flash_attn_varlen_func(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
cu_seqlens_q=cu_seq_lens_q,
|
||||
cu_seqlens_k=cu_seq_lens_k,
|
||||
max_seqlen_q=max_length_q,
|
||||
max_seqlen_k=max_length_k,
|
||||
**{
|
||||
"causal": self.causal,
|
||||
"softmax_scale": self.softmax_scale,
|
||||
},
|
||||
)
|
||||
if isinstance(out_unpad, tuple):
|
||||
out_unpad = out_unpad[0]
|
||||
|
||||
out = _pad_input(out_unpad, indices_q, query.size(0), query_length)
|
||||
|
||||
else:
|
||||
out: torch.Tensor = flash_attn_func(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
causal=self.causal,
|
||||
softmax_scale=self.softmax_scale,
|
||||
)
|
||||
return out
|
||||
@@ -0,0 +1,13 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""
|
||||
Utils for attention backends.
|
||||
"""
|
||||
|
||||
from vllm_omni.diffusion.attention.backends.utils.fa import _pad_input, _unpad_input, _upad_input
|
||||
|
||||
__all__ = [
|
||||
"_pad_input",
|
||||
"_unpad_input",
|
||||
"_upad_input",
|
||||
]
|
||||
@@ -0,0 +1,209 @@
|
||||
# Copyright 2025 The Fairseq Authors and the HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# Adapted from https://github.com/huggingface/transformers/blob/main/src/transformers/modeling_flash_attention_utils.py
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def _index_first_axis(tensor, indices):
|
||||
"""
|
||||
A local implementation of the PyTorch indexing operation `tensor[indices]` on the first axis,
|
||||
after flattening the first two dimensions of the tensor. This is functionally equivalent to
|
||||
FA2's `index_first_axis` and replaces the need to import it.
|
||||
"""
|
||||
# The input tensor is expected to be of shape (batch, seq_len, ...). We flatten the first
|
||||
# two dimensions to get (total_tokens, ...) before indexing.
|
||||
reshaped_tensor = tensor.reshape(-1, *tensor.shape[2:])
|
||||
return reshaped_tensor[indices]
|
||||
|
||||
|
||||
def _unpad_input(hidden_states, attention_mask, unused_mask=None):
|
||||
"""
|
||||
unpad_input function for flash attention variants that do not have them within their pkg themselves, e.g. fa3.
|
||||
|
||||
Arguments:
|
||||
hidden_states: (batch, seqlen, ...)
|
||||
attention_mask: (batch, seqlen), bool / int, 1 means valid and 0 means not valid.
|
||||
unused_mask: (batch, seqlen), bool / int, 1 means the element is allocated but unused.
|
||||
|
||||
Return:
|
||||
hidden_states: (total_nnz, ...), where total_nnz = number of tokens selected in attention_mask + unused_mask.
|
||||
indices: (total_nnz), the indices of masked tokens from the flattened input sequence.
|
||||
cu_seqlens: (batch + 1), the cumulative sequence lengths, used to index into hidden_states.
|
||||
max_seqlen_in_batch: int
|
||||
seqused: (batch), returns the number of tokens selected in attention_mask + unused_mask.
|
||||
"""
|
||||
all_masks = (attention_mask + unused_mask) if unused_mask is not None else attention_mask
|
||||
seqlens_in_batch = all_masks.sum(dim=-1, dtype=torch.int32)
|
||||
used_seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32)
|
||||
indices = torch.nonzero(all_masks.flatten(), as_tuple=False).flatten()
|
||||
max_seqlen_in_batch = seqlens_in_batch.max().item()
|
||||
cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.int32), (1, 0))
|
||||
|
||||
return (
|
||||
_index_first_axis(hidden_states, indices),
|
||||
indices,
|
||||
cu_seqlens,
|
||||
max_seqlen_in_batch,
|
||||
used_seqlens_in_batch,
|
||||
)
|
||||
|
||||
|
||||
def _pad_input(hidden_states, indices, batch, seqlen):
|
||||
"""
|
||||
pad_input function for flash attention variants that do not have them within their pkg themselves, e.g. fa3.
|
||||
|
||||
Arguments:
|
||||
hidden_states: (total_nnz, ...), where total_nnz = number of tokens in selected in attention_mask.
|
||||
indices: (total_nnz), the indices that represent the non-masked tokens of the original padded input sequence.
|
||||
batch: int, batch size for the padded sequence.
|
||||
seqlen: int, maximum sequence length for the padded sequence.
|
||||
|
||||
Return:
|
||||
hidden_states: (batch, seqlen, ...)
|
||||
"""
|
||||
dim = hidden_states.shape[1:]
|
||||
output = torch.zeros((batch * seqlen), *dim, device=hidden_states.device, dtype=hidden_states.dtype)
|
||||
output[indices] = hidden_states
|
||||
return output.view(batch, seqlen, *dim)
|
||||
|
||||
|
||||
def _get_unpad_data(attention_mask: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, int]:
|
||||
"""
|
||||
Retrieves indexing data required to repad unpadded (ragged) tensors.
|
||||
|
||||
Arguments:
|
||||
attention_mask (`torch.Tensor`):
|
||||
Boolean or int tensor of shape (batch_size, sequence_length), 1 means valid and 0 means not valid.
|
||||
|
||||
Return:
|
||||
indices (`torch.Tensor`):
|
||||
The indices of non-masked tokens from the flattened input sequence.
|
||||
cu_seqlens (`torch.Tensor`):
|
||||
The cumulative sequence lengths, used to index into ragged (unpadded) tensors.
|
||||
`cu_seqlens` shape is (batch_size + 1,).
|
||||
max_seqlen_in_batch (`int`):
|
||||
Maximum sequence length in batch.
|
||||
"""
|
||||
seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32)
|
||||
indices = torch.nonzero(attention_mask.flatten(), as_tuple=False).flatten()
|
||||
# NOTE: Similar to the `.item()` in prepare_fa2_from_position_ids, with torch compile,
|
||||
# this might cause a graph break
|
||||
max_seqlen_in_batch = seqlens_in_batch.max().item()
|
||||
cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.int32), (1, 0))
|
||||
return (
|
||||
indices,
|
||||
cu_seqlens,
|
||||
max_seqlen_in_batch,
|
||||
)
|
||||
|
||||
|
||||
def _upad_input(
|
||||
query_layer: torch.Tensor,
|
||||
key_layer: torch.Tensor,
|
||||
value_layer: torch.Tensor,
|
||||
attention_mask: torch.Tensor,
|
||||
query_length: int,
|
||||
unpad_input_func,
|
||||
):
|
||||
"""
|
||||
Unpads query, key, and values tensors, using a single dimension for all tokens even though they belong
|
||||
to different batches. This function is used instead of `flash_attn.bert_padding.unpad_input` in
|
||||
order to avoid the recomputation of the same intermediary tensors for query, key, value tensors.
|
||||
|
||||
Arguments:
|
||||
query_layer (`torch.Tensor`):
|
||||
Query state with padding. Shape: (batch_size, query_length, num_heads, head_dim).
|
||||
key_layer (`torch.Tensor`):
|
||||
Key state with padding. Shape: (batch_size, kv_seq_len, num_key_value_heads, head_dim).
|
||||
value_layer (`torch.Tensor`):
|
||||
Value state with padding. Shape: (batch_size, kv_seq_len, num_key_value_heads, head_dim).
|
||||
attention_mask (`torch.Tensor`):
|
||||
Boolean or int tensor of shape (batch_size, sequence_length), 1 means valid and 0 means not valid.
|
||||
query_length (`int`):
|
||||
Target length.
|
||||
unpad_input_func:
|
||||
The function to use for unpadding the input tensors.
|
||||
|
||||
Return:
|
||||
query_layer (`torch.Tensor`):
|
||||
Query state without padding. Shape: (total_target_length, num_heads, head_dim).
|
||||
key_layer (`torch.Tensor`):
|
||||
Key state with padding. Shape: (total_source_length, num_key_value_heads, head_dim).
|
||||
value_layer (`torch.Tensor`):
|
||||
Value state with padding. Shape: (total_source_length, num_key_value_heads, head_dim).
|
||||
indices_q (`torch.Tensor`):
|
||||
The indices of non-masked tokens from the flattened input target sequence.
|
||||
(cu_seqlens_q, cu_seqlens_k) (`tuple[int]`):
|
||||
The cumulative sequence lengths for the target (query) and source (key, value), used to index into
|
||||
ragged (unpadded) tensors. `cu_seqlens` shape is (batch_size + 1,).
|
||||
(max_seqlen_in_batch_q, max_seqlen_in_batch_k) (`tuple[int]`):
|
||||
Maximum sequence length in batch (`max_seqlen_in_batch_q` for the target sequence i.e. query,
|
||||
`max_seqlen_in_batch_k` for the source sequence i.e. key/value).
|
||||
"""
|
||||
if torch.compiler.is_compiling():
|
||||
# allow PyTorch compiler to include operations that return scalar values (like .item()
|
||||
torch._dynamo.config.capture_scalar_outputs = True
|
||||
indices_k, cu_seqlens_k, max_seqlen_in_batch_k = _get_unpad_data(attention_mask)
|
||||
|
||||
# With static caches, the k/v states may be larger than the mask ->
|
||||
# we need to slice them to avoid generating garbage
|
||||
# It's a bit of an anti-pattern, but otherwise we silently compute wrong attentions scores
|
||||
if key_layer.shape[1] > (seq_len := attention_mask.shape[-1]):
|
||||
key_layer, value_layer = key_layer[:, :seq_len, :, :], value_layer[:, :seq_len, :, :]
|
||||
|
||||
batch_size, kv_seq_len, num_key_value_heads, head_dim = key_layer.shape
|
||||
|
||||
key_layer = _index_first_axis(key_layer, indices_k)
|
||||
value_layer = _index_first_axis(value_layer, indices_k)
|
||||
if query_length == kv_seq_len:
|
||||
query_layer = _index_first_axis(query_layer, indices_k)
|
||||
cu_seqlens_q = cu_seqlens_k
|
||||
max_seqlen_in_batch_q = max_seqlen_in_batch_k
|
||||
indices_q = indices_k
|
||||
elif query_length == 1:
|
||||
max_seqlen_in_batch_q = 1
|
||||
cu_seqlens_q = torch.arange(
|
||||
batch_size + 1, dtype=torch.int32, device=query_layer.device
|
||||
) # There is a memcpy here, that is very bad.
|
||||
indices_q = cu_seqlens_q[:-1]
|
||||
query_layer = query_layer.squeeze(1)
|
||||
else:
|
||||
# The -q_len: slice assumes left padding.
|
||||
attention_mask = attention_mask[:, -query_length:]
|
||||
query_layer, indices_q, cu_seqlens_q, max_seqlen_in_batch_q, *_ = unpad_input_func(query_layer, attention_mask)
|
||||
|
||||
return (
|
||||
query_layer,
|
||||
key_layer,
|
||||
value_layer,
|
||||
indices_q,
|
||||
(cu_seqlens_q, cu_seqlens_k),
|
||||
(max_seqlen_in_batch_q, max_seqlen_in_batch_k),
|
||||
)
|
||||
|
||||
|
||||
def _is_packed_sequence(position_ids, batch_size):
|
||||
"""
|
||||
Check the position ids whether packed sequences are indicated or not
|
||||
1. Position ids exist
|
||||
2. Flattened sequences only are supported
|
||||
3. Compile-friendly `not (torch.diff(position_ids, dim=-1) >= 0).all()`, i.e.
|
||||
we have multiple increasing sequences
|
||||
"""
|
||||
if position_ids is None:
|
||||
return False
|
||||
|
||||
increasing_position_sequences = torch.arange(position_ids.shape[1], device=position_ids.device) + position_ids.min()
|
||||
return batch_size == 1 and (increasing_position_sequences - position_ids).abs().sum().bool()
|
||||
@@ -31,6 +31,8 @@ _BACKEND_CONFIG = {
|
||||
"ASCEND": {"module": "vllm_omni.diffusion.attention.backends.ascend_attn", "class": "AscendAttentionBackend"},
|
||||
}
|
||||
|
||||
_BACKENDS_SUPPORT_ATTENTION_MASK = ["SDPA", "ASCEND", "FLASH_ATTN"]
|
||||
|
||||
|
||||
def load_backend(backend_name: str) -> type[AttentionBackend]:
|
||||
config = _BACKEND_CONFIG[backend_name]
|
||||
|
||||
@@ -23,7 +23,7 @@ from vllm_omni.diffusion.attention.backends.abstract import (
|
||||
AttentionMetadata,
|
||||
)
|
||||
from vllm_omni.diffusion.attention.layer import Attention
|
||||
from vllm_omni.diffusion.attention.selector import get_attn_backend
|
||||
from vllm_omni.diffusion.attention.selector import _BACKENDS_SUPPORT_ATTENTION_MASK, get_attn_backend
|
||||
from vllm_omni.diffusion.cache.base import CachedTransformer
|
||||
from vllm_omni.diffusion.data import OmniDiffusionConfig
|
||||
from vllm_omni.diffusion.distributed.parallel_state import (
|
||||
@@ -790,8 +790,8 @@ class QwenImageTransformer2DModel(CachedTransformer):
|
||||
sp_size = get_sequence_parallel_world_size()
|
||||
|
||||
if seq_len % sp_size != 0:
|
||||
# flash_attn, ring_attn, sage_attn do not support attention_mask
|
||||
if get_attn_backend(-1).get_name() != "SDPA" and get_attn_backend(-1).get_name() != "ASCEND":
|
||||
# ring_attn, sage_attn do not support attention_mask
|
||||
if get_attn_backend(-1).get_name() not in _BACKENDS_SUPPORT_ATTENTION_MASK:
|
||||
raise ValueError(
|
||||
f"When generating image shape that the sequence length is NOT divisible by sp_size={sp_size},"
|
||||
f"cannot use {get_attn_backend(-1).get_name()} which does not support attention_mask."
|
||||
|
||||
Reference in new issue
Block a user