Skip to content

Latest commit

 

History

History
65 lines (53 loc) · 3.66 KB

debug-backend-delegate.md

File metadata and controls

65 lines (53 loc) · 3.66 KB

Debugging Delegation

We provide a list of util functions to give users insights on what happened to the graph modules during the to_backend() stage.

Get delegation summary

The get_delegation_info() method provides a summary of what happened to the model after the to_backend() call:

from executorch.devtools.backend_debug import get_delegation_info
from tabulate import tabulate

# ... After call to to_backend(), but before to_executorch()
graph_module = edge_manager.exported_program().graph_module
delegation_info = get_delegation_info(graph_module)
print(delegation_info.get_summary())
df = delegation_info.get_operator_delegation_dataframe()
print(tabulate(df, headers="keys", tablefmt="fancy_grid"))

Example printout:

Total  delegated  subgraphs:  86
Number  of  delegated  nodes:  473
Number  of  non-delegated  nodes:  430
op_type occurrences_in_delegated_graphs occurrences_in_non_delegated_graphs
0 aten__softmax_default 12 0
1 aten_add_tensor 37 0
2 aten_addmm_default 48 0
3 aten_arange_start_step 0 25
...
23 aten_view_copy_default 170 48
...
26 Total 473 430

From the table, the operator aten_view_copy_default appears 170 times in delegate graphs and 48 times in non-delegated graphs. Users can use information like this to debug.

Visualize delegated graph

To see a more detailed view, use the format_delegated_graph() method to get a str of printout of the whole graph or use print_delegated_graph() to print directly:

from executorch.exir.backend.utils import format_delegated_graph
graph_module = edge_manager.exported_program().graph_module
print(format_delegated_graph(graph_module)) # or call print_delegated_graph(graph_module)

It will print the whole model as well as the subgraph consumed by the backend. The generic debug function provided by fx like print_tabular() or print_readable() will only show call_delegate but hide the the subgraph consumes by the backend, while this function exposes the contents inside the subgraph.

In the example printout below, observe that embedding and add operators are delegated to XNNPACK while the sub operator is not.

%aten_unsqueeze_copy_default_22 : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.unsqueeze_copy.default](args = (%aten_arange_start_step_23, -2), kwargs = {})
  %aten_unsqueeze_copy_default_23 : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.unsqueeze_copy.default](args = (%aten_arange_start_step_24, -1), kwargs = {})
  %lowered_module_0 : [num_users=1] = get_attr[target=lowered_module_0]
    backend_id: XnnpackBackend
    lowered graph():
      %aten_embedding_default : [num_users=1] = placeholder[target=aten_embedding_default]
      %aten_embedding_default_1 : [num_users=1] = placeholder[target=aten_embedding_default_1]
      %aten_add_tensor : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.add.Tensor](args = (%aten_embedding_default, %aten_embedding_default_1), kwargs = {})
      return (aten_add_tensor,)
  %executorch_call_delegate : [num_users=1] = call_function[target=torch.ops.higher_order.executorch_call_delegate](args = (%lowered_module_0, %aten_embedding_default, %aten_embedding_default_1), kwargs = {})
  %aten_sub_tensor : [num_users=1] = call_function[target=executorch.exir.dialects.edge._ops.aten.sub.Tensor](args = (%aten_unsqueeze_copy_default, %aten_unsqueeze_copy_default_1), kwargs = {})