Skip to content

0.2.0: t0 and Timestral, a shared forecaster interface, and batching - #2

Merged
markhammond merged 9 commits into
mainfrom
feat/202610_t0-timestral-batching
Oct 19, 2026
Merged

markhammond merged 9 commits into
mainfrom
feat/202610_t0-timestral-batching

Conversation

@markhammond

@markhammond markhammond commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Adds two model families, a quantile-forecaster interface shared with TimesFM, batch forecasting for every model, and a host-chosen Kronos seeding policy. All packages move to 0.2.0.

Changes

t0 — Tsfm.Forecasting.T0 (new, Apache-2.0). t0-alpha and t0-beta, ported from tfc-t0 0.6.0. Same call shape as TimesFM (ForecastJoint with known-future flags, Forecast over OHLCVA). Accepts any context length (left-padded and masked) and decodes up to 1024 steps per pass. Each checkpoint's scaler convention is read from its config.json. No weights are packaged, because both checkpoints exceed nuget.org's 250 MB limit; the README has pinned, checksummed download commands.

Timestral — Tsfm.Forecasting.Timestral (new, Apache-2.0). Timestral 5M/28M/123M/209M, ported from mistralai/timestral 1.0.0. Applies the reference forecaster's defaults (symmetric averaging, non-negative clipping, refine frame, quantile cap) so published numbers reproduce. The averaging's negated input runs in the same forward pass as the original rather than a second call, which makes the port faster than Python on CPU at every size (5M: 11 vs 19 ms; 209M: 138 vs 191 ms).

Timestral weights — .Weights.5M, .Weights.28M (new). Embedded at pinned revisions; the build checks each payload's SHA-256. They are under Mistral's Modified MIT licence, which is revenue-restricted (USD 20M/month), not open source:

  • the licence ships as each package's LICENSE.txt, set via PackageLicenseFile, and must be accepted on install;
  • the repo's MIT LICENSE is not packed into these two packages.

Shared interface and batching.

  • IQuantileForecaster (core) is implemented by TimesFM, t0 and Timestral.
  • ForecastJointBatch / ForecastBatch take ReadOnlyMemory entries and write [entry][step][quantile] into a caller-sized span.
  • t0 and Timestral batch arbitrary entries in one pass; TimesFM stacks entries of the same shape.
  • KronosForecaster.InferBatch forecasts the final window of many instruments in one pass.
  • Core also gains IModelWeights for tokenizer-free checkpoints.

Kronos seeding policy. KronosForecaster.Seed decides each window's random draws from its anchor bar and a series key. The default AnchorTime keeps the 0.1.x behaviour; PerSeries gives each SeriesKey its own stream; Salted derives a fresh, reproducible run from either.

Tooling.

  • scripts/pack.sh: fetches the weights, packs, verifies, and prints the upload order.
  • Fetch scripts for t0 and Timestral, with pinned revisions and SHA-256 checks.
  • CI runs the new weight-free tests, and fetches the 5M/28M weights for the pack job.

Compatibility

  • TimesFM and Kronos single calls produce bit-identical output to 0.1.3, checked against captured 0.1.3 outputs after both were refactored onto the batch paths.
  • Binary-breaking: KronosForecaster.Infer/InferPath gain a trailing optional seriesKey. Callers' source is unchanged, but code compiled against 0.1.3 must be recompiled.

Verification

  • t0 parity (tests/Tsfm.Forecasting.T0.Parity), both checkpoints: scaling and masks match exactly; layers within 6e-7 relative; forecasts within 5e-7. Includes the quickstart notebook's electricity-price and M4 examples.
  • Timestral parity (tests/Tsfm.Forecasting.Timestral.Parity), all four sizes, defaults on and off: every stage within 1.5e-6 relative, forecasts within 9e-7, including the covariate path and a mixed batch.
  • Tests: t0 27, Timestral 14 weight-free + 7 on the embedded weights, Kronos integration 10, TimesFM 9, Kronos unit 16. All pass.
  • Packaging: dotnet pack + verify-packages.sh pass for all 9 packages. Each package's licence was checked in its packed .nuspec.

claude and others added 9 commits October 19, 2026 13:46
Port The Forecasting Company's t0 family from tfc-t0 0.6.0 as
Tsfm.Forecasting.T0 (Apache-2.0): causal scaler, patch encoder,
alternating time/group transformer with xPos rotary embedding, residual
decoder and monotone quantile head. Each checkpoint's scaler convention
is read from its config; t0-beta's differs from alpha's.

