00
Weights: SFT · DPO · RL Context: RAG · context engineering Data: design → generate → select → curate → label → denoise → quality card

Customizing modern AI:
SFT, RL, RAG and
data-centric AI

Every team rents the same foundation models. The difference is the data you train on, the reward you optimize and the knowledge you retrieve.

1.3B InstructGPT outputs preferred to 175B GPT-3 outputs Paper InstructGPT
+28% Cursor Tab accept rate after online RL, with 21% fewer suggestions Blog Cursor Tab RL
−67% Retrieval failures with Contextual Retrieval and reranking Doc Contextual Retrieval

The course moves from the decision, to the mechanisms, to the data, to evaluation in production

Decide
Mechanisms
Data
Prove and ship
01 Choose the leverprompt · context
RAG · SFT
DPO · RL
2026 shake-out
02 Supervised fine-tuningLoRA · QLoRA · DoRA · TRL
Unsloth · Gemma · Vertex AI
03 Reinforcement learningRLHF/PPO · DPO family · GRPO
RLVR · reward models · RFT
04 Retrieval (RAG)pipeline · Vertex RAG Engine
contextual retrieval
agentic RAG · RAG vs FT
05 Dataset designcards · Croissant · contracts
coverage · slices · drift
06 Labels and leakageagreement · LLM annotators
confident · active learning
leakage · contamination
07 LLM + synthetic dataFineWeb-Edu · DCLM · Magpie
generate → verify → keep
collapse · RAG test sets
09 Full lifecycleworkflow · quality card
2025-26 recipes
open challenges
08 Eval designerror analysis
slice metrics + CIs
LLM judge · calibration
cost-sensitive thresholds
Interview slideends every chapter
designch 05
generatech 07
selectch 07
curatech 06 · 07
labelch 06
denoisech 06 · 07
quality cardch 09
01
prompt / context long context RAG SFT DPO RL / RFT cost · data · platform risk ↑ context: change the tokens weights: change the model

Choose the cheapest lever that fixes the failure

Prompt, retrieve, tune or reinforce. Each rung up the ladder costs more data, more money and more platform risk.

context engineering RAG SFT DPO RFT / GRPO

“you use the right optimization tool for the right job”

Knowledge failures go to retrieval; behavior failures go to weights

context optimization → what the model knows LLM optimization ↑ how the model behaves prompt RAG fine-tune RAG + FT

“RAG only solves our in-context learning issues”

Knowledge → retrieval

Missing, stale or private facts. Use prompting and RAG.

Ovadia 2023: “RAG consistently outperforms” unsupervised fine-tuning for knowledge injection.

Gekhman 2024: models learn new facts slowly, and then hallucinate more.

Behavior → weights

Format, tone, style and consistent reasoning. Use fine-tuning, preference tuning or RL.

Balaguer 2024 (agriculture): fine-tuning gave +6 p.p. RAG added +5 p.p. on top. The two levers combine.

BLEU · Icelandic grammar correction (OpenAI) GPT-4zero-shot GPT-43-shot GPT-3.5FT, 1,000 ex. GPT-4FT, 1,000 ex. GPT-4FT + RAG
Do not add retrieval to a style task: BLEU fell from 87 to 83. Classify the failure before you pick the tool.

Climb the ladder one rung at a time, and let the eval decide each step

Eval set20+ gold + metric
Prompt + contextinstructions, tools
Pass → ship
Failure analysisclassify each miss
knowledge
KB < 200k tokenslong context + cache
RAGhybrid + rerank
Agentic RAGplan→retrieve→reflect
behavior
Gold outputs → SFTLoRA, or distill
Pairs → DPOchosen vs rejected
Grader → RFT / GRPOno gold outputs
Combine, re-evalRAFT · SFT → DPO
monitor drift · re-tune when the base model retires

Read 20 to 50 failures. Ask one question per failure: is a fact missing, or is the behavior wrong?

RAFT: fine-tune with distractor documents so the model ignores noise and cites the right chunk. Vertex: SFT first, then preference tuning.

RungData neededCost and latencyWins when
1 · Prompt / context0 to tens of examples + a 20+ case evalHours. Prompt caching offsets long promptsAlways first; the task fits the model's priors
2 · Long contextKB under ~200k tokens (~500 pages)Very low upfront; high per call unless cachedSmall, stable corpora
3 · RAG / agentic RAGA corpus, no labels; eval Q/A pairsDays. + a retrieval hop (tens to hundreds of ms)Fresh, large, private, permissioned facts
4 · SFT (LoRA / full)50 to 500 (OpenAI ≥50; Vertex 100 to 500)Tokens × epochs + hosting; can shrink promptsFormat, tone, narrow tasks, distillation
5 · Preference tuning (DPO)Pairs (chosen, rejected)~1.1x to 4x the SFT token price (Together)Judgments with no single gold answer
6 · RL / RFTTens to thousands of prompts + a graderHighest: rollouts + trainingVerifiable tasks: code, math, tools

Rung 1 is context engineering: keep “the optimal set of tokens” in the window. Fight context rot with just-in-time retrieval, compaction and sub-agents.

Doc Anthropic, Effective context engineering for AI agents

Set the bar in money: +$20 per success, −$40 per escalation, −$1,000 churn in 5% of failures. Break-even accuracy is 81.5%. Below it, climb a rung.

Doc OpenAI, Optimizing LLM accuracy

LoRA is usually enough: it matches full fine-tuning on small and medium SFT sets, and in RL even at rank 1. Anthropic has no public fine-tuning API.

Blog Thinking Machines, LoRA Without Regret

In October 2026 managed tuning splits into hyperscaler tuning and do-it-yourself RL APIs

2025 2026 2027 May 2025 · Google Gemini API + AI Studiolose tuning (1.5 Flash-001)tuning only on Vertex AI 2025-12-03 · AWS Bedrock RFT · Nova 2 LiteLambda (RLVR) or judge reward“66% gains”, vendor-reported 2026-05-07 · OpenAI announces fine-tuningwind-down 2026-07-02 jobs closed to orgswith no FT inference 2026-10-23 · today ft-o4-mini-2025-04-16,ft-gpt-4.1-nano, ft-gpt-4,ft-gpt-3.5-turbo … shut down RFT SUNSET 2027-01-06 existing customers canno longer create jobs

OpenAI: platform closes

“Inference on fine-tuned models will be disabled only when the underlying base model is deprecated.” RFT ran only on o4-mini, so it ends today.

Doc OpenAI, Deprecations

Fireworks: RL as code

“Managed Training no longer accepts new RFT jobs”. The Training API gives “Async GRPO with a custom rollout function” and multi-turn tool harnesses.

Doc Fireworks, Reinforcement fine-tuning

Tinker: training primitives

You write the loop with forward_backward, optim_step, sample and save. LoRA on 28+ models, from 1B to 1T+ parameters, dense and MoE.

Doc Tinker docs, Thinking Machines
Lesson: portable recipes (data + grader + eval) matter more than checkpoints. Tuned weights die with their base model.
ProviderWhat you can tune, Oct 2026Trend
Vertex AISFT: Gemini 3.5 Flash-Lite, 3.5 Flash, 3.1 Flash-Lite, 2.5 Pro, 2.5 Flash-Lite, 2.5 Flash. PREFERENCE_TUNINGgrowth
Amazon BedrockSFT, RFT (Nova 2 Lite), distillationgrowth
Together AILoRA, full fine-tuning, DPO on open modelsopen
Fireworks AITraining API: async GRPO, custom rolloutsDIY RL
Tinkerforward_backward / optim_step, LoRADIY RL
OpenAIgpt-4.1 family, existing customers onlysunset
AnthropicNo first-party fine-tuning; context, caching, Skillscontext
retune_on_vertex.py · google-genai

# Portable recipe: same JSONL, new provider
from google import genai
from google.genai import types
from google.genai.types import HttpOptions

DATA = "gs://your-gcs-bucket/your-tuning-data.jsonl"
client = genai.Client(
    http_options=HttpOptions(api_version="v1"))
job = client.tunings.tune(
    base_model="gemini-3.5-flash",
    training_dataset=types.TuningDataset(gcs_uri=DATA),
    config=types.CreateTuningJobConfig(
        tuned_model_display_name="support-tone-v1",
        epoch_count=1),
)
# Keep the eval set and the grader in your own repo.

Interview: choosing a customization lever

Your support bot quotes the wrong refund policy and also sounds too formal. What do you change?
knowledge behavior
These are two failures. The policy is knowledge: put it in retrieval, so it stays fresh, citable and permissioned. The tone is behavior: fix it with the prompt first, then SFT or DPO on preferred replies. Build a 20+ case eval before you start. RAG can hurt a behavior task: in the OpenAI Icelandic case BLEU fell from 87 to 83.
When is long context better than RAG?
cost
When the KB is under ~200k tokens (about 500 pages) and stable. Use prompt caching to cut the per-call cost. Watch for context rot: recall drops as the window fills. Move to RAG when the corpus grows, changes often or needs per-user ACLs.
Why not fine-tune your product catalogue into the weights?
knowledge
Models learn new facts slowly, and hallucination rises once they learn them (Gekhman, EMNLP 2024). The facts go stale, and each update needs a new training run. The tuned weights also die with the base model. Keep the catalogue in retrieval and tune only the behavior.
Your vendor retires the base model you tuned. What survives?
platform risk
The data, the data contract, the grader, the eval set and the pinned recipe survive. The checkpoint does not. OpenAI ft-o4-mini shut down on 2026-10-23. Teams with portable recipes re-tune on Vertex AI, Bedrock or open models, then re-run the same eval to prove parity.
02
<user> Is my refund approved ? <assistant> {"status": "approved", "days": 3} <eos>
context: label −100, no losstarget tokens: cross-entropy loss
LoRAQLoRADoRA TRL v1.16.0 · 2026-10-15PEFT v0.21.0Unsloth Gemma 4Vertex AI

Supervised fine-tuning teaches format, style and skills from examples

SFT minimizes the cross-entropy of the target tokens. The data and the mask decide what the model learns.

Fine-tuning is “the most reliable way to enforce a specific output format”.

training steps train loss

SFT learns only from unmasked tokens, so the chat template and the mask define the task

Objective

L = −Σt log pθ(yt | y<t)

The sum skips tokens with label −100 (the ignore index).

Data quality beats size: LIMA used 1,000 curated examples.

Chat template

A Jinja template adds role markers and special tokens. SFTTrainer applies it to conversational data: messages, or prompt + completion; tool_calls and images also work.

Loss mask in TRL v1.16.0

  • completion_only_loss=None (default): prompt-completion data gets loss on the completion only.
  • assistant_only_loss=True: loss on assistant turns only. The template needs {% generation %} … {% endgeneration %}.
  • TRL patches known families (Qwen3). Vision data: since v1.15.0.
A train/serve template mismatch fails silently. For a base model such as Qwen/Qwen2.5-1.5B, set eos_token="<|im_end|>". If not, the model never learns to stop.

Packing

  • packing=True; max_length (default 1024) sets the row length.
  • packing_strategy: "bfd" (default, best-fit decreasing), "bfd_split", "wrapped".
  • padding_free=True (FlashAttention): no attention across examples.

