-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Introduce FXGraphExtractor into torch.onnx.dynamo_export #99940
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
Introduce FXGraphExtractor into torch.onnx.dynamo_export #99940
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/99940
Note: Links to docs will display an error until the docs builds have been completed. ⏳ No Failures, 1 PendingAs of commit 640a5c7: This comment was automatically generated by Dr. CI and updates every 15 minutes. |
bdaba31
to
4d9c912
Compare
3f92a81
to
db4e8b9
Compare
torch/onnx/_internal/exporter.py
Outdated
|
||
# Private only attributes | ||
decomposition_table: Dict[torch._ops.OpOverload, Callable] | ||
"""Dictionary with all ATen operators supported by the exporter.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docstring not accurate, more like
Dictionary with ATen/Prims operators and their decompositions. Exporter runs decomposition based on this table after getting the FX graph from FXGraphExtractor. If not provided, the default decomposition table is based on the full PyTorch decomposition table, excluding Prims decomposition, and any ATen operator decomposition if the ATen operator has a matching OnnxFunction in torch_lib.
And based on the level of internal details, not sure why it needs to be exposed in this PR. Unless it is needed urgently for Prims experiments. cc @justinchuby. Ideally a much more natural api emerges after the dispatcher refactor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not publicly exposed; ResolvedExportOptions
is internal-only. This serves as an internal extension point and it is a natural Dynamo concept.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using docstring from https://github.com/thiagocrepaldi/pytorch/blob/c8bf6f3c699bfd9833e9b9b65a1c39878f1ad440/torch/_dynamo/eval_frame.py#L705
"""A dictionary that maps operators to their decomposition functions."""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is similar to the docstring for backend
of DynamoOptimize
that we discussed earlier. Both need code rework to match the same concept of its original fx/dynamo counterpart, which is what stated in the docstring. I won't block on this, will revisit in dispatcher theme.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get why """A dictionary that maps operators to their decomposition functions."""
doesn't match the same concept.
I understand we are not giving a full description on how it is used, but it is still a truthful explanation; we do build a decomposition_table (with whatever our registry can support) and pass it to FX APIs o consume it, as they document it they should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough. I felt we are adding it before designing the purpose and the expected outcome under exporter context, which is what dispatcher design going to do anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got your point.
not all changes here are final because the rest of the code is not final.
next redesigns may refactor some of the things done on previous refactoring until everything make sense :)
self.output_adapter: OutputAdapter = OutputAdapter() | ||
|
||
@_beartype.beartype | ||
def _export_fx_to_onnx( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels weird to be here. This class is FXGraphExtractor
, but this method finishes everything off with a onnx ModelProto. Feels this method belongs to Exporter
(or DynamoExporter(Exporter)
for the sake of import dep problems).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, there is no right place on the repo for _export_fx_to_onnx
. Next round of design can split it in several methods in their correct places, as we discussed offline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is the "fx passes" + "conversion to onnxscript" part. Again as discussed my hunch is they should be the general part of exporter. I'm just surprised that it is moved from DynamoExporter
to Extractor
. So now all the work e2e is completed in Extractor
, that feels like just a rename of the previous FXExporter
though. Anyway won't block on this too, as you mentioned there will be follow up designs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class Exporter
as proposed by Aaron is just a thread-safety container to keep user model+data+export options together so that we could run exporter in parallel (in theory). It was never meant to have hold code. The fact we did specialize and put a lot of helpers on it is the actual infraction we need to address.
_export_fx_to_onnx
does too much (passes, graph building and protobuf serialization) and not even Exporter
is the right place to it. In next steps, we will be able to break all three tasks in 3 different blocks, and neither Exporter
nor FXGraphExtractor
will need to hold any of it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree these are 3 separate tasks. In my understanding the Exporter
, instead of FXGraphExtractor
, should drive these components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, agreed. Exporter
must drive the 3 classes that are to be born.
_export_fx_to_onnx
will be gone and not inside fx_tracer very soon
torch/onnx/_internal/exporter.py
Outdated
@@ -96,6 +115,16 @@ def resolve(value: Optional[T], fallback: Union[T, Callable[[], T]]) -> T: | |||
|
|||
self.opset_version = resolve(options.opset_version, _DEFAULT_OPSET_VERSION) | |||
self.dynamic_shapes = resolve(options.dynamic_shapes, False) | |||
# TODO: Prevent circular dep + [onnx]script dep | |||
import torch.onnx._internal.fx.dynamo_graph_extractor as dynamo_graph_extractor | |||
from torch.onnx._internal.fx import ( # TODO: onnxscript is not available on Pytorch CI yet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can maybe also delay the table evaluation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ResolvedExportOptions
is only evaluated when an export has started. This is as delayed as it can be.
decomposition_table
is not evaluated when we do import torch
or import torch.onnx
, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I meant the DEAFAULT_xxx table so as to avoid requiring onnxscript when importing these lines? Just a thought. May not be a good idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that is something I also feel needs another round of refactoring. We shouldn't have code being called on global scope as decomp.py
does.
This can be tackled along with the passes
redesign, as decomposition is done as a pass today
The current API architecture can be seen as 3 independent exporters as shown below. The public API `dynamo_export()` defaults to one of the 3 variants and the other 2 must be used by instantiating private classes:  This PR refactors the API in a way that `dynamo_export` is the only way to use the ONNX exporter. A ``FXGraphExtractor`` layer was created to abstract all the FX extraction from a model out of the exporter. It defaults to a FX tracer based on ``torch.export``, but an internal-only idiom allows switching the FX tracer (aka `FXGraphExtractor` interface), as shown below:  Summary of changes: * Unifies all exporter variants under a single `dynamo_export` API * `ResolvedExportOptions` was expanded to allow `fx_tracer: FXGraphExtractor` to be specified, selecting which FX graph extractor to use, according to the design proposal * As a consequence, `torch.onnx._internal.exporter.Exporter` does not have to *internally* specialize for each type of FX API that the exporter might be used. This leads to a single `Exporter` with many `FX graph extractors` * Before in red, after in green:  * `ResolvedExportOptions` was expanded to allow `decomposition_table` to be specified as an internal-only property. * Similar to input processing, this helper is not actually consumed at tyhe `Exporter` layer. This PR moves it to the layer in which it is used * Demoted `Exporter.model_signature` to a simple standalone helper * There is no need to have this as a exporter method; this is a standard `inpect.signature` usage without any state Possible next steps are: * Decouple `passes` and `dispatching` from the cluttered `export_fx_to_onnx` * Further integration with http://github.com/pytorch/pytorch/pull/98421/ into `FXGraphExtractor` public API + helper for unit testing * Some passes are changing input processing, which are not captured by the proposed input adapter ** COPILOT SUMMARY** copilot:all
c8bf6f3
to
10d5d75
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, with separate follow-ups for dispatcher(decomposition_table) and _export_fx_to_onnx next.
10d5d75
to
7059a17
Compare
@pytorchbot merge |
Merge failedReason: Approval needed from one of the following: |
7059a17
to
9fed5a4
Compare
Signed-off-by: Thiago Crepaldi <[email protected]>
9fed5a4
to
640a5c7
Compare
@pytorchbot merge |
Merge startedYour change will be merged once all checks pass (ETA 0-4 Hours). Learn more about merging in the wiki. Questions? Feedback? Please reach out to the PyTorch DevX Team |
The current API architecture can be seen as 3 independent exporters as shown below. The public API
dynamo_export()
defaults to one of the 3 variants and the other 2 must be used by instantiating private classes:This PR refactors the API in a way that
dynamo_export
is the only way to use the ONNX exporter. It defaults to a FX tracer based ontorch.export
, but an internal-only idiom allows switching the FX tracer (akaFXGraphExtractor
interface), as shown below:Summary of changes:
dynamo_export
APIResolvedExportOptions
was expanded to allowfx_tracer: FXGraphExtractor
to be specified, selecting which FX graph extractor to use, according to the design proposaltorch.onnx._internal.exporter.Exporter
does not have to internally specialize for each type of FX API that the exporter might be used. This leads to a singleExporter
with manyFX graph extractors
Exporter
subclasses toFXGraphExtractor
subclasses, where they are actually consumedExporter
is a [data]class that holds export options, model and input data in a single cohesive object. Specializing it means create different exporters instead of having one exporter capable of exporting models through different options.Exporter
doesn't consume themodel_args
that caused it to specializeimport torch.onnx
to after all dynamo subcomponents, preventingtorch.onnx
to have circular depemndencies whentorch.XXXX
is imported during initializationdecomposition_table
as an internal-onlyResolvedExportOptions
property.Exporter
layer. This PR moves it to the layer in which it is usedExporter.model_signature
to a simple standalone helperinpect.signature
usage without any statePossible next steps are:
passes
anddispatching
from the clutteredexport_fx_to_onnx
FXGraphExtractor
public API + helper for unit testing** COPILOT SUMMARY**
🤖 Generated by Copilot at bdaba31
Summary
📝🚀🔧
This pull request refactors the ONNX exporter code to use the FX graph representation and the new
io_adapter
module for input and output adaptation. It also adds support for custom export options and flattening HuggingFace model outputs in the ONNX test framework. It updates the ONNX dynamo exporter API tests and adds a new moduletorch/onnx/_internal/fx/dynamo_graph_extractor.py
for exporting FX models to ONNX with dynamo support. It fixes some type annotations, imports, and formatting issues in the ONNX exporter code.Walkthrough
fx
submodule fromtorch/onnx/_internal
totorch/onnx/_internal/fx
, and rename some of its modules ( link, link, link, link)torch/onnx/_internal/fx/dynamo_graph_extractor.py
that defines aDynamoExport
class for generating FX graphs using thetorch._dynamo.export
API (link)torch/onnx/_internal/fx/io_adapter.py
that defines the input and output adapter classes and steps for the ONNX exporter, and a helper function to wrap models with output adapters (link, link, link, link)ResolvedExportOptions
class intorch/onnx/_internal/exporter.py
to inherit from theExportOptions
class, and to set thefx_tracer
anddecomposition_table
attributes based on thedynamo_graph_extractor
andfunction_dispatcher
modules (link, link)Exporter
class intorch/onnx/_internal/exporter.py
to remove theexport
method and add a new abstractgenerate_fx
method, and to use thefx_tracer
attribute to generate and export the FX graph (link, link)FXSymbolicTraceExporter
class intorch/onnx/_internal/fx/fx_symbolic_graph_extractor.py
to be renamed toFXSymbolicTracer
, and to inherit fromexporter.FXGraphExtractor
and implement thegenerate_fx
method (link, link)export_fx_to_onnx
method of theFXSymbolicTracer
class to be renamed to_export_fx_to_onnx
, and to be moved to theexporter.FXGraphExtractor
class (link)dynamo_export
function intorch/onnx/_internal/exporter.py
to accept and returnResolvedExportOptions
andExporter
objects, respectively (link)run_test_with_fx_to_onnx_exporter_and_onnx_runtime
function intest/onnx/onnx_test_common.py
to add a new parameterexport_options
for passing custom export options to thetorch.onnx.dynamo_export
function (link, link)test_log_sigmoid
and_test_large_scale_exporter
tests intest/onnx/test_fx_to_onnx_with_onnxruntime.py
to use the updatedrun_test_with_fx_to_onnx_exporter_and_onnx_runtime
function and thetorch.onnx.dynamo_export
function (link, link, link, link)test_raise_on_invalid_save_argument_type
test intest/onnx/dynamo/test_exporter_api.py
to use theio_adapter.InputAdapter
andio_adapter.OutputAdapter
classes instead of theexporter.InputAdapter
andexporter.OutputAdapter
classes (link)model_signature
property from theExporter
class intorch/onnx/_internal/exporter.py
to a standalone function intorch/onnx/utils.py
, and update the references to it (link, link, link)UnsatisfiedDependencyError
class from theExporter
class intorch/onnx/_internal/exporter.py
to the top level of the module, and update the references to it (link)_create_onnx_friendly_decomposition_table
function and the_ONNX_FRIENDLY_DECOMPOSITION_TABLE
dictionary intorch/onnx/_internal/fx/function_dispatcher.py
to_create_default_onnx_decomposition_table
and_DEFAULT_ONNX_EXPORTER_DECOMPOSITION_TABLE
, respectively, and update the references to them (link, link)torch/onnx/_internal/fx/function_dispatcher.py
to use thetorch._ops
andtorch._decomp
modules instead of thetorch.ops
andtorch.decomp
modules, and to use aliases for accessing theonnxscript.function_libs.torch_aten.ops
andtorch._ops
modules (link, link, link, link, link, link, link)ExportOutput
class intorch/onnx/_internal/exporter.py
to use theInputAdapter
andOutputAdapter
classes fromio_adapter
instead of the ones defined in the same module (link)torch/onnx/_internal/fx/serialization.py
andtorch/onnx/_internal/exporter.py
to fix some inconsistencies (link, link, link, link)inspect
fromtorch/onnx/_internal/exporter.py
(link)torch._dynamo
fromtorch/onnx/_internal/fx/passes/shape_inference.py
(link)torch/onnx/_internal/fx/passes/shape_inference.py
to explain why the import oftorch._dynamo
is done inside the_run
method of theShapeInferenceWithFakeTensor
class (link)_module_expansion_symbolic_trace
function intorch/onnx/_internal/fx/fx_symbolic_graph_extractor.py
(link)torch/onnx/__init__.py
for formatting purposes (link)torch/onnx/_internal/fx/__init__.py
file (link)Fixes #ISSUE_NUMBER
cc @soumith @voznesenskym @penguinwu @anijain2305 @EikanWang @jgong5 @Guobing-Chen @XiaobingSuper @zhuhaozhe @blzheng @Xia-Weiwen @wenzhe-nrv @jiayisunx @desertfire