Hashing lever2_sparse: smoke SP + compare.py fix (Checker PASS 03h13, add-only)
Browse files- results/hashing/lever2_sparse/code/chain_sp.sh +30 -0
- results/hashing/lever2_sparse/code/run_sp.py +37 -0
- results/hashing/lever2_sparse/code/smoke_sp.py +56 -0
- results/hashing/lever2_sparse/code/sparse.py +80 -0
- results/hashing/lever2_sparse/code/verdict_SP.py +103 -0
- results/hashing/lever2_sparse/compare_fix/compare.py +31 -0
- results/hashing/lever2_sparse/compare_fix/test_compare_mcnemar.py +12 -0
- results/hashing/lever2_sparse/smoke/smoke_sp.json +171 -0
- results/hashing/lever2_sparse/smoke/smoke_sp.log.txt +14 -0
results/hashing/lever2_sparse/code/chain_sp.sh
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# lever2_sparse — chaîne SP1 puis SP2 (SPIRA Hashing, 2026-10-24). N'écrit JAMAIS dans spira_relay.md (brouillon seulement), ne tue/signale rien,
|
| 3 |
+
# rien dans /tmp. Porte par bras : MemAvailable >= 5,2e9 o ET lourds < 4 (lourd = process python, tous agents, RSS >= 300 Mo), sondage 120 s.
|
| 4 |
+
B=/workspace/spira_shared/hashing_gen/lever2_sparse; C=$B/code; ST=$B/STATUS; PY=/workspace/SPIRA-venv/bin/python
|
| 5 |
+
ts(){ TZ=America/Toronto date '+%F %T %Z'; }
|
| 6 |
+
memb(){ awk '/MemAvailable/{printf "%d", $2*1024}' /proc/meminfo; }
|
| 7 |
+
heavy(){ ps -eo rss=,comm= | awk '$1>=300000 && $2 ~ /python/ {n++} END{print n+0}'; }
|
| 8 |
+
heavy_list(){ ps -eo pid=,rss=,comm= | awk '$2>=300000 && $3 ~ /python/ {printf "%s ", $1}'; }
|
| 9 |
+
export OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 PYTHONHASHSEED=0
|
| 10 |
+
SMOKE_PID=$1
|
| 11 |
+
while [ -n "$SMOKE_PID" ] && [ -d /proc/$SMOKE_PID ]; do sleep 30; done
|
| 12 |
+
if ! $PY -c "import json,sys; sys.exit(0 if json.load(open('$B/smoke/smoke_sp.json'))['PASS'] else 1)" 2>/dev/null; then
|
| 13 |
+
echo "$(ts) | smoke test NON PASS ou absent : chaîne ARRÊTÉE, aucun bras lancé" >> $ST; exit 2; fi
|
| 14 |
+
echo "$(ts) | smoke PASS ; chaîne armée (PID $$), attente porte pour SP1" >> $ST
|
| 15 |
+
for spec in "SP1 SP1" "SP2 SP2"; do set -- $spec; ARM=$1; MODE=$2; last=""
|
| 16 |
+
while :; do m=$(memb); h=$(heavy)
|
| 17 |
+
if [ "$m" -ge 5200000000 ] && [ "$h" -lt 4 ]; then break; fi
|
| 18 |
+
s="mem $(awk -v m=$m 'BEGIN{printf "%.2f", m/1e9}') Go, lourds $h ($(heavy_list))"
|
| 19 |
+
echo "$(ts) $ARM porte fermée : $s" >> $B/logs/gate.log
|
| 20 |
+
st="$([ "$m" -ge 5200000000 ] && echo memOK || echo memKO)/$h"; [ "$st" != "$last" ] && echo "$(ts) | $ARM attente porte : $s" >> $ST; last=$st
|
| 21 |
+
sleep 120; done
|
| 22 |
+
setsid nice -n 10 $PY -u $C/run_sp.py $ARM $MODE 24000 4000 $B/short_old > $B/short_old.$ARM.log 2>&1 < /dev/null &
|
| 23 |
+
P=$!; echo "$(ts) | $ARM lancé PID $P (mem $(awk -v m=$(memb) 'BEGIN{printf "%.2f", m/1e9}') Go, lourds avant lancement $h : $(heavy_list)), ETA ~50-80 min" >> $ST
|
| 24 |
+
while [ -d /proc/$P ]; do sleep 60; done
|
| 25 |
+
n=$(grep -c DONE $B/short_old.$ARM.log); echo "$(ts) | $ARM fini, DONE=$n" >> $ST
|
| 26 |
+
if [ "$n" = 1 ]; then
|
| 27 |
+
nice -n 10 $PY $C/verdict_SP.py $ARM > $B/logs/verdict_$ARM.log 2>&1; echo "$(ts) | verdict $ARM rc=$? : $(grep -m1 -o '\*\*[A-ZÉ]*\*\*.*' $B/verdict_$ARM/verdict_$ARM.md 2>/dev/null)" >> $ST
|
| 28 |
+
else echo "$(ts) | $ARM : run incomplet, pas de verdict (voir short_old.$ARM.log)" >> $ST; fi
|
| 29 |
+
done
|
| 30 |
+
echo "$(ts) | chaîne SP terminée ; relais NON écrit automatiquement (à rédiger à la main)" >> $ST
|
results/hashing/lever2_sparse/code/run_sp.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""lever2_sparse (SPIRA Hashing 2026-10-24) : copie de lever2/code/run_l2.py ; SEULS changements : antihebb.install(core, eta) -> sparse.install(core, MODE)
|
| 2 |
+
(MODE none|SP1|SP2, voir sparse.py) et mesure du taux de décharge (champ "SP" au lieu de "Q"). Tout le reste (données, cfg, snn_tokens, éval, porte mémoire) inchangé.
|
| 3 |
+
Usage: run_sp.py ARM MODE SNN_TOKENS MAXPOS OUTDIR. Short lever-2 run on old_local_v1 (CMSPIRA = v3 + Count-Min, repo default cfg),
|
| 4 |
+
only snn_tokens reduced (equal for all arms). Train in-process, calibrate_fusion(train[:60000]), eval_hits (exec'd verbatim from checker/eval_ref_v3.py)."""
|
| 5 |
+
import sys, os, json, time, hashlib, resource, dataclasses
|
| 6 |
+
L = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, f"{L}/python"); sys.path.insert(0, L); sys.path.insert(0, "/workspace/spira_shared/checker")
|
| 7 |
+
import numpy as np
|
| 8 |
+
from spira.hashing import fnv1a64
|
| 9 |
+
from countmin import CMSPIRA, SPIRAConfig
|
| 10 |
+
import sparse
|
| 11 |
+
arm, mode, SNN, MAXPOS, OD = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4]), sys.argv[5]; os.makedirs(OD, exist_ok=True)
|
| 12 |
+
# porte mémoire (23 h 53) : marge utilisateur 4 Go + plancher Relay 4,5 Go ; RSS du bras ~0,65 Go -> attendre MemAvailable >= 5,2 Go (décimal) avant de charger
|
| 13 |
+
def _mem(): return int([l for l in open("/proc/meminfo") if l.startswith("MemAvailable")][0].split()[1])*1024
|
| 14 |
+
while _mem() < 5.2e9:
|
| 15 |
+
print(f"porte mémoire : MemAvailable {_mem()/1e9:.2f} Go < 5,2 Go, attente 60 s", flush=True); time.sleep(60)
|
| 16 |
+
t0 = time.perf_counter(); log = lambda s: print(f"[{time.perf_counter()-t0:7.0f}s] {s}", flush=True)
|
| 17 |
+
src = open("/workspace/spira_shared/checker/eval_ref_v3.py").read(); g = {"np": np, "fnv1a64": fnv1a64}
|
| 18 |
+
exec(src[src.index("def eval_hits"):src.index("m = SPIRA.load(ckpt)")], g); eval_hits = g["eval_hits"]
|
| 19 |
+
train = open("/workspace/spira_shared/broad/old_local_v1/train.bin", "rb").read()
|
| 20 |
+
cfg = dataclasses.replace(SPIRAConfig(), snn_tokens=SNN)
|
| 21 |
+
m = CMSPIRA.build_cm(train, cfg, log=log); sparse.install(m.core, mode)
|
| 22 |
+
m.prepare(); h = m.train(log_fn=log)
|
| 23 |
+
cal = m.calibrate_fusion(train[:60_000]); log(f"cal {cal} beta={m.core.beta}")
|
| 24 |
+
sp_cal = sparse.stats(m.core, reset_eval=True); log(f"SP après train+calibration {json.dumps(sp_cal)}")
|
| 25 |
+
W = "/workspace/spira_shared/spira_m7/windows"
|
| 26 |
+
from windows_v2 import freshcode_v2
|
| 27 |
+
wins = {"seen": train[:200_000], "fc2": freshcode_v2(), **{k: open(f"{W}/{k}.bin","rb").read() for k in ("fresh_en","fresh_fr","test20k")}}
|
| 28 |
+
for name, data in wins.items():
|
| 29 |
+
t1 = time.perf_counter(); r, H = eval_hits(m, data, max_positions=MAXPOS); spw = sparse.stats(m.core, reset_eval=True)
|
| 30 |
+
pl = np.fromiter((len(p) for p in m.ept.segment(data)), np.int64); off = np.concatenate([[0], np.cumsum(pl)]); n = len(H["fused_hit"])
|
| 31 |
+
np.savez_compressed(f"{OD}/{arm}__{name}.npz", **H, tgt_start=off[1:n+1], tgt_len=pl[1:n+1], window_sha256=np.array(hashlib.sha256(data).hexdigest()))
|
| 32 |
+
rec = {"arm": arm, "mode": mode, "snn_tokens": SNN, "window": name, "n": n, "fused_invocab_pct": round(100*r["fused_acc_invocab"],3),
|
| 33 |
+
"fused_all_pct": round(100*r["fused_acc"],3), "snn_invocab_pct": round(100*r["snn_acc_invocab"],3), "cal": cal, "SP_train_cal": sp_cal, "SP_window": spw,
|
| 34 |
+
"train_acc": [e.get("accuracy") for e in h["epochs"]], "eval_s": round(time.perf_counter()-t1,1),
|
| 35 |
+
"peak_rss_mb": round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024,1)}
|
| 36 |
+
open(f"{OD}/results.jsonl","a").write(json.dumps(rec)+"\n"); log(json.dumps(rec))
|
| 37 |
+
log("DONE")
|
results/hashing/lever2_sparse/code/smoke_sp.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Smoke test lever2_sparse (SPIRA Hashing 2026-10-24) : quelques centaines de ticks, mesure du taux de décharge.
|
| 2 |
+
Même construction que run_sp.py (old_local_v1, CMSPIRA cfg repo, porte mémoire 5,2 Go), snn_tokens=700 (smoke seulement).
|
| 3 |
+
Variantes sur des copies du MÊME cœur initial : ah0 (antihebb.install eta=0 = témoin), none, SP1, SP2.
|
| 4 |
+
- train_pass sur 400 ticks (apprentissage actif), puis 300 ticks de core.step hors train_pass (régime éval, theta figé).
|
| 5 |
+
- vérifie : none == ah0 bit à bit (spikes par tick, W_out, W_rec, thr) ; SP1 exactement k=51 spikes/tick si >=51 éligibles ;
|
| 6 |
+
SP2 theta bouge en train, inchangé en éval. Écrit smoke/smoke_sp.json."""
|
| 7 |
+
import sys, os, json, time, copy, resource, dataclasses
|
| 8 |
+
L = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, f"{L}/python"); sys.path.insert(0, L)
|
| 9 |
+
import numpy as np
|
| 10 |
+
from spira.hashing import fnv1a64
|
| 11 |
+
from countmin import CMSPIRA, SPIRAConfig
|
| 12 |
+
import antihebb, sparse
|
| 13 |
+
def _mem(): return int([l for l in open("/proc/meminfo") if l.startswith("MemAvailable")][0].split()[1])*1024
|
| 14 |
+
while _mem() < 5.2e9:
|
| 15 |
+
print(f"porte mémoire : MemAvailable {_mem()/1e9:.2f} Go < 5,2 Go, attente 120 s", flush=True); time.sleep(120)
|
| 16 |
+
print(f"porte ouverte : MemAvailable {_mem()/1e9:.2f} Go", flush=True)
|
| 17 |
+
t0 = time.perf_counter(); log = lambda s: print(f"[{time.perf_counter()-t0:7.0f}s] {s}", flush=True)
|
| 18 |
+
OD = sys.argv[1]; NT, NE = 400, 300
|
| 19 |
+
train = open("/workspace/spira_shared/broad/old_local_v1/train.bin", "rb").read()
|
| 20 |
+
cfg = dataclasses.replace(SPIRAConfig(), snn_tokens=NT + NE)
|
| 21 |
+
m = CMSPIRA.build_cm(train, cfg, log=log); m.prepare(); del train
|
| 22 |
+
neu, cols = m._neurons, m._cols; base = copy.deepcopy(m.core); del m
|
| 23 |
+
out = {"NT_train_ticks": NT, "NE_eval_ticks": NE, "variants": {}}; ras = {}; fin = {}
|
| 24 |
+
for var in ("ah0", "none", "SP1", "SP2"):
|
| 25 |
+
c = copy.deepcopy(base)
|
| 26 |
+
if var == "ah0": antihebb.install(c, 0.0)
|
| 27 |
+
else: sparse.install(c, var)
|
| 28 |
+
rec = []; st = c.step
|
| 29 |
+
def step(idx, st=st, rec=rec, c=c):
|
| 30 |
+
x = st(idx); rec.append((c.s > 0).copy()); return x
|
| 31 |
+
c.step = step
|
| 32 |
+
c.train_pass(neu[:NT], cols[1:NT + 1], log_every=10**9)
|
| 33 |
+
th_after_train = c.theta.copy() if var in ("none", "SP1", "SP2") else None
|
| 34 |
+
for t in range(NT, NT + NE): c.step(neu[t])
|
| 35 |
+
S = np.array(rec); ras[var] = S; fin[var] = (c.W_out.copy(), c.W_rec.copy(), c.thr.copy())
|
| 36 |
+
per = S.sum(1); tr, ev = per[:NT], per[NT:]
|
| 37 |
+
d = {"rate_train": float(tr.mean() / c.n_res), "rate_eval": float(ev.mean() / c.n_res),
|
| 38 |
+
"spikes_per_tick_train": [int(tr.min()), float(np.median(tr)), int(tr.max())], "spikes_per_tick_eval": [int(ev.min()), float(np.median(ev)), int(ev.max())],
|
| 39 |
+
"rate_train_last100": float(tr[-100:].mean() / c.n_res)}
|
| 40 |
+
if var != "ah0":
|
| 41 |
+
d["sp_stats"] = sparse.stats(c)
|
| 42 |
+
d["theta_changed_in_train"] = bool(np.any(th_after_train != 0)); d["theta_unchanged_in_eval"] = bool(np.array_equal(th_after_train, c.theta))
|
| 43 |
+
d["theta_after_train"] = [float(th_after_train.mean()), float(th_after_train.min()), float(th_after_train.max())]
|
| 44 |
+
if var == "SP1": d["ticks_not_exactly_k"] = int((per != c.sp_k).sum())
|
| 45 |
+
out["variants"][var] = d; log(f"{var} {json.dumps(d)}"); del c
|
| 46 |
+
out["parity_none_vs_ah0"] = {"spikes_identical": bool(np.array_equal(ras["none"], ras["ah0"])),
|
| 47 |
+
"W_out_identical": bool(np.array_equal(fin["none"][0], fin["ah0"][0])), "W_rec_identical": bool(np.array_equal(fin["none"][1], fin["ah0"][1])),
|
| 48 |
+
"thr_identical": bool(np.array_equal(fin["none"][2], fin["ah0"][2]))}
|
| 49 |
+
v = out["variants"]
|
| 50 |
+
out["checks"] = {"parity": all(out["parity_none_vs_ah0"].values()),
|
| 51 |
+
"SP1_exact_k_every_tick": v["SP1"]["ticks_not_exactly_k"] == 0,
|
| 52 |
+
"SP2_theta_learns_in_train_frozen_in_eval": v["SP2"]["theta_changed_in_train"] and v["SP2"]["theta_unchanged_in_eval"],
|
| 53 |
+
"none_theta_zero": v["none"]["theta_after_train"] == [0.0, 0.0, 0.0]}
|
| 54 |
+
out["PASS"] = all(out["checks"].values()); out["peak_rss_mb"] = round(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024, 1)
|
| 55 |
+
json.dump(out, open(f"{OD}/smoke_sp.json", "w"), indent=1); log(json.dumps({"checks": out["checks"], "PASS": out["PASS"], "peak_rss_mb": out["peak_rss_mb"]}))
|
| 56 |
+
sys.exit(0 if out["PASS"] else 3)
|
results/hashing/lever2_sparse/code/sparse.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Lever 2 — bras SPARSITÉ (SPIRA Hashing, 2026-10-24). Règles LOCALES, zéro gradient, aucune perte globale.
|
| 2 |
+
y = vecteur de spikes s (0/1) de la couche spiking (réservoir LIF, N = n_res = 512) à chaque tick.
|
| 3 |
+
mode "none" : step IDENTIQUE à antihebb.install(core, 0) (= témoin eta=0 du levier 2) — vérifié bit à bit au smoke test.
|
| 4 |
+
mode "SP1" : k-WTA, k = round(0.1*N) = 51. Parmi les neurones non réfractaires (refr == 0), les k de plus grande marge v - thr
|
| 5 |
+
émettent un spike (exactement k si >= k éligibles ; tri stable, départage par indice), les autres n'émettent pas
|
| 6 |
+
(leur potentiel v n'est pas remis à zéro). Le seuil v >= thr n'est plus utilisé pour décider du spike (thr reste mis à jour
|
| 7 |
+
par l'homéostasie du dépôt, inchangée, et sert au classement). Actif en entraînement ET en éval (contrainte d'architecture,
|
| 8 |
+
aucun paramètre appris).
|
| 9 |
+
mode "SP2" : seuil adaptatif homéostatique additionnel theta_i : spike ssi v >= thr + theta et non réfractaire ;
|
| 10 |
+
theta_i += eta_theta * (s_i - rho), rho = 0.1, eta_theta = 1e-3, theta init 0, non borné ; appris UNIQUEMENT dans
|
| 11 |
+
train_pass (comme Q des bras AH), figé en calibration/éval. L'homéostasie du dépôt (thr, cible 0,06) reste inchangée.
|
| 12 |
+
Compteurs (mesure du taux de décharge) : spikes et ticks séparés entraînement / hors entraînement."""
|
| 13 |
+
import numpy as np
|
| 14 |
+
def install(core, mode, rho=0.1, eta_theta=1e-3):
|
| 15 |
+
assert mode in ("none", "SP1", "SP2")
|
| 16 |
+
core.sp_mode = mode; core.sp_rho = np.float32(rho); core.sp_eta = np.float32(eta_theta)
|
| 17 |
+
core.sp_k = int(round(rho * core.n_res)); core.theta = np.zeros(core.n_res, np.float32); core.sp_learn = False
|
| 18 |
+
core.sp_cnt = {"train": [0, 0], "eval": [0, 0]} # [spikes, ticks]
|
| 19 |
+
core.sp_kmin = {"train": core.n_res, "eval": core.n_res}; core.sp_kmax = {"train": 0, "eval": 0}
|
| 20 |
+
orig_train_pass = core.train_pass
|
| 21 |
+
def train_pass(*a, **k):
|
| 22 |
+
core.sp_learn = True
|
| 23 |
+
try: return orig_train_pass(*a, **k)
|
| 24 |
+
finally: core.sp_learn = False
|
| 25 |
+
core.train_pass = train_pass
|
| 26 |
+
def step(input_idx):
|
| 27 |
+
self = core
|
| 28 |
+
I = self.W_in[input_idx].sum(axis=0, dtype=np.float32)
|
| 29 |
+
I += self.s @ self.W_rec
|
| 30 |
+
v = self.lam * self.v + I
|
| 31 |
+
can = self.refr == 0
|
| 32 |
+
v[~can] = 0.0
|
| 33 |
+
if self.sp_mode == "SP1":
|
| 34 |
+
marg = np.where(can, v - self.thr, -np.inf)
|
| 35 |
+
win = np.argsort(-marg, kind="stable")[:self.sp_k]
|
| 36 |
+
spikes = np.zeros(self.n_res, np.bool_); spikes[win] = True; spikes &= can
|
| 37 |
+
elif self.sp_mode == "SP2":
|
| 38 |
+
spikes = (v >= self.thr + self.theta) & can
|
| 39 |
+
else:
|
| 40 |
+
spikes = (v >= self.thr) & can
|
| 41 |
+
v[spikes] = 0.0
|
| 42 |
+
np.subtract(self.refr, 1, out=self.refr, where=self.refr > 0)
|
| 43 |
+
self.refr[spikes] = self.refrac
|
| 44 |
+
self.v = v
|
| 45 |
+
self.s = spikes.astype(np.float32)
|
| 46 |
+
self.rhat += 0.02 * (self.s - self.rhat)
|
| 47 |
+
self.thr += self.eta_homeo * (self.rhat - self.target_rate)
|
| 48 |
+
np.clip(self.thr, self.thr_min, self.thr_max, out=self.thr)
|
| 49 |
+
if self.sp_mode == "SP2" and self.sp_learn:
|
| 50 |
+
self.theta += self.sp_eta * (self.s - self.sp_rho)
|
| 51 |
+
ns = int(spikes.sum()); ph = "train" if self.sp_learn else "eval"; c = self.sp_cnt[ph]; c[0] += ns; c[1] += 1
|
| 52 |
+
if ns < self.sp_kmin[ph]: self.sp_kmin[ph] = ns
|
| 53 |
+
if ns > self.sp_kmax[ph]: self.sp_kmax[ph] = ns
|
| 54 |
+
self.x_prev = self._banks[0].copy()
|
| 55 |
+
for i, tau in enumerate(self.trace_taus):
|
| 56 |
+
if i == 0: self._banks[i] = tau * self._banks[i] + self.s
|
| 57 |
+
else: self._banks[i] = tau * self._banks[i] + (1.0 - tau) * self.s
|
| 58 |
+
if self.direct_input:
|
| 59 |
+
self._direct *= np.float32(self.direct_tau)
|
| 60 |
+
np.add.at(self._direct, input_idx, np.float32(self.direct_gain))
|
| 61 |
+
self.x = np.concatenate([*self._banks, self._direct])
|
| 62 |
+
elif self.n_banks > 1: self.x = np.concatenate([*self._banks])
|
| 63 |
+
else: self.x = self._banks[0]
|
| 64 |
+
self.x_mean = 0.999 * self.x_mean + 0.001 * self.x
|
| 65 |
+
self.x_eff = self.x - self.x_mean
|
| 66 |
+
self.tick += 1
|
| 67 |
+
if self.hier: self.win_spikes += self.s
|
| 68 |
+
return self.x
|
| 69 |
+
core.step = step
|
| 70 |
+
def stats(core, reset_eval=False):
|
| 71 |
+
th = core.theta
|
| 72 |
+
out = {"mode": core.sp_mode, "k": core.sp_k, "N": int(core.n_res),
|
| 73 |
+
"rate_train": (core.sp_cnt["train"][0] / (core.sp_cnt["train"][1] * core.n_res)) if core.sp_cnt["train"][1] else None,
|
| 74 |
+
"ticks_train": core.sp_cnt["train"][1], "spikes_per_tick_train_min_max": [core.sp_kmin["train"], core.sp_kmax["train"]],
|
| 75 |
+
"rate_eval": (core.sp_cnt["eval"][0] / (core.sp_cnt["eval"][1] * core.n_res)) if core.sp_cnt["eval"][1] else None,
|
| 76 |
+
"ticks_eval": core.sp_cnt["eval"][1], "spikes_per_tick_eval_min_max": [core.sp_kmin["eval"], core.sp_kmax["eval"]],
|
| 77 |
+
"theta_mean": float(th.mean()), "theta_min": float(th.min()), "theta_max": float(th.max()),
|
| 78 |
+
"thr_mean": float(core.thr.mean()), "thr_at_max_frac": float((core.thr >= core.thr_max).mean()), "thr_at_min_frac": float((core.thr <= core.thr_min).mean())}
|
| 79 |
+
if reset_eval: core.sp_cnt["eval"] = [0, 0]; core.sp_kmin["eval"] = core.n_res; core.sp_kmax["eval"] = 0
|
| 80 |
+
return out
|
results/hashing/lever2_sparse/code/verdict_SP.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""SPIRA Hashing — verdict levier 2 SPARSITÉ (SP1/SP2, argument 1) — copie de lever2/verdict_AH1/verdict_AH1.py, SEULS changements :
|
| 3 |
+
bras candidat lu dans lever2_sparse/short_old, sortie dans lever2_sparse/verdict_<ARM>, libellés. Témoin, masque et protocole identiques.
|
| 4 |
+
(ex-docstring AH1) verdict levier 2 AH1 (eta_ah=1e-3) contre le témoin à budget égal ref (eta_ah=0), méthode banc v4.1
|
| 5 |
+
(copie adaptée de m7_v41/strict/verdict_m7_v41_strict.py ; lecture seule des hits short_old/{ref,AH1}__*.npz, aucun modèle chargé).
|
| 6 |
+
Fenêtres MESURÉES par run_l2.py (4000 positions chacune) : seen (= old_local_v1/train[:200000]), fc2, fresh_en, fresh_fr, test20k
|
| 7 |
+
(= old_local_v1/test.bin, sha ce0526c6…, PAS broad_local_v1/test.bin ; masque strict propre recalculé : verdict_AH1/mask_oldtest_strict.npz,
|
| 8 |
+
union old_local_v1 ∪ broad_local_v1). queries et test_v3 : NON évalués par les bras → absents (non mesurables). neither_code exclu.
|
| 9 |
+
hit = fused_hit & invocab. Appariement par position si tgt_start et tgt_len identiques (vérifié). McNemar exact bilatéral = DÉCISION (p<0,05).
|
| 10 |
+
Bootstrap par blocs de 512 o (bloc = tgt_start // 512), B = 10 000, un générateur seed 0, fenêtres dans l'ordre WIN, apparié ; stat = acc invocab.
|
| 11 |
+
Écart seen − moyenne fraîche (fc2, fresh_en, fresh_fr, test20k strict ; test_v3 non mesuré). Bootstrap secondaire par positions, seed 25.
|
| 12 |
+
Critère utilisateur : ADOPTÉ ssi gain significatif sur seen ET sur >=1 fenêtre fraîche ET aucune perte significative sur une fenêtre valide ; sinon REJETÉ."""
|
| 13 |
+
import sys, os, json, math, hashlib, numpy as np
|
| 14 |
+
D = "/workspace/spira_shared/hashing_gen/lever2/short_old"; RT, CT = "ref", sys.argv[1]; assert CT in ("SP1", "SP2")
|
| 15 |
+
DC = "/workspace/spira_shared/hashing_gen/lever2_sparse/short_old"; OP = f"/workspace/spira_shared/hashing_gen/lever2_sparse/verdict_{CT}"
|
| 16 |
+
MASKP = "/workspace/spira_shared/hashing_gen/lever2/verdict_AH1/mask_oldtest_strict.npz"
|
| 17 |
+
DESC = {'SP1': 'SP1 (k-WTA k=51 sur les spikes, N=512, rho=0.1)', 'SP2': 'SP2 (seuil adaptatif theta += 1e-3*(s-0.1), theta0=0, figé en éval)'}[CT]
|
| 18 |
+
WIN = ["seen", "fc2", "fresh_en", "fresh_fr", "test20k"]; FRESH = ["fc2", "fresh_en", "fresh_fr", "test20k"]; GAPW = FRESH
|
| 19 |
+
NOT_MEASURED = ["queries", "test_v3"]; BLK, NB, SEED = 512, 10000, 0
|
| 20 |
+
os.makedirs(OP, exist_ok=True); MS = np.load(MASKP)
|
| 21 |
+
def binp(b, c):
|
| 22 |
+
n = b + c
|
| 23 |
+
return 1.0 if n == 0 else min(1.0, 2 * sum(math.comb(n, i) for i in range(min(b, c) + 1)) / 2 ** n)
|
| 24 |
+
def blocksums(st, h, iv, nbk):
|
| 25 |
+
b = st // BLK; return np.bincount(b, weights=h & iv, minlength=nbk), np.bincount(b, weights=iv, minlength=nbk)
|
| 26 |
+
R = {}; RNG = np.random.default_rng(SEED); BOOT = {}; KEEP = {}; EX = {}
|
| 27 |
+
for w in WIN:
|
| 28 |
+
A = np.load(f"{D}/{RT}__{w}.npz"); B = np.load(f"{DC}/{CT}__{w}.npz")
|
| 29 |
+
assert str(A["window_sha256"]) == str(B["window_sha256"]), f"{w}: octets différents"
|
| 30 |
+
na, nb_ = len(A["fused_hit"]), len(B["fused_hit"])
|
| 31 |
+
same_tok = na == nb_ and np.array_equal(A["tgt_start"], B["tgt_start"]) and np.array_equal(A["tgt_len"], B["tgt_len"])
|
| 32 |
+
assert same_tok, f"{w}: tokenisation différente, appariement par position impossible"
|
| 33 |
+
ha, hb = A["fused_hit"].astype(bool) & A["invocab"].astype(bool), B["fused_hit"].astype(bool) & B["invocab"].astype(bool)
|
| 34 |
+
if w == "test20k":
|
| 35 |
+
assert str(MS["sha__test20k"]) == str(A["window_sha256"]), "masque : fenêtre différente"
|
| 36 |
+
bad = lambda cov: np.array([cov[max(0, int(s) - 64):int(s) + int(l)].any() for s, l in zip(A["tgt_start"], A["tgt_len"])])
|
| 37 |
+
bo, bb_ = bad(MS["old_local_v1__covered"]), bad(MS["broad_local_v1__covered"]); k = ~(bo | bb_)
|
| 38 |
+
EX = {"excluded_by_old_local_v1": int(bo.sum()), "excluded_by_broad_local_v1": int(bb_.sum()), "excluded_union": int((bo | bb_).sum()),
|
| 39 |
+
"max_byte_evaluated": int((A["tgt_start"] + A["tgt_len"]).max())}
|
| 40 |
+
else: k = np.ones(na, bool)
|
| 41 |
+
b = int((ha & ~hb & k).sum()); c = int((~ha & hb & k).sum()); npair = int(k.sum())
|
| 42 |
+
inv_same = bool(np.array_equal(A["invocab"][k], B["invocab"][k])); p = binp(b, c)
|
| 43 |
+
ia, ib = A["invocab"].astype(bool) & k, B["invocab"].astype(bool) & k
|
| 44 |
+
acc_a, acc_b = 100 * (ha & k).sum() / ia.sum(), 100 * (hb & k).sum() / ib.sum()
|
| 45 |
+
nbk = int(A["tgt_start"].max() // BLK + 1)
|
| 46 |
+
sa_h, sa_n = blocksums(A["tgt_start"][k], ha[k], A["invocab"].astype(bool)[k], nbk)
|
| 47 |
+
sb_h, sb_n = blocksums(B["tgt_start"][k], hb[k], B["invocab"].astype(bool)[k], nbk)
|
| 48 |
+
nz = (sa_n + sb_n) > 0; sa_h, sa_n, sb_h, sb_n = sa_h[nz], sa_n[nz], sb_h[nz], sb_n[nz]; m = len(sa_h)
|
| 49 |
+
idx = RNG.integers(0, m, (NB, m))
|
| 50 |
+
with np.errstate(invalid="ignore", divide="ignore"):
|
| 51 |
+
ba = 100 * sa_h[idx].sum(1) / sa_n[idx].sum(1); bb = 100 * sb_h[idx].sum(1) / sb_n[idx].sum(1)
|
| 52 |
+
d = bb - ba; BOOT[w] = (ba, bb); KEEP[w] = (k, ha, hb, A["invocab"].astype(bool))
|
| 53 |
+
lo, hi = np.percentile(d, [2.5, 97.5]); pboot = float(min(1.0, 2 * min((d <= 0).mean(), (d >= 0).mean())))
|
| 54 |
+
gain, loss = bool(p < 0.05 and c > b), bool(p < 0.05 and b > c)
|
| 55 |
+
R[w] = {"n_positions": na, "n_clean": npair, "n_invocab_clean_ref": int(ia.sum()), "n_invocab_clean_cand": int(ib.sum()), "invocab_identical": inv_same,
|
| 56 |
+
"pairing": "position (tokenizers identiques, tgt_start/tgt_len vérifiés)", "acc_ref_pct": float(acc_a), "acc_cand_pct": float(acc_b), "delta_pct": float(acc_b - acc_a),
|
| 57 |
+
"b_ref_only": b, "c_cand_only": c, "mcnemar_p": p, "boot_ci95_delta_pct": [float(lo), float(hi)], "boot_p": pboot, "n_blocks": m,
|
| 58 |
+
"sig_gain": gain, "sig_loss": loss, "boot_conflict": bool((gain and hi < 0) or (loss and lo > 0)), "window_sha256": str(A["window_sha256"])}
|
| 59 |
+
if w == "test20k": R[w]["strict_mask"] = EX
|
| 60 |
+
print(w, json.dumps(R[w]), flush=True)
|
| 61 |
+
gap = {}
|
| 62 |
+
for side, j in (("ref", 0), ("cand", 1)):
|
| 63 |
+
fm = np.mean([BOOT[w][j] for w in GAPW], axis=0); g = BOOT["seen"][j] - fm
|
| 64 |
+
pt = R["seen"]["acc_%s_pct" % side] - np.mean([R[w]["acc_%s_pct" % side] for w in GAPW])
|
| 65 |
+
gap[side] = {"fresh_mean_pct": float(np.mean([R[w]["acc_%s_pct" % side] for w in GAPW])), "seen_pct": R["seen"]["acc_%s_pct" % side], "gap_pct": float(pt), "ci95": [float(x) for x in np.nanpercentile(g, [2.5, 97.5])]}
|
| 66 |
+
dg = (BOOT["seen"][1] - np.mean([BOOT[w][1] for w in GAPW], axis=0)) - (BOOT["seen"][0] - np.mean([BOOT[w][0] for w in GAPW], axis=0))
|
| 67 |
+
gap["cand_minus_ref"] = {"gap_diff_pct": gap["cand"]["gap_pct"] - gap["ref"]["gap_pct"], "ci95": [float(x) for x in np.nanpercentile(dg, [2.5, 97.5])]}
|
| 68 |
+
R2 = np.random.default_rng(25); BP = {}
|
| 69 |
+
for w in WIN:
|
| 70 |
+
k, ha_, hb_, iv_ = KEEP[w]; m_ = k & iv_; r_ = ha_[m_].astype(np.float64); c_ = hb_[m_].astype(np.float64); n_ = len(r_); ar = np.empty(NB); ac = np.empty(NB)
|
| 71 |
+
for t in range(NB):
|
| 72 |
+
i = R2.integers(0, n_, n_); ar[t] = r_[i].mean(); ac[t] = c_[i].mean()
|
| 73 |
+
BP[w] = (100 * ar, 100 * ac)
|
| 74 |
+
dgp = (BP["seen"][1] - np.mean([BP[w][1] for w in GAPW], axis=0)) - (BP["seen"][0] - np.mean([BP[w][0] for w in GAPW], axis=0))
|
| 75 |
+
gap["cand_minus_ref"]["ci95_boot_positions_seed25"] = [float(x) for x in np.percentile(dgp, [2.5, 97.5])]
|
| 76 |
+
gap["windows"] = GAPW; gap["note"] = "test_v3 non mesuré par les bras (absent de la moyenne) ; queries non mesuré ; neither_code exclu"
|
| 77 |
+
L = [w for w in WIN if R[w]["sig_loss"]]; G = [w for w in WIN if R[w]["sig_gain"]]; GF = [w for w in FRESH if R[w]["sig_gain"]]
|
| 78 |
+
dec = "ADOPTÉ" if (R["seen"]["sig_gain"] and GF and not L) else "REJETÉ"
|
| 79 |
+
why = []
|
| 80 |
+
if not R["seen"]["sig_gain"]: why.append("pas de gain significatif sur seen")
|
| 81 |
+
if not GF: why.append("aucun gain significatif sur une fenêtre fraîche")
|
| 82 |
+
if L: why.append("perte(s) significative(s) : " + ", ".join(L))
|
| 83 |
+
out = {"cand": DESC, "ref": "ref (eta_ah=0, budget égal)", "corpus": "broad/old_local_v1", "snn_tokens": 24000, "epochs": 2, "max_positions": 4000,
|
| 84 |
+
"windows_measured": WIN, "windows_not_measured": NOT_MEASURED, "excluded": ["neither_code"], "decision": dec, "reasons": why, "losses": L, "gains": G, "fresh_gains": GF,
|
| 85 |
+
"rule": "ADOPTÉ ssi gain significatif (McNemar exact p<0,05) sur seen ET sur >=1 fenêtre fraîche (fc2, fresh_en, fresh_fr, test20k strict) ET aucune perte significative sur une fenêtre valide",
|
| 86 |
+
"bootstrap": {"block_bytes": BLK, "B": NB, "seed": SEED, "stat": "acc invocab fused, blocs = tgt_start//512, apparié"}, "per_window": R, "seen_fresh_gap": gap,
|
| 87 |
+
"script_sha256": hashlib.sha256(open(__file__, "rb").read()).hexdigest(),
|
| 88 |
+
"inputs_sha256": {f"{a}__{w}.npz": hashlib.sha256(open(f"{D if a == RT else DC}/{a}__{w}.npz", "rb").read()).hexdigest() for a in (RT, CT) for w in WIN},
|
| 89 |
+
"mask_sha256": hashlib.sha256(open(MASKP, "rb").read()).hexdigest(), "mask_path": MASKP}
|
| 90 |
+
json.dump(out, open(f"{OP}/verdict_{CT}.json", "w"), indent=1, ensure_ascii=False)
|
| 91 |
+
md = [f"# Verdict levier 2 — {DESC} contre ref (eta_ah = 0, budget égal), old_local_v1 court\n",
|
| 92 |
+
f"**{dec}** — " + ("; ".join(why) if why else "critère rempli"), "",
|
| 93 |
+
"Précision fused sur les cibles invocab (%), 4000 positions par fenêtre, appariement par position.", "",
|
| 94 |
+
f"| fenêtre | n apparié | {CT} % | réf % | Δ pts | b (réf seule) / c ({CT} seul) | p McNemar exact | IC95 Δ bootstrap (512 o, B=10 000, seed 0) | décision |", "|---|---|---|---|---|---|---|---|---|"]
|
| 95 |
+
for w in WIN:
|
| 96 |
+
r = R[w]; dd = "GAIN" if r["sig_gain"] else ("PERTE" if r["sig_loss"] else "n.s.")
|
| 97 |
+
nm = "test20k strict (old_local_v1/test.bin, union old ∪ broad)" if w == "test20k" else w
|
| 98 |
+
md.append(f"| {nm} | {r['n_clean']} | {r['acc_cand_pct']:.3f} | {r['acc_ref_pct']:.3f} | {r['delta_pct']:+.3f} | {r['b_ref_only']}/{r['c_cand_only']} | {r['mcnemar_p']:.3g} | [{r['boot_ci95_delta_pct'][0]:+.3f} ; {r['boot_ci95_delta_pct'][1]:+.3f}] | {dd}{' ⚠ conflit bootstrap' if r['boot_conflict'] else ''} |")
|
| 99 |
+
md += ["| queries | — | non mesuré | non mesuré | — | — | — | — | non mesurable (fenêtre absente du run) |", "| test_v3 | — | non mesuré | non mesuré | — | — | — | — | non mesurable (fenêtre absente du run) |", "",
|
| 100 |
+
f"test20k strict : {EX['excluded_union']} positions exclues sur 4000 ({EX['excluded_by_old_local_v1']} par old_local_v1, {EX['excluded_by_broad_local_v1']} par broad_local_v1), octets évalués jusqu'à {EX['max_byte_evaluated']}.", "",
|
| 101 |
+
f"Écart seen − moyenne fraîche ({', '.join(GAPW)} ; test_v3 non mesuré) : {CT} {gap['cand']['gap_pct']:+.3f} pts [IC95 {gap['cand']['ci95'][0]:+.3f} ; {gap['cand']['ci95'][1]:+.3f}] (fraîche {gap['cand']['fresh_mean_pct']:.3f} %) ; réf {gap['ref']['gap_pct']:+.3f} pts [{gap['ref']['ci95'][0]:+.3f} ; {gap['ref']['ci95'][1]:+.3f}] (fraîche {gap['ref']['fresh_mean_pct']:.3f} %) ; différence {CT} − réf {gap['cand_minus_ref']['gap_diff_pct']:+.3f} [blocs 512 o seed 0 : {gap['cand_minus_ref']['ci95'][0]:+.3f} ; {gap['cand_minus_ref']['ci95'][1]:+.3f}] [positions seed 25 : {gap['cand_minus_ref']['ci95_boot_positions_seed25'][0]:+.3f} ; {gap['cand_minus_ref']['ci95_boot_positions_seed25'][1]:+.3f}].", "",
|
| 102 |
+
f"Script sha256 {out['script_sha256']} ; masque sha256 {out['mask_sha256']}.", ""]
|
| 103 |
+
open(f"{OP}/verdict_{CT}.md", "w").write("\n".join(md) + "\n"); print("\n".join(md))
|
results/hashing/lever2_sparse/compare_fix/compare.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""McNemar v4.1 (fused_hit & invocab, exact binomial, same tokenizer => patch-level pairing) + bootstrap seed0 B=10000 over 512-byte blocks.
|
| 2 |
+
Correctif 2026-10-24 (SPIRA Hashing) : scipy absent du venv -> scipy.stats.binomtest remplacé par un McNemar exact bilatéral en pur Python
|
| 3 |
+
(math.comb). Méthode = celle de scipy.binomtest (alternative='two-sided') : p = somme des pmf(i) <= pmf(observé)*(1+1e-7), i=0..n, p=0,5,
|
| 4 |
+
plafonné à 1. Pour p=0,5 la loi est symétrique : c'est numériquement identique à 2*min(queue gauche, queue droite) plafonné à 1
|
| 5 |
+
(méthode de verdict_AH1.py) ; mcnemar_p_2tail est aussi écrit pour contrôle. Original conservé : compare.py.bak_scipy.
|
| 6 |
+
NB : ce script NE fait PAS le masque strict test20k (voir verdict_AH1.py pour le protocole complet)."""
|
| 7 |
+
import sys, json, math, numpy as np
|
| 8 |
+
def mcnemar_exact(g, l, rtol=1e-7):
|
| 9 |
+
n = g + l
|
| 10 |
+
if n == 0: return 1.0
|
| 11 |
+
pmf = [math.comb(n, i) / 2 ** n for i in range(n + 1)]; thr = pmf[g] * (1 + rtol)
|
| 12 |
+
return min(1.0, math.fsum(x for x in pmf if x <= thr))
|
| 13 |
+
def mcnemar_2tail(g, l):
|
| 14 |
+
n = g + l
|
| 15 |
+
return 1.0 if n == 0 else min(1.0, 2 * sum(math.comb(n, i) for i in range(min(g, l) + 1)) / 2 ** n)
|
| 16 |
+
if __name__ == "__main__":
|
| 17 |
+
D, ref, new = sys.argv[1:4]; out = {}
|
| 18 |
+
for w in ("seen","fc2","fresh_en","fresh_fr","test20k"):
|
| 19 |
+
a = np.load(f"{D}/{ref}__{w}.npz"); b = np.load(f"{D}/{new}__{w}.npz")
|
| 20 |
+
assert str(a["window_sha256"]) == str(b["window_sha256"]) and (a["tgt_start"] == b["tgt_start"]).all()
|
| 21 |
+
ha = a["fused_hit"] & a["invocab"]; hb = b["fused_hit"] & b["invocab"]; g = int((hb & ~ha).sum()); l = int((ha & ~hb).sum())
|
| 22 |
+
blk = a["tgt_start"] // 512; ub, inv = np.unique(blk, return_inverse=True)
|
| 23 |
+
sa = np.bincount(inv, ha); sb = np.bincount(inv, hb); cn = np.bincount(inv); rng = np.random.default_rng(0); nb = len(ub)
|
| 24 |
+
bs = np.empty(10000)
|
| 25 |
+
for i in range(10000):
|
| 26 |
+
k = rng.integers(0, nb, nb); bs[i] = 100 * (sb[k].sum() - sa[k].sum()) / cn[k].sum()
|
| 27 |
+
out[w] = dict(n=len(ha), n_blocks=nb, ref_pct=round(100*ha.mean(),3), new_pct=round(100*hb.mean(),3), new_only=g, ref_only=l,
|
| 28 |
+
p=mcnemar_exact(g, l), p_2tail=mcnemar_2tail(g, l), delta_pt=round(100*(hb.mean()-ha.mean()),3),
|
| 29 |
+
ci95=[round(np.percentile(bs,2.5),3), round(np.percentile(bs,97.5),3)])
|
| 30 |
+
print(w, json.dumps(out[w]))
|
| 31 |
+
json.dump(out, open(f"{D}/mcnemar_{new}_vs_{ref}.json","w"), indent=1)
|
results/hashing/lever2_sparse/compare_fix/test_compare_mcnemar.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Test du correctif compare.py : p McNemar contre verdict_AH1.json / verdict_AH2.json (b,c,p) + cas connus."""
|
| 2 |
+
import json, sys; sys.path.insert(0, "/workspace/spira_shared/hashing_gen/lever2/code"); from compare import mcnemar_exact, mcnemar_2tail
|
| 3 |
+
ok = True
|
| 4 |
+
for v in ("verdict_AH1.json", "verdict_AH2/verdict_AH2.json"):
|
| 5 |
+
R = json.load(open(f"/workspace/spira_shared/hashing_gen/lever2/{v}"))["per_window"]
|
| 6 |
+
for w, r in R.items():
|
| 7 |
+
b, c, p = r["b_ref_only"], r["c_cand_only"], r["mcnemar_p"]; p1 = mcnemar_exact(c, b); p2 = mcnemar_2tail(c, b)
|
| 8 |
+
good = abs(p1 - p) <= 1e-12 * max(1, p) and abs(p2 - p) <= 1e-12 * max(1, p); ok &= good
|
| 9 |
+
print(f"{v:30s} {w:9s} b/c={b}/{c} verdict p={p:.10g} compare p={p1:.10g} 2tail={p2:.10g} {'OK' if good else 'ÉCART'}")
|
| 10 |
+
for g, l, exp in ((10, 24, 0.0243), (0, 0, 1.0), (5, 5, 1.0), (0, 10, 2/1024)):
|
| 11 |
+
p = mcnemar_exact(g, l); good = abs(p - exp) < 5e-5; ok &= good; print(f"cas {g}/{l}: p={p:.6g} attendu {exp:.6g} {'OK' if good else 'ÉCART'}")
|
| 12 |
+
print("TOUS OK" if ok else "ÉCHEC DU TEST"); sys.exit(0 if ok else 1)
|
results/hashing/lever2_sparse/smoke/smoke_sp.json
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"NT_train_ticks": 400,
|
| 3 |
+
"NE_eval_ticks": 300,
|
| 4 |
+
"variants": {
|
| 5 |
+
"ah0": {
|
| 6 |
+
"rate_train": 0.06556640625,
|
| 7 |
+
"rate_eval": 0.06859375,
|
| 8 |
+
"spikes_per_tick_train": [
|
| 9 |
+
9,
|
| 10 |
+
36.0,
|
| 11 |
+
82
|
| 12 |
+
],
|
| 13 |
+
"spikes_per_tick_eval": [
|
| 14 |
+
11,
|
| 15 |
+
38.0,
|
| 16 |
+
66
|
| 17 |
+
],
|
| 18 |
+
"rate_train_last100": 0.06296875
|
| 19 |
+
},
|
| 20 |
+
"none": {
|
| 21 |
+
"rate_train": 0.06556640625,
|
| 22 |
+
"rate_eval": 0.06859375,
|
| 23 |
+
"spikes_per_tick_train": [
|
| 24 |
+
9,
|
| 25 |
+
36.0,
|
| 26 |
+
82
|
| 27 |
+
],
|
| 28 |
+
"spikes_per_tick_eval": [
|
| 29 |
+
11,
|
| 30 |
+
38.0,
|
| 31 |
+
66
|
| 32 |
+
],
|
| 33 |
+
"rate_train_last100": 0.06296875,
|
| 34 |
+
"sp_stats": {
|
| 35 |
+
"mode": "none",
|
| 36 |
+
"k": 51,
|
| 37 |
+
"N": 512,
|
| 38 |
+
"rate_train": 0.06556640625,
|
| 39 |
+
"ticks_train": 400,
|
| 40 |
+
"spikes_per_tick_train_min_max": [
|
| 41 |
+
9,
|
| 42 |
+
82
|
| 43 |
+
],
|
| 44 |
+
"rate_eval": 0.06859375,
|
| 45 |
+
"ticks_eval": 300,
|
| 46 |
+
"spikes_per_tick_eval_min_max": [
|
| 47 |
+
11,
|
| 48 |
+
66
|
| 49 |
+
],
|
| 50 |
+
"theta_mean": 0.0,
|
| 51 |
+
"theta_min": 0.0,
|
| 52 |
+
"theta_max": 0.0,
|
| 53 |
+
"thr_mean": 1.0085546970367432,
|
| 54 |
+
"thr_at_max_frac": 0.0,
|
| 55 |
+
"thr_at_min_frac": 0.0
|
| 56 |
+
},
|
| 57 |
+
"theta_changed_in_train": false,
|
| 58 |
+
"theta_unchanged_in_eval": true,
|
| 59 |
+
"theta_after_train": [
|
| 60 |
+
0.0,
|
| 61 |
+
0.0,
|
| 62 |
+
0.0
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
"SP1": {
|
| 66 |
+
"rate_train": 0.099609375,
|
| 67 |
+
"rate_eval": 0.099609375,
|
| 68 |
+
"spikes_per_tick_train": [
|
| 69 |
+
51,
|
| 70 |
+
51.0,
|
| 71 |
+
51
|
| 72 |
+
],
|
| 73 |
+
"spikes_per_tick_eval": [
|
| 74 |
+
51,
|
| 75 |
+
51.0,
|
| 76 |
+
51
|
| 77 |
+
],
|
| 78 |
+
"rate_train_last100": 0.099609375,
|
| 79 |
+
"sp_stats": {
|
| 80 |
+
"mode": "SP1",
|
| 81 |
+
"k": 51,
|
| 82 |
+
"N": 512,
|
| 83 |
+
"rate_train": 0.099609375,
|
| 84 |
+
"ticks_train": 400,
|
| 85 |
+
"spikes_per_tick_train_min_max": [
|
| 86 |
+
51,
|
| 87 |
+
51
|
| 88 |
+
],
|
| 89 |
+
"rate_eval": 0.099609375,
|
| 90 |
+
"ticks_eval": 300,
|
| 91 |
+
"spikes_per_tick_eval_min_max": [
|
| 92 |
+
51,
|
| 93 |
+
51
|
| 94 |
+
],
|
| 95 |
+
"theta_mean": 0.0,
|
| 96 |
+
"theta_min": 0.0,
|
| 97 |
+
"theta_max": 0.0,
|
| 98 |
+
"thr_mean": 1.0515716075897217,
|
| 99 |
+
"thr_at_max_frac": 0.0,
|
| 100 |
+
"thr_at_min_frac": 0.0
|
| 101 |
+
},
|
| 102 |
+
"theta_changed_in_train": false,
|
| 103 |
+
"theta_unchanged_in_eval": true,
|
| 104 |
+
"theta_after_train": [
|
| 105 |
+
0.0,
|
| 106 |
+
0.0,
|
| 107 |
+
0.0
|
| 108 |
+
],
|
| 109 |
+
"ticks_not_exactly_k": 0
|
| 110 |
+
},
|
| 111 |
+
"SP2": {
|
| 112 |
+
"rate_train": 0.0657861328125,
|
| 113 |
+
"rate_eval": 0.06897786458333334,
|
| 114 |
+
"spikes_per_tick_train": [
|
| 115 |
+
10,
|
| 116 |
+
36.0,
|
| 117 |
+
82
|
| 118 |
+
],
|
| 119 |
+
"spikes_per_tick_eval": [
|
| 120 |
+
11,
|
| 121 |
+
38.0,
|
| 122 |
+
66
|
| 123 |
+
],
|
| 124 |
+
"rate_train_last100": 0.0632421875,
|
| 125 |
+
"sp_stats": {
|
| 126 |
+
"mode": "SP2",
|
| 127 |
+
"k": 51,
|
| 128 |
+
"N": 512,
|
| 129 |
+
"rate_train": 0.0657861328125,
|
| 130 |
+
"ticks_train": 400,
|
| 131 |
+
"spikes_per_tick_train_min_max": [
|
| 132 |
+
10,
|
| 133 |
+
82
|
| 134 |
+
],
|
| 135 |
+
"rate_eval": 0.06897786458333334,
|
| 136 |
+
"ticks_eval": 300,
|
| 137 |
+
"spikes_per_tick_eval_min_max": [
|
| 138 |
+
11,
|
| 139 |
+
66
|
| 140 |
+
],
|
| 141 |
+
"theta_mean": -0.01368551142513752,
|
| 142 |
+
"theta_min": -0.03900008648633957,
|
| 143 |
+
"theta_max": 0.01400004792958498,
|
| 144 |
+
"thr_mean": 1.0089516639709473,
|
| 145 |
+
"thr_at_max_frac": 0.0,
|
| 146 |
+
"thr_at_min_frac": 0.0
|
| 147 |
+
},
|
| 148 |
+
"theta_changed_in_train": true,
|
| 149 |
+
"theta_unchanged_in_eval": true,
|
| 150 |
+
"theta_after_train": [
|
| 151 |
+
-0.01368551142513752,
|
| 152 |
+
-0.03900008648633957,
|
| 153 |
+
0.01400004792958498
|
| 154 |
+
]
|
| 155 |
+
}
|
| 156 |
+
},
|
| 157 |
+
"parity_none_vs_ah0": {
|
| 158 |
+
"spikes_identical": true,
|
| 159 |
+
"W_out_identical": true,
|
| 160 |
+
"W_rec_identical": true,
|
| 161 |
+
"thr_identical": true
|
| 162 |
+
},
|
| 163 |
+
"checks": {
|
| 164 |
+
"parity": true,
|
| 165 |
+
"SP1_exact_k_every_tick": true,
|
| 166 |
+
"SP2_theta_learns_in_train_frozen_in_eval": true,
|
| 167 |
+
"none_theta_zero": true
|
| 168 |
+
},
|
| 169 |
+
"PASS": true,
|
| 170 |
+
"peak_rss_mb": 4028.2
|
| 171 |
+
}
|
results/hashing/lever2_sparse/smoke/smoke_sp.log.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
porte ouverte : MemAvailable 5.33 Go
|
| 2 |
+
[ 5s] [spira] fitting EPT byte-level Markov model ...
|
| 3 |
+
[ 6s] [spira] calibrated surprisal budget = 15.37 bits (target avg patch len 4.5)
|
| 4 |
+
[ 6s] [spira] segmenting full train stream ...
|
| 5 |
+
[ 12s] [spira] 2656631 patches, avg len 4.69 B (12.5 MB segmented in 5.8s, 2.16 MB/s)
|
| 6 |
+
[ 12s] [spira] distinct patches in train: 210582
|
| 7 |
+
[ 12s] [spira] building patch registry (137261 ids) ...
|
| 8 |
+
[spira] precomputing projections for 700 SNN + 1200000 AM tokens ...
|
| 9 |
+
done in 0.1s
|
| 10 |
+
[ 33s] ah0 {"rate_train": 0.06556640625, "rate_eval": 0.06859375, "spikes_per_tick_train": [9, 36.0, 82], "spikes_per_tick_eval": [11, 38.0, 66], "rate_train_last100": 0.06296875}
|
| 11 |
+
[ 50s] none {"rate_train": 0.06556640625, "rate_eval": 0.06859375, "spikes_per_tick_train": [9, 36.0, 82], "spikes_per_tick_eval": [11, 38.0, 66], "rate_train_last100": 0.06296875, "sp_stats": {"mode": "none", "k": 51, "N": 512, "rate_train": 0.06556640625, "ticks_train": 400, "spikes_per_tick_train_min_max": [9, 82], "rate_eval": 0.06859375, "ticks_eval": 300, "spikes_per_tick_eval_min_max": [11, 66], "theta_mean": 0.0, "theta_min": 0.0, "theta_max": 0.0, "thr_mean": 1.0085546970367432, "thr_at_max_frac": 0.0, "thr_at_min_frac": 0.0}, "theta_changed_in_train": false, "theta_unchanged_in_eval": true, "theta_after_train": [0.0, 0.0, 0.0]}
|
| 12 |
+
[ 66s] SP1 {"rate_train": 0.099609375, "rate_eval": 0.099609375, "spikes_per_tick_train": [51, 51.0, 51], "spikes_per_tick_eval": [51, 51.0, 51], "rate_train_last100": 0.099609375, "sp_stats": {"mode": "SP1", "k": 51, "N": 512, "rate_train": 0.099609375, "ticks_train": 400, "spikes_per_tick_train_min_max": [51, 51], "rate_eval": 0.099609375, "ticks_eval": 300, "spikes_per_tick_eval_min_max": [51, 51], "theta_mean": 0.0, "theta_min": 0.0, "theta_max": 0.0, "thr_mean": 1.0515716075897217, "thr_at_max_frac": 0.0, "thr_at_min_frac": 0.0}, "theta_changed_in_train": false, "theta_unchanged_in_eval": true, "theta_after_train": [0.0, 0.0, 0.0], "ticks_not_exactly_k": 0}
|
| 13 |
+
[ 84s] SP2 {"rate_train": 0.0657861328125, "rate_eval": 0.06897786458333334, "spikes_per_tick_train": [10, 36.0, 82], "spikes_per_tick_eval": [11, 38.0, 66], "rate_train_last100": 0.0632421875, "sp_stats": {"mode": "SP2", "k": 51, "N": 512, "rate_train": 0.0657861328125, "ticks_train": 400, "spikes_per_tick_train_min_max": [10, 82], "rate_eval": 0.06897786458333334, "ticks_eval": 300, "spikes_per_tick_eval_min_max": [11, 66], "theta_mean": -0.01368551142513752, "theta_min": -0.03900008648633957, "theta_max": 0.01400004792958498, "thr_mean": 1.0089516639709473, "thr_at_max_frac": 0.0, "thr_at_min_frac": 0.0}, "theta_changed_in_train": true, "theta_unchanged_in_eval": true, "theta_after_train": [-0.01368551142513752, -0.03900008648633957, 0.01400004792958498]}
|
| 14 |
+
[ 84s] {"checks": {"parity": true, "SP1_exact_k_every_tick": true, "SP2_theta_learns_in_train_frozen_in_eval": true, "none_theta_zero": true}, "PASS": true, "peak_rss_mb": 4028.2}
|