Skip to content

Add support for TimesFM 3.0 foundation model (multivariate, past & future covariates) - #3199

Merged
dennisbader merged 21 commits into
unit8co:masterfrom
JuanCruzC97:feature/timesfm-3.0
Sep 17, 2026
Merged

dennisbader merged 21 commits into
unit8co:masterfrom
JuanCruzC97:feature/timesfm-3.0

Conversation

@JuanCruzC97

@JuanCruzC97 JuanCruzC97 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Checklist before merging this PR:

  • Mentioned all issues that this PR fixes or addresses.
  • Summarized the updates of this PR under Summary.
  • Added an entry under Unreleased in the Changelog.

Fixes #3196

Summary

This PR adds support for Google's TimesFM 3.0 time-series foundation model (google/timesfm-3.0-pytorch, 330M parameters) as TimesFM3Model, following the discussion in #3196 and the license clarification in google-research/timesfm#501.

Unlike previous versions, TimesFM 3.0 natively supports:

  • Multivariate forecasting: all target components are modeled jointly with variate attention (no per-component folding).
  • Past covariates (past-only channels) and future covariates (past-and-future channels), mapped from Darts' mixed covariates batch.
  • Probabilistic forecasts via 9 pre-trained quantiles (QuantileRegression likelihood, single-pass decoding with patch stitching).

The implementation follows the established foundation-model pattern in Darts (FoundationModel base class, HuggingFaceConnector weight loading, lazy model creation in _create_model()), with the architecture ported natively into darts/models/components/timesfm3_submodels.py from google-research/timesfm (Apache-2.0, commit pinned in the module docstring).

Since the TimesFM 3.0 pre-trained weights are non-commercial (timesfm-non-commercial-license-v1.0), model creation requires accept_license=True, following the TiRexModel pattern. There are no new runtime dependencies (huggingface-hub and safetensors are already part of darts[torch]).

Validation: the ported architecture was verified to be bit-exact against the original implementation — the Darts one-shot forecast matches the upstream timesfm3.TimesFM3Forecaster output with max abs diff 0.0 (univariate, multivariate, covariates, output_chunk_shift, and batched scenarios), and the slow fidelity test compares against reference forecasts generated with the original forecaster and the real weights (rtol=atol=1e-5).

Design decisions for review

  1. >32 variates raise a clear error (validated at fit() time and at prediction time), instead of the non-deterministic random covariate subsampling performed by the upstream TimesFM3Evaluator. max_variates is read from the checkpoint's config.json, not hardcoded.
  2. Missing values: masks instead of interpolation — upstream linearly interpolates NaNs in predict_batch() before calling decode(); we use the masking logic of decode() itself (the model was trained with masked inputs). Behavioral deviation: with NaN-containing series, results do not match upstream 1:1. Easy to switch to interpolation if strict fidelity is preferred.
  3. Context length: error instead of silent truncation — upstream silently truncates the context to 15,360 points; we validate input_chunk_length <= 15360 with an error. Neither 15,360 nor the horizon cap are part of config.json (they are upstream TimesFM3Forecaster constants); they remain documented class constants. What is read from config.json (same spirit as Chronos2Model): the pre-trained quantiles and max_variates.
  4. Autoregression vs single-pass — for n > output_chunk_length, Darts splits the forecast into autoregressive chunks, which can differ from upstream's single-pass stitched decoding. Documented in the docstring; set output_chunk_length >= n for a single-pass forecast.

Other Information

Fidelity & implementation notes
  • Port deviations (documented in the module docstring): no DecodeCache (autoregression is handled by TorchForecastingModel), no segment_ids support and SDPA-only attention (the path used by the released checkpoint), only the identity input transform, swiglu mapped to plain SiLU (as upstream does — no gated FFN in the PyTorch backend).
  • Module defaults mirror the released checkpoint's config.json (e.g. use_rope_var=False), not the inconsistent defaults of the upstream _make_torch_model().
  • hub_model_revision is pinned to the current commit of google/timesfm-3.0-pytorch for reproducibility.
  • Known performance note: _get_running_stats loops over patches in Python (like upstream; ~480 iterations at maximum context). Not vectorized to avoid numerical divergence; can be a follow-up.
