docs: address PR #369 review comments and add dataset preflight

- Add context limit rationale to system_requirements.md with memory formula
- Update SKILL.md to include XReg/covariates in description and usage sections
- Add dataset-aware memory estimation to check_system.py with new CLI args
- Document memory estimation in api_reference.md with Mermaid diagram
- Add dataset preflight section to SKILL.md with examples

Resolves review comments about:
- How context limits (512/1024) were determined
- Including XReg mode description in skill documentation

Bonus enhancement: Dataset preflight checking prevents OOM before loading data.
This commit is contained in:
borealBytes
2026-02-25 21:27:31 -05:00
parent 1c959c8919
commit 5aad77bd61
4 changed files with 377 additions and 5 deletions
+67 -4
View File
@@ -4,10 +4,12 @@ description: >
Zero-shot time series forecasting with Google's TimesFM foundation model. Use this
skill when forecasting ANY univariate time series — sales, sensor readings, stock prices,
energy demand, patient vitals, weather, or scientific measurements — without training a
custom model. Automatically checks system RAM/GPU before loading the model, supports
CSV/DataFrame/array inputs, and returns point forecasts with calibrated prediction
intervals. Includes a preflight system checker script that MUST be run before first use
to verify the machine can load the model.
custom model. Supports both basic forecasting and advanced covariate forecasting (XReg)
with dynamic and static exogenous variables. Automatically checks system RAM/GPU before
loading the model, validates dataset fit before processing, supports CSV/DataFrame/array
inputs, and returns point forecasts with calibrated prediction intervals. Includes a
preflight system checker script that MUST be run before first use to verify the machine
can load the model and handle your specific dataset.
license: Apache-2.0
metadata:
author: Clayton Young (@borealBytes)
@@ -40,6 +42,8 @@ Use this skill when:
- You have time series of **any length** (the model handles 116,384 context points)
- You need to **batch-forecast** hundreds or thousands of series efficiently
- You want a **foundation model** approach instead of hand-tuning ARIMA/ETS parameters
- You need **covariate forecasting** with exogenous variables (price, promotions, holidays, day-of-week effects) → use `forecast_with_covariates()` (TimesFM 2.5 + `pip install timesfm[xreg]`)
Do **not** use this skill when:
@@ -47,6 +51,8 @@ Do **not** use this skill when:
- You need time series classification or clustering → use `aeon`
- You need multivariate vector autoregression or Granger causality → use `statsmodels`
- Your data is tabular (not temporal) → use `scikit-learn`
- You cannot install optional dependencies → XReg requires scikit-learn and JAX
> **Note on Anomaly Detection**: TimesFM does not have built-in anomaly detection, but you
> can use the **quantile forecasts as prediction intervals** — values outside the 90% CI
@@ -88,6 +94,39 @@ flowchart TD
disk -->|"No"| block_disk["🛑 BLOCKED<br/>Need space for weights"]
```
### Dataset Preflight (NEW)
Before loading your actual data, verify it will fit in memory:
```bash
# Quick estimate for your dataset
python scripts/check_system.py \
--num-series 1000 \
--context-length 1024 \
--horizon 24 \
--batch-size 32 \
--estimate-only
```
This will show you the estimated memory requirements and warn if your dataset is too large.
**Memory Estimation Formula**:
`RAM ≈ 0.8 GB (model) + 0.5 GB (overhead) + (0.2 MB × num_series × context_length / 1000)`
**Example Outputs**:
**Dataset Fits**:
```
Total CPU memory: 2.34 GB
Total GPU memory: 2.15 GB
```
⚠️ **Dataset Too Large**:
```
Dataset requires ~12.5 GB RAM but system has 8.0 GB.
Try: context_length=512 or process in chunks of 50 series.
```
### Hardware Requirements by Model Version
| Model | Parameters | RAM (CPU) | VRAM (GPU) | Disk | Context |
@@ -336,11 +375,35 @@ for i in range(0, len(inputs), CHUNK):
### `scripts/check_system.py`
Mandatory preflight checker — run before first model load.
Now includes **dataset-aware memory estimation** to prevent OOM errors before loading your data.
```bash
# Basic system check
python scripts/check_system.py
# Check if your specific dataset will fit
python scripts/check_system.py \
--num-series 1000 \
--context-length 1024 \
--horizon 24 \
--batch-size 32
# Quick memory estimate without system checks
python scripts/check_system.py \
--num-series 5000 \
--context-length 2048 \
--estimate-only
```
**What it checks**:
1. **Available RAM** — warns if below 4 GB, blocks if below 2 GB
2. **GPU availability** — detects CUDA/MPS devices and VRAM
3. **Disk space** — verifies room for the ~800 MB model download
4. **Python version** — requires 3.10+
5. **Existing installation** — checks if `timesfm` and `torch` are installed
6. **Dataset fit** (NEW) — estimates memory for your specific dataset and warns if it won't fit
### `scripts/forecast_csv.py`
End-to-end CSV forecasting CLI.
@@ -221,6 +221,70 @@ Where `B` = batch size (number of input series), `H` = forecast horizon.
---
---
## Memory Estimation
Before running forecasts on large datasets, estimate memory requirements:
### Formula
```mermaid
block-beta
columns 3
ram["Total RAM Required"] model["Model Weights<br/>~0.8 GB"] overhead["Runtime Overhead<br/>~0.5 GB"] buffers["I/O Buffers<br/>~0.2 MB per 1000 series<br/>per 1000 context"]
ram --> model
ram --> overhead
ram --> buffers
```
**Formula**:
`RAM (GB) ≈ 0.8 + 0.5 + (0.0002 × num_series × context_length)`
**Variables**:
- `num_series`: Number of time series in your batch
- `context_length`: Your `max_context` value (or max series length)
- `batch_size`: Your `per_core_batch_size` (affects parallel processing overhead)
### Quick Reference
| Dataset Size | Context=512 | Context=1024 | Context=2048 |
|--------------|-------------|--------------|--------------|
| 100 series | ~1.4 GB | ~1.5 GB | ~1.7 GB |
| 1,000 series | ~1.9 GB | ~2.3 GB | ~3.1 GB |
| 10,000 series| ~9.0 GB | ~17.0 GB | ~33.0 GB |
### Using the Preflight Checker
```bash
python scripts/check_system.py \
--num-series 1000 \
--context-length 1024 \
--batch-size 32
```
This validates both system requirements AND dataset fit before loading the model.
### Reducing Memory Usage
If your dataset is too large:
1. **Reduce context length**: Use `max_context=512` instead of 1024+ (50% reduction)
2. **Process in chunks**: Split large batches into smaller groups:
```python
CHUNK_SIZE = 100
for i in range(0, len(inputs), CHUNK_SIZE):
chunk = inputs[i:i+CHUNK_SIZE]
point, quantiles = model.forecast(horizon=H, inputs=chunk)
# Save chunk results
```
3. **Reduce batch size**: Lower `per_core_batch_size` (slower but less memory)
4. **Use CPU**: If GPU OOM, the model will automatically fall back to CPU
## Error Handling
| Error | Cause | Fix |
@@ -5,6 +5,33 @@
TimesFM can run on a variety of hardware configurations. This guide helps you
choose the right setup and tune performance for your machine.
### How Context Limits Are Determined
The `max_context` values in each tier are **conservative recommendations** based on memory-performance tradeoffs, not hard limits. TimesFM 2.5 supports up to 16,384 context points, but smaller values are recommended for most use cases.
**Why 512 and 1024?**
| Factor | 512 Context | 1024 Context |
|--------|-------------|--------------|
| **Memory per 1000 series** | ~100 MB | ~200 MB |
| **Typical Use Case** | Daily data, ~1-2 years | Daily data, ~2-3 years |
| **Inference Speed** | Faster | Moderate |
| **Hardware** | 4-8 GB RAM | 16 GB RAM or GPU |
**Memory Formula**: `RAM ≈ model_weights + 0.5 GB + (0.2 MB × num_series × context_length / 1000)`
Where:
- `model_weights` = ~800 MB (TimesFM 2.5)
- `context_length` = your `max_context` value
- `num_series` = number of time series in your batch
**You can use larger contexts** if your hardware supports it:
- **Up to 2048**: Requires ~16 GB RAM for moderate batch sizes
- **Up to 4096**: Requires GPU or 32+ GB RAM
- **Up to 16384**: Maximum supported, requires significant memory
See [Data Preparation Guide](data_preparation.md) for context length recommendations by data frequency.
### Tier 1: Minimal (CPU-Only, 48 GB RAM)
- **Use case**: Light exploration, single-series forecasting, prototyping
+219 -1
View File
@@ -25,6 +25,7 @@ import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
import math
# ---------------------------------------------------------------------------
@@ -424,6 +425,168 @@ def recommend_batch_size(report: SystemReport) -> int:
return 4
def estimate_memory_gb(
num_series: int,
context_length: int,
horizon: int = 0,
batch_size: int = 32,
model_version: str = "v2.5",
) -> dict[str, float]:
"""Estimate memory requirements for a dataset.
Args:
num_series: Number of time series in the dataset
context_length: Length of each time series context window
horizon: Forecast horizon (optional, for output storage)
batch_size: Batch size for inference
model_version: Model version being used
Returns:
Dictionary with memory estimates in GB for different components
"""
# Base model memory (weights + overhead)
model_memory_gb = 0.8 # ~800MB for model weights
overhead_gb = 0.5 # Python overhead, libraries, etc.
# Input data memory: each value is float32 (4 bytes)
# Formula: num_series * context_length * 4 bytes / (1024^3)
input_gb = (num_series * context_length * 4) / (1024**3)
# Batch processing memory (peak during inference)
# Each batch needs: batch_size * context_length * 4 bytes
batch_input_gb = (batch_size * context_length * 4) / (1024**3)
# Output memory: horizon * num_series * quantiles * 4 bytes
# Default is 10 quantiles (mean + 9 quantiles)
num_quantiles = 10
output_gb = (num_series * horizon * num_quantiles * 4) / (1024**3) if horizon > 0 else 0
# Total memory with some headroom for intermediate computations
total_gb = model_memory_gb + overhead_gb + input_gb + batch_input_gb + output_gb
# Add 20% buffer for intermediate tensors and OS overhead
total_with_buffer = total_gb * 1.2
return {
"model_weights": model_memory_gb,
"overhead": overhead_gb,
"input_data": input_gb,
"batch_processing": batch_input_gb,
"output_data": output_gb,
"total": total_gb,
"total_with_buffer": total_with_buffer,
}
def check_dataset_fit(
num_series: int,
context_length: int,
horizon: int = 0,
batch_size: int = 32,
model_version: str = "v2.5",
) -> tuple[bool, str, dict[str, float]]:
"""Check if a dataset will fit in available memory.
Args:
num_series: Number of time series in the dataset
context_length: Length of each time series context window
horizon: Forecast horizon (optional)
batch_size: Batch size for inference
model_version: Model version being used
Returns:
Tuple of (fits: bool, message: str, memory_details: dict)
"""
memory = estimate_memory_gb(num_series, context_length, horizon, batch_size, model_version)
total_ram = _get_total_ram_gb()
available_ram = _get_available_ram_gb()
required = memory["total_with_buffer"]
# Leave 10% headroom for OS and other processes
usable_ram = total_ram * 0.9
usable_available = available_ram * 0.9 if available_ram > 0 else usable_ram
if required > total_ram:
return (
False,
f"Dataset requires {required:.1f} GB but system only has {total_ram:.1f} GB RAM. "
f"Consider processing in chunks or using a machine with more RAM.",
memory,
)
elif required > usable_available:
return (
False,
f"Dataset requires {required:.1f} GB but only {available_ram:.1f} GB is available. "
f"Close other applications or restart to free memory.",
memory,
)
elif required > usable_ram * 0.8:
return (
True,
f"Dataset will fit ({required:.1f} GB needed, {total_ram:.1f} GB total) "
f"but memory usage will be high. Consider reducing batch_size.",
memory,
)
else:
return (
True,
f"Dataset fits comfortably: {required:.1f} GB needed, {total_ram:.1f} GB available.",
memory,
)
def print_memory_estimate(
num_series: int,
context_length: int,
horizon: int = 0,
batch_size: int = 32,
model_version: str = "v2.5",
) -> None:
"""Print a detailed memory estimate for a dataset.
Args:
num_series: Number of time series in the dataset
context_length: Length of each time series context window
horizon: Forecast horizon (optional)
batch_size: Batch size for inference
model_version: Model version being used
"""
memory = estimate_memory_gb(num_series, context_length, horizon, batch_size, model_version)
total_ram = _get_total_ram_gb()
available_ram = _get_available_ram_gb()
print(f"\n{'=' * 50}")
print(f" Memory Estimate for Dataset")
print(f"{'=' * 50}")
print(f" Dataset: {num_series:,} series × {context_length} context length")
if horizon > 0:
print(f" Horizon: {horizon} steps")
print(f" Batch size: {batch_size}")
print(f" Model: {model_version}")
print(f"{'-' * 50}")
print(f" Model weights: {memory['model_weights']:.2f} GB")
print(f" Overhead: {memory['overhead']:.2f} GB")
print(f" Input data: {memory['input_data']:.2f} GB")
print(f" Batch processing: {memory['batch_processing']:.2f} GB")
if horizon > 0:
print(f" Output data: {memory['output_data']:.2f} GB")
print(f"{'-' * 50}")
print(f" Total (raw): {memory['total']:.2f} GB")
print(f" Total (+20% buf): {memory['total_with_buffer']:.2f} GB")
print(f"{'-' * 50}")
print(f" System RAM: {total_ram:.1f} GB")
print(f" Available RAM: {available_ram:.1f} GB")
print(f"{'=' * 50}")
fits, message, _ = check_dataset_fit(
num_series, context_length, horizon, batch_size, model_version
)
status_icon = "" if fits else "🛑"
print(f" {status_icon} {message}")
print(f"{'=' * 50}\n")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
@@ -490,7 +653,7 @@ def print_report(report: SystemReport) -> None:
def main() -> None:
parser = argparse.ArgumentParser(
description="Check system requirements for TimesFM."
description="Check system requirements for TimesFM.",
)
parser.add_argument(
"--model",
@@ -503,10 +666,65 @@ def main() -> None:
action="store_true",
help="Output results as JSON (machine-readable)",
)
# Dataset preflight options (NEW)
dataset_group = parser.add_argument_group("dataset preflight (optional)")
dataset_group.add_argument(
"--num-series",
type=int,
metavar="N",
help="Number of time series in your dataset (for memory estimation)",
)
dataset_group.add_argument(
"--context-length",
type=int,
metavar="LEN",
help="Length of each input time series (max_context value)",
)
dataset_group.add_argument(
"--horizon",
type=int,
metavar="H",
default=24,
help="Forecast horizon length (default: 24)",
)
dataset_group.add_argument(
"--batch-size",
type=int,
metavar="SIZE",
default=32,
help="per_core_batch_size from ForecastConfig (default: 32)",
)
dataset_group.add_argument(
"--estimate-only",
action="store_true",
help="Only show memory estimate, skip system checks",
)
args = parser.parse_args()
# Handle dataset estimation only mode
if args.estimate_only and args.num_series and args.context_length:
print_memory_estimate(
args.num_series,
args.context_length,
args.horizon,
args.batch_size,
args.model,
)
sys.exit(0)
# Run system checks
report = run_checks(args.model)
# Add dataset check if parameters provided
if args.num_series and args.context_length:
print_memory_estimate(
args.num_series,
args.context_length,
args.horizon,
args.batch_size,
args.model,
)
if args.json:
print(json.dumps(report.to_dict(), indent=2))
else: