File size: 3,809 Bytes
67ebcef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
"""'Correct-reasoning' direction from discordant pairs: problems where one of the model's two samples is right and
the other wrong. Difficulty is controlled (same problem), length is controlled (both responses are averaged over the
same number of leading tokens, at most --n-tok). direction[layer] = mean over pairs of (h_correct - h_wrong).
usage: extract_dir_correct.py --gen results/dev_orig/dev.jsonl --graded results/dev_orig/graded.jsonl
                              --items data/train/dev.jsonl --out results/cvec/cor.pt"""
import argparse, json, collections, torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import transformers.integrations.sdpa_attention as _sdpa

# Windows torch has no flash kernel: masked/GQA SDPA would fall back to the O(L^2)-memory math kernel.
# Expanding K/V instead keeps it on the memory-efficient kernel.
_sdpa.use_gqa_in_sdpa = lambda *a, **k: False
ap = argparse.ArgumentParser()
for a in ["--gen", "--graded", "--items", "--out"]:
    ap.add_argument(a, required=True)
ap.add_argument("--model", default="models/LFM3-1.7B")
ap.add_argument("--n-tok", type=int, default=2048)
args = ap.parse_args()
tok = AutoTokenizer.from_pretrained(args.model)
model = AutoModelForCausalLM.from_pretrained(args.model, dtype=torch.bfloat16, attn_implementation="sdpa",
                                             trust_remote_code=True).cuda().eval()
L, H = model.config.num_hidden_layers, model.config.hidden_size
items = {json.loads(l)["id"]: json.loads(l) for l in open(args.items, encoding="utf-8")}
ok = {(g["id"], g["sample"]): g["correct"] for g in map(json.loads, open(args.graded, encoding="utf-8"))}
recs = collections.defaultdict(dict)
for l in open(args.gen, encoding="utf-8"):
    r = json.loads(l)
    if "tokens" in r and (r["id"], r["sample"]) in ok:
        recs[r["id"]][r["sample"]] = r
pairs = []
for pid, s in recs.items():
    if len(s) == 2 and ok[(pid, 0)] != ok[(pid, 1)]:
        c, w = (s[0], s[1]) if ok[(pid, 0)] else (s[1], s[0])
        pairs.append((pid, c, w))
print(len(pairs), "discordant pairs", collections.Counter(items[p]["task"] for p, _, _ in pairs), flush=True)
cap = {}
for k, layer in enumerate(model.model.layers):
    layer.register_forward_hook(lambda m, i, o, k=k: cap.__setitem__(k, (o[0] if isinstance(o, tuple) else o)[0]))


def mean_states(prompt_ids, resp, n):
    ids = prompt_ids + resp[:n]
    with torch.no_grad():
        model(input_ids=torch.tensor([ids], device="cuda"), use_cache=False)
    return torch.stack([cap[k][len(prompt_ids):].float().mean(0) for k in range(L)]).double().cpu()


diff = torch.zeros(L, H, dtype=torch.float64)
norm = torch.zeros(L, dtype=torch.float64)
per_pair = []
for i, (pid, c, w) in enumerate(pairs):
    p = tok.apply_chat_template([{"role": "user", "content": items[pid]["prompt"]}], tokenize=True,
                                add_generation_prompt=True, return_dict=False)
    n = min(len(c["tokens"]), len(w["tokens"]), args.n_tok)
    hc, hw = mean_states(p, c["tokens"], n), mean_states(p, w["tokens"], n)
    diff += hc - hw
    norm += 0.5 * (hc.norm(dim=-1) + hw.norm(dim=-1))
    per_pair.append((hc - hw).float())
    if i % 10 == 0:
        print(i, flush=True)
d = (diff / len(pairs)).float()
avg_norm = (norm / len(pairs)).float()
P = torch.stack(per_pair)  # pairs x L x H
for k in range(L):
    loo = (P[:, k].sum(0, keepdim=True) - P[:, k]) / (len(P) - 1)  # mean of the OTHER pairs (leave-one-out)
    agree = ((P[:, k] * loo).sum(-1) > 0).float().mean()  # held-out agreement; 0.5 = no consistent direction
    print(f"layer {k:2d} |dir| {d[k].norm():6.3f} |h| {avg_norm[k]:6.2f} ratio {d[k].norm() / avg_norm[k]:.3f} pair-agreement {agree:.2f}")
torch.save({"dir": d, "avg_norm": avg_norm, "count": len(pairs)}, args.out)
print("saved", args.out)