diff --git a/README.md b/README.md index bfa371e..5ce6607 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ Some models require specific Python versions (e.g. FlowState 3.11-3.13, TabPFN & - **Moirai:** `Salesforce/moirai-{1.0,1.1,2.0}-R-{small,base,large}`, `Salesforce/moirai-moe-1.0-R-*` - **PatchTST-FM:** `ibm-research/patchtst-fm-r1` - **Sundial:** `thuml/sundial-base-128m` -- **T0:** `theforecastingcompany/t0-alpha` +- **T0:** `theforecastingcompany/t0-alpha`, `theforecastingcompany/t0-beta` (requires `tfc-t0>=0.5.0`) - **TabPFN:** default `tabpfn-v2-regressor-2noar4o2.ckpt`; TabPFN-3 via `model_path="tabpfn-v3-regressor-v3_20260506_timeseries.ckpt"` (LOCAL, `TABPFN_TOKEN`); or CLIENT mode with a Prior Labs API token - **Tafsut:** `Tafsut-FM/tafsut-univariate-base` - **TiRex:** `NX-AI/TiRex`, `NX-AI/TiRex-2` diff --git a/docs/changelogs/index.md b/docs/changelogs/index.md index 80b6b3a..c3ca65f 100644 --- a/docs/changelogs/index.md +++ b/docs/changelogs/index.md @@ -2,6 +2,7 @@ Welcome to the FoundationForecast Changelog. Here you will find a list of changes, updates, and improvements for each release. Explore the details of each version below. +- [v0.1.8](v0.1.8.md) - [v0.1.7](v0.1.7.md) - [v0.1.6](v0.1.6.md) - [v0.1.5](v0.1.5.md) diff --git a/docs/changelogs/v0.1.8.md b/docs/changelogs/v0.1.8.md new file mode 100644 index 0000000..714c89d --- /dev/null +++ b/docs/changelogs/v0.1.8.md @@ -0,0 +1,31 @@ +### Features + +* **T0 beta support**: The shared [`T0`](../api/models/foundation/models.md#foundationforecast.models.t0.T0) wrapper now works with [`theforecastingcompany/t0-beta`](https://huggingface.co/theforecastingcompany/t0-beta) (~256M). Register it in gift-eval `models.yaml`; GIFT-Eval CI still replicates `t0-alpha` until leaderboard results exist for beta. + + ```python + import pandas as pd + from foundationforecast.models.t0 import T0 + + df = pd.read_csv( + "https://timecopilot.s3.amazonaws.com/public/data/air_passengers.csv", + parse_dates=["ds"], + ) + model = T0( + repo_id="theforecastingcompany/t0-beta", + alias="t0-beta", + ) + fcst = model.forecast(df, h=12, freq="D", quantiles=[0.1, 0.5, 0.9]) + assert "t0-beta" in fcst.columns + ``` + +### Fixes + +* **T0 / gift-eval CI**: Bump `tfc-t0` to `>=0.5.0` and call `T0Forecaster.predict(quantile_levels=...)` instead of the removed `quantiles=` kwarg. Fixes gift-eval Modal runs after PyPI resolved `tfc-t0` 0.5.0 alongside the t0-beta release. + +### Dependencies + +* Bumped `tfc-t0` from `>=0.2.3` to `>=0.5.0` (required for t0-beta; still supports t0-alpha). + +--- + +**Full Changelog**: https://github.com/TimeCopilot/foundationforecast/compare/v0.1.7...v0.1.8 diff --git a/docs/model-hub.md b/docs/model-hub.md index 375230f..f7234ea 100644 --- a/docs/model-hub.md +++ b/docs/model-hub.md @@ -41,7 +41,7 @@ Below is the list of available foundation models. Click a model name for API det - [Moirai](api/models/foundation/models.md#foundationforecast.models.moirai.Moirai) ([arXiv:2402.02592](https://arxiv.org/abs/2402.02592)) - [PatchTST-FM](api/models/foundation/models.md#foundationforecast.models.patchtst_fm.PatchTSTFM) ([arXiv:2602.06909](https://arxiv.org/abs/2602.06909)) — Python 3.11–3.13 - [Sundial](api/models/foundation/models.md#foundationforecast.models.sundial.Sundial) ([arXiv:2502.00816](https://arxiv.org/pdf/2502.00816)) -- [T0](api/models/foundation/models.md#foundationforecast.models.t0.T0) ([model card](https://huggingface.co/theforecastingcompany/t0-alpha)) — Python 3.11–3.13 +- [T0](api/models/foundation/models.md#foundationforecast.models.t0.T0) ([t0-alpha](https://huggingface.co/theforecastingcompany/t0-alpha), [t0-beta](https://huggingface.co/theforecastingcompany/t0-beta); requires `tfc-t0>=0.5.0` for beta) — Python 3.11–3.13 - [TabPFN](api/models/foundation/models.md#foundationforecast.models.tabpfn.TabPFN) ([arXiv:2501.02945](https://arxiv.org/abs/2501.02945)) — Python 3.10–3.12; TabPFN-2 (default) and TabPFN-3 via `model_path`; TabPFN-3 is LOCAL-only; weights use the [TabPFN NC license](https://ux.priorlabs.ai) (`TABPFN_TOKEN`) - [Tafsut](api/models/foundation/models.md#foundationforecast.models.tafsut.Tafsut) ([GitHub](https://github.com/Tafsut-FM/tafsut)) - [TiRex / TiRex-2](api/models/foundation/models.md#foundationforecast.models.tirex.TiRex) ([arXiv:2505.23719](https://arxiv.org/abs/2505.23719), [arXiv:2607.01204](https://arxiv.org/abs/2607.01204)) — Python 3.11+ diff --git a/experiments/gift-eval/configs/models.yaml b/experiments/gift-eval/configs/models.yaml index bea1c62..ad70e49 100644 --- a/experiments/gift-eval/configs/models.yaml +++ b/experiments/gift-eval/configs/models.yaml @@ -250,6 +250,14 @@ models: repo_id: theforecastingcompany/t0-alpha batch_size: 32 + theforecastingcompany--t0-beta: + class: foundationforecast.models.t0.T0 + reference_slug: null + kwargs: + repo_id: theforecastingcompany/t0-beta + alias: t0-beta + batch_size: 16 + thuml--sundial-base-128m: class: foundationforecast.models.sundial.Sundial reference_slug: sundial_base_128m diff --git a/experiments/gift-eval/tests/test_models_config.py b/experiments/gift-eval/tests/test_models_config.py new file mode 100644 index 0000000..fada6ee --- /dev/null +++ b/experiments/gift-eval/tests/test_models_config.py @@ -0,0 +1,10 @@ +from src.eval.jobs import load_models_config + + +def test_t0_beta_registered_in_models_yaml(): + models = load_models_config() + entry = models["theforecastingcompany--t0-beta"] + assert entry["class"] == "foundationforecast.models.t0.T0" + assert entry["reference_slug"] is None + assert entry["kwargs"]["repo_id"] == "theforecastingcompany/t0-beta" + assert entry["kwargs"]["alias"] == "t0-beta" diff --git a/experiments/gift-eval/uv.lock b/experiments/gift-eval/uv.lock index 1b7d9b4..48863ab 100644 --- a/experiments/gift-eval/uv.lock +++ b/experiments/gift-eval/uv.lock @@ -1203,7 +1203,7 @@ dependencies = [ ] name = "foundationforecast" source = {editable = "../../"} -version = "0.1.6" +version = "0.1.7" [package.metadata] provides-extras = ["plot"] @@ -1214,7 +1214,7 @@ requires-dist = [ {marker = "python_full_version < '3.13'", name = "tabpfn-time-series", specifier = ">=1.2.0"}, {marker = "python_full_version < '3.13'", name = "transformers", specifier = ">=4.41,<6"}, {marker = "python_full_version < '3.14'", name = "timecopilot-uni2ts", specifier = ">=0.1.3"}, - {marker = "python_full_version >= '3.11' and python_full_version < '3.14'", name = "tfc-t0", specifier = ">=0.2.3"}, + {marker = "python_full_version >= '3.11' and python_full_version < '3.14'", name = "tfc-t0", specifier = ">=0.5.0"}, {marker = "python_full_version >= '3.11' and python_full_version < '3.14'", name = "timecopilot-granite-tsfm", specifier = ">=0.2.1"}, {marker = "python_full_version >= '3.11'", name = "timecopilot-tirex", specifier = ">=0.1.1"}, {marker = "python_full_version >= '3.11'", name = "timecopilot-tirex2", specifier = ">=0.1.0"}, @@ -5011,11 +5011,11 @@ dependencies = [ {marker = "python_full_version == '3.13.*'", name = "numpy", source = {registry = "https://pypi.org/simple"}, version = "2.5.2"}, ] name = "tfc-t0" -sdist = {hash = "sha256:0792f5088ecf898b13e51290da0508d1b96c8abf74843aea4b397d6e807331d4", size = 327018, upload-time = "2026-07-30T15:51:35.942Z", url = "https://files.pythonhosted.org/packages/85/59/de499b9a1ec1a6ca95745525cf01f336a4999a7c4436d8b71dcfd0a975ae/tfc_t0-0.2.3.tar.gz"} +sdist = {hash = "sha256:603e43a16da6210ca11fc26f957b1b43a7f2a60caa59e34643a015ec85335da7", size = 523589, upload-time = "2026-09-17T14:21:35.373Z", url = "https://files.pythonhosted.org/packages/cd/50/72f469a14395b80efa72c5a94a6a595673cf5fa73e2f92c5dbba082c6c63/tfc_t0-0.5.0.tar.gz"} source = {registry = "https://pypi.org/simple"} -version = "0.2.3" +version = "0.5.0" wheels = [ - {hash = "sha256:cb67a7e1aa9832124c69494455f9e02119cc4480e8dc5a7608b9da9bb5f31cea", size = 44898, upload-time = "2026-07-30T15:51:34.692Z", url = "https://files.pythonhosted.org/packages/d8/47/8e96a5147597e5db6fddd7c54fadcc6be77cab124c008891805918517cf2/tfc_t0-0.2.3-py3-none-any.whl"}, + {hash = "sha256:551936dfe72b0464d3963d0b3bf4415d49b6ac37c5695ff97b8ff844f12c6a8c", size = 55148, upload-time = "2026-09-17T14:21:33.802Z", url = "https://files.pythonhosted.org/packages/77/6b/a33a4382e70f7612ae82f0c5f4178f5c1132f317508ef6def180054f73cd/tfc_t0-0.5.0-py3-none-any.whl"}, ] [[package]] diff --git a/foundationforecast/models/t0.py b/foundationforecast/models/t0.py index 21b0c13..3cff739 100644 --- a/foundationforecast/models/t0.py +++ b/foundationforecast/models/t0.py @@ -61,14 +61,15 @@ def __init__( Notes: **Requirements:** - - T0 requires Python 3.11 to 3.13 (via the - [`tfc-t0`](https://pypi.org/project/tfc-t0/) package). + - T0 requires Python 3.11 to 3.13 and ``tfc-t0>=0.5.0`` (required + for ``t0-beta``; also supports ``t0-alpha``). **Available models:** | Model ID | Parameters | | ------------------------------------------------------------------------------------------------- | ---------- | | [`theforecastingcompany/t0-alpha`](https://huggingface.co/theforecastingcompany/t0-alpha) | ~102M | + | [`theforecastingcompany/t0-beta`](https://huggingface.co/theforecastingcompany/t0-beta) | ~256M | **Resources:** @@ -79,9 +80,9 @@ def __init__( - The model is loaded onto the best available device (GPU if available, otherwise CPU). - - T0 predicts 5 quantile knots (0.1, 0.25, 0.5, 0.75, 0.9); the - median (0.5) is used as the point forecast and other requested - quantiles are obtained by linear interpolation across the knots. + - ``t0-alpha`` predicts 5 quantile knots (0.1, 0.25, 0.5, 0.75, 0.9); + ``t0-beta`` predicts 21 native levels (0.01–0.99). Requested + quantiles are interpolated; the median (0.5) is the point forecast. - NaN values in the context are treated as missing observations. - T0 natively supports past and known-future covariates through its `predict` API; this integration currently exposes the univariate @@ -194,7 +195,7 @@ def forecast( out = model.predict( self._to_context(batch), horizon=h, - quantiles=pred_quantiles, + quantile_levels=pred_quantiles, ) # shape: (batch, h, n_quantiles) fcsts.append(out.quantiles.cpu().numpy()) diff --git a/mkdocs.yml b/mkdocs.yml index 48effb7..594ec1b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,6 +41,7 @@ nav: - Contributing: contributing.md - Changelogs: - changelogs/index.md + - changelogs/v0.1.8.md - changelogs/v0.1.7.md - changelogs/v0.1.6.md - changelogs/v0.1.5.md diff --git a/pyproject.toml b/pyproject.toml index 692122b..5df8534 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ dependencies = [ "scipy", "tabpfn-time-series>=1.2.0 ; python_full_version < '3.13'", "tafsut>=0.1.0", - "tfc-t0>=0.2.3 ; python_full_version >= '3.11' and python_full_version < '3.14'", + "tfc-t0>=0.5.0 ; python_full_version >= '3.11' and python_full_version < '3.14'", "timecopilot-chronos-forecasting>=0.2.2", "timecopilot-granite-tsfm>=0.2.1 ; python_full_version >= '3.11' and python_full_version < '3.14'", "timecopilot-timesfm>=0.4.0", diff --git a/tests/models/conftest.py b/tests/models/conftest.py index 450077c..4d9390c 100644 --- a/tests/models/conftest.py +++ b/tests/models/conftest.py @@ -58,6 +58,14 @@ from foundationforecast.models.t0 import T0 models.append(T0(context_length=256, batch_size=2)) + models.append( + T0( + repo_id="theforecastingcompany/t0-beta", + alias="t0-beta", + context_length=256, + batch_size=2, + ) + ) if (3, 11) <= sys.version_info < (3, 14): from foundationforecast.models.flowstate import FlowState diff --git a/tests/models/test_t0.py b/tests/models/test_t0.py new file mode 100644 index 0000000..57fda20 --- /dev/null +++ b/tests/models/test_t0.py @@ -0,0 +1,68 @@ +import sys +from unittest.mock import MagicMock + +import numpy as np +import pytest + +if sys.version_info < (3, 11) or sys.version_info >= (3, 14): + pytest.skip( + "T0 requires Python >= 3.11 and < 3.14", + allow_module_level=True, + ) + +import pandas as pd # noqa: E402 + +from tests.helpers import generate_series # noqa: E402 +from foundationforecast.models.t0 import T0 # noqa: E402 + +pytestmark = pytest.mark.models + + +def test_t0_forecast_passes_quantile_levels(mocker): + model = T0(context_length=64, batch_size=2) + df = generate_series(2, freq="D", min_length=32, max_length=32) + + mock_forecaster = MagicMock() + mock_out = MagicMock() + mock_out.quantiles.cpu.return_value.numpy.return_value = np.ones((2, 3, 3)) + mock_forecaster.predict.return_value = mock_out + + mock_cm = MagicMock() + mock_cm.__enter__.return_value = mock_forecaster + mock_cm.__exit__.return_value = None + mocker.patch.object(model, "_get_model", return_value=mock_cm) + + model.forecast(df, h=3, freq="D", quantiles=[0.1, 0.9]) + + kwargs = mock_forecaster.predict.call_args.kwargs + assert kwargs["quantile_levels"] == [0.1, 0.5, 0.9] + assert "quantiles" not in kwargs + + +def test_t0_beta_forecast(): + df = generate_series(2, freq="D", min_length=50, max_length=50) + model = T0( + repo_id="theforecastingcompany/t0-beta", + alias="t0-beta", + context_length=256, + batch_size=2, + ) + fcst = model.forecast(df, h=3, freq="D") + assert fcst.shape == (6, 3) + assert "t0-beta" in fcst.columns + + +def test_t0_beta_quantile_forecast(): + df = generate_series(2, freq="D", min_length=50, max_length=50) + model = T0( + repo_id="theforecastingcompany/t0-beta", + alias="t0-beta", + context_length=256, + batch_size=2, + ) + fcst = model.forecast(df, h=3, freq="D", quantiles=[0.1, 0.9]) + assert fcst.shape == (6, 5) + assert "t0-beta" in fcst.columns + assert "t0-beta-q-10" in fcst.columns + assert "t0-beta-q-90" in fcst.columns + assert pd.api.types.is_numeric_dtype(fcst["t0-beta-q-10"]) diff --git a/uv.lock b/uv.lock index 8ff25c7..07e999e 100644 --- a/uv.lock +++ b/uv.lock @@ -1507,7 +1507,7 @@ requires-dist = [ {marker = "python_full_version < '3.13'", name = "tabpfn-time-series", specifier = ">=1.2.0"}, {marker = "python_full_version < '3.13'", name = "transformers", specifier = ">=4.41,<6"}, {marker = "python_full_version < '3.14'", name = "timecopilot-uni2ts", specifier = ">=0.1.3"}, - {marker = "python_full_version >= '3.11' and python_full_version < '3.14'", name = "tfc-t0", specifier = ">=0.2.3"}, + {marker = "python_full_version >= '3.11' and python_full_version < '3.14'", name = "tfc-t0", specifier = ">=0.5.0"}, {marker = "python_full_version >= '3.11' and python_full_version < '3.14'", name = "timecopilot-granite-tsfm", specifier = ">=0.2.1"}, {marker = "python_full_version >= '3.11'", name = "timecopilot-tirex", specifier = ">=0.1.1"}, {marker = "python_full_version >= '3.11'", name = "timecopilot-tirex2", specifier = ">=0.1.0"}, @@ -6357,11 +6357,11 @@ dependencies = [ {marker = "python_full_version >= '3.11' and python_full_version < '3.14'", name = "torch"}, ] name = "tfc-t0" -sdist = {hash = "sha256:0792f5088ecf898b13e51290da0508d1b96c8abf74843aea4b397d6e807331d4", size = 327018, upload-time = "2026-07-30T15:51:35.942Z", url = "https://files.pythonhosted.org/packages/85/59/de499b9a1ec1a6ca95745525cf01f336a4999a7c4436d8b71dcfd0a975ae/tfc_t0-0.2.3.tar.gz"} +sdist = {hash = "sha256:603e43a16da6210ca11fc26f957b1b43a7f2a60caa59e34643a015ec85335da7", size = 523589, upload-time = "2026-09-17T14:21:35.373Z", url = "https://files.pythonhosted.org/packages/cd/50/72f469a14395b80efa72c5a94a6a595673cf5fa73e2f92c5dbba082c6c63/tfc_t0-0.5.0.tar.gz"} source = {registry = "https://pypi.org/simple"} -version = "0.2.3" +version = "0.5.0" wheels = [ - {hash = "sha256:cb67a7e1aa9832124c69494455f9e02119cc4480e8dc5a7608b9da9bb5f31cea", size = 44898, upload-time = "2026-07-30T15:51:34.692Z", url = "https://files.pythonhosted.org/packages/d8/47/8e96a5147597e5db6fddd7c54fadcc6be77cab124c008891805918517cf2/tfc_t0-0.2.3-py3-none-any.whl"}, + {hash = "sha256:551936dfe72b0464d3963d0b3bf4415d49b6ac37c5695ff97b8ff844f12c6a8c", size = 55148, upload-time = "2026-09-17T14:21:33.802Z", url = "https://files.pythonhosted.org/packages/77/6b/a33a4382e70f7612ae82f0c5f4178f5c1132f317508ef6def180054f73cd/tfc_t0-0.5.0-py3-none-any.whl"}, ] [[package]]