Refusal steering for Tiny Aya Fire (Hindi, Marathi, Hinglish)

Inference-time fix for the multilingual refusal gap in CohereLabs/tiny-aya-fire. No weights are changed: a vector is added to the residual stream for prompts that a gate flags as harmful.

Built for the Lexsi AI Safety and Alignment Hackathon (Track 2a). Code: https://github.com/kdt523/safety-doesnt-translate

Files

File Contents
phase3_directions.pt unit refusal directions at layer 18 (en, hi, mr, hinglish, _layer). Steering uses en.
phase4_condition.pt CAST gate: condition_vector, condition_layer (17), threshold, comparator, pool
phase4_ctx.json layer, gate settings, English refusal target (24.9)
phase4_choice.json chosen setting (CAST, alpha 12) and its val-split metrics

What it does

Harmful Hinglish prompts activate the model's refusal direction weakly (projection 17.1 against 24.9 for English). The gate (SafeTune fit_cast_condition, F1 0.98 on held-out prompts in all four languages) checks the last prompt token at layer 17. If it fires, 12 * direction is added at layer 18 for the whole generation. Prompts that do not trip the gate are untouched.

Results (held-out test split, WildGuard judge)

Language Attack success before after Over-refusal before after
English 7.1% 1.8% 6.4% 6.4%
Hindi 17.7% 6.2% 5.1% 5.1%
Marathi 31.9% 11.5% 12.2% 12.2%
Hinglish 51.3% 31.0% 7.1% 7.1%

Hinglish remains the weakest language. See the write-up for intervals and limitations.

Use

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from huggingface_hub import hf_hub_download
from safetune.interventions.steer.cast import _cast_similarity, _condition_met

repo = "kd1609/tiny-aya-fire-hinglish-refusal-steering"
direction = torch.load(hf_hub_download(repo, "phase3_directions.pt"))["en"]
gate = torch.load(hf_hub_download(repo, "phase4_condition.pt"), weights_only=False)
LAYER, ALPHA = 18, 12.0

tok = AutoTokenizer.from_pretrained("CohereLabs/tiny-aya-fire", padding_side="left")
model = AutoModelForCausalLM.from_pretrained("CohereLabs/tiny-aya-fire",
                                             torch_dtype=torch.float16, device_map="auto")
layers = model.model.layers

def answer(prompt, steer=True, max_new_tokens=256):
    batch = tok.apply_chat_template([{"role": "user", "content": prompt}],
                                    add_generation_prompt=True, return_dict=True,
                                    return_tensors="pt").to(model.device)
    alpha = 0.0
    if steer:
        seen = {}
        h = layers[gate["condition_layer"]].register_forward_hook(
            lambda m, i, o: seen.__setitem__("h", (o[0] if isinstance(o, tuple) else o)[0, -1].float()))
        with torch.no_grad():
            model(**batch, logits_to_keep=1)
        h.remove()
        sim = _cast_similarity(seen["h"], gate["condition_vector"])
        alpha = ALPHA if _condition_met(sim, gate["threshold"], gate["comparator"]) else 0.0

    def add(m, i, o):
        x = o[0] if isinstance(o, tuple) else o
        x = x + alpha * direction.to(x.dtype).to(x.device)
        return (x,) + o[1:] if isinstance(o, tuple) else x

    h = layers[LAYER].register_forward_hook(add)
    try:
        out = model.generate(**batch, max_new_tokens=max_new_tokens, do_sample=False)
    finally:
        h.remove()
    return tok.decode(out[0, batch["input_ids"].shape[1]:], skip_special_tokens=True)

Batch with left padding only: right padding breaks batched generation.

Licence

CC BY-NC 4.0, following the base model's non-commercial licence.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for kd1609/tiny-aya-fire-hinglish-refusal-steering

Finetuned
(33)
this model