LoRA trains a thin low-rank update; its variants change the scale, the init and the precision

lora_config.py · PEFT v0.21.0

from peft import LoraConfig, get_peft_model
config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules="all-linear",       # include MLP layers
    lora_dropout=0.05,
    use_rslora=True,                   # alpha/sqrt(r) scaling
    use_dora=False,                    # True -> DoRA (merge before inference)
    init_lora_weights="pissa_niter_4", # PiSSA init via fast SVD
    task_type="CAUSAL_LM",
)
model = get_peft_model(base_model, config)
model.print_trainable_parameters()

Other init options in PEFT v0.21.0: olora, eva, corda, loftq, orthogonal.

LoRA Without Regret (2025-09-29): LoRA matches full FT on small and medium SFT sets. Use all layers, MLP and MoE too. Use a LR about 10× the full-FT LR. For RL, rank 1 is enough. Large continued pretraining exceeds the adapter capacity.

TRL v1.16 puts QLoRA, packing and assistant-only loss in one SFTConfig

sft_qlora.py · TRL v1.16.0 (from the TRL SFT docs, combined)

from datasets import load_dataset
from peft import LoraConfig
from transformers import BitsAndBytesConfig
from trl import SFTConfig, SFTTrainer

args = SFTConfig(
    output_dir="qwen3-sft",
    max_length=2048,
    packing=True, packing_strategy="bfd",   # padding-free BFD packing
    assistant_only_loss=True,                # mask user/system tokens
    learning_rate=1e-4,                      # adapters -> higher LR
)
trainer = SFTTrainer(
    model="Qwen/Qwen3-0.6B",
    args=args,
    train_dataset=load_dataset("trl-lib/Capybara", split="train"),
    peft_config=LoraConfig(r=16, lora_alpha=32, target_modules="all-linear"),
    quantization_config=BitsAndBytesConfig(load_in_4bit=True,
                                           bnb_4bit_quant_type="nf4"),  # QLoRA
)
trainer.train()

Packing: BFD, padding-free

v1.13.0: chunked-CE lm_head, 1.2–1.7× throughput. v1.15.0: fused LM head, selective activation checkpointing. v1.16.0: pad_to_multiple_of="auto".

Assistant-only loss

Loss only on assistant turns. New loss option: loss_type="dft" (Dynamic Fine-Tuning, arXiv 2508.05629).

QLoRA = 4-bit NF4 base + LoRA

The docs say: “Combine with peft_config for QLoRA training.” Only the LoRA matrices train.

Learning rate: adapters about 1e-4

SFTConfig defaults: learning_rate=2e-5, gradient_checkpointing=True, bf16=True, logging_steps=10. CLI: trl sft …

API drift. The Cloud Run Jobs codelab uses the pre-1.0 style. Pin your versions.
Pre-1.0, on SFTTrainerTRL 1.x
max_seq_length=SFTConfig(max_length=)
dataset_text_field=, packing=in SFTConfig
tokenizer=processing_class=
requirements · pin the versions this slide uses

$ pip install "trl==1.16.0" "peft==0.21.0" bitsandbytes
$ pip show trl peft | grep Version

Upgrade one library at a time. Run a short smoke test with max_steps=10 after each upgrade.

On Google, tune Gemma with open tooling or tune Gemini with managed Vertex AI

Gemma · open weights, open tooling
Gemini · managed Vertex AI tuning
Colab / Kagglefree T4
QLoRATRL or Unsloth
merge + 4-bit270M: <300 MB
Cloud Run + vLLMserve the model
On-deviceLiteRT · WebGPU
JSONL in GCS100–500+ rows
tunings.tuneepoch_count …
Tuned endpointgenerate_content

Gemma lane: four starting points

QLoRA pagegoogle/gemma-4-E2B (or E4B, 12B, 31B, 26B-A4B) · synthetic text-to-SQL data · r=16, α=16, dropout 0.05 · saves lm_head + embed_tokens · 3 epochs, LR 2e-4, constant schedule
Cloud Run Jobs codelabgemma-3-1b-it on Text2Emoji · one L4 GPU · r=8, α=16 on q_proj, v_proj · serve with vLLM on Cloud Run
Own your AIGemma 3 270M · QLoRA on a free T4 “in minutes” · 4-bit model under 300 MB · LiteRT / MediaPipe, or Transformers.js with WebGPU
Unsloth“2× faster with 70% less VRAM” · r 16 or 32, α = r or 2r, LR 2e-4, 1–3 epochs · train_on_responses_only with <start_of_turn>user / <start_of_turn>model

Gemma docs now point to gemma.ai. The Keras LoRA tutorial is marked deprecated.

Gemini lane: Vertex AI supervised tuning

Models: Gemini 3.5 Flash-Lite, 3.5 Flash, 3.1 Flash-Lite, 2.5 Pro, 2.5 Flash-Lite, 2.5 Flash. Data: JSONL in Cloud Storage, “at least 100 to 500 examples”. Knobs: epoch_count, adapter_size, learning_rate_multiplier (default 1). Run the first job with the defaults. For tuned tasks, turn thinking off or use the minimum budget.

vertex_tune.py · google-genai SDK (adapted)

import time
from google import genai
from google.genai import types
client = genai.Client(http_options=types.HttpOptions(api_version="v1"))
job = client.tunings.tune(
    base_model="gemini-3.5-flash",
    training_dataset=types.TuningDataset(gcs_uri="gs://b/tune.jsonl"),
    config=types.CreateTuningJobConfig(
        tuned_model_display_name="support-tone-v1", epoch_count=1))
done = {"JOB_STATE_SUCCEEDED", "JOB_STATE_FAILED", "JOB_STATE_CANCELLED"}
while job.state not in done:
    time.sleep(60); job = client.tunings.get(name=job.name)
resp = client.models.generate_content(
    model=job.tuned_model.endpoint, contents="why is the sky blue?")
The Gemini API and AI Studio have no tuning. Tune Gemini on Vertex AI, or tune open Gemma weights yourself.

Limit forgetting with adapters, merging and on-policy distillation

Why a fine-tuned model forgets

distribution shifthigh LRtoo many epochs narrow datatemplate mismatchsafety-refusal drift

Mitigations, cheapest first

  1. Use PEFT. Keep the adapter separate and hot-swappable.
  2. Lower the LR. Train 1–3 epochs. Stop early on held-out general benchmarks.
  3. Watch the loss: below 0.2 usually means overfitting. Scale an over-fit adapter by α × 0.5 (Unsloth).
  4. Merge back toward the base model.

Real example: SmolLM3

A linear merge of 0.9 × the APO soup + 0.1 × a mid-training checkpoint recovered the long-context RULER scores.

Merge methods

Task arithmeticτ = θft − θbase; add or negate task vectors (2212.04089)
TIEStrim redundant values, elect the sign, average the values that agree (2306.01708)
DAREdrop a fraction p of the deltas, rescale by 1/(1−p); often dare_ties (2311.03099)
SLERPmerge exactly two models along the arc, not the chord

mergekit (LGPL-3.0, 18+ methods): mergekit-yaml config.yml ./out. PEFT: add_weighted_adapter(…, combination_type="dare_ties", density=0.2).

Special tokens added at the same embedding index collide when you merge fully fine-tuned models.

Distillation: a small student learns from a large teacher

Off-policy

SFT on teacher outputs, as in the DeepSeek-R1-Distill Qwen and Llama students. The student never sees its own mistakes.

On-policy (GKD, 2306.13649)

The student writes, and the teacher scores every token. This removes the train/inference mismatch.

TRL DistillationTrainer

Stable since v1.10.0 (2026-08-13). beta: 0.0 = forward KL, 0.5 = JSD, 1.0 = reverse KL (default). Teacher and student must share a vocabulary. Tool-result tokens are masked.

Interview: supervised fine-tuning

After SFT the model never stops generating. Why?
The EOS token or the chat template is wrong, so the stop token never got a loss. Set eos_token (for example "<|im_end|>" on a Qwen2.5 base). Use the same template in training and serving. Check the mask: the end-of-turn token must be unmasked.
LoRA or full fine-tuning for 20k support conversations?
LoRA on all linear layers, MLP included, with a LR about 10× the full-FT LR (LoRA Without Regret). On small and medium SFT sets it matches full FT. Use full FT only when the data exceeds the adapter capacity, for example large continued pretraining.
How do you measure and limit forgetting?
Track held-out general benchmarks next to the target metric each epoch. Stop early. Keep the adapters separate. Mix in general data. Merge toward the base with TIES, DARE or a linear merge, as SmolLM3 did to recover long-context scores.
Explain where QLoRA saves memory.
The base stays frozen in 4-bit NF4 with double quantization. Only the small LoRA matrices train, and paged optimizers absorb memory spikes. QLoRA fine-tuned a 65B model on one 48 GB GPU. Full FT with AdamW needs about 16 bytes per parameter.
03

Reinforcement learning optimizes the policy against a reward, not against gold answers

Three eras: RLHF with PPO (2022-23), direct preference optimization (2023-24), verifiable rewards with GRPO-family algorithms (2024-26). The 2026 frontier is agentic, multi-turn, tool-using RL with async rollouts.

PPODPOGRPODAPO RLVRRFTagentic RL
Promptsno gold answers
Policysamples K outputs
RewardRM / verifier / rubric
Advantagecritic or group norm
Updateclipped PG (+ KL)
2022 RLHF + PPO 2023 DPO family 2025 GRPO + RLVR

RLHF with PPO keeps four models in memory, and generation takes most of the time

Demonstrationshuman-written
SFT policystep 1
Human rankingsK outputs per prompt
Reward modelscalar head, step 2
PPO loop, step 3
Policyinit from SFT
RolloutsvLLM generation
Reward modelfrozen
Criticvalue model
Clipped updatePPO step
Referencefrozen SFT copy
stop here proxy RM score gold score KL from the initial policy → score shape after Gao et al. 2022 (qualitative)

“outputs from the 1.3B parameter InstructGPT model are preferred to outputs from the 175B GPT-3”

  1. Fine-tune the base model on human demonstrations (SFT).
  2. Train a reward model on human rankings. Loss: −log σ(r(x,yw) − r(x,yl)).
  3. Run PPO against the reward model. A per-token KL penalty keeps the policy near the SFT model. PPO-ptx mixes in pretraining gradients to reduce the alignment tax.

Four models live at once

policyreferencerewardcritic
0
of RLHF time is generation (OpenRLHF README). Fast vLLM rollouts matter most.
Overoptimization: the proxy score keeps rising with KL, but the gold score peaks and then falls (Goodhart). The reward model is the main surface for reward hacking. Stop at the gold peak.

DPO turns preference learning into a classification loss on chosen and rejected pairs

LDPO = −E[ log σ( βKL strength ( log πθ(y+|x)πref(y+|x)chosen: push up − log πθ(y−|x)πref(y−|x)rejected: push down ) ) ]

The closed-form optimal policy of the KL-regularized RLHF objective turns RLHF into a logistic loss. DPO “eliminat[es] the need for sampling from the LM during fine-tuning”.

