Run the output shape matcher for delegates - #22236
Conversation
Summary:
The Inspector's shape/dtype output matcher was gated behind `num_outputs == 1`.
Dropping that one term is the whole functional change:
if (
- num_outputs == 1
- and len(runtime_intermediate_output) > 1
+ len(runtime_intermediate_output) > 1
and isinstance(aot_intermediate_output, torch.Tensor)
):
The matcher was written for multi-output aten ops (`native_layer_norm.out`,
`native_dropout.out`), where the runtime logs several tensors but AOT captured
only the primary one, and the gate confined it to exactly that. Delegates
returning more than one tensor have the same problem for a different reason and
were left on positional pairing. However, positional pairing is unsound here.
Take a fused FFN block -- five nodes in one partition, the last two escaping it:
handle node shape
10 linear_gate [1, 4, 1408]
11 linear_up [1, 4, 1408]
12 silu [1, 4, 1408]
13 mul [1, 4, 1408] escapes
14 linear_down [1, 4, 512] escapes
The delegate carries the whole tuple `(10, 11, 12, 13, 14)` as its debug handle
and returns two tensors, so `num_outputs` is 2 and the walk is:
i=0 negative_index = -1
AOT handle[-1] = 14, linear_down [1, 4, 512]
runtime outputs[-1] = mul [1, 4, 1408] mispaired
i=1 negative_index = -2
AOT handle[-2] = 13, mul [1, 4, 1408]
runtime outputs[-2] = linear_down [1, 4, 512] mispaired
Both AOT picks are the right nodes. Only the runtime side is crossed, because
the two sides index different lists:
* AOT side: `_combine_aot_overlapped_intermediate_outputs` indexes the
delegate's *debug-handle tuple* --
`last_int = runtime_debug_handle[negative_index]`.
* runtime side: `_process_single_runtime_output` indexes the delegate's
*output list* -- `negative_index = -1 * (output_index + 1)`.
The handle tuple is ordered by the backend's node serialization; the output list
by the partition's output spec. Nothing ties the two orders together, and nothing
in the debug handles records which fused node produced which output. With a
single output they trivially agree, which is why the gate hid this. With several
they usually do not, and the result is either
* a shape mismatch, raising `ValueError: Error computing SNR difference
between tensors: The size of tensor a (N) must match the size of tensor b
(M) at non-singleton dimension K`. `NumericalComparatorBase.compare` builds
its rows in a plain loop, so one bad pair aborts the entire DataFrame rather
than the offending row.
* a coincidental shape match, and a silently wrong SNR.
Letting the matcher run replaces the runtime half of each pair by content:
exactly one runtime output is `[1, 4, 512]`, so the AOT `linear_down` row takes
that one instead of `outputs[-1]`, and `mul` gets the other. Shape settles this
example; where two outputs share a shape the matcher narrows by dtype, which is
what separates the int8 and float32 tensors a quantized partition returns.
Differential Revision: D117735521
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22236
Note: Links to docs will display an error until the docs builds have been completed. ❌ 3 New FailuresAs of commit 39ab85a with merge base 41db862 ( NEW FAILURES - The following jobs have failed:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@pssrawat has exported this pull request. If you are a Meta employee, you can view the originating Diff in D117735521. |
This PR needs a
|
Summary:
The Inspector's shape/dtype output matcher was gated behind
num_outputs == 1.Dropping that one term is the whole functional change:
The matcher was written for multi-output aten ops (
native_layer_norm.out,native_dropout.out), where the runtime logs several tensors but AOT capturedonly the primary one, and the gate confined it to exactly that. Delegates
returning more than one tensor have the same problem for a different reason and
were left on positional pairing. However, positional pairing is unsound here.
Take a fused FFN block -- five nodes in one partition, the last two escaping it:
The delegate carries the whole tuple
(10, 11, 12, 13, 14)as its debug handleand returns two tensors, so
num_outputsis 2 and the walk is:Both AOT picks are the right nodes. Only the runtime side is crossed, because
the two sides index different lists:
_combine_aot_overlapped_intermediate_outputsindexes thedelegate's debug-handle tuple --
last_int = runtime_debug_handle[negative_index]._process_single_runtime_outputindexes the delegate'soutput list --
negative_index = -1 * (output_index + 1).The handle tuple is ordered by the backend's node serialization; the output list
by the partition's output spec. Nothing ties the two orders together, and nothing
in the debug handles records which fused node produced which output. With a
single output they trivially agree, which is why the gate hid this. With several
they usually do not, and the result is either
ValueError: Error computing SNR difference between tensors: The size of tensor a (N) must match the size of tensor b (M) at non-singleton dimension K.NumericalComparatorBase.comparebuildsits rows in a plain loop, so one bad pair aborts the entire DataFrame rather
than the offending row.
Letting the matcher run replaces the runtime half of each pair by content:
exactly one runtime output is
[1, 4, 512], so the AOTlinear_downrow takesthat one instead of
outputs[-1], andmulgets the other. Shape settles thisexample; where two outputs share a shape the matcher narrows by dtype, which is
what separates the int8 and float32 tensors a quantized partition returns.
Differential Revision: D117735521