How post-training runs DPO
DPO runs as a custom loss function, which splits the work between Together AI GPUs and your local process:forwardscores a batch without accumulating gradients and returns the model’s per-token log-probabilities of the target tokens. Each step opens with this pass. Before training starts, the loop also runs it over every training and validation pair to snapshot the reference policy (the model before any updates).- Your local process computes the loss in PyTorch from the live log-probabilities and the reference snapshot, then calls
.backward()to get the gradient of the loss with respect to each token’s log-probability. custom_forward_backwardsends those per-token gradients to the model resource’s trainer, which runs the forward pass again and backpropagates them through the model to accumulate parameter gradients.optim_stepapplies the accumulated gradients, as in every other loop.
Run the DPO recipe
The cookbook’s DPO loop trains on HelpSteer3 preference pairs:uv run python recipes/dpo/helpsteer3.py. The train function provisions a trainer-only model resource for model, attaches one session to it, and stops both when it returns. DPOConfig has no model_resources_id field, so to train DPO on a model resource that’s already running, attach to it from the SDK as described in Manage model resources.
Settings worth knowing:
beta: The temperature on the implicit reward. Higher values keep the policy closer to the reference.enable_thinking: Set it toFalseon Qwen models for preference data without reasoning, such as HelpSteer3. Every Qwen model in the catalog turns thinking on by default. With thinking on, the chat template can’t render these pairs, sotrainraises aValueErrorbefore it provisions anything. GLM-5.3 always thinks and renders these pairs without the setting, so leaveenable_thinkingunset for it, because the cookbook rejectsenable_thinking=Falseon GLM-5.3.label_smoothing: Conservative-DPO smoothing. Off by default.learning_rate: The recipe default is5e-6, on a cosine schedule. DPO uses lower learning rates than SFT.precompute_ref_batch_size: The batch size for the reference log-probability forward passes that run before training, and for each validation pass. Lower it ifforwardruns out of memory on long pairs.val_interval: The number of steps between validation runs on held-out pairs.
DPOConfig field.
Prepare preference pairs
Each example is a pair of full conversations that share everything up to the final assistant turn, including the prompt. One ends in the chosen response, and the other ends in the rejected response. To build a pair, call the cookbook’sbuild_preference_pair. It renders both sides with the model’s chat template and returns two SFT-style samples, with loss masks over the response tokens:
None when max_length leaves either side with no trained token. Skip those pairs, because DPO needs both sides. It raises a ValueError when the chat template can’t render a side, for the same reason as the enable_thinking setting above. For that data, pass enable_thinking=False to get_codec.
The training loop interleaves pairs into a flat batch, [chosen_0, rejected_0, chosen_1, rejected_1, ...], so even indexes hold chosen responses and odd indexes hold their rejected counterparts.
Use your own preference dataset
train turns each dataset row into a pair with its to_pair argument, a function that returns (chosen_messages, rejected_messages) for a row, or None to skip the row. The default, helpsteer3_pair in post_training_cookbook.dpo.train, picks the chosen side from HelpSteer3’s overall_preference score and skips ties. For another preference dataset, set dataset_name to it and pass your own function. This one reads prompt, chosen, and rejected text columns:
val_split, which defaults to "validation". If your dataset has no split by that name, set val_split to a held-out split it has, or set val_interval=0 to turn validation off. Don’t set val_split to the training split. Validation then scores pairs the run trains on, which inflates val/preference_accuracy.
Write your own DPO loop
When none of the cookbook recipes fit your use case, you can write your own training loop. This loop trains on an in-memory list of preference pairs:make_dpo_loss builds the loss function for one batch, bound to that batch’s reference sums and response masks. compute_custom_forward_backward runs forward, PyTorch autograd, and custom_forward_backward around it.
The loss function reduces each sample’s per-token log-probabilities to a masked sum, forms the chosen-minus-rejected margin relative to the reference, and returns the mean of -log σ(β · margin) over the batch’s pairs. To add conservative-DPO smoothing, pass label_smoothing to make_dpo_loss.
compute_logprobs scores everything you pass it in one forward pass, so the loop snapshots the reference in chunks of REF_CHUNK_SIZE samples. Lower it if forward runs out of memory on long pairs. The recipe does the same with precompute_ref_batch_size, and also adds a cosine learning-rate schedule, validation, and periodic checkpoints.
Validate with preference accuracy
DPO has no token-level accuracy, so the recipe validates with implicit-reward preference accuracy instead. Everyval_interval steps, it reports the fraction of held-out pairs where the policy’s reward shift prefers the chosen response. Watch val/preference_accuracy and val/mean_margin, which should both rise. At step 0, preference accuracy reads 0, because the policy still equals the reference and ties count as wrong. For details, see DPO metrics.
Next steps
Supervised fine-tuning
Warm up a model on demonstrations before preference training.
Monitoring
Read the DPO validation metrics.
Write a custom loss function
Adapt the DPO pattern to your own objective.
Deploy a checkpoint
Deploy the trained model.