DPO works “by suppressing the likelihood of dispreferred completions rather than by increasing the likelihood of preferred ones” (TRL docs). So logps/chosen can fall. Watch rewards/margins.
pairedunpaired needs ref
DPO 2305.18290
logistic loss on log-ratios
IPO 2310.12036
resists overfitting on deterministic prefs
KTO 2402.01306
thumbs up / down per sample
no ref
ORPO 2403.07691
SFT + odds ratio, one stage
SimPO 2405.14734
length-normalized + margin γ
empty
dpo_lora_mpo.py · TRL DPO docs, reflowed

from trl import DPOConfig, DPOTrainer
from peft import LoraConfig
from datasets import load_dataset

args = DPOConfig(
    beta=0.1, learning_rate=1e-5,  # higher LR for adapters
    loss_type=["sigmoid", "bco_pair", "sft"],  # MPO
    loss_weights=[0.8, 0.2, 1.0])
ds = load_dataset("trl-lib/ultrafeedback_binarized",
                  split="train")
trainer = DPOTrainer("Qwen/Qwen3-0.6B", args=args,
                     train_dataset=ds,
                     peft_config=LoraConfig())
trainer.train()
  • Defaults: beta 0.1, learning_rate 1e-6, max_length 1024.
  • loss_type is a list. Options include hinge, ipo, robust, bco_pair, apo_zero, apo_down, discopop, sft, sigmoid_norm (SimPO-style).
  • ref_model=None uses the initial policy.
  • sync_ref_model=True gives TR-DPO. label_smoothing gives Robust DPO.
  • Vertex AI preference tuning: beta 0.01 to 0.5. Do SFT on the preferred responses first, then preference-tune.

GRPO drops the critic and scores each answer against its own group

  1. Sample a group of G answers for each prompt. TRL default: num_generations=8.
  2. A rule scores each answer: 1 if the final answer matches, else 0. R1 uses accuracy, format (think tags) and language-consistency rewards.
  3. Ai = (ri − mean(r)) / std(r). The group mean is the baseline. Then apply a PPO-style clipped ratio, with an optional KL term.
  4. No value model: about half the memory of PPO. No neural RM either: DeepSeek says neural RMs “are susceptible to reward hacking during large-scale RL”.
  5. R1-Zero is pure RL on the base model. AIME 2024 pass@1 rises from 15.6% to 77.9%, and to 86.7% with self-consistency (paper v2).

Cost of the RL stage: about US$294K on 648 H800 GPUs, base model not included (Nature supplementary). The Register argues the true cost is far higher.

DAPO and Dr. GRPO fix entropy collapse and length bias, and TRL ships them as defaults

1 · Clip-Higher

1−0.2ratio 11+0.28

Set εlow 0.2 and εhigh 0.28. Rare tokens can gain probability. It “avoids entropy collapse”.

2 · Dynamic sampling

0/88/8keep

Drop prompts with group accuracy 0 or 1. They give zero advantage and zero gradient.

3 · Token-level loss

short answereach token has weight 1

Average the loss over all tokens in the batch, not per sample. Long answers keep their weight.

4 · Overlong shaping

penalty 012,28816,384→ −1

Apply a soft length penalty. Max length 16,384 tokens, with a 4,096-token cache before it.

5 · Dr. GRPO: length bias

GRPODr. GRPOresponse length vs steps

GRPO “artificially increases response length”, mostly for wrong answers. Fix: divide by a constant, drop the std term.

0DAPO, AIME 2024, Qwen2.5-32B base
0DeepSeek-R1-Zero-Qwen-32B
0of the training steps for DAPO
0Dr. GRPO, AIME 2024, 7B base

GSPO (Qwen, 2507.18071) uses sequence-level ratios and clipping. It stabilizes MoE RL and trained the Qwen3 models.

Clip-Higherdynamic samplingtoken-level lossoverlong shapingDr. GRPOGSPO
grpo_verifiable.py · adapted from the TRL reward-function pattern

import re
from trl import GRPOConfig, GRPOTrainer

def correct_answer(completions, ground_truth, **kwargs):
    texts = [c[0]["content"] for c in completions]  # conversational
    preds = [re.search(r"\\boxed\{(.+?)\}", t) for t in texts]
    return [1.0 if p and p.group(1).strip() == gt else 0.0
            for p, gt in zip(preds, ground_truth)]

def format_reward(completions, **kwargs):
    return [0.2 if "<think>" in c[0]["content"] else 0.0
            for c in completions]

args = GRPOConfig(loss_type="dr_grpo", scale_rewards=False,  # Dr. GRPO
                  num_generations=8, beta=0.0, use_vllm=True)
trainer = GRPOTrainer(model="Qwen/Qwen2.5-0.5B-Instruct",
                      reward_funcs=[correct_answer, format_reward],
                      args=args, train_dataset=dataset)
trainer.train()

TRL v1.16.0 GRPOConfig defaults

fielddefaultother values
loss_type"dapo"dr_grpo, sapo, cispo, vespo
beta0.0no KL, no reference model
num_generations8group size G
scale_rewards"group""batch"; False = Dr. GRPO
vllm_mode"colocate""server"; set use_vllm=True
A reward function gets the completions plus the dataset columns as kwargs. It returns list[float]. Unsloth: expect the reward to rise only after about 300 steps. Use 500+ rows and a model of 1.5B or larger.

Rewards range from human preference to programs, and checkable rewards are the hardest to hack

A reward can come from people, from a model trained on people, from a judge, from a rubric or from a program. The source sets the cost, the scale and the attack surface.

RLVR: a program is the reward

Tulu 3 named it: “a novel method we call Reinforcement Learning with Verifiable Rewards”.

  • math answer match (math-verify)
  • unit tests in a sandbox
  • JSON schema or regex checks
  • IFEval-style constraint checks; SQL result match

Cost and hackability fall to the right

A program is cheap per label and hard to fool. But most real tasks (writing, medicine, law) are not verifiable. Judges and rubrics are the bridge.

RLAIF. Constitutional AI: the model critiques and revises its outputs with a list of principles, then RL uses AI preference labels. Lee et al.: AI labels perform comparably to human labels on summarization and dialogue.

Outcome vs process reward models

An ORM scores only the final answer. A PRM scores each reasoning step.

“Let's Verify Step by Step”: the process-supervised model solves 78% of a MATH test subset. PRM800K has 800,000 step labels.

In 2025-26, teams use PRMs mostly for test-time reranking. DeepSeek-R1 did not train with PRMs.

Rubrics and graders

Rubrics as Rewards (Scale AI): a judge grades a checklist for each prompt. Up to 28% relative gain on HealthBench-1k vs Likert scores.

OpenAI RFT graders: string_check, text_similarity, score_model, python, multi. The platform is winding down, so keep the grader as a design pattern. Bedrock RFT: reward in AWS Lambda or model-as-judge.

Hacks to know

length inflationsycophancy to an RMedit unit tests, sys.exit(0)regex format gamingjudge prompt injection

Fireworks: test the grader on full, partial, zero, malformed and edge-case answers before you train.

Graders are the new labels: red-team them like a dataset.

Production RL runs as online loops and multi-turn agent training on rented training APIs

OpenRLHF README · PPO-family job on Ray + vLLM + DeepSpeed

$ ray job submit --address="http://127.0.0.1:8265" \
  --runtime-env-json='{"working_dir": "/openrlhf"}' \
  -- python3 -m openrlhf.cli.train_ppo_ray \
  --actor.model_name_or_path OpenRLHF/Llama-3-8b-sft-mixture \
  --reward.model_name_or_path OpenRLHF/Llama-3-8b-rm-700k \
  --algo.advantage.estimator reinforce_baseline \
  --vllm.num_engines 4 --vllm.tensor_parallel_size 2 \
  --train.colocate_all --ds.zero_stage 3 --ds.param_dtype bf16
0suggestions
0accept rate
0requests / day

Cursor (2025-09-12): policy gradient on live accept and reject signals. “we learn a policy that targets a particular accept rate”

Agentic RL tooling, Oct 2026

  • TRL AsyncGRPO: v1.14 LoRA + async tools; v1.15 OpenEnv harness; v1.16 tree attention, up to ~3× on shared-prompt rollouts.
  • verl (HybridFlow): ReTool, uni-agent, Megatron MoE (DeepSeek-671B, Qwen3-235B); GRPO LoRA on a 1T model, 64 H800s.
  • OpenRLHF v0.10: AgentInstanceBase reset()/step(), --train.agent_func_path, --train.async_enable.
  • Rented APIs: Fireworks Training API (async GRPO, sandboxes); Tinker (forward_backward, optim_step).
vendor-reported

Managed RFT results

OpenAI RFT: Ambience ICD-10 0.39 → 0.57; Harvey citation F1 0.563 → 0.6765; SafetyKit F1 86% → 90%. Fireworks: Genspark +10% quality vs a SOTA closed model, +33% tool calls, ~50% cost cut.

Interview: reinforcement learning for LLMs

Why does GRPO not need a critic, and what does that cost?
The mean and std of the rewards of G sampled answers act as the baseline. You save the value model, about half the memory of PPO. But you generate G completions per prompt, and groups with all-0 or all-1 rewards give no gradient. DAPO dynamic sampling drops them.
Reward goes up, but human-rated quality goes down. What happened?
Overoptimization or reward hacking (Gao et al.). Read samples. Look for length inflation or edited tests. Add hidden tests, stop at the gold peak, use KL or early stopping, and rotate the judges. Trust a held-out gold signal, not the proxy.
DPO chosen log-probs fall during training. Is it a bug?
Not necessarily. DPO mostly suppresses the rejected completions (TRL docs), so the margin can grow while both log-probs fall. Watch rewards/margins and an eval. Add an SFT term (MPO) to anchor the chosen responses.
You have 2,000 prompts and a unit-test harness. Which method?
RLVR with a GRPO-family loss: TRL default dapo, beta=0, dynamic sampling. Run the tests in a sandbox and decontaminate the eval. Start from an SFT checkpoint of 1.5B or larger. Expect the reward to rise only after about 300 steps.
04

RAG gives the model fresh, private and citable knowledge at run time

Retrieval changes the tokens in the window, not the weights. It wins when knowledge is large, changing, permissioned or needs citations.

hybrid search contextual retrieval reranking Vertex AI RAG Engine agentic RAG RAFT
First check the size. If the knowledge base is “smaller than 200,000 tokens”, Anthropic suggests that you put all of it in the prompt and use prompt caching.

Hybrid search, contextual chunks and reranking cut retrieval failures by two thirds

IngestPDF · HTML · tables
Chunk512 tok · 100 overlap
Enrichcontextual header
Evalrecall@kfaithfulness
Index
Embeddingsdense vectors
BM25exact terms
Cite + checkgrounding test
Query
Rewriteexpand query
Hybrid retrievetop-k 20–150
Reranktop-n 3–20
Generatepack + cite

Why a contextual header helps

A chunk such as “revenue grew 3%” loses its company and its quarter. An LLM reads the full document and writes a short header. The header goes into the embedding and the BM25 text.

