Chapter 34
Reinforcement Learning Foundations
MDPs, the policy-gradient theorem, REINFORCE, baselines, importance sampling, and GAE.
Reinforcement learning (RL) trains a policy from consequences instead of target tokens. For LLMs, the policy is the model, an action is a generated token or response, and the reward may come from tests, a judge, or a learned preference model. This chapter keeps the world tiny so the core math is visible: returns, Bellman equations, score-function policy gradients, off-policy correction, and generalized advantage estimation.
34.1 MDPs, returns, and values
A Markov decision process has states , actions , transition probabilities, rewards, and a discount . The Markov assumption says the next state and reward depend on the past only through the current . A policy chooses actions. A trajectory’s discounted return from time is
The discount is not just a mathematical trick. It makes far-future rewards count less, and when it keeps infinite-horizon sums finite. In episodic problems the sum also stops at termination. LLM post-training often has short episodes: prompt in, response out, reward at the end; the same notation still applies, with many intermediate rewards equal to zero.
The value of a policy is the expected return after starting from : . Split off the first reward and use the Markov property:
For a fixed policy this is a linear system, . The code solves it for a three-state chain where state 0 moves to state 1, state 1 receives reward 1 and terminates, and the terminal state has value 0. With , the values are exactly , and the test asserts the Bellman residual.
An action-value is the same idea after forcing the first action. Policy improvement chooses actions with larger , but estimating directly is expensive for a large language model because the action space is the vocabulary or the set of whole responses. Policy gradients avoid enumerating all actions by using samples from the current policy.
def discounted_returns(rewards, gamma):
"""Return G_t = r_t + gamma r_{t+1} + ... for one trajectory."""
rewards = np.asarray(rewards, dtype=np.float64)
returns = np.zeros_like(rewards)
running = 0.0
for t in range(len(rewards) - 1, -1, -1):
running = rewards[t] + gamma * running
returns[t] = running
return returns
def evaluate_policy(transition, reward, gamma):
"""Solve v = r + gamma P v for a fixed policy's transition matrix."""
transition = np.asarray(transition, dtype=np.float64)
reward = np.asarray(reward, dtype=np.float64)
system = np.eye(transition.shape[0]) - gamma * transition
return np.linalg.solve(system, reward)
def tiny_chain(gamma):
"""Three-state chain: 0 -> 1 -> terminal, reward 1 on state 1."""
transition = np.array([[0, 1, 0], [0, 0, 1], [0, 0, 1]], dtype=np.float64)
reward = np.array([0, 1, 0], dtype=np.float64)
return evaluate_policy(transition, reward, gamma)
34.2 The policy-gradient theorem
Let be expected return. The score-function identity from Section 6.4.1 gives
The environment dynamics do not depend on , so the trajectory log-probability contributes only action log-probabilities:
Replacing by is the causality step: rewards before action do not depend on that action, so their expected score term is zero. In a one-state bandit, the theorem says to raise the logit of sampled actions in proportion to reward. The exact categorical gradient is , which the test checks by finite differences; the sampled REINFORCE loop learns to put more than 94% probability on the best arm.
This is a theorem about an expectation, not about a single rollout. One sampled action can be lucky or unlucky, so the estimator is noisy even when it is unbiased. The update becomes useful by averaging many samples, using a baseline, or both. The bandit example keeps rewards deterministic so the only randomness is action sampling; that isolates the policy-gradient estimator itself.
def reinforce_bandit(action_values, steps=200, batch_size=64, lr=0.2, seed=0):
"""Sampled REINFORCE updates for a one-state bandit."""
rng = np.random.default_rng(seed)
logits = np.zeros(len(action_values), dtype=np.float64)
for _ in range(steps):
probabilities = softmax(logits)
actions = rng.choice(len(action_values), size=batch_size, p=probabilities)
rewards = action_values[actions]
baseline = np.mean(rewards)
grad = np.zeros_like(logits)
for action, reward in zip(actions, rewards):
grad_logp = -probabilities.copy()
grad_logp[action] += 1.0
grad += (reward - baseline) * grad_logp
logits += lr * grad / batch_size
return logits, softmax(logits)
34.3 Baselines, advantages, and off-policy data
Subtracting a baseline that does not depend on the sampled action leaves the policy gradient unchanged:
The equality holds because the expectation is . A good baseline reduces variance. The usual choice is a value estimate, giving an advantage : positive means the sampled action did better than expected from that state, negative means it did worse.
The baseline must not depend on which action was sampled at that state. If it did, it could add a systematic push toward or away from that action. A value function is safe because it predicts the average return before seeing the sampled action. In code, many implementations also normalize a batch of advantages to mean zero and unit scale; that changes optimization dynamics but not the sign of which samples were better than their peers.
Logged data often came from a behavior policy , not the target policy . Importance sampling rewrites one expectation as another:
For trajectories, the ratio is a product over time, which can have high variance. The chapter code shows the one-step version used by bandits and by per-token corrections.
The formula also states its own failure mode. If for an action that might take, the ratio is undefined and the logged data cannot tell us what would have happened. If is merely tiny, a few samples receive huge weights. That is why off-policy RL methods usually add clipping, trust regions, or replay rules instead of relying on raw products of ratios over long generations.
def importance_ratios(actions, target_probs, behavior_probs):
"""rho_t = pi(a_t) / b(a_t) for logged bandit actions."""
actions = np.asarray(actions, dtype=np.int64)
target_probs = np.asarray(target_probs, dtype=np.float64)
behavior_probs = np.asarray(behavior_probs, dtype=np.float64)
return target_probs[actions] / behavior_probs[actions]
def off_policy_value(actions, rewards, target_probs, behavior_probs):
"""Ordinary importance-sampling estimate of a target policy value."""
ratios = importance_ratios(actions, target_probs, behavior_probs)
return float(np.mean(ratios * rewards))
34.4 Generalized advantage estimation
A learned value function gives a one-step temporal-difference error
The -step advantage bootstraps after rewards:
Expanding the TD errors shows a telescoping identity: . Generalized advantage estimation mixes all -step advantages with geometric weights [schulman2015highdimensional]:
Thus is one-step TD and approaches the Monte Carlo advantage. The backward recursion follows by separating the first term from the sum:
The tests compare this recursion against the explicit weighted sum for several and values.
is a bias-variance knob. Small trusts the value function and uses short, low-variance estimates; large trusts sampled returns and uses longer, higher-variance estimates. If the value function is poor, a very small can be biased; if rewards are noisy, a very large can make updates unstable. The recursive implementation is the one used in practice because it is linear in trajectory length and works naturally from the end of a rollout buffer backward.
def gae_recursive(rewards, values, gamma, lam):
"""Generalized advantage estimates by the backward recursion."""
deltas = td_errors(rewards, values, gamma)
advantages = np.zeros_like(deltas)
running = 0.0
for t in range(len(deltas) - 1, -1, -1):
running = deltas[t] + gamma * lam * running
advantages[t] = running
return advantages
|
In practice
|
Modern LLM post-training still uses these ingredients: sample from the current policy, score the sample, subtract a baseline or normalize advantages, and push up the log-probabilities of above-average samples [lambert2025reinforcement]. Value functions and GAE are common when rewards arrive over a sequence, while simpler response-level methods often use one final reward as the return. Importance sampling is mathematically exact but can explode on long trajectories, so practical algorithms usually clip or otherwise constrain policy changes. The next chapter adds those constraints through PPO. |
34.5 Teach it
The one-sentence version. RL raises the probability of sampled actions that beat expectation and lowers actions that disappoint.
An analogy. A coach cannot show the perfect move for every board position, but can say whether the game went better than expected after a move.
At the board.
-
Draw a three-state chain and write .
-
Write and cross out environment terms.
-
Subtract a baseline and show .
-
Write three TD errors and show how GAE discounts them by .
Misconceptions to address.
-
"Reward is a label." It is feedback on a sampled action, not the target action itself.
-
"A baseline changes the optimum." It changes variance, not the expected gradient.
-
"Off-policy data is free." Importance ratios can make it very noisy.
Check for understanding. In a bandit with reward 1 for action A and 0 for action B, what sign should the policy-gradient update give to the logit of A when A is sampled?
34.6 Exercises
For the chain 0 → 1 → terminal, with reward 1 in state 1 and , compute , , and .
Starting from , derive (34.4) and explain the causality step from to .
Prove (34.5). Then, for behavior probabilities , target probabilities , and rewards , compute the exact one-step importance-sampling value.
References
-
[williams1992] R. J. Williams. Simple statistical gradient-following algorithms for connectionist reinforcement learning. Machine Learning 8, 229–256, 1992.
-
[sutton2018] R. S. Sutton and A. G. Barto. Reinforcement Learning: An Introduction, 2nd edition. MIT Press, 2018. http://incompleteideas.net/book/the-book-2nd.html
-
[lambert2025reinforcement] N. Lambert. Reinforcement Learning from Human Feedback. 2025. arXiv:2504.12501
-
[schulman2015highdimensional] J. Schulman et al. High-Dimensional Continuous Control Using Generalized Advantage Estimation. 2015. arXiv:1506.02438