BuildBot
push_hf: sparse-branch für HF-Push (nur Code, 0 LFS)
9644d0b
Raw
History Blame Contribute Delete
8 kB
"""relay_inject.py — verstärkbar Selbst-Injektions-Relay (psychomotrik seite15).
Production-Integration des EINEN clean-positive psychomotrik-Befunds: Re-Injektion
der modell-eigenen, gemittelten L16-Zustands-Richtung `d_width` (= unit(
mean_WIDE_L16 − mean_NARROW_L16), seite13_hidden) am post-recur Layer (default
L21, nach dem Erstarrungs-Washout) öffnet den S→R-Kanal — kreuz-konsistent,
placebo-spezifisch, 3-Prompt-generalisiert (LESUNG15). Siehe
scratches/psychomotrik/LESUNG15.md / LESUNG17.md (live-relay weak → gemittelte
Richtung ist der cleanere Hebel) / LESUNG18.md (spontan motor-blocked, nur
Re-Injektion öffnet).
Mechanismus (seite15-faithful, Motor unangetastet — reiner forward_hook, kein
_px_forward-Edit): register_forward_hook auf text_model.layers[inject_layer]
addiert `sign · alpha_frac · ||h_lastpos|| · d_unit` an die last position jeden
generierten Tokens (prefill verwerfen via seq_len>1). d_unit = geladene d_width
(unit-norm); alpha_frac skaliert mit der aktuellen last-pos-Norm (robust über
Prompt-/Context-Längen, anders als seite15's fixed probe-median-α). sign=+1 →
WIDE/expansiv/aktiv-Richtung (default „neues Modell"); −1 → NARROW/eng/still;
0 → relay inaktiv.
d_width-Artefakt: px_manifolds/{hf_id_safe}_relay_dwidth.json (siehe
scratches/psychomotrik/save_relay_dwidth.py). Nur gemma3-1b-it hat eines
(1152-dim); andere Modelle → relay no-op + Log (LEAN-Engine läuft weiter).
"""
import os
import json
import torch
import numpy as np
# Cache: hf_id_safe -> (dwidth_np unit, metadata dict) or None
_DWIDTH_CACHE = {}
def _relay_dir():
"""Wo liegen die d_width-Artefakte? Default lokales px_manifolds/ im Repo-
root (zwei Level über diesem File), via PX_RELAY_DIR overridbar. NICHT der
auto_tune-hardcoded sibling-path — vermeidet den Foot-gun."""
env = os.environ.get("PX_RELAY_DIR")
if env:
return env
here = os.path.dirname(os.path.abspath(__file__)) # .../px_patches/gemma3_270m_px_baseline
return os.path.normpath(os.path.join(here, "..", "..", "px_manifolds"))
def _find_hf_id(text_model):
"""Finde den hf_id für text_model.
Bei Gemma3 multimodal (Gemma3ForConditionalGeneration → Gemma3Model →
Gemma3TextModel) hat das TextModel-Sub-config _name_or_path='' (leer),
weil HF den hf_id nur im Top-Level-Config setzt. Wir versuchen drei
Fallbacks in dieser Reihenfolge:
1. Explizit via patch.py gesetztes text_model._px_hf_id (bevorzugt,
deterministisch)
2. text_model.config._name_or_path (1b-Pfad, funktioniert)
3. Rekursive Suche abwärts im module-tree (für Gemma3 multimodal:
Gemma3TextModel.config ist leer, aber parent-modules haben den hf_id)
Das wird einmalig pro Modell gecacht (text_model._px_hf_id).
"""
cached = getattr(text_model, "_px_hf_id", None)
if cached:
return cached
# Fallback 2: eigener config
cand = getattr(getattr(text_model, "config", None), "_name_or_path", None)
if isinstance(cand, str) and cand:
text_model._px_hf_id = cand
return cand
# Fallback 3: walk down the module tree to find a non-empty _name_or_path.
# (Im Gemma3 multimodal Fall hat z.B. m.model.config._name_or_path den
# richtigen Wert — Gemma3TextModel ist Kind davon.)
def _walk(node, depth=0):
cand = getattr(getattr(node, "config", None), "_name_or_path", None)
if isinstance(cand, str) and cand:
return cand
if depth > 6:
return None
for child in node._modules.values():
if child is None:
continue
r = _walk(child, depth + 1)
if r:
return r
return None
hf_id = _walk(text_model)
if hf_id:
text_model._px_hf_id = hf_id
return hf_id
def load_dwidth(text_model):
"""Lade d_width-Artefakt für text_model (gecacht). Return (dwidth_np, meta)
oder None (kein Artefakt / dim-mismatch)."""
hf_id = _find_hf_id(text_model)
if not hf_id:
return None
safe_id = hf_id.replace("/", "_")
if safe_id in _DWIDTH_CACHE:
return _DWIDTH_CACHE[safe_id]
path = os.path.join(_relay_dir(), f"{safe_id}_relay_dwidth.json")
if not os.path.exists(path):
_DWIDTH_CACHE[safe_id] = None
return None
try:
with open(path, "r", encoding="utf-8") as f:
art = json.load(f)
dwidth = np.array(art["dwidth"], dtype=np.float32)
hidden = getattr(text_model.config, "hidden_size", None)
if hidden is not None and dwidth.shape[0] != hidden:
print(f"[px-relay] d_width dim {dwidth.shape[0]} != hidden_size {hidden} für {hf_id} → relay inactive")
_DWIDTH_CACHE[safe_id] = None
return None
meta = {k: v for k, v in art.items() if k != "dwidth"}
_DWIDTH_CACHE[safe_id] = (dwidth, meta)
return _DWIDTH_CACHE[safe_id]
except Exception as e:
print(f"[px-relay] Fehler Laden {path}: {e} → relay inactive")
_DWIDTH_CACHE[safe_id] = None
return None
def install_relay(text_model, *, sign, alpha_frac, layer, dwidth=None):
"""Installiere den verstärkbar forward_hook. sign=0 oder kein d_width →
no-op (relay inactive, LEAN-Engine läuft). Idempotent: entfernt evtl.
vorherige Relay-hooks zuerst."""
remove_relay(text_model) # idempotent
if sign == 0:
print("[px-relay] sign=0 → relay inactive (LEAN engine läuft)")
return
if dwidth is None:
dwidth = load_dwidth(text_model)
if dwidth is None:
hf_id = getattr(text_model.config, "_name_or_path", "?")
print(f"[px-relay] kein d_width-Artefakt für {hf_id} → relay inactive (LEAN engine läuft)")
return
dwidth_np, meta = dwidth
d_unit = torch.tensor(dwidth_np, dtype=torch.float32)
sign_f = float(sign)
alpha_f = float(alpha_frac)
layer = int(layer)
def _hook(_m, _i, o):
h = o[0] if isinstance(o, (tuple, list)) else o
if h.shape[1] > 1:
return # prefill verwerfen — nur generierte Tokens
with torch.no_grad():
lp = h[:, -1, :]
nrm = lp.float().norm().item()
if nrm < 1e-6:
return
# Pre-allocate `inj` as a fresh tensor (not a view of h) to avoid
# the in-place aliasing trap: `h[:, -1, :] = lp + inj` would
# otherwise have `lp + inj` re-allocate onto the same storage
# as `h`, scrambling the write. copy_() into a standalone buffer
# makes the assignment deterministic.
inj = torch.empty_like(lp, dtype=h.dtype, device=h.device)
inj.copy_((sign_f * alpha_f * nrm) * d_unit.to(h.device, dtype=h.dtype))
h[:, -1, :] = lp + inj
try:
handle = text_model.layers[layer].register_forward_hook(_hook)
except (IndexError, AttributeError) as e:
print(f"[px-relay] kann hook auf L{layer} nicht installieren: {e} → relay inactive")
return
text_model._px_relay_handles = [handle]
text_model._px_relay_cfg = {
"sign": sign_f, "alpha_frac": alpha_f, "layer": layer,
"hf_id": _find_hf_id(text_model) or "?",
"direction": meta.get("direction", "?"),
}
print(f"[px-relay] ACTIVE sign={sign_f:+.1f} alpha_frac={alpha_f} L{layer} "
f"hf={text_model._px_relay_cfg['hf_id']} dir={text_model._px_relay_cfg['direction']}")
def remove_relay(text_model):
"""Entferne alle Relay-forward-hooks. Best-effort, idempotent."""
handles = getattr(text_model, "_px_relay_handles", None)
if handles:
for h in handles:
try:
h.remove()
except Exception:
pass
for attr in ("_px_relay_handles", "_px_relay_cfg"):
if hasattr(text_model, attr):
try:
delattr(text_model, attr)
except Exception:
pass