Tune chunking with retrieval recall on a labeled set, not by eye.

Top-20 retrieval failure rate

5.7%baseline embeddings
  • Baseline: 5.7%
  • + contextual embeddings: 3.7% (−35%)
  • + contextual BM25: 2.9% (−49%)
  • + reranking: 1.9% (−67%)

Source: Anthropic, 2024-09-19.
Failure rate = 1 − recall@20.

Vertex AI RAG Engine manages the corpus, chunking and retrieval behind one client

rag_engine.py · adapted from the quickstart; check exact kwarg names

import agentplatform
from agentplatform import types
from google import genai
from google.genai import types as genai_types

client = agentplatform.Client(project=PROJECT, location="us-central1")
corpus = client.rag.create_corpus(display_name="policies")
client.rag.import_files(corpus.name, paths=["gs://bucket/policies/"],
    transformation_config=types.RagFileTransformationConfig(
        chunking_config=types.RagFileChunkingConfig(
            chunk_size=512, chunk_overlap=100)))
ctx = client.rag.retrieve_contexts(rag_resources=[corpus.name],
    rag_retrieval_config=types.RagRetrievalConfig(
        top_k=3, vector_distance_threshold=0.5),
    text="What is the refund window?")

rag_tool = genai_types.Tool(retrieval=genai_types.Retrieval(
    vertex_rag_store=genai_types.VertexRagStore(...)))  # corpus ref
gem = genai.Client(vertexai=True, project=PROJECT, location="us-central1")
resp = gem.models.generate_content(model="gemini-3.5-flash",
    contents="What is the refund window?",
    config=genai_types.GenerateContentConfig(tools=[rag_tool]))
GCS filesgs://bucket/
RAG corpus
policiescreate_corpus
Retrieveretrieve_contexts
Gemini + Retrieval toolgenerate_content
Grounded answercites [1] policies chunk
  • create_corpus: make an empty, managed corpus.
  • import_files: parse, chunk (512 / 100), embed.
  • retrieve_contexts: top 3 chunks. Test recall here.
  • Tool(retrieval=…): Gemini grounds and cites.
Pin the SDK version. The 2026 quickstart drops vertexai.preview.rag and rag.retrieval_query. The docs now use the “Gemini Enterprise Agent Platform” name. Check kwarg names on the live page.

Agentic RAG plans, retrieves and reflects in a loop, and pays for recall with more LLM calls

One-shot RAG

Query
Retrievetop-k chunks
Generateanswer + cite

1 LLM call. Use it for single-hop lookups with a tight latency budget.

vs

Agentic RAG loop · patterns: “reflection, planning, tool use, and multi-agent collaboration”

0LLM calls so far

Plan: split the question into sub-questions.

Choose a tool: vector search, SQL, web or an API. Retrieve.

Reflect: “enough evidence?” No. Re-query with a new tool.

Yes: answer and cite. Use the loop for multi-hop or ambiguous questions.

Same idea as context engineering

The agent keeps only paths and queries in context and loads the data with tools. Sub-agents return short summaries.

RL teaches when and how to retrieve

Genspark’s RFT-trained Deep Research agent: +33% tool calls, +10% quality vs a SOTA closed model.

Choose RAG for changing facts, fine-tuning for fixed behavior, and RAFT when you need both

RAFT trains with distractor documents. The model learns to ignore irrelevant chunks and cite the relevant one.
CriterionChoose RAGChoose fine-tuning
FreshnessFacts change oftenThe task spec is fixed
Corpus sizeOver ~200k tokensKnowledge is not the gap
Citations / ACLCitations, audit trail, per-user ACLsNot needed
Output formatNot the problemConsistent format and style
Latency budget+1 retrieval hop; more if agenticSmall model, short prompts
Data availableA corpus + eval Q/A pairsGold examples, pairs or a grader
Paper Ovadia et al. 2023

“RAG consistently outperforms” unsupervised FT for knowledge.

Paper Gekhman et al. 2024

Fine-tuning on new facts makes hallucination rise.

Doc OpenAI: LLM accuracy

Behavior task: FT 87 BLEU; FT + RAG 83.

Paper Balaguer et al. 2024

FT gave +6 p.p., and RAG added +5 p.p. on top.

Interview: retrieval-augmented generation

Retrieval recall@20 is high, but the answers are still wrong. Where do you look?
Look past retrieval. Check context packing and order, and rerank to a small top-n (3–20). Find conflicting chunks. Run faithfulness checks on the generator. If the model uses distractors, train it RAFT-style with distractor documents.
How do you evaluate a RAG system?
Score retrieval and generation separately: recall@k on labeled question–chunk pairs, then faithfulness and answer correctness. Mix real queries with a synthetic set. Verify the set (closed-book leak test, humans check 5–10%). Report per slice.
When does agentic RAG not pay off?
On single-hop lookups with tight latency budgets. The loop costs 2–10x more LLM calls and more latency. Use it for multi-hop or ambiguous questions. Keep a one-shot pipeline for simple lookups.
How do you choose the chunk size?
Start at about 512 tokens with 100 overlap. Add a contextual header to each chunk. Tune on retrieval recall with a labeled set, not by eye. In Anthropic’s test, contextual embeddings + BM25 + reranking cut top-20 failures by 67%.
0top-20 failures (5.7% → 1.9%)
0tokens per chunk, 100 overlap
0more LLM calls for agentic RAG
0tokens: below this, use long context
05

The dataset is the spec: design it before you generate it

The model learns what the data covers. Gaps in coverage become gaps in capability, and missing provenance becomes legal risk.

Data Cards Croissant 1.1 ODCS v3.1.0 Pandera 0.34.1 Great Expectations 1.24.1 Evidently 0.7.23

In this chapter: document the data for people, machines and CI. Make the contract executable. Measure coverage against a taxonomy and name the gaps as slices. Then compare live data with a reference to find drift.

Document a dataset three ways: a card for people, Croissant for machines, a contract for CI

1 · Card: for people

  • Datasheets for Datasets (2018; CACM 2021): seven question groups, from motivation and composition to collection, labeling, uses, distribution and maintenance.
  • Data Cards (Google, FAccT 2022): 20+ deployed cards. Playbook: Ask, Inspect, Answer, Audit; 15 themes.
  • Hugging Face card: README.md + YAML (license, task_categories, tags). New in 2026: rl-environment.

2 · Croissant JSON-LD: for machines

  • JSON-LD on schema.org: metadata, resources, RecordSet / Field, semantics.
  • 1.1: machine-actionable provenance and usage policies. -RAI: labeling, safety, fairness.
  • Exported by Hugging Face, Kaggle, OpenML.
mlcroissant

import mlcroissant as mlc
ds = mlc.Dataset(
  "https://huggingface.co/api/"
  "datasets/fashion_mnist/croissant")
print(ds.metadata.to_json()["name"])

3 · Contract: for CI

Schema, semantics, SLAs and a change policy as code. CI runs it on every batch and stops the build when it fails. Pandera, Great Expectations, Soda, dbt, ODCS (next slide).

From prose to machine-checked metadata

2018Datasheets 2019Model Cards 2022Data Cards 2024Croissant, -RAI RL envHub tag 2026Croissant 1.1
0text datasets audited by the Data Provenance Initiative
0license omission on popular hosting sites (error rates 50%+)

Training, validation and testing data must be “relevant, sufficiently representative, and to the best extent possible, free of errors and complete”.

Article 10 also asks for documented design choices, data origin, preparation steps and bias examination. A Data Card maps onto these items.

A data contract makes schema, semantics and SLAs executable in CI

1 · Schema

Fields, types, nullability, keys.

2 · Semantics

Allowed values, ranges, formats, units, meaning: “amount is gross EUR incl. VAT”.

3 · SLAs and quality

Freshness, completeness, volume, uniqueness, an owner.

4 · Change policy

Versioned in git. CI finds breaking changes.

sft_contract.py · Pandera 0.34.1

import pandas as pd
import pandera.pandas as pa       # not: import pandera

class SFTRow(pa.DataFrameModel):  # contract for an SFT shard
    prompt: str   = pa.Field(str_length={"min_value": 1})
    response: str = pa.Field(str_length={"min_value": 1})
    lang: str     = pa.Field(isin=["en", "de", "fr"])
    quality: float = pa.Field(ge=0.0, le=1.0)

    class Config:
        strict = True             # no unexpected columns

SFTRow.validate(df, lazy=True)    # collect all failures
Synthetic generatorLLM writes rows
Contract testSFTRow.validate
TrainSFT job
response: ""
✕ build blocked
ODCS v3.1.0 · datacontract-cli

$ datacontract lint odcs.yaml
$ datacontract test odcs.yaml       # schema + quality on the live source
$ datacontract breaking v1.odcs.yaml v2.odcs.yaml
# v2 removes a column -> breaking change -> the CI job fails
NeedBest fit
Validate frames in Python training codePandera 0.34.1
Expectation classes with severity, checkpointsGreat Expectations 1.24.1
YAML contract on a table, soda contract verifySoda Core 4
SQL models: enforced: true, breaking-change gatedbt model contracts
Vendor-neutral spec, executable SLAsODCS v3.1.0 + datacontract-cli

Measure coverage against a taxonomy, and turn every gap into a named slice

Taxonomy = domain × task × skill × difficulty × language × format × risk category. Tag every row with its leaf ID.

Design the tree first, then generate per leaf

GLAN (2024): a taxonomy is the only input; to add a skill, insert a node. #InsTag (2023): 6.6K open-set tags. TagLM, tuned on 6K tag-selected samples, beats far larger SFT sets on MT-Bench.

Also measure embedding coverage (production clusters with training mass), nearest-neighbour distance from production queries, and diversity (tags, distinct n-grams, self-BLEU).

slices/shipping_customs.yaml (illustrative)

slice: shipping_customs
predicate: "domain == 'shipping' and task == 'customs'"
min_eval_n: 200
metric: [task_success, groundedness]
gate: "task_success >= overall - 0.03"
owner: support-ml@company
Find the slices you did not declare. Slice Finder (ICDE 2019) tests for large, interpretable failing slices. Domino (ICLR 2022) finds 36% of 1,235 planted slice settings (+12 pp) and names 35% exactly. Promote recurring failing clusters to declared slices.

Drift tests compare today’s data with a reference, and the right statistic depends on type and sample size

Evidently 0.7.23 defaultn ≤ 1000n > 1000
Numericalks (p < 0.05)wasserstein (≥ 0.1)
Categoricalchisquare (z if binary)jensenshannon (≥ 0.1)
Textdomain classifier (95th pct)domain classifier (ROC AUC 0.55)

Dataset drift: the share of drifted columns ≥ drift_share (default 0.5). Embedding drift (legacy API): model AUC 0.55, ratio 0.2, distance 0.2, mmd.

