# 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_{