Roadmap / Post-Training & Alignment

✓

Direct Preference Optimization (DPO)

mediumpost-traininglossespreferences

The problem

Learning from human preferences used to need two steps: train a reward model, then use reinforcement learning against it. DPO (Direct Preference Optimization) skips both and trains the language model directly on pairs of answers. For a prompt xx with a chosen answer ycy_c and a rejected answer yry_r, it needs a policy πθ\pi_\theta (the model being trained) and a frozen reference πref\pi_{\text{ref}} (the model before this training, usually the SFT (supervised fine-tuning) model).

The loss

For an answer yy, how much more likely has the policy made it, compared with the reference?

Δ=log⁡πθ(y∣x)−log⁡πref(y∣x)\Delta = \log \pi_\theta(y \mid x) - \log \pi_{\text{ref}}(y \mid x)

Here log⁡π(y∣x)\log \pi(y \mid x) is the sum of the log-probabilities of the answer's tokens. The loss wants Δc\Delta_c (chosen) to grow more than Δr\Delta_r (rejected):

L=−log⁡σ(β (Δc−Δr))\mathcal{L} = -\log \sigma\big(\beta\,(\Delta_c - \Delta_r)\big)

where σ\sigma is the sigmoid and β\beta (beta) is a constant that controls how strongly the policy may move away from the reference.

Example. At the start of training, πθ=πref\pi_\theta = \pi_{\text{ref}}, so Δc=Δr=0\Delta_c = \Delta_r = 0 and the loss is −log⁡σ(0)=log⁡2=0.693-\log \sigma(0) = \log 2 = 0.693: a good first check.

Later, the sequence log-probabilities are: policy −10-10 for the chosen and −12-12 for the rejected; reference −11-11 for both. Then Δc=+1\Delta_c = +1 and Δr=−1\Delta_r = -1. With β=0.1\beta = 0.1: −log⁡σ(0.1⋅2)=0.598-\log \sigma(0.1 \cdot 2) = 0.598, lower than 0.6930.693: the policy has moved in the right direction.

The length-normalized variant

A longer answer has a sum of more log-probabilities, so its Δ\Delta tends to be larger in size. OLMo 3 divides each Δ\Delta by the length of its answer before taking the difference:

L=−log⁡σ ⁣(β(Δclenc−Δrlenr))\mathcal{L} = -\log \sigma\!\left(\beta\left(\frac{\Delta_c}{\text{len}_c} - \frac{\Delta_r}{\text{len}_r}\right)\right)

Example. The same log-probabilities, with the chosen answer 10 tokens long and the rejected one 20, and β=5\beta = 5. The normalized values are Δc/10=0.1\Delta_c / 10 = 0.1 and Δr/20=−0.05\Delta_r / 20 = -0.05; the difference is 0.150.15, and −log⁡σ(5⋅0.15)=0.387-\log \sigma(5 \cdot 0.15) = 0.387.

The log-probability of an answer

sequence_logprob computes log⁡π(y∣x)\log \pi(y \mid x) from the model's logits: for each position take the log-softmax (section 2), pick the entry of the actual token, and sum over the answer's positions only (where mask is true: the prompt is not part of it).

What to implement

  • sequence_logprob(logits, tokens, mask): logits has shape (T,V)(T, V) and row tt is the model's distribution for tokens[t] (already aligned: no shift to do). Return the sum of the log-probabilities of the tokens where mask is true.
  • dpo_loss(pi_c, pi_r, ref_c, ref_r, beta): arrays of shape (N,)(N,) with the sequence log-probabilities of the chosen and rejected answers under the policy and the reference. Return the mean loss over the NN pairs.
  • dpo_loss_length_normalized(pi_c, pi_r, ref_c, ref_r, len_c, len_r, beta): the variant above.

Requirements

  • Stable for any values: −log⁡σ(z)-\log\sigma(z) is np.logaddexp(0, -z).
  • No loops over pairs or tokens.
solution.py
Python idle

Press Run (⌘/Ctrl + Enter) to test your code.