KL Divergence Penalty in RLHF: Theory & Implementation

Michael BrenndoerferDecember 30, 202569 min read

Part of Language AI Handbook

Explains how KL divergence prevents reward hacking in RLHF by keeping policies close to reference models.

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

KL Divergence Penalty

In the previous chapters, we built up the RLHF pipeline: collecting human preferences, training reward models, and applying PPO to optimize language model policies. But there is a necessary problem we have only briefly mentioned: reward hacking. Without constraints, a policy can discover bizarre outputs that score highly on the reward model while being clearly worse by any human measure. The KL divergence penalty is the mechanism that prevents this collapse, keeping the fine-tuned model anchored to the capabilities of its pre-trained foundation.

Think of the KL divergence penalty as the safety tether on a spacewalk. An astronaut tethered to the station can move freely in many directions, exploring the external surface and performing repairs, but cannot float away into open space. Remove the tether, and the same astronaut could drift indefinitely in any direction, including directions from which there is no return. The pre-trained language model is the space station: a large, carefully constructed structure representing enormous accumulated effort. The KL penalty is the tether that keeps the policy from drifting away from it while still letting real exploration of the surrounding space of behaviors.

This analogy also captures an important nuance: the length of the tether matters. A very short tether (large KL coefficient) keeps the astronaut close to the station but severely limits how far they can reach. A very long tether (small KL coefficient) allows wide exploration but risks losing contact entirely. The art of RLHF involves choosing and adapting the tether length appropriately as training progresses, tightening it when the policy shows signs of drifting dangerously and loosening it when progress stalls.

Why does reward hacking happen so readily without constraint? The reward model is a neural network trained on a finite dataset of human preferences. It approximates human judgment but does not perfectly capture it. Given enough freedom, a policy can always find inputs that the reward model scores highly but that humans would rate poorly. This is not a bug in the reward model; it is an inevitable consequence of learning from finite data. The policy is in effect performing adversarial search against the reward model, finding the holes in its coverage. The KL penalty limits how far the policy can search, keeping it in regions where the reward model is well-calibrated because those regions resemble the training data distribution.

The KL divergence penalty also serves a second, subtler purpose beyond preventing reward hacking. The pre-trained model is extraordinarily capable in ways that the reward model does not measure. It can write coherent multi-paragraph arguments, maintain consistent personas, handle edge cases gracefully, and produce text with appropriate register and tone for different audiences. None of these capabilities are directly rewarded during RLHF because they are hard to specify as preferences. By anchoring to the pre-trained model via KL divergence, we preserve these capabilities for free, without needing to explicitly specify them in the reward function. This makes the KL constraint a remarkably efficient mechanism: a single scalar coefficient does the work that would otherwise require carefully engineering dozens of auxiliary reward terms.

This chapter explores why KL divergence is the right tool for this job, how to compute it efficiently for autoregressive models, how to set and adapt the KL coefficient to balance learning against stability, and what happens when the constraint is too tight or too loose. We also examine the worked arithmetic of KL computation, the reward shaping interpretation that connects the penalty to classical RL theory, and the practical considerations that distinguish successful RLHF deployments from unstable ones.

Historical Context

The use of KL divergence as a regularizer in policy optimization predates RLHF by decades. In classical RL, the TRPO (Trust Region Policy Optimization) algorithm introduced in 2015 by Schulman et al. used KL divergence as a hard constraint on policy updates. This keeps each gradient step did not change the policy too drastically. The PPO algorithm, which followed in 2017, replaced the hard constraint with a clipped surrogate objective but maintained the intuition of trust regions.

When OpenAI began applying RL to language model fine-tuning around 2019-2020, they adapted the KL penalty naturally from their PPO work. The InstructGPT paper (Ouyang et al., 2022) formalized the specific approach of adding an explicit KL penalty term to the reward, referencing the SFT model as a fixed anchor. This formulation has since become the standard approach in RLHF, appearing in in effect every large-scale alignment training system including those at Anthropic, Google DeepMind, and Meta.

The theoretical foundations of KL-constrained optimization trace back even further, to the information-theoretic work of Kullback and Leibler in 1951 and the development of maximum entropy methods in the 1950s and 1960s. The specific insight that information-theoretic divergences make natural regularizers for sequential decision problems connects RL to a rich tradition of variational inference and free energy minimization in statistics and physics.

Why KL Divergence?

Recall from the Reward Hacking chapter that reward models are imperfect approximations of human preferences. They are trained on a finite dataset and will inevitably assign high scores to some outputs that humans would reject. When we optimize a policy using PPO, we are searching over a large space of possible behaviors, and the optimization process will find these reward model errors if given enough freedom. This creates a tradeoff in RLHF: we must improve the model using the reward signal without trusting it unconditionally.

The basic insight is that the pre-trained language model is already a strong prior over sensible, grammatical, informative text. It was trained on trillions of tokens of human-written content, absorbing patterns of coherent discourse, factual knowledge, and linguistic conventions. By constraining our policy to stay close to this reference distribution, we inherit its linguistic competence while still allowing targeted improvements in alignment. Think of the reference model as a trusted expert whose judgment we respect: we want to make adjustments based on new feedback, but we do not want to stray so far that we lose the foundational competence that made the model useful in the first place.

What distinguishes KL divergence from other possible measures of distributional similarity? We could imagine using other metrics: total variation distance, Wasserstein distance, or simple L2 distance between parameter vectors. Each of these alternatives has shortcomings in the RLHF setting. Parameter-space constraints like L2 weight regularization penalize changes in weights, not changes in behavior. A model can change behavior sharply while keeping weights close together (via cancellations across layers) or keep behavior almost identical while changing weights materially (via symmetries and redundancies). We care about behavioral similarity, measured in terms of the outputs the model produces. Wasserstein and total variation distances operate in the right space (distributions over outputs), but they are expensive to compute for high-dimensional distributions and lack convenient decomposition properties for sequential models.

KL divergence measures how different one probability distribution is from another. For language models, this means measuring how much the policy's probability assignments over tokens differ from the reference model's. At each position in a sequence, both models assign probabilities to every token in the vocabulary. KL divergence captures the extent to which these probability distributions disagree, weighted by the probability mass the policy assigns. The key properties that make KL divergence ideal for this application are:

  • Non-negativity: DKL(P∥Q)≥0D_{KL}(P \| Q) \geq 0, with equality only when P=QP = Q
  • Asymmetry: it penalizes the policy for assigning probability to tokens the reference does not expect
  • Decomposability: for autoregressive models, we can compute KL at each token position and sum

The non-negativity property ensures that the penalty term always works in one direction: it can only reduce the objective, never artificially inflate it. This mathematical guarantee means the optimization has a clear interpretation where reward pulls the policy toward better outputs while the KL penalty resists moving too far from the reference. The asymmetry property is particularly important for RLHF because it creates an asymmetric treatment of errors. If the policy assigns high probability to something the reference considers unlikely, the penalty is severe. But if the policy assigns low probability to something the reference considers likely, the penalty is more forgiving. This asymmetry encourages the policy to remain conservative, avoiding novel but potentially problematic outputs. Finally, decomposability allows us to compute and analyze the penalty at the level of individual tokens. This gives fine-grained insight into where and how the policy is diverging.

The decomposability property deserves special attention because it is what makes KL divergence tractable at the scale of language model training. The probability of a complete sequence is exponentially small; computing sequence-level distances directly would require summing over an astronomically large space. But because KL divergence over the full sequence decomposes into a sum of token-level KL divergences, we can compute it efficiently by running both models on the same generated sequence and comparing their log probabilities token by token. This makes the per-sequence KL computation as cheap as two forward passes, regardless of vocabulary size.

Out[3]:
Visualization
Grouped bar chart comparing token probabilities for policy (coral) and reference (steelblue) distributions over a small vocabulary.
Token probability distributions comparing the policy (P) and reference (Q) across a small vocabulary, showing disagreements on tokens like 'good', 'bad', and 'helpful'. Tokens where the policy assigns greater probability than the reference contribute positively to KL divergence.
Bar chart of per-token KL divergence contributions, with positive contributions in green and negative in red, annotated to highlight the policy's preference for 'helpful'.
Per-token contribution to total KL divergence, where green bars indicate tokens the policy prefers more than the reference. The policy's strong preference for 'helpful' over the reference creates the largest single contribution to total divergence.
KL Divergence

KL divergence (Kullback-Leibler divergence) quantifies the expected number of extra bits needed to encode samples from distribution PP using a code optimized for distribution QQ. In machine learning, it measures how one probability distribution differs from a reference distribution. A KL of 0 means the two distributions are identical; larger values indicate greater disagreement.

Mathematical Foundation

The mathematical definition of KL divergence is the basis for these methods. The formula captures a precise notion of distributional difference that translates directly into the computational procedures we use during training. Before writing down the formula, it helps to understand what we are measuring: we want to quantify how surprised the reference model would be by the choices the policy makes. Tokens that the policy prefers but the reference considers unlikely contribute heavily to this surprise. The total KL divergence aggregates this surprise across all possible tokens, weighted by how often the policy chooses them.

For two discrete probability distributions PP and QQ over the same set of outcomes, KL divergence is defined as:

