[diffusion] Clean up VSA attention hot path (#25514)
This commit is contained in:
+4
-4
@@ -323,8 +323,8 @@ class SparseLinearAttentionImpl(AttentionImpl, nn.Module):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Apply feature maps
|
# Apply feature maps
|
||||||
query = self.feature_map_q(query).contiguous().to(self.dtype) # c_q
|
query = self.feature_map_q(query).to(self.dtype) # c_q
|
||||||
key = self.feature_map_k(key).contiguous().to(self.dtype) # c_k
|
key = self.feature_map_k(key).to(self.dtype) # c_k
|
||||||
# Linear attention computation
|
# Linear attention computation
|
||||||
o_l = self._calc_linear_attention_with_torch(query, key, value)
|
o_l = self._calc_linear_attention_with_torch(query, key, value)
|
||||||
|
|
||||||
@@ -681,8 +681,8 @@ class SageSparseLinearAttentionImpl(AttentionImpl, nn.Module):
|
|||||||
########## SPARGE END ##########
|
########## SPARGE END ##########
|
||||||
|
|
||||||
# Linear attention with feature maps
|
# Linear attention with feature maps
|
||||||
q_linear = self.feature_map_q(q).contiguous().to(self.dtype)
|
q_linear = self.feature_map_q(q).to(self.dtype)
|
||||||
k_linear = self.feature_map_k(k).contiguous().to(self.dtype)
|
k_linear = self.feature_map_k(k).to(self.dtype)
|
||||||
o_l = self._calc_linear_attention_with_torch(q_linear, k_linear, v)
|
o_l = self._calc_linear_attention_with_torch(q_linear, k_linear, v)
|
||||||
|
|
||||||
# Project linear attention output and combine
|
# Project linear attention output and combine
|
||||||
|
|||||||
+28
-30
@@ -159,6 +159,8 @@ class VideoSparseAttentionMetadata(AttentionMetadata):
|
|||||||
reverse_tile_partition_indices: torch.LongTensor
|
reverse_tile_partition_indices: torch.LongTensor
|
||||||
variable_block_sizes: torch.LongTensor
|
variable_block_sizes: torch.LongTensor
|
||||||
non_pad_index: torch.LongTensor
|
non_pad_index: torch.LongTensor
|
||||||
|
untile_combined_index: torch.LongTensor
|
||||||
|
tile_buf: torch.Tensor | None = None
|
||||||
|
|
||||||
# adaption for FastWan2.1-T2V-1.3B-Diffusers
|
# adaption for FastWan2.1-T2V-1.3B-Diffusers
|
||||||
# Sequence lengths for the forward batch
|
# Sequence lengths for the forward batch
|
||||||
@@ -211,6 +213,7 @@ class VideoSparseAttentionMetadataBuilder(AttentionMetadataBuilder):
|
|||||||
non_pad_index = get_non_pad_index(
|
non_pad_index = get_non_pad_index(
|
||||||
variable_block_sizes, math.prod(VSA_TILE_SIZE)
|
variable_block_sizes, math.prod(VSA_TILE_SIZE)
|
||||||
)
|
)
|
||||||
|
untile_combined_index = non_pad_index[reverse_tile_partition_indices]
|
||||||
|
|
||||||
return VideoSparseAttentionMetadata(
|
return VideoSparseAttentionMetadata(
|
||||||
current_timestep=current_timestep,
|
current_timestep=current_timestep,
|
||||||
@@ -222,6 +225,7 @@ class VideoSparseAttentionMetadataBuilder(AttentionMetadataBuilder):
|
|||||||
reverse_tile_partition_indices=reverse_tile_partition_indices,
|
reverse_tile_partition_indices=reverse_tile_partition_indices,
|
||||||
variable_block_sizes=variable_block_sizes,
|
variable_block_sizes=variable_block_sizes,
|
||||||
non_pad_index=non_pad_index,
|
non_pad_index=non_pad_index,
|
||||||
|
untile_combined_index=untile_combined_index,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -244,58 +248,52 @@ class VideoSparseAttentionImpl(AttentionImpl):
|
|||||||
def tile(
|
def tile(
|
||||||
self,
|
self,
|
||||||
x: torch.Tensor,
|
x: torch.Tensor,
|
||||||
num_tiles: list[int],
|
attn_metadata: VideoSparseAttentionMetadata,
|
||||||
tile_partition_indices: torch.LongTensor,
|
|
||||||
non_pad_index: torch.LongTensor,
|
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
|
num_tiles = attn_metadata.num_tiles
|
||||||
t_padded_size = num_tiles[0] * VSA_TILE_SIZE[0]
|
t_padded_size = num_tiles[0] * VSA_TILE_SIZE[0]
|
||||||
h_padded_size = num_tiles[1] * VSA_TILE_SIZE[1]
|
h_padded_size = num_tiles[1] * VSA_TILE_SIZE[1]
|
||||||
w_padded_size = num_tiles[2] * VSA_TILE_SIZE[2]
|
w_padded_size = num_tiles[2] * VSA_TILE_SIZE[2]
|
||||||
|
target_shape = (
|
||||||
x_padded = torch.zeros(
|
x.shape[0],
|
||||||
(
|
t_padded_size * h_padded_size * w_padded_size,
|
||||||
x.shape[0],
|
x.shape[-2],
|
||||||
t_padded_size * h_padded_size * w_padded_size,
|
x.shape[-1],
|
||||||
x.shape[-2],
|
|
||||||
x.shape[-1],
|
|
||||||
),
|
|
||||||
device=x.device,
|
|
||||||
dtype=x.dtype,
|
|
||||||
)
|
)
|
||||||
x_padded[:, non_pad_index] = x[:, tile_partition_indices]
|
|
||||||
return x_padded
|
buf = attn_metadata.tile_buf
|
||||||
|
if (
|
||||||
|
buf is None
|
||||||
|
or buf.shape != target_shape
|
||||||
|
or buf.dtype != x.dtype
|
||||||
|
or buf.device != x.device
|
||||||
|
):
|
||||||
|
buf = torch.zeros(target_shape, device=x.device, dtype=x.dtype)
|
||||||
|
attn_metadata.tile_buf = buf
|
||||||
|
|
||||||
|
buf[:, attn_metadata.non_pad_index] = x[:, attn_metadata.tile_partition_indices]
|
||||||
|
return buf
|
||||||
|
|
||||||
def untile(
|
def untile(
|
||||||
self,
|
self,
|
||||||
x: torch.Tensor,
|
x: torch.Tensor,
|
||||||
reverse_tile_partition_indices: torch.LongTensor,
|
untile_combined_index: torch.LongTensor,
|
||||||
non_pad_index: torch.LongTensor,
|
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
x = x[:, non_pad_index][:, reverse_tile_partition_indices]
|
return x[:, untile_combined_index]
|
||||||
return x
|
|
||||||
|
|
||||||
def preprocess_qkv(
|
def preprocess_qkv(
|
||||||
self,
|
self,
|
||||||
qkv: torch.Tensor,
|
qkv: torch.Tensor,
|
||||||
attn_metadata: VideoSparseAttentionMetadata,
|
attn_metadata: VideoSparseAttentionMetadata,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
return self.tile(
|
return self.tile(qkv, attn_metadata)
|
||||||
qkv,
|
|
||||||
attn_metadata.num_tiles,
|
|
||||||
attn_metadata.tile_partition_indices,
|
|
||||||
attn_metadata.non_pad_index,
|
|
||||||
)
|
|
||||||
|
|
||||||
def postprocess_output(
|
def postprocess_output(
|
||||||
self,
|
self,
|
||||||
output: torch.Tensor,
|
output: torch.Tensor,
|
||||||
attn_metadata: VideoSparseAttentionMetadata,
|
attn_metadata: VideoSparseAttentionMetadata,
|
||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
return self.untile(
|
return self.untile(output, attn_metadata.untile_combined_index)
|
||||||
output,
|
|
||||||
attn_metadata.reverse_tile_partition_indices,
|
|
||||||
attn_metadata.non_pad_index,
|
|
||||||
)
|
|
||||||
|
|
||||||
def forward( # type: ignore[override]
|
def forward( # type: ignore[override]
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -624,6 +624,8 @@ class WanTransformerBlock_VSA(nn.Module):
|
|||||||
added_kv_proj_dim: int | None = None,
|
added_kv_proj_dim: int | None = None,
|
||||||
supported_attention_backends: set[AttentionBackendEnum] | None = None,
|
supported_attention_backends: set[AttentionBackendEnum] | None = None,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
|
attention_type: str = "original",
|
||||||
|
sla_topk: float = 0.0,
|
||||||
quant_config: QuantizationConfig | None = None,
|
quant_config: QuantizationConfig | None = None,
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@@ -221,12 +221,20 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
|||||||
if not backends:
|
if not backends:
|
||||||
return None
|
return None
|
||||||
if len(backends) > 1:
|
if len(backends) > 1:
|
||||||
|
sparse_backends = {backend for backend in backends if backend.is_sparse}
|
||||||
|
selected_backend = (
|
||||||
|
sorted(sparse_backends, key=lambda backend: backend.name)[0]
|
||||||
|
if sparse_backends
|
||||||
|
else sorted(backends, key=lambda backend: backend.name)[0]
|
||||||
|
)
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Multiple transformer attention backends detected: %s. "
|
"Multiple transformer attention backends detected: %s. "
|
||||||
"Using one backend for denoising metadata.",
|
"Using %s for denoising metadata.",
|
||||||
sorted(backend.name.lower() for backend in backends),
|
sorted(backend.name.lower() for backend in backends),
|
||||||
|
selected_backend.name.lower(),
|
||||||
)
|
)
|
||||||
return sorted(backends, key=lambda backend: backend.name)[0]
|
return selected_backend
|
||||||
|
return next(iter(backends))
|
||||||
|
|
||||||
def component_uses(
|
def component_uses(
|
||||||
self, server_args: ServerArgs, stage_name: str | None = None
|
self, server_args: ServerArgs, stage_name: str | None = None
|
||||||
@@ -1474,12 +1482,16 @@ class DenoisingStage(PipelineStage, RolloutDenoisingMixin):
|
|||||||
self.attn_backend.get_enum() == AttentionBackendEnum.SLIDING_TILE_ATTN
|
self.attn_backend.get_enum() == AttentionBackendEnum.SLIDING_TILE_ATTN
|
||||||
or self.attn_backend.get_enum() == AttentionBackendEnum.VIDEO_SPARSE_ATTN
|
or self.attn_backend.get_enum() == AttentionBackendEnum.VIDEO_SPARSE_ATTN
|
||||||
):
|
):
|
||||||
|
attention_backend_config = server_args.attention_backend_config or {}
|
||||||
|
vsa_sparsity = attention_backend_config.get(
|
||||||
|
"VSA_sparsity", attention_backend_config.get("sparsity", 0.0)
|
||||||
|
)
|
||||||
attn_metadata = self.attn_metadata_builder.build(
|
attn_metadata = self.attn_metadata_builder.build(
|
||||||
current_timestep=i,
|
current_timestep=i,
|
||||||
raw_latent_shape=batch.raw_latent_shape[2:5],
|
raw_latent_shape=batch.raw_latent_shape[2:5],
|
||||||
patch_size=server_args.pipeline_config.dit_config.patch_size,
|
patch_size=server_args.pipeline_config.dit_config.patch_size,
|
||||||
STA_param=batch.STA_param,
|
STA_param=batch.STA_param,
|
||||||
VSA_sparsity=server_args.attention_backend_config.VSA_sparsity,
|
VSA_sparsity=vsa_sparsity,
|
||||||
device=get_local_torch_device(),
|
device=get_local_torch_device(),
|
||||||
)
|
)
|
||||||
elif (
|
elif (
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.multimodal_gen.runtime.layers.attention.backends.video_sparse_attn import (
|
||||||
|
VideoSparseAttentionImpl,
|
||||||
|
VideoSparseAttentionMetadataBuilder,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_video_sparse_attention_tile_buffer_reuse_and_untile():
|
||||||
|
metadata = VideoSparseAttentionMetadataBuilder().build(
|
||||||
|
current_timestep=0,
|
||||||
|
raw_latent_shape=(5, 7, 9),
|
||||||
|
patch_size=(1, 1, 1),
|
||||||
|
VSA_sparsity=0.5,
|
||||||
|
device=torch.device("cpu"),
|
||||||
|
)
|
||||||
|
|
||||||
|
impl = object.__new__(VideoSparseAttentionImpl)
|
||||||
|
total_seq_length = metadata.total_seq_length
|
||||||
|
x = torch.arange(2 * total_seq_length * 3 * 4, dtype=torch.float32).reshape(
|
||||||
|
2, total_seq_length, 3, 4
|
||||||
|
)
|
||||||
|
|
||||||
|
tiled = impl.preprocess_qkv(x, metadata)
|
||||||
|
assert metadata.tile_buf is tiled
|
||||||
|
assert torch.equal(
|
||||||
|
metadata.untile_combined_index,
|
||||||
|
metadata.non_pad_index[metadata.reverse_tile_partition_indices],
|
||||||
|
)
|
||||||
|
assert torch.equal(impl.postprocess_output(tiled, metadata), x)
|
||||||
|
|
||||||
|
next_x = x + 1
|
||||||
|
next_tiled = impl.preprocess_qkv(next_x, metadata)
|
||||||
|
assert next_tiled.data_ptr() == tiled.data_ptr()
|
||||||
|
assert torch.equal(impl.postprocess_output(next_tiled, metadata), next_x)
|
||||||
|
|
||||||
|
pad_mask = torch.ones(next_tiled.shape[1], dtype=torch.bool)
|
||||||
|
pad_mask[metadata.non_pad_index.cpu()] = False
|
||||||
|
assert torch.all(next_tiled[:, pad_mask] == 0)
|
||||||
Reference in New Issue
Block a user