When Do Foundation Models Pay Off? Data Requirements for Pretrained Time Series Forecasters
Code for measuring break-even points — the training data size at which foundation models (Chronos, Moirai, Lag-Llama) start outperforming classical methods (Naive, ETS, ARIMA, XGBoost) across 30 benchmark datasets.
# Setup
git clone https://github.com/nicolaisi/fm-breakeven.git
cd fm-breakeven
pip install -r requirements.txt
# 0. Pre-fetch all 30 datasets (recommended before long runs)
./run_all.sh download
# 1. Run everything (classical + foundation + Lag-Llama, all 30 datasets)
./run_all.sh
# 2. Or run selectively
./run_all.sh classical # CPU only, all 30 datasets
./run_all.sh foundation # GPU: Chronos-Bolt + Moirai
./run_all.sh lagllama # GPU: Lag-Llama zero-shot only
./run_all.sh new # New 20 datasets only (classical + foundation)
./run_all.sh rq3 # RQ3 LOO analysis (after experiments complete)
Foundation models require a GPU:
pip install -r requirements-gpu.txt
./run_all.sh foundation
30 datasets total: 10 original + 20 new.
| Dataset | Frequency | Horizon | Source |
|---|---|---|---|
| ETTh1 | Hourly | 24 | ETDataset |
| ETTh2 | Hourly | 24 | ETDataset |
| ETTm1 | 15-min | 96 | ETDataset |
| ETTm2 | 15-min | 96 | ETDataset |
| Electricity | Hourly | 24 | thuml/Time-Series-Library |
| Traffic | Hourly | 24 | thuml/Time-Series-Library |
| Weather | 10-min | 720 | thuml/Time-Series-Library |
| Exchange | Daily | 30 | thuml/Time-Series-Library |
| M4-Hourly | Hourly | 24 | M4 Competition |
| ILI | Weekly | 24 | thuml/Time-Series-Library |
| Dataset | Frequency | Horizon | Source |
|---|---|---|---|
| M4-Daily | Daily | 14 | M4 Competition |
| M4-Weekly | Weekly | 13 | M4 Competition |
| M4-Monthly-Monash | Monthly | 18 | Monash |
| M3-Monthly | Monthly | 18 | M3 Competition |
| M3-Quarterly | Quarterly | 8 | M3 Competition |
| Hospital | Monthly | 12 | Monash |
| COVID-Deaths | Daily | 30 | Monash |
| NN5-Daily | Daily | 56 | Monash |
| FRED-MD | Monthly | 12 | Monash |
| Dominick | Weekly | 8 | Monash |
| KDD-Cup-Hourly | Hourly | 48 | Monash |
| Solar-10min | 10-min | 60 | Monash |
| AusElectricity | 30-min | 48 | Monash |
| Wind-Farms | 1-min | 60 | Monash |
| Sunspot | Daily | 30 | Monash |
| Saugeen | Daily | 30 | Monash |
| Pedestrian | Hourly | 24 | Monash |
| Rideshare | Hourly | 24 | Monash |
| Tourism-Monthly | Monthly | 24 | Monash |
| USBirths | Daily | 30 | Monash |
All datasets download automatically on first use. Run ./run_all.sh download to pre-fetch.
Classical (CPU):
Foundation (GPU):
# Training fractions (% of full training set)
TRAIN_FRACTIONS = [0.02, 0.05, 0.10, 0.20, 0.50, 1.00]
# Random seeds for variance estimation
SEEDS = [0, 1, 2]
# Split: 60% train / 20% val / 20% test (chronological)
results/
├── classical_results_*.csv # Raw classical results
├── foundation_raw_*.csv # Raw foundation results
├── aggregated_results.csv # Mean ± std across seeds
├── fitted_parameters.csv # Power-law fit: Error(n) = A·n^(-α) + B
├── dataset_features.csv # Features: τ_acf, Hurst, noise ratio, seasonal strength, spectral entropy
└── breakeven_analysis.csv # Break-even points n*
figures/
├── fig1_scaling_curves.* # RQ1: scaling curves per dataset
├── fig2_feature_space.* # RQ2: dataset feature embedding
├── fig3_finetuning.* # RQ3: fine-tuning vs. zero-shot
└── fig4_pareto.* # RQ4: accuracy–cost Pareto frontier
logs/
└── *_<timestamp>.log # Per-run logs
fm-breakeven/
├── run_all.sh # Main entry point
├── audit_paper.py # Numerical audit of tex against ground-truth CSVs
├── rq3_analysis.py # RQ3 decision-tree analysis
├── plot_fig1.py # Figure 1: scaling curves
├── plot_fig2.py # Figure 2: feature space
├── plot_fig3.py # Figure 3: fine-tuning
├── src/
│ ├── data_loader.py # Dataset loading (auto-download, 30 datasets)
│ ├── models.py # Classical + foundation model wrappers
│ ├── config.py # Experiment configuration
│ ├── features.py # Feature extraction (τ_acf, H, S, Φ)
│ ├── analysis.py # Scaling curve fitting, break-even computation
│ ├── experiments.py # Experiment runner
│ └── visualization.py # Plotting utilities
├── scripts/
│ ├── run_classical.py # Run classical experiments (CPU)
│ ├── run_foundation.py # Run foundation model experiments (GPU)
│ ├── run_quick.py # Minimal run for installation verification
│ ├── smoke_test.py # Sanity check all 30 datasets load and run
│ ├── analyze_results.py # Aggregate results and generate figures
│ ├── analyze_finetuning.py # Fine-tuning analysis: full vs LoRA vs zero-shot
│ ├── validate_findings.py # Validates all 5 paper findings against raw CSVs
│ ├── compute_features.py # Dataset feature extraction script
│ ├── compute_costs.py # Extract per-model GPU-seconds; feeds plot_fig4.py
│ ├── generate_table5.py # Generate Table V (computational cost) as LaTeX
│ ├── plot_fig4.py # Figure 4: Pareto frontier
│ ├── download_datasets.py # Pre-fetch all 30 datasets
│ └── rq3_analysis_extended.py # RQ3 LOO validation
├── requirements.txt # CPU dependencies
└── requirements-gpu.txt # GPU dependencies (Chronos, Moirai, Lag-Llama)
Run scripts/validate_findings.py to verify every number cited in Findings 1–5 against the raw results CSVs:
python scripts/validate_findings.py
Runs 25 checks covering all 5 findings. Exits with code 0 if all pass, 1 if any fail. Example output:
FINDING 1: Zero-shot is already competitive
✓ FM zero-shot beats fully-trained classical on 19/30 datasets
✓ 15 datasets are FM-dominant (classical never beats FM at any fraction)
...
ALL CHECKS PASSED (25 checks)
Run scripts/analyze_finetuning.py to reproduce the numbers cited in Finding 3 of the paper:
python scripts/analyze_finetuning.py
This script produces four sections:
| Section | What it computes |
|---|---|
| 1. Per-dataset comparison | Best MASE for full / LoRA / zero-shot at train_fraction=1.0; counts how often full beats LoRA and zero-shot |
| 2. Seed-level variance | Mean/median/max std across seeds per setting — proxy for instability |
| 3. Catastrophic failures | Cases with MASE > 10 at low fractions (≤5%), broken down by model family |
| 4. Inflation ratio | Per-dataset ratio of best full fine-tune MASE (at low fraction) vs zero-shot MASE |
Key findings from the analysis:
All numbers below are verified by scripts/validate_findings.py (25 automated checks).
| Finding | Result | Command to reproduce |
|---|---|---|
| FM zero-shot beats classical (frac=1.0) | 19/30 datasets | python scripts/validate_findings.py |
| FM-dominant (classical never beats FM at any fraction) | 15/30 datasets | python scripts/validate_findings.py |
| LoRA improves over zero-shot at full data | 17/30 datasets | python scripts/validate_findings.py |
| Full fine-tuning beats LoRA at full data | 22/30 datasets | python scripts/validate_findings.py |
| Catastrophic failures (MASE > 10) at frac ≤ 5% | 109 cases | python scripts/validate_findings.py |
| XGBoost best classical at full data | 14/30 datasets | python scripts/validate_findings.py |
| Data starvation rule resolves without model training | 10/30 datasets | python scripts/validate_findings.py |
# Reproduce all findings in one command
python scripts/validate_findings.py
# Expected output: ALL CHECKS PASSED (25 checks)
Foundation models need:
| Model | Zero-shot inference | LoRA fine-tune | Total GPU-hours (all 30 datasets) |
|---|---|---|---|
| Chronos-Bolt-S | <1s | ~1.6s | 0.23h |
| Chronos-Bolt-B | <1s | ~2.9s | 0.44h |
| Moirai-S | ~0.1s | ~1.5s | 0.23h |
| Moirai-B | ~0.1s | ~2.5s | 0.37h |
| Moirai-L | ~0.2s | ~4.3s | 0.65h |
| Lag-Llama | ~9.5s | N/A | 0.24h |
Runtimes measured from duration_s column in raw results CSVs. Full fine-tuning adds ~50% over LoRA.
| Model | Inference | Fine-tuning |
|---|---|---|
| Chronos-Bolt-S | ~1 GB | ~3 GB |
| Chronos-Bolt-B | ~2 GB | ~6 GB |
| Moirai-S | <1 GB | ~2 GB |
| Moirai-B | ~1 GB | ~4 GB |
| Moirai-L | ~2 GB | ~8 GB |
| Lag-Llama | <1 GB | N/A |
Consumer GPUs (RTX 4090/5090 with 24-32GB) can run everything, including fine-tuning.
# 1. Test single model + dataset first (~10 min)
python scripts/run_foundation.py --gpu \
--datasets ETTh1 \
--models Chronos-Bolt-S
# 2. Expand to more models
python scripts/run_foundation.py --gpu \
--datasets ETTh1 Exchange \
--models Chronos-Bolt-S Chronos-Bolt-B Moirai-S
# 3. Full run overnight (~8-12 hours on RTX 5090)
./run_all.sh
Only use a compute cluster if you need results faster. RTX 5090 ≈ 80% A100 speed for this workload.
@inproceedings{jerome2026breakeven,
title={When Do Foundation Models Pay Off?
Data Requirements for Pretrained Time Series Forecasters},
author={Nicholas Tan Jerome and Frank Simon},
booktitle={IEEE International Conference on Data Mining (ICDM)},
year={2026}
}
MIT
1 commits
Python
98.2%
Shell
1.8%
When Do Foundation Models Pay Off? Data Requirements for Pretrained Time Series Forecasters
Code for measuring break-even points — the training data size at which foundation models (Chronos, Moirai, Lag-Llama) start outperforming classical methods (Naive, ETS, ARIMA, XGBoost) across 30 benchmark datasets.
# Setup
git clone https://github.com/nicolaisi/fm-breakeven.git
cd fm-breakeven
pip install -r requirements.txt
# 0. Pre-fetch all 30 datasets (recommended before long runs)
./run_all.sh download
# 1. Run everything (classical + foundation + Lag-Llama, all 30 datasets)
./run_all.sh
# 2. Or run selectively
./run_all.sh classical # CPU only, all 30 datasets
./run_all.sh foundation # GPU: Chronos-Bolt + Moirai
./run_all.sh lagllama # GPU: Lag-Llama zero-shot only
./run_all.sh new # New 20 datasets only (classical + foundation)
./run_all.sh rq3 # RQ3 LOO analysis (after experiments complete)
Foundation models require a GPU:
pip install -r requirements-gpu.txt
./run_all.sh foundation
30 datasets total: 10 original + 20 new.
| Dataset | Frequency | Horizon | Source |
|---|---|---|---|
| ETTh1 | Hourly | 24 | ETDataset |
| ETTh2 | Hourly | 24 | ETDataset |
| ETTm1 | 15-min | 96 | ETDataset |
| ETTm2 | 15-min | 96 | ETDataset |
| Electricity | Hourly | 24 | thuml/Time-Series-Library |
| Traffic | Hourly | 24 | thuml/Time-Series-Library |
| Weather | 10-min | 720 | thuml/Time-Series-Library |
| Exchange | Daily | 30 | thuml/Time-Series-Library |
| M4-Hourly | Hourly | 24 | M4 Competition |
| ILI | Weekly | 24 | thuml/Time-Series-Library |
| Dataset | Frequency | Horizon | Source |
|---|---|---|---|
| M4-Daily | Daily | 14 | M4 Competition |
| M4-Weekly | Weekly | 13 | M4 Competition |
| M4-Monthly-Monash | Monthly | 18 | Monash |
| M3-Monthly | Monthly | 18 | M3 Competition |
| M3-Quarterly | Quarterly | 8 | M3 Competition |
| Hospital | Monthly | 12 | Monash |
| COVID-Deaths | Daily | 30 | Monash |
| NN5-Daily | Daily | 56 | Monash |
| FRED-MD | Monthly | 12 | Monash |
| Dominick | Weekly | 8 | Monash |
| KDD-Cup-Hourly | Hourly | 48 | Monash |
| Solar-10min | 10-min | 60 | Monash |
| AusElectricity | 30-min | 48 | Monash |
| Wind-Farms | 1-min | 60 | Monash |
| Sunspot | Daily | 30 | Monash |
| Saugeen | Daily | 30 | Monash |
| Pedestrian | Hourly | 24 | Monash |
| Rideshare | Hourly | 24 | Monash |
| Tourism-Monthly | Monthly | 24 | Monash |
| USBirths | Daily | 30 | Monash |
All datasets download automatically on first use. Run ./run_all.sh download to pre-fetch.
Classical (CPU):
Foundation (GPU):
# Training fractions (% of full training set)
TRAIN_FRACTIONS = [0.02, 0.05, 0.10, 0.20, 0.50, 1.00]
# Random seeds for variance estimation
SEEDS = [0, 1, 2]
# Split: 60% train / 20% val / 20% test (chronological)
results/
├── classical_results_*.csv # Raw classical results
├── foundation_raw_*.csv # Raw foundation results
├── aggregated_results.csv # Mean ± std across seeds
├── fitted_parameters.csv # Power-law fit: Error(n) = A·n^(-α) + B
├── dataset_features.csv # Features: τ_acf, Hurst, noise ratio, seasonal strength, spectral entropy
└── breakeven_analysis.csv # Break-even points n*
figures/
├── fig1_scaling_curves.* # RQ1: scaling curves per dataset
├── fig2_feature_space.* # RQ2: dataset feature embedding
├── fig3_finetuning.* # RQ3: fine-tuning vs. zero-shot
└── fig4_pareto.* # RQ4: accuracy–cost Pareto frontier
logs/
└── *_<timestamp>.log # Per-run logs
fm-breakeven/
├── run_all.sh # Main entry point
├── audit_paper.py # Numerical audit of tex against ground-truth CSVs
├── rq3_analysis.py # RQ3 decision-tree analysis
├── plot_fig1.py # Figure 1: scaling curves
├── plot_fig2.py # Figure 2: feature space
├── plot_fig3.py # Figure 3: fine-tuning
├── src/
│ ├── data_loader.py # Dataset loading (auto-download, 30 datasets)
│ ├── models.py # Classical + foundation model wrappers
│ ├── config.py # Experiment configuration
│ ├── features.py # Feature extraction (τ_acf, H, S, Φ)
│ ├── analysis.py # Scaling curve fitting, break-even computation
│ ├── experiments.py # Experiment runner
│ └── visualization.py # Plotting utilities
├── scripts/
│ ├── run_classical.py # Run classical experiments (CPU)
│ ├── run_foundation.py # Run foundation model experiments (GPU)
│ ├── run_quick.py # Minimal run for installation verification
│ ├── smoke_test.py # Sanity check all 30 datasets load and run
│ ├── analyze_results.py # Aggregate results and generate figures
│ ├── analyze_finetuning.py # Fine-tuning analysis: full vs LoRA vs zero-shot
│ ├── validate_findings.py # Validates all 5 paper findings against raw CSVs
│ ├── compute_features.py # Dataset feature extraction script
│ ├── compute_costs.py # Extract per-model GPU-seconds; feeds plot_fig4.py
│ ├── generate_table5.py # Generate Table V (computational cost) as LaTeX
│ ├── plot_fig4.py # Figure 4: Pareto frontier
│ ├── download_datasets.py # Pre-fetch all 30 datasets
│ └── rq3_analysis_extended.py # RQ3 LOO validation
├── requirements.txt # CPU dependencies
└── requirements-gpu.txt # GPU dependencies (Chronos, Moirai, Lag-Llama)
Run scripts/validate_findings.py to verify every number cited in Findings 1–5 against the raw results CSVs:
python scripts/validate_findings.py
Runs 25 checks covering all 5 findings. Exits with code 0 if all pass, 1 if any fail. Example output:
FINDING 1: Zero-shot is already competitive
✓ FM zero-shot beats fully-trained classical on 19/30 datasets
✓ 15 datasets are FM-dominant (classical never beats FM at any fraction)
...
ALL CHECKS PASSED (25 checks)
Run scripts/analyze_finetuning.py to reproduce the numbers cited in Finding 3 of the paper:
python scripts/analyze_finetuning.py
This script produces four sections:
| Section | What it computes |
|---|---|
| 1. Per-dataset comparison | Best MASE for full / LoRA / zero-shot at train_fraction=1.0; counts how often full beats LoRA and zero-shot |
| 2. Seed-level variance | Mean/median/max std across seeds per setting — proxy for instability |
| 3. Catastrophic failures | Cases with MASE > 10 at low fractions (≤5%), broken down by model family |
| 4. Inflation ratio | Per-dataset ratio of best full fine-tune MASE (at low fraction) vs zero-shot MASE |
Key findings from the analysis:
All numbers below are verified by scripts/validate_findings.py (25 automated checks).
| Finding | Result | Command to reproduce |
|---|---|---|
| FM zero-shot beats classical (frac=1.0) | 19/30 datasets | python scripts/validate_findings.py |
| FM-dominant (classical never beats FM at any fraction) | 15/30 datasets | python scripts/validate_findings.py |
| LoRA improves over zero-shot at full data | 17/30 datasets | python scripts/validate_findings.py |
| Full fine-tuning beats LoRA at full data | 22/30 datasets | python scripts/validate_findings.py |
| Catastrophic failures (MASE > 10) at frac ≤ 5% | 109 cases | python scripts/validate_findings.py |
| XGBoost best classical at full data | 14/30 datasets | python scripts/validate_findings.py |
| Data starvation rule resolves without model training | 10/30 datasets | python scripts/validate_findings.py |
# Reproduce all findings in one command
python scripts/validate_findings.py
# Expected output: ALL CHECKS PASSED (25 checks)
Foundation models need:
| Model | Zero-shot inference | LoRA fine-tune | Total GPU-hours (all 30 datasets) |
|---|---|---|---|
| Chronos-Bolt-S | <1s | ~1.6s | 0.23h |
| Chronos-Bolt-B | <1s | ~2.9s | 0.44h |
| Moirai-S | ~0.1s | ~1.5s | 0.23h |
| Moirai-B | ~0.1s | ~2.5s | 0.37h |
| Moirai-L | ~0.2s | ~4.3s | 0.65h |
| Lag-Llama | ~9.5s | N/A | 0.24h |
Runtimes measured from duration_s column in raw results CSVs. Full fine-tuning adds ~50% over LoRA.
| Model | Inference | Fine-tuning |
|---|---|---|
| Chronos-Bolt-S | ~1 GB | ~3 GB |
| Chronos-Bolt-B | ~2 GB | ~6 GB |
| Moirai-S | <1 GB | ~2 GB |
| Moirai-B | ~1 GB | ~4 GB |
| Moirai-L | ~2 GB | ~8 GB |
| Lag-Llama | <1 GB | N/A |
Consumer GPUs (RTX 4090/5090 with 24-32GB) can run everything, including fine-tuning.
# 1. Test single model + dataset first (~10 min)
python scripts/run_foundation.py --gpu \
--datasets ETTh1 \
--models Chronos-Bolt-S
# 2. Expand to more models
python scripts/run_foundation.py --gpu \
--datasets ETTh1 Exchange \
--models Chronos-Bolt-S Chronos-Bolt-B Moirai-S
# 3. Full run overnight (~8-12 hours on RTX 5090)
./run_all.sh
Only use a compute cluster if you need results faster. RTX 5090 ≈ 80% A100 speed for this workload.
@inproceedings{jerome2026breakeven,
title={When Do Foundation Models Pay Off?
Data Requirements for Pretrained Time Series Forecasters},
author={Nicholas Tan Jerome and Frank Simon},
booktitle={IEEE International Conference on Data Mining (ICDM)},
year={2026}
}
MIT
1 commits
Python
98.2%
Shell
1.8%