rfdetr-s-litter

The release record for the detector currently in the request path, rendered. Every field is read off the artifact, its sidecar, the exporter, or the deploy — not off a description of any of them.

§ 01 · Identity
Versionv2.0.1-fp32
Version grammarv<MAJOR>.<MINOR>.<PATCH>-<precision>, precision one of fp32 · fp16 · int8. Rejected at export if it does not match.
ArchitectureRF-DETR-Small (rfdetr 1.9.0), fine-tuned
Served keylitter-detector/v2.0.1-fp32/rfdetr-s-litter.onnx
Consumerstwo Lambdas — inference-on-upload and cleanup-diff — both pinned to that same key
Sidecar keyderived, not configured: the loader rewrites the .onnx suffix to .meta.json
Artifact size120,039,167 bytes (120.0 MB)
Exported2026-08-02T15:57:45Z
Export targetrfdetr-s-512-fp32 in export.precision.TARGETS — fp32 compute, fp32 IO, opset 17, 512×512, no size ceiling. It is the source row of the target matrix, not a conversion of anything.
Sibling targetsthree more descriptors over these same bytes: rfdetr-s-512-fp16 (what the exporter's default path writes — ORT refuses to load it), rfdetr-s-512-fp16-repaired, and rfdetr-s-512-int8-dynamic. Same source, same three calls, one dict entry apart.
Serving targetonnxruntime-node ^1.27.0 · cpu · aws-lambda-arm64-nodejs20
Registry record31486ca5ebe0 — v2.0.1-fp32, role: reference, measuredOnTarget: false

Precision is encoded in the version string and in the S3 prefix, and nowhere else. The serving contract has no precision field — see the pipeline page for why that is a gap rather than a detail. The two target rows above are also the two senses of the word in this codebase: what was built, and where it runs. The evidence on this record was gathered under Python onnxruntime 1.29.0, not under the runtime named on the serving row.

§ 02 · Exporter and precision mechanism
Exporterexport.export_rfdetr → RFDETRSmall.export(format="onnx", opset_version=17, shape=(512, 512), batch_size=1)
fp32 paththe graph as the torch exporter writes it. No post-pass. This is what serves.
fp16 mechanisma post-pass over the finished graph: onnxconverter_common.float16.convert_float_to_float16(model, keep_io_types=True)
int8refused by the exporter — "INT8 export requires a calibration pass; not wired for V2."
Compression at rest.onnx.gz written alongside, gzip level 9, mtime pinned to 0 so identical bytes produce identical archives

The two exporters do not mean the same thing by "fp16"

This repository has two exporters that both accept a -fp16 version string, and they produce artifacts with different IO boundaries by different mechanisms:

Exporter Mechanism Graph IO dtype
export_yolov8 precision suffix mapped to half=True and handed to the torch exporter — the cast happens during export float16 in, float16 out
export_rfdetr convert_float_to_float16 applied after export, with keep_io_types=True float32 in, float32 out

Both write fp16 into the version string. A consumer reading only the version string cannot tell whether it must feed float16 or float32, and the loader does not read the graph to find out. That is a real defect in the naming scheme, not a stylistic difference between two scripts — it is stated here because the card is where it would have to be caught.

§ 03 · Graph contract
ONNX opset17 (default domain only) · IR version 8
Inputinput · float32 · [1, 3, 512, 512] NCHW · batch fixed at 1
Preprocesssquash resize to 512×512 (no letterbox), /255, then per-channel ImageNet mean/std. Not baked into the graph — the consumer must apply it.
Output detsfloat32 · [1, 300, 4] · one row per query
Output labelsfloat32 · [1, 300, 44] · raw logits, no sigmoid in the graph
Coordinatescx, cy, w, h, normalized to [0, 1] of the model input. Because the resize is a squash and not a letterbox, those are also normalized to the original image — decode converts to x_min, y_min, x_max, y_max and clamps to [0, 1].
Decodeper-query argmax over the named columns → sigmoid of the winner → threshold → top-k 100. No NMS: DETR's set-based training already yields one query per object.

The 44th column

classCount is 43 and the logits tensor has 44 columns. RF-DETR builds its class head with one extra background column, so columns 0–42 are the canonical classes and column 43 is background. Decode iterates 0 <= c < 43 and ignores the trailing column; it raises if the channel count is neither classCount nor classCount + 1. That check catches an arity mismatch. It cannot catch a permutation, which is why the ordering is enforced upstream instead.

§ 04 · Class-order contract

Column i of the logits tensor must be canonical class i. Nothing at serving time can verify that — a permuted head produces confident, well-formed, silently mislabelled output — so it is enforced where the artifact is produced.

Canonical orderthe 43 leaf classes from the training data.yaml, alphabetical: alcohol.bottle … softdrinks.straw
Enforced atexport time. _check_class_alignment compares the checkpoint's training_config.json class_names against that list and raises on any difference, including a reorder: "Logit column i would not be canonical class i."
Published inthe sidecar's classes array, in that same order — which is what the backend uses to name a class index
Escape hatches--skip-class-check disables the comparison; and if training_config.json is absent next to the checkpoint the exporter logs a warning and proceeds on trust. Both are honest holes in an otherwise hard gate.
§ 05 · Sidecar — rfdetr-s-litter.meta.json

Fetched alongside the graph on cold start and parsed into the backend's ModelMeta. These are the fields the artifact actually carries:

Field Value Consumed by
versionv2.0.1-fp32session cache key — a changed version invalidates the warm session
modelNamerfdetr-s-litterartifact filename stem
architecturerfdetrroutes decode to the DETR path. Absent means yolo — every sidecar written before the field existed is a YOLO export.
inputShape[1, 3, 512, 512]preprocess target size
classes43 strings, canonical orderindex → label at decode
classCount43arity check against the logits tensor
normalizationmean .485/.456/.406
std .229/.224/.225
preprocess. Absent means ImageNet defaults.
confThresholdRecommended0.4the operating point — see § 06
iouThresholdRecommended0.45nothing. The DETR path runs no NMS. It is carried only so the sidecar satisfies the ModelMeta schema shared with the YOLO exporter.
trainingMetricsmap50 0.2153
map5095 0.1823
hardNegativeFpr 0.0244
hazardsMeanRecall 0.0
nothing at run time — provenance only
artifactSizeBytes120,039,167nothing at run time — provenance only
trainedAt / exportedAt2026-08-02nothing at run time — provenance only

There is no precision field. There is no field for a parity result, a fixture set, or a runtime the artifact was measured on. The right-hand column is deliberately blunt about which fields do nothing: four of twelve are provenance a human reads, one is inert, and that is worth seeing on the record rather than discovering during an incident.

§ 06 · Operating point
Threshold0.4 (sigmoid of the winning logit, per query)
Top-k100 detections after threshold
Upstream defaultRF-DETR's own predict() uses 0.5

0.4 is a deliberate move below the upstream default, and the reason is a property of the product rather than of the model: every suggestion this detector produces is surfaced to a contributor who confirms or rejects it before it becomes data. A missed detection costs more than an extra suggestion — the extra suggestion is one tap to dismiss, the miss is a piece of litter that never enters the record. So the threshold is biased toward recall.

DETR confidences are not comparable to YOLO's. There is no NMS score-shaping here, so a number that means "loose" on the YOLO path does not mean "loose" on this one. 0.4 is not transferable to a model with a different decode.

§ 07 · Evaluation

The numbers below are the ones published on /benchmarks, measured on a shared internal validation split (826 images / 1,004 boxes). They are reproduced rather than restated so the release record and the benchmark page cannot drift apart:

Metric v1 · YOLO11n v2 · RF-DETR-S (this artifact)
mAP@0.5, internal val (43 classes)0.0740.215 (~3×)
Hard-negative false-positive rate0.0490.024 (halved)
Hazard classes (needles, batteries) recall0.00.0 — unsolved, stated plainly
Training cost—8.06 h · one RTX 4070 · 1.613 kWh

Internal validation only. The 3× bundles an architecture change and a data change together, so it is a relative signal and not a headline accuracy claim.

§ 08 · Conversion parity

Status: this artifact is the reference. fp32 is what serves, and no converted artifact has been promoted past it. Two conversions have been built and measured against it; both were held. The measurement and the reasoning are in the build log: What a conversion gate is for.

All rows: 200 real photographs, ONNX Runtime 1.29.0, CPU execution provider, decode mirroring the backend exactly, at the sidecar's 0.4.

Artifact Size p50 vs fp32 Detections @0.4 Disagreements @0.4
fp32 (serving) 120.0 MB reference 202 —
fp16 63.1 MB 1.89× slower 203 19 threshold · 6 class · 10 of 200 images
int8 dynamic 36.0 MB 1.85× faster 202 — exactly equal 112 threshold (56 each way) · 19 class · 50 of 200 images

The int8 row is the one to read twice. It is 30% of the size, 1.85× faster, and returns exactly the same number of detections as the reference — and a quarter of the corpus comes back different. The counts match because 56 losses and 56 gains happen to be the same size, not because the artifacts agree. A release check that compared detection counts would have called that perfect parity.

What those flips cost, measured against human labels

Parity says the decisions changed. It does not say the model got worse, and that gap has since been closed from outside the harness: the same 200 fixtures labelled blind from the pixels — four reviewers, no model output, no boxes, per-image presence of a coarse 10-category rollup. Presence-level ground truth, not mAP, and not comparable to § 07.

Image-level litter recall @ 0.4 fp32 int8 dynamic
All 200 images — 156 contain litter, 44 are clean 52.6% (82/156) 53.2% (83/156)
Excluding non-scene images 52.9% 53.5%
Excluding non-scene and crew-bag images 46.9% (38/81) 50.6% (41/81)
High-confidence labels only 59.2% (61/103) 57.3% (59/103)
False positives on genuinely clean scenes 9.1% (4/44) 9.1% — the same four images

One to three images out of 81 to 156, pointing both ways depending on subset: on a set this size none of it is distinguishable from noise. So the 112 flips are instability, not degradation — an argument for putting a flip count in front of a person rather than wiring it to an automatic reject, and not an argument that int8 is better. The 202-against-202 result stands unchanged: a count-based check would still have reported perfect parity on a model whose decisions moved.

Reading those fixtures by hand also found that roughly six of the 200 are not photographs of scenes at all — video-game screenshots, a cartoon flyer, a marketing graphic, a phone screenshot of a web search. No parity harness can surface that: both artifacts agree happily on a screenshot. Status is unchanged by any of this. fp32 is still the reference, and nothing has been promoted.

§ 09 · Limitations
  • Hazard classes are at zero recall. Needles and batteries are in the 43-class space and the model does not find them. Nothing on this card should be read as hazard coverage.
  • Labels are weaker than boxes. An earlier blind grading of this detector's lineage found ~95% of boxes on real litter and ~36% carrying the right label, with the material composition inverted relative to ground truth. See what a litter detector actually sees.
  • Counting is not usable. Presence is. Quantity is not.
  • Presence recall is about half. Blind human labels over the 200 conversion fixtures put image-level litter recall at 52.6% — this artifact returns nothing at all on roughly half the images a person says contain litter. Per category it recovers bottles at 49%, bags_film_wrappers at 30% and paper at 2%. glass_broken appeared in none of the 200 images and smoking in one, so both are unmeasured, not good. Presence only — no boxes were drawn, so this is not a localization number and not comparable to the mAP above.
  • No Pi 5 number is published for this model. The edge benchmarks on /benchmarks are a stock COCO YOLOv8n proving the board can sustain inference; nothing on that page describes this artifact.
  • The class-order gate can be bypassed by --skip-class-check, and it degrades to a warning when the checkpoint ships without training_config.json.

What these numbers do not license

  • The eval numbers are internal validation only. They do not license a comparison against any published litter benchmark, and they are a relative signal between v1 and v2, not an accuracy claim.
  • The parity numbers describe these two artifacts, this runtime, this execution provider, this fixture set. They say nothing about a GPU, an accelerator, the arm64 Lambda host that actually serves, or a different input distribution.
  • Flips are still not accuracy — and the accuracy has now been measured. A query moving from 0.41 to 0.39 counts as a disagreement and might be the better answer. Blind presence-level labels on the fixture set say the 112 flips cost nothing measurable: 52.6% against 53.2% image-level recall, with an identical false-positive rate on clean scenes. That licenses "no measurable difference", not "int8 is better" — every gap is inside the noise of this fixture set.
  • The ground truth behind those recall numbers is one reviewer per image — 200 fixtures split into four independently-labelled shards — so there is no inter-rater agreement figure, and where a reviewer was unsure of a category that image was excluded from scoring rather than resolved either way.
  • The fixture set is 200 cleanup photographs from three subreddits. It describes Reddit, not the uploads this model sees in service.
  • Nothing here separates "int8 arithmetic is lossy for this architecture" from "this converter quantizes this graph badly". Those have different fixes.
  • Latency was measured on desktop CPU. The fp32 reference's own p50 wandered between 284 and 330 ms across runs — about 16% — so only within-run ratios are quoted.
§ 10 · Where this record sits

This page is the registry record for one artifact. The edges it travels — what each stage is allowed to change, and who owns the contract when it does — are drawn once on how a model ships here.