Downloads · 30 days
0
Shamima/dpo-implicit-reward
dpo-implicit-reward is a machine learning model from Shamima. Use it for the machine learning task on the model card, and read the license before you ship it in a product. It is set up for peft. The card lists the license as apache-2.0.
Research checkpoints for a controlled study asking whether DPO's implicit reward
Downloads · 30 days
0
Access
Public
Updated Sep 8, 2026
Repo size
2.4 GB
Likes
0
Public
Click a slice to open those files.
.safetensors2.6 GB · 92%
From the Hugging Face model README
Research checkpoints for a controlled study asking whether DPO's implicit reward
r̂(x, y) = β · [ log π_θ(y|x) − log π_ref(y|x) ]
works as well as an explicitly trained Bradley–Terry reward model, under matched data, in-distribution and under distribution shift.
Code: github.com/silvererudite/dpo-implicit-reward
π_ref must be sft_merged/. Every adapter here was trained on top of the merged SFT
checkpoint, not on raw Qwen/Qwen2.5-0.5B. Loading an adapter onto the raw base model gives a
different — and wrong — model. For the implicit reward specifically, using the wrong reference
silently changes the quantity you compute.
These are research artifacts at 0.5B scale, not production reward models. They are not safety-aligned and should not be used to rank or moderate content in a deployed setting.
| Path | What it is |
|---|---|
sft_merged/ | π_ref. SFT policy with LoRA merged into the weights. A complete model. Everything else is an adapter on top of this. |
sft/ | The SFT LoRA adapter alone (applies to Qwen/Qwen2.5-0.5B). Provided for provenance; you normally want sft_merged/. |
The headline result. Each run saw exactly 8,000 preference pairs in 500 optimizer steps.
| Path | What it is |
|---|---|
dpo_beta0.1_8k_s{0,1,2,3,4}/ | DPO policies, β=0.1. The implicit reward is read off these. |
rm_8k_s{0,1,2,3,4}/ | Explicit Bradley–Terry reward models (LoRA + scalar head). |
dpo_beta0.1_8k/, rm_8k/ | Earlier single-seed run, kept for provenance. Superseded — trained before the pair-matching fix, so not comparable to the seeded runs. |
Trained to convergence with held-out evaluation every 100 steps, to test whether the 1-epoch ranking was an artifact of stopping early. It was not.
| Path | What it is |
|---|---|
long/dpo_beta0.1_8k_s{0,1}/ | DPO, 4 epochs (2,000 steps). |
long/rm_8k_s{0,1}/ | Reward model, 4 epochs. |
long/*/checkpoint-{250,500,...,2000}/ | Snapshots every 250 steps = every half epoch. Adapter weights + trainer_state.json only; optimizer state was stripped, so these are for analysis, not resuming. |
| Path | What it is |
|---|---|
results/eval_s*.json | Per-seed metrics on all six test sets. |
results/raw_s*.json | Per-pair scores. Every metric is recomputable from these without re-running any model. |
results/aggregate_*.json | Cross-seed means with 95% CIs. |
results/bias_over_training_*.json | Surface-form preference at each checkpoint. |
results/curves/*.csv | Every training run's loss/accuracy curves. |
figures/ | All result figures. |
Every training directory carries a budget.json recording steps, epochs, effective batch, LR,
seed and wall-clock, so the compute-matching claim is auditable rather than asserted.
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
REPO = "Shamima/dpo-implicit-reward"
tok = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B")
if tok.pad_token is None:
tok.pad_token = tok.eos_token
# subfolder= pulls just this directory out of the repo
ref = AutoModelForCausalLM.from_pretrained(
REPO, subfolder="sft_merged", torch_dtype=torch.bfloat16).cuda().eval()
from peft import PeftModel
backbone = AutoModelForCausalLM.from_pretrained(
REPO, subfolder="sft_merged", torch_dtype=torch.bfloat16).cuda()
policy = PeftModel.from_pretrained(backbone, REPO, subfolder="dpo_beta0.1_8k_s0").cuda().eval()
# r̂ = β · (log π_θ(y|x) − log π_ref(y|x)), summed over RESPONSE tokens only
# (prompt masked). See src/scoring.py in the GitHub repo for the exact implementation.
The reward is a difference of two models, so you need both policy and ref loaded. Ranking
within a prompt is invariant to β, so β only affects calibration, not pairwise accuracy.
from transformers import AutoModelForSequenceClassification
base = AutoModelForSequenceClassification.from_pretrained(
REPO, subfolder="sft_merged", num_labels=1, torch_dtype=torch.bfloat16)
base.config.pad_token_id = tok.pad_token_id
rm = PeftModel.from_pretrained(base, REPO, subfolder="rm_8k_s0").cuda().eval()
text = tok.apply_chat_template(
[{"role": "user", "content": prompt},
{"role": "assistant", "content": response}], tokenize=False)
score = rm(**tok(text, return_tensors="pt", truncation=True, max_length=1024).to("cuda")).logits[0].item()
The trained scalar head ships inside the adapter under modules_to_save, so loading the adapter
restores it. Transformers will warn that score.weight was "newly initialized" when the base is
created — that warning is expected and the adapter overwrites it.
rm_at_epoch2 = PeftModel.from_pretrained(
base, REPO, subfolder="long/rm_8k_s0/checkpoint-1000").cuda().eval()
Step → epoch mapping: 500 steps = 1 epoch (8,000 pairs at effective batch 16).
So checkpoint-250 = 0.5 epochs, checkpoint-1000 = 2 epochs, checkpoint-2000 = 4 epochs.
from huggingface_hub import hf_hub_download
import json
p = hf_hub_download(REPO, "results/aggregate_beta0.1_8k.json", repo_type="model")
agg = json.load(open(p))["results"]
| If you want… | Use |
|---|---|
| The best reward model here | long/rm_8k_s0/checkpoint-1000 — its held-out peak, ~epoch 2 |
| The headline-experiment reward model | rm_8k_s0 (1 epoch, matched budget) |
| The best implicit reward | dpo_beta0.1_8k_s0 — DPO peaks at ~1 epoch and degrades after |
| To measure seed variance | all five of *_s0 … *_s4 |
| To study overfitting dynamics | long/*/checkpoint-* |
Do not use long/*/checkpoint-2000 (4 epochs) expecting the best model — both scorers are
past their peak there. The reward model's held-out accuracy falls from 0.731 at epoch 2 to ~0.69
at epoch 4.
Length-controlled pairwise accuracy, mean ± 95% CI over 5 seeds. Length control restricts scoring to pairs whose two responses are within 1.2× in length, because raw accuracy on these benchmarks is substantially a length signal.
| Test set | DPO implicit | Explicit RM | Untrained baseline | pairs |
|---|---|---|---|---|
| UltraFeedback (ID) | 0.600 ± 0.026 | 0.663 ± 0.009 | 0.529 | 442 |
| RewardBench Chat | 0.800 ± 0.044 | 0.747 ± 0.080 | 0.500 | 32 |
| RewardBench Chat-Hard | 0.535 ± 0.052 | 0.615 ± 0.061 | 0.661 | 62 |
| RewardBench Safety | 0.492 ± 0.036 | 0.549 ± 0.059 | 0.530 | 83 |
| RewardBench Reasoning | 0.850 ± 0.003 | 0.663 ± 0.019 | 0.906 | 908 |
| HH-RLHF harmless | 0.472 ± 0.016 | 0.429 ± 0.036 | 0.435 | 276 |
The "untrained baseline" is the length-normalized log-probability of the response under π_ref — no reward training at all.
Training both to 4 epochs: the reward model peaks at 0.731 held-out accuracy at epoch 2 then overfits; DPO peaks at 0.676 at epoch 1 and is flat thereafter. The ranking never inverts.
Scoring every checkpoint for surface-form preference produced a result that refuted our hypothesis: we expected the reward model's length bias to grow with training. It peaks at +0.069 around epoch 1.5 and then fades to +0.002. DPO's drifts the other way, from −0.134 to −0.232 — it progressively prefers shorter answers than humans do, and its accuracy falls with it.
Qwen/Qwen2.5-0.5B, LoRA r=16 / α=32 / dropout=0.05 on all attention and MLP projections, bf16,
single NVIDIA A10G per job, TRL 0.15.2. DPO and the reward model both initialize from
sft_merged/ and see byte-identical pairs (verified by hash) so neither is advantaged.
| Stage | Steps | Epochs | Effective batch | LR | Wall-clock |
|---|---|---|---|---|---|
| SFT → π_ref (32k pairs) | 1998 | 1 | 16 | 2e-4 | 1h 13m |
| DPO β=0.1 (8k pairs) | 500 | 1 | 16 | 5e-5 | ~40 min |
| Reward model (8k pairs) | 500 | 1 | 16 | 5e-5 | ~24 min |
| Convergence runs | 2000 | 4 | 16 | 5e-5 | 175 / 105 min |
Apache 2.0, following the Qwen/Qwen2.5-0.5B base model.