Files
vllm-omni/vllm_omni/diffusion/attention/parallel/ring.py
T

269 lines
9.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
import torch
from vllm.logger import init_logger
# import torch.distributed as dist # Not used directly here, but good practice if needed
from vllm_omni.diffusion.attention.backends.ring.ring_globals import (
FA3_SUPPORTED_CUDA_MAJORS,
HAS_AITER,
HAS_FA3,
HAS_FA4,
HAS_FLASH_ATTN,
)
from vllm_omni.diffusion.attention.backends.ring.ring_selector import AttnType
from vllm_omni.diffusion.attention.parallel.base import (
ParallelAttentionContext,
# ParallelAttentionStrategy, # Not used in type hint below currently
)
from vllm_omni.diffusion.distributed.group_coordinator import SequenceParallelGroupCoordinator
if TYPE_CHECKING:
from vllm_omni.diffusion.attention.backends.abstract import AttentionMetadata
logger = init_logger(__name__)
def _can_use_fa3(device: torch.device) -> bool:
"""Return whether the installed FA3 kernels support ``device``.
Importing an extension only proves that its Python module is installed.
When the source publishes a supported-major contract, also check it before
entering a CUDA launcher that may abort instead of raising an exception.
"""
if not HAS_FA3 or device.type != "cuda":
return False
if FA3_SUPPORTED_CUDA_MAJORS is None:
return True
major, _minor = torch.cuda.get_device_capability(device)
return major in FA3_SUPPORTED_CUDA_MAJORS
def _can_use_fa4(device: torch.device) -> bool:
"""Return whether the installed FA4 kernels support ``device``."""
if not HAS_FA4 or device.type != "cuda":
return False
major, _minor = torch.cuda.get_device_capability(device)
return major >= 10
def _can_use_fa2(device: torch.device) -> bool:
"""Return whether the FA2 backend supports ``device``.
FA2's CUDA backend supports Ampere, Ada, and Hopper. Blackwell support is
provided by the separate FA4 implementation.
"""
if not HAS_FLASH_ATTN or device.type != "cuda":
return False
major, _minor = torch.cuda.get_device_capability(device)
return major in (8, 9)
@dataclass(frozen=True, slots=True)
class _RingCtx(ParallelAttentionContext):
"""Per-forward context for Ring sequence-parallel attention."""
# Ring attention typically doesn't need complex context for post-processing
# as the output is already correctly sharded along sequence dimension.
pass
class RingParallelAttention:
"""Ring sequence-parallel strategy.
This strategy prepares inputs for Ring Attention.
Key responsibilities:
- Concatenate joint_query (Text) to query (Image) if present.
- Keep joint_key/value separate in metadata for the Ring kernel to handle as static prefix.
"""
def __init__(
self,
sp_group: SequenceParallelGroupCoordinator,
attn_backend_pref: str | None = None,
attn_backend_explicit: bool = False,
) -> None:
self._sp_group = sp_group
self.attn_backend_pref = attn_backend_pref
self.attn_backend_explicit = attn_backend_explicit
@property
def enabled(self) -> bool:
return True
@property
def name(self) -> str:
return "ring"
def pre_attention(
self,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attn_metadata: AttentionMetadata | None,
):
joint_tensor_query = None
joint_strategy = "front"
if attn_metadata is not None:
joint_tensor_query = attn_metadata.joint_query
joint_strategy = attn_metadata.joint_strategy
if joint_tensor_query is not None:
supported_joint_strategy = ["front", "rear"]
if joint_strategy not in supported_joint_strategy:
raise ValueError(f"joint_strategy: {joint_strategy} not supported.")
if joint_strategy == "front":
query = torch.cat([joint_tensor_query, query], dim=1)
else:
query = torch.cat([query, joint_tensor_query], dim=1)
# Note: We do NOT concatenate joint_key/value here.
# They are preserved in attn_metadata and will be passed
# explicitly to ring_flash_attn_func.
ctx = _RingCtx(name=self.name)
return query, key, value, attn_metadata, ctx
def post_attention(self, attn_output: torch.Tensor, ctx: ParallelAttentionContext | None) -> torch.Tensor:
# Ring attention output is already sharded correctly along sequence dimension.
return attn_output
def run_attention(
self,
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
attn_metadata: AttentionMetadata | None,
softmax_scale: float | None = None,
causal: bool = False,
) -> torch.Tensor:
"""Run the actual Ring Attention kernel."""
if softmax_scale is None:
softmax_scale = query.shape[-1] ** -0.5
backend_pref = self.attn_backend_pref
if backend_pref is not None:
backend_pref = backend_pref.lower()
# Extract joint tensors
joint_key, joint_value = None, None
joint_strategy = "front"
valid_kv_length = None
if attn_metadata is not None:
joint_key = attn_metadata.joint_key
joint_value = attn_metadata.joint_value
valid_kv_length = attn_metadata.extra.get("valid_kv_length")
if attn_metadata.joint_strategy is not None:
joint_strategy = attn_metadata.joint_strategy
def _run_sdpa() -> torch.Tensor:
from vllm_omni.diffusion.attention.backends.ring_pytorch_attn import ring_pytorch_attn_func
return ring_pytorch_attn_func(
query,
key,
value,
softmax_scale=softmax_scale,
causal=causal,
group=self._sp_group.ring_group,
op_type="efficient",
joint_tensor_key=joint_key,
joint_tensor_value=joint_value,
joint_strategy=joint_strategy,
valid_kv_length=valid_kv_length,
)
# Ring only implements local Flash/AITER and SDPA kernels. HuggingFace
# Hub FA and other non-local backends can use the SDPA ring path only
# when they came from automatic platform selection. An explicit
# request must never silently substitute local FA4/FA3/FA2.
_sdpa_prefs = {
"sdpa",
"torch",
"torch_sdpa",
}
_non_ring_prefs = {
"cudnn_attn",
"flashinfer_attn",
"trtllm_attn",
"sage_attn",
"sage_attn_3",
"flash_attn_hub",
"flash_attn_3_hub",
}
if backend_pref in _sdpa_prefs:
return _run_sdpa()
if backend_pref in _non_ring_prefs:
if self.attn_backend_explicit:
raise ValueError(
f"{self.attn_backend_pref} was explicitly selected, but ring sequence parallelism "
"has no implementation for that backend. Select TORCH_SDPA/FLASH_ATTN or use Ulysses SP."
)
return _run_sdpa()
if query.dtype == torch.float32:
if self.attn_backend_explicit:
raise ValueError(
f"{self.attn_backend_pref} was explicitly selected for ring attention, "
"but its ring kernel does not support float32. Select TORCH_SDPA or use a supported dtype."
)
return _run_sdpa()
can_use_fa4 = _can_use_fa4(query.device)
can_use_fa3 = _can_use_fa3(query.device)
can_use_fa2 = _can_use_fa2(query.device)
if not can_use_fa4 and not can_use_fa3 and not can_use_fa2 and not HAS_AITER:
if self.attn_backend_explicit:
raise RuntimeError(
f"{self.attn_backend_pref} was explicitly selected, but no compatible ring kernel "
f"is available for device {query.device}."
)
logger.warning_once(
"Automatic ring backend selection chose TORCH_SDPA because no compatible "
"FA2/FA3/FA4/AITER ring kernel is available for this device."
)
return _run_sdpa()
from vllm_omni.diffusion.attention.backends.ring_flash_attn import ring_flash_attn_func
# Prefer FA4 on Blackwell. An importable Hopper-only FA3 wheel can
# otherwise be selected and fail at launch with "no kernel image".
if can_use_fa4:
attn_type = AttnType.FA4
# Prefer FA3 over FA2 on Ampere/Ada/Hopper. On ROCm, use AITER.
elif can_use_fa3:
attn_type = AttnType.FA3
elif HAS_AITER:
attn_type = AttnType.AITER
elif can_use_fa2:
attn_type = AttnType.FA
else:
raise RuntimeError("No compatible Flash Attention backend is available for ring attention.")
return ring_flash_attn_func(
query,
key,
value,
dropout_p=0.0,
softmax_scale=softmax_scale,
causal=causal,
window_size=(-1, -1),
softcap=0.0,
alibi_slopes=None,
deterministic=False,
group=self._sp_group.ring_group,
attn_type=attn_type,
joint_tensor_key=joint_key,
joint_tensor_value=joint_value,
joint_strategy=joint_strategy,
valid_kv_length=valid_kv_length,
)