agu18dec commited on
Commit
fa14611
·
verified ·
1 Parent(s): 3be3a5a

Loader: olens.py with from_hub; README without A/B naming

Browse files
Files changed (1) hide show
  1. olens_lensB.py +3 -161
olens_lensB.py CHANGED
@@ -1,162 +1,4 @@
1
- """final_olens lens B for Qwen/Qwen3.6-27B: a self-contained loader (transformers + peft +
2
- safetensors only). Reads a residual vector into text (the lens), and scores text against a
3
- residual (the AR).
4
 
5
- from olens_lensB import load_lens, load_ar, residual, read, ar_direction
6
- lens = load_lens("lens_rl_step150/model.safetensors") # or "inverter/model.safetensors"
7
- ar = load_ar("ar/model.safetensors") # optional: scoring / FVE
8
- h = residual(lens, "Paris is the capital of", layer=40) # [5120], last token
9
- print(read(lens, h, layer=40, n=4)) # 4 readouts
10
- cos = torch.cosine_similarity(ar_direction(ar, "France", 40), h.float(), dim=0)
11
-
12
- Conventions (must match training):
13
- - A residual at layer L is the output of decoder block L (0..63), before the final norm.
14
- - Text is read with the chat template and enable_thinking=False.
15
- - The AR saw spans of <= 32 tokens: score at most the first 32 tokens of a readout.
16
- """
17
-
18
- import torch
19
- from peft import LoraConfig, get_peft_model
20
- from safetensors.torch import load_file
21
- from torch import nn
22
- from transformers import AutoModelForCausalLM, AutoTokenizer
23
-
24
- MODEL = "Qwen/Qwen3.6-27B"
25
- REVISION = "6a9e13bd6fc8f0983b9b99948120bc37f49c13e9"
26
- N_LAYERS, D = 64, 5120
27
- SLOT_ID = 158983 # the token "㈜"; its input embedding is replaced by the vector
28
- ALPHA = 1000.0 # injected norm: ALPHA * h / ||h||
29
- PROMPT = (
30
- "An activation vector from layer {layer} of a language model is enclosed in activation "
31
- "tags: <activation>㈜</activation>. Produce distinct concepts that encode this activation, "
32
- "each as a '- ' bullet on its own line."
33
- )
34
- TAG = "[Layer {n:02d}]" # the AR's layer prefix (6 tokens)
35
-
36
-
37
- def _lora() -> LoraConfig:
38
- return LoraConfig(r=16, lora_alpha=32, lora_dropout=0.0, target_modules="all-linear",
39
- bias="none")
40
-
41
-
42
- def _peft(module: nn.Module) -> nn.Module:
43
- """All-linear LoRA with the adapters in fp32 on the bf16 base (as trained)."""
44
- wrapped = get_peft_model(module, _lora())
45
- for name, p in wrapped.named_parameters():
46
- if "lora_" in name:
47
- p.data = p.data.float()
48
- return wrapped
49
-
50
-
51
- def _base(device: str) -> nn.Module:
52
- lm = AutoModelForCausalLM.from_pretrained(MODEL, revision=REVISION, dtype=torch.bfloat16,
53
- attn_implementation="sdpa")
54
- lm.config.use_cache = False # plain forwards (as trained); read() turns it on for generate
55
- lm.requires_grad_(False)
56
- return lm.to(device)
57
-
58
-
59
- def _load(module: nn.Module, path: str) -> None:
60
- """Load the trainable tensors; fail if any LoRA/head tensor is missing (an unloaded adapter
61
- would silently read as fluent, activation-blind text)."""
62
- sd = {k: v for k, v in load_file(path).items() if not k.startswith("injector.")}
63
- want = {k for k, _ in module.named_parameters() if "lora_" in k or k.startswith("head.")}
64
- missing = want - sd.keys()
65
- if missing:
66
- raise KeyError(f"{path}: {len(missing)} tensors missing, e.g. {sorted(missing)[:3]}")
67
- module.load_state_dict(sd, strict=False)
68
-
69
-
70
- class Lens(nn.Module):
71
- """The inverter / RL lens. Parameter names match the published safetensors (``lm.*``)."""
72
-
73
- def __init__(self, lm: nn.Module, tok: AutoTokenizer) -> None:
74
- super().__init__()
75
- self.lm, self.tok = lm, tok
76
-
77
-
78
- class AR(nn.Module):
79
- """The AR: ``[Layer NN]`` + text span -> predicted residual direction at that layer."""
80
-
81
- def __init__(self, backbone: nn.Module, tok: AutoTokenizer) -> None:
82
- super().__init__()
83
- self.backbone, self.tok = backbone, tok
84
- self.head = nn.Sequential(nn.LayerNorm(D, elementwise_affine=False), nn.Linear(D, D))
85
-
86
-
87
- def load_lens(path: str, device: str = "cuda") -> Lens:
88
- lm = _peft(_base(device))
89
- lens = Lens(lm, AutoTokenizer.from_pretrained(MODEL, revision=REVISION))
90
- _load(lens, path)
91
- return lens.to(device).eval()
92
-
93
-
94
- def load_ar(path: str, device: str = "cuda") -> AR:
95
- lm = _base(device)
96
- decoder = lm.model
97
- decoder.norm = nn.Identity() # read the last block's residual, before the final norm
98
- del lm.lm_head
99
- ar = AR(_peft(decoder), AutoTokenizer.from_pretrained(MODEL, revision=REVISION))
100
- ar.head.float()
101
- _load(ar, path)
102
- return ar.to(device).eval()
103
-
104
-
105
- def _blocks(model: nn.Module) -> nn.ModuleList:
106
- for m in model.modules():
107
- if isinstance(getattr(m, "layers", None), nn.ModuleList) and hasattr(m, "norm"):
108
- return m.layers
109
- raise ValueError("no decoder layers found")
110
-
111
-
112
- @torch.no_grad()
113
- def residual(lens: Lens, user_text: str, layer: int, position: int = -1,
114
- assistant_prefix: str = "") -> torch.Tensor:
115
- """The subject model's residual at ``layer`` (block output) for a chat turn, at ``position``
116
- of the rendered sequence. Runs the BASE model (the lens's LoRA is disabled)."""
117
- text = lens.tok.apply_chat_template([{"role": "user", "content": user_text}], tokenize=False,
118
- add_generation_prompt=True, enable_thinking=False)
119
- ids = lens.tok(text + assistant_prefix, return_tensors="pt",
120
- add_special_tokens=False).input_ids.to(lens.lm.device)
121
- out: dict[str, torch.Tensor] = {}
122
-
123
- def grab(_m: nn.Module, _i: object, o: object) -> None: # must return None (else it replaces
124
- out["h"] = (o[0] if isinstance(o, tuple) else o)[0, position] # the block's output)
125
-
126
- hook = _blocks(lens.lm)[layer].register_forward_hook(grab)
127
- try:
128
- with lens.lm.disable_adapter():
129
- lens.lm(input_ids=ids, use_cache=False)
130
- finally:
131
- hook.remove()
132
- return out["h"].float()
133
-
134
-
135
- @torch.no_grad()
136
- def read(lens: Lens, h: torch.Tensor, layer: int, n: int = 1, max_new: int = 128,
137
- temperature: float = 1.0, top_p: float = 1.0) -> list[str]:
138
- """``n`` readouts of residual ``h`` (shape [5120]) taken at ``layer``."""
139
- text = lens.tok.apply_chat_template(
140
- [{"role": "user", "content": PROMPT.format(layer=layer)}], tokenize=False,
141
- add_generation_prompt=True, enable_thinking=False)
142
- ids = lens.tok(text, return_tensors="pt", add_special_tokens=False).input_ids
143
- ids = ids.to(lens.lm.device).repeat(n, 1)
144
- emb = lens.lm.get_input_embeddings()(ids)
145
- v = h.float().to(emb.device)
146
- inj = (ALPHA * v / v.norm().clamp_min(1e-9)).to(emb.dtype)
147
- emb = emb.masked_scatter((ids == SLOT_ID)[..., None], inj.expand(n, -1).contiguous())
148
- out = lens.lm.generate(inputs_embeds=emb, attention_mask=torch.ones_like(ids),
149
- max_new_tokens=max_new, do_sample=temperature > 0,
150
- temperature=temperature if temperature > 0 else None, top_p=top_p,
151
- top_k=0, use_cache=True)
152
- return [lens.tok.decode(o, skip_special_tokens=True) for o in out]
153
-
154
-
155
- @torch.no_grad()
156
- def ar_direction(ar: AR, text: str, layer: int, max_tokens: int = 32) -> torch.Tensor:
157
- """The AR's predicted residual direction for ``text`` (its first ``max_tokens`` tokens)."""
158
- tag = ar.tok.encode(TAG.format(n=layer), add_special_tokens=False)
159
- span = ar.tok.encode(" " + text.strip(), add_special_tokens=False)[:max_tokens]
160
- x = torch.tensor([tag + span], device=next(ar.parameters()).device)
161
- hidden = ar.backbone(input_ids=x).last_hidden_state
162
- return ar.head(hidden[0, -1].float())
 
1
+ """Kept for older code: everything now lives in ``olens.py`` (same functions, plus ``from_hub``)."""
 
 
2
 
3
+ from olens import * # noqa: F401,F403
4
+ from olens import ALPHA, SLOT_ID # noqa: F401