| """ |
| CogNet1B: Non-Transformer Language Model with Cognitive Routing |
| ================================================================ |
| Replaces self-attention with O(n) cognitive routing and |
| hierarchical memory, enabling linear-time sequence processing. |
| |
| Key architectural innovations: |
| - CognitiveChannel: Depthwise separable conv + SwiGLU FFN (O(n) per channel) |
| - CoherenceRouter: O(n) routing via learned coherence scoring |
| - SharedHierarchicalMemory: 3-tier key-value memory (Working/Episodic/Semantic) |
| - AdaptiveComputationBlock: Variable-depth processing per token |
| - CompositionalReasoner: Hyperdimensional computing for role-filler binding |
| """ |
|
|
| import math |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| from typing import Dict, Optional, Tuple |
|
|
|
|
| |
|
|
| class TokenEncoder(nn.Module): |
| """Token embedding + learned positional encoding.""" |
|
|
| def __init__(self, vocab_size: int, hidden_dim: int, max_seq_len: int, dropout: float = 0.1): |
| super().__init__() |
| self.token_emb = nn.Embedding(vocab_size, hidden_dim) |
| self.pos_emb = nn.Embedding(max_seq_len, hidden_dim) |
| self.dropout = nn.Dropout(dropout) |
| self.norm = nn.LayerNorm(hidden_dim) |
| self._init_weights() |
|
|
| def _init_weights(self): |
| nn.init.normal_(self.token_emb.weight, mean=0.0, std=0.02) |
| nn.init.normal_(self.pos_emb.weight, mean=0.0, std=0.02) |
|
|
| def forward(self, input_ids: torch.Tensor) -> torch.Tensor: |
| B, T = input_ids.shape |
| positions = torch.arange(T, device=input_ids.device).unsqueeze(0).expand(B, -1) |
| x = self.token_emb(input_ids) + self.pos_emb(positions) |
| return self.dropout(self.norm(x)) |
|
|
|
|
| |
|
|
| class CognitiveChannel(nn.Module): |
| """Depthwise separable convolution + SwiGLU FFN β O(n) per channel.""" |
|
|
| def __init__(self, channel_dim: int, ff_dim: int, dropout: float = 0.1): |
| super().__init__() |
| |
| self.dw_conv = nn.Conv1d( |
| channel_dim, channel_dim, kernel_size=3, padding=1, |
| groups=channel_dim |
| ) |
| self.pw_conv = nn.Conv1d(channel_dim, channel_dim, kernel_size=1) |
| self.conv_norm = nn.LayerNorm(channel_dim) |
| self.conv_dropout = nn.Dropout(dropout) |
|
|
| |
| self.ff_gate = nn.Linear(channel_dim, ff_dim, bias=False) |
| self.ff_up = nn.Linear(channel_dim, ff_dim, bias=False) |
| self.ff_down = nn.Linear(ff_dim, channel_dim, bias=False) |
| self.ff_norm = nn.LayerNorm(channel_dim) |
| self.ff_dropout = nn.Dropout(dropout) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| |
| residual = x |
| |
| h = x.transpose(1, 2) |
| h = self.dw_conv(h) |
| h = self.pw_conv(h) |
| h = h.transpose(1, 2) |
| h = self.conv_norm(h) |
| x = residual + self.conv_dropout(h) |
|
|
| |
| residual = x |
| gate = F.silu(self.ff_gate(x)) |
| up = self.ff_up(x) |
| h = gate * up |
| h = self.ff_down(h) |
| h = self.ff_norm(h) |
| x = residual + self.ff_dropout(h) |
| return x |
|
|
|
|
| |
|
|
| class CoherenceRouter(nn.Module): |
| """O(n) routing: compute which channel handles each token.""" |
|
|
| def __init__(self, hidden_dim: int, num_channels: int, routing_iters: int = 1): |
| super().__init__() |
| self.num_channels = num_channels |
| self.routing_iters = routing_iters |
| self.query = nn.Linear(hidden_dim, num_channels, bias=False) |
| self.key = nn.Linear(hidden_dim, num_channels, bias=False) |
| self.refine = nn.Linear(num_channels, num_channels, bias=False) |
|
|
| def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: |
| """ |
| Args: |
| x: (B, T, D) |
| Returns: |
| routing_weights: (B, T, num_channels) β soft assignment |
| channel_masks: (B, T, num_channels) β hard top-k for efficiency |
| """ |
| B, T, D = x.shape |
| q = self.query(x) |
| k = self.key(x) |
|
|
| |
| mean_key = k.mean(dim=1, keepdim=True) |
| scores = q * mean_key |
| scores = scores + self.refine(scores) * 0.1 |
| routing_weights = F.softmax(scores, dim=-1) |
|
|
| |
| _, top_idx = routing_weights.topk(2, dim=-1) |
| channel_masks = torch.zeros_like(routing_weights) |
| channel_masks.scatter_(-1, top_idx, 1.0) |
|
|
| return routing_weights, channel_masks |
|
|
|
|
| |
|
|
| class SharedHierarchicalMemory(nn.Module): |
| """3-tier memory: Working β Episodic β Semantic with key-value attention.""" |
|
|
| def __init__(self, hidden_dim: int, key_dim: int, |
| working_slots: int, episodic_slots: int, semantic_slots: int, |
| dropout: float = 0.1): |
| super().__init__() |
| self.key_dim = key_dim |
| self.working_slots = working_slots |
| self.episodic_slots = episodic_slots |
| self.semantic_slots = semantic_slots |
|
|
| |
| self.q_proj = nn.Linear(hidden_dim, key_dim, bias=False) |
| self.k_proj = nn.Linear(hidden_dim, key_dim, bias=False) |
| self.v_proj = nn.Linear(hidden_dim, hidden_dim, bias=False) |
| self.out_proj = nn.Linear(hidden_dim, hidden_dim, bias=False) |
|
|
| |
| self.working_keys = nn.Parameter(torch.randn(working_slots, key_dim) * 0.02) |
| self.working_vals = nn.Parameter(torch.randn(working_slots, hidden_dim) * 0.02) |
| self.episodic_keys = nn.Parameter(torch.randn(episodic_slots, key_dim) * 0.02) |
| self.episodic_vals = nn.Parameter(torch.randn(episodic_slots, hidden_dim) * 0.02) |
| self.semantic_keys = nn.Parameter(torch.randn(semantic_slots, key_dim) * 0.02) |
| self.semantic_vals = nn.Parameter(torch.randn(semantic_slots, hidden_dim) * 0.02) |
|
|
| |
| self.tier_gate = nn.Linear(hidden_dim * 3, 3, bias=False) |
| self.norm = nn.LayerNorm(hidden_dim) |
| self.dropout = nn.Dropout(dropout) |
|
|
| def _read_tier(self, queries: torch.Tensor, keys: torch.Tensor, |
| values: torch.Tensor) -> torch.Tensor: |
| """ |
| Read from one memory tier. |
| queries: (B, T, key_dim) |
| keys: (S, key_dim) |
| values: (S, hidden_dim) |
| Returns: (B, T, hidden_dim) |
| """ |
| B = queries.shape[0] |
| |
| keys_expanded = keys.unsqueeze(0).expand(B, -1, -1) |
| values_expanded = values.unsqueeze(0).expand(B, -1, -1) |
|
|
| |
| scale = math.sqrt(self.key_dim) |
| |
| attn = torch.bmm(queries, keys_expanded.transpose(1, 2)) / scale |
| attn = F.softmax(attn, dim=-1) |
| |
| out = torch.bmm(attn, values_expanded) |
| return out |
|
|
| def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Dict[str, torch.Tensor]]: |
| B, T, D = x.shape |
| queries = self.q_proj(x) |
|
|
| |
| w_out = self._read_tier(queries, self.working_keys, self.working_vals) |
| e_out = self._read_tier(queries, self.episodic_keys, self.episodic_vals) |
| s_out = self._read_tier(queries, self.semantic_keys, self.semantic_vals) |
|
|
| |
| gate_input = torch.cat([w_out, e_out, s_out], dim=-1) |
| gates = F.softmax(self.tier_gate(gate_input), dim=-1) |
| combined = (gates[..., 0:1] * w_out + |
| gates[..., 1:2] * e_out + |
| gates[..., 2:3] * s_out) |
|
|
| |
| out = self.out_proj(self.v_proj(x) + combined) |
| out = self.norm(out) |
| x = x + self.dropout(out) |
|
|
| stats = { |
| 'mem_w_gate': gates[..., 0].mean(), |
| 'mem_e_gate': gates[..., 1].mean(), |
| 'mem_s_gate': gates[..., 2].mean(), |
| } |
| return x, stats |
|
|
|
|
| |
|
|
| class GatedFFN(nn.Module): |
| """SwiGLU feed-forward network.""" |
|
|
| def __init__(self, hidden_dim: int, ff_dim: int, dropout: float = 0.1): |
| super().__init__() |
| self.gate_proj = nn.Linear(hidden_dim, ff_dim, bias=False) |
| self.up_proj = nn.Linear(hidden_dim, ff_dim, bias=False) |
| self.down_proj = nn.Linear(ff_dim, hidden_dim, bias=False) |
| self.norm = nn.LayerNorm(hidden_dim) |
| self.dropout = nn.Dropout(dropout) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| residual = x |
| gate = F.silu(self.gate_proj(x)) |
| up = self.up_proj(x) |
| h = gate * up |
| h = self.down_proj(h) |
| h = self.norm(h) |
| return residual + self.dropout(h) |
|
|
|
|
| |
|
|
| class AdaptiveComputationBlock(nn.Module): |
| """Variable-depth processing: each token may take 1..max_adaptive_steps.""" |
|
|
| def __init__(self, hidden_dim: int, ff_dim: int, max_adaptive_steps: int, |
| dropout: float = 0.1): |
| super().__init__() |
| self.max_steps = max_adaptive_steps |
| self.layers = nn.ModuleList([ |
| GatedFFN(hidden_dim, ff_dim, dropout) for _ in range(max_adaptive_steps) |
| ]) |
| self.halt_prob = nn.Linear(hidden_dim, 1, bias=False) |
| self.norm = nn.LayerNorm(hidden_dim) |
|
|
| def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Dict[str, torch.Tensor]]: |
| B, T, D = x.shape |
| output = torch.zeros_like(x) |
| total_weight = torch.zeros(B, T, 1, device=x.device) |
|
|
| stats = {'avg_steps': torch.tensor(0.0, device=x.device)} |
|
|
| for step_idx in range(self.max_steps): |
| x = self.layers[step_idx](x) |
|
|
| |
| p = torch.sigmoid(self.halt_prob(x)) |
|
|
| |
| remaining = 1.0 - total_weight |
| |
| steps_left = self.max_steps - step_idx |
| min_remaining = 0.01 * max(steps_left - 1, 0) |
| max_val = torch.clamp(remaining - min_remaining, min=0.01) |
| p = torch.clamp(p, min=torch.tensor(0.01, device=x.device), max=max_val) |
|
|
| |
| if step_idx == self.max_steps - 1: |
| p = torch.clamp(remaining, min=0.01) |
|
|
| output = output + p * x |
| total_weight = total_weight + p |
|
|
| output = self.norm(output) |
|
|
| avg_steps = torch.tensor(float(self.max_steps), device=x.device) |
| stats['avg_steps'] = avg_steps |
| return output, stats |
|
|
|
|
| |
|
|
| class CompositionalReasoner(nn.Module): |
| """Hyperdimensional computing for role-filler binding.""" |
|
|
| def __init__(self, hidden_dim: int, key_dim: int, dropout: float = 0.1): |
| super().__init__() |
| self.role_proj = nn.Linear(hidden_dim, key_dim, bias=False) |
| self.filler_proj = nn.Linear(hidden_dim, key_dim, bias=False) |
| self.unbind_proj = nn.Linear(key_dim, hidden_dim, bias=False) |
| self.norm = nn.LayerNorm(hidden_dim) |
| self.dropout = nn.Dropout(dropout) |
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor: |
| residual = x |
| roles = self.role_proj(x) |
| fillers = self.filler_proj(x) |
|
|
| |
| bound = roles * fillers |
|
|
| |
| bound_shifted = torch.roll(bound, shifts=1, dims=1) |
| composed = bound + bound_shifted |
|
|
| out = self.unbind_proj(composed) |
| out = self.norm(out) |
| return residual + self.dropout(out) |
|
|
|
|
| |
|
|
| class CognitiveRouter(nn.Module): |
| """Routes tokens to channels based on coherence scores.""" |
|
|
| def __init__(self, hidden_dim: int, num_channels: int, channel_dim: int, |
| routing_iters: int = 1): |
| super().__init__() |
| self.num_channels = num_channels |
| self.channel_dim = channel_dim |
| self.coherence_router = CoherenceRouter(hidden_dim, num_channels, routing_iters) |
|
|
| |
| self.to_channels = nn.Linear(hidden_dim, num_channels * channel_dim, bias=False) |
| self.from_channels = nn.Linear(num_channels * channel_dim, hidden_dim, bias=False) |
|
|
| |
| self.channels = nn.ModuleList([ |
| CognitiveChannel(channel_dim, channel_dim * 4) for _ in range(num_channels) |
| ]) |
|
|
| self.norm = nn.LayerNorm(hidden_dim) |
|
|
| def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Dict[str, torch.Tensor]]: |
| B, T, D = x.shape |
|
|
| |
| routing_weights, channel_masks = self.coherence_router(x) |
|
|
| |
| channel_input = self.to_channels(x) |
| channel_input = channel_input.view(B, T, self.num_channels, self.channel_dim) |
|
|
| |
| channel_outputs = [] |
| for c in range(self.num_channels): |
| |
| w = routing_weights[:, :, c:c+1].unsqueeze(-1) |
| ch_in = (channel_input[:, :, c, :] * routing_weights[:, :, c:c+1]) |
| ch_out = self.channels[c](ch_in) |
| channel_outputs.append(ch_out) |
|
|
| |
| combined = torch.cat(channel_outputs, dim=-1) |
| out = self.from_channels(combined) |
| out = self.norm(out) |
| x = x + out |
|
|
| stats = { |
| 'routing_entropy': -(routing_weights * (routing_weights + 1e-8).log()).sum(-1).mean(), |
| } |
| return x, stats |
|
|
|
|
| |
|
|
| class CogNetBlock(nn.Module): |
| """Router + Memory Read + FFN with residual connections.""" |
|
|
| def __init__(self, hidden_dim: int, num_channels: int, channel_dim: int, |
| ff_dim: int, key_dim: int, routing_iters: int, |
| max_adaptive_steps: int, |
| working_slots: int, episodic_slots: int, semantic_slots: int, |
| dropout: float = 0.1): |
| super().__init__() |
| self.router = CognitiveRouter(hidden_dim, num_channels, channel_dim, routing_iters) |
| self.memory = SharedHierarchicalMemory( |
| hidden_dim, key_dim, working_slots, episodic_slots, semantic_slots, dropout |
| ) |
| self.adaptive_ffn = AdaptiveComputationBlock( |
| hidden_dim, ff_dim, max_adaptive_steps, dropout |
| ) |
| self.composer = CompositionalReasoner(hidden_dim, key_dim, dropout) |
| self.norm = nn.LayerNorm(hidden_dim) |
|
|
| def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Dict[str, torch.Tensor]]: |
| stats = {} |
|
|
| x, r_stats = self.router(x) |
| stats.update(r_stats) |
|
|
| x, m_stats = self.memory(x) |
| stats.update(m_stats) |
|
|
| x, a_stats = self.adaptive_ffn(x) |
| stats.update(a_stats) |
|
|
| x = self.composer(x) |
| x = self.norm(x) |
|
|
| return x, stats |
|
|
|
|
| |
|
|
| class CogNet1B(nn.Module): |
| """Non-transformer language model with cognitive routing.""" |
|
|
| def __init__( |
| self, |
| vocab_size: int = 256, |
| hidden_dim: int = 2048, |
| num_blocks: int = 13, |
| num_channels: int = 8, |
| channel_dim: int = 256, |
| ff_dim: int = 4096, |
| routing_iters: int = 1, |
| max_adaptive_steps: int = 2, |
| max_seq_len: int = 2048, |
| working_slots: int = 64, |
| episodic_slots: int = 128, |
| semantic_slots: int = 256, |
| key_dim: int = 256, |
| dropout: float = 0.1, |
| ): |
| super().__init__() |
| self.vocab_size = vocab_size |
| self.hidden_dim = hidden_dim |
| self.num_blocks = num_blocks |
| self.num_channels = num_channels |
| self.channel_dim = channel_dim |
| self.ff_dim = ff_dim |
| self.max_seq_len = max_seq_len |
|
|
| |
| self.encoder = TokenEncoder(vocab_size, hidden_dim, max_seq_len, dropout) |
|
|
| |
| self.blocks = nn.ModuleList([ |
| CogNetBlock( |
| hidden_dim, num_channels, channel_dim, ff_dim, |
| key_dim, routing_iters, max_adaptive_steps, |
| working_slots, episodic_slots, semantic_slots, dropout |
| ) |
| for _ in range(num_blocks) |
| ]) |
|
|
| |
| self.final_norm = nn.LayerNorm(hidden_dim) |
|
|
| |
| self.output_proj = nn.Linear(hidden_dim, vocab_size, bias=False) |
| self.output_proj.weight = self.encoder.token_emb.weight |
|
|
| |
| self.apply(self._init_weights) |
|
|
| def _init_weights(self, module): |
| if isinstance(module, nn.Linear): |
| torch.nn.init.normal_(module.weight, mean=0.0, std=0.02) |
| if module.bias is not None: |
| torch.nn.init.zeros_(module.bias) |
| elif isinstance(module, nn.LayerNorm): |
| torch.nn.init.ones_(module.weight) |
| torch.nn.init.zeros_(module.bias) |
|
|
| def forward(self, input_ids: torch.Tensor, |
| return_stats: bool = False) -> Dict[str, torch.Tensor]: |
| """ |
| Args: |
| input_ids: (B, T) integer token ids |
| return_stats: whether to collect intermediate statistics |
| Returns: |
| dict with 'logits' (B, T, vocab_size) and optional 'stats' |
| """ |
| x = self.encoder(input_ids) |
|
|
| all_stats = {} if return_stats else None |
|
|
| for i, block in enumerate(self.blocks): |
| x, block_stats = block(x) |
| if return_stats: |
| for k, v in block_stats.items(): |
| key = f'block{i}_{k}' |
| |
| if isinstance(v, torch.Tensor): |
| v = v.detach().float() |
| if torch.isnan(v) or torch.isinf(v): |
| v = torch.tensor(0.0) |
| all_stats[key] = v |
|
|
| x = self.final_norm(x) |
| logits = self.output_proj(x) |
|
|
| result = {'logits': logits} |
| if return_stats: |
| result['stats'] = all_stats |
| return result |
|
|
| @torch.no_grad() |
| def generate(self, input_ids: torch.Tensor, max_new_tokens: int = 50, |
| temperature: float = 1.0, top_k: int = 0, |
| ) -> torch.Tensor: |
| """Autoregressive generation.""" |
| self.eval() |
| for _ in range(max_new_tokens): |
| |
| idx = input_ids[:, -self.max_seq_len:] |
| result = self(idx) |
| logits = result['logits'][:, -1, :] / max(temperature, 1e-8) |
|
|
| |
| if top_k > 0: |
| v, _ = torch.topk(logits, min(top_k, logits.size(-1))) |
| logits[logits < v[:, [-1]]] = float('-inf') |
|
|
| probs = F.softmax(logits, dim=-1) |
| next_token = torch.multinomial(probs, num_samples=1) |
| input_ids = torch.cat([input_ids, next_token], dim=1) |
|
|
| return input_ids |
|
|
| def count_parameters(self) -> Dict[str, int]: |
| total = sum(p.numel() for p in self.parameters()) |
| trainable = sum(p.numel() for p in self.parameters() if p.requires_grad) |
| return {'total': total, 'trainable': trainable} |
|
|
| def get_complexity_analysis(self) -> Dict[str, str]: |
| return { |
| 'architecture': 'CogNet (Non-Transformer)', |
| 'routing': f'O(n) coherence routing x {self.num_channels} channels', |
| 'memory': '3-tier hierarchical (Working/Episodic/Semantic)', |
| 'attention': 'None (replaced by cognitive routing + memory)', |
| 'ffn': 'SwiGLU with adaptive computation', |
| 'composition': 'Hyperdimensional role-filler binding', |
| 'sequence_complexity': 'O(n) per layer (vs O(n^2) for transformers)', |
| 'params': f'{self.count_parameters()["total"]:,}', |
| } |
|
|
|
|
| |
|
|
| def create_cognet_1b_small(vocab_size: int = 256, max_seq_len: int = 2048, |
| dropout: float = 0.1) -> CogNet1B: |
| """Create ~87M parameter model.""" |
| return CogNet1B( |
| vocab_size=vocab_size, |
| hidden_dim=1024, |
| num_blocks=8, |
| num_channels=8, |
| channel_dim=128, |
| ff_dim=2048, |
| routing_iters=1, |
| max_adaptive_steps=2, |
| max_seq_len=max_seq_len, |
| working_slots=32, |
| episodic_slots=64, |
| semantic_slots=128, |
| key_dim=256, |
| dropout=dropout, |
| ) |
|
|
|
|
| def create_cognet_1b(vocab_size: int = 256, max_seq_len: int = 2048, |
| dropout: float = 0.1) -> CogNet1B: |
| """Create ~1B parameter model.""" |
| return CogNet1B( |
| vocab_size=vocab_size, |
| hidden_dim=2048, |
| num_blocks=13, |
| num_channels=8, |
| channel_dim=256, |
| ff_dim=4096, |
| routing_iters=1, |
| max_adaptive_steps=2, |
| max_seq_len=max_seq_len, |
| working_slots=64, |
| episodic_slots=128, |
| semantic_slots=256, |
| key_dim=256, |
| dropout=dropout, |
| ) |
|
|
|
|
| |
|
|
| if __name__ == '__main__': |
| print("=" * 60) |
| print("CogNet1B Self-Test") |
| print("=" * 60) |
|
|
| |
| model = CogNet1B( |
| vocab_size=128, |
| hidden_dim=128, |
| num_blocks=2, |
| num_channels=4, |
| channel_dim=32, |
| ff_dim=256, |
| routing_iters=1, |
| max_adaptive_steps=2, |
| max_seq_len=64, |
| working_slots=8, |
| episodic_slots=16, |
| semantic_slots=32, |
| key_dim=64, |
| dropout=0.1, |
| ) |
|
|
| params = model.count_parameters() |
| print(f"\nParameters: {params['total']:,} total, {params['trainable']:,} trainable") |
|
|
| |
| x = torch.randint(0, 128, (2, 16)) |
| result = model(x, return_stats=True) |
| logits = result['logits'] |
| print(f"Input shape: {x.shape}") |
| print(f"Output logits shape: {logits.shape}") |
| print(f"Stats keys: {len(result.get('stats', {}))}") |
|
|
| |
| loss = logits.sum() |
| loss.backward() |
| print("Backward pass OK") |
|
|
| |
| gen = model.generate(x[:, :4], max_new_tokens=8, temperature=0.8, top_k=10) |
| print(f"Generated shape: {gen.shape}") |
|
|
| |
| analysis = model.get_complexity_analysis() |
| for k, v in analysis.items(): |
| print(f" {k}: {v}") |
|
|
| print("\nβ All self-tests passed!") |
|
|
| |
| small = create_cognet_1b_small(vocab_size=128, max_seq_len=64) |
| print(f"\nSmall model params: {small.count_parameters()['total']:,}") |
|
|