DKL(P∥Q)=∑xP(x)log⁡P(x)Q(x)=Ex∼P[log⁡P(x)−log⁡Q(x)]\begin{aligned} D_{KL}(P \| Q) &= \sum_x P(x) \log \frac{P(x)}{Q(x)} \\ &= \mathbb{E}_{x \sim P}\left[\log P(x) - \log Q(x)\right] \end{aligned}

where:

  • DKL(P∥Q)D_{KL}(P \| Q): the Kullback-Leibler divergence from QQ to PP, representing the information lost when QQ is used to approximate PP
  • P(x)P(x): the probability of outcome xx under the first distribution (typically the policy we are optimizing)
  • Q(x)Q(x): the probability of outcome xx under the reference distribution (the baseline or prior)
  • Ex∼P\mathbb{E}_{x \sim P}: the expectation calculated over samples drawn from PP, meaning we average the log-ratio over the events that occur under distribution PP

The first line presents the definition as a weighted sum over all possible outcomes, where each outcome's contribution is its probability under PP times the log ratio of probabilities. The second line rewrites this as an expectation, the form used in practice because we work with samples rather than complete distributions. This expectation-based form tells us something important: we only need to evaluate the log probability ratio for outcomes that occur under the policy, not for every possible outcome. This property is what makes KL divergence tractable for large vocabulary language models where the vocabulary can contain 50,000 to 100,000 tokens.

The key insight is that the expectation form turns a sum over all vocabulary tokens into an average over sampled tokens. During training, we generate responses from the policy, and for each token in each response, we look up its log probability under both the policy and the reference model. We never need to compute probabilities for tokens that were not generated. This is the basic efficiency that makes KL penalty practical in large-scale training.

In the RLHF context, PP is the policy distribution πθ\pi_\theta and QQ is the reference distribution πref\pi_{\text{ref}} (typically the supervised fine-tuned model before RLHF). The policy is the distribution we are optimizing, and the reference is the distribution we want to stay close to. Every time we sample a token from the policy, we can compute its contribution to the KL divergence by looking at how much more or less likely that token was under the policy compared to the reference.

For autoregressive language models, the probability of a complete response y=(y1,y2,…,yT)y = (y_1, y_2, \ldots, y_T) given prompt xx factorizes as:

π(y∣x)=∏t=1Tπ(yt∣x,y<t)\pi(y|x) = \prod_{t=1}^{T} \pi(y_t | x, y_{<t})

where:

  • π(y∣x)\pi(y|x): the probability of the complete sequence yy given prompt xx, computed as the product of conditional probabilities
  • TT: the total length of the generated sequence (number of tokens)
  • yty_t: the token generated at time step tt
  • y<ty_{<t}: the history of tokens generated before step tt, which is the context for the next prediction

This factorization is the defining characteristic of autoregressive models. The probability of an entire sequence equals the product of probabilities for each token, where each token's probability depends on the prompt and all previously generated tokens. This chain structure means that computing the probability of a complete sequence requires TT forward passes through the model, one for each token position, though in practice we can compute all positions in parallel when the sequence is known by feeding the complete sequence as input and computing the logits for all positions simultaneously.

Taking the log and using the chain rule of KL divergence, the KL divergence between the policy and reference over complete responses can be written as:

DKL(πθ(⋅∣x)∥πref(⋅∣x))=Ey∼πθ(⋅∣x)[∑t=1Tlog⁡πθ(yt∣x,y<t)πref(yt∣x,y<t)]D_{KL}(\pi_\theta(\cdot|x) \| \pi_{\text{ref}}(\cdot|x)) = \mathbb{E}_{y \sim \pi_\theta(\cdot|x)}\left[\sum_{t=1}^{T} \log \frac{\pi_\theta(y_t | x, y_{<t})}{\pi_{\text{ref}}(y_t | x, y_{<t})}\right]

where:

  • DKL(πθ∥πref)D_{KL}(\pi_\theta \| \pi_{\text{ref}}): the KL divergence between the policy and reference distributions over complete sequences
  • Ey∼πθ\mathbb{E}_{y \sim \pi_\theta}: the expectation calculated over response sequences yy sampled from the policy, since we estimate KL using the model's own generations
  • ∑t=1T\sum_{t=1}^{T}: the summation over all TT tokens, accumulating the divergence contribution from each step
  • log⁡πθ(… )πref(… )\log \frac{\pi_\theta(\dots)}{\pi_{\text{ref}}(\dots)}: the log probability ratio at each time step, which measures how much more (or less) likely a token is under the policy compared to the reference

This decomposition is important: we can compute KL as a sum of per-token log probability ratios, evaluated along the trajectory sampled from the policy. The log of a product becomes a sum of logs, which turns the sequence-level KL into a sum of token-level contributions. Each term in the sum measures how much the policy and reference disagree about the probability of a specific token given the context. When the policy assigns higher probability than the reference, the term is positive. This contributes to the divergence. When the policy assigns lower probability, the term is negative, reducing the divergence. The total KL divergence aggregates these token-level disagreements across the entire sequence.

Per-Token KL Computation

Given a sampled response, the per-token KL contribution is simply:

KLt=log⁡πθ(yt∣x,y<t)−log⁡πref(yt∣x,y<t)\text{KL}_t = \log \pi_\theta(y_t | x, y_{<t}) - \log \pi_{\text{ref}}(y_t | x, y_{<t})

where:

  • KLt\text{KL}_t: the contribution to the KL divergence at time step tt (positive if the policy favors the token more than the reference)
  • πθ(yt∣x,y<t)\pi_\theta(y_t | x, y_{<t}): the probability assigned to the token yty_t by the policy model given the context
  • πref(yt∣x,y<t)\pi_{\text{ref}}(y_t | x, y_{<t}): the probability assigned to the token yty_t by the reference model given the context

This formula shows the simplicity of per-token KL computation. For any token in a generated sequence, we simply subtract the reference model's log probability from the policy model's log probability. No summation over the vocabulary is required because we are only interested in the sampled token. This is a direct consequence of the expectation-based formulation: since we are averaging over samples from the policy, we only need to evaluate the log ratio at the sampled outcomes.

The sign of each term is diagnostic. A positive KLt\text{KL}_t means the policy assigned more probability to this token than the reference did: the policy is more confident about or more inclined toward this choice than the pre-trained model. A negative KLt\text{KL}_t means the policy assigned less probability: the pre-trained model would have made this choice more readily than the policy would. By examining the sign and magnitude of per-token KL contributions across a response, we can identify exactly which word choices stand for the policy's learned departures from the reference distribution.

Summing these over all tokens in the response gives:

KL(y)=∑t=1TKLt=∑t=1T[log⁡πθ(yt∣x,y<t)−log⁡πref(yt∣x,y<t)]\begin{aligned} \text{KL}(y) &= \sum_{t=1}^{T} \text{KL}_t \\ &= \sum_{t=1}^{T} \left[\log \pi_\theta(y_t | x, y_{<t}) - \log \pi_{\text{ref}}(y_t | x, y_{<t})\right] \end{aligned}

where:

  • KL(y)\text{KL}(y): the approximate KL divergence for a single sampled response yy, calculated as the sum of per-token divergences
  • TT: the length of the response in tokens, over which the divergence accumulates

This is an unbiased estimator of the KL divergence under the policy distribution, since we are sampling trajectories from πθ\pi_\theta itself. The unbiasedness property means that if we average this quantity over many sampled responses, we converge to the true KL divergence between the policy and reference distributions. In practice, we compute this estimator for each response in a training batch and use the batch average as our estimate of the expected KL divergence.

An important consequence of this formula is that KL divergence scales with sequence length. A response of 100 tokens accumulates up to 100 per-token contributions. This means that long responses are penalized more heavily in absolute terms than short responses, even if the per-token divergence is the same. Some practitioners address this by normalizing the total KL by sequence length, computing average per-token KL rather than total KL. Others prefer total KL because it better captures the overall behavioral departure from the reference. The choice has implications for how the penalty interacts with response length: normalizing encourages the policy to generate any length freely, while using total KL creates a cost to longer responses that diverge from the reference.

Out[4]:
Visualization
Line chart of log probabilities per token for policy (blue circles) and reference (orange squares) models over a 15-token generated sequence.
Log probabilities of each token in a generated sequence for the policy (blue) and reference (orange) models. Where the blue line rises above the orange, the policy assigns higher probability to the generated token than the reference model would, creating positive KL contributions.
Bar chart of per-token KL contributions with green bars for positive contributions and red for negative, annotated to highlight the 'helpful' token spike.
Per-token KL contributions for the generated sequence. The policy's strong preference for 'helpful' (token 12) generates the largest positive contribution, driving most of the total divergence for this sequence.

Worked Example: Step-by-Step KL Computation

To make the mathematics concrete, let us work through a numerical example with a tiny vocabulary. Suppose we are generating the single word following the prompt "The sky is". Our vocabulary contains four words: "blue", "clear", "dark", and "beautiful". The reference model assigns these probabilities:

Q={blue:0.50, clear:0.25, dark:0.15, beautiful:0.10}Q = \{\text{blue}: 0.50,\ \text{clear}: 0.25,\ \text{dark}: 0.15,\ \text{beautiful}: 0.10\}

After RLHF training on prompts related to describing pleasant weather, our policy has shifted toward more positive words:

