apocaliss92 commited on
Commit
1f57eba
·
verified ·
1 Parent(s): 8f4b385

source: YOLO26 @320/@256 CoreML recipe is static (batch 1)

Browse files

Corresponding Source for the re-exported YOLO26 @320/@256 CoreML packages: build-camstack-models.py and source/README.md, verbatim from the CamStack repository.

Files changed (2) hide show
  1. source/README.md +10 -4
  2. 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 | flexible batch 1..16, FP16 | 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,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 flexible-batch CoreML exports
52
- need coremltools 8.3 with PyTorch 2.5.1, the macOS pins in `requirements.txt`;
53
- coremltools 9 aborts on them.
 
 
 
 
 
 
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.append(f'{self.folder}/coreml/{self.name}.mlpackage/')
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, batch-flexible (1..16) FP16 CoreML, IR fp32 + INT8."""
 
 
 
 
 
 
 
 
 
 
 
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=True, batch=16, half=True),
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.name}.mlpackage')
 
 
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