T0Forecaster takes the TimesFM shape (ForecastJoint with known-future
flags, Forecast over OHLCVA). Both now implement a shared
IQuantileForecaster in the core package, so one call site drives either.
Unlike TimesFM, any context length is accepted and up to 1024 steps are
decoded in one pass.

Verified stage by stage against the reference on both checkpoints,
including the quickstart notebook's electricity-price and M4 examples.
Weights are fetched at pinned, checksummed revisions; both exceed the
nuget.org size limit, so neither is packaged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
IQuantileForecaster gains ForecastJointBatch and ForecastBatch over
JointSeries / ReadOnlyMemory entries sharing a horizon, written
[entry][step][quantile] into a caller-sized span.

- t0 batches any entries in one pass, each its own attention group,
  shorter contexts left-padded and masked, cut into passes by
  MaxRowsPerPass. Checked against the reference in the parity harness.
- TimesFM stacks same-shape entries on the model's batch axis. Single
  calls now run as a batch of one, bit-identical to 0.1.3.
- KronosForecaster.InferBatch forecasts the final window of many series;
  Infer/InferPath share the generalised loop, bit-identical to 0.1.3.

Also: clearer t0 weight installation (pinned, checksummed download
commands; Load names them when files are missing), corrected wording on
covariate-to-target information flow, and all packages move to 0.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
KronosForecaster.Seed decides each window's sampling stream from its
anchor bar and a series key (KronosSeries.SeriesKey, or seriesKey: on
Infer/InferPath). SamplingSeeds.AnchorTime, the default, keeps the
current behaviour: series anchored on one bar share draws. PerSeries
gives each key an independent stream; Salted derives a fresh,
reproducible run from any policy. Batch invariance holds under all of
them, and the default is bit-identical to before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
…mbedded

Port mistralai/timestral 1.0.0 as Tsfm.Forecasting.Timestral (Apache-2.0):
causal per-patch scaler, gated time/variate transformer with partial
RoPE, Decoupled Horizon Forecasting decoder tokens and the monotone
21-quantile head. TimestralForecaster implements IQuantileForecaster,
batches included, and applies the reference forecaster's defaults
(symmetric averaging, non-negative clipping, advancing refine frame,
quantile cap); the mirrored input rides in the same forward pass.

Verified stage by stage against the reference on all four sizes, with
and without the defaults, including the covariate path and the shared
notebook examples.

Timestral-5M and -28M ship in Tsfm.Forecasting.Timestral.Weights.*,
embedded at pinned revisions whose SHA-256 the build checks. The weights
are under Mistral's revenue-restricted Modified MIT licence, carried
verbatim as the package licence file with acceptance required; 123M
and 209M are fetched by script. Core gains IModelWeights for
tokenizer-free checkpoints.

Also updates the README byline as requested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
Mistral's Modified MIT licence is not MIT-compatible, so each Timestral
weights package now declares <PackageLicenseFile>LICENSE.txt</> and
packs its own LICENSE.txt at the package root: the Mistral licence
verbatim with that package's pinned revision, then the MIT notice for
the small loader class. The repository's MIT LICENSE is no longer packed
into those two packages (PackRepositoryLicense=false), so they carry one
licence, the right one. Every other package is unchanged.

verify-packages.sh now checks the weights packages declare LICENSE.txt,
carry no stray MIT LICENSE, and require licence acceptance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
Fetches the checkpoints the weights packages embed, packs every
package into one directory (artifacts/ by default) after clearing stale
packages, runs verify-packages.sh, and prints the upload order by
dependency tier. Fails if a package is produced that the upload list
does not name. Nothing is pushed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
Updated changelog to reflect version 0.2.0 release.
The batch tests asserted batched forecasts equal single ones bit for bit
(TimesFM) or after rounding to fixed decimals (t0, Timestral). Neither
holds across runners: a matmul over more rows need not reproduce one
over fewer, and the macOS and some Linux runners take BLAS kernels whose
last bit differs. Rounding to decimals made it worse — values 2e-6 apart
failed whenever they straddled a rounding boundary, so the same commit
passed in one run and failed in the next.

Compare to a relative 1e-5 instead (~80 float32 ulps), and correct the
README, which claimed batching was exact for TimesFM and Kronos. The CI
failures were 6e-8 to 4e-6 apart; a stacking bug is orders larger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1dTUhvq2UnFWdBmH7AA8G
@markhammond
markhammond merged commit 6755c8e into main Oct 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants