refactor: unify rotary embedding interface and update docs

- Merge cos/sin into single freqs_cis tensor [batch, seq, dim/2, 2] throughout the pipeline: RotaryEmbedding buffer, forward return type, apply_rotary_emb signature, CUDA kernel interface
- CUDA kernel now takes freqs_cis directly and reads cos/sin via stride offset internally, eliminating Python-side slice/copy overhead
- Kernel interface: rotary_emb(x, freqs_cis) replaces rotary_emb(x, cos, sin)
- All call sites pass rotary_emb as Tensor (was tuple), type annotations consistent
- Update build threads from 8 to 16
- Fix all docs: get-started, inference, training, cuda_kernels, architecture, internals — reflect new rotary interface, KVCache fields, rotary backend dispatch, .so path, kernel registry count, file layout
This commit is contained in:
2026-07-31 16:52:25 +08:00
parent 75411ce0cc
commit 7aa5ed09d9
11 changed files with 120 additions and 83 deletions
+12 -11
View File
@@ -1,17 +1,16 @@
"""Rotary embedding with auto-dispatch to CUDA kernel.
Single entry point ``apply_rotary_emb(x, cos, sin)`` — uses the fused
Single entry point ``apply_rotary_emb(x, freqs_cis)`` — uses the fused
CUDA kernel when available, falls back to torch complex multiply otherwise.
Layout: x is [batch, seq_len, n_heads, head_dim] (bf16).
cos/sin are [batch, seq_len, head_dim/2] (f32).
freqs_cis is [batch, seq_len, dim/2, 2] (f32) — [cos, sin] pairs.
"""
import torch
from torch import Tensor
from astrai.extension.loader import is_available
from astrai.extension.rotary_ops import rotary_emb as _cuda_rotary
_cache = {"available": None}
@@ -22,32 +21,34 @@ def _cuda_available() -> bool:
return _cache["available"]
def _torch_apply(x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
def _torch_apply(x: Tensor, freqs_cis: Tensor) -> Tensor:
cos, sin = freqs_cis[..., 0], freqs_cis[..., 1]
dtype = x.dtype
x_ = x.float().reshape(*x.shape[:-1], -1, 2)
x_complex = torch.view_as_complex(x_)
freqs_cis = torch.complex(cos, sin).unsqueeze(2)
x_rotated = x_complex * freqs_cis
freqs_cis_complex = torch.complex(cos, sin).unsqueeze(2)
x_rotated = x_complex * freqs_cis_complex
x_out = torch.view_as_real(x_rotated).flatten(-2)
return x_out.to(dtype)
def apply_rotary_emb(x: Tensor, rotary_emb: tuple[Tensor, Tensor]) -> Tensor:
def apply_rotary_emb(x: Tensor, freqs_cis: Tensor) -> Tensor:
"""Apply rotary embedding to x.
Args:
x: [batch, seq_len, n_heads, head_dim] (bf16)
rotary_emb: (cos, sin) tuple, each [batch, seq_len, head_dim/2] (f32)
freqs_cis: [batch, seq_len, dim/2, 2] (f32) — [cos, sin] pairs
Returns:
[batch, seq_len, n_heads, head_dim] (bf16)
"""
cos, sin = rotary_emb
if (
_cuda_available()
and not torch.is_grad_enabled()
and x.is_cuda
and x.dtype == torch.bfloat16
):
return _cuda_rotary(x, cos, sin)
return _torch_apply(x, cos, sin)
from astrai.extension.rotary_ops import rotary_emb as _cuda_rotary
return _cuda_rotary(x, freqs_cis)
return _torch_apply(x, freqs_cis)