Predicting Model Performance › Uncertainty Quantification
Language AI HandbookAll chapters Predicting Model Performance › Uncertainty QuantificationOne section · Sections in this chapter4 / 10 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Any prediction is incomplete without uncertainty bounds. Scaling law predictions face several sources of uncertainty that compound as we extrapolate further. Understanding and quantifying this uncertainty is needed for sound decision-making. A prediction of loss 2.0 with uncertainty ±0.1 means something very different from the same prediction with uncertainty ±0.5.
The sources of uncertainty fall into two broad categories. First, there's parameter uncertainty: our fitted parameters are estimates based on noisy data, and different training runs might yield slightly different fits. Second, there's model uncertainty: the scaling law functional form itself is an approximation that might break down at extreme scales. Both types of uncertainty grow as we extrapolate further from observed data.
Parameter Uncertainty
The fitted parameters have uncertainty, captured in the covariance matrix from our optimization. When we fit a nonlinear model like the scaling law, the optimization process estimates the best-fit parameters and how much those estimates might vary given the noise in our data.
The covariance matrix encodes this uncertainty. It's a symmetric matrix where diagonal elements represent the variance of each parameter (how much that parameter's estimate might fluctuate if we repeated the fitting with different data), and off-diagonal elements capture how parameter uncertainties correlate (if one parameter is estimated too high, is another likely to be estimated too high or too low?). These correlations matter for prediction because errors in different parameters can either compound or cancel.
import numpy as np
from scipy.optimize import curve_fit
# Re-define necessary variables for this cell
training_runs = [
(10, 2, 3.45),
(10, 5, 3.28),
(10, 10, 3.15),
(50, 10, 2.95),
(50, 25, 2.78),
(50, 50, 2.65),
(100, 20, 2.75),
(100, 50, 2.58),
(100, 100, 2.48),
(300, 60, 2.52),
(300, 150, 2.35),
(300, 300, 2.25),
]
N_params = np.array([r[0] * 1e6 for r in training_runs])
D_tokens = np.array([r[1] * 1e9 for r in training_runs])
losses = np.array([r[2] for r in training_runs])
def chinchilla_loss(X, E, A, B, alpha, beta):
N, D = X
return E + A / np.power(N, alpha) + B / np.power(D, beta)
X_data = np.array([N_params, D_tokens])
p0 = [1.5, 400, 400, 0.34, 0.28]
bounds = ([0, 0, 0, 0.1, 0.1], [3, 1e6, 1e6, 0.8, 0.8])
params, covariance = curve_fit(
chinchilla_loss, X_data, losses, p0=p0, bounds=bounds, maxfev=10000
)
# Standard errors from covariance matrix diagonal
# The diagonal of the covariance matrix contains variances; sqrt gives standard errors
param_stderrs = np.sqrt(np.diag(covariance))
param_names = ["E", "A", "B", "alpha", "beta"]Parameter estimates with standard errors: --------------------------------------------- E: 0.7311 ± 0.5081 (69.5% relative) A: 95.1544 ± 145.3404 (152.7% relative) B: 19.2514 ± 8.3036 (43.1% relative) α: 0.3282 ± 0.1071 (32.6% relative) β: 0.1000 ± 0.0314 (31.4% relative)
The relative errors reveal which parameters are best-constrained by our data. Parameters with smaller relative errors are more reliably estimated, while those with larger relative errors contribute more uncertainty to predictions. Typically, the exponents and have higher relative uncertainty because power-law exponents are inherently difficult to estimate exactly. Small changes in the exponent create large changes in the prediction at extreme scales.
These uncertainties propagate through our predictions via error propagation. The mathematical challenge is that our scaling law is a nonlinear function of the parameters, so we can't simply add uncertainties in quadrature as we might for a linear model. Instead, we use Monte Carlo sampling: generate many sets of plausible parameters from the fitted distribution, compute predictions for each, and examine the spread of results.
def predict_with_uncertainty(
N_millions, D_billions, params, covariance, n_samples=1000
):
"""
Predict loss with uncertainty via Monte Carlo sampling from the parameter posterior.
The idea: instead of using point estimates for scaling law parameters,
we sample from their joint distribution (approximated as multivariate normal)
and compute loss for each sample. This propagates parameter uncertainty
through to prediction uncertainty.
Mathematically, if $\theta \sim \mathcal{N}(\hat{\theta}, \Sigma)$ represents our uncertain parameters, then:
$$L(N,D) \approx \mathbb{E}[f(N, D; \theta)] \pm \text{std}[f(N, D; \theta)]$$
where $f$ is the scaling law and the expectation/std are over parameter samples.
Args:
N_millions: Model size in millions of parameters
D_billions: Training data in billions of tokens
params: Fitted parameter estimates [E, A, B, α, β]
covariance: Parameter covariance matrix from curve fitting
n_samples: Number of Monte Carlo samples (more = tighter estimates)
Returns dict with mean, std, and 95% confidence interval bounds.
"""
N = N_millions * 1e6
D = D_billions * 1e9
# Sample parameters from multivariate normal
param_samples = np.random.multivariate_normal(params, covariance, n_samples)
# Compute loss for each parameter sample
loss_samples = []
for p in param_samples:
E, A, B, alpha, beta = p
# Skip unphysical samples
if alpha <= 0 or beta <= 0 or A < 0 or B < 0:
continue
loss = E + A / np.power(N, alpha) + B / np.power(D, beta)
if loss > 0: # Must be positive
loss_samples.append(loss)
loss_samples = np.array(loss_samples)
return {
"mean": np.mean(loss_samples),
"std": np.std(loss_samples),
"ci_lower": np.percentile(loss_samples, 2.5),
"ci_upper": np.percentile(loss_samples, 97.5),
}The Monte Carlo approach has a clear interpretation: we're asking "given everything we know about parameter uncertainty, what range of predictions is plausible?" Each sample represents a possible true state of the world consistent with our observations. The spread of predictions across samples reflects our uncertainty about the outcome.
Predictions with 95% confidence intervals:
-----------------------------------------------------------------
1B params, 500B tokens: 2.032 [0.919, 2.277]
7B params, 2.0T tokens: 1.860 [1.101, 2.098]
70B params, 15.0T tokens: 1.636 [0.759, 1.959]Extrapolation Distance and Uncertainty Growth
Uncertainty grows with extrapolation distance. A useful heuristic tracks how far beyond training data we're predicting. This growth isn't arbitrary. It reflects a mathematical property: the further we extrapolate, the more our predictions depend on the precise values of parameters we've estimated imperfectly, and small errors in those parameters translate to larger errors in predictions.
The relationship between extrapolation distance and uncertainty isn't linear. Near our training data, even moderate parameter uncertainty produces tight predictions because all parameter sets agree closely. Far from training data, the fan-out effect appears: different parameter sets that all fit the training data equally well can diverge dramatically in their extrapolated predictions. This is especially true for the exponents, where a small difference (say, 0.34 vs 0.36) makes little difference at but substantial difference at .

