"""Norm-bounded algebraic graph reads around an otherwise frozen LM.""" from __future__ import annotations from dataclasses import dataclass import torch from torch import nn from torch.nn import functional as F from strata.modeling.algebra import BoundedRelativeReZero @dataclass(frozen=True, slots=True) class ComposeLMOutput: logits: torch.Tensor loss: torch.Tensor | None hidden_states: torch.Tensor graph_update: torch.Tensor relative_update_rms: torch.Tensor @dataclass(frozen=True, slots=True) class SparseAlgebraGraphRead: """CSR-like graph values addressed to selected batch/token positions.""" values: torch.Tensor batch_indices: torch.Tensor token_indices: torch.Tensor reliability: torch.Tensor def validate(self, hidden: torch.Tensor, graph_dim: int) -> None: count = self.values.shape[0] if self.values.ndim != 2 or self.values.shape[1] != graph_dim: raise ValueError("sparse graph values must have shape [reads, graph_dim]") if self.batch_indices.shape != (count,) or self.token_indices.shape != (count,): raise ValueError("sparse graph indices must have shape [reads]") if self.reliability.shape != (count,): raise ValueError("sparse graph reliability must have shape [reads]") if count and ( int(self.batch_indices.min()) < 0 or int(self.batch_indices.max()) >= hidden.shape[0] or int(self.token_indices.min()) < 0 or int(self.token_indices.max()) >= hidden.shape[1] ): raise ValueError("sparse graph index is out of range") class AlgebraGraphReadAdapter(nn.Module): """Project exact graph values and inject them through the fixed BRR bound.""" def __init__(self, graph_dim: int, hidden_dim: int, *, gamma_max: float = 0.02) -> None: super().__init__() self.graph_projection = nn.Linear(graph_dim, hidden_dim, bias=False) self.brr = BoundedRelativeReZero(hidden_dim, gamma_max=gamma_max, per_channel=False) def forward( self, hidden: torch.Tensor, graph_values: torch.Tensor, reliability: torch.Tensor, ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: if graph_values.shape[:-1] != hidden.shape[:-1]: raise ValueError("graph values must align with token hidden states") if reliability.shape != hidden.shape[:-1]: raise ValueError("reliability must have shape [batch, sequence]") active = reliability != 0 if not bool(active.any()): return hidden, torch.zeros_like(hidden), hidden.new_zeros(hidden.shape[:-1]) projection_dtype = self.graph_projection.weight.dtype if bool(active.all()): read = self.graph_projection(graph_values.to(projection_dtype)).to(hidden.dtype) output = self.brr(hidden, read, reliability) relative = (output.update_rms / output.hidden_rms.clamp_min(1e-8)).squeeze(-1) return output.hidden, output.update, relative active_hidden = hidden[active] active_read = self.graph_projection(graph_values[active].to(projection_dtype)).to(hidden.dtype) active_output = self.brr(active_hidden, active_read, reliability[active]) updated = hidden.clone() updated[active] = active_output.hidden update = torch.zeros_like(hidden) update[active] = active_output.update relative = hidden.new_zeros(hidden.shape[:-1]) relative[active] = ( active_output.update_rms / active_output.hidden_rms.clamp_min(1e-8) ).squeeze(-1) return updated, update, relative def forward_sparse( self, hidden: torch.Tensor, read: SparseAlgebraGraphRead, ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: read.validate(hidden, self.graph_projection.in_features) if read.values.shape[0] == 0: return hidden, torch.zeros_like(hidden), hidden.new_zeros(hidden.shape[:-1]) batch = read.batch_indices.to(device=hidden.device, dtype=torch.long) token = read.token_indices.to(device=hidden.device, dtype=torch.long) selected_hidden = hidden[batch, token] projection_dtype = self.graph_projection.weight.dtype selected_read = self.graph_projection( read.values.to(device=hidden.device, dtype=projection_dtype) ).to(hidden.dtype) selected_output = self.brr( selected_hidden, selected_read, read.reliability.to(device=hidden.device, dtype=hidden.dtype), ) updated = hidden.clone() updated[batch, token] = selected_output.hidden update = torch.zeros_like(hidden) update[batch, token] = selected_output.update relative = hidden.new_zeros(hidden.shape[:-1]) relative[batch, token] = ( selected_output.update_rms / selected_output.hidden_rms.clamp_min(1e-8) ).squeeze(-1) return updated, update, relative class FrozenLMWithAlgebraRead(nn.Module): """Attach graph reads after a frozen decoder without changing its base path. The wrapped model must expose ``forward_hidden``, ``final_norm``, and ``lm_head``. When graph reads are disabled, the adapter is not called. """ def __init__(self, base_model: nn.Module, adapter: AlgebraGraphReadAdapter) -> None: super().__init__() self.base_model = base_model self.adapter = adapter for parameter in self.base_model.parameters(): parameter.requires_grad_(False) def forward( self, input_ids: torch.Tensor, *, attention_mask: torch.Tensor | None = None, labels: torch.Tensor | None = None, graph_values: torch.Tensor | None = None, graph_reliability: torch.Tensor | None = None, graph_enabled: bool = False, ) -> ComposeLMOutput: hidden, _mask = self.base_model.forward_hidden(input_ids, attention_mask=attention_mask) update = torch.zeros_like(hidden) relative = hidden.new_zeros(hidden.shape[:-1]) if graph_enabled: if graph_values is None or graph_reliability is None: raise ValueError("enabled graph reads require values and reliability") hidden, update, relative = self.adapter(hidden, graph_values, graph_reliability) normalized = self.base_model.final_norm(hidden) logits = self.base_model.lm_head(normalized) loss = None if labels is not None: loss = F.cross_entropy( logits[:, :-1].contiguous().view(-1, logits.shape[-1]), labels[:, 1:].contiguous().view(-1), ignore_index=-100, ) return ComposeLMOutput(logits, loss, normalized, update, relative) __all__ = [ "AlgebraGraphReadAdapter", "ComposeLMOutput", "FrozenLMWithAlgebraRead", "SparseAlgebraGraphRead", ]