Skip to content Skip to table of contents

KL Divergence

How to measure the difference between two probability distributions, and why it sits at the heart of modern LLM alignment.

When you fine-tune a large language model, how do you measure whether its output distribution is actually moving closer to the desired distribution? This question is a central to modern alignment techniques like RLHF [1, 2].

Large language models like GPTs output a probability distribution over tokens at each step. They assign a likelihood for each possible next word and then append the most likely next word to the output. During training the model outputs a predicted distribution QQ of the most likely next words. For ensuring correct output, the distribution QQ needs to be compared to the true distribution PP. This is exactly what KL-divergence does. KL-divergence takes the original distribution and compares it to a new generated distribution and outputs a value that tells how much the two distributions differ. The closer the value is to zero, the more similar the distributions are.

Theory

The Kullback-Leibler divergence from Q to P is defined as:

DKL(PQ)=klogpkpkqk D_{KL}(P||Q) = \sum_k \operatorname{log}p_k\frac{p_k}{q_k}

where pk=P(X=k)p_k = P(X = k) and qk=Q(X=k)q_k = Q(X = k) are the probabilities assigned by distributions PP and QQ to outcome kk, and the sum is taken over all outcomes kk in the support of PP.

KL divergence can be interpreted intuitively using the concept of entropy form information theory.

The entropy of PP is

H(P)=kpklogpk H(P) = - \sum_k p_k \operatorname{log}p_k

This is the average number of bits needed to encode samples from P using an optimal code for PP [3].

If instead if a code optimized for QQ was used to encode samples that came form PP, the average code length is the cross-entropy:

H(P,Q)=kpklogqk H(P, Q) = - \sum_k p_k \operatorname{log}q_k

Now combining entropy and cross entropy together, KL divergence is the extra cost you pay for using a code for QQ to encode samples from PP:

DKL(PQ)=H(P,Q)H(P)=kpklogqk(kpklogpk)=kpklogqk+kpklogpk=kpklogpkkpklogqk=kpk(logpklogqk)=kpklogpkqk \begin{align*} D_{KL}(P||Q) &= H(P, Q) - H(P) \\ &= - \sum_k p_k \operatorname{log}q_k - (- \sum_k p_k \operatorname{log}p_k) \\ &= - \sum_k p_k \operatorname{log}q_k + \sum_k p_k \operatorname{log}p_k \\ &= \sum_k p_k \operatorname{log}p_k - \sum_k p_k \operatorname{log}q_k \\ &= \sum_k p_k (\operatorname{log}p_k - \operatorname{log}q_k) \\ &= \sum_k p_k \operatorname{log} \frac{p_k}{q_k} \\ \end{align*}

Key properties

  • Asymmetric: DKL(PQ)DKL(QP)D_{KL}(P \| Q) \neq D_{KL}(Q \| P)

  • Always non-negative: DKL(PQ)0D_{KL}(P \| Q) \geq 0

  • Equal to zero only when the two distributions are identical. DKL(PQ)=0    P=QD_{KL}(P \| Q) = 0 \iff P = Q

So intuitively, the KL-divergence asks how supriced you would be about the current distribution of the model given you know what the training data looks like. The more suprised you would be, the further the model is from the truth and thus the KL-divergence would be bigger.

Code demo

Now imagine a small vocabulary of five tokens: ["cat", "sat", "on", "the", "mat"]. We have an imaginary LLM that has a predicted distribution QQ for the vocabulary and we also know the true distribution PP from the training data for the vocabulary.

import numpy as np

vocab = ["cat", "sat", "on", "the", "mat"]

# True distribution (training data)
P = np.array([0.4, 0.3, 0.1, 0.15, 0.05])

# Model's predicted distribution
Q = np.array([0.2, 0.25, 0.3, 0.15, 0.1])

By plotting PP and QQ the mismatch is immediately visible.

Show code
import matplotlib.pyplot as plt

x = np.arange(len(vocab))
width = 0.35

fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(x - width/2, P, width, label="P (true)", color="steelblue")
ax.bar(x + width/2, Q, width, label="Q (model)", color="tomato")

ax.set_xticks(x)
ax.set_xticklabels(vocab)
ax.set_ylabel("Probability")
ax.set_title(f"P vs Q")
ax.legend()
plt.tight_layout()
plt.show()

Directly applying the discrete formula DKL(PQ)=kpklogpkqkD_{KL}(P \| Q) = \sum_k p_k \log \frac{p_k}{q_k}:

def kl_divergence(p, q):
    return np.sum(p * np.log(p / q))

kl_pq = kl_divergence(P, Q)
print(f"D_KL(P || Q) = {kl_pq:.4f} nats")
D_KL(P || Q) = 0.1874 nats

The KL-divergence is non-zero so there is a difference between the true distribution PP and the current distribution QQ of our model. The difference between the true distribution PP and the predicted distribution QQ is obvious.

The order of the distributions is not trivial when computing KL-divergence. This can be shown shown numerically:

kl_qp = kl_divergence(Q, P)

print(f"D_KL(P || Q) = {kl_pq:.4f} nats")
print(f"D_KL(Q || P) = {kl_qp:.4f} nats")
print(f"Symmetric?   {np.isclose(kl_pq, kl_qp)}")
D_KL(P || Q) = 0.1874 nats
D_KL(Q || P) = 0.2147 nats
Symmetric?   False

Summary

KL-divergence can be used answering the question: how different are two probability distributions? The distribution is used, for example, LLM alignment to tell how far away the models predicted vocabulary distribution is from the training data distribution. KL-divergence has a mathematical foundation in information theory.

References

[1] D. M. Ziegler et al., “Fine-Tuning Language Models from Human Preferences,” arXiv preprint arXiv:1909.08593, 2019. [Online]. Available: https://arxiv.org/abs/1909.08593
[2] L. Ouyang et al., “Training language models to follow instructions with human feedback,” arXiv preprint arXiv:2203.02155, 2022. [Online]. Available: https://arxiv.org/abs/2203.02155
[3] I. Goodfellow, Y. Bengio, and A. Courville, Deep Learning. MIT Press, 2016. [Online]. Available: https://www.deeplearningbook.org