|
|
|
|
| """
|
| FFN y modulación contextual para PamparV3.
|
|
|
| Componentes:
|
| StreamFFN — Feed-forward SwiGLU
|
| ContextModulator — Modulación FiLM de selectividad mixta (63 indicadores)
|
| """
|
|
|
| from __future__ import annotations
|
|
|
| import torch
|
| import torch.nn as nn
|
| import torch.nn.functional as F
|
|
|
| from .config import ConfigV3
|
|
|
|
|
| class StreamFFN(nn.Module):
|
| """
|
| Feed-forward SwiGLU especializado por stream territorial.
|
|
|
| SwiGLU = SiLU(gate) ⊙ up → down.
|
| Cada stream (SINTAXIS/SEMANTICA/LOGICO/ESTRUCTURAL) tiene su propio
|
| conjunto de pesos — como neuronas de áreas corticales distintas.
|
|
|
| hidden_dim = 2/3 × dim × ffn_mult (compensa la gate extra de SwiGLU).
|
| """
|
|
|
| def __init__(self, config: ConfigV3):
|
| super().__init__()
|
| hidden = config.ffn_hidden
|
|
|
| self.gate = nn.Linear(config.dim, hidden, bias=False)
|
| self.up = nn.Linear(config.dim, hidden, bias=False)
|
| self.down = nn.Linear(hidden, config.dim, bias=False)
|
| self.drop = nn.Dropout(config.dropout)
|
|
|
| def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| """SwiGLU: SiLU(gate(x)) ⊙ up(x) → down."""
|
| return self.drop(self.down(F.silu(self.gate(x)) * self.up(x)))
|
|
|
|
|
| class ContextModulator(nn.Module):
|
| """
|
| Modulador de selectividad mixta — inspirado en neurociencia cortical.
|
|
|
| Un solo FFN compartido codifica el conocimiento (2.2M params).
|
| Este modulador genera gamma/beta por token usando un vector de contexto
|
| rico (zona_acts + terr_acts + depth + conf = 63 indicadores) para que
|
| la MISMA memoria se lea de formas diferentes según el contexto.
|
|
|
| Reemplaza 4 StreamFFN independientes (8.8M/nivel) por:
|
| 1 SharedFFN (2.2M) + 4 modulaciones (gamma, beta) desde contexto (200K)
|
| = 2.4M/nivel → ahorro de ~6.4M/nivel → ~32M total
|
|
|
| Basado en:
|
| - FiLM (Perez et al., 2018): Feature-wise Linear Modulation
|
| - Mixed Selectivity (Rigotti et al., 2013): misma neurona, múltiples roles
|
| - Superposition (Anthropic, 2022): más conceptos que dimensiones
|
|
|
| El contexto de 63d se compone de:
|
| zona_acts [52] — tipo de token (keyword, variable, string...)
|
| terr_acts [4] — área dominante
|
| depth [1] — nivel actual (0..n_levels-1), normalizado
|
| conf [1] — confianza del modelo en este punto
|
| n_levels [1] — total de niveles (para normalización)
|
| stream_id [4] — one-hot del stream que se está modulando
|
|
|
| Total: 52 + 4 + 1 + 1 + 1 + 4 = 63 indicadores.
|
| """
|
|
|
|
|
| CONTEXT_DIM: int = 63
|
|
|
| def __init__(self, config: ConfigV3):
|
| super().__init__()
|
| self.dim = config.dim
|
| mid = config.modulator_bottleneck
|
|
|
|
|
| self.proj = nn.Sequential(
|
| nn.Linear(self.CONTEXT_DIM, mid, bias=False),
|
| nn.SiLU(),
|
| nn.Linear(mid, config.dim * 2, bias=False),
|
| )
|
|
|
|
|
| nn.init.zeros_(self.proj[2].weight)
|
|
|
| def forward(
|
| self,
|
| ffn_out: torch.Tensor,
|
| zona_acts: torch.Tensor,
|
| terr_acts: torch.Tensor,
|
| stream_idx: int,
|
| nivel_idx: int,
|
| n_levels: int,
|
| conf: float,
|
| ) -> torch.Tensor:
|
| """
|
| Modula la salida del FFN compartido según contexto completo.
|
|
|
| Args:
|
| ffn_out: [B, L, D] salida del FFN compartido
|
| zona_acts: [B, L, 52] activaciones por zona
|
| terr_acts: [B, L, 4] activaciones territoriales
|
| stream_idx: índice del stream actual (0-3)
|
| nivel_idx: índice del nivel actual (0-4)
|
| n_levels: total de niveles
|
| conf: confianza actual (0.0-1.0)
|
|
|
| Returns:
|
| [B, L, D] salida modulada para este stream/contexto
|
| """
|
| B, L, _ = ffn_out.shape
|
| device = ffn_out.device
|
|
|
|
|
| depth = torch.full(
|
| (B, L, 1),
|
| nivel_idx / max(n_levels - 1, 1),
|
| device=device,
|
| dtype=ffn_out.dtype,
|
| )
|
|
|
| conf_t = torch.full(
|
| (B, L, 1),
|
| conf,
|
| device=device,
|
| dtype=ffn_out.dtype,
|
| )
|
|
|
| nl_t = torch.full(
|
| (B, L, 1),
|
| n_levels / 10.0,
|
| device=device,
|
| dtype=ffn_out.dtype,
|
| )
|
|
|
|
|
| stream_oh = torch.zeros(
|
| B,
|
| L,
|
| 4,
|
| device=device,
|
| dtype=ffn_out.dtype,
|
| )
|
| stream_oh[:, :, stream_idx] = 1.0
|
|
|
|
|
| ctx = torch.cat(
|
| [zona_acts, terr_acts, depth, conf_t, nl_t, stream_oh],
|
| dim=-1,
|
| )
|
|
|
|
|
| modulation = self.proj(ctx)
|
| gamma, beta = modulation.chunk(2, dim=-1)
|
|
|
|
|
|
|
| return (1.0 + gamma) * ffn_out + beta
|
|
|