Tests
  • test_timesfm3.py: creation validation (license, caps, likelihood, fine-tuning), deterministic / probabilistic / multivariate / past / future / both-covariates / multiple-series forecasts, missing values (interior & trailing target NaNs, past & future covariate NaNs), output_chunk_shift alignment + autoregression lock, variate limit boundary (32 ok / 33 raises) — fast tests run against a committed tiny random-weight artefact (structural patch sizes 32/64 and 9 quantiles intact).
  • Slow fidelity tests (@pytest.mark.slow) against timesfm3.npz reference forecasts generated with the original forecaster and the full checkpoint (Zurich electricity dataset, deterministic + probabilistic).
  • Integrated into the cross-model tests of test_foundation.py (variable input chunk length fit/predict/save/load/weights, min_train_series_length, fixed-vs-variable ICL equivalence); only excluded from the load_from_checkpoint part, which requires fine-tuning (as with TiRex).
Docs & artefacts
  • Model tables updated in README.md, docs/source/index.rst, docs/userguide/covariates.md; CHANGELOG.md entry under Unreleased; notebook 25-FoundationModel-examples.ipynb mentions the model (covariates support, quantiles, license note). No INSTALL.md changes needed (no extra dependencies).
  • Artefact generation and parity-check scripts used during development are intentionally not part of the PR.

