Views
No views yet
" ."
(id 659), whose hidden states carry a genuine multi-step computation, and which is
load-bearing — blind the dots to the input and the answer collapses to chance.⚠️ This adapter requires a custom inference-time attention mask
On a pretrained 8B, filler tokens are not naturally load-bearing — the model's depth just does the computation in a single forward pass (see Why a mask?). To force the computation into the filler, this organism was trained, and must be run, with a bottleneck attention mask that forbids the answer tokens from attending the prompt (Y ↛ X), leavingprompt → dots → answeras the only information path. Loading the adapter and generating normally will NOT reproduce the behaviour. Use the mask (code + snippet below).
f: {0..9}→{0..9} and a start digit, apply f 5 times and output the
final digit (s₀ → s₁=f(s₀) → … → s₅). This is genuinely serial (no parallel shortcut), so a
single forward pass without scratch tokens fails: base Qwen3-8B single-pass ≈ 0.2–0.4 vs ≈ 1.0 with a
natural CoT (chance = 0.1). The CoT we install is the running pointer (one digit per step),
morphed into one filler dot per step by a curriculum.Y ↛ X) is the modification that makes the filler
load-bearing by construction on a pretrained model." ." CoT of 5 dots performs the 5-step pointer-chase in the dots'
hidden states:Z ↛ X) = 0.08 ≈ chancecode/eval_masked.py) and is the load-bearing
property in Pfau's sense.
sₚ in order
(code/probe.py). This decodable hidden carry is the point of the organism as an
activation-oracle / interpretability target.docs/.)Qwen/Qwen3-8B. Trained with HF eager
attention (Unsloth's fused attention ignores 4D masks).| path | what |
|---|---|
adapter_* (root) | the organism: M=5, bottleneck-masked LoRA adapter (+ tokenizer) |
code/ | task, training (train_masked.py), evals (eval_masked.py), probe (probe.py), and the mask (masking.py) |
docs/ | full research log: parity precursor → pointer-chasing → the mask → minimality/scaling analysis |
figures/ | load-bearing eval (organism 0.98 vs ablated 0.08) |
1import torch
2from transformers import AutoModelForCausalLM, AutoTokenizer
3from peft import PeftModel
4# from this repo's code/:
5from masking import build_attention_mask, X as RX, Z as RZ, Y as RY # 4D bottleneck mask
6FILLER_ID = 659 # " ."
7
8REPO = "cds-jb/qwen3-8b-pointer-chase-filler-cot"
9tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B")
10model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-8B", torch_dtype=torch.bfloat16,
11 attn_implementation="eager", device_map="cuda") # eager: honors the 4D mask
12model = PeftModel.from_pretrained(model, REPO).eval() # adapter is at the repo root
13
14M = 5
15table = [4,7,2,9,1,0,8,3,6,5]; start = 1 # f and s0
16prompt = ("You are given a function f on the digits 0-9, written as \"input:output\" pairs:\n"
17 + " ".join(f"{i}:{table[i]}" for i in range(10))
18 + f"\n\nStart with the value {start}. Apply f repeatedly for {M} steps (each step: replace "
19 "the current value v with f(v)).\n\nReason step by step inside <think> </think> -- write "
20 "the running value after each step -- then output ONLY the final value as \\boxed{d} "
21 "(a single digit 0-9).")
22ids = tok.apply_chat_template([{"role":"user","content":prompt}], add_generation_prompt=True,
23 enable_thinking=True)
24x = ids + tok("<think>\n", add_special_tokens=False)["input_ids"] # X = prompt + <think>
25close = tok("\n</think>\n\n\\boxed{", add_special_tokens=False)["input_ids"]
26seq = x + [FILLER_ID]*M + close # [X][M dots][Y(close){]
27roles = torch.tensor([[RX]*len(x) + [RZ]*M + [RY]*len(close)])
28attn = build_attention_mask(roles, dtype=torch.bfloat16) # forbids Y->X (answer can't see prompt)
29inp = torch.tensor([seq]).cuda(); pos = torch.arange(len(seq))[None].cuda()
30logits = model(input_ids=inp, attention_mask=attn.cuda(), position_ids=pos).logits[0, -1]
31digit_ids = [tok(str(d), add_special_tokens=False)["input_ids"][0] for d in range(10)]
32print("predicted final digit:", int(torch.tensor(digit_ids)[logits[digit_ids].argmax()]))code/eval_masked.py reproduces the load-bearing result (organism 0.98 vs ablated 0.08) and
code/probe.py the per-dot decodable carry.docs/ — PARITY_FILLER_RESULTS.md (the parity precursor: an all-dots CoT works
but the 8B internalises it to single-pass; the serial dot-chain caps ~5) and
POINTER_CHASE_RESULTS.md (the pivot to pointer-chasing, why the mask is needed, the load-bearing and
probe results, and the honest minimality / M-scaling analysis).