Skip to content

Commit 6d191fa

Browse files
committed
Wrap iOS18 quantization errors with ExecuTorch-specific hint
When the user lowers a model that was prepared with torchao's `quantize_(...)` (e.g. blockwise int4) but does not pass an iOS18+ `minimum_deployment_target` to the CoreML partitioner, coremltools raises a generic ValueError pointing at coremltools internals. The user has no obvious way to discover that the target is set via `CoreMLBackend.generate_compile_specs` and plumbed through `CoreMLPartitioner(compile_specs=...)`. Catch the ValueError around the two coremltools utilities used by our overridden `dequantize_affine` / `dequantize_codebook` handlers and re-raise it with an ExecuTorch-flavored hint that shows the exact partitioner call to make. Fixes #13122.
1 parent 31f0692 commit 6d191fa

2 files changed

Lines changed: 61 additions & 11 deletions

File tree

backends/apple/coreml/compiler/torch_ops.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@
1212
import torch as _torch
1313
from coremltools import _logger
1414
from coremltools.converters.mil.frontend import _utils
15+
16+
_IOS18_QUANT_HINT = (
17+
"ExecuTorch hint: pass `compile_specs=CoreMLBackend.generate_compile_specs("
18+
"minimum_deployment_target=ct.target.iOS18)` (or higher) to "
19+
"`CoreMLPartitioner` when lowering models that use `quantize_(...)`."
20+
)
21+
22+
23+
def _raise_with_executorch_hint(err: Exception) -> "BaseException":
24+
"""Re-raise a coremltools quantization error with ExecuTorch-specific guidance."""
25+
msg = str(err)
26+
if "iOS18" in msg or "iOS 18" in msg:
27+
raise ValueError(f"{msg}\n{_IOS18_QUANT_HINT}") from err
28+
raise err
1529
from coremltools.converters.mil.frontend.torch.ops import (
1630
_get_inputs,
1731
_get_kwinputs,
@@ -159,12 +173,15 @@ def dequantize_affine(context, node):
159173
f"Unsupported quantization range: {quant_min} to {quant_max}. CoreML only supports 4-bit and 8-bit quantization."
160174
)
161175

162-
output = _utils._construct_constexpr_dequant_op(
163-
int_data.astype(quantized_np_dtype),
164-
zero_point,
165-
scale,
166-
name=node.name,
167-
)
176+
try:
177+
output = _utils._construct_constexpr_dequant_op(
178+
int_data.astype(quantized_np_dtype),
179+
zero_point,
180+
scale,
181+
name=node.name,
182+
)
183+
except ValueError as e:
184+
_raise_with_executorch_hint(e)
168185
context.add(output, node.name)
169186

170187

@@ -211,9 +228,12 @@ def dequantize_codebook(context, node):
211228
f"Core ML ignores output_dtype {out_np_dtype} on torchao.dequantize_affine and instead uses the native precision."
212229
)
213230

214-
output = _utils._construct_constexpr_lut_op(
215-
codes.astype(np.int8),
216-
codebook,
217-
name=node.name,
218-
)
231+
try:
232+
output = _utils._construct_constexpr_lut_op(
233+
codes.astype(np.int8),
234+
codebook,
235+
name=node.name,
236+
)
237+
except ValueError as e:
238+
_raise_with_executorch_hint(e)
219239
context.add(output, node.name)

backends/apple/coreml/test/test_torch_ops.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,36 @@ def forward(self, x):
318318
self._compare_outputs(et_prog, model, example_inputs)
319319

320320

321+
def test_dequantize_affine_below_ios18_raises_with_hint(self):
322+
"""
323+
Regression test for https://github.com/pytorch/executorch/issues/13122.
324+
325+
`quantize_(...)` with blockwise / int4 configurations requires iOS18.
326+
coremltools raises a ValueError that does not mention how to fix the
327+
deployment target on the ExecuTorch side; we wrap it to add the
328+
partitioner-level guidance.
329+
"""
330+
model = torch.nn.Linear(64, 64)
331+
quantize_(
332+
model,
333+
IntxWeightOnlyConfig(weight_dtype=torch.int4, granularity=PerGroup(32)),
334+
)
335+
ep = torch.export.export(model.eval(), (torch.randn(1, 64),), strict=True)
336+
with self.assertRaises(ValueError) as cm:
337+
executorch.exir.to_edge_transform_and_lower(
338+
ep,
339+
partitioner=[
340+
self._coreml_partitioner(
341+
minimum_deployment_target=ct.target.iOS17
342+
)
343+
],
344+
)
345+
msg = str(cm.exception)
346+
self.assertIn("iOS18", msg)
347+
self.assertIn("CoreMLPartitioner", msg)
348+
self.assertIn("minimum_deployment_target", msg)
349+
350+
321351
if __name__ == "__main__":
322352
test_runner = TestTorchOps()
323353
test_runner.test_dequantize_affine_b4w_embedding()

0 commit comments

Comments
 (0)