Skip to content

Add TimesFM 3 multivariate forecasting support - #718

Open
justinchuby wants to merge 4 commits into
mainfrom
justinchuby-add-timesfm3-support
Open

Add TimesFM 3 multivariate forecasting support#718
justinchuby wants to merge 4 commits into
mainfrom
justinchuby-add-timesfm3-support

Conversation

@justinchuby

@justinchuby justinchuby commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

  • add a native declarative ONNX implementation of TimesFM 3
  • export a five-component tensor-only pipeline:
    1. raw_preprocessor: padded-batch interpolation, dynamic padding, detrending, role/mask construction, and patching
    2. preprocessor: running RevIN and patched feature construction
    3. model: every learned layer (residual projection, temporal/variate transformer, output head), with no Scan, Loop, or If, so it can be captured independently
    4. postprocessor: iterative CPM RevIN refinement and reverse normalization
    5. stitcher: overlap stitching, trend reversal, quantile sorting, optional positivity, and padded validity masks
  • support heterogeneous context and horizon lengths in one padded batch
  • recognize the official google/timesfm-3.0-pytorch config, which does not declare a Transformers model_type
  • add examples/timesfm3_forecasting.py, a reusable five-session driver with CUDA I/O binding, fixed-shape CUDA graph capture, latency reporting, CSV output, and official PyTorch parity/benchmark comparison

The implementation follows google-research/timesfm commit 331c6d33cb1ac2611de3056d0ac7164aab6301eb and checkpoint revision 900fcab43d1bfe71733a33b3fec61a41fce28a27.

Input contract

raw_preprocessor accepts right-aligned context tensors [B,V,C], left-aligned future-covariate tensors [B,V,H], observed-value masks, per-row context/horizon lengths, and roles 0=target, 1=past-only, 2=past-future. Outputs remain padded and include explicit validity/target masks; no ragged tensor convention is required.

Symmetric averaging and outer z-normalization remain opt-in host orchestration policies because they duplicate/remap whole requests rather than form part of the checkpoint forward pass.

Validation

  • exact tiny PyTorch/ONNX parity across preprocessing, temporal attention, variate attention, and CPM refinement: max absolute error about 1.1e-6
  • real official-weight AirPassengers forecast matches the upstream PyTorch forecaster with max absolute difference 9.2e-5
  • AirPassengers 24-month holdout: TimesFM MAE 36.1 versus seasonal-naive MAE 71.3
  • official 20-layer config builds and all 445 learned parameters route uniquely to the capture-friendly model component
  • FP32/FP16 patched execution, raw interpolation/detrending, heterogeneous padding invariance, per-row stitching, quantile sorting, and positivity policy covered by focused ORT tests
  • targeted model suite: 10 passed
  • transformer config/builder/coverage regressions: 861 passed, 277 skipped
  • full non-integration baseline before the component split: 9,850 passed, 68 skipped; 17 unrelated existing/environment-dependent failures outside the changed surfaces

CUDA benchmark

Measured on an NVIDIA RTX 4060 Laptop GPU with ONNX Runtime 1.29.0 and PyTorch 2.11.0+cu128, using the official 330M checkpoint, batch 1, one variate, 512 context points, 64 forecast points, and 50 steady-state learned-core iterations:

Runtime Precision Core p50 Notes
ONNX Runtime CUDA graph FP16 105.9 ms Fixed-shape I/O binding; no host/device copies in core timing
ONNX Runtime CUDA FP16 133.6 ms Same-process comparison without graph capture
Official PyTorch CUDA FP32 147.0 ms 10 iterations; upstream FP16 RoPE promotes Q/K but not V, which PyTorch SDPA rejects

CUDA graph capture reduced ORT learned-core median latency by about 21% in the controlled same-process comparison. FP16 ONNX versus FP32 PyTorch is not a like-for-like precision comparison; the example reports this fallback explicitly and still checks outputs (learned-core max absolute difference 0.00843, final forecast 0.00926).

Licensing and waivers

The upstream implementation is Apache-2.0. The official TimesFM 3 weights use Google's non-commercial license, so this PR does not include, redistribute, cache, or download those weights in CI.

