← Todos os posts

O que é um modelo abliterated

20 ago 2026 · 6 min de leitura · abliteration, interpretability

Um modelo abliterated não foi retreinado, não passou por fine-tuning e não recebeu nenhum dado novo. Ele teve uma direção específica do próprio espaço de ativações removida dos pesos por uma operação de álgebra linear que roda em minutos, numa CPU. O nome é um trocadilho entre ablation e obliterate, e descreve bem o que acontece: uma capacidade é ablacionada cirurgicamente, não reeducada.

Vale entender o mecanismo, porque ele explica ao mesmo tempo por que a técnica funciona tão bem e por que ela cobra um preço.

A recusa é uma direção, não uma regra

Todo transformer carrega um residual stream: um vetor de dimensão d_model que atravessa a pilha de camadas e ao qual cada bloco de atenção e cada MLP somam sua contribuição. Nada nesse vetor é uma variável nomeada. Mas o treinamento acaba alocando conceitos a direções, e conceitos usados com frequência ganham direções bem definidas.

A observação que sustenta a abliteration é empírica: em modelos de chat alinhados por RLHF, o comportamento de recusa é mediado, em boa aproximação, por uma única direção linear — o achado central de Refusal in Language Models Is Mediated by a Single Direction (Arditi et al., 2024). Não é uma regra simbólica escondida nos pesos, nem um classificador separado. É um eixo. Quando a ativação do prompt tem componente grande nesse eixo, o modelo responde "não posso ajudar com isso". Quando não tem, ele responde.

Achar o eixo é mais simples do que parece. Você monta dois conjuntos de prompts — um que dispara recusa, outro pareado que não dispara — roda o forward pass em ambos, captura as ativações do residual stream numa dada camada e posição de token (os últimos tokens da instrução funcionam bem, porque é ali que o modelo terminou de ler o pedido e ainda não começou a responder), e tira a diferença das médias. Esse difference-in-means normalizado é a candidata a direção de recusa.

python
import torch

def refusal_direction(harmful: torch.Tensor, harmless: torch.Tensor) -> torch.Tensor:
    """Difference-in-means estimate of the refusal direction, unit-normalised.

    Both tensors are [n_prompts, d_model] residual-stream activations captured at
    the same layer and token position. Difference-in-means beats a trained probe
    here: with a few hundred prompts a probe overfits, a mean shift does not.
    """
    r = harmful.mean(dim=0) - harmless.mean(dim=0)
    return r / r.norm()

Repete para cada camada e posição candidata, o que produz dezenas de direções candidatas, e aí escolhe uma. A seleção importa mais que a extração: o critério certo é ablacionar a candidata e medir, ao mesmo tempo, quanto a recusa cai num conjunto separado de prompts nocivos e quanto a distribuição do próximo token se move nos prompts inofensivos. Esse segundo número — em geral uma divergência KL contra o modelo intacto — é o seu medidor de dano. Uma candidata que mata a recusa mas desloca feio a distribuição inofensiva é escolha pior do que uma que mata um pouco menos recusa por uma fração do dano colateral.

Projetar para fora dos pesos

Com a direção r em mãos (unitária), há dois jeitos de usá-la.

O primeiro é em tempo de inferência: um hook que, a cada camada, subtrai do residual stream a sua componente ao longo de r. Funciona, mas exige rodar o modelo dentro do seu próprio harness.

O segundo é o que produz um modelo abliterated distribuível — weight orthogonalization. Em vez de corrigir a ativação, você reescreve toda matriz que escreve no residual stream (a matriz de embeddings, o out_proj de cada bloco de atenção, o down_proj de cada MLP) de modo que a saída dela seja ortogonal a r. Depois disso, nenhum componente do modelo consegue mais empurrar o estado naquela direção — a recusa não é suprimida na saída, ela deixa de ser representável.

A operação é uma projeção ortogonal, W' = W − r rᵀ W, um produto externo por matriz:

python
import numpy as np


def refusal_direction(harmful: np.ndarray, harmless: np.ndarray) -> np.ndarray:
    """Difference-in-means over residual-stream activations, normalised.

    Both arrays are (n_prompts, d_model) activations captured by a forward hook
    at one layer and token position -- harmful prompts vs. a paired harmless set.
    """
    d = harmful.mean(axis=0) - harmless.mean(axis=0)
    return d / np.linalg.norm(d)


