Skip to content

Commit 6980eb0

Browse files
committed
supports integers for value_if_missing
1 parent 5380d55 commit 6980eb0

3 files changed

Lines changed: 182 additions & 29 deletions

File tree

_unittests/ut_investigate/test_input_observer.py

Lines changed: 138 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ def forward(self, x=None, y=None):
887887
# self.assertEqual(2, len(args))
888888
# self.assertEqual(len([v for v in args.values() if v is not None]), 2)
889889

890-
def test_infer_dynamic_shapes_missing(self):
890+
def test_infer_dynamic_shapes_missing_kwargs(self):
891891
class Model(torch.nn.Module):
892892
def forward(
893893
self,
@@ -903,34 +903,34 @@ def forward(
903903

904904
inputs = [
905905
dict(
906-
input_ids=torch.ones((1, 282), dtype=torch.int64),
907-
pixel_values=torch.ones((1, 3, 896, 896), dtype=torch.int64),
908-
attention_mask=torch.ones((1, 282), dtype=torch.int64),
909-
position_ids=torch.ones((1, 282), dtype=torch.int64),
910-
token_type_ids=torch.ones((1, 282), dtype=torch.int64),
911-
cache_position=torch.ones((282,), dtype=torch.int64),
906+
input_ids=torch.ones((1, 28), dtype=torch.int64),
907+
pixel_values=torch.ones((1, 3, 112, 112), dtype=torch.int64),
908+
attention_mask=torch.ones((1, 28), dtype=torch.int64),
909+
position_ids=torch.ones((1, 28), dtype=torch.int64),
910+
token_type_ids=torch.ones((1, 28), dtype=torch.int64),
911+
cache_position=torch.ones((28,), dtype=torch.int64),
912912
),
913913
dict(
914914
input_ids=torch.ones((1, 1), dtype=torch.int64),
915-
attention_mask=torch.ones((1, 283), dtype=torch.int64),
915+
attention_mask=torch.ones((1, 29), dtype=torch.int64),
916916
position_ids=torch.ones((1, 1), dtype=torch.int64),
917-
past_key_values=torch.rand((1, 1, 282, 32)),
917+
past_key_values=torch.rand((1, 1, 28, 32)),
918918
token_type_ids=torch.ones((1, 1), dtype=torch.int64),
919919
cache_position=torch.ones((1,), dtype=torch.int64),
920920
),
921921
dict(
922922
input_ids=torch.ones((1, 1), dtype=torch.int64),
923-
attention_mask=torch.ones((1, 284), dtype=torch.int64),
923+
attention_mask=torch.ones((1, 30), dtype=torch.int64),
924924
position_ids=torch.ones((1, 1), dtype=torch.int64),
925-
past_key_values=torch.rand((1, 1, 283, 32)),
925+
past_key_values=torch.rand((1, 1, 29, 32)),
926926
token_type_ids=torch.ones((1, 1), dtype=torch.int64),
927927
cache_position=torch.ones((1,), dtype=torch.int64),
928928
),
929929
]
930930

931931
model = Model()
932932
observer = InputObserver(
933-
value_if_missing=dict(pixel_values=torch.empty((0, 3, 896, 896)))
933+
value_if_missing=dict(pixel_values=torch.empty((0, 3, 112, 112)))
934934
)
935935
with observer(model):
936936
for kwargs in inputs:
@@ -948,6 +948,132 @@ def forward(
948948
"cache_position": {0: cst},
949949
}
950950
self.assertEqual(expected, shapes)
951+
kwargs = observer.infer_arguments()
952+
self.assertEqual(list(expected), list(kwargs))
953+
self.assertEqual((0, 3, 112, 112), kwargs["pixel_values"].shape)
954+
955+
def test_infer_dynamic_shapes_missing_args(self):
956+
class Model(torch.nn.Module):
957+
def forward(
958+
self,
959+
input_ids=None,
960+
pixel_values=None,
961+
attention_mask=None,
962+
past_key_values=None,
963+
):
964+
return input_ids
965+
966+
inputs = [
967+
(
968+
torch.ones((1, 28), dtype=torch.int64),
969+
torch.ones((1, 3, 112, 112), dtype=torch.int64),
970+
torch.ones((1, 28), dtype=torch.int64),
971+
),
972+
(
973+
torch.ones((1, 1), dtype=torch.int64),
974+
None,
975+
torch.ones((1, 29), dtype=torch.int64),
976+
torch.rand((1, 1, 28, 32)),
977+
),
978+
(
979+
torch.ones((1, 1), dtype=torch.int64),
980+
None,
981+
torch.ones((1, 30), dtype=torch.int64),
982+
torch.rand((1, 1, 29, 32)),
983+
),
984+
]
985+
986+
model = Model()
987+
observer = InputObserver(
988+
value_if_missing={1: torch.empty((0, 3, 112, 112), dtype=torch.int64)}
989+
)
990+
with observer(model):
991+
for args in inputs:
992+
model(*args)
993+
994+
shapes = observer.infer_dynamic_shapes(set_batch_dimension_for=True)
995+
cst = torch.export.Dim.DYNAMIC
996+
expected = ({0: cst, 1: cst}, {0: cst}, {0: cst, 1: cst}, {0: cst, 2: cst})
997+
self.assertEqual(expected, shapes)
998+
args = observer.infer_arguments()
999+
self.assertEqual(len(expected), len(args))
1000+
self.assertEqual((0, 3, 112, 112), args[1].shape)
1001+
1002+
def test_infer_dynamic_shapes_missing_kwargs_nested(self):
1003+
class Model(torch.nn.Module):
1004+
def forward(
1005+
self,
1006+
input_ids=None,
1007+
pixel_values=None,
1008+
attention_mask=None,
1009+
position_ids=None,
1010+
past_key_values=None,
1011+
token_type_ids=None,
1012+
cache_position=None,
1013+
):
1014+
return input_ids
1015+
1016+
inputs = [
1017+
dict(
1018+
input_ids=torch.ones((1, 28), dtype=torch.int64),
1019+
pixel_values=(
1020+
torch.ones((1, 3, 112, 112), dtype=torch.int64),
1021+
torch.ones((1, 3, 112, 112), dtype=torch.int64),
1022+
),
1023+
attention_mask=torch.ones((1, 28), dtype=torch.int64),
1024+
position_ids=torch.ones((1, 28), dtype=torch.int64),
1025+
token_type_ids=torch.ones((1, 28), dtype=torch.int64),
1026+
cache_position=torch.ones((28,), dtype=torch.int64),
1027+
),
1028+
dict(
1029+
input_ids=torch.ones((1, 1), dtype=torch.int64),
1030+
attention_mask=torch.ones((1, 29), dtype=torch.int64),
1031+
position_ids=torch.ones((1, 1), dtype=torch.int64),
1032+
past_key_values=torch.rand((1, 1, 28, 32)),
1033+
token_type_ids=torch.ones((1, 1), dtype=torch.int64),
1034+
cache_position=torch.ones((1,), dtype=torch.int64),
1035+
),
1036+
dict(
1037+
input_ids=torch.ones((1, 1), dtype=torch.int64),
1038+
attention_mask=torch.ones((1, 30), dtype=torch.int64),
1039+
position_ids=torch.ones((1, 1), dtype=torch.int64),
1040+
past_key_values=torch.rand((1, 1, 29, 32)),
1041+
token_type_ids=torch.ones((1, 1), dtype=torch.int64),
1042+
cache_position=torch.ones((1,), dtype=torch.int64),
1043+
),
1044+
]
1045+
1046+
model = Model()
1047+
observer = InputObserver(
1048+
value_if_missing=dict(
1049+
pixel_values=(
1050+
torch.empty((0, 3, 112, 112), dtype=torch.int64),
1051+
torch.empty((0, 3, 112, 112), dtype=torch.int64),
1052+
)
1053+
)
1054+
)
1055+
with observer(model):
1056+
for kwargs in inputs:
1057+
model(**kwargs)
1058+
1059+
shapes = observer.infer_dynamic_shapes(set_batch_dimension_for=True)
1060+
cst = torch.export.Dim.DYNAMIC
1061+
expected = {
1062+
"input_ids": {0: cst, 1: cst},
1063+
"pixel_values": ({0: cst}, {0: cst}),
1064+
"attention_mask": {0: cst, 1: cst},
1065+
"position_ids": {0: cst, 1: cst},
1066+
"past_key_values": {0: cst, 2: cst},
1067+
"token_type_ids": {0: cst, 1: cst},
1068+
"cache_position": {0: cst},
1069+
}
1070+
self.assertEqual(expected, shapes)
1071+
kwargs = observer.infer_arguments()
1072+
self.assertEqual(list(expected), list(kwargs))
1073+
self.assertIsInstance(kwargs["pixel_values"], tuple)
1074+
self.assertEqual(2, len(kwargs["pixel_values"]))
1075+
self.assertEqual((0, 3, 112, 112), kwargs["pixel_values"][0].shape)
1076+
self.assertEqual((0, 3, 112, 112), kwargs["pixel_values"][1].shape)
9511077

9521078
def test_io_captured_kwargs_kwargs(self):
9531079
class Model(torch.nn.Module):

_unittests/ut_investigate/test_input_observer_transformers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,9 @@ def forward(
280280

281281
model = Model()
282282
observer = InputObserver(
283-
value_if_missing=dict(pixel_values=torch.empty((0, 3, 896, 896)))
283+
value_if_missing=dict(
284+
pixel_values=torch.empty((0, 3, 896, 896), dtype=torch.int64)
285+
)
284286
)
285287
with (
286288
register_additional_serialization_functions(patch_transformers=True),

onnx_diagnostic/investigate/input_observer.py

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def _infer_dynamic_dimensions(
8787
unique_ranks = {len(shape) for shape in shape_list}
8888
torch._check(
8989
len(unique_ranks) == 1,
90-
lambda: "all shapes in shape_list must have the same rank",
90+
lambda: f"All shapes in shape_list must have the same rank but {shape_list=}.",
9191
)
9292
rank = unique_ranks.pop()
9393
dynamic = []
@@ -129,7 +129,6 @@ def __init__(
129129
self.args = args
130130
self.kwargs = kwargs
131131
self.flat_list, self.spec = torch.utils._pytree.tree_flatten((args, kwargs))
132-
self.n_tensors = sum(t is not None for t in self.flat_list)
133132
self._position_to_args_kwargs: list[int | str] | None = None
134133
self._n_tensors_for_args_kwargs: dict[int | str, int] | None = None
135134
self.cst_kwargs = cst_kwargs.copy()
@@ -290,7 +289,7 @@ class InputObserverInfo:
290289
to be the same in the ordered dictionaries `add_inputs` receive.
291290
default_values: Default values defined by the signature of the function,
292291
any value equal to that is ignored to simplify the export.
293-
value_if_missing: If a named argument (in kwargs) is missing,
292+
value_if_missing: If an argument is missing,
294293
a default value will be taken in this dictionary,
295294
this is used when after the prefill step, an argument
296295
disappears (such as `pixel_values`) and another one
@@ -308,7 +307,7 @@ def __init__(
308307
self,
309308
signature_names: list[str],
310309
default_values: dict[str, int | bool | str | float],
311-
value_if_missing: dict[str, Any],
310+
value_if_missing: dict[str | int, Any],
312311
args_name_and_position: tuple[str, int] | None,
313312
kwargs_name: str | None,
314313
):
@@ -351,21 +350,47 @@ def add_inputs(self, args: tuple[Any, ...], kwargs: dict[str, Any]):
351350

352351
# adds value_if_missing attributes
353352
for k, v in self.value_if_missing.items():
354-
if k not in kwargs:
355-
# Validate that `value_if_missing` keys are compatible
356-
# with the observed signature.
357-
# If the function does not accept **kwargs,
358-
# all value_if_missing keys must be
359-
# present in the observed signature names.
360-
if k not in self.signature_names and not self.kwargs_name:
353+
if isinstance(k, str):
354+
if k not in kwargs:
355+
# Validate that `value_if_missing` keys are compatible
356+
# with the observed signature.
357+
# If the function does not accept **kwargs,
358+
# all value_if_missing keys must be
359+
# present in the observed signature names.
360+
if k not in self.signature_names and not self.kwargs_name:
361+
raise ValueError(
362+
f"Unexpected keyword argument {k!r} "
363+
f"provided as a value_if_missing input "
364+
"for a function that does not accept it. "
365+
f"All value_if_missing keys must "
366+
f"be in the observed signature: {tuple(self.signature_names)}."
367+
)
368+
kwargs[k] = v
369+
elif isinstance(k, int):
370+
if k >= len(self.signature_names):
361371
raise ValueError(
362-
f"Unexpected keyword argument '{k}' "
372+
f"Unexpected keyword argument {k=} "
363373
f"provided as a value_if_missing input "
364374
"for a function that does not accept it. "
365-
f"All value_if_missing keys must "
375+
f"All value_if_missing indices must "
366376
f"be in the observed signature: {tuple(self.signature_names)}."
367377
)
368-
kwargs[k] = v
378+
if k >= len(args):
379+
raise NotImplementedError(
380+
f"Unexpected keyword argument {k=} "
381+
f"provided as a value_if_missing input "
382+
"for a function that does not accept it. "
383+
f"All value_if_missing indices must "
384+
f"be in the observed signature: {tuple(self.signature_names)}, "
385+
f"only {len(args)} were given."
386+
)
387+
list_args = list(args)
388+
list_args[k] = v
389+
args = tuple(list_args)
390+
else:
391+
raise TypeError(
392+
f"Unexepcted type {type(k)} for a missing value. The key is {k!r}."
393+
)
369394

370395
# kwargs may come in a different order each time.
371396
# dictionaries are ordered and torch.export.export expects
@@ -793,7 +818,7 @@ class InputObserver:
793818
export arguments.
794819
795820
Args:
796-
value_if_missing: If a named argument (in kwargs) is missing,
821+
value_if_missing: If an argument is missing,
797822
a default value will be taken in this dictionary,
798823
this is used when after the prefill step, an argument
799824
disappears (such as `pixel_values`) and another one
@@ -877,7 +902,7 @@ class InputObserver:
877902
:ref:`l-plot-gemma3-tiny-export-input-observer`.
878903
"""
879904

880-
def __init__(self, value_if_missing: dict[str, Any] | None = None):
905+
def __init__(self, value_if_missing: dict[str | int, Any] | None = None):
881906
self.info: InputObserverInfo | None = None # type: ignore[annotation-unchecked]
882907
self.value_if_missing = value_if_missing or {}
883908

0 commit comments

Comments
 (0)