Skip to content

Commit 2852bda

Browse files
cccclaifacebook-github-bot
authored andcommitted
move delegate debug tools to sdk (#4459)
Summary: Pull Request resolved: #4459 It happens a few times for the version mismatch for numpy and pandas and resulting in to_backend api failure. The version issue may not be resolved soon and pandas dependency isn't really needed by the `to_backend` api. Move the related code to sdk ``` File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/backends/mediatek/preprocess.py", line 18, in <module> from executorch.exir.backend.backend_details import ( File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/exir/__init__.py", line 9, in <module> from executorch.exir.capture import ( File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/exir/capture/__init__.py", line 9, in <module> from executorch.exir.capture._capture import ( File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/exir/capture/_capture.py", line 17, in <module> from executorch.exir.program import ExirExportedProgram File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/exir/program/__init__.py", line 10, in <module> from executorch.exir.program._program import ( File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/exir/program/_program.py", line 17, in <module> from executorch.exir.backend.backend_api import to_backend File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/exir/backend/backend_api.py", line 19, in <module> from executorch.exir.backend.utils import ( File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/executorch/exir/backend/utils.py", line 15, in <module> import pandas as pd File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/pandas/__init__.py", line 22, in <module> from pandas.compat import is_numpy_dev as _is_numpy_dev # pyright: ignore # noqa:F401 File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/pandas/compat/__init__.py", line 25, in <module> from pandas.compat.numpy import ( File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/pandas/compat/numpy/__init__.py", line 4, in <module> from pandas.util.version import Version File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/pandas/util/__init__.py", line 2, in <module> from pandas.util._decorators import ( # noqa:F401 File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/pandas/util/_decorators.py", line 14, in <module> from pandas._libs.properties import cache_readonly File "/home/riandymdn/.conda/envs/executorch/lib/python3.10/site-packages/pandas/_libs/__init__.py", line 13, in <module> from pandas._libs.interval import Interval File "pandas/_libs/interval.pyx", line 1, in init pandas._libs.interval ValueError: numpy.dtype size changed, may indicate binary incompatibility. Expected 96 from C header, got 88 from PyObject ``` Reviewed By: Olivia-liu Differential Revision: D60426829 fbshipit-source-id: 04501de212911eb46786aaf7ea3da97a60cf3e1e
1 parent 2f8ecf3 commit 2852bda

File tree

7 files changed

+307
-232
lines changed

7 files changed

+307
-232
lines changed

exir/backend/test/test_utils.py

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,20 @@
66

77
import unittest
88

9-
import pandas as pd
10-
119
import torch
1210
from executorch import exir
1311
from executorch.exir import to_edge
1412
from executorch.exir.backend.backend_api import to_backend
1513
from executorch.exir.backend.partitioner import Partitioner, PartitionResult
1614
from executorch.exir.backend.test.op_partitioner_demo import AddMulPartitionerDemo
1715
from executorch.exir.backend.utils import (
18-
DelegationBreakdown,
1916
format_delegated_graph,
2017
get_delegates,
21-
get_delegation_info,
2218
get_non_lowered_nodes,
2319
is_identical_graph,
2420
)
2521

2622
from executorch.exir.dialects._ops import bind_pattern_to_op, ops as exir_ops
27-
from pandas.testing import assert_frame_equal
2823
from torch.export import export, ExportedProgram
2924
from torch.fx import symbolic_trace
3025
from torch.fx.passes.utils.matcher_utils import SubgraphMatcher
@@ -277,65 +272,3 @@ def forward(self, a, x, b):
277272
graph_str,
278273
"Expect to see the aten.mm in the delegated graph",
279274
)
280-
281-
def test_get_delegation_info(self):
282-
class Model(torch.nn.Module):
283-
def __init__(self):
284-
super().__init__()
285-
286-
def forward(self, a, x, b):
287-
y = torch.mm(a, x)
288-
z = y + b
289-
a = z - a
290-
y = torch.mm(a, x)
291-
z = y + b
292-
return z
293-
294-
m = Model()
295-
inputs = (torch.randn(2, 2), torch.randn(2, 2), torch.randn(2, 2))
296-
edge = to_edge(torch.export.export(m, inputs)).to_backend(
297-
AddMulPartitionerDemo()
298-
)
299-
delegation_info = get_delegation_info(edge.exported_program().graph_module)
300-
301-
self.assertEqual(delegation_info.num_delegated_subgraphs, 2)
302-
self.assertEqual(delegation_info.num_delegated_nodes, 4)
303-
self.assertEqual(delegation_info.num_non_delegated_nodes, 3)
304-
expected_delegation_by_op_dict = {
305-
"aten_add_tensor": DelegationBreakdown(
306-
op_type="aten_add_tensor", delegated=2, non_delegated=0
307-
),
308-
"aten_mm_default": DelegationBreakdown(
309-
op_type="aten_mm_default", delegated=2, non_delegated=0
310-
),
311-
"aten_sub_tensor": DelegationBreakdown(
312-
op_type="aten_sub_tensor", delegated=0, non_delegated=1
313-
),
314-
"getitem": DelegationBreakdown(
315-
op_type="getitem", delegated=0, non_delegated=2
316-
),
317-
}
318-
self.assertEqual(
319-
delegation_info.delegation_by_operator, expected_delegation_by_op_dict
320-
)
321-
322-
self.assertIn(
323-
"Total delegated subgraphs",
324-
delegation_info.get_summary(),
325-
)
326-
327-
df = delegation_info.get_operator_delegation_dataframe()
328-
expected_df = pd.DataFrame(
329-
{
330-
"op_type": [
331-
"aten_add_tensor",
332-
"aten_mm_default",
333-
"aten_sub_tensor",
334-
"getitem",
335-
"Total",
336-
],
337-
"occurrences_in_delegated_graphs": [2, 2, 0, 0, 4],
338-
"occurrences_in_non_delegated_graphs": [0, 0, 1, 2, 3],
339-
}
340-
)
341-
assert_frame_equal(expected_df, df)

