| """
|
| 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)
|
|
|
|
|
| scaled = logits / temperature
|
| probs = F.softmax(scaled, dim=-1)
|
|
|
|
|
| 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
|
|
|
| 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)
|
|
|
|
|
|
|
| f = torch.sigmoid(key.norm(dim=-1, keepdim=True) + forget_bias)
|
|
|
|
|
| candidate = torch.bmm(
|
| key.unsqueeze(-1),
|
| value.unsqueeze(-2)
|
| )
|
|
|
|
|
| cand_norm = candidate.flatten(1).norm(dim=1, keepdim=True).unsqueeze(-1) + 1e-8
|
| candidate = candidate / cand_norm
|
|
|
|
|
| f_exp = f.unsqueeze(-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)
|
|
|
|
|
| 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]
|
| k_slot = keys[:, s]
|
| v_slot = values[:, s]
|
| 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)
|
|
|
|
|
| batch, seq_len, d_model = x.shape
|
| num_experts = gate_weights.shape[1]
|
|
|
|
|
| x_flat = x.reshape(batch * seq_len, d_model)
|
|
|
|
|
| scores = torch.mm(x_flat, gate_weights)
|
| probs = F.softmax(scores, dim=-1)
|
|
|
|
|
| top_vals, top_idx = torch.topk(probs, top_k, dim=-1)
|
| top_vals = top_vals / (top_vals.sum(dim=-1, keepdim=True) + 1e-8)
|
|
|
|
|
| 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)
|
|
|
|
|
| batch, q_len, d_model = query.shape
|
| kv_len = key.shape[1]
|
| scale = d_model ** 0.5
|
|
|
|
|
| Q = torch.matmul(query, W_Q)
|
| K = torch.matmul(key, W_K)
|
| V = torch.matmul(value, W_V)
|
|
|
|
|
| scores = torch.bmm(Q, K.transpose(-2, -1)) / scale
|
| attn = F.softmax(scores, dim=-1)
|
| output = torch.bmm(attn, V)
|
|
|
| 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)
|
|
|
|
|
| d_head = Q.shape[-1]
|
| seq_len = Q.shape[-2]
|
| scale = d_head ** 0.5
|
|
|
|
|
| scores = torch.matmul(Q, K.transpose(-2, -1)) / scale
|
|
|
|
|
| if causal:
|
| mask = torch.triu(
|
| torch.full((seq_len, seq_len), float("-inf"), device=Q.device, dtype=Q.dtype),
|
| diagonal=1
|
| )
|
| scores = scores + mask
|
|
|
|
|
| attn = F.softmax(scores, dim=-1)
|
| return torch.matmul(attn, V)
|
|
|