InstructGPT: Training Language Models to Follow Instructions with Human Feedback
The recipe that turned a text completer into an assistant, run end to end on a toy small enough to break.
Download instructgpt_from_scratch.py — everything below runs from it. NumPy for the world, a few hundred steps of CPU PyTorch for the three training loops. About two seconds.
I'd used GPT-3 through the API, and I'd used ChatGPT, and I could not have told you what happened in between. This paper is the in-between. I expected the hard part to be the RL. It isn't — the RL is one line. The hard part was seeing that there are three separate problems, and each stage fixes one.
Onboarding a new hire
A new engineer joins. Smart, has read everything, and on day one still doesn't know how you do things. So you show them a few worked examples. Then you review their work — "this one, not that one" — until they pick up your taste. Then they practise on real tickets, with a mentor nearby to say "you're drifting, come back" the moment they refactor the codebase to fix a typo. That's the paper. Before it, GPT-3 had one job:
prompt = "Explain what a mutex is."
gpt3(prompt) # "what comes next in a document containing this line?"
# -> "Explain what a semaphore is. Explain what a deadlock is."
Not stupid, not stubborn. A document containing "Explain what a mutex is." is usually an exam paper, and the next line of an exam paper is the next question. Every trick for making GPT-3 behave — few-shot examples, "Q: … A:" scaffolding — was a way of forging a document in which the answer happens to come next. InstructGPT is three stages on top of that:
policy = base # completes text, does not answer
policy = fit(policy, demonstrations) # 1. SFT: copy the worked examples
reward = fit(comparisons(policy.samples())) # 2. RM: learn their taste
policy = argmax(reward(policy) - beta * KL(policy, sft)) # 3. RL, on a leash
| Term | Means | New-hire analogy |
|---|---|---|
| Demonstration | A human wrote the answer they wanted | The worked example on day one |
| Comparison | A human picked the better of two model outputs | Code review: "this one, not that one" |
| Reward model | A network trained to predict which output a human prefers | The reviewer's taste, compiled |
| KL penalty | A cost for drifting away from the SFT model | The mentor saying "come back" |
The headline: on real prompts people had sent to the API, labelers preferred the 1.3B InstructGPT to the 175B GPT-3, a model a hundred times bigger. The 175B InstructGPT beat 175B GPT-3 85% of the time, and 71% of the time against GPT-3 with a carefully written few-shot prompt. That's the interview answer to "why doesn't scaling fix this?": you cannot scale your way out of the wrong objective.
The one formula
That's the stage-3 objective from the method section. Three things being traded off:
| Term | Shape | What it does |
|---|---|---|
y ∼ πφ | samples | The policy answers its own prompts. PPO estimates the expectation by sampling; my toy has six candidates per prompt, so I compute it exactly |
rθ(x, y) | scalar | The reward model's score. "What a labeler would think", compiled into a network |
β log(πφ / πSFT) | scalar, per token | The leash. Averaged over samples it's KL(πφ ‖ πSFT): how far the policy has drifted from stage 1. β = 0.02 in the paper |
γ E[log πφ(x)] | pretraining loss | PPO-ptx. Keep doing the original job on pretraining data while you practise. γ = 27.8 in the paper; γ = 0 is plain PPO |
Drop the γ term and the objective has an exact answer: π*(y|x) ∝ πSFT(y|x) · er(x,y)/β — the SFT distribution, re-weighted by the reward. β → ∞ is SFT; β → 0 is the reward model's argmax. The script checks the trained policy lands there (max difference 0.0005).
Stage 0: GPT-3 completes; it does not answer
The toy world: four prompts, six candidate responses each, and a hidden helpfulness score (0 to 10) that nothing in training ever reads — it only grades the pipeline afterwards. A policy is a 4×6 table of logits; answering means softmax the row and sample. Per prompt: three continuations, a terse answer, a good answer, a padded "Great question!" essay. The base logits make a continuation e2 ≈ 7× more natural than a one-word answer, and an answer rarer the longer it gets:
prompt: "Explain what a mutex is."
base says: "Explain what a semaphore is. Explain what a deadlock is."
base mass on answers: 0.053 true helpfulness: 0.79
base policy on prompt 0: continuations 0.965, terse 0.030,
good 0.0044, padded 0.0005
Stage 1: SFT teaches the format
Labelers write the answer they want to see — about 13k prompts' worth. Then plain supervised fine-tuning: cross-entropy on those answers, the same loss as pretraining, 16 epochs. Nothing new in the loss; what's new is the data. Here it's eight demonstrations: per prompt, the labeler wrote the terse answer once and the good answer once.
base mass on answers: 0.053 true helpfulness: 0.79
SFT mass on answers: 0.876 true helpfulness: 5.69
SFT policy on prompt 0, per response: [0.043 0.043 0.043 0.442 0.428 0.001]
(terse answer 0.44, good answer 0.43, padded answer 0.0006) ← a coin flip
Eight examples took it from 5% answering to 88%. That is what SFT is for: the format. But look at the split. SFT copies what it was shown, and it was shown a terse answer and a good one equally often, so it has no opinion about which is better. To give it one you'd have to write down what "better" means. You can't.
Stage 2: a reward model captures what you can't write down
Labelers stop writing. They see several of the model's own outputs for a prompt and rank them — 4 to 9 at a time in the paper, every pair from one ranking trained as a single batch, about 33k prompts. I can't define a good answer, but I can tell you which of two is better. Turning "a beat b" into a scalar is Bradley-Terry: P(a beats b) = sigmoid(r(a) − r(b)), minimise −log of that. The RLHF page derives it; here it's three lines. The paper's reward model is a 6B transformer with a scalar head. Mine is a linear layer over three numbers per response: does it answer, how long is it, that length squared. Same loss, same lesson. The labelers are simulated and imperfect; I tuned their noise until two of them agree about as often as the paper's real ones did, 72.6%.
comparisons collected on SFT outputs: 240
simulated inter-labeler agreement: 0.737 (paper: about 0.73)
padded answers that reached a labeler: 0 of 4
reward model vs hidden score, on responses labelers saw: corr = 0.979
reward model vs hidden score, on ALL 24 responses: corr = 0.708
prompt 0, learned reward vs hidden score:
reward 1.96 hidden 0.5 'Explain what a semaphore is. E'
reward 4.60 hidden 5.0 'A lock one thread holds at a t'
reward 6.12 hidden 8.0 'A lock one thread holds while '
reward 7.86 hidden 3.5 'Great question! A mutex, or mu' <- never seen
The reward model is near-perfect on what labelers actually saw: 0.979. And it is guessing about what they never saw. The padded essay has 0.06% probability under SFT, so no labeler was ever shown one. The RM learned "longer is better" from terse-vs-good — true on that data — and extrapolated straight past the good answer to the essay. Hold that thought.
Stage 3: practise against the reward model, on a leash
Now the policy answers prompts (about 31k), the reward model scores the answers, and the policy is updated to score higher — with a per-token KL penalty against the SFT model so it can't wander off. The paper uses PPO; the RLHF page walks through the sampling and clipping and I'm not re-deriving it. With six candidates per prompt I compute the expectation exactly and let autograd do the rest.
prompt: "Explain what a mutex is."
RLHF says: "A lock one thread holds while the rest wait; guards shared state."
stage mass on answers true helpfulness KL to SFT
base 0.053 0.79 1.795
sft 0.876 5.69 0.000
rlhf 0.999 7.68 0.539
ceiling (always the good answer): 8.12
RLHF policy on prompt 0: terse 0.10, good 0.88, padded 0.015
closed form pi_SFT * exp(r/beta): max |diff| from trained policy = 0.0005
RL did the one thing SFT couldn't: it moved the coin flip to 0.10 / 0.88, because the reward model ranks the two. Helpfulness 0.79 → 5.69 → 7.68 against a ceiling of 8.12. And the padded essay crept up to 1.5% — the RM over-rates it, the policy leaned that way, and the leash is the only reason it stopped. Two experiments show what the leash is for.
Point the leash at the wrong model
Skip SFT — start RL from the base model and leash it to the base model, because that's the only model you have. Or do SFT, then point the KL at the base model.
steps until true helpfulness >= 7.0, then the final numbers:
run steps mass on answers helpfulness
SFT -> RL, KL to SFT 7 0.999 7.68
base -> RL, KL to base never 0.892 4.75
SFT -> RL, KL to base never 0.892 4.75
helpfulness at step 10: with SFT 7.41 without SFT 1.50
From the base model the good answer is a rounding error, so RL has almost nothing to push on and crawls: 1.50 after ten steps against 7.41. And the leash pulls back toward continuations the whole way, so it settles at 4.75 and never arrives. The two bad rows end in the same place: the reference model decides the destination, the start only decides the speed. (With real sampling, the start also decides whether you ever see a good answer at all.)
Take the leash off
beta = 0 says: "Great question! A mutex, or mutual exclusion... [4 paragraphs]"
probability on the padded answer, prompt 0: 0.999
beta = 0.7 mass on answers: 0.999 true helpfulness: 7.68
beta = 0 mass on answers: 1.000 true helpfulness: 3.50
+ 240 comparisons on the drifted policy's own outputs, RM retrained:
corr with hidden score on ALL 24 responses: 0.979 (was 0.708)
RM on prompt 0: good 6.28 padded 4.04 (was good 6.12, padded 7.86)
retrained RM mass on answers: 0.998 true helpfulness: 7.63
With β = 0 the policy runs straight to whatever the reward model over-rates: the essay, 99.9% of the time, and true helpfulness drops from 7.68 to 3.50. That's reward hacking, and the RM was never wrong on its own data — it was asked about something it had never seen. The paper has two guards: the KL penalty, and collecting comparisons on outputs from the models being trained, not only the SFT model, then retraining the RM on all of it — so it's trained where the policy actually lives. The bottom half of the block is that fix: 240 fresh comparisons on the drifted policy's outputs, the RM learns the essay is worse than the good answer (4.04 against 6.28), and the policy comes back.
The alignment tax, and PPO-ptx
Stage 3 made the model better at what labelers wanted and worse at things nobody was checking: after RLHF, scores dropped on public benchmarks like SQuAD, DROP, HellaSwag and a French→English translation task. The paper calls this the alignment tax. It's distribution shift again — practise on 31k API-style prompts and you drift from what the pretraining corpus taught you, even on the leash. The fix is the γ term: while running PPO, keep taking ordinary next-token gradient steps on batches of pretraining data. They call it PPO-ptx, and it recovers most of the lost benchmark performance at little cost in labeler preference. Programmer version: keep the old regression suite running while you refactor. It's not in the script because the toy has no pretraining corpus to mix in; it's one extra loss term, added, and the interesting part is why it's there.
GPT-3-ஐ API வழியாகப் பயன்படுத்தியிருக்கிறேன், ChatGPT-யையும் பயன்படுத்தியிருக்கிறேன் — ஆனால் அந்த இரண்டுக்கும் இடையில் என்ன நடந்தது என்று என்னால் சொல்ல முடியவில்லை. இந்தக் கட்டுரைதான் அந்த இடைவெளி. கடினமான பகுதி RL-ஆக இருக்கும் என்று நினைத்தேன். இல்லை — RL ஒரே ஒரு வரிதான். கடினமான பகுதி, இங்கே மூன்று தனித்தனி பிரச்சனைகள் இருக்கின்றன, ஒவ்வொரு stage-ம் ஒன்றை மட்டும் சரிசெய்கிறது என்பதைப் பார்ப்பதுதான்.
புதிய ஊழியரை வேலைக்குச் சேர்த்தல்
ஒரு புதிய engineer குழுவில் சேர்கிறார். கெட்டிக்காரர், எல்லாம் படித்தவர், ஆனாலும் முதல் நாள் நீங்கள் எப்படி வேலை செய்கிறீர்கள் என்று அவருக்குத் தெரியாது. அதனால் சில செய்முறை உதாரணங்களைக் காட்டுகிறீர்கள். பிறகு அவர் வேலையை review செய்கிறீர்கள் — "இது சரி, அது இல்லை" — உங்கள் ரசனை அவருக்குப் புரியும் வரை. பிறகு அவர் நிஜ tickets-இல் பயிற்சி செய்கிறார், ஒரு mentor அருகில் இருந்து "வழி மாறுகிறாய், திரும்பி வா" என்று சொல்ல — ஒரு typo-வைச் சரிசெய்ய codebase முழுவதையும் refactor செய்யத் தொடங்கும் அந்த நொடியில். இந்தக் கட்டுரை அதுதான். இதற்கு முன், GPT-3-க்கு ஒரே ஒரு வேலைதான் இருந்தது:
prompt = "Explain what a mutex is."
gpt3(prompt) # "இந்த வரி உள்ள ஒரு document-இல் அடுத்து என்ன வரும்?"
# -> "Explain what a semaphore is. Explain what a deadlock is."
முட்டாள்தனம் இல்லை, பிடிவாதமும் இல்லை. "Explain what a mutex is." என்ற வரி உள்ள document பொதுவாக ஒரு தேர்வுத்தாள்தான், தேர்வுத்தாளின் அடுத்த வரி அடுத்த கேள்விதான். GPT-3-ஐ நடக்க வைக்கும் ஒவ்வொரு தந்திரமும் — few-shot உதாரணங்கள், "Q: … A:" scaffolding — விடை அடுத்ததாக வரும்படியான ஒரு document-ஐப் போலியாக உருவாக்கும் வழிதான். InstructGPT என்பது அதன் மேல் மூன்று stages:
policy = base # text முடிக்கும், பதில் சொல்லாது
policy = fit(policy, demonstrations) # 1. SFT: உதாரணங்களை நகலெடு
reward = fit(comparisons(policy.samples())) # 2. RM: அவர்கள் ரசனையைக் கற்று
policy = argmax(reward(policy) - beta * KL(policy, sft)) # 3. RL, சங்கிலியுடன்
| சொல் | அர்த்தம் | புதிய ஊழியர் உவமை |
|---|---|---|
| Demonstration | வேண்டிய பதிலை ஒரு மனிதர் எழுதினார் | முதல் நாள் காட்டிய செய்முறை உதாரணம் |
| Comparison | இரண்டு model outputs-இல் சிறந்ததை ஒரு மனிதர் தேர்ந்தெடுத்தார் | Code review: "இது சரி, அது இல்லை" |
| Reward model | எந்த output-ஐ மனிதர் விரும்புவார் என்று கணிக்கப் பயிற்றுவிக்கப்பட்ட network | Reviewer-இன் ரசனை, compile செய்யப்பட்டது |
| KL penalty | SFT model-இலிருந்து விலகிச் செல்வதற்கான விலை | "திரும்பி வா" என்று சொல்லும் mentor |
முக்கியச் செய்தி: மக்கள் API-க்கு அனுப்பிய நிஜ prompts-இல், 175B GPT-3-ஐ விட 1.3B InstructGPT-யை labelers விரும்பினார்கள் — நூறு மடங்கு பெரிய model-ஐ விட. 175B InstructGPT, 175B GPT-3-ஐ 85% நேரமும், கவனமாக எழுதப்பட்ட few-shot prompt உள்ள GPT-3-ஐ 71% நேரமும் வென்றது. "Scaling ஏன் இதைச் சரிசெய்யாது?" என்ற interview கேள்விக்கு இதுதான் பதில்: தவறான objective-ஐ விட்டு scaling மூலம் வெளியேற முடியாது.
Moonu stage: SFT format-ai katrudhu, reward model rasanai-ai pidikkudhu, RL adha vachu payirchi pannudhu. Thavaraana objective-ai scaling sari panna mudiyaadhu — adhaan 1.3B model 175B-ai jeikkiradhu.
அந்த ஒரு சூத்திரம்
அதுதான் method பகுதியில் உள்ள stage-3 objective. மூன்று விஷயங்கள் சமன்செய்யப்படுகின்றன:
| சொல் | Shape | என்ன செய்கிறது |
|---|---|---|
y ∼ πφ | samples | Policy தன் சொந்த prompts-க்குப் பதில் சொல்கிறது. PPO expectation-ஐ sampling மூலம் மதிப்பிடுகிறது; என் toy-இல் prompt ஒன்றுக்கு ஆறு candidates, அதனால் நான் அதைச் சரியாகக் கணக்கிடுகிறேன் |
rθ(x, y) | scalar | Reward model-இன் score. "ஒரு labeler என்ன நினைப்பார்", ஒரு network-ஆக compile செய்யப்பட்டது |
β log(πφ / πSFT) | scalar, token ஒன்றுக்கு | சங்கிலி. Samples மேல் சராசரி எடுத்தால் இது KL(πφ ‖ πSFT): stage 1-இலிருந்து policy எவ்வளவு விலகியது. கட்டுரையில் β = 0.02 |
γ E[log πφ(x)] | pretraining loss | PPO-ptx. பயிற்சி செய்யும்போதே pretraining data-வில் பழைய வேலையையும் தொடர்ந்து செய். கட்டுரையில் γ = 27.8; γ = 0 என்றால் சாதா PPO |
γ term-ஐ நீக்கிவிட்டால் objective-க்கு ஒரு துல்லியமான விடை உண்டு: π*(y|x) ∝ πSFT(y|x) · er(x,y)/β — SFT distribution, reward-ஆல் மறு-எடையிடப்பட்டது. β → ∞ என்பது SFT; β → 0 என்பது reward model-இன் argmax. பயிற்சி பெற்ற policy அங்குதான் வந்து நிற்கிறதா என்பதை script சரிபார்க்கிறது (அதிகபட்ச வேறுபாடு 0.0005).
Stage 0: GPT-3 முடிக்கிறது; பதில் சொல்லவில்லை
Toy உலகம்: நான்கு prompts, ஒவ்வொன்றுக்கும் ஆறு candidate responses, மற்றும் training-இல் யாரும் படிக்காத ஒரு மறைவான helpfulness score (0 முதல் 10) — அது pipeline-ஐ பின்னால் மதிப்பிட மட்டுமே. ஒரு policy என்பது 4×6 logits அட்டவணை; பதில் சொல்வது என்றால் அந்த வரிசைக்கு softmax போட்டு sample எடுப்பது. Prompt ஒன்றுக்கு: மூன்று continuations, ஒரு சுருக்கமான பதில், ஒரு நல்ல பதில், ஒரு "Great question!" என்று நீட்டிய கட்டுரை. Base logits ஒரு continuation-ஐ ஒரு-வார்த்தைப் பதிலை விட e2 ≈ 7× இயல்பானதாக்குகின்றன, மேலும் பதில் நீளும் தோறும் அரிதாகிறது:
prompt: "Explain what a mutex is."
base says: "Explain what a semaphore is. Explain what a deadlock is."
base mass on answers: 0.053 true helpfulness: 0.79
base policy on prompt 0: continuations 0.965, terse 0.030,
good 0.0044, padded 0.0005
Stage 1: SFT format-ஐக் கற்பிக்கிறது
Labelers தாங்கள் பார்க்க விரும்பும் பதிலை எழுதுகிறார்கள் — சுமார் 13k prompts அளவுக்கு. பிறகு சாதாரண supervised fine-tuning: அந்தப் பதில்களின் மேல் cross-entropy, pretraining-இல் இருந்த அதே loss, 16 epochs. Loss-இல் புதிதாக ஒன்றுமில்லை; புதிது data-தான். இங்கே எட்டு demonstrations: prompt ஒன்றுக்கு labeler சுருக்கமான பதிலை ஒரு முறையும் நல்ல பதிலை ஒரு முறையும் எழுதினார்.
base mass on answers: 0.053 true helpfulness: 0.79
SFT mass on answers: 0.876 true helpfulness: 5.69
SFT policy on prompt 0, per response: [0.043 0.043 0.043 0.442 0.428 0.001]
(terse answer 0.44, good answer 0.43, padded answer 0.0006) ← a coin flip
எட்டு உதாரணங்கள் 5% பதிலிலிருந்து 88%-க்குக் கொண்டு வந்துவிட்டன. SFT-இன் வேலை அதுதான்: format. ஆனால் பிரிவைப் பாருங்கள். SFT தனக்குக் காட்டியதை நகலெடுக்கிறது, சுருக்கமான பதிலும் நல்ல பதிலும் சமமான அளவு காட்டப்பட்டன, அதனால் எது சிறந்தது என்பதில் அதற்கு எந்த அபிப்ராயமும் இல்லை. அதற்கு ஒன்றைக் கொடுக்க வேண்டுமானால் "சிறந்தது" என்றால் என்ன என்பதை எழுத வேண்டும். உங்களால் முடியாது.
SFT format-ai kathukkudhu — 5% irundhu 88% answer-ku. Aana terse-um good-um samamaa kaatapattadhaala, edhu nalladhu-nu adhukku theriyaadhu. "Nalladhu" enna-nu ezhudha mudiyaadhu — adhaan stage 2-oda kaaranam.
Stage 2: எழுத முடியாததை reward model பிடிக்கிறது
Labelers எழுதுவதை நிறுத்துகிறார்கள். ஒரு prompt-க்கு model-இன் சொந்த outputs பலவற்றைப் பார்த்து அவற்றை வரிசைப்படுத்துகிறார்கள் — கட்டுரையில் ஒரு முறைக்கு 4 முதல் 9, ஒரு ranking-இல் உள்ள ஒவ்வொரு ஜோடியும் ஒரே batch-ஆகப் பயிற்றுவிக்கப்படுகிறது, சுமார் 33k prompts. நல்ல பதில் என்றால் என்ன என்பதை என்னால் வரையறுக்க முடியாது, ஆனால் இரண்டில் எது சிறந்தது என்று சொல்ல முடியும். "a, b-ஐ வென்றது" என்பதை ஒரு scalar-ஆக மாற்றுவது Bradley-Terry: P(a beats b) = sigmoid(r(a) − r(b)), அதன் −log-ஐக் குறைக்கவும். RLHF page அதை வருவிக்கிறது; இங்கே அது மூன்று வரிகள். கட்டுரையின் reward model ஒரு 6B transformer, scalar head உடன். என்னுடையது response ஒன்றுக்கு மூன்று எண்களின் மேல் ஒரு linear layer: இது பதில் சொல்கிறதா, எவ்வளவு நீளம், அந்த நீளத்தின் வர்க்கம். அதே loss, அதே பாடம். Labelers simulated, குறையுள்ளவர்கள்; கட்டுரையின் நிஜ labelers ஒத்துப்போன அளவுக்கே இருவர் ஒத்துப்போகும் வரை அவர்கள் noise-ஐ சரிசெய்தேன், 72.6%.
comparisons collected on SFT outputs: 240
simulated inter-labeler agreement: 0.737 (paper: about 0.73)
padded answers that reached a labeler: 0 of 4
reward model vs hidden score, on responses labelers saw: corr = 0.979
reward model vs hidden score, on ALL 24 responses: corr = 0.708
prompt 0, learned reward vs hidden score:
reward 1.96 hidden 0.5 'Explain what a semaphore is. E'
reward 4.60 hidden 5.0 'A lock one thread holds at a t'
reward 6.12 hidden 8.0 'A lock one thread holds while '
reward 7.86 hidden 3.5 'Great question! A mutex, or mu' <- never seen
Labelers உண்மையில் பார்த்தவற்றில் reward model கிட்டத்தட்ட சரியானது: 0.979. அவர்கள் பார்க்காதவை பற்றி அது ஊகிக்கிறது. நீட்டிய கட்டுரைக்கு SFT-இன் கீழ் 0.06% probability மட்டுமே, அதனால் எந்த labeler-க்கும் அது காட்டப்படவே இல்லை. RM "நீளமானது சிறந்தது" என்பதை terse-vs-good-இலிருந்து கற்றது — அந்த data-வில் அது உண்மை — பிறகு நல்ல பதிலைத் தாண்டி நேராக அந்தக் கட்டுரை வரை நீட்டி ஊகித்தது. இதை மனதில் வையுங்கள்.
Stage 3: reward model-க்கு எதிராகப் பயிற்சி, சங்கிலியுடன்
இப்போது policy prompts-க்குப் பதில் சொல்கிறது (சுமார் 31k), reward model அந்தப் பதில்களுக்கு score போடுகிறது, அதிக score பெறும்படி policy புதுப்பிக்கப்படுகிறது — SFT model-க்கு எதிராக token ஒன்றுக்கு ஒரு KL penalty உடன், அதனால் அது வழி தவற முடியாது. கட்டுரை PPO-ஐப் பயன்படுத்துகிறது; RLHF page sampling மற்றும் clipping வழியாக நடந்து செல்கிறது, நான் அதை மீண்டும் வருவிக்கவில்லை. Prompt ஒன்றுக்கு ஆறு candidates இருப்பதால் expectation-ஐ நான் துல்லியமாகக் கணக்கிட்டு மீதியை autograd-இடம் விடுகிறேன்.
prompt: "Explain what a mutex is."
RLHF says: "A lock one thread holds while the rest wait; guards shared state."
stage mass on answers true helpfulness KL to SFT
base 0.053 0.79 1.795
sft 0.876 5.69 0.000
rlhf 0.999 7.68 0.539
ceiling (always the good answer): 8.12
RLHF policy on prompt 0: terse 0.10, good 0.88, padded 0.015
closed form pi_SFT * exp(r/beta): max |diff| from trained policy = 0.0005
SFT-ஆல் செய்ய முடியாத ஒரு காரியத்தை RL செய்தது: அந்த coin flip-ஐ 0.10 / 0.88-ஆக நகர்த்தியது, ஏனெனில் reward model அந்த இரண்டையும் வரிசைப்படுத்துகிறது. Helpfulness 0.79 → 5.69 → 7.68, உச்ச வரம்பு 8.12. மேலும் நீட்டிய கட்டுரை 1.5%-ஆக ஏறியது — RM அதை அதிகமாக மதிப்பிடுகிறது, policy அந்தப் பக்கம் சாய்ந்தது, அது நின்றதற்குக் காரணம் சங்கிலி மட்டுமே. சங்கிலி எதற்கு என்பதை இரண்டு சோதனைகள் காட்டுகின்றன.
சங்கிலியைத் தவறான model-இல் கட்டுங்கள்
SFT-ஐத் தவிருங்கள் — base model-இலிருந்து RL தொடங்கி, base model-உடனேயே சங்கிலி கட்டுங்கள், ஏனெனில் உங்களிடம் இருப்பது அது மட்டுமே. அல்லது SFT செய்துவிட்டு, KL-ஐ base model-இல் சுட்டுங்கள்.
steps until true helpfulness >= 7.0, then the final numbers:
run steps mass on answers helpfulness
SFT -> RL, KL to SFT 7 0.999 7.68
base -> RL, KL to base never 0.892 4.75
SFT -> RL, KL to base never 0.892 4.75
helpfulness at step 10: with SFT 7.41 without SFT 1.50
Base model-இலிருந்து பார்த்தால் நல்ல பதில் ஒரு rounding error அளவுதான், அதனால் RL-க்குத் தள்ள ஏதும் இல்லாமல் ஊர்ந்து செல்கிறது: பத்து steps-க்குப் பிறகு 7.41-க்குப் பதிலாக 1.50. மேலும் சங்கிலி முழு வழியிலும் continuations பக்கம் இழுக்கிறது, அதனால் அது 4.75-இல் நின்றுவிடுகிறது, ஒருபோதும் வந்து சேர்வதில்லை. மோசமான இரண்டு வரிசைகளும் ஒரே இடத்தில் முடிகின்றன: எங்கே சேருவது என்பதை reference model முடிவு செய்கிறது, தொடக்கம் வேகத்தை மட்டுமே முடிவு செய்கிறது. (நிஜ sampling-இல், ஒரு நல்ல பதிலை நீங்கள் எப்போதாவது பார்ப்பீர்களா என்பதையும் தொடக்கம் முடிவு செய்யும்.)
சங்கிலியைக் கழற்றுங்கள்
beta = 0 says: "Great question! A mutex, or mutual exclusion... [4 paragraphs]"
probability on the padded answer, prompt 0: 0.999
beta = 0.7 mass on answers: 0.999 true helpfulness: 7.68
beta = 0 mass on answers: 1.000 true helpfulness: 3.50
+ 240 comparisons on the drifted policy's own outputs, RM retrained:
corr with hidden score on ALL 24 responses: 0.979 (was 0.708)
RM on prompt 0: good 6.28 padded 4.04 (was good 6.12, padded 7.86)
retrained RM mass on answers: 0.998 true helpfulness: 7.63
β = 0 என்றால் reward model எதை அதிகமாக மதிப்பிடுகிறதோ அதை நோக்கி policy நேராக ஓடுகிறது: அந்தக் கட்டுரை, 99.9% நேரம், true helpfulness 7.68-இலிருந்து 3.50-ஆகக் குறைகிறது. அதுதான் reward hacking, மேலும் RM தன் சொந்த data-வில் ஒருபோதும் தவறு செய்யவில்லை — அது பார்த்திராத ஒன்றைப் பற்றி அதனிடம் கேட்கப்பட்டது. கட்டுரையில் இரண்டு காவல்கள்: KL penalty, மற்றும் SFT model-இலிருந்து மட்டுமல்லாமல் பயிற்சி பெறும் models-இன் outputs-இலும் comparisons சேகரித்து, எல்லாவற்றின் மேலும் RM-ஐ மறுபடி பயிற்றுவிப்பது — அதனால் policy உண்மையில் வாழும் இடத்தில் அது பயிற்சி பெறுகிறது. Block-இன் கீழ் பாதி அந்தத் திருத்தம்தான்: விலகிய policy-இன் outputs-இல் 240 புதிய comparisons, நல்ல பதிலை விடக் கட்டுரை மோசமானது என்று RM கற்கிறது (6.28-க்கு எதிராக 4.04), policy திரும்பி வருகிறது.
Beta = 0 vachaa policy reward model over-rate panra padded essay-kku 99.9% odum — adhu dhaan reward hacking. Rendu kaaval: KL leash, appuram drift aana policy-oda sonta outputs-la fresh comparisons vaangi RM-ai marubadi train panradhu.
Alignment tax, மற்றும் PPO-ptx
Labelers விரும்பியதில் model-ஐ Stage 3 சிறப்பாக்கியது, யாரும் சரிபார்க்காத விஷயங்களில் மோசமாக்கியது: RLHF-க்குப் பிறகு SQuAD, DROP, HellaSwag மற்றும் ஒரு French→English translation task போன்ற பொது benchmarks-இல் scores குறைந்தன. கட்டுரை இதை alignment tax என்று அழைக்கிறது. இது மீண்டும் distribution shift-தான் — 31k API-பாணி prompts-இல் பயிற்சி செய்தால், சங்கிலியில் இருந்தாலும் pretraining corpus கற்பித்ததிலிருந்து நீங்கள் விலகுகிறீர்கள். தீர்வு γ term: PPO ஓடும்போதே, pretraining data batches-இல் சாதாரண next-token gradient steps-ஐத் தொடர்ந்து எடுங்கள். அதை அவர்கள் PPO-ptx என்று அழைக்கிறார்கள், labeler preference-இல் மிகக் குறைந்த விலையில் இழந்த benchmark performance-இல் பெரும்பாலானதைத் திரும்பப் பெறுகிறது. Programmer வடிவம்: refactor செய்யும்போது பழைய regression suite-ஐயும் ஓடவிடுங்கள். Toy-இல் கலக்க pretraining corpus இல்லை என்பதால் இது script-இல் இல்லை; இது கூட்டப்படும் ஒரு loss term மட்டுமே, சுவாரஸ்யமான பகுதி அது ஏன் அங்கே இருக்கிறது என்பதுதான்.
मैंने GPT-3 को API से इस्तेमाल किया था, और ChatGPT भी इस्तेमाल किया था — पर बीच में क्या हुआ, यह मैं आपको नहीं बता सकता था। यह paper वही बीच का हिस्सा है। मुझे लगा था मुश्किल हिस्सा RL होगा। नहीं है — RL तो एक ही लाइन है। मुश्किल हिस्सा यह देखना था कि यहाँ तीन अलग-अलग समस्याएँ हैं, और हर stage उनमें से एक को ठीक करता है।
नए बंदे को onboard करना
एक नया engineer टीम में आता है। होशियार है, सब कुछ पढ़ चुका है, फिर भी पहले दिन उसे यह नहीं पता कि आप काम कैसे करते हैं। तो आप उसे कुछ हल किए हुए उदाहरण दिखाते हैं। फिर उसका काम review करते हैं — "यह वाला, वह वाला नहीं" — जब तक उसे आपकी पसंद समझ न आ जाए। फिर वह असली tickets पर अभ्यास करता है, और पास में एक mentor बैठा रहता है जो कहे "तुम भटक रहे हो, वापस आओ" — ठीक उसी पल जब वह एक typo ठीक करने के लिए पूरा codebase refactor करने लगे। यही paper है। इससे पहले GPT-3 का बस एक ही काम था:
prompt = "Explain what a mutex is."
gpt3(prompt) # "इस लाइन वाले document में आगे क्या आएगा?"
# -> "Explain what a semaphore is. Explain what a deadlock is."
न बेवक़ूफ़ी, न ज़िद। "Explain what a mutex is." वाला document आम तौर पर एक exam paper होता है, और exam paper की अगली लाइन अगला सवाल होती है। GPT-3 से काम निकलवाने की हर तरकीब — few-shot उदाहरण, "Q: … A:" scaffolding — असल में ऐसा document गढ़ने का तरीका थी जिसमें जवाब ही अगली चीज़ बने। InstructGPT उसके ऊपर तीन stages हैं:
policy = base # text पूरा करता है, जवाब नहीं
policy = fit(policy, demonstrations) # 1. SFT: उदाहरणों की नक़ल करो
reward = fit(comparisons(policy.samples())) # 2. RM: उनकी पसंद सीखो
policy = argmax(reward(policy) - beta * KL(policy, sft)) # 3. RL, पट्टे पर
| शब्द | मतलब | नए बंदे वाली उपमा |
|---|---|---|
| Demonstration | इंसान ने वही जवाब ख़ुद लिखा जो वह चाहता था | पहले दिन दिखाया गया हल किया हुआ उदाहरण |
| Comparison | इंसान ने दो model outputs में से बेहतर चुना | Code review: "यह वाला, वह वाला नहीं" |
| Reward model | वह network जो अनुमान लगाना सीखता है कि इंसान कौन-सा output पसंद करेगा | Reviewer की पसंद, compile की हुई |
| KL penalty | SFT model से दूर भटकने की क़ीमत | Mentor का कहना "वापस आओ" |
मुख्य नतीजा: लोगों ने API पर जो असली prompts भेजे थे, उन पर labelers ने 175B GPT-3 के मुक़ाबले 1.3B InstructGPT को पसंद किया — यानी सौ गुना बड़े model से बेहतर। 175B InstructGPT ने 175B GPT-3 को 85% बार हराया, और ध्यान से लिखे few-shot prompt वाले GPT-3 को 71% बार। "Scaling इसे ठीक क्यों नहीं करती?" — interview का जवाब यही है: ग़लत objective से scaling करके बाहर नहीं निकला जा सकता।
वह एक सूत्र
यह method section वाला stage-3 objective है। तीन चीज़ों के बीच सौदा हो रहा है:
| शब्द | Shape | यह क्या करता है |
|---|---|---|
y ∼ πφ | samples | Policy अपने ही prompts का जवाब देती है। PPO expectation को sampling से आँकता है; मेरे toy में हर prompt पर छह candidates हैं, इसलिए मैं उसे ठीक-ठीक निकाल लेता हूँ |
rθ(x, y) | scalar | Reward model का score. "एक labeler क्या सोचेगा", एक network में compile किया हुआ |
β log(πφ / πSFT) | scalar, प्रति token | पट्टा। Samples पर औसत लो तो यही KL(πφ ‖ πSFT) है: policy stage 1 से कितनी भटकी। Paper में β = 0.02 |
γ E[log πφ(x)] | pretraining loss | PPO-ptx. अभ्यास करते हुए भी pretraining data पर पुराना काम करते रहो। Paper में γ = 27.8; γ = 0 यानी सादा PPO |
γ वाला term हटा दें तो objective का एक सटीक जवाब है: π*(y|x) ∝ πSFT(y|x) · er(x,y)/β — यानी SFT distribution, reward से दोबारा भारित। β → ∞ यानी SFT; β → 0 यानी reward model का argmax. Script जाँचता है कि trained policy वहीं पहुँचती है (अधिकतम अंतर 0.0005)।
Stage 0: GPT-3 पूरा करता है; जवाब नहीं देता
Toy दुनिया: चार prompts, हर एक पर छह candidate responses, और एक छिपा हुआ helpfulness score (0 से 10) जिसे training में कोई नहीं पढ़ता — वह सिर्फ़ बाद में pipeline को परखता है। Policy यानी logits की 4×6 table; जवाब देना यानी उस row पर softmax लगाकर sample लेना। हर prompt पर: तीन continuations, एक छोटा जवाब, एक अच्छा जवाब, और एक फूला हुआ "Great question!" निबंध। Base logits एक continuation को एक-शब्द वाले जवाब से e2 ≈ 7× ज़्यादा स्वाभाविक बनाते हैं, और जवाब जितना लंबा, उतना दुर्लभ:
prompt: "Explain what a mutex is."
base says: "Explain what a semaphore is. Explain what a deadlock is."
base mass on answers: 0.053 true helpfulness: 0.79
base policy on prompt 0: continuations 0.965, terse 0.030,
good 0.0044, padded 0.0005
Stage 1: SFT format सिखाता है
Labelers वही जवाब लिखते हैं जो वे देखना चाहते हैं — क़रीब 13k prompts जितना। फिर सादा supervised fine-tuning: उन जवाबों पर cross-entropy, वही loss जो pretraining में था, 16 epochs. Loss में नया कुछ नहीं; नया है data. यहाँ आठ demonstrations हैं: हर prompt पर labeler ने छोटा जवाब एक बार और अच्छा जवाब एक बार लिखा।
base mass on answers: 0.053 true helpfulness: 0.79
SFT mass on answers: 0.876 true helpfulness: 5.69
SFT policy on prompt 0, per response: [0.043 0.043 0.043 0.442 0.428 0.001]
(terse answer 0.44, good answer 0.43, padded answer 0.0006) ← a coin flip
आठ उदाहरणों ने इसे 5% जवाब देने से 88% तक पहुँचा दिया। SFT इसी के लिए है: format. पर बँटवारे को देखिए। SFT वही नक़ल करता है जो उसे दिखाया गया, और उसे छोटा जवाब और अच्छा जवाब बराबर बार दिखाए गए, इसलिए कौन-सा बेहतर है इस पर उसकी कोई राय नहीं है। राय देने के लिए आपको लिखना पड़ेगा कि "बेहतर" का मतलब क्या है। आप लिख नहीं सकते।
Stage 2: जो लिखा नहीं जा सकता, reward model उसे पकड़ता है
Labelers लिखना बंद कर देते हैं। वे एक prompt पर model के अपने ही कई outputs देखते हैं और उन्हें क्रम में लगाते हैं — paper में एक बार में 4 से 9, एक ranking के हर जोड़े को एक ही batch की तरह train किया जाता है, क़रीब 33k prompts. मैं अच्छा जवाब परिभाषित नहीं कर सकता, पर दो में से कौन बेहतर है यह बता सकता हूँ। "a ने b को हराया" को scalar में बदलना Bradley-Terry है: P(a beats b) = sigmoid(r(a) − r(b)), उसका −log घटाइए। RLHF page इसे निकालकर दिखाता है; यहाँ यह तीन लाइनें हैं। Paper का reward model scalar head वाला 6B transformer है। मेरा हर response पर तीन संख्याओं के ऊपर एक linear layer है: यह जवाब देता है या नहीं, कितना लंबा है, और उस लंबाई का वर्ग। वही loss, वही सबक़। Labelers simulated और ख़ामियों वाले हैं; मैंने उनका noise तब तक tune किया जब तक दो labelers उतनी ही बार सहमत न हों जितनी बार paper के असली labelers होते थे, 72.6%.
comparisons collected on SFT outputs: 240
simulated inter-labeler agreement: 0.737 (paper: about 0.73)
padded answers that reached a labeler: 0 of 4
reward model vs hidden score, on responses labelers saw: corr = 0.979
reward model vs hidden score, on ALL 24 responses: corr = 0.708
prompt 0, learned reward vs hidden score:
reward 1.96 hidden 0.5 'Explain what a semaphore is. E'
reward 4.60 hidden 5.0 'A lock one thread holds at a t'
reward 6.12 hidden 8.0 'A lock one thread holds while '
reward 7.86 hidden 3.5 'Great question! A mutex, or mu' <- never seen
Labelers ने जो सचमुच देखा, उस पर reward model लगभग सटीक है: 0.979. और जो उन्होंने कभी नहीं देखा, उसके बारे में वह अंदाज़ा लगा रहा है। फूले हुए निबंध की SFT के तहत probability 0.06% है, इसलिए किसी labeler को वह कभी दिखाया ही नहीं गया। RM ने terse-बनाम-good से "लंबा यानी बेहतर" सीखा — उस data पर यह सच था — और अच्छे जवाब के आगे बढ़कर सीधे निबंध तक extrapolate कर गया। यह बात याद रखिए।
Stage 3: reward model के ख़िलाफ़ अभ्यास, पट्टे पर
अब policy prompts के जवाब देती है (क़रीब 31k), reward model उन जवाबों को score देता है, और policy को ऊँचा score पाने के लिए update किया जाता है — साथ में SFT model के ख़िलाफ़ प्रति-token KL penalty, ताकि वह भटक न जाए। Paper PPO इस्तेमाल करता है; RLHF page sampling और clipping से गुज़रता है और मैं उसे दोबारा नहीं निकाल रहा। हर prompt पर छह candidates होने से मैं expectation ठीक-ठीक निकाल लेता हूँ और बाक़ी autograd पर छोड़ देता हूँ।
prompt: "Explain what a mutex is."
RLHF says: "A lock one thread holds while the rest wait; guards shared state."
stage mass on answers true helpfulness KL to SFT
base 0.053 0.79 1.795
sft 0.876 5.69 0.000
rlhf 0.999 7.68 0.539
ceiling (always the good answer): 8.12
RLHF policy on prompt 0: terse 0.10, good 0.88, padded 0.015
closed form pi_SFT * exp(r/beta): max |diff| from trained policy = 0.0005
RL ने वह किया जो SFT नहीं कर सका: उसने उस सिक्के वाली बराबरी को 0.10 / 0.88 कर दिया, क्योंकि reward model दोनों को क्रम में लगाता है। Helpfulness 0.79 → 5.69 → 7.68, जबकि छत 8.12 है। और फूला हुआ निबंध चढ़कर 1.5% पर आ गया — RM उसे ज़्यादा आँकता है, policy उधर झुकी, और वह रुकी सिर्फ़ पट्टे की वजह से। पट्टा किसलिए है, यह दो प्रयोग दिखाते हैं।
पट्टे को ग़लत model से बाँधिए
SFT छोड़ दीजिए — RL base model से शुरू कीजिए और पट्टा भी base model से बाँधिए, क्योंकि आपके पास वही एक model है। या SFT कीजिए, फिर KL को base model की तरफ़ मोड़ दीजिए।
steps until true helpfulness >= 7.0, then the final numbers:
run steps mass on answers helpfulness
SFT -> RL, KL to SFT 7 0.999 7.68
base -> RL, KL to base never 0.892 4.75
SFT -> RL, KL to base never 0.892 4.75
helpfulness at step 10: with SFT 7.41 without SFT 1.50
Base model से देखें तो अच्छा जवाब एक rounding error भर है, इसलिए RL के पास धकेलने को कुछ ख़ास नहीं होता और वह रेंगता है: दस steps के बाद 7.41 की जगह 1.50. और पट्टा पूरे रास्ते continuations की तरफ़ खींचता रहता है, इसलिए वह 4.75 पर ठहर जाता है और कभी पहुँचता ही नहीं। दोनों ख़राब rows एक ही जगह ख़त्म होती हैं: मंज़िल reference model तय करता है, शुरुआत सिर्फ़ रफ़्तार तय करती है। (असली sampling में शुरुआत यह भी तय करती है कि आपको कभी अच्छा जवाब दिखेगा भी या नहीं।)
पट्टा हटा दीजिए
beta = 0 says: "Great question! A mutex, or mutual exclusion... [4 paragraphs]"
probability on the padded answer, prompt 0: 0.999
beta = 0.7 mass on answers: 0.999 true helpfulness: 7.68
beta = 0 mass on answers: 1.000 true helpfulness: 3.50
+ 240 comparisons on the drifted policy's own outputs, RM retrained:
corr with hidden score on ALL 24 responses: 0.979 (was 0.708)
RM on prompt 0: good 6.28 padded 4.04 (was good 6.12, padded 7.86)
retrained RM mass on answers: 0.998 true helpfulness: 7.63
β = 0 पर policy सीधे उसी तरफ़ भागती है जिसे reward model ज़्यादा आँकता है: वह निबंध, 99.9% बार, और true helpfulness 7.68 से गिरकर 3.50 हो जाती है। यही reward hacking है, और RM अपने ही data पर कभी ग़लत नहीं था — उससे ऐसी चीज़ के बारे में पूछा गया जो उसने कभी देखी ही नहीं थी। Paper में दो पहरे हैं: KL penalty, और comparisons सिर्फ़ SFT model के नहीं बल्कि train हो रहे models के outputs पर भी इकट्ठा करना, फिर उन सब पर RM को दोबारा train करना — ताकि वह वहीं train हो जहाँ policy सचमुच रहती है। Block का निचला आधा हिस्सा यही सुधार है: भटकी हुई policy के outputs पर 240 नए comparisons, RM सीखता है कि निबंध अच्छे जवाब से ख़राब है (6.28 के मुक़ाबले 4.04), और policy वापस आ जाती है।
Alignment tax, और PPO-ptx
Stage 3 ने model को उसमें बेहतर बनाया जो labelers चाहते थे, और उसमें ख़राब जिसे कोई जाँच नहीं रहा था: RLHF के बाद SQuAD, DROP, HellaSwag और एक French→English translation task जैसे सार्वजनिक benchmarks पर scores गिर गए। Paper इसे alignment tax कहता है। यह फिर से distribution shift है — 31k API-जैसे prompts पर अभ्यास कीजिए और आप उससे भटक जाते हैं जो pretraining corpus ने सिखाया था, पट्टे पर रहते हुए भी। इलाज γ वाला term है: PPO चलाते हुए ही pretraining data के batches पर सामान्य next-token gradient steps लेते रहिए। वे इसे PPO-ptx कहते हैं, और यह labeler preference में बहुत कम क़ीमत पर खोया हुआ ज़्यादातर benchmark performance वापस दिला देता है। Programmer वाला अनुवाद: refactor करते समय पुराना regression suite चलता रहने दीजिए। यह script में नहीं है क्योंकि toy के पास मिलाने को कोई pretraining corpus ही नहीं; यह बस एक और loss term है, जोड़ा हुआ, और दिलचस्प बात यह है कि वह वहाँ है क्यों।
The code
NumPy for the toy world and every measurement. PyTorch, CPU only, for the three training loops — each stage is a training objective, and hand-written backprop would bury that. No function longer than 15 lines.
Download the full script — about 230 lines including the toy world, plus six runnable demos. Then:
python3 instructgpt_from_scratch.py
# STAGE 1 -- SFT (supervised fine-tuning). Section 3.5 of the paper.
def sft(base_logits, demos, steps=60, lr=1.0):
"""demos[p] = indices of the responses a labeler wrote for prompt p."""
theta = torch.tensor(base_logits, requires_grad=True)
opt = torch.optim.SGD([theta], lr=lr)
prompts = torch.tensor([p for p, ds in enumerate(demos) for _ in ds])
targets = torch.tensor([d for ds in demos for d in ds])
for _ in range(steps):
logp = torch.log_softmax(theta, dim=-1)
loss = -logp[prompts, targets].mean() # cross-entropy on the demos
opt.zero_grad(); loss.backward(); opt.step()
return theta.detach().numpy()
# STAGE 2 -- reward model from comparisons. Section 3.5 of the paper.
def labeler(p, a, b, rng):
"""One human, one pair. Returns (winner, loser). Not perfectly reliable."""
p_a_wins = 1 / (1 + np.exp(-(HIDDEN[p, a] - HIDDEN[p, b]) / LABELER_TEMP))
return (a, b) if rng.random() < p_a_wins else (b, a)
def collect_comparisons(sample_logits, n, rng):
"""Sample two DISTINCT responses from the given policy, ask a labeler.
Which policy you sample from is the whole distribution-shift story."""
probs, out = softmax(sample_logits), []
for _ in range(n):
p = rng.integers(N_PROMPTS)
a, b = rng.choice(N_RESP, size=2, replace=False, p=probs[p])
out.append((p, *labeler(p, a, b, rng)))
return out
def train_reward_model(comparisons, steps=400, lr=0.1):
"""Linear reward on FEATURES. Returns a (prompt, response) reward table."""
F = torch.tensor(FEATURES)
w = torch.zeros(F.shape[-1], requires_grad=True)
b = torch.zeros(1, requires_grad=True)
opt = torch.optim.Adam([w, b], lr=lr)
ps, ws, ls = (torch.tensor(c) for c in zip(*comparisons))
for _ in range(steps):
r = F @ w + b # reward of every candidate
margin = r[ps, ws] - r[ps, ls] # winner minus loser
loss = -torch.nn.functional.logsigmoid(margin).mean() # Bradley-Terry
opt.zero_grad(); loss.backward(); opt.step()
return (F @ w + b).detach().numpy()
# STAGE 3 -- RL against the reward model, on a KL leash. Section 3.5.
def rlhf(init_logits, ref_logits, reward, beta, steps=300, lr=0.1):
"""Returns the whole trajectory of logits, so demos can watch it move."""
theta = torch.tensor(init_logits, requires_grad=True)
ref_logp = torch.log_softmax(torch.tensor(ref_logits), dim=-1)
R = torch.tensor(reward)
opt = torch.optim.Adam([theta], lr=lr)
history = [init_logits.copy()]
for _ in range(steps):
logp = torch.log_softmax(theta, dim=-1)
p = logp.exp()
expected_reward = (p * R).sum(-1) # what the RM thinks
leash = (p * (logp - ref_logp)).sum(-1) # KL(pi || pi_ref)
objective = (expected_reward - beta * leash).mean()
opt.zero_grad(); (-objective).backward(); opt.step()
history.append(theta.detach().numpy().copy())
return np.stack(history)
Now break it
This is the part that actually makes it stick. Reading won't do it. You debug for a living — reverse-engineering from broken behaviour is your native mode. Every row is a one-line change; the script already prints the before-and-after.
| Break this | Watch for |
|---|---|
Skip SFT: in demo_4, call rlhf(BASE_LOGITS, BASE_LOGITS, …) | RL starts from a completion engine. Helpfulness 1.50 at step 10 instead of 7.41, never reaches 7.0, settles at 4.75 instead of 7.68. |
Wrong leash: rlhf(STAGES["sft"], BASE_LOGITS, …) | Same 4.75 as skipping SFT. The leash pulls toward continuations; the reference model decides where you end up. |
In demo_6, retrain the RM without the fresh comparisons: drop + fresh | The "was" and "now" numbers become identical — 0.708 and 0.708, padded still 7.86. An RM can't learn about outputs it never sees; that's why the paper collected comparisons on the policy's own samples. |
beta=0.0 in demo_4 | Reward hacking. The policy puts 0.999 on the "Great question!" essay and helpfulness falls to 3.50. The RLHF page has the general case. |
LABELER_TEMP = 8.0 | Labelers agree 0.54 of the time. The good-minus-terse reward gap goes 1.52 → 0.09 and RL on that RM lands at 6.60 instead of 7.68. The paper's real labelers: about 73%. |
On the maths anxiety
There are four objects in this paper and you've met all of them. Cross-entropy is −log of the probability you gave the right answer. The Bradley-Terry loss is a sigmoid of a difference. An expectation is a weighted average. KL divergence is the expected log-ratio of two distributions — "how surprised would the SFT model be by what the new policy says" — and it's zero when they're the same model.
What I skipped is PPO's clipped objective and the value function, because they're machinery: they estimate the gradient of the formula above from a handful of samples without blowing up. You don't derive them to use them, any more than you derive Adam. The RLHF page goes through them; the GPT page is what stage 0 actually is. What an interview will probe is the data: why comparisons instead of ratings, why you compare the policy's own outputs, what the KL is for. All of that is in the six demos.
Where to go next
This page gave you three stages, each fixing one problem, on a toy you can break. Two other people fill in what I skipped.
-
Training language models to follow instructions with human feedback
Read §3.5 with the script open — SFT, RM and RL are one subsection each and map onto the three functions above. Table 6 has the dataset sizes, Figure 1 the 1.3B-beats-175B result, §4.2 the alignment tax.
-
Illustrating Reinforcement Learning from Human Feedback
The three stages as pictures, and the PPO loop I replaced with an exact expectation drawn as it actually runs: sample, score, penalise, update.
-
RLHF: Reinforcement Learning from Human Feedback
What each stage costs in data and compute, why comparisons beat ratings, what the labelers' instructions looked like — the paper as a data operation rather than a training loop.