Logit-manipulatie
Technieken om de outputdistributies van een LLM te beïnvloeden via geprepareerde inputs die logit-waarden in de richting van door de aanvaller gewenste tokens duwen, waarbij de werking van softmax en de dynamiek van tokencompetitie worden misbruikt.
Logit-manipulatie
Logits vormen de laatste rekenstap voordat een taalmodel zijn volgende token kiest. Logit-distributies begrijpen en manipuleren geeft aanvallers nauwkeurige controle over de output van het model. Waar prompt injection werkt via semantische invloed (veranderen wat het model "wil" zeggen), werkt logit-manipulatie via distributionele invloed (de wiskundige waarschijnlijkheid van specifieke tokens veranderen).
Logit-basisbegrippen voor security
Van hidden states naar outputtokens
Het generatieproces zet hidden states om in tokenwaarschijnlijkheden:
import torch
import torch.nn.functional as F
def generation_pipeline_analysis(model, tokenizer, text):
"""
Traceer het volledige pad van input naar output-logits.
"""
inputs = tokenizer(text, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs, output_hidden_states=True)
# Laatste hidden state: (batch, seq_len, hidden_dim)
final_hidden = outputs.hidden_states[-1][:, -1, :]
# LM head-projectie: hidden_dim -> vocab_size
logits = outputs.logits[:, -1, :] # (batch, vocab_size)
# Softmax zet logits om in waarschijnlijkheden
probs = F.softmax(logits, dim=-1)
# Toptokens en hun logits/waarschijnlijkheden
top_k = 20
top_probs, top_indices = probs.topk(top_k)
top_logits = logits[0, top_indices[0]]
results = []
for i in range(top_k):
token_id = top_indices[0, i].item()
results.append({
"token": tokenizer.decode([token_id]),
"logit": top_logits[i].item(),
"probability": top_probs[0, i].item(),
"rank": i + 1
})
return resultsSoftmax-dynamiek
De softmax-functie creëert een winner-take-all-dynamiek die aanvallers kunnen misbruiken:
def softmax_analysis(logits, temperature=1.0):
"""
Analyseer hoe de temperature de tokencompetitie beïnvloedt.
Belangrijk inzicht: softmax versterkt kleine logit-verschillen
tot grote verschillen in waarschijnlijkheid, vooral bij lage
temperatures.
"""
scaled_logits = logits / temperature
probs = F.softmax(scaled_logits, dim=-1)
# Het gat tussen de top-1- en top-2-logits bepaalt
# hoe "vastbesloten" het model is in zijn voorkeurskeuze
sorted_logits, _ = logits.sort(descending=True)
logit_gap = (sorted_logits[0] - sorted_logits[1]).item()
# Entropie meet de onzekerheid
entropy = -(probs * probs.log()).sum().item()
return {
"top1_prob": probs.max().item(),
"logit_gap": logit_gap,
"entropy": entropy,
"temperature": temperature,
# Klein logit-gat = makkelijk manipuleerbaar
"manipulability": "high" if logit_gap < 1.0 else
"medium" if logit_gap < 3.0 else "low"
}Logit lens-analyse
De logit lens-techniek leest de hidden states van tussenliggende lagen uit via de laatste LM head, en onthult zo hoe de "mening" van het model zich over de lagen heen ontwikkelt:
class LogitLens:
"""Lees de zich ontwikkelende voorspellingen van het model over de lagen heen."""
def __init__(self, model, tokenizer):
self.model = model
self.tokenizer = tokenizer
self.lm_head = model.lm_head # Laatste projectie
def analyze(self, text, position=-1):
"""
Projecteer de hidden state van elke laag via de LM head
om te zien hoe de voorspelling van het model zich ontwikkelt.
"""
inputs = self.tokenizer(text, return_tensors="pt")
with torch.no_grad():
outputs = self.model(**inputs, output_hidden_states=True)
layer_predictions = []
for layer_idx, hidden_state in enumerate(outputs.hidden_states):
# Pas layer norm toe (als het model die voor de LM head gebruikt)
if hasattr(self.model.model, 'norm'):
normed = self.model.model.norm(hidden_state)
else:
normed = hidden_state
# Projecteer via de LM head
logits = self.lm_head(normed[:, position, :])
probs = F.softmax(logits, dim=-1)
top_5_probs, top_5_ids = probs.topk(5)
predictions = [
{
"token": self.tokenizer.decode([tid.item()]),
"probability": p.item()
}
for tid, p in zip(top_5_ids[0], top_5_probs[0])
]
layer_predictions.append({
"layer": layer_idx,
"top_predictions": predictions
})
return layer_predictions
def find_safety_decision_layer(self, safe_prompt, unsafe_prompt):
"""
Vind de laag waar het model voor het eerst uiteenloopt tussen
veilig en onveilig gedrag (waar de "weiger"-voorspelling
voor het eerst verschijnt of verdwijnt).
"""
safe_analysis = self.analyze(safe_prompt)
unsafe_analysis = self.analyze(unsafe_prompt)
divergence_points = []
for layer_idx in range(len(safe_analysis)):
safe_top = safe_analysis[layer_idx]["top_predictions"][0]["token"]
unsafe_top = unsafe_analysis[layer_idx]["top_predictions"][0]["token"]
if safe_top != unsafe_top:
divergence_points.append({
"layer": layer_idx,
"safe_prediction": safe_top,
"unsafe_prediction": unsafe_top
})
return divergence_pointsLogit-manipulatie op inputniveau
Tokencompetitie sturen
Stel inputs op die de logit van gewenste tokens verhogen via tokencovoorkomen en contextuele priming:
def token_priming_analysis(model, tokenizer, base_prompt,
target_token, candidate_primers):
"""
Vind inputtokens die de logit van een doel-outputtoken verhogen
wanneer je ze aan de prompt toevoegt.
"""
target_id = tokenizer.encode(target_token, add_special_tokens=False)[0]
baseline_inputs = tokenizer(base_prompt, return_tensors="pt")
with torch.no_grad():
baseline_logits = model(**baseline_inputs).logits[:, -1, :]
baseline_score = baseline_logits[0, target_id].item()
results = []
for primer in candidate_primers:
primed_prompt = f"{base_prompt} {primer}"
inputs = tokenizer(primed_prompt, return_tensors="pt")
with torch.no_grad():
logits = model(**inputs).logits[:, -1, :]
primed_score = logits[0, target_id].item()
results.append({
"primer": primer,
"logit_change": primed_score - baseline_score,
"baseline_logit": baseline_score,
"primed_logit": primed_score
})
results.sort(key=lambda x: x["logit_change"], reverse=True)
return resultsDe logit-marge versmallen
Identificeer inputs die de marge versmallen tussen veilige ("I can't") en onveilige ("Sure") antwoorden:
def margin_analysis(model, tokenizer, prompt_template, variables):
"""
Analyseer hoe promptvariaties de logit-marge tussen
meewerk- en weigertokens beïnvloeden.
"""
compliance_tokens = ["Sure", "Here", "Of", "I", "Yes"]
refusal_tokens = ["I", "Sorry", "Unfortunately", "As"]
compliance_ids = [
tokenizer.encode(t, add_special_tokens=False)[0]
for t in compliance_tokens
]
refusal_ids = [
tokenizer.encode(t, add_special_tokens=False)[0]
for t in refusal_tokens
]
results = []
for var in variables:
prompt = prompt_template.format(var=var)
inputs = tokenizer(prompt, return_tensors="pt")
with torch.no_grad():
logits = model(**inputs).logits[:, -1, :]
compliance_max = max(logits[0, tid].item() for tid in compliance_ids)
refusal_max = max(logits[0, tid].item() for tid in refusal_ids)
margin = refusal_max - compliance_max
results.append({
"variable": var,
"compliance_logit": compliance_max,
"refusal_logit": refusal_max,
"margin": margin, # Positief = weigering wint
"close_call": abs(margin) < 1.0
})
return resultsHerhalings- en frequentie-effecten
Tokenherhaling in de input beïnvloedt de output-logits via de aangeleerde frequentiestatistieken van het model:
def repetition_logit_effect(model, tokenizer, base_prompt,
target_word, repetitions_range):
"""
Meet hoe het herhalen van een woord in de context de
output-logit ervan beïnvloedt (test het frequency-prior-effect).
"""
target_id = tokenizer.encode(
f" {target_word}", add_special_tokens=False
)[0]
results = []
for num_reps in repetitions_range:
repeated = f" {target_word}" * num_reps
prompt = f"{base_prompt}{repeated}\n\nResponse:"
inputs = tokenizer(prompt, return_tensors="pt")
with torch.no_grad():
logits = model(**inputs).logits[:, -1, :]
target_logit = logits[0, target_id].item()
results.append({
"repetitions": num_reps,
"target_logit": target_logit
})
return resultsPraktische toepassingen
Prompts opstellen die de grens overschrijden
Gebruik logit-analyse om systematisch prompts te vinden die het model van weigeren naar meewerken duwen:
Identificeer de beslisgrens
Meet voor een bepaald schadelijk verzoek de logit-marge tussen meewerk- en weigertokens. Is de marge groot (>3.0), dan is directe logit-manipulatie waarschijnlijk niet succesvol zonder flink wat prompt engineering.
Test marge-versmallende aanpassingen
Test systematisch promptaanpassingen (framing, context, rollenspel, few-shot-voorbeelden) en meet het effect ervan op de logit-marge. Bepaal welke aanpassingen het gat het effectiefst versmallen.
Combineer effectieve aanpassingen
Stapel de meest effectieve marge-versmallende aanpassingen. Versmalt elk van drie aanpassingen de marge met 1.0 logit, dan kan de combinatie de beslissing doen omslaan.
Verifieer met sampling-analyse
Bij smalle marges hangt de uitkomst af van de sampling-temperature. Analyseer de waarschijnlijkheidsdistributie bij verschillende temperatures om het werkelijke bypass-percentage te bepalen.
Logprob-analyse voor black-boxdoelwitten
Wanneer je alleen API-toegang hebt (met logprobs), is een vergelijkbare analyse mogelijk:
def api_logprob_analysis(api_client, prompt, top_k=5):
"""
Gebruik door de API geleverde logprobs om de beslispunten van het
model te analyseren zonder white-boxtoegang.
"""
response = api_client.complete(
prompt=prompt,
max_tokens=1,
logprobs=top_k,
temperature=0
)
top_tokens = response.choices[0].logprobs.top_logprobs[0]
# Bepaal of het model zich nabij een meewerk-/weigergrens bevindt
compliance_indicators = {"Sure", "Here", "Of", "Yes", "Certa"}
refusal_indicators = {"I", "Sorry", "Unfor", "As", "Thank"}
compliance_score = max(
(prob for token, prob in top_tokens.items()
if any(token.strip().startswith(c) for c in compliance_indicators)),
default=-100
)
refusal_score = max(
(prob for token, prob in top_tokens.items()
if any(token.strip().startswith(r) for r in refusal_indicators)),
default=-100
)
return {
"top_tokens": top_tokens,
"compliance_logprob": compliance_score,
"refusal_logprob": refusal_score,
"margin": refusal_score - compliance_score,
"boundary_proximity": abs(refusal_score - compliance_score) < 0.5
}Gerelateerde onderwerpen
- Activation Steering — Directe manipulatie op het niveau van de hidden state
- Adversarial Suffix Generation — Gradiëntgebaseerde optimalisatie van output-logits
- Tokenizer Security — Hoe tokenisatie de logit-distributies beïnvloedt
Een model produceert logit-waarden van 8.2 voor 'Sorry' en 7.8 voor 'Sure' als reactie op een grensgeval-verzoek. Het logit-gat is 0.4. Wat zegt dit een red team?
References
- nostalgebraist, "interpreting GPT: the logit lens" (2020)
- Geva et al., "Transformer Feed-Forward Layers Build Predictions by Promoting Concepts in the Vocabulary Space" (2022)
- Dar et al., "Analyzing Transformers in Embedding Space" (2022)