[PoC] HF exporters#41992
Conversation
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
|
Currently all models (except a select few) are tested and pass the tests successfully !
skipped tests either:
|
| "qwen3_vl", # fast_pos_embed_interpolate is data-dependent | ||
| "qwen3_vl_moe", # fast_pos_embed_interpolate is highly data-dependent | ||
| "siglip2", # torch.export is failing on torch.nn.functional.interpolate | ||
| "siglip2_vision_model", # torch.export is failing on torch.nn.functional.interpolate |
There was a problem hiding this comment.
Is this the issue related to unsupported combination of parameters (e.g., antialias=True, mode=bicubic)? In such a case, I think adding a warning and falling back to a similar mode is sufficient.
There was a problem hiding this comment.
not really the issue i was seeing, dynamo is having problems with the for loops in
for example, but I haven't dug as deep as with the MoEs to not block the PR.There was a problem hiding this comment.
same for siglip2_vision_model? 👀
There was a problem hiding this comment.
ooh let me check
There was a problem hiding this comment.
the problems I see are in this loop:
there are a couple problems, the first one we see when we export is
torch.fx.experimental.symbolic_shapes.GuardOnDataDependentSymNode: Could not guard on data-dependent expression u0 > 0 (unhinted: u0 > 0) which is fixable by adding some hints on max_h and max_w.however there's a second one that pops right afterward that I'm not sure how to fix, and then there's the fact that this is all happening in data-dependent loop (static batch size).
There was a problem hiding this comment.
okay I just found the hint that was missing to fix the dynamo export ! 😁
There was a problem hiding this comment.
now lfm2_vl and siglip2 are both exportable 😁
(siglip2_vision onnx exported model however seems to have very different outputs than torch)
| MODEL_TYPES_WITH_UNSUPPORTED_CACHE_CLASS: set[str] = { | ||
| "falcon_mamba", | ||
| "jamba", | ||
| "lfm2", |
There was a problem hiding this comment.
Example of model with this cache class supported: https://huggingface.co/onnx-community/LFM2-350M-ONNX
(in case we will add it eventually)
There was a problem hiding this comment.
yes pytree support is easy to add for all of them, I'm leaving them as leftover for other PRs because this one got big enough 😭 (it was supposed to be a simple poc but well 😂)
| "mamba", | ||
| "mamba2", |
There was a problem hiding this comment.
Example of model with this cache class supported: https://huggingface.co/onnx-community/granite-4.0-h-350m-ONNX
(in case we will add it eventually)
ArthurZucker
left a comment
There was a problem hiding this comment.
Hey! Thanks a lot, most of the changes look very good to me!
- https://github.com/huggingface/transformers/pull/41992/files#diff-09c713c5a5648641bf5f40f555ae70f673976b2dab3adb1d3b7d2a358204063f and the alike are more than welcome (as long as is does not hurt perfs)
- https://github.com/huggingface/transformers/pull/41992/files#diff-a3217f302c0342c58f8d8d3bbee17e3ab29d98e73d23b4497d760a76a35cc3f0 are the ones I am bothered by. We should promote a single path. It can be a bit ugly but we want to avoid if else.
- https://github.com/huggingface/transformers/pull/41992/files#diff-0b7f8c458cd3687faa764605c5193e2d8cb9d76e08f99168a9187941d36220b0 is really against the philosophy.
For MoE we are working on removing the data dependent issues on our side
Finally for onnx, I don't think we want to handle the list of ops to patch ourselves.
It is a good start tho, I am not very closed to this if it helps making sure our exports are correct. Its something important that we have to talk about.
But for now I would isolate changes related to modeling code, bench a tad bit and if its not affecting perf we can alreaddy merge them
There was a problem hiding this comment.
this I don't mind, I think @Cyrilvallez would know a bit better
There was a problem hiding this comment.
@IlyasMoutawwakil @ArthurZucker thoughts on moving static cache export tests like these into a single inherited test under CausalLMModelTest such that all new causal LM models automatically run this test, similar to what you are doing with ModelTesterMixin? The advantage of this is that model authors who have their models pass this test will be able to run on ExecuTorch.
| "encodec", # torch.export struggles with torch.nn.functional.pad with "reflect" mode | ||
| "esm", # uses compute_tm function which data-dependent | ||
| "fastspeech2_conformer", # Even after making parts of it exportable, dynamo still struggle with the convolutions in FastSpeech2ConformerMultiLayeredConv1d | ||
| "fastspeech2_conformer_with_hifigan", # Even after making parts of it exportable, dynamo still struggle with the convolutions in FastSpeech2ConformerMultiLayeredConv1d |
There was a problem hiding this comment.
Is it a missing op or a tracer issue?
There was a problem hiding this comment.
data-dep guard error so tracer issue i guess, i haven't seen any missing ops (only ones i can think are when the model uses custom kernels or torch compile).
you can check the error by commenting the model_type from this list and running RUN_SLOW=1 pytest tests/models/{model_type}/ -k "test_torch_export" -vvvv
ArthurZucker
left a comment
There was a problem hiding this comment.
You're missing some docs! But otherwise LGTM!
ExportConfig is nice,
|
|
||
|
|
||
| # (object, attribute, factory) triples installed by patch_torch_ops. | ||
| _TORCH_PATCH_TABLE = [ |
There was a problem hiding this comment.
I like this a lot! 🤗
There was a problem hiding this comment.
most of these should be upstreamed into either torch or executorch
| # ── Stage 1: Torch patches ───────────────────────────────────────────────────── | ||
| # Monkey-patches applied during torch.export / Dynamo tracing. | ||
| # Each _patch_* function is a factory: receives the original op and returns the | ||
| # replacement, closing over the original. | ||
| # | ||
| # _TORCH_PATCH_TABLE is a list of (object, attr, factory) triples. | ||
| # patch_torch_ops installs them and restores on exit. | ||
| # | ||
| # To add a new patch: define a _patch_* factory and append to _TORCH_PATCH_TABLE. |
There was a problem hiding this comment.
probably want this in the doc as well not only the code!
| # ── Stage 2: FX graph patches ────────────────────────────────────────────────── | ||
| # Rewrite FX nodes between torch.export (stage 1) and run_decompositions. | ||
| # Each fixer: (gm, node) -> bool. Return True = node consumed, stop. | ||
| # |
There was a problem hiding this comment.
same a doc on this exporter would be nice, for potentially other PRs and explaining why we do this! i have no idea :)
| if is_tracing(): | ||
| seq_len = seq_len.item() | ||
| torch_compilable_check( |
There was a problem hiding this comment.
do we need allll those checks?
There was a problem hiding this comment.
unfortunately, idefics is a very messy model (it changes its state during forward) we can just not support export for it at this point since it's kinda old.
There was a problem hiding this comment.
actually no longer needed, it seems that the vlm decomposition i introduced makes the components easy to export (compared to exporting the entire prefill graph)
| output_hidden_states: bool | None = None, | ||
| return_dict: bool | None = None, |
There was a problem hiding this comment.
decorator is probably missing here instead
There was a problem hiding this comment.
should use decorator instead
There was a problem hiding this comment.
Non generative models won't have export tests anymore?
There was a problem hiding this comment.
they do, basically now we do a forward test for all models (through ExportTesterMixin) and a generate test for all generative models (through ExportGenerateTesterMixin)
There was a problem hiding this comment.
Pretty sure we also had some integration tests re executorch: Kind of the same comment that we might want to move forward and uses exporters and remove the executorch integration (via alias or similar)
| @deprecate_kwarg("image_grid_thw", new_name="grid_thw", version="5.14.0") | ||
| def forward( | ||
| self, pixel_values: torch.Tensor, image_grid_thw: torch.Tensor, **kwargs: Unpack[TransformersKwargs] | ||
| self, pixel_values: torch.Tensor, grid_thw: torch.Tensor, **kwargs: Unpack[TransformersKwargs] | ||
| ) -> BaseModelOutputWithPooling: | ||
| r""" | ||
| image_grid_thw (`torch.Tensor` of shape `(num_images, 3)`): | ||
| grid_thw (`torch.Tensor` of shape `(num_images, 3)`): | ||
| The temporal, height and width of feature shape of each image. | ||
| """ | ||
| embeds = self.embeddings(pixel_values).to(self.pre_layrnorm.weight.dtype) | ||
| cos, sin = self.rotary_emb(image_grid_thw, device=embeds.device, dtype=embeds.dtype) | ||
| cos, sin = self.rotary_emb(grid_thw, device=embeds.device, dtype=embeds.dtype, kwargs=kwargs) |
There was a problem hiding this comment.
Can we increase the version a bit? But otherwise fine with that
There was a problem hiding this comment.
Pull request overview
This PR introduces a native “HF exporters” subsystem inside Transformers, providing a unified export API across torch.export (Dynamo), ONNX, and ExecuTorch, along with test mixins to exercise exportability across the model test suite and initial documentation.
Changes:
- Adds a new
transformers.exporterspackage (configs, base API, backend exporters, shared utils, auto-factory). - Integrates exporter test coverage into
ModelTesterMixin/GenerationTesterMixinvia newtests/exporters/test_utils.pymixins and updates/annotates per-model exportability flags. - Extends a few core utilities needed by exporters (vision position IDs, pytree registration, availability checks, small model-side changes to accept precomputed kwargs).
Reviewed changes
Copilot reviewed 87 out of 88 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_modeling_common.py | Mixes export test coverage into ModelTesterMixin and removes legacy test_torch_export implementation. |
| tests/generation/test_utils.py | Mixes generation export tests into GenerationTesterMixin. |
| tests/exporters/test_utils.py | New exporter test mixins + skip/optimize-control registries for export sweeps. |
| tests/exporters/init.py | Declares tests.exporters package for test mixin imports. |
| tests/models/vits/test_modeling_vits.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/vilt/test_modeling_vilt.py | Adds reason comment for test_torch_exportable = False and removes redundant subclass override. |
| tests/models/videomt/test_modeling_videomt.py | Removes test_torch_exportable = False override. |
| tests/models/video_llama_3/test_modeling_video_llama_3.py | Marks VideoLlama3 vision + VLM tests as not torch-exportable with rationale. |
| tests/models/timesfm/test_modeling_timesfm.py | Removes test_torch_exportable = False override. |
| tests/models/tapas/test_modeling_tapas.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/superpoint/test_modeling_superpoint.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/superglue/test_modeling_superglue.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/slanext/test_modeling_slanext.py | Adjusts tiny config sizes and removes test_torch_exportable = False override. |
| tests/models/slanet/test_modeling_slanet.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/rwkv/test_modeling_rwkv.py | Removes test_torch_exportable = False override. |
| tests/models/reformer/test_modeling_reformer.py | Removes one test_torch_exportable override and adds rationale to another. |
| tests/models/qwen3_omni_moe/test_modeling_qwen3_omni_moe.py | Removes nested model_type from audio config in tests. |
| tests/models/qwen2_5_omni/test_modeling_qwen2_5_omni.py | Removes nested model_type from audio config in tests. |
| tests/models/qianfan_ocr/test_modeling_qianfan_ocr.py | Removes test_torch_exportable = False override. |
| tests/models/pixtral/test_modeling_pixtral.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/pi0/test_modeling_pi0.py | Removes test_torch_exportable = False override. |
| tests/models/phi4_multimodal/test_modeling_phi4_multimodal.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/pe_audio_video/test_modeling_pe_audio_video.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/parakeet/test_modeling_parakeet.py | Removes test_torch_exportable = False overrides. |
| tests/models/paddleocr_vl/test_modeling_paddleocr_vl.py | Removes nested model_type in test configs. |
| tests/models/oneformer/test_modeling_oneformer.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/omdet_turbo/test_modeling_omdet_turbo.py | Updates rationale for test_torch_exportable = False. |
| tests/models/nemotron3_5_asr/test_modeling_nemotron3_5_asr.py | Removes test_torch_exportable = False override. |
| tests/models/mra/test_modeling_mra.py | Removes test_torch_exportable = False override. |
| tests/models/mm_grounding_dino/test_modeling_mm_grounding_dino.py | Adjusts test shapes (batch size, image size). |
| tests/models/mistral3/test_modeling_mistral3.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/minicpmv4_6/test_modeling_minicpmv4_6.py | Ensures target_sizes tensor is created on torch_device. |
| tests/models/mimi/test_modeling_mimi.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/longformer/test_modeling_longformer.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/llava_onevision/test_modeling_llava_onevision.py | Removes test_torch_exportable = False override. |
| tests/models/llava_next/test_modeling_llava_next.py | Removes test_torch_exportable = False override. |
| tests/models/llava_next_video/test_modeling_llava_next_video.py | Removes test_torch_exportable = False override. |
| tests/models/lighton_ocr/test_modeling_lighton_ocr.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/lightglue/test_modeling_lightglue.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/lfm2_vl/test_modeling_lfm2_vl.py | Removes test_torch_exportable = False override. |
| tests/models/led/test_modeling_led.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/instructblipvideo/test_modeling_instructblipvideo.py | Removes test_torch_exportable = False override. |
| tests/models/instructblip/test_modeling_instructblip.py | Removes test_torch_exportable = False override. |
| tests/models/ibert/test_modeling_ibert.py | Moves/clarifies test_torch_exportable = False with updated rationale. |
| tests/models/hiera/test_modeling_hiera.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/grounding_dino/test_modeling_grounding_dino.py | Adjusts test shapes (batch size, image size). |
| tests/models/granite4_vision/test_modeling_granite4_vision.py | Removes test_torch_exportable = False override. |
| tests/models/gemma4/test_modeling_gemma4.py | Ensures boolean mask tensor is created on torch_device. |
| tests/models/gemma4_unified/test_modeling_gemma4_unified.py | Ensures boolean mask tensor is created on torch_device. |
| tests/models/funnel/test_modeling_funnel.py | Adds reason comments for test_torch_exportable = False. |
| tests/models/fastspeech2_conformer/test_modeling_fastspeech2_conformer.py | Adds reason comments for test_torch_exportable = False. |
| tests/models/evolla/test_modeling_evolla.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/esm/test_modeling_esmfold.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/eomt/test_modeling_eomt.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/eomt_dinov3/test_modeling_eomt_dinov3.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/encodec/test_modeling_encodec.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/emu3/test_modeling_emu3.py | Adds reason comment for test_torch_exportable = False. |
| tests/models/diffusion_gemma/test_modeling_diffusion_gemma.py | Removes test_torch_exportable = False override (commented rationale removed). |
| tests/models/deepseek_ocr2/test_modeling_deepseek_ocr2.py | Removes test_torch_exportable = False override. |
| tests/models/colqwen2/test_modeling_colqwen2.py | Removes test_torch_exportable = False override. |
| tests/models/clvp/test_modeling_clvp.py | Removes dataset dependency in test input creation; adjusts imports; removes test_torch_exportable override. |
| tests/models/canine/test_modeling_canine.py | Removes test_torch_exportable = False override. |
| tests/models/bridgetower/test_modeling_bridgetower.py | Removes test_torch_exportable = False override. |
| tests/models/bigbird_pegasus/test_modeling_bigbird_pegasus.py | Adjusts test sequence length. |
| tests/models/autoformer/test_modeling_autoformer.py | Updates rationale comment for test_torch_exportable = False. |
| src/transformers/vision_utils.py | Extends get_vision_position_ids with include_temporal support and refactors block-major computation. |
| src/transformers/utils/import_utils.py | Adds is_onnxscript_available, is_onnxruntime_available, is_executorch_available. |
| src/transformers/utils/generic.py | Adds flatten_with_keys_fn to ModelOutput pytree registration. |
| src/transformers/utils/init.py | Re-exports new availability helpers. |
| src/transformers/testing_utils.py | Adds require_onnxscript, require_onnxruntime, require_executorch decorators. |
| src/transformers/models/qwen3_asr/modular_qwen3_asr.py | Passes kwargs into get_audio_cu_seqlens for precomputed export inputs. |
| src/transformers/models/qwen3_asr/modeling_qwen3_asr.py | Same kwargs pass-through in generated modeling file. |
| src/transformers/models/minimax_m3_vl/modular_minimax_m3_vl.py | Reuses shared vision position ID helper, deprecates image_grid_thw kwarg, and adjusts rotary embedding math. |
| src/transformers/models/minimax_m3_vl/modeling_minimax_m3_vl.py | Same changes in generated modeling file. |
| src/transformers/integrations/sdpa_attention.py | Avoids unnecessary KV repetition when num_key_value_groups == 1. |
| src/transformers/integrations/executorch.py | Docstring wording update (“dynamo” instead of “torchdynamo”). |
| src/transformers/exporters/base.py | Introduces base HfExporter API + environment validation and generation export helper. |
| src/transformers/exporters/configs.py | Adds export config dataclasses and ExportFormat enum. |
| src/transformers/exporters/utils.py | Adds shared export utilities: patch registries, tensor helpers, input precomputers, and decomposition helpers. |
| src/transformers/exporters/exporter_dynamo.py | Implements DynamoExporter wrapper around torch.export + patching/pytree/dynamic-shape/state-reset. |
| src/transformers/exporters/exporter_executorch.py | Implements ExecutorchExporter with backend preparation, torch/executorch patching, and FX fixes. |
| src/transformers/exporters/auto.py | Adds auto-factory classes for exporters/configs (currently has runtime issues). |
| src/transformers/exporters/init.py | Exposes exporters public API. |
| src/transformers/init.py | Adds exporters to lazy import structure. |
| docs/source/en/main_classes/exporters.md | Adds API reference page for exporter classes/configs. |
| docs/source/en/exporters.md | Adds user guide for exporters usage (install, quickstart, dynamic shapes, generation). |
| docs/source/en/_toctree.yml | Adds exporters docs to the documentation TOC. |
vasqu
left a comment
There was a problem hiding this comment.
Some last comments from my side, only took a look at the tests but lmk if I should take a look at something else
Also looks like CI is failing because certain packages are not installed on CI
|
[For maintainers] Suggested jobs to run (before merge) run-slow: autoformer, bigbird_pegasus, bridgetower, canine, clvp, colqwen2, deepseek_ocr2, diffusion_gemma, emu3, encodec, eomt, eomt_dinov3, esm, evolla, fastspeech2_conformer, funnel |
CI recapDashboard: View test results in Grafana |
|
Congrats!! |
HfExporters: Native, Unified export for PyTorch / ONNX / ExecuTorch
What this adds
A native, in-Transformers export pipeline — one base class (
HfExporter), three subclasses for the runtimes we care about, one unified API:DynamoExporterExportedProgramOnnxExporterONNXProgramExecutorchExporterExecutorchProgramManagerSame call shape across all three. Dynamic shapes by default. Generation-style models split automatically into prefill + decode (+ vision/audio sub-encoders for VLMs).
Swap one line for another runtime —
DynamoExporter()/DynamoConfigorExecutorchExporter()/ExecutorchConfig(backend=...).For generative models the prefill/decode split is captured automatically:
Why native, not via Optimum
torch.exportis fast and clean but strict compared to TorchScript — data-dependentifs that TorchScript would just trace through (with a warning) become hard guard failures, hence theif not torch.compiler.is_exporting()opt-outs landing throughout modeling code. Living downstream in Optimum means every new architecture (a new attention pattern, a new cache type, a new modality) waits for someone to write export glue. By colocating the exporters with the models, the export evolves with the model and breakages surface in the model's own test file, not weeks later in a separate repo.Optimum will keep building on this — generating inputs, dynamic axes, splitting components, wiring inference. This PR is the API and the registry it builds on.
How it works
export_for_generationruns the model's realgenerate(..., max_new_tokens=2)once, hooksforwardto capture the actual prefill and decode kwargs, then exports each independently. Multimodal models decompose further into per-component sub-graphs. Decoder-only, SSM, encoder-decoder — same machinery.Multi-stage patching
Every backend follows the same pipeline (5 stages for ONNX, 4 for the others). The principle: fix exports without touching modeling code wherever possible. Each fix lives at the lowest stage that can express it cleanly.
@register_patch(backend, "torch.X.method")reversible class-attribute swap during tracing@register_fx_node_fix(backend)per-node rewriters on the post-export graph@register_fx_program_fix(backend)program-level passes on theExportedProgram_ONNX_TRANSLATION_TABLEcustom onnxscript per aten opONNXProgramTopK(sorted=1)for ORT)@register_export_input_preparer(*markers)covers a related case: precompute data-dependent kwargs (cu_seqlens, position ids, window indices, bilinear interpolation grids, audio chunking) outside the traced graph.Adding support for a new model is a function and a decorator. No new files, no plumbing, no per-backend boilerplate.
Other moving parts
dynamic=Truewalks tensors / caches / dicts / lists /ModelOutputs and assignsDim.AUTOeverywhere — no per-modeldynamic_shapesdict.Cache.__subclasses__()is walked at export time and each registered as a pytree node; model-specific cache types (anything ending inCache) get picked up too.past_key_valuesbecomes just another tensor structure to the exporter.torch.exportmay leave asFakeTensors are reset so a follow-up eager forward on the same module stays safe.**kwargs: Unpack[TransformersKwargs]is rewritten to a flat signature derived from the user's inputs — otherwisetorch.exportbundles**kwargsinto one blob that doesn't line up withdynamic_shapes.What's tested
The export tests are wired into
ModelTesterMixindirectly, so every model that uses the standard test mixin getstest_torch_export_*,test_onnx_export_*, andtest_executorch_export_*for free (forward + generation variants where relevant). That's the bulk of the model zoo — LLMs, VLMs, audio encoders, vision backbones, detection / segmentation.Skip lists in
tests/exporters/test_utils.pyare short, scoped, and each entry points to a specific upstreamtorch.export/onnxscriptissue or modeling bug we want to fix at the source — not paper over here.What's not perfect yet
export_for_generationreturns independent graphs, not a turnkey inference pipeline. Wiring them together (encoder → projector → language model → lm_head, decode loop) is downstream's job. We're actively working to reduce that glue.Reproducing
pip install -e . torch onnxscript executorch onnxruntimeThen any of the snippets above; full per-model tests: