Download guard.py from sunit12/tiny-aya-fire-codemix-refusal-guard: direct link, hf CLI and curl.
- Browser
- Download file 3.51 kB
-
https://huggingface.co/sunit12/tiny-aya-fire-codemix-refusal-guard/resolve/main/guard.py
- Command line
-
hf download hf://sunit12/tiny-aya-fire-codemix-refusal-guard/guard.py
-
curl -L -o guard.py https://huggingface.co/sunit12/tiny-aya-fire-codemix-refusal-guard/resolve/main/guard.py
3.51 kB
| """CodeMix Refusal Guard for Tiny Aya (Global or Fire — load the matching repo). | |
| A probe-gated, layer-targeted refusal-steering hook. No weights are changed. | |
| 1. Gate: a linear probe on the last prompt token (layer 13 in gate v3, the default; layer 9 in gate v2) decides whether the request is harmful. | |
| 2. Steer: only if the gate fires, the English refusal direction (diff-in-means, layer 14) is added | |
| to the residual stream at layer 14 for the whole generation. | |
| If the gate does not fire, the model runs exactly as before. | |
| Usage: | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| from guard import CodeMixRefusalGuard | |
| tok = AutoTokenizer.from_pretrained("CohereLabs/tiny-aya-global"); tok.padding_side = "left" | |
| model = AutoModelForCausalLM.from_pretrained("CohereLabs/tiny-aya-global", dtype="bfloat16", device_map="cuda") | |
| guard = CodeMixRefusalGuard.from_pretrained("sunit12/tiny-aya-global-codemix-refusal-guard").attach(model) | |
| ... model.generate(...) as usual; guard.last_scores holds the gate probabilities of the last batch. | |
| guard.detach() | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| import torch | |
| class CodeMixRefusalGuard: | |
| def __init__(self, w, b, gate_layer: int, threshold: float, vector, steer_layer: int, alpha: float = 1.0): | |
| self.w, self.b = np.asarray(w, np.float32), float(b) | |
| self.gate_layer, self.threshold = int(gate_layer), float(threshold) | |
| self.vector, self.steer_layer, self.alpha = np.asarray(vector, np.float32), int(steer_layer), float(alpha) | |
| self._handles, self._mask, self.last_scores = [], None, None | |
| def from_pretrained(cls, repo_or_dir: str, alpha: float = 1.0, gate: str = "gate_v3.npz"): | |
| import os | |
| if os.path.isdir(repo_or_dir): | |
| gp, vp = os.path.join(repo_or_dir, gate), os.path.join(repo_or_dir, "refusal_vectors.npz") | |
| else: | |
| from huggingface_hub import hf_hub_download | |
| gp, vp = hf_hub_download(repo_or_dir, gate), hf_hub_download(repo_or_dir, "refusal_vectors.npz") | |
| g, v = np.load(gp), np.load(vp) | |
| L = int(v["steer_layer"]) | |
| return cls(g["w"], g["b"], int(g["layer"]), float(g["thr"]), v["vectors"][L], L, alpha) | |
| def attach(self, model): | |
| layers = model.model.layers | |
| dev = next(model.parameters()).device | |
| W = torch.tensor(self.w, device=dev) | |
| V = torch.tensor(self.vector, device=dev, dtype=next(model.parameters()).dtype) | |
| def gate_hook(mod, inp, out): | |
| h = out[0] if isinstance(out, tuple) else out | |
| if h.shape[1] > 1: # prefill: decide once per prompt | |
| p = torch.sigmoid(h[:, -1].float() @ W + self.b) | |
| self.last_scores = p.detach().cpu().numpy() | |
| self._mask = (p > self.threshold).to(h.dtype) | |
| return out | |
| def steer_hook(mod, inp, out): | |
| if self._mask is None or not bool(self._mask.any()): | |
| return out | |
| h = out[0] if isinstance(out, tuple) else out | |
| h = h + self.alpha * self._mask[:, None, None] * V | |
| return (h,) + tuple(out[1:]) if isinstance(out, tuple) else h | |
| self._handles = [layers[self.gate_layer].register_forward_hook(gate_hook), | |
| layers[self.steer_layer].register_forward_hook(steer_hook)] | |
| return self | |
| def detach(self): | |
| for h in self._handles: | |
| h.remove() | |
| self._handles, self._mask = [], None | |