mirror of
https://github.com/ruvnet/RuView
synced 2026-06-09 10:13:17 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cfd255730 | |||
| f5d0e1e69e |
+2
-2
@@ -1048,7 +1048,7 @@ The Rust sensing server binary accepts the following flags:
|
||||
| `--dataset` | (none) | Path to dataset directory (MM-Fi or Wi-Pose) |
|
||||
| `--dataset-type` | `mmfi` | Dataset format: `mmfi` or `wipose` |
|
||||
| `--epochs` | `100` | Training epochs |
|
||||
| `--export-rvf` | (none) | Export RVF model container and exit |
|
||||
| `--export-rvf` | (none) | Export a **placeholder** RVF container-format demo and exit — **not a trained model**. For a real model use `--train` (+ `--save-rvf`) or download a pretrained encoder. |
|
||||
| `--save-rvf` | (none) | Save model state to RVF on shutdown |
|
||||
| `--model` | (none) | Load a trained `.rvf` model for inference |
|
||||
| `--load-rvf` | (none) | Load model config from RVF container |
|
||||
@@ -1359,7 +1359,7 @@ docker run --rm \
|
||||
-v $(pwd)/output:/output \
|
||||
--entrypoint /app/sensing-server \
|
||||
ruvnet/wifi-densepose:latest \
|
||||
--train --dataset /data --epochs 100 --export-rvf /output/model.rvf
|
||||
--train --dataset /data --epochs 100 --save-rvf /output/model.rvf
|
||||
```
|
||||
|
||||
The pipeline runs 10 phases:
|
||||
|
||||
@@ -5570,6 +5570,65 @@ fn vitals_snapshots_from_sensing_json(
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn a `ProgressiveLoader::new` failure into an actionable diagnostic (#894).
|
||||
///
|
||||
/// The published HuggingFace `ruvnet/wifi-densepose-pretrained` files
|
||||
/// (`model.safetensors`, `model-q{2,4,8}.bin`, `model.rvf.jsonl`) are a
|
||||
/// different *format* — and a different encoder architecture — than the RVF
|
||||
/// binary container the `--model` progressive loader expects (`RVFS` magic
|
||||
/// `0x52564653`). Feeding one to `--model` produced a bare
|
||||
/// "invalid magic at offset 0 …" that left users stuck. Detect the common
|
||||
/// cases and explain plainly what's loadable instead.
|
||||
fn diagnose_model_load_error(path: &std::path::Path, data: &[u8], err: &str) -> String {
|
||||
let name = path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
let ext = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
|
||||
// safetensors: 8-byte LE header length, then a JSON object starting with '{'.
|
||||
let looks_safetensors = ext == "safetensors" || (data.len() > 9 && data[8] == b'{');
|
||||
// JSONL manifest: starts with '{' (or the well-known suffix).
|
||||
let looks_jsonl =
|
||||
ext == "jsonl" || name.ends_with(".rvf.jsonl") || data.first() == Some(&b'{');
|
||||
// Quantized weight blob shipped on HF (model-q2/q4/q8.bin).
|
||||
let looks_quant_bin = ext == "bin" || name.contains("-q");
|
||||
|
||||
let kind = if looks_safetensors {
|
||||
"a safetensors weight file"
|
||||
} else if looks_jsonl {
|
||||
"a JSONL manifest, not the binary container"
|
||||
} else if looks_quant_bin {
|
||||
"a quantized weight blob (e.g. HuggingFace model-q4.bin)"
|
||||
} else {
|
||||
"not an RVF binary container"
|
||||
};
|
||||
|
||||
format!(
|
||||
"model `{}` could not be loaded: it is {kind}. The --model flag expects an \
|
||||
RVF binary container (`RVFS` magic 0x52564653) produced by the \
|
||||
wifi-densepose-train pipeline. The HuggingFace ruvnet/wifi-densepose-pretrained \
|
||||
files are a different format and encoder architecture, so they do not load \
|
||||
here directly (issue #894). Continuing with signal heuristics. (loader: {err})",
|
||||
path.display()
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether `--export-rvf` should emit the placeholder container-format demo.
|
||||
///
|
||||
/// It must only do so **standalone**. Combined with `--train`/`--pretrain` the
|
||||
/// real model is produced by the training pipeline, so short-circuiting here
|
||||
/// would silently skip training and write placeholder weights — the #894 bug
|
||||
/// where the documented `--train … --export-rvf` workflow produced a fake model.
|
||||
fn export_emits_placeholder_demo(export_set: bool, train: bool, pretrain: bool) -> bool {
|
||||
export_set && !train && !pretrain
|
||||
}
|
||||
|
||||
// ── Main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// If `--ui-path` points nowhere (wrong cwd), try common repo layouts relative to cwd.
|
||||
@@ -5613,9 +5672,24 @@ async fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle --export-rvf mode: build an RVF container package and exit
|
||||
if let Some(ref rvf_path) = args.export_rvf {
|
||||
eprintln!("Exporting RVF container package...");
|
||||
// Handle --export-rvf: writes a CONTAINER-FORMAT DEMO with placeholder
|
||||
// weights — it is NOT a trained model. Only short-circuit when standalone:
|
||||
// combined with --train/--pretrain the real model is exported by the
|
||||
// training pipeline, and short-circuiting here would silently skip training
|
||||
// and write placeholder weights (#894 — the documented `--train …
|
||||
// --export-rvf` workflow produced a placeholder and never trained).
|
||||
if export_emits_placeholder_demo(args.export_rvf.is_some(), args.train, args.pretrain) {
|
||||
let rvf_path = args
|
||||
.export_rvf
|
||||
.as_ref()
|
||||
.expect("export_emits_placeholder_demo implies export_rvf is set");
|
||||
eprintln!(
|
||||
"WARNING: --export-rvf writes a CONTAINER-FORMAT DEMO with placeholder \
|
||||
weights — it is NOT a trained model. Train one with \
|
||||
`--train --dataset <DIR>` (which exports a calibrated .rvf to the \
|
||||
models/ directory), or download a pretrained encoder. See issue #894."
|
||||
);
|
||||
eprintln!("Exporting RVF container package (placeholder weights)...");
|
||||
use rvf_pipeline::RvfModelBuilder;
|
||||
|
||||
let mut builder = RvfModelBuilder::new("wifi-densepose", "1.0.0");
|
||||
@@ -5664,6 +5738,13 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if args.export_rvf.is_some() {
|
||||
// --export-rvf alongside --train/--pretrain: don't emit a placeholder.
|
||||
// Fall through so training runs; it exports the real calibrated model.
|
||||
eprintln!(
|
||||
"Note: --export-rvf is ignored in training mode — the trained model \
|
||||
is exported by the training pipeline to the models/ directory."
|
||||
);
|
||||
}
|
||||
|
||||
// Handle --pretrain mode: self-supervised contrastive pretraining (ADR-024)
|
||||
@@ -6207,7 +6288,9 @@ async fn main() {
|
||||
model_loaded = true;
|
||||
progressive_loader = Some(loader);
|
||||
}
|
||||
Err(e) => error!("Progressive loader init failed: {e}"),
|
||||
Err(e) => {
|
||||
error!("{}", diagnose_model_load_error(mp, &data, &e.to_string()))
|
||||
}
|
||||
},
|
||||
Err(e) => error!("Failed to read model file: {e}"),
|
||||
}
|
||||
@@ -7216,3 +7299,72 @@ mod mqtt_bridge_tests {
|
||||
assert!(!snaps[0].presence);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod model_load_diagnostic_tests {
|
||||
use super::diagnose_model_load_error;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn safetensors_is_named_and_points_at_894() {
|
||||
// 8-byte LE header length then '{' — the safetensors signature.
|
||||
let data = [0x10, 0, 0, 0, 0, 0, 0, 0, b'{', b'"'];
|
||||
let msg = diagnose_model_load_error(
|
||||
Path::new("models/wifi-densepose-pretrained/model.safetensors"),
|
||||
&data,
|
||||
"invalid magic at offset 0",
|
||||
);
|
||||
assert!(msg.contains("safetensors"), "{msg}");
|
||||
assert!(msg.contains("#894"), "{msg}");
|
||||
assert!(msg.contains("signal heuristics"), "{msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quantized_bin_is_identified() {
|
||||
let data = [0x35, 0x57, 0x45, 0x77]; // the 0x77455735 the loader reports
|
||||
let msg = diagnose_model_load_error(Path::new("model-q4.bin"), &data, "bad magic");
|
||||
assert!(msg.contains("quantized weight blob"), "{msg}");
|
||||
assert!(msg.contains("RVFS") || msg.contains("0x52564653"), "{msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jsonl_manifest_is_identified() {
|
||||
let data = *b"{\"seg\":0}";
|
||||
let msg = diagnose_model_load_error(Path::new("model.rvf.jsonl"), &data, "x");
|
||||
assert!(msg.contains("JSONL manifest"), "{msg}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_format_still_gives_guidance() {
|
||||
let data = [0u8, 1, 2, 3];
|
||||
let msg = diagnose_model_load_error(Path::new("weird.dat"), &data, "x");
|
||||
assert!(msg.contains("RVF binary container"), "{msg}");
|
||||
assert!(msg.contains("wifi-densepose-train"), "{msg}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod export_rvf_mode_tests {
|
||||
use super::export_emits_placeholder_demo;
|
||||
|
||||
#[test]
|
||||
fn standalone_export_emits_placeholder() {
|
||||
// --export-rvf alone → the container-format demo (placeholder weights).
|
||||
assert!(export_emits_placeholder_demo(true, false, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_with_train_does_not_short_circuit() {
|
||||
// #894: `--train --export-rvf` must NOT emit a placeholder + skip
|
||||
// training — it must fall through to the real training pipeline.
|
||||
assert!(!export_emits_placeholder_demo(true, true, false));
|
||||
assert!(!export_emits_placeholder_demo(true, false, true));
|
||||
assert!(!export_emits_placeholder_demo(true, true, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_export_flag_never_emits() {
|
||||
assert!(!export_emits_placeholder_demo(false, false, false));
|
||||
assert!(!export_emits_placeholder_demo(false, true, false));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user