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 with a chosen answer and a rejected answer , it needs a policy (the model being trained) and a frozen reference (the model before this training, usually the SFT (supervised fine-tuning) model).
The loss
For an answer , how much more likely has the policy made it, compared with the reference?
Here is the sum of the log-probabilities of the answer's tokens. The loss wants (chosen) to grow more than (rejected):
where is the sigmoid and (beta) is a constant that controls how strongly the policy may move away from the reference.
Example. At the start of training, , so and the loss is : a good first check.
Later, the sequence log-probabilities are: policy for the chosen and for the rejected; reference for both. Then and . With : , lower than : the policy has moved in the right direction.
The length-normalized variant
A longer answer has a sum of more log-probabilities, so its tends to be larger in size. OLMo 3 divides each by the length of its answer before taking the difference:
Example. The same log-probabilities, with the chosen answer 10 tokens long and the rejected one 20, and . The normalized values are and ; the difference is , and .
The log-probability of an answer
sequence_logprob computes 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):logitshas shape and row is the model's distribution fortokens[t](already aligned: no shift to do). Return the sum of the log-probabilities of the tokens wheremaskis true.dpo_loss(pi_c, pi_r, ref_c, ref_r, beta): arrays of shape with the sequence log-probabilities of the chosen and rejected answers under the policy and the reference. Return the mean loss over the 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: is
np.logaddexp(0, -z). - No loops over pairs or tokens.