Port the TimesFM 3.0 PyTorch architecture (commit 9de33f6f487baf8adc26eb757f29b6a7a557b823)
to darts/models/components/timesfm3_submodels.py as a first step towards
TimesFM 3.0 support (issue unit8co#3196):

- ResidualBlock, RMSNorm (torch>=2.0 compatible), PerDimScale,
  RotaryPositionalEmbedding, MultiHeadAttention (SDPA), MixingTransformer
  (sequence + variate attention) and stacked variant
- Patching/stitching and CPM-RevIN refinement utilities
- Config dataclasses buildable from the HuggingFace config.json

Trims vs upstream (documented in the module docstring): no PyTorchModelHubMixin
(weights are loaded via HuggingFaceConnector), no autoregressive KV-cache,
no multi-segment support, SDPA-only attention path.

Verified: state_dict keys and shapes match the google/timesfm-3.0-pytorch
checkpoint exactly (445/445 tensors).
Implements the Darts integration of TimesFM 3.0 (issue unit8co#3196) on top of the
ported submodels:

- _TimesFM3Module: assembles the architecture from the HuggingFace config.json
  and ports the original preprocessing and single-pass decode (stitching,
  linear detrending, iterative CPM-RevIN refinement). Multivariate series are
  forecast natively with variate attention; past covariates are mapped to
  past-only channels and future covariates to past-and-future channels
  (output_chunk_shift gaps are masked out).
- TimesFM3Model: FoundationModel wrapper with accept_license gate (TimesFM 3.0
  weights are non-commercial), context (15,360) and prediction (1,024) caps,
  QuantileRegression likelihood restricted to the 9 pre-trained quantiles.
  Fine-tuning is not supported yet.

Verified bit-exact parity (max abs diff 0.0) against the upstream
timesfm3.TimesFM3Forecaster on univariate, multivariate, covariates,
output_chunk_shift and batched scenarios.
- tiny_timesfm3: tiny random-weight checkpoint (2 layers, model_dims 64,
  4 heads; structural values input/output patch 32/64 and 9 quantiles kept)
  generated with the upstream timesfm3 package, loaded by fast tests via
  local_dir (49 tensors, 716KB)
- timesfm3_prediction/timesfm3.npz: reference forecasts (time, variables,
  quantiles) = (128, 2, 9) generated with the upstream timesfm3
  TimesFM3Forecaster and the real google/timesfm-3.0-pytorch weights on the
  Zurich electricity dataset (last 1024 points of context, horizon 128)
- update TimesFM3Model docstring examples with real AirPassengers outputs

Fidelity verified: Darts predictions match the reference with
rtol=1e-5, atol=1e-5 for both deterministic and probabilistic modes.
- test_timesfm3.py: fast tests (creation validation, deterministic,
  probabilistic, multivariate, past/future/both covariates, variate limit,
  multiple series) using the tiny local artefact, and slow fidelity tests
  comparing against the original implementation reference
- register TimesFM3Model in the lazy public API and foundation model docs
- add TimesFM3Model to the cross-model variable input chunk length tests
  in test_foundation.py
- CHANGELOG entry under Unreleased
- model tables in README.md, docs/source/index.rst (multivariate,
  past and future covariates support, no static covariates), and
  docs/userguide/covariates.md
- notebook 25: mention TimesFM 3.0's pre-trained quantiles and license
  acceptance, covariates support, and API reference link
Add TimesFM3Model to the remaining parametrized cross-model tests:
- min_train_series_length for variable and fixed input chunk lengths
- variable ICL save/load (save(), load_weights(); load_from_checkpoint is
  skipped for TimesFM3 as it requires fine-tuning, which is not supported
  yet - same as TiRex)
- variable ICL predictions matching fixed ICL predictions (validates the
  masked context padding semantics)
- loading weights into a model with different chunk parameters
Map ff_activation="swiglu" to plain SiLU, matching the released timesfm
PyPI package (which implements no gated FFN in its PyTorch backend).
This keeps configurations of checkpoints published with earlier timesfm
releases loadable; the ported commit 9de33f6f had removed the alias.
No effect on the released checkpoint, which uses relu.
…imesFM3Model

- test_missing_values: NaNs in the target (interior and trailing) and in
  past / future covariates are handled via the masking logic of the ported
  decode() - predictions never contain NaNs
- test_output_chunk_shift: predictions start after the shifted gap and
  auto-regressive prediction with output_chunk_shift raises
- test_too_many_variates: also cover the boundary case of exactly 32
  target components (the checkpoint maximum)
…M3Model

- load the HuggingFace config.json at model construction (as done by
  Chronos2Model) and validate the QuantileRegression likelihood against
  the pre-trained quantiles read from it, instead of hardcoded constants
- validate the total number of target components and covariates at fit
  time, early, using max_variates from the checkpoint configuration (the
  prediction-time check in the PL module is kept as a safety net)
- document the provenance of the context / prediction length caps
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.67524% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.06%. Comparing base (ff1865f) to head (09dd15a).

Files with missing lines Patch % Lines
darts/models/forecasting/timesfm3_model.py 90.67% 29 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3199      +/-   ##
==========================================
- Coverage   97.14%   97.06%   -0.09%     
==========================================
  Files         169      169              
  Lines       18826    18891      +65     
==========================================
+ Hits        18288    18336      +48     
- Misses        538      555      +17     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dennisbader

Copy link
Copy Markdown
Collaborator

Thanks for the PR @JuanCruzC97, I'll have time to review soon (was a bit busy implementing the torch I/O refactor from #3204, will apply the required changes to your PR once I've merge that one).

@JuanCruzC97

Copy link
Copy Markdown
Contributor Author

No problem at all! Let me know if anything else is needed. The next issue I'm interested in helping with would be #2968 as I'm currently using some regression models with long output_chunk_length and multi_models. Also interested in #1853.

@dennisbader dennisbader left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful PR and implementation @JuanCruzC97, congrats 🚀 👏

It was in an excellent state already from the beginning, thanks a lot for that :)

I pushed a couple of updates that added the last couple of missing things:

  • fine-tuning is now supported
  • removed the upper limit for output_chunk_length (if the original implementation doesn't have a limit, we should not set one either)
  • refactored forward() a bit to leverage our new PLModuleInput (and PLModuleOutput)
  • some minor doc updates

We can merge once all tests have passed. Let me know if you would like to have a look at the changes before I merge.

@dennisbader
dennisbader merged commit 6103e29 into unit8co:master Sep 17, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Google's TimesFM 3.0 foundation model (multivariate, past & future covariates)

2 participants