LLM and RAG data: test descriptors, not raw text: length, language, toxicity, taxonomy leaf, retrieval-hit rate, refusal rate. Embedding drift between the RAG corpus and live queries shows topics the index does not cover. Compare SFT data with production prompts (skew) and week N with week N−1 (drift). whylogs 1.6.4 keeps mergeable profiles; WhyLabs’ hosted platform was discontinued, so keep profiles in open formats.
StatisticDefinitionDefault
PSIΣ (ai − ei) · ln(ai / ei), 10 bins. Convention: < 0.1 stable, 0.1–0.25 moderate, > 0.25 major0.1
KSD = sup |Fref(x) − Fcur(x)|, gives a p-value. Numeric onlyp < 0.05
Jensen-ShannonDistance: symmetric and bounded0.1
WassersteinEarth mover’s distance ÷ reference std0.1
Large n: p-values flag tiny shifts. Use distances when n > 1000.
drift.py · Evidently 0.7.23

from sklearn import datasets
from evidently import Report
from evidently.presets import DataDriftPreset
from evidently.metrics import ValueDrift
iris = datasets.load_iris(as_frame=True).frame
report = Report([
    DataDriftPreset(method="psi", drift_share=0.7),
    ValueDrift(column="sepal length (cm)", method="ks"),
], include_tests=True)
my_eval = report.run(iris.iloc[:60], iris.iloc[60:])  # cur, ref
my_eval.save_html("drift.html")

Interview: dataset design and drift

“We have 50k SFT rows, ready to train.” What do you ask?
coverage provenance
Which taxonomy leaves do the rows cover, and how many examples does each leaf have? What is the provenance and license of each subset? What is the synthetic share, and which generator made it? Which contract validates each shard? Which slices and min n does the eval split have? Is it decontaminated against the eval sets?
Your KS test flags drift on every column, every day. Why, and what do you do?
statistics
At a large n, a p-value test flags tiny shifts as significant. Use distances with thresholds: Wasserstein, Jensen-Shannon or PSI (Evidently switches above n = 1000). Monitor the descriptors that matter, and tie each alert to a slice metric that drops.
How do you find failing slices that you did not think of?
discovery
Cluster failing production traffic and its embeddings. Use Slice Finder or Domino-style discovery; Domino finds 36% of 1,235 planted slice settings, so treat it as a help, not a proof. Promote each recurring cluster to a declared slice with a predicate, a min n, an owner and a gate.
What does EU AI Act Article 10 ask of training data?
governance
Governance of the design choices, the data origin, the preparation steps such as labeling and cleaning, the assumptions, and a bias examination. The data must be “relevant, sufficiently representative, and to the best extent possible, free of errors and complete”. A Data Card maps onto these items.
06

Labels are measurements: check their agreement, their noise and their splits

Test sets contain errors, annotators disagree, and leaked splits inflate scores. Measure each one before you trust a number.

design›generate›select›curate›label›denoise›quality card
Krippendorff α LLM-as-annotator cleanlab 2.9.0 BADGE GroupKFold MinHash LSH GSM1k
0

2,916 label errors in the ImageNet validation set (Northcutt et al. 2021). Confident learning flagged them. Humans confirmed about half of the candidates.

Measure agreement before you scale labeling, and accept an LLM only when it agrees like a human

Cohen's κ: two raters, corrected for chance

κ = (po − pe) / (1 − pe)
po
5/7 = 0.714
pe
15/49 = 0.306
κ = (0.714 − 0.306) / (1 − 0.306) = 0.588

po is the observed agreement. pe comes from the label shares of each rater. The quadratic-weighted κ on the same data is 0.800, because it gives partial credit on ordinal scales. Kappa paradox: with skewed classes pe is high, so κ is low while raw agreement is high. Report both.

i1i2i3i4 R1R2R3 012 0120 110 nannan

Krippendorff's α = 1 − Do / De

Observed over expected disagreement. It accepts any number of raters, missing ratings (grey cells), and nominal to ratio levels. Use it for crowd and LLM + human panels. Fleiss' κ needs a fixed number of raters per item.

Loop: guidelines → pilot 50–200 items with 3+ raters → α per label → adjudicate → revise → repeat until α ≥ 0.8 → scale with 5–10% overlap items. Low agreement can be signal on subjective tasks: keep soft or per-annotator labels.

Route each LLM label by its confidence

Gold set200–500 items
LLM labelertemp 0, fixed labels
Confidence gateseveral judges
AcceptLLM label
Human reviewdisagreements
cleanlab auditmerged labels
0tasks where zero-shot ChatGPT beat MTurk (Gilardi, PNAS 2023, 2,382 tweets)
0per annotation or less, “about twenty times cheaper than MTurk”
0GPT-4 judge agreement with humans (Zheng et al. 2023)
agreement.py · sklearn 1.9.x, statsmodels, krippendorff 0.9.0

import numpy as np, krippendorff
from sklearn.metrics import cohen_kappa_score
from statsmodels.stats.inter_rater import aggregate_raters, fleiss_kappa

a = [0, 1, 1, 2, 0, 2, 1]
b = [0, 1, 2, 2, 0, 2, 0]
cohen_kappa_score(a, b)                       # nominal: 0.588
cohen_kappa_score(a, b, weights="quadratic")  # ordinal: 0.800

ratings = np.array([[0, 0, 1], [1, 1, 1], [2, 2, 1], [0, 0, 0]])
table, _ = aggregate_raters(ratings)   # items x raters -> counts
fleiss_kappa(table, method="fleiss")

# Krippendorff: raters x items, np.nan = missing rating
rel = np.array([[0, 1, 2, np.nan], [0, 1, 2, 0], [1, 1, np.nan, 0]])
krippendorff.alpha(reliability_data=rel, level_of_measurement="nominal")

Landis and Koch (1977) bands for κ. They are widely cited, but the cut points are arbitrary.

0.41–0.60 moderate0.61–0.80 substantial0.81+ almost perfect

Hybrid LLM + human labeling recipe

  1. Humans label a gold set of 200–500 items with high agreement.
  2. The LLM labels the same items. Compare LLM–human α with human–human α. Accept the LLM only if the two are close.
  3. Reduce known biases: swap the answer order, use a fixed label set, set temperature 0, use several judges and vote.
  4. Send low-confidence and disagreeing items to humans.
  5. Run cleanlab on the merged labels. Re-audit 2–5% of the LLM labels each week.

Argilla 2.8.0 and Label Studio 1.23.2 show LLM pre-annotations as suggestions. A human accepts or corrects each one.

“LLMs should be carefully validated against human judgments before being used as evaluators.”

Confident learning finds label errors from out-of-sample probabilities

0average label errors across 10 test sets (vision, text, audio)
0errors in ImageNet validation, about 6%
0of flagged candidates confirmed by humans (arXiv / NeurIPS)

On corrected ImageNet labels, ResNet-18 outperforms ResNet-50 if the share of originally mislabeled test examples rises by 6%. The model ranking flips.

  1. Get out-of-sample probabilities with K-fold cross_val_predict.
  2. Per class j, set the threshold tj = mean self-confidence of the examples labeled j.
  3. Count x (labeled i) into cell (i, j) if p̂(j|x) ≥ tj. This is the confident joint.
  4. Off-diagonal cells hold likely errors. Rank them by self-confidence.
  5. Prune or relabel them, then retrain.
find_errors.py · cleanlab 2.9.0, adapted from the in-depth overview

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_predict
from cleanlab.filter import find_label_issues
from cleanlab.classification import CleanLearning
from cleanlab import Datalab
model = LogisticRegression(max_iter=1000)
# 1) out-of-sample probabilities (critical!)
pred_probs = cross_val_predict(model, X, labels, cv=5,
                               method="predict_proba")
# 2) ranked indices of likely label errors
issues = find_label_issues(
    labels=labels, pred_probs=pred_probs,
    filter_by="both", return_indices_ranked_by="self_confidence")
# 3) finds the issues, then trains on the cleaned data
cl = CleanLearning(model).fit(X, labels)   # cl.get_label_issues()
# 4) Datalab: label issues, outliers, near-duplicates, non-IID
lab = Datalab({"X": X, "y": labels}, label_name="y")
lab.find_issues(pred_probs=pred_probs, features=X)
lab.report()
Out-of-sample. In-sample probabilities memorize the noise, so a wrong label looks correct to the model.
The pred_probs columns must be in the order of the class labels. A wrong column order makes every label look wrong.

Training dynamics: a data map

Record p(true label | x) at the end of each epoch. Confidence = the mean over epochs. Variability = the standard deviation.

regionsignatureuse
easy-to-learnhigh confidence, low variabilitylargest region; helps optimization
ambiguoushigh variabilityhelps out-of-distribution generalization; train on about 33%
hard-to-learnlow confidence, low variabilityoften label errors: send to the relabel queue
Use both signals. cleanlab gives a ranked list from one set of probabilities. The data map adds the history over epochs.

Active learning spends the labeling budget on uncertain and diverse examples

Loop: seed → train → score the pool → select a batch of B → a human or an LLM labels it → add → repeat until the budget ends.

Uncertainty only

Least confidence 1 − max p, margin p1 − p2, or entropy. It is cheap. But near points get the same score, so one batch holds redundant examples.

Uncertainty + diversity

Core-set (Sener and Savarese, ICLR 2018): greedy k-center in embedding space. BADGE (Ash et al., ICLR 2020): embed each point as the last-layer loss gradient, with the predicted label as a hypothetical label. The norm gives uncertainty, the direction gives diversity. Then run k-means++ seeding. It “balances uncertainty and diversity” with no hand-tuned trade-off.

LLM era and open problems

Uncertainty: LLM disagreement or self-consistency. Diversity: sentence embeddings. Route to an LLM annotator or a human. Problems: cold start; sampling bias (test on a separate random sample); small gains over random with strong embeddings.

Weak supervision: many noisy voters give one probabilistic label

Labeling functions (rules, regexes, LLM prompts) vote or abstain. The LabelModel learns the accuracy of each function from their agreements, without ground truth. An end model then trains on the probabilistic labels.

0faster model building by experts
0vs 7 hours of hand labeling
0gap to large hand-curated training sets
snorkel 0.10.0: no release since Feb 2024; 3 open CVEs (2026) Paper Snorkel (PVLDB 2017) Repo snorkel-tutorials

Split by entity and by time, or the test set leaks the answer

Entity

Several scans per patient. A random split puts one patient in both folds.

random K-fold optimistic ↑ traintest P1 P1 P2 P3 P3 P4 P2 P4 GroupKFold honest traintest P1 P1 P2 P2 P3 P3 P4 P4 no sharedpatient

Time

Train on the past, test on the future. Each training set is a superset of the one before. A gap stops boundary bleed.

train test gap=7 fold 1 train test fold 2 train test fold 3 rows sorted by time →

Preprocessing

A scaler, imputer, SMOTE or vocabulary fit on all rows sees the test set. Fit each step inside the fold.

