Repository navigation
Exporters: run exported models on their backend's runtime (ModelRunner) - #50032
IlyasMoutawwakil wants to merge 74 commits into
Conversation
…t/save_artifact `export()` and `export_for_generation()` return an `ExportArtifacts`, a mapping of `ExportedComponent`s (the backend's program and the metadata recorded while tracing it), saved with `save_pretrained`. Backends implement `export_artifact` and `save_artifact`, and register through `register_backend`. The configs drop their saving fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n names `ExportArtifacts.from_pretrained` loads an export from a local directory or the Hub (only the files the manifest names), through each backend's `load_artifact`; `push_to_hub` and `save_pretrained(push_to_hub=True)` upload it. ONNX and OpenVINO record their final graph's input and output names in the metadata. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`.xml` / `.bin` don't name their format, so OpenVINO components save as `openvino_<name>.xml`, as optimum-intel's do. Other backends keep `<name>.<ext>`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`ExportFormat` is a `StrEnum`, so configs serialize as they are and custom backends use plain strings. The registry lookup splits into `get_export_config_class` and `get_exporter_class`. The metadata drops the fields that described the traced program rather than the saved graph. Overlapping tests are merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nst the Hub `export_program` / `save_program` / `load_program`, `program_files` and `.program` replace the `artifact` names, which now only mean the `ExportArtifacts` container. A loaded ONNX graph doesn't read its weights; calling it runs the saved file in ONNX Runtime. `get_exporter_class` / `get_export_config_class` are public. Hub tests move to `test_hub.py`, with staging tests pushing and loading a real export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cuTorch A loaded ONNX program reads its graph without the weights, and a call runs the saved file in ONNX Runtime. `num_user_outputs` and `constant_inputs` are recorded again for Dynamo and ExecuTorch, whose saved programs bind positionally; ONNX and OpenVINO drop them, as they describe the traced program rather than those saved graphs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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. Pages changed in this PR: |
echarlaix
left a comment
There was a problem hiding this comment.
Very cool work @IlyasMoutawwakil
…cord the export config Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… through one path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A runner per backend (`exporters/runtimes/`) runs a program as an export returns it or a load reads it back. `ExportArtifacts.runtime()` picks how an export runs and returns an `ExportedModel`, called like the model it came from; `AutoExportedModel.from_pretrained` loads any saved export to run. The per-model forward export tests run through the runners, and the ExecuTorch test helper keeps only its skip policy around the runner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cuTorch runner A dynamic export returns a static cache's positions as integers, which the model keeps inside its cache rather than returning as tensors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…toExportedModel and can_generate Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ExportedModel, AutoExportedModel)ExportedModel)
75f27de to
7de7cb1
Compare
…ore loading it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inding) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e back (save_pretrained, push_to_hub) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Python scalar kwarg (`output_router_logits=False`) is a constant of the graph, not an input: binding it shifted every later input by one slot. OpenVINO's `infer` already copies its outputs out of the request's buffers, so the extra copy is dropped, and inputs are shared instead of copied in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dict keys in a saved spec ExecuTorch has no string value, and a program's string constant (`vision_feature_select_strategy="default"`) takes `None` in its slot. A cache's pytree context keeps a dict with non-string keys as pairs, since JSON reads `EncoderDecoderCache.is_updated`'s int keys back as strings and the saved input spec no longer matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and session options `ExportArtifacts.runtime(device, **runner_kwargs)` hands the options to the backend's runner: an ONNX Runtime `providers` list (TensorRT with its options, say) and `session_options`. ONNX Runtime sizes its thread pool from every core of the machine rather than the ones the process may run on, which `intra_op_num_threads` fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mpile properties, share outputs on request On CPU, OpenVINO otherwise keeps a stateful cache in uint8 and may run fp32 in bf16 where the CPU has it: inference and KV cache now follow the export's dtype, tuned for latency, and `runtime(config=...)` passes compile properties over those defaults (the uint8 cache, say). `share_outputs` lets a caller that reads each call's outputs before the next take views of the request's buffers instead of copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onfig lookup Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # src/transformers/exporters/auto.py # src/transformers/exporters/metadata.py
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ExportedModel)ModelRunner)
…ata; import without torch - _patch_sdpa is back to #50029's: the boolean-mask change is #50309's, with more - ExportedModel.dtype reads the metadata instead of building every runner - the bf16 convolution fix keeps its torch ops local, so the module imports without torch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… imports without it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…opies
`patch_16bit_model` upcast the norms and other small 16-bit tensors through `.data`, which keeps a
view's 16-bit base: `torch.export` replays the view when it fakifies the parameters and fails
("size(-1) must be divisible by 2 to view BFloat16 as Float"). A loaded checkpoint holds such views
(EmbeddingGemma2's `layer_scalar`, 504 tensors), so the OpenVINO export of the bf16 model failed while
a randomly initialized one passed. Each slot now holds an f32 copy for the trace (one per tensor, so
shared tensors stay shared) and gets its original tensor back afterwards.
…nt indices exports as Gather - An ONNX session opened with `enable_cuda_graph` / `trt_cuda_graph_enable` replays CUDA graphs, one per input-shape signature (`gpu_graph_id`): each keeps its input buffers and the outputs ONNX Runtime allocated on its first run. - A mask indexed by constant grids exports as a `GatherND`, whose CUDA kernel breaks graph capture (microsoft/onnxruntime#33875): it is now a `Gather` over the flattened indexed dimensions, so static Llama, BERT, T5 and GPT-2 exports capture. - The runner maps dtypes with onnx_ir instead of a torch.onnx internal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…time # Conflicts: # docs/source/en/exporters.md # docs/source/en/exporters_quantization.md # docs/source/en/main_classes/exporters.md # src/transformers/exporters/auto.py # src/transformers/exporters/base.py # src/transformers/exporters/exporter_dynamo.py # src/transformers/exporters/exporter_executorch.py # src/transformers/exporters/exporter_onnx.py # src/transformers/exporters/exporter_openvino.py # src/transformers/exporters/metadata.py # tests/exporters/test_export.py # tests/exporters/test_hub.py # tests/exporters/test_quantization.py # tests/exporters/test_runtime.py # tests/exporters/test_save_load.py
… its temporary directory goes
`onnx_ir.external_data.load_to_model` loads only the initializers, so the quantized graph's `Constant`
values kept pointing at `quantized.onnx.data` in the deleted temporary directory: the saved export
referenced a file that no longer exists, and loading it in a fresh process failed ("External data path
validation failed for initializer"). Running it in the exporting process hid it. Each external tensor
attribute is now loaded into memory too.
… OpenVINO's 16-bit view buffers - A quantized static export whose mask indices ONNX Runtime's quantizer wrote externally saves and loads. - A bf16 model whose buffers are views (a loaded EmbeddingGemma2's clipping bounds) exports to OpenVINO and runs. - One input-binding helper in the ONNX runner; `_load_external_tensor_attributes` names what it loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A static export's inputs keep one shape, and capture needs a static export: the runner only ever held one graph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
quick heads up, since the approvals a few things changed api-wise:
also fixed a few things the cpu benchmark surfaced (ort quantizer constants, openvino bf16 view buffers), each with a regression test, and benchmarks are updated (fp16, dynamic + static w/ cuda graphs) and fixed what the onnx + openvino export sweeps through the runners turned up (non-tensor outputs, onnx bool outputs, onnx outputs returning an input as is, openvino broadcasts to 0, openvino bicubic antialias resizing, executorch precomputed kwargs, |
… runner compiles for `patch_16bit_model` traces the float32 upcast, so the metadata recorded on the trace said float32 and the runner compiled every bf16 OpenVINO graph to run in float32. The metadata now takes the model's precision once the patch has given it back. EmbeddingGemma2 bf16, 8 x 128 tokens, 53 Xeon cores: 99 -> 48 ms static, 118 -> 70 ms dynamic, min cosine of the pooled embeddings to eager 0.9999. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keep the metadata's dtype the graph's The metadata's dtype is what the graph takes and returns, which the generation runtime builds caches and casts inputs in: for a 16-bit OpenVINO export, the float32 of the trace. The model's own precision is recorded on the OpenVINO model instead, as the `runtime_options` it compiles with (`INFERENCE_PRECISION_HINT`, `KV_CACHE_PRECISION`), saved with it; the runner no longer sets them, and `config` still goes over them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plugin keeps in 16-bit OpenVINO's frontend converts `aten.expand` (`repeat_kv` for grouped-query attention) into a bidirectional `Broadcast`, which the CPU plugin runs, when the target shape is computed, as a multiply by a broadcast float32 tensor in reference code. A numpy broadcast to the shape the bidirectional one produces, the elementwise maximum of the target and input shapes, gives the same result in the graph's precision. Dynamic-shape EmbeddingGemma2 in bf16, 8 x 128 tokens on 53 Xeon cores: 73 -> 52 ms, bit-identical outputs. The decode fusion tests still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ONNX Runtime's CPU provider has no bfloat16 kernels. Measured against bf16 eager, OpenVINO and ONNX Runtime CUDA stay within 0.0024 on these models. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nVINO graphs An expand that adds leading dimensions has a target with more entries than its input's rank, so the elementwise maximum of the two shapes failed OpenVINO's shape inference and the export with it. Only a broadcast whose target and input have the same rank becomes numpy now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…docs and comments A replay copies each input into the buffer the graph was captured with, which broadcast a smaller input silently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts come back with their classes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-path sweep failures Rebuild outputs that aren't tensors (`None` hidden states, spatial sizes): the export records them as `out_constants`, and the traced scalar kwargs as `in_constants`. ONNX bool outputs read back as bool, an output returning an input as is no longer renames that input, the OpenVINO numpy broadcast uses the exact bidirectional shape, and ExecuTorch keeps the precomputed kwargs in the caller's inputs. Output wrapping, name disambiguation, the feeds, the post-conversion quantize step, cummax/cummin and the assert dropping now live once in `exporters.utils`; one dotted-path importer; one `module_dtype`. The metadata keeps no I/O name lists (the specs and the programs hold them) and names its precision and device `export_dtype` / `export_device`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…resizing in OpenVINO graphs `openvino_feed` read the batch off the first input, a sliding cache's 0-d counter for HyperCLOVAX's language model; it now reads the cache's own batch axis. The OpenVINO `interpolate` patch built antialiased bicubic weights with the bilinear triangle filter; it now uses torch's cubic kernel (UnlimitedOcr's position embeddings). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…time # Conflicts: # src/transformers/exporters/exporter_onnx.py
What does this PR do?
Stacked on #50029, which adds
ExportedModel. This PR makes it run: an export is called like the model it came from, on its backend's runtime.Why
ModelOutputclass as the source model, caches included.How
exporters/runners/runner_<backend>.py), registered withregister_backend(exporter, runner).from_pretrained(..., device=...)builds them while loading..to(device, **runner_kwargs)rebuilds them elsewhere or with backend options:providersandsession_optionsfor ONNX Runtime,configandtorch_threadsfor OpenVINO.from_pretrainedtakes download options as named parameters, asPreTrainedModel.from_pretraineddoes, and runner options as kwargs, checked before any program file is downloaded.enable_cuda_graph,trt_cuda_graph_enable) replays one: its inputs and outputs keep fixed device buffers. Capture needs a static export.torch.export's input and output specs, so a runner rebuilds the model's output structure, and loading registers the cache classes it names.ExportedProgram.module()forwardAlso fixed: a
GatherNDat constant indices (the attention mask's) exports as aGather, since ONNX Runtime's CUDAGatherNDbreaks graph capture (microsoft/onnxruntime#33875); ONNX input names recorded before a lazily initialized cache grows; bfloat16 convolutions run in float32 for ONNX (no bfloat16Convbefore opset 22); a saved program keepsEncoderDecoderCache.is_updated's int keys; 16-bit OpenVINO exports compile in their own precision, recorded on the model, instead of fp32 (bf16 EmbeddingGemma2 on CPU: 99 → 48 ms static);repeat_kv's expand broadcasts in numpy mode, which OpenVINO's CPU plugin keeps in 16-bit (73 → 52 ms dynamic); a bf16 model whose buffers are views exports to OpenVINO; a quantized ONNX export keeps its large constants when saved.Performance
EmbeddingGemma2 text embeddings, 8 sentences of 128 tokens, like for like: dynamic exports against
torch.compile(dynamic=True), and static ones replayed as CUDA graphs againsttorch.compile(mode="reduce-overhead"). Reproduce withexporters_runtime.py, a self-contained uv script (uv run exporters_runtime.py --device cuda [--dynamic]); its docstring has the methodology.CUDA, A100-SXM4-80GB, every row fp16. Fused rows use
OnnxConfig(ort_fusions="cuda")(#50309).Dynamic shapes:
Static shapes, replayed as CUDA graphs (ONNX Runtime's
enable_cuda_graph, TensorRT'strt_cuda_graph_enable), againsttorch.compile(mode="reduce-overhead"):CPU, Xeon Platinum 8570, 53 physical cores of one socket, every row bf16.
Dynamic shapes:
Static shapes, against
torch.compile(dynamic=False):Tests
test_runtime.py: every backend called fresh and loaded, against eager; runner options and their refusals; CUDA graph replay; caches loaded in a fresh process; the decode's static cache through the runner.test_save_load.py: ONNX exports from the Hub cache, in bfloat16 and past 2GB, run through the runtime.🤖 Generated with Claude Code