Skip to content

Updated the comparison logic to handle sequences separately #12251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
5 changes: 3 additions & 2 deletions devtools/inspector/_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from executorch.devtools.etrecord import ETRecord, parse_etrecord
from executorch.devtools.inspector._inspector_utils import (
calculate_time_scale_factor,
compare_intermediate_outputs,
create_debug_handle_to_op_node_mapping,
DebugHandle,
display_or_print_df,
Expand Down Expand Up @@ -1415,8 +1416,8 @@ def calculate_numeric_gap(self, distance: str = "MSE") -> pd.DataFrame:
runtime_debug_handle, runtime_debug_handle_to_op_name
),
"runtime_intermediate_output": runtime_intermediate_output,
"gap": comparator.compare(
aot_intermediate_output, runtime_intermediate_output
"gap": compare_intermediate_outputs(
aot_intermediate_output, runtime_intermediate_output, comparator
),
}
)
Expand Down
47 changes: 38 additions & 9 deletions devtools/inspector/_inspector_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,32 +762,31 @@ def convert_to_float_tensor(input_data: Any) -> torch.Tensor:
This function handles the following types of input:
- Scalar (int or float): Converts to a tensor with a single element.
- Tensor: Converts to a float64 tensor on CPU.
- Sequence of Tensors: Stacks the tensors into a single float64 tensor on CPU.
The resulting tensor is detached, moved to CPU, and cast to torch.float64.
Parameters:
input_data (Any): The input data to be converted to a tensor. It can be a scalar,
a tensor, or a list of tensors.
input_data (Any): The input data to be converted to a tensor. It can be a scalar
or a tensor.
Returns:
torch.Tensor: A tensor on CPU with dtype torch.float64.
Raises:
ValueError: If the input_data cannot be converted to a tensor.
AssertionError: If the input_data is a Sequence.
"""
# Assert that the input is not a Sequence
assert not isinstance(input_data, Sequence)
try:
# Check if the input is a Sequence of tensors
if isinstance(input_data, Sequence):
input_tensor = torch.stack([convert_to_float_tensor(a) for a in input_data])
# Try to convert the input to a tensor
else:
input_tensor = torch.as_tensor(input_data, dtype=torch.float64)
input_tensor = torch.as_tensor(input_data, dtype=torch.float64)
except Exception as e:
raise ValueError(
f"Cannot convert value of type {type(input_data)} to a tensor: {e}"
)
input_tensor = input_tensor.detach().cpu().double()

input_tensor = input_tensor.detach().cpu().double()
# Convert NaN to 0.0
if torch.isnan(input_tensor).any():
input_tensor = torch.nan_to_num(input_tensor)

return input_tensor


Expand Down Expand Up @@ -837,3 +836,33 @@ def find_op_names(
result.append(op_name)

return result


def compare_intermediate_outputs(a: Any, b: Any, comparator) -> List[float]:
"""
Compare two outputs, handling both sequence and non-sequence cases,
and return a list of comparison results.
Parameters:
a: The first intermediate output to compare.
b: The second intermediate output to compare.
comparator: A comparator object with a `compare` method.
Returns:
List[float]: A list of comparison results.
Raises:
ValueError: If one input is a sequence and the other is not, or if sequences have different lengths.
"""
is_a_sequence = isinstance(a, Sequence)
is_b_sequence = isinstance(b, Sequence)
if is_a_sequence and is_b_sequence:
# Ensure both sequences have the same length
if len(a) != len(b):
raise ValueError("Sequences must have the same length for comparison.")

# Compare each element in the sequences and return the list of results
return [comparator.compare(x, y) for x, y in zip(a, b)]
elif not is_a_sequence and not is_b_sequence:
# Compare non-sequence items and return the result in a list
return [comparator.compare(a, b)]
else:
# Raise an error if one is a sequence and the other is not
raise ValueError("Both inputs must be sequences or both must be non-sequences.")
2 changes: 1 addition & 1 deletion devtools/inspector/tests/inspector_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ def test_calculate_numeric_gap(self):
)
)
# gap should equal 3.0
self.assertEqual(row["gap"], 3.0)
self.assertEqual(row["gap"][0], 3.0)

def _gen_random_float_list(self) -> List[float]:
return [random.uniform(0, 10) for _ in range(RAW_DATA_SIZE)]
Expand Down
35 changes: 23 additions & 12 deletions devtools/inspector/tests/inspector_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
calculate_mse,
calculate_snr,
calculate_time_scale_factor,
compare_intermediate_outputs,
convert_to_float_tensor,
create_debug_handle_to_op_node_mapping,
EDGE_DIALECT_GRAPH_KEY,
Expand All @@ -42,6 +43,7 @@
NodeFilter,
TimeScale,
)
from executorch.devtools.inspector.numerical_comparator import L1Comparator


class TestInspectorUtils(unittest.TestCase):
Expand Down Expand Up @@ -420,19 +422,10 @@ def test_convert_input_to_tensor_convertible_inputs(self):
)
self.assertEqual(actual_output2.device.type, "cpu")

# List of tensors -> stacked tensor float32 CPU
# List of tensors -> AssertionError
t_list = [torch.tensor([1, 2]), torch.tensor([2, 3]), torch.tensor([3, 4])]
actual_output3 = convert_to_float_tensor(t_list)
self.assertIsInstance(actual_output3, torch.Tensor)
self.assertEqual(actual_output3.dtype, torch.float64)
self.assertEqual(tuple(actual_output3.shape), (3, 2))
self.assertTrue(
torch.allclose(
actual_output3,
torch.tensor([[1.0, 2.0], [2.0, 3.0], [3.0, 4.0]], dtype=torch.float64),
)
)
self.assertEqual(actual_output3.device.type, "cpu")
with self.assertRaises(AssertionError):
convert_to_float_tensor(t_list)

def test_convert_input_to_tensor_non_convertible_raises(self):
class X:
Expand Down Expand Up @@ -566,6 +559,24 @@ def test_find_op_names_matching_handles(self):
find_op_names(debug_handle, debug_handle_to_op_name), ["op1", "op2"]
)

def test_compare_intermediate_outputs_sequences(self):
a = [1.0, 2.0, 3.0]
b = [1.0, 2.5, 3.5]
result = compare_intermediate_outputs(a, b, L1Comparator())
self.assertEqual(result, [0.0, 0.5, 0.5])

def test_compare_intermediate_outputs_diff_len_sequences(self):
a = [1.0, 2.0]
b = [1.0, 2.0, 3.0]
with self.assertRaises(ValueError):
compare_intermediate_outputs(a, b, L1Comparator())

def test_compare_intermediate_outputs_sequence_and_non_sequence(self):
a = [1.0, 2.0]
b = 1.0
with self.assertRaises(ValueError):
compare_intermediate_outputs(a, b, L1Comparator())


def gen_mock_operator_graph_with_expected_map() -> (
Tuple[OperatorGraph, Dict[int, OperatorNode]]
Expand Down
Loading