How post-training runs SFT
SFT runs on a trainer-only model resource. Nothing samples from the model, so the resource runs withnum_generator_replicas=0. Each step runs two operations:
forward_backwardwithLOSS_TYPE_CROSS_ENTROPY, which computes the weighted next-token cross-entropy over the batch and accumulates gradients.optim_step, which applies the accumulated gradients with the step’s learning rate.
Run an SFT recipe
The cookbook’sSFTConfig and train function handle dataset loading, tokenization, loss masks, the training loop, and checkpoints. This example trains Qwen3.8 27B on the No Robots dataset, with every other setting at its default:
train provisions a trainer-only model resource for model, attaches one session to it, and stops both when it returns. To train on a resource that’s already running, set model_resources_id instead of model. See Manage model resources.
The config sets enable_thinking=False because No Robots conversations contain no reasoning, as Data for reasoning models explains.
The config also sets wandb_project, so train logs to Weights & Biases. Export WANDB_API_KEY before you run it, or set WANDB_MODE=offline to log locally. Without either, the run fails after it provisions the model resource, or, in an interactive terminal, waits at a W&B login prompt while the resource bills.
Settings worth adjusting first:
num_epochsandmax_steps:trainmakesnum_epochspasses over the dataset, one by default, and reshuffles the rows before each pass.max_stepscaps the total number of steps, which keeps a test run short. Its default,None, sets no cap, so a config without it trains on every full batch: about 1,187 steps for No Robots at abatch_sizeof 8. The learning rate decays linearly toward zero over the run.learning_rate: The right rate depends on the model and the task. For a low-rank adapter (LoRA) run,1e-4to5e-4is typical. Full-weight training needs a lower rate. The cookbook’s No Robots recipes use2e-5for full-weight training and1e-4for LoRA on the same model.train_on_what:TRAIN_ON_ALLtrains on every assistant turn.TRAIN_ON_LASTtrains on only the final one, which suits multi-turn datasets where only the last response matters.batch_size: Larger batches (16 to 32) produce smoother loss curves.use_lora=False: Trains the full weights instead of an adapter. Not every model supports full-weight training, so check Supported models first. See LoRA vs. full-weight training.
SFTConfig field.
Prepare your data
The post-training service receives tokenizedSample objects containing input tokens, next-token targets, and loss weights. Your code controls data loading, preprocessing, tokenization, and which tokens train, so any dataset schema works.
For the cookbook recipe, select your data with dataset_name, dataset_config, and dataset_split. Its default mapping reads a messages column, a list of {"role": ..., "content": ...} dicts. For other schemas, pass to_messages to map each row to its messages. Put any instruction you want the model to learn, such as an answer format, in that mapping:
datasets, so local files work too. For example, save one row per line in ./my_data/train.jsonl and set dataset_name="./my_data" with dataset_split="train". Relative paths resolve from your working directory. train checks the first row with to_messages before provisioning a model resource or session, so schema errors fail before billing.
Data for reasoning models
When the cookbook renders your conversations, match the model’s chat template and thinking settings to your data:- Qwen data without reasoning: Set
enable_thinking=False, as the No Robots example does. Thinking is on by default for the catalog’s Qwen models. Leaving it on makes prompt and full-conversation rendering disagree, so the recipe skips the rows and fails after provisioning the resource. The GLM-5.3 codec accepts these rows with thinking on and rejectsenable_thinking=False. - Data with reasoning: Put assistant reasoning in
reasoning_contentalongsidecontent. Qwen3.8 27B’s template reads reasoning only from that field, so the recipe skips rows with<think>tags insidecontent.preserve_thinkingcontrols whether earlier turns retain reasoning. See Thinking settings. - Consecutive GLM assistant turns: Merge or split them before training. GLM closes each turn with the next message’s role marker, so consecutive assistant turns have no stop token and the recipe skips those rows with a warning.
Loss masks
Choose which tokens contribute to cross-entropy with per-targetfloat32 weights: 0 excludes a token, 1 trains it fully, and fractional values reduce its contribution.
The cookbook’s render_conversation uses 0 for prompt content and role headers, including the assistant header, and 1 for selected assistant content and end-of-turn tokens. Training on end-of-turn tokens teaches the model when to stop. train_on_what selects all assistant turns or only the last one. For custom weighting, supply your own mask to build_cross_entropy_sample. Data and samples explains token alignment.
Write your own SFT loop
To customize data loading, preprocessing, tokenization, batching, or loss weights, write your own training loop and submit samples to the post-training service. This example renders an in-memory list of conversations with the cookbook’s chat-template codec. Replace that preparation with your own sample construction as needed:render_conversation returns a list of sequences, not a single one, because it splits a conversation wherever the chat template strips earlier reasoning.
build_cross_entropy_sample shifts the tokens into input and target positions for you. For cross-entropy, result.loss is the sum over all trained positions, not a mean. Divide it by the batch’s total weight to get the per-token mean the recipes log.
To train with a variant of cross-entropy, such as one with an added regularization term, write a custom loss function. Each step then uses forward and custom_forward_backward instead of forward_backward.
Chain SFT and GRPO
To continue an SFT adapter with GRPO, hand it over with a training checkpoint. Combine methods explains when this helps. The handoff takes three settings:- Set
training_checkpoint_intervalso the SFT run writes a training checkpoint at its last step. The cookbook writes one only when the interval fires, andtrainreturns the latest checkpoint ID (orNone). - Start the GRPO run with
resume_from_checkpoint_idset to that ID andload_optimizer=False. The new session loads the adapter weights with a fresh optimizer and its step counter at zero. - Use the same LoRA rank and alpha in both phases. A checkpoint with a different rank fails to load, and a different alpha silently changes how strongly the adapter applies.
question and answer columns, not messages, so the SFT phase maps each row with to_messages. The mapping poses each question in the GRPO prompt format, so SFT teaches the answer format that GRPO then scores. The cookbook’s recipes/sft_then_grpo.py is the complete worked example.
Unlike SFTConfig, GRPOConfig logs to Weights & Biases by default, so export WANDB_API_KEY or WANDB_MODE=offline, or pass wandb_project=None to GRPOConfig, before you run this example.
Next steps
Direct preference optimization
Train on preference pairs with a custom loss function.
Reinforcement learning with GRPO
Continue the adapter with reinforcement learning.
Cookbook reference
Every
SFTConfig field and its default.Deploy a checkpoint
Deploy the trained model on dedicated model inference.