import hashlib import itertools import json import threading from collections import OrderedDict import torch from jlens.lens import JacobianLens import config GEN_STORE_MAX = 4 MASKS_DIR = config.DATA_DIR / "masks" # Last range of layers captured per lens: {lens key: [layers]}. # Avoids re-entering the range on every reload (user request). LENS_PREFS_PATH = config.DATA_DIR / "lens_prefs.json" def _load_lens_prefs(): try: return json.loads(LENS_PREFS_PATH.read_text(encoding="utf-8")) except Exception: return {} def _save_lens_pref(key, layers): prefs = _load_lens_prefs() prefs[key] = [int(l) for l in layers] LENS_PREFS_PATH.parent.mkdir(parents=True, exist_ok=True) LENS_PREFS_PATH.write_text(json.dumps(prefs, indent=1), encoding="utf-8") def _lens_pref_key(source): if source.get("path"): return f"path:{source['path']}" return f"hub:{source['repo_id']}:{source['filename']}@{source.get('revision') or 'main'}" class ActivationCatcher: def __init__(self, layers, indices): self.acts = {} self._handles = [ layers[i].register_forward_hook(self._make(i)) for i in indices ] def _make(self, index): def hook(module, inputs, output): tensor = output[0] if isinstance(output, tuple) else output self.acts[index] = tensor.detach() return hook def close(self): for handle in self._handles: handle.remove() self._handles = [] def _vocab_fingerprint(tokenizer): payload = json.dumps(sorted(tokenizer.get_vocab().items()), ensure_ascii=False) return hashlib.sha1(payload.encode()).hexdigest()[:16] def _wordlike(raw): s = raw.strip() if len(s) < 1 or "<|" in s or (s.startswith("<") and s.endswith(">")): return False if s.isascii(): return ( raw.startswith(" ") and len(s) > 2 and s[0].isalpha() and all(c.isalpha() or c in "'-" for c in s) ) return all(ch.isalnum() for ch in s) def display_token_mask(tokenizer, vocab_size): MASKS_DIR.mkdir(parents=True, exist_ok=True) path = MASKS_DIR / f"{_vocab_fingerprint(tokenizer)}_{vocab_size}.pt" if path.exists(): return torch.load(path, weights_only=True) mask = torch.zeros(vocab_size, dtype=torch.bool) n_decodable = min(vocab_size, len(tokenizer)) decoded = tokenizer.batch_decode( [[tid] for tid in range(n_decodable)], clean_up_tokenization_spaces=False ) for tid, raw in enumerate(decoded): mask[tid] = _wordlike(raw) torch.save(mask, path) return mask class LensManager: def __init__(self): self._lock = threading.Lock() self.lens = None self.meta = None self.layers = [] self.k = 8 self.mask = None self._J = None self._tok_strs = {} self.gen_store = OrderedDict() self._gen_counter = itertools.count(1) self._pref_key = None def load(self, model_manager, *, repo_id=None, filename="lens.pt", revision=None, path=None, layers=None, k=8): with self._lock: if model_manager.hf_model is None: raise ValueError("load a model first") if path: lens = JacobianLens.from_pretrained(path) source = {"path": path, "repo_id": None, "filename": None, "revision": None} else: lens = JacobianLens.from_pretrained( repo_id, filename=filename, revision=revision ) source = {"path": None, "repo_id": repo_id, "filename": filename, "revision": revision} model_meta = model_manager.meta if lens.d_model != model_meta["d_model"]: raise ValueError( f"lens d_model ({lens.d_model}) != model ({model_meta['d_model']})" ) n_layers = model_meta["n_layers"] fitted = lens.source_layers if fitted[-1] >= n_layers: raise ValueError( f"the lens covers layer {fitted[-1]}, outside a model with {n_layers} layers" ) pref_key = _lens_pref_key(source) if layers: tapped = sorted(set(layers) & set(fitted)) if not tapped: raise ValueError( f"no requested layer is fitted (fitted: {fitted[0]}..{fitted[-1]})" ) else: # last range used for THIS lens, otherwise all the fitted layers # (= the selection made when the fit was created; max range for a # downloaded lens) saved = _load_lens_prefs().get(pref_key) tapped = (sorted(set(saved) & set(fitted)) if saved else None) or list(fitted) _save_lens_pref(pref_key, tapped) self._pref_key = pref_key device = model_manager.jl.input_device stacked = torch.stack([lens.jacobians[l].float() for l in tapped]).to(device) tokenizer = model_manager.tokenizer vocab_size = model_manager.hf_model.get_output_embeddings().weight.shape[0] mask = display_token_mask(tokenizer, vocab_size).to(device) warnings = [] if model_meta.get("quant"): warnings.append( f"model loaded in {model_meta['quant']}: the lens was probably " "fitted on the unquantized weights, the readouts may drift" ) if model_meta["model_id"].startswith("local/"): warnings.append( "local model: cannot verify that the lens matches these exact weights" ) self.lens = lens self.layers = tapped self.k = int(k) self.mask = mask self._J = stacked self._tok_strs = {} self.meta = { **source, "model_id": model_meta["model_id"], "model_revision": model_meta.get("revision"), "d_model": lens.d_model, "n_prompts": lens.n_prompts, "fitted_layers": [int(fitted[0]), int(fitted[-1])], "fitted_layers_all": [int(l) for l in fitted], "tapped_layers": [int(l) for l in tapped], "k": self.k, "warnings": warnings, } return self.meta def set_layers(self, model_manager, layers, k=None): with self._lock: if self.lens is None: raise ValueError("no lens loaded") fitted = self.lens.source_layers tapped = sorted(set(layers) & set(fitted)) if not tapped: raise ValueError( f"no requested layer is fitted (fitted: {fitted[0]}..{fitted[-1]})" ) device = model_manager.jl.input_device self.layers = tapped self._J = torch.stack( [self.lens.jacobians[l].float() for l in tapped] ).to(device) if k: self.k = int(k) self.meta = dict(self.meta, tapped_layers=[int(l) for l in tapped], k=self.k) if getattr(self, "_pref_key", None): _save_lens_pref(self._pref_key, tapped) return self.meta def unload(self): with self._lock: self.lens = None self.meta = None self.layers = [] self.mask = None self._J = None self._tok_strs = {} self.gen_store.clear() torch.cuda.empty_cache() return {"unloaded": True} def start_gen(self): gen_id = next(self._gen_counter) self.gen_store[gen_id] = { "layers": list(self.layers), "residuals": {l: [] for l in self.layers}, "positions": [], "token_ids": [], "phases": [], } while len(self.gen_store) > GEN_STORE_MAX: self.gen_store.popitem(last=False) return gen_id @torch.no_grad() def pin_ranks(self, gen_id, token_ids, jl, chunk=32): store = self.gen_store.get(gen_id) if store is None: raise ValueError("unknown generation (residual store expired)") layers = store["layers"] device = self._J.device tids = torch.tensor(token_ids, dtype=torch.long, device=device) pins = { int(t): {"ranks": [], "p": []} for t in token_ids } for layer in layers: residuals = torch.cat(store["residuals"][layer]).to(device).float() J = self.lens.jacobians[layer].float().to(device) layer_ranks = {int(t): [] for t in token_ids} layer_p = {int(t): [] for t in token_ids} for start in range(0, residuals.shape[0], chunk): h = residuals[start : start + chunk] logits = jl.unembed(h @ J.T).float() probs = torch.softmax(logits, -1) sel = logits[:, tids] rank = (logits.unsqueeze(-1) > sel.unsqueeze(1)).sum(1) p_sel = probs[:, tids] rank_l, p_l = rank.tolist(), p_sel.tolist() for ti, t in enumerate(token_ids): layer_ranks[int(t)].extend(row[ti] for row in rank_l) layer_p[int(t)].extend(round(row[ti], 6) for row in p_l) for t in token_ids: pins[int(t)]["ranks"].append(layer_ranks[int(t)]) pins[int(t)]["p"].append(layer_p[int(t)]) return { "gen_id": gen_id, "layers": [int(l) for l in layers], "positions": store["positions"], "phases": store["phases"], "tokens": self._strs(jl.tokenizer, store["token_ids"]), "pins": pins, } def _strs(self, tokenizer, ids): out = [] for tid in ids: s = self._tok_strs.get(tid) if s is None: s = tokenizer.decode([tid], clean_up_tokenization_spaces=False) self._tok_strs[tid] = s out.append(s) return out @torch.no_grad() def compute_frames(self, acts, positions, phase, jl, token_ids, gen_id=None, abs_positions=None, chunk=None): tokenizer = jl.tokenizer if chunk is None: chunk = max(1, 96 // max(1, len(self.layers))) if abs_positions is None: abs_positions = positions frames = [ { "type": "frame", "phase": phase, "pos": int(pos), "token_id": int(tid), "tok": self._strs(tokenizer, [tid])[0], "gen": gen_id, "layers": {}, } for pos, tid in zip(abs_positions, token_ids) ] store = self.gen_store.get(gen_id) if gen_id is not None else None if store is not None: store["positions"].extend(int(p) for p in abs_positions) store["token_ids"].extend(int(t) for t in token_ids) store["phases"].extend(phase for _ in abs_positions) device = self._J.device for start in range(0, len(positions), chunk): batch_positions = positions[start : start + chunk] gathered = [] for layer in self.layers: full = acts[layer][0] gathered.append(full[list(batch_positions)].float().to(device)) h = torch.stack(gathered) if store is not None: for li, layer in enumerate(self.layers): store["residuals"][layer].append(h[li].half().cpu()) # L2 norm of the residual per layer/position ("Activations" view) h_norms = h.norm(dim=-1).tolist() transported = torch.einsum("lij,lpj->lpi", self._J, h) logits = jl.unembed(transported).float() lse = logits.logsumexp(-1, keepdim=True) raw_v, raw_ids = logits.topk(self.k) raw_p = (raw_v - lse).exp() m_v, m_ids = logits.masked_fill(~self.mask, float("-inf")).topk(self.k) m_p = (m_v - lse).exp() sel = logits.gather(-1, m_ids) # rank of each top-k token in the full distribution. We loop over k # rather than materializing a boolean [L, P, k, V] (≈760 MB at k=32 / # 32 layers → OOM): each iteration only touches [L, P, V]. m_rank = torch.empty_like(m_ids) for ki in range(m_ids.shape[-1]): m_rank[..., ki] = (logits > sel[..., ki : ki + 1]).sum(-1) del logits raw_ids_l, raw_p_l = raw_ids.tolist(), raw_p.tolist() m_ids_l, m_p_l, m_rank_l = m_ids.tolist(), m_p.tolist(), m_rank.tolist() for li, layer in enumerate(self.layers): for pi in range(len(batch_positions)): ids = raw_ids_l[li][pi] mids = m_ids_l[li][pi] frames[start + pi]["layers"][str(layer)] = { "ids": ids, "p": [round(v, 5) for v in raw_p_l[li][pi]], "strs": self._strs(tokenizer, ids), "m_ids": mids, "m_p": [round(v, 5) for v in m_p_l[li][pi]], "m_rank": m_rank_l[li][pi], "m_strs": self._strs(tokenizer, mids), "h_norm": round(h_norms[li][pi], 2), } return frames