harmonic-convergence / mamba3_prime_native.py
batteryphil's picture
Upload folder using huggingface_hub
0ae232d verified
Raw
History Blame Contribute Delete
26.7 kB
import os
os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
import sys
import time
import json
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint
from transformers import AutoTokenizer
from datasets import load_dataset
from huggingface_hub import login
# ── Config ────────────────────────────────────────────────────────────────────
CONFIG = {
'd_model': 1024,
'n_layers': 28,
'expand': 2,
'd_state': 16,
'seq_len': 256,
'grad_accum': 4,
'lr': 1e-4,
'total_steps': 25000,
'device': 'cuda' if torch.cuda.is_available() else 'cpu',
'stats_file': 'stats_mamba3_native.json',
'samples_file': 'samples_mamba3_native.json',
'log_file': 'training_mamba3_native.log',
}
_tok_path = os.path.expanduser('~/.hf_token')
login(token=open(_tok_path).read().strip() if os.path.exists(_tok_path)
else os.environ.get('HF_TOKEN', ''))
# ── Logger ────────────────────────────────────────────────────────────────────
class LoggerTee:
def __init__(self, path):
self.terminal = sys.__stdout__
self.log = open(path, 'a')
def write(self, msg):
self.terminal.write(msg)
self.log.write(msg)
self.log.flush()
def flush(self):
self.terminal.flush()
def isatty(self):
return False
sys.stdout = LoggerTee(CONFIG['log_file'])
sys.stderr = sys.stdout
# ── Prime Harmonic Grid LUT ───────────────────────────────────────────────────
def build_prime_lut(n_points=65536):
"""Protocol v6.00 β€” interpolated between prime reciprocal anchors."""
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
reciprocals = [1.0 / p for p in primes]
tails = [1.0, 1.5, 2.0]
anchors = sorted(
[0.0] + reciprocals + tails + [-r for r in reciprocals] + [-t for t in tails]
)
t = torch.linspace(0, 1, n_points, dtype=torch.float32)
lut = torch.zeros(n_points, dtype=torch.float32)
n = len(anchors) - 1
for i in range(n_points):
pos = t[i].item() * n
lo = int(pos); hi = min(lo + 1, n); frac = pos - lo
lut[i] = anchors[lo] * (1 - frac) + anchors[hi] * frac
print(f"[PRIME] LUT: {n_points} pts, range [{lut.min():.4f}, {lut.max():.4f}]")
return lut # stays on CPU
# ── PrimeLinear β€” the ONLY optimizer is vote pressure ─────────────────────────
class PrimeLinear(nn.Module):
"""
Discrete weight matrix on the prime harmonic grid.
No AdamW, no scale, no continuous descent.
Gradient signs accumulate into a GPU vote buffer.
Supermajority gate β†’ index steps (+1 or -1) across the LUT.
Exactly the mechanism from prime-revisited, applied from scratch.
v2 changes (reviewer-directed):
SUPERMAJORITY 5 β†’ 12 : forces stricter consensus, lowers migration rate
MAX_STRIDE 32 β†’ 2 : clamps velocity, prevents overstepping basins
DECAY_RATE 0.95 β†’ 0.90 : faster stale-vote clearing
vote_buffer/last_dir/momentum: CPU β†’ GPU registered buffers
β†’ eliminates all CPU↔GPU transfers in the optimization hot path
β†’ GPU utilization: 10% β†’ ~60%+
"""
SUPERMAJORITY = 12
BLOCK_SIZE = 32
MAX_STRIDE = 2
DECAY_RATE = 0.90
def __init__(self, module: nn.Linear, lut: torch.Tensor):
super().__init__()
self.in_features = module.in_features
self.out_features = module.out_features
self.lut = lut # shared CPU tensor β€” moved to device on first forward
# Map random init weights onto the nearest LUT index
with torch.no_grad():
w = module.weight.float()
lut_min, lut_max = lut[0].item(), lut[-1].item()
span = lut_max - lut_min + 1e-8
combined = ((w - lut_min) / span * 65535.0).round().clamp(0, 65535).to(torch.int32)
base = torch.div(combined, 256, rounding_mode='floor').to(torch.uint8)
fine = (combined % 256).to(torch.uint8)
n_blocks = (self.out_features * self.in_features) // self.BLOCK_SIZE
# All buffers registered β†’ move to GPU with model.to(device)
self.register_buffer('base_idx', base) # uint8 GPU
self.register_buffer('fine_idx', fine) # uint8 GPU
self.register_buffer('init_combined', combined.to(torch.int32)) # int32 GPU
self.register_buffer('vote_buffer', torch.zeros( # int16 GPU
self.out_features, self.in_features, dtype=torch.int16))
self.register_buffer('last_dir', torch.zeros(n_blocks, 1, dtype=torch.int8)) # int8 GPU
self.register_buffer('momentum', torch.zeros(n_blocks, 1, dtype=torch.int8)) # int8 GPU
self.bias = nn.Parameter(module.bias.data.clone()) if module.bias is not None else None
def forward(self, x):
combined = self.base_idx.long() * 256 + self.fine_idx.long()
w = self.lut.to(combined.device)[combined].to(x.dtype)
if self.training:
w = w.detach().requires_grad_(True)
w.register_hook(self._vote_hook)
return F.linear(x, w, self.bias)
def _vote_hook(self, grad):
"""Accumulate signΓ—10 pressure β€” fully on GPU, zero CPU transfer."""
with torch.no_grad():
pressure = (torch.sign(grad) * 10).to(torch.int32)
self.vote_buffer = torch.clamp(
self.vote_buffer.to(torch.int32) + pressure, -32760, 32760
).to(torch.int16)
@torch.no_grad()
def apply_votes(self, lr=1e-4):
"""Block supermajority gate β†’ step indices. Fully GPU-resident. Returns telemetry dict."""
bs = self.BLOCK_SIZE
flat = self.vote_buffer.view(-1) # GPU int16
n = flat.numel()
aligned = n - (n % bs)
flat_a = flat[:aligned]
# ── Capture vote distribution BEFORE any modification ─────────────────
vote_pos = (flat_a > 0).float().mean().item()
vote_neg = (flat_a < 0).float().mean().item()
vote_neut = max(0.0, 1.0 - vote_pos - vote_neg)
blocks = flat_a.float().view(-1, bs)
magnitude = blocks.abs().mean(dim=1, keepdim=True)
authorized = (magnitude * (lr / 1e-4) >= self.SUPERMAJORITY)
# Current index state β€” already on GPU
combined = (self.base_idx.view(-1)[:aligned].to(torch.int32) * 256 +
self.fine_idx.view(-1)[:aligned].to(torch.int32))
# ── Shared telemetry helper (operates on whichever combined is passed) ─
def _telemetry(c):
counts = torch.bincount(c.long(), minlength=65536)
p = counts[counts > 0].float() / c.numel()
entropy = -(p * torch.log2(p + 1e-12)).sum().item()
occupancy = (counts > 0).float().mean().item()
init_f = self.init_combined.to(c.device).view(-1)[:aligned]
diff = (c - init_f).float().abs()
disp_95 = torch.quantile(diff[::max(1, len(diff)//100000)], 0.95).item()
mom_mean = self.momentum.float().mean().item()
return {
'entropy': round(entropy, 4),
'disp_95': round(disp_95, 2),
'occupancy': round(occupancy, 4),
'momentum_mean': round(mom_mean, 4),
'vote_pos': round(vote_pos, 4),
'vote_neg': round(vote_neg, 4),
'vote_neut': round(vote_neut, 4),
}
if not authorized.any():
return {'flips': 0, 'migration_rate': 0.0, **_telemetry(combined)}
# ── Active branch ─────────────────────────────────────────────────────
step_dir = torch.sign(blocks)
block_dir = step_dir.mean(dim=1, keepdim=True).sign().to(torch.int8)
auth_sq = authorized.squeeze()
ld_sq = self.last_dir.squeeze()
bd_sq = block_dir.squeeze()
reversed_blocks = auth_sq & (bd_sq != ld_sq) & (ld_sq != 0)
same_dir = (bd_sq == ld_sq).to(torch.int8)
new_momentum = torch.clamp(
(self.momentum.squeeze() * same_dir + same_dir).to(torch.int8), 0, 8)
self.momentum = new_momentum.view(-1, 1)
cblocks = combined.view(-1, bs)
center_dist = (cblocks - 32768).float().abs().mean(dim=1, keepdim=True)
# MAX_STRIDE cap: stride scales from 1 β†’ MAX_STRIDE based on center proximity
base_stride = torch.clamp(
(self.MAX_STRIDE * (1.0 - center_dist / 32768.0)).long(), min=1)
dyn_stride = torch.clamp(
base_stride * (1 + self.momentum.float() / 2.0).to(torch.long),
max=self.MAX_STRIDE)
dyn_stride[reversed_blocks.view(-1, 1)] = 0
update = (authorized.float() * step_dir * dyn_stride).to(torch.int32)
moved = authorized & ~reversed_blocks.unsqueeze(-1).expand_as(authorized)
total_flips = int(moved.sum().item() * bs)
new_combined = torch.clamp(combined - update.view(-1), 0, 65535)
self.base_idx.copy_(
torch.div(new_combined, 256, rounding_mode='floor')
.to(torch.uint8).view(self.base_idx.shape))
self.fine_idx.copy_(
(new_combined % 256).to(torch.uint8).view(self.fine_idx.shape))
self.last_dir = block_dir.clone()
# Clear authorized blocks, decay remainder with class-level DECAY_RATE
self.vote_buffer.view(-1)[:aligned].view(-1, bs).masked_fill_(authorized, 0)
self.vote_buffer = (self.vote_buffer.float() * self.DECAY_RATE).to(torch.int16)
return {
'flips': total_flips,
'migration_rate': round(total_flips / max(1, n), 6),
**_telemetry(new_combined), # post-update stats on GPU
}
# ── Pure-PyTorch Mamba Selective Scan ────────────────────────────────────────
class RealMambaSSM(nn.Module):
def __init__(self, d_model, d_state=16, d_conv=4, expand=2):
super().__init__()
self.d_inner = d_model * expand
self.d_state = d_state
self.dt_rank = max(1, math.ceil(d_model / 16))
# These two get wrapped with PrimeLinear after construction
self.in_proj = nn.Linear(d_model, self.d_inner * 2, bias=False)
self.out_proj = nn.Linear(self.d_inner, d_model, bias=False)
nn.init.zeros_(self.out_proj.weight)
# SSM dynamics β€” continuous, small, stay as-is
self.conv1d = nn.Conv1d(self.d_inner, self.d_inner,
kernel_size=d_conv, padding=d_conv-1,
groups=self.d_inner, bias=True)
self.x_proj = nn.Linear(self.d_inner, self.dt_rank + d_state * 2, bias=False)
self.dt_proj = nn.Linear(self.dt_rank, self.d_inner, bias=True)
A = torch.arange(1, d_state+1, dtype=torch.float32).unsqueeze(0).expand(self.d_inner, -1)
self.A_log = nn.Parameter(torch.log(A))
self.D = nn.Parameter(torch.ones(self.d_inner))
dt_std = self.dt_rank ** -0.5
nn.init.uniform_(self.dt_proj.weight, -dt_std, dt_std)
dt = torch.exp(torch.rand(self.d_inner) * (math.log(0.1) - math.log(0.001)) + math.log(0.001))
with torch.no_grad():
self.dt_proj.bias.copy_(dt + torch.log(-torch.expm1(-dt)))
def forward(self, x):
xz = self.in_proj(x)
x_in, z = xz.chunk(2, dim=-1)
x_conv = F.conv1d(x_in.transpose(1,2), self.conv1d.weight, self.conv1d.bias,
padding=self.conv1d.padding[0],
groups=self.conv1d.groups)[:, :, :x.shape[1]].transpose(1,2)
x_conv = F.silu(x_conv)
y = self._scan(x_conv)
return self.out_proj(y * F.silu(z))
def _scan(self, x):
xf = x.float()
dbl = self.x_proj(xf)
dt_r, B_p, C = dbl.split([self.dt_rank, self.d_state, self.d_state], dim=-1)
dt = F.softplus(self.dt_proj(dt_r))
A = -torch.exp(self.A_log.float())
dtA = torch.einsum('bld,ds->blds', dt, A)
log_A_cum = torch.clamp(torch.cumsum(dtA, dim=1), min=-80.0)
Bu = torch.einsum('bld,bls->blds', dt * xf, B_p)
h = torch.exp(log_A_cum) * torch.cumsum(Bu * torch.exp(-log_A_cum), dim=1)
y = torch.einsum('blds,bls->bld', h, C)
return (y + xf * self.D.float()).to(x.dtype)
# ── Model ────────────────────────────────────────────────────────────────────
class MambaLayer(nn.Module):
def __init__(self, d_model, expand, d_state):
super().__init__()
self.norm = nn.LayerNorm(d_model)
self.ssm = RealMambaSSM(d_model, d_state=d_state, expand=expand)
def forward(self, x):
return torch.utils.checkpoint.checkpoint(
lambda inp: self.ssm(self.norm(inp)) + inp, x, use_reentrant=False)
class Mamba3LM(nn.Module):
def __init__(self, vocab_size):
super().__init__()
d = CONFIG['d_model']
self.embedding = nn.Embedding(vocab_size, d)
self.layers = nn.ModuleList([
MambaLayer(d, CONFIG['expand'], CONFIG['d_state'])
for _ in range(CONFIG['n_layers'])
])
self.norm_f = nn.LayerNorm(d)
self.lm_head = nn.Linear(d, vocab_size, bias=False)
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Embedding): nn.init.normal_(m.weight, std=0.02)
elif isinstance(m, nn.LayerNorm):
nn.init.ones_(m.weight); nn.init.zeros_(m.bias)
def forward(self, input_ids, labels=None):
x = self.embedding(input_ids)
for layer in self.layers:
x = layer(x)
x = self.norm_f(x)
logits = self.lm_head(x)
loss = None
if labels is not None:
loss = F.cross_entropy(
logits[..., :-1, :].contiguous().view(-1, logits.size(-1)),
labels[..., 1:].contiguous().view(-1))
return logits, loss
# ── Dataset ──────────────────────────────────────────────────────────────────
def make_dataset(tokenizer):
import random
# Load the C/C++ instruct dataset
c_instruct_path = '/home/phil/.gemini/antigravity/scratch/analysis_project/mamba-prime/oo_c_instruct.jsonl'
c_instruct = []
try:
with open(c_instruct_path) as f:
for line in f:
c_instruct.append(json.loads(line)['text'])
print(f"[DATA] Loaded {len(c_instruct)} C/C++ instruction examples")
except Exception as e:
print(f"[DATA] Failed to load C instruct: {e}")
# Load the Operating-Organism codebase corpus
oo_corpus_path = '/home/phil/.gemini/antigravity/scratch/analysis_project/mamba-prime/oo_corpus.jsonl'
oo_corpus = []
try:
with open(oo_corpus_path) as f:
for line in f:
oo_corpus.append(json.loads(line)['text'])
print(f"[DATA] Loaded {len(oo_corpus)} Operating-Organism chunks")
except Exception as e:
print(f"[DATA] Failed to load OO corpus: {e}")
# Fallback to random tokens if datasets are missing
if not c_instruct and not oo_corpus:
print("[WARN] No datasets found! Yielding random tokens.")
c_instruct = ["### Instruction:\nFail\n### Response:\nData missing"]
def gen():
while True:
# 60% chance to yield C/C++ instruct, 40% chance to yield OO corpus
if oo_corpus and (not c_instruct or random.random() < 0.40):
text = random.choice(oo_corpus)
else:
text = random.choice(c_instruct)
if not text.endswith(tokenizer.eos_token):
text += tokenizer.eos_token
tok = tokenizer(text, truncation=True, max_length=CONFIG['seq_len'],
padding='max_length', return_tensors='pt')
ids = tok['input_ids'][0]
yield ids, ids.clone()
return gen
# ── Main ─────────────────────────────────────────────────────────────────────
if __name__ == '__main__':
print(f"\n{'='*60}")
print(f"[PRIME] Mamba3-300M from scratch β€” PID {os.getpid()}")
print(f"[PRIME] Discrete-only optimizer: vote pressure on prime grid")
print(f"{'='*60}")
lut = build_prime_lut()
tokenizer = AutoTokenizer.from_pretrained('EleutherAI/gpt-neox-20b')
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
vocab_size = len(tokenizer)
print("[INIT] Building model...")
model = Mamba3LM(vocab_size)
# Wrap in_proj and out_proj in every layer with PrimeLinear
wrapped = 0
for layer in model.layers:
layer.ssm.in_proj = PrimeLinear(layer.ssm.in_proj, lut)
layer.ssm.out_proj = PrimeLinear(layer.ssm.out_proj, lut)
wrapped += 2
print(f"[INIT] Wrapped {wrapped} linear layers with PrimeLinear (prime grid).")
model = model.to(CONFIG['device'])
total_params = sum(p.numel() for p in model.parameters())
print(f"[INIT] Total params: {total_params/1e6:.1f}M")
print(f"[INIT] VRAM: {torch.cuda.memory_allocated()/1e9:.2f} GB")
step = 0
history = []
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--resume', type=str, default=None, help='Path to checkpoint to resume from')
args, _ = parser.parse_known_args()
if args.resume and os.path.exists(args.resume):
print(f"[INIT] Resuming from checkpoint: {args.resume}")
ckpt = torch.load(args.resume, map_location=CONFIG['device'])
model.load_state_dict(ckpt['state_dict'])
step = ckpt.get('step', 0)
# Load and trim history to match resumed step
if os.path.exists(CONFIG['stats_file']):
try:
with open(CONFIG['stats_file'], 'r') as f:
full_history = json.load(f)
history = [h for h in full_history if h.get('step', 0) <= step]
except Exception as e:
print(f"[WARN] Failed to load history: {e}")
print(f"[INIT] Successfully resumed at step {step}")
data_gen = make_dataset(tokenizer)
batch_idx = 0
total_loss = 0.0
start_time = time.time()
accum = CONFIG['grad_accum']
print("[TRAIN] Starting pure-discrete PRIME training...")
for input_ids, labels in data_gen():
input_ids = input_ids.unsqueeze(0).to(CONFIG['device'])
labels = labels.unsqueeze(0).to(CONFIG['device'])
try:
_, loss = model(input_ids, labels)
except Exception as e:
print(f"[WARN] Forward error: {e}")
continue
if loss is None or torch.isnan(loss) or torch.isinf(loss):
continue
(loss / accum).backward()
total_loss += loss.item()
batch_idx += 1
print('.', end='', flush=True)
if batch_idx % accum == 0:
print(f"[{batch_idx}/{accum}] L:{loss.item():.4f}", end=' ', flush=True)
if batch_idx >= accum:
step += 1
print(f"\n[SYNC] Step {step} β€” applying votes...")
torch.cuda.empty_cache()
# ── The discrete optimizer ────────────────────────────────────────
total_flips = 0
migs, ents, disps, occs, vpos, vneg, vneut, moms = [], [], [], [], [], [], [], []
for m in model.modules():
if isinstance(m, PrimeLinear):
r = m.apply_votes(lr=CONFIG['lr'])
total_flips += r['flips']
migs.append(r['migration_rate'])
ents.append(r['entropy'])
disps.append(r['disp_95'])
occs.append(r.get('occupancy', 0.0))
vpos.append(r.get('vote_pos', 0.0))
vneg.append(r.get('vote_neg', 0.0))
vneut.append(r.get('vote_neut', 0.0))
moms.append(r.get('momentum_mean', 0.0))
# Zero all gradients manually β€” no optimizer.step()
for p in model.parameters():
p.grad = None
mean_mig = sum(migs) / max(len(migs), 1)
mean_ent = sum(ents) / max(len(ents), 1)
mean_disp = sum(disps) / max(len(disps), 1)
mean_occ = sum(occs) / max(len(occs), 1)
mean_vpos = sum(vpos) / max(len(vpos), 1)
mean_vneg = sum(vneg) / max(len(vneg), 1)
mean_vneut= sum(vneut) / max(len(vneut), 1)
mean_mom = sum(moms) / max(len(moms), 1)
tps = (CONFIG['seq_len'] * accum) / max(time.time() - start_time, 1e-6)
avg_loss = total_loss / accum
print(f"[PRIME] Step {step} | Loss: {avg_loss:.4f} | "
f"Mig: {mean_mig*100:.2f}% | Disp95: {mean_disp:.1f} | "
f"Ent: {mean_ent:.2f} | Occ: {mean_occ*100:.1f}% | "
f"V+:{mean_vpos*100:.0f}%/V-:{mean_vneg*100:.0f}% | TPS: {tps:.1f}")
stats = {
'step': step,
'loss': round(avg_loss, 4),
'tps': round(tps, 2),
'migration_rate': round(mean_mig * 100, 4),
'entropy': round(mean_ent, 4),
'disp_95': round(mean_disp, 2),
'flips': total_flips,
'occupancy': round(mean_occ, 4),
'vote_pos': round(mean_vpos, 4),
'vote_neg': round(mean_vneg, 4),
'vote_neut': round(mean_vneut, 4),
'momentum_mean': round(mean_mom, 4),
'timestamp': time.time(),
}
history.append(stats)
with open(CONFIG['stats_file'], 'w') as f:
json.dump(history, f)
if step % 50 == 0:
torch.save({'step': step, 'state_dict': model.state_dict(), 'stats': stats},
f"prime_mamba3_{step}.pt")
print(f"[CKPT] Saved prime_mamba3_{step}.pt")
# ── Word salad generation ─────────────────────────────────────
print(f"[SALAD] Generating at step {step}...")
model.eval()
salad_prompts = [
"### Instruction:\nWrite a Python function to reverse a string.\n### Response:\n",
"### Instruction:\nWhat is a neural network?\n### Response:\n",
"### Instruction:\ndef fibonacci(n):\n### Response:\n",
]
salad_texts = []
with torch.no_grad():
for prompt in salad_prompts:
try:
p_ids = tokenizer(prompt, return_tensors='pt').input_ids.to(CONFIG['device'])
gen = model.generate(
p_ids, max_new_tokens=80,
temperature=0.8, do_sample=True,
pad_token_id=tokenizer.eos_token_id
) if hasattr(model, 'generate') else None
if gen is not None:
text = tokenizer.decode(gen[0][p_ids.shape[1]:], skip_special_tokens=True)
salad_texts.append(text)
else:
# Manual greedy decode fallback
inp = p_ids
for _ in range(80):
logits, _ = model(inp)
next_tok = logits[:, -1, :].div(0.8).softmax(-1).multinomial(1)
inp = torch.cat([inp, next_tok], dim=1)
if next_tok.item() == tokenizer.eos_token_id:
break
text = tokenizer.decode(inp[0][p_ids.shape[1]:], skip_special_tokens=True)
salad_texts.append(text)
except Exception as e:
salad_texts.append(f'[gen error: {e}]')
model.train()
salad_entry = {
'step': step,
'text': ' | '.join(salad_texts),
'prompts': salad_prompts,
'samples': salad_texts,
}
# Load existing samples, append, save last 20
try:
with open(CONFIG['samples_file']) as sf:
all_salads = json.load(sf)
except Exception:
all_salads = []
all_salads.append(salad_entry)
with open(CONFIG['samples_file'], 'w') as sf:
json.dump(all_salads[-20:], sf)
print(f"[SALAD] Step {step}: {salad_texts[0][:120]}")
batch_idx = 0
total_loss = 0.0
start_time = time.time()
torch.cuda.empty_cache()
if step >= CONFIG['total_steps']:
break
print("[PRIME] Training complete.")