Skip to content

Implement window_size for decomposed forecasting in TimesFM 2.5 - #487

Open
philyuchkoff wants to merge 3 commits into
google-research:masterfrom
philyuchkoff:feature/window-size-decomposed-forecasting
Open

Implement window_size for decomposed forecasting in TimesFM 2.5#487
philyuchkoff wants to merge 3 commits into
google-research:masterfrom
philyuchkoff:feature/window-size-decomposed-forecasting

Conversation

@philyuchkoff

@philyuchkoff philyuchkoff commented Sep 3, 2026

Copy link
Copy Markdown

Summary

Implements the window_size parameter in ForecastConfig that was declared but never implemented (TODO(siriuz42)).

Decomposed forecasting was originally present in TimesFM 1.0 but was lost during the 2.5 migration. This PR restores the feature.

How it works

When window_size > 0 is set in ForecastConfig:

  1. Each input time series is decomposed into a trend (moving average) and a residual component via moving_average()
  2. Both components are fed to the model as independent inputs in the same batch
  3. Output forecasts are summed element-wise: forecast(trend) + forecast(residual)

This improves long-range forecast quality by letting the model separately handle slow trends and fast residuals.

Changes

  • src/timesfm/timesfm_2p5/timesfm_2p5_base.py: Added moving_average(), modified forecast(), blocked forecast_with_covariates() with explicit error when window_size > 0
  • src/timesfm/configs.py: Replaced TODO(siriuz42) with full description
  • tests/test_base_utils.py: 9 unit tests for moving_average() (all pass)
  • tests/test_model_loading.py: Integration tests for forecast() with window_size and error case with covariates

Edge cases

  • window_size = 0 (default): backwards-compatible, no behavior change
  • window_size >= len(ts): uses actual array length, decomposition degenerates
  • window_size = 1: trend equals original series, residual is zero
  • Empty array
  • forecast_with_covariates() raises ValueError with clear message

See docs/plans/2026-09-03-window-size-design.md for full design rationale and algorithm.

@philyuchkoff
philyuchkoff force-pushed the feature/window-size-decomposed-forecasting branch from bdff12d to 8d491be Compare September 3, 2026 12:50

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This drops the existing uncompiled-model guard. When forecast_config is None, the first condition is false and the elif not self.forecast_config.return_backcast dereferences None, so forecast_with_covariates() now raises AttributeError instead of the documented “Model is not compiled” ValueError. Please keep the compile check first and add an uncompiled regression.

- Add moving_average() function to split series into trend and residual
- Modify forecast(): when window_size > 0, decompose inputs into trend/residual, run forecast on both, sum the outputs
- Block forecast_with_covariates() with explicit error when window_size > 0
- Update configs.py: remove TODO, add full parameter description
- Add 9 unit tests for moving_average and integration tests for forecast() with window_size
@philyuchkoff
philyuchkoff force-pushed the feature/window-size-decomposed-forecasting branch from 8d491be to 51da429 Compare September 3, 2026 13:46
The previous change replaced 'if forecast_config is None' with a
window_size check, dropping the uncompiled guard. When forecast_config
is None, this caused an AttributeError instead of the documented
ValueError. Restore the compile check first, then check window_size.
@philyuchkoff

philyuchkoff commented Sep 3, 2026

Copy link
Copy Markdown
Author

@sylvesterkaczmarek
Thanks!
Fixed: restored the forecast_config is None check as the first guard, then the window_size check as the second. Both now raise ValueError with the expected messages.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hi, decomposition now runs before the existing max_context truncation. For long inputs, the retained trend/residual values can therefore be influenced by samples outside the configured model context; the v1 path truncated first and then applied the moving average. Could the input be clipped to max_context before moving_average()?

Truncate long inputs to max_context before applying moving_average(),
matching v1 behavior. Without this, the trend/residual values were
influenced by samples outside the configured model context.
@philyuchkoff

Copy link
Copy Markdown
Author

@sylvesterkaczmarek
Thanks!
Fixed: input is now truncated to max_context before moving_average(), matching the v1 path order.

@sylvesterkaczmarek sylvesterkaczmarek left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Re-checked both issues I raised. The uncompiled-model guard is again evaluated before window_size, preserving the documented ValueError, and long inputs are clipped to max_context before moving-average decomposition, matching the v1 ordering. Both findings are resolved.

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