KL Divergence Penalty (RLHF) - Reinforcement Learning
Implement KL Divergence Penalty (RLHF) in Python with NumPy. medium exercise with examples, hints, and instant feedback. No setup or signup required.
KL Divergence Penalty (RLHF)
What you'll build
The sampled KL penalty β·KL(π_θ ‖ π_ref) that RLHF uses to keep the fine-tuned policy close to the reference model.
Background
A policy optimized against a learned reward model soon finds outputs that the reward model overrates ("reward hacking") and loses fluency. RLHF penalizes divergence from the frozen reference (SFT) model:
reward_total = reward - β · KL(π_θ ‖ π_ref)
The exact KL needs the full vocabulary distribution at every position. In practice it is estimated from the sampled tokens using the log-probs you already have.
Formula
For tokens y_t sampled from π_θ, the per-token estimate (often called k1) is the log-ratio:
kl_t = log π_θ(y_t) - log π_ref(y_t)
penalty = β · mean_t(kl_t)
Its average over samples from π_θ is the true KL, so it is unbiased. A single token's kl_t can be negative, and so can the mean over a few tokens. The k3 estimator, exp(-kl_t) + kl_t - 1, is never negative; GRPO uses it (see GRPO in Post-training & Efficiency).
Inputs
logp_policy: (T,) log-probs of the sampled tokens under π_θlogp_reference: (T,) log-probs of the same tokens under π_refbeta: KL coefficient (e.g. 0.01 to 0.2)
Expected return format
A Python float: β · mean(logp_policy - logp_reference), rounded to 2 decimals. Keep the sign; don't clip negative estimates to 0.
Edge cases and conventions
- Identical log-probs give 0.0.
- The mean is over all given tokens (no mask in this problem).
Constraints
NumPy only.
Follow-up
- InstructGPT-style PPO subtracts β·kl_t from the reward of every token, not once per sequence. How does that change the credit assignment?
- Adaptive KL control raises β when the measured KL exceeds a target. Sketch the update rule.
Examples
Input: logp_policy = [-1, -2], logp_reference = [-1.5, -2.5], beta = 0.1 Output: 0.05
kl_t = [0.5, 0.5], mean 0.5, penalty 0.1 · 0.5 = 0.05
Input: logp_policy = [-2, -3], logp_reference = [-1, -1], beta = 1.0 Output: -1.5
These tokens are less likely under the policy, so the sampled estimate is negative: mean([-1, -2]) = -1.5
Practice in Python
import numpy as np
def kl_penalty(logp_policy: np.ndarray, logp_reference: np.ndarray,
beta: float) -> float:
"""
Compute the sampled KL penalty for RLHF.
Args:
logp_policy: (T,) log-probs of the sampled tokens under pi_theta
logp_reference: (T,) log-probs of the same tokens under pi_ref
beta: KL penalty coefficient
Returns:
beta * mean(logp_policy - logp_reference) as a float, rounded to 2 decimals
"""
logp_policy = np.array(logp_policy, dtype=float)
logp_reference = np.array(logp_reference, dtype=float)
# Your code here
pass
The interactive editor loads with Python and NumPy. Run your implementation against 5 tests, or use the progressive hints.