yolox_s — ExecuTorch
- Source: Megvii-BaseDetection/YOLOX (yolox_s)
- License: Apache-2.0
- Input: [[1, 3, 640, 640]] — BGR 0..255 float, NO normalization (YOLOX v0.3+ convention), 640x640 letterbox pad 114
- Output: [1,8400,85]: cx,cy,w,h (input px), objectness, 80 class scores; postprocess = obj*cls threshold + NMS (required)
Variants
All variants take and return fp32 tensors — swap the .pte file, keep your app code.
| build | file | size (MB) | parity vs fp32 eager (worst corr) | Mac median (ms)* |
|---|
| fp32 | yolox_s_xnnpack_fp32.pte | 35.9 | 1.000000 | 24.8 |
| Core ML (fp16, iOS) | yolox_s_coreml_all.pte | 18.5 | 0.999991 | 15.9 |
The Core ML build is the same graph lowered to Apple's Neural Engine instead of
XNNPACK, which is CPU-only. Measured on an iPhone 17 Pro across seven models, it
runs 3.5x to 13.9x faster (median 12x) at roughly half the file size — for
example Depth-Anything-V2-Small at 500.8 ms against 42.7 ms, and MODNet at 81.7 ms
against 5.9 ms. It computes in fp16 and is iOS-only; the XNNPACK files stay the
portable option and are what runs on Android.
*Mac arm64, single process, median of 10 — a reference point for relative cost
only, not a device number (torch eager fp32 on the same machine: 36.0 ms).
Checked in the task's own units
Correlation cannot judge a detector, and this model is the clearest case of it on the
shelf. The head is [1, 8400, 85] and almost all 8400 anchors are background that every
build agrees on trivially, so the correlation is dominated by the part nobody reads. The
withdrawn int8 build below reads 0.999800 and drops ten objects.
So both arms run the post-processing this card specifies — score = objectness x class,
threshold 0.30, per-class NMS at 0.65 — and the build has to reproduce eager's detection
set. Measured over 142 detections on 32 photographs of people and animals, held out
from the street images the int8 build was calibrated on:
| build | added | dropped | relabelled | worst matched IoU | worst score shift |
|---|
| fp32 | 0 | 0 | 0 | 1.000 | 0.0000 |
| Core ML | 1 | 2 | 0 | 0.832 | 0.0793 |
Core ML's three disagreements all sit within its own score error of the threshold —
at +0.001, +0.030 and +0.076 against a score shift of 0.0793. Those are detections the cut
is ambivalent about anyway; a photograph shifted by a pixel moves them in eager too. No
object more than a score error above the threshold changes.
Withdrawn: int8 (2026-08-27)
yolox_s_xnnpack_int8.pte was published and has been withdrawn. Measured the same way:
- adds 11, drops 10 and relabels 3 of 142 detections — 17% of the answer changes
- objects lost 0.24 and 0.21 above the threshold, not at the boundary
- boxes that survive move to IoU 0.689 against eager's
- worst score shift 0.3585, larger than the threshold itself is deep (0.30)
At 9.2 MB against fp32's 35.9 it was the small build, and it was cleared on a correlation
of 0.999800 — a number that, on this output shape, a build could earn while finding
nothing at all. The Core ML build is 18.5 MB, runs faster than either XNNPACK build, and
keeps the answers.
Builds that did not earn a slot
- fp16 is not shipped: it comes out at 101% of the fp32 file (36.1 MB vs 35.9 MB), so it buys nothing. XNNPACK serializes convolution weights as fp32 no matter what dtype the graph carries, so on a conv-heavy model fp16 saves no disk and only adds cast operations. Quantization, not fp16, is the reduction that can shrink a graph like this.
Verification (executorch 1.4.0, torch 2.13.0)
Parity is measured against the fp32 eager model on real image input; corr is
the correlation over all elements of each output tensor.
| output | shape | max_abs_diff | corr |
|---|
| 0 | [1, 8400, 85] | 4.013e-03 | 1.000000 |
XNNPACK delegate coverage (fp32): 88.2% (351/398 ops); ops left on the portable kernels: aten.view_copy.default x9, aten.slice_copy.Tensor x8, aten.arange.start_step x6, aten.expand_copy.default x6, aten.unsqueeze_copy.default x6, aten.cat.default x5, aten.full.default x3, dim_order_ops._to_dim_order_copy.default x2, aten.upsample_nearest2d.vec x2
Conversion
torch.export -> to_edge_transform_and_lower(partitioner) -> .pte
(conversion scripts:
executorch-models)