Waivers:

  • L2/L4/L5 CI goldens and real-weight integration are not committed because they would require CI to download the non-commercial 1.3 GB checkpoint. Real-weight parity was run locally against the pinned revision instead.
  • ORT GenAI and text-generation metadata do not apply to the time-series forecasting pipeline.

Implement the TimesFM 3 patched-core forward graph with running RevIN,
temporal and variate attention, iterative CPM refinement, and the
forecasting task contract. Detect the official checkpoint config despite
its missing Transformers model_type and cover FP32/FP16 execution and
reference behavior.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 3b9976ac-d466-4ff5-a901-3503f99adfc7
Signed-off-by: Justin Chu <[email protected]>
@justinchuby
justinchuby requested review from a team and a lite review from Copilot September 1, 2026 19:39
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing dddd632bbcb2ca

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 68 68 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 66 66 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 105 105 +0.0%
gpt2 model_size_bytes 324 KB 324 KB +0.0%
gpt2 num_nodes 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 60 60 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 56 56 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 94 94 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 58 58 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 54 54 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 60 60 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 56 56 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 265 265 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 127 127 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 450 450 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 176 176 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing dddd632bbcb2ca

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision_encoder 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are a couple of concrete correctness/robustness issues (config validation and task config typing/guarding) that can lead to invalid graphs or CI type-check failures.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a native declarative ONNX implementation of TimesFM 3’s patched-core multivariate forecasting graph, including a new time-series-forecasting task and config detection for checkpoints that omit a Transformers model_type.

Changes:

  • Introduces TimesFM3Model/TimesFM3Config plus a new forecasting task (TimeSeriesForecastingTask) and registers them in the model/task registries.
  • Extends Transformers config resolution to infer timesfm3 from a distinctive config schema when model_type is missing.
  • Adds co-located unit/runtime tests and hooks the model into the repo’s build-graph/model-coverage harnesses.
File summaries
File Description
tests/model_coverage_test.py Marks timesfm3 as covered by co-located tests due to non-redistributable official weights.
tests/build_graph/_support.py Includes timesfm3 in the build-graph coverage list.
src/mobius/tasks/_time_series_forecasting.py New task defining the patched-input forecasting I/O contract and exporting the model graph.
src/mobius/tasks/init.py Exposes/registers the new task under time-series-forecasting.
src/mobius/models/timesfm3.py Implements TimesFM 3 patched-core forward pass and config parsing.
src/mobius/models/timesfm3_test.py Adds tests for config parsing, graph contract/weight names, runtime execution, and registry/task wiring.
src/mobius/models/init.py Exports the new TimesFM3 model/config symbols.
src/mobius/integrations/transformers/_config_resolver.py Adds schema-signature detection to infer timesfm3 when model_type is absent.
src/mobius/_registry.py Registers timesfm3 with the new task/config class and adds fallback registration metadata.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/mobius/models/timesfm3.py
Comment thread src/mobius/tasks/_time_series_forecasting.py
justinchuby and others added 3 commits September 1, 2026 23:12
Separate raw-series preparation, patched preprocessing, learned model
execution, CPM postprocessing, and forecast stitching into tensor-only
ONNX components. Keep all learned layers in the control-flow-free model
component for execution-provider capture while supporting padded batches
with heterogeneous context and horizon lengths.

Add ONNX interpolation, detrending, stitching, quantile policies, and
regressions for padding invariance and upstream numerical behavior.

Co-authored-by: Copilot App <[email protected]>
Copilot-Session: 3b9976ac-d466-4ff5-a901-3503f99adfc7
Signed-off-by: Justin Chu <[email protected]>
Demonstrate the full five-component ONNX pipeline with reusable sessions, CUDA I/O binding, fixed-shape CUDA graph capture, latency reporting, and an official PyTorch comparison.

Co-authored-by: Copilot App <[email protected]>

Copilot-Session: 3b9976ac-d466-4ff5-a901-3503f99adfc7
Signed-off-by: Justin Chu <[email protected]>
Reject invalid patch lengths before graph construction and guard the forecasting task against incompatible base configurations.

Co-authored-by: Copilot App <[email protected]>

Copilot-Session: 3b9976ac-d466-4ff5-a901-3503f99adfc7
Signed-off-by: Justin Chu <[email protected]>
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.

2 participants