@@ -155,8 +155,12 @@ impl YOLOModel {
155155 }
156156 #[ cfg( feature = "coreml" ) ]
157157 crate :: Device :: CoreMl => {
158- eps. push ( Self :: build_coreml_ep ( path) ) ;
159- provider_name = "CoreMLExecutionProvider" ;
158+ if matches ! ( Self :: macos_version( ) , Some ( ( major, _) ) if major >= 11 ) {
159+ eps. push ( Self :: build_coreml_ep ( path) ) ;
160+ provider_name = "CoreMLExecutionProvider" ;
161+ } else {
162+ warn ! ( "WARNING ⚠️ CoreML requires macOS 11+; falling back to CPU." ) ;
163+ }
160164 }
161165 #[ cfg( feature = "tensorrt" ) ]
162166 crate :: Device :: TensorRt ( i) => {
@@ -222,7 +226,7 @@ impl YOLOModel {
222226 }
223227
224228 #[ cfg( feature = "coreml" ) ]
225- {
229+ if matches ! ( Self :: macos_version ( ) , Some ( ( major , _ ) ) if major >= 11 ) {
226230 eps. push ( Self :: build_coreml_ep ( path) ) ;
227231 if provider_name == "CPUExecutionProvider" {
228232 provider_name = "CoreMLExecutionProvider" ;
@@ -433,19 +437,12 @@ impl YOLOModel {
433437 #[ cfg( feature = "coreml" ) ]
434438 fn build_coreml_ep ( model_path : & Path ) -> ort:: execution_providers:: ExecutionProviderDispatch {
435439 use ort:: execution_providers:: coreml:: ModelFormat ;
436- let format = match Self :: macos_version ( ) {
437- Some ( ( major, _) ) if major >= 12 => ModelFormat :: MLProgram ,
438- ver => {
439- let label = ver. map_or_else (
440- || "unknown" . to_owned ( ) ,
441- |( maj, min) | format ! ( "{maj}.{min} < 12" ) ,
442- ) ;
443- warn ! (
444- "WARNING ⚠️ macOS {label}: CoreML using NeuralNetwork format; FP16 models may fail."
445- ) ;
446- ModelFormat :: NeuralNetwork
447- }
448- } ;
440+ // `MLProgram` (macOS 12+) causes ORT's `CoreML` EP to insert a FP32→FP16 cast node at
441+ // graph input, renaming it from the `ONNX` name (e.g. "images") to "graph_input_cast_0".
442+ // ORT then feeds the tensor by the original name, which `CoreML` can't find → crash.
443+ // `NeuralNetwork` (`CoreML` 3, macOS 10.15+) avoids the rename and supports FP16 inputs
444+ // natively — confirmed working with both `FP32` and `FP16` `YOLO` `ONNX` models on macOS 12+.
445+ let format = ModelFormat :: NeuralNetwork ;
449446 let mut ep =
450447 ort:: execution_providers:: CoreMLExecutionProvider :: default ( ) . with_model_format ( format) ;
451448
@@ -518,11 +515,7 @@ impl YOLOModel {
518515
519516 if let Err ( e) = warmup_result {
520517 let msg = e. to_string ( ) ;
521- // CoreML + all-zeros input: GatherElements out-of-range in the DFL head is benign.
522- if self . execution_provider != "CoreMLExecutionProvider"
523- || !msg. contains ( "GatherElements" )
524- || !msg. contains ( "Out of range" )
525- {
518+ if !is_benign_coreml_warmup_error ( & self . execution_provider , & msg) {
526519 return Err ( e) ;
527520 }
528521 }
@@ -1161,6 +1154,15 @@ impl std::fmt::Debug for YOLOModel {
11611154 }
11621155}
11631156
1157+ /// Returns true if `err` is the benign `GatherElements` out-of-range error that `CoreML` produces
1158+ /// on all-zeros dummy input (issue #148). All other errors, including `graph_input_cast_0`,
1159+ /// must propagate so callers see real failures.
1160+ fn is_benign_coreml_warmup_error ( provider : & str , msg : & str ) -> bool {
1161+ provider == "CoreMLExecutionProvider"
1162+ && msg. contains ( "GatherElements" )
1163+ && msg. contains ( "Out of range" )
1164+ }
1165+
11641166#[ cfg( test) ]
11651167mod tests {
11661168 use super :: * ;
@@ -1212,4 +1214,60 @@ mod tests {
12121214 assert ! ( debug_str. contains( "num_classes" ) ) ;
12131215 }
12141216 }
1217+
1218+ // Issue #148 , PR #149: GatherElements out-of-range on an all-zeros dummy input is benign
1219+ // during `CoreML` warmup (the DFL head produces invalid gather indices for zero activations).
1220+ #[ test]
1221+ fn test_warmup_gather_elements_suppressed_for_coreml ( ) {
1222+ assert ! ( is_benign_coreml_warmup_error(
1223+ "CoreMLExecutionProvider" ,
1224+ "GatherElements op: Out of range value in index tensor"
1225+ ) ) ;
1226+ }
1227+
1228+ // The same GatherElements error on CPU/CUDA is a real bug and must not be hidden.
1229+ #[ test]
1230+ fn test_warmup_gather_elements_propagates_for_other_providers ( ) {
1231+ assert ! ( !is_benign_coreml_warmup_error(
1232+ "CPUExecutionProvider" ,
1233+ "GatherElements op: Out of range value in index tensor"
1234+ ) ) ;
1235+ assert ! ( !is_benign_coreml_warmup_error(
1236+ "CUDAExecutionProvider" ,
1237+ "GatherElements op: Out of range value in index tensor"
1238+ ) ) ;
1239+ }
1240+
1241+ // graph_input_cast_0 is a real `CoreML` misconfiguration (MLProgram adds a cast node that
1242+ // renames the ONNX input). It must propagate so the caller sees the failure.
1243+ // The fix (`NeuralNetwork` format) prevents this error from occurring at all, but it must
1244+ // never be silently swallowed if it somehow reappears.
1245+ #[ test]
1246+ fn test_warmup_graph_input_cast_error_propagates ( ) {
1247+ assert ! ( !is_benign_coreml_warmup_error(
1248+ "CoreMLExecutionProvider" ,
1249+ "Feature graph_input_cast_0 is required but not specified"
1250+ ) ) ;
1251+ }
1252+
1253+ // Any unrecognized `CoreML` error must propagate.
1254+ #[ test]
1255+ fn test_warmup_unrecognised_coreml_error_propagates ( ) {
1256+ assert ! ( !is_benign_coreml_warmup_error(
1257+ "CoreMLExecutionProvider" ,
1258+ "Some unexpected `CoreML` error"
1259+ ) ) ;
1260+ }
1261+
1262+ #[ cfg( all( feature = "coreml" , target_os = "macos" ) ) ]
1263+ #[ test]
1264+ fn test_macos_version_parses_on_macos ( ) {
1265+ let version = YOLOModel :: macos_version ( ) ;
1266+ assert ! (
1267+ version. is_some( ) ,
1268+ "macos_version() must return Some on macOS"
1269+ ) ;
1270+ let ( major, _minor) = version. unwrap ( ) ;
1271+ assert ! ( major >= 10 , "macOS major version should be >= 10" ) ;
1272+ }
12151273}
0 commit comments