Transformers documentation
Extending the exporters
Extending the exporters
torch.export traces the model into a graph, later stages transform
that graph, and a final stage lowers or emits it for the target runtime. Most models pass through
untouched. When there’s a PyTorch pattern the backend can’t handle, the exporter applies a
small workaround at that stage rather than editing the model.
Add a workaround by writing one function and registering it with a decorator. Each workaround belongs at the lowest stage that can express it cleanly.
Patches and fixes
A workaround is either a patch or a fix. The two differ in whether they can be reverted.
| Patch | Fix | |
|---|---|---|
| What it does | Swaps out an attribute (a torch op, an ExecuTorch internal, or a model method) for the duration of the export | Rewrites the traced graph or program |
| Reverted | Yes, the original is restored afterward | No, it repairs the artifact before the next stage runs |
| Register with | @register_patch(backend, *paths) | @register_fx_node_fix(backend) or @register_fx_program_fix(backend) |
Both live in a registry in exporters/utils.py, and the exporter installs everything registered for its backend at the right stage.
Reach for a patch when the issue is a single backend’s lowering bug: a missing ONNX translation, an ORT validation quirk, or an FX decomposition that emits a dead op. The workaround stays in the exporter, and the modeling code stays clean.
When the pattern blocks export across multiple backends, such as data-dependent loops, stateful
caches outside Cache, or hand-written split-loop attention, fix the model instead. Fixing it
once in the model helps every exporter.
Add a patch
Suppose a model method does something torch.export can’t trace. NLLB-MoE’s NllbMoeTop2Router._cast_classifier casts the classifier weights to another dtype,
which isn’t traceable. Replace it with a no-op for the duration of the export.
Write a factory that takes the original method and returns its replacement, then register the factory against the method’s dotted path:
from transformers.exporters.utils import register_patch
@register_patch("dynamo", "transformers.models.nllb_moe.modeling_nllb_moe.NllbMoeTop2Router._cast_classifier")
def _patch_classifier_cast(_original):
# Replace the untraceable dtype cast with a no-op during export.
return lambda self, *args, **kwargs: NoneThe exporter swaps the method in before tracing and restores it afterward, so the patch only affects export. A few variations:
- Pass extra paths to share one factory across call sites, for example
@register_patch("dynamo", path_a, path_b). - Patch a
torchop by pointing the path at it, for example@register_patch("onnx", "torch.where"). The factory receives the real op as its argument, so the replacement can call through to it. - Write a fix instead of a patch when you need to rewrite the graph after tracing. The mechanism is the same, a decorated function in the matching registry.
Stage reference
Each exporter’s source labels its stages as # ── Stage N: … ── comment blocks, so the file and
this reference line up. Look there for the exact ops and classes each stage handles.
DynamoExporter
The base exporter runs one patch stage and four helpers, in order, inside DynamoExporter.export (see exporter_dynamo.py).
- Forward-signature patch: gives
model.forwarda flat argument signature sotorch.exportdoesn’t bundle inputs into one**kwargstuple. This is internal and not an extension point. - Model patches: swap untraceable model methods for export-safe equivalents during tracing. Extend
with
@register_patch("dynamo", ...). - Pytree registration: register each
CacheandModelOutputsotorch.exportcan flatten and rebuild it (usually happens automatically). Add a branch to_flatten_to_context/_unflatten_from_contextfor a type the attribute walk can’t reach. - Dynamic shapes: assign
Dim.AUTOto every tensor and cache leaf whendynamic=True. Override withDynamoConfig.dynamic_shapes. - State cleanup: reset tensor attributes a model sets inside
forwardthattorch.exportleaves as fake tensors. Extend by adding the attribute name to_STATEFUL_CACHE_ATTRS.
OnnxExporter
OnnxExporter adds five stages around torch.onnx.export (see exporter_onnx.py).
Grep the file for the full list of patches:
grep -nE "^def (_patch_|_fix_|_aten_)" src/transformers/exporters/exporter_onnx.py- Torch patches: swap
torchops the ONNX exporter can’t translate as-is. Extend with@register_patch("onnx", ...). - ONNX patches: re-run the node fixes after
run_decompositionsso newly introduced shape-guard nodes get repaired before lowering. Uses the same@register_patch("onnx", ...)registry. - FX node fixes: rewrite graph nodes the ONNX exporter can’t lower, such as alias ops, in-place
views, and dead asserts. Extend with
@register_fx_node_fix("onnx"). - ONNX translations: supply a custom lowering for an aten op where the default is missing or buggy
(for example
aten.index_putoraten._grouped_mm). Add an_aten_*function to_ONNX_TRANSLATION_TABLE. - ONNX IR fixes: rewrite the finished ONNX program to work around ONNX Runtime bugs (for example
forcing
TopK(sorted=True)). Add a_fix_ir_*function to_IR_FIXES.
ExecutorchExporter
ExecutorchExporter adds five stages around to_edge_transform_and_lower and to_executorch,
starting with backend preparation (see exporter_executorch.py).
- Backend preparation: move the model to the target device and dtype and pick its partitioner
(
prepare_for_xnnpack,prepare_for_cuda). Add a backend by registeringprepare_for_<name>in_BACKEND_PREPARE. - Torch patches: replace
torchops the ExecuTorch backends can’t accept, such assplit_copy,chunk, andtopk(k>dim). Extend with@register_patch("executorch", ...). - ExecuTorch patches: swap ExecuTorch internals that crash on valid dynamic-shape graphs. Uses the
same
@register_patch("executorch", ...)registry. - FX program fixes: repair the exported program where the fix needs whole-program context, such as
widening range constraints or filling missing placeholder metadata. Extend with
@register_fx_program_fix("executorch"). - FX node fixes: rewrite individual nodes, such as mapping Python sym ops to
executorch_prim.*or rewritingpowas amulchain. Extend with@register_fx_node_fix("executorch").
Known upstream workarounds
A few model classes hit confirmed bugs in the onnxscript graph optimizer (constant folding crashing
on SplitToSequence, FPN initializers being dropped). ONNX_DISABLE_OPTIMIZE disables onnxscript optimization for those models. Each entry records the
upstream issue next to the model name. The list is expected to shrink as upstream bugs land, so a
new entry must reference a specific upstream bug rather than disable optimization arbitrarily.
EXPORT_SKIPS, opts a handful of model classes out of the export sweep entirely when the model is fundamentally non-exportable as-is (data-dependent control flow that can’t be vectorized, or modules treated as forward arguments). Each entry carries a reason naming the model-side change needed. This list is also expected to shrink, not grow.
Update on GitHub