@fig-uncertainty-growth shows a necessary pattern: within the training regime (green shading), predictions are well-constrained with tight confidence bands. Beyond our largest training run (red line), confidence intervals expand rapidly. The shaded region fans out as we push into unexplored territory. This visual representation helps calibrate how much trust to place in extrapolated predictions. A prediction at 1B parameters might be reliable enough to bet on; a prediction at 100B parameters should be treated as indicative rather than definitive.
Model Uncertainty
Beyond parameter uncertainty, there's model uncertainty. The scaling law functional form itself might not hold at extreme scales. Model uncertainty is harder to quantify because it involves questioning assumptions that underlie the approach. Parameter uncertainty asks "given that the scaling law is correct, what's our uncertainty in the prediction?" Model uncertainty asks "what if the scaling law itself is wrong?"
Several approaches help assess model uncertainty:
Holdout validation: Fit on smaller runs, predict on largest held-out runs. This tests the key extrapolation scenario: can we predict performance of models larger than those used for fitting? The errors on holdout data give an empirical estimate of extrapolation accuracy within our observed range.
Functional form comparison: Compare predictions from different scaling law variants. If the Chinchilla form, the OpenAI form, and other variants all agree at some scale, we have more confidence; if they diverge, we know model uncertainty is significant.
Domain expertise: Physics-based constraints can bound plausible loss values. The irreducible loss can't be negative. Loss can't drop below certain information-theoretic limits. These constraints don't quantify uncertainty but can flag implausible predictions.
import numpy as np
from scipy.optimize import curve_fit
def holdout_validation(
training_runs, holdout_fraction=0.3, p0=p0, bounds=bounds
):
"""
Validate scaling law by holding out largest runs.
This tests whether scaling laws fitted on smaller models can predict
performance of larger models - the main use case for extrapolation.
Args:
training_runs: List of (params_millions, tokens_billions, loss) tuples
holdout_fraction: Fraction of largest runs to hold out for testing
Returns:
Dict with train/test counts, individual errors, MAE, and bias
"""
# Sort by scale (parameters × tokens)
sorted_runs = sorted(training_runs, key=lambda r: r[0] * r[1])
# Hold out largest runs
n_holdout = max(1, int(len(sorted_runs) * holdout_fraction))
train_runs = sorted_runs[:-n_holdout]
test_runs = sorted_runs[-n_holdout:]
# Fit on training subset
N_train = np.array([r[0] * 1e6 for r in train_runs])
D_train = np.array([r[1] * 1e9 for r in train_runs])
L_train = np.array([r[2] for r in train_runs])
try:
fit_params, _ = curve_fit(
chinchilla_loss,
np.array([N_train, D_train]),
L_train,
p0=p0,
bounds=bounds,
maxfev=10000,
)
# Predict on holdout
errors = []
for r in test_runs:
N, D, actual = r[0] * 1e6, r[1] * 1e9, r[2]
predicted = chinchilla_loss(np.array([[N], [D]]), *fit_params)[0]
errors.append(predicted - actual)
return {
"train_runs": len(train_runs),
"test_runs": len(test_runs),
"errors": errors,
"mae": np.mean(np.abs(errors)),
"bias": np.mean(errors),
}
except:
return NoneHoldout validation results: Training runs: 9 Holdout runs: 3 Mean absolute error: 0.0435 Bias (positive = underpredict): -0.0435 Individual holdout errors: ['-0.067', '-0.034', '-0.030']
The holdout validation gives us an empirical estimate of how well our scaling law extrapolates within the observed range. The mean absolute error tells us typical prediction accuracy in absolute terms. The bias reveals systematic tendencies: positive bias indicates our predictions are optimistic (actual loss is lower than predicted, meaning models perform better than expected), while negative bias indicates pessimism (models underperform predictions). Understanding the direction of bias helps calibrate expectations and make appropriately conservative or aggressive decisions.