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
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"export": {
"opset_version": 17,
"batch_size": 1,
"export_params": true,
"do_constant_folding": true,
"verbose": false,
"dynamo": false,
"enable_hierarchy_tags": true,
"clean_onnx": false,
"hierarchy_tag_format": "full",
"input_tensors": [
{
"name": "input_ids",
"dtype": "int32",
"shape": [
1,
16
],
"value_range": [
0,
49408
]
},
{
"name": "pixel_values",
"dtype": "float32",
"shape": [
1,
3,
960,
960
],
"value_range": [
0,
1
]
},
{
"name": "attention_mask",
"dtype": "int32",
"shape": [
1,
16
],
"value_range": [
0,
2
]
}
],
"output_tensors": [
{
"name": "logits"
},
{
"name": "pred_boxes"
},
{
"name": "text_embeds"
},
{
"name": "image_embeds"
}
]
},
"optim": {
"clamp_constant_values": true
},
"quant": null,
"loader": {
"task": "zero-shot-object-detection",
"model_class": "AutoModelForZeroShotObjectDetection",
"model_type": "owlv2"
}
}
19 changes: 18 additions & 1 deletion src/winml/modelkit/export/htp/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from __future__ import annotations

import contextlib
import inspect
import logging
import sys
import time
Expand Down Expand Up @@ -445,9 +446,25 @@ def _convert_model_to_onnx(
output_path = str(Path(output_path).resolve())
Path(output_path).parent.mkdir(parents=True, exist_ok=True)

# Input names from config, fallback to inputs dict keys
# Input names from config, fallback to inputs dict keys.
input_names = export_config.get_input_names() or list(inputs.keys())

if not hasattr(model, "get_export_args"):
# torch.onnx.export binds kwargs by name when invoking forward(), but
# applies input_names positionally to the resulting graph inputs in
# forward-signature order. Keep kwargs for safe invocation and align
# the ONNX names independently so recipe order cannot swap bindings.
try:
parameters = inspect.signature(model.forward).parameters
forward_input_names = [name for name in parameters if name in input_names]
if len(forward_input_names) == len(input_names):
input_names = forward_input_names
except (TypeError, ValueError):
logger.debug(
"Could not inspect %s.forward; preserving configured input order.",
type(model).__name__,
)

# Output names: infer from traced hierarchy, validate against config
traced_outputs = self._hierarchy_builder.get_outputs() if self._hierarchy_builder else None
inferred_names = infer_output_names(traced_outputs) if traced_outputs is not None else []
Expand Down
19 changes: 12 additions & 7 deletions tests/unit/export/test_pytorch_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,12 @@ def test_normalize_false_skips_normalization(self, tmp_path) -> None:
assert not _all_value_info_have_shape(onnx_model)

def test_mismatched_input_order_exports_successfully(self, tmp_path) -> None:
"""Export succeeds when InputTensorSpec order differs from forward() param order.
"""ONNX names bind correctly when specs differ from forward() param order.

Regression test for CLIP bug: OnnxConfig listed pixel_values before input_ids,
but CLIPModel.forward() expected input_ids first. With positional args this
caused 'not enough values to unpack (expected 4, got 2)'. The fix uses
kwargs= in torch.onnx.export so name-based binding makes order irrelevant.
but CLIPModel.forward() expected input_ids first. kwargs make model invocation
order-independent, while input_names must separately follow forward order
because torch.onnx.export applies those names positionally to graph inputs.
"""
model = MismatchedInputOrderModel()

Expand All @@ -374,9 +374,14 @@ def test_mismatched_input_order_exports_successfully(self, tmp_path) -> None:

onnx_model = onnx.load(str(tmp_path / "model.onnx"))
onnx.checker.check_model(onnx_model)
input_names = {i.name for i in onnx_model.graph.input}
assert "pixel_values" in input_names
assert "text_input" in input_names
input_shapes = {
tensor.name: tuple(dim.dim_value for dim in tensor.type.tensor_type.shape.dim)
for tensor in onnx_model.graph.input
}
assert input_shapes == {
"text_input": (1, 16),
"pixel_values": (1, 3, 8, 8),
}


class TestStaleExternalDataCleanup:
Expand Down
Loading