exir/backend/utils.py

Lines changed: 0 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66

77
import logging
88
import operator
9-
import re
109
from collections import defaultdict
11-
from dataclasses import asdict, dataclass
1210
from functools import lru_cache
1311
from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
1412

15-
import pandas as pd
1613
import torch
1714
from executorch.exir.backend.backend_details import ExportedProgram
1815
from executorch.exir.backend.canonical_partitioners.duplicate_constant_node_pass import (
@@ -30,11 +27,6 @@
3027
T_QuantPerTensor = exir_ops.edge.quantized_decomposed.quantize_per_tensor.default
3128
T_DQuantPerTensor = exir_ops.edge.quantized_decomposed.dequantize_per_tensor.default
3229

33-
# Column names of the DataFrame returned by DelegationInfo.get_operator_delegation_dataframe()
34-
# which describes the summarized delegation information grouped by each operator type
35-
_OCCURRENCES_IN_DELEGATED_GRAPHS = "occurrences_in_delegated_graphs"
36-
_OCCURRENCES_IN_NON_DELEGATED_GRAPHS = "occurrences_in_non_delegated_graphs"
37-
3830

3931
log: logging.Logger = logging.getLogger(__name__)
4032

@@ -291,163 +283,6 @@ def get_delegates(graph: torch.fx.Graph) -> List[torch.fx.Node]:
291283
]
292284

293285

