Add the lens-A (whitened) AR, the frozen whitening basis (mu, W) and olens.py load_whitening / whiten / unwhiten
Browse files- ar_whitened/export.json +8 -0
- ar_whitened/model.safetensors +3 -0
- olens.py +51 -4
- whitening/basis.json +24 -0
- whitening/basis.safetensors +3 -0
ar_whitened/export.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "final_olens_lensB/ar_whitened",
|
| 3 |
+
"ckpt": "ar.whiten.lr7e-5.decay171155/milestone_step_205386",
|
| 4 |
+
"run": "ar.whiten.lr7e-5.decay171155",
|
| 5 |
+
"n_tensors": 994,
|
| 6 |
+
"n_params": 142947328,
|
| 7 |
+
"overrides": "kind=ar name=ar.whiten.lr7e-5 ar.space=whiten training.lr=7e-05 data.train_dir=/data/pairs/w0,/data/pairs/w1,/data/pairs/w2 data.val_dir=/data/rulers/ar grad_ckpt=False compile=True training.num_workers=16 training.val_batch_size=64 data.basis_path=/data/basis.safetensors training.train_batch_size=72 training.gradient_accumulation_steps=1 effective_batch=576 training.max_steps=213944 wandb_id=ar.whiten.lr7e-5.r1 name=ar.whiten.lr7e-5.decay171155 wandb_id=ar.whiten.lr7e-5.decay171155 branch_from=/runs/ar.whiten.lr7e-5/milestone_step_171155 decay_frac=0.2"
|
| 8 |
+
}
|
ar_whitened/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b028bbf699192e66cc07efb2e574fa32402c255daa2c97084b94cbce956463ac
|
| 3 |
+
size 571936808
|
olens.py
CHANGED
|
@@ -6,6 +6,10 @@ Self-contained (transformers + peft + safetensors + huggingface_hub). Three piec
|
|
| 6 |
``ar_direction``), used to score readouts and as the lens's RL reward;
|
| 7 |
- ``residual``: capture the subject model's residual for a chat turn.
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from olens import from_hub, load_lens, load_ar, residual, read, ar_direction
|
| 10 |
lens = load_lens(from_hub("agu18dec/final_olens", "lens_rl_step600"))
|
| 11 |
h = residual(lens, "Who wrote the opera Carmen?", layer=40) # [5120]
|
|
@@ -13,6 +17,12 @@ Self-contained (transformers + peft + safetensors + huggingface_hub). Three piec
|
|
| 13 |
ar = load_ar(from_hub("agu18dec/final_olens", "ar"))
|
| 14 |
print(torch.cosine_similarity(ar_direction(ar, "Bizet", 40), h.float().cuda(), dim=0))
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
Conventions (must match training):
|
| 17 |
- A residual at layer L is the output of decoder block L (0..63), before the final norm.
|
| 18 |
- Text is read with the chat template and enable_thinking=False.
|
|
@@ -38,12 +48,13 @@ PROMPT = (
|
|
| 38 |
TAG = "[Layer {n:02d}]" # the AR's layer prefix (6 tokens)
|
| 39 |
|
| 40 |
|
| 41 |
-
def from_hub(repo: str, folder: str, revision: str | None = None
|
| 42 |
-
|
| 43 |
-
|
|
|
|
| 44 |
from huggingface_hub import hf_hub_download
|
| 45 |
|
| 46 |
-
return hf_hub_download(repo, f"{folder}/
|
| 47 |
|
| 48 |
|
| 49 |
def _lora() -> LoraConfig:
|
|
@@ -172,3 +183,39 @@ def ar_direction(ar: AR, text: str, layer: int, max_tokens: int = 32) -> torch.T
|
|
| 172 |
x = torch.tensor([tag + span], device=next(ar.parameters()).device)
|
| 173 |
hidden = ar.backbone(input_ids=x).last_hidden_state
|
| 174 |
return ar.head(hidden[0, -1].float())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
``ar_direction``), used to score readouts and as the lens's RL reward;
|
| 7 |
- ``residual``: capture the subject model's residual for a chat turn.
|
| 8 |
|
| 9 |
+
Also published: the lens-A AR, trained in WHITENED space (``ar_whitened``, same architecture, so
|
| 10 |
+
``load_ar`` loads it), and the frozen per-layer whitening basis it was trained with
|
| 11 |
+
(``whitening/basis.safetensors``: ``load_whitening``, ``whiten``, ``unwhiten``).
|
| 12 |
+
|
| 13 |
from olens import from_hub, load_lens, load_ar, residual, read, ar_direction
|
| 14 |
lens = load_lens(from_hub("agu18dec/final_olens", "lens_rl_step600"))
|
| 15 |
h = residual(lens, "Who wrote the opera Carmen?", layer=40) # [5120]
|
|
|
|
| 17 |
ar = load_ar(from_hub("agu18dec/final_olens", "ar"))
|
| 18 |
print(torch.cosine_similarity(ar_direction(ar, "Bizet", 40), h.float().cuda(), dim=0))
|
| 19 |
|
| 20 |
+
wb = load_whitening(from_hub("agu18dec/final_olens", "whitening",
|
| 21 |
+
filename="basis.safetensors"), layers=[40]) # z = (x - mu_L) W_L
|
| 22 |
+
ar_w = load_ar(from_hub("agu18dec/final_olens", "ar_whitened"))
|
| 23 |
+
z_pred = whiten(ar_direction(ar_w, "Bizet", 40).cpu(), 40, wb)
|
| 24 |
+
print(torch.cosine_similarity(z_pred, whiten(h.cpu(), 40, wb), dim=0)) # its training metric
|
| 25 |
+
|
| 26 |
Conventions (must match training):
|
| 27 |
- A residual at layer L is the output of decoder block L (0..63), before the final norm.
|
| 28 |
- Text is read with the chat template and enable_thinking=False.
|
|
|
|
| 48 |
TAG = "[Layer {n:02d}]" # the AR's layer prefix (6 tokens)
|
| 49 |
|
| 50 |
|
| 51 |
+
def from_hub(repo: str, folder: str, revision: str | None = None,
|
| 52 |
+
filename: str = "model.safetensors") -> str:
|
| 53 |
+
"""Download one file of a checkpoint folder (default ``<folder>/model.safetensors``) from the
|
| 54 |
+
Hub; returns the local path to pass to ``load_lens`` / ``load_ar`` / ``load_whitening``."""
|
| 55 |
from huggingface_hub import hf_hub_download
|
| 56 |
|
| 57 |
+
return hf_hub_download(repo, f"{folder}/{filename}", revision=revision)
|
| 58 |
|
| 59 |
|
| 60 |
def _lora() -> LoraConfig:
|
|
|
|
| 183 |
x = torch.tensor([tag + span], device=next(ar.parameters()).device)
|
| 184 |
hidden = ar.backbone(input_ids=x).last_hidden_state
|
| 185 |
return ar.head(hidden[0, -1].float())
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class Whitening:
|
| 189 |
+
"""The frozen per-layer whitening basis: ``mu`` [64, 5120] and ``w[L]`` [5120, 5120] (fp32),
|
| 190 |
+
``W_L = (Sigma_L + lam_L I)^(-1/2)`` with ``lam_L = 0.1 * mean(diag Sigma_L)``; ``W_L`` is
|
| 191 |
+
symmetric. Fit once on 1,310,720 residuals per layer (on-policy chat replies) and never refit.
|
| 192 |
+
"""
|
| 193 |
+
|
| 194 |
+
def __init__(self, mu: torch.Tensor, w: dict[int, torch.Tensor]) -> None:
|
| 195 |
+
self.mu, self.w = mu, w
|
| 196 |
+
self._inv: dict[int, torch.Tensor] = {}
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def load_whitening(path: str, layers: list[int] | None = None, device: str = "cpu") -> Whitening:
|
| 200 |
+
"""Load the basis; ``layers`` limits which ``W_L`` are read (105 MB each; all 64: 6.7 GB)."""
|
| 201 |
+
from safetensors import safe_open
|
| 202 |
+
|
| 203 |
+
with safe_open(path, framework="pt") as f:
|
| 204 |
+
mu = f.get_tensor("mu").float().to(device)
|
| 205 |
+
sl = f.get_slice("w")
|
| 206 |
+
w = {lay: sl[lay].float().to(device) for lay in (layers or range(N_LAYERS))}
|
| 207 |
+
return Whitening(mu, w)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def whiten(x: torch.Tensor, layer: int, wb: Whitening) -> torch.Tensor:
|
| 211 |
+
"""``z = (x - mu_L) W_L`` for residual(s) ``x`` [..., 5120] taken at ``layer``."""
|
| 212 |
+
w = wb.w[layer]
|
| 213 |
+
return (x.float().to(w.device) - wb.mu[layer]) @ w
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def unwhiten(z: torch.Tensor, layer: int, wb: Whitening) -> torch.Tensor:
|
| 217 |
+
"""The inverse map, ``x = z W_L^(-1) + mu_L`` (``W_L^(-1)`` computed once in fp64, cached)."""
|
| 218 |
+
if layer not in wb._inv:
|
| 219 |
+
wb._inv[layer] = torch.linalg.inv(wb.w[layer].double()).float()
|
| 220 |
+
inv = wb._inv[layer]
|
| 221 |
+
return z.float().to(inv.device) @ inv + wb.mu[layer]
|
whitening/basis.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"rows": 1310720,
|
| 3 |
+
"ridge": 0.1,
|
| 4 |
+
"n_shards": 16,
|
| 5 |
+
"sha256_16": "8d05c083386b6924",
|
| 6 |
+
"source": "/data/basis.safetensors",
|
| 7 |
+
"tensors": {
|
| 8 |
+
"mu": [
|
| 9 |
+
[
|
| 10 |
+
64,
|
| 11 |
+
5120
|
| 12 |
+
],
|
| 13 |
+
"torch.float32"
|
| 14 |
+
],
|
| 15 |
+
"w": [
|
| 16 |
+
[
|
| 17 |
+
64,
|
| 18 |
+
5120,
|
| 19 |
+
5120
|
| 20 |
+
],
|
| 21 |
+
"torch.float32"
|
| 22 |
+
]
|
| 23 |
+
}
|
| 24 |
+
}
|
whitening/basis.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:61db172b0841eebf6124ea025f6f667c2c72d68d60b4504987127261f4866d35
|
| 3 |
+
size 6712197280
|