source: YOLO26 @320/@256 CoreML recipe is static (batch 1)
Browse filesCorresponding Source for the re-exported YOLO26 @320/@256 CoreML packages: build-camstack-models.py and source/README.md, verbatim from the CamStack repository.
- source/README.md +10 -4
- source/build-camstack-models.py +35 -4
source/README.md
CHANGED
|
@@ -39,7 +39,7 @@ What the recipes encode (the hosted files record each of these):
|
|
| 39 |
| Folder | Files | ONNX | CoreML | OpenVINO IR |
|
| 40 |
| --- | --- | --- | --- | --- |
|
| 41 |
| `objectDetection/yolo26` | `camstack-yolo26{n,s,m,l,x}` @640 | static, opset 17 | static, batch 1 | FP16 as `<stem>.xml` and `<stem>-fp16.xml`; INT8 `<stem>-int8.xml` (128 images) |
|
| 42 |
-
| `objectDetection/yolo26` | `camstack-yolo26{n,s,m,l}-{320,256}` | dynamic axes, opset 17 |
|
| 43 |
| `objectDetection/yolov9` | `camstack-yolov9{t,s,m,c}-{320,640}` | static, opset 13 | static, batch 1 | FP16 `<stem>-fp16.xml`; INT8 `<stem>-int8.xml` (300 images) |
|
| 44 |
| `segmentation/yolo26-seg` | `camstack-yolo26{n,s,m}-seg` @640 | static, opset 17 | static, batch 1 | FP16 `<stem>.xml` |
|
| 45 |
|
|
@@ -48,9 +48,15 @@ YOLO26 @640 and YOLO26-seg 8.4.24 / 2.10.0 / 9.0 / 2026.2.1; YOLO26 @320 and
|
|
| 48 |
@256 8.4.92 / 2.13.0 for ONNX, 2.5.1 for CoreML / 8.3.0 / 2026.2.1; YOLOv9
|
| 49 |
8.4.98 / 2.13.0 / 9.0 / 2026.2.1. The script warns when the installed Ultralytics differs.
|
| 50 |
With these versions a rebuild gives the hosted weights. Other versions give the
|
| 51 |
-
same weights in a possibly different graph. The
|
| 52 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
### INT8 calibration
|
| 56 |
|
|
|
|
| 39 |
| Folder | Files | ONNX | CoreML | OpenVINO IR |
|
| 40 |
| --- | --- | --- | --- | --- |
|
| 41 |
| `objectDetection/yolo26` | `camstack-yolo26{n,s,m,l,x}` @640 | static, opset 17 | static, batch 1 | FP16 as `<stem>.xml` and `<stem>-fp16.xml`; INT8 `<stem>-int8.xml` (128 images) |
|
| 42 |
+
| `objectDetection/yolo26` | `camstack-yolo26{n,s,m,l}-{320,256}` | dynamic axes, opset 17 | static, batch 1, FP16, as `<stem>-static.mlpackage` and `<stem>.mlpackage` | FP32 `<stem>.xml`; INT8 `<stem>-int8.xml` (128 images) |
|
| 43 |
| `objectDetection/yolov9` | `camstack-yolov9{t,s,m,c}-{320,640}` | static, opset 13 | static, batch 1 | FP16 `<stem>-fp16.xml`; INT8 `<stem>-int8.xml` (300 images) |
|
| 44 |
| `segmentation/yolo26-seg` | `camstack-yolo26{n,s,m}-seg` @640 | static, opset 17 | static, batch 1 | FP16 `<stem>.xml` |
|
| 45 |
|
|
|
|
| 48 |
@256 8.4.92 / 2.13.0 for ONNX, 2.5.1 for CoreML / 8.3.0 / 2026.2.1; YOLOv9
|
| 49 |
8.4.98 / 2.13.0 / 9.0 / 2026.2.1. The script warns when the installed Ultralytics differs.
|
| 50 |
With these versions a rebuild gives the hosted weights. Other versions give the
|
| 51 |
+
same weights in a possibly different graph. The @320 and @256 CoreML exports
|
| 52 |
+
use coremltools 8.3 with PyTorch 2.5.1, the macOS pins in `requirements.txt`.
|
| 53 |
+
|
| 54 |
+
The @320 and @256 CoreML packages were batch-flexible (1..16) until
|
| 55 |
+
2026-10-07. That package aborts any process that loads it on macOS 26.2, on
|
| 56 |
+
every compute unit, because Core ML's `gather_nd` shape pass fails on a
|
| 57 |
+
flexible batch. They are now exported with a static batch of 1. The same
|
| 58 |
+
package is published as `<stem>-static.mlpackage` (the name the catalog uses)
|
| 59 |
+
and under the old `<stem>.mlpackage` name.
|
| 60 |
|
| 61 |
### INT8 calibration
|
| 62 |
|
source/build-camstack-models.py
CHANGED
|
@@ -134,6 +134,22 @@ class Recipe:
|
|
| 134 |
openvino: tuple[OpenvinoOutput, ...] = ()
|
| 135 |
calib_samples: int = 0
|
| 136 |
note: str = ''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
def outputs(self) -> list[str]:
|
| 139 |
"""Mirror paths this recipe produces (an .mlpackage is a directory)."""
|
|
@@ -141,7 +157,7 @@ class Recipe:
|
|
| 141 |
if self.onnx is not None:
|
| 142 |
out.append(f'{self.folder}/onnx/{self.name}.onnx')
|
| 143 |
if self.coreml is not None:
|
| 144 |
-
out.
|
| 145 |
for ov in self.openvino:
|
| 146 |
stem = f'{self.folder}/openvino/{self.name}{ov.suffix}'
|
| 147 |
out.extend([f'{stem}.xml', f'{stem}.bin'])
|
|
@@ -176,14 +192,27 @@ def _yolo26_full(tier: str) -> Recipe:
|
|
| 176 |
|
| 177 |
|
| 178 |
def _yolo26_reduced(tier: str, res: int) -> Recipe:
|
| 179 |
-
"""yolo26<tier>-<res>: dynamic-axes ONNX,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
return Recipe(
|
| 181 |
name=f'camstack-yolo26{tier}-{res}', folder='objectDetection/yolo26', imgsz=res,
|
| 182 |
toolchain=TC_2607_REDUCED, checkpoint=f'yolo26{tier}',
|
| 183 |
onnx=OnnxExport(dynamic=True, opset=17),
|
| 184 |
-
coreml=CoremlExport(dynamic=
|
| 185 |
openvino=(OpenvinoOutput('', 'fp32'), OpenvinoOutput('-int8', 'int8')),
|
| 186 |
calib_samples=128,
|
|
|
|
|
|
|
| 187 |
)
|
| 188 |
|
| 189 |
|
|
@@ -354,7 +383,9 @@ def build_coreml(recipe: Recipe, out: Path, verify: bool) -> Path:
|
|
| 354 |
checkpoint_path(recipe), format='coreml', imgsz=recipe.imgsz,
|
| 355 |
dynamic=c.dynamic, batch=c.batch, half=c.half, nms=False,
|
| 356 |
)
|
| 357 |
-
dest = place(src, out / recipe.folder / 'coreml' / f'{recipe.
|
|
|
|
|
|
|
| 358 |
if verify and c.dynamic:
|
| 359 |
verify_coreml_batch(dest, recipe.imgsz)
|
| 360 |
return dest
|
|
|
|
| 134 |
openvino: tuple[OpenvinoOutput, ...] = ()
|
| 135 |
calib_samples: int = 0
|
| 136 |
note: str = ''
|
| 137 |
+
# The CoreML package's stem when it differs from `name`. A package that had
|
| 138 |
+
# to be re-exported in an incompatible way gets a NEW file name, because a
|
| 139 |
+
# node keeps any package already on disk under the old one forever.
|
| 140 |
+
coreml_name: Optional[str] = None
|
| 141 |
+
# Earlier stems the SAME package is also published under, so a release
|
| 142 |
+
# whose catalog still names them downloads the working package too.
|
| 143 |
+
coreml_aliases: tuple[str, ...] = ()
|
| 144 |
+
|
| 145 |
+
@property
|
| 146 |
+
def coreml_stem(self) -> str:
|
| 147 |
+
return self.coreml_name or self.name
|
| 148 |
+
|
| 149 |
+
def coreml_paths(self) -> list[str]:
|
| 150 |
+
"""Every mirror path of the CoreML package: its own, then its aliases."""
|
| 151 |
+
return [f'{self.folder}/coreml/{stem}.mlpackage/'
|
| 152 |
+
for stem in (self.coreml_stem, *self.coreml_aliases)]
|
| 153 |
|
| 154 |
def outputs(self) -> list[str]:
|
| 155 |
"""Mirror paths this recipe produces (an .mlpackage is a directory)."""
|
|
|
|
| 157 |
if self.onnx is not None:
|
| 158 |
out.append(f'{self.folder}/onnx/{self.name}.onnx')
|
| 159 |
if self.coreml is not None:
|
| 160 |
+
out.extend(self.coreml_paths())
|
| 161 |
for ov in self.openvino:
|
| 162 |
stem = f'{self.folder}/openvino/{self.name}{ov.suffix}'
|
| 163 |
out.extend([f'{stem}.xml', f'{stem}.bin'])
|
|
|
|
| 192 |
|
| 193 |
|
| 194 |
def _yolo26_reduced(tier: str, res: int) -> Recipe:
|
| 195 |
+
"""yolo26<tier>-<res>: dynamic-axes ONNX, STATIC FP16 CoreML, IR fp32 + INT8.
|
| 196 |
+
|
| 197 |
+
The CoreML package was batch-flexible (1..16) until 2026-10-07, and that
|
| 198 |
+
package aborts the process that loads it (SIGABRT, macOS 26.2, every compute
|
| 199 |
+
unit, coremltools 8.3 and 9.0 alike): Core ML's Espresso shape pass frees an
|
| 200 |
+
unallocated pointer in `gather_nd` when the input batch is a range. YOLO26's
|
| 201 |
+
NMS-free head is the only hosted graph with a `gather_nd`, which is why the
|
| 202 |
+
one other batch-flexible package (bird-classifier) never hit it, and why the
|
| 203 |
+
static @640 YOLO26 packages load. The static export loads
|
| 204 |
+
and runs on every compute unit, so it is published under a new stem
|
| 205 |
+
(`-static`) and under the old one.
|
| 206 |
+
"""
|
| 207 |
return Recipe(
|
| 208 |
name=f'camstack-yolo26{tier}-{res}', folder='objectDetection/yolo26', imgsz=res,
|
| 209 |
toolchain=TC_2607_REDUCED, checkpoint=f'yolo26{tier}',
|
| 210 |
onnx=OnnxExport(dynamic=True, opset=17),
|
| 211 |
+
coreml=CoremlExport(dynamic=False, batch=1, half=True),
|
| 212 |
openvino=(OpenvinoOutput('', 'fp32'), OpenvinoOutput('-int8', 'int8')),
|
| 213 |
calib_samples=128,
|
| 214 |
+
coreml_name=f'camstack-yolo26{tier}-{res}-static',
|
| 215 |
+
coreml_aliases=(f'camstack-yolo26{tier}-{res}',),
|
| 216 |
)
|
| 217 |
|
| 218 |
|
|
|
|
| 383 |
checkpoint_path(recipe), format='coreml', imgsz=recipe.imgsz,
|
| 384 |
dynamic=c.dynamic, batch=c.batch, half=c.half, nms=False,
|
| 385 |
)
|
| 386 |
+
dest = place(src, out / recipe.folder / 'coreml' / f'{recipe.coreml_stem}.mlpackage')
|
| 387 |
+
for alias in recipe.coreml_aliases:
|
| 388 |
+
place(dest, out / recipe.folder / 'coreml' / f'{alias}.mlpackage')
|
| 389 |
if verify and c.dynamic:
|
| 390 |
verify_coreml_batch(dest, recipe.imgsz)
|
| 391 |
return dest
|