Preference optimisation

Post-training · Learning from comparisons

Offline preference optimisation

Six ways to teach a model with comparisons instead of examples. SFT only ever says “write this”. These methods also say “not that”: from a chosen and a rejected answer, or from single answers labelled good or bad, they raise the better answer and push the worse one down. They train on a fixed dataset collected in advance, with no reward model and no sampling during training: that is what offline and RL-free mean.

Fact 1

SFT only pushes up

From the SFT workbook: training raises the target token, and every other token loses a little, in proportion to its probability. A specific bad answer is never pushed down on purpose.

Fact 2

A preference is a comparison

People and judges find it much easier to say which of two answers is better than to write the perfect one or to score one from 1 to 10. A preference pair is a prompt \(x\) with a chosen answer \(y_w\) (w for win) and a rejected one \(y_l\) (l for lose).

Fact 3

The reference model is a leash

Most methods keep a frozen copy of the starting model, \(\pi_{\text{ref}}\). An answer is scored by how much more likely the trained model makes it than the copy does, so the model improves without forgetting how to write.

Colour key Prompt: read, never graded Chosen: the better answer, pushed up Rejected: the worse answer, pushed down The model being trained Frozen reference, or a judge β, a margin or a weight
Symbol tables datacomes from your dataset trainedthe model being trained, or what it outputs frozena fixed model: the reference or a judge generatedwritten by a model while training runs settinga number you choose: β, a margin, a weight mathnotation: a sign or an operation resultthe number the formula computes

One loss, four levers · click a technique

Nothing changedthe baseline
What one example holdsa pair, or one labelled answer
What stops the drifta reference model, or something else
How the gap is scoredthe loss curve, the length
Where the comparison is cutone step, or a whole session

The loss all six build on

\(\displaystyle \mathcal{L}(\theta) = -\log\sigma\Big(\beta\,\big[\,\hat r_\theta(x,y_w) - \hat r_\theta(x,y_l)\,\big]\Big)\)
\(\displaystyle \hat r_\theta(x,y) = \log\frac{\pi_\theta(y\mid x)}{\pi_{\text{ref}}(y\mid x)}\)

Give the chosen answer a higher score than the rejected one. An answer's score \(\hat r\) is how much more likely the trained model \(\pi_\theta\) makes it than the frozen reference \(\pi_{\text{ref}}\) does.

What each symbol means
\(\mathcal{L}(\theta)\)“L of theta”resultThe loss for one comparison: one positive number, lower is better. It depends on the weights \(\theta\).
\(\theta\)“theta”trainedAll the weights of the model being trained. Training changes only these.
\(x\)dataThe prompt.
\(y_w\)“y win”dataThe chosen answer, the better one of the pair.
\(y_l\)“y lose”dataThe rejected answer, the worse one.
\(y\)dataAny one answer: in the second line it stands for \(y_w\) or \(y_l\) in turn.
\(\pi_\theta(y\mid x)\)“pi theta of y given x”trainedThe probability that the trained model writes the whole answer \(y\) after the prompt \(x\). \(\pi\) (pi) is the usual letter for a model that chooses what to write, called a policy; it has nothing to do with 3.14 here.
\(\pi_{\text{ref}}(y\mid x)\)“pi ref”frozenThe same probability under a frozen copy of the starting model, the reference. It never changes during training.
\(\hat r_\theta(x,y)\)“r hat”, the scoretrainedThe answer's score: the log of how many times more likely the trained model makes \(y\) than the reference does. 0 means “no change”, positive means “more likely than before”, negative means “less likely”. The hat \(\hat{\ }\) marks something estimated rather than given.
\(\log\frac{a}{b}\)mathA log-ratio. It equals \(\log a - \log b\), so the score is simply the trained model's log-probability minus the reference's.
\(\beta\)“beta”settingA number you choose, usually 0.1: how strongly the gap counts, and so how far the model may move from the reference.
\(\big[\,\hat r_\theta(x,y_w) - \hat r_\theta(x,y_l)\,\big]\)trainedThe gap, or margin: the chosen answer's score minus the rejected answer's. Positive when the model already favours the chosen one.
\(\sigma\)“sigma”, the sigmoidmath\(\sigma(u) = 1/(1+e^{-u})\): squashes any number into a probability between 0 and 1. \(\sigma(0) = 0.5\), large positive gaps give almost 1, large negative ones almost 0.
\(-\log\)mathTurns that probability into a penalty: 0.69 at a coin flip (\(\sigma = 0.5\)), near 0 when the model clearly prefers the chosen answer, large when it prefers the rejected one.

Three questions every method answers

Q1
What does one example hold?

A pair costs two answers and a comparison for every prompt. Single good or bad labels are cheaper and far more plentiful.

Q2
What stops the model drifting?

A model can win every comparison by turning strange. DPO ties it to a frozen reference; others use an SFT term, a target gap, or nothing at all.

Q3
Which part gets the credit?

A comparison of whole answers does not say which words or steps made the difference. The unit can be one step, one answer or a whole session.

The base · every other technique changes one part of it

DPO Direct preference optimisation

Show the model a prompt with two answers, one marked better. Raise the better one and lower the worse one, both measured against a frozen copy of the starting model, so the model learns the preference without wandering far from what it already does well.

A chosen and a rejected answer, a frozen reference, and the one scalar that pushes them apart.

