Files
wehub-resource-sync a8262fc01e
docs / build (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:22 +08:00

247 lines
6.1 KiB
Markdown

# Objectives, Losses & Perplexity
The architecture defines what the model can compute. The objective defines what behavior training
rewards.
For a decoder-only language model, the base objective is next-token prediction.
## From logits to probability
At position \(t\), the model emits logits:
\[
z_t \in \mathbb{R}^{V}
\]
Softmax turns logits into a distribution:
\[
p_\theta(x_{t+1}=i \mid x_{\leq t})
= \frac{\exp(z_{t,i})}{\sum_{j=1}^{V}\exp(z_{t,j})}
\]
The target is one integer token id \(y_t\). Cross-entropy for that position is:
\[
\ell_t = -\log p_\theta(y_t \mid x_{\leq t})
\]
The batch loss is the mean over positions:
\[
\mathcal{L}_{\text{LM}}
= -\frac{1}{BT}\sum_{b=1}^{B}\sum_{t=1}^{T}
\log p_\theta(y_{b,t} \mid x_{b,\leq t})
\]
## The shift
The model receives:
\[
x = [t_0,t_1,\ldots,t_{T-1}]
\]
and predicts:
\[
y = [t_1,t_2,\ldots,t_T]
\]
In `src/models/transformer.py`, the simple `forward` path computes cross-entropy over all positions:
```python
logits, loss = model(idx, targets)
flat_logits = logits.view(B * T, C)
targets = targets.view(B * T).long()
loss = F.cross_entropy(flat_logits, targets)
```
The post-training SFT path makes the shift explicit because it needs a mask:
```python
logits = logits[:, :-1, :]
targets = tokens[:, 1:]
mask = loss_mask[:, 1:].to(logits.dtype)
```
## Perplexity
Perplexity is exponentiated cross-entropy:
\[
\text{PPL} = \exp(\mathcal{L})
\]
If the model assigns high probability to the true next tokens, loss falls and perplexity falls.
Interpretation:
- loss near \(\log(V)\) means the model is close to uniform random guessing;
- lower loss means the model is concentrating probability on plausible next tokens;
- validation loss matters more than training loss for generalization.
With `V = 50304`:
\[
\log(V) \approx 10.83
\]
So an untrained model often starts near that value.
## SFT masked loss
SFT still uses next-token cross-entropy, but only assistant completion tokens count:
\[
\mathcal{L}_{\text{SFT}} =
\frac{\sum_{b,t} m_{b,t}\,\ell_{b,t}}
{\sum_{b,t} m_{b,t}}
\]
where \(m_{b,t}=1\) for assistant tokens and \(0\) for prompt tokens.
Implementation in `src/post_training/sft.py`:
```python
ce = F.cross_entropy(
logits.reshape(-1, V).float(),
targets.reshape(-1).long(),
reduction="none",
)
ce = ce.view(targets.shape) * mask
return ce.sum() / mask.sum().clamp(min=1.0)
```
This distinction is crucial. The model should learn how to answer the prompt, not how to predict the
prompt itself.
## Sequence log-probabilities
Preference optimization and RL need the log-probability of an entire response, not only one token.
For response tokens \(a_1,\ldots,a_L\) after prompt \(p\):
\[
\log \pi_\theta(a \mid p)
= \sum_{t=1}^{L}
\log \pi_\theta(a_t \mid p, a_{<t})
\]
The repo computes this in `src/post_training/rollout.py` with `sequence_logprobs`. It applies a
response mask and sums token log-probs over answer positions.
That one primitive is reused by:
- DPO, ORPO, and KTO;
- PPO policy ratios;
- GRPO policy ratios;
- KL measurements against a frozen reference model.
## DPO objective
DPO uses preference pairs: chosen response \(y_w\) and rejected response \(y_l\). It compares the policy
against a frozen reference model:
\[
\Delta_\pi =
\log \pi_\theta(y_w \mid x) - \log \pi_\theta(y_l \mid x)
\]
\[
\Delta_{\text{ref}} =
\log \pi_{\text{ref}}(y_w \mid x) - \log \pi_{\text{ref}}(y_l \mid x)
\]
\[
\mathcal{L}_{\text{DPO}} =
-\log \sigma\left(\beta(\Delta_\pi - \Delta_{\text{ref}})\right)
\]
In `src/post_training/dpo.py`:
```python
pi_logratios = policy_chosen_logps - policy_rejected_logps
ref_logratios = ref_chosen_logps - ref_rejected_logps
logits = pi_logratios - ref_logratios
loss = -F.logsigmoid(beta * logits).mean()
```
Intuition: make the chosen response more likely than the rejected response, but measure the change
relative to the reference model so the policy does not drift without constraint.
## PPO objective in one picture
PPO samples responses, scores them, and updates the policy using the ratio between new and old action
probabilities:
\[
r_t(\theta) =
\frac{\pi_\theta(a_t \mid s_t)}
{\pi_{\text{old}}(a_t \mid s_t)}
= \exp(\log \pi_\theta - \log \pi_{\text{old}})
\]
The clipped policy objective is:
\[
\mathcal{L}_{\text{PPO}} =
-\mathbb{E}_t
\left[
\min
\left(
r_t(\theta) A_t,
\text{clip}(r_t(\theta),1-\epsilon,1+\epsilon) A_t
\right)
\right]
\]
The repo implements that in `src/post_training/ppo.py`:
```python
ratio = torch.exp(new_logp - old_logp)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1.0 - clip, 1.0 + clip) * advantages
loss = -masked_mean(torch.min(surr1, surr2), mask)
```
The clip prevents one update from moving too far from the sampled policy.
## GRPO objective in one picture
GRPO avoids a learned value function. For each prompt, it samples a group of responses and normalizes
their rewards within the group:
\[
A_i = \frac{r_i - \text{mean}(r_1,\ldots,r_G)}
{\text{std}(r_1,\ldots,r_G)+\epsilon}
\]
That advantage says: "Was this answer better or worse than its siblings for the same prompt?"
In `src/post_training/grpo.py`:
```python
r = rewards.view(-1, group_size)
adv = (r - r.mean(1, keepdim=True)) / (r.std(1, keepdim=True) + eps)
```
This is useful for verifiable-reward reasoning tasks because it removes PPO's value head and critic
training loop.
## Objective comparison
| Stage | Data | Main signal | Learns |
|---|---|---|---|
| Pretraining | raw token stream | next-token CE | language modeling |
| SFT | prompt/answer examples | masked next-token CE | instruction following format |
| Reward model | chosen/rejected pairs | Bradley-Terry preference loss | scalar preference scoring |
| DPO | chosen/rejected pairs | sequence log-prob preference loss | preference alignment without RL rollout |
| PPO | sampled responses + reward | clipped policy gradient | reward-seeking behavior under KL control |
| GRPO | grouped sampled responses + verifier | group-relative clipped policy gradient | verifier-driven reasoning without critic |
## Next
Loss gives direction. Optimization decides whether the model can follow it reliably. Continue to
[Optimization & Training Systems](optimization.md).