Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions coremltools/converters/mil/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import Optional, Text, Tuple

import coremltools as ct
from coremltools import _SPECIFICATION_VERSION_IOS_18
from coremltools.converters._profile_utils import _profile
from coremltools.converters.mil import input_types
from coremltools.converters.mil.mil import Builder as mb
Expand Down Expand Up @@ -303,6 +304,28 @@ def mil_convert_to_proto(

prog._check_early_error_out_for_invalid_program()

# Models with no inputs require specification version >= iOS18; otherwise
# the resulting mlpackage fails to compile downstream with
# "Empty input is only valid in specification version >= 9".
# Surface that as an explicit error here rather than letting the user
# discover it at compile time.
spec_version = kwargs.get("specification_version")
if (
convert_to == "mlprogram"
and spec_version is not None
and spec_version < _SPECIFICATION_VERSION_IOS_18
):
main_func = prog.functions.get(prog.default_function_name)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case I think we should fail, with a clear error message that telling the user they need to change their deployment target.

if main_func is not None and len(main_func.inputs) == 0:
raise ValueError(
"Model has no inputs, which requires a deployment target of "
"iOS18 / macOS15 or later (specification version "
f"{_SPECIFICATION_VERSION_IOS_18}); the requested target maps "
f"to specification version {spec_version}. Re-run "
"`ct.convert(...)` with "
"`minimum_deployment_target=ct.target.iOS18` (or a later target)."
)

backend_converter_type = converter_registry.backends.get(convert_to.lower())
if not backend_converter_type:
raise NotImplementedError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,31 @@ def forward(self, x):
)
assert mlmodel.user_defined_metadata[_METADATA_SOURCE_DIALECT] == dialect_name

@pytest.mark.parametrize("frontend", frontends)
def test_no_input_model_requires_ios18(self, frontend):
# Regression test for #2578: a model with no inputs requires
# specification version >= iOS18, otherwise the produced mlpackage
# fails to compile with "Empty input is only valid in specification
# version >= 9". Verify both the happy path (iOS18 succeeds) and the
# error path (lower target raises a clear ValueError pointing the
# user at minimum_deployment_target).
class Model(torch.nn.Module):
def forward(self):
return torch.ones(5, 5)

exported_model = export_torch_model_to_frontend(Model().eval(), (), frontend)

# Default target (iOS15) must fail with an actionable error message.
with pytest.raises(ValueError, match=r"minimum_deployment_target=ct\.target\.iOS18"):
ct.convert(exported_model)

# Explicit iOS18 target must succeed and produce a runnable mlpackage.
mlmodel = ct.convert(exported_model, minimum_deployment_target=ct.target.iOS18)
spec = mlmodel.get_spec()
assert len(spec.description.input) == 0
assert spec.specificationVersion >= ct.target.iOS18.value
verify_prediction(mlmodel)


@pytest.mark.skipif((version_info.major, version_info.minor) == (3, 13), reason="rdar://158079341")
class TestExecuTorchExamples(TorchBaseTest):
Expand Down