Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/train.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion api/src/temporal_model/api/model_card.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <https://github.com/pyronear/temporal-model>
4 changes: 3 additions & 1 deletion core/src/temporal_model/core/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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} "
Expand Down
7 changes: 4 additions & 3 deletions core/src/temporal_model/core/detector.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 5 additions & 1 deletion core/src/temporal_model/core/fetch_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 5 additions & 4 deletions core/tests/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 8 additions & 2 deletions core/tests/test_fetch_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"

Expand All @@ -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

Expand Down
27 changes: 15 additions & 12 deletions docs/model-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()`
Expand All @@ -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 (`<arch>_<codename>_v<ver>`); `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

Expand All @@ -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/<new-name>/yolo_weights.pt`.
3. Track it with DVC (§5).
Expand Down Expand Up @@ -198,7 +201,7 @@ sha256sum train/data/06_models/detectors/<name>/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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
outs:
- md5: abae965ab4a3a761433ef043a1c2c8ee
size: 19225626
hash: md5
path: yolo_weights.pt
2 changes: 1 addition & 1 deletion train/dvc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading