Download tiny_gdn/kdn/recurrence.py from KerzgrAI/Rondo-Exp: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/KerzgrAI/Rondo-Exp/resolve/main/tiny_gdn/kdn/recurrence.py
- Command line
-
hf download hf://KerzgrAI/Rondo-Exp/tiny_gdn/kdn/recurrence.py
-
curl -L -o recurrence.py https://huggingface.co/KerzgrAI/Rondo-Exp/resolve/main/tiny_gdn/kdn/recurrence.py
10.1 kB
| """Exact PyTorch Diagonal KDN recurrences that carry state across calls. | |
| The Triton kernels cover stateless fixed-length training. These functions | |
| take and return ``(memory, precision)`` so prefill, decode, and padded batches | |
| continue a sequence exactly; the sequential form is also the test oracle. | |
| Per head, with one covariance coordinate ``p`` per key channel:: | |
| p_hat_t = alpha_t**2 * p_{t-1} + omega_t | |
| kappa_t = p_hat_t * k_t / (r_t + sum_i p_hat_ti * k_ti**2) | |
| 1 / p_t = 1 / p_hat_t + info_scale * k_t**2 / r_t | |
| S_t = (I - kappa_t k_t^T) diag(alpha_t) S_{t-1} + kappa_t v_t^T | |
| o_t = scale * S_t^T q_t | |
| Shapes: ``q, k, log_alpha, omega`` are ``[B, T, H, K]``, ``v`` is | |
| ``[B, T, H, V]``, ``r`` is ``[B, T, H]``, ``memory`` is ``[B, H, K, V]`` and | |
| ``precision`` (``1 / p``) is ``[B, H, K]``. All math runs in float32. | |
| """ | |
| from __future__ import annotations | |
| import math | |
| import torch | |
| import torch.nn.functional as F | |
| OMEGA_MIN = 0.0 | |
| R_MIN = 0.01 | |
| INITIAL_PRECISION = 1.0 | |
| INITIAL_PRECISION_FLOOR = 0.1 | |
| LOG_MEMORY_ALPHA_FLOOR = math.log(1e-6) | |
| NORM_EPS = 1e-12 | |
| DEFAULT_CHUNK_SIZE = 64 | |
| def initial_precision_logit() -> float: | |
| """Raw parameter value whose floored softplus equals ``INITIAL_PRECISION``.""" | |
| return math.log(math.expm1(INITIAL_PRECISION - INITIAL_PRECISION_FLOOR)) | |
| def _work(tensor: torch.Tensor) -> torch.Tensor: | |
| return tensor.to(torch.promote_types(tensor.dtype, torch.float32)) | |
| def kalman_noise( | |
| retention_raw: torch.Tensor, | |
| omega_raw: torch.Tensor, | |
| r_raw: torch.Tensor, | |
| A_log: torch.Tensor, | |
| dt_bias: torch.Tensor, | |
| initial_precision_param: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Return ``(log_alpha, omega, r, initial_precision)`` like the Triton frontend.""" | |
| heads, key_dim = retention_raw.shape[-2:] | |
| rate = _work(A_log).exp().view(heads, 1) | |
| log_alpha = -rate * F.softplus( | |
| _work(retention_raw) + _work(dt_bias).view(heads, key_dim) | |
| ) | |
| omega = OMEGA_MIN + F.softplus(_work(omega_raw)) | |
| r = R_MIN + F.softplus(_work(r_raw)) | |
| initial_precision = INITIAL_PRECISION_FLOOR + F.softplus( | |
| _work(initial_precision_param) | |
| ) | |
| return log_alpha, omega, r, initial_precision | |
| def normalize_query_key( | |
| q: torch.Tensor, k: torch.Tensor | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| return ( | |
| F.normalize(_work(q), dim=-1, eps=NORM_EPS), | |
| F.normalize(_work(k), dim=-1, eps=NORM_EPS), | |
| ) | |
| def initial_state( | |
| initial_precision: torch.Tensor, | |
| *, | |
| batch_size: int, | |
| key_dim: int, | |
| value_dim: int, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Zero memory and the learned per-head prior precision for new sequences.""" | |
| heads = initial_precision.shape[0] | |
| prior = _work(initial_precision) | |
| memory = prior.new_zeros((batch_size, heads, key_dim, value_dim)) | |
| precision = prior.view(1, heads, 1).expand(batch_size, heads, key_dim).contiguous() | |
| return memory, precision | |
| def mask_padding( | |
| token_mask: torch.Tensor, | |
| k: torch.Tensor, | |
| v: torch.Tensor, | |
| log_alpha: torch.Tensor, | |
| omega: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Turn padded positions into identity steps that leave the state untouched.""" | |
| keep = token_mask[:, :, None, None] | |
| return ( | |
| torch.where(keep, k, 0.0), | |
| torch.where(keep, v, 0.0), | |
| torch.where(keep, log_alpha, 0.0), | |
| torch.where(keep, omega, 0.0), | |
| ) | |
| def diag_kdn_step( | |
| q: torch.Tensor, | |
| k: torch.Tensor, | |
| v: torch.Tensor, | |
| log_alpha: torch.Tensor, | |
| omega: torch.Tensor, | |
| r: torch.Tensor, | |
| memory: torch.Tensor, | |
| precision: torch.Tensor, | |
| *, | |
| scale: float, | |
| info_scale: float, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Advance one token: ``q, k, log_alpha, omega`` are ``[B, H, K]``.""" | |
| k_squared = k.square() | |
| predicted = (2.0 * log_alpha).exp() / precision + omega | |
| noise = r[..., None] | |
| gain = predicted * k / (noise + (predicted * k_squared).sum(-1, keepdim=True)) | |
| precision = predicted.reciprocal() + info_scale * k_squared / noise | |
| decayed = memory * log_alpha.clamp_min(LOG_MEMORY_ALPHA_FLOOR).exp()[..., None] | |
| innovation = v - torch.einsum("bhk,bhkv->bhv", k, decayed) | |
| memory = decayed + gain[..., None] * innovation[..., None, :] | |
| output = scale * torch.einsum("bhk,bhkv->bhv", q, memory) | |
| return output, memory, precision | |
| def diag_kdn_recurrent( | |
| q: torch.Tensor, | |
| k: torch.Tensor, | |
| v: torch.Tensor, | |
| log_alpha: torch.Tensor, | |
| omega: torch.Tensor, | |
| r: torch.Tensor, | |
| memory: torch.Tensor, | |
| precision: torch.Tensor, | |
| *, | |
| scale: float, | |
| info_scale: float, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Token-by-token reference; returns ``(output [B,T,H,V], memory, precision)``.""" | |
| outputs = [] | |
| for token in range(q.shape[1]): | |
| output, memory, precision = diag_kdn_step( | |
| q[:, token], | |
| k[:, token], | |
| v[:, token], | |
| log_alpha[:, token], | |
| omega[:, token], | |
| r[:, token], | |
| memory, | |
| precision, | |
| scale=scale, | |
| info_scale=info_scale, | |
| ) | |
| outputs.append(output) | |
| return torch.stack(outputs, dim=1), memory, precision | |
| def _compose( | |
| later: tuple[torch.Tensor, ...], earlier: tuple[torch.Tensor, ...] | |
| ) -> tuple[torch.Tensor, ...]: | |
| """Product ``later @ earlier`` of 2x2 maps stored as (m00, m01, m10, m11).""" | |
| a00, a01, a10, a11 = later | |
| b00, b01, b10, b11 = earlier | |
| product = ( | |
| a00 * b00 + a01 * b10, | |
| a00 * b01 + a01 * b11, | |
| a10 * b00 + a11 * b10, | |
| a10 * b01 + a11 * b11, | |
| ) | |
| # Mobius maps are invariant to scaling, so renormalize to stay in range. | |
| magnitude = torch.stack(product).abs().amax(dim=0).clamp_min( | |
| torch.finfo(product[0].dtype).tiny | |
| ) | |
| return tuple(entry / magnitude for entry in product) | |
| def _inclusive_scan(maps: tuple[torch.Tensor, ...]) -> tuple[torch.Tensor, ...]: | |
| """Hillis-Steele prefix products ``M_t @ ... @ M_1`` along dim 1.""" | |
| length = maps[0].shape[1] | |
| offset = 1 | |
| while offset < length: | |
| combined = _compose( | |
| tuple(entry[:, offset:] for entry in maps), | |
| tuple(entry[:, :-offset] for entry in maps), | |
| ) | |
| maps = tuple( | |
| torch.cat((entry[:, :offset], update), dim=1) | |
| for entry, update in zip(maps, combined) | |
| ) | |
| offset *= 2 | |
| return maps | |
| def kalman_gain( | |
| k: torch.Tensor, | |
| log_alpha: torch.Tensor, | |
| omega: torch.Tensor, | |
| r: torch.Tensor, | |
| precision: torch.Tensor, | |
| *, | |
| info_scale: float, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Vector gains for every token via a parallel Mobius scan, plus final precision.""" | |
| k_squared = k.square() | |
| noise = r[..., None] | |
| alpha_squared = (2.0 * log_alpha).exp() | |
| information = info_scale * k_squared / noise | |
| maps = _inclusive_scan( | |
| ( | |
| alpha_squared, | |
| omega, | |
| information * alpha_squared, | |
| 1.0 + information * omega, | |
| ) | |
| ) | |
| start = precision.reciprocal()[:, None] | |
| covariance = (maps[0] * start + maps[1]) / (maps[2] * start + maps[3]) | |
| previous = torch.cat((start, covariance[:, :-1]), dim=1) | |
| predicted = alpha_squared * previous + omega | |
| gain = predicted * k / (noise + (predicted * k_squared).sum(-1, keepdim=True)) | |
| return gain, covariance[:, -1].reciprocal() | |
| def chunked_memory( | |
| q: torch.Tensor, | |
| k: torch.Tensor, | |
| v: torch.Tensor, | |
| log_alpha: torch.Tensor, | |
| gain: torch.Tensor, | |
| memory: torch.Tensor, | |
| *, | |
| scale: float, | |
| chunk_size: int = DEFAULT_CHUNK_SIZE, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Two-key delta-rule memory in WY form, one triangular solve per chunk.""" | |
| if chunk_size <= 0: | |
| raise ValueError("chunk_size must be positive") | |
| log_alpha = log_alpha.clamp_min(LOG_MEMORY_ALPHA_FLOOR) | |
| length = q.shape[1] | |
| outputs = [] | |
| for start in range(0, length, chunk_size): | |
| stop = min(start + chunk_size, length) | |
| size = stop - start | |
| q_chunk, k_chunk, v_chunk, gain_chunk = ( | |
| tensor[:, start:stop].transpose(1, 2) for tensor in (q, k, v, gain) | |
| ) | |
| cumulative = log_alpha[:, start:stop].transpose(1, 2).cumsum(dim=2) | |
| causal = torch.ones(size, size, dtype=torch.bool, device=q.device).tril() | |
| differences = cumulative[:, :, :, None, :] - cumulative[:, :, None, :, :] | |
| weights = ( | |
| torch.where(causal[:, :, None], differences, -math.inf).exp() | |
| * gain_chunk[:, :, None, :, :] | |
| ) | |
| interaction = torch.einsum("bhtk,bhtsk->bhts", k_chunk, weights) | |
| readout = torch.einsum("bhtk,bhtsk->bhts", q_chunk, weights) | |
| decay = cumulative.exp() | |
| corrected = torch.linalg.solve_triangular( | |
| interaction, | |
| v_chunk - (decay * k_chunk) @ memory, | |
| upper=False, | |
| unitriangular=True, | |
| ) | |
| outputs.append( | |
| (scale * ((q_chunk * decay) @ memory + readout @ corrected)).transpose(1, 2) | |
| ) | |
| to_end = (cumulative[:, :, -1:] - cumulative).exp() * gain_chunk | |
| memory = memory * decay[:, :, -1, :, None] + to_end.transpose(-1, -2) @ corrected | |
| return torch.cat(outputs, dim=1), memory | |
| def diag_kdn_chunked( | |
| q: torch.Tensor, | |
| k: torch.Tensor, | |
| v: torch.Tensor, | |
| log_alpha: torch.Tensor, | |
| omega: torch.Tensor, | |
| r: torch.Tensor, | |
| memory: torch.Tensor, | |
| precision: torch.Tensor, | |
| *, | |
| scale: float, | |
| info_scale: float, | |
| chunk_size: int = DEFAULT_CHUNK_SIZE, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Parallel form of :func:`diag_kdn_recurrent` with the same signature.""" | |
| gain, precision = kalman_gain( | |
| k, log_alpha, omega, r, precision, info_scale=info_scale | |
| ) | |
| output, memory = chunked_memory( | |
| q, k, v, log_alpha, gain, memory, scale=scale, chunk_size=chunk_size | |
| ) | |
| return output, memory, precision | |