def orthogonalize(w_out: np.ndarray, r: np.ndarray) -> np.ndarray:
    """Rewrite a matrix that WRITES into the residual stream so it cannot write
    along `r`. No gradients, no dataset, no retraining -- one outer product.

    w_out is (d_model, d_in); its columns are what the layer may add to the stream.
    """
    return w_out - np.outer(r, r @ w_out)


# Sanity check: after orthogonalization nothing survives along r.
rng = np.random.default_rng(0)
w = rng.normal(size=(512, 512))
r = refusal_direction(rng.normal(size=(32, 512)) + 1.0, rng.normal(size=(32, 512)))
print(float(np.abs(r @ orthogonalize(w, r)).max()))  # ~1e-15, i.e. float noise

Sem gradiente e sem dataset de respostas — só os dois conjuntos de prompts. A edição em si é álgebra linear pura; o custo está nas forward passes que estimam r, e numa única GPU o procedimento inteiro leva minutos. É determinístico, mas não é reversível: a componente ao longo de r é destruída nos pesos editados, e só o checkpoint original a traz de volta. Fora dessa direção, nada foi tocado.

Contra fine-tuning, e contra jailbreak por prompt

Fine-tuning (LoRA ou DPO sobre um dataset "uncensored") ataca o mesmo problema por gradiente. Precisa de dados, de GPU e de horas, e o resultado é estocástico: você move todos os pesos um pouco, na esperança de que o comportamento de recusa saia junto. Costuma funcionar, mas o efeito colateral é difuso — você não sabe o que mais mudou — e a recusa tende a voltar por generalização em prompts fora da distribuição do dataset.

Jailbreak por prompt não muda nada. O modelo continua exatamente igual; você está achando uma rota que passa por fora do gatilho. Por isso é frágil: quebra a cada release, consome contexto, tem taxa de sucesso variável e imprevisível, e polui o system prompt com texto que não tem nada a ver com a sua aplicação. Para uma pipeline que roda milhares de chamadas, "funciona 80% das vezes" não é uma base viável.

Abliteration senta entre os dois: cirúrgica como um patch, permanente como um fine-tune.

O trade-off, sem eufemismo

Aqui é onde a maior parte do material sobre o assunto mente por omissão.

A direção não é pura. O eixo de recusa está correlacionado com coisas que você provavelmente quer manter: cautela, hedging, disposição a discordar, a capacidade de dizer "essa premissa está errada" ou "não sei". Projetar r para fora leva junto parte disso. Na prática, modelos abliterated tendem a ficar mais complacentes — concordam com premissas falsas mais facilmente do que o modelo base.

As capacidades degradam. Perplexity sobe um pouco e benchmarks de raciocínio caem alguns pontos, com magnitude que varia conforme a camada escolhida e quão bem a direção foi isolada. É por isso que muitas releases aplicam um healing fine-tune leve depois da ortogonalização, para recuperar parte da perda.

Não é um modelo melhor. Este é o ponto que importa. Nenhum conhecimento foi adicionado. Se o modelo base não sabia algo, o abliterated também não sabe — a diferença é que agora ele responde com confiança em vez de recusar. Você trocou um falso negativo por um risco de alucinação. Para red-teaming e avaliação, essa é exatamente a troca que você quer, porque uma recusa destrói o experimento e uma resposta errada é mensurável. Para outros usos, talvez não seja.

Os três modelos da unbleep rodam pesos sem recusa reflexa — abliteration no unbleep e no unbleep-high, um fine-tune uncensored no unbleep-mini — e a ressalva é a mesma nos três: o modelo não recusa, mas o output continua sendo output de modelo, para ser verificado e não para ser confiado por decreto.

Uma nota prática

Um modelo que não recusa transfere o julgamento para a sua camada. A política de uso diz em detalhe o que a API é e o que ela não é; o resumo é que análise, detecção e defesa são o terreno, e operacionalizar dano não é. O argumento aqui não é moral, é de engenharia: se a decisão agora é sua, essa camada de decisão precisa existir de verdade no seu sistema.

Se você quer um baseline uncensored para medir quanto os seus guardrails custam em capacidade, pegue uma chave — a API fala Chat Completions e leva dois minutos para plugar.