fit before the split optimistic ↑ scaler.fit(all X) train test leak Pipeline, per fold fit(train) model test transformonly honest
leakage typeexamplefix
entity / groupsame patient, user, speaker or repo in both splitsGroupKFold
temporaltrain on the future; "next 30 days" featuresTimeSeriesSplit
preprocessingscaler, imputer, SMOTE fit before the splitPipeline
near-duplicatethe same or a paraphrased item in both splitsMinHash, embeddings
targeta proxy for the label, e.g. “treatment given”feature audit
contaminationthe test set is in the pretraining corpusfresh, dated sets
0papers with leakage errors (Kapoor and Narayanan)
0fields affected
0leakage types in their taxonomy, plus model info sheets
Civil-war prediction, after the leakage fix: “complex ML models don't perform substantively better than decades-old LR models.”
Paper Leakage and the Reproducibility Crisis in ML-based Science
splits.py · scikit-learn 1.9.x

from sklearn.model_selection import GroupKFold, StratifiedGroupKFold
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
pipe = make_pipeline(StandardScaler(),                # fit per fold
                     LogisticRegression(max_iter=1000))
# entity: no patient_id in both train and test
cross_val_score(pipe, X, y, groups=patient_id,
                cv=GroupKFold(n_splits=5))
cross_val_score(pipe, X, y, groups=patient_id,      # + class balance
                cv=StratifiedGroupKFold(n_splits=5))
# temporal: rows sorted by time; the gap stops boundary bleed
tscv = TimeSeriesSplit(n_splits=5, gap=7)
cross_val_score(pipe, X_sorted, y_sorted, cv=tscv)
Random K-fold on multi-scan medical images measures memorized patients, not diagnosis.

scikit-learn: i.i.d. data “rarely holds in practice”; with group structure “it is safer to use group-wise cross-validation”. For LLM and RAG evals, hold out by time: test only on documents and questions created after the training cutoff.

Docs scikit-learn: Cross-validation: evaluating estimator performance

Deduplicate across splits with MinHash, and test LLMs on data newer than their cutoff

0copies of one 61-word sentence in C4
0less memorized text emitted after dedup
0of standard validation sets overlap the training set
  1. Normalize the text. Shingle it into word 5-grams.
  2. MinHash the shingle set into a signature (128 permutations). The chance that two signatures match at one position equals their Jaccard similarity s.
  3. Split the signature into b bands of r rows. Two documents are candidates if any band matches:
    P = 1 − (1 − sr)b · threshold ≈ (1/b)1/r
  4. Verify the Jaccard of each candidate pair. Find the connected components. Keep one document per cluster.
dedup.py · datasketch, pattern from the LSH docs

from datasketch import MinHash, MinHashLSH
def mh(text, num_perm=128, n=5):
    toks = text.lower().split()
    m = MinHash(num_perm=num_perm)
    for sh in {" ".join(toks[i:i + n])
               for i in range(max(1, len(toks) - n + 1))}:
        m.update(sh.encode("utf8"))
    return m

lsh = MinHashLSH(threshold=0.8, num_perm=128)
keep = []
for doc_id, text in corpus.items():
    m = mh(text)
    if lsh.query(m):          # near-dup of a kept doc
        continue
    lsh.insert(doc_id, m); keep.append(doc_id)
Cross-split dedup: build the index on the test set, query every training document, and drop the matches. For paraphrases, use embedding cosine above about 0.9 (FAISS or HNSW).
Docs datasketch: MinHash LSH

Is the benchmark in the training data?

  • Measure per benchmark (Sainz et al., EMNLP 2023 Findings). Contamination can lead to “incorrect scientific conclusions”.
  • GSM1k: a fresh clone of GSM8k with matched difficulty. Accuracy drops by up to 8% for some families. The chance to generate GSM8k items correlates with the gap (r² = 0.36).
  • Min-K% Prob: the mean log-prob of the k% least likely tokens. It needs no reference model; +7.4% on WikiMIA.
  • LiveCodeBench tests each model on problems released after its cutoff. BIG-bench embeds a canary GUID so corpus builders can filter it.

Checklist for SFT, RL and RAG projects

  • Decontaminate SFT and RL sets against every eval set: n-gram + MinHash + embeddings.
  • Keep a private, time-stamped holdout. Report the training cutoff with each score.
  • Check that RL reward problems do not overlap benchmark problems.
  • Write the method, thresholds and number removed in the quality card.

Interview: labels, noise and leakage

Raw agreement is 92% but Cohen's κ is 0.3. Who is right?
Both numbers are correct. With a skewed class prior, the chance agreement pe is high, so κ = (po − pe)/(1 − pe) is low. This is the kappa paradox. Report both numbers, look at the agreement on the minority class, and use Krippendorff's α if raters skip items.
Your medical imaging model scores 0.97 AUC. What do you check first?
Patient-level leakage. Several scans per patient can sit in both folds. Re-run with GroupKFold on the patient id. Fit all preprocessing inside a Pipeline. Then search for near-duplicate images across the splits.
How do you use an LLM as a labeler safely?
Experts label a gold set of 200–500 items. Compare LLM–human α with human–human α. Swap answer orders, fix the label set, set temperature 0, and use several judges. Send low-confidence items to humans. Audit the merged labels with cleanlab. Re-audit 2–5% of the LLM labels each week.
How do you show that your RL gains are not contamination?
Decontaminate the reward prompts against every eval with n-grams, MinHash and embeddings. Score a private holdout dated after the training cutoff. Test on a fresh clone such as GSM1k, or on LiveCodeBench problems released after the cutoff. Write the method and the number removed in the quality card.
07
raw web tokens quality classifier about 10% kept

Modern LLM data is filtered hard, synthesized at scale and verified before use

From 2023 to 2026 most gains came from model-based filtering and verified synthetic data, not from more raw tokens.

FineWeb-Edu DCLM Nemotron-CC Magpie Persona Hub OpenThoughts distilabel RAGAS

PyPI, Oct 2026: datatrove 0.10.1 · nemo-curator 1.4.0 · ragas 0.4.3 · distilabel 1.5.3 (no PyPI release since Jan 2025)

Model-based filters throw away about 90% of the web and still win

DCLM: one fastText classifier beat every other filter

Positives: OpenHermes 2.5 and top r/ExplainLikeImFive posts. Keep the top 10%.

Core score: fastText 30.2 · perplexity 29.0 · AskLLM 28.6 · RefinedWeb 27.5

DCLM-7B gets 64% MMLU after 2.6T tokens. Llama 3 8B gets 66% with 6.6x more compute. “model-based filtering is key”.

Distill an LLM judge into a tiny classifier

A large LLM labels about 0.5M documents on a rubric. A small classifier learns the labels. Then the classifier runs over trillions of tokens at a low cost.

Nemotron-CC: filter less, rephrase more

6.3T tokens from a classifier ensemble and synthetic rephrasing. Its 8B model gets +5 MMLU over Llama 3.1 8B. The authors say that a 90% cut hurts long training horizons.

fineweb_edu_score.py · from the model card

from transformers import AutoTokenizer
from transformers import AutoModelForSequenceClassification
tokenizer = AutoTokenizer.from_pretrained(
    "HuggingFaceTB/fineweb-edu-classifier")
model = AutoModelForSequenceClassification.from_pretrained(
    "HuggingFaceTB/fineweb-edu-classifier")
text = "This is a test sentence."
inputs = tokenizer(text, return_tensors="pt",
                   padding="longest", truncation=True)
outputs = model(**inputs)
logits = outputs.logits.squeeze(-1).float().detach().numpy()
score = logits.item()
result = {"text": text, "score": score,
          "int_score": int(round(max(0, min(score, 5))))}
keep = result["int_score"] >= 3   # the card advice
Too much cleaning removes good data. The FineWeb-Edu card warns that the classifier may “overfit to academic-looking content”. Give every datatrove filter an exclusion_writer. Put each removal rate on the quality card.

Four generators make instruction data: seeds, evolution, empty templates and personas

1 · Self-Instruct

Paper ACL 2023
seed task seed task seed task
→
new task new task new task new task new task near-dup

The LLM writes new tasks from seed tasks. Drop near-duplicates. +33% absolute on Super-NaturalInstructions over vanilla GPT-3.

2 · Evol-Instruct

Paper ICLR 2024
instruction harder harder again new topic in-depth in-breadth

Rewrite each instruction “step by step into more complex instructions”. WizardLM: >90% of ChatGPT capacity on 17 of 29 skills (GPT-4 judge).

<|start_header_id|>user<|end_header_id|>
▌
example · the model writes this turn

Send the template only up to the user slot. The model writes the query. 4M made, 300K kept; Base + Magpie SFT is close to Llama-3-8B-Instruct on some tasks.

4 · Persona Hub

Repo persona-hub
a nurse a chess coach a bus driver
× “math problem” → 3 tasks

1B personas; 370M “elite personas” (2025-02-18). Tulu 3 SFT, 939,344 rows: persona MATH 149,960 · GSM 49,980 · Python 34,999 · IF 29,980.

magpie.py · distilabel components gallery

from distilabel.models import TransformersLLM
from distilabel.steps.tasks import MagpieGenerator

generator = MagpieGenerator(
    llm=TransformersLLM(
        model="meta-llama/Meta-Llama-3-8B-Instruct",
        magpie_pre_query_template="llama3",
        generation_kwargs={"temperature": 1.0,
                           "max_new_tokens": 256},
        device="mps",
    ),
    only_instruction=True,
    num_rows=5,
)
generator.load()
result = next(generator.process())
OpenThoughts, 1,000+ ablations: more teacher answers per question grow a source “by at least 16x”. The “top 1 or 2” sources beat many. “QwQ-32B is a stronger teacher than DeepSeek-R1”. OpenThinker3-7B: AIME25 53%, GPQA-D 54%.

Generate many candidates, verify them from cheap to costly, and keep only what passes

  1. Sample K candidates per prompt from seeds, personas or Magpie. Llama 3 uses K “typically between 10 and 30”.
  2. Static checks first: format, parse and lint. They cost almost nothing, so they run on every candidate.
  3. Execute: unit tests for code; exact match or a symbolic check for math. Llama 3: over 2.7M synthetic code examples; about 1M dialogues used execution feedback.
  4. Judge, then critique: a reward model or a judge rubric, then a constitutional critique. Keep best-of-K (SFT) or pass/fail pairs (DPO). Dedup and decontaminate.

Judges are biased. GPT-4 judges agree with humans “over 80%”, but they show position, verbosity and self-enhancement bias. Swap the answer order. Use rubrics and reference answers. Use a judge from another model family.

RLVR (Tulu 3) moves the same check into the RL reward: the reward is 1 only if the answer is verifiably correct.

reject_sample.py · illustrative pattern, standard library only

import subprocess, tempfile, textwrap

def passes_tests(solution: str, tests: str, timeout=5) -> bool:
    prog = solution + "\n\n" + textwrap.dedent(tests)
    with tempfile.NamedTemporaryFile("w", suffix=".py",
                                     delete=False) as f:
        f.write(prog)
    try:
        r = subprocess.run(["python", "-I", f.name],
                           capture_output=True, timeout=timeout)
        return r.returncode == 0
    except subprocess.TimeoutExpired:
        return False

def rejection_sample(prompt, tests, generate, k=16):
    cands = [generate(prompt, temperature=0.8) for _ in range(k)]
    good = [c for c in cands if passes_tests(c, tests)]
    # keep all passing (SFT) or pair passing/failing (DPO)
    return good
