t0-alpha and t0-beta, The Forecasting Company's time-series foundation models, running in the browser: Rust compiled to WebAssembly, WebGPU inference with a CPU fallback, weights quantized to Q8_0 or Q4_0 GGUF.
Try the compare page →: the same engine next to the official ONNX INT8 export and, opt-in, an F32 reference, side by side in one tab. A run downloads one ONNX file and one GGUF: about 210 MB with Q8_0, 160 MB with Q4_0; turning on the F32 reference adds 407 MB more.
Disclaimer: an independent port by idle-intelligence, not affiliated with or endorsed by The Forecasting Company. Weights are quantized from theforecastingcompany/t0-alpha and theforecastingcompany/t0-beta (Apache-2.0); forecasts may differ slightly from the PyTorch implementation.
- F32 port matches the PyTorch reference to 1.6e-6 max-abs error for alpha and 3.2e-6 for beta, worst case across the native backends (CPU and Metal).
- Full GIFT-Eval protocol, 97 configs, normalized to Seasonal Naive: Q8_0 scores MASE 0.7258 / CRPS 0.4943 and Q4_0 0.7334 / 0.4973, against our F32 control at 0.7255 / 0.4942 and the published t0-alpha card at 0.7240 / 0.4941 (
docs/runs/2026-09-20-official-protocol-full97-normalized.md). - Quantized weights on Hugging Face: alpha Q4_0 · alpha Q8_0 · beta Q4_0 · beta Q8_0.
- Architecture research in
docs/reports/t0-alpha.mdanddocs/reports/t0-alpha-anatomy.md.
crates/t0-core: the model, generic overburn::tensor::backend::Backend. Safetensors and GGUF loaders, RMSNorm, RoPE (interleaved-pair/xpos, matchingrotary_embedding_torch), QK-norm multi-head attention (time + group/cross-variate), SwiGLU MLP, causal per-timestep scaler, patch encoder, quantile head. The reference path for parity checks, and the CPU engine in the browser.crates/t0-fast: the WebGPU engine the demo runs. Rawwgpuand hand-written WGSL kernels, no Burn at inference; Q8_0/Q4_0 weights stay resident on the GPU as GGUF blocks and are dequantized inside the matmul shaders; one command encoder and one readback per forecast.crates/t0-wasm: thewasm-bindgensurface. Thefastfeature builds it overt0-fast(WebGPU), the defaultndarrayfeature overt0-core(CPU).crates/cli:t0-cli, native binary.ndarrayfeature (default),wgpuorfastpicks the backend. Commands:forecast,parity,bench,export-gguf,drift,gifteval,forecast-raw.web/: the demo page (index.html) and its Web Worker (worker.js).bench/compare/: the compare page, this engine next to the official ONNX INT8 export in one tab.scripts/build.sh: builds both wasm engines and assembles the site.tools/: Python helpers for fixtures and GIFT-Eval scoring;tools/make_fixtures.pygeneratesfixtures/from the reference PyTorch model.fixtures/: committed (small: ~430 KB), one manifest + per-case context/output/intermediate-activation binaries (little-endian f32).docs/reports/: architecture research and The Forecasting Company's published numbers.docs/BENCHMARKS.md,docs/runs/: research log.
Native (default ndarray backend):
cargo build --release
cargo test
Native with wgpu backend (feature-gated in the cli crate, not the library):
cargo build -p t0-cli --release --no-default-features --features wgpu
Browser (both wasm engines and the site, into _site/):
scripts/build.sh
The wasm32 flags live in .cargo/config.toml: +simd128, and --cfg getrandom_backend="wasm_js" because Burn's dependency chain pulls in rand/getrandom even though this inference-only crate never calls it. The build fails if a built wasm loses simd128.
tools/make_fixtures.py requires the reference PyTorch package:
python3 -m venv .venv && . .venv/bin/activate && pip install tfc-t0
python3 tools/make_fixtures.py
Python is used here only because the reference implementation is itself PyTorch: re-deriving golden outputs any other way would just be re-implementing the thing being checked, telling us nothing. This never runs as part of the Rust build; fixtures are committed.
Needs the t0-alpha checkpoint (not committed; weights are never committed). $MODELS_DIR below is wherever you keep downloaded Hugging Face checkpoints (defaults to models under the repo if unset):
hf download theforecastingcompany/t0-alpha --local-dir $MODELS_DIR/hf/theforecastingcompany/t0-alpha
cargo run -p t0-cli --release -- parity \
--fixtures fixtures \
--weights $MODELS_DIR/hf/theforecastingcompany/t0-alpha/model.safetensors \
--config $MODELS_DIR/hf/theforecastingcompany/t0-alpha/config.json
Gate: quantile output within 1e-4 max-abs of the reference on every fixture. Current result: 1.2e-6 (docs/BENCHMARKS.md).
The same checkpoint is needed for cargo test -- --ignored (crates/t0-core/tests/{loader,parity}.rs).
t0-cli export-gguf writes a GGUF v3 container (crates/t0-core/src/gguf.rs): llama.cpp's own block layouts, Q8_0 (32 values, f16 scale + 32 i8) and Q4_0 (32 values, f16 scale + 16 bytes of nibbles, matching ggml_quantize_row_q4_0's exact rounding/sign convention), plus the model config as t0.* GGUF metadata keys, so a GGUF file is self-describing (no separate config.json needed to load one).
Which tensors are quantized (Weights::is_quantizable in crates/t0-core/src/weights.rs): the four big per-layer matmuls (attention.wQKV.weight, attention.wO.weight, mlp.0.weight, mlp.2.weight) to the requested Q8_0/Q4_0/f16 scheme. Everything else stays f16: patch encoder, type embeddings, decoder, every norm (q_norm/k_norm/attention_block.norm/layer norm/out_norm), and head.quantile_levels. Rationale: the patch encoder gates every downstream layer's accuracy, the decoder produces the quantile values directly, and norms/quantile_levels are tiny: quantizing them buys negligible size for real precision risk.
t0-cli export-gguf --weights <model.safetensors> --config <config.json> --quant f16|q8_0|q4_0 --out <file.gguf>
t0-fast keeps the four quantized matmuls as Q8_0/Q4_0 blocks in GPU buffers and dequantizes them inside the matmul shaders; the small f16 tensors are expanded to f32 at load. t0-core (the CPU engine, and the Burn wgpu backend) dequantizes every GGUF tensor to f32 at load, so there quantization shrinks the download, not the matmul dtype. Round-trip error against the PyTorch reference and the latency tables are in docs/BENCHMARKS.md; accuracy on the full GIFT-Eval protocol is in the Status section above.
t0-cli --backend ndarray|wgpu|fast picks the engine: ndarray and wgpu are Burn backends of t0-core, fast is t0-fast on native wgpu. Each is a Cargo feature of the cli crate (cargo build -p t0-cli --release --no-default-features --features wgpu, or --features fast), and --backend errors out when it does not match the binary's compiled feature: backend selection stays a compile-time choice, not a runtime switch. Burn wgpu F32 parity matches the PyTorch reference to the same order of magnitude as ndarray (worst case 1.6e-6 vs ndarray's 1.2e-6), and t0-fast to 1.25e-6.
forecast_batch/TimeSeries::from_context_batch run N independent signals in one forward pass by giving each its own group id; the existing cross-variate group-attention mask (group-id equality) isolates them. 8 batched copies of fixture 0 match 8x the single-signal forecast to <=1e-5 max-abs (crates/t0-core/tests/batch.rs, #[ignore]d, needs the checkpoint).
# Export quantized weights
t0-cli export-gguf --weights <safetensors> --config <config.json> --quant q4_0 --out t0-alpha-q4_0.gguf
# Forecast/parity accept either format; --config is optional for GGUF (metadata is embedded)
t0-cli parity --fixtures fixtures --weights t0-alpha-q4_0.gguf --backend ndarray
t0-cli forecast --fixture 0 --weights <safetensors> --config <config.json> --backend wgpu
# Latency: median of 5 timed forward passes after 1 warm-up
t0-cli bench --weights t0-alpha-q4_0.gguf --backend ndarray --signals 1000 --context 512 --horizon 96 --reps 5
web/ offers all four released weights, alpha Q8_0/Q4_0 and beta Q8_0/Q4_0, fetched from their Hugging Face repos (worker.js's MODELS table). The worker runs t0-fast on WebGPU when the browser can create a WebGPU device, and the t0-core CPU engine otherwise. LOCAL_MODELS_DIR in worker.js can point at a local web/models/<key>/ instead of Hugging Face, for testing new weights.
scripts/build.sh builds both wasm engines (fast for WebGPU at web/pkg-wgpu, ndarray for CPU at web/pkg) and assembles _site/ with web/ plus bench/compare/index.html and bench/compare/compare.js (no other assets: that page loads everything from Hugging Face and jsdelivr). scripts/serve.py serves _site/ locally. The pages GitHub Actions workflow runs the same build on push to main and deploys _site/ to GitHub Pages.
FUTUREcovariate rows in the scaler (crates/t0-core/src/scaler.rsrejects them; only target and historical rows are ported).