Implement Masked Cross-Entropy with Label Smoothing
OpenAIMachine Learning Engineer · Technical ScreenLast updated
Implement a numerically stable, masked cross-entropy loss function with label smoothing and temperature scaling in NumPy, returning both the loss and analytical gradients.
Problem Statement
In autoregressive language model training, the loss function must handle sequence padding via boolean/binary masks, avoid overconfidence via label smoothing, and maintain numerical stability across mixed-precision regimes.
Implement the function masked_smoothed_cross_entropy in pure NumPy. The function computes the smoothed cross-entropy loss averaged across all unmasked tokens and calculates the analytical gradient with respect to the input logits.
Mathematical Formulation
For a vocabulary of size C, label smoothing parameter epsilon in [0, 1), and ground-truth index y, the target probability distribution q is:
q(c) = (1 - epsilon) + (epsilon / C)ifc == yq(c) = epsilon / Cifc != y
Given logits z and temperature T > 0, the scaled logits are s = z / T. The model probabilities are obtained via softmax: p(c) = exp(s_c) / sum_k exp(s_k).
The cross-entropy loss for an active token is:
loss = - sum_c q(c) * log p(c) = (1 - epsilon) * (-log p(y)) - (epsilon / C) * sum_c log p(c)
The total loss is the average over all valid tokens where mask == 1. If no valid tokens exist, the loss should be 0.0.
Function Signature
import numpy as np
from typing import Tuple
def masked_smoothed_cross_entropy(
logits: np.ndarray, # shape: (B, S, C), float32/float64
targets: np.ndarray, # shape: (B, S), int32/int64
mask: np.ndarray, # shape: (B, S), bool or {0, 1}
epsilon: float = 0.1,
temperature: float = 1.0
) -> Tuple[float, np.ndarray]:
"""
Returns:
loss: Scalar float representing the average loss over valid tokens.
grad_logits: ndarray of shape (B, S, C), gradient w.r.t. input logits.
"""
pass
Requirements and Edge Cases
- Numerical Stability: Use the
log-sum-exptrick to computelog p(c)directly without underflowing or overflowing. - Input Validation: Check that
logits.ndim == 3,targets.ndim == 2,mask.ndim == 2, matching batch/sequence dimensions, valid target ranges0 <= targets < C, and all inputs are finite. RaiseValueErrorif validation fails. - Zero Valid Tokens: If
np.sum(mask) == 0, return0.0for loss and an array of zeros with shape(B, S, C)for the gradient. - Gradient: Compute the exact gradient
d(loss) / d(logits). Masked token positions must have zero gradient.
Hints
Hint 1
Decompose the per-token loss into two terms: standard negative log-likelihood of the target class scaled by (1 - epsilon) and the mean negative log-probability across all classes scaled by epsilon.
Hint 2
To avoid numerical overflow and underflow, compute log-probabilities using the identity: log_softmax(s) = s - max(s) - log(sum(exp(s - max(s)))).
Hint 3
By chain rule, the gradient with respect to unscaled logits z is (p - q) / (T * N_valid) for unmasked tokens, where N_valid is the total number of valid tokens in the batch.
Follow-up questions
- How would you optimize the memory footprint of this loss calculation in CUDA or Triton to avoid materializing the full (B, S, C) probability tensor?
- In distributed training with pipeline parallelism or micro-batching, how should the denominator (number of valid tokens) be normalized across micro-batches?
- How does temperature scaling affect the magnitude of gradients when using optimizers with adaptive learning rates (e.g., AdamW) versus standard SGD?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.