How to Forecast Time Series with Mistral Timestral on CPU: Install, Covariates, Quantiles, License
If you forecast anything (server load, sales, sensor readings, energy demand), Mistral’s new Timestral models are worth an afternoon: they forecast zero-shot, meaning you hand them a history array and get a forecast back with no training step, they return a full spread of quantiles instead of one line, and they run on a plain CPU. The largest model has only 209M parameters. This guide takes you from pip install to a forecast with covariates, using the code from Mistral’s official repository, and shows what we measured when we ran it on a real public dataset.
Key facts (from Mistral’s announcement, GitHub repo and Hugging Face model cards, checked October 19, 2026):
- Four sizes: Timestral-5M, 28M, 123M and 209M. fp32 weights are 21 MB, 111 MB, 490 MB and 837 MB.
- Context up to 32,768 time steps; up to 1,024 steps forecast per forward pass. Longer horizons are forecast block by block.
- Output: 21 quantiles per time step, from 0.01 to 0.99.
- Inputs: univariate or multivariate targets, plus past-only, known-future and categorical covariates. NaN marks a missing value.
- No GPU needed. Mistral reports 10 ms (5M) to 140 ms (209M) to forecast one day of 15-minute data from three weeks of history on an 8-thread server CPU.
- Licenses differ: the code is Apache 2.0, but the weights use a Modified MIT license that does not apply to companies with more than $20 million in monthly revenue.
If you’re new to the site, the 7minai homepage lists our other step-by-step model guides. Like Mistral’s Shieldstral moderation model, Timestral is a small, specialised open-weight model, but the setup is even simpler: there’s no serving stack at all, just a Python package.
What Timestral is (and when to use it)
Timestral is a time-series foundation model: a transformer pre-trained on a large corpus of time series so it can forecast series it has never seen. Mistral’s announcement contrasts this with classical models like ARIMA, which are fitted to one series at a time, and with deep-learning models like PatchTST, which “are still typically trained from scratch on each dataset.”
According to the model card, the architecture is a patch-based transformer with time attention and variate attention. The 209M model has 24 layers and a 768-dimension embedding, and each patch covers 32 time steps. Mistral says most of the pre-training data was synthetic, with a second post-training stage on a mix of real and synthetic data at a 4x longer context.
Reach for it when:
- You have many related series (thousands of sensors, SKUs, servers) and don’t want to fit and maintain one model per series.
- You need uncertainty, not just a point forecast, for alert thresholds, safety stock or capacity headroom.
- You know some future drivers in advance (holidays, a production schedule, a weather forecast) and want the forecast to condition on them.
- You want something that runs inside an existing Python job on CPU.
It’s not a language model. You don’t prompt it, and it doesn’t explain its forecasts. It takes arrays of numbers and returns arrays of quantiles.
How it scores
Mistral says Timestral-209M ranked first among pretrained, zero-shot models on GIFT-Eval, TIME and fev-bench at launch. The model card gives these numbers (lower is better for MASE and CRPS, higher is better for skill scores):
| Benchmark | Metric | Timestral-209M | TimesFM-3 | Chronos-2 |
|---|---|---|---|---|
| GIFT-Eval | MASE ↓ | 0.658 | 0.667 | 0.698 |
| GIFT-Eval | CRPS ↓ | 0.446 | 0.456 | 0.485 |
| TIME | MASE ↓ | 0.626 | 0.640 | 0.662 |
| fev-bench | SQL skill score ↑ | 50.39 | 48.99 | 47.28 |
These are Mistral’s reported results. The announcement says they were submitted to the GIFT-Eval and TIME leaderboards, and a technical report is listed as “coming soon.” Treat the margins as small: on GIFT-Eval MASE, 209M and 123M differ by 0.001.
Step 1: Pick a model size
All four sizes share the same architecture and training recipe. From the model cards:
| Model | Hub repo | Weights (fp32) | GIFT-Eval MASE | fev-bench SQL skill |
|---|---|---|---|---|
| Timestral-5M | mistralai/Timestral-5M | 21 MB | 0.705 | 46.19 |
| Timestral-28M | mistralai/Timestral-28M | 111 MB | 0.678 | 48.42 |
| Timestral-123M | mistralai/Timestral-123M | 490 MB | 0.659 | 50.18 |
| Timestral-209M | mistralai/Timestral-209M | 837 MB | 0.658 | 50.39 |
Mistral’s own guidance: the 28M model is five times faster than the 209M, and the 123M model “comes within 0.2% of the 209M score at three quarters of its latency.”
A practical rule:
- Prototyping, or forecasting thousands of series on a small box: start with 5M or 28M.
- Production accuracy at moderate volume: 123M gives almost all of the 209M’s accuracy.
- Benchmarks, or when every point of error matters: 209M.
Step 2: Install
You need Python 3.10 or newer and PyTorch 2.4 or newer. The README says the package needs only PyTorch, NumPy, safetensors and huggingface_hub.
On a CPU-only machine, install the CPU build of PyTorch first so pip doesn’t pull the much larger CUDA build, then install Timestral from GitHub:
python3 -m venv .venv
source .venv/bin/activate
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install git+https://github.com/mistralai/timestral.git
The first pip install torch line uses PyTorch’s standard CPU wheel index. On a GPU machine, skip it and let pip resolve torch normally. The official one-liner from the README is just:
pip install git+https://github.com/mistralai/timestral.git # Python >= 3.10, torch >= 2.4
If you want the exact dependency versions Mistral pinned, use uv from a clone:
git clone https://github.com/mistralai/timestral.git && cd timestral
uv sync --frozen
source .venv/bin/activate
There’s no PyPI release at the time of writing. The package is installed from GitHub and reports itself as timestral 1.0.0. Mistral says it doesn’t accept pull requests, but bug reports go to GitHub issues.
Step 3: Run your first forecast
This is the README’s minimal example:
import numpy as np
from timestral import TimestralForecaster
forecaster = TimestralForecaster.from_pretrained("mistralai/Timestral-209M") # device="cuda" for a GPU
context = np.random.randn(2, 1, 2000).cumsum(-1) # (series, variates, time); NaN = missing
forecast = forecaster.forecast(context, prediction_length=96)
forecast.quantiles.shape # (2, 1, 96, 21): series, variates, horizon, quantile levels
forecast.quantile_levels # (0.01, 0.05, 0.1, ..., 0.9, 0.95, 0.99)
forecast.median # (2, 1, 96)
forecast.quantile(0.9) # (2, 1, 96)
from_pretrained downloads config.json and model.safetensors from Hugging Face once and caches them. No Hugging Face token is needed for the public repos.
Input shapes
context accepts three shapes, as a NumPy array or a torch tensor:
(T,): one series, for exampledf["sales"].to_numpy().(B, T): B series, one variate each.(B, V, T): B series with V variates each. The variates of a series are forecast jointly, so related signals (say a pump’s vibration, temperature and pressure) can inform each other.
In our test a pandas Series was also accepted directly, but passing .to_numpy() is the safer habit.
Two more rules from the README:
- Context is capped at 32,768 steps. Pass more and the most recent steps are kept.
- Horizons over 1,024 steps are forecast in blocks, with each block’s median fed back as context. We asked for 2,000 steps and got a
(1, 1, 2000, 21)result without error, but expect accuracy to degrade the further out you go.
Reading the output
forecast.quantiles has shape (B, V, horizon, 21). The 21 levels are 0.01, 0.05, 0.10, 0.15 and so on in steps of 0.05 up to 0.95, then 0.99. Common uses:
median = forecast.median # your point forecast
lo, hi = forecast.quantile(0.1), forecast.quantile(0.9) # an 80% interval
p95 = forecast.quantile(0.95) # e.g. capacity planning headroom
Step 4: Try it on real data
To see what this looks like outside the synthetic example, we ran Timestral on the box we use for testing (8 CPU threads, PyTorch 2.14.1 CPU build) against ETTh1, a widely used public benchmark of hourly electricity-transformer readings from the ETDataset repository. We gave each model the 12 weeks of oil-temperature (OT) history before the last 96 hours, forecast those 96 hours, and compared them to what actually happened:
import time, numpy as np, pandas as pd, torch
from timestral import TimestralForecaster
torch.set_num_threads(8)
df = pd.read_csv("ETTh1.csv", parse_dates=["date"])
y = df["OT"].to_numpy(dtype=np.float32)
H = 96
ctx, actual = y[-(H + 2016):-H], y[-H:] # 12 weeks of hourly history, hold out 4 days
for name in ["mistralai/Timestral-5M", "mistralai/Timestral-209M"]:
m = TimestralForecaster.from_pretrained(name)
m.forecast(ctx, prediction_length=H) # warm-up
t = time.time()
f = m.forecast(ctx, prediction_length=H)
print(name, f"{time.time() - t:.3f}s")
med = np.asarray(f.median).reshape(-1)
lo = np.asarray(f.quantile(0.1)).reshape(-1)
hi = np.asarray(f.quantile(0.9)).reshape(-1)
print(" MAE", np.abs(med - actual).mean(),
" 80% interval coverage", ((actual >= lo) & (actual <= hi)).mean())
What we got:
| Mean absolute error (°C) | Actuals inside the 10–90% band | Forecast time (warm) | |
|---|---|---|---|
| Seasonal naive (repeat last 24 h) | 2.89 | n/a | n/a |
| Timestral-5M | 0.97 | 97% | 0.014 s |
| Timestral-209M | 0.70 | 97% | 0.116 s |
This is one forecast window on one dataset, not a benchmark. It’s enough to show the workflow, the CPU speed (in line with the 10–140 ms Mistral reports) and that both sizes clearly beat a naive baseline here. Before trusting it on your own data, run a proper backtest over many windows (Step 7).
First-time loading took a few seconds per model, including the download. After that the weights load from the Hugging Face cache.
Step 5: Add covariates
Covariates are extra input rows that help the forecast without being forecast themselves. The README says: “covariate rows never read the targets.” There are two kinds:
- Past covariates are known only over the history window: other sensors, or yesterday’s ad spend. Shape
(B, C_past, T), the same length as the context. - Known-future covariates are known over history and the forecast horizon: calendar features, planned promotions, a production schedule, a weather forecast. Shape
(B, C_known, T + prediction_length).
forecast = forecaster.forecast(
context, # (B, V, T)
prediction_length=96,
past_covariates=past, # (B, C_past, T): known over the context only
known_covariates=known, # (B, C_known, T + 96): known over context and horizon
)
Here’s a concrete version we ran on the same ETTh1 data: three target variates forecast jointly, plus hour-of-day encoded as sine and cosine as a known-future covariate:
X = df[["HUFL", "HULL", "OT"]].to_numpy(dtype=np.float32).T # (V, T)
ctx_m = X[:, -(H + 2016):-H][None] # (1, 3, 2016)
hours = df["date"].dt.hour.to_numpy()[-(H + 2016):] # covers context + horizon
known = np.stack([np.sin(2 * np.pi * hours / 24),
np.cos(2 * np.pi * hours / 24)]).astype(np.float32)[None] # (1, 2, 2112)
m = TimestralForecaster.from_pretrained("mistralai/Timestral-5M")
f = m.forecast(ctx_m, prediction_length=H, known_covariates=known)
print(f.quantiles.shape) # (1, 3, 96, 21)
Two details from the README worth knowing:
- Binary rows are treated as indicators. A known-future row whose values are all 0 or 1 (for example
is_holiday) is handled as an on/off flag. - NaN in a covariate means missing, same as in the target.
Categorical covariates
For values like day of week, store ID or machine state (running, idle, maintenance), don’t encode them as ordinary numbers, since that implies an order. Pass numeric codes through the categorical arguments instead:
codes, _ = pd.factorize(df["machine_state"]) # any numeric codes; NaN = missing
# slice codes into codes_past (context length T) and codes_known (T + horizon), shaped (B, C, ...)
forecast = forecaster.forecast(
context,
prediction_length=96,
categorical_past_covariates=codes_past, # (B, C, T)
categorical_known_covariates=codes_known, # (B, C, T + 96)
)
For a single-variate target, Timestral target-encodes each category per series. The README gives the formula: category c becomes (mean + sum_c) / (1 + count_c) over the forward-filled context, and a category never seen in the context maps to the series mean. For multivariate targets the raw codes are used as is, so categorical covariates are most useful with one target variate per series. The encoder is exposed as timestral.covariate_encoding.target_encode if you want to inspect it.
Step 6: Missing data, GPUs and offline use
Gaps. Put NaN wherever a value is missing. You don’t need to impute first. You can also pass observation_mask (same shape as context, False = missing) to mark more values as missing. When we blanked out 50 points in the middle of a series, the forecast came back with no NaNs.
GPU. Pass device="cuda". The model card’s example picks it automatically:
import torch
model = TimestralForecaster.from_pretrained(
"mistralai/Timestral-209M",
device="cuda" if torch.cuda.is_available() else "cpu",
)
Pin a version and run offline. from_pretrained takes revision (a branch, tag or commit), cache_dir and token, which work as in huggingface_hub. For air-gapped or reproducible jobs, download once, then load with local_files_only=True or set HF_HUB_OFFLINE=1. You can also point from_pretrained at a local directory containing config.json and model.safetensors.
Two defaults to know about. forecast() has two flags that are on by default, documented in its docstring:
clip_nonnegative=True: if a series’ observed history is never negative, the forecast is clamped at 0. That’s right for sales or request counts. If a series can go negative but happened not to in your window (temperature, profit, a differenced series), setclip_nonnegative=False.symmetric_averaging=True: the model also forecasts the negated series and averages the two results. The README says the benchmark results were produced with the forecaster defaults, so leave it on unless you’re trading accuracy for latency.
Step 7: Backtest before you trust it
Zero-shot doesn’t mean “right for your data.” A minimal backtest is a loop that cuts the history at several points, forecasts the next horizon, and scores it:
def backtest(model, y, horizon=96, windows=10, context_len=2016):
errs = []
for k in range(windows, 0, -1):
end = len(y) - k * horizon
ctx, actual = y[end - context_len:end], y[end:end + horizon]
med = np.asarray(model.forecast(ctx, prediction_length=horizon).median).reshape(-1)
errs.append(np.abs(med - actual).mean())
return float(np.mean(errs))
When we ran this over 10 consecutive 96-hour windows of the same ETTh1 series, Timestral-5M’s average error was 1.63 °C, well above the 0.97 °C from the single window in Step 4. That gap is exactly why one good-looking forecast isn’t enough.
Run it for the model sizes you’re considering and for the simple baseline you use today (last week repeated, or your current ARIMA or Prophet model). Pick Timestral only if it wins across many windows, not just one.
If zero-shot isn’t good enough, Mistral says fine-tuning on proprietary data, domain-specific covariates and control over the training mix are available through Mistral Forge, its commercial training pipeline. The open repo is inference only.
Troubleshooting
AssertionError: known_covariates must be (B=1, C, 548), got (1, 1, 500)
Known-future covariates must cover the history and the horizon. With 500 context steps and prediction_length=48, they need 548 steps. Extend your calendar or schedule frame to the forecast end date.
AssertionError: past_covariates must be (B=1, C, 500), got (1, 1, 400)
Past covariates must line up exactly with the context. Trim or pad (with NaN) to the same length as context.
Forecast never goes below zero.
That’s clip_nonnegative=True acting on a series whose history happened to be non-negative. Pass clip_nonnegative=False.
A multivariate forecast ignores your categories. Categorical covariates are only target-encoded for single-variate targets. For multivariate targets, the raw codes go in as is. Split the series, or encode the category yourself.
pip downloads gigabytes of CUDA libraries on a CPU box.
Install the CPU PyTorch wheel first (pip install torch --index-url https://download.pytorch.org/whl/cpu), then install Timestral.
ModuleNotFoundError: timestral after installing.
Make sure you installed into the same virtual environment you’re running. The import is from timestral import TimestralForecaster.
Long horizons look flat or drift. Past 1,024 steps the model feeds its own median back in as context, so errors compound. Forecast a shorter horizon at a coarser frequency (daily instead of hourly) if you need to look far ahead.
Check the license before you ship
This is the part most likely to matter for a business:
- The code in
mistralai/timestralis Apache 2.0. - The weights on Hugging Face use a separate Modified MIT license. It allows use, modification and redistribution with attribution, but the license text says you may not exercise any rights under it “if the global consolidated monthly revenue of your company (or that of your employer) exceeds $20 million” for the preceding month. That restriction also covers derivatives and fine-tunes. Larger companies are told to contact sales@mistral.ai for a commercial license or use Mistral’s hosted services.
Mistral’s docs list Timestral 1.0 as a Public Preview, so expect details to change before a stable release. Pin a revision in production.
FAQ
Does Timestral need a GPU? No. Mistral designed it for CPU inference. In our single test, a 96-step forecast took about 14 ms with the 5M model and 116 ms with the 209M model on 8 CPU threads.
How much history should I give it? Up to 32,768 steps are used. More history helps with long seasonal cycles: our test used 12 weeks of hourly data, which covers daily and weekly patterns. Mistral’s latency example used three weeks of 15-minute data.
Can it forecast many series at once?
Yes. Stack them on the batch axis, (B, V, T), where each batch entry is a separate series. Variates inside one entry are forecast jointly, so put related signals in the same entry.
Is it available through the Mistral API? The open release is a Python package plus Hugging Face weights that you run yourself. Mistral’s announcement describes on-prem, VPC and air-gapped deployment, and Mistral Forge for fine-tuning.
How does it compare to TimesFM or Chronos? On Mistral’s reported benchmarks, Timestral-209M edges out TimesFM-3 and Chronos-2 (see the table above), and Mistral says its 28M model beats Chronos-2, a model about four times larger, on GIFT-Eval and TIME. Independent leaderboard entries and the upcoming technical report are the better place to confirm that.
Can I use it commercially? Yes, if your company’s monthly revenue is $20 million or less, under the Modified MIT terms. Above that, you need a separate license from Mistral.
Sources
- Mistral AI, Introducing Timestral (October 15, 2026)
- mistralai/timestral on GitHub (README, install, API)
- Hugging Face, Timestral-209M model card and Timestral collection
- Timestral Modified MIT license
- Mistral Docs, Timestral 1.0 209M
- ETDataset (ETTh1), used for our test run
For more open-model setup guides, see how to run Leanstral 1.5 locally and the 7minai homepage.