From 30ca47be669545c834ad14c1b4b129ea18f0af1f Mon Sep 17 00:00:00 2001 From: Mateo Date: Sat, 12 Sep 2026 13:08:52 +0200 Subject: [PATCH 1/3] feat(core): optional HF revision on the companion detector The detector HF repo may be tag-versioned with a moving default branch. Detector gains an optional revision passed to hf_hub_download so a declared tag is what gets fetched and hash-checked. --- core/src/temporal_model/core/detector.py | 4 +++- core/src/temporal_model/core/fetch_detector.py | 6 +++++- core/tests/test_fetch_detector.py | 10 ++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/core/src/temporal_model/core/detector.py b/core/src/temporal_model/core/detector.py index 1fcaa89..acb6ec4 100644 --- a/core/src/temporal_model/core/detector.py +++ b/core/src/temporal_model/core/detector.py @@ -30,10 +30,12 @@ class Detector(BaseModel): name: str source: str sha256: str + # HF git revision (tag) to download; None follows the repo's default branch. + revision: str | None = None @property def repo_id(self) -> str: - """The HF repo id, e.g. ``pyronear/yolo11s_nimble-narwhal_v6.0.0``.""" + """The HF repo id, e.g. ``pyronear/yolov11s``.""" if not self.source.startswith(_HF_PREFIX): raise ValueError( f"Unsupported detector source: {self.source!r} " diff --git a/core/src/temporal_model/core/fetch_detector.py b/core/src/temporal_model/core/fetch_detector.py index 02e743e..5791a5b 100644 --- a/core/src/temporal_model/core/fetch_detector.py +++ b/core/src/temporal_model/core/fetch_detector.py @@ -38,7 +38,11 @@ def fetch_detector(output_path: Path, detector: Detector | None = None) -> Path: """ det = detector or load_detector() downloaded = Path( - hf_hub_download(repo_id=det.repo_id, filename=DETECTOR_WEIGHTS_FILENAME) + hf_hub_download( + repo_id=det.repo_id, + filename=DETECTOR_WEIGHTS_FILENAME, + revision=det.revision, + ) ) actual = _sha256(downloaded) if actual != det.sha256: diff --git a/core/tests/test_fetch_detector.py b/core/tests/test_fetch_detector.py index 54d3d75..d133816 100644 --- a/core/tests/test_fetch_detector.py +++ b/core/tests/test_fetch_detector.py @@ -14,7 +14,10 @@ def _sha256_bytes(data: bytes) -> str: return hashlib.sha256(data).hexdigest() -def test_fetch_verifies_hash_and_writes_output(tmp_path: Path) -> None: +@pytest.mark.parametrize("revision", [None, "v8.2.0"]) +def test_fetch_verifies_hash_and_writes_output( + tmp_path: Path, revision: str | None +) -> None: weights = b"pretend-yolo-weights" src = tmp_path / "best.pt" src.write_bytes(weights) @@ -23,6 +26,7 @@ def test_fetch_verifies_hash_and_writes_output(tmp_path: Path) -> None: name="test-detector", source="hf:org/test-detector", sha256=_sha256_bytes(weights), + revision=revision, ) out = tmp_path / "yolo_weights.pt" @@ -32,7 +36,9 @@ def test_fetch_verifies_hash_and_writes_output(tmp_path: Path) -> None: ) as mock_dl: result = fetch_detector(out, det) - mock_dl.assert_called_once_with(repo_id="org/test-detector", filename="best.pt") + mock_dl.assert_called_once_with( + repo_id="org/test-detector", filename="best.pt", revision=revision + ) assert result == out assert out.read_bytes() == weights From 63f35db95c9e64b38e679fdb757c47db316e4356 Mon Sep 17 00:00:00 2001 From: Mateo Date: Sat, 12 Sep 2026 13:08:52 +0200 Subject: [PATCH 2/3] chore(core): bump companion detector to pyronear/yolov11s v8.2.0 Source hf:pyronear/yolov11s at tag v8.2.0 (release Swift Swallow), sha256 verified locally. Update the coupled dvc.yaml dep, train workflow pull path, docs and model card. --- .github/workflows/train.yml | 2 +- api/src/temporal_model/api/model_card.md | 2 +- core/src/temporal_model/core/detector.yaml | 7 +++--- core/tests/test_detector.py | 9 ++++---- docs/model-versioning.md | 27 ++++++++++++---------- train/dvc.yaml | 2 +- 6 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/workflows/train.yml b/.github/workflows/train.yml index 8f0f1de..9a3d287 100644 --- a/.github/workflows/train.yml +++ b/.github/workflows/train.yml @@ -101,7 +101,7 @@ jobs: uv run dvc pull \ data/01_raw/datasets_full/train.dvc \ data/01_raw/datasets_full/val.dvc \ - data/06_models/detectors/yolo11s_nimble-narwhal_v6.0.0/yolo_weights.pt.dvc + data/06_models/detectors/yolo11s_swift-swallow_v8.2.0/yolo_weights.pt.dvc uv run dvc repro --pull - name: Push train artifacts to DVC remote diff --git a/api/src/temporal_model/api/model_card.md b/api/src/temporal_model/api/model_card.md index 91dd4bb..fb1df4d 100644 --- a/api/src/temporal_model/api/model_card.md +++ b/api/src/temporal_model/api/model_card.md @@ -146,7 +146,7 @@ per-tube breakdown and decision config. Every `model.zip` manifest records how it was built — the training git SHA, the classifier backbone (`vit_small_patch14_dinov2.lvd142m`), and the exact companion -detector (e.g. `pyronear/yolo11s_nimble-narwhal_v6.0.0`, verified by SHA-256). So +detector (e.g. `pyronear/yolov11s` @ `v8.2.0`, verified by SHA-256). So a served model always traces back to its detector + training code. Source & pipeline: diff --git a/core/src/temporal_model/core/detector.yaml b/core/src/temporal_model/core/detector.yaml index e0f9b16..0f9fadc 100644 --- a/core/src/temporal_model/core/detector.yaml +++ b/core/src/temporal_model/core/detector.yaml @@ -4,6 +4,7 @@ # See docs/specs/2026-06-03-model-versioning-design.md. detector: type: yolo - name: yolo11s_nimble-narwhal_v6.0.0 - source: hf:pyronear/yolo11s_nimble-narwhal_v6.0.0 # downloads best.pt - sha256: 0bf3c7ee9f720c26613c30719fea32f47ed04fc384e443de72414d9f8148ac9d + name: yolo11s_swift-swallow_v8.2.0 + source: hf:pyronear/yolov11s # downloads best.pt + revision: v8.2.0 # HF tag; the repo's main branch moves + sha256: 20cbcae36898dc5a5f2700ad603bde4d3b3b67ed62e64239b7b0e3fe6869827d diff --git a/core/tests/test_detector.py b/core/tests/test_detector.py index 10dfda1..b38a4b3 100644 --- a/core/tests/test_detector.py +++ b/core/tests/test_detector.py @@ -9,16 +9,17 @@ def test_load_detector_returns_expected_identity() -> None: det = load_detector() assert det.type == "yolo" - assert det.name == "yolo11s_nimble-narwhal_v6.0.0" - assert det.source == "hf:pyronear/yolo11s_nimble-narwhal_v6.0.0" + assert det.name == "yolo11s_swift-swallow_v8.2.0" + assert det.source == "hf:pyronear/yolov11s" + assert det.revision == "v8.2.0" assert det.sha256 == ( - "0bf3c7ee9f720c26613c30719fea32f47ed04fc384e443de72414d9f8148ac9d" + "20cbcae36898dc5a5f2700ad603bde4d3b3b67ed62e64239b7b0e3fe6869827d" ) def test_repo_id_strips_hf_prefix() -> None: det = load_detector() - assert det.repo_id == "pyronear/yolo11s_nimble-narwhal_v6.0.0" + assert det.repo_id == "pyronear/yolov11s" def test_detector_is_frozen() -> None: diff --git a/docs/model-versioning.md b/docs/model-versioning.md index a8b4ff5..155458e 100644 --- a/docs/model-versioning.md +++ b/docs/model-versioning.md @@ -57,9 +57,10 @@ provenance: backbone: vit_small_patch14_dinov2.lvd142m detector: # copied verbatim from core/detector.yaml (§3) type: yolo - name: yolo11s_nimble-narwhal_v6.0.0 - source: hf:pyronear/yolo11s_nimble-narwhal_v6.0.0 - sha256: 0bf3c7ee9f720c26613c30719fea32f47ed04fc384e443de72414d9f8148ac9d + name: yolo11s_swift-swallow_v8.2.0 + source: hf:pyronear/yolov11s + revision: v8.2.0 + sha256: 20cbcae36898dc5a5f2700ad603bde4d3b3b67ed62e64239b7b0e3fe6869827d ``` All fields are **additive** — packages built before they existed still load, and @@ -88,9 +89,10 @@ by hash. ```yaml detector: type: yolo - name: yolo11s_nimble-narwhal_v6.0.0 - source: hf:pyronear/yolo11s_nimble-narwhal_v6.0.0 # downloads best.pt - sha256: 0bf3c7ee9f720c26613c30719fea32f47ed04fc384e443de72414d9f8148ac9d + name: yolo11s_swift-swallow_v8.2.0 + source: hf:pyronear/yolov11s # downloads best.pt + revision: v8.2.0 # HF tag; the repo's main branch moves + sha256: 20cbcae36898dc5a5f2700ad603bde4d3b3b67ed62e64239b7b0e3fe6869827d ``` This is the **only** place the detector is named. `core.detector.load_detector()` @@ -99,7 +101,8 @@ reads and validates it; packaging copies it verbatim into `provenance.detector`. **Bumping the detector = editing this one file** (a data change, reviewed as a one-line diff). `type` is generic so the schema survives a detector swap; `name` follows the pyronear HF convention (`__v`); `source` is the HF -repo id; `sha256` is the published weights hash and the tamper-evident anchor. +repo id; `revision` is the HF tag to download (the repo's `main` moves with each +release); `sha256` is the published weights hash and the tamper-evident anchor. ## 4. Fetching a (new) detector @@ -110,19 +113,19 @@ you choose. It fails loudly on any mismatch. ```bash cd core uv run python -m temporal_model.core.fetch_detector \ - --output ../train/data/06_models/detectors/yolo11s_nimble-narwhal_v6.0.0/yolo_weights.pt + --output ../train/data/06_models/detectors/yolo11s_swift-swallow_v8.2.0/yolo_weights.pt ``` Output: ``` -Fetched yolo11s_nimble-narwhal_v6.0.0 -> .../yolo_weights.pt (sha256 0bf3c7ee… verified) +Fetched yolo11s_swift-swallow_v8.2.0 -> .../yolo_weights.pt (sha256 20cbcae3… verified) ``` **To switch to a new detector:** -1. Edit `core/detector.yaml` — set `name`, `source`, and the new `sha256` (the - value published on the detector's HuggingFace model card). +1. Edit `core/detector.yaml` — set `name`, `source`, `revision`, and the new + `sha256` (the value published on the detector's HuggingFace model card). 2. Run `fetch_detector` (above) into a new `train/data/06_models/detectors//yolo_weights.pt`. 3. Track it with DVC (§5). @@ -198,7 +201,7 @@ sha256sum train/data/06_models/detectors//yolo_weights.pt ``` This is how the currently-served detector was confirmed to be -`yolo11s_nimble-narwhal_v6.0.0` (byte-identical to the HF release). +`yolo11s_swift-swallow_v8.2.0` (byte-identical to the HF release). ## 7. What's deferred diff --git a/train/dvc.yaml b/train/dvc.yaml index ff02bb4..4d7bb89 100644 --- a/train/dvc.yaml +++ b/train/dvc.yaml @@ -121,7 +121,7 @@ stages: - ../core/src/temporal_model/core/sequences.py - ../core/src/temporal_model/core/detector.yaml - data/06_models/vit_dinov2_finetune/best_checkpoint.pt - - data/06_models/detectors/yolo11s_nimble-narwhal_v6.0.0/yolo_weights.pt + - data/06_models/detectors/yolo11s_swift-swallow_v8.2.0/yolo_weights.pt - data/05_model_input/val - data/01_raw/datasets/train - data/01_raw/datasets/val From 9375009d55de51363bc26dceb8ecd50a664f4773 Mon Sep 17 00:00:00 2001 From: Mateo Date: Sat, 12 Sep 2026 13:08:52 +0200 Subject: [PATCH 3/3] chore(train): dvc-track detector yolo11s_swift-swallow_v8.2.0 --- .../yolo11s_swift-swallow_v8.2.0/yolo_weights.pt.dvc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 train/data/06_models/detectors/yolo11s_swift-swallow_v8.2.0/yolo_weights.pt.dvc diff --git a/train/data/06_models/detectors/yolo11s_swift-swallow_v8.2.0/yolo_weights.pt.dvc b/train/data/06_models/detectors/yolo11s_swift-swallow_v8.2.0/yolo_weights.pt.dvc new file mode 100644 index 0000000..55badd7 --- /dev/null +++ b/train/data/06_models/detectors/yolo11s_swift-swallow_v8.2.0/yolo_weights.pt.dvc @@ -0,0 +1,5 @@ +outs: +- md5: abae965ab4a3a761433ef043a1c2c8ee + size: 19225626 + hash: md5 + path: yolo_weights.pt