nvidia-modelopt 0.35.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modelopt/__init__.py +20 -0
- modelopt/deploy/__init__.py +16 -0
- modelopt/deploy/llm/__init__.py +30 -0
- modelopt/deploy/llm/generate.py +345 -0
- modelopt/onnx/__init__.py +34 -0
- modelopt/onnx/autocast/__init__.py +19 -0
- modelopt/onnx/autocast/__main__.py +187 -0
- modelopt/onnx/autocast/convert.py +202 -0
- modelopt/onnx/autocast/graphsanitizer.py +422 -0
- modelopt/onnx/autocast/logging_config.py +78 -0
- modelopt/onnx/autocast/nodeclassifier.py +416 -0
- modelopt/onnx/autocast/precisionconverter.py +1032 -0
- modelopt/onnx/autocast/referencerunner.py +159 -0
- modelopt/onnx/autocast/utils.py +117 -0
- modelopt/onnx/llm_export_utils/__init__.py +16 -0
- modelopt/onnx/llm_export_utils/export_utils.py +162 -0
- modelopt/onnx/llm_export_utils/quantization_utils.py +122 -0
- modelopt/onnx/llm_export_utils/surgeon_utils.py +120 -0
- modelopt/onnx/logging_config.py +77 -0
- modelopt/onnx/op_types.py +300 -0
- modelopt/onnx/quantization/__init__.py +20 -0
- modelopt/onnx/quantization/__main__.py +291 -0
- modelopt/onnx/quantization/calib_utils.py +178 -0
- modelopt/onnx/quantization/extensions.py +35 -0
- modelopt/onnx/quantization/fp8.py +375 -0
- modelopt/onnx/quantization/graph_utils.py +1479 -0
- modelopt/onnx/quantization/gs_patching.py +112 -0
- modelopt/onnx/quantization/int4.py +1347 -0
- modelopt/onnx/quantization/int8.py +283 -0
- modelopt/onnx/quantization/operators.py +121 -0
- modelopt/onnx/quantization/ort_patching.py +1766 -0
- modelopt/onnx/quantization/ort_utils.py +346 -0
- modelopt/onnx/quantization/partitioning.py +439 -0
- modelopt/onnx/quantization/qdq_utils.py +1349 -0
- modelopt/onnx/quantization/quant_utils.py +283 -0
- modelopt/onnx/quantization/quantize.py +528 -0
- modelopt/onnx/quantization/src/modelopt_round_and_pack_ext.cpp +127 -0
- modelopt/onnx/trt_utils.py +424 -0
- modelopt/onnx/utils.py +753 -0
- modelopt/torch/__init__.py +41 -0
- modelopt/torch/_deploy/__init__.py +23 -0
- modelopt/torch/_deploy/_runtime/__init__.py +29 -0
- modelopt/torch/_deploy/_runtime/common.py +72 -0
- modelopt/torch/_deploy/_runtime/ort_client.py +193 -0
- modelopt/torch/_deploy/_runtime/registry.py +83 -0
- modelopt/torch/_deploy/_runtime/runtime_client.py +154 -0
- modelopt/torch/_deploy/_runtime/tensorrt/constants.py +108 -0
- modelopt/torch/_deploy/_runtime/tensorrt/engine_builder.py +324 -0
- modelopt/torch/_deploy/_runtime/tensorrt/hw_param_config.py +51 -0
- modelopt/torch/_deploy/_runtime/tensorrt/layerwise_profiling.py +178 -0
- modelopt/torch/_deploy/_runtime/tensorrt/parse_trtexec_log.py +151 -0
- modelopt/torch/_deploy/_runtime/tensorrt/tensorrt_utils.py +200 -0
- modelopt/torch/_deploy/_runtime/trt_client.py +219 -0
- modelopt/torch/_deploy/compilation.py +142 -0
- modelopt/torch/_deploy/device_model.py +157 -0
- modelopt/torch/_deploy/profiling.py +193 -0
- modelopt/torch/_deploy/utils/__init__.py +18 -0
- modelopt/torch/_deploy/utils/onnx_optimizer.py +120 -0
- modelopt/torch/_deploy/utils/onnx_utils.py +58 -0
- modelopt/torch/_deploy/utils/torch_onnx.py +593 -0
- modelopt/torch/distill/__init__.py +28 -0
- modelopt/torch/distill/config.py +130 -0
- modelopt/torch/distill/distillation.py +84 -0
- modelopt/torch/distill/distillation_model.py +318 -0
- modelopt/torch/distill/loss_balancers.py +140 -0
- modelopt/torch/distill/losses.py +275 -0
- modelopt/torch/distill/mode.py +223 -0
- modelopt/torch/distill/plugins/__init__.py +24 -0
- modelopt/torch/distill/plugins/huggingface.py +110 -0
- modelopt/torch/distill/plugins/megatron.py +476 -0
- modelopt/torch/distill/registry.py +23 -0
- modelopt/torch/export/__init__.py +24 -0
- modelopt/torch/export/convert_hf_config.py +117 -0
- modelopt/torch/export/distribute.py +300 -0
- modelopt/torch/export/hf_config_map.py +84 -0
- modelopt/torch/export/layer_utils.py +1893 -0
- modelopt/torch/export/mcore_config_map.py +25 -0
- modelopt/torch/export/model_config.py +623 -0
- modelopt/torch/export/model_config_export.py +552 -0
- modelopt/torch/export/model_config_utils.py +392 -0
- modelopt/torch/export/model_utils.py +71 -0
- modelopt/torch/export/plugins/__init__.py +21 -0
- modelopt/torch/export/plugins/mcore_common.py +67 -0
- modelopt/torch/export/plugins/mcore_custom.py +530 -0
- modelopt/torch/export/plugins/mcore_deepseek.py +143 -0
- modelopt/torch/export/plugins/mcore_gptoss.py +71 -0
- modelopt/torch/export/plugins/mcore_llama.py +164 -0
- modelopt/torch/export/plugins/mcore_nemotron.py +90 -0
- modelopt/torch/export/plugins/mcore_qwen.py +99 -0
- modelopt/torch/export/plugins/megatron_importer.py +647 -0
- modelopt/torch/export/postprocess.py +847 -0
- modelopt/torch/export/quant_utils.py +1069 -0
- modelopt/torch/export/tensorrt_llm_type.py +42 -0
- modelopt/torch/export/tensorrt_llm_utils.py +405 -0
- modelopt/torch/export/transformer_engine.py +94 -0
- modelopt/torch/export/unified_export_hf.py +537 -0
- modelopt/torch/export/unified_export_megatron.py +1164 -0
- modelopt/torch/nas/__init__.py +27 -0
- modelopt/torch/nas/algorithms.py +856 -0
- modelopt/torch/nas/autonas.py +808 -0
- modelopt/torch/nas/conversion.py +119 -0
- modelopt/torch/nas/hparams/__init__.py +19 -0
- modelopt/torch/nas/hparams/concat.py +319 -0
- modelopt/torch/nas/hparams/container.py +48 -0
- modelopt/torch/nas/modules/__init__.py +21 -0
- modelopt/torch/nas/modules/container.py +99 -0
- modelopt/torch/nas/modules/conv.py +254 -0
- modelopt/torch/nas/modules/linear.py +76 -0
- modelopt/torch/nas/modules/norm.py +193 -0
- modelopt/torch/nas/modules/utils.py +61 -0
- modelopt/torch/nas/patch.py +261 -0
- modelopt/torch/nas/plugins/__init__.py +29 -0
- modelopt/torch/nas/plugins/megatron.py +1497 -0
- modelopt/torch/nas/plugins/torch.py +71 -0
- modelopt/torch/nas/plugins/transformer_engine.py +42 -0
- modelopt/torch/nas/plugins/transformers.py +210 -0
- modelopt/torch/nas/registry.py +23 -0
- modelopt/torch/nas/search_space.py +260 -0
- modelopt/torch/nas/traced_hp.py +149 -0
- modelopt/torch/nas/utils.py +408 -0
- modelopt/torch/opt/__init__.py +41 -0
- modelopt/torch/opt/_hooks.py +98 -0
- modelopt/torch/opt/config.py +383 -0
- modelopt/torch/opt/conversion.py +620 -0
- modelopt/torch/opt/dynamic.py +1367 -0
- modelopt/torch/opt/hparam.py +275 -0
- modelopt/torch/opt/mode.py +353 -0
- modelopt/torch/opt/plugins/__init__.py +38 -0
- modelopt/torch/opt/plugins/diffusers.py +40 -0
- modelopt/torch/opt/plugins/huggingface.py +162 -0
- modelopt/torch/opt/plugins/mcore_dist_checkpointing.py +211 -0
- modelopt/torch/opt/plugins/megatron.py +142 -0
- modelopt/torch/opt/plugins/peft.py +108 -0
- modelopt/torch/opt/plugins/transformers.py +160 -0
- modelopt/torch/opt/searcher.py +363 -0
- modelopt/torch/opt/utils.py +125 -0
- modelopt/torch/prune/__init__.py +26 -0
- modelopt/torch/prune/fastnas.py +376 -0
- modelopt/torch/prune/gradnas.py +349 -0
- modelopt/torch/prune/plugins/__init__.py +24 -0
- modelopt/torch/prune/plugins/mcore_minitron.py +263 -0
- modelopt/torch/prune/plugins/transformers.py +41 -0
- modelopt/torch/prune/pruning.py +211 -0
- modelopt/torch/quantization/__init__.py +26 -0
- modelopt/torch/quantization/algorithms.py +671 -0
- modelopt/torch/quantization/backends/__init__.py +20 -0
- modelopt/torch/quantization/backends/fp8_per_tensor_gemm.py +204 -0
- modelopt/torch/quantization/backends/gemm_registry.py +156 -0
- modelopt/torch/quantization/backends/nvfp4_gemm.py +201 -0
- modelopt/torch/quantization/backends/utils.py +28 -0
- modelopt/torch/quantization/calib/__init__.py +25 -0
- modelopt/torch/quantization/calib/bias.py +172 -0
- modelopt/torch/quantization/calib/calibrator.py +63 -0
- modelopt/torch/quantization/calib/histogram.py +434 -0
- modelopt/torch/quantization/calib/max.py +110 -0
- modelopt/torch/quantization/compress.py +227 -0
- modelopt/torch/quantization/config.py +1180 -0
- modelopt/torch/quantization/conversion.py +389 -0
- modelopt/torch/quantization/export_onnx.py +639 -0
- modelopt/torch/quantization/extensions.py +89 -0
- modelopt/torch/quantization/mode.py +428 -0
- modelopt/torch/quantization/model_calib.py +949 -0
- modelopt/torch/quantization/model_quant.py +477 -0
- modelopt/torch/quantization/nn/__init__.py +26 -0
- modelopt/torch/quantization/nn/functional.py +109 -0
- modelopt/torch/quantization/nn/modules/__init__.py +16 -0
- modelopt/torch/quantization/nn/modules/quant_activations.py +22 -0
- modelopt/torch/quantization/nn/modules/quant_batchnorm.py +24 -0
- modelopt/torch/quantization/nn/modules/quant_conv.py +123 -0
- modelopt/torch/quantization/nn/modules/quant_instancenorm.py +39 -0
- modelopt/torch/quantization/nn/modules/quant_linear.py +258 -0
- modelopt/torch/quantization/nn/modules/quant_module.py +211 -0
- modelopt/torch/quantization/nn/modules/quant_pooling.py +99 -0
- modelopt/torch/quantization/nn/modules/quant_rnn.py +527 -0
- modelopt/torch/quantization/nn/modules/tensor_quantizer.py +1293 -0
- modelopt/torch/quantization/plugins/__init__.py +73 -0
- modelopt/torch/quantization/plugins/accelerate.py +214 -0
- modelopt/torch/quantization/plugins/apex.py +52 -0
- modelopt/torch/quantization/plugins/attention.py +312 -0
- modelopt/torch/quantization/plugins/custom.py +192 -0
- modelopt/torch/quantization/plugins/diffusers.py +237 -0
- modelopt/torch/quantization/plugins/fairscale.py +45 -0
- modelopt/torch/quantization/plugins/huggingface.py +736 -0
- modelopt/torch/quantization/plugins/megatron.py +462 -0
- modelopt/torch/quantization/plugins/peft.py +148 -0
- modelopt/torch/quantization/plugins/transformer_engine.py +60 -0
- modelopt/torch/quantization/plugins/transformers.py +52 -0
- modelopt/torch/quantization/plugins/transformers_trainer.py +426 -0
- modelopt/torch/quantization/plugins/trl.py +27 -0
- modelopt/torch/quantization/plugins/vllm.py +199 -0
- modelopt/torch/quantization/qtensor/__init__.py +24 -0
- modelopt/torch/quantization/qtensor/base_qtensor.py +370 -0
- modelopt/torch/quantization/qtensor/fp8_tensor.py +151 -0
- modelopt/torch/quantization/qtensor/int4_tensor.py +130 -0
- modelopt/torch/quantization/qtensor/int8_tensor.py +124 -0
- modelopt/torch/quantization/qtensor/mxfp4_tensor.py +144 -0
- modelopt/torch/quantization/qtensor/nf4_tensor.py +199 -0
- modelopt/torch/quantization/qtensor/nvfp4_tensor.py +295 -0
- modelopt/torch/quantization/src/tensor_quant.cpp +77 -0
- modelopt/torch/quantization/src/tensor_quant.h +69 -0
- modelopt/torch/quantization/src/tensor_quant_fp8.cpp +48 -0
- modelopt/torch/quantization/src/tensor_quant_gpu.cu +361 -0
- modelopt/torch/quantization/src/tensor_quant_gpu_fp8.cu +122 -0
- modelopt/torch/quantization/src/tensor_quant_mx.cu +411 -0
- modelopt/torch/quantization/src/tensor_quant_mx.h +247 -0
- modelopt/torch/quantization/tensor_quant.py +816 -0
- modelopt/torch/quantization/triton/__init__.py +36 -0
- modelopt/torch/quantization/triton/fp4_kernel.py +303 -0
- modelopt/torch/quantization/utils.py +443 -0
- modelopt/torch/sparsity/__init__.py +19 -0
- modelopt/torch/sparsity/config.py +51 -0
- modelopt/torch/sparsity/magnitude.py +148 -0
- modelopt/torch/sparsity/mode.py +203 -0
- modelopt/torch/sparsity/module.py +87 -0
- modelopt/torch/sparsity/plugins/__init__.py +27 -0
- modelopt/torch/sparsity/plugins/megatron.py +93 -0
- modelopt/torch/sparsity/searcher.py +84 -0
- modelopt/torch/sparsity/sparsegpt.py +276 -0
- modelopt/torch/sparsity/sparsification.py +123 -0
- modelopt/torch/speculative/__init__.py +20 -0
- modelopt/torch/speculative/config.py +100 -0
- modelopt/torch/speculative/eagle/__init__.py +20 -0
- modelopt/torch/speculative/eagle/conversion.py +67 -0
- modelopt/torch/speculative/eagle/default_config.py +50 -0
- modelopt/torch/speculative/eagle/eagle_model.py +51 -0
- modelopt/torch/speculative/eagle/utils.py +91 -0
- modelopt/torch/speculative/medusa/__init__.py +19 -0
- modelopt/torch/speculative/medusa/conversion.py +59 -0
- modelopt/torch/speculative/medusa/medusa_model.py +32 -0
- modelopt/torch/speculative/mode.py +86 -0
- modelopt/torch/speculative/plugins/__init__.py +33 -0
- modelopt/torch/speculative/plugins/megatron_eagle.py +2119 -0
- modelopt/torch/speculative/plugins/megatron_medusa.py +312 -0
- modelopt/torch/speculative/plugins/transformers.py +1138 -0
- modelopt/torch/speculative/speculative_decoding.py +59 -0
- modelopt/torch/speculative/utils.py +364 -0
- modelopt/torch/trace/__init__.py +25 -0
- modelopt/torch/trace/analyzer.py +1368 -0
- modelopt/torch/trace/modules/__init__.py +19 -0
- modelopt/torch/trace/modules/concat.py +384 -0
- modelopt/torch/trace/modules/nn.py +153 -0
- modelopt/torch/trace/plugins/__init__.py +34 -0
- modelopt/torch/trace/plugins/megatron.py +36 -0
- modelopt/torch/trace/plugins/transformers.py +58 -0
- modelopt/torch/trace/symbols.py +545 -0
- modelopt/torch/trace/tracer.py +331 -0
- modelopt/torch/utils/__init__.py +28 -0
- modelopt/torch/utils/_pytree.py +133 -0
- modelopt/torch/utils/cpp_extension.py +91 -0
- modelopt/torch/utils/dataset_utils.py +484 -0
- modelopt/torch/utils/distributed.py +311 -0
- modelopt/torch/utils/graph.py +123 -0
- modelopt/torch/utils/image_processor.py +112 -0
- modelopt/torch/utils/import_utils.py +35 -0
- modelopt/torch/utils/list.py +54 -0
- modelopt/torch/utils/logging.py +127 -0
- modelopt/torch/utils/memory_monitor.py +146 -0
- modelopt/torch/utils/network.py +658 -0
- modelopt/torch/utils/perf.py +174 -0
- modelopt/torch/utils/plugins/__init__.py +27 -0
- modelopt/torch/utils/plugins/megatron_generate.py +299 -0
- modelopt/torch/utils/plugins/megatron_mmlu.py +152 -0
- modelopt/torch/utils/plugins/megatron_preprocess_data.py +200 -0
- modelopt/torch/utils/random.py +163 -0
- modelopt/torch/utils/speech_dataset_utils.py +134 -0
- modelopt/torch/utils/tensor.py +87 -0
- modelopt/torch/utils/vlm_dataset_utils.py +110 -0
- nvidia_modelopt-0.35.0.dist-info/METADATA +171 -0
- nvidia_modelopt-0.35.0.dist-info/RECORD +272 -0
- nvidia_modelopt-0.35.0.dist-info/WHEEL +5 -0
- nvidia_modelopt-0.35.0.dist-info/licenses/LICENSE +14 -0
- nvidia_modelopt-0.35.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
"""AutoCast module for converting ONNX models to mixed precision.
|
|
18
|
+
|
|
19
|
+
AutoCast is a tool for converting FP32 ONNX models to mixed precision FP32-FP16 or FP32-BF16 models.
|
|
20
|
+
While casting FP32 to FP6/BF16, some nodes might be more sensitive to effecting accuracy.
|
|
21
|
+
AutoCast intelligently selects nodes to keep in FP32 precision to maintain model accuracy while benefiting from
|
|
22
|
+
reduced precision on the rest of the nodes. AutoCast automatically injects cast operations around the selected
|
|
23
|
+
nodes.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
import onnx
|
|
28
|
+
|
|
29
|
+
import modelopt.onnx.autocast.utils as utils
|
|
30
|
+
import modelopt.onnx.utils as onnx_utils
|
|
31
|
+
from modelopt.onnx.autocast.graphsanitizer import GraphSanitizer
|
|
32
|
+
from modelopt.onnx.autocast.logging_config import logger
|
|
33
|
+
from modelopt.onnx.autocast.nodeclassifier import NodeClassifier, NodeRuleBase
|
|
34
|
+
from modelopt.onnx.autocast.precisionconverter import PrecisionConverter
|
|
35
|
+
from modelopt.onnx.autocast.referencerunner import ReferenceRunner
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
FP16 accuracy decreases in accordance with the data's magnitude.
|
|
39
|
+
For 512, the unit in last place (ULP) is 0.5, for 1024 it is 1.0, etc.
|
|
40
|
+
"""
|
|
41
|
+
DEFAULT_DATA_MAX = 512
|
|
42
|
+
DEFAULT_INIT_MAX = np.finfo(np.float16).max
|
|
43
|
+
LATEST_IR_VERSION_SUPPORTED_BY_ORT = 10
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def convert_to_mixed_precision(
|
|
47
|
+
onnx_path: str,
|
|
48
|
+
low_precision_type: str = "fp16",
|
|
49
|
+
nodes_to_exclude: list[str] | None = None,
|
|
50
|
+
op_types_to_exclude: list[str] | None = None,
|
|
51
|
+
data_max: float = DEFAULT_DATA_MAX,
|
|
52
|
+
init_max: float = DEFAULT_INIT_MAX,
|
|
53
|
+
keep_io_types: bool = False,
|
|
54
|
+
calibration_data: str | None = None,
|
|
55
|
+
custom_rule: NodeRuleBase | None = None,
|
|
56
|
+
init_conversion_max_bytes: int | None = None,
|
|
57
|
+
providers: list[str] = ["cpu"],
|
|
58
|
+
trt_plugins: list[str] = [],
|
|
59
|
+
max_depth_of_reduction: int | None = None,
|
|
60
|
+
) -> onnx.ModelProto:
|
|
61
|
+
"""Convert model to mixed precision.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
onnx_path: Path to the input ONNX model.
|
|
65
|
+
low_precision_type: Target precision to reduce to ('fp16' or 'bf16').
|
|
66
|
+
nodes_to_exclude: List of regex patterns to match node names that should remain in FP32.
|
|
67
|
+
op_types_to_exclude: List of operation types that should remain in FP32.
|
|
68
|
+
data_max: Maximum absolute value for node input and output values.
|
|
69
|
+
init_max: Maximum absolute value for initializers.
|
|
70
|
+
keep_io_types: Whether to preserve input/output types.
|
|
71
|
+
calibration_data: Path to input data file for reference runner.
|
|
72
|
+
custom_rule: Optional custom rule for node classification (inherits from NodeRuleBase).
|
|
73
|
+
init_conversion_max_bytes: Maximum size in bytes for initializer conversion. Larger initializers will be cast at
|
|
74
|
+
runtime.
|
|
75
|
+
providers: List of ORT execution providers.
|
|
76
|
+
trt_plugins: List of TensorRT plugin library paths in .so format (compiled shared library).
|
|
77
|
+
max_depth_of_reduction: Maximum depth of reduction for node classification.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
onnx.ModelProto: The converted mixed precision model.
|
|
81
|
+
"""
|
|
82
|
+
# Load and process model
|
|
83
|
+
model = onnx.load(onnx_path, load_external_data=True)
|
|
84
|
+
assert low_precision_type in ["fp16", "bf16"], "low_precision_type must be either fp16 or bf16"
|
|
85
|
+
|
|
86
|
+
# Apply graph sanitization and optimizations
|
|
87
|
+
# Opsets < 22 have a very limited support for bfloat16
|
|
88
|
+
# Otherwise, prefer to keep the original opset version unless it's very old
|
|
89
|
+
min_opset = 22 if low_precision_type == "bf16" else 13
|
|
90
|
+
graph_sanitizer = GraphSanitizer(
|
|
91
|
+
model, min_opset, trt_plugins=trt_plugins, max_ir_version=LATEST_IR_VERSION_SUPPORTED_BY_ORT
|
|
92
|
+
)
|
|
93
|
+
graph_sanitizer.sanitize()
|
|
94
|
+
model = graph_sanitizer.model
|
|
95
|
+
|
|
96
|
+
# Setup internal mappings
|
|
97
|
+
model = onnx_utils.infer_shapes(model)
|
|
98
|
+
value_info_map, initializer_map, node_to_init_map = utils.setup_mappings(model)
|
|
99
|
+
|
|
100
|
+
# Automatically add 'trt' to list of providers if custom ops are detected
|
|
101
|
+
if "trt" not in providers and graph_sanitizer.custom_ops:
|
|
102
|
+
providers.insert(0, "trt")
|
|
103
|
+
|
|
104
|
+
# Initialize classifiers and converters
|
|
105
|
+
node_classifier = NodeClassifier(
|
|
106
|
+
model,
|
|
107
|
+
node_to_init_map,
|
|
108
|
+
initializer_map,
|
|
109
|
+
nodes_to_exclude=nodes_to_exclude or [],
|
|
110
|
+
op_types_to_exclude=op_types_to_exclude or [],
|
|
111
|
+
data_max=data_max,
|
|
112
|
+
init_max=init_max,
|
|
113
|
+
custom_rule=custom_rule,
|
|
114
|
+
max_depth_of_reduction=max_depth_of_reduction,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
precision_converter = PrecisionConverter(
|
|
118
|
+
model,
|
|
119
|
+
value_info_map,
|
|
120
|
+
initializer_map,
|
|
121
|
+
node_to_init_map,
|
|
122
|
+
keep_io_types=keep_io_types,
|
|
123
|
+
low_precision_type=low_precision_type,
|
|
124
|
+
init_conversion_max_bytes=init_conversion_max_bytes,
|
|
125
|
+
custom_ops=graph_sanitizer.custom_ops,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Obtain reference data
|
|
129
|
+
ref_outputs_dict = None
|
|
130
|
+
if (data_max is not None and data_max != np.inf) or graph_sanitizer.custom_ops:
|
|
131
|
+
ref_runner = ReferenceRunner(model, providers, trt_plugins)
|
|
132
|
+
ref_outputs_dict = ref_runner.run(calibration_data)
|
|
133
|
+
|
|
134
|
+
# Run conversion
|
|
135
|
+
low_precision_nodes, high_precision_nodes = node_classifier.run(ref_outputs_dict)
|
|
136
|
+
model_mod = precision_converter.convert(high_precision_nodes, low_precision_nodes)
|
|
137
|
+
|
|
138
|
+
# Log results
|
|
139
|
+
total_nodes = len(low_precision_nodes) + len(high_precision_nodes)
|
|
140
|
+
low_precision_percentage = (
|
|
141
|
+
100 * len(low_precision_nodes) / total_nodes if total_nodes > 0 else 0
|
|
142
|
+
)
|
|
143
|
+
logger.info(
|
|
144
|
+
f"Converted {len(low_precision_nodes)}/{total_nodes} nodes "
|
|
145
|
+
f"({low_precision_percentage:.2f}%) to {low_precision_type}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return model_mod
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def convert_to_f16(
|
|
152
|
+
model: onnx.ModelProto,
|
|
153
|
+
low_precision_type: str = "fp16",
|
|
154
|
+
keep_io_types: bool = True,
|
|
155
|
+
op_block_list: list[str] = [],
|
|
156
|
+
trt_plugins: list[str] | None = [],
|
|
157
|
+
) -> onnx.ModelProto:
|
|
158
|
+
"""Convert model to mixed precision, using PrecisionConverter.
|
|
159
|
+
|
|
160
|
+
This method bypasses NodeClassifier, and uses a simple op_block_list.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
model: ONNX model to convert.
|
|
164
|
+
low_precision_type: Target precision to reduce to ('fp16' or 'bf16').
|
|
165
|
+
keep_io_types: Whether to preserve input/output types.
|
|
166
|
+
disable_shape_infer: Whether to disable shape inference.
|
|
167
|
+
op_block_list: List of operation types that should remain in FP32.
|
|
168
|
+
trt_plugins: List of TensorRT plugin library paths in .so format (compiled shared library).
|
|
169
|
+
"""
|
|
170
|
+
assert low_precision_type in ["fp16", "bf16"], "low_precision_type must be either fp16 or bf16"
|
|
171
|
+
|
|
172
|
+
# Opset 21 is needed for NVFP4 quantization support (DQ with 'block_size' attribute)
|
|
173
|
+
sanitizer = GraphSanitizer(
|
|
174
|
+
model,
|
|
175
|
+
min_opset=21,
|
|
176
|
+
trt_plugins=trt_plugins,
|
|
177
|
+
max_ir_version=LATEST_IR_VERSION_SUPPORTED_BY_ORT,
|
|
178
|
+
)
|
|
179
|
+
sanitizer.find_custom_nodes()
|
|
180
|
+
sanitizer.convert_opset()
|
|
181
|
+
sanitizer.ensure_graph_name_exists()
|
|
182
|
+
model = sanitizer.model
|
|
183
|
+
|
|
184
|
+
# Setup internal mappings
|
|
185
|
+
model = onnx_utils.infer_shapes(model)
|
|
186
|
+
value_info_map, initializer_map, node_to_init_map = utils.setup_mappings(model)
|
|
187
|
+
|
|
188
|
+
precision_converter = PrecisionConverter(
|
|
189
|
+
model,
|
|
190
|
+
value_info_map,
|
|
191
|
+
initializer_map,
|
|
192
|
+
node_to_init_map,
|
|
193
|
+
keep_io_types=keep_io_types,
|
|
194
|
+
low_precision_type=low_precision_type,
|
|
195
|
+
custom_ops=sanitizer.custom_ops,
|
|
196
|
+
)
|
|
197
|
+
high_precision_nodes = [node.name for node in model.graph.node if node.op_type in op_block_list]
|
|
198
|
+
low_precision_nodes = [
|
|
199
|
+
node.name for node in model.graph.node if node.op_type not in op_block_list
|
|
200
|
+
]
|
|
201
|
+
model_mod = precision_converter.convert(high_precision_nodes, low_precision_nodes)
|
|
202
|
+
return model_mod
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""Graph sanitization and optimization for ONNX models."""
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
import onnx
|
|
20
|
+
import onnx_graphsurgeon as gs
|
|
21
|
+
from onnx import helper, numpy_helper
|
|
22
|
+
|
|
23
|
+
import modelopt.onnx.autocast.utils as utils
|
|
24
|
+
import modelopt.onnx.utils as onnx_utils
|
|
25
|
+
from modelopt.onnx.autocast.logging_config import logger
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GraphSanitizer:
|
|
29
|
+
"""A class for sanitizing ONNX model graphs, a part of the AutoCast tool."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
model: onnx.ModelProto,
|
|
34
|
+
min_opset: int = 13,
|
|
35
|
+
max_ir_version: int | None = None,
|
|
36
|
+
trt_plugins: list[str] | None = [],
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Initialize GraphSanitizer.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
model: ONNX model to sanitize
|
|
42
|
+
min_opset: minimum opset version to use
|
|
43
|
+
max_ir_version: maximum IR version supported by ORT
|
|
44
|
+
trt_plugins: list of TensorRT plugin library paths in .so format (compiled shared library).
|
|
45
|
+
"""
|
|
46
|
+
self.model = model
|
|
47
|
+
self.min_opset = min_opset
|
|
48
|
+
self.max_ir_version = max_ir_version
|
|
49
|
+
self.standard_ops = {schema.name for schema in onnx.defs.get_all_schemas()}
|
|
50
|
+
self.custom_ops = None
|
|
51
|
+
self.trt_plugins = trt_plugins
|
|
52
|
+
|
|
53
|
+
def sanitize(self) -> None:
|
|
54
|
+
"""Sanitize the model graph.
|
|
55
|
+
|
|
56
|
+
Currently, this finds decomposed LayerNorm patterns and replaces them with a single LayerNormalization operator.
|
|
57
|
+
Additional functionality may be added in the future.
|
|
58
|
+
"""
|
|
59
|
+
self.find_custom_nodes()
|
|
60
|
+
self.remove_disconnected_outputs()
|
|
61
|
+
self.convert_opset()
|
|
62
|
+
self.replace_layernorm_pattern()
|
|
63
|
+
self.ensure_graph_name_exists()
|
|
64
|
+
onnx_utils.name_onnx_nodes(self.model.graph)
|
|
65
|
+
self.replace_custom_domain_nodes()
|
|
66
|
+
self.cleanup_model()
|
|
67
|
+
self.set_ir_version(self.max_ir_version)
|
|
68
|
+
|
|
69
|
+
def find_custom_nodes(self) -> None:
|
|
70
|
+
"""Find custom nodes in the model.
|
|
71
|
+
|
|
72
|
+
Scans through all nodes in the graph and logs any nodes that use custom operators
|
|
73
|
+
that are not part of the standard ONNX operator set.
|
|
74
|
+
"""
|
|
75
|
+
self.custom_ops = {
|
|
76
|
+
node.op_type for node in self.model.graph.node if node.op_type not in self.standard_ops
|
|
77
|
+
}
|
|
78
|
+
if self.custom_ops:
|
|
79
|
+
from modelopt.onnx.trt_utils import infer_types_shapes_tensorrt, set_trt_plugin_domain
|
|
80
|
+
|
|
81
|
+
# Set TensorRT plugin domain info in the graph for ORT compatibility
|
|
82
|
+
self.model = set_trt_plugin_domain(self.model, self.custom_ops)
|
|
83
|
+
|
|
84
|
+
# Infer types and shapes in the graph for ORT compatibility
|
|
85
|
+
self.model = infer_types_shapes_tensorrt(self.model, self.trt_plugins)
|
|
86
|
+
|
|
87
|
+
def remove_disconnected_outputs(self) -> None:
|
|
88
|
+
"""Remove disconnected outputs from the model."""
|
|
89
|
+
tensors_to_remove = []
|
|
90
|
+
for tensor in self.model.graph.output:
|
|
91
|
+
if not utils.get_producer_nodes(self.model, tensor.name):
|
|
92
|
+
tensors_to_remove.append(tensor)
|
|
93
|
+
logger.debug(f"Found disconnected output: {tensor.name}")
|
|
94
|
+
|
|
95
|
+
if tensors_to_remove:
|
|
96
|
+
logger.warning(
|
|
97
|
+
f"Found {len(tensors_to_remove)} disconnected outputs. Removing disconnected outputs from the graph."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
for tensor in tensors_to_remove:
|
|
101
|
+
self.model.graph.output.remove(tensor)
|
|
102
|
+
|
|
103
|
+
def convert_opset(self) -> None:
|
|
104
|
+
"""Convert the model to the given opset version.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
min_opset: minimum opset version to use
|
|
108
|
+
|
|
109
|
+
The method checks all opset imports and converts the model if any are below the minimum version.
|
|
110
|
+
"""
|
|
111
|
+
# Check all opset imports
|
|
112
|
+
default_opsets = list(self.model.opset_import)
|
|
113
|
+
|
|
114
|
+
# Check for quantization nodes and update min_opset if needed
|
|
115
|
+
# Before opset 19, QuantizeLinear and DequantizeLinear data and scale must be fp32
|
|
116
|
+
has_quant_nodes = any(
|
|
117
|
+
node.op_type in ["QuantizeLinear", "DequantizeLinear"] for node in self.model.graph.node
|
|
118
|
+
)
|
|
119
|
+
if has_quant_nodes and self.min_opset < 19:
|
|
120
|
+
logger.warning(
|
|
121
|
+
f"Found QuantizeLinear/DequantizeLinear nodes. Updating minimum opset from {self.min_opset} to 19."
|
|
122
|
+
)
|
|
123
|
+
self.min_opset = 19
|
|
124
|
+
|
|
125
|
+
# Convert if any default domain opset is below minimum
|
|
126
|
+
if any(op.version < self.min_opset for op in default_opsets):
|
|
127
|
+
invalid_opsets = [op.version for op in default_opsets if op.version < self.min_opset]
|
|
128
|
+
try:
|
|
129
|
+
self.model = onnx.version_converter.convert_version(self.model, self.min_opset)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.warning(f"Failed to convert model to opset {self.min_opset}: {e!s}")
|
|
132
|
+
logger.warning(f"Attempting to continue with the original opsets: {invalid_opsets}")
|
|
133
|
+
|
|
134
|
+
def set_ir_version(self, max_ir_version: int | None) -> None:
|
|
135
|
+
"""Set the model's IR version to the maximum supported version.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
max_ir_version: maximum IR version to use.
|
|
139
|
+
|
|
140
|
+
The method checks the IR version and cuts it off at the maximum supported version.
|
|
141
|
+
See https://onnxruntime.ai/docs/reference/compatibility.html#onnx-opset-support
|
|
142
|
+
"""
|
|
143
|
+
if self.custom_ops:
|
|
144
|
+
# Set ir_version to 10, remove it once ORT supports ir_version 11
|
|
145
|
+
self.max_ir_version = 10
|
|
146
|
+
max_ir_version = max_ir_version or self.max_ir_version
|
|
147
|
+
if max_ir_version and self.model.ir_version > max_ir_version:
|
|
148
|
+
try:
|
|
149
|
+
self.model.ir_version = max_ir_version
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.warning(f"Failed to set IR version to {max_ir_version}: {e!s}")
|
|
152
|
+
logger.warning(
|
|
153
|
+
f"Attempting to continue with the original IR version: {self.model.ir_version}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def replace_layernorm_pattern(self) -> None:
|
|
157
|
+
"""Detects and replaces LayerNorm operation patterns.
|
|
158
|
+
|
|
159
|
+
This method scans through the graph looking for sequences of operations that implement LayerNorm functionality
|
|
160
|
+
and replaces them with the more efficient LayerNormalization operator.
|
|
161
|
+
"""
|
|
162
|
+
nodes_to_remove = []
|
|
163
|
+
modified = False
|
|
164
|
+
|
|
165
|
+
for node in self.model.graph.node:
|
|
166
|
+
if node.op_type != "ReduceMean":
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
pattern = self._match_layernorm_pattern(node)
|
|
171
|
+
if not pattern:
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
ln_node = self._create_layernorm_node(pattern)
|
|
175
|
+
insert_idx = self._find_insertion_point(pattern["input_name"])
|
|
176
|
+
|
|
177
|
+
# Insert LayerNorm node and update graph
|
|
178
|
+
self.model.graph.node.insert(insert_idx, ln_node)
|
|
179
|
+
nodes_to_remove.extend(pattern["nodes_to_remove"])
|
|
180
|
+
modified = True
|
|
181
|
+
|
|
182
|
+
logger.info(f"Replaced LayerNorm pattern starting at {node.name}")
|
|
183
|
+
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.debug(f"Failed to match LayerNorm pattern at {node.name}: {e!s}")
|
|
186
|
+
continue
|
|
187
|
+
self._remove_nodes(nodes_to_remove)
|
|
188
|
+
|
|
189
|
+
if modified:
|
|
190
|
+
self._update_opset_version()
|
|
191
|
+
self.model = onnx_utils.infer_shapes(self.model, strict_mode=True)
|
|
192
|
+
|
|
193
|
+
def ensure_graph_name_exists(self) -> None:
|
|
194
|
+
"""Ensures that the model's name exists."""
|
|
195
|
+
if not self.model.graph.name:
|
|
196
|
+
self.model.graph.name = "model"
|
|
197
|
+
|
|
198
|
+
def _match_layernorm_pattern(self, mean_node: onnx.NodeProto) -> dict | None:
|
|
199
|
+
"""Match the sequence of operations that constitute a LayerNorm.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
mean_node: The ReduceMean node to start pattern matching from.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Dict | None: Pattern information if matched, None otherwise.
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
axis = mean_node.attribute[0].ints[0]
|
|
209
|
+
sub_node = utils.get_unique_consumer_node(self.model, mean_node.output[0])
|
|
210
|
+
if sub_node.op_type != "Sub":
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
# Find variance computation branch
|
|
214
|
+
pow_nodes = [
|
|
215
|
+
n
|
|
216
|
+
for n in utils.get_consumer_nodes(self.model, sub_node.output[0])
|
|
217
|
+
if n.op_type == "Pow"
|
|
218
|
+
]
|
|
219
|
+
if len(pow_nodes) != 1:
|
|
220
|
+
return None
|
|
221
|
+
pow_node = pow_nodes[0]
|
|
222
|
+
pow_of_value = self._get_initializer_value(pow_node.input[1])
|
|
223
|
+
if pow_of_value != 2:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
var_mean_node = utils.get_unique_consumer_node(self.model, pow_node.output[0])
|
|
227
|
+
if var_mean_node.op_type != "ReduceMean":
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
add_eps_node = utils.get_unique_consumer_node(self.model, var_mean_node.output[0])
|
|
231
|
+
if add_eps_node.op_type != "Add":
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
sqrt_node = utils.get_unique_consumer_node(self.model, add_eps_node.output[0])
|
|
235
|
+
if sqrt_node.op_type != "Sqrt":
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
# Find Div node
|
|
239
|
+
# Find the Div node that consumes both sqrt and sub outputs
|
|
240
|
+
sqrt_consumers = utils.get_consumer_nodes(self.model, sqrt_node.output[0])
|
|
241
|
+
sub_consumers = utils.get_consumer_nodes(self.model, sub_node.output[0])
|
|
242
|
+
|
|
243
|
+
div_nodes = [n for n in sqrt_consumers if n in sub_consumers and n.op_type == "Div"]
|
|
244
|
+
if len(div_nodes) != 1:
|
|
245
|
+
div_node = None
|
|
246
|
+
else:
|
|
247
|
+
div_node = div_nodes[0]
|
|
248
|
+
if (
|
|
249
|
+
div_node.input[0] != sub_node.output[0]
|
|
250
|
+
or div_node.input[1] != sqrt_node.output[0]
|
|
251
|
+
):
|
|
252
|
+
div_node = None
|
|
253
|
+
if not div_node:
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
# Get epsilon value
|
|
257
|
+
epsilon = self._get_initializer_value(add_eps_node.input[1])
|
|
258
|
+
if epsilon is None:
|
|
259
|
+
logger.warning(
|
|
260
|
+
f"Could not find epsilon value for LayerNorm pattern starting at {mean_node.name}"
|
|
261
|
+
)
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
# Check for scale/bias
|
|
265
|
+
# Find and extract scale and bias nodes if present
|
|
266
|
+
scale = None
|
|
267
|
+
bias = None
|
|
268
|
+
final_node = div_node
|
|
269
|
+
nodes_to_remove = [
|
|
270
|
+
mean_node,
|
|
271
|
+
sub_node,
|
|
272
|
+
pow_node,
|
|
273
|
+
var_mean_node,
|
|
274
|
+
add_eps_node,
|
|
275
|
+
sqrt_node,
|
|
276
|
+
div_node,
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
consumers = utils.get_consumer_nodes(self.model, div_node.output[0])
|
|
280
|
+
if len(consumers) == 1 and consumers[0].op_type == "Mul":
|
|
281
|
+
mul_node = consumers[0]
|
|
282
|
+
scale = self._get_initializer_value(mul_node.input[1], return_array=True)
|
|
283
|
+
final_node = mul_node
|
|
284
|
+
nodes_to_remove.append(mul_node)
|
|
285
|
+
|
|
286
|
+
consumers = utils.get_consumer_nodes(self.model, mul_node.output[0])
|
|
287
|
+
if len(consumers) == 1 and consumers[0].op_type == "Add":
|
|
288
|
+
add_node = consumers[0]
|
|
289
|
+
bias = self._get_initializer_value(add_node.input[1], return_array=True)
|
|
290
|
+
final_node = add_node
|
|
291
|
+
nodes_to_remove.append(add_node)
|
|
292
|
+
elif len(consumers) == 1 and consumers[0].op_type == "Add":
|
|
293
|
+
# just bias, no scale
|
|
294
|
+
add_node = consumers[0]
|
|
295
|
+
bias = self._get_initializer_value(add_node.input[1], return_array=True)
|
|
296
|
+
final_node = add_node
|
|
297
|
+
nodes_to_remove.append(add_node)
|
|
298
|
+
|
|
299
|
+
if scale is not None:
|
|
300
|
+
scale_dimension = scale.shape
|
|
301
|
+
|
|
302
|
+
# Skip pattern if we can't determine the scale dimension
|
|
303
|
+
if scale_dimension is None:
|
|
304
|
+
logger.debug(
|
|
305
|
+
f"Could not determine scale dimension for LayerNorm pattern at {mean_node.name}"
|
|
306
|
+
)
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
"mean_node": mean_node,
|
|
311
|
+
"input_name": sub_node.input[0],
|
|
312
|
+
"scale_dimension": scale_dimension,
|
|
313
|
+
"output_name": final_node.output[0],
|
|
314
|
+
"epsilon": epsilon,
|
|
315
|
+
"scale": scale,
|
|
316
|
+
"bias": bias,
|
|
317
|
+
"axis": axis,
|
|
318
|
+
"nodes_to_remove": nodes_to_remove,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logger.debug(f"Failed to match LayerNorm pattern at {mean_node.name}: {e!s}")
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
def _create_layernorm_node(self, pattern: dict) -> onnx.NodeProto:
|
|
326
|
+
"""Create a LayerNormalization node with optional bias."""
|
|
327
|
+
ln_name = f"LayerNorm_{pattern['mean_node'].name}"
|
|
328
|
+
scale_name = f"{ln_name}_scale"
|
|
329
|
+
bias_name = f"{ln_name}_bias" if pattern["bias"] is not None else ""
|
|
330
|
+
axis = pattern["axis"]
|
|
331
|
+
|
|
332
|
+
# Always create scale tensor, default to ones if not provided
|
|
333
|
+
scale_tensor = (
|
|
334
|
+
pattern["scale"]
|
|
335
|
+
if pattern["scale"] is not None
|
|
336
|
+
else np.ones(pattern["scale_dimension"], dtype=np.float32)
|
|
337
|
+
)
|
|
338
|
+
self.model.graph.initializer.append(numpy_helper.from_array(scale_tensor, name=scale_name))
|
|
339
|
+
|
|
340
|
+
if pattern["bias"] is not None:
|
|
341
|
+
self.model.graph.initializer.append(
|
|
342
|
+
numpy_helper.from_array(pattern["bias"], name=bias_name)
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
inputs = [pattern["input_name"], scale_name]
|
|
346
|
+
if pattern["bias"] is not None:
|
|
347
|
+
inputs.append(bias_name)
|
|
348
|
+
|
|
349
|
+
return helper.make_node(
|
|
350
|
+
"LayerNormalization",
|
|
351
|
+
inputs=inputs,
|
|
352
|
+
outputs=[pattern["output_name"]],
|
|
353
|
+
name=ln_name,
|
|
354
|
+
epsilon=pattern["epsilon"],
|
|
355
|
+
axis=axis,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
def _find_insertion_point(self, input_name: str) -> int:
|
|
359
|
+
"""Find the correct insertion point for the new LayerNorm node."""
|
|
360
|
+
producer_nodes = utils.get_producer_nodes(self.model, input_name)
|
|
361
|
+
if not producer_nodes:
|
|
362
|
+
return 0
|
|
363
|
+
|
|
364
|
+
producer_indices = [i for i, n in enumerate(self.model.graph.node) if n in producer_nodes]
|
|
365
|
+
return max(producer_indices) + 1
|
|
366
|
+
|
|
367
|
+
def _remove_nodes(self, nodes_to_remove: list[onnx.NodeProto]) -> None:
|
|
368
|
+
"""Remove replaced nodes and their corresponding value_info entries."""
|
|
369
|
+
tensors_to_remove = set()
|
|
370
|
+
for node in nodes_to_remove:
|
|
371
|
+
tensors_to_remove.update(node.output)
|
|
372
|
+
if node in self.model.graph.node:
|
|
373
|
+
self.model.graph.node.remove(node)
|
|
374
|
+
|
|
375
|
+
value_info_to_remove = [
|
|
376
|
+
vi for vi in self.model.graph.value_info if vi.name in tensors_to_remove
|
|
377
|
+
]
|
|
378
|
+
for vi in value_info_to_remove:
|
|
379
|
+
self.model.graph.value_info.remove(vi)
|
|
380
|
+
|
|
381
|
+
def _update_opset_version(self) -> None:
|
|
382
|
+
"""Update the model's opset version to support LayerNormalization."""
|
|
383
|
+
current_opset = None
|
|
384
|
+
for opset in self.model.opset_import:
|
|
385
|
+
if opset.domain in {"", "ai.onnx"}:
|
|
386
|
+
current_opset = opset.version
|
|
387
|
+
break
|
|
388
|
+
|
|
389
|
+
if current_opset is None or current_opset < 17:
|
|
390
|
+
# Remove existing default domain opsets
|
|
391
|
+
default_opsets = [op for op in self.model.opset_import if op.domain in ("", "ai.onnx")]
|
|
392
|
+
for op in default_opsets:
|
|
393
|
+
self.model.opset_import.remove(op)
|
|
394
|
+
# Add opset 17
|
|
395
|
+
self.model.opset_import.append(helper.make_opsetid("", 17))
|
|
396
|
+
logger.info(
|
|
397
|
+
f"Updated model opset to 17 for LayerNormalization support (was {current_opset})"
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
def _get_initializer_value(self, name: str, return_array: bool = False) -> np.ndarray | None:
|
|
401
|
+
"""Get value from an initializer by name."""
|
|
402
|
+
for init in self.model.graph.initializer:
|
|
403
|
+
if init.name == name:
|
|
404
|
+
value = numpy_helper.to_array(init)
|
|
405
|
+
return value if return_array else value.item()
|
|
406
|
+
return None
|
|
407
|
+
|
|
408
|
+
def cleanup_model(self) -> None:
|
|
409
|
+
"""Use GraphSurgeon to cleanup unused nodes, tensors and initializers."""
|
|
410
|
+
gs_graph = gs.import_onnx(self.model)
|
|
411
|
+
gs_graph.cleanup()
|
|
412
|
+
self.model = gs.export_onnx(gs_graph)
|
|
413
|
+
|
|
414
|
+
def replace_custom_domain_nodes(self):
|
|
415
|
+
"""Replace custom domain nodes with standard ONNX nodes."""
|
|
416
|
+
for node in self.model.graph.node:
|
|
417
|
+
if node.domain.startswith("com.microsoft") and node.op_type in self.standard_ops:
|
|
418
|
+
logger.warning(
|
|
419
|
+
f"Replacing custom domain node {node.name}, domain {node.domain} with standard ONNX "
|
|
420
|
+
f"node {node.op_type}"
|
|
421
|
+
)
|
|
422
|
+
node.domain = ""
|