google-research/timesfm · error · ValueError

Horizon must be less than the max horizon. {horizon} > {fc.m

Error message

Horizon must be less than the max horizon. {horizon} > {fc.max_horizon}.

What it means

_compiled_decode is the compiled inference closure created inside compile(); torch.compile fixes graph shapes sized by forecast_config.max_horizon, so requesting a horizon larger than the compiled maximum raises ValueError at inference time. Every forecast() call on a compiled model passes through this check.

Source

Thrown at src/timesfm/timesfm_2p5/timesfm_2p5_torch.py:423

        self.model.o,
        new_horizon := math.ceil(fc.max_horizon / self.model.o) * self.model.o,
      )
      fc = dataclasses.replace(fc, max_horizon=new_horizon)
    if fc.max_context + fc.max_horizon > self.model.config.context_limit:
      raise ValueError(
        "Context + horizon must be less than the context limit."
        f" {fc.max_context} + {fc.max_horizon} >"
        f" {self.model.config.context_limit}."
      )
    if fc.use_continuous_quantile_head and (fc.max_horizon > self.model.os):
      raise ValueError(
        f"Continuous quantile head is not supported for horizons > {self.model.os}."
      )
    self.forecast_config = fc

    def _compiled_decode(horizon, inputs, masks):
      if horizon > fc.max_horizon:
        raise ValueError(
          f"Horizon must be less than the max horizon. {horizon} > {fc.max_horizon}."
        )

      inputs = (
        torch.from_numpy(np.array(inputs)).to(self.model.device).to(torch.float32)
      )
      masks = torch.from_numpy(np.array(masks)).to(self.model.device).to(torch.bool)
      batch_size = inputs.shape[0]

      if fc.infer_is_positive:
        is_positive = torch.all(inputs >= 0, dim=-1, keepdim=True)
      else:
        is_positive = None

      if fc.normalize_inputs:
        mu = torch.mean(inputs, dim=-1, keepdim=True)
        sigma = torch.std(inputs, dim=-1, keepdim=True)
        inputs = revin(inputs, mu, sigma, reverse=False)

View on GitHub (pinned to 331c6d33cb)

Solutions

  1. Recompile with a larger max_horizon (multiple of output patch length 128) covering every horizon you request.
  2. Clamp the per-call horizon to model.forecast_config.max_horizon before calling forecast.
  3. Compile once with the largest needed horizon and slice outputs down for smaller requests.
  4. Keep compile-time max_horizon and inference horizons in the same app config so they stay in sync.

Example fix

// before
model.compile(ForecastConfig(max_horizon=128))
model.forecast(horizon=256, inputs=...)  # ValueError: 256 > 128
// after
model.compile(ForecastConfig(max_horizon=256))
model.forecast(horizon=256, inputs=...)  # OK
Defensive patterns

Strategy: validation

Validate before calling

max_h = model.forecast_config.max_horizon
horizon = min(horizon, max_h)
predictions = model.forecast(horizon=horizon, inputs=inputs)

Try / catch

try:
    preds = model.forecast(horizon=h, inputs=inputs)
except ValueError as e:
    if "max horizon" in str(e):
        h = model.forecast_config.max_horizon
        preds = model.forecast(horizon=h, inputs=inputs)
    else:
        raise

Prevention

When it happens

Trigger: Calling model.forecast(horizon=N) (or forecast_on_df) with N greater than the max_horizon supplied to the preceding compile() — e.g. compile with max_horizon=128 then forecast(horizon=256).

Common situations: Choosing the horizon per-request after compiling with a small max_horizon; reusing a compiled model configured for short horizons in a new long-horizon use case; confusion because max_horizon was silently rounded up to a multiple of 128 at compile time.

Related errors


AI-assisted analysis of google-research/timesfm@331c6d33cb (2026-08-29). Data as JSON: /api/errors/e6fc9dedc8e0cc99. Report an issue: GitHub.