mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-21 22:22:33 -05:00
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.
535 lines
30 KiB
Markdown
535 lines
30 KiB
Markdown
# 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:
|
||
|
||
1. 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.
|
||
2. 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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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`:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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:
|
||
|
||
1. Last-value and seasonal-naive baselines.
|
||
2. RuForecast without RuVector retrieval.
|
||
3. 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_ci` only. Nothing here has been run against `large_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_quantiles` internally draws
|
||
samples (default `num_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_quantiles` samples 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.
|