chore(release): publish timesfm + ruvector-timesfm 2.2.4 (#613)

timesfm gained quantization/f16/select_device/serde after 2.2.3 was published;
bump to 2.2.4 and publish so ruvector-timesfm (new crate, uses those APIs) can
depend on it. Adds ruvector-timesfm README. Only ruvector-timesfm depends on
timesfm, so the off-workspace-version pin is self-contained.

Co-authored-by: ruvnet <ruvnet@gmail.com>
This commit is contained in:
rUv 2026-06-27 11:06:41 -04:00 committed by GitHub
parent 1329db1f24
commit 48dbbb663c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 3 deletions

View file

@ -1,6 +1,7 @@
[package]
name = "ruvector-timesfm"
version.workspace = true
# Tracks timesfm 2.2.4 (the version carrying the APIs this crate uses).
version = "2.2.4"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
@ -9,6 +10,7 @@ repository.workspace = true
description = "RuVector integration for the TimesFM time-series foundation model: a high-level quantile Forecaster, forecast-band anomaly detection, and TimesFM-driven early-stopping for optimization sweeps (ADR-191)"
keywords = ["timesfm", "forecasting", "time-series", "anomaly", "ruvector"]
categories = ["science", "algorithms"]
readme = "README.md"
publish = true
[lib]
@ -39,7 +41,7 @@ cuda = ["candle", "timesfm/cuda", "candle-core/cuda", "candle-nn/cuda"]
metal = ["candle", "timesfm/metal", "candle-core/metal", "candle-nn/metal"]
[dependencies]
timesfm = { path = "../timesfm", version = "2.2.3" }
timesfm = { path = "../timesfm", version = "2.2.4" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }

View file

@ -0,0 +1,79 @@
# ruvector-timesfm
RuVector-facing integration for the [`timesfm`](https://crates.io/crates/timesfm)
TimesFM 1.0 200M time-series foundation model. The base crate is a faithful,
parity-validated candle port of the model; this crate wraps it in the things
RuVector and ruflo actually call.
[![Crates.io](https://img.shields.io/crates/v/ruvector-timesfm)](https://crates.io/crates/ruvector-timesfm)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
---
## What you get
| Module | Purpose |
|---|---|
| [`Forecaster`] | Load weights once, `forecast(series, horizon)` → point + calibrated **p10..p90 quantile bands**; `forecast_batch` for throughput. |
| [`anomaly`] | Forecast-band anomaly detection — flag observed points that fall outside their p10/p90 band (host/vector-db telemetry: disk-fill, GPU memory, query load). |
| [`sweep::EarlyStopper`] | TimesFM-driven early stopping for optimization sweeps (ADR-191) — kill doomed ruflo/Darwin runs early, with a `min_history` warm-up + confidence gate. |
| [`rebuild`] | Forecast an index's recall-drift curve and advise *when* to rebuild an HNSW index — just before the conservative (p10) forecast crosses a recall floor. |
| `ruvector-timesfm-forecast` | A JSON-in/JSON-out CLI = the `time_series_forecast` MCP tool entry point. |
## Feature gating
The numeric path is behind the **`candle`** feature (and `cuda`/`metal`, which
imply it), mirroring `timesfm`. Without it, only the plain data types compile, so
a stock `cargo build` stays light.
```toml
[dependencies]
ruvector-timesfm = { version = "2.2", features = ["candle"] }
```
## Quick start
```rust,ignore
use ruvector_timesfm::Forecaster;
// Pick CPU / cuda / metal via the TIMESFM_DEVICE env var.
let f = Forecaster::load("/path/timesfm.safetensors", timesfm::select_device()?)?;
let forecast = f.forecast(&history, 64)?; // 64-step forecast
let (lo, mid, hi) = (forecast.p10(), forecast.p50(), forecast.p90());
// Forecast-band anomaly detection on an observed window:
let report = f.detect_anomalies(&history, &observed, 0)?;
println!("{} anomalies", report.n_anomalies);
```
## Precision knobs
| API | Use | Tradeoff (measured, real weights) |
|---|---|---|
| `Forecaster::load` (f32) | default | reference accuracy; CPU ~45 ms, cuda ~4 ms / forecast |
| `Forecaster::load_f16` | GPU latency | ~1.6× faster batched on GPU; rel error ~2e-2 (CPU f16 is slower) |
| `Forecaster::load_quantized(Quant::Q8_0)` | edge memory | ~4× smaller (~212 MB), rel error ~3e-3; CPU slower (dequant) |
| `Quant::Q4_0` | tightest memory | ~7× smaller (~112 MB), rel error ~3e-2 |
## MCP tool
`ruvector-timesfm-forecast` reads a JSON request on stdin and writes a forecast
on stdout — the shell-out entry point for the RuVector `time_series_forecast`
MCP tool:
```bash
echo '{"weights":"/path/timesfm.safetensors","series":[...],"horizon":32}' \
| ruvector-timesfm-forecast
# → {"horizon":32,"device":"cpu","point":[...],"p10":[...],"p50":[...],"p90":[...]}
```
## Weights
Weights are not bundled — download `google/timesfm-1.0-200m` from HuggingFace and
convert with `timesfm`'s `scripts/convert_weights.py` (PyTorch state_dict →
candle safetensors). See the [`timesfm`](https://crates.io/crates/timesfm) crate.
## License
Apache-2.0.

View file

@ -1,6 +1,9 @@
[package]
name = "timesfm"
version.workspace = true
# Pinned ahead of the shared workspace version: this crate gained the
# quantization / f16 / select_device APIs after timesfm 2.2.3 was published.
# Only ruvector-timesfm depends on it (and tracks the same 2.2.4).
version = "2.2.4"
edition.workspace = true
rust-version.workspace = true
license.workspace = true