Signed-off-by: Didan Deng <33117903+wtomin@users.noreply.github.com> Signed-off-by: zjy0516 <riverclouds.zhu@qq.com> Co-authored-by: Jiangyun Zhu <riverclouds.zhu@qq.com>
136 lines
5.5 KiB
Python
136 lines
5.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
import torch
|
|
import torch.distributed as dist
|
|
|
|
from vllm_omni.diffusion.attention.backends.abstract import AttentionMetadata
|
|
from vllm_omni.diffusion.attention.parallel.base import ParallelAttentionContext
|
|
from vllm_omni.diffusion.distributed.comm import SeqAllToAll4D
|
|
from vllm_omni.diffusion.distributed.group_coordinator import SequenceParallelGroupCoordinator
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class _UlyssesCtx(ParallelAttentionContext):
|
|
"""Per-forward context for Ulysses sequence-parallel attention."""
|
|
|
|
ulysses_pg: dist.ProcessGroup
|
|
scatter_idx: int
|
|
gather_idx: int
|
|
use_sync: bool
|
|
|
|
|
|
class UlyssesParallelAttention:
|
|
"""Ulysses sequence-parallel strategy (all-to-all over seq/head dims).
|
|
|
|
This preserves the semantics previously implemented in
|
|
`Attention._forward_ulysses`:
|
|
- If `AttentionMetadata.joint_*` is provided, joint_query is concatenated to
|
|
query *before* all-to-all; joint_key/value are concatenated *after* all-to-all.
|
|
- joint_key/value are assumed to be replicated across SP ranks and are sliced
|
|
by ulysses head rank before concatenation.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
sp_group: SequenceParallelGroupCoordinator,
|
|
scatter_idx: int,
|
|
gather_idx: int,
|
|
use_sync: bool,
|
|
) -> None:
|
|
self._sp_group = sp_group
|
|
self._ulysses_pg = sp_group.ulysses_group
|
|
self._scatter_idx = scatter_idx
|
|
self._gather_idx = gather_idx
|
|
self._use_sync = use_sync
|
|
|
|
@property
|
|
def enabled(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return "ulysses"
|
|
|
|
def pre_attention(
|
|
self,
|
|
query: torch.Tensor,
|
|
key: torch.Tensor,
|
|
value: torch.Tensor,
|
|
attn_metadata: AttentionMetadata | None,
|
|
):
|
|
joint_tensor_query = joint_tensor_key = joint_tensor_value = None
|
|
joint_strategy = None
|
|
|
|
if attn_metadata is not None:
|
|
joint_tensor_query = attn_metadata.joint_query
|
|
joint_tensor_key = attn_metadata.joint_key
|
|
joint_tensor_value = attn_metadata.joint_value
|
|
joint_strategy = attn_metadata.joint_strategy
|
|
|
|
is_joint = False
|
|
if joint_tensor_query is not None and joint_tensor_key is not None and joint_tensor_value 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."
|
|
f" supported joint strategy: {supported_joint_strategy}"
|
|
)
|
|
if joint_strategy == "rear":
|
|
query = torch.cat([query, joint_tensor_query], dim=1)
|
|
else:
|
|
query = torch.cat([joint_tensor_query, query], dim=1)
|
|
is_joint = True
|
|
elif joint_tensor_query is None and joint_tensor_key is None and joint_tensor_value is None:
|
|
pass
|
|
else:
|
|
raise ValueError("joint_query, joint_key, and joint_value should be None or not None simultaneously.")
|
|
|
|
if is_joint:
|
|
# Slice joint key/value heads for this ulysses rank.
|
|
ulysses_world_size = self._sp_group.ulysses_world_size
|
|
ulysses_rank = self._sp_group.ulysses_rank
|
|
attn_heads_per_ulysses_rank = joint_tensor_key.shape[-2] // ulysses_world_size
|
|
joint_tensor_key = joint_tensor_key[
|
|
...,
|
|
attn_heads_per_ulysses_rank * ulysses_rank : attn_heads_per_ulysses_rank * (ulysses_rank + 1),
|
|
:,
|
|
]
|
|
joint_tensor_value = joint_tensor_value[
|
|
...,
|
|
attn_heads_per_ulysses_rank * ulysses_rank : attn_heads_per_ulysses_rank * (ulysses_rank + 1),
|
|
:,
|
|
]
|
|
|
|
# (bs, seq_len/P, head_cnt, head_size) -> (bs, seq_len, head_cnt/P, head_size)
|
|
query = SeqAllToAll4D.apply(self._ulysses_pg, query, self._scatter_idx, self._gather_idx, self._use_sync)
|
|
key = SeqAllToAll4D.apply(self._ulysses_pg, key, self._scatter_idx, self._gather_idx, self._use_sync)
|
|
value = SeqAllToAll4D.apply(self._ulysses_pg, value, self._scatter_idx, self._gather_idx, self._use_sync)
|
|
|
|
if is_joint:
|
|
# Concatenate joint key/value after all-to-all (matches previous implementation).
|
|
if joint_strategy == "front":
|
|
key = torch.cat([joint_tensor_key, key], dim=1)
|
|
value = torch.cat([joint_tensor_value, value], dim=1)
|
|
else: # "rear"
|
|
key = torch.cat([key, joint_tensor_key], dim=1)
|
|
value = torch.cat([value, joint_tensor_value], dim=1)
|
|
|
|
ctx = _UlyssesCtx(
|
|
name=self.name,
|
|
ulysses_pg=self._ulysses_pg,
|
|
scatter_idx=self._scatter_idx,
|
|
gather_idx=self._gather_idx,
|
|
use_sync=self._use_sync,
|
|
)
|
|
return query, key, value, attn_metadata, ctx
|
|
|
|
def post_attention(self, attn_output: torch.Tensor, ctx: ParallelAttentionContext | None) -> torch.Tensor:
|
|
assert isinstance(ctx, _UlyssesCtx), f"Unexpected ctx type: {type(ctx)!r}"
|
|
# Reverse: (bs, seq_len, head_cnt/P, head_size) -> (bs, seq_len/P, head_cnt, head_size)
|
|
return SeqAllToAll4D.apply(ctx.ulysses_pg, attn_output, ctx.gather_idx, ctx.scatter_idx, ctx.use_sync)
|