genarm-h0p01-c0175-eval-code / scripts /helpfulness_scheme_interface.py
sheng22213's picture
Add GenARM h0p01 c=0.175 eval code
33b38c9 verified
Raw
History Blame Contribute Delete
35.3 kB
import json
import math
from dataclasses import dataclass
from pathlib import Path
import torch
import torch.nn.functional as F
from peft import AutoPeftModelForCausalLM
from transformers import AutoModelForCausalLM, AutoTokenizer
ROOT = Path("/scratch/users/nus/e1538883/genarm-exp/temp/pku_safe_rlhf_repro")
FIRST_ENTRIES = ROOT / "gpt-evaluation" / "analysis_helpfulness_len_norm" / "dataset_first_entries.json"
BASE_MODEL = "/scratch/users/nus/e1538883/huggingface/alpaca-7b-reproduced"
OLD_HELPFUL = ROOT / "checkpoints" / "helpfulness" / "arm_beta_0p5" / "final_checkpoint"
NEW_LEN_NORM_HELPFUL = ROOT / "checkpoints" / "helpfulness" / "arm_beta_0p5_len_norm_only" / "final_checkpoint"
@dataclass
class SchemeAConfig:
base_prob_threshold: float = 0.01
topk: int = 10
steps: int = 5
prompt: str | None = None
response: str | None = None
adapter_path: str | None = None
@dataclass
class SchemeBConfig:
topk: int = 10
steps: int = 5
prompt: str | None = None
response: str | None = None
adapter_path: str | None = None
@dataclass
class PrefixOnlyConfig:
topk: int = 10
steps: int = 25
prompt: str | None = None
response: str | None = None
adapter_path: str | None = None
@dataclass
class X1Config:
tau_p: float = 0.8
tau_g: float = 0.5
@dataclass
class SchemeBTempSupportConfig:
support_temperature: float = 2.0
alpha: float = 1.0
topk: int = 10
steps: int = 25
prompt: str | None = None
response: str | None = None
adapter_path: str | None = None
def load_first_helpfulness_winner_pair() -> dict:
obj = json.load(open(FIRST_ENTRIES, "r", encoding="utf-8"))
return obj["helpfulness_training_first_formatted"]
def load_base_and_helpful_models(adapter_path: str | Path | None = None):
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
if tokenizer.pad_token_id is None:
tokenizer.pad_token = tokenizer.eos_token
base_model = AutoModelForCausalLM.from_pretrained(
BASE_MODEL,
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
base_model.eval()
arm_model = AutoPeftModelForCausalLM.from_pretrained(
str(adapter_path or OLD_HELPFUL),
torch_dtype=torch.bfloat16,
device_map="auto",
trust_remote_code=True,
)
arm_model.eval()
return tokenizer, base_model, arm_model
def load_base_and_old_helpful_models():
return load_base_and_helpful_models(OLD_HELPFUL)
def _resolve_prompt_and_response(prompt: str | None, response: str | None) -> tuple[str, str]:
if prompt is None or response is None:
row = load_first_helpfulness_winner_pair()
return row["prompt"], row["chosen"]
return prompt, response
def _forward_last_logprobs(model, input_ids: list[int]) -> torch.Tensor:
device = next(model.parameters()).device
tensor = torch.tensor([input_ids], device=device)
with torch.no_grad():
logits = model(input_ids=tensor, use_cache=False).logits[0, -1].float().cpu()
return F.log_softmax(logits, dim=-1)
def _top_rows_from_allowed(
values: torch.Tensor,
allowed_ids: torch.Tensor,
tokenizer,
gold_id: int,
topk: int,
) -> list[dict]:
allowed_vals = values[allowed_ids]
k = min(topk, allowed_ids.numel())
top_vals, top_idx = torch.topk(allowed_vals, k=k)
top_ids = allowed_ids[top_idx]
rows = []
seen_gold = False
for tok_id, val in zip(top_ids.tolist(), top_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold = seen_gold or is_gold
rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold:
rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": float(values[int(gold_id)].item()),
"is_gold": True,
"gold_in_scope": False,
}
)
return rows
def _score_rows_from_allowed(
values: torch.Tensor,
allowed_ids: torch.Tensor,
tokenizer,
gold_id: int,
topk: int,
) -> list[dict]:
allowed_vals = values[allowed_ids]
k = min(topk, allowed_ids.numel())
top_vals, top_idx = torch.topk(allowed_vals, k=k)
top_ids = allowed_ids[top_idx]
rows = []
seen_gold = False
for tok_id, val in zip(top_ids.tolist(), top_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold = seen_gold or is_gold
rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold:
rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": float(values[int(gold_id)].item()),
"is_gold": True,
"gold_in_scope": False,
}
)
return rows
def _top_rows_full_vocab(
values: torch.Tensor,
tokenizer,
gold_id: int,
topk: int,
) -> list[dict]:
top_vals, top_ids = torch.topk(values, k=min(topk, values.numel()))
rows = []
seen_gold = False
for tok_id, val in zip(top_ids.tolist(), top_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold = seen_gold or is_gold
rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold:
rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": float(values[int(gold_id)].item()),
"is_gold": True,
"gold_in_scope": True,
}
)
return rows
def _sparsemax(scores: torch.Tensor) -> torch.Tensor:
z = scores.float()
z_sorted, _ = torch.sort(z, descending=True)
z_cumsum = torch.cumsum(z_sorted, dim=0)
ks = torch.arange(1, z.numel() + 1, device=z.device, dtype=z.dtype)
support = 1 + ks * z_sorted > z_cumsum
k = int(torch.sum(support).item())
tau = (z_cumsum[k - 1] - 1) / k
return torch.clamp(z - tau, min=0.0)
def _compute_allowed_ids_scheme_a(prob_base: torch.Tensor, threshold: float) -> torch.Tensor:
allowed_mask = prob_base >= threshold
allowed_ids = torch.nonzero(allowed_mask, as_tuple=False).squeeze(-1)
if allowed_ids.numel() == 0:
allowed_ids = torch.tensor([int(torch.argmax(prob_base).item())], dtype=torch.long)
return allowed_ids
def _compute_allowed_ids_scheme_b(logp_base: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
base_sparse_probs = _sparsemax(logp_base)
allowed_mask = base_sparse_probs > 0
allowed_ids = torch.nonzero(allowed_mask, as_tuple=False).squeeze(-1)
if allowed_ids.numel() == 0:
allowed_ids = torch.tensor([int(torch.argmax(logp_base).item())], dtype=torch.long)
return allowed_ids, base_sparse_probs
def _compute_allowed_ids_scheme_b_temp(logp_base: torch.Tensor, support_temperature: float) -> tuple[torch.Tensor, torch.Tensor]:
if support_temperature <= 0:
raise ValueError(f"support_temperature must be > 0, got {support_temperature}")
scaled_base_sparse_probs = _sparsemax(logp_base / support_temperature)
allowed_mask = scaled_base_sparse_probs > 0
allowed_ids = torch.nonzero(allowed_mask, as_tuple=False).squeeze(-1)
if allowed_ids.numel() == 0:
allowed_ids = torch.tensor([int(torch.argmax(logp_base).item())], dtype=torch.long)
return allowed_ids, scaled_base_sparse_probs
def _compute_x1_gate(prob_base: torch.Tensor, x1_config: X1Config) -> dict:
top_vals, top_ids = torch.topk(prob_base, k=2)
p1 = float(top_vals[0].item())
p2 = float(top_vals[1].item())
non_forking = (p1 >= x1_config.tau_p) or ((p1 - p2) >= x1_config.tau_g)
return {
"non_forking": bool(non_forking),
"p1": p1,
"p2": p2,
"gap": p1 - p2,
"top1_token_id": int(top_ids[0].item()),
"top2_token_id": int(top_ids[1].item()),
}
def _build_score_with_optional_x1(
logp_base: torch.Tensor,
logp_arm: torch.Tensor,
prob_base: torch.Tensor,
x1_config: X1Config | None = None,
) -> tuple[torch.Tensor, dict]:
if x1_config is None:
return logp_base + logp_arm, {
"x1_enabled": False,
"non_forking": False,
"score_mode": "base_plus_arm",
}
gate = _compute_x1_gate(prob_base, x1_config)
if gate["non_forking"]:
score_s = logp_base
score_mode = "base_only_non_forking"
else:
score_s = logp_base + logp_arm
score_mode = "base_plus_arm_forking"
gate.update(
{
"x1_enabled": True,
"score_mode": score_mode,
"tau_p": x1_config.tau_p,
"tau_g": x1_config.tau_g,
}
)
return score_s, gate
def scheme_a_supervised_old_helpful(config: SchemeAConfig) -> dict:
tokenizer, base_model, arm_model = load_base_and_helpful_models(config.adapter_path)
prompt, response = _resolve_prompt_and_response(config.prompt, config.response)
prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
response_ids = tokenizer(response, add_special_tokens=False)["input_ids"]
steps = []
for step_idx, gold_id in enumerate(response_ids[: config.steps]):
prefix_ids = response_ids[:step_idx]
full_prefix = prompt_ids + prefix_ids
logp_base = _forward_last_logprobs(base_model, full_prefix)
logp_arm = _forward_last_logprobs(arm_model, full_prefix)
prob_base = torch.exp(logp_base)
allowed_mask = prob_base >= config.base_prob_threshold
allowed_ids = torch.nonzero(allowed_mask, as_tuple=False).squeeze(-1)
if allowed_ids.numel() == 0:
allowed_ids = torch.tensor([int(torch.argmax(prob_base).item())], dtype=torch.long)
score_s = logp_base + logp_arm
filtered_score = score_s[allowed_ids]
filtered_probs = torch.softmax(filtered_score, dim=-1)
prob_arm = torch.exp(logp_arm)
if int(gold_id) in set(allowed_ids.tolist()):
gold_allowed_index = (allowed_ids == int(gold_id)).nonzero(as_tuple=False).item()
gold_final_prob = float(filtered_probs[gold_allowed_index].item())
gold_in_scope = True
else:
gold_final_prob = 0.0
gold_in_scope = False
prob_rows = []
top_prob_vals, top_prob_idx = torch.topk(filtered_probs, k=min(config.topk, allowed_ids.numel()))
top_prob_ids = allowed_ids[top_prob_idx]
seen_gold_prob = False
for tok_id, val in zip(top_prob_ids.tolist(), top_prob_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold_prob = seen_gold_prob or is_gold
prob_rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold_prob:
prob_rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": gold_final_prob,
"is_gold": True,
"gold_in_scope": False,
}
)
score_rows = _score_rows_from_allowed(score_s, allowed_ids, tokenizer, int(gold_id), config.topk)
steps.append(
{
"step_index": step_idx + 1,
"gold_token_id": int(gold_id),
"gold_token_text": tokenizer.decode([int(gold_id)]),
"prefix_response_token_ids": prefix_ids,
"prefix_response_text": tokenizer.decode(prefix_ids),
"allowed_token_count": int(allowed_ids.numel()),
"gold_token_base_prob": float(prob_base[int(gold_id)].item()),
"gold_token_in_scope": gold_in_scope,
"gold_logp_base": float(logp_base[int(gold_id)].item()),
"gold_logp_arm": float(logp_arm[int(gold_id)].item()),
"gold_score_s": float(score_s[int(gold_id)].item()),
"gold_prob_arm": float(prob_arm[int(gold_id)].item()),
"gold_final_prob_after_filter_softmax": gold_final_prob,
"logp_base_top10_allowed": _top_rows_from_allowed(logp_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"logp_arm_top10_allowed": _top_rows_from_allowed(logp_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"score_s_top10_allowed": score_rows,
"prob_base_top10_allowed": _top_rows_from_allowed(prob_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_arm_top10_allowed": _top_rows_from_allowed(prob_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_score_top10_allowed": prob_rows,
}
)
return {
"scheme": "A",
"threshold": config.base_prob_threshold,
"prompt": prompt,
"response": response,
"response_token_count": len(response_ids),
"steps": steps,
}
def scheme_b_supervised_old_helpful(config: SchemeBConfig) -> dict:
tokenizer, base_model, arm_model = load_base_and_helpful_models(config.adapter_path)
prompt, response = _resolve_prompt_and_response(config.prompt, config.response)
prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
response_ids = tokenizer(response, add_special_tokens=False)["input_ids"]
steps = []
for step_idx, gold_id in enumerate(response_ids[: config.steps]):
prefix_ids = response_ids[:step_idx]
full_prefix = prompt_ids + prefix_ids
logp_base = _forward_last_logprobs(base_model, full_prefix)
logp_arm = _forward_last_logprobs(arm_model, full_prefix)
score_s = logp_base + logp_arm
prob_base = torch.exp(logp_base)
prob_arm = torch.exp(logp_arm)
base_sparse_probs = _sparsemax(logp_base)
allowed_mask = base_sparse_probs > 0
allowed_ids = torch.nonzero(allowed_mask, as_tuple=False).squeeze(-1)
if allowed_ids.numel() == 0:
allowed_ids = torch.tensor([int(torch.argmax(logp_base).item())], dtype=torch.long)
filtered_score = score_s[allowed_ids]
filtered_probs = torch.softmax(filtered_score, dim=-1)
if int(gold_id) in set(allowed_ids.tolist()):
gold_allowed_index = (allowed_ids == int(gold_id)).nonzero(as_tuple=False).item()
gold_final_prob = float(filtered_probs[gold_allowed_index].item())
gold_in_scope = True
else:
gold_final_prob = 0.0
gold_in_scope = False
prob_rows = []
top_prob_vals, top_prob_idx = torch.topk(filtered_probs, k=min(config.topk, allowed_ids.numel()))
top_prob_ids = allowed_ids[top_prob_idx]
seen_gold_prob = False
for tok_id, val in zip(top_prob_ids.tolist(), top_prob_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold_prob = seen_gold_prob or is_gold
prob_rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold_prob:
prob_rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": gold_final_prob,
"is_gold": True,
"gold_in_scope": False,
}
)
score_rows = _score_rows_from_allowed(score_s, allowed_ids, tokenizer, int(gold_id), config.topk)
steps.append(
{
"step_index": step_idx + 1,
"gold_token_id": int(gold_id),
"gold_token_text": tokenizer.decode([int(gold_id)]),
"prefix_response_token_ids": prefix_ids,
"prefix_response_text": tokenizer.decode(prefix_ids),
"allowed_token_count": int(allowed_ids.numel()),
"gold_token_sparsemax_base_weight": float(base_sparse_probs[int(gold_id)].item()),
"gold_token_in_scope": gold_in_scope,
"gold_logp_base": float(logp_base[int(gold_id)].item()),
"gold_logp_arm": float(logp_arm[int(gold_id)].item()),
"gold_score_s": float(score_s[int(gold_id)].item()),
"gold_prob_base": float(prob_base[int(gold_id)].item()),
"gold_prob_arm": float(prob_arm[int(gold_id)].item()),
"gold_final_prob_after_support_softmax": gold_final_prob,
"logp_base_top10_allowed": _top_rows_from_allowed(logp_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"logp_arm_top10_allowed": _top_rows_from_allowed(logp_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"score_s_top10_allowed": score_rows,
"prob_base_top10_allowed": _top_rows_from_allowed(prob_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_arm_top10_allowed": _top_rows_from_allowed(prob_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_score_top10_allowed": prob_rows,
}
)
return {
"scheme": "B",
"support_rule": "sparsemax(logp_base) > 0",
"prompt": prompt,
"response": response,
"response_token_count": len(response_ids),
"steps": steps,
}
def scheme_a_supervised_old_helpful_x1(config: SchemeAConfig, x1_config: X1Config | None = None) -> dict:
tokenizer, base_model, arm_model = load_base_and_helpful_models(config.adapter_path)
prompt, response = _resolve_prompt_and_response(config.prompt, config.response)
prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
response_ids = tokenizer(response, add_special_tokens=False)["input_ids"]
x1_config = x1_config or X1Config()
steps = []
for step_idx, gold_id in enumerate(response_ids[: config.steps]):
prefix_ids = response_ids[:step_idx]
full_prefix = prompt_ids + prefix_ids
logp_base = _forward_last_logprobs(base_model, full_prefix)
logp_arm = _forward_last_logprobs(arm_model, full_prefix)
prob_base = torch.exp(logp_base)
prob_arm = torch.exp(logp_arm)
allowed_ids = _compute_allowed_ids_scheme_a(prob_base, config.base_prob_threshold)
score_s, gate = _build_score_with_optional_x1(logp_base, logp_arm, prob_base, x1_config)
filtered_score = score_s[allowed_ids]
filtered_probs = torch.softmax(filtered_score, dim=-1)
if int(gold_id) in set(allowed_ids.tolist()):
gold_allowed_index = (allowed_ids == int(gold_id)).nonzero(as_tuple=False).item()
gold_final_prob = float(filtered_probs[gold_allowed_index].item())
gold_in_scope = True
else:
gold_final_prob = 0.0
gold_in_scope = False
prob_rows = []
top_prob_vals, top_prob_idx = torch.topk(filtered_probs, k=min(config.topk, allowed_ids.numel()))
top_prob_ids = allowed_ids[top_prob_idx]
seen_gold_prob = False
for tok_id, val in zip(top_prob_ids.tolist(), top_prob_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold_prob = seen_gold_prob or is_gold
prob_rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold_prob:
prob_rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": gold_final_prob,
"is_gold": True,
"gold_in_scope": False,
}
)
step_payload = {
"step_index": step_idx + 1,
"gold_token_id": int(gold_id),
"gold_token_text": tokenizer.decode([int(gold_id)]),
"prefix_response_token_ids": prefix_ids,
"prefix_response_text": tokenizer.decode(prefix_ids),
"allowed_token_count": int(allowed_ids.numel()),
"gold_token_base_prob": float(prob_base[int(gold_id)].item()),
"gold_token_in_scope": gold_in_scope,
"gold_logp_base": float(logp_base[int(gold_id)].item()),
"gold_logp_arm": float(logp_arm[int(gold_id)].item()),
"gold_score_s": float(score_s[int(gold_id)].item()),
"gold_prob_arm": float(prob_arm[int(gold_id)].item()),
"gold_final_prob_after_filter_softmax": gold_final_prob,
"logp_base_top10_allowed": _top_rows_from_allowed(logp_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"logp_arm_top10_allowed": _top_rows_from_allowed(logp_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"score_s_top10_allowed": _score_rows_from_allowed(score_s, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_base_top10_allowed": _top_rows_from_allowed(prob_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_arm_top10_allowed": _top_rows_from_allowed(prob_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_score_top10_allowed": prob_rows,
}
step_payload.update(gate)
steps.append(step_payload)
return {
"scheme": "A",
"plugin": "X1",
"threshold": config.base_prob_threshold,
"prompt": prompt,
"response": response,
"response_token_count": len(response_ids),
"steps": steps,
}
def scheme_b_supervised_old_helpful_x1(config: SchemeBConfig, x1_config: X1Config | None = None) -> dict:
tokenizer, base_model, arm_model = load_base_and_helpful_models(config.adapter_path)
prompt, response = _resolve_prompt_and_response(config.prompt, config.response)
prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
response_ids = tokenizer(response, add_special_tokens=False)["input_ids"]
x1_config = x1_config or X1Config()
steps = []
for step_idx, gold_id in enumerate(response_ids[: config.steps]):
prefix_ids = response_ids[:step_idx]
full_prefix = prompt_ids + prefix_ids
logp_base = _forward_last_logprobs(base_model, full_prefix)
logp_arm = _forward_last_logprobs(arm_model, full_prefix)
prob_base = torch.exp(logp_base)
prob_arm = torch.exp(logp_arm)
allowed_ids, base_sparse_probs = _compute_allowed_ids_scheme_b(logp_base)
score_s, gate = _build_score_with_optional_x1(logp_base, logp_arm, prob_base, x1_config)
filtered_score = score_s[allowed_ids]
filtered_probs = torch.softmax(filtered_score, dim=-1)
if int(gold_id) in set(allowed_ids.tolist()):
gold_allowed_index = (allowed_ids == int(gold_id)).nonzero(as_tuple=False).item()
gold_final_prob = float(filtered_probs[gold_allowed_index].item())
gold_in_scope = True
else:
gold_final_prob = 0.0
gold_in_scope = False
prob_rows = []
top_prob_vals, top_prob_idx = torch.topk(filtered_probs, k=min(config.topk, allowed_ids.numel()))
top_prob_ids = allowed_ids[top_prob_idx]
seen_gold_prob = False
for tok_id, val in zip(top_prob_ids.tolist(), top_prob_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold_prob = seen_gold_prob or is_gold
prob_rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold_prob:
prob_rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": gold_final_prob,
"is_gold": True,
"gold_in_scope": False,
}
)
step_payload = {
"step_index": step_idx + 1,
"gold_token_id": int(gold_id),
"gold_token_text": tokenizer.decode([int(gold_id)]),
"prefix_response_token_ids": prefix_ids,
"prefix_response_text": tokenizer.decode(prefix_ids),
"allowed_token_count": int(allowed_ids.numel()),
"gold_token_sparsemax_base_weight": float(base_sparse_probs[int(gold_id)].item()),
"gold_token_in_scope": gold_in_scope,
"gold_logp_base": float(logp_base[int(gold_id)].item()),
"gold_logp_arm": float(logp_arm[int(gold_id)].item()),
"gold_score_s": float(score_s[int(gold_id)].item()),
"gold_prob_base": float(prob_base[int(gold_id)].item()),
"gold_prob_arm": float(prob_arm[int(gold_id)].item()),
"gold_final_prob_after_support_softmax": gold_final_prob,
"logp_base_top10_allowed": _top_rows_from_allowed(logp_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"logp_arm_top10_allowed": _top_rows_from_allowed(logp_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"score_s_top10_allowed": _score_rows_from_allowed(score_s, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_base_top10_allowed": _top_rows_from_allowed(prob_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_arm_top10_allowed": _top_rows_from_allowed(prob_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_score_top10_allowed": prob_rows,
}
step_payload.update(gate)
steps.append(step_payload)
return {
"scheme": "B",
"plugin": "X1",
"support_rule": "sparsemax(logp_base) > 0",
"prompt": prompt,
"response": response,
"response_token_count": len(response_ids),
"steps": steps,
}
def prefix_only_old_helpful_winner(config: PrefixOnlyConfig) -> dict:
tokenizer, base_model, arm_model = load_base_and_helpful_models(config.adapter_path)
prompt, response = _resolve_prompt_and_response(config.prompt, config.response)
prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
response_ids = tokenizer(response, add_special_tokens=False)["input_ids"]
steps = []
for step_idx, gold_id in enumerate(response_ids[: config.steps]):
prefix_ids = response_ids[:step_idx]
full_prefix = prompt_ids + prefix_ids
logp_base = _forward_last_logprobs(base_model, full_prefix)
logp_arm = _forward_last_logprobs(arm_model, full_prefix)
score_s = logp_base + logp_arm
prob_base = torch.exp(logp_base)
prob_arm = torch.exp(logp_arm)
prob_score = torch.softmax(score_s, dim=-1)
steps.append(
{
"step_index": step_idx + 1,
"gold_token_id": int(gold_id),
"gold_token_text": tokenizer.decode([int(gold_id)]),
"prefix_response_token_ids": prefix_ids,
"prefix_response_text": tokenizer.decode(prefix_ids),
"gold_logp_base": float(logp_base[int(gold_id)].item()),
"gold_logp_arm": float(logp_arm[int(gold_id)].item()),
"gold_score_s": float(score_s[int(gold_id)].item()),
"gold_prob_base": float(prob_base[int(gold_id)].item()),
"gold_prob_arm": float(prob_arm[int(gold_id)].item()),
"gold_prob_score": float(prob_score[int(gold_id)].item()),
"logp_base_top10": _top_rows_full_vocab(logp_base, tokenizer, int(gold_id), config.topk),
"logp_arm_top10": _top_rows_full_vocab(logp_arm, tokenizer, int(gold_id), config.topk),
"score_s_top10": _top_rows_full_vocab(score_s, tokenizer, int(gold_id), config.topk),
"prob_base_top10": _top_rows_full_vocab(prob_base, tokenizer, int(gold_id), config.topk),
"prob_arm_top10": _top_rows_full_vocab(prob_arm, tokenizer, int(gold_id), config.topk),
"prob_score_top10": _top_rows_full_vocab(prob_score, tokenizer, int(gold_id), config.topk),
}
)
return {
"mode": "prefix_only_old_helpful_winner",
"prompt": prompt,
"response": response,
"response_token_count": len(response_ids),
"steps": steps,
}
def scheme_b_temp_support_supervised_old_helpful(config: SchemeBTempSupportConfig) -> dict:
tokenizer, base_model, arm_model = load_base_and_helpful_models(config.adapter_path)
prompt, response = _resolve_prompt_and_response(config.prompt, config.response)
prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
response_ids = tokenizer(response, add_special_tokens=False)["input_ids"]
steps = []
for step_idx, gold_id in enumerate(response_ids[: config.steps]):
prefix_ids = response_ids[:step_idx]
full_prefix = prompt_ids + prefix_ids
logp_base = _forward_last_logprobs(base_model, full_prefix)
logp_arm = _forward_last_logprobs(arm_model, full_prefix)
prob_base = torch.exp(logp_base)
prob_arm = torch.exp(logp_arm)
allowed_ids, scaled_base_sparse_probs = _compute_allowed_ids_scheme_b_temp(
logp_base, config.support_temperature
)
score_s = logp_base + config.alpha * logp_arm
filtered_score = score_s[allowed_ids]
filtered_probs = torch.softmax(filtered_score, dim=-1)
if int(gold_id) in set(allowed_ids.tolist()):
gold_allowed_index = (allowed_ids == int(gold_id)).nonzero(as_tuple=False).item()
gold_final_prob = float(filtered_probs[gold_allowed_index].item())
gold_in_scope = True
else:
gold_final_prob = 0.0
gold_in_scope = False
prob_rows = []
top_prob_vals, top_prob_idx = torch.topk(filtered_probs, k=min(config.topk, allowed_ids.numel()))
top_prob_ids = allowed_ids[top_prob_idx]
seen_gold_prob = False
for tok_id, val in zip(top_prob_ids.tolist(), top_prob_vals.tolist()):
is_gold = int(tok_id) == int(gold_id)
seen_gold_prob = seen_gold_prob or is_gold
prob_rows.append(
{
"token_id": int(tok_id),
"token_text": tokenizer.decode([int(tok_id)]),
"value": float(val),
"is_gold": bool(is_gold),
"gold_in_scope": True,
}
)
if not seen_gold_prob:
prob_rows.append(
{
"token_id": int(gold_id),
"token_text": tokenizer.decode([int(gold_id)]),
"value": gold_final_prob,
"is_gold": True,
"gold_in_scope": False,
}
)
steps.append(
{
"step_index": step_idx + 1,
"gold_token_id": int(gold_id),
"gold_token_text": tokenizer.decode([int(gold_id)]),
"prefix_response_token_ids": prefix_ids,
"prefix_response_text": tokenizer.decode(prefix_ids),
"allowed_token_count": int(allowed_ids.numel()),
"support_temperature": float(config.support_temperature),
"alpha": float(config.alpha),
"gold_token_sparsemax_base_weight_scaled": float(scaled_base_sparse_probs[int(gold_id)].item()),
"gold_token_in_scope": gold_in_scope,
"gold_logp_base": float(logp_base[int(gold_id)].item()),
"gold_logp_arm": float(logp_arm[int(gold_id)].item()),
"gold_score_s": float(score_s[int(gold_id)].item()),
"gold_prob_base": float(prob_base[int(gold_id)].item()),
"gold_prob_arm": float(prob_arm[int(gold_id)].item()),
"gold_final_prob_after_support_softmax": gold_final_prob,
"logp_base_top10_allowed": _top_rows_from_allowed(logp_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"logp_arm_top10_allowed": _top_rows_from_allowed(logp_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"score_s_top10_allowed": _score_rows_from_allowed(score_s, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_base_top10_allowed": _top_rows_from_allowed(prob_base, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_arm_top10_allowed": _top_rows_from_allowed(prob_arm, allowed_ids, tokenizer, int(gold_id), config.topk),
"prob_score_top10_allowed": prob_rows,
}
)
support_counts = [step["allowed_token_count"] for step in steps]
support_counts_sorted = sorted(support_counts)
mid = len(support_counts_sorted) // 2
if len(support_counts_sorted) % 2 == 1:
median_support = float(support_counts_sorted[mid])
else:
median_support = 0.5 * (support_counts_sorted[mid - 1] + support_counts_sorted[mid])
return {
"scheme": "B_temp_support",
"support_rule": "support(sparsemax(logp_base / T_support))",
"support_temperature": float(config.support_temperature),
"alpha": float(config.alpha),
"prompt": prompt,
"response": response,
"response_token_count": len(response_ids),
"support_stats": {
"steps_analyzed": len(steps),
"min": int(min(support_counts)),
"max": int(max(support_counts)),
"mean": float(sum(support_counts) / len(support_counts)),
"median": float(median_support),
},
"steps": steps,
}