PPO e TRPO: come si fa policy optimization senza far esplodere la policy
REINFORCE e A2C convergono in teoria e divergono in pratica: prima o poi una update troppo grande rovina la policy e il training collassa. Trust Region Policy Optimization (Schulman 2015) risolve il problema con vincoli KL e natural gradient, formalmente eleganti ma costosi. Proximal Policy Optimization (Schulman 2017) ottiene gran parte dello stesso risultato con un trucco di clipping che si scrive in dieci righe. Da quel trucco passa il fine-tuning di InstructGPT, ChatGPT, Claude, Llama Instruct e in generale il workhorse del reinforcement learning from human feedback.
Perché questo capitolo
Sezione intitolata “Perché questo capitolo”I capitoli precedenti hanno fissato il policy gradient (126) e l’architettura actor-critic (127) come paradigma standard del deep reinforcement learning moderno. Resta una lacuna pratica grossa: gli algoritmi visti finora — REINFORCE, A2C, A3C — funzionano in teoria ma sono fragili in pratica. Chiunque abbia provato a girare un policy gradient su un ambiente non-toy lo sa: il training procede stabile per ore, poi una update rovina la policy, le successive raccolgono rollout pessimi, il critic stima male, e il reward crolla a zero. Su Mujoco si vede come collassi improvvisi. Su LLM si vede come una completion che da utile diventa nonsense in poche iterate PPO.
Il problema ha un nome operativo: la dimensione dello step in spazio delle policy. Anche con learning rate piccolo, lo stesso passo nello spazio dei parametri può produrre cambi enormi nello spazio delle distribuzioni quando il gradiente è rumoroso o l’advantage ha varianza alta. Trust Region Policy Optimization e Proximal Policy Optimization sono la risposta pulita a questa domanda: limita esplicitamente il movimento della policy, non solo il movimento dei parametri.
Tre motivi per dedicargli un capitolo intero. Il primo è di filiazione: la catena Kakade-Langford 2002 (Conservative Policy Iteration con monotonic improvement bound) -> Schulman 2015 (TRPO con KL constraint, natural gradient, conjugate gradient) -> Schulman 2017 (PPO con clipped surrogate) -> Ouyang 2022 (PPO RLHF di InstructGPT) -> Shao 2024 (GRPO di DeepSeek) è una linea continua di vent’anni in cui ogni passo semplifica il precedente mantenendone la garanzia operativa. Il secondo è strategico: PPO è l’algoritmo dietro la fase 3 di ogni RLHF di produzione fra 2020 e 2026. Capirlo nel dettaglio è capire come si fine-tuna un LLM con feedback senza farlo divergere. Il terzo è tecnico: gli algoritmi successivi (DPO, GRPO, RLAIF) si capiscono solo a partire da PPO; non sono alternative astratte, sono semplificazioni o riformulazioni di problemi specifici di PPO.
La promessa esecutiva: alla fine si saprà derivare il bound di improvement, leggere il vincolo trust region di TRPO, capire perché clip è un’approssimazione, scrivere PPO minimal in PyTorch, leggere il loop RLHF di InstructGPT separando reward-from-RM, KL-against-reference e advantage GAE, ed elencare con cognizione i dieci modi in cui PPO si rompe in pratica.
Contesto
Sezione intitolata “Contesto”Il grafo concettuale ha quattro radici teoriche e tre punti di confluenza pratici.
Conservative Policy Iteration, 2002. Sham Kakade (allora Gatsby Computational Neuroscience Unit, Londra; oggi Microsoft Research e University of Washington) e John Langford (allora IBM, oggi Microsoft Research New York) pubblicano “Approximately Optimal Approximate Reinforcement Learning” (ICML 2002). Il contributo centrale è il monotonic improvement bound: dato l’objective e una nuova policy , vale la disuguaglianza
con il surrogate objective (l’advantage della nuova policy stimato sulla distribuzione delle vecchie traiettorie), la massima total variation distance fra e , e una costante che dipende da e dal range delle advantage. TEOREMA: se è piccolo, è una lower bound certificata su . Quindi è lecito ottimizzare il surrogate localmente: si guadagna almeno quanto il bound dice, fintanto che ci si muove poco. Questo è il punto teorico cruciale; tutto il resto è ingegneria.
Trust Region Policy Optimization, 2015. John Schulman (allora dottorando Berkeley con Pieter Abbeel, oggi cofondatore OpenAI), Sergey Levine, Pieter Abbeel, Michael Jordan, Philipp Moritz pubblicano “Trust Region Policy Optimization” (ICML 2015, arXiv:1502.05477). Tre mosse: sostituiscono con la KL divergence (lower-bounded da via Pinsker, e con derivate seconde trattabili); formulano un problema vincolato s.t. ; lo risolvono via natural gradient (Amari 1998) calcolato con conjugate gradient + Hessian-vector products, seguito da backtracking line search per garantire che il vincolo sia rispettato sul KL effettivo (non solo sull’approssimazione quadratica).
Generalized Advantage Estimation, 2016. Schulman, Moritz, Levine, Jordan, Abbeel pubblicano “High-Dimensional Continuous Control Using Generalized Advantage Estimation” (ICLR 2016, arXiv:1506.02438), introducendo GAE: uno stimatore dell’advantage parametrizzato da che interpola fra TD a un passo (lambda=0, max bias min varianza) e Monte Carlo (lambda=1, zero bias max varianza). GAE è usato in pratica con tutti gli algoritmi della famiglia PPO/TRPO; lo riprendiamo nella meccanica.
Proximal Policy Optimization, 2017. John Schulman, Filip Wolski, Prafulla Dhariwal, Alec Radford, Oleg Klimov pubblicano “Proximal Policy Optimization Algorithms” (arXiv:1707.06347). Due varianti: PPO-Penalty (KL come termine penalty con adattivo) e PPO-Clip (clipped surrogate ). La seconda diventa di gran lunga la dominante. Il paper non ha una prova rigorosa di monotonic improvement; la motivazione è euristica e EQUIVALENZA argomentabile: è un’approssimazione pratica del trust region di TRPO. Il punto del paper è empirico: meno codice, più veloce, performance equivalente o migliore.
Innesto LLM, 2020-2022. Nisan Stiennon e colleghi OpenAI in “Learning to summarize from human feedback” (NeurIPS 2020, arXiv:2009.01325) applicano PPO a un task di sintesi con reward model addestrato su preference umane. Long Ouyang e colleghi in “Training language models to follow instructions with human feedback” (NeurIPS 2022, arXiv:2203.02155) generalizzano l’approccio a instruction following producendo InstructGPT, da cui ChatGPT. Yuntao Bai e colleghi Anthropic in “Training a Helpful and Harmless Assistant with RLHF” (arXiv:2204.05862) descrivono varianti pratiche per gli assistenti di Anthropic. Da qui PPO entra nel mainstream LLM. Vedi chatgpt-2022 per la cornice storica e instruction-rlhf-era per l’adozione standard.
Code-level optimizations, 2020. Logan Engstrom, Andrew Ilyas e colleghi (MIT-Madry lab) in “Implementation Matters in Deep Policy Gradients” (ICLR 2020, arXiv:2005.12729) mostrano che gran parte del guadagno PPO sopra TRPO viene da dettagli implementativi (advantage normalization, value clipping, learning rate annealing, gradient clipping, orthogonal init, observation normalization) e non dal clipping in sé. Marcin Andrychowicz e colleghi (Google Brain) in “What Matters in On-Policy Reinforcement Learning?” (ICLR 2021, arXiv:2006.05990) confermano con uno studio empirico su 250.000+ run. La conclusione operativa: PPO non è un algoritmo, è un pacchetto di una dozzina di scelte implementative attorno a un objective semplice.
Anti-pattern recente, 2024. Zhihong Shao e DeepSeek-AI in “DeepSeekMath” (arXiv:2402.03300) introducono GRPO (Group Relative Policy Optimization). Per ogni prompt si campionano completion (tipicamente 8-64), il reward model le valuta tutte, e l’advantage di ciascuna è calcolato come relativo al gruppo. Niente value head, niente critic parametrico. DeepSeek-R1 (2025) mostra che funziona su reasoning di scala. Il debate al 2026 è aperto: in setting LLM con gruppi grandi, il critic forse è ridondante. Per noi qui restiamo su PPO classico; GRPO e DPO (Rafailov 2023) sono cenni in chiusura.
L’intuizione
Sezione intitolata “L’intuizione”Tre angoli prima del formalismo. I primi due sono didattici e diversi nel modo in cui inquadrano l’idea; il terzo è la lettura pratica in cornice LLM.
Angolo 1 — La trust region come safety net
Sezione intitolata “Angolo 1 — La trust region come safety net”ANALOGIA, didattica e non causale. Immagina di star camminando in montagna in mezzo alla nebbia. Hai una mappa imprecisa che ti dice in quale direzione il pendio scende. Puoi farti guidare dalla mappa, ma non oltre un certo raggio: la mappa è affidabile solo nei dintorni del punto in cui sei. Se ti fidi e fai un passo lungo, rischi di camminare oltre la zona dove la mappa è valida e finire in un dirupo che la mappa non mostrava.
Questa è la trust region. La mappa è il surrogate objective : ti dice come l’advantage cambia nei dintorni della policy attuale, calcolata sulle traiettorie raccolte con essa. Il “raggio di affidabilità” è la distanza KL fra e . Kakade e Langford ti dicono: “fintanto che è piccolo, ti garantisco che non cadi in nessun dirupo: l’objective vero è almeno il surrogate meno una correzione che ho calcolato per te”. Schulman dice: “ottimizziamo il surrogate sotto la condizione che stia sotto un budget ”. Marca la classe: ANALOGIA, somiglianza didattica.
L’asimmetria importante è che la mappa scade in un solo modo: muoversi troppo lontano. Muoversi poco non aiuta ma non danneggia. Per questo “trust region” è un vincolo unilaterale: limita lo step massimo, non ne richiede un minimo.
Angolo 2 — Il clip come scorciatoia ingegneristica
Sezione intitolata “Angolo 2 — Il clip come scorciatoia ingegneristica”Risolvere il problema vincolato di TRPO costa: serve calcolare la matrice di Fisher, fare conjugate gradient, fare line search. Per reti grandi e dataset grandi è caro. Schulman 2017 si chiede: posso ottenere lo stesso effetto operativo con un trucco più grossolano?
L’osservazione chiave è che il policy gradient con importance sampling guarda al probability ratio . Per , . Quando si allontana, si allontana da 1. Massimizzare l’objective REINFORCE con importance sampling è massimizzare . Se per un’azione con il gradiente spinge a crescere senza freno, finiamo a , lontano dalla regione di validità.
Idea: quando l’incentivo a far crescere è troppo, azzeriamo il gradiente. Concretamente, se con (sto già aumentando troppo la probabilita’ di un’azione buona), uso il valore clipped al posto di nell’objective: il gradiente rispetto a diventa zero. Symmetricamente, se con (sto già diminuendo troppo la probabilita’ di un’azione cattiva), uso e gradiente zero.
Questo non è una trust region rigorosa: non garantisce monotonic improvement, non è una proiezione su un vincolo KL. E’ un freno applicato nelle due direzioni in cui un naive policy gradient farebbe danno. Il prezzo da pagare è che la teoria sparisce; il guadagno è che in cinque righe di PyTorch hai un algoritmo che funziona quasi sempre. EQUIVALENZA argomentabile (e così la chiama Schulman 2017 nel paper): approssimazione di TRPO trust region. Non c’è un teorema rigoroso. C’è un design intentale.
Angolo 3 — Perché funziona su LLM (e perché a volte no)
Sezione intitolata “Angolo 3 — Perché funziona su LLM (e perché a volte no)”In RLHF, la “policy” è un LLM che genera token autoregressivamente. Una “azione” è un token; uno “stato” è il context fino a quel token. Una “traiettoria” è una completion intera (centinaia o migliaia di token). Il reward è valutato sul completion finale dal reward model.
Il problema operativo è che la policy (l’LLM) ha molti miliardi di parametri. Uno step di update spostera’ la distribuzione di output in modo ampiamente non-uniforme: alcuni token spostano molto, altri poco. Senza un vincolo che limiti lo spostamento, uno step troppo aggressivo distrugge il modello come language model — diventa nonsense, ripete, collassa su pattern. PPO fa due cose contemporaneamente in RLHF:
- Clipping sui token-level ratios: per ogni token della completion, si calcola e si applica il clip. Se la policy spinge troppo su un singolo token, il clip ferma il gradiente.
- KL penalty contro una reference policy (tipicamente il modello SFT congelato): si aggiunge alla reward un termine , che spinge la policy a non distanziarsi dal SFT.
Le due protezioni operano su scale diverse. Il clip è locale per-step (limita ogni update); la KL contro è globale per-trajectory (limita la deriva totale). Insieme tengono il modello vicino al territorio dove era prima. EQUIVALENZA argomentabile (Ouyang 2022): RLHF reward = RM - KL è equivalente a un actor-critic KL-regularized con reward shaping; la policy ottima sotto questo objective ha forma chiusa — ed è esattamente questo il punto da cui Rafailov 2023 deriva DPO.
La meccanica
Sezione intitolata “La meccanica”Dalla teoria di Kakade-Langford alla riga di codice di PPO. Tre passaggi.
Da CPI al monotonic improvement bound
Sezione intitolata “Da CPI al monotonic improvement bound”Definiamo l’objective del policy:
Dato e una candidata , vale l’identità di policy difference (Kakade-Langford 2002):
dove è la distribuzione discounted degli stati sotto e l’advantage di . Il problema è che dipende dalla policy nuova, che non abbiamo ancora: non possiamo campionare da li’.
Il surrogate sostituisce con (la distribuzione delle traiettorie raccolte con la policy vecchia, che abbiamo):
L’errore di approssimazione è bounded da fra e . Kakade-Langford 2002 (TEOREMA): esiste tale che
dove . Il bound dice: se ottimizzo entro un raggio piccolo, l’objective vero migliora almeno tanto quanto meno una correzione quadratica.
TRPO: dal bound al vincolo KL
Sezione intitolata “TRPO: dal bound al vincolo KL”TRPO (Schulman 2015) sostituisce con via Pinsker () e formula il problema vincolato:
con tipicamente .
Per risolvere il problema, TRPO fa quattro mosse:
- Stima campionaria di via rollout.
- Approssimazione quadratica del vincolo: , dove è la Fisher Information Matrix della policy.
- Soluzione analitica del problema linearizzato + quadratico via Lagrangiano:
La direzione è il natural gradient (Amari 1998): la steepest ascent direction nella metrica indotta da . 4. Conjugate gradient per evitare di calcolare esplicitamente. Si risolve iterativamente, valutando via Hessian-vector products (dieci-venti iterazioni di CG, ciascuna costa una forward-backward pass aggiuntiva). 5. Backtracking line search: lo step proposto può violare il vincolo effettivo (l’approssimazione quadratica non è perfetta) o non migliorare . Si tenta con , accettando il primo tale che E migliorato. Se nessun funziona, si salta l’update.
TRPO è rigoroso e funziona, ma è costoso: ogni update richiede CG con HVP, line search, e mantenere la struttura del backbone. Per reti molto grandi (LLM da molti miliardi di parametri) il costo del Fisher-vector product e della line search diventa proibitivo.
PPO-Clip: il trucco
Sezione intitolata “PPO-Clip: il trucco”PPO (Schulman 2017) parte dallo stesso surrogate ma sostituisce il vincolo con un freno integrato nell’objective. Definiamo
Il clipped surrogate è:
con tipicamente.
Lettura per casi:
- , : , gradiente normale, la policy aumenta la probabilita’ dell’azione buona.
- , : , gradiente zero. Si è già aumentata abbastanza, freno.
- , : , gradiente normale, la policy diminuisce la probabilita’ dell’azione cattiva.
- , : (perché con sceglie il più negativo, cioè il non-clipped), gradiente normale.
Il design è intenzionale: il clip ferma il gradiente solo quando lo step si è già allontanato troppo nella direzione “incoraggiata”, non quando si sta tornando verso . Le due direzioni “pericolose” sono con e con , ed entrambe vengono freni.
L’objective totale di PPO include anche una loss del value e un bonus di entropia:
con la value loss del critic, l’entropia della policy (bonus per esplorazione), , .
GAE: come si calcola
Sezione intitolata “GAE: come si calcola AtA_tAt”L’advantage in PPO si stima sempre via Generalized Advantage Estimation (Schulman 2016). Sia il TD residual a un passo. Allora
Casi limite: da’ (massimo bias dal critic, minima varianza); da’ (Monte Carlo, zero bias dal critic, massima varianza). In pratica è il tradeoff standard.
Calcolo backward su un rollout:
A[T] = 0for t in reversed(range(T)): delta = r[t] + gamma * V[t+1] * (1 - done[t]) - V[t] A[t] = delta + gamma * lam * A[t+1] * (1 - done[t])Il return-to-go target per il critic si ottiene come .
Pseudocodice PPO completo
Sezione intitolata “Pseudocodice PPO completo”import torchimport torch.nn as nnimport torch.nn.functional as F
class ActorCritic(nn.Module): def __init__(self, obs_dim, act_dim, hidden=64): super().__init__() self.shared = nn.Sequential( nn.Linear(obs_dim, hidden), nn.Tanh(), nn.Linear(hidden, hidden), nn.Tanh()) self.policy_head = nn.Linear(hidden, act_dim) self.value_head = nn.Linear(hidden, 1)
def forward(self, obs): h = self.shared(obs) return self.policy_head(h), self.value_head(h).squeeze(-1)
def compute_gae(rewards, values, dones, gamma=0.99, lam=0.95): T = len(rewards) A = torch.zeros(T) last = 0.0 for t in reversed(range(T)): next_v = values[t+1] if t+1 < T else 0.0 delta = rewards[t] + gamma * next_v * (1 - dones[t]) - values[t] last = delta + gamma * lam * last * (1 - dones[t]) A[t] = last returns = A + values[:T] return A, returns
def ppo_update(model, optimizer, obs, actions, old_logp, advantages, returns, clip_eps=0.2, vf_coef=0.5, ent_coef=0.01, n_epochs=4, mb_size=64): advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8) N = len(obs) for _ in range(n_epochs): idx = torch.randperm(N) for start in range(0, N, mb_size): mb = idx[start:start+mb_size] logits, values = model(obs[mb]) dist = torch.distributions.Categorical(logits=logits) new_logp = dist.log_prob(actions[mb]) entropy = dist.entropy().mean()
ratio = torch.exp(new_logp - old_logp[mb]) unclipped = ratio * advantages[mb] clipped = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps) * advantages[mb] policy_loss = -torch.min(unclipped, clipped).mean()
value_loss = F.mse_loss(values, returns[mb]) loss = policy_loss + vf_coef * value_loss - ent_coef * entropy
optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 0.5) optimizer.step()Il loop esterno raccoglie rollout, calcola GAE, normalizza advantage, fa epoch di update con minibatch shuffle. Su CartPole-v1, in step questa implementazione minimal arriva a reward stabile.
flowchart TD
A[Raccoglie rollout con π_old] --> B[Calcola V values]
B --> C[Calcola GAE advantages]
C --> D[Normalizza advantages]
D --> E[Per E epoch]
E --> F[Shuffle minibatch]
F --> G[Calcola new logp, ratio r = π_θ / π_old]
G --> H[Calcola L^CLIP = min unclipped, clipped]
H --> I[Calcola value loss + entropy bonus]
I --> J[Backprop, gradient clip, step Adam]
J --> K{epoch finito?}
K -->|no| F
K -->|sì| L[Scarta rollout, ripeti]
Figura 5 — pipeline di un PPO update step — rollout con π_old, calcolo V e GAE, normalizzazione advantage, epoch loop su minibatch con clipped surrogate, value loss ed entropy bonus, gradient clip e step Adam
Tre esempi eterogenei: numerico, codice, scenario reale RLHF.
Esempio 1 — Numerico: come opera il clip su una transition
Sezione intitolata “Esempio 1 — Numerico: come opera il clip su una transition”Stato: il critic stima . L’agente sceglie azione . La completion produce reward cumulativo che porta a . Quindi (advantage positivo: l’azione è stata buona).
Sotto la policy vecchia . Dopo qualche step di update (anche all’interno della stessa epoch), . Quindi .
Con :
- Termine non clipped: .
- Clip: .
- Termine clipped: .
- .
L’objective per questa transition vale e il gradiente rispetto a è zero (perché il min sceglie il termine clipped, che non dipende da ). PPO ha frenato.
Ora cambia l’esempio: (azione cattiva). Con (la policy ha già diminuito molto la probabilita’):
- Non clipped: .
- Clip: .
- Clipped: .
- , cioè il clipped.
Anche qui usa il termine clipped, gradiente zero. PPO ha frenato. Nota: se invece (la policy non ha ancora abbastanza diminuito), il clip non si attiva (), termine clipped , non clipped , gradiente normale.
Il design produce un effetto a “morsa” attorno a : per ogni transition, fintanto che resta dentro il gradiente è attivo; appena esce nella direzione coerente con il segno di , freno.
Esempio 2 — Codice: PPO su CartPole
Sezione intitolata “Esempio 2 — Codice: PPO su CartPole”Lo pseudocodice della sezione precedente, integrato con il rollout loop:
import gymnasium as gymimport torchimport torch.optim as optim
env = gym.make("CartPole-v1")obs_dim = env.observation_space.shape[0]act_dim = env.action_space.n
model = ActorCritic(obs_dim, act_dim)optimizer = optim.Adam(model.parameters(), lr=3e-4)
n_steps = 2048total_steps = 0target_steps = 500_000
while total_steps < target_steps: obs_buf, act_buf, logp_buf = [], [], [] rew_buf, val_buf, done_buf = [], [], []
obs, _ = env.reset() for t in range(n_steps): obs_t = torch.tensor(obs, dtype=torch.float32) with torch.no_grad(): logits, value = model(obs_t.unsqueeze(0)) dist = torch.distributions.Categorical(logits=logits) action = dist.sample() logp = dist.log_prob(action)
next_obs, reward, terminated, truncated, _ = env.step(action.item()) done = terminated or truncated
obs_buf.append(obs_t) act_buf.append(action.squeeze()) logp_buf.append(logp.squeeze()) rew_buf.append(reward) val_buf.append(value.squeeze()) done_buf.append(float(done))
obs = next_obs if not done else env.reset()[0] total_steps += 1
obs = torch.stack(obs_buf) actions = torch.stack(act_buf) old_logp = torch.stack(logp_buf).detach() values = torch.stack(val_buf).detach() rewards = torch.tensor(rew_buf, dtype=torch.float32) dones = torch.tensor(done_buf, dtype=torch.float32)
advantages, returns = compute_gae(rewards, values, dones) ppo_update(model, optimizer, obs, actions, old_logp, advantages, returns)Roba mancante per produzione: vectorized envs (8-16 in parallelo via gym.vector), observation normalization running, learning rate annealing lineare, value clipping, reward scaling. Vedi CleanRL per implementazione completa con tutti i 37 dettagli di Huang 2022.
Esempio 3 — Scenario reale: RLHF di InstructGPT
Sezione intitolata “Esempio 3 — Scenario reale: RLHF di InstructGPT”Pseudocodice del loop PPO della fase 3 di InstructGPT (Ouyang 2022). Tre modelli in memoria contemporaneamente:
# Setup: tutti inizializzati da pi_SFT (modello supervised fine-tuned)pi_theta = LLM_with_value_head(init_from=pi_SFT) # actor + criticpi_ref = LLM(init_from=pi_SFT, frozen=True) # reference per KLRM = RewardModel(frozen=True) # reward model congelato
beta = 0.02 # KL coefficientclip_eps = 0.2 # PPO clipn_epochs = 4mini_batch = 256
for iteration in range(N_iterations): # 1. Sample rollouts prompts = sample_prompts(batch_size=512) completions = [] for p in prompts: with no_grad(): tokens, logp_per_token = pi_theta.generate(p, sample=True, max_new=256) completions.append((p, tokens, logp_per_token))
# 2. Compute per-token reward rollouts = [] for p, tokens, old_logp in completions: with no_grad(): r_final = RM.score(p + tokens) # scalar reward, only at end ref_logp = pi_ref.log_prob(p, tokens) # per-token values = pi_theta.value_head(p, tokens) # per-token V
# Token-level reward = -beta * KL per token + RM at last token kl_per_token = old_logp - ref_logp # log ratio pi_theta_old / pi_ref rewards = -beta * kl_per_token rewards[-1] += r_final
# GAE on token sequence (gamma=1, lambda=0.95) advantages, returns = compute_gae(rewards, values, dones=[0]*(len(tokens)-1)+[1], gamma=1.0, lam=0.95) rollouts.append((p, tokens, old_logp, advantages, returns))
# 3. PPO update with multi-epoch + minibatch flatten_data = flatten_rollouts(rollouts) advantages_norm = whiten(flatten_data.advantages)
for epoch in range(n_epochs): for mb in shuffle_minibatches(flatten_data, mini_batch): new_logp = pi_theta.log_prob(mb.prompts, mb.tokens) new_values = pi_theta.value_head(mb.prompts, mb.tokens)
ratio = exp(new_logp - mb.old_logp) policy_loss = -min(ratio * mb.adv_norm, clip(ratio, 1-clip_eps, 1+clip_eps) * mb.adv_norm).mean() # Value clipping a la PPO2 v_clipped = mb.old_values + clip(new_values - mb.old_values, -0.2, 0.2) v_loss = max(mse(new_values, mb.returns), mse(v_clipped, mb.returns)).mean()
loss = policy_loss + 0.5 * v_loss loss.backward() clip_grad_norm(pi_theta.parameters(), 1.0) optimizer.step()
# 4. Adaptive beta (optional, InstructGPT variant) measured_kl = mean(kl_per_token across batch) if measured_kl > 1.5 * target_kl: beta *= 1.5 elif measured_kl < target_kl / 1.5: beta /= 1.5Punti chiave:
- La reward shaping mette la KL per-token ad ogni step e il reward del RM solo all’ultimo token. Questo distribuisce il segnale: penalizza un singolo token che si discosta troppo dal reference, e premia la completion globale.
- Two heads sul backbone: il policy head (i logits sul vocabolario, ereditato dal pretraining) e il value head (proiezione scalare, freshly init). La value head si aggiorna insieme al policy.
- Reference policy congelata serve da ancora KL: assicura che la policy non si distacchi troppo dal punto di partenza supervised. Senza questa ancora, PPO degrada il modello catastroficamente.
- GAE su token con (no discount: tutta la completion ha lo stesso peso) e .
- Adaptive beta: tiene la KL effettiva vicina a un target. Senza, beta troppo basso lascia diverge; beta troppo alto blocca l’apprendimento.
flowchart TD
A[Raccoglie rollout con π_old] --> B[Calcola V values]
B --> C[Calcola GAE advantages]
C --> D[Normalizza advantages]
D --> E[Per E epoch]
E --> F[Shuffle minibatch]
F --> G[Calcola new logp, ratio r = π_θ / π_old]
G --> H[Calcola L^CLIP = min unclipped, clipped]
H --> I[Calcola value loss + entropy bonus]
I --> J[Backprop, gradient clip, step Adam]
J --> K{epoch finito?}
K -->|no| F
K -->|sì| L[Scarta rollout, ripeti]
Figura 5 — diagramma architetturale dell’RLHF loop: prompt -> pi_theta (actor) genera tokens -> RM scoring -> pi_ref calcola KL per-token -> reward composto -> GAE -> PPO clipped update; con freccia di feedback
flowchart TD
A[Raccoglie rollout con π_old] --> B[Calcola V values]
B --> C[Calcola GAE advantages]
C --> D[Normalizza advantages]
D --> E[Per E epoch]
E --> F[Shuffle minibatch]
F --> G[Calcola new logp, ratio r = π_θ / π_old]
G --> H[Calcola L^CLIP = min unclipped, clipped]
H --> I[Calcola value loss + entropy bonus]
I --> J[Backprop, gradient clip, step Adam]
J --> K{epoch finito?}
K -->|no| F
K -->|sì| L[Scarta rollout, ripeti]
Figura 5 — sequence diagram in mermaid: actor sample, reward+kl compute, gae compute, ppo multi-epoch update, adaptive beta
sequenceDiagram
participant Actor as Actor π_θ
participant Ref as Reference π_ref (frozen)
participant RM as Reward Model (frozen)
participant PPO as PPO Updater
loop iterazione
Actor->>Actor: campiona prompt → genera completion
Actor->>Ref: per-token logprob
Ref-->>Actor: per-token KL = log π_θ_old / π_ref
Actor->>RM: completion
RM-->>Actor: scalar reward
Actor->>PPO: rollout + reward shaping
PPO->>PPO: GAE + clipped surrogate + value + entropy
PPO->>Actor: update θ (e value head)
PPO->>PPO: adatta β se KL drift
end
Figura 6 — sequenza temporale degli scambi tra Actor π_θ, Reference π_ref, Reward Model e PPO Updater nel loop RLHF, con per-token KL, scalar reward, GAE, clipped surrogate e adattamento di β su KL drift
Applicazioni pratiche
Sezione intitolata “Applicazioni pratiche”PPO è di gran lunga l’algoritmo di policy optimization più usato fra 2017 e 2026. Sintesi dei domini.
RLHF su LLM. InstructGPT, ChatGPT (OpenAI), Claude (Anthropic), Gemini (Google), Llama Instruct (Meta), Mistral Instruct, Qwen Instruct (Alibaba), Yi Chat (01.AI), DeepSeek Chat: tutti hanno usato o usano ancora una variante di PPO RLHF nella fase 3. Le scelte specifiche variano (constitutional AI di Anthropic aggiunge una fase RLAIF; Meta in Llama 3 ha switchato a DPO per la maggior parte; Mistral usa varianti DPO + KTO), ma la pipeline di base è PPO.
Agent fine-tuning. Agenti che fanno tool use, browsing, coding (Claude Code, Cursor, Devin, OpenAI Codex agent) sono spesso fine-tuned con PPO o varianti. Il reward viene da rubric automatiche (test che passano, output corretto) o da LLM-as-judge. La differenza con RLHF testo è che il reward è più programmaticamente disponibile.
Robotica simulata. PPO è lo standard de facto per locomotion e manipulation in simulazione (MuJoCo, Isaac Gym, Genesis). Boston Dynamics, NVIDIA, DeepMind robotics usano varianti PPO per pretraining su rollout massivamente paralleli (GPU envs).
Game-playing. AlphaStar (DeepMind 2019, StarCraft II) usa PPO components (anche se l’architettura è più complessa, con league play). OpenAI Five (Dota 2) è essenzialmente PPO scalato a 256 GPU. AlphaGo successors (AlphaZero, MuZero) usano famiglie diverse (MCTS + neural net, vedi alphago-2016) ma PPO compare in ablation e baseline.
Reasoning RL. DeepSeek-R1 (2025) e successori usano GRPO (variante PPO senza critic) per addestrare reasoning chains via outcome reward. La pipeline è PPO-clip identico a livello di update, modulo la baseline relativa di gruppo. Vedi reasoning-rl (in preparazione) per dettagli sul reward design.
Alternative semplificate emerse 2023-2024:
- DPO (Rafailov 2023) sostituisce intero pipeline RM + PPO con una loss closed-form su preference pair. Adottato da Llama 3 (in parte), Zephyr, Mixtral instruct. Vantaggi: single-stage, no rollout, no critic, stabile. Svantaggi: meno espressivo, no reward shaping iterativo, peggio in dynamic preference.
- GRPO (Shao 2024) sostituisce il critic con baseline di gruppo. Adottato da DeepSeekMath, DeepSeek-R1. Vantaggi: meno memoria (no value head), no critic da tunare. Svantaggi: richiede rollout per prompt (memoria orizzontale invece che verticale).
- IPO, KTO, ORPO (varianti DPO) e RLAIF (Bai 2022 Anthropic): ogni famiglia ottimizza tradeoff diversi.
Una nota di scelta progettuale al 2026: PPO è ancora il default in OpenAI/Anthropic perché la modularita’ del reward model permette reward shaping iterativo (RM aggiornato durante PPO con preference fresh) e perché il pipeline è più espressivo per reward complessi multi-componente (helpfulness + harmlessness + honesty separati). Open source è migrato in larga parte a DPO per costo. La “guerra” PPO vs DPO sara’ nei prossimi anni terreno di benchmarking pratico.
Dove si rompe
Sezione intitolata “Dove si rompe”Sezione lunga perché PPO ha molti modi di fallire in pratica. Decalogo.
1. PPO clipping non garantisce monotonic improvement. A differenza di TRPO (che lo garantisce con line search e KL constraint), PPO-clip è una euristica. Esistono casi patologici in cui il clip non protegge: ad esempio, se il gradiente combinato di una minibatch è grande e la ratio media è dentro ma con varianza alta, l’update può spingere alcuni lontano. APPROFONDIRE: Wang et al. 2020 “Truly PPO” propongono varianti rollback per chiudere il gap.
2. Ratio explosion in long sequences (LLM). Una completion di 1000 token ha 1000 ratios per-token. Anche con KL piccola in media, alcune possono essere fuori dal range in modo concentrato. Il clip per-token aiuta ma non risolve completamente. La soluzione standard è calcolare la ratio per-token e applicare il clip per-token, mai sequence-level (sequence-level esploderebbe). Anche così, su completion molto lunghe (32k+ token) compaiono instabilità.
3. KL coefficient drift. In RLHF con fisso, dopo molte iterate la KL effettiva contro può drift verso valori molto alti (la policy si allontana dal SFT) o molto bassi (la policy non si muove). Soluzione: adaptive (Stiennon 2020): se KL > target, alza ; se KL < target, abbassa. Anche con adaptive , su training molto lunghi serve early stopping se KL eccede una soglia di sicurezza.
4. Reward hacking / Goodhart’s law. Il RM è un proxy del giudizio umano. PPO trova exploit del proxy: verbosity bias (completion più lunghe ricevono reward più alto), sycophancy (concordare con l’user a prescindere), formatting tricks (markdown, bullet points), repetition di frasi-chiave. Il KL contro mitiga ma non elimina. Mitigazioni: RM ensemble, RM aggiornato periodicamente con preference fresh, length penalty esplicita, RM trained su esempi di hacking.
5. Mode collapse / entropy collapse. Senza entropy bonus (e a volte anche con), la policy può collassare su pochi pattern: una sola completion possibile per ogni prompt, perdita di diversità. Inversamente, entropy bonus troppo alto blocca la convergenza. In RLHF su LLM, entropy collapse è meno acuto (il prior linguistico del backbone è forte) ma succede in long-horizon agent training.
6. Implementation matters. Engstrom 2020 e Andrychowicz 2021 lo hanno sancito: la maggior parte dei “PPO non funziona” su Reddit/Stack Overflow è dovuta a un dettaglio implementativo mancante. La lista minima: advantage normalization per minibatch, value loss clipping, learning rate annealing lineare, observation normalization running, gradient clipping a norma 0.5, orthogonal initialization, reward scaling. Senza, PPO sembra non funzionare. Conclusione operativa: per produzione, usare libreria tested (CleanRL, Stable-Baselines3, Spinning Up, trl per RLHF). Implementare PPO da zero solo per studio.
7. Sample efficiency povera. PPO è on-policy: ogni rollout è usato per 3-10 update poi buttato. Off-policy SAC è molto più sample-efficient su robotica continua. Per LLM la sample efficiency è meno cruciale (il rollout costa ma la stabilità compensa); per simulazione robotica costosa, PPO è subottimale rispetto a SAC.
8. Batch size sensitivity. Batch troppo piccolo: gradiente rumoroso, instabilità, clip si attiva troppo (perché la varianza dei singoli ratio è alta). Batch troppo grande: pochi step di update per epoca, slow learning. RLHF tipicamente usa batch di migliaia di prompt per stabilità sulla varianza dei reward. APPROFONDIRE: il rapporto fra batch size e effettivo del clip è studiato in Andrychowicz 2021.
9. Importance sampling variance. La ratio è un importance sampling estimator. La varianza di un IS estimator esplode quando le distribuzioni source e target divergono ( grande). Il clip mitiga (limita gli outlier) ma non risolve la causa. Su training prolungato, se la KL fra e all’interno di un singolo update step diverge, il gradiente diventa rumoroso. Soluzione pratica: ridurre a 3-4 e a un valore moderato.
10. Stato non Markov in agent LLM. Un agent LLM con tool use ha “stato” che include il context window intero (anche tens of thousands di token). Non è Markov in senso classico (lo stato è la storia, non un riassunto sufficiente). PPO assume Markov per la formulazione GAE; in pratica funziona perché il context window è considerato lo stato, ma su orizzonti che eccedono il context (multi-session agent) PPO non scala direttamente. APPROFONDIRE: long-horizon RL su agent LLM è frontiera 2026.
11. Reward model OOD. Il RM è addestrato su una distribuzione di completion (tipicamente quelle di ). Quando PPO sposta verso distribuzioni nuove, il RM è valutato out-of-distribution e i suoi reward diventano inaffidabili. Sintomo classico: il reward sale ma la qualità percepita umana scende. Mitigazione: RM aggiornato periodicamente sulle distribuzioni nuove (RLHF iterativo); KL contro tiene sotto controllo lo spostamento.
12. GRPO suggerisce che il critic forse è superfluo. DeepSeek-R1 (2025) mostra performance comparabili usando solo baseline di gruppo, senza value head. Implicazione: il critic in PPO RLHF, su LLM con gruppi , non aggiunge segnale rispetto a una baseline empirica. Il debate al 2026 è aperto. Possibile che il valore del critic sia situational, non universale.
13. DPO mostra che intero PPO loop può essere closed-form. Rafailov 2023 deriva analiticamente la policy ottima sotto KL-regularized RLHF objective: . Sostituendo nella loss del RM, si ottiene una loss DPO direttamente su preference pair, senza addestrare RM e senza fare PPO. Vantaggio: single-stage, no rollout, no critic. Svantaggio: meno espressivo, statico (la preference dataset è fissato), peggio quando la preference è raccolta iterativamente. PPO sopravvive quando si vuole reward shaping iterativo o reward complessi.
14. PPO RLHF è costoso. Per un LLM 70B, il training PPO richiede contemporaneamente: actor 70B + reference 70B (frozen ma in memoria) + RM (tipicamente più piccolo, 7B) + critic (value head, ~marginal extra) + ottimizzatore states. In totale: hundreds of GB GPU per node, multi-node training, rollout autoregressive sequenziale (latenza alta). DPO e GRPO esistono in parte come risposta a questo costo.
Collegamenti
Sezione intitolata “Collegamenti”- probabilita-base — distribuzioni e attese, base di tutto.
- entropia-cross-entropy — KL divergence, base del trust region e della reference penalty.
- gradienti-intuito — gradient ascent, base degli update.
- discesa-gradiente — Adam, gradient clipping, learning rate.
- softmax-sigmoid — la policy categoriale è un softmax sui logit.
- bias-varianza — il tradeoff in GAE fra e .
- markov-decision-process — l’MDP, ambiente formale per tutti questi algoritmi.
- equazione-bellman — il critic risolve Bellman.
- q-learning — alternativa value-based, no policy parametrica.
- policy-gradient — il punto di partenza, REINFORCE e baseline.
- actor-critic — l’architettura su cui PPO opera.
- rlhf-ppo (in preparazione) — applicazione di PPO al RLHF di LLM nel dettaglio.
- dpo-family (in preparazione) — DPO, IPO, KTO, ORPO come alternative single-stage.
- prm-vs-orm (in preparazione) — process vs outcome reward, rilevante per design del reward in PPO.
Per andare oltre
Sezione intitolata “Per andare oltre”- John Schulman et al. (2017). Proximal Policy Optimization Algorithms. arXiv:1707.06347. Il paper PPO originale, accessibile, incluso pseudocodice.
- John Schulman et al. (2015). Trust Region Policy Optimization. arXiv:1502.05477. Per il fondamento teorico, da leggere insieme a Kakade-Langford 2002.
- Joshua Achiam. Spinning Up in Deep RL. spinningup.openai.com — sezione PPO con derivazione, pseudocodice e riferimenti.
- Logan Engstrom et al. (2020). Implementation Matters in Deep Policy Gradients. arXiv:2005.12729. Da leggere prima di implementare PPO da zero.
- Long Ouyang et al. (2022). Training language models to follow instructions with human feedback (InstructGPT). arXiv:2203.02155. Per la fase RLHF, riferimento canonico.
- Costa Huang et al. (2022). The 37 Implementation Details of Proximal Policy Optimization. ICLR Blog Track. Catalogo esaustivo dei dettagli che fanno la differenza in pratica.