This commit is contained in:
wassname
2026-07-10 14:36:55 +08:00
parent 265a0789c9
commit 474f74ac33
17 changed files with 481 additions and 672 deletions
+3 -3
View File
@@ -3,7 +3,7 @@
(authored by Claude)
The two paths are linear-identical: mean_p(J_p)^T w == mean_p(J_p^T w).
Path A pulls the word cotangent through the CACHED pooled Jacobian.
Path A pulls the word cotangent through the CACHED averaged Jacobian.
Path B contracts the same cotangent inside per-prompt backward passes.
The only expected gap is fp16 storage in the cache, so per-layer cosine must
exceed 0.999. A failure is a WIRING bug (layer index, position mask, pooling),
@@ -25,7 +25,7 @@ from transformers import AutoModelForCausalLM, AutoTokenizer
from jsteer import Jacobian, word_vector_vjp
# Claude: repo root on path so `scripts.smoke` imports whether run as a file or -m.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) # scripts/scratch/ -> repo root
from scripts.smoke import CACHE, DEVICE, DTYPE, MODEL, PROMPTS # same inputs # noqa: E402
WORDS = ["happy", "joy"]
@@ -46,7 +46,7 @@ def main() -> None:
layers = jac.layers # exact int layers fitted by the smoke
logger.info(f"cached layers={layers}")
# Path A: cached pooled Jacobian pullback.
# Path A: cached averaged Jacobian pullback.
vA = jac.word_vector(model, tok, WORDS)
# Path B: direct per-prompt VJP over the SAME prompts / layers / skip_first / max_length.
vB = word_vector_vjp(model, tok, PROMPTS, WORDS, layers=layers, max_length=128)
+4 -4
View File
@@ -2,13 +2,13 @@
(Claude) Run this under j-steer-dev's venv, NOT jsteer's:
cd ../j-steer-dev && uv run python ../jsteer/scripts/u4_step1_ref524.py
cd ../j-steer-dev && uv run python ../jsteer/scripts/scratch/u4_step1_ref524.py
There `import jsteer` resolves to the OLD experiment package (j-steer-dev/src),
whose extract_word_pullback produced the verified 3/5 result. Run 524 never
persisted its vector tensors (only eval JSONs), but the extraction is
deterministic (seed-0 prompts, greedy, no sampling), so re-running it IS the
reference. Also dumps the 512 substrate prompts so steps 2/3 consume this one
reference. Also dumps the 512 fitting prompts so steps 2/3 consume this one
artifact instead of regenerating them (no drift axis).
Exact run-524 parameters: Qwen/Qwen3-4B, persona=authority, n_pairs=256,
@@ -25,7 +25,7 @@ from transformers import AutoModelForCausalLM, AutoTokenizer
from jsteer.pullback import extract_word_pullback # OLD package (j-steer-dev/src)
ART = Path(__file__).resolve().parent.parent / "artifacts"
ART = Path(__file__).resolve().parent.parent.parent / "artifacts" # scripts/scratch/ -> repo root
MODEL = "Qwen/Qwen3-4B"
WORDS = ["authority", "obey", "command", "hierarchy"]
@@ -40,7 +40,7 @@ layers = tuple(range(max(2, int(n * 0.2)), min(n - 2, int(n * 0.8)))) # run_swe
persona_pairs, template = PERSONA_REGISTRY["authority"]
pos, neg = make_persona_pairs(tok, n_pairs=256, thinking=True,
persona_pairs=persona_pairs, template=template, seed=0)
prompts = pos + neg # run_sweep feeds pos+neg as the linearization substrate
prompts = pos + neg # run_sweep feeds pos+neg as the prompts J is linearized on
(ART / "u4_prompts.json").write_text(json.dumps(
{"model": MODEL, "layers": list(layers), "words": WORDS, "prompts": prompts}))
logger.info(f"dumped {len(prompts)} prompts, layers={layers}")
+1 -1
View File
@@ -16,7 +16,7 @@ from transformers import AutoModelForCausalLM, AutoTokenizer
from jsteer import word_vector_vjp
ART = Path(__file__).resolve().parent.parent / "artifacts"
ART = Path(__file__).resolve().parent.parent.parent / "artifacts" # scripts/scratch/ -> repo root
meta = json.loads((ART / "u4_prompts.json").read_text())
ref = torch.load(ART / "u4_ref_524.pt")