lucas-mella's picture
feat: upload PAMPAr-Coder code and documentation
a2d6a0d verified
Raw
History Blame Contribute Delete
5.51 kB
# SPDX-License-Identifier: BUSL-1.1
# Copyright (c) 2024-2026 Lucas Ricardo Mella Chillemi
"""
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.
"""
# Dimensión fija del vector de contexto
CONTEXT_DIM: int = 63
def __init__(self, config: ConfigV3):
super().__init__()
self.dim = config.dim
mid = config.modulator_bottleneck
# Contexto (63d) → bottleneck → gamma + beta (dim + dim)
self.proj = nn.Sequential(
nn.Linear(self.CONTEXT_DIM, mid, bias=False),
nn.SiLU(),
nn.Linear(mid, config.dim * 2, bias=False),
)
# Inicializar cerca de identidad: gamma≈1, beta≈0
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
# Construir vector de contexto [B, L, 63]
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 ID one-hot [B, L, 4]
stream_oh = torch.zeros(
B,
L,
4,
device=device,
dtype=ffn_out.dtype,
)
stream_oh[:, :, stream_idx] = 1.0
# Concatenar: [52] + [4] + [1] + [1] + [1] + [4] = 63
ctx = torch.cat(
[zona_acts, terr_acts, depth, conf_t, nl_t, stream_oh],
dim=-1,
) # [B, L, 63]
# Proyectar a gamma + beta
modulation = self.proj(ctx) # [B, L, dim*2]
gamma, beta = modulation.chunk(2, dim=-1) # [B, L, dim] cada uno
# FiLM: gamma modula la escala, beta desplaza
# +1 para que gamma inicie en identidad (init de proj es zeros)
return (1.0 + gamma) * ffn_out + beta