flownet / modules /flownet_model.py
Ashu9675's picture
Add FlowNet: Post-Transformer Architecture
d4fff7c
Raw
History Blame Contribute Delete
17.2 kB
"""
FlowNet Model — Full Architecture Integration.
This is the complete model that integrates all novel components:
- Particle encoding (not embeddings)
- Flow field dynamics (not attention)
- Wave interference (not softmax)
- Bond system (not fixed weights)
- Phase transitions (not fixed depth)
- Consistency field (not just cross-entropy)
- Topological memory (not vector DB)
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from typing import Dict, Optional, Tuple
from ..core.particle import ParticleEncoder, ParticleState, ParticleCombiner, ParticleUpdater
from ..core.flow_field import FlowField, FlowFieldEfficient
from ..core.wave_processor import WaveProcessor, WaveAttention
from ..core.bond_system import BondSystem, BondState
from ..core.phase_engine import PhaseEngine
from ..core.consistency_field import ConsistencyField
from ..core.topological_memory import TopologicalMemory
class FlowNetBlock(nn.Module):
"""A single FlowNet processing block.
Equivalent to a Transformer block, but with completely different
internal computation:
Transformer block:
x → LayerNorm → MultiHeadAttention → Residual → LayerNorm → FFN → Residual
FlowNet block:
x → ParticleUpdate → WaveProcess → BondUpdate → PhaseTransition → Residual
"""
def __init__(
self,
d_semantic: int = 256,
d_charge: int = 32,
d_spin: int = 32,
d_memory: int = 128,
n_wave_heads: int = 8,
d_wave: int = 32,
use_efficient_flow: bool = False,
local_radius: int = 64,
):
super().__init__()
self.d_semantic = d_semantic
# Particle updater (replaces attention)
self.particle_updater = ParticleUpdater(d_semantic, d_charge, d_spin, d_memory)
# Wave processor (novel — no Transformer equivalent)
self.wave_processor = WaveProcessor(d_semantic, n_wave_heads, d_wave)
# Bond system (novel — no Transformer equivalent)
self.bond_system = BondSystem(d_semantic, d_charge, d_spin)
# Flow field (replaces FFN)
if use_efficient_flow:
self.flow_field = FlowFieldEfficient(d_semantic, local_radius=local_radius)
else:
self.flow_field = FlowField(d_semantic)
# Normalization (topology-preserving, not LayerNorm)
self.norm1 = nn.LayerNorm(d_semantic)
self.norm2 = nn.LayerNorm(d_semantic)
# Gating — controls information flow
self.gate = nn.Sequential(
nn.Linear(d_semantic * 2, d_semantic),
nn.Sigmoid(),
)
def forward(
self,
particles: ParticleState,
bonds: Optional[BondState] = None,
) -> Tuple[ParticleState, BondState, Dict]:
"""Process one FlowNet block.
Args:
particles: Input particle state
bonds: Previous bond state
Returns:
Updated particles, bonds, and diagnostics
"""
residual = particles.semantic
# === Step 1: Particle interaction update ===
particles = self.particle_updater(particles)
# === Step 2: Wave processing ===
wave_output, wave_diag = self.wave_processor(particles)
# Gate wave influence
gate = self.gate(torch.cat([particles.semantic, wave_output], dim=-1))
particles_semantic = particles.semantic + wave_output * gate
# Residual + norm
particles_semantic = self.norm1(particles_semantic + residual)
# === Step 3: Bond system ===
particles_for_bonds = ParticleState(
semantic=particles_semantic,
position=particles.position,
charge=particles.charge,
mass=particles.mass,
spin=particles.spin,
amplitude=particles.amplitude,
phase=particles.phase,
memory_trace=particles.memory_trace,
)
bonds, bond_output, bond_diag = self.bond_system(particles_for_bonds, bonds)
particles_semantic = particles_semantic + bond_output * 0.1
# === Step 4: Flow field ===
residual2 = particles_semantic
particles_for_flow = ParticleState(
semantic=particles_semantic,
position=particles.position,
charge=particles.charge,
mass=particles.mass,
spin=particles.spin,
amplitude=particles.amplitude,
phase=particles.phase,
memory_trace=particles.memory_trace,
)
particles_for_flow, flow_diag = self.flow_field(particles_for_flow)
particles_semantic = self.norm2(particles_for_flow.semantic + residual2)
# Reconstruct particle state
final_particles = ParticleState(
semantic=particles_semantic,
position=particles.position,
charge=particles.charge,
mass=particles.mass,
spin=particles.spin,
amplitude=particles_for_flow.amplitude,
phase=particles_for_flow.phase,
memory_trace=particles_for_flow.memory_trace,
)
diagnostics = {
'wave': wave_diag,
'bond': bond_diag,
'flow': flow_diag,
}
return final_particles, bonds, diagnostics
class FlowNetModel(nn.Module):
"""Complete FlowNet model for language modeling.
Architecture:
1. Token → Particle encoding
2. N × FlowNet blocks (wave + bond + flow)
3. Phase transition engine (adaptive depth)
4. Consistency field (truth verification)
5. Topological memory (persistent storage)
6. Particle → output decoding
This is NOT a Transformer. It processes language through
physical dynamics rather than matrix multiplication.
"""
def __init__(
self,
vocab_size: int = 32000,
d_semantic: int = 256,
d_position: int = 64,
d_charge: int = 32,
d_spin: int = 32,
d_memory: int = 128,
n_blocks: int = 6,
n_wave_heads: int = 8,
d_wave: int = 32,
use_phase_engine: bool = True,
use_consistency: bool = True,
use_topological_memory: bool = True,
max_seq_len: int = 8192,
use_efficient_flow: bool = False,
local_radius: int = 64,
):
super().__init__()
self.vocab_size = vocab_size
self.d_semantic = d_semantic
self.n_blocks = n_blocks
self.use_phase_engine = use_phase_engine
self.use_consistency = use_consistency
self.use_topological_memory = use_topological_memory
# === Particle Encoder ===
self.encoder = ParticleEncoder(
vocab_size=vocab_size,
d_semantic=d_semantic,
d_position=d_position,
d_charge=d_charge,
d_spin=d_spin,
d_memory=d_memory,
max_seq_len=max_seq_len,
)
# === FlowNet Blocks ===
self.blocks = nn.ModuleList([
FlowNetBlock(
d_semantic=d_semantic,
d_charge=d_charge,
d_spin=d_spin,
d_memory=d_memory,
n_wave_heads=n_wave_heads,
d_wave=d_wave,
use_efficient_flow=use_efficient_flow,
local_radius=local_radius,
)
for _ in range(n_blocks)
])
# === Phase Transition Engine ===
if use_phase_engine:
self.phase_engine = PhaseEngine(
d_semantic=d_semantic,
min_steps=2,
max_steps=16,
)
# === Consistency Field ===
if use_consistency:
self.consistency_field = ConsistencyField(
d_semantic=d_semantic,
)
# === Topological Memory ===
if use_topological_memory:
self.topological_memory = TopologicalMemory(
d_semantic=d_semantic,
)
# === Output Head ===
# Converts final particle state to token logits
self.output_head = nn.Sequential(
nn.Linear(d_semantic + d_memory, d_semantic),
nn.GELU(),
nn.LayerNorm(d_semantic),
nn.Linear(d_semantic, vocab_size),
)
# Particle combiner for sequence-level tasks
self.combiner = ParticleCombiner(d_semantic, d_memory, vocab_size)
def forward(
self,
token_ids: torch.Tensor, # [batch, seq_len]
labels: Optional[torch.Tensor] = None,
store_memory: bool = False,
use_stored_memory: bool = True,
) -> Dict:
"""Forward pass through the complete FlowNet architecture.
Args:
token_ids: Input token indices [batch, seq_len]
labels: Target tokens for loss computation [batch, seq_len]
store_memory: Whether to store processed patterns in memory
use_stored_memory: Whether to retrieve from topological memory
Returns:
Dictionary with logits, loss, diagnostics
"""
batch, seq_len = token_ids.shape
device = token_ids.device
# === Step 1: Encode tokens into particles ===
particles = self.encoder(token_ids)
# === Step 2: Retrieve from topological memory ===
memory_diag = {}
if self.use_topological_memory and use_stored_memory and self.training is False:
# Query memory with current context
particles.semantic, memory_diag = self.topological_memory(
particles.semantic, particles.semantic
)
# === Step 3: Process through FlowNet blocks ===
bonds = None
block_diagnostics = []
for block in self.blocks:
particles, bonds, block_diag = block(particles, bonds)
block_diagnostics.append(block_diag)
# === Step 4: Phase transition (adaptive refinement) ===
phase_diag = {}
if self.use_phase_engine:
# Use first block as the refinement function
def flow_fn(p):
updated, _, diag = self.blocks[0](p, bonds)
return updated, diag
particles, phase_diag = self.phase_engine(
particles, flow_fn=flow_fn
)
# === Step 5: Consistency field ===
consistency_diag = {}
if self.use_consistency:
particles.semantic, consistency_diag = self.consistency_field(
particles.semantic
)
# === Step 6: Store in topological memory ===
if self.use_topological_memory and store_memory:
# Store the processed pattern
for b in range(batch):
self.topological_memory.store(particles.semantic[b])
# === Step 7: Decode to token logits ===
# Combine semantic and memory for output
combined = torch.cat([particles.semantic, particles.memory_trace], dim=-1)
logits = self.output_head(combined) # [batch, seq_len, vocab_size]
# === Compute loss ===
loss = None
if labels is not None:
# Standard language modeling loss
shift_logits = logits[..., :-1, :].contiguous()
shift_labels = labels[..., 1:].contiguous()
loss = F.cross_entropy(
shift_logits.reshape(-1, self.vocab_size),
shift_labels.reshape(-1),
ignore_index=-100,
)
# Add consistency field loss (penalize high energy)
if self.use_consistency:
energy_penalty = consistency_diag.get('final_energy', 0)
if isinstance(energy_penalty, torch.Tensor):
loss = loss + energy_penalty * 0.01
# Add phase transition regularization
if self.use_phase_engine:
# Encourage proper phase transitions
pass # PhaseEngine has its own loss, applied externally
# === Compile diagnostics ===
diagnostics = {
'blocks': block_diagnostics,
'phase': phase_diag,
'consistency': consistency_diag,
'memory': memory_diag,
'particles': {
'mean_amplitude': particles.amplitude.mean().item(),
'mean_phase': particles.phase.mean().item(),
'phase_coherence': torch.abs(
torch.exp(1j * particles.phase).mean()
).item(),
},
}
return {
'logits': logits,
'loss': loss,
'diagnostics': diagnostics,
'particles': particles,
}
def generate(
self,
token_ids: torch.Tensor,
max_new_tokens: int = 100,
temperature: float = 1.0,
top_k: int = 50,
top_p: float = 0.9,
consistency_check: bool = True,
) -> torch.Tensor:
"""Generate tokens autoregressively.
Unlike standard generation, FlowNet can:
1. Use topological memory to recall relevant patterns
2. Check consistency of generated tokens
3. Adapt computation depth based on difficulty
"""
self.eval()
generated = token_ids.clone()
with torch.no_grad():
for _ in range(max_new_tokens):
# Forward pass
output = self.forward(
generated,
store_memory=False,
use_stored_memory=True,
)
logits = output['logits'][:, -1, :] # last token logits
# Consistency check — penalize inconsistent continuations
if consistency_check and self.use_consistency:
# Check if top candidates are consistent
top_logits, top_indices = logits.topk(top_k)
# Quick energy check for top candidates
# (In production, this would be more sophisticated)
pass
# Temperature scaling
logits = logits / temperature
# Top-k filtering
if top_k > 0:
indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
logits[indices_to_remove] = float('-inf')
# Top-p (nucleus) filtering
if top_p < 1.0:
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
sorted_indices_to_remove = cumulative_probs > top_p
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
sorted_indices_to_remove[..., 0] = 0
indices_to_remove = sorted_indices_to_remove.scatter(
1, sorted_indices, sorted_indices_to_remove
)
logits[indices_to_remove] = float('-inf')
# Sample
probs = F.softmax(logits, dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
generated = torch.cat([generated, next_token], dim=1)
return generated
class FlowNetForClassification(nn.Module):
"""FlowNet variant for sequence classification tasks."""
def __init__(
self,
vocab_size: int = 32000,
d_semantic: int = 256,
n_classes: int = 2,
**kwargs
):
super().__init__()
self.model = FlowNetModel(vocab_size=vocab_size, d_semantic=d_semantic, **kwargs)
self.classifier = nn.Sequential(
nn.Linear(d_semantic + 128, 256), # +128 for memory
nn.GELU(),
nn.Dropout(0.1),
nn.Linear(256, n_classes),
)
def forward(self, token_ids: torch.Tensor, labels: Optional[torch.Tensor] = None):
output = self.model(token_ids, store_memory=False)
particles = output['particles']
# Pool particles (phase-weighted)
combined = torch.cat([particles.semantic, particles.memory_trace], dim=-1)
phase_weights = F.softmax(torch.cos(particles.phase), dim=-1)
pooled = (combined * phase_weights.unsqueeze(-1)).sum(dim=1)
logits = self.classifier(pooled)
loss = None
if labels is not None:
loss = F.cross_entropy(logits, labels)
return {'logits': logits, 'loss': loss}