Skip to content

Commit ebcf2eb

Browse files
authored
[CoreML] Update Conv and Softmax ops (#24594)
### Description Moved the dimension limit because it seems to only apply to conv operations (texture memory is typically used for conv operations in the GPU because it has a slow write but fast read -- ChromaDB model had a slice operation with an input > 16384 -- operation worked fine after I had moved the dim check) Also added extra checks for Softmax on MLProgram that allows more softmax nodes to be moved to CoreML
1 parent 5cae8d2 commit ebcf2eb

File tree

6 files changed

+98
-18
lines changed

6 files changed

+98
-18
lines changed

onnxruntime/core/providers/coreml/builders/helper.cc

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,6 @@ bool IsInputSupported(const Node& node, const NodeArg& input, const OpBuilderInp
7474
}
7575

7676
for (const auto dim : shape) {
77-
// For some undocumented reason, Apple CoreML framework will fail loading the model if the model
78-
// input has dimension > 16384
79-
// See this issue, https://github.com/apple/coremltools/issues/1003
80-
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf has maximum texture widths which may be the
81-
// root cause.
82-
if (dim > 16384) {
83-
LOGS(logger, WARNING) << "CoreML does not support input dim > 16384. Input:" << input_name
84-
<< ", shape: " << Shape2String(shape);
85-
return false;
86-
}
87-
8877
if (dim == 0 && !allow_empty_input) {
8978
LOGS(logger, WARNING) << "CoreML does not support shapes with dimension values of 0. Input:" << input_name
9079
<< ", shape: " << Shape2String(shape);
@@ -173,5 +162,22 @@ bool HasNeuralEngine() {
173162
return has_neural_engine;
174163
}
175164

165+
bool CheckShapeForConvMemoryLimit(gsl::span<const int64_t> shape, const logging::Logger& logger) {
166+
// For some undocumented reason, Apple CoreML framework will fail loading the model if the model
167+
// input has dimension > 16384
168+
// See this issue, https://github.com/apple/coremltools/issues/1003
169+
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf has maximum texture widths which may be the
170+
// root cause.
171+
// Only seems to apply to convolution networks -- limit comes from the size of the texture memory
172+
for (auto dim : shape) {
173+
if (dim > 16384) {
174+
LOGS(logger, VERBOSE) << "Input shape: " << Shape2String(shape)
175+
<< " exceeds CoreML convolution memory limit of 16384";
176+
return false;
177+
}
178+
}
179+
return true;
180+
}
181+
176182
} // namespace coreml
177183
} // namespace onnxruntime

onnxruntime/core/providers/coreml/builders/helper.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,10 @@ bool CheckIsConstantInitializer(const NodeArg& node_arg, const GraphViewer& grap
4848
// This is to detect if the current system has Apple Neural Engine
4949
bool HasNeuralEngine();
5050

51+
// See this issue, https://github.com/apple/coremltools/issues/1003
52+
// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf has maximum texture widths which may be the
53+
// root cause.
54+
bool CheckShapeForConvMemoryLimit(gsl::span<const int64_t> shape, const logging::Logger& logger);
55+
5156
} // namespace coreml
5257
} // namespace onnxruntime