294-
@dataclass
295-
class DelegationBreakdown:
296-
"""
297-
DelegationBreakdown contains the number of delegated and non-delegated nodes
298-
of the operator type op_type.
299-
300-
Args:
301-
delegated: The number of delegated nodes.
302-
non_delegated: The number of non-delegated nodes.
303-
"""
304-
305-
op_type: str = ""
306-
delegated: int = 0
307-
non_delegated: int = 0
308-
309-
310-
@dataclass
311-
class DelegationInfo:
312-
"""
313-
DelegationInfo contains information of a delegated graph module.
314-
315-
Args:
316-
num_delegated_subgraphs: The number of delegated subgraphs.
317-
num_delegated_nodes: The number of delegated nodes.
318-
num_non_delegated_nodes: The number of non-delegated nodes.
319-
delegation_by_operator: A dictionary of operator type to DelegationBreakdown.
320-
"""
321-
322-
num_delegated_subgraphs: int
323-
num_delegated_nodes: int
324-
num_non_delegated_nodes: int
325-
delegation_by_operator: Dict[str, DelegationBreakdown]
326-
327-
def get_summary(self) -> str:
328-
"""
329-
Get a summary of the delegation information in string format.
330-
331-
Args:
332-
None
333-
334-
Returns:
335-
A string containing information of some class attributes for easy print-out.
336-
"""
337-
338-
# Assemble and return the summary string
339-
summary_str = f"Total delegated subgraphs: {self.num_delegated_subgraphs}\n"
340-
summary_str += f"Number of delegated nodes: {self.num_delegated_nodes}\n"
341-
summary_str += (
342-
f"Number of non-delegated nodes: {self.num_non_delegated_nodes}\n"
343-
)
344-
return summary_str
345-
346-
def get_operator_delegation_dataframe(self) -> pd.DataFrame:
347-
"""
348-
Get the delegation information grouped by operator type in a pandas DataFrame.
349-
350-
Args:
351-
None
352-
353-
Returns:
354-
Returns a pandas DataFrame containing the following columns:
355-
- op_type: The operator type, with the last row being "Total".
356-
- occurrences_in_delegated_graphs: The number of occurrences of the op_type in delegated subgraphs.
357-
- occurrences_in_non_delegated_graphs: The number of occurrences of the op_type not in delegated subgraphs.
358-
With the last row being the total number of delegated and non-delegated occurrences of each op_type.
359-
"""
360-
361-
# Convert the dict to a dataframe
362-
list_of_dicts = [
363-
asdict(breakdown) for breakdown in self.delegation_by_operator.values()
364-
]
365-
df = pd.DataFrame(list_of_dicts)
366-
# Rename columns for better understandability
367-
df = df.rename(
368-
columns={
369-
"delegated": _OCCURRENCES_IN_DELEGATED_GRAPHS,
370-
"non_delegated": _OCCURRENCES_IN_NON_DELEGATED_GRAPHS,
371-
}
372-
)
373-
df = df.sort_values(by="op_type", ignore_index=True)
374-
375-
# Add a Total row at the bottom
376-
total_delegated_nodes = df[_OCCURRENCES_IN_DELEGATED_GRAPHS].sum()
377-
total_non_delegated_nodes = df[_OCCURRENCES_IN_NON_DELEGATED_GRAPHS].sum()
378-
df.loc[len(df)] = ["Total", total_delegated_nodes, total_non_delegated_nodes]
379-
380-
return df
381-
382-
383-
def get_delegation_info(
384-
graph_module: torch.fx.GraphModule,
385-
) -> DelegationInfo:
386-
"""
387-
Util function to get the delegation information of the given graph module.
388-
389-
Args:
390-
graph_module: The lowered graph module to get the delegation information from.
391-
392-
Returns:
393-
Return a DelegationInfo object containing the delegation information.
394-
"""
395-
396-
def _get_op_type(node_name: str) -> str:
397-
# node_name is in format <op_type> or <op_type>_x in which x is an integer suffix.
398-
return re.sub(r"_[\d]+$", "", node_name)
399-
400-
op_occurrences_dict = defaultdict(lambda: DelegationBreakdown())
401-
402-
def _insert_op_occurrences_dict(node_name: str, delegated: bool) -> None:
403-
op_type = _get_op_type(node_name)
404-
op_occurrences_dict[op_type].op_type = op_type
405-
if delegated:
406-
op_occurrences_dict[op_type].delegated += 1
407-
else:
408-
op_occurrences_dict[op_type].non_delegated += 1
409-
410-
delegated_subgraph_counter = 0
411-
412-
lowered_module_dict = {
413-
node.name: getattr(graph_module, node.name)
414-
for node in graph_module.graph.nodes
415-
if node.op == "get_attr" and node.name.startswith("lowered_module_")
416-
}
417-
418-
for node in graph_module.graph.nodes:
419-
if (
420-
node.op == "call_function"
421-
and _get_op_type(node.name) != "executorch_call_delegate"
422-
):
423-
# Non-delegated node
424-
_insert_op_occurrences_dict(node_name=node.name, delegated=False)
425-
# Check if the node is a lowered module
426-
if node.op == "get_attr" and node.name.startswith("lowered_module_"):
427-
lowered_module = lowered_module_dict[node.name]
428-
delegated_subgraph_counter += 1
429-
for node_in_lowered_module in lowered_module.original_module.graph.nodes:
430-
if node_in_lowered_module.op == "call_function":
431-
# Delegated node
432-
_insert_op_occurrences_dict(
433-
node_name=node_in_lowered_module.name, delegated=True
434-
)
435-
436-
# Calculate the total number of delegated and non-delegated nodes
437-
num_delegated_nodes = 0
438-
num_non_delegated_nodes = 0
439-
for value in op_occurrences_dict.values():
440-
num_delegated_nodes += value.delegated
441-
num_non_delegated_nodes += value.non_delegated
442-
443-
return DelegationInfo(
444-
num_delegated_nodes=num_delegated_nodes,
445-
num_non_delegated_nodes=num_non_delegated_nodes,
446-
num_delegated_subgraphs=delegated_subgraph_counter,
447-
delegation_by_operator=op_occurrences_dict,
448-
)
449-
450-
451286
def print_delegated_graph(graph_module: torch.fx.GraphModule) -> None:
452287
"""
453288
Print the formatted graph string.

sdk/backend_debug/TARGETS

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")
2+
3+
oncall("executorch")
4+
5+
runtime.python_library(
6+
name = "delegation_info",
7+
srcs = [
8+
"__init__.py",
9+
"delegation_info.py",
10+
],
11+
visibility = [
12+
"//executorch/...",
13+
"//executorch/exir/backend/...",
14+
"//executorch/test/...",
15+
"@EXECUTORCH_CLIENTS",
16+
],
17+
deps = [
18+
"fbsource//third-party/pypi/pandas:pandas",
19+
"//caffe2:torch",
20+
"//executorch/exir:lowered_backend_module",
21+
"//executorch/exir/backend/canonical_partitioners:duplicate_constant_node_pass",
22+
],
23+
)

sdk/backend_debug/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
from executorch.sdk.backend_debug.delegation_info import (
8+
DelegationBreakdown,
9+
get_delegation_info,
10+
)
11+
12+
__all__ = ["DelegationBreakdown", "get_delegation_info"]

0 commit comments

Comments
 (0)