195 lines
5.6 KiB
Python
195 lines
5.6 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Adapted from vllm: https://github.com/vllm-project/vllm/blob/v0.7.3/vllm/attention/backends/abstract.py
|
|
|
|
import re
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field, fields
|
|
from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar
|
|
|
|
if TYPE_CHECKING:
|
|
pass
|
|
|
|
import torch
|
|
|
|
_LAYER_IDX_RE = re.compile(r"blocks\.(\d+)")
|
|
|
|
|
|
def layer_idx_from_prefix(prefix: str, default: int | None = None) -> int:
|
|
"""Parse the transformer-block index out of a layer prefix.
|
|
|
|
Shared by backends that key per-layer behavior off the block number
|
|
(vmoba, VSA-H3). Raises when unparsable unless ``default`` is given.
|
|
"""
|
|
match = _LAYER_IDX_RE.search(prefix)
|
|
if match:
|
|
return int(match.group(1))
|
|
if default is None:
|
|
raise ValueError(f"cannot parse a layer index from attention prefix {prefix!r}")
|
|
return default
|
|
|
|
|
|
class AttentionBackend(ABC):
|
|
"""Abstract class for attention backends."""
|
|
# For some attention backends, we allocate an output tensor before
|
|
# calling the custom op. When piecewise cudagraph is enabled, this
|
|
# makes sure the output tensor is allocated inside the cudagraph.
|
|
accept_output_buffer: bool = False
|
|
|
|
@staticmethod
|
|
@abstractmethod
|
|
def get_name() -> str:
|
|
raise NotImplementedError
|
|
|
|
@staticmethod
|
|
@abstractmethod
|
|
def get_impl_cls() -> type["AttentionImpl"]:
|
|
raise NotImplementedError
|
|
|
|
@staticmethod
|
|
@abstractmethod
|
|
def get_metadata_cls() -> type["AttentionMetadata"]:
|
|
raise NotImplementedError
|
|
|
|
# @staticmethod
|
|
# @abstractmethod
|
|
# def get_state_cls() -> Type["AttentionState"]:
|
|
# raise NotImplementedError
|
|
|
|
# @classmethod
|
|
# def make_metadata(cls, *args, **kwargs) -> "AttentionMetadata":
|
|
# return cls.get_metadata_cls()(*args, **kwargs)
|
|
|
|
@staticmethod
|
|
@abstractmethod
|
|
def get_builder_cls() -> type["AttentionMetadataBuilder"]:
|
|
raise NotImplementedError
|
|
|
|
|
|
@dataclass
|
|
class AttentionMetadata:
|
|
"""Attention metadata for prefill and decode batched together."""
|
|
# Current step of diffusion process
|
|
current_timestep: int
|
|
VSA_sparsity: float = field(default=0.0, kw_only=True)
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
|
|
|
|
def asdict_zerocopy(self, skip_fields: set[str] | None = None) -> dict[str, Any]:
|
|
"""Similar to dataclasses.asdict, but avoids deepcopying."""
|
|
if skip_fields is None:
|
|
skip_fields = set()
|
|
# Note that if we add dataclasses as fields, they will need
|
|
# similar handling.
|
|
return {field.name: getattr(self, field.name) for field in fields(self) if field.name not in skip_fields}
|
|
|
|
|
|
T = TypeVar("T", bound=AttentionMetadata)
|
|
|
|
|
|
class AttentionMetadataBuilder(ABC, Generic[T]):
|
|
"""Abstract class for attention metadata builders."""
|
|
|
|
@abstractmethod
|
|
def __init__(self) -> None:
|
|
"""Create the builder, remember some configuration and parameters."""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def prepare(self) -> None:
|
|
"""Prepare for one batch."""
|
|
raise NotImplementedError
|
|
|
|
@abstractmethod
|
|
def build(
|
|
self,
|
|
**kwargs: Any,
|
|
) -> AttentionMetadata:
|
|
"""Build attention metadata with on-device tensors."""
|
|
raise NotImplementedError
|
|
|
|
|
|
class AttentionLayer(Protocol):
|
|
|
|
_k_scale: torch.Tensor
|
|
_v_scale: torch.Tensor
|
|
_k_scale_float: float
|
|
_v_scale_float: float
|
|
|
|
def forward(
|
|
self,
|
|
query: torch.Tensor,
|
|
key: torch.Tensor,
|
|
value: torch.Tensor,
|
|
kv_cache: torch.Tensor,
|
|
attn_metadata: AttentionMetadata,
|
|
) -> torch.Tensor:
|
|
...
|
|
|
|
|
|
class AttentionImpl(ABC, Generic[T]):
|
|
|
|
@abstractmethod
|
|
def __init__(
|
|
self,
|
|
num_heads: int,
|
|
head_size: int,
|
|
softmax_scale: float,
|
|
causal: bool = False,
|
|
num_kv_heads: int | None = None,
|
|
prefix: str = "",
|
|
**extra_impl_args,
|
|
) -> None:
|
|
raise NotImplementedError
|
|
|
|
def preprocess_qkv(self, qkv: torch.Tensor, attn_metadata: T) -> torch.Tensor:
|
|
"""Preprocess QKV tensor before performing attention operation.
|
|
|
|
Default implementation returns the tensor unchanged.
|
|
Subclasses can override this to implement custom preprocessing
|
|
like reshaping, tiling, scaling, or other transformations.
|
|
|
|
Called AFTER all_to_all for distributed attention
|
|
|
|
Args:
|
|
qkv: The query-key-value tensor
|
|
attn_metadata: Metadata for the attention operation
|
|
|
|
Returns:
|
|
Processed QKV tensor
|
|
"""
|
|
return qkv
|
|
|
|
def postprocess_output(
|
|
self,
|
|
output: torch.Tensor,
|
|
attn_metadata: T,
|
|
) -> torch.Tensor:
|
|
"""Postprocess the output tensor after the attention operation.
|
|
|
|
Default implementation returns the tensor unchanged.
|
|
Subclasses can override this to implement custom postprocessing
|
|
like untiling, scaling, or other transformations.
|
|
|
|
Called BEFORE all_to_all for distributed attention
|
|
|
|
Args:
|
|
output: The output tensor from the attention operation
|
|
attn_metadata: Metadata for the attention operation
|
|
|
|
Returns:
|
|
Postprocessed output tensor
|
|
"""
|
|
|
|
return output
|
|
|
|
@abstractmethod
|
|
def forward(
|
|
self,
|
|
query: torch.Tensor,
|
|
key: torch.Tensor,
|
|
value: torch.Tensor,
|
|
attn_metadata: T,
|
|
) -> torch.Tensor:
|
|
raise NotImplementedError
|