Migrate bnb4, io_datatype_converter, dynamic_to_fixed_shape, and static_llm passes to onnx_ir#2563
Migrate bnb4, io_datatype_converter, dynamic_to_fixed_shape, and static_llm passes to onnx_ir#2563justinchuby wants to merge 15 commits into
Conversation
…s to onnx_ir - OnnxBnb4Quantization: traverse/replace MatMul->MatMulBnb4 nodes via onnx_ir instead of the ORT MatMulBnb4Quantizer proto wrapper, keeping the native quantize_matmul_bnb4 kernel for FP4/NF4 packing. Preserves graph output names. - OnnxIODataTypeConverter: reimplement input/output Cast insertion and rewiring on onnx_ir. - DynamicToFixedShape: add IR-native fix_dim_params_ir/fix_input_shapes_ir helpers in common.py (native reimpl of ORT make_dim_param_fixed/make_input_shape_fixed/ remove_invalid_dim_values over ir.Graph incl. subgraphs); output shape inference still uses ORT SymbolicShapeInference. Proto helpers kept for static_llm. - Add OpType.MatMulBnb4 and OpType.Cast. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aphs() The IR shape-fixing helpers are only used by DynamicToFixedShape, so move them out of common.py into the pass module. Replace the custom subgraph iterator with ir.Model.graphs() for traversal. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@copilot Update olive/passes/onnx/static_llm.py to use onnx-ir completely. Remove any import/usage of OnnxModel from onnxruntime, and update / recreate the logic using onnx-ir / onnxscript rewriter instead |
There was a problem hiding this comment.
Pull request overview
This PR continues Olive’s ONNX pass migration from onnx.ModelProto-based graph edits to onnx_ir, aligning these passes with the newer IR-based weight-only quantization pattern already used elsewhere in the codebase.
Changes:
- Migrates
OnnxBnb4Quantizationto traverse/modify graphs viaonnx_irand replaceMatMulwithMatMulBnb4. - Reimplements
OnnxIODataTypeConverterinput/outputCastinsertion + rewiring usingonnx_ir. - Reimplements
DynamicToFixedShapedimension fixing overir.Model(including subgraph traversal), while keeping ORTSymbolicShapeInferencefor output shape inference.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/passes/onnx/test_bnb_quantization.py | Updates the test to use IR loading (load_ir_model) for MatMul discovery. |
| olive/passes/onnx/io_datatype_converter.py | Moves I/O Cast insertion and consumer/provider rewiring to onnx_ir. |
| olive/passes/onnx/dynamic_to_fixed_shape.py | Rebuilds dim-param and input-shape fixing logic on ir.Model, with ORT-based inference preserved. |
| olive/passes/onnx/bnb_quantization.py | Migrates BNB4 MatMul quantization to IR and performs node/value replacement in the IR graph. |
| olive/constants.py | Adds OpType.MatMulBnb4 and OpType.Cast to support the migrated passes. |
Done.
Verified the shape-fixing parity and external-data sharing (both components load, pass |
|
@copilot error: |
|
... Root cause: in Note: I have the fix committed locally, but the push is being rejected by a repository ruleset on |
@copilot can you try again |
|
... Fixed in |
@copilot is onnx-shape-inference failing on certain nodes that led to missing output shapes? Can you isolate the issue and let me know? |
I isolated it.
For this path, the So this is an incomplete-shape-metadata case on that subgraph boundary, not |
|
I now get please fix and validate all |
|
Fixed in
Validation run:
|
|
There is |
|
... Fixed in
Implemented in |
| ) | ||
| onnx.save(model_2, model_2_path) | ||
|
|
||
| output_model = ComposeOnnxModels._get_composed_model([model_1_path, model_2_path], tmp_path / "output.onnx", {}) |
Verify that each split's context_i and iterator_i models reference the same shared transformer_i.onnx.data external file (mmapped once for both prompt and token-gen) instead of writing per-component duplicates. Guards against a regression where routing the pipeline save through a helper that rewrites the external-data path would fork the shared weights into context_i.onnx.data / iterator_i.onnx.data. Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
ir.external_data.load_to_model eagerly materializes every external tensor into memory. Neither of these two paths needs it: * OnnxBnb4Quantization: reads each MatMul weight lazily via const_value.numpy() (ExternalTensor reads on demand) and writes new quantized initializers; the remaining lazy tensors are read from the still-present source file when the output is saved to a different path. * StaticLLM._run_qnn_gpu: only edits shape metadata (fix_shape) and saves to a fresh model.onnx.data under a distinct output directory, so the lazy references stay valid at save time. Removing the eager materialization lowers peak memory for large models with no behavior change. Mirrors the earlier DynamicToFixedShape removal. Existing bnb and static_llm tests pass (19). Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
Same rationale as bnb: each pass reads MatMul weights lazily via const_value.numpy() and writes new quantized initializers, saving to a distinct output path. The remaining lazy tensors are read from the still- present source at save time, so eagerly materializing the whole model up front is unnecessary and only inflates peak memory. onnx_ir tensors are read-only whether or not load_to_model is called (verified), so this changes no behavior; the quant kernels already copy (.float()/.copy()/new buffers) before use. rtn/kquant/hqq tests pass (21). Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
| ir_model = model.load_ir_model() | ||
| # load_ir_model() references external data lazily; materialize it so the model can be | ||
| # re-saved into a fresh external data file under the output directory | ||
| ir.external_data.load_to_model(ir_model) |
There was a problem hiding this comment.
if the external data is not loaded, during save at 224, will the original external data be resaved to external_data_file.name? this external data resave is needed here since the model with new shapes is being saved in a new output directory.
otherwise, we could also resave the model first like in line 141 and then fix shape + save the .onnx file again?
There was a problem hiding this comment.
Oh ok. In that case we should have copied the data over? Or did you still want the reference to be shared?
There was a problem hiding this comment.
Also for the npu scenario with two models sharing the same external data, does the model save just save the same external data twice or it just saves the .onnx file with the initializers pointing to whatever file name is in the info?
asking this since the preferred behavior is we resave the model with weights to the new location first (it takes advantage of hardlinks when possible), and then we save the static shapes .onnx files to this new location that references the previously resaved external weights.
There was a problem hiding this comment.
I see. I can discuss more offline. Recently onnx harden the checks to ban all links due to the security reports.
Describe your changes
Continues the ongoing migration of ONNX passes from
onnx.ModelProtoAPIs toonnx_ir(following the pattern established by the weight-only quantizersrtn/hqq/kquant). Migrates four self-contained graph-transform passes:OnnxBnb4Quantization(bnb_quantization.py): Traverses the graph withonnx_irand replacesMatMulnodes withMatMulBnb4nodes viair.convenience.replace_nodes_and_values, instead of going through the ORTMatMulBnb4Quantizerproto wrapper. The nativequantize_matmul_bnb4kernel is retained for the FP4/NF4 bit-packing (that packing is defined by the kernel). Graph output names are preserved, consistent with the other weight-only quant passes.OnnxIODataTypeConverter(io_datatype_converter.py): Reimplements the input/outputCastinsertion and consumer rewiring ononnx_ir.DynamicToFixedShape(dynamic_to_fixed_shape.py): Reimplements ORT'smake_dim_param_fixed/make_input_shape_fixed/remove_invalid_dim_valuesnatively overir.Model(traversing subgraphs viair.Model.graphs()). Output shape inference uses theonnx-shape-inferencepackage (infer_symbolic_shapes), which runs directly on their.Modelwith no proto round-trip and refines shapes in place, replacing the earlier ORTSymbolicShapeInference(viair.to_proto).StaticLLM(static_llm.py): Now operates entirely onir.Model. Allonnx.ModelProto/onnx.load/onnx.saveusage is removed in favor ofir.load/ONNXModelHandler.load_ir_model()andir.save.fix_shaperuns on the IR model and reuses the IR-basedfix_dim_paramsfromdynamic_to_fixed_shape, removing the indirect dependency on onnxruntime'smake_dim_param_fixed/onnx_model_utils(output shape inference uses theonnx-shape-inferencepackage, as with the other migrated passes). The external-data sharing between the context and iterator component models is preserved by lazily loading the intermediate model so both fixed-shape outputs reference the sametransformer*.onnx.datafile.Also adds
OpType.MatMulBnb4andOpType.Casttoolive/constants.py, adds an IR-basedadd_version_metadata_to_ir_modelhelper tocommon.py, and removes the now-unused proto-basedfix_dim_params/fix_input_shapes/_fix_output_shapeshelpers fromcommon.py(they were the only remaining callers of the onnxruntimeonnx_model_utilsshape helpers, andstatic_llm.pyno longer depends on them). Addsonnx-shape-inference>=0.2.0torequirements.txt.Other remaining proto-based ONNX passes (e.g.
mixed_precision,mixed_precision_overrides,add_metadata, and external-tool wrappers likeaimet/inc/nvmo/transformer_optimization/optimum) are intentionally left as-is: they are thin wrappers over third-party proto APIs where an IR migration would only force an IR→proto→IR round-trip with no benefit.Checklist before requesting a review
lintrunner -aExisting pass tests (
test_bnb_quantization.py,test_io_datatype_converter.py,test_dynamic_to_fixed_shape.py) all pass (27 total). Verified end-to-end with ONNX Runtime inference, exact parity against the ORT reference for shape fixing (including subgraph recursion), andonnx.checkervalidity. Forstatic_llm.py, verified shape-fixing parity on a symbolic-dim model and that the context/iterator external-data sharing is preserved (both components load, passonnx.checker, and carryolive_versionmetadata). No new public config/behavior, so not user-facing.(Optional) Issue link