animap-gpu / models /README.md
bluman1's picture
Publish services/inference
4b98524 verified
|
Raw
History Blame Contribute Delete
8.5 kB
# Model artefacts
**Three capabilities have a model behind them.** `cattle_detection` and
`poultry_count` run a checksummed YOLOX-m ONNX graph (ADR 0017, ADR 0018), and
`cattle_identity` has a frozen DINOv2-small embedding backbone (ADR 0020) β€” a
backbone, not an identity model, and the capability stays `coming_soon` because
nothing has tested it on the same animal twice. The rest have nothing, and the
service says so rather than inventing a result.
```
models/
_artefacts/
yolox_m-0.1.1rc0.onnx the shipped detector, shared by two capabilities
dinov3_vits16-onnx-1.onnx frozen embeddings, locally exported
cattle_detection/
model_card.json
poultry_count/
model_card.json
cattle_identity/
model_card.json
alternates/
yolox_onnx/ Apache-2.0, kept working, never registered
model_card.json
yolox_s-0.1.1rc0.onnx
dinov2_embedding/ Apache-2.0 fallback; measurably worse, kept working
model_card.json
dinov2_small-onnx-1.onnx
megadescriptor/ CC-BY-NC-4.0. Installed to be measured, never served
model_card.json
megadescriptor_l_384-onnx-1.onnx
miewid/ no licence at all. Same: measured, never served
model_card.json
miewid_msv3-onnx-1.onnx
```
**Two of those alternates are weights Animap may not serve.** They are on disk
because a licence stopped being a reason to leave a model unmeasured β€” directive
Β§40.2 asked for MegaDescriptor against DINOv3 and the honest answer needed the
benchmark, not an argument about why it could not be run. Nothing about the terms
changed: `app/adapters/licences.py` still reports both as non-servable,
`registry.refused()` still names them, and they load only under
`ANIMAP_LICENCE_POLICY=record`, which logs every load at ERROR and keeps it.
Under the default `enforce` policy they are refused and their adapters report
unavailable.
An alternate keeps its artefact **inside its own directory**. `load_card` refuses
a path that climbs out of the card's grandparent, which is what stops a card
being a way to load whatever is on the box.
Weights are not in git. Cards are β€” a governance record nobody can see in a diff
is not one.
## Installing them
```bash
.venv/bin/python scripts/install_models.py # fetch what the cards name
.venv/bin/python scripts/install_models.py --check # verify, never fetch
```
**Some artefacts have no URL to fetch.** An ONNX export of a published
checkpoint is not a file the publisher hosts, so its card carries `produced_by`
naming the script that makes it, and `install_models.py` refuses to download
rather than writing a model-card web page into a `.onnx` file:
```bash
.venv/bin/pip install -r requirements-export.txt # build-time only, never shipped
.venv/bin/python scripts/export_embedding.py
```
The export is checked against torch before it is written and is
byte-reproducible, so the sha256 on a card pins the exact bytes that were
validated. **It is reproducible from the same exporter code, not from the
weights alone** β€” renaming a wrapper attribute changes the graph's node names and
therefore the checksum, without changing a single weight.
The script reads the committed cards, downloads what each one names, and refuses
anything whose sha256 does not match. It has no list of its own, so there is
nothing to drift out of date. **The service never calls it.** A model that is not
on disk leaves its capability unavailable.
## Permissive licences only
**A card naming AGPL-3.0 is refused at load, not warned about.** `discover()`
skips it and the capability stays unavailable.
**And a card is not trusted about its own licence.** `app/adapters/licences.py`
holds what each *runtime* really loads under, read from a primary source and
dated, and `discover()` refuses a card whose declared licence disagrees with it.
That closes the exploit ADR 0017 records β€” `"license": "Apache-2.0"` written over
a path to copyleft weights β€” without depending on somebody having hard-coded the
right runtime name. It also catches the version of the trap that arrives from
upstream: `BVRA/MegaDescriptor-L-384/config.json` declares `"license": "mit"`
inside the checkpoint config while the repository is CC-BY-NC-4.0. Ultralytics YOLO was the shipped
detector until ADR 0017 and is now not installed at all; read
[`docs/adr/0017-ultralytics-licence.md`](../../../docs/adr/0017-ultralytics-licence.md)
before reaching for it again.
Four mechanisms hold that position, because it is the failure that is cheap now
and expensive later: `requirements.txt` omits the package, `.dockerignore`
excludes `*.pt`, the `Dockerfile` fails the build if one arrives anyway, and
`providers.discover()` refuses the card. `GET /health` reports
`artefact_licenses`, so a deployment that picked one up says so from outside.
## Adding one
Create a directory named for the capability, containing a `model_card.json`.
Every field below is required; a card missing any of them is refused at load, and
the capability stays unavailable:
```json
{
"model_id": "animap-cattle-weight",
"version": "0.1.0",
"capability_key": "cattle_weight",
"runtime": "ultralytics",
"artefact": "../_artefacts/model.pt",
"source": "https://… where it came from, exactly",
"license": "the licence, and it must permit this use",
"sha256": "checksum of the artefact file",
"validation_notes": "What was tested, on what data, with what result.",
"geography_validated": ["NG"]
}
```
### Why each field is enforced
**`source` and `license`** β€” a model of unknown origin cannot be shipped, and a
licence that forbids commercial use is a legal problem discovered too late.
**`sha256`** β€” verified on every load. The artefact on disk must be the one that
was validated, not one swapped in afterwards.
**`validation_notes`** β€” must be non-empty, and whitespace does not count. This
is the field that says somebody checked the model works. A card can otherwise be
filled in purely to get past the loader.
**`geography_validated`** β€” a body-condition model trained on European dairy
cattle is not validated for White Fulani in Kaduna. Recording where it was tested
is what stops that being assumed. The sentinel `global-coco` means "validated
nowhere, only pretrained", and `Capability.may_be_enabled` refuses to promote a
capability past `beta` while it is present.
**`runtime`** β€” not required by governance, because governance is about
provenance. But a card without it can be inspected and not run: `detectors.build`
refuses rather than defaulting, since a default would let a typo change which
licensed model produced a farmer's result.
### Where the artefact may live
The `artefact` path may point sideways β€” several capabilities share one detector,
and storing the file twice would mean two things to keep in step. It may not
point out of the models tree; `load_card` refuses that. A card under
`alternates/` is two levels down and outside the discovery glob, so an alternate
can never silently become the model that answered a request.
## What is not allowed
- **No runtime downloads.** The service never fetches a model. An artefact
arrives through a reviewed change, or it does not arrive.
- **No binaries in git.** `.gitignore` excludes `*.tflite`, `*.onnx`, `*.pt` and
`*.pth`, and everything in this directory except the READMEs and the cards.
- **No placeholder adapters.** A capability with a validated artefact but no
implemented adapter returns `501`, not a plausible-looking result. An adapter
that returns something believable is precisely the failure ADR 0005 exists to
prevent.
## Promoting a capability
The path from nothing to trusted, per ADR 0006:
1. Register the capability β€” already done for all 21.
2. Install a validated artefact here.
3. Implement its adapter and check the output against this contract.
4. Move `coming_soon` β†’ `beta`. The app shows a Beta badge; results are stored
with their confidence, and nothing acts on them automatically.
5. Collect corrections. `corrections.selected_interpretation_id` captures when
the model's second-ranked candidate was the right one, which is the highest
value training signal the schema produces.
6. Promote to `enabled` when the corrections say it earns it.
Step 6 is a decision about evidence, not a configuration change β€” and today it is
also blocked mechanically, because both beta capabilities carry the
`global-coco` sentinel.