burt-imma / python /burt_imma /kernels.py
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/burt-imma
b88c26d verified
Raw
History Blame Contribute Delete
8.63 kB
"""
BURT-IMMA Kernel Functions with CPU Fallbacks
License: BSL-1.1
Contact: jessica@collectivekitty.com
For each kernel function: try CUDA version first, fall back to pure PyTorch.
"""
import torch
import torch.nn.functional as F
from typing import Optional
try:
import _burt_imma_cuda
_HAS_CUDA = True
except ImportError:
_HAS_CUDA = False
def constrained_softmax(
logits: torch.Tensor,
max_entropy: float = 0.20,
temperature: float = 1.0,
) -> torch.Tensor:
"""Softmax with spectral norm constraint (entropy bounded).
Applies softmax along the last dimension, iteratively sharpening
(reducing temperature) until entropy <= max_entropy.
Args:
logits: Input logits of any shape (..., N)
max_entropy: Maximum allowed entropy of output distribution
temperature: Initial temperature for scaling
Returns:
Probability distribution with entropy <= max_entropy
"""
if _HAS_CUDA and logits.is_cuda:
return _burt_imma_cuda.constrained_softmax(logits, max_entropy, temperature)
# Pure PyTorch fallback
scaled = logits / temperature
probs = F.softmax(scaled, dim=-1)
# Check entropy and sharpen if needed
ent = -(probs * (probs + 1e-10).log()).sum(dim=-1)
violations = ent > max_entropy
if violations.any():
temp = temperature
for _ in range(50):
temp *= 0.8
p = F.softmax(logits / temp, dim=-1)
h = -(p * (p + 1e-10).log()).sum(dim=-1)
if (h <= max_entropy).all():
return p
# Update only violating entries
probs = torch.where(
violations.unsqueeze(-1).expand_as(probs),
p, probs
)
ent = -(probs * (probs + 1e-10).log()).sum(dim=-1)
violations = ent > max_entropy
if not violations.any():
break
return probs
def cifg_update(
C_old: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
forget_bias: float = 0.0,
) -> torch.Tensor:
"""Coupled Input-Forget Gate memory update.
Implements: C_new = f * C_old + (1-f) * candidate
where candidate = normalized outer product of key and value,
and f = sigmoid(||key|| + forget_bias).
Args:
C_old: (batch, d_mem, d_mem) - current memory matrix
key: (batch, d_mem) - write key
value: (batch, d_mem) - write value
forget_bias: bias term for forget gate
Returns:
C_new: (batch, d_mem, d_mem) - updated memory matrix
"""
if _HAS_CUDA and C_old.is_cuda:
return _burt_imma_cuda.cifg_update(C_old, key, value, forget_bias)
# Pure PyTorch fallback
# Forget gate from key norm
f = torch.sigmoid(key.norm(dim=-1, keepdim=True) + forget_bias) # (batch, 1)
# Outer product candidate
candidate = torch.bmm(
key.unsqueeze(-1), # (batch, d_mem, 1)
value.unsqueeze(-2) # (batch, 1, d_mem)
) # (batch, d_mem, d_mem)
# Normalize candidate
cand_norm = candidate.flatten(1).norm(dim=1, keepdim=True).unsqueeze(-1) + 1e-8
candidate = candidate / cand_norm
# CIFG update
f_exp = f.unsqueeze(-1) # (batch, 1, 1)
C_new = f_exp * C_old + (1.0 - f_exp) * candidate
return C_new
def batched_cifg_update(
C_old: torch.Tensor,
keys: torch.Tensor,
values: torch.Tensor,
forget_biases: torch.Tensor,
) -> torch.Tensor:
"""Batched CIFG update for multiple memory slots.
Args:
C_old: (batch, num_slots, d_mem, d_mem)
keys: (batch, num_slots, d_mem)
values: (batch, num_slots, d_mem)
forget_biases: (num_slots,)
Returns:
C_new: (batch, num_slots, d_mem, d_mem)
"""
if _HAS_CUDA and C_old.is_cuda:
return _burt_imma_cuda.batched_cifg_update(C_old, keys, values, forget_biases)
# Pure PyTorch fallback
batch, num_slots, d_mem, _ = C_old.shape
C_new = torch.zeros_like(C_old)
for s in range(num_slots):
C_slot = C_old[:, s] # (batch, d_mem, d_mem)
k_slot = keys[:, s] # (batch, d_mem)
v_slot = values[:, s] # (batch, d_mem)
bias = forget_biases[s].item()
C_new[:, s] = cifg_update(C_slot, k_slot, v_slot, bias)
return C_new
def sparse_moe_dispatch(
x: torch.Tensor,
gate_weights: torch.Tensor,
expert_weights: torch.Tensor,
top_k: int = 1,
) -> torch.Tensor:
"""Sparse Mixture-of-Experts dispatch with top-k routing.
Routes each token to its top-k experts based on gating scores.
Args:
x: (batch, seq_len, d_model) - input tokens
gate_weights: (d_model, num_experts) - gating projection
expert_weights: (num_experts, d_model, d_model) - expert matrices
top_k: number of experts per token
Returns:
output: (batch, seq_len, d_model) - routed output
"""
if _HAS_CUDA and x.is_cuda:
return _burt_imma_cuda.sparse_moe_dispatch(x, gate_weights, expert_weights, top_k)
# Pure PyTorch fallback
batch, seq_len, d_model = x.shape
num_experts = gate_weights.shape[1]
# Flatten
x_flat = x.reshape(batch * seq_len, d_model)
# Gating scores
scores = torch.mm(x_flat, gate_weights) # (B*S, num_experts)
probs = F.softmax(scores, dim=-1)
# Top-k
top_vals, top_idx = torch.topk(probs, top_k, dim=-1)
top_vals = top_vals / (top_vals.sum(dim=-1, keepdim=True) + 1e-8)
# Dispatch
output = torch.zeros_like(x_flat)
for k in range(top_k):
for e in range(num_experts):
mask = (top_idx[:, k] == e)
if mask.any():
x_masked = x_flat[mask]
W_e = expert_weights[e]
out_e = torch.mm(x_masked, W_e)
output[mask] = output[mask] + top_vals[mask, k:k+1] * out_e
return output.reshape(batch, seq_len, d_model)
def biencoder_attention(
query: torch.Tensor,
key: torch.Tensor,
value: torch.Tensor,
W_Q: torch.Tensor,
W_K: torch.Tensor,
W_V: torch.Tensor,
) -> torch.Tensor:
"""Bi-encoder cross-attention between two sequences.
Args:
query: (batch, q_len, d_model) - query sequence
key: (batch, kv_len, d_model) - key sequence
value: (batch, kv_len, d_model) - value sequence
W_Q: (d_model, d_model) - query projection
W_K: (d_model, d_model) - key projection
W_V: (d_model, d_model) - value projection
Returns:
output: (batch, q_len, d_model)
"""
if _HAS_CUDA and query.is_cuda:
return _burt_imma_cuda.biencoder_attention(query, key, value, W_Q, W_K, W_V)
# Pure PyTorch fallback
batch, q_len, d_model = query.shape
kv_len = key.shape[1]
scale = d_model ** 0.5
# Project
Q = torch.matmul(query, W_Q) # (batch, q_len, d_model)
K = torch.matmul(key, W_K) # (batch, kv_len, d_model)
V = torch.matmul(value, W_V) # (batch, kv_len, d_model)
# Attention scores
scores = torch.bmm(Q, K.transpose(-2, -1)) / scale # (batch, q_len, kv_len)
attn = F.softmax(scores, dim=-1)
output = torch.bmm(attn, V) # (batch, q_len, d_model)
return output
def attention_softmax(
Q: torch.Tensor,
K: torch.Tensor,
V: torch.Tensor,
causal: bool = True,
) -> torch.Tensor:
"""Fused attention + softmax with optional causal mask.
Computes scaled dot-product attention efficiently.
Args:
Q: (batch, heads, seq_len, d_head)
K: (batch, heads, seq_len, d_head)
V: (batch, heads, seq_len, d_head)
causal: whether to apply causal (autoregressive) mask
Returns:
output: (batch, heads, seq_len, d_head)
"""
if _HAS_CUDA and Q.is_cuda:
return _burt_imma_cuda.attention_softmax(Q, K, V, causal)
# Pure PyTorch fallback
d_head = Q.shape[-1]
seq_len = Q.shape[-2]
scale = d_head ** 0.5
# Compute scores
scores = torch.matmul(Q, K.transpose(-2, -1)) / scale
# Apply causal mask
if causal:
mask = torch.triu(
torch.full((seq_len, seq_len), float("-inf"), device=Q.device, dtype=Q.dtype),
diagonal=1
)
scores = scores + mask
# Softmax + weighted sum
attn = F.softmax(scores, dim=-1)
return torch.matmul(attn, V)