File size: 6,916 Bytes
5b63d11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7778e87
cd5d04d
 
fe4d46b
 
 
 
 
 
 
 
 
cd5d04d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2db2408
cd5d04d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7778e87
 
5b63d11
 
 
 
7453022
5b63d11
7453022
5b63d11
 
7453022
5b63d11
7453022
 
 
 
5b63d11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ed6139
 
 
 
 
 
 
 
 
 
7e09902
 
 
 
 
 
 
 
 
 
 
 
5b63d11
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
---
license: apache-2.0
base_model: roboflow/rf-detr
tags:
- coreai
- aimodel
- object-detection
- rf-detr
- detr
- apple
- ios
- macos
pipeline_tag: object-detection
---

# RF-DETR β€” Core AI (`.aimodel`)

[RF-DETR](https://github.com/roboflow/rf-detr) (Roboflow's real-time detection
transformer, COCO-pretrained) converted to Apple **Core AI** for iOS 27 / macOS 27 β€”
the answer to [apple/coreai-models#14](https://github.com/apple/coreai-models/issues/14).
**DETR family = no NMS**: post-processing is one sigmoid + top-k.

<p align="center"><img src="demo_coco_cats.jpg" width="440" alt="RF-DETR medium on Core AI"></p>

<!-- gen-cards:use-it begin id=rf-detr (managed by scripts/gen-cards β€” edit cards.json / QuickStart.swift, not this block) -->
## Use it

⚑ **One line** β€” this model is the default behind the kit's task op
(`import CoreAIOps`; no session, no model plumbing, downloads on first use):

```swift
let boxes = try await CoreAI.detect(inImageAt: url)
```

Twenty ops, one shape β€” [Cookbook](https://github.com/john-rocky/coreai-kit/blob/main/docs/COOKBOOK.md).

▢️ **Run it (source)** β€” the [DetectCamera runner](https://github.com/john-rocky/coreai-kit/tree/main/Examples/DetectCamera)
(real-time object detection on the zero-copy camera path):

```bash
git clone https://github.com/john-rocky/coreai-kit
open coreai-kit/Examples/DetectCamera/DetectCamera.xcodeproj
# β†’ Run, then pick "Nano" in the model picker

# agents / headless (macOS):
cd coreai-kit/Examples/DetectCamera
swift run detect-cli --model rf-detr --image Resources/gate_image.jpg
```

πŸ’» **Build with it** β€” complete; the glue is kit API, copy-paste runs:

```swift
import CoreAIKitVision

let detector = try await KitDetector(catalog: "rf-detr")
let image = try ImageFile.load(imageURL)  // any image file β†’ CGImage + EXIF orientation
let detections = try await detector.detect(in: image.cgImage)
// detections: [Detection] β€” label, score, normalized box (top-left origin)
```

The take-home is [`Examples/DetectCamera/Sources/QuickStart.swift`](https://github.com/john-rocky/coreai-kit/blob/main/Examples/DetectCamera/Sources/QuickStart.swift)
β€” this exact code as one typed function, no UI; the CLI is an argument shell over it, and
the GUI runs the same detector per camera frame on a zero-copy pixel-buffer fast path.
Real time? Use `detect(in: CVPixelBuffer)` β€” vImage scales the frame with no CGImage
round-trip; `CameraFeed` (kit API) streams the buffers.

**Integration checklist**

- SPM: `https://github.com/john-rocky/coreai-kit` β†’ product **CoreAIKitVision**
- Info.plist: `NSCameraUsageDescription` β€” only for the live camera; the snippet needs none
- Entitlements: none needed
- First run downloads the model β€” 0.1 GB (Mac) / 0.1 GB (iPhone) β€” then it loads from the
  local cache (Application Support; progress via the `downloadProgress` callback)
- Measure in Release β€” Debug is ~3Γ— slower on per-token host work
<!-- gen-cards:use-it end -->

## Files

| file | input | params | M4 Max GPU | iPhone 17 Pro GPU |
|---|---|---|---|---|
| `rfdetr-nano_float32.aimodel` | 384Γ—384 | 30.5M | **8.6 ms** (~116 FPS) | **~25 ms (33–39 FPS live)** |
| `rfdetr-small_float32.aimodel` | 512Γ—512 | 32.1M | **12.0 ms** (~83 FPS) | β€” |
| `rfdetr-medium_float32.aimodel` | 576Γ—576 | 33.7M | **14.8 ms** (~68 FPS) | **56–63 ms (15–17 FPS live)** |
| `rfdetr-large_float32.aimodel` | 704Γ—704 | 33.9M | **19.1 ms** (~52 FPS) | β€” |

iPhone numbers are end-to-end live-camera measurements from the
[CoreAIKit DetectCamera example](https://github.com/john-rocky/coreai-kit)
(Release; zero-copy capture pipeline β€” AVCaptureVideoPreviewLayer display,
hardware-scaled 32BGRA buffers, vImage preprocessing overlapped with GPU
inference). Peak measured 39.6 FPS β‰ˆ the nano model ceiling; sustained
max-load throughput drops on a hot chassis (thermal).

fp32 is the ship dtype: it gates **detection-set exact** vs the PyTorch fp32 reference on
CPU and GPU (per confident detection: same class, IoU β‰₯ 0.999 measured, score within 2e-3),
and fp16 only bought ~7% latency on M4 Max while adding near-tie ranking noise.

## Graph contract

```
input  "image"  [1, 3, R, R]  float32, RGB in [0, 1]  (ImageNet mean/std folded in-graph)
output "dets"   [1, 300, 4]   boxes, cxcywh normalized to [0, 1]
output "labels" [1, 300, 91]  raw class logits; column index = ORIGINAL COCO id (0 unused, 1=person … 17=cat … 90)
```

Python decode sketch (Swift is the same three steps):

```python
import numpy as np, coreai.runtime as rt

model = await rt.AIModel.load(path, rt.SpecializationOptions.default())
fn = model.load_function("main")
out = await fn({"image": rt.NDArray(rgb01)})          # rgb01: [1,3,R,R] in [0,1]
prob = 1 / (1 + np.exp(-out["labels"].numpy()[0]))    # [300, 91]
scores, classes = prob.max(-1), prob.argmax(-1)       # column index IS the COCO id
boxes = out["dets"].numpy()[0]                        # cxcywh, multiply by image W/H
keep = scores > 0.5                                   # done β€” no NMS
```

## RF-DETR-Seg (instance segmentation)

`rfdetr-seg-{nano,small,medium,large,xlarge,2xlarge}_float32.aimodel` β€” same
contract plus `masks [1, Q, R/4, R/4]`: per-query FULL-FRAME logit planes at
stride 4 (host: sigmoid > 0.5; no ROI plumbing, no NMS). All six gate on CPU
and GPU with binary-mask IoU 1.000 on stable scenes. M4 Max GPU:
seg-nano 312Β² **10.7 ms** β†’ seg-2xlarge 768Β² **59.1 ms**.

<p align="center"><img src="demo_seg_coco_cats.jpg" width="440" alt="RF-DETR-Seg nano on Core AI"></p>

## Split deployment (`split/`)

`split/rfdetr-{nano,medium}_{backbone,head}.aimodel` separate the pure-ViT
backbone (image β†’ features) from the deformable head (features β†’ dets/labels;
position encodings baked in). The chain is bit-exact vs the monolith. Purpose:
per-stage compute-unit preferences β€” e.g. backbone on the Neural Engine.
Measured honestly: on iOS 27 beta the runtime still executes the backbone on
the GPU delegate even under `.neuralEngine` preference (identical detection
fingerprint, no ANE-compile pause), so today the monolith on GPU is the
fastest config; the split exists so ANE placement can be adopted the moment
the runtime honors it. Regenerate with `export_rf_detr.py --variant <v> --split`.

## Conversion

Exported with
[`conversion/export_rf_detr.py`](https://github.com/john-rocky/coreai-model-zoo/blob/main/conversion/export_rf_detr.py)
from `rfdetr==1.7.1` weights. The port surfaced four Core AI converter/runtime bugs
(float-arg `arange` abort, int64-comparison buffer clobber, GPU-delegate
floor/trunc/ceil = identity, cast-pair cancellation) β€” each worked around numerically
identically; details and minimal repros in
[zoo/rf-detr.md](https://github.com/john-rocky/coreai-model-zoo/blob/main/zoo/rf-detr.md).

License: Apache-2.0 (upstream RF-DETR code and COCO-pretrained weights are Apache-2.0).