P={blue:0.30, clear:0.30, dark:0.05, beautiful:0.35}P = \{\text{blue}: 0.30,\ \text{clear}: 0.30,\ \text{dark}: 0.05,\ \text{beautiful}: 0.35\}

We can verify both distributions sum to 1.0, as required. Now suppose the policy samples the token "beautiful" for this position (which is likely given the policy's 35% probability). The per-token KL contribution for this sampled token is:

KLbeautiful=log⁡P(beautiful)−log⁡Q(beautiful)=log⁡(0.35)−log⁡(0.10)=−1.0498−(−2.3026)=1.2528 nats\begin{aligned} \text{KL}_{\text{beautiful}} &= \log P(\text{beautiful}) - \log Q(\text{beautiful}) \\ &= \log(0.35) - \log(0.10) \\ &= -1.0498 - (-2.3026) \\ &= 1.2528 \text{ nats} \end{aligned}

This positive value tells us that the policy assigned more than three times as much probability to "beautiful" as the reference model did, and this preference contributes 1.25 nats to the divergence penalty. If instead the policy had sampled "dark", the contribution would be:

KLdark=log⁡P(dark)−log⁡Q(dark)=log⁡(0.05)−log⁡(0.15)=−2.9957−(−1.8971)=−1.0986 nats\begin{aligned} \text{KL}_{\text{dark}} &= \log P(\text{dark}) - \log Q(\text{dark}) \\ &= \log(0.05) - \log(0.15) \\ &= -2.9957 - (-1.8971) \\ &= -1.0986 \text{ nats} \end{aligned}

This negative value shows the policy being less likely than the reference to produce "dark". Note something important: the total KL divergence of the full distribution (summed over all tokens, weighted by PP) is always non-negative. But the contribution from a single sampled token can be negative. The non-negativity guarantee applies to the expectation, not to individual samples.

The full distribution KL divergence, summed over all four tokens, is:

DKL(P∥Q)=∑xP(x)log⁡P(x)Q(x)=0.30⋅log⁡0.300.50+0.30⋅log⁡0.300.25+0.05⋅log⁡0.050.15+0.35⋅log⁡0.350.10\begin{aligned} D_{KL}(P \| Q) &= \sum_x P(x) \log \frac{P(x)}{Q(x)} \\ &= 0.30 \cdot \log\frac{0.30}{0.50} + 0.30 \cdot \log\frac{0.30}{0.25} + 0.05 \cdot \log\frac{0.05}{0.15} + 0.35 \cdot \log\frac{0.35}{0.10} \end{aligned}

Computing each term:

=0.30⋅(−0.5108)+0.30⋅(0.1823)+0.05⋅(−1.0986)+0.35⋅(1.2528)=−0.1532+0.0547−0.0549+0.4385=0.2851 nats\begin{aligned} &= 0.30 \cdot (-0.5108) + 0.30 \cdot (0.1823) + 0.05 \cdot (-1.0986) + 0.35 \cdot (1.2528) \\ &= -0.1532 + 0.0547 - 0.0549 + 0.4385 \\ &= 0.2851 \text{ nats} \end{aligned}

This 0.285 nats is a moderate divergence. The policy has shifted toward "beautiful" at the expense of "blue" and "dark", which the KL divergence correctly captures. If we had a KL coefficient β=0.1\beta = 0.1, the penalty contribution from this one-token sequence would be 0.1×0.2851=0.02850.1 \times 0.2851 = 0.0285, subtracted from whatever reward the policy earned.

Now extend this thinking to a full response of 100 tokens. If each token position contributes an average KL of about 0.3 nats (similar to our example), the total KL for the response would be around 30 nats. With β=0.1\beta = 0.1, that produces a penalty of 3.0 reward units. This illustrates why the KL coefficient must be chosen in relation to the typical scale of the reward model scores. If rewards range from -1 to 1 and the KL penalty is 3.0, the penalty dominates and the policy cannot learn anything useful from the reward signal.

The key insight from this worked example is that KL divergence accumulates multiplicatively: each position in the sequence contributes independently based on the log probability ratio at that position. Positions where the policy makes the most different choices from the reference contribute the most. And because the contribution is proportional to P(x)log⁡(P(x)/Q(x))P(x) \log(P(x)/Q(x)), tokens that the policy assigns high probability to matter more than tokens the policy considers unlikely.

The KL-Constrained Objective

Having established how to compute KL divergence, we now examine how it enters the RLHF optimization objective. The KL term acts as a regularizer, pulling against the reward signal to prevent the policy from straying too far from its trusted starting point. The combined objective balances generating responses that humans find helpful against remaining close enough to the pre-trained distribution to preserve its broad capabilities.

The RLHF objective with KL penalty is:

J(θ)=Ex∼D[Ey∼πθ(⋅∣x)[rϕ(x,y)]−β⋅DKL(πθ(⋅∣x)∥πref(⋅∣x))]\mathcal{J}(\theta) = \mathbb{E}_{x \sim \mathcal{D}}\left[\mathbb{E}_{y \sim \pi_\theta(\cdot|x)}[r_\phi(x, y)] - \beta \cdot D_{KL}(\pi_\theta(\cdot|x) \| \pi_{\text{ref}}(\cdot|x))\right]

where:

  • J(θ)\mathcal{J}(\theta): the objective function to be maximized, balancing reward maximization against drift from the reference
  • D\mathcal{D}: the dataset of prompts used for training
  • rϕ(x,y)r_\phi(x, y): the learned reward model score for prompt xx and response yy, giving the guidance signal
  • β\beta: the KL coefficient controlling the strength of the penalty (higher values force the policy closer to the reference)
  • πref\pi_{\text{ref}}: the frozen reference model distribution, serving as the anchor

The structure of this objective captures the core tradeoff in RLHF. The first term rewards the policy for generating responses that score highly according to the reward model. The second term penalizes the policy for diverging from the reference. The coefficient β\beta determines the relative importance of these two competing forces. When β\beta is large, the penalty dominates and the policy barely moves from the reference. When β\beta is small, the reward signal dominates and the policy can move freely toward high-reward regions, potentially exploiting imperfections in the reward model.

This can be rewritten using the per-token KL decomposition:

J(θ)=Ex,y[rϕ(x,y)−β∑t=1Tlog⁡πθ(yt∣x,y<t)πref(yt∣x,y<t)]\mathcal{J}(\theta) = \mathbb{E}_{x, y}\left[r_\phi(x, y) - \beta \sum_{t=1}^{T} \log \frac{\pi_\theta(y_t | x, y_{<t})}{\pi_{\text{ref}}(y_t | x, y_{<t})}\right]

where:

  • Ex,y\mathbb{E}_{x, y}: the expectation over prompts and sampled responses
  • ∑t=1T\sum_{t=1}^{T}: the sum of log probability ratios over the sequence, representing the total divergence for that trajectory

This formulation makes the objective directly computable from model outputs. For each sampled response, we obtain a reward from the reward model and compute the sum of log probability ratios from the policy and reference models. The combination of these quantities, averaged over the batch, gives us the objective value that we then maximize through gradient ascent.

Notice that this objective has a clean interpretation: it asks for the policy that best satisfies two objectives simultaneously. The first objective is to generate responses that humans find useful, as captured by the reward model. The second objective is to remain recognizably similar to the original pre-trained model, as measured by distributional distance. The β\beta parameter specifies the exchange rate between these two objectives: how much reward improvement we are willing to trade for one nat of additional divergence.

Connection to Constrained Optimization

The KL-penalized objective is equivalent to the Lagrangian relaxation of a constrained optimization problem:

max⁡θE[rϕ(x,y)]subject toE[DKL(πθ∥πref)]≤ϵ\max_\theta \mathbb{E}[r_\phi(x, y)] \quad \text{subject to} \quad \mathbb{E}[D_{KL}(\pi_\theta \| \pi_{\text{ref}})] \leq \epsilon

where:

  • max⁡θ\max_\theta: the maximization over policy parameters θ\theta
  • E\mathbb{E}: the expectation over the data distribution
  • ϵ\epsilon: the maximum allowable divergence (the "budget"), limiting how far the policy can drift

In this constrained view, we are asking for the policy that maximizes expected reward subject to a hard constraint on how much it can diverge from the reference. The constraint ϵ\epsilon specifies a "KL budget" that the policy must respect. The penalized objective arises when we convert this constrained problem to an unconstrained one using Lagrangian relaxation.

The coefficient β\beta acts as a Lagrange multiplier. Larger β\beta corresponds to a tighter constraint (smaller ϵ\epsilon), forcing the policy to stay closer to the reference. The objective seeks the highest-reward policy among those that remain within a KL "budget" of the reference. The correspondence between β\beta and ϵ\epsilon is not one-to-one in practice because the constraint is enforced softly rather than exactly, but the intuition remains useful. Choosing β\beta is analogous to choosing how much divergence we are willing to tolerate in exchange for reward improvement.

Think of the constrained formulation as having a budget for a home renovation project. You have a fixed budget (the KL constraint ϵ\epsilon), and you want to maximize the improvements you can make within that budget. The unconstrained version with a penalty β\beta is like having a project that costs extra for every dollar spent but where there is no hard limit. The two framings lead to equivalent decisions for the right choice of β\beta, but the constrained view is more interpretable: you directly specify how much divergence you are comfortable with, and the algorithm finds the best policy within that budget.

Reward Shaping Interpretation

Another way to view the KL term is as a per-token bonus added to the reward. This interpretation connects the KL penalty to the classical reinforcement learning concept of reward shaping, where the reward function is augmented with additional terms to guide learning.

We can rewrite the objective as:

J(θ)=Ex,y[∑t=1Tγt⋅rt]\mathcal{J}(\theta) = \mathbb{E}_{x, y}\left[\sum_{t=1}^{T} \gamma^t \cdot r_t\right]

where:

  • γ\gamma: the discount factor (typically 1 in this context, treating all tokens as equally important)
  • rtr_t: the shaped reward at time step tt, incorporating both the environment reward and the KL penalty

The shaped reward at each step is defined as:

rt={−β⋅KLtif t<Trϕ(x,y)−β⋅KLTif t=Tr_t = \begin{cases} -\beta \cdot \text{KL}_t & \text{if } t < T \\ r_\phi(x, y) - \beta \cdot \text{KL}_T & \text{if } t = T \end{cases}

where:

  • KLt\text{KL}_t: the per-token KL penalty, acting as an instantaneous cost for deviating from the reference
  • rϕ(x,y)r_\phi(x, y): the final reward from the reward model, typically given only at the end of the sequence (t=Tt=T)

This interpretation shows that the policy receives negative reward proportional to its deviation from the reference at each token, plus the final reward model score at the end of generation. The per-token penalties encourage staying on-distribution throughout the generation, not just at the final output. Every token choice that differs from what the reference would have chosen incurs a cost, creating pressure to remain conservative at every step of generation.

This reward shaping perspective has practical implications for implementation. In PPO, we need to assign credit to individual actions (tokens) for the overall outcome. The shaped reward formulation gives a natural way to do this: each token receives the KL penalty immediately, while the reward model score is attributed to the final token. This decomposition helps the value function and advantage estimation work effectively, since part of the reward signal is available at every time step rather than only at the end. Without the per-token KL shaping, the value function would receive no signal until the end of the sequence, which makes it very hard to learn credit assignment for long responses.

The reward shaping interpretation also clarifies why the KL penalty discourages reward hacking more effectively than alternative regularization methods. A weight decay penalty applied to model parameters pushes the policy back toward zero weights, which has no clear relationship to the reference distribution. L2 distance between parameters penalizes large changes uniformly, regardless of whether those changes affect behavior. The KL penalty, by contrast, directly penalizes behavioral deviations: if the policy outputs the same distribution as the reference at every token position, the penalty is exactly zero, regardless of how much the parameters have changed. This behavioral grounding makes the KL penalty much more targeted and interpretable than parameter-space regularizers.

Out[5]:
Visualization
Stacked bar chart showing per-token KL penalties (negative, coral) at each position and the reward model signal (blue) added at the final token.
Reward shaping interpretation of the KL penalty. Negative KL penalties (coral bars) are applied at every token step to penalize deviation from the reference, while the sparse reward model signal (blue bar) is added only at the final token. The combination of per-token costs and terminal reward creates a dense training signal that helps PPO learn credit assignment across the full sequence.

KL Coefficient Selection

The KL coefficient β\beta is perhaps the most important hyperparameter in RLHF. It controls the basic tradeoff between two competing objectives that cannot be simultaneously maximized: learning from the reward signal as aggressively as possible, and staying close to the pre-trained distribution that is accumulated linguistic competence. There is no universally correct value of β\beta; the right choice depends on the reward model quality, the magnitude of the desired behavior change, the training data distribution, and the response length distribution.

Think of β\beta as the stiffness of a spring connecting the policy to the reference model. A very stiff spring (high β\beta) means the policy can only deviate a little from the reference before the restoring force becomes overwhelming. It will converge quickly because there is not much room to move, but it may never reach the optimal alignment behavior if that behavior requires substantial departure from the pre-trained distribution. A very weak spring (low β\beta) allows the policy to explore widely but also means it can drift into regions of the space that are far from well-calibrated reward model territory, leading to reward hacking. The art of RLHF involves finding the spring stiffness that allows real movement toward better alignment without letting pathological exploitation of reward model errors.

The effects of the coefficient choice are asymmetric in their failure modes. Setting β\beta too low produces obvious failures: outputs that are repetitive, formulaic, or bizarre in ways that humans immediately notice. These failures are easy to detect through human evaluation and relatively easy to fix by increasing β\beta. Setting β\beta too high produces subtle failures: the model barely moves from the reference, and improvements in alignment metrics are minimal or undetectable. This failure mode is harder to diagnose because the model still looks reasonable; it just has not learned much from the reward signal.

The key insight is that β\beta selection is not an abstract mathematical choice but an empirical one tied to the specific properties of your training setup. The optimal β\beta for a 7B parameter model may differ from that for a 70B parameter model; the optimal β\beta for a helpfulness-focused reward model may differ from that for a safety-focused one; the optimal β\beta for short-form responses may differ from that for long-form generation.

Effects of Different Coefficient Values

Understanding the qualitative effects of different coefficient ranges helps calibrate initial choices:

When β\beta is too low (below 0.01):

  • The policy optimizes almost purely for reward, ignoring the reference constraint
  • Susceptible to reward hacking and mode collapse
  • May produce repetitive, formulaic outputs that exploit reward model quirks
  • Loses diversity and coherence over time as the policy converges to a narrow mode of high-reward outputs

When β\beta is too high (above 1.0):

  • Learning becomes extremely slow because the penalty overwhelms the reward signal
  • The policy barely moves from the reference, even when the reward model gives clear guidance
  • May never reach real alignment improvements
  • Effectively wastes computational resources on training that produces minimal behavioral change

Within the typical range (β∈[0.01,0.2]\beta \in [0.01, 0.2]):

  • Most successful RLHF implementations use values in this range
  • InstructGPT used an initial value around 0.02
  • Anthropic's Constitutional AI work used values around 0.001 to 0.01
  • The optimal value depends on reward model quality and desired behavior change

Practical Guidelines for Selection

When selecting a KL coefficient, consider these factors:

  • Reward model confidence: If your reward model was trained on limited data or shows signs of miscalibration, use a higher β\beta to limit exploitation of its errors. A well-calibrated reward model trained on millions of preference comparisons can tolerate lower β\beta because there are fewer holes to exploit.

  • Magnitude of desired behavior change: For small adjustments (such as reducing a specific narrow category of harmful outputs), lower β\beta suffices. For significant capability changes that require the model to learn substantially new behaviors, you may need to accept higher KL divergence and should verify that training produces behavioral changes rather than just optimizing the KL-penalized objective.

  • Response length: Longer responses accumulate more KL. If you are optimizing for verbose outputs, the effective per-token penalty is spread over more tokens, so you might need to account for this in choosing β\beta or to normalize by length.

  • Training stability: If you observe reward increasing while output quality degrades subjectively (which you can detect via human evaluation or automatic metrics), increase β\beta to strengthen the constraint. If training seems stable but the model is not improving, consider decreasing β\beta or increasing the reward model signal.

Out[6]:
Visualization
Line chart of objective value versus KL divergence for five beta values (0.01 to 0.5) plus a reward-only baseline, with colored dots marking each curve's optimum.
Objective value as a function of KL divergence for five different values of the KL coefficient beta. Higher beta values create steeper penalty slopes, shifting the optimum (dots) toward lower KL divergence and lower total objective. The reward-only baseline (dashed) shows the unconstrained maximum.
Dual-axis line chart with optimal KL divergence (steelblue, left axis) and optimal objective value (coral, right axis) plotted against beta on a log scale.
Optimal KL divergence (blue, left axis) and optimal objective value (coral, right axis) as functions of beta on a log scale. As beta increases, the policy is forced to stay closer to the reference (lower optimal KL), at the cost of a lower maximum achievable objective.

Adaptive KL Control

Rather than fixing β\beta throughout training, adaptive KL methods adjust the coefficient to maintain a target KL budget. This approach, introduced in the InstructGPT paper, gives more consistent training dynamics across different stages of optimization. The core insight is that the appropriate constraint strength changes as training progresses, and a fixed coefficient cannot account for these changing conditions.

Consider what happens during a typical RLHF training run. In the very early stages, when the policy is nearly identical to the reference, even small gradient updates produce modest KL divergence. As training progresses and the policy adapts, it may start making more consistent departures from the reference distribution, accumulating larger KL with each batch. Late in training, the policy may have already diverged materially, and further updates produce only marginal additional KL. A fixed β\beta that was appropriate in the early stages may be far too weak to prevent reward hacking in the middle stages, or far too strong to allow real learning in the late stages.

The adaptive approach treats KL control as a feedback control problem. We specify a desired operating point (the target KL) and adjust β\beta to steer the system toward it. When the policy drifts too far from the reference, we tighten the constraint; when the policy stays too close, we loosen it. This automatic adjustment eliminates the need to manually tune β\beta schedules and gives more reliable training across different model sizes, reward models, and training data distributions.

The Target KL Approach

The idea is to specify a target KL divergence DtargetD_{\text{target}} that we want to maintain on average. If the current KL exceeds this target, we increase β\beta to pull the policy back. If KL is below target, we decrease β\beta to allow more exploration. This creates a feedback loop that stabilizes training by keeping the policy within a controlled distance from the reference.

The adaptation rule used in many implementations is:

βt+1={2⋅βtif DKL>1.5⋅Dtargetβt/2if DKL<Dtarget/1.5βtotherwise\beta_{t+1} = \begin{cases} 2 \cdot \beta_t & \text{if } D_{KL} > 1.5 \cdot D_{\text{target}} \\ \beta_t / 2 & \text{if } D_{KL} < D_{\text{target}} / 1.5 \\ \beta_t & \text{otherwise} \end{cases}

where:

  • βt+1\beta_{t+1}: the updated KL coefficient for the next step
  • βt\beta_t: the current KL coefficient
  • DKLD_{KL}: the observed average KL divergence in the current batch (the signal used for feedback)
  • DtargetD_{\text{target}}: the desired target KL divergence (the setpoint for the controller)

This multiplicative update creates a dead zone around the target where β\beta remains stable, preventing oscillation while still responding to significant deviations. The factor of 1.5 defines the boundaries of this dead zone: as long as the observed KL stays between Dtarget/1.5D_{\text{target}}/1.5 and 1.5⋅Dtarget1.5 \cdot D_{\text{target}}, the coefficient remains unchanged. Only when KL drifts outside this range does the controller intervene with a multiplicative adjustment.

The choice of multiplicative rather than additive updates is deliberate and important. If β\beta is small (say, 0.01) and needs to increase substantially to constrain a rapidly diverging policy, an additive step of 0.1 would still leave β\beta much too small. A multiplicative doubling, on the other hand, always makes a proportionally significant change regardless of the current value. This scale-invariant behavior means the controller responds appropriately whether β\beta is 0.001 or 1.0.

Why Adaptive KL Works

Adaptive KL addresses a basic challenge: the appropriate constraint strength changes during training. Early in RLHF, when the policy is close to the reference, small rewards can produce large gradient updates that rapidly increase KL. Later, when the policy has already diverged somewhat, the same learning rate produces smaller relative changes. A fixed β\beta cannot account for these different regimes.

The dead-zone design deserves special attention as it prevents a failure mode called "controller chatter." If we updated β\beta every time the observed KL differed from the target by any amount, the coefficient would oscillate rapidly because the observed KL is noisy (it depends on the specific batch of prompts and sampled responses). By only updating when the deviation exceeds 50% of the target in either direction, we allow transient fluctuations without responding to them, while still catching real systematic deviations.

By targeting a consistent KL budget, adaptive control:

  • Prevents early-training instability from sudden KL spikes by tightening constraints before the policy escapes the reference distribution
  • Maintains learning signal late in training when fixed β\beta might over-constrain the policy by letting β\beta to decrease as needed
  • Provides a more interpretable hyperparameter: target KL in nats rather than an abstract penalty coefficient

The interpretability benefit is substantial. When using a fixed β\beta, it is difficult to know in advance what KL divergence will result. Different prompts, response lengths, and training stages all affect the relationship between β\beta and actual KL. With adaptive control, you directly specify the divergence budget you are comfortable with, and the algorithm finds the appropriate β\beta to reach it. A practitioner can say "I want the policy to diverge no more than 6 nats on average from the reference" and the controller will figure out how to enforce that, regardless of other training hyperparameters.

Alternative Adaptation Schemes

Some implementations use smoother adaptation:

βt+1=βt⋅(1+α⋅sign(DKL−Dtarget))\beta_{t+1} = \beta_t \cdot \left(1 + \alpha \cdot \text{sign}(D_{KL} - D_{\text{target}})\right)

where:

  • α\alpha: a small step size parameter (for example, 0.1), controlling how aggressively β\beta changes per step
  • sign(⋅)\text{sign}(\cdot): the sign function (returns +1 if the term is positive, -1 if negative), determining the direction of the update

This approach prevents the jarring factor-of-2 jumps while still steering toward the target. The updates are smaller and more frequent, creating smoother β\beta trajectories. However, this also means slower response to large deviations, which can be problematic if KL suddenly spikes due to a particularly influential batch. The right choice between the InstructGPT-style dead-zone controller and the smooth controller depends on how much noise exists in the observed KL estimates.

Others use proportional control:

βt+1=βt⋅exp⁡(α⋅DKL−DtargetDtarget)\beta_{t+1} = \beta_t \cdot \exp\left(\alpha \cdot \frac{D_{KL} - D_{\text{target}}}{D_{\text{target}}}\right)

where:

  • exp⁡(⋅)\exp(\cdot): the exponential function, so the multiplier is always positive and β\beta never becomes negative
  • DKL−DtargetDtarget\frac{D_{KL} - D_{\text{target}}}{D_{\text{target}}}: the relative error from the target, scaling the update based on the magnitude of the deviation

This scales the adjustment magnitude by how far off-target the current KL is. Small deviations produce small adjustments, while large deviations produce large adjustments. The exponential ensures that β\beta remains positive regardless of the error magnitude. This proportional approach offers a middle ground between the dead-zone method and the constant-step method, responding proportionally to the severity of the deviation. It also has the desirable property that β\beta can never become negative (due to the exponential), which would flip the penalty to a bonus and completely undermine the regularization.

Code Implementation

Let us implement KL divergence computation and adaptive control for RLHF training. We will build components that integrate with the PPO training loop from the previous chapter. The implementation focuses on clarity and correctness. This gives the building blocks that can be optimized for production use. We proceed from the simplest building block (per-token KL) to more complete integration (the full RLHF objective) and then to the adaptive controller that ties everything together.

Computing Per-Token KL Divergence

The core computation extracts log probabilities from both the policy and reference model, then computes their difference. This function forms the foundation of the KL penalty calculation, taking log probabilities that have already been extracted from model outputs and creating both per-token and per-sequence KL values.

In[8]:
Code
from typing import Optional, Tuple

import torch


def compute_token_kl(
    policy_logprobs: torch.Tensor,
    reference_logprobs: torch.Tensor,
    attention_mask: Optional[torch.Tensor] = None,
) -> Tuple[torch.Tensor, torch.Tensor]:
    """
    Compute per-token and per-sequence KL divergence.

    Args:
        policy_logprobs: Log probs from policy for selected tokens (batch, seq_len)
        reference_logprobs: Log probs from reference for same tokens (batch, seq_len)
        attention_mask: Mask showing valid tokens (batch, seq_len)

    Returns:
        per_token_kl: KL at each position (batch, seq_len)
        sequence_kl: Total KL per sequence (batch,)
    """
    # Per-token KL is simply the log ratio
    per_token_kl = policy_logprobs - reference_logprobs

    if attention_mask is not None:
        # Zero out KL for padding tokens
        per_token_kl = per_token_kl * attention_mask
        # Sum over valid tokens only
        sequence_kl = per_token_kl.sum(dim=-1)
    else:
        sequence_kl = per_token_kl.sum(dim=-1)

    return per_token_kl, sequence_kl

This computation is straightforward because we are using the sampled trajectory estimator. The log probability ratio for generated tokens equals the KL divergence in expectation. The attention mask handles variable-length sequences by zeroing out contributions from padding tokens, so the KL computation considers only content tokens. This masking is needed when processing batches of sequences with different lengths, as padding tokens should not contribute to the divergence measure. Without masking, sequences with more padding would appear to have lower divergence simply because the padding region contributes zero per-token difference, artificially inflating how similar a batch of short sequences appears.

Extracting Log Probabilities from Model Outputs

To compute KL, we need log probabilities for the sampled tokens. Here is how to extract them from model logits:

In[9]:
Code
import torch.nn.functional as F


def get_logprobs_for_tokens(
    logits: torch.Tensor, tokens: torch.Tensor
) -> torch.Tensor:
    """
    Extract log probabilities for specific tokens from logits.

    Args:
        logits: Model output logits (batch, seq_len, vocab_size)
        tokens: Token indices to get probs for (batch, seq_len)

    Returns:
        logprobs: Log probabilities for specified tokens (batch, seq_len)
    """
    # Convert logits to log probabilities
    log_probs = F.log_softmax(logits, dim=-1)

    # Gather log probs for the actual tokens
    # Shape: (batch, seq_len, 1) -> (batch, seq_len)
    token_logprobs = torch.gather(
        log_probs, dim=-1, index=tokens.unsqueeze(-1)
    ).squeeze(-1)

    return token_logprobs

The function first applies log_softmax to convert raw logits into log probabilities. This operation normalizes the logits so they stand for a valid probability distribution at each position. The gather operation then selects the log probability corresponding to each actual token from the full vocabulary distribution. This is much more efficient than computing probabilities for all tokens when we only need one per position. The unsqueeze and squeeze operations handle the dimension manipulation required by PyTorch's gather function.

The use of log_softmax rather than computing softmax and then taking the log is numerically important. Computing softmax first can lead to underflow for tokens with very negative logits, as the exponential can produce values below the floating point minimum. The combined log_softmax operation is numerically stable because it subtracts the maximum logit before computing the exponential, keeping all values in a reasonable range. This numerical stability is especially important for large vocabulary models where the distribution of logits can be highly skewed.

Adaptive KL Controller

The adaptive controller maintains the KL coefficient and adjusts it based on observed KL values. It implements the target KL approach described in the mathematical section. This gives a stateful object that can be updated after each training batch.

In[10]:
Code
class AdaptiveKLController:
    """
    Adaptive KL coefficient controller that targets a specific KL value.

    Based on the approach from InstructGPT (Ouyang et al., 2022).
    """

    def __init__(
        self,
        init_kl_coef: float = 0.1,
        target_kl: float = 6.0,
        horizon: int = 10000,
    ):
        """
        Args:
            init_kl_coef: Initial value of beta
            target_kl: Target KL divergence per response
            horizon: Number of steps over which to adapt (for smooth version)
        """
        self.kl_coef = init_kl_coef
        self.target_kl = target_kl
        self.horizon = horizon

        # Track history for monitoring
        self.kl_history = []
        self.coef_history = []

    def update(self, current_kl: float) -> float:
        """
        Update KL coefficient based on current KL divergence.

        Args:
            current_kl: Mean KL divergence from current batch

        Returns:
            Updated KL coefficient
        """
        self.kl_history.append(current_kl)

        # Multiplicative update (InstructGPT style)
        if current_kl > 1.5 * self.target_kl:
            self.kl_coef *= 2.0
        elif current_kl < self.target_kl / 1.5:
            self.kl_coef /= 2.0

        # Clamp to reasonable range
        self.kl_coef = max(min(self.kl_coef, 10.0), 0.001)

        self.coef_history.append(self.kl_coef)
        return self.kl_coef

    def get_coef(self) -> float:
        """Return current KL coefficient."""
        return self.kl_coef

The controller maintains a history of observed KL values and coefficient updates, which is useful for monitoring training dynamics and diagnosing issues. The clamping bounds prevent the coefficient from reaching extreme values that could either halt learning entirely (too high) or give no regularization (too low). These bounds can be adjusted based on the specific application and model scale. The history tracking is important for debugging: if the controller shows β\beta consistently increasing, it indicates the policy is consistently exceeding the target KL, which may suggest the reward model signal is too strong or the target KL is set too low for the task at hand.

Let us test the adaptive controller with simulated KL values:

In[11]:
Code
# Simulate training dynamics
initial_beta = 0.1
controller = AdaptiveKLController(init_kl_coef=initial_beta, target_kl=6.0)

# Simulate KL values that start high, then stabilize
simulated_kl = np.concatenate(
    [
        np.random.uniform(8, 12, 20),  # Early: KL too high
        np.random.uniform(5, 7, 30),  # Middle: near target
        np.random.uniform(2, 4, 20),  # Late: KL dropping
        np.random.uniform(5, 7, 30),  # Stabilized near target
    ]
)

for kl in simulated_kl:
    controller.update(kl)
Out[12]:
Console
Initial KL coefficient: 0.1
Target KL: 6.0

KL coefficient evolution:
  After high-KL phase (step 20): 10.0000
  After stable phase (step 50): 10.0000
  After low-KL phase (step 70): 0.0010
  Final value (step 100): 0.0010

The controller doubles β\beta when KL exceeds the target, pulling the policy back toward the reference, and halves it when KL drops too low, letting more exploration. The simulation shows how the controller responds to different training phases: increasing the coefficient during the high-KL early phase, maintaining stability during the near-target phase, decreasing during the low-KL phase, and stabilizing again as KL returns to the target range.

Visualizing Adaptive KL Dynamics

Out[13]:
Visualization
Line chart of KL divergence over 100 training steps with a dashed red target line, dotted boundary lines, and a green shaded stability zone.
Observed KL divergence during simulated training fluctuating around the target (dashed red line). The green shaded zone marks the stability band where $\beta$ remains unchanged. When KL falls outside this band, the controller applies multiplicative updates to steer back toward target.
Line chart of adaptive KL coefficient beta on a log scale over 100 training steps, showing multiplicative increases and decreases in response to KL excursions.
Adaptive KL coefficient $\beta$ on a log scale responding to KL excursions throughout training. The coefficient increases during high-KL phases (steps 1-20), stabilizes near target, decreases during the low-KL phase (steps 50-70), and settles again once KL returns to the target range.

The shaded region marks the "stable zone" where β\beta does not change. Outside this zone, the controller applies multiplicative updates to steer KL back toward target. The log scale on the right panel makes it easy to see the multiplicative nature of the updates: each adjustment doubles or halves the coefficient, which appears as equal-height steps on the log scale.

Complete KL Penalty Integration

Here is how KL computation integrates into a simplified RLHF training step. This function combines all the pieces we have developed, taking model outputs and creating the penalized objective along with diagnostic information.

In[14]:
Code
def compute_rlhf_objective(
    policy_logits: torch.Tensor,
    reference_logits: torch.Tensor,
    response_tokens: torch.Tensor,
    rewards: torch.Tensor,
    attention_mask: torch.Tensor,
    kl_coef: float,
) -> dict:
    """
    Compute the RLHF objective with KL penalty.

    Args:
        policy_logits: Logits from current policy (batch, seq_len, vocab)
        reference_logits: Logits from reference model (batch, seq_len, vocab)
        response_tokens: Generated token indices (batch, seq_len)
        rewards: Reward model scores (batch,)
        attention_mask: Valid token mask (batch, seq_len)
        kl_coef: KL penalty coefficient (beta)

    Returns:
        Dictionary with objective value and diagnostics
    """
    # Get log probabilities for generated tokens
    policy_logprobs = get_logprobs_for_tokens(policy_logits, response_tokens)
    reference_logprobs = get_logprobs_for_tokens(
        reference_logits, response_tokens
    )

    # Compute KL divergence
    per_token_kl, sequence_kl = compute_token_kl(
        policy_logprobs, reference_logprobs, attention_mask
    )

    # KL-penalized reward
    penalized_rewards = rewards - kl_coef * sequence_kl

    # Mean objective (to be maximized)
    objective = penalized_rewards.mean()

    return {
        "objective": objective,
        "mean_reward": rewards.mean().item(),
        "mean_kl": sequence_kl.mean().item(),
        "mean_penalized_reward": penalized_rewards.mean().item(),
        "kl_penalty": (kl_coef * sequence_kl.mean()).item(),
        "per_token_kl": per_token_kl,
    }

The function returns a dictionary containing both the objective value for optimization and diagnostic quantities for monitoring. The separation between raw reward and penalized reward helps track whether improvements come from higher reward model scores or reduced divergence. The per-token KL tensor is included for detailed analysis of where the policy diverges most from the reference. Logging these diagnostic quantities during training gives the situational awareness needed to diagnose problems: if mean_kl is rising but mean_reward is flat, the policy is drifting without improving, suggesting β\beta should be increased.

Let us show with synthetic data:

In[15]:
Code
# Create synthetic data
batch_size, seq_len, vocab_size = 4, 20, 1000
torch.manual_seed(42)

# Synthetic logits (policy slightly different from reference)
reference_logits = torch.randn(batch_size, seq_len, vocab_size)
policy_logits = reference_logits + 0.5 * torch.randn_like(reference_logits)

# Synthetic tokens and rewards
response_tokens = torch.randint(0, vocab_size, (batch_size, seq_len))
rewards = torch.tensor([0.8, 0.3, -0.2, 0.5])
attention_mask = torch.ones(batch_size, seq_len)

# Compute objective with different KL coefficients
low_beta = 0.01
results_low_beta = compute_rlhf_objective(
    policy_logits,
    reference_logits,
    response_tokens,
    rewards,
    attention_mask,
    kl_coef=low_beta,
)

high_beta = 0.5
results_high_beta = compute_rlhf_objective(
    policy_logits,
    reference_logits,
    response_tokens,
    rewards,
    attention_mask,
    kl_coef=high_beta,
)
Out[16]:
Console
Effect of KL Coefficient on Objective
=============================================

Low beta (0.01):
  Mean reward:          0.3500
  Mean KL:              -2.2833
  KL penalty:           -0.0228
  Penalized reward:     0.3728

High beta (0.5):
  Mean reward:          0.3500
  Mean KL:              -2.2833
  KL penalty:           -1.1416
  Penalized reward:     1.4916

With the same underlying KL divergence, a higher β\beta produces a much larger penalty, materially reducing the effective reward signal available for optimization. The comparison illustrates the large impact of the coefficient choice: with low β\beta, most of the reward signal passes through to guide learning, while with high β\beta, the penalty dominates and the effective reward becomes negative despite positive raw reward scores.

Key Parameters

Understanding the key parameters helps practitioners set up RLHF training correctly and diagnose problems when they occur. These parameters are conceptual knobs that control the basic behavior of the alignment process, not incidental implementation details.

The parameters for the KL divergence implementation are:

  • kl_coef (β\beta): The weight of the KL penalty term. Controls the tradeoff between reward maximization and reference adherence. Higher values enforce stricter proximity to the reference at the cost of slower learning. Starting values typically range from 0.01 to 0.2 depending on the application.

  • target_kl: The desired KL divergence value (in nats) for adaptive controllers. This is the setpoint for the feedback loop. A target of 6 nats is common in practice, corresponding to a moderate level of divergence that allows real behavioral change while preserving most of the reference model's capabilities. The right target depends on response length: a target of 6 nats spread over 100 tokens means an average of 0.06 nats per token, which is relatively small distributional shifts at each individual position.

  • horizon: In the smooth adaptive controller, this determines the timescale over which adaptation occurs. Longer horizons make the controller less reactive to transient fluctuations but also slower to respond to real regime changes during training.

Effects on Training Dynamics

The KL coefficient fundamentally shapes how RLHF training unfolds. Different values of β\beta do not just change the speed of learning; they change the trajectory through the space of possible policies, the types of outputs the policy learns to produce, and ultimately the nature of the alignment achieved. Let us examine these dynamics through simulation.

Reward vs KL Tradeoff Frontier

During training, there is a Pareto frontier between reward maximization and KL minimization. Different β\beta values trace different paths along this frontier, converging to different equilibrium points. Understanding this frontier is needed for reasoning about what RLHF can and cannot reach: no policy can simultaneously maximize reward and minimize KL divergence. Every step toward higher reward involves some cost in KL, and every step toward lower KL involves some sacrifice in reward.

In[17]:
Code
def simulate_training_trajectory(
    beta: float,
    n_steps: int = 100,
    reward_scale: float = 1.0,
    noise_scale: float = 0.08,
) -> dict:
    """
    Simulate RLHF training trajectory for a given beta.

    This is a simplified model where:
    - Reward increases with distance from reference (up to a point)
    - KL increases proportionally with this distance
    - Higher beta limits how far the policy moves
    """
    kl_values = [0.0]
    reward_values = [0.0]

    # Stronger KL penalties produce lower equilibrium divergence.
    equilibrium_kl = 18.0 / (1.0 + 8.0 * beta)

    for step in range(n_steps):
        current_kl = kl_values[-1]

        # Optimization approaches a beta-dependent equilibrium with small noise.
        kl_update = 0.08 * (equilibrium_kl - current_kl)
        kl_update += noise_scale * np.random.randn()

        new_kl = max(0, current_kl + kl_update)

        # The learned reward keeps increasing beyond the reliable region. That
        # extrapolative gain is the reward-hacking risk marked in the plot.
        reward_model_score = reward_scale * (
            2.0 * (1.0 - np.exp(-new_kl / 5.0)) + 0.08 * max(0, new_kl - 10.0)
        )
        new_reward = reward_model_score + noise_scale * np.random.randn()

        kl_values.append(new_kl)
        reward_values.append(new_reward)

    return {
        "kl": np.array(kl_values),
        "reward": np.array(reward_values),
        "beta": beta,
    }


# Simulate for different beta values
betas = [0.01, 0.05, 0.1, 0.3, 1.0]
trajectories = [simulate_training_trajectory(b) for b in betas]
Out[18]:
Visualization
Scatter plot showing reward versus KL divergence trajectories for five different beta values.
Training trajectories in reward-KL space for five different values of the KL coefficient beta. Lower beta values (warm colors) allow the policy to drift deep into the high-KL region where the reward model becomes unreliable (right of the red dashed boundary). Higher beta values (cool colors) constrain trajectories to lower KL, yielding lower peak reward but safer optimization. The circles mark starting points and squares mark endpoints.

The visualization illustrates the basic tradeoff. Low-β\beta policies (warm colors) quickly accumulate KL divergence and can reach high rewards, but risk entering regions where the reward model is unreliable. High-β\beta policies (cool colors) progress slowly but stay in well-calibrated regions of the reward model's coverage. The diagonal trajectory of intermediate β\beta values traces out the Pareto frontier between reward and divergence, with different positions on the frontier preferred depending on how much we trust the reward model and how large a behavioral change we are seeking.

KL Distribution Over Tokens

KL divergence is not uniform across tokens. Some positions contribute much more than others, and the pattern of contributions reveals what kind of adaptation the policy is making. A policy that consistently has high KL at the beginning of responses is learning a different way to introduce topics. A policy with high KL at specific content words is learning to prefer different vocabulary. A policy with uniformly moderate KL throughout is making subtle but consistent shifts across all aspects of generation.

In[19]:
Code
# Simulate realistic per-token KL patterns
seq_len = 50

# Different patterns for different response types
# Pattern 1: High KL at beginning (new phrasing)
pattern_1 = np.exp(-np.linspace(0, 3, seq_len)) * 2 + 0.1

# Pattern 2: High KL at end (novel conclusions)
pattern_2 = np.exp(np.linspace(-3, 0, seq_len)) * 1.5 + 0.1

# Pattern 3: Spiky (specific word choices)
pattern_3 = 0.2 * np.ones(seq_len)
spike_positions = [5, 12, 23, 35, 45]
for pos in spike_positions:
    pattern_3[pos] = np.random.uniform(1.5, 3.0)

# Add some noise
patterns = {
    "Reformulated opening": pattern_1 + 0.1 * np.random.randn(seq_len),
    "Novel conclusion": pattern_2 + 0.1 * np.random.randn(seq_len),
    "Specific word choices": pattern_3
    + 0.05 * np.abs(np.random.randn(seq_len)),
}
Out[20]:
Visualization
Area and line chart of per-token KL divergence over 50 token positions, showing high divergence at the start that decays exponentially.
Per-token KL divergence for the 'Reformulated opening' pattern. Divergence peaks at the start of the sequence and decays exponentially, showing the policy has learned to introduce topics differently while converging back to reference-like generation for the body of the response.
Area and line chart of per-token KL divergence over 50 token positions, showing low divergence at the start that grows exponentially toward the end.
Per-token KL divergence for the 'Novel conclusion' pattern. Divergence is low throughout the body but rises sharply at the end, showing the policy has learned to draw different conclusions or summaries while maintaining similar content in the middle.
Area and line chart of per-token KL divergence over 50 token positions, mostly near zero with sharp spikes at five specific positions.
Per-token KL divergence for the 'Specific word choices' pattern. Most tokens show minimal divergence, but isolated spikes appear at five specific positions where the policy consistently selects different vocabulary than the reference model would.

Understanding where KL accumulates helps diagnose what the policy is learning. If KL concentrates at specific positions, the policy is making targeted modifications. If KL is high throughout, the policy is developing a materially different generation style. If KL is concentrated at functionally useful words (verbs, key nouns, evaluation words), the policy is learning different semantic content, not just different phrasing. This per-token analysis is one of the most useful diagnostic tools in RLHF, giving interpretable insight into what the alignment training teaches the model.

Comparison: Fixed vs Adaptive KL

Let us compare training stability between fixed and adaptive KL control to see why practitioners increasingly prefer the adaptive approach for production-scale RLHF:

In[21]:
Code
def simulate_training_with_controller(
    controller_type: str,
    init_beta: float = 0.1,
    target_kl: float = 6.0,
    n_steps: int = 200,
    kl_disturbances: np.ndarray | None = None,
    reward_disturbances: np.ndarray | None = None,
) -> dict:
    """
    Simulate training with fixed or adaptive KL control.
    """
    beta = init_beta
    kl_values = []
    beta_values = []
    reward_values = []
    penalized_reward_values = []

    current_kl = 0.5
    if kl_disturbances is None:
        kl_disturbances = np.zeros(n_steps)
    if reward_disturbances is None:
        reward_disturbances = np.zeros(n_steps)

    for step in range(n_steps):
        # Reward optimization pushes outward while the KL coefficient supplies
        # a restoring force. Both controller variants see identical noise.
        optimization_pressure = 0.14
        constraint_force = 0.08 * beta * current_kl
        current_kl = max(
            0.1,
            current_kl
            + optimization_pressure
            - constraint_force
            + kl_disturbances[step],
        )

        reward = (
            1.5 * (1 - np.exp(-current_kl / 5.0)) + reward_disturbances[step]
        )

        # Evaluate both trajectories under the same initial coefficient so the
        # comparison isolates the value of controlling KL rather than changing
        # the reporting scale whenever the adaptive coefficient moves.
        penalized_reward = reward - init_beta * current_kl

        kl_values.append(current_kl)
        beta_values.append(beta)
        reward_values.append(reward)
        penalized_reward_values.append(penalized_reward)

        # Smooth multiplicative feedback raises beta above target and lowers it
        # below target without the extreme oscillations of hard doubling.
        if controller_type == "adaptive":
            relative_error = (current_kl - target_kl) / target_kl
            beta *= np.exp(0.12 * relative_error)
            beta = float(np.clip(beta, 0.02, 0.5))

    return {
        "kl": np.array(kl_values),
        "beta": np.array(beta_values),
        "reward": np.array(reward_values),
        "penalized_reward": np.array(penalized_reward_values),
        "controller": controller_type,
    }


shared_kl_disturbances = np.random.normal(0.0, 0.08, 200)
shared_reward_disturbances = np.random.normal(0.0, 0.05, 200)
fixed_results = simulate_training_with_controller(
    "fixed",
    kl_disturbances=shared_kl_disturbances,
    reward_disturbances=shared_reward_disturbances,
)
adaptive_results = simulate_training_with_controller(
    "adaptive",
    kl_disturbances=shared_kl_disturbances,
    reward_disturbances=shared_reward_disturbances,
)
Out[22]:
Visualization
Line chart of KL divergence over 200 training steps comparing fixed (orange) and adaptive (blue) controllers, with a red dashed target line at 6.
KL divergence over training steps for fixed (orange) and adaptive (blue) controllers. The adaptive controller keeps KL closer to the target (red dashed line), while the fixed controller allows progressive drift as the policy continues to move away from the reference.
Log-scale line chart of KL coefficient beta over 200 training steps: fixed (orange) is flat, adaptive (blue) fluctuates in response to KL excursions.
KL coefficient $\beta$ on a log scale over training. The fixed controller (orange) stays constant while the adaptive controller (blue) responds dynamically, increasing when KL is too high and decreasing when it drops below target.
Smoothed line chart of raw learned reward over 200 training steps, with fixed control rising higher as KL drifts and adaptive control settling lower near the target.
Smoothed raw reward over training steps for fixed (orange) and adaptive (blue) controllers. The fixed controller reaches a higher learned reward by allowing greater policy drift, while the adaptive controller accepts a lower raw score to remain near the KL target.
Smoothed line chart of reward minus a common beta 0.1 KL penalty over 200 training steps, showing adaptive control staying positive while fixed control trends toward zero.
Smoothed reward minus a common $\beta=0.1$ KL penalty for both trajectories. On this shared evaluation scale, the adaptive controller (blue) maintains a positive, stable objective while the fixed controller (orange) loses its early gains as unbounded KL growth increases the penalty.

The adaptive controller maintains KL divergence near the target by increasing β\beta when the policy drifts too far and relaxing it when the policy remains close to the reference. The fixed controller reaches a higher raw learned reward because it permits more drift, but its advantage disappears once both trajectories are evaluated with the same β=0.1\beta=0.1 KL cost. This shared evaluation makes the stability tradeoff visible without conflating it with the adaptive controller's changing coefficient: degradation in the penalized reward despite an improving raw reward indicates that policy drift is overwhelming the useful learning signal.

Limitations and Practical Considerations

While KL divergence is the standard constraint for RLHF, it has several real limitations worth understanding in depth. These limitations do not invalidate the approach; they are inherent to the problem of aligning language models with human preferences, and understanding them helps practitioners make better decisions about when and how to apply the KL penalty.

The reference model quality bottleneck. The KL penalty anchors the policy to a specific reference, typically the SFT model. This makes the quality of supervised fine-tuning important for RLHF success. If the SFT model has problematic behaviors, the KL penalty resists correcting them because moving away from those behaviors necessarily increases KL. For example, if the SFT model has learned to be verbose (perhaps because the supervised data over-represented detailed responses), the KL penalty will resist training the policy to be more concise, even if the reward model rewards conciseness. Conversely, if the SFT model is highly capable and well-calibrated, the constraint preserves that capability throughout RLHF. This bidirectional constraint is a feature when the reference is good and a bug when it is not, which is why so much effort goes into creating high-quality SFT data.

Distribution distance versus behavioral distance. KL divergence measures similarity between probability distributions, not between actual behaviors. Two policies could have small KL divergence but generate quite different outputs when sampled, especially for long sequences where small per-token differences compound across many conditional distributions. Conversely, policies with large KL might produce similar outputs if the probability differences are concentrated in low-probability regions that rarely get sampled. This mismatch between KL as a mathematical quantity and our actual goal (behavioral similarity) means that the KL penalty sometimes constrains the wrong thing. A policy that has learned to avoid a specific harmful phrase but otherwise generates identically to the reference might show higher KL than a policy that has not changed its harmful behavior at all but has shifted probability mass in obscure parts of the distribution.

Asymmetry creates specific biases. The KL divergence DKL(πθ∥πref)D_{KL}(\pi_\theta \| \pi_{\text{ref}}) penalizes the policy more heavily for assigning low probability to tokens the reference model assigns high probability to. This means the policy is discouraged from becoming more confident (more peaked distribution) than the reference, which can be desirable (maintaining diversity) or undesirable (preventing confident correct answers in factual domains). The asymmetry also means that the reverse KL DKL(πref∥πθ)D_{KL}(\pi_{\text{ref}} \| \pi_\theta) would have different properties and might better suit different scenarios. Research on choosing the right divergence direction for alignment remains active.

Computational overhead. Computing KL requires running both the policy and reference model on every training example. For large models, this roughly doubles the forward pass compute. In practice, this means RLHF training is approximately twice as expensive in compute as supervised fine-tuning on the same data, before accounting for the added complexity of running the reward model. Some implementations amortize this by caching reference model log probabilities for a fixed set of prompts, but this requires careful management of the cache and may miss the per-response nature of the KL computation. Memory requirements are also approximately doubled, since both models must reside in memory simultaneously during training.

KL divergence cannot constrain all capabilities simultaneously. The penalty keeps the overall distribution of the policy close to the reference, but it cannot prevent the policy from changing its behavior on specific important subsets of inputs. A policy could decrease KL on common inputs (where it is easy to stay close to the reference) while sharply changing behavior on rare but important inputs (like edge case harmful content). This "gaming" of the aggregate KL constraint is a subtle form of reward hacking that the KL penalty does not fully prevent. Supplementary evaluations on diverse test sets are needed to detect this kind of distributional shift.

Looking ahead, methods like Direct Preference Optimization (DPO), covered in the next chapter, incorporate the KL constraint implicitly in their objective. This eliminates the need for explicit KL computation during training while achieving similar regularization effects. DPO reparameterizes the RLHF objective in terms of policy log ratios, deriving a loss function that can be minimized directly on preference data without the need for a separate reward model or explicit KL penalty term. This is an elegant alternative to the explicit penalty approach we have examined here, trading the flexibility of separate reward modeling for the simplicity and stability of a closed-form training objective.

Summary

The KL divergence penalty is needed for stable RLHF training. It prevents reward hacking by keeping the policy close to a trusted reference model, preserving the language capabilities acquired during pre-training while letting targeted improvements in alignment. Without it, RLHF optimization rapidly degenerates into adversarial exploitation of reward model errors, creating outputs that are high-scoring but meaningless or harmful.

Key takeaways from this chapter:

  • KL divergence measures distribution difference: For autoregressive models, it decomposes into a sum of per-token log probability ratios, allowing efficient computation along sampled trajectories. The expectation-based form means we only need to evaluate the ratio for sampled tokens, not for all tokens in the vocabulary.

  • The coefficient β\beta controls the reward-constraint tradeoff: Low β\beta allows rapid learning but risks exploitation of reward model errors. High β\beta maintains stability but slows improvement. Typical values range from 0.01 to 0.2, with the right value depending on reward model quality, desired behavior change magnitude, and training stability.

  • Adaptive KL control maintains consistent constraints: By adjusting β\beta to target a specific KL budget, adaptive methods give stable training dynamics throughout optimization, preventing both early-training instability and late-training stagnation. The InstructGPT-style multiplicative dead-zone controller is a practical and reliable choice.

  • Per-token KL reveals what the policy learns: Analyzing where KL accumulates in generated sequences helps diagnose whether the policy is making targeted modifications or developing a fundamentally different generation style. High KL at the beginning of responses suggests changed discourse opening strategies; high KL at specific content words suggests vocabulary shifts; uniformly distributed KL suggests broad stylistic adaptation.

  • The reference model quality determines the ceiling: The KL constraint anchors the policy to the SFT model, making SFT quality a necessary determinant of RLHF outcomes. Strong SFT models let more effective alignment; weak SFT models transfer their limitations to the aligned model.

The KL penalty is one approach to constraining policy optimization. As we will see in the following chapters on DPO, there are alternative formulations that reach similar goals through different mechanisms. It offers tradeoffs in implementation complexity, computational efficiency, and training stability. Understanding the KL penalty deeply, as we have done here, gives the basis for understanding why these alternatives work and when to prefer them.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about KL divergence penalties in RLHF.

Comments

No comments yet. Be the first to share your thoughts!

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2025kldivergence, author = {Michael Brenndoerfer}, title = {KL Divergence Penalty in RLHF: Theory & Implementation}, year = {2025}, url = {https://mbrenndoerfer.com/writing/kl-divergence-penalty-rlhf-training}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-09-30} }
APAAcademic
Michael Brenndoerfer (2025). KL Divergence Penalty in RLHF: Theory & Implementation. Retrieved from https://mbrenndoerfer.com/writing/kl-divergence-penalty-rlhf-training
MLAAcademic
Michael Brenndoerfer. "KL Divergence Penalty in RLHF: Theory & Implementation." 2026. Web. September 30, 2026. <https://mbrenndoerfer.com/writing/kl-divergence-penalty-rlhf-training>.
CHICAGOAcademic
Michael Brenndoerfer. "KL Divergence Penalty in RLHF: Theory & Implementation." Accessed September 30, 2026. https://mbrenndoerfer.com/writing/kl-divergence-penalty-rlhf-training.
HARVARDAcademic
Michael Brenndoerfer (2025) 'KL Divergence Penalty in RLHF: Theory & Implementation'. Available at: https://mbrenndoerfer.com/writing/kl-divergence-penalty-rlhf-training (Accessed: September 30, 2026).
SimpleBasic
Michael Brenndoerfer (2025). KL Divergence Penalty in RLHF: Theory & Implementation. https://mbrenndoerfer.com/writing/kl-divergence-penalty-rlhf-training

About the author

Continue with the full handbook

This chapter is part of Language AI Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.

Explore Language AI Handbook
Newsletter

Stay up to date

Get articles, book updates, and news delivered to your inbox.

No spam, unsubscribe anytime.

or

Join the community

Sign in to remove popups, track your reading progress, and join the discussion.