From 0cfd255730a83ec97dcd0124b69962e5a8c25af7 Mon Sep 17 00:00:00 2001 From: rUv Date: Wed, 3 Jun 2026 08:55:36 +0200 Subject: [PATCH] fix: --export-rvf no longer silently produces a placeholder model (#920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --export-rvf handler ran *before* the --train/--pretrain handlers and unconditionally wrote placeholder sine-wave weights, then returned. So the documented `--train --dataset … --export-rvf ` workflow (user-guide.md) short-circuited to a PLACEHOLDER model and never trained — printing "exported successfully" for a non-functional model. Given the project's anti-"is it fake" stance, silently emitting a fake model is the wrong default. Fix: - Only emit the placeholder container-format demo when --export-rvf is used *standalone* (new `export_emits_placeholder_demo` guard). With --train/--pretrain, fall through so the real training pipeline runs and exports calibrated weights. - The standalone path now prints a clear WARNING that it writes a container-format demo with placeholder weights — not a trained model — pointing to --train / a pretrained encoder (#894). - Docs: flag --export-rvf as a placeholder demo in the flag table, and fix the Docker training example to use --save-rvf (consistent with the from-source example) instead of the placeholder --export-rvf. 3 unit tests for the guard. Full crate unit suite: 429 + 117 passed, 0 failed. --- docs/user-guide.md | 4 +- .../wifi-densepose-sensing-server/src/main.rs | 64 ++++++++++++++++++- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/docs/user-guide.md b/docs/user-guide.md index 5bf2e0cb..1e149d24 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -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: diff --git a/v2/crates/wifi-densepose-sensing-server/src/main.rs b/v2/crates/wifi-densepose-sensing-server/src/main.rs index eaebdbdf..cec524c5 100644 --- a/v2/crates/wifi-densepose-sensing-server/src/main.rs +++ b/v2/crates/wifi-densepose-sensing-server/src/main.rs @@ -5619,6 +5619,16 @@ fn diagnose_model_load_error(path: &std::path::Path, data: &[u8], err: &str) -> ) } +/// 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. @@ -5662,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 ` (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"); @@ -5713,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) @@ -7310,3 +7342,29 @@ mod model_load_diagnostic_tests { 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)); + } +}