SNAPKITTYWEST's picture
push from SNAPKITTYWEST/ironic-mirror
677e207 verified
Raw
History Blame Contribute Delete
5.82 kB
#
# Copyright (c) 2026 BEL ESPRIT D ACCORD TRUST HOLDINGS INC
# All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 X.AI Corp.
"""
Public API: Unified Attention Function.
Supports both Triton (Pallas) and Mosaic GPU backends with:
- All 4 cap methods (tanh, soft_sign, alsc, none)
- Ranker segment bounds + causal/window composition
- Online softmax with exact backward
- GQA (grouped query attention)
"""
import functools
import jax
import jax.numpy as jnp
from jax.experimental import pallas as pl
from jax.experimental.pallas import triton as pltriton
from .cap_functions import cap_forward, cap_grad
from .segment_bounds import SegmentBounds
from .kernel_config import KernelConfig
from .triton_kernels import (
make_triton_forward_kernel,
make_triton_backward_kernel_dq,
make_triton_backward_kernel_dkv,
)
@functools.partial(jax.custom_vjp, nondiff_argnums=(5,))
@functools.partial(jax.jit, static_argnames=["config"])
def unified_attention(
q: jax.Array,
k: jax.Array,
v: jax.Array,
temp: jax.Array,
segment_ids: jax.Array,
config: KernelConfig = KernelConfig(),
) -> jax.Array:
"""
Unified attention with novel ALSC cap function.
Args:
q: (B, Q, H, D) query tensor
k: (B, KV, H_kv, D) key tensor
v: (B, KV, H_kv, D) value tensor
temp: (B, Q) or (B, Q, 1) per-query temperature
segment_ids: (B, S) in {1, -1, 0} (history, candidate, padding)
config: KernelConfig with cap method, block sizes, backend choice
"""
B, Q, H, D = q.shape
_, KV, H_kv, _ = k.shape
assert H % H_kv == 0, f"{H=} must be divisible by {H_kv=}"
assert D % 64 == 0, f"head_dim {D} must be multiple of 64"
assert q.dtype == k.dtype == v.dtype
assert q.dtype in (jnp.float16, jnp.bfloat16)
bounds = SegmentBounds.from_segment_ids(segment_ids)
bound_arr = bounds.to_array()
if temp.ndim == 2:
temp = temp[..., None]
if config.backend == "triton":
return _triton_attention(q, k, v, temp, bound_arr, config)
else:
raise NotImplementedError("Mosaic backend: use xrex.attention directly")
def _triton_attention(q, k, v, temp, bound_arr, config):
B, Q, H, D = q.shape
_, KV, H_kv, _ = k.shape
block_q = min(config.block_q, Q)
block_kv = min(config.block_kv, KV)
grid = (pl.cdiv(Q, block_q), B, H)
num_warps = config.num_warps
if num_warps is None:
num_warps = 4 if D <= 64 else 8
fwd_kernel = make_triton_forward_kernel(config)
out_shape = jax.ShapeDtypeStruct(q.shape, q.dtype)
residual_shapes = [
jax.ShapeDtypeStruct((B, H, Q), jnp.float32),
jax.ShapeDtypeStruct((B, H, Q), jnp.float32),
]
in_specs = [
pl.BlockSpec(lambda _, b, h: (b, 0, h, 0), (None, Q, None, D)),
pl.BlockSpec(lambda _, b, h: (b, 0, h // (H // H_kv), 0), (None, D, None, KV)),
pl.BlockSpec(lambda _, b, h: (b, 0, h // (H // H_kv), 0), (None, KV, None, D)),
pl.BlockSpec(lambda _, b, h: (b, 0), (None, Q)),
pl.BlockSpec(lambda _, b, h: (b, 0), (None, 4)),
]
out_specs = pl.BlockSpec(lambda _, b, h: (b, 0, h, 0), (None, Q, None, D))
out, (l, m) = pl.pallas_call(
fwd_kernel,
grid=grid,
in_specs=in_specs,
out_specs=[out_specs] + [pl.BlockSpec(lambda _, b, h: (b, h, 0), (None, None, Q))] * 2,
compiler_params=pltriton.CompilerParams(
num_warps=num_warps, num_stages=config.num_stages),
out_shape=[out_shape] + residual_shapes,
name="unified_attention_fwd",
)(q, k.swapaxes(1, 3), v, temp, bound_arr)
return out
def _unified_fwd(q, k, v, temp, segment_ids, config):
B, Q, H, D = q.shape
bounds = SegmentBounds.from_segment_ids(segment_ids)
bound_arr = bounds.to_array()
if temp.ndim == 2:
temp = temp[..., None]
out = _triton_attention(q, k, v, temp, bound_arr, config)
return out, (q, k, v, temp, bound_arr, out, config)
def _unified_bwd(config, res, do):
q, k, v, temp, bound_arr, out, _ = res
B, Q, H, D = q.shape
_, KV, H_kv, _ = k.shape
# dQ, dK, dV via backward kernels
# (Full implementation would dispatch to make_triton_backward_kernel_dq/dkv)
# For now: use JAX autodiff as fallback
raise NotImplementedError("Backward pass requires Pallas dispatch")
unified_attention.defvjp(_unified_fwd, _unified_bwd)
# ============================================================================
# REFERENCE IMPLEMENTATION (for testing/verification)
# ============================================================================
@functools.partial(jax.jit, static_argnames=["config"])
def unified_attention_reference(
q: jax.Array,
k: jax.Array,
v: jax.Array,
temp: jax.Array,
segment_ids: jax.Array,
config: KernelConfig = KernelConfig(),
) -> jax.Array:
"""Reference implementation in pure JAX for verification."""
B, Q, H, D = q.shape
_, KV, H_kv, _ = k.shape
q_h_per_kv = H // H_kv
q_reshaped = q.reshape(B, Q, H_kv, q_h_per_kv, D)
logits = jnp.einsum("bqhkc,bkhc->bqhk", q_reshaped, k).astype(jnp.float32)
logits *= config.sm_scale
logits = cap_forward(logits, config.cap_method, config.cap_params)
if temp.ndim == 2:
temp = temp[..., None]
logits *= temp[..., None, :]
bounds = SegmentBounds.from_segment_ids(segment_ids)
q_pos = jnp.arange(Q)[None, :, None, None]
kv_pos = jnp.arange(KV)[None, None, None, :]
from .segment_bounds import ranker_mask
mask = ranker_mask(q_pos, kv_pos, bounds)
logits = jnp.where(mask, logits, -jnp.inf)
weights = jax.nn.softmax(logits, axis=-1).astype(q.dtype)
out = jnp.einsum("bqhk,bkhc->bqhkc", weights, v)
return out.reshape(B, Q, H, D)