Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_infer/README.md

5.1 KiB, 1 run

created by r1870400018:19767, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1# oxedyne_fe2o3_infer
2
3Convolutional inference on the CPU, in safe Rust, with no dependency beyond
4`oxedyne_fe2o3_core`.
5
6The crate carries the numerics for two small networks -- a face detector and a
7face embedder -- and the pieces around them: an ONNX subset loader, the
8anchor-free decode and suppression a detector needs, the similarity transform
9and warp an embedder's preprocessing needs, and a hundred and twenty-eight
10dimensional unit vector at the end that two faces can be compared with.
11
12It owns nothing. Weights arrive as `&[u8]`, images arrive as pixels, and where
13they came from and how the work is spread across cores are the caller's
14business.
15
16## Using it
17
18```rust
19use oxedyne_fe2o3_infer::prelude::*;
20
21let cpu = Cpu::detect();
22let det = res!(Detector::load(&detector_onnx));
23let emb = res!(Embedder::load(&embedder_onnx));
24
25// Fit the photograph into the detector's canvas.
26let img = res!(Image::new(&rgb, width, height, 3));
27let (canvas, lb) = res!(letterbox(&img, 640, 640));
28let view = res!(Image::new(&canvas, 640, 640, 3));
29
30// Detect, then embed each face out of the original photograph.
31for d in res!(det.detect(cpu, &view, &DetectorOptions::default())) {
32 let d = d.unletterbox(&lb);
33 let e = res!(emb.embed(cpu, &img, &d.landmarks));
34 // `cosine(&e, &other)` compares two faces.
35}
36```
37
38## The two networks
39
40Neither is in this repository. Weights are not source, and the pair comes to
41thirty-nine megabytes.
42
43| | Detector | Embedder |
44|---|---|---|
45| File | `face_detection_yunet_2023mar.onnx` | `face_recognition_sface_2021dec.onnx` |
46| Bytes | 232,589 | 38,696,353 |
47| SHA-256 | `8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4` | `0ba9fbfa01b5270c96627c4ef784da859931e02f04419c829e83484087c34e79` |
48| Licence | MIT | Apache-2.0 |
49| Parameters | 53,121 | 9,671,000 |
50| Output | boxes, scores, five landmarks | 128 dimensions |
51
52Both come from the OpenCV model zoo, over git-lfs media URLs:
53
54```
55https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx
56https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx
57```
58
59The plain `raw.githubusercontent.com` URL answers a git-lfs pointer, not the
60model. Each hash above is also the `oid` in that pointer, so fetching both and
61comparing is a provenance check as well as an integrity one.
62
63Redistributing either is permitted, with the licence text and notices carried
64along -- Apache-2.0 §4 for the embedder, the copyright notice for the detector.
65Note separately that both were trained on corpora whose own terms are
66research-only; the distributor's grant is what a licence question turns on, but
67the provenance is worth knowing.
68
69**Channel order differs between them and neither says so.** The detector was
70exported against blue-green-red input and the embedder against red-green-blue.
71Both entry points here take ordinary red-green-blue pixels and reorder for the
72network, so a caller never has to know, but anyone feeding the graph directly
73does.
74
75## Testing
76
77```bash
78CARGO_TARGET_DIR=~/.cache/cargo-targets/fe2o3_infer_target \
79 FE2O3_INFER_MODELS=/path/to/the/onnx/files \
80 cargo test --release -p oxedyne_fe2o3_infer
81```
82
83Without `FE2O3_INFER_MODELS` the tests that want a model report that they were
84skipped and pass. `tests/models.rs` carries a hundred and twenty-eight floats
85and seventy-two summary values recorded from an independent implementation
86(tract 0.23.4) on a fixed input, so the check is against something other than
87this crate's own answer.
88
89`tests/guard.rs` is a performance guard rather than a correctness one, and it
90is not optional. The register tile in the matrix kernel sits on a cliff:
91whether the accumulator lives in vector registers or spills to the stack is an
92all-or-nothing decision the code generator makes, adjacent tile heights differ
93by a factor of thirty-two, and a compiler upgrade can move the boundary without
94changing a line. The second guard catches `mul_add` reaching the path that has
95no fused multiply-add, where it becomes a library call and costs the same
96thirty times.
97
98## Measured
99
100AMD Ryzen 7 6800H, one core, stock `x86-64` target with no `-C target-cpu`.
101
102| | this crate | tract 0.23.4 |
103|---|---|---|
104| Detector, 640×640 | 37.4 ms | 99.4 ms |
105| Embedder, 112×112 | 16.8 ms | 46.7 ms |
106| Matrix kernel, weighted over the embedder's layers | 39.4 GMAC/s | 12.3 GMAC/s |
107| The same on the baseline path | 11.0 GMAC/s | -- |
108
109Agreement with tract over seventy-eight photographs: largest absolute
110difference in any detector head, 1.9 × 10⁻⁵; every decoded box identical to
111five decimal places of intersection over union. Over ninety-three faces: cosine
112similarity between the two embeddings 1.000000000, largest difference in any
113component of the unit vector 2.1 × 10⁻⁶.
114
115## Safety
116
117The crate is `#![deny(unsafe_code)]` with exactly one documented exception, in
118`kern::run`, where calling a `#[target_feature]` function from an unfeatured
119context requires the token even though the body is entirely safe. There are no
120raw pointers, no intrinsics and no hand-written assembly. There is no
121`unwrap()`, no `expect()`, and no `?`.