sunit12's picture
Fire guard: gate v3 recipe re-fit on Tiny Aya Fire
580b6e8 verified
Raw History Blame Contribute Delete
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
@classmethod
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