Skip to content

fix(timesfm_2p5): align timesfm + xreg residuals with the backcast - #550

Open
rudycelekli wants to merge 1 commit into
google-research:masterfrom
rudycelekli:fix/tfm25-xreg-truncated-context
Open

rudycelekli wants to merge 1 commit into
google-research:masterfrom
rudycelekli:fix/tfm25-xreg-truncated-context

Conversation

@rudycelekli

Copy link
Copy Markdown

What's wrong

TimesFM_2p5.forecast_with_covariates(..., xreg_mode="timesfm + xreg") fits the covariate regression on input - backcast. The backcast only covers the last max_context - input_patch_len points of the context. train_len, though, is computed from the full input length. Two cases crash:

  1. Context longer than max_context. With max_context=128, return_backcast=True, a 200-point input and a covariate of length 216:

    tfm.forecast_with_covariates(
        [y], dynamic_numerical_covariates={"x": [x]}, xreg_mode="timesfm + xreg")

    Before: ValueError: operands could not be broadcast together with shapes (168,) (96,). Here 168 = 200 - 32 is the train length, and 96 = 128 - 32 is the backcast length.
    After: returns point and quantile forecasts of shape (1, 16), all finite.

  2. Input no longer than one patch (32 points). This input gets train_len = 0, and x[-0:] selects the whole array, so a 20-point input fails with shapes (20,) (96,). The failure happens even when other inputs in the batch are long. If the slice is fixed but nothing else changes, normalize() computes the mean and std of an empty target. That makes the stats NaN, so the forecast for that series comes out all NaN.

xreg_mode="xreg + timesfm" is not affected.

Fix

  • Cap the train length at what the model sees: max(0, min(input_len, max_context) - p).
  • Slice the input and the backcast with explicit start indices, so train_len = 0 gives an empty target instead of the whole array.
  • xreg_lib.normalize() returns identity stats (0, 1) for an empty series. That series then contributes no rows to the batched fit and still gets the covariate term from the shared coefficients.
  • If no input in the batch is longer than one patch, there is nothing to fit on. In that case, raise a ValueError that names the input patch length instead of a broadcast error.

Testing

tests/test_forecast_with_covariates.py (new) uses a randomly initialized 2.5 torch model with max_context=128. It skips via pytest.importorskip when jax or sklearn is not installed. It checks output shapes and finiteness for these contexts:

  • [200], with and without target normalization
  • [128] (control)
  • [100, 20], with and without target normalization

It also checks that [20] alone raises the new error.

  • Source changes reverted, test kept: 5 of 6 fail. The two cases above give the broadcast errors, and the error-message check fails. The [128] control passes before and after.
  • Only the normalize() change reverted: [100, 20] with normalization fails on NaN output.
  • With the fix: 6 passed. All of tests/ (excluding test_model_loading.py, which needs einshape, not installed here) passes: 65 tests.

forecast_with_covariates(xreg_mode="timesfm + xreg") fits the linear
model on input minus backcast. The backcast only covers the last
max_context - input_patch_len points, but train_len was computed from the
full input length, so any context longer than max_context failed with
"operands could not be broadcast together with shapes (168,) (96,)".
An input no longer than one patch got train_len 0, and x[-0:] selects
the whole array, which failed the same way.

Cap train_len at what the model sees, slice with explicit start indices,
keep normalize() from producing NaN stats for an empty target, and raise
a clear error when no input in the batch has anything to fit on.
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.

1 participant