From c8146df7ef87316d40fe256b7de90a9c93091411 Mon Sep 17 00:00:00 2001 From: Tinnci <23432137+Tinnci@users.noreply.github.com> Date: Sun, 12 Jul 2026 13:30:33 +0800 Subject: [PATCH] Preserve LavaSR digital silence --- CHANGELOG.md | 9 ++++++ ROADMAP.md | 4 +-- docs/ACCELERATORS.md | 14 +++++++++ examples/colab_lavasr_validation.py | 3 ++ .../backends/lavasr_compat.py | 5 +++- src/audio_super_resolution/cli.py | 2 +- tests/test_cli.py | 21 +++++++++++++ tests/test_lavasr_compat.py | 30 +++++++++++++++++++ 8 files changed, 84 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3f955..50b26f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. This project follows semantic versioning once published to PyPI. +## Unreleased + +### Fixed + +- Preserved exact digital silence in `lavasr-compat` instead of passing it through the model and + producing deterministic low-level output texture. +- Made `audio-super-res eval matrix` return a non-zero exit status when any matrix run fails, so + remote evidence workflows cannot report success for failed stability checks. + ## 0.6.0 - 2026-07-12 ### Added diff --git a/ROADMAP.md b/ROADMAP.md index b503646..f2d4d85 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,7 +14,7 @@ evidence and a smaller number of well-supported paths. | Evaluation | Full-reference, no-reference, downstream, listening, matrix, report, and bundle workflows are implemented. | | Packaging | Source version is `0.6.0`; build, metadata, wheel, lint, type, and default test gates are available through Pixi. | -## Priority 0: Ship v0.6.0 Cleanly +## Completed: Ship v0.6.0 Cleanly The immediate goal is a release-quality package, not another model backend. @@ -24,7 +24,7 @@ Release-preparation status: - [x] Run the complete local release checklist in [docs/RELEASE.md](docs/RELEASE.md). - [x] Confirm CI on the merge commit and inspect the built wheel/sdist. - [x] Run a fresh installed-wheel CLI smoke test outside the source tree. -- [ ] Tag `v0.6.0`, publish the GitHub release, and verify the trusted-publishing workflow and PyPI +- [x] Tag `v0.6.0`, publish the GitHub release, and verify the trusted-publishing workflow and PyPI installation. Release scope deliberately excludes new heavyweight evaluators, new model weights, and a new diff --git a/docs/ACCELERATORS.md b/docs/ACCELERATORS.md index 4a30553..291253f 100644 --- a/docs/ACCELERATORS.md +++ b/docs/ACCELERATORS.md @@ -224,3 +224,17 @@ A fresh Colab Tesla T4 run was recorded on 2026-07-01 UTC at commit This is historical evidence for the eager CUDA path, not a permanent performance baseline. Repeat the workflow on the release commit before making new device/provider claims. + +A v0.6.0 Colab CLI run on 2026-07-12 validated the published tag at commit +`5e4241c5ed7399b29d04bbfe39f04fdfd9f100dc` on the same Tesla T4 environment: + +- real-weight download/verification and CUDA torch smoke passed; +- explicit CUDA device detection passed; +- four of five generated failure cases passed; +- exact digital silence exposed deterministic model output around `-75 dBFS` RMS and was correctly + reported as `silence_hallucination` by the eval harness; +- the follow-up exact-silence preservation fix passed all five cases on the same remote session, + with zero duration drift for the silence fixture. + +The downloaded evidence archive belongs under ignored `runs/` or the associated GitHub issue/PR, +not in the source distribution. diff --git a/examples/colab_lavasr_validation.py b/examples/colab_lavasr_validation.py index cfd9753..440d297 100644 --- a/examples/colab_lavasr_validation.py +++ b/examples/colab_lavasr_validation.py @@ -194,6 +194,9 @@ def main() -> int: ], cwd=REPO_DIR, ) + matrix_manifest = json.loads((matrix_dir / "matrix.json").read_text(encoding="utf-8")) + if not matrix_manifest.get("passed"): + raise RuntimeError("LavaSR failure-case matrix reported one or more failed runs") _write_summary(status="passed") except Exception as exc: error = f"{type(exc).__name__}: {exc}" diff --git a/src/audio_super_resolution/backends/lavasr_compat.py b/src/audio_super_resolution/backends/lavasr_compat.py index edc2238..811252e 100644 --- a/src/audio_super_resolution/backends/lavasr_compat.py +++ b/src/audio_super_resolution/backends/lavasr_compat.py @@ -117,10 +117,13 @@ def enhance(self, audio: np.ndarray, sample_rate: int, target_sample_rate: int) device = resolve_device(self.config.device, supported_devices=LAVASR_CAPABILITY.accelerators) _require_torch_runtime() + prepared_audio, was_mono = _prepare_lavasr_input(audio, sample_rate) + if not np.any(prepared_audio): + return _restore_lavasr_output(np.zeros_like(prepared_audio).T, was_mono=was_mono) + import torch model = self._load_model(resolved_weights, bundle_info, device=device) - prepared_audio, was_mono = _prepare_lavasr_input(audio, sample_rate) waveform = torch.from_numpy(prepared_audio.T.copy()).to(device=device, dtype=torch.float32) with torch.inference_mode(): diff --git a/src/audio_super_resolution/cli.py b/src/audio_super_resolution/cli.py index b922d24..88e7f41 100644 --- a/src/audio_super_resolution/cli.py +++ b/src/audio_super_resolution/cli.py @@ -602,7 +602,7 @@ def _run_eval_command(argv: list[str]) -> int: ), ) print(f"Wrote eval matrix {args.output_dir / 'matrix.json'} ({manifest['run_count']} run(s))") - return 0 + return 0 if manifest["passed"] else 1 if args.eval_command == "run": model_cache_dir = args.model_cache_dir if args.model_cache_dir is not None else default_model_cache_dir() manifest = run_eval_dataset( diff --git a/tests/test_cli.py b/tests/test_cli.py index 4fa1a4b..b050d29 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -423,6 +423,27 @@ def test_cli_eval_matrix(tmp_path: Path, capsys) -> None: assert {run["degrader"] for run in matrix["runs"]} == {"lowpass_4k", "mp3_32kbps"} +def test_cli_eval_matrix_returns_nonzero_when_a_run_fails(monkeypatch, tmp_path: Path, capsys) -> None: + monkeypatch.setattr( + "audio_super_resolution.cli.run_eval_matrix", + lambda **kwargs: {"passed": False, "run_count": 1}, + ) + + exit_code = main( + [ + "eval", + "matrix", + "--dataset", + str(tmp_path / "dataset"), + "--output-dir", + str(tmp_path / "matrix"), + ] + ) + + assert exit_code == 1 + assert "Wrote eval matrix" in capsys.readouterr().out + + def test_cli_eval_matrix_compare_report_bundle_and_validate_dataset(tmp_path: Path, capsys) -> None: evalset = tmp_path / "evalset" assert ( diff --git a/tests/test_lavasr_compat.py b/tests/test_lavasr_compat.py index 917000b..a9bb108 100644 --- a/tests/test_lavasr_compat.py +++ b/tests/test_lavasr_compat.py @@ -32,3 +32,33 @@ def test_lavasr_backend_rejects_unsupported_precision_before_weight_resolution(t with pytest.raises(ValueError, match="precision modes"): backend.enhance(np.zeros(100, dtype=np.float32), 16000, 48000) + + +def test_lavasr_backend_preserves_exact_digital_silence(monkeypatch, tmp_path) -> None: + resolved_weights = object() + bundle_info = object() + monkeypatch.setattr( + "audio_super_resolution.weight_store.resolve_weights_for_spec", + lambda *args, **kwargs: resolved_weights, + ) + monkeypatch.setattr( + "audio_super_resolution.backends.lavasr_validation.validate_lavasr_v2_weight_bundle", + lambda resolved: bundle_info, + ) + monkeypatch.setattr("audio_super_resolution.backends.lavasr_compat.resolve_runtime_provider", lambda *args: None) + monkeypatch.setattr("audio_super_resolution.backends.lavasr_compat.resolve_device", lambda *args, **kwargs: "cuda") + monkeypatch.setattr("audio_super_resolution.backends.lavasr_compat._require_torch_runtime", lambda: None) + + backend = LavaSRCompatBackend( + config=InferenceConfig( + device="cuda", + runtime_provider="torch-eager", + model_cache_dir=tmp_path / "models", + ) + ) + monkeypatch.setattr(backend, "_load_model", lambda *args, **kwargs: pytest.fail("model should not load")) + + enhanced = backend.enhance(np.zeros(100, dtype=np.float32), 16000, 48000) + + assert enhanced.shape == (300,) + assert np.count_nonzero(enhanced) == 0