# Copyright (c) Meta Platforms, Inc. and affiliates.# All rights reserved.## This source code is licensed under the BSD-style license found in the# LICENSE file in the root directory of this source tree.importcopyimportloggingfromcontextlibimportcontextmanagerfromfunctoolsimportsingledispatchfromtypingimportGenerator,List,Typeimporttorchfromexecutorch.exir.backend.backend_detailsimportBackendDetails,PreprocessResultfromexecutorch.exir.backend.compile_spec_schemaimportCompileSpecfromexecutorch.exir.backend.partitionerimport(Partitioner,PartitionResult,TPartitioner,)fromexecutorch.exir.backend.utilsimportis_identical_graphfromexecutorch.exir.delegateimportexecutorch_call_delegate,get_lowered_module_namefromexecutorch.exir.graph_moduleimportget_control_flow_submodulesfromexecutorch.exir.lowered_backend_moduleimport(_get_new_signature,create_exported_program_from_submodule,create_submodule_from_nodes,LoweredBackendModule,)fromexecutorch.exir.pass_baseimportExportPassfromtorch.exportimportExportedProgram
[docs]@singledispatchdefto_backend(args):""" A generic function the dispatch happens on the type of the first argument. There are currently to overloaded to_backend function: Note: Python is dynamically-typed language and therefore cannot have proper method overloading as that requires the language to be able to discriminate between types at compile-time. @to_backend.register will attach the function to to_backend() base on the type of the first argument (type annotation is required). However, it can't take multiple types as arguments. :: def to_backend( backend_id: str, edge_graph_module: ExportedProgram, compile_specs: List[CompileSpec], ) -> LoweredBackendModule: def to_backend( graph_module: torch.fx.GraphModule, partitioner: Type[TPartitioner], ) -> torch.fx.GraphModule """pass
@to_backend.registerdef_(backend_id:str,edge_program:ExportedProgram,compile_specs:List[CompileSpec],)->LoweredBackendModule:""" Add overloaded implementations for to_backend: :: def to_backend( backend_id: str, edge_program: ExportedProgram, compile_specs: List[CompileSpec], ) -> LoweredBackendModule: Requires the passed in exported program in Edge dialect to be executed in the backend identified by backend_id. The forward method of the given edge_graph_module will be targeted for execution. Args: backend_id: The backend identifier. exported_program: An exported program in Edge dialect to target for lowering to the backend. compile_specs: A list of backend-specific objects with static metadata to configure the "compilation" process (e.g. it could be another dictionary itself). Returns: LoweredBackendModule: A Module that has been lowered to the target backend. Internally, the lowered Module contains these special attributes: backend_id (str: backend id), __processed_module__ (str: a compiled module) compile_spec, original_module (original exported program) Raises: NotImplementedError: The backend is not implemented (e.g. it was not found). This exception is derived from RuntimeError and should be caught accordingly. RuntimeError: The module cannot be processed by the backend. """assertisinstance(edge_program,ExportedProgram)# All backend implementation are final, so we don't need to consider nested subclasses.forclsinBackendDetails.__subclasses__():ifbackend_id==cls.__name__:copied_edge_program=copy.deepcopy(edge_program)preprocess_result:PreprocessResult=cls.preprocess(copied_edge_program,compile_specs,)lowered_module=LoweredBackendModule(edge_program=edge_program,backend_id=backend_id,processed_bytes=preprocess_result.processed_bytes,compile_specs=compile_specs,)lowered_module.meta={"debug_handle_map":preprocess_result.debug_handle_map}returnlowered_moduleraiseNotImplementedError(f"Backend {backend_id} was not found.")_ENABLE_VALIDATION:bool=Truedefdisable_validation()->None:"""Disables validation"""global_ENABLE_VALIDATION_ENABLE_VALIDATION=False@contextmanagerdefvalidation_disabled()->Generator[None,None,None]:""" Disables checking functions (ex. if the partitioned graph is identical to the original graph). This context manager should only be used in certain scenarios (such as when it has been profiled that checks are taking too long, and are not necessarily needed) """global_ENABLE_VALIDATIONexisting_setting=_ENABLE_VALIDATIONdisable_validation()try:yieldfinally:_ENABLE_VALIDATION=existing_settingdef_partition_and_lower(tagged_graph_module:torch.fx.GraphModule,partition_result:PartitionResult,owning_program:ExportedProgram,)->torch.fx.GraphModule:fortag,delegation_specinpartition_result.partition_tags.items():# Create partition with nodes containing this tag. There should only be# one contained submodule per tagnode_list=[]fornodeintagged_graph_module.graph.nodes:ifnode.meta.get("delegation_tag","")==tag:node_list.append(node)iflen(node_list)==0:logging.debug(f"Did not find any nodes for tag {tag}")continuelogging.debug(f"For tag {tag}, found nodes {node_list}")# Tag the nodes that are params as buffers, so we can order the submodule as (Parms + Buffers) (User Inputs)submodule,call_module_node=create_submodule_from_nodes(tagged_graph_module,node_list,tag)tagged_graph_module_output_node=[nodefornodeintagged_graph_module.graph.nodesifnode.op=="output"]submodule_output_node=[nodefornodeinsubmodule.graph.nodesifnode.op=="output"]# Copy the output node meta from the original output node, because create_submodule_from_nodes doesn't cover the meta fieldsubmodule_output_node[0].meta=tagged_graph_module_output_node[0].metalogging.debug(f"Partitioned graph module: {tagged_graph_module}")submodule_program=create_exported_program_from_submodule(submodule,owning_program)lowered_submodule=to_backend(delegation_spec.backend_id,submodule_program,delegation_spec.compile_specs,)# call delegate args should only use user_inputscall_delegate_args=[]forinpincall_module_node.all_input_nodes:ifinp.nameinsubmodule_program.graph_signature.user_inputs:call_delegate_args.append(inp)# Replace the partitioned submodule with a lowered submodule# Add call_method node with function "forward"withtagged_graph_module.graph.inserting_before(call_module_node):lowered_name=get_lowered_module_name(tagged_graph_module,lowered_submodule)lowered_node=tagged_graph_module.graph.get_attr(lowered_name)call_delegate_node=tagged_graph_module.graph.call_function(executorch_call_delegate,(lowered_node,)+tuple(call_delegate_args),call_module_node.kwargs,)call_delegate_node.meta["debug_handle"]=len(tagged_graph_module.graph.nodes)call_module_node.replace_all_uses_with(call_delegate_node)tagged_graph_module.graph.erase_node(call_module_node)# Delete all parameters/buffers consumed by the created exported programtoplevel_signature=owning_program.graph_signaturefornodeintagged_graph_module.graph.nodes:# Find placeholders consumed by the delegateifnode.op!="placeholder"orlen(node.users)!=0:continueifnode.nameintoplevel_signature.inputs_to_buffers:# Delete the consumed buffersbuffer_name=toplevel_signature.inputs_to_buffers.pop(node.name)toplevel_signature.buffers.remove(buffer_name)owning_program.state_dict.pop(buffer_name)tagged_graph_module.graph.erase_node(node)elifnode.nameintoplevel_signature.inputs_to_parameters:# Delete the consumed parametersparam_name=toplevel_signature.inputs_to_parameters.pop(node.name)toplevel_signature.parameters.remove(param_name)owning_program.state_dict.pop(param_name)tagged_graph_module.graph.erase_node(node)tagged_graph_module.recompile()# Recursively partition and lower for submodulesforname,submod,_nodeinget_control_flow_submodules(tagged_graph_module):partitioned_submodule=_partition_and_lower(submod,partition_result,owning_program)tagged_graph_module.add_module(name,partitioned_submodule)# Run the export pass over the graph module so that the call delegate# nodes will match Edge dialect# TODO(angelayi): ExportPass will rerun the graph, however all we need# here is to add metadata to the call delegate nodes to preserve Edge# dialect. There's work going on to generate a random tensor from a# fake tensor and possibly it can help to address the issue.res=ExportPass()(tagged_graph_module)assertresisnotNonetagged_graph_module=res.graph_modulereturntagged_graph_module@to_backend.registerdef_(edge_program:ExportedProgram,partitioner:Type[TPartitioner],)->ExportedProgram:""" Add overloaded implementations for to_backend: :: def to_backend( edge_program: ExportedProgram, partitioner: Type[TPartitioner], ) -> ExportedProgram: Returns a semantically-equivalent program to the one given as input (represented as a graph module in Edge dialect), but with portions of the program targeted for delegation as determined by the partitioner. Args: ExportedProgram: Program in Edge dialect. partitioner: An instance of the Partitioner class type, in charge with tagging portions of the input program for delegation. A valid partitioner must have partition_tags: Dict[str, DelegationSpec], where each key is a tag name and the nodes with same tag will be fused a one subgraph and delegated to backend specififed in delegation spec. Returns: ExportedProgram: The input program, with some portions targeted for delegation. """copied_edge_program=copy.deepcopy(edge_program)# Call the partitioner on the given graph modulepartitioner_instance:Partitioner=partitioner()partitioner_result=partitioner_instance(copied_edge_program)tagged_exported_program=partitioner_result.tagged_exported_program# Check that the partitioner did not modify the original graphif_ENABLE_VALIDATION:assertis_identical_graph(tagged_exported_program.graph_module,edge_program.graph_module,),f"The partitioner {partitioner} should not modify the graph module"else:logging.warning("Disabled validating the partitioner.")assert(partitioner_result.partition_tagsisnotNone),f"Partitioner {partitioner} needs a `partition_tags` field containing a mapping of tags to delegate spec"tagged_graph_module=_partition_and_lower(tagged_exported_program.graph_module,partitioner_result,edge_program)# TODO(angelayi): Update this signature in a less manual way (maybe through# retracing)new_signature,new_state_dict=_get_new_signature(edge_program,tagged_graph_module)returnExportedProgram(tagged_graph_module,tagged_graph_module.graph,new_signature,copy.deepcopy(edge_program.call_spec),new_state_dict,copy.deepcopy(edge_program.range_constraints),copy.deepcopy(edge_program.equality_constraints),copy.deepcopy(edge_program.module_call_graph),)
Docs
Access comprehensive developer documentation for PyTorch
To analyze traffic and optimize your experience, we serve cookies on this site. By clicking or navigating, you agree to allow our usage of cookies. As the current maintainers of this site, Facebook’s Cookies Policy applies. Learn more, including about available controls: Cookies Policy.