Run generated code in a sandbox: a container, no network. python -I and a timeout are not a sandbox. Llama 3 ran its tests in containers.

Synthetic data collapses a model when it replaces real data, not when it is anchored and added

Replace: training on model output causes “irreversible defects in the resulting models, where tails of the original content distribution disappear”.

Accumulate: “accumulating the successive generations of synthetic data alongside the original real data avoids model collapse”.

Did verification help?

SFT distillation from a strong teacher: “none gave significant performance improvements” (OpenThoughts).

RLVR rewards and code: verification is essential. Tulu 3 puts it in the reward; Llama 3 kept only code that passed all checks.

Anchored synthesis works: Phi-4 “substantially surpasses its teacher model on STEM-focused QA”.

Mitigations for the quality card

  • Keep a real-data anchor in every mix.
  • Flag provenance: source=synthetic.
  • Control diversity: personas, Magpie, temperature.
  • Dedup synthetic outputs against each other.
  • Track distinct-n and embedding spread.
  • Verify with programs, not self-judgment.

Collapse by curation: most labs now filter for “educational-looking” text. That risks one narrow style and lost tails.

Build RAG test sets from a knowledge graph, then verify them before they count as gold

1 · Documents become graph nodes

RAGAS 0.4.3 loads each document as a node of a KnowledgeGraph.

2 · Transforms add structure

default_transforms and apply_transforms add summaries, entities, embeddings and relationships. Docs example: 10 nodes become 48 nodes with 605 relationships. The graph gives scenarios. Each scenario gives a query.

3 · Synthesizers write the queries

Default mix: 50% single-hop specific, 25% multi-hop abstract, 25% multi-hop specific. Each row has four columns:

user_input reference_contexts reference synthesizer_name
0LLMContextRecall
0Faithfulness
0FactualCorrectness (docs example)
make_testset.py · condensed from the RAGAS docs

import openai
from langchain_community.document_loaders import DirectoryLoader
from langchain_openai import ChatOpenAI
from ragas.embeddings import OpenAIEmbeddings
from ragas.llms import LangchainLLMWrapper
from ragas.testset import TestsetGenerator
docs = DirectoryLoader("Sample_Docs_Markdown/", glob="**/*.md").load()
llm = LangchainLLMWrapper(ChatOpenAI(model="gpt-4o"))
embeddings = OpenAIEmbeddings(client=openai.OpenAI())
generator = TestsetGenerator(llm=llm, embedding_model=embeddings)
dataset = generator.generate_with_langchain_docs(docs, testset_size=10)
dataset.to_pandas()
# user_input, reference_contexts, reference, synthesizer_name
  1. Check that each reference is supported by its contexts (faithfulness judge).
  2. Drop questions that the model answers without retrieval. closed-book leak test
  3. Have humans spot-check 5–10% of the items.
  4. Rebalance single-hop vs multi-hop to match real, PII-scrubbed query logs.
  5. Version the set. Never train on it.

Interview: LLM datasets and synthetic data

Why might verifying distilled SFT data not improve the student?
With a strong teacher, most traces are already good. Wrong but well-formed traces still teach format and reasoning style. OpenThoughts found no significant gain from verification. Spend the verification budget on RL rewards and code, where a wrong signal does damage.
How do you stop a synthetic data loop from collapsing the model?
Keep the real data and add to it; never replace it. Flag provenance. Control diversity with personas and sampling. Dedup synthetic outputs against each other. Verify with programs. Track distinct-n and tail coverage on the card.
Design a quality filter for 10T web tokens.
A large LLM labels about 0.5M pages on a rubric. Train a small classifier (fastText, or an embedding + linear head). Set the threshold by F1 on a held-out split (FineWeb-Edu: ≥ 3, F1 82%). Log the removal rate per filter. Check the classifier for bias.
Dedup globally or per snapshot?
Measure; do not assume. FineWeb found that per-snapshot dedup (20T tokens) scored better than global dedup (about 4T tokens). Global dedup removed too much good repeated content and kept the low-quality tail.
0F1 of the FineWeb-Edu classifier at score ≥ 3
0fewer tokens to 33.6% MMLU (38B vs ~300B)
0source growth from many teacher answers per question
0Llama 3 code solutions first wrong, then self-corrected
08

“Fundamentally, evaluations are experiments.”

An eval is an experiment: design it like a dataset and report it with error bars

The eval set decides what ships, so it needs more care than the training set.

error analysis golden sets slice metrics paired bootstrap LLM-as-judge calibration cost-sensitive thresholds
DESIGNGENERATESELECT CURATELABELDENOISEQUALITY CARD error analysis on failing traces → new failure modes → new slices

Start with error analysis on real traces, then freeze a versioned golden set

“Error analysis is the most important activity in evals.”

  1. Read at least 100 real traces. Annotate at least 30 yourself before you trust the suggestions of a model.
  2. Open coding: write one free-text note on each failing trace.
  3. Axial coding: cluster the notes into a failure taxonomy. “Write evaluators for errors you discover, not errors you imagine.”
  4. Each failure mode becomes a slice with a binary pass/fail evaluator. “Binary evaluations force clearer thinking.” Use code assertions first (regex, JSON schema, exact match). Use an LLM judge only for subjective criteria.

One benevolent dictator owns the quality definition. Align the judge to that expert (TPR / TNR). Gate CI on each slice. Watch criteria drift: “grading outputs helps users define criteria” (Shankar et al., arXiv 2404.12272). Evals before code? The FAQ says “Generally no”: the loop is error-analysis-first.

Golden-set rules

propertyrule
Source mixProduction traces + targeted synthetic edge cases from a dimensions × values grid. A human validates each synthetic item.
Size100–500 to start. n = 400, p = 0.8 → 95% CI ≈ ±3.9 pts.
SlicesIntent, language, user tier, length, failure mode, safety.
Decontam.n-gram + embedding dedup against SFT and training data. Keep a private holdout.
VersionsImmutable (golden_v3.2), changelog, one owner.
Splitsdev (iterate) / test (report) / judge-calibration (expert labels).
RefreshAdd new failure modes monthly. Retire saturated items.

Report every score per slice with n and a confidence interval, and compare models on paired items

Rules for a score you can trust

  • Report n and a 95% CI with every score. SE ≈ √(p(1−p)/n).
  • Use Wilson intervals for small n, or for p near 0 or 1: proportion_confint(method="wilson").
  • At n = 500 and p ≈ 0.8, each CI is about ±3.4 pts. The two CIs overlap. Use clustered SEs when questions share a passage or a conversation.
  • Report each slice with its n and CI. The aggregate hides slice regressions (Simpson’s paradox).
  • Compare models with paired differences on the same items. Pairing removes item-difficulty variance. Resample each answer k times to cut within-item variance.

Do a power analysis before you build the set

At α = 0.05 and power 0.8, the minimum detectable effect is ≈ 2.8 × SE(Δ). Unpaired, p = 0.8, n = 500 per arm: SE(Δ) ≈ 0.025, so you detect only ≈ 7-point deltas. Pairing shrinks this.

paired_bootstrap.py · from 07-eval-design §5

# Paired bootstrap on the same golden set
import numpy as np
rng = np.random.default_rng(0)
a = np.load("scores_A.npy")   # per-item scores, same order
b = np.load("scores_B.npy")
d = b - a
boots = np.array([
    rng.choice(d, size=len(d), replace=True).mean()
    for _ in range(10_000)])
lo, hi = np.percentile(boots, [2.5, 97.5])
print(f"delta={d.mean():+.3f}  95% CI [{lo:+.3f}, {hi:+.3f}]  "
      f"P(delta<=0)={np.mean(boots<=0):.3f}")

SciPy: scipy.stats.bootstrap((d,), np.mean, confidence_level=0.95, method="BCa"). For clustered items, resample the clusters (passages, conversations), not the items.

Many SOTA deltas sit inside the noise. Most reports still omit CIs and clustering.

Validate the LLM judge against expert labels, then correct its pass rate

GPT-4 as a judge reaches “over 80% agreement, the same level of agreement between humans.”

Zheng et al. also find position, verbosity and self-enhancement bias, and limited reasoning.

Managed: Vertex Gen AI evaluation service. The default GENERAL_QUALITY is an adaptive rubric metric: rubrics per prompt, then judged. AutoSxS runs side-by-side checks for migrations.
Pairwise: Chatbot Arena (240K+ votes) fits Bradley-Terry, P(i beats j) = σ(βi − βj), with bootstrap CIs and style control.

Position bias

In pairwise grading, the judge prefers the answer in one fixed position.

Fix: judge both orders

Swap A and B. Count a win only if it holds in both orders. Else score a tie.

Verbosity bias

The judge gives longer answers higher scores.

Fix: control length

Use length-controlled scoring (Length-Controlled AlpacaEval, arXiv 2404.04475). Write “do not reward length” in the rubric.

Self-preference

The judge prefers outputs of its own model family (self-enhancement).

Fix: another family

Use a judge from a different model family than the candidate. The bias is not fully solved.

Vague 1–5 scale

A Likert score does not say what “good” means, so labels vary.

Fix: binary per criterion

One pass/fail judge per failure mode. G-Eval (arXiv 2303.16634) adds a CoT rubric; DeepEval GEval uses it.

Also check

Rubric drift → freeze and version the judge prompt like code. Never validated → expert calibration split with TPR, TNR, κ. Reference leakage → grade against a reference only where one exists.

Correct the pass rate (Rogan-Gladen)

θ = (p_obs + TNR − 1) / (TPR + TNR − 1)

Bootstrap the CI over the calibration items and the eval items together.

judge_agreement.py · from 07-eval-design §8

from sklearn.metrics import cohen_kappa_score, confusion_matrix
# human, judge: 0/1 labels on the calibration split
k = cohen_kappa_score(human, judge)
tn, fp, fn, tp = confusion_matrix(
    human, judge, labels=[0, 1]).ravel()
tpr, tnr = tp/(tp+fn), tn/(tn+fp)
print(f"kappa={k:.2f}  TPR={tpr:.2f}  TNR={tnr:.2f}")
# p_obs: the judge pass rate on the eval set
theta = (p_obs + tnr - 1) / (tpr + tnr - 1)  # Rogan-Gladen

Calibrate the scores, then pick the threshold with the cost of each error

“Modern neural networks, unlike those from a decade ago, are poorly calibrated.”

Expected calibration error (ECE)

ECE = Σb (|Bb| / n) · |acc(Bb) − conf(Bb)|

Use 10 or 15 equal-width bins. ECE depends on the binning, so also report the Brier score or log loss, and show a reliability diagram.

Fix it on a held-out split

Temperature scaling, a one-parameter form of Platt scaling, “is surprisingly effective”. Or use CalibratedClassifierCV(method="sigmoid") or "isotonic".

LLM uses: guardrail and classifier heads; verbalized or token-probability confidence for abstention; judge confidence to send hard items to humans.

A score is not a decision

With calibrated p, flag when

p > t* = CFP / (CFP + CFN)

A miss costs 20× a false alarm → t* = 1/21 ≈ 0.048.

