E13: real-weight model speciation — the Git Re-Basin residual confirms E12

The real-weight image of E12, and the answer to the mode-connectivity reviewer.
Small no-BN MLPs on MNIST, forked from a shared base and trained independently,
are weight-averaged; we measure the linear-mode-connectivity barrier before and
after in-house deterministic Git Re-Basin permutation alignment (neural/rebasin.py,
scipy linear_sum_assignment), decomposing it into removable (coordinate artefact)
and residual (reproductive isolation). kind: speciation_real.

Result (3 reps):
- shared (same task, shared fork): no barrier — trivially mergeable.
- independent (same task, different init): naive 0.056, alignment removes 98%
  (residual 0.001) — the incompatibility is a coordinate artefact.
- conflict (conflicting label maps): naive 0.496, alignment removes 0% (residual
  0.496) — genuine reproductive isolation. Because alignment demonstrably works on
  the independent case, the conflict residual is real, not a failure to align.
- Isolation cliff (speciation_real_cliff): residual rises 0.00->0.13->0.19->0.28->
  0.40->0.49 with the fraction of conflicting classes — the real-weight mirror of
  E12's cliff; residual==naive throughout (functional, not coordinate).

rebasin.py sanity-gated (recovers a known permutation exactly). plot_speciation_real.py
(2-panel), +4 pure-NumPy tests (142 green), README with honest positioning vs
Git Re-Basin / Entezari / Frankle / Pari 2024 / Zhou 2026. Wired into make mnist
(needs torchvision).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-07-09 00:19:04 +01:00
parent 56f642e7f9
commit 01d87e504f
15 changed files with 620 additions and 3 deletions

66
tests/test_rebasin.py Normal file
View file

@ -0,0 +1,66 @@
"""Tests for E13's Git Re-Basin weight-matching + barrier (pure NumPy, always runnable)."""
from __future__ import annotations
import numpy as np
import pytest
from neural.rebasin import apply_perms, barrier, interpolate, weight_matching
def _mlp(sizes, rng):
return [(rng.standard_normal((sizes[i + 1], sizes[i])), rng.standard_normal(sizes[i + 1]))
for i in range(len(sizes) - 1)]
def _forward(params, X):
h = X
for i, (W, b) in enumerate(params):
h = h @ W.T + b
if i < len(params) - 1:
h = np.maximum(h, 0)
return h
def test_weight_matching_recovers_a_known_permutation():
# A random-but-functionally-identical permuted copy is in a different "basis"; weight matching must
# recover the permutation, so realigning makes the copy functionally identical to the original again.
rng = np.random.default_rng(0)
A = _mlp([8, 16, 16, 3], rng)
p1, p2 = rng.permutation(16), rng.permutation(16)
B = apply_perms(A, [p1, p2]) # functionally identical to A, permuted basis
X = rng.standard_normal((32, 8))
assert np.allclose(_forward(A, X), _forward(B, X)) # permutation preserves the function
perms = weight_matching(A, B, np.random.default_rng(1))
B_realigned = apply_perms(B, perms)
assert np.allclose(_forward(A, X), _forward(B_realigned, X), atol=1e-6) # recovered -> function matches
def test_weight_matching_is_deterministic():
rng = np.random.default_rng(2)
A, B = _mlp([6, 10, 4], rng), _mlp([6, 10, 4], rng)
p1 = weight_matching(A, B, np.random.default_rng(3))
p2 = weight_matching(A, B, np.random.default_rng(3))
assert all(np.array_equal(a, b) for a, b in zip(p1, p2)) # deterministic given inputs + seed
def test_interpolate_endpoints_and_barrier_zero_for_identical():
rng = np.random.default_rng(4)
A = _mlp([5, 8, 2], rng)
B = _mlp([5, 8, 2], rng)
assert np.allclose(interpolate(A, B, 0.0)[0][0], A[0][0])
assert np.allclose(interpolate(A, B, 1.0)[0][0], B[0][0])
X = rng.standard_normal((20, 5)); tgt = rng.standard_normal((20, 2))
def loss_fn(P):
L = float(((_forward(P, X) - tgt) ** 2).mean()); return L, L
b = barrier(A, A, loss_fn) # a model with itself: no barrier
assert b["loss_barrier"] == pytest.approx(0.0, abs=1e-9)
def test_apply_perms_preserves_function():
rng = np.random.default_rng(5)
A = _mlp([4, 7, 7, 3], rng)
perms = [rng.permutation(7), rng.permutation(7)]
X = rng.standard_normal((16, 4))
assert np.allclose(_forward(A, X), _forward(apply_perms(A, perms), X))