refactor: extension dispatch layer with CUDA/torch fallback

- Add gqa_decode_attn/gqa_prefill_attn dispatch functions
- Internal _available/__modules with underscore prefix
- CUDA kernel path with F.scaled_dot_product_attention fallback
- GQA head expansion in fallback path
This commit is contained in:
ViperEkura 2026-07-06 21:07:16 +08:00
parent f1cc7cedce
commit 53ed52b4b8
2 changed files with 82 additions and 5 deletions

View File

@ -16,7 +16,6 @@ from astrai.dataset import (
Store, Store,
StoreFactory, StoreFactory,
) )
from astrai.extension import available
from astrai.factory import BaseFactory from astrai.factory import BaseFactory
from astrai.inference import ( from astrai.inference import (
GenerationRequest, GenerationRequest,

View File

@ -1,13 +1,91 @@
import importlib import importlib
import logging import logging
import torch
import torch.nn.functional as F
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
available: dict[str, bool] = {} _available: dict[str, bool] = {}
_modules: dict[str, object] = {}
for _name in ["gqa_decode_attn", "gqa_prefill_attn"]: for _name in ["gqa_decode_attn", "gqa_prefill_attn"]:
try: try:
importlib.import_module(f".{_name}", package=__package__) _mod = importlib.import_module(f".{_name}", package=__package__)
available[_name] = True _available[_name] = True
_modules[_name] = _mod
except ImportError: except ImportError:
available[_name] = False _available[_name] = False
_modules[_name] = None
def _expand_kv_heads(
k: torch.Tensor, v: torch.Tensor, q_head: int
) -> tuple[torch.Tensor, torch.Tensor]:
"""Expand K/V heads to match Q heads for GQA fallback."""
kv_head = k.size(1)
if kv_head == q_head:
return k, v
group = q_head // kv_head
k = k.repeat_interleave(group, dim=1)
v = v.repeat_interleave(group, dim=1)
return k, v
def _torch_fallback(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
mask: torch.Tensor | None,
is_causal: bool,
scale: float | None,
) -> torch.Tensor:
k, v = _expand_kv_heads(k, v, q.size(1))
attn_mask = mask[:, None, None, :] if mask is not None else None
return F.scaled_dot_product_attention(
q, k, v, attn_mask=attn_mask, is_causal=is_causal and mask is None, scale=scale
)
def gqa_decode_attn(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
mask: torch.Tensor | None = None,
is_causal: bool = False,
causal_offset: int = 0,
scale: float | None = None,
) -> torch.Tensor:
if _available["gqa_decode_attn"]:
return _modules["gqa_decode_attn"].gqa_decode_attn(
q,
k,
v,
mask=mask,
is_causal=is_causal,
causal_offset=causal_offset,
scale=scale,
)
return _torch_fallback(q, k, v, mask, is_causal, scale)
def gqa_prefill_attn(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
mask: torch.Tensor | None = None,
is_causal: bool = False,
causal_offset: int = 0,
scale: float | None = None,
) -> torch.Tensor:
if _available["gqa_prefill_attn"]:
return _modules["gqa_prefill_attn"].gqa_prefill_attn(
q,
k,
v,
mask=mask,
is_causal=is_causal,
causal_offset=causal_offset,
scale=scale,
)
return _torch_fallback(q, k, v, mask, is_causal, scale)