EC(t) = π·FNR(t)·CFN + (1 − π)·FPR(t)·CFP

π is the prevalence in production. On the ROC curve, the optimum touches an iso-cost line of slope CFP(1 − π) / (CFN·π).

Golden sets are enriched with positives. Re-weight to production prevalence before you pick t. Check t per slice too: one global t can be unfair across slices.
pick_threshold.py · from 07-eval-design §7

import numpy as np
from sklearn.metrics import roc_curve
fpr, tpr, thr = roc_curve(y_true, scores)
pi, C_fp, C_fn = 0.02, 1.0, 20.0  # prod prevalence, costs
ec = pi*(1-tpr)*C_fn + (1-pi)*fpr*C_fp
best = np.argmin(ec)
print(f"threshold={thr[best]:.3f} TPR={tpr[best]:.2f} "
      f"FPR={fpr[best]:.3f} EC={ec[best]:.4f}")
Chapter 1 used the same cost logic: OpenAI’s example (+$20 per success, −$40 per escalation, −$1,000 churn in 5% of failures) gives a break-even accuracy of 81.5%.

Interview: AI eval design

Model B beats model A by 1.1 points on 500 items. Do you ship?
Not yet. Compute a paired bootstrap CI of the per-item delta. Unpaired at n = 500, you can detect only about 7 points. Check every slice for regressions. If items come in groups, cluster by conversation or passage.
How do you trust an LLM judge?
Experts label a calibration split. Measure TPR, TNR and κ of the judge against them. Mitigate position, length and self-preference bias. Freeze and version the judge prompt. Correct the pass rate with Rogan-Gladen.
Pick the threshold for a PII filter where a miss costs 20× a false alarm.
Calibrate first. Then t* = 1/(1 + 20) ≈ 0.048. Compute the expected cost at the production prevalence, not at the enriched eval prevalence. Check the threshold per slice.
What goes on an eval quality card?
Set version and owner. Provenance mix. n, slices and the min n per slice. Decontamination method. Label agreement and noise audit. Judge model, prompt version, TPR / TNR. Headline score with CI, worst slice, calibration (ECE), operating point, known gaps.
0real traces to read before you write evaluators
095% CI at n = 400, p = 0.8
0detectable delta, unpaired, n = 500 per arm
0t* when a miss costs 20× a false alarm
09

Run the whole lifecycle as one workflow, then face the open problems

Design, generate, select, curate, label, denoise and document every release. Then measure what still breaks: reward hacking, contamination, collapse, licensing and platform risk.

workflow quality card Tulu 3 OLMo 3 SmolLM3 open challenges

“The model flow is the full lifecycle of an LM: every stage, checkpoint, dataset, and dependency.”

One workflow runs design, generate, select, curate, label, denoise and the quality card for every release

1 Design

contract lint

Write the taxonomy, slices and contract first: ODCS v3.1.0 with datacontract-cli, or Pandera 0.34.1 in Python.

2 Generate

provenance per row

Mix logs with synthetic rows: distilabel Magpie, personas, teacher distillation (TRL DistillationTrainer).

3 Select

removal rate logged

Score with a FineWeb-Edu-style classifier. Target with DSIR or LESS: the LESS 5% often beats the full set.

4 Curate

split overlap = 0

MinHash + semantic dedup, then decontaminate. datatrove 0.10.1; NeMo Curator 1.4.0 GPU fuzzy dedup: 10.7 h → 0.65 h.

5 Label

α ≥ 0.8

Experts accept or fix LLM suggestions in Label Studio 1.23.2. Measure agreement before you scale.

6 Denoise

audit rate on card

Find label issues with cleanlab 2.9.0. Run verifiers and unit tests on every synthetic row.

7 Train + eval

no slice drop beyond CI

TRL v1.16.0, Unsloth or Vertex tuning. Inspect AI 0.3.283, lm-eval 0.4.13, DeepEval 4.3.1, promptfoo, Vertex client.evals.

8 Card + monitor

ships with release

Data Card + Croissant-RAI + eval card. Evidently 0.7.23 tests drift on descriptors.

A drift alert goes back to design: update the taxonomy, publish contract v2, add the new slice.
Tools stop. Argilla: maintenance only. distilabel: community-maintained. Snorkel: no release since 2024, 3 CVEs. HELM: maintenance mode since 2026-06-01. WhyLabs hosted: shut down. torchtune: wound down in 2025. Pin versions, keep open formats (ODCS, Croissant, JSONL) and keep recipes portable.

The quality card closes the loop: no number without n, CI, slice and version

2018 · people

Datasheets ask why the data exists, what is in it and how it was collected.

Paper Datasheets for Datasets
2019 · models

Model Cards: intended use, factors, metrics, evaluation data, quantitative analyses, caveats.

Paper Model Cards for Model Reporting
2022 · product

“documentation must be treated as a user-centric product in and of itself”

Paper Data Cards (FAccT 2022)
2025 · live trace

OlmoTrace traces OLMo 3 outputs back to training documents in real time.

Blog Ai2, Olmo 3
No number without n, CI, slice and version.
quality_card.yaml · example template (a synthesis for this course)

# eval card: the eval set is a dataset product
eval_set: support-bot-golden v3.2    # owner: domain expert, 2026-10
provenance: {prod_traces: 62%, synthetic: 38%}   # PII-scrubbed / human-validated
size: {n: 640, slices: 9, min_slice_n: 48, positive_rate: 4.1%}
decontamination: 13-gram + embedding (cos > 0.92) vs SFT set -> 11 removed
labels: {experts: 2, adjudication: true, cohen_kappa: 0.78, noise_audit: 3.1%}
judge: {model: "<model>@<version>", prompt: v7, TPR: 0.91, TNR: 0.88, n: 200}
headline: "pass 84.2% [81.3, 86.9]"        # bootstrap 95% CI
worst_slice: "refunds-ES 71% [58, 81]"
calibration: "ECE 0.04 (15 bins) after temperature scaling"
operating_point: {t: 0.05, C_FN_over_C_FP: 20, prod_prevalence: 2%}
known_gaps: [multi-turn > 8 turns, voice channel]
# data card: fields from FineWeb and Llama 3 practice
sources: Common Crawl snapshots, each with its date
filters: every filter with its removal rate
dedup: {level: per-snapshot, minhash: 5-gram, 14 buckets x 8 hashes}
classifier: {annotator: Llama-3-70B-Instruct, labels: 460k, F1: 0.82, keep: ">= 3"}
synthetic: {share: "...", generator_or_teacher: "..."}
verifiers: {types: [exec, unit tests], note: "about 20% of code self-corrected"}
decontamination_train: {method: n-gram, benchmarks_checked: [...]}
diversity: [distinct-n, embedding spread]
license: ODC-BY
known_biases: grade-school bias   # FineWeb-Edu card

Open 2025-26 recipes share one pipeline: distilled SFT, preference tuning, verifiable RL and merging

Raw web, PDF and code → filter, dedup, classifiers → pretrain → mid-train → SFT on decontaminated synthetic + curated data → DPO / APO → RLVR → merge → eval on dev and unseen splits → card + traceability.

OLMo 3 data funnel (Ai2, 2025-11-20; 3.1 on 2025-12-12)

Olmo 3-Think 32B

0MATH
0AIME24
0AIME25
0IFEval

OpenThoughts3 7B: +15.3 / +17.2 / +20.5 pts over R1-Distill-Qwen-7B on AIME25 / LiveCodeBench / GPQA-D.

Gemma 3: all models are trained with distillation, “making Gemma3-4B-IT competitive with Gemma2-27B-IT”. Gemma 4: E2B, E4B, 12B, 26B A4B MoE, 31B dense; 128K / 256K context; Apache 2.0 license link.

Phi-4 (14B): a recipe “centrally focused on data quality”, with synthetic data throughout training.

Open challenges: reward hacking, inflated RL gains, collapse, contamination, licensing and platform risk

RLVR sharpens; it does not widen

“the base models achieve a higher pass@k score when k is large”

RL wins at pass@1. Report pass@k for large k before you claim new reasoning.

Random rewards also “work”

GRPO on Qwen2.5-Math-7B: +21.4 pts on MATH-500 with random rewards, +29.1 with ground truth. Test other model families and decontaminated evals.

Reward hacking hides and spreads

GPT-4o monitored o3-mini CoT. Training against the monitor gave obfuscated reward hacking, so pay a “monitorability tax”. Anthropic: hacking coding tasks spread to ~12% sabotage and ~50% alignment-faking. Inoculation prompting fixed it: “Learning to cheat no longer generalized to other misaligned behaviors.”

saturation

Humanity's Last Exam: 2,500 questions, because “benchmarks are not keeping pace in difficulty”. Dynamic benchmarks have no standard criteria yet.

collapse

Recursive training makes the tails vanish (Nature 631, 2024). Classifier monoculture removes tails by curation.

licensing

Bartz v. Anthropic: lawful books were fair use (2025-06-23); pirated copies went to trial; US$1.5B settlement. Provenance decides the risk.

platform risk

OpenAI self-serve fine-tuning winds down (last jobs 2027-01-06). Fireworks: “Managed Training no longer accepts new RFT jobs”.

judge validity

Non-verifiable tasks need graders: rubric quality, position, verbosity and self-preference bias.

agent evals

Trajectory scoring, nondeterminism, cost and side effects. pass@k is not pass^k reliability.

vendor claims

66% (Bedrock RFT), up to 73% (Salesforce), 40x (Vercel v0): vendor-reported, not replicated.

doc decay

Cards are written once and go stale. Regenerate them in CI from contracts and profiles.

synthetic provenance

Which model, prompt, seed and filter made each row? Few formats have fields for it.

Interview: the end-to-end customization system

Design an assistant for insurance-claims agents. Policies change monthly, answers must cite, and the tone must follow brand rules.
knowledge behavior data
Put the policies in RAG over versioned documents, with hybrid search, reranking and a citation check. Fix the tone with SFT or DPO. Write a data contract and a taxonomy of claim types. Build the golden set from real traces, with a slice per product line. Validate the judge against experts. Monitor query-vs-corpus drift and ship a quality card per release.
A paper claims +20 points on MATH-500 from RL on Qwen2.5-Math. How do you check it?
inflated gains
Run a random-reward control: on Qwen2.5-Math-7B, random rewards gave +21.4 and true rewards +29.1. Repeat on other model families. Decontaminate the eval. Report pass@k for large k, where base models often win. Compare on the same items with paired CIs.
How do you limit reward hacking in agentic coding RL?
reward hacking
Use hidden tests and make test files read-only. Run every rollout in a sandbox. Audit a sample of high-reward episodes. Monitor the CoT, but keep the monitor out of the reward, or the hack goes hidden. Add inoculation prompting. Stop training at the peak of the gold metric.
Your platform deprecates your tuned model next quarter. What do you keep?
platform risk
Keep the data with its contract and card, the grader, the eval set and the pinned training recipe. Then re-tune on Vertex AI, Bedrock or open weights, and compare the new model with paired CIs on the same frozen eval set.
Your recipe is the asset: data, grader, eval and card.