Chronos-Bolt (base/small), Chronos-T5-small, and TimesFM-2.5-200M run zero-shot (no fine-tuning) on the same BIDMC cross-entity test windows, baselines, and WQL/MAE metrics as the prior from-scratch RuForecast BIDMC result. Unlike every from-scratch RuForecast configuration tried this session, all four pretrained checkpoints beat both last-value and seasonal-naive baselines on WQL, on both independent patient-partition judges (12-42% margin), consistent with the massive-pretraining-corpus hypothesis from the prior literature pass.
30 KiB
RuForecast benchmark protocol
Evidence status
No RuForecast runtime, accuracy, calibration, memory, or operational result is
recorded here yet. Every threshold below is an ADR-348 target, not a measured
claim. The first accepted row must identify a clean commit, the Cargo.lock
digest, the exact Rust toolchain, host, backend, model/configuration digest,
fixture or corpus digest, command, and evidence label.
Benchmark boundaries
RuForecast uses two different forms of evidence:
- Deterministic correctness evidence comes from Rust unit, property, replay, split-isolation, and artifact-tamper tests. A fixed input and artifact must produce the same output within its declared platform class.
- Runtime evidence comes from Criterion on a named host. Criterion inputs are generated from fixed checked-in code and seeds, but elapsed time is not deterministic. Timing from shared GitHub runners is informational only.
The benchmark implementation must not download a model or dataset, read raw
CSI, use a hosted model output, or enable CUDA implicitly. CPU benchmarks use
the explicit cpu feature and the Burn ndarray backend. CUDA validation belongs
to a separately governed Linux or hosted-accelerator receipt.
Required benchmark targets
| Package | Target | Purpose | CI authority |
|---|---|---|---|
ruview-forecast-model |
forecast_inference |
Fixed-seed forward pass, batch and shape scaling, ordered-quantile output | Compile gate; shared-runner timing is informational |
ruview-forecast-train |
data_pipeline |
Fixed generated records through validation, windowing, masking and batching | Compile gate; shared-runner timing is informational |
Both targets must use code-generated synthetic inputs, fixed seeds, bounded
allocations, criterion::black_box, and required-features = ["cpu"]. Setup,
artifact construction, and dataset generation stay outside the timed region
unless a benchmark name explicitly says they are included.
The model implementation owns structural parameter-count assertions. The currently reviewed design values are 35,700 parameters for the tiny CI preset and 20,285,108 for the large preset. These are design invariants, not benchmark results, and must be derived by a test from the actual module graph before they are quoted in a model card.
Local Linux reproducer
Run from a clean checkout after installing Rust 1.92.0:
RUFORECAST_CPUSET=0-7 \
RUFORECAST_THREADS=8 \
scripts/run-ruforecast-benchmarks.sh
The runner executes the focused contract/model/training tests, one real
optimizer step over a local hash-addressed synthetic JSONL shard, and the
idempotent synthetic CLI smoke. It then compile-checks both Criterion targets,
runs the targets, captures the CPU and toolchain metadata, and hashes every
output. A failed run retains its partial logs with status=FAILED and its exit
code rather than looking like a complete report. Results go under
target/ruforecast-evidence/, which is excluded from source control.
For a conservative single-thread reproducibility check, omit both environment
variables. To run against an uncommitted tree for diagnosis only, set
RUFORECAST_ALLOW_DIRTY=1; the resulting metadata is labelled SYNTHETIC
with scope DIRTY_WORKTREE_DIAGNOSTIC_ONLY and cannot support a release claim.
A clean run labels its host timing MEASURED and its input class SYNTHETIC,
but remains UNREVIEWED. Only a maintainer may append it to the accepted ledger
after checking the digests, shape, command, Criterion report and host scope.
The runner intentionally has no CUDA option and does not parse Criterion output into a pass/fail performance verdict. This prevents a noisy host result from silently acquiring release authority.
To compile the two benchmark targets without measuring them:
cd v2
cargo +1.92.0 bench --locked -p ruview-forecast-model \
--no-default-features --features cpu --bench forecast_inference --no-run
cargo +1.92.0 bench --locked -p ruview-forecast-train \
--no-default-features --features cpu --bench data_pipeline --no-run
For a short informational run, use the same targets without --no-run:
cargo +1.92.0 bench --locked -p ruview-forecast-model \
--no-default-features --features cpu --bench forecast_inference -- \
--warm-up-time 1 --measurement-time 2 --sample-size 10
cargo +1.92.0 bench --locked -p ruview-forecast-train \
--no-default-features --features cpu --bench data_pipeline -- \
--warm-up-time 1 --measurement-time 2 --sample-size 10
Running these commands does not add a ledger row automatically. Preserve the raw report and environment metadata, then have a maintainer assign its evidence scope before publishing a number.
The inference bench runs only tiny_ci by default so a routine CI trend step
cannot accidentally start the very expensive large CPU probe. Set
RUFORECAST_BENCH_LARGE=1 only on a controlled host when intentionally
measuring the fixed deployment shape:
RUFORECAST_BENCH_LARGE=1 scripts/run-ruforecast-benchmarks.sh
Deployment measurement shape
The initial CPU deployment probe is batch 1, context 1,024, 32 declared feature
streams, the fixed large_linux horizon of 300, and all seven declared
quantiles. Record at least 20 warmup
iterations and 200 measured iterations for a release candidate. Report p50,
p95, p99 or maximum, throughput, and the process peak resident set size.
ADR-348 G5 currently targets p95 at or below one second for 32 declared streams and peak process memory at or below 4 GiB. A Criterion result alone cannot close the memory gate because Criterion and Cargo are not the production inference process. G5 remains open until a standalone inference probe reports its own peak resident set size.
Accuracy and calibration protocol
Runtime speed never substitutes for forecasting quality. The frozen evaluation manifest must report identical examples for:
- Last-value and seasonal-naive baselines.
- RuForecast without RuVector retrieval.
- RuForecast with split-scoped RuVector retrieval.
Required report fields include weighted quantile loss by horizon, nominal 80% interval coverage, missingness, abstention coverage, selective risk, site and device slices, interference regime, and retrieval ablation. ADR-348 G3 targets weighted quantile loss at least 10% better than seasonal naive and 80% interval coverage between 75% and 85%. Those targets remain unmeasured until a frozen, leakage-free report is attached.
Informal HPO exploration note (unaccepted, not a release claim)
2026-09-01. An exploratory session ran the accuracy protocol above end to
end against a governed 24-window synthetic dataset (tiny_ci profile,
context 64 / horizon 12, temporal train/test split, not entity-holdout —
only one synthetic generator was used, so entity holdout does not apply) and
a small OptimizerSpec hyperparameter search (learning rate, weight decay,
gradient clip norm, batch size, epochs) using a new Darwin Mode numeric-genome
evolution engine (upstream: ruvnet/metaharness PR #260, not yet merged).
This is exploratory evidence only — not a frozen, leakage-free,
maintainer-reviewed report, and not eligible for the ledger below until one
is produced.
Prior to this exploration, a single real household window (76 real 1 Hz vital-signs samples, one physical ESP32 sensor, temporal not entity holdout) scored worse than both baselines (WQL 0.537 vs. last-value 0.106 and seasonal-naive 0.123) — consistent with a single training window overfitting rather than generalizing.
With a larger (still synthetic, still tiny_ci) 24-window training set and
three rounds of hyperparameter search, weighted quantile loss on the held-out
synthetic split improved and stayed ahead of both baselines throughout:
| Round | learning_rate | weight_decay | grad_clip | batch | epochs | WQL (model) | WQL (last-value) | WQL (seasonal-naive) |
|---|---|---|---|---|---|---|---|---|
| Default config | 0.0010000 | 1.00e-4 | 1.000 | 8 | 60 | 0.257 | 0.277 | 0.514 |
| Search round 1 | 0.0002356 | 3.05e-6 | 0.100 | 27 | 195 | 0.161 | 0.277 | 0.514 |
| Search round 2 | 0.0000298 | 4.09e-11 | 4.746 | 26 | 356 | 0.153 | 0.277 | 0.514 |
Round-2 gain over round 1 (−0.008) was much smaller than round-1's gain over
the default (−0.096) — a diminishing-returns signal consistent with a local
optimum for this model size and dataset, not a converged global result.
gradient_clip_norm landed at opposite bound extremes across rounds
(0.1 then 4.7), so no directional recommendation on that parameter should be
drawn from this exploration alone.
Explicit scope limits — do not generalize beyond these:
tiny_cionly. Nothing here has been run againstlarge_linux; its far larger parameter count and different compute profile mean these hyperparameters are not a starting point for it without their own search.- Synthetic dataset only (24 windows, one generator/seed family). Not validated against any real corpus at this scale.
- Self-signed, evaluation-only model activation (a throwaway local Ed25519 key, not a release signature) was used to run inference for scoring.
- No security/provenance/maintainer-approval gate has passed — the Darwin Mode promotion rule correctly refused to promote any candidate here.
Reproducer: harness/ruview/flywheel/ruforecast/ (genome, gate, evaluator,
dry-run/--confirm driver) in the ruvnet/RuView repo, paired with
ruvnet/metaharness PR #260 (evolve-numeric) linked locally via
npm link. Neither the genome defaults here nor any repo default config
were changed by this note — it is a record of exploratory evidence, not a
committed recommendation.
Amendment (2026-09-01, later same day): the round-2 result above did not generalize -- retracted
The "Search round 2" row above (WQL 0.153, learning_rate=0.0000298 etc.) is
RETRACTED as a claim of improvement. It is kept in the table (append-only,
never silently edit a prior measurement) but must be read together with this
amendment: independent verification, run the same day using a new
regression-candidate promotion path (ruvnet/autogenous PR-in-progress,
branch feat/regression-candidate-kind, not yet merged/pushed --
v2/crates/ruforecast-autogenous-bridge in this worktree) against TWO FRESH
synthetic corpora that were never part of that search (seeds 1000/1097, vs.
the search's single fixed seed 0), showed that "winner" genome performing
WORSE than the baseline on both:
| Judge corpus | Candidate WQL | Baseline WQL | Candidate beats baseline by |
|---|---|---|---|
| seed 1000 | 0.155 | 0.099 | -0.056 (worse) |
| seed 1097 | 0.219 | 0.109 | -0.110 (worse) |
Root cause: every evaluation in the three search rounds above (default,
round 1, round 2) trained and scored every candidate against the exact same
fixed synthetic corpus (prepare-synthetic-dataset's implicit --seed 0
default). The search had learned to exploit that one corpus's specific
random windows -- textbook overfitting -- not found a genuinely better
hyperparameter configuration. This is a real, measured failure mode, not a
hypothetical caveat.
Fix applied: harness/ruview/flywheel/ruforecast/gate.mjs's
evaluateGenome now trains and scores every candidate against THREE
independent synthetic corpora (DEFAULT_SEARCH_SEEDS = [11, 23, 47], none
of which overlap the retracted search's seed 0 or the verification seeds
1000/1097) and takes the WORST CASE primary across them, not an average --
a candidate only counts as a win if it beats both baselines on every corpus.
See harness/ruview/flywheel/ruforecast/README.md's "Multi-seed fitness"
section for the full account.
Re-running the search with the fix, still no verified improvement. A
fresh search under the corrected multi-seed fitness (2 generations x 2
children, cleared .metaharness-numeric archive so no stale pre-fix state
leaked in) found a genuine winner ON ITS OWN THREE SEARCH SEEDS --
learning_rate=0.008012, weight_decay=0.000159, gradient_clip_norm=0.267, batch_size=24, epochs=20, beating the baseline on all three (primary
0.099 / 0.556 / 0.576, worst-case 0.099). Independent verification against
the same fresh seeds 1000/1097 (never part of this candidate's own search)
again showed it losing to the baseline on both:
| Judge corpus | Candidate WQL | Baseline WQL | Candidate beats baseline by |
|---|---|---|---|
| seed 1000 | 0.178 | 0.099 | -0.079 (worse) |
| seed 1097 | 0.314 | 0.109 | -0.205 (worse) |
Honest conclusion: two independent search rounds (pre- and post- the
multi-seed fitness fix) both produced a genome that looked like a real
improvement on its own search data and both failed independent out-of-sample
verification. This converges on a different, deeper explanation than "the
search methodology was broken" (that part IS fixed): at this dataset scale
(24 synthetic training windows), held-out WQL varies enormously by which
corpus is drawn REGARDLESS of hyperparameters -- the baseline genome itself,
evaluated with the corrected multi-seed fitness function, swings from
primary 0.83 (a strong win) to a full regression (primary 0, WQL worse than
last-value) purely from which of the three fixed search seeds was used, with
identical hyperparameters throughout. The corpus-noise floor at n=24
windows appears to dominate any real hyperparameter effect. No RuForecast
hyperparameter configuration has been shown, by this exploration, to
reliably beat the trivial baselines out-of-sample at this scale. A larger
training corpus (more windows, ideally real governed data under the
large_linux profile) is the more promising next lever than further
hyperparameter search on this fixture.
A related implementation-only fix: the promotion verifier
(envelope::regression::verify_regression_promotion in the Autogenous
branch above) initially required all judges' receipts to share one
corpus_id, inherited unreviewed from a same-evidence review model that
does not fit this kind's intentionally cross-corpus judge design. This was
corrected (ReceiptCorpusMismatch is no longer produced by that function);
it did not change either REJECT verdict above, both of which were already
correctly driven by the real NotBetterThanParent signal.
Reproducer for both verification runs above: same as below, plus
v2/crates/ruforecast-autogenous-bridge (cargo +1.92.0 run --manifest-path crates/ruforecast-autogenous-bridge/Cargo.toml -- --ruforecast-bin ./target/debug/ruforecast --candidate-genome <genome>.json --parent-genome <baseline>.json --judges 2 --work-dir <scratch>) against the unpushed
ruvnet/autogenous branch feat/regression-candidate-kind.
Real-household-data result (2026-09-01, MEASURED, unaccepted, not a release claim)
Following the informal HPO exploration above, this session also collected
6,390 real 1 Hz vital-signs samples (heart rate, breathing rate,
signal quality) from a real, live, ESP32-sourced household sensing
deployment over a continuous 2-hour window (88.75% real sample coverage;
gaps handled honestly via observed_mask=0, never fabricated
interpolation) — the "more real training data" lever flagged as the
credible next step in the note above. This directly answers that open
question.
Two genuinely independent temporal splits of the same real corpus (not
synthetic seeds — real data has no seed to vary, so independence here
means two different train/test boundary choices on the same timeline,
each with its own 90s embargo gap) were trained (default, untuned
OptimizerSpec: lr=0.001, weight_decay=0.0001, gradient_clip_norm=1.0, batch_size=8, epochs=60) and scored via the real evaluate CLI, then
independently, cryptographically verified through
ruforecast-autogenous-bridge's real signed regression-candidate
promotion path (ruvnet/autogenous, envelope::regression):
| Judge | Split | Real test windows | Model WQL | Best trivial baseline WQL | Model beats baseline by |
|---|---|---|---|---|---|
| 1 | 70% train / 90s embargo / 30% test | 27 | 0.0514 | 0.0563 (last-value) | +0.0049 |
| 2 | 50% train / 90s embargo / 50% test | 46 | 0.0670 | 0.0543 (seasonal-naive) | −0.0128 |
Signed verdict: REJECT. Judge 1's nominal win (+0.0049) is below the
0.01 non-inferiority margin, so it doesn't clear the promotion bar even
on its own; Judge 2 lost outright. Both rejections are recorded as
NotBetterThanParent in the signed promotion envelope.
Honest conclusion: even with a real household corpus (n=6,390 real
samples, not synthetic), the result is exactly the same shape as every
synthetic search this session — a result that looks like a win on one
split does not hold up on an independently verified second split. This
is not evidence that real data can't help; it is evidence that this
scale of real data (6,390 samples, one household, one physical sensor)
is not yet enough to distinguish a genuine effect from split-dependent
noise. The credible next lever remains more real data — more households,
longer collection windows, or the large_linux profile — not further
hyperparameter search on any fixture this small, synthetic or real.
Reproducer: v2/crates/ruforecast-autogenous-bridge/examples/real_data_verify.rs
and v2/crates/ruforecast/crates/ruforecast-train/examples/real_data_windows.rs
in this worktree (train/ruforecast-rust branch, commits 130f547 in the
ruforecast submodule and 788401c5 in this repo — both local, not yet
pushed). Raw real vitals data never left the collecting/training hosts and
was never written to any git-tracked or pushed path.
Append-only evidence ledger
Never replace a prior measurement. Append a row and retain the failed or stale row when code, model, configuration, corpus, hardware, or methodology changes.
| Date | Commit | Lock SHA-256 | Host/toolchain | Backend/config | Shape | Samples | p50 | p95 | p99/max | Peak RSS | Evidence | Reproducer |
|---|
No rows have been accepted.
Real public dataset: BIDMC PPG/Respiration (2026-09-02, cross-entity holdout)
Every real-data test up to this point used a single household/entity with only a temporal holdout (same physical sensor, different time windows). This test is the first with a genuine cross-entity holdout: 53 real ICU patients from the BIDMC PPG and Respiration Dataset (PhysioNet, Open Data Commons Attribution License v1.0, public and openly licensed — no credentialing, https://physionet.org/content/bidmc/1.0.0/), splitting by patient, not by time, so the held-out test set contains real people the model never saw during training.
25,546 real 1 Hz rows across 53 recordings (~8 minutes each), heart rate +
respiratory rate + SpO2. 3 of 53 patients' windows were excluded honestly
(genuine sensor-dropout NaN values in the source recordings, not
fabricated/interpolated). Two independent, disjoint patient-partition splits:
| Judge | Train patients | Test patients | Test windows | Model WQL | Best baseline WQL | Result |
|---|---|---|---|---|---|---|
| A (contiguous split) | 34 | 16 | 16 | 0.01964 | 0.01159 (last-value) | worse, +69% |
| B (interleaved split) | 24 | 26 | 26 | 0.07022 | 0.01002 (last-value) | worse, +601% |
Same conclusion as every prior test this session, now on real, public, multi-subject clinical data with genuine cross-entity generalization: the model does not beat trivial forecasting baselines. The margin is decisive on both independent splits, not a near-miss — the earlier hypothesis that a larger, genuinely diverse real dataset (many different people, not one household) might change the picture does not hold at this scale/model configuration either.
Honest scope note on verification: prior real-data tests in this document
were independently checked through Autogenous's signed regression-candidate
promotion path. That additional cryptographic-signing step was not run
for this test — the result is reported directly from the evaluate CLI's
real output on two genuinely disjoint, real patient-holdout splits, which is
itself real, independent, out-of-sample evidence, but it does not carry a
signed promotion-gate verdict the way the earlier entries do. Flagging this
explicitly rather than presenting it with the same evidentiary weight.
Reproducer: v2/crates/ruforecast/crates/ruforecast-train/examples/bidmc_prepare.rs
(untracked scratch example, worktree train/ruforecast-rust) — downloads
bidmc_NN_Numerics.csv for patients 01-53 directly from PhysioNet, builds
one 76-row (context 64 + horizon 12) window per eligible patient, and writes
governed train.jsonl/test.jsonl/train-local.toml per judge split. Raw
data cached at /tmp/bidmc-raw/ on the training host only.
Zero-shot pretrained-checkpoint comparison (2026-09-02)
Every RuForecast result above this line is a from-scratch-trained model (RuForecast's own Burn/Rust architecture, trained on a tiny real or synthetic corpus) evaluated against trivial baselines. All of them lost. A literature pass after the BIDMC result above (arXiv:2505.11349, "context parroting"; arXiv:2607.20027, zero-shot HRV forecasting with TimesFM/Chronos/MOIRAI on wearables) converged on an explanation: published foundation-model wins over naive baselines on short-horizon, smooth physiological signals come from massive real pretraining corpora (Chronos: ~10M real+augmented series; TimesFM: ~100B real time points), not from architecture alone — and RuForecast's from-scratch training used a real corpus many orders of magnitude smaller (tens to low hundreds of windows), so re-implementing these architectures from scratch and training small was never going to replicate their wins.
This test checks the other half of that hypothesis directly: does a real, public, pretrained checkpoint, run zero-shot (no fine-tuning, no training of any kind on this repo's data) on the exact same BIDMC cross-entity test windows and metrics as the BIDMC result above, actually beat the naive baselines? If the massive-pretraining-corpus explanation is right, it should — unlike every from-scratch attempt this session.
Setup (kept identical to the BIDMC test above for a fair comparison):
same /tmp/bidmc-raw/ cache (53 real ICU patients, PhysioNet BIDMC, Open
Data Commons Attribution License v1.0), same window construction (context
64s + horizon 12s, 1 Hz, variates HR/RESP/SpO2, one window per patient from
that patient's first 76 rows), same two disjoint patient-partition judges (A:
train 1-37/test 38-53 contiguous; B: train odd/test even interleaved — train
patients are irrelevant here since nothing is trained, only the test
partition is used), same quantile set
[0.05,0.10,0.25,0.50,0.75,0.90,0.95], same seasonal period (12), and the
same WQL formula (2*sum(pinball)/(numQuantiles*sum|actual|)) and MAE
definition as ruforecast-core::metrics.rs. Baselines were independently
re-derived from ruforecast-core::baseline.rs's exact logic in Python and
cross-checked against the BIDMC section above: reproduced last-value WQL was
0.01171 (A) / 0.01013 (B) versus the Rust CLI's originally reported
0.01159 / 0.01002 — a ~1% gap attributable to f32 (Rust) vs f64 (Python)
accumulation, not a methodology difference, so this reproduction is treated
as validated.
Four real, public, pretrained-checkpoint models were run zero-shot on GPU
(RTX 5080, ruvultra), per-variate univariate (none of these checkpoints has
a native multivariate mode for these two families, so HR/RESP/SpO2 were
forecast independently and recombined into the same window-level WQL/MAE as
the baselines and the from-scratch RuForecast model):
- Chronos-Bolt base/small (
amazon/chronos-bolt-base,amazon/chronos-bolt-small) — direct quantile-regression heads, deterministic given input (no sampling). - Chronos (T5) small (
amazon/chronos-t5-small) — the original sampling-based Chronos family;predict_quantilesinternally draws samples (defaultnum_samples), so this one model's numbers below carry run-to-run sampling variance the other three don't. - TimesFM 2.5 (200M, PyTorch) (
google/timesfm-2.5-200m-pytorch) — quantile output uses 10 channels; per the installed package's source (timesfm_2p5_base.py,decode_index: int = 5) and empirical verification across multiple windows/variates, channel 5 is the point forecast and channels 1-9 are the native quantile levels [0.1..0.9] in order (channel 0 is an unused duplicate, not a real quantile). Levels 0.05 and 0.95 were clamped to the nearest native level (0.10/0.90); 0.25 and 0.75 were linearly interpolated between the adjacent native levels — the same class of approximation Chronos-Bolt's own pipeline applies internally when asked for quantile levels outside its trained range (a warning it printed for 0.05/0.95 on every call).
| Judge | Model | Test windows | MAE | WQL | WQL vs last-value |
|---|---|---|---|---|---|
| A | last-value (baseline) | 16 | 0.7882 | 0.01171 | — |
| A | seasonal-naive (baseline) | 16 | 1.0885 | 0.01617 | worse |
| A | Chronos-Bolt-base (zero-shot) | 16 | 0.7802 | 0.00756 | -35.4% |
| A | Chronos-Bolt-small (zero-shot) | 16 | 0.7212 | 0.00684 | -41.6% |
| A | Chronos-T5-small (zero-shot) | 16 | 0.9487 | 0.00955 | -18.4% |
| A | TimesFM-2.5-200M (zero-shot) | 16 | 0.7490 | 0.00731 | -37.6% |
| B | last-value (baseline) | 26 | 0.6944 | 0.01013 | — |
| B | seasonal-naive (baseline) | 26 | 0.8697 | 0.01269 | worse |
| B | Chronos-Bolt-base (zero-shot) | 26 | 0.7096 | 0.00719 | -29.0% |
| B | Chronos-Bolt-small (zero-shot) | 26 | 0.6852 | 0.00676 | -33.3% |
| B | Chronos-T5-small (zero-shot) | 26 | 0.8254 | 0.00886 | -12.5% |
| B | TimesFM-2.5-200M (zero-shot) | 26 | 0.6511 | 0.00698 | -31.1% |
Honest result: this is the first test all session where a model beats the trivial baselines, and it does so decisively and consistently — all four pretrained checkpoints beat both last-value and seasonal-naive on WQL, on both independently-partitioned judges, by double-digit-to-40% margins. This is a materially larger and more consistent margin than the closest published direct analog found in the literature pass (zero-shot HRV forecasting, arXiv:2607.20027: Chronos beat EWMA by ~3%, MOIRAI showed no significant advantage over a mean baseline, p=0.953) — plausibly because BIDMC's 1 Hz ICU HR/RESP/SpO2 signals are smoother and more autocorrelated than the HRV series in that paper, which is itself a fair, disclosed difference between the two comparisons rather than a claim of a stronger general result.
On MAE (point accuracy) the picture is more mixed: Chronos-Bolt-small and TimesFM beat last-value's MAE on both judges, but Chronos-Bolt-base and Chronos-T5-small do not consistently (e.g. Chronos-Bolt-base's MAE is essentially tied with last-value on judge A, worse on judge B, despite a much better WQL there) — the pretrained models' advantage is concentrated in calibrated quantile spread (what WQL scores), not in shifting the central point forecast much past what last-value already gets from strong autocorrelation in these signals.
This directly confirms the massive-pretraining-corpus hypothesis from the literature pass: the same short-horizon, smooth physiological-signal task on which every from-scratch RuForecast configuration this session lost to naive baselines is won, cleanly, by checkpoints that carry a pretraining corpus many orders of magnitude larger than any real dataset available to this project. This is evidence for zero-shot/foundation-model inference (or later, fine-tuning a pretrained checkpoint) as the credible next lever for RuForecast-style short-horizon vitals forecasting — not for continuing to scale from-scratch training on this project's own small real corpora, which the BIDMC and household tests above already showed does not close this gap even with genuine, cross-entity real data.
Scope and caveats, stated plainly:
- This is zero-shot inference only — no fine-tuning was attempted on any checkpoint. A fine-tuned comparison (e.g. LoRA-adapting Chronos-Bolt on RuForecast's own training windows) is a natural, not-yet-run follow-up.
- Univariate-per-variate forecasting was used for all four models because neither Chronos nor TimesFM in these releases has a native multivariate mode matching RuForecast's joint HR/RESP/SpO2 modeling; a true multivariate foundation model (e.g. MOIRAI, which does support cross-variate attention) was not tested here and is a natural next step.
- Chronos-T5-small's
predict_quantilessamples internally (unlike Chronos-Bolt's direct quantile heads or TimesFM's quantile head); its numbers above are one run and carry unquantified sampling variance the other three models' numbers don't. - No inference-cost/latency comparison against RuForecast's own (much smaller, ~tens-of-KB) model is included; all four pretrained checkpoints are orders of magnitude larger (small-to-200M+ parameters) and ran on a discrete GPU (RTX 5080), not the ESP32-class edge target RuForecast's own architecture is designed for. A model that wins on WQL here is not automatically deployable to the same edge budget RuForecast targets — that tradeoff is unresolved by this test and is itself a reason zero-shot inference (call out to a hosted/cloud checkpoint) and from-scratch small-model training remain different tools for different deployment constraints, not a strict replacement of one by the other.
Reproducer: scripts/ruforecast-zeroshot/bidmc_windows.py (window builder,
baselines, and metrics — ported line-for-line from
ruforecast-train/examples/bidmc_prepare.rs and
ruforecast-core/src/{baseline,metrics}.rs, verified against this document's
own BIDMC last-value/seasonal-naive numbers) and
scripts/ruforecast-zeroshot/run_zeroshot_eval.py (zero-shot inference and
scoring), both in this worktree (research/ruforecast-zeroshot-eval). Run
against /tmp/bidmc-raw/ (downloaded fresh from PhysioNet if not already
cached) with python run_zeroshot_eval.py --models chronos-bolt-base chronos-bolt-small chronos-t5-small timesfm-2.5-200m --judges a b inside a
venv with chronos-forecasting, timesfm, and a CUDA-enabled torch
installed (amazon/chronos-* and google/timesfm-2.5-200m-pytorch download
automatically from HuggingFace Hub on first run; no HF token was required).
Model weights and the raw BIDMC CSVs are not committed — /tmp/bidmc-raw/
and the HuggingFace cache live only on the host that ran this evaluation.