onnxruntime/core/providers/coreml/builders/impl/conv_op_builder.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,32 @@ bool ConvOpBuilder::IsOpSupportedImpl(const Node& node, const OpBuilderInputPara
236236
// use the weight for the shape as it should always be known
237237
const auto* weight_shape = input_defs[1]->Shape();
238238
int64_t num_dims = weight_shape ? weight_shape->dim_size() : -1;
239+
const auto& output = *node.OutputDefs()[0];
240+
241+
std::vector<int64_t> weight_shape_vec;
242+
std::vector<int64_t> x_shape_vec;
243+
std::vector<int64_t> output_shape_vec;
244+
245+
if (!GetShape(*input_defs[1], weight_shape_vec, logger)) {
246+
LOGS(logger, VERBOSE) << "Unable to get the shape of 'W' input, which is necessary to check for valid convolutions.";
247+
return false;
248+
}
249+
250+
if (!GetShape(*input_defs[0], x_shape_vec, logger)) {
251+
LOGS(logger, VERBOSE) << "Unable to get the shape of 'X' input, which is necessary to check for valid convolutions.";
252+
return false;
253+
}
254+
255+
if (!GetShape(output, output_shape_vec, logger)) {
256+
LOGS(logger, VERBOSE) << "Unable to get the shape of the output, which is necessary to check for valid convolutions.";
257+
return false;
258+
}
259+
260+
if (!CheckShapeForConvMemoryLimit(weight_shape_vec, logger) ||
261+
!CheckShapeForConvMemoryLimit(x_shape_vec, logger) ||
262+
!CheckShapeForConvMemoryLimit(output_shape_vec, logger)) {
263+
return false;
264+
}
239265

240266
// ONNX spec requires N and C as first 2 dims
241267
if (num_dims != 3 && num_dims != 4) {

onnxruntime/core/providers/coreml/builders/impl/convtranspose_op_builder.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ bool ConvTransposeOpBuilder::IsOpSupportedImpl(const Node& node, const OpBuilder
137137
return false;
138138
}
139139

140+
if (!CheckShapeForConvMemoryLimit(weight_shape, logger) || !CheckShapeForConvMemoryLimit(input_shape, logger)) {
141+
return false;
142+
}
143+
140144
int64_t num_spatial_dims = narrow<int64_t>(weight_shape.size()) - 2;
141145

142146
NodeAttrHelper helper(node);

onnxruntime/core/providers/coreml/builders/impl/softmax_op_builder.cc

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,19 @@ Status SoftmaxOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
3030
const auto& output_name = node.OutputDefs()[0]->Name();
3131

3232
std::vector<int64_t> data_shape;
33-
ORT_RETURN_IF_NOT(GetStaticShape(*node.InputDefs()[0], data_shape, logger), "Failed to get input shape.");
3433

3534
NodeAttrHelper helper(node);
3635
int32_t axis_default_value = (node.SinceVersion() < 13) ? 1 : -1;
3736
const auto axis = helper.Get("axis", axis_default_value);
38-
auto axis_nonnegative = HandleNegativeAxis(axis, data_shape.size());
37+
int64_t axis_nonnegative = axis;
38+
39+
if (node.SinceVersion() < 13) {
40+
ORT_RETURN_IF_NOT(GetStaticShape(*node.InputDefs()[0], data_shape, logger), "Failed to get input shape.");
41+
axis_nonnegative = HandleNegativeAxis(axis, data_shape.size());
42+
} else {
43+
ORT_RETURN_IF_NOT(GetShape(*node.InputDefs()[0], data_shape, logger),
44+
"Softmax input must have shape information.");
45+
}
3946

4047
// CoreML's softmax match onnx's softmax behavior since opset 13.
4148
// For opset < 13, we need to reshape to 2D and set axis to -1 to simulate onnx softmax behavior.
@@ -125,16 +132,18 @@ Status SoftmaxOpBuilder::AddToModelBuilderImpl(ModelBuilder& model_builder,
125132
return Status::OK();
126133
}
127134

128-
bool SoftmaxOpBuilder::IsOpSupportedImpl(const Node& node, const OpBuilderInputParams& /*input_params*/,
135+
bool SoftmaxOpBuilder::IsOpSupportedImpl(const Node& node, const OpBuilderInputParams& input_params,
129136
const logging::Logger& logger) const {
130137
const auto& input_defs = node.InputDefs();
131138
std::vector<int64_t> input_shape;
132-
if (!GetStaticShape(*input_defs[0], input_shape, logger))
139+
140+
if (!GetShape(*input_defs[0], input_shape, logger)) {
141+
LOGS(logger, VERBOSE) << "Softmax input [" << input_defs[0]->Name() << "] must have shape information.";
133142
return false;
143+
}
134144

135-
const TensorShape shape(input_shape);
136-
if (shape.Size() == 0) {
137-
LOGS(logger, VERBOSE) << "Empty input data is not supported.";
145+
if (!IsStaticShape(input_shape) && node.SinceVersion() < 13) {
146+
LOGS(logger, VERBOSE) << "Softmax input must have static shape for ONNX opset < 13";
138147
return false;
139148
}
140149

onnxruntime/test/providers/cpu/tensor/tensor_op_test.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,36 @@ TEST(TensorOpTest, Reshape_WithOutAllowZero) {
9191
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider});
9292
}
9393

94+
TEST(TensorOpTest, Reshape_WithOutAllowZeroToDiffRank) {
95+
OpTester test("Reshape", 14);
96+
97+
test.AddInput<float>("data", {2, 3, 12}, std::vector<float>(72, 1.0f));
98+
test.AddInput<int64_t>("shape", {4}, {2, 3, 3, 4}, true);
99+
test.AddAttribute<int64_t>("allowzero", 0);
100+
test.AddOutput<float>("reshaped", {2, 3, 3, 4}, std::vector<float>(72, 1.0f));
101+
test.Run();
102+
}
103+
104+
TEST(TensorOpTest, Reshape_WithOutAllowZeroToDiffRankOneZero) {
105+
OpTester test("Reshape", 14);
106+
107+
test.AddInput<float>("data", {2, 3, 12}, std::vector<float>(72, 1.0f));
108+
test.AddInput<int64_t>("shape", {4}, {0, 3, 3, 4}, true);
109+
test.AddAttribute<int64_t>("allowzero", 0);
110+
test.AddOutput<float>("reshaped", {2, 3, 3, 4}, std::vector<float>(72, 1.0f));
111+
test.Run();
112+
}
113+
114+
TEST(TensorOpTest, Reshape_WithOutAllowZeroToDiffRankTwoZeroes) {
115+
OpTester test("Reshape", 14);
116+
117+
test.AddInput<float>("data", {2, 3, 12}, std::vector<float>(72, 1.0f));
118+
test.AddInput<int64_t>("shape", {4}, {0, 0, 3, 4}, true);
119+
test.AddAttribute<int64_t>("allowzero", 0);
120+
test.AddOutput<float>("reshaped", {2, 3, 3, 4}, std::vector<float>(72, 1.0f));
121+
test.Run();
122+
}
123+
94124
TEST(TensorOpTest, Reshape_WithAllowZero) {
95125
// TODO: Unskip when fixed #41968513
96126
if (DefaultDmlExecutionProvider().get() != nullptr) {

0 commit comments

Comments
 (0)