Changes vs SFT
Adds a rejected answer and pushes it down; keeps a frozen reference model
Its answers
Q1 pairs · Q2 reference + β · Q3 whole answers
Cost
●●●●● pairs + two models' passes
Library
Ready: TRL DPOTrainer, multi-turn and tool calls supported (tool turns are scored too: see #15)
1

The idea in plain words

SFT is told only what to write. DPO is also told what not to write. Each training example is a prompt \(x\) with two answers: the chosen one \(y_w\) and the rejected one \(y_l\). Someone decided the first is better: a person, an LLM judge or an automatic check.

Training raises the chosen answer and lowers the rejected one, but not in absolute terms. Each answer is scored by how much more likely the trained model makes it than a frozen copy of the starting model, the reference \(\pi_{\text{ref}}\). The loss wants the chosen answer's score above the rejected one's, and it pushes less and less once the gap is comfortable.

The strength \(\beta\) sets how tightly the model is tied to the reference. The name says what DPO skips: classic RLHF first trains a separate reward model and then runs reinforcement learning against it. DPO reaches the same goal directly, with one loss on the preference pairs.

2

See it work

Push the chosen up, the rejected down

A tiny model chooses among four whole answers to one prompt. Pick a pair, press Train, and watch the chosen answer gain on the frozen reference while the rejected one loses. Then lower β to see how far the model will drift to win, or switch to SFT to see what plain imitation does to the rejected answer.

3

Where the training signal comes from

Three decisions: who writes the two answers, who decides which is better, and which pairs you keep.

4

A small example

The prompt and its two answers
User · promptExplain why the sea is salty, for a 10-year-old.
Answer A · chosenRain slowly dissolves tiny bits of salt from rocks, and rivers carry them to the sea. The sun lifts the water back up as vapour, but the salt stays behind, so over millions of years the sea got salty.
Answer B · rejectedOcean salinity results from the weathering of crustal minerals and from hydrothermal inputs, chiefly sodium and chloride ions, concentrated by evaporation.
The judge's verdict
To the judge · rubricWhich answer is better for a 10-year-old: accurate, simple and complete? Reply A or B, then one sentence.
JudgeA. Both are accurate, but A uses everyday words and explains the evaporation step; B is written for an expert.
Judge · order swappedB. It uses everyday words; A is written for an expert.

Same winner in both orders, so the pair is kept.

What DPO computes for this row

Each log-probability is the sum over the answer's tokens, as in SFT.

trained π_θ reference π_ref score r̂ chosen A −41.2 −41.6 +0.4 rejected B −58.9 −58.2 −0.7 gap +0.4 − (−0.7) = 1.1 β × gap = 0.1 × 1.1 = 0.11 loss −log σ(0.11) = 0.64

The model already leans the right way: the loss is below its starting value of \(\log 2 = 0.69\). Training keeps widening the gap.

5

What a training batch looks like

PromptChosenRejectedDecided by
Explain why the sea is salty, for a 10-year-old.Rain slowly dissolves tiny bits of salt from rocks…Ocean salinity results from the weathering of crustal minerals…judge, order swapped
Is 91 a prime number?No. 91 = 7 × 13.Yes, 91 is prime: it is odd and not divisible by 3 or 5.checker
Write a one-line reminder to water the plants.Don't forget to water the plants today!This is a friendly reminder that it would be a good idea to water the plants at some point today, if you have the time.human rater
Translate to French: “The shop opens at nine.”Le magasin ouvre à neuf heures.La boutique ouvre neuf.judge
# dpo_train.jsonl: one pair per line. In conversational rows, "prompt" holds the messages
# so far and "chosen" / "rejected" hold the competing assistant turns.
{"prompt": [{"role": "user", "content": "Is 91 a prime number?"}],
 "chosen": [{"role": "assistant", "content": "No. 91 = 7 × 13."}],
 "rejected": [{"role": "assistant", "content": "Yes, 91 is prime: it is odd and not divisible by 3 or 5."}]}
{"prompt": [{"role": "user", "content": "Translate to French: \"The shop opens at nine.\""}],
 "chosen": [{"role": "assistant", "content": "Le magasin ouvre à neuf heures."}],
 "rejected": [{"role": "assistant", "content": "La boutique ouvre neuf."}]}

The judge's verdicts, the swap check and the source are kept in a separate file while you build the set. The trainer reads only these three fields.

Every row needs four log-probabilities: the chosen and the rejected answer, each under the trained model and under the frozen reference.

The 91 rowtrained \(\log\pi_\theta\) · gradients onreference \(\log\pi_{\text{ref}}\) · frozenscore \(\hat r\)
chosen: “No. 91 = 7 × 13.”−9.8−10.6+0.8
rejected: “Yes, 91 is prime…”−24.1−23.3−0.8

The reference never changes, so its two numbers can be computed once before training starts (precompute_ref_log_probs=True in TRL). With a new LoRA adapter the reference is the same network with the adapter switched off; if you continue training an existing adapter, TRL keeps a frozen copy of that adapter as the reference. Either way no second copy of the model is loaded.

6

The loss

$$\htmlClass{term term-1}{\mathcal{L}_{\text{DPO}}(\theta)} = -\,\mathbb{E}_{(x,y_w,y_l)\sim\mathcal{D}}\Big[\htmlClass{term term-2}{\log\sigma}\Big(\htmlClass{term term-3}{\beta}\Big[\htmlClass{term term-4}{\log\frac{\pi_\theta(y_w\mid x)}{\pi_{\text{ref}}(y_w\mid x)}} - \htmlClass{term term-5}{\log\frac{\pi_\theta(y_l\mid x)}{\pi_{\text{ref}}(y_l\mid x)}}\Big]\Big)\Big]$$

Read it aloud: “For every pair, score each answer by how much more likely the trained model makes it than the reference does. Subtract the rejected answer's score from the chosen one's, scale by β, and squash it through a sigmoid: that is the probability that the model prefers the chosen answer. Push the log of that probability up.” Point at a row of the table to light its term up in the formula.

one pair Inside \(\mathbb{E}\) is the loss of one row, and here a row is a pair: a prompt \(x\) with a chosen and a rejected answer. \(\mathbb{E}_{(x,y_w,y_l)\sim\mathcal{D}}\) means “averaged over pairs”, in practice over the pairs of a batch. Station 7 follows one full training step, from the batch to the update.

Every symbol in the formula

SymbolWhat it means
The data
\(x\)dataThe prompt, for example “Explain why the sea is salty, for a 10-year-old.”
\(y_w\)“y win”dataThe chosen answer: the one the judge, checker or person preferred (A in the example above).
\(y_l\)“y lose”dataThe rejected answer, the worse one of the pair (B in the example).
\(\mathcal{D}\)“calligraphic D”dataThe preference dataset: all your \((x, y_w, y_l)\) triples, one per row of the JSONL file.
\((x,y_w,y_l)\sim\mathcal{D}\)“a triple drawn from D”mathThe tilde \(\sim\) means “is picked from”: one row of the dataset at a time.
\(\mathbb{E}_{(x,y_w,y_l)\sim\mathcal{D}}\big[\;\cdot\;\big]\)“expected value over the dataset”mathThe average of the bracket over every row. In practice: compute the bracket for each pair in a batch and average.
The two models
\(\theta\)“theta”trainedThe weights being trained, the only thing that changes.
\(\pi_\theta(y\mid x)\)“pi theta of y given x”trainedThe probability that the trained model writes the whole answer \(y\) after prompt \(x\): the product of its token probabilities, as in SFT. \(\pi\) is the usual letter for a model that chooses what to write (a policy). The bar \(\mid\) reads “given”.
\(\pi_{\text{ref}}(y\mid x)\)“pi ref”frozenThe same probability under the reference: a frozen copy of the model as it was before this training. It is computed once and never changes.
The scores and the gap
\(\log\frac{\pi_\theta(y_w\mid x)}{\pi_{\text{ref}}(y_w\mid x)}\)“log-ratio of the chosen answer”trainedThe chosen answer's score \(\hat r_w\) (“r hat w”). It equals \(\log\pi_\theta(y_w\mid x) - \log\pi_{\text{ref}}(y_w\mid x)\): positive when the trained model makes it more likely than the reference did, 0 when nothing has changed. In the example, \(-41.2 - (-41.6) = +0.4\).
\(\log\frac{\pi_\theta(y_l\mid x)}{\pi_{\text{ref}}(y_l\mid x)}\)“log-ratio of the rejected answer”trainedThe rejected answer's score \(\hat r_l\). Making the rejected answer less likely than the reference did lowers it, and that widens the gap. In the example, \(-58.9 - (-58.2) = -0.7\).
\(\big[\;\hat r_w - \hat r_l\;\big]\)“the gap”, or margintrainedThe inner square bracket: chosen score minus rejected score. Positive means the model already prefers the chosen answer more than the reference did. Example: \(0.4 - (-0.7) = 1.1\).
\(\beta\)“beta”settingThe leash, usually 0.1. It multiplies the gap before the sigmoid. A small β lets the model move far from the reference to win a pair; a large β keeps it close (see “Why the reference matters” below).
From gap to loss
\(\log\sigma\)“log sigmoid”math\(\sigma(u) = 1/(1+e^{-u})\) turns the scaled gap into a probability between 0 and 1: the probability that the model prefers the chosen answer. \(\sigma(0) = 0.5\), \(\sigma(3) = 0.95\), \(\sigma(-3) = 0.05\). The log makes a confident wrong ranking very costly and a confident right one almost free.
\(-\)mathThe log of a probability is negative; the minus sign makes the loss positive, so training can push it down.
\(\mathcal{L}_{\text{DPO}}(\theta)\)“L DPO of theta”resultThe loss averaged over the preference dataset. It starts at \(\log 2 = 0.69\), while the model is still identical to the reference, and falls toward 0 as the gap grows. It never reaches 0, so it keeps pushing.

What you must supply: rows of prompt \(x\), chosen answer \(y_w\) and rejected answer \(y_l\), plus the value of \(\beta\). No scores and no reward model: “which one is better” is the whole label. The reference \(\pi_{\text{ref}}\) is simply your starting model, frozen.

How the loss works, step by step

  1. Four log-probabilities. Run the chosen and the rejected answer through the trained model and through the reference. Each log-probability is the sum of the answer's token log-probabilities, as in SFT.
  2. Two scores. For each answer, subtract the reference's number from the trained model's: \(\hat r_w\) and \(\hat r_l\). They say how much each answer has moved since training began.
  3. One gap. \(\hat r_w - \hat r_l\), times \(\beta\). Only the difference counts: raising both answers by the same amount changes nothing.
  4. Squash and penalise. \(\sigma\) of the scaled gap is the model's probability of preferring the chosen answer; \(-\log\) of it is the loss.
  5. Step. The gradient raises the chosen answer's log-probability and lowers the rejected one's, with a weight \(\sigma(-\beta[\hat r_w - \hat r_l])\) that is large while the pair is ranked badly and fades once it is ranked well.

Worked example: the sea-salt pair, β = 0.1

r̂_w r̂_l gap β·gap σ(β·gap) loss −log σ push weight σ(−β·gap) start (model = ref) 0.0 0.0 0.0 0.00 0.50 0.69 0.50 the example above +0.4 −0.7 +1.1 0.11 0.53 0.64 0.47 ranked the wrong way −1.0 +2.0 −3.0 −0.30 0.43 0.85 0.57 later in training +3.0 −5.0 +8.0 0.80 0.69 0.37 0.31 far apart +10.0 −20.0 +30.0 3.00 0.95 0.05 0.05

The loss goes down when the chosen answer gains probability relative to the reference, or the rejected one loses it, or both. It goes up when the model starts preferring the rejected answer. Because only the gap counts, the loss can fall even while both answers lose probability, as long as the rejected one falls faster: a catch listed below.

7

One training step on the GPU

A DPO step is #1's step run on pairs, with two changes: a frozen reference model also reads every answer, and the slope at each answer token is no longer always “raise the target”. It raises the chosen answer and lowers the rejected one, scaled by one number per pair. Plain SFT's station 7 walks through the full step with every number; this one shows what changes.

1BatchB pairs become 2B sequences: chosen and rejected
2Forwardtrained model on all 2B with gradient; reference on all 2B, frozen
3Lossone gap h per pair → −log σ(βh), averaged over pairs
4Backwardc(p − y) on chosen tokens, −c(p − y) on rejected ones
5Updateonce; the reference never changes

One step, slope by slope

Exactly what one training step does with one pair: how many slopes, what each one needs, and when the weights move. Take a prompt with a chosen answer of 10 tokens and a rejected answer of 8.

one pair: prompt + chosen (10 tokens), prompt + rejected (8 tokens) → ONE training step 1 forward trained model: both sequences, every position at once, with gradient reference: the same two sequences, frozen, no gradient (or its numbers stored before training) 2 loss 4 sums of log p (two answers × two models) → 2 scores → 1 gap h → −log σ(βh) 3 backward first, one number for the whole pair: c = β σ(−βh) then 18 slopes at the same time, one per answer token of both answers: chosen, 10 tokens: g_t = +c (p_t − y_t) raise its token rejected, 8 tokens: g_t = −c (p_t − y_t) lower its token 4 add each weight sums the 18 contributions → 1 gradient per weight 5 update the trained model moves once; the reference never does
  • A slope cannot be computed from its own token alone. c needs every token of both answers under both models first. That is the one order inside DPO's backward pass: c first, then all 18 slopes at once. In SFT, each slope needs only its own position.
  • No division by length. Every token gets the full c, so the 10-token answer gets 10 pushes and the 8-token one 8. In a batch of B pairs, c is divided by B.
  • Read, not graded: the prompt, in both sequences.
  • Not one after the other. The 18 slopes are measured on the same old weights and added into each weight; the weights move once, as in #1.
1The batch

Each pair is a prompt with two answers. The trainer writes both out in full, prompt + chosen and prompt + rejected, and stacks them: B pairs give one grid of 2B sequences. The prompt is masked in both.

2Forward pass

The trained model reads all 2B sequences at once, as in #1, and keeps its activations. For each sequence it adds up log p over the answer tokens: a sum, not an average. The reference reads the same 2B sequences, frozen and with no gradient; since it never changes, its 2B numbers can also be computed once before training and stored.

3Loss: from each pair to one number

Each pair gives one gap \(h = \hat r_w - \hat r_l\), the chosen score minus the rejected score of station 6, and one loss \(-\log\sigma(\beta h)\); the batch averages the B pair losses.

four pairs in one batch, in the states of the table above, β = 0.1 pair gap h loss −log σ(βh) the example above +1.1 0.64 ranked the wrong way −3.0 0.85 later in training +8.0 0.37 far apart +30.0 0.05 batch loss = (0.64 + 0.85 + 0.37 + 0.05) / 4 ≈ 0.48
4Backward pass: the slope at every answer token
$$c = \frac{\beta\,\sigma(-\beta h)}{B} \qquad g_t^{\text{chosen}} = c\,\big(p_t - y_t\big) \qquad g_t^{\text{rejected}} = -\,c\,\big(p_t - y_t\big)$$

(\(y_t\) is 1 at the target token and 0 at every other word.) The whole pair collapses into one number, c, computed from its gap. Every token of the chosen answer gets #1's direction, raise the target, scaled by c; every token of the rejected answer gets the opposite, lower its own token. A badly ranked pair has a large c and pushes hard; a well-separated pair has c near 0 and stops pushing. Unlike SFT there is no division by the answer length: a score is a sum over tokens, so every token gets the full c, and a longer answer gets more pushes in total. That is the length bias #13 addresses. Below the logits nothing is new: #1's backward pass runs on all 2B sequences, and every weight adds up the pushes of both answers of every pair.

the same four pairs pair gap h β σ(−βh) c = ÷ 4 chosen tokens rejected tokens the example above +1.1 0.047 0.0118 raised lowered ranked the wrong way −3.0 0.057 0.0144 raised hardest lowered hardest later in training +8.0 0.031 0.0078 raised lowered far apart +30.0 0.005 0.0012 barely moved barely moved
5Update

Every weight of the trained model moves once. The reference stays frozen for the whole run.

On a real GPU

  • Four sequence reads per pair (two answers × two models), done as two forward calls: the trained model on all 2B sequences at once, and the reference on the same 2B. Only the trained model's call is backpropagated.
  • Memory holds two models: the trained one with gradients and optimiser state, and the reference's weights. Computing the reference's numbers once, before training, takes it off the GPU.
  • The prompt is processed twice, once with each answer.
8

The maths behind it

  1. Where it comes from: the RLHF goal

    Classic RLHF trains a reward model \(r(x,y)\) from the comparisons, then asks the language model to collect as much reward as possible while staying close to the reference:

    $$\max_{\pi}\;\mathbb{E}_{x,\;y\sim\pi(\cdot\mid x)}\big[r(x,y)\big] \;-\; \beta\,\mathrm{KL}\big(\pi(\cdot\mid x)\,\big\|\,\pi_{\text{ref}}(\cdot\mid x)\big)$$
    \(\max_{\pi}\)“maximise over pi”mathFind the model \(\pi\) that makes everything after it as large as possible.
    \(\pi\)“pi”trainedThe language model being trained, seen as a policy: for each prompt, a probability for every possible answer.
    \(x\)dataA prompt from the dataset.
    \(y\sim\pi(\cdot\mid x)\)“y drawn from pi given x”generatedAn answer the model itself writes for \(x\). The dot \(\cdot\) stands for “every possible answer”: \(\pi(\cdot\mid x)\) is the whole list of answer probabilities.
    \(\mathbb{E}_{x,\,y\sim\pi}\big[\;\cdot\;\big]\)mathThe average over prompts and over the answers the model writes for them.
    \(r(x,y)\)“r”, the rewardfrozenA reward model: a separate network, trained on the comparisons, that gives each answer a number, higher for better answers.
    \(\beta\)settingThe price of moving away from the reference. The same β that appears in the DPO loss.
    \(\mathrm{KL}\big(\pi \,\|\, \pi_{\text{ref}}\big)\)“KL divergence of pi from pi ref”mathHow different the trained model's answer probabilities are from the reference's: \(\sum_y \pi(y\mid x)\log\frac{\pi(y\mid x)}{\pi_{\text{ref}}(y\mid x)}\). It is 0 when they are identical and grows as the model drifts. The double bar \(\|\) separates the two lists.
    \(\pi_{\text{ref}}\)frozenThe frozen copy of the starting model.

    The reward pulls toward good answers; the KL term charges for every move away from the reference. \(\beta\) is the price of moving.

  2. The best model for that goal has a closed form
    $$\pi^{*}(y\mid x) = \frac{1}{Z(x)}\;\pi_{\text{ref}}(y\mid x)\;\exp\!\Big(\frac{r(x,y)}{\beta}\Big)$$
    \(\pi^{*}(y\mid x)\)“pi star”resultThe best possible model for the goal of step 1: the probability it gives to answer \(y\). The star means “the best one”.
    \(\pi_{\text{ref}}(y\mid x)\)frozenThe reference model's probability for the same answer: the starting point.
    \(r(x,y)\)frozenThe answer's reward.
    \(\beta\)settingThe price of moving. Dividing by a small β makes the rewards count more, so the best model moves further.
    \(\exp(u)\)“exp”, or \(e^u\)math\(e \approx 2.718\) raised to the power \(u\). It is always positive, equals 1 at \(u = 0\), and grows fast: the multiplier for each answer.
    \(Z(x)\)“Z of x”, the normalisermathThe sum of \(\pi_{\text{ref}}(y\mid x)\,e^{r(x,y)/\beta}\) over every possible answer. Dividing by it makes the new probabilities add up to 1. It depends only on the prompt, and in practice it cannot be computed, because there are too many possible answers.

    Take the reference and multiply each answer's probability by \(e^{r/\beta}\): high-reward answers grow, low-reward ones shrink. \(Z(x)\) rescales everything so the probabilities add up to 1 again.

    two answers, both at π_ref = 0.50; rewards: A = +1.0, B = 0.0 β = 1: A: 0.50 × e¹ = 1.36 B: 0.50 × e⁰ = 0.50 Z = 1.86 → π*: A 0.73, B 0.27 β = 0.5: A: 0.50 × e² = 3.69 B: 0.50 × e⁰ = 0.50 Z = 4.19 → π*: A 0.88, B 0.12 the smaller β, the further the best model moves from the reference
  3. Turn it round: the reward hides inside the model

    Solve the last formula for \(r\):

    $$r(x,y) = \beta\log\frac{\pi^{*}(y\mid x)}{\pi_{\text{ref}}(y\mid x)} + \beta\log Z(x)$$
    \(r(x,y)\)resultThe reward that this model implies for answer \(y\).
    \(\log\frac{\pi^{*}(y\mid x)}{\pi_{\text{ref}}(y\mid x)}\)mathThe log-ratio: how many times more likely the model makes the answer than the reference, on a log scale. With \(\pi_\theta\) in place of \(\pi^*\) this is exactly the score \(\hat r\) of the DPO loss.
    \(\beta\)settingThe same leash; here it converts the log-ratio into reward units.
    \(\beta\log Z(x)\)mathA constant for each prompt: the same number for every answer to \(x\). That is why it vanishes when two answers to the same prompt are compared.

    Any model, compared with the reference, implies a reward. Apart from the \(\beta\log Z(x)\) term, which the next step cancels, that implied reward is the score \(\hat r\) in the DPO loss, times \(\beta\). It is why the DPO paper is subtitled “your language model is secretly a reward model”.

  4. Compare two answers and \(Z\) cancels

    The Bradley–Terry model, the standard model of comparisons, says the chance that \(y_w\) beats \(y_l\) depends only on the difference of their rewards:

    $$P(y_w \succ y_l \mid x) = \sigma\big(r(x,y_w) - r(x,y_l)\big)$$
    \(P(\,\cdots\mid x)\)resultA probability: here, the chance that a person or judge, shown both answers to \(x\), picks \(y_w\).
    \(y_w \succ y_l\)“y w is preferred to y l”mathThe curly \(\succ\) means “beats” or “is preferred to”.
    \(r(x,y_w)\), \(r(x,y_l)\)frozenThe rewards of the two answers. Only their difference matters.
    \(\sigma\)mathThe sigmoid, \(\sigma(u) = 1/(1+e^{-u})\): turns the reward difference into a probability. Equal rewards give 0.5.
    rewards 2.0 against 0.5 → P(first wins) = σ(+1.5) = 0.82 rewards 0.5 against 0.5 → σ(0) = 0.50 (a coin flip) rewards 0.5 against 2.0 → σ(−1.5) = 0.18

    Substitute the implied reward. The awkward \(\beta\log Z(x)\) is the same for both answers to the same prompt, so it disappears from the difference. Maximum likelihood on the observed preferences, exactly as SFT does on tokens, then gives the DPO loss: no reward model to train, no reinforcement learning to run.

  5. What one training step does
    $$\nabla_\theta\mathcal{L}_{\text{DPO}} = -\beta\;\underbrace{\sigma\big(\beta\,[\hat r_\theta(y_l) - \hat r_\theta(y_w)]\big)}_{\text{how wrong the ranking still is}}\;\Big[\underbrace{\nabla_\theta\log\pi_\theta(y_w\mid x)}_{\text{raise the chosen}} - \underbrace{\nabla_\theta\log\pi_\theta(y_l\mid x)}_{\text{lower the rejected}}\Big]$$
    \(\nabla_\theta\)“nabla theta”, the gradientmathFor every weight in \(\theta\): how much the quantity after it changes when that weight is nudged up. It is a direction in which to move the weights.
    \(\nabla_\theta\mathcal{L}_{\text{DPO}}\)resultThe direction that increases the loss. Training steps the opposite way, so every minus sign flips: the chosen answer goes up, the rejected one down.
    \(\hat r_\theta(y_w)\), \(\hat r_\theta(y_l)\)trainedThe two scores, log-ratios against the reference, as in the loss.
    \(\sigma\big(\beta\,[\hat r_\theta(y_l) - \hat r_\theta(y_w)]\big)\)trainedThe weight of this pair: the probability the model currently gives to the wrong ranking. Note the order: rejected minus chosen.
    \(\nabla_\theta\log\pi_\theta(y_w\mid x)\)trainedThe SFT direction for the chosen answer: the change to the weights that makes it more likely.
    \(\nabla_\theta\log\pi_\theta(y_l\mid x)\)trainedThe same for the rejected answer; subtracting it makes the rejected answer less likely.
    \(\beta\)settingScales the whole step, and inside the sigmoid sets how fast the weight fades.
    \(\underbrace{\;\cdots\;}_{\text{note}}\)mathAn underbrace is a label: it names the part above it.

    The update is SFT on the chosen answer minus SFT on the rejected one, with a weight in front. The weight is large while the model ranks the pair the wrong way and fades once the chosen answer is comfortably ahead: DPO learns most from the pairs it still gets wrong.

    scaled gap β(r̂_w − r̂_l) weight σ(−scaled gap) −3.0 0.95 ← ranked the wrong way: full push 0.0 0.50 ← where training starts, model = reference +3.0 0.05 ← already right: almost no push
  6. Why the reference matters

    Without \(\pi_{\text{ref}}\), the easiest way to win a pair would be to make the rejected answer impossible, along with everything that resembles it, and to inflate the chosen one: the model would forget how to write. The reference turns the target into “more likely than you used to be”, and \(\beta\) sets the price. In the lab, train 50 steps at \(\beta = 0.1\) and then at \(\beta = 1\): the model puts about 99% of its probability on the chosen answer at the small value, against 72% at the large one, and at the small value the push is still strong.

9

Watch out for

  • Length wins pairs

    Judges and people often prefer longer answers, so chosen answers are longer on average. DPO then learns that length pays. Compare the lengths of chosen and rejected answers; consider length-matched pairs or a length-normalised loss (#13).

  • Both answers can lose probability

    DPO only looks at the gap. In practice the chosen answer's log-probability often falls during training too, just less than the rejected one's. Watch both, not only the margin. Mixing in a small SFT loss on the chosen answers helps, as does DPO-Positive, which penalises any fall in the chosen answer's probability (Pal et al., 2024).

  • Pairs from another model learn slowly

    Pairs written by a different model sit where your model puts little probability, so the gradients are weak. Pairs sampled from the model itself, or SFT on the chosen answers first, work better.

  • The labeller is the ceiling

    DPO learns the preferences it is given, mistakes included. A judge that favours a confident style trains a confidently styled model. Check the labels against people on a sample.

  • The gap keeps growing

    With clean, certain preferences the loss keeps pushing the gap wider long after the ranking is right, and the leash stops holding. One or two epochs are usually enough; IPO (#13) addresses this directly.

  • Silent truncation

    DPOConfig keeps max_length = 1024 tokens by default. A long prompt pushes the answers out of the window and the pair compares cut-off text, and rows whose prompt alone fills the window are dropped without notice. Raise the limit or shorten the prompts.

No pairs needed · one answer, one label

KTO Kahneman–Tversky optimisation

Label single answers as good or bad, with no partner answer. KTO raises good answers above a reference point and pushes bad ones below it, each as a separate example. It borrows its shape from prospect theory, the economics of how people value gains and losses.

Two unpaired rows, one good and one bad, scored against a reference point.

Changes vs DPO
One answer per row with a good or bad label, instead of a pair
Its answers
Q1 changed: single labelled answers. Q2 and Q3 as in DPO
Cost
●●●●● one label per answer
Library
Ready: TRL KTOTrainer
1

The idea in plain words

A pair needs two answers to the same prompt and a comparison between them. Often you have something simpler: a thumbs-up or thumbs-down on one answer, a judge that labels each step good or bad, a test that passes or fails. KTO trains on exactly that.

Each answer is scored as in DPO, by how much more likely the trained model makes it than the reference does. With no partner to beat, the score is compared with a reference point \(z_0\) instead: an estimate of how far the model has drifted from the reference overall. A good answer should score above the reference point and a bad one below it. Once an answer is clearly on the right side, the loss barely pushes it.

The name comes from Kahneman and Tversky's prospect theory: people judge outcomes as gains or losses against a reference point, and both feel less intense the further they go. KTO gives good and bad answers separate weights, \(\lambda_D\) for desirable (good) answers and \(\lambda_U\) for undesirable (bad) ones, which also balance a dataset with many more bad answers than good ones.

2

See it work

Six answers, each labelled on its own

Every answer carries a judge's label, good or bad, and no partner. Train and watch the good answers rise above the reference point and the bad ones fall below it, each along its own curve. Then flip some labels so the data is unbalanced, and see why KTO has two weights.

3

Where the training signal comes from

Two decisions: who labels each answer, and how you balance the good rows against the bad ones.

4

A small example

Labelled good, from a thumbs-up
UserGive me a tip for remembering names.
Answer · label: goodRepeat the name right away, “Nice to meet you, Sam”, then use it once more before you say goodbye.
Labelled bad, by the judge
UserWrite a haiku about rain.
Answer · label: badRain is falling down
it is wet
the end
Judgebad: the lines have 5, 3 and 2 syllables, not 5, 7 and 5.
No partner needed

The two rows have different prompts, and neither has a second answer. DPO could not use either one; KTO uses both.

Going the other way is easy: any DPO pair splits into two KTO rows, its chosen answer as good and its rejected answer as bad.

5

What a training batch looks like

PromptAnswerLabelSource
Give me a tip for remembering names.Repeat the name right away, “Nice to meet you, Sam”, then use it once more before you say goodbye.goodthumbs-up
Write a haiku about rain.Rain is falling down / it is wet / the endbadjudge
Is 91 a prime number?No. 91 = 7 × 13.goodchecker
Is 91 a prime number?Yes, 91 is prime.badchecker
Translate “thank you” to Spanish.Gracias.goodjudge
Summarise the email in one line.The email is about some things.badthumbs-down
# kto_train.jsonl: one answer per line, with a true / false label
{"prompt": [{"role": "user", "content": "Give me a tip for remembering names."}],
 "completion": [{"role": "assistant", "content": "Repeat the name right away, \"Nice to meet you, Sam\", then use it once more before you say goodbye."}],
 "label": true}
{"prompt": [{"role": "user", "content": "Write a haiku about rain."}],
 "completion": [{"role": "assistant", "content": "Rain is falling down\nit is wet\nthe end"}],
 "label": false}

# KTOConfig(beta=0.1, desirable_weight=λ_D, undesirable_weight=λ_U)

The reference point is measured from the batch itself. The trainer pairs each prompt with another row's answer, which has nothing to do with it, and measures how much more likely the trained model makes those mismatched answers than the reference does. That average, clamped at 0, is \(z_0\).

row 1 prompt + row 2 answer log π_θ − log π_ref = +0.12 row 2 prompt + row 3 answer +0.05 row 3 prompt + row 1 answer −0.02 average z₀ = +0.05

\(z_0\) only sets the bar: no gradient flows through it. A batch of one row would pair the row with itself, which is why TRL asks for a per-device batch size above 1.

6

The loss

$$\htmlClass{term term-1}{\mathcal{L}_{\text{KTO}}(\theta)} = \mathbb{E}_{(x,y)\sim\mathcal{D}}\big[\,\htmlClass{term term-2}{\lambda_y} - \htmlClass{term term-3}{v(x,y)}\,\big]$$ $$\htmlClass{term term-3}{v(x,y)} = \begin{cases}\lambda_D\;\sigma\big(\htmlClass{term term-4}{\beta}\,(\htmlClass{term term-5}{\hat r_\theta(x,y)} - \htmlClass{term term-6}{z_0})\big) & \text{if } y \text{ is good}\\[6pt] \lambda_U\;\sigma\big(\beta\,(z_0 - \hat r_\theta(x,y))\big) & \text{if } y \text{ is bad}\end{cases}$$

Read it aloud: “Score each answer against the reference, as in DPO. A good answer earns value when its score is above the reference point, a bad one when its score is below it. The value goes through a sigmoid, so it can never exceed the row's weight. The loss is the value still missing.” The first line is the loss; the second defines the value \(v\) it uses, with one case for good rows and one for bad rows. Point at a row of the table to light its term up.

one row + batch Inside \(\mathbb{E}\) is the loss of one row: one answer with its good/bad label. One piece is not per row: the reference point \(z_0\) is measured on the whole batch, so a row's loss also depends on the rows it was batched with. Station 7 follows one full training step, from the batch to the update.

Every symbol · both lines of the formula

SymbolWhat it means
The data
\(x\)dataThe prompt, for example “Write a haiku about rain.”
\(y\)dataOne answer to that prompt. It has no partner: each row is a single answer with a label.
“good” / “bad”dataThe row's label: true or false in the JSONL file. It decides which of the two cases of \(v\) applies.
\(\mathcal{D}\)dataThe dataset of labelled rows \((x, y, \text{label})\).
\(\mathbb{E}_{(x,y)\sim\mathcal{D}}\big[\;\cdot\;\big]\)“expected value over the dataset”mathThe average of the bracket over the rows: in practice, over the rows of a batch. \(\sim\) reads “drawn from”.
The score and the bar
\(\hat r_\theta(x,y)\)“r hat”, the scoretrainedThe answer's score, \(\log\pi_\theta(y\mid x) - \log\pi_{\text{ref}}(y\mid x)\), as in DPO: how much more likely the trained model \(\pi_\theta\) makes this answer than the frozen reference \(\pi_{\text{ref}}\) does. 0 at the start.
\(z_0\)“z nought”, the reference pointtrainedThe reference point: an estimate of how far the model has drifted from the reference overall, measured on mismatched pairs inside the batch (the third tab above), clamped at 0 and not trained on. It is the bar an answer's score is compared with. No gradient flows through it.
\(\hat r_\theta - z_0\)trainedFor a good answer: how far its score is above the bar. Positive is good.
\(z_0 - \hat r_\theta\)trainedFor a bad answer, the order flips: how far its score is below the bar. Positive is good here too.
\(\beta\)“beta”settingHow steep the sigmoid is: the same role as in DPO, usually 0.1 (KTOConfig(beta=0.1)).
\(\sigma\)“sigma”, the sigmoidmath\(\sigma(u) = 1/(1+e^{-u})\): squashes any number into the range 0 to 1, with \(\sigma(0) = 0.5\). It is the S shape that makes far-away answers count less and less.
Value, weights and loss
\(v(x,y)\)“v”, the valuetrainedThe value of the answer, as in prospect theory: how far it sits on the right side of the reference point, flattened by a sigmoid so that distant answers matter less and less. It lies between 0 and the row's weight.
\(\begin{cases}\cdots & \text{if } \dots\\ \cdots & \text{if } \dots\end{cases}\)a curly brace with casesmath“Use the top line when the condition on its right is true, otherwise the bottom line.” Here: top line for good answers, bottom line for bad ones.
\(\lambda_y\)“lambda y”settingThe weight of the row's class: \(\lambda_D\) for good (desirable) answers, \(\lambda_U\) for bad (undesirable) ones. It is also the most value a row can earn, so each row's loss lies between 0 and \(\lambda_y\).
\(\lambda_D\), \(\lambda_U\)“lambda D”, “lambda U”settingdesirable_weight and undesirable_weight in KTOConfig. Both default to 1; raise the one for the rarer label (step 3 of the maths).
\(\mathcal{L}_{\text{KTO}}(\theta)\)“L KTO of theta”resultThe loss over single labelled answers. Rows have no partner; each one counts on its own. \(\lambda_y - v\) is “the value still missing”: 0 when the answer sits far on the right side of the bar.

What you must supply: rows of prompt \(x\), one answer \(y\) and a good/bad label, plus \(\beta\), \(\lambda_D\) and \(\lambda_U\). No second answer and no score. The reference model is your frozen starting model, and \(z_0\) is measured by the trainer from each batch.

How the loss works, step by step

  1. Score the answer. As in DPO: trained log-probability minus reference log-probability, \(\hat r_\theta\).
  2. Measure the bar. The trainer pairs prompts with other rows' answers, scores those mismatched pairs the same way, averages and clamps at 0: \(z_0\). It is how much the model has drifted on answers that have nothing to do with quality.
  3. Distance on the right side. Good answer: \(\hat r_\theta - z_0\). Bad answer: \(z_0 - \hat r_\theta\). Either way, positive means “where it should be”.
  4. Value. Multiply by \(\beta\), pass through \(\sigma\), multiply by the class weight: \(v\), between 0 and \(\lambda_y\).
  5. Loss. \(\lambda_y - v\): the value still missing. The gradient raises a good answer's log-probability and lowers a bad one's, most strongly when the answer sits near the bar, since that is where the sigmoid is steepest.

Worked example: β = 0.1, z₀ = 0.05 (from the batch tab), λ_D = λ_U = 1

row label score r̂ distance on the right side β × distance value v = σ(…) loss 1 − v any row, at the start either 0.0 0.0 (z₀ = 0 too) 0.00 0.50 0.50 names tip good +2.0 +2.0 − 0.05 = +1.95 0.195 0.55 0.45 names tip, later good +20.0 +19.95 1.995 0.88 0.12 haiku bad −3.0 0.05 − (−3.0) = +3.05 0.305 0.58 0.42 haiku, rising by mistake bad +1.0 0.05 − 1.0 = −0.95 −0.095 0.48 0.52 haiku, later bad −20.0 +20.05 2.005 0.88 0.12

A good answer lowers the loss by rising faster than the general drift; a bad one by falling, or at least rising less than the drift. With \(\lambda_D = 4\) the names-tip row would count four times as much: loss \(4 \times 0.451 = 1.81\) and a push four times stronger. That is how the weights rebalance a dataset that is mostly complaints.

7

One training step on the GPU

A KTO step is #1's step on single answers, each with a good/bad label, plus an extra pass without gradient that measures the reference point \(z_0\) on the batch itself. Plain SFT's station 7 walks through the full step with every number; this one shows what changes.

1BatchB rows (answer + label), plus B mismatched pairs built from them
2Forwardtrained + reference on the rows; both again on the mismatched pairs, no gradient
3Lossλ_y − v per row with the batch's z₀, averaged
4Backward+k(p − y) for good rows, −k(p − y) for bad ones
5Updateonce; the next batch measures a new z₀

One step, slope by slope

Take a batch of 4 single rows with 10 answer tokens each: 2 labelled good, 2 bad. No row has a partner.

4 rows, each labelled good or bad, 10 answer tokens each → ONE training step 1 forward trained model and reference: each of the 4 rows (only the trained model's read has gradient) both also read the mismatched pairs (a prompt with another row's answer), no gradient, for z₀ 2 loss per row: score r̂_i = Σ log-ratio over its tokens → value v_i against z₀ → average of λ_y − v_i 3 backward first, one number per row: k_i = λ_y β σ_i (1 − σ_i) / 4 z₀ is a constant: no slope then 40 slopes at the same time, one per answer token: good rows: g_t = +k_i (p_t − y_t) raise bad rows: g_t = −k_i (p_t − y_t) lower 4 add each weight sums the 40 contributions → 1 gradient per weight 5 update the trained model moves once; the reference never does
  • A row's k needs its whole row and z₀, but no partner. Its score is a sum over its own 10 tokens; z₀ comes from the batch and is treated as a constant. One row's k never depends on another row's probabilities, except through z₀.
  • No division by length: every token of a row gets that row's full k.
  • Not one after the other. The 40 slopes are measured on the same old weights and added into each weight; the weights move once, as in #1.
1The batch

Each row is a prompt with one answer and its label. From the same batch the trainer builds B mismatched pairs, each prompt with another row's answer; they are only used to measure \(z_0\).

2Forward pass

As in DPO, the trained model and the frozen reference both read every row, and each answer gets its score \(\hat r = \log\pi_\theta - \log\pi_{\text{ref}}\), summed over its tokens. Then both models read the mismatched pairs, with no gradient: their scores, averaged and clamped at 0, give \(z_0\).

3Loss: from each row to one number

Each row's loss is \(\lambda_y - v\), with the \(z_0\) shared by the whole batch; the batch averages them.

four rows, β = 0.1, z₀ = 0.05 measured on this batch, λ_D = λ_U = 1 row label score r̂ distance value v loss 1 − v names tip good +2.0 +1.95 0.549 0.451 haiku bad −3.0 +3.05 0.576 0.424 a good row, behind good −0.5 −0.55 0.486 0.514 a bad row, rising bad +1.0 −0.95 0.476 0.524 batch loss = (0.451 + 0.424 + 0.514 + 0.524) / 4 = 0.478
4Backward pass: the slope at every answer token
$$k_i = \frac{\lambda_y\,\beta\,\sigma_i\,(1-\sigma_i)}{B} \qquad g_t = +\,k_i\,\big(p_t - y_t\big)\ \text{(good row)} \qquad g_t = -\,k_i\,\big(p_t - y_t\big)\ \text{(bad row)}$$

(\(y_t\) is 1 at the target token and 0 at every other word.) \(\sigma_i\) is the sigmoid inside the row's value. \(\sigma(1-\sigma)\) is largest, ¼, when the row sits right at the bar and fades on both sides, so rows near \(z_0\) push hardest. No partner is needed: each row pushes on its own, up if good, down if bad. \(z_0\) is a constant for the gradient. Below the logits, #1's backward pass, on the B real rows only.

the same four rows row label σ β σ(1 − σ) k = ÷ 4 push names tip good 0.549 0.0248 0.0062 up haiku bad 0.576 0.0244 0.0061 down a good row, behind good 0.486 0.0250 0.0062 up a bad row, rising bad 0.476 0.0249 0.0062 down all four sit near the steep middle, so they push almost equally hard
5Update

Once. The reference stays frozen; the next batch measures its own \(z_0\).

On a real GPU

  • Four sequence reads per row: the row and its mismatched pair, each through both models, batched into a few forward calls. Only the trained model's read of the real rows is backpropagated.
  • z₀ comes from the batch, so a bigger batch, or averaging \(z_0\) over the rows of all GPUs, gives a steadier bar.
  • Memory holds the trained model and the reference, as in DPO.
8

The maths behind it

  1. Prospect theory in one table

    Kahneman and Tversky found that people value outcomes as gains or losses relative to a reference point, not in absolute terms, and that both flatten out: a second £1,000 feels smaller than the first. KTO's value has the same S shape. With \(\beta = 1\) for readable numbers:

    score above the reference point r̂ − z₀ −4 −2 0 +2 +4 value of a good answer σ(r̂ − z₀) 0.02 0.12 0.50 0.88 0.98 push on it σ·(1 − σ) 0.02 0.10 0.25 0.10 0.02

    The push is largest at the reference point and fades on both sides. A good answer far above the bar barely moves. So does one hopelessly far below it, which makes KTO somewhat robust to wrong labels: an answer labelled good whose score is already far below the reference point gets little push.

  2. Why a reference point, not a partner

    In DPO the rejected answer is the bar the chosen one has to clear. KTO has no partner, so the bar is the model's general drift from the reference:

    $$z_0 \;\approx\; \max\Big(0,\ \frac{1}{m}\sum_{i=1}^{m}\log\frac{\pi_\theta(y'_i\mid x_i)}{\pi_{\text{ref}}(y'_i\mid x_i)}\Big)$$
    \(z_0\)resultThe reference point, the bar each answer's score is compared with.
    \(\approx\)“is approximately”mathThe batch gives an estimate, not the exact value.
    \(\max(0,\ \cdot\,)\)mathTake the larger of 0 and the average: a negative estimate is clamped to 0.
    \(m\)mathThe number of mismatched pairs in the batch.
    \(\frac{1}{m}\sum_{i=1}^{m}\)mathThe average over those \(m\) pairs, \(i = 1, 2, \dots, m\).
    \(x_i\)dataThe prompt of row \(i\).
    \(y'_i\)“y prime i”dataThe answer of another row, glued to prompt \(x_i\). The prime mark \('\) means “a different one”.
    \(\log\frac{\pi_\theta(y'_i\mid x_i)}{\pi_{\text{ref}}(y'_i\mid x_i)}\)trainedThe score of that mismatched pair: trained log-probability minus the frozen reference's. In the batch tab: +0.12, +0.05 and −0.02, average +0.05.

    Here \(y'_i\) is another row's answer, so the pair \((x_i, y'_i)\) is unrelated: the average measures drift that has nothing to do with quality, an estimate of \(\mathrm{KL}(\pi_\theta\,\|\,\pi_{\text{ref}})\). A good answer has to rise by more than the general drift to count as a gain; a bad one earns its value by staying below the drift, rising by less than it or falling.

  3. Balancing good and bad rows

    The KTO paper recommends weights that put the good side's total weight level with the bad side's, or up to one and a half times it:

    $$1 \;\le\; \frac{\lambda_D\, n_D}{\lambda_U\, n_U} \;\le\; \frac{3}{2}$$
    \(n_D\), \(n_U\)dataHow many good (desirable) and bad (undesirable) rows the dataset has.
    \(\lambda_D\), \(\lambda_U\)settingThe weights you give each good row and each bad row.
    \(\lambda_D\, n_D\), \(\lambda_U\, n_U\)mathThe total weight of each side: number of rows times weight per row.
    \(1 \le \cdots \le \frac{3}{2}\)math\(\le\) reads “at most”. The ratio of the two totals should be between 1 (level) and 1.5 (the good side up to half as heavy again).

    Earlier versions of the paper, and the warning KTOTrainer prints, use 4/3 as the upper bound.

    n_D = 200 good rows, n_U = 800 bad rows, λ_U = 1 λ_D × 200 / 800 ≥ 1 → λ_D ≥ 4 λ_D × 200 / 800 ≤ 3/2 → λ_D ≤ 6
  4. Less information per row, more rows

    “B is worse than A” says which way to move between two specific answers. “B is bad” only says that B should fall. A label carries less information than a comparison, but labels are far easier to collect. The KTO paper reports matching or beating DPO at model sizes from 1B to 30B parameters, and, for a 7B model, still beating DPO after discarding up to 90% of the good answers.

9

Watch out for

  • Unbalanced labels

    Real feedback is mostly complaints. Without adjusting \(\lambda_D\) and \(\lambda_U\), the model mostly learns what not to do. Check the ratio first.

  • The bar needs a real batch

    \(z_0\) is measured on mismatched pairs inside each batch. Tiny batches give a noisy bar, and a batch of one gives none: TRL requires a per-device batch size above 1.

  • Labels without context

    A step labelled bad because the whole session failed may have been a fine step. Label steps, not sessions, when you can.

  • Not a free upgrade

    With plenty of clean pairs, try DPO as well. KTO's strength is the data pairs cannot use, not a better use of pairs.

One stage, no reference model

ORPO Odds ratio preference optimisation

Do SFT and preference learning in a single run. ORPO trains on the chosen answer exactly like SFT and adds a penalty that grows as the rejected answer's odds come close to the chosen one's, or pass them. The SFT part keeps the model writing well, so no frozen reference model is needed.

SFT on the chosen answer plus an odds-ratio penalty, with no reference model.

Changes vs DPO
No reference model and no separate SFT stage: one loss, SFT plus an odds-ratio penalty
Its answers
Q2 changed: the SFT term takes the reference model's job
Cost
●●●●● one run, one model in memory
Library
Ready (exp.): TRL ORPOTrainer. Pin the TRL version
1

The idea in plain words

The usual recipe has two stages: SFT on good answers, then DPO on pairs, with the SFT model frozen as the reference. That is two training runs, and a second model in memory during the second one.

ORPO merges them. Its loss is the SFT loss on the chosen answer, plus \(\lambda\) times a penalty that compares the odds of the chosen answer with the odds of the rejected one. The SFT part teaches the model to write the good answer; the penalty adds the “not that” which SFT lacks. Because the model keeps learning to produce good text, it cannot win comparisons by turning into nonsense. In DPO, preventing that was the reference model's job.

Odds are another way of writing a probability: \(p/(1-p)\). A probability of 0.5 is odds of 1 (“one to one”); 0.8 is odds of 4. ORPO uses each answer's average per-token probability, so that long answers do not always look unlikely.

2

See it work

SFT alone against SFT plus the odds penalty

The four answers from the DPO lab. Two models start equal: one trains with SFT on the chosen answer only, the other with ORPO. Train both and compare where the rejected answer ends up. Raise λ to strengthen the penalty.

3

Where the training signal comes from

The same pairs as DPO, with one difference: the chosen answer is now also the SFT target, so it has to be good enough to imitate.

4

A small example

One row, two jobs
User · promptExplain why the sea is salty, for a 10-year-old.
Chosen · imitated and preferredRain slowly dissolves tiny bits of salt from rocks, and rivers carry them to the sea…
Rejected · only pushed downOcean salinity results from the weathering of crustal minerals…
What ORPO computes
average per-token probability chosen 0.60 rejected 0.40 odds p / (1 − p) 1.50 0.67 log odds ratio ln(1.50 / 0.667) = ln 2.25 = 0.81 penalty −log σ(0.81) = 0.37 SFT loss on the chosen −ln 0.60 = 0.51 per token total 0.51 + λ × 0.37 = 0.51 + 0.1 × 0.37 = 0.55
What is not computed

DPO: the trained model and the frozen reference, four log-probabilities per row, after a separate SFT run.

ORPO: the trained model only, two log-probabilities per row, in one run that starts from the base model.

5

What a training batch looks like

PromptChosen · SFT target and preferredRejected · pushed down
Explain why the sea is salty, for a 10-year-old.Rain slowly dissolves tiny bits of salt from rocks…Ocean salinity results from the weathering of crustal minerals…
Is 91 a prime number?No. 91 = 7 × 13.Yes, 91 is prime: it is odd and not divisible by 3 or 5.
Write a one-line reminder to water the plants.Don't forget to water the plants today!This is a friendly reminder that it would be a good idea to water the plants…
# orpo_train.jsonl: exactly the DPO format
{"prompt": [{"role": "user", "content": "Is 91 a prime number?"}],
 "chosen": [{"role": "assistant", "content": "No. 91 = 7 × 13."}],
 "rejected": [{"role": "assistant", "content": "Yes, 91 is prime: it is odd and not divisible by 3 or 5."}]}

# ORPOConfig(beta=0.1)  ← "beta" here is the paper's λ, the weight of the odds penalty
SFT, then DPO ORPO stage 1 SFT on the chosen answers one stage, from the base model: stage 2 DPO on the pairs, with the SFT on the chosen answer stage-1 model frozen as reference + λ × the odds penalty 2 runs · 2 models in memory in stage 2 1 run · 1 model
6

The loss

$$\htmlClass{term term-1}{\mathcal{L}_{\text{ORPO}}(\theta)} = \mathbb{E}_{(x,y_w,y_l)}\Big[\,\htmlClass{term term-2}{\mathcal{L}_{\text{SFT}}(\theta;\,x,y_w)} \;+\; \htmlClass{term term-3}{\lambda}\cdot\htmlClass{term term-4}{\Big(-\log\sigma\Big(\log\frac{\text{odds}_\theta(y_w\mid x)}{\text{odds}_\theta(y_l\mid x)}\Big)\Big)}\Big]$$ $$\htmlClass{term term-5}{\text{odds}_\theta(y\mid x)} = \frac{P_\theta(y\mid x)}{1-P_\theta(y\mid x)}, \qquad \htmlClass{term term-6}{P_\theta(y\mid x)} = \exp\Big(\frac{1}{|y|}\sum_{t=1}^{|y|}\log p_\theta(y_t\mid x, y_{<t})\Big)$$

Read it aloud: “Do ordinary SFT on the chosen answer. Then compare the two answers' odds, and add a penalty that grows as the rejected answer's odds come close to the chosen one's, or pass them. λ sets how much the penalty counts.” The second line defines the two ingredients: an answer's odds, and the average per-token probability \(P_\theta\) they are built from. Point at a row of the table to light its term up.

one pair Inside \(\mathbb{E}\) is the loss of one pair: the SFT term on its chosen answer plus \(\lambda\) times its odds penalty. \(\mathbb{E}\) means “averaged over pairs”, in practice over a batch. Station 7 follows one full training step, from the batch to the update.

Every symbol · both lines of the formula

SymbolWhat it means
The data
\(x\)dataThe prompt.
\(y_w\)“y win”dataThe chosen answer. In ORPO it is both imitated (the SFT term) and preferred (the penalty).
\(y_l\)“y lose”dataThe rejected answer. It is only pushed down, never imitated.
\(y\)dataIn the second line: either answer, \(y_w\) or \(y_l\), in turn.
\(\mathbb{E}_{(x,y_w,y_l)}\big[\;\cdot\;\big]\)mathThe average of the bracket over the pairs in the dataset (in practice, in a batch). The same rows as DPO.
From tokens to odds
\(\theta\)trainedThe weights being trained. There is no reference model: \(\theta\) is the only model in the formula.
\(p_\theta(y_t\mid x, y_{<t})\)trainedThe probability the model gives to token \(t\) of the answer, having read the prompt and the answer's earlier tokens \(y_{<t}\).
\(|y|\), \(\frac{1}{|y|}\sum_{t=1}^{|y|}\)math\(|y|\) is the number of tokens in the answer; \(\frac{1}{|y|}\sum_t\) averages over them. So the inside of \(\exp\) is the average token log-probability.
\(\exp(\cdot)\)math\(e\) to the power of the bracket: it undoes the log, turning the average log-probability back into a probability.
\(P_\theta(y\mid x)\)capital P, “average probability”trainedThe answer's average per-token probability (the geometric mean). Using the average, not the product, stops long answers from always having tiny odds. Between 0 and 1.
\(\text{odds}_\theta(y\mid x)\)trainedOdds: the probability divided by its complement, \(P / (1-P)\). Odds of 1 mean fifty-fifty; \(P = 0.60\) gives odds 1.5 (“three to two”), \(P = 0.40\) gives 0.67.
The two terms of the loss
\(\mathcal{L}_{\text{SFT}}(\theta;\,x,y_w)\)“SFT loss on the chosen answer”trainedPlain SFT loss (#1 in the SFT workbook) on the chosen answer only: the average of \(-\log p_\theta\) over its tokens. This is the anchor that replaces the reference model. The semicolon separates the weights from the data they are evaluated on.
\(\frac{\text{odds}_\theta(y_w\mid x)}{\text{odds}_\theta(y_l\mid x)}\)“the odds ratio”, ORtrainedHow many times larger the chosen answer's odds are than the rejected one's. Above 1 when the model already favours the chosen answer.
\(\log\text{OR}\)“log odds ratio”trainedThe log of that ratio: positive when the chosen answer is ahead, 0 when they are level, negative when the rejected answer is ahead. It plays the part of DPO's gap.
\(\sigma\)mathThe sigmoid, \(\sigma(u) = 1/(1+e^{-u})\): turns the log odds ratio into a probability that the chosen answer wins.
\(-\log\sigma(\log\text{OR})\)trainedThe odds-ratio penalty: DPO's log-sigmoid shape applied to the log of the two answers' odds ratio. 0.69 when the answers are level, near 0 when the chosen answer's odds are far higher, large when the rejected answer is ahead.
\(\lambda\)“lambda”settingHow much the odds penalty counts, often 0.1. TRL calls it beta. Note that there is no β inside the sigmoid here.
\(\mathcal{L}_{\text{ORPO}}(\theta)\)resultThe ORPO loss: one number per pair, with nothing frozen beside the model. SFT term plus λ times the penalty.

What you must supply: the same rows as DPO, prompt \(x\), chosen \(y_w\) and rejected \(y_l\), plus \(\lambda\). No reference model and no separate SFT run first: ORPO starts from the base model. Because the chosen answers are imitated, they must be good enough to copy.

How the loss works, step by step

  1. One pass per answer. Run the chosen and the rejected answer through the model. Each gives a log-probability per token.
  2. SFT term. On the chosen answer only: the average of \(-\log p_\theta\) over its tokens, exactly as in plain SFT.
  3. Average probability. For each answer, average its token log-probabilities and take \(\exp\): \(P_w\) and \(P_l\).
  4. Odds and their ratio. \(P/(1-P)\) for each, then the log of chosen odds over rejected odds.
  5. Penalty. \(-\log\sigma\) of that log ratio: small when the chosen answer is well ahead.
  6. Add. SFT term \(+\ \lambda \times\) penalty. The gradient of the first term raises every chosen token; the second also lowers the rejected answer, pushing hardest when the rejected answer is already likely.

Worked example: λ = 0.1, numbers per token

P_w P_l log OR penalty −log σ SFT −ln P_w total SFT + 0.1 × penalty level 0.50 0.50 0.00 0.69 0.69 0.76 rejected ahead 0.40 0.60 −0.81 1.18 0.92 1.03 the sea-salt row 0.60 0.40 +0.81 0.37 0.51 0.55 later in training 0.80 0.20 +2.77 0.06 0.22 0.23

The loss goes down as the chosen answer becomes more likely (both terms fall) and as the rejected one becomes less likely (the penalty falls). Unlike DPO, the chosen answer cannot quietly lose probability while the gap grows: the SFT term would rise and stop it.

7

One training step on the GPU

An ORPO step runs on pairs like DPO, but with no reference model: one model, two answers per pair, and a slope that combines SFT on the chosen answer with a push that separates the two. Plain SFT's station 7 walks through the full step with every number; this one shows what changes.

1BatchB pairs become 2B sequences
2Forwardthe trained model only, with gradient
3LossSFT on the chosen + λ × odds penalty, averaged over pairs
4Backwardchosen tokens up (SFT + penalty), rejected tokens down
5Updateonce

One step, slope by slope

Take one pair: a chosen answer of 10 tokens and a rejected answer of 8. There is no reference model.

one pair: prompt + chosen (10 tokens), prompt + rejected (8 tokens) → ONE training step 1 forward trained model only: both sequences, every position at once, with gradient 2 loss P_w, P_l = each answer's average per-token probability → odds → log odds ratio ℓ L = −(1/10) Σ log p(chosen tokens) + λ (−log σ(ℓ)) 3 backward first, two numbers for the whole pair: k_w = (1/10) (1 + λ σ(−ℓ) / (1 − P_w)) k_l = −(1/8) λ σ(−ℓ) / (1 − P_l) then 18 slopes at the same time, one per answer token: chosen, 10 tokens: g_t = k_w (p_t − y_t) SFT's push plus the penalty's, up rejected, 8 tokens: g_t = k_l (p_t − y_t) the penalty only, down 4 add each weight sums the 18 contributions → 1 gradient per weight 5 update every weight moves once
  • The two k's need the whole pair first: both answers' average probabilities and the log odds ratio.
  • Each answer's push is shared over its own length (÷ 10, ÷ 8), because P is an average per token. In a batch of B pairs, also ÷ B.
  • Not one after the other. The 18 slopes are measured on the same old weights and added into each weight; the weights move once, as in #1.
1The batch

As in DPO: prompt + chosen and prompt + rejected, stacked into one grid of 2B sequences.

2Forward pass

Only the trained model runs, with gradient, on all 2B sequences. For each answer it averages log p over the tokens, which gives \(P_w\) and \(P_l\).

3Loss: from each pair to one number

Each pair's loss is the SFT term on its chosen answer plus \(\lambda\) times its odds penalty; the batch averages the pairs. (TRL pools the SFT term over all chosen-answer tokens of the batch, as in #1, and averages the penalty over the pairs.)

four pairs in one batch, in the states of the table above, λ = 0.1 pair total SFT + 0.1 × penalty level 0.76 rejected ahead 1.03 the sea-salt row 0.55 later in training 0.23 batch loss = (0.76 + 1.03 + 0.55 + 0.23) / 4 = 0.64
4Backward pass: the slope at every answer token
$$g_t^{\text{chosen}} = \frac{1}{B\,|y_w|}\Big(1 + \frac{\lambda\,\sigma(-\ell)}{1-P_w}\Big)\big(p_t - y_t\big) \qquad g_t^{\text{rejected}} = -\,\frac{1}{B\,|y_l|}\cdot\frac{\lambda\,\sigma(-\ell)}{1-P_l}\,\big(p_t - y_t\big)$$

(\(y_t\) is 1 at the target token and 0 at every other word.) \(\ell\) is the log odds ratio. The chosen answer always gets the full SFT push (the 1) plus a little more from the penalty; the rejected answer gets only the penalty, downward. The penalty's push grows when the rejected answer is likely (\(1-P_l\) small) and fades once the chosen answer is well ahead (\(\sigma(-\ell)\) small). Below the logits, #1's backward pass on all 2B sequences.

the same four pairs, before dividing by B × answer length pair P_w P_l σ(−ℓ) chosen: 1 + λσ(−ℓ)/(1 − P_w) rejected: −λσ(−ℓ)/(1 − P_l) level 0.50 0.50 0.500 1 + 0.100 −0.100 rejected ahead 0.40 0.60 0.692 1 + 0.115 −0.173 the sea-salt row 0.60 0.40 0.308 1 + 0.077 −0.051 later in training 0.80 0.20 0.059 1 + 0.029 −0.007
5Update

Once. There is no reference to keep.

On a real GPU

  • Two answers per pair, one model. Both answers are backpropagated and there is no reference: that saves the reference's forward passes and its weights (2 bytes per weight, 16 GB for an 8B model, next to about 130 GB for the trained model). With LoRA, DPO's reference is the same network with the adapter switched off, so the memory saving disappears; the time saving stays.
  • No separate SFT run. The SFT term replaces the SFT stage that usually comes before DPO.
8

The maths behind it

  1. Probability, odds and log odds
    p odds p/(1−p) log odds 0.10 0.11 −2.20 0.50 1.00 0.00 0.60 1.50 +0.41 0.80 4.00 +1.39 0.95 19.00 +2.94

    Log odds run from \(-\infty\) to \(+\infty\) and stretch out near 0 and 1, where a probability looks almost stuck.

  2. The penalty compares the two answers' log odds
    $$\log\frac{\text{odds}_\theta(y_w)}{\text{odds}_\theta(y_l)} = \log\text{odds}_\theta(y_w) - \log\text{odds}_\theta(y_l)$$
    \(\text{odds}_\theta(y_w)\), \(\text{odds}_\theta(y_l)\)trainedThe odds of the chosen and of the rejected answer, \(P/(1-P)\), from the model's average per-token probability. The prompt \(x\) is left out of the notation to keep it short.
    \(\log\frac{a}{b} = \log a - \log b\)mathThe log of a ratio is a difference of logs. So the log odds ratio is just “chosen log odds minus rejected log odds”, a gap like DPO's.
    chosen 0.60, rejected 0.40: +0.405 − (−0.405) = 0.81 → penalty −log σ(0.81) = 0.37 chosen 0.80, rejected 0.20: +1.386 − (−1.386) = 2.77 → penalty −log σ(2.77) = 0.06
  3. What the penalty's gradient does

    Writing \(P_w\) and \(P_l\) for the two average probabilities, and using \(\nabla\log\text{odds}(P) = \nabla\log P\,/\,(1-P)\):

    $$\nabla_\theta\Big(-\log\sigma(\log\text{OR})\Big) = -\,\sigma\big(-\log\text{OR}\big)\left[\frac{\nabla_\theta\log P_w}{1-P_w} - \frac{\nabla_\theta\log P_l}{1-P_l}\right]$$
    \(\nabla_\theta\)“nabla theta”, the gradientmathFor every weight: how much the quantity after it changes when that weight is nudged up. Training steps the opposite way to the loss's gradient.
    \(\log\text{OR}\)trainedThe log odds ratio, chosen log odds minus rejected log odds.
    \(\sigma(-\log\text{OR})\)trainedThe weight of this pair: the probability the model currently gives to the wrong ranking. Near 1 while the rejected answer is ahead, near 0 once the chosen one is well ahead.
    \(P_w\), \(P_l\)trainedThe average per-token probabilities of the chosen and the rejected answer.
    \(\nabla_\theta\log P_w\), \(\nabla_\theta\log P_l\)trainedThe SFT directions that make each answer more likely. The minus sign in front of the rejected one turns it into “less likely”.
    \(\frac{1}{1-P}\)mathAn amplifier that comes from the odds: it is 1.25 at \(P = 0.2\) and 5 at \(P = 0.8\), so a rejected answer the model already finds likely gets the strongest push.

    As in DPO: raise the chosen answer, lower the rejected one, harder while the ranking is poor. The \(1/(1-P)\) factors add something new: the more likely the rejected answer already is, the harder it is pushed down.

    rejected answer's average probability 0.20 0.50 0.80 factor 1 / (1 − P) 1.25 2.00 5.00 ← a likely rejected answer gets the strongest push
  4. Why no reference model is needed

    DPO needs \(\pi_{\text{ref}}\) because its loss on its own can be satisfied by wrecking the model. In ORPO the SFT term keeps pulling toward the chosen answers, token by token, so the model must stay a good writer of them; the penalty only reshapes how much probability the rejected answers keep. The price is that the chosen answers must be good enough to imitate, because now they are imitated, not just preferred.

9

Watch out for

  • Chosen answers are copied

    In DPO a chosen answer only has to beat the rejected one. In ORPO it is also the SFT target, so a mediocre chosen answer is learned word for word.

  • An experimental API

    ORPOTrainer lives under trl.experimental and can change between releases. Pin the TRL version.

  • λ trades fluency for contrast

    Too small and ORPO is just SFT; too large and the penalty swamps the SFT anchor. Sweep a few values and evaluate both answer quality and preference win-rate.

  • Style comes with the chosen answers

    The SFT term pulls the model's style toward the chosen answers, whatever model you start from. If they were written by another model, expect its voice too.

Same pairs, a different loss

SimPO, IPO and other DPO variants Changing pieces of the DPO loss

DPO's loss has three parts you can change: the score of an answer, the reference it is measured against, and the curve that turns the gap into a loss. Each variant changes one or more of them to fix a specific problem: answers that keep growing longer, a gap that never stops growing, or the cost of the reference model.

The same pair trained three ways: SimPO, IPO and DPO, and what length does to each.

Changes vs DPO
The loss only: the pairs stay the same, and most variants keep DPOTrainer; SimPO moves to CPOTrainer
Its answers
Q2 changed: a target gap (IPO), or no reference at all (SimPO)
Cost
●●●●● as DPO; SimPO saves the reference
Library
Ready: DPOTrainer with loss_type="ipo", "sigmoid_norm" (length-normalised) and about a dozen more. SimPO without a reference: CPOTrainer (exp.)
1

The idea in plain words

These are not new families; they are settings. The data is still (prompt, chosen, rejected) pairs, and most variants run in the same trainer with a different loss_type. What changes is how the gap between the two answers becomes a loss.

  • IPO (identity preference optimisation) replaces DPO's log-sigmoid with a squared loss around a target gap. DPO keeps pushing the gap wider for as long as you train; IPO stops at the target, and pulls back if the gap overshoots, so it overfits less when the preferences are clean.
  • Length-normalised DPO divides each answer's score by its number of tokens. A summed score rewards long answers just for being long.
  • SimPO (simple preference optimisation) drops the reference model, scores each answer by its average log-probability, and asks the chosen answer to lead by a fixed margin \(\gamma\). No reference model, and much less length bias.
  • Hinge, robust and the rest. A hinge loss stops pushing once a margin is met, as in SLiC. The robust loss assumes a known share of the labels are flipped and corrects for it. TRL lists about a dozen more.
2

See it work

How hard each loss pushes, and whether padding pays

Part one plots how strongly each loss pushes the gap wider at every gap size: move the gap and find the loss that never stops. Part two pads an answer with extra tokens and compares a summed score with an averaged one.

3

Which variant for which problem

The signal is the same pairs as DPO, so the choice here is about the symptom. Pick the problem you see.

4

A small example: one pair, three scores

The pair
User · promptSummarise this news story in one sentence.
Chosen · 40 tokensOn Tuesday the city council approved new protected bike lanes on Mill Road, Station Street and the High Street, with work starting in May and finishing before the new school year.
Rejected · 120 tokensIn a meeting that took place on Tuesday evening, the city council, after a long discussion involving several members and some residents, voted to approve a plan that will see new bike lanes… [and so on for three sentences]
Three ways to score it
chosen rejected gap summed log-ratio Σ log(π_θ/π_ref) +2.0 +3.0 −1.0 ← DPO: the long answer looks better average log-ratio summed ÷ length +0.050 +0.025 +0.025 ← length-normalised: chosen ahead average log-prob (1/|y|) Σ log π_θ −0.80 −1.10 +0.30 ← SimPO: no reference at all

Per token, the chosen answer improved twice as much. The summed score hides that, because the rejected answer has three times as many tokens to add up.

5

What a training batch looks like

The rows are exactly the DPO rows of #10: prompt, chosen, rejected. Switching variant is a configuration change, so you can compare variants on the same data in a few runs.

# DPOTrainer: same data as DPO, only the config changes
loss_type = "ipo"            # IPO: target gap 1 / (2β), measured per token in TRL: sweep β, do not reuse 0.1
loss_type = "sigmoid_norm"   beta = 0.1                # DPO on length-normalised scores
loss_type = "robust"         label_smoothing = 0.1     # assume 10% of the pairs are flipped
loss_type = "hinge"          beta = 0.1                # stop pushing once β·gap reaches 1

# CPOTrainer (experimental: pin the TRL version): SimPO, no reference model
loss_type = "simpo"   cpo_alpha = 0.0   beta = 2.0   simpo_gamma = 0.5
# SimPO uses a much larger β than DPO (the paper: β 2 to 2.5, γ 0.5 to 1.5).
# cpo_alpha defaults to 1.0, which adds an SFT term: set it to 0 for pure SimPO.
6

The loss

DPO, for comparison: summed scores, a reference, a log-sigmoid that never stops

$$-\log\sigma\big(\beta\,h\big), \qquad h = \log\frac{\pi_\theta(y_w\mid x)}{\pi_{\text{ref}}(y_w\mid x)} - \log\frac{\pi_\theta(y_l\mid x)}{\pi_{\text{ref}}(y_l\mid x)}$$

Read it aloud (DPO): “Call the gap between the chosen and the rejected answer's log-ratios \(h\). The loss is minus the log of the sigmoid of β times \(h\).” This is the loss of #10, rewritten with a short name for the gap so that the variants below can reuse it.

IPO: a squared loss around a target gap

$$\htmlClass{term term-1}{\mathcal{L}_{\text{IPO}}} = \Big(\htmlClass{term term-2}{h} - \htmlClass{term term-3}{\frac{1}{2\beta}}\Big)^{2}$$

In TRL (loss_type="ipo") each answer's log-ratio is first divided by its number of tokens, so \(h\) and the target are per-token gaps.

Read it aloud (IPO): “Square the distance between the gap and a target of \(1/(2\beta)\), so the loss is zero exactly at the target.” Too small a gap and too large a gap are both penalised.

SimPO: average log-probabilities, no reference, a fixed margin

$$\htmlClass{term term-4}{\mathcal{L}_{\text{SimPO}}} = -\log\sigma\Big(\htmlClass{term term-5}{\frac{\beta}{|y_w|}\log\pi_\theta(y_w\mid x)} - \frac{\beta}{|y_l|}\log\pi_\theta(y_l\mid x) - \htmlClass{term term-6}{\gamma}\Big)$$

Read it aloud (SimPO): “Score each answer by its average log-probability, scaled by β, with no reference model, and ask the chosen answer to lead by at least γ.” Point at a row of the table to light its term up in the formulas.

one pair Each formula here is the loss of one pair. Training averages it over the pairs of a batch, exactly as DPO does. Station 7 follows one full training step, from the batch to the update.

Every symbol · the three formulas share most of them

SymbolWhat it means
Shared by all three
\(x\)dataThe prompt, for example “Summarise this news story in one sentence.”
\(y_w\), \(y_l\)“y win”, “y lose”dataThe chosen and the rejected answer: exactly the DPO rows of #10.
\(\pi_\theta(y\mid x)\)“pi theta of y given x”trainedThe probability that the model being trained writes the whole answer \(y\), the product of its token probabilities. \(\log\pi_\theta(y\mid x)\) is the sum of its token log-probabilities.
\(\beta\)“beta”settingA number you choose, but its job differs per variant: DPO's leash (around 0.1); IPO's target \(1/(2\beta)\); SimPO's scale on the average log-probabilities (around 2 to 2.5). Do not reuse a value across variants.
\(\sigma\)“sigma”, the sigmoidmath\(\sigma(u) = 1/(1+e^{-u})\): squashes any number into a probability between 0 and 1. \(-\log\sigma(u)\) is 0.69 at \(u = 0\) and falls toward 0, never reaching it, as \(u\) grows.
DPO and IPO: the gap against a reference
\(\pi_{\text{ref}}(y\mid x)\)“pi ref”frozenThe same probability under the frozen reference, the model as it was before training.
\(\log\frac{\pi_\theta(y\mid x)}{\pi_{\text{ref}}(y\mid x)}\)trainedAn answer's score, or log-ratio: how much more likely the trained model makes it than the reference does. A sum over the answer's tokens, so longer answers add up more.
\(h\)“h”, the gaptrainedThe gap between the two answers' log-ratios, as in DPO but without β in front: chosen score minus rejected score. In the example above, \(+2.0 - 3.0 = -1.0\).
\(\frac{1}{2\beta}\)“one over two beta”settingThe target gap: β = 0.1 gives 5. A larger β means a smaller target, a tighter leash. In TRL the gap is per token, where 5 is very large: tune β again.
\(\big(\cdots\big)^{2}\)“squared”mathMultiply the bracket by itself. A square is never negative and is 0 only when the bracket is 0, so missing the target in either direction costs the same.
\(\mathcal{L}_{\text{IPO}}\)resultThe IPO loss for one pair. Its minimum is 0, reached exactly at the target gap.
SimPO: no reference
\(|y_w|\), \(|y_l|\)“length of y”dataThe number of tokens in each answer: 40 and 120 in the example.
\(\frac{\beta}{|y|}\log\pi_\theta(y\mid x)\)trainedSimPO's score: the average log-probability per token, times β. No reference model appears anywhere. Dividing by the length means a long answer gains nothing just by being long.
\(\gamma\)“gamma”settingThe target margin, around 0.5 to 1.5: the curve is shifted so that the push only falls below half its strength once the chosen answer leads by more than γ. Unlike a hinge, it never reaches zero. A larger γ asks for a clearer separation.
\(\mathcal{L}_{\text{SimPO}}\)resultThe SimPO loss: DPO's shape with a different score inside.

What you must supply: the same pairs as DPO, \(x\), \(y_w\), \(y_l\). Switching variant only changes settings: \(\beta\) for each, plus \(\gamma\) for SimPO. IPO and length-normalised DPO still need the frozen reference; SimPO does not.

How each loss works, step by step

  1. Score both answers. DPO and IPO: summed log-ratio against the reference. SimPO: average log-probability of the trained model alone.
  2. Take the gap. Chosen score minus rejected score; SimPO also subtracts the margin \(\gamma\).
  3. Apply the curve. DPO and SimPO: \(-\log\sigma\), which keeps rewarding a wider gap forever, more and more weakly. IPO: a square around the target, which pushes the gap up when it is too small and pulls it back when it is too large.
  4. Step. As in DPO, the gradient raises the chosen answer and lowers the rejected one. Only how hard, and when it stops, changes from variant to variant.

Worked example: the news-summary pair above, chosen 40 tokens, rejected 120

DPO β = 0.1 h = +2.0 − 3.0 = −1.0 loss −log σ(0.1 × −1.0) = −log σ(−0.10) = 0.74 ← ranked the wrong way, because of length IPO β = 0.1 target 1 / (2 × 0.1) = 5 h = −1.0: (−1.0 − 5)² = 36 pushed up hard h = +5.0: (5 − 5)² = 0 stops h = +7.0: (7 − 5)² = 4 pulled back SimPO β = 2, γ = 0.5 average log-probs chosen −0.80 rejected −1.10 2 × (−0.80) − 2 × (−1.10) − 0.5 = −1.60 + 2.20 − 0.50 = +0.10 loss −log σ(0.10) = 0.64 ← ranked the right way, just past the margin
Score of an answerNeeds the referenceWhere the loss reaches 0Settings
DPOsummed log-ratioyesnever: only as the gap goes to infinityβ
IPOsummed log-ratio (per token in TRL)yesexactly at the target gap \(1/(2\beta)\)β
SimPOaverage log-probabilitynonever, but the push drops below half once the lead passes γβ, γ
7

One training step on the GPU

All three variants run the DPO step on pairs. They differ in how an answer is scored (summed, per token or averaged), in whether the reference runs, and in the curve that turns the gap into the size of the push. Plain SFT's station 7 walks through the full step with every number; this one shows what changes.

1BatchB pairs become 2B sequences
2Forwardtrained model; the reference too for DPO and IPO, not for SimPO
3Lossone value per pair, averaged over pairs
4Backward+k_w(p − y) on chosen tokens, −k_l(p − y) on rejected ones
5Updateonce

One step, slope by slope

Take one pair: a chosen answer of 10 tokens and a rejected answer of 8. The variants differ only in the numbers computed before the slopes.

one pair: prompt + chosen (10 tokens), prompt + rejected (8 tokens) → ONE training step 1 forward trained model: both sequences, every position at once, with gradient DPO and IPO also run the reference on both, frozen, no gradient; SimPO has none 2 loss one score per answer (DPO, IPO: summed log-ratio · SimPO: average log p) → one gap → the variant's curve 3 backward first, one number per answer, from the whole pair: DPO k_w = +β σ(−βh) k_l = −β σ(−βh) IPO k_w = +2 (1/(2β) − h) k_l = −2 (1/(2β) − h) SimPO k_w = +β σ(−m) / 10 k_l = −β σ(−m) / 8 then 18 slopes at the same time: chosen, 10 tokens: g_t = k_w (p_t − y_t) rejected, 8 tokens: g_t = k_l (p_t − y_t) 4 add each weight sums the 18 contributions → 1 gradient per weight 5 update every weight moves once
  • Only the first numbers change. The forward pass, the form of the 18 slopes, the sum and the one move are the same for all three.
  • IPO's k changes sign past the target gap: then the chosen tokens are lowered and the rejected ones raised, pulling the gap back.
  • SimPO divides by each answer's length, so each of the 8 rejected tokens carries 1/8 of its answer's push. In the summed form, DPO and IPO give every token the full k. In a batch of B pairs, every k is also ÷ B.
  • Not one after the other. The 18 slopes are measured on the same old weights and added into each weight; the weights move once, as in #1.
1The batch

As in DPO: 2B sequences, prompt masked.

2Forward pass

The trained model reads all 2B sequences with gradient. DPO and IPO also run the frozen reference, or read its stored numbers. SimPO does not: it scores each answer by its average log p under the trained model alone.

3Loss: from each pair to one number

Each pair gives one loss with its variant's formula; the batch averages them.

IPO, β = 0.1, three pairs in one batch, the gaps of the example above pair gap h (h − 5)² 1 −1.0 36 2 +5.0 0 3 +7.0 4 batch loss = (36 + 0 + 4) / 3 = 13.3
4Backward pass: the slope at every answer token

Each variant hands every token of the chosen answer \(+k_w\,(p_t - y_t)\) and every token of the rejected answer \(-k_l\,(p_t - y_t)\), with \(y_t\) 1 at the target token and 0 elsewhere. For DPO and IPO \(k_w = k_l = k\); SimPO divides by each answer's own length. Below the logits, #1's backward pass on all 2B sequences.

variantk for the pair, before dividing by Bhow it behaves
DPO\(\beta\,\sigma(-\beta h)\)always positive, fades as the gap grows
IPO\(2\,\big(\tfrac{1}{2\beta} - h\big)\)changes sign: pushes the pair apart below the target gap, pulls it back above (TRL's per-token version also divides by each answer's length)
SimPO\(\beta\,\sigma(-m)/|y|\), with \(m = \tfrac{\beta}{|y_w|}\log\pi_\theta(y_w) - \tfrac{\beta}{|y_l|}\log\pi_\theta(y_l) - \gamma\)divided by each answer's own length; fades once the lead passes γ
IPO, the same three pairs: k = 2 (5 − h) ÷ 3 pair gap h k chosen tokens rejected tokens 1 −1.0 +4.00 raised hard lowered hard 2 +5.0 0 no push no push 3 +7.0 −1.33 lowered raised ← pulled back toward the target
5Update

Once.

On a real GPU

  • SimPO is the cheapest: two forward passes per pair and one model in memory.
  • DPO and IPO need the reference: two more sequence reads per pair, without gradient, or its numbers computed once before training.
8

The maths behind it

  1. Why DPO's gap grows forever

    \(-\log\sigma(\beta h)\) only reaches 0 as \(h \to \infty\). When a pair's preference is certain, as it is for every pair in a clean dataset, the best answer to the loss is to make the rejected answer infinitely unlikely, whatever \(\beta\) is. The leash only holds where the data itself is uncertain.

    β·h loss −log σ(βh) push σ(−βh) 0 0.69 0.50 2 0.13 0.12 5 0.0067 0.0067 10 0.000045 0.000045 ← tiny, but never zero: with enough steps the gap keeps growing
  2. IPO stops at a target
    $$\frac{\partial\mathcal{L}_{\text{IPO}}}{\partial h} = 2\Big(h - \frac{1}{2\beta}\Big)$$
    \(\frac{\partial\mathcal{L}_{\text{IPO}}}{\partial h}\)“partial L-IPO over partial h”resultThe slope of the IPO loss as the gap \(h\) changes. Training moves \(h\) against the slope: a negative slope means “widen the gap”, a positive one “narrow it”.
    \(h\)trainedThe gap: chosen log-ratio minus rejected log-ratio.
    \(\frac{1}{2\beta}\)settingThe target gap, set by your choice of β.
    \(2(\cdots)\)mathThe slope of a square \(u^2\) is \(2u\): twice the distance from the target, so the pull is proportional to how far off the gap is.

    Negative below the target, so the gap is pushed up; zero at the target; positive above it, so the gap is pulled back. The leash now holds even when every label is certain.

    β = 0.1 → target gap 1 / (2 × 0.1) = 5 h = 0: slope 2 × (0 − 5) = −10 pushed up hard h = 4: slope 2 × (4 − 5) = −2 pushed up gently h = 5: slope 0 stops h = 7: slope 2 × (7 − 5) = +4 pulled back
  3. Summed scores pay for length

    DPO's score is a sum over tokens, \(\sum_t \log\frac{\pi_\theta(y_t\mid\cdot)}{\pi_{\text{ref}}(y_t\mid\cdot)}\). Once training has nudged every token of the preferred style up a little, each extra token of that style adds to the score: doubling the answer doubles the score.

    per-token log-ratio after some training: +0.05 40-token answer: summed 40 × 0.05 = 2.0 average 0.05 80-token answer: summed 80 × 0.05 = 4.0 average 0.05 ← twice the score for the same quality
  4. SimPO: score answers the way generation does

    SimPO's authors point out that generation, especially with beam search, ranks text by its average log-probability per token. So they use exactly that as the score, \(\frac{1}{|y|}\log\pi_\theta(y\mid x)\). No reference is needed, training runs one model, and the margin \(\gamma\) replaces DPO's zero line.

  5. A map of the variants
    VariantScore of an answerReferenceLoss curveFixes
    DPOsummed log-ratioyeslog-sigmoidthe baseline
    Length-normalised DPOaverage log-ratioyeslog-sigmoidlength bias
    IPOsummed log-ratio; per token in TRLyessquared, target \(1/(2\beta)\)overfitting clean pairs
    Hingesummed log-ratio, as in RSO and TRL; SLiC-HF's original uses plain log-probsyesstops at a marginoverfitting
    Robust DPOsummed log-ratioyeslog-sigmoid, corrected for flipsnoisy labels
    SimPOaverage log-probnolog-sigmoid with margin \(\gamma\)length bias, memory
    CPOlog-probnolog-sigmoid + SFT termmemory
9

Watch out for

  • No reference, no leash

    SimPO and CPO keep one model in memory. SimPO has no anchor at all; CPO's SFT term ties the model to the chosen answers, as in ORPO, but not to where it started. Watch fluency and general skills, not just the win-rate.

  • Each variant brings its own knob

    IPO's β sets a target gap; SimPO adds γ and needs a much larger β than DPO. Values tuned for DPO do not carry over.

  • Experimental trainers

    CPOTrainer, where SimPO lives, is under trl.experimental. Pin the version.

  • Fix the data first

    If the chosen answers are systematically longer, a length-normalised loss treats the symptom; length-matched pairs fix the cause.

Cut the pair at the first wrong step

Step-level DPO Step-DPO · step-wise preference optimisation

In a multi-step answer, find the first step that went wrong. Keep everything before it as a shared history, and build the pair right there: the correct next step as chosen, the wrong one as rejected. The preference lands on the exact step that made the difference.

A recorded solution, the first wrong step, and a pair where only one word moves.

Changes vs DPO
What the pair compares: one step after a shared history, not two whole answers
Its answers
Q3 changed: the credit lands on one step
Cost
●●●●● find the first error, write and check a fix
Library
Build it: pairs cut at the first wrong step → DPOTrainer
1

The idea in plain words

Two whole answers to a maths problem usually differ everywhere: different wording, a different order, and somewhere a mistake. A whole-answer pair tells the model “this whole text is better than that whole text”, so the push lands on every token, including all the correct steps of the losing answer.

Step-level DPO cuts the pair where it matters. A judge or a checker finds the first wrong step. The history up to that point goes into the prompt, shared by both sides and never graded. The rejected completion is the wrong step; the chosen completion is a correct step written for the same history. Nothing else differs, so all of the signal falls on that one decision.

It is the preference version of correction SFT (#6 in the SFT workbook). Correction SFT trains only on the right step; step-level DPO also pushes the wrong step down, from the same situation.

2

See it work

Whole answers against one step

Two solutions to the same problem: A is right, B goes wrong at step 3. Compare what a whole-answer pair and a step-level pair push up and down. Then move the cut and see why the first wrong step is the place to cut.

3

Where the training signal comes from

Three jobs: find the first wrong step, write a correct one, and check it.

4

A small example

The recorded solution
UserA train ticket costs £12. A railcard takes a third off. How much do 5 tickets cost with the railcard?
Step 1 · judged correctTaking a third off £12 removes £4.
Step 2 · judged correctEach ticket is then £8.
Step 3 · the first errorFor five tickets: 5 × £8 = £45.
Step 4 · follows from the mistakeTotal: £45.
The step-level pair
Prompt · shared, never gradedA train ticket costs £12. A railcard takes a third off. How much do 5 tickets cost with the railcard?
Step 1: Taking a third off £12 removes £4.
Step 2: Each ticket is then £8.
Chosen stepFor five tickets: 5 × £8 = £40.
Rejected stepFor five tickets: 5 × £8 = £45.
What is left out of the row

Step 4 of the recorded solution (“Total: £45”), which only follows from the mistake, and any other complete solution. Neither one is about the decision at step 3.

The two completions differ by a single number. Everything the model learns from this row is about that number.

5

What a training batch looks like

Shared history · the promptChosen stepRejected stepError found by
Train tickets + steps 1–2For five tickets: 5 × £8 = £40.For five tickets: 5 × £8 = £45.checker
“Is 2025 a leap year?” + “2025 ÷ 4 = 506.25.”506.25 is not a whole number, so 2025 is not a leap year.So 2025 is a leap year.judge
“Sort [3, 1, 2] in Python.” + “I'll use the built-in sort.”sorted([3, 1, 2]) returns [1, 2, 3].[3, 1, 2].sorted() returns [1, 2, 3].interpreter
“Rice for 4 is 300 g. How much for 6?” + “Per person: 300 ÷ 4 = 75 g.”For 6 people: 6 × 75 = 450 g.For 6 people: 300 + 6 = 306 g.checker
# step-level pairs: "prompt" holds the problem and the shared steps, already put through the
# chat template; "chosen" and "rejected" are the two versions of the next step.
# For agents, where every step is its own assistant turn, conversational rows work as well.
{"prompt": "<|user|>A train ticket costs £12. A railcard takes a third off. How much do 5 tickets cost with the railcard?<|end|><|assistant|>Step 1: Taking a third off £12 removes £4.\nStep 2: Each ticket is then £8.\nStep 3:",
 "chosen": " For five tickets: 5 × £8 = £40.",
 "rejected": " For five tickets: 5 × £8 = £45.",
 "first_error_step": 3, "found_by": "checker"}

The last two fields are provenance for you; the trainer ignores them. With plain-text rows TRL appends an end-of-sequence token to chosen and rejected, so each pair also teaches the model to stop after this step. That is natural when each step is its own turn, as for agents; for one long answer, remove that final token from both completions in a custom collator.

6

The loss

$$\htmlClass{term term-1}{\mathcal{L}_{\text{step}}(\theta)} = -\,\mathbb{E}\Big[\log\sigma\Big(\beta\Big[\log\frac{\pi_\theta(\htmlClass{term term-2}{s^{+}_k}\mid \htmlClass{term term-3}{x,\,s_{1:k-1}})}{\pi_{\text{ref}}(s^{+}_k\mid x,\,s_{1:k-1})} - \log\frac{\pi_\theta(\htmlClass{term term-4}{s^{-}_k}\mid x,\,s_{1:k-1})}{\pi_{\text{ref}}(s^{-}_k\mid x,\,s_{1:k-1})}\Big]\Big)\Big]$$

Read it aloud: “Exactly the DPO loss. The prompt is the problem plus the first \(k-1\) steps, and the two answers are two versions of step \(k\): the correct one and the wrong one.” Point at a row of the table to light its term up in the formula.

one pair Inside \(\mathbb{E}\) is the loss of one pair, and a pair here is two versions of one step after one shared history. \(\mathbb{E}\) means “averaged over step pairs”, in practice over a batch. Station 7 follows one full training step, from the batch to the update.

Every symbol in the formula

SymbolWhat it means
The data: one step-level pair
\(x\)dataThe problem, for example the train-ticket question.
\(k\)dataThe number of the first wrong step in the recorded solution: 3 in the example. It is found by a judge, a checker or roll-outs.
\(s_{1:k-1}\)“s one to k minus one”dataThe steps before it, step 1 up to step \(k-1\): here steps 1 and 2. The colon means “from … to …”.
\(x,\,s_{1:k-1}\)dataThe shared history: the problem and the steps before the first error. It is the prompt, read and never graded.
\(s^{+}_k\)“s plus k”dataThe chosen step: a correct step \(k\), written for this exact history and checked (“5 × £8 = £40.”). The plus marks the good version.
\(s^{-}_k\)“s minus k”dataThe rejected step: the step \(k\) that was actually written, the first wrong one (“5 × £8 = £45.”). \(k\) is found by a judge, a checker or roll-outs.
\(\mathbb{E}\big[\;\cdot\;\big]\)mathThe average over all step-level pairs in the dataset; the subscript is left out to keep the formula short.
Models, scores and loss: as in DPO
\(\pi_\theta(s_k\mid x, s_{1:k-1})\)trainedThe probability that the model being trained writes step \(s_k\) after reading the problem and the earlier steps. It is the product of the step's token probabilities.
\(\pi_{\text{ref}}(s_k\mid x, s_{1:k-1})\)frozenThe same probability under the frozen reference model.
\(\log\frac{\pi_\theta(\cdot)}{\pi_{\text{ref}}(\cdot)}\)trainedThe step's score: how much more likely the trained model makes it than the reference does. One for the chosen step, one for the rejected step.
\(\beta\)settingThe leash, as in DPO, usually 0.1.
\(\log\sigma\)mathThe log of the sigmoid \(\sigma(u) = 1/(1+e^{-u})\), which turns the scaled gap into the probability that the model prefers the correct step.
\(\mathcal{L}_{\text{step}}(\theta)\)resultThe DPO loss of #10, with steps as the answers. It starts at 0.69 and falls as the correct step pulls ahead.

What you must supply: for each pair, the problem \(x\), the shared steps \(s_{1:k-1}\) (put into the prompt), a checked correct step \(s^+_k\) and the first wrong step \(s^-_k\), plus \(\beta\). Finding \(k\), the first error, is the expensive part; the training itself is ordinary DPO.

How the loss works, step by step

  1. Find the first error. A checker, a judge or roll-outs mark step \(k\) as the first wrong step of a recorded solution.
  2. Build the row. Prompt: the problem plus steps 1 to \(k-1\). Rejected: the wrong step. Chosen: a correct step \(k\) written for the same history. Everything after step \(k\) is thrown away.
  3. Run DPO. Four log-probabilities, of the two steps only, under the trained model and the reference; two scores; the gap; \(-\log\sigma(\beta \cdot \text{gap})\).
  4. Step. The gradient raises the correct step and lowers the wrong one. Because the history is in the prompt, the earlier correct steps are never pushed down.

Worked example: step 3 of the train-ticket solution, β = 0.1

trained log π_θ reference log π_ref score chosen "5 × £8 = £40." −6.2 −6.9 +0.7 rejected "5 × £8 = £45." −5.8 −5.1 −0.7 gap +0.7 − (−0.7) = 1.4 β × gap = 0.14 loss −log σ(0.14) = 0.63 push weight σ(−0.14) = 0.47 as whole answers with steps 1–2 attached (score +0.4 on both sides): gap (0.4 + 0.7) − (0.4 − 0.7) = 1.4 ← the shared steps cancel; only step 3 counts

The loss goes down only by making “£40” more likely than “£45” after this exact history, relative to the reference. With a whole-answer pair, step 4 and any other differences between the answers would also enter the gap and blur that lesson.

7

One training step on the GPU

A Step-DPO step is a DPO step whose rows are step pairs: the prompt holds the problem and the steps so far, and each answer is a single step. Plain SFT's station 7 walks through the full step with every number; this one shows what changes.

1BatchB step pairs become 2B sequences: history + one step
2Forwardtrained model with gradient; reference frozen
3Lossone gap per step pair → −log σ, averaged
4Backwardc(p − y) on the right step's tokens, −c(p − y) on the wrong step's
5Updateonce

One step, slope by slope

Take one pair built at the first wrong step: the shared history, then a correct step of 6 tokens or a wrong step of 6 tokens.

one pair: history + correct step (6 tokens), history + wrong step (6 tokens) → ONE training step 1 forward trained model: both sequences, every position at once, with gradient reference: the same two, frozen, no gradient 2 loss DPO's: each step's score against the reference → one gap → −log σ(β · gap) 3 backward first, one number for the pair: c = β σ(−β · gap) then 12 slopes at the same time, one per token of the two steps: correct step, 6 tokens: g_t = +c (p_t − y_t) raise wrong step, 6 tokens: g_t = −c (p_t − y_t) lower 4 add each weight sums the 12 contributions → 1 gradient per weight 5 update the trained model moves once; the reference never does
  • The history makes no slope. The problem and the earlier correct steps are read, never graded, so they are never pushed down. They get gradient through attention, because the step's tokens read them.
  • c needs both steps first, as in #10. The steps after the error are not in the row at all. In a batch of B pairs, c is ÷ B.
  • Not one after the other. The 12 slopes are measured on the same old weights and added into each weight; the weights move once, as in #1.
1The batch

Each row: the problem and steps 1 to k − 1 as the prompt, masked; then the correct step k in one sequence and the wrong step k in the other. Pairs from different problems, with different k, sit in the same batch.

2Forward pass

As in DPO, both models read all 2B sequences; each answer's log p is summed over the step's tokens only.

3Loss: from each pair to one number

Each step pair gives one gap and one loss \(-\log\sigma(\beta\cdot\text{gap})\); the batch averages them.

three step pairs in one batch, β = 0.1 (the first is the ticket step above, the others illustrative) pair gap loss −log σ(β·gap) ticket, step 3 +1.4 0.626 a pair ranked wrongly −0.5 0.718 a pair well apart +3.0 0.554 batch loss = (0.626 + 0.718 + 0.554) / 3 = 0.633
4Backward pass: the slope at every answer token

(\(y_t\) is 1 at the target token and 0 at every other word.) #10's slope on the step's tokens: \(c = \beta\sigma(-\beta\cdot\text{gap})/B\), then \(+c\,(p_t - y_t)\) on the correct step and \(-c\,(p_t - y_t)\) on the wrong one. The history has no slope of its own, so the earlier correct steps are never pushed down; they still receive gradient through attention, because the step's tokens read them.

the same three pairs pair gap β σ(−β·gap) c = ÷ 3 ticket, step 3 +1.4 0.047 0.0155 a pair ranked wrongly −0.5 0.051 0.0171 a pair well apart +3.0 0.043 0.0142
5Update

Once.

On a real GPU

  • Costs like DPO. The history is long and the step short, so most of the compute goes into the shared history, read once with each step.
  • Building the data is the expensive part: finding the first wrong step can take many roll-outs, each written token by token.
8

The maths behind it

  1. Credit: where the push lands

    The DPO gradient raises every token of the chosen answer and lowers every token of the rejected one. With two whole answers that share nothing, the correct steps of the losing answer are pushed down too.

    whole-answer pair step-level pair pushed up A1 A2 A3 A4 pushed up the fixed step 3 pushed down B1 B2 B3 B4 pushed down B3 correct steps pushed down: 2 (B1, B2) correct steps pushed down: 0 graded steps: 8 graded steps: 2
  2. A shared beginning cancels

    By the chain rule, an answer's log-probability is the sum of its pieces' log-probabilities, each given what came before. If both answers start with the same steps, those steps contribute the same amount to both scores and drop out of the gap (\(x\) is left out to save space):

    $$\hat r(s_{1:k-1}\,s^{+}_k) - \hat r(s_{1:k-1}\,s^{-}_k) = \underbrace{\hat r(s_{1:k-1}) - \hat r(s_{1:k-1})}_{=\,0} + \hat r(s^{+}_k\mid s_{1:k-1}) - \hat r(s^{-}_k\mid s_{1:k-1})$$
    \(\hat r(\cdot)\)“r hat”, the scoretrainedA piece of text's log-ratio, \(\log\pi_\theta - \log\pi_{\text{ref}}\): how much more likely the trained model makes it than the reference does.
    \(s_{1:k-1}\,s^{+}_k\)dataA whole answer: the shared steps 1 to \(k-1\) followed by the correct step \(k\). Writing pieces side by side means “this, then that”.
    \(s_{1:k-1}\,s^{-}_k\)dataThe same shared steps followed by the wrong step \(k\).
    \(\hat r(s_{1:k-1})\)trainedThe score of the shared steps alone. It appears once on each side, so the difference is 0.
    \(\hat r(s^{\pm}_k\mid s_{1:k-1})\)trainedThe score of step \(k\) alone, given the shared steps before it. These are the only terms left.
    \(\underbrace{\;\cdots\;}_{=\,0}\)mathA label under the brace: the part above it equals 0.

    Step-level DPO makes the shared beginning exact by construction and puts it in the prompt, so only the step after it counts.

  3. Why the first wrong step

    Cut before it and both sides would be the same correct step: no pair. Cut after it and the history already contains the mistake, so the pair can only teach a repair; the first error itself is never pushed down. The first wrong step is the only cut that targets the mistake directly. The lab's cut slider shows all three cases.

  4. Fewer pairs, cleaner ones

    A solution with an error gives one pair, at its first error; a solution with no error gives none. Step-level pairs are scarcer and costlier than whole-answer pairs, but each one carries a much cleaner signal. The Step-DPO paper reports gains on maths benchmarks from about ten thousand such pairs.

9

Watch out for

  • A late first error

    If the judge names a step after the real first error, the history holds an uncaught mistake and the pair teaches the wrong lesson. Check the judge's first-error labels against people.

  • The chosen step must be right

    An unverified fix that is itself wrong pushes the model toward a new mistake, exactly where it is most fragile.

  • Fixes in someone else's voice

    A fix written by another model sits where the model puts little probability and teaches slowly. Resampling the step from the model itself and keeping a verified one works better.

  • Near-identical pairs can lower both sides

    Two completions that differ by a single number are exactly the case where DPO most often lowers the chosen side's probability too (Pal et al., 2024). Watch the chosen step's log-probability; an SFT term on the chosen step helps.

  • Only some tasks have steps

    Maths, code and agent actions split cleanly into steps. Free prose does not, and a cut in the middle of a paragraph has no clear meaning.

Whole sessions as the unit

Trajectory-level preference ETO-style · exploration-based trajectory optimisation

For an agent that works through many turns, compare whole sessions: a successful one as chosen, a failed one on the same task as rejected. Only the outcome needs judging. Every action in the winning session is pushed up and every action in the losing one down, while the tool and environment outputs in between are read and never graded.

Two agent sessions: every action pushed, every observation left ungraded.

Changes vs DPO
The answer is a whole session; only the agent's own turns are graded
Its answers
Q3 changed: the credit is spread over the whole session
Cost
●●●●● many sessions to run, long sequences to train on
Library
Build it: whole sessions as chosen / rejected → DPOTrainer
1

The idea in plain words

An agent's session is a trajectory: action, observation, action, observation, until the task succeeds or fails. Often the only reliable signal is at the end: the item was bought or not, the tests pass or not. Judging every step is expensive and uncertain.

Trajectory-level preference uses that end signal directly. For the same task, take a session that succeeded and one that failed, and use them as the chosen and the rejected answer in the DPO loss. A session's log-probability is the sum over the agent's turns. The observations, written by the environment, are part of the context: read, never graded.

ETO (Song et al., 2024) builds this into a loop: start from an SFT agent, let it explore the training tasks, pair its failures with the expert's sessions on the same tasks, train with DPO, and explore again with the improved agent. Each round finds new failures to learn from. That loop is a step toward the online methods of the later families.

2

See it work

Credit over a whole session

Two sessions of a shopping agent on the same task: one bought the right item, one did not. See which turns the trajectory pair pushes up or down and which ones are only read, then compare with a step-level pair. Below, see how often a task gives a pair at all.

3

Where the training signal comes from

Three decisions: where the successful session comes from, who decides success, and which sessions you pair.

4

A small example

Chosen: the session that succeeded
Task · promptBuy a blue water bottle under £15.
Agent · gradedsearch[blue water bottle]
Environment · read, not gradedBlue steel bottle £12 · Red bottle £9 · Blue glass bottle £18
Agent · gradedclick[Blue steel bottle £12]
Environment · read, not gradedBlue · £12 · in stock
Agent · gradedbuy
Environment · rewardOrder placed · reward 1
Rejected: the session that failed
Task · promptBuy a blue water bottle under £15.
Agent · gradedsearch[water bottle]
Environment · read, not gradedRed bottle £9 · Blue glass bottle £18 · Green bottle £11
Agent · gradedclick[Blue glass bottle £18]
Environment · read, not gradedBlue · £18 · in stock
Agent · gradedbuy
Environment · rewardOrder placed, over budget · reward 0
5

What a training batch looks like

TaskChosen session · actionsRejected session · actionsRewards
Buy a blue water bottle under £15.search → click the £12 blue bottle → buysearch → click the £18 blue bottle → buy1 vs 0
Book the earliest train to Leeds on Friday.search Friday → sort by time → pick 06:10 → booksearch Friday → pick the first listed, 08:40 → book1 vs 0
Find the opening hours of the city library.search → open the library's own page → read the hourssearch → open a 2019 news article → answer from it1 vs 0
# trajectory pairs: the task is the prompt; chosen and rejected are whole sessions.
# Only the assistant turns may be graded: the tool turns were written by the environment.
{"prompt": [{"role": "user", "content": "Buy a blue water bottle under £15."}],
 "chosen": [{"role": "assistant", "content": "search[blue water bottle]"},
            {"role": "tool", "content": "Blue steel bottle £12 · Red bottle £9 · Blue glass bottle £18"},
            {"role": "assistant", "content": "click[Blue steel bottle £12]"},
            {"role": "tool", "content": "Blue · £12 · in stock"},
            {"role": "assistant", "content": "buy"}],
 "rejected": [{"role": "assistant", "content": "search[water bottle]"},
              {"role": "tool", "content": "Red bottle £9 · Blue glass bottle £18 · Green bottle £11"},
              {"role": "assistant", "content": "click[Blue glass bottle £18]"},
              {"role": "tool", "content": "Blue · £18 · in stock"},
              {"role": "assistant", "content": "buy"}],
 "task_id": "shop-0412", "rewards": [1, 0]}

TRL's DPOTrainer (checked in v1.15 and v1.16) scores every token after the prompt, tool messages included, and has no option to skip them. To grade only the agent's turns you need a custom collator that zeroes completion_mask on the tool turns. ETO itself masks the observations.

6

The loss

$$\htmlClass{term term-1}{\mathcal{L}_{\text{traj}}(\theta)} = -\,\mathbb{E}\Big[\log\sigma\Big(\beta\Big[\htmlClass{term term-2}{\sum_{t}\log\frac{\pi_\theta(a^{+}_t\mid h^{+}_t)}{\pi_{\text{ref}}(a^{+}_t\mid h^{+}_t)}} - \htmlClass{term term-3}{\sum_{t}\log\frac{\pi_\theta(a^{-}_t\mid h^{-}_t)}{\pi_{\text{ref}}(a^{-}_t\mid h^{-}_t)}}\Big]\Big)\Big]$$ $$\htmlClass{term term-4}{h_t} = \big(u,\; a_1, o_1,\; \dots,\; a_{t-1}, o_{t-1}\big)$$

Read it aloud: “The DPO loss, with each answer replaced by a whole session. A session's score is the sum of its actions' scores, each action taken given everything before it. The observations sit in the history and have no term of their own.” The second line spells out that history. Point at a row of the table to light its term up.

one pair Inside \(\mathbb{E}\) is the loss of one pair of whole sessions. Each session's score adds up its actions; the observations are masked. \(\mathbb{E}\) means “averaged over session pairs”, in practice over a batch. Station 7 follows one full training step, from the batch to the update.

Every symbol · both lines of the formula

SymbolWhat it means
A session, piece by piece
\(u\)dataThe task, the user's request that opens the session: “Buy a blue water bottle under £15.” It plays the part of the prompt \(x\).
\(t\)mathThe turn number: \(t = 1\) is the agent's first action.
\(a_t\)“a sub t”, an actiondataThe agent's action at turn \(t\): a whole assistant turn, such as search[blue water bottle]. Its probability is the product of its tokens' probabilities.
\(o_t\)“o sub t”, an observationdataWhat the environment answered after action \(t\), such as the search results. Written by the environment, not by the model.
\(h_t\)“h sub t”, the historydataThe history before action \(t\): the task \(u\), then every earlier action \(a\) and observation \(o\). Observations come from the environment, so they appear only here, as context.
\(\big(u,\; a_1, o_1,\; \dots,\; a_{t-1}, o_{t-1}\big)\)mathA list in order; the dots \(\dots\) stand for the turns in between. \(h_1\) is just \((u)\); \(h_2 = (u, a_1, o_1)\); and so on.
\(a^{+}_t,\ h^{+}_t\)“a plus”, “h plus”dataThe actions and histories of the successful session \(\tau^{+}\) (“tau plus”), the chosen side.
\(a^{-}_t,\ h^{-}_t\)“a minus”, “h minus”dataThe same for the failed session \(\tau^{-}\) on the same task, the rejected side. The two sessions can have different lengths.
\(\mathbb{E}\big[\;\cdot\;\big]\)mathThe average over all session pairs in the dataset.
Scores and loss: as in DPO
\(\pi_\theta(a_t\mid h_t)\)trainedThe probability that the agent being trained takes action \(a_t\) after reading history \(h_t\).
\(\pi_{\text{ref}}(a_t\mid h_t)\)frozenThe same probability under the frozen reference (in ETO, the agent from the previous round).
\(\sum_t\)mathAdd up over every action of the session.
\(\sum_t \log\frac{\pi_\theta(a^{+}_t\mid h^{+}_t)}{\pi_{\text{ref}}(a^{+}_t\mid h^{+}_t)}\)trainedThe successful session's score: the sum over its actions \(a^{+}_t\). Each action is a whole assistant turn, itself a sum over tokens.
\(\sum_t \log\frac{\pi_\theta(a^{-}_t\mid h^{-}_t)}{\pi_{\text{ref}}(a^{-}_t\mid h^{-}_t)}\)trainedThe failed session's score, summed the same way.
\(\beta\)settingThe leash you choose, as in DPO, usually 0.1.
\(\log\sigma\)mathThe log of the sigmoid \(\sigma(u) = 1/(1+e^{-u})\), which turns the scaled gap into the probability of preferring the successful session.
\(\mathcal{L}_{\text{traj}}(\theta)\)resultDPO over sessions. Each pair is a success \(\tau^{+}\) against a failure \(\tau^{-}\) on the same task.

What you must supply: pairs of whole sessions on the same task, one that succeeded and one that failed (the environment's reward decides which), plus \(\beta\). Each session is a list of messages in which the agent's turns are graded and the tool or environment turns are masked out.

How the loss works, step by step

  1. Collect two sessions on one task. Run the agent (or take an expert session) until one session succeeds and another fails.
  2. Score every action. For each agent turn, compare the trained model's log-probability with the reference's, given the history up to that turn.
  3. Skip the observations. Environment turns are context only. They get no term, because neither model wrote them.
  4. Add up per session. The successful session's score minus the failed one's is the gap; then \(-\log\sigma(\beta \cdot \text{gap})\), as in DPO.
  5. Step. Every action of the successful session is pushed up and every action of the failed one down, all with the same pair-level weight, whether or not that action decided the outcome.

Worked example: the two bottle sessions above, β = 0.1

successful session score failed session score search[blue water bottle] +0.3 search[water bottle] −0.2 click[Blue steel bottle £12] +0.5 click[Blue glass bottle £18] −0.6 buy 0.0 buy 0.0 sum +0.8 sum −0.8 gap 0.8 − (−0.8) = 1.6 β × gap = 0.16 loss −log σ(0.16) = 0.62 push weight σ(−0.16) = 0.46 if the observation tokens were scored too (say +0.4 and −0.1): gap (0.8 + 0.4) − (−0.8 − 0.1) = 2.1 → loss 0.59, lower for text the agent never wrote

The loss goes down when the agent's successful actions become more likely and its failing actions less likely, relative to the reference. Note that buy appears in both sessions and is pushed up in one and down in the other; across many pairs such shared, innocent actions roughly cancel, while the decisive ones (searching for “blue”, picking the £12 bottle) add up.

7

One training step on the GPU

A trajectory step is a DPO step on whole sessions: very long sequences in which only the agent's own turns are graded. Plain SFT's station 7 walks through the full step with every number; this one shows what changes.

1BatchB session pairs become 2B long sequences
2Forwardtrained model with gradient; reference frozen
3Lossone gap per session pair → −log σ, averaged
4Backwardc(p − y) on the successful session's actions, −c(p − y) on the failed one's
5Updateonce

One step, slope by slope

Take one pair of whole sessions on the same task. Say each has 4 agent turns of 10 tokens, so 40 action tokens, with the environment's observations in between.

one pair of sessions: successful and failed, 40 action tokens each → ONE training step 1 forward trained model: both sessions, every position at once, with gradient reference: the same two sessions, frozen, no gradient 2 loss each session's score = the sum of its action tokens' log-ratios → one gap → −log σ(β · gap) 3 backward first, one number for the pair: c = β σ(−β · gap) then 80 slopes at the same time, one per action token: successful session, 40 tokens: g_t = +c (p_t − y_t) raise failed session, 40 tokens: g_t = −c (p_t − y_t) lower observation tokens: no slope 4 add each weight sums the 80 contributions → 1 gradient per weight 5 update the trained model moves once; the reference never does
  • Every action of the failed session is lowered, decisive or not: c is one number for the whole session.
  • Observations make no slope. The environment wrote them; they are read as history and get gradient through attention.
  • Long sessions, still one move. Often only one or two pairs fit on a GPU, so trainers use gradient accumulation: the micro-batches run one after the other, but they only add into each weight's gradient, and the weights move once per batch.
  • Not one after the other. The 80 slopes are measured on the same old weights and added into each weight; the weights move once, as in #1.
1The batch

Each session is written out as one sequence: the task, then the agent's turns and the observations in order. The mask is 1 on the agent's tokens and 0 on the task and the observations.

2Forward pass

Both models read every session; each session's score is the sum of log p over the agent's tokens only.

3Loss: from each pair to one number

Each session pair gives one gap and one loss; the batch averages them.

two session pairs in one batch, β = 0.1 (the bottle pair above, and one ranked the wrong way) pair gap loss −log σ(β·gap) bottle sessions +1.6 0.616 a pair ranked wrong −0.4 0.713 batch loss = (0.616 + 0.713) / 2 = 0.66
4Backward pass: the slope at every answer token

(\(y_t\) is 1 at the target token and 0 at every other word.) #10's slope: \(c = \beta\sigma(-\beta\cdot\text{gap})/B\) per pair; \(+c\,(p_t - y_t)\) at every agent token of the successful session and \(-c\,(p_t - y_t)\) at every agent token of the failed one, decisive or not. Observations have no slope of their own, but receive gradient through attention.

the same two pairs pair gap β σ(−β·gap) c = ÷ 2 bottle sessions +1.6 0.046 0.0230 a pair ranked wrong −0.4 0.051 0.0255
5Update

Once.

On a real GPU

  • Activations dominate memory. Sessions run to thousands of tokens, so trainers fit one or two pairs per GPU and use gradient accumulation and activation checkpointing.
  • Like DPO: four sequence reads per pair in two forward calls, only the trained model's backpropagated; the reference can be computed once before training.
  • Collecting the sessions, by running the agent in its environment, happens before training and is usually the slowest part.
8

The maths behind it

  1. Why the observations drop out

    The probability of a whole session multiplies the agent's choices and the environment's responses:

    $$P(\tau) = \prod_{t}\pi(a_t\mid h_t)\;P_{\text{env}}(o_t\mid h_t, a_t)$$
    \(\tau\)“tau”, a trajectorydataA whole session: the task, then every action and observation in order.
    \(P(\tau)\)resultThe probability of that exact session happening, agent and environment together.
    \(\prod_t\)mathMultiply over every turn \(t\) of the session.
    \(\pi(a_t\mid h_t)\)trainedThe agent's part: the probability of choosing action \(a_t\) given the history \(h_t\).
    \(P_{\text{env}}(o_t\mid h_t, a_t)\)“P env”frozenThe environment's part: the probability that it answers \(o_t\) after that action. The website, the shop or the tool, never the model.

    In the score, the trained model and the reference face the same environment, so its factors are identical on the top and the bottom of the ratio and cancel:

    $$\log\frac{P_\theta(\tau)}{P_{\text{ref}}(\tau)} = \sum_t \log\frac{\pi_\theta(a_t\mid h_t)}{\pi_{\text{ref}}(a_t\mid h_t)} + \underbrace{\sum_t \log\frac{P_{\text{env}}(o_t\mid h_t,a_t)}{P_{\text{env}}(o_t\mid h_t,a_t)}}_{=\,0}$$
    \(P_\theta(\tau)\), \(P_{\text{ref}}(\tau)\)trainedThe probability of the session with the trained agent, and with the frozen reference agent, in the same environment.
    \(\log\frac{P_\theta(\tau)}{P_{\text{ref}}(\tau)}\)trainedThe session's score. The log of a product is a sum of logs, so it splits into one sum for the actions and one for the observations.
    \(\log\frac{\pi_\theta(a_t\mid h_t)}{\pi_{\text{ref}}(a_t\mid h_t)}\)trainedOne action's score: the only terms that survive.
    \(\log\frac{P_{\text{env}}}{P_{\text{env}}}\)mathThe same number on top and bottom: a ratio of 1, whose log is 0.
    \(\underbrace{\;\cdots\;}_{=\,0}\)mathA label under the brace: the whole sum above it is 0.

    So the right score has no observation terms at all. A trainer that also runs the observation tokens through the language model adds terms \(\log\frac{\pi_\theta(o_t\mid\cdot)}{\pi_{\text{ref}}(o_t\mid\cdot)}\), the models' guesses of text the environment wrote. Those do not cancel and do not belong in the score, which is why the observations are masked.

  2. Credit is spread thin

    Every action in a session gets the same pair-level weight. A long losing session with one decisive mistake spends most of its push on actions that decided nothing.

    losing session: 20 actions, 1 decisive mistake actions pushed down: 20 of which the decisive one: 1 the other 19 are punished for being in a failed session; across many pairs this averages out
  3. Which tasks give a pair

    With \(N\) attempts per task at success rate \(s\), a task gives a self-made pair only if it has at least one success and at least one failure:

    $$P(\text{the task gives a pair}) = 1 - s^{N} - (1-s)^{N}$$
    \(s\)trainedThe agent's success rate on this task: the chance that one attempt succeeds.
    \(N\)settingHow many attempts you run per task.
    \(s^{N}\)mathThe chance that all \(N\) attempts succeed: no failure to pair with.
    \((1-s)^{N}\)mathThe chance that all \(N\) attempts fail: no success to pair with.
    \(1 - \cdots - \cdots\)resultWhatever is left: the chance of at least one success and at least one failure, that is, of getting a pair.
    s = 0.10, N = 4: 1 − 0.0001 − 0.6561 = 0.34 ← most tasks are never solved: no pair s = 0.50, N = 4: 1 − 0.0625 − 0.0625 = 0.88 s = 0.95, N = 4: 1 − 0.8145 − 0.0000 = 0.19 ← most tasks never fail with an expert session for every task: 1 − sᴺ (any one failure is enough)
  4. The ETO loop
    round 0 SFT on the expert sessions → agent π₀ round k explore: πₖ runs every training task pair: each failure with the expert session on that task (pairs with equal rewards are dropped) train: DPO on the new pairs, reference = πₖ, no SFT term → agent πₖ₊₁

    Each round's agent fails in new ways, so each round produces new pairs about its own current mistakes. Training on fresh failures of the current model is the idea that the iterative and online families take further.

9

Watch out for

  • Good actions punished

    The losing session's sensible turns are pushed down along with its mistake. With many pairs this averages out; with few it can teach odd avoidances. When you can find the decisive step, step-level pairs (#14) are sharper.

  • Mask the environment

    The observations must not be graded, and TRL's DPOTrainer grades them: it scores every token after the prompt, tool messages included. Zero their completion_mask in a custom collator.

  • Long sessions, short windows

    Sessions run to many thousands of tokens. With DPOConfig's default max_length of 1,024, only the first turns of each session would be compared, the decisive later turns would be cut away, and a row whose task prompt alone fills the window would be dropped. Raise the limit, and budget the memory: every pair is two long sequences, each run through two models.

  • Length shortcuts

    Failures often run long, as the agent wanders, and successes short. The model may learn to stop early. Pair sessions of similar length, or check what the pairs reward.

  • Same task only

    Pairing a success on an easy task with a failure on a hard one teaches task difficulty, not behaviour.

Putting the family together

How they stack

In practice you pick one preference method, not six, and run it after SFT; ORPO is the exception, because it replaces both stages. Step-level and session pairs (#14, #15) are extra data for that same DPO stage. The choice follows from the data you have.

1

Choose by the data you have

  1. Pairs of whole answers: a chosen and a rejected answer per prompt#10 DPO
  2. …and the answers drift longer, the gap overfits, or there is no memory for a reference#13 SimPO, IPO
  3. Single answers labelled good or bad, or a judge's per-step labels#11 KTO
  4. Pairs, a base model, and one training run instead of two#12 ORPO
  5. Answers made of steps, and a way to find the first wrong one#14 Step-level
  6. Agent sessions where only the outcome is known#15 Trajectory-level
2

A typical pipeline

  1. SFT on good demonstrations, from the SFT familyA SFT family
  2. Sample several answers per prompt from the SFT model, then judge them: pairs for DPO, single labels for KTO
  3. One preference stage, with the loss that fits the data#10 DPO#11 KTO#13 Variants
  4. Optional, for multi-step tasks: add pairs cut at the first wrong step#14 Step-level
  5. Optional, for agents: session pairs, and the loop of explore, pair and train#15 Trajectory
  6. Evaluate: the win-rate against the SFT model, answer length, and regression checks on general skills

Instead of steps 1 to 3, ORPO (#12) does SFT and the preference stage in one run from the base model. It needs ready-made pairs, such as an existing preference set or a strong model against a weak one, because there is no SFT model to sample from yet.

3

The family at a glance

#TechniqueOne example holdsReference modelWhat changes vs DPOCost
10DPOA pair of whole answersYesThe baseline●●
11KTOOne answer and a good or bad labelYesNo pairs needed●●
12ORPOA pairNoSFT and an odds penalty in one stage●●
13SimPO, IPO, variantsA pairIPO yes, SimPO noThe loss curve, the length, the margin, or no reference●●
14Step-level DPOTwo next steps after a shared historyYesWhere the pair is cut●●●
15Trajectory-levelTwo whole sessions on the same taskYesThe unit is a session●●●
4

The ceiling, and what comes next

Everything on this page learns from a fixed set of comparisons made in advance, and two limits follow. The labeller is the ceiling: the model learns the judge's or the raters' taste, blind spots included. And the data goes stale: the pairs were drawn from an earlier model, and as training moves the model away from it, they describe its old mistakes rather than its new ones.

The next families address both. Reward and value models turn the labels into a scorer that can grade answers nobody has labelled yet. Iterative methods sample, judge and train again in rounds, as ETO does. Online reinforcement learning scores the model's own answers during training.