Salta ai contenuti

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.

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.

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 J(π)=Eπ[tγtrt]J(\pi) = \mathbb{E}_\pi[\sum_t \gamma^t r_t] e una nuova policy π\pi', vale la disuguaglianza

J(π)Lπ(π)CDTVmax(ππ)2J(\pi') \geq L_\pi(\pi') - C \cdot D_{TV}^{\max}(\pi \| \pi')^2

con Lπ(π)=J(π)+Esdπ,aπ[Aπ(s,a)]L_\pi(\pi') = J(\pi) + \mathbb{E}_{s \sim d^\pi, a \sim \pi'}[A^\pi(s,a)] il surrogate objective (l’advantage della nuova policy stimato sulla distribuzione delle vecchie traiettorie), DTVmaxD_{TV}^{\max} la massima total variation distance fra π\pi e π\pi', e CC una costante che dipende da γ\gamma e dal range delle advantage. TEOREMA: se DTVmaxD_{TV}^{\max} è piccolo, LπL_\pi è una lower bound certificata su JJ. 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 DTVD_{TV} con la KL divergence DKLD_{KL} (lower-bounded da DTVD_{TV} via Pinsker, e con derivate seconde trattabili); formulano un problema vincolato maxL\max L s.t. DˉKLδ\bar{D}_{KL} \leq \delta; 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 λ[0,1]\lambda \in [0,1] 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 β\beta adattivo) e PPO-Clip (clipped surrogate LCLIPL^{CLIP}). La seconda diventa di gran lunga la dominante. Il paper non ha una prova rigorosa di monotonic improvement; la motivazione è euristica e EQUIVALENZA argomentabile: LCLIPL^{CLIP} è 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 KK completion (tipicamente 8-64), il reward model le valuta tutte, e l’advantage di ciascuna è calcolato come (RiμK)/σK(R_i - \mu_K)/\sigma_K 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.

diagramma di filiazione: CPI 2002, TRPO 2015, GAE 2016, PPO 2017, RLHF Stiennon 2020, InstructGPT 2022, Engstrom 2020, GRPO 2024, DPO 2023; frecce di derivazione e cluster colorati per famiglia (theoretical foundations, deep RL classico, RLHF, simplifications)

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.

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 Lπ(π)L_\pi(\pi'): 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 π\pi e π\pi'. Kakade e Langford ti dicono: “fintanto che DKLD_{KL} è 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 DKLD_{KL} stia sotto un budget δ\delta”. 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 rt(θ)=πθ(atst)/πθold(atst)r_t(\theta) = \pi_\theta(a_t|s_t) / \pi_{\theta_{old}}(a_t|s_t). Per θ=θold\theta = \theta_{old}, rt=1r_t = 1. Quando θ\theta si allontana, rtr_t si allontana da 1. Massimizzare l’objective REINFORCE con importance sampling è massimizzare E[rtAt]\mathbb{E}[r_t \cdot A_t]. Se per un’azione con At>0A_t > 0 il gradiente spinge rtr_t a crescere senza freno, finiamo a rt1r_t \gg 1, lontano dalla regione di validità.

Idea: quando l’incentivo a far crescere rtr_t è troppo, azzeriamo il gradiente. Concretamente, se rt>1+ϵr_t > 1+\epsilon con At>0A_t > 0 (sto già aumentando troppo la probabilita’ di un’azione buona), uso il valore clipped 1+ϵ1+\epsilon al posto di rtr_t nell’objective: il gradiente rispetto a θ\theta diventa zero. Symmetricamente, se rt<1ϵr_t < 1-\epsilon con At<0A_t < 0 (sto già diminuendo troppo la probabilita’ di un’azione cattiva), uso 1ϵ1-\epsilon 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): LCLIPL^{CLIP} \approx 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:

  1. Clipping sui token-level ratios: per ogni token della completion, si calcola rtr_t e si applica il clip. Se la policy spinge troppo su un singolo token, il clip ferma il gradiente.
  2. KL penalty contro una reference policy (tipicamente il modello SFT congelato): si aggiunge alla reward un termine βlog(πθ/πref)-\beta \log(\pi_\theta / \pi_{ref}), 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 πref\pi_{ref} è globale per-trajectory (limita la deriva totale). Insieme tengono il modello vicino al territorio dove era prima. EQUIVALENZA argomentabile (Ouyang 2022): RLHF reward = RM - β\beta KL è equivalente a un actor-critic KL-regularized con reward shaping; la policy ottima sotto questo objective ha forma chiusa ππrefexp(R/β)\pi^* \propto \pi_{ref} \cdot \exp(R/\beta) — ed è esattamente questo il punto da cui Rafailov 2023 deriva DPO.

Dalla teoria di Kakade-Langford alla riga di codice di PPO. Tre passaggi.

Definiamo l’objective del policy:

J(π)=Eτπ[t=0γtrt]J(\pi) = \mathbb{E}_{\tau \sim \pi}\left[\sum_{t=0}^\infty \gamma^t r_t\right]

Dato π\pi e una candidata π\pi', vale l’identità di policy difference (Kakade-Langford 2002):

J(π)J(π)=11γEsdπ,aπ[Aπ(s,a)]J(\pi') - J(\pi) = \frac{1}{1-\gamma} \mathbb{E}_{s \sim d^{\pi'}, a \sim \pi'}[A^\pi(s,a)]

dove dπd^{\pi'} è la distribuzione discounted degli stati sotto π\pi' e AπA^\pi l’advantage di π\pi. Il problema è che dπd^{\pi'} dipende dalla policy nuova, che non abbiamo ancora: non possiamo campionare da li’.

Il surrogate sostituisce dπd^{\pi'} con dπd^\pi (la distribuzione delle traiettorie raccolte con la policy vecchia, che abbiamo):

Lπ(π)=J(π)+11γEsdπ,aπ[Aπ(s,a)]L_\pi(\pi') = J(\pi) + \frac{1}{1-\gamma} \mathbb{E}_{s \sim d^\pi, a \sim \pi'}[A^\pi(s,a)]

L’errore di approssimazione è bounded da DTVD_{TV} fra π\pi e π\pi'. Kakade-Langford 2002 (TEOREMA): esiste C=4ϵγ/(1γ)2C = 4 \epsilon \gamma / (1-\gamma)^2 tale che

J(π)Lπ(π)CDTVmax(ππ)2J(\pi') \geq L_\pi(\pi') - C \cdot D_{TV}^{\max}(\pi \| \pi')^2

dove ϵ=maxs,aAπ(s,a)\epsilon = \max_{s,a} |A^\pi(s,a)|. Il bound dice: se ottimizzo LL entro un raggio DTVD_{TV} piccolo, l’objective vero JJ migliora almeno tanto quanto LL meno una correzione quadratica.

TRPO (Schulman 2015) sostituisce DTVD_{TV} con DKLD_{KL} via Pinsker (DTVDKL/2D_{TV} \leq \sqrt{D_{KL}/2}) e formula il problema vincolato:

maxθEs,aπold[πθ(as)πold(as)Aπold(s,a)]\max_\theta \mathbb{E}_{s, a \sim \pi_{old}}\left[\frac{\pi_\theta(a|s)}{\pi_{old}(a|s)} A^{\pi_{old}}(s,a)\right] s.t.Esdπold[DKL(πold(s)πθ(s))]δ\text{s.t.} \quad \mathbb{E}_{s \sim d^{\pi_{old}}}[D_{KL}(\pi_{old}(\cdot|s) \| \pi_\theta(\cdot|s))] \leq \delta

con δ\delta tipicamente 0.010.01.

Per risolvere il problema, TRPO fa quattro mosse:

  1. Stima campionaria di g=θLθoldg = \nabla_\theta L|_{\theta_{old}} via rollout.
  2. Approssimazione quadratica del vincolo: DKL12(θθold)TF(θθold)D_{KL} \approx \frac{1}{2} (\theta - \theta_{old})^T F (\theta - \theta_{old}), dove F=E[(θlogπ)(θlogπ)T]F = \mathbb{E}[(\nabla_\theta \log \pi)(\nabla_\theta \log \pi)^T] è la Fisher Information Matrix della policy.
  3. Soluzione analitica del problema linearizzato + quadratico via Lagrangiano:
θnew=θold+2δgTF1gF1g\theta_{new} = \theta_{old} + \sqrt{\frac{2\delta}{g^T F^{-1} g}} \cdot F^{-1} g

La direzione F1gF^{-1} g è il natural gradient (Amari 1998): la steepest ascent direction nella metrica indotta da DKLD_{KL}. 4. Conjugate gradient per evitare di calcolare F1F^{-1} esplicitamente. Si risolve Fx=gF x = g iterativamente, valutando FF 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 DKLD_{KL} effettivo (l’approssimazione quadratica non è perfetta) o non migliorare LL. Si tenta θnew=θold+αjs\theta_{new} = \theta_{old} + \alpha^j \cdot s con αj=0.5j\alpha^j = 0.5^j, accettando il primo jj tale che DKLδD_{KL} \leq \delta E LL migliorato. Se nessun jj funziona, si salta l’update.

visualizzazione 2D dello spazio dei parametri: regione ellittica del vincolo KL attorno a \theta_{old} (la metrica Fisher la rende ellittica in coordinate dei parametri, sferica in coordinate KL); freccia del gradient g, freccia del natural gradient F^{-1} g; ellisse del vincolo, punto di tangenza \theta_{new}; linea search backwards lungo la direzione

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 (Schulman 2017) parte dallo stesso surrogate ma sostituisce il vincolo con un freno integrato nell’objective. Definiamo

rt(θ)=πθ(atst)πθold(atst)r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{old}}(a_t|s_t)}

Il clipped surrogate è:

LCLIP(θ)=Et[min(rt(θ)At, clip(rt(θ),1ϵ,1+ϵ)At)]L^{CLIP}(\theta) = \mathbb{E}_t\left[\min\left(r_t(\theta) \cdot A_t,\ \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \cdot A_t\right)\right]

con ϵ[0.1,0.2]\epsilon \in [0.1, 0.2] tipicamente.

Lettura per casi:

  • At>0A_t > 0, rt1+ϵr_t \leq 1+\epsilon: LCLIP=rtAtL^{CLIP} = r_t A_t, gradiente normale, la policy aumenta la probabilita’ dell’azione buona.
  • At>0A_t > 0, rt>1+ϵr_t > 1+\epsilon: LCLIP=(1+ϵ)AtL^{CLIP} = (1+\epsilon) A_t, gradiente zero. Si è già aumentata abbastanza, freno.
  • At<0A_t < 0, rt1ϵr_t \geq 1-\epsilon: LCLIP=rtAtL^{CLIP} = r_t A_t, gradiente normale, la policy diminuisce la probabilita’ dell’azione cattiva.
  • At<0A_t < 0, rt<1ϵr_t < 1-\epsilon: LCLIP=rtAtL^{CLIP} = r_t A_t (perché min\min con At<0A_t < 0 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 rt=1r_t = 1. Le due direzioni “pericolose” sono rtr_t \to \infty con A>0A > 0 e rt0r_t \to 0 con A<0A < 0, ed entrambe vengono freni.

curve di L^{CLIP} vs r per i due casi A > 0 e A < 0, con linee di clip a 1-\epsilon e 1+\epsilon, evidenziando dove il gradiente è zero e dove è attivo

L’objective totale di PPO include anche una loss del value e un bonus di entropia:

LTOTAL(θ,ϕ)=LCLIP(θ)c1LVF(ϕ)+c2S[πθ]L^{TOTAL}(\theta, \phi) = L^{CLIP}(\theta) - c_1 \cdot L^{VF}(\phi) + c_2 \cdot S[\pi_\theta]

con LVF=Et[(Vϕ(st)Vttarget)2]L^{VF} = \mathbb{E}_t[(V_\phi(s_t) - V_t^{target})^2] la value loss del critic, SS l’entropia della policy (bonus per esplorazione), c10.5c_1 \approx 0.5, c20.01c_2 \approx 0.01.

L’advantage AtA_t in PPO si stima sempre via Generalized Advantage Estimation (Schulman 2016). Sia δt=rt+γVϕ(st+1)Vϕ(st)\delta_t = r_t + \gamma V_\phi(s_{t+1}) - V_\phi(s_t) il TD residual a un passo. Allora

AtGAE(γ,λ)=l=0(γλ)lδt+lA_t^{GAE(\gamma, \lambda)} = \sum_{l=0}^\infty (\gamma \lambda)^l \delta_{t+l}

Casi limite: λ=0\lambda = 0 da’ At=δtA_t = \delta_t (massimo bias dal critic, minima varianza); λ=1\lambda = 1 da’ At=GtV(st)A_t = G_t - V(s_t) (Monte Carlo, zero bias dal critic, massima varianza). In pratica λ[0.95,0.97]\lambda \in [0.95, 0.97] è il tradeoff standard.

Calcolo backward su un rollout:

A[T] = 0
for 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 Vttarget=At+Vϕ(st)V^{target}_t = A_t + V_\phi(s_t).

import torch
import torch.nn as nn
import 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 EE epoch di update con minibatch shuffle. Su CartPole-v1, in 100k\sim 100k step questa implementazione minimal arriva a reward 500500 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 V(st)=5V(s_t) = 5. L’agente sceglie azione ata_t. La completion produce reward cumulativo che porta a Gt=7G_t = 7. Quindi At=GtV(st)=2A_t = G_t - V(s_t) = 2 (advantage positivo: l’azione è stata buona).

Sotto la policy vecchia πold(atst)=0.4\pi_{old}(a_t|s_t) = 0.4. Dopo qualche step di update (anche all’interno della stessa epoch), πθ(atst)=0.52\pi_\theta(a_t|s_t) = 0.52. Quindi rt=0.52/0.4=1.3r_t = 0.52 / 0.4 = 1.3.

Con ϵ=0.2\epsilon = 0.2:

  • Termine non clipped: rtAt=1.32=2.6r_t \cdot A_t = 1.3 \cdot 2 = 2.6.
  • Clip: clip(1.3,0.8,1.2)=1.2\text{clip}(1.3, 0.8, 1.2) = 1.2.
  • Termine clipped: 1.22=2.41.2 \cdot 2 = 2.4.
  • LCLIP=min(2.6,2.4)=2.4L^{CLIP} = \min(2.6, 2.4) = 2.4.

L’objective per questa transition vale 2.42.4 e il gradiente rispetto a θ\theta è zero (perché il min sceglie il termine clipped, che non dipende da θ\theta). PPO ha frenato.

Ora cambia l’esempio: At=2A_t = -2 (azione cattiva). Con rt=0.7r_t = 0.7 (la policy ha già diminuito molto la probabilita’):

  • Non clipped: 0.7(2)=1.40.7 \cdot (-2) = -1.4.
  • Clip: clip(0.7,0.8,1.2)=0.8\text{clip}(0.7, 0.8, 1.2) = 0.8.
  • Clipped: 0.8(2)=1.60.8 \cdot (-2) = -1.6.
  • min(1.4,1.6)=1.6\min(-1.4, -1.6) = -1.6, cioè il clipped.

Anche qui LCLIPL^{CLIP} usa il termine clipped, gradiente zero. PPO ha frenato. Nota: se invece rt=0.9r_t = 0.9 (la policy non ha ancora abbastanza diminuito), il clip non si attiva (clip(0.9,0.8,1.2)=0.9\text{clip}(0.9, 0.8, 1.2) = 0.9), termine clipped =1.8= -1.8, non clipped =1.8= -1.8, gradiente normale.

Il design produce un effetto a “morsa” attorno a r=1r = 1: per ogni transition, fintanto che rr resta dentro [1ϵ,1+ϵ][1-\epsilon, 1+\epsilon] il gradiente è attivo; appena esce nella direzione coerente con il segno di AA, freno.

Lo pseudocodice della sezione precedente, integrato con il rollout loop:

import gymnasium as gym
import torch
import 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 = 2048
total_steps = 0
target_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.

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 + critic
pi_ref = LLM(init_from=pi_SFT, frozen=True) # reference per KL
RM = RewardModel(frozen=True) # reward model congelato
beta = 0.02 # KL coefficient
clip_eps = 0.2 # PPO clip
n_epochs = 4
mini_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.5

Punti chiave:

  • La reward shaping mette la KL per-token ad ogni step e il reward RR 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: πref=πSFT\pi_{ref} = \pi_{SFT} 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 γ=1\gamma = 1 (no discount: tutta la completion ha lo stesso peso) e λ=0.95\lambda = 0.95.
  • 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

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 KK 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.

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 rr ratio media è dentro [1ϵ,1+ϵ][1-\epsilon, 1+\epsilon] ma con varianza alta, l’update può spingere alcuni rr 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 rtr_t possono essere fuori dal range [1ϵ,1+ϵ][1-\epsilon, 1+\epsilon] 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 r=rtr = \prod r_t esploderebbe). Anche così, su completion molto lunghe (32k+ token) compaiono instabilità.

3. KL coefficient drift. In RLHF con β\beta fisso, dopo molte iterate la KL effettiva contro πref\pi_{ref} può drift verso valori molto alti (la policy si allontana dal SFT) o molto bassi (la policy non si muove). Soluzione: adaptive β\beta (Stiennon 2020): se KL > target, alza β\beta; se KL < target, abbassa. Anche con adaptive β\beta, 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 πref\pi_{ref} 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 ϵ\epsilon effettivo del clip è studiato in Andrychowicz 2021.

9. Importance sampling variance. La ratio rtr_t è un importance sampling estimator. La varianza di un IS estimator esplode quando le distribuzioni source e target divergono (DKLD_{KL} grande). Il clip mitiga (limita gli outlier) ma non risolve la causa. Su training prolungato, se la KL fra πθ\pi_\theta e πold\pi_{old} all’interno di un singolo update step diverge, il gradiente diventa rumoroso. Soluzione pratica: ridurre nepochsn_{epochs} a 3-4 e nstepsn_{steps} 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 πSFT\pi_{SFT}). Quando PPO sposta πθ\pi_\theta 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 πref\pi_{ref} 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 K=864K = 8-64, 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: ππrefexp(R/β)\pi^* \propto \pi_{ref} \cdot \exp(R/\beta). 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.

  • 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 λ=0\lambda = 0 e λ=1\lambda = 1.
  • 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.
  • 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.