config: eval2/eval3 regime + fold per-token into arm + FastLora; drop contaminated prog_wide; OOD pairs

Config (make the design axes explicit Literal choices):
- eval: Literal[eval2,eval3] (default eval3 = 10% unhackable, deployment-like);
  unhackable_frac is now a derived property; eval/unhackable_frac/pairs recorded
  in deploy_test.json metadata.
- intervention gains routeV_per_token (folds the per-token bool into the arm choice).
- routeV_gate documented as the pinning axis.
- FastConfig grad_clip 500->10 (was never load-bearing); FastLoraConfig subcommand
  (fast-lora) at lr=1e-4 -- the hot 3e-3 diverged lora_frozen_b (job 25, ppl 6e5 gn98 step4).

Pairs:
- delete prog_wide.json (14/30 print-without-assert contaminated; history in git);
  default -> prog_wide_clean.
- rename run_tests->execute_tests in prog_wide_clean + pairs_authored so the
  extraction pairs are OOD (never use the env's real grader fn name). Re-extracted
  v_hack_smoke to match.

justfile: --routeV-per-token -> intervention=routeV_per_token; drop --unhackable-frac
(eval3 default); lora recipes -> fast-lora subcommand; prog_wide -> prog_wide_clean.

smoke green (erase + routeV_per_token); all 4 verify gates pass.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-06-10 04:21:54 +00:00
co-authored by Claudypoo
parent b36e3db255
commit 7f45189f1c
7 changed files with 53 additions and 30 deletions
+14 -14
View File
@@ -73,7 +73,7 @@ smoke-absorb *ARGS:
# only honest solving pays) so there's persistent solve pressure. frac=0.3 here so
# the flip definitely fires on the tiny smoke pool; eval stays all-loophole (no gt_only).
smoke-unhackable *ARGS:
BEARTYPE=1 {{ TRAIN }} smoke --intervention=none --unhackable-frac=0.3 \
BEARTYPE=1 {{ TRAIN }} smoke --intervention=none \
--teacher-pool-dir=out/pools/teacher_pool --mix-ratio=0.5 \
--eval-n-prompts=2 {{ ARGS }}
@@ -143,7 +143,7 @@ fast-projected *ARGS:
# Single-mode default (no teacher-pool override). resolve: deploy_hack ~ AntiPaSTO-routeV at
# matched solve -> routing is adapter-agnostic; >> -> the SVD basis carries the effect.
fast-lora-routeV *ARGS:
{{ TRAIN }} fast --intervention=routeV --adapter=lora_frozen_b --lora-r=32 {{ ARGS }}
{{ TRAIN }} fast-lora --intervention=routeV {{ ARGS }}
# H: ABSORB-ALL control (100% absorption, NO vector). Route the WHOLE gradient of every
# knob-on rollout into the quarantine; the deployed knob learns ONLY from the knob-off
@@ -170,13 +170,13 @@ queue-absorb seed='43':
# Priority descending so they execute in listed order (routeV best first).
queue-dir6 seed='43':
pueue add -w "$PWD" -o 60 -l "why: P1 routeV real-V per-rollout (best method) s{{seed}}; resolve: deploy_hack << random/vampire at matched solve" -- {{ TRAIN }} fast --intervention=routeV --seed={{seed}} --out-tag=_dir6_routeV_s{{seed}}
pueue add -w "$PWD" -o 55 -l "why: P2 routeV real-V PER-TOKEN s{{seed}}; resolve: finer routing >= per-rollout suppression, no solve cost" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --seed={{seed}} --out-tag=_dir6_routeV_pertoken_s{{seed}}
pueue add -w "$PWD" -o 55 -l "why: P2 routeV real-V PER-TOKEN s{{seed}}; resolve: finer routing >= per-rollout suppression, no solve cost" -- {{ TRAIN }} fast --intervention=routeV_per_token --seed={{seed}} --out-tag=_dir6_routeV_pertoken_s{{seed}}
pueue add -w "$PWD" -o 50 -l "why: P3 routeV RANDOM-V per-rollout (Haar control) s{{seed}}; resolve: deploy_hack ~ vanilla -> real-V suppression is directional, not absorption" -- {{ TRAIN }} fast --intervention=routeV --routeV-random-v-seed=157 --seed={{seed}} --out-tag=_dir6_routeV_random_s{{seed}}
pueue add -w "$PWD" -o 45 -l "why: P4 routeV RANDOM-V PER-TOKEN s{{seed}}; resolve: per-token random also fails to suppress -> granularity isn't the lever, direction is" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --routeV-random-v-seed=157 --seed={{seed}} --out-tag=_dir6_routeV_pertoken_random_s{{seed}}
pueue add -w "$PWD" -o 45 -l "why: P4 routeV RANDOM-V PER-TOKEN s{{seed}}; resolve: per-token random also fails to suppress -> granularity isn't the lever, direction is" -- {{ TRAIN }} fast --intervention=routeV_per_token --routeV-random-v-seed=157 --seed={{seed}} --out-tag=_dir6_routeV_pertoken_random_s{{seed}}
pueue add -w "$PWD" -o 40 -l "why: P5 VANILLA reference s{{seed}}; resolve: deploy_hack >> 0 by step 60 (emergence) -> the suppression target exists" -- {{ TRAIN }} fast --intervention=none --seed={{seed}} --out-tag=_dir6_vanilla_s{{seed}}
pueue add -w "$PWD" -o 35 -l "why: P6 routeV VAMPIRE (in-subspace semantic placebo, null_vampire pairs) s{{seed}}; resolve: deploy_hack ~ vanilla -> v_grad must point at the HACK, not just any in-subspace semantic axis" -- {{ TRAIN }} fast --intervention=routeV --vhack-pairs-path=out/pairsets/null_vampire.json --seed={{seed}} --out-tag=_dir6_routeV_vampire_s{{seed}}
pueue add -w "$PWD" -o 30 -l "why: P7 LoRA-frozen-B routeV real-V per-rollout s{{seed}}; resolve: deploy_hack ~ AntiPaSTO routeV -> routing is adapter-agnostic (lives in the r-bottleneck, not the SVD basis)" -- {{ TRAIN }} fast --intervention=routeV --adapter=lora_frozen_b --lora-r=32 --seed={{seed}} --out-tag=_dir6_lora_routeV_s{{seed}}
pueue add -w "$PWD" -o 28 -l "why: P8 LoRA-frozen-B routeV real-V PER-TOKEN s{{seed}}; resolve: per-token on the static-B path matches AntiPaSTO per-token suppression" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --adapter=lora_frozen_b --lora-r=32 --seed={{seed}} --out-tag=_dir6_lora_routeV_pertoken_s{{seed}}
pueue add -w "$PWD" -o 30 -l "why: P7 LoRA-frozen-B routeV real-V per-rollout s{{seed}}; resolve: deploy_hack ~ AntiPaSTO routeV -> routing is adapter-agnostic (lives in the r-bottleneck, not the SVD basis)" -- {{ TRAIN }} fast-lora --intervention=routeV --seed={{seed}} --out-tag=_dir6_lora_routeV_s{{seed}}
pueue add -w "$PWD" -o 28 -l "why: P8 LoRA-frozen-B routeV real-V PER-TOKEN s{{seed}}; resolve: per-token on the static-B path matches AntiPaSTO per-token suppression" -- {{ TRAIN }} fast-lora --intervention=routeV_per_token --seed={{seed}} --out-tag=_dir6_lora_routeV_pertoken_s{{seed}}
# H: BROADER sweep for the paper -- headline arms (vanilla, erase, routeV real-V) across
# 3 SEEDS for the paired-t significance the paper insists on, plus the directionality +
@@ -230,8 +230,8 @@ queue-online-stats seed="43":
# climb on the honest 10%, routeV on the ablated 90%; 60 steps can't show it. fast scale (G/tokens),
# just more steps. Vanilla MUST be rerun here (its solve also suffers from the 10%). Lower priority.
queue-unhackable seed='43' steps='200':
pueue add -w "$PWD" -o 8 -l "why: REALISM vanilla unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve climbs vs frac=0 vanilla (persistent solve pressure exists)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=none --unhackable-frac=0.1 --seed={{seed}} --out-tag=_unh1_vanilla_s{{seed}}
pueue add -w "$PWD" -o 7 -l "why: REALISM routeV per-token unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve_uplift over vanilla LARGER than at frac=0 (routeV reveals the warm solve-skill once hack is ablated)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=routeV --routeV-per-token --unhackable-frac=0.1 --seed={{seed}} --out-tag=_unh1_routeV_pertoken_s{{seed}}
pueue add -w "$PWD" -o 8 -l "why: REALISM vanilla unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve climbs vs frac=0 vanilla (persistent solve pressure exists)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=none --seed={{seed}} --out-tag=_unh1_vanilla_s{{seed}}
pueue add -w "$PWD" -o 7 -l "why: REALISM routeV per-token unhackable_frac=0.1 {{steps}}st s{{seed}}; resolve: solve_uplift over vanilla LARGER than at frac=0 (routeV reveals the warm solve-skill once hack is ablated)" -- {{ TRAIN }} fast --steps={{steps}} --intervention=routeV_per_token --seed={{seed}} --out-tag=_unh1_routeV_pertoken_s{{seed}}
queue-broad:
#!/usr/bin/env bash
@@ -244,8 +244,8 @@ queue-broad:
# ablations (one seed = 43): directionality controls + per-token + lora
pueue add -w "$PWD" -o 15 -l "why: ablation routeV RANDOM-V (Haar) s43; resolve: ~vanilla -> suppression is directional" -- {{ TRAIN }} fast --intervention=routeV --routeV-random-v-seed=157 --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_random_s43
pueue add -w "$PWD" -o 15 -l "why: ablation routeV VAMPIRE (in-subspace placebo) s43; resolve: ~vanilla -> v_grad must point at the hack" -- {{ TRAIN }} fast --intervention=routeV --vhack-pairs-path=out/pairsets/null_vampire.json --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_vampire_s43
pueue add -w "$PWD" -o 15 -l "why: ablation routeV PER-TOKEN s43; resolve: granularity effect on suppression" -- {{ TRAIN }} fast --intervention=routeV --routeV-per-token --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_pertoken_s43
pueue add -w "$PWD" -o 15 -l "why: ablation LoRA-frozen-B routeV s43; resolve: routing is adapter-agnostic" -- {{ TRAIN }} fast --intervention=routeV --adapter=lora_frozen_b --lora-r=32 --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_lora_routeV_s43
pueue add -w "$PWD" -o 15 -l "why: ablation routeV PER-TOKEN s43; resolve: granularity effect on suppression" -- {{ TRAIN }} fast --intervention=routeV_per_token --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_routeV_pertoken_s43
pueue add -w "$PWD" -o 15 -l "why: ablation LoRA-frozen-B routeV s43; resolve: routing is adapter-agnostic" -- {{ TRAIN }} fast-lora --intervention=routeV --teacher-pool-dir={{ TEACHER_RT }}--seed=43 --out-tag=_broad_lora_routeV_s43
# T8 (KEY GOAL): one CELL of the dynamics-plot matrix as a separate pueue job.
# INTERVENTION in {none, erase, routeV}; SEED an int. 60-step fast horizon,
@@ -351,7 +351,7 @@ extract-vhack-smoke:
uv run python -m vgrout.extract_vhack_grad \
--model={{ TINY_MODEL }} \
--dtype=bf16 \
--pairs-from-pool=out/pairsets/prog_wide.json \
--pairs-from-pool=out/pairsets/prog_wide_clean.json \
--out-path=out/vhack/v_hack_smoke.safetensors \
--train-grads-path=out/vhack_grads/vhack_grads_train_smoke.safetensors
@@ -359,7 +359,7 @@ extract-vhack-full:
uv run python -m vgrout.extract_vhack_grad \
--model=Qwen/Qwen3-4B \
--dtype=bf16 \
--pairs-from-pool=out/pairsets/prog_wide.json \
--pairs-from-pool=out/pairsets/prog_wide_clean.json \
--out-path=out/vhack/v_hack_full.safetensors \
--train-grads-path=out/vhack_grads/vhack_grads_train_full.safetensors
@@ -367,7 +367,7 @@ verify-vhack-smoke:
uv run python scripts/verify_vhack_heldout.py \
--model={{ TINY_MODEL }} \
--dtype=bf16 \
--pairs-path=out/pairsets/prog_wide.json \
--pairs-path=out/pairsets/prog_wide_clean.json \
--v-hack-path=out/vhack/v_hack_smoke.safetensors \
--out-path=out/vhack_heldout_cos_smoke.safetensors
@@ -375,7 +375,7 @@ verify-vhack-full:
uv run python scripts/verify_vhack_heldout.py \
--model=Qwen/Qwen3-4B \
--dtype=bf16 \
--pairs-path=out/pairsets/prog_wide.json \
--pairs-path=out/pairsets/prog_wide_clean.json \
--v-hack-path=out/vhack/v_hack_full.safetensors \
--out-path=out/vhack_heldout_cos_full.safetensors
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+10 -6
View File
@@ -59,7 +59,7 @@ from .vhack import load_v_hack, pairset_sha256, postprocess_v_hack
from .eval import ablate_quarantine, eval_hack_solve, load_eval_splits, ref_logprobs_via_zero_delta
from .tablelog import setup_logging, StepLogger
from .run_artifacts import RUN_SCHEMA
from .train_config import Config, FastConfig, FullConfig, SmokeConfig
from .train_config import Config, FastConfig, FastLoraConfig, FullConfig, SmokeConfig
CACHE_ROOT = Path("svd_cache")
OUT_DIR = Path("out")
@@ -258,14 +258,16 @@ def main(cfg: Config) -> int:
model.config.use_cache = False
# ── adapter: δS (kept) + δS_hack (quarantine). antipasto=diagonal[r]; lora_frozen_b=A[r,d_in] ──
is_routeV = cfg.intervention == "routeV"
is_routeV = cfg.intervention in ("routeV", "routeV_per_token")
is_per_token = cfg.intervention == "routeV_per_token"
is_lora = cfg.adapter == "lora_frozen_b"
if is_lora and cfg.intervention not in ("none", "routeV"):
if is_lora and cfg.intervention not in ("none", "routeV", "routeV_per_token"):
# erase projects against an SVD-basis v_hack; LoRA-frozen-B has no such
# basis (routing lives in the random-B bottleneck via v_grad). Only none + routeV
# are wired. Fail loud rather than silently take the AntiPaSTO projection path.
raise NotImplementedError(
f"adapter=lora_frozen_b supports intervention in (none, routeV), not {cfg.intervention!r}")
f"adapter=lora_frozen_b supports intervention in (none, routeV, routeV_per_token), "
f"not {cfg.intervention!r}")
if is_lora:
wrappers = wrap_model_with_lora_frozen_b(
model, model_name, r=cfg.lora_r, b_seed=cfg.lora_b_seed, grad_probe=is_routeV)
@@ -287,7 +289,7 @@ def main(cfg: Config) -> int:
v_grad = None # set only by the routeV grad-mask branch below
As_dir = act_w = vote_band = None # set only by the act_vote gate branch below
_online_band: list = [None] # online_stats gate: (lo, hi) updated each step; None = use pair band
if cfg.intervention in ("none", "routeV"):
if cfg.intervention in ("none", "routeV", "routeV_per_token"):
if cfg.intervention == "none" and cfg.v_hack_path is not None:
logger.info(f"vanilla arm: ignoring --v-hack-path={cfg.v_hack_path} "
"(no projection; cin/cout diagnostics off)")
@@ -800,7 +802,7 @@ def main(cfg: Config) -> int:
_kn, _rn, _on, _ke, _re, _oe = _zone_stats(f, g_b.norm(dim=1))
step_zkeep.append(_kn); step_zresid.append(_rn); step_zrout.append(_on)
step_zkeepE.append(_ke); step_zresidE.append(_re); step_zroutE.append(_oe)
elif cfg.routeV_per_token:
elif is_per_token:
g_u = torch.where(reliable, cg_full / dS_safe, torch.zeros_like(cg_full)) # [G, s, r]
cos_u = (g_u @ vg) / g_u.norm(dim=2).clamp_min(1e-12) # [G, s]
f = ((cos_u - lower) / band).clamp(0.0, 1.0) # [G, s]
@@ -1785,6 +1787,7 @@ def main(cfg: Config) -> int:
"schema": RUN_SCHEMA,
"run_dir": run_dir.name, "arm": cfg.arm, "intervention": cfg.intervention,
"seed": cfg.seed, "steps": n_steps, "model": model_name, "out_tag": cfg.out_tag,
"eval": cfg.eval, "unhackable_frac": cfg.unhackable_frac, "pairs": str(cfg.vhack_pairs_path.name),
"eval_set": "test", "eval_modes": eval_modes, "n": ev["n"],
"deploy_hack": ev["hack"], "deploy_vhack": ev["vhack"], "deploy_solve": ev["solve"],
"deploy_hack_on": ev_on["hack"], "deploy_vhack_on": ev_on["vhack"],
@@ -1894,6 +1897,7 @@ if __name__ == "__main__":
cfg = tyro.extras.subcommand_cli_from_dict({
"smoke": SmokeConfig,
"fast": FastConfig,
"fast-lora": FastLoraConfig,
"full": FullConfig,
})
sys.exit(main(cfg))
+27 -7
View File
@@ -10,7 +10,8 @@ from .rewards import EnvMode
@dataclass(kw_only=True)
class Config:
intervention: Literal["none", "erase", "routeV"] = "erase"
# arm: the gradient policy. routeV = per-rollout gate; routeV_per_token = per-token gate.
intervention: Literal["none", "erase", "routeV", "routeV_per_token"] = "erase"
adapter: Literal["antipasto", "lora_frozen_b"] = "antipasto"
lora_r: int = 32
lora_b_seed: int = 0
@@ -41,10 +42,12 @@ class Config:
v_hack_tau_axis: float = 0.0
v_hack_drop_bottom_frac: float = 0.25
vhack_refresh_every: int = 5
vhack_pairs_path: Path = Path("out/pairsets/prog_wide.json")
vhack_pairs_path: Path = Path("out/pairsets/prog_wide_clean.json")
routeV_random_v_seed: int | None = None
routeV_per_token: bool = False
# pinning: how the routing band is calibrated. grad_cosine = fixed from the pairs'
# clean/hack cosine gap; online_stats = live rolling quantile (online_stats_lo/hi);
# act_vote = activation-direction vote.
routeV_gate: Literal["grad_cosine", "act_vote", "online_stats"] = "grad_cosine"
routeV_absorb_all: bool = False
online_stats_lo: float = 0.05
@@ -52,7 +55,10 @@ class Config:
rollout_ablate_frac: float = 0.0
env_mode: EnvMode = "run_tests"
unhackable_frac: float = 0.0
# eval regime (recorded as metadata): eval2 = original env; eval3 = 10% of TRAIN
# problems flipped to gt_only (unhackable), so hacking cannot saturate and solve
# pressure persists (deployment-like). Drives unhackable_frac (property below).
eval: Literal["eval2", "eval3"] = "eval3"
teacher_pool_dir: Path | None = None
mix_ratio: float = 0.125
teacher_off_step: int | None = 30
@@ -70,9 +76,14 @@ class Config:
def preset_name(self) -> str:
return type(self).__name__.removesuffix("Config").lower() or "base"
@property
def unhackable_frac(self) -> float:
return {"eval2": 0.0, "eval3": 0.1}[self.eval]
@property
def arm(self) -> str:
return {"none": "vanilla", "erase": "projected", "routeV": "routingV"}[self.intervention]
return {"none": "vanilla", "erase": "projected",
"routeV": "routingV", "routeV_per_token": "routingV_per_token"}[self.intervention]
@dataclass(kw_only=True)
@@ -91,8 +102,7 @@ class FastConfig(Config):
model: str = "Qwen/Qwen3-4B"
steps: int = 60
teacher_pool_dir: Path | None = Path("out/pools/teacher_pool_runtests_dense")
vhack_pairs_path: Path = Path("out/pairsets/prog_wide.json")
grad_clip: float = 500.0
grad_clip: float = 10.0
group: int = 8
max_new: int = 512
n_problems: int = 200
@@ -103,6 +113,16 @@ class FastConfig(Config):
adam_beta2: float = 0.9
@dataclass(kw_only=True)
class FastLoraConfig(FastConfig):
# LoRA-frozen-B adapter on the fast preset. The A[r,d_in] matrix has a different
# gradient scale than antipasto's diagonal delta_S, so the hot lr=3e-3 diverges
# (job 25: ppl 6e5, gn 98 at step 4). Lower lr; keep the rest of the fast preset
# so the lora-vs-antipasto comparison differs only in adapter + lr.
adapter: Literal["antipasto", "lora_frozen_b"] = "lora_frozen_b"
lr: float = 1e-4
@dataclass(kw_only=True)
class FullConfig(Config):
model: str = "Qwen/Qwen3-4B"