How to Interpret the Quantile Forecast Output from TimesFM
TimesFM returns probabilistic forecasts as a tensor of shape (num_series, horizon, 1 + num_quantiles) where the first slice contains the mean prediction and subsequent slices map to specific quantile levels using a +1 index offset.
TimesFM is a pretrained time series foundation model from Google Research that generates uncertainty-aware predictions through quantile regression. When calling the forecast API, the model outputs both point estimates and a full quantile tensor representing the conditional distribution at each future timestep. Correctly interpreting the quantile forecast output from TimesFM requires understanding the specific indexing convention used across the codebase.
Understanding the model.forecast() Return Values
The forecast() method in v1/src/timesfm/timesfm_base.py returns a tuple containing two distinct arrays:
point: A tensor of shape(num_series, horizon)containing the mean forecast for each input series and prediction horizon.quantiles: A tensor of shape(num_series, horizon, 1 + num_quantiles)containing the mean followed by all configured quantile predictions.
The third dimension always includes one extra slot for the mean, making the actual tensor depth 1 + len(model.hparams.quantiles). This design allows the model to output both central tendencies and uncertainty bounds from a single forward pass.
Decoding the Quantile Tensor Indexing
According to the source code in v1/src/timesfm/timesfm_base.py, the quantile tensor uses a fixed offset scheme:
- Index 0 (
[..., 0]): The mean forecast (identical to thepointarray). - Index 1 + i (
[..., 1 + i]): The quantile corresponding tomodel.hparams.quantiles[i].
The model ships with DEFAULT_QUANTILES = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9) defined in timesfm_base.py. This creates the following mapping for the default configuration:
| Quantile | Position i |
Tensor Index |
|---|---|---|
| 0.1 (10th percentile) | 0 | [..., 1] |
| 0.2 (20th percentile) | 1 | [..., 2] |
| 0.5 (median) | 4 | [[..., 5] |
| 0.9 (90th percentile) | 8 | [..., 9] |
The decoder implementation in v1/src/timesfm/patched_decoder.py confirms this structure, calculating output_dims = horizon_len * (1 + len(config.quantiles)) to accommodate the mean slot before the quantile-specific outputs.
Extracting Specific Percentiles in Python
To access individual quantiles from the forecast output, apply the +1 offset to the quantile's position in the configuration list:
import timesfm
import numpy as np
# Initialize model with default quantiles
model = timesfm.TimesFM.from_pretrained("timesfm_small")
inputs = [np.random.randn(500).astype(np.float32) for _ in range(3)]
# Generate forecasts
point, quantile_tensor = model.forecast(horizon=30, inputs=inputs)
# Access default quantiles (0.1, 0.2, ..., 0.9)
quantiles_list = model.hparams.quantiles # (0.1, 0.2, ..., 0.9)
median_idx = quantiles_list.index(0.5) # Returns 4
# Extract specific percentiles using +1 offset
mean_forecast = quantile_tensor[:, :, 0] # Same as 'point'
p10_forecast = quantile_tensor[:, :, 1] # 10th percentile (index 0 + 1)
median_forecast = quantile_tensor[:, :, 1 + median_idx] # 50th percentile (index 5)
p90_forecast = quantile_tensor[:, :, 9] # 90th percentile (index 8 + 1)
print(f"Median shape: {median_forecast.shape}") # (3, 30)
For custom quantile configurations, calculate the index dynamically:
def get_quantile_slice(quantile_tensor, target_quantile, model):
"""Extract specific quantile using the +1 offset rule."""
idx = model.hparams.quantiles.index(target_quantile)
return quantile_tensor[:, :, 1 + idx]
p25 = get_quantile_slice(quantile_tensor, 0.25, model)
Using the High-Level CSV Helper
The forecast_csv.py script in timesfm-forecasting/scripts/ provides a reference implementation for mapping tensor indices to human-readable keys:
results[col] = {
"forecast": point[i].tolist(),
"lower_90": quantiles[i, :, 1].tolist(), # 10th percentile (index 1)
"lower_80": quantiles[i, :, 2].tolist(), # 20th percentile (index 2)
"median": quantiles[i, :, 5].tolist(), # 50th percentile (index 5)
"upper_80": quantiles[i, :, 8].tolist(), # 80th percentile (index 8)
"upper_90": quantiles[i, :, 9].tolist(), # 90th percentile (index 9)
}
This pattern demonstrates the consistent application of the offset rule: lower_90 maps to the 0.1 quantile at tensor index 1, while upper_90 maps to the 0.9 quantile at tensor index 9.
Enabling Quantile Outputs in Configuration
Quantile forecasts require the continuous quantile head to be active. In src/timesfm/configs.py, the ForecastConfig class includes use_continuous_quantile_head=True to enable this output mode. When this configuration is set, the model produces the (1 + num_quantiles) dimensional output described above; otherwise, it may return only point predictions.
Summary
- TimesFM returns a tuple
(point, quantiles)wherequantileshas shape(num_series, horizon, 1 + num_quantiles). - The mean occupies index 0 of the last dimension, with configured quantiles starting at index 1.
- Apply a +1 offset when accessing quantiles: the i-th quantile in
hparams.quantilesis found at tensor index1 + i. - Default quantiles range from 0.1 to 0.9, placing the median (0.5) at index 5.
- Reference implementations in
forecast_csv.pydemonstrate practical extraction patterns for common confidence intervals.
Frequently Asked Questions
Why does the quantile tensor have an extra dimension for the mean?
The tensor includes the mean at index 0 to provide a consistent output structure regardless of whether quantile regression is enabled. As implemented in v1/src/timesfm/patched_decoder.py, the decoder computes output_dims = horizon_len * (1 + len(config.quantiles)), ensuring the mean is always available alongside uncertainty estimates. This design allows the point return value to simply reference quantiles[..., 0] without requiring separate computation paths.
How do I calculate the 95% prediction interval from TimesFM output?
With the default quantile configuration (0.1 to 0.9), you cannot extract exact 95% bounds directly. You would access quantiles[:, :, 1] for the 10th percentile (lower bound) and quantiles[:, :, 9] for the 90th percentile (upper bound) to create an 80% interval. For a true 95% interval, you must configure custom quantiles including 0.025 and 0.975 in hparams.quantiles before model initialization, then access them at indices 1 + i where i corresponds to their positions in your custom list.
What is the difference between the point return value and quantiles[:, :, 0]?
There is no mathematical difference; they contain identical values. The point array is provided as a convenience method to access mean forecasts without indexing into the quantile tensor. According to timesfm_base.py, the implementation returns the mean slice separately while also including it as the first channel of the quantile tensor for architectural consistency in the decoder output.
Can I change the quantile levels after loading a pretrained model?
No, the quantile levels are fixed during model initialization based on hparams.quantiles. The decoder in patched_decoder.py constructs the output projection layer with dimensions determined by len(config.quantiles), making the architecture dependent on the specific quantile configuration chosen at load time. To use different quantile levels, you must reload the model with a modified quantiles hyperparameter tuple in the configuration object passed to from_pretrained().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →