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.
Files changed (272) hide show
  1. modelopt/__init__.py +20 -0
  2. modelopt/deploy/__init__.py +16 -0
  3. modelopt/deploy/llm/__init__.py +30 -0
  4. modelopt/deploy/llm/generate.py +345 -0
  5. modelopt/onnx/__init__.py +34 -0
  6. modelopt/onnx/autocast/__init__.py +19 -0
  7. modelopt/onnx/autocast/__main__.py +187 -0
  8. modelopt/onnx/autocast/convert.py +202 -0
  9. modelopt/onnx/autocast/graphsanitizer.py +422 -0
  10. modelopt/onnx/autocast/logging_config.py +78 -0
  11. modelopt/onnx/autocast/nodeclassifier.py +416 -0
  12. modelopt/onnx/autocast/precisionconverter.py +1032 -0
  13. modelopt/onnx/autocast/referencerunner.py +159 -0
  14. modelopt/onnx/autocast/utils.py +117 -0
  15. modelopt/onnx/llm_export_utils/__init__.py +16 -0
  16. modelopt/onnx/llm_export_utils/export_utils.py +162 -0
  17. modelopt/onnx/llm_export_utils/quantization_utils.py +122 -0
  18. modelopt/onnx/llm_export_utils/surgeon_utils.py +120 -0
  19. modelopt/onnx/logging_config.py +77 -0
  20. modelopt/onnx/op_types.py +300 -0
  21. modelopt/onnx/quantization/__init__.py +20 -0
  22. modelopt/onnx/quantization/__main__.py +291 -0
  23. modelopt/onnx/quantization/calib_utils.py +178 -0
  24. modelopt/onnx/quantization/extensions.py +35 -0
  25. modelopt/onnx/quantization/fp8.py +375 -0
  26. modelopt/onnx/quantization/graph_utils.py +1479 -0
  27. modelopt/onnx/quantization/gs_patching.py +112 -0
  28. modelopt/onnx/quantization/int4.py +1347 -0
  29. modelopt/onnx/quantization/int8.py +283 -0
  30. modelopt/onnx/quantization/operators.py +121 -0
  31. modelopt/onnx/quantization/ort_patching.py +1766 -0
  32. modelopt/onnx/quantization/ort_utils.py +346 -0
  33. modelopt/onnx/quantization/partitioning.py +439 -0
  34. modelopt/onnx/quantization/qdq_utils.py +1349 -0
  35. modelopt/onnx/quantization/quant_utils.py +283 -0
  36. modelopt/onnx/quantization/quantize.py +528 -0
  37. modelopt/onnx/quantization/src/modelopt_round_and_pack_ext.cpp +127 -0
  38. modelopt/onnx/trt_utils.py +424 -0
  39. modelopt/onnx/utils.py +753 -0
  40. modelopt/torch/__init__.py +41 -0
  41. modelopt/torch/_deploy/__init__.py +23 -0
  42. modelopt/torch/_deploy/_runtime/__init__.py +29 -0
  43. modelopt/torch/_deploy/_runtime/common.py +72 -0
  44. modelopt/torch/_deploy/_runtime/ort_client.py +193 -0
  45. modelopt/torch/_deploy/_runtime/registry.py +83 -0
  46. modelopt/torch/_deploy/_runtime/runtime_client.py +154 -0
  47. modelopt/torch/_deploy/_runtime/tensorrt/constants.py +108 -0
  48. modelopt/torch/_deploy/_runtime/tensorrt/engine_builder.py +324 -0
  49. modelopt/torch/_deploy/_runtime/tensorrt/hw_param_config.py +51 -0
  50. modelopt/torch/_deploy/_runtime/tensorrt/layerwise_profiling.py +178 -0
  51. modelopt/torch/_deploy/_runtime/tensorrt/parse_trtexec_log.py +151 -0
  52. modelopt/torch/_deploy/_runtime/tensorrt/tensorrt_utils.py +200 -0
  53. modelopt/torch/_deploy/_runtime/trt_client.py +219 -0
  54. modelopt/torch/_deploy/compilation.py +142 -0
  55. modelopt/torch/_deploy/device_model.py +157 -0
  56. modelopt/torch/_deploy/profiling.py +193 -0
  57. modelopt/torch/_deploy/utils/__init__.py +18 -0
  58. modelopt/torch/_deploy/utils/onnx_optimizer.py +120 -0
  59. modelopt/torch/_deploy/utils/onnx_utils.py +58 -0
  60. modelopt/torch/_deploy/utils/torch_onnx.py +593 -0
  61. modelopt/torch/distill/__init__.py +28 -0
  62. modelopt/torch/distill/config.py +130 -0
  63. modelopt/torch/distill/distillation.py +84 -0
  64. modelopt/torch/distill/distillation_model.py +318 -0
  65. modelopt/torch/distill/loss_balancers.py +140 -0
  66. modelopt/torch/distill/losses.py +275 -0
  67. modelopt/torch/distill/mode.py +223 -0
  68. modelopt/torch/distill/plugins/__init__.py +24 -0
  69. modelopt/torch/distill/plugins/huggingface.py +110 -0
  70. modelopt/torch/distill/plugins/megatron.py +476 -0
  71. modelopt/torch/distill/registry.py +23 -0
  72. modelopt/torch/export/__init__.py +24 -0
  73. modelopt/torch/export/convert_hf_config.py +117 -0
  74. modelopt/torch/export/distribute.py +300 -0
  75. modelopt/torch/export/hf_config_map.py +84 -0
  76. modelopt/torch/export/layer_utils.py +1893 -0
  77. modelopt/torch/export/mcore_config_map.py +25 -0
  78. modelopt/torch/export/model_config.py +623 -0
  79. modelopt/torch/export/model_config_export.py +552 -0
  80. modelopt/torch/export/model_config_utils.py +392 -0
  81. modelopt/torch/export/model_utils.py +71 -0
  82. modelopt/torch/export/plugins/__init__.py +21 -0
  83. modelopt/torch/export/plugins/mcore_common.py +67 -0
  84. modelopt/torch/export/plugins/mcore_custom.py +530 -0
  85. modelopt/torch/export/plugins/mcore_deepseek.py +143 -0
  86. modelopt/torch/export/plugins/mcore_gptoss.py +71 -0
  87. modelopt/torch/export/plugins/mcore_llama.py +164 -0
  88. modelopt/torch/export/plugins/mcore_nemotron.py +90 -0
  89. modelopt/torch/export/plugins/mcore_qwen.py +99 -0
  90. modelopt/torch/export/plugins/megatron_importer.py +647 -0
  91. modelopt/torch/export/postprocess.py +847 -0
  92. modelopt/torch/export/quant_utils.py +1069 -0
  93. modelopt/torch/export/tensorrt_llm_type.py +42 -0
  94. modelopt/torch/export/tensorrt_llm_utils.py +405 -0
  95. modelopt/torch/export/transformer_engine.py +94 -0
  96. modelopt/torch/export/unified_export_hf.py +537 -0
  97. modelopt/torch/export/unified_export_megatron.py +1164 -0
  98. modelopt/torch/nas/__init__.py +27 -0
  99. modelopt/torch/nas/algorithms.py +856 -0
  100. modelopt/torch/nas/autonas.py +808 -0
  101. modelopt/torch/nas/conversion.py +119 -0
  102. modelopt/torch/nas/hparams/__init__.py +19 -0
  103. modelopt/torch/nas/hparams/concat.py +319 -0
  104. modelopt/torch/nas/hparams/container.py +48 -0
  105. modelopt/torch/nas/modules/__init__.py +21 -0
  106. modelopt/torch/nas/modules/container.py +99 -0
  107. modelopt/torch/nas/modules/conv.py +254 -0
  108. modelopt/torch/nas/modules/linear.py +76 -0
  109. modelopt/torch/nas/modules/norm.py +193 -0
  110. modelopt/torch/nas/modules/utils.py +61 -0
  111. modelopt/torch/nas/patch.py +261 -0
  112. modelopt/torch/nas/plugins/__init__.py +29 -0
  113. modelopt/torch/nas/plugins/megatron.py +1497 -0
  114. modelopt/torch/nas/plugins/torch.py +71 -0
  115. modelopt/torch/nas/plugins/transformer_engine.py +42 -0
  116. modelopt/torch/nas/plugins/transformers.py +210 -0
  117. modelopt/torch/nas/registry.py +23 -0
  118. modelopt/torch/nas/search_space.py +260 -0
  119. modelopt/torch/nas/traced_hp.py +149 -0
  120. modelopt/torch/nas/utils.py +408 -0
  121. modelopt/torch/opt/__init__.py +41 -0
  122. modelopt/torch/opt/_hooks.py +98 -0
  123. modelopt/torch/opt/config.py +383 -0
  124. modelopt/torch/opt/conversion.py +620 -0
  125. modelopt/torch/opt/dynamic.py +1367 -0
  126. modelopt/torch/opt/hparam.py +275 -0
  127. modelopt/torch/opt/mode.py +353 -0
  128. modelopt/torch/opt/plugins/__init__.py +38 -0
  129. modelopt/torch/opt/plugins/diffusers.py +40 -0
  130. modelopt/torch/opt/plugins/huggingface.py +162 -0
  131. modelopt/torch/opt/plugins/mcore_dist_checkpointing.py +211 -0
  132. modelopt/torch/opt/plugins/megatron.py +142 -0
  133. modelopt/torch/opt/plugins/peft.py +108 -0
  134. modelopt/torch/opt/plugins/transformers.py +160 -0
  135. modelopt/torch/opt/searcher.py +363 -0
  136. modelopt/torch/opt/utils.py +125 -0
  137. modelopt/torch/prune/__init__.py +26 -0
  138. modelopt/torch/prune/fastnas.py +376 -0
  139. modelopt/torch/prune/gradnas.py +349 -0
  140. modelopt/torch/prune/plugins/__init__.py +24 -0
  141. modelopt/torch/prune/plugins/mcore_minitron.py +263 -0
  142. modelopt/torch/prune/plugins/transformers.py +41 -0
  143. modelopt/torch/prune/pruning.py +211 -0
  144. modelopt/torch/quantization/__init__.py +26 -0
  145. modelopt/torch/quantization/algorithms.py +671 -0
  146. modelopt/torch/quantization/backends/__init__.py +20 -0
  147. modelopt/torch/quantization/backends/fp8_per_tensor_gemm.py +204 -0
  148. modelopt/torch/quantization/backends/gemm_registry.py +156 -0
  149. modelopt/torch/quantization/backends/nvfp4_gemm.py +201 -0
  150. modelopt/torch/quantization/backends/utils.py +28 -0
  151. modelopt/torch/quantization/calib/__init__.py +25 -0
  152. modelopt/torch/quantization/calib/bias.py +172 -0
  153. modelopt/torch/quantization/calib/calibrator.py +63 -0
  154. modelopt/torch/quantization/calib/histogram.py +434 -0
  155. modelopt/torch/quantization/calib/max.py +110 -0
  156. modelopt/torch/quantization/compress.py +227 -0
  157. modelopt/torch/quantization/config.py +1180 -0
  158. modelopt/torch/quantization/conversion.py +389 -0
  159. modelopt/torch/quantization/export_onnx.py +639 -0
  160. modelopt/torch/quantization/extensions.py +89 -0
  161. modelopt/torch/quantization/mode.py +428 -0
  162. modelopt/torch/quantization/model_calib.py +949 -0
  163. modelopt/torch/quantization/model_quant.py +477 -0
  164. modelopt/torch/quantization/nn/__init__.py +26 -0
  165. modelopt/torch/quantization/nn/functional.py +109 -0
  166. modelopt/torch/quantization/nn/modules/__init__.py +16 -0
  167. modelopt/torch/quantization/nn/modules/quant_activations.py +22 -0
  168. modelopt/torch/quantization/nn/modules/quant_batchnorm.py +24 -0
  169. modelopt/torch/quantization/nn/modules/quant_conv.py +123 -0
  170. modelopt/torch/quantization/nn/modules/quant_instancenorm.py +39 -0
  171. modelopt/torch/quantization/nn/modules/quant_linear.py +258 -0
  172. modelopt/torch/quantization/nn/modules/quant_module.py +211 -0
  173. modelopt/torch/quantization/nn/modules/quant_pooling.py +99 -0
  174. modelopt/torch/quantization/nn/modules/quant_rnn.py +527 -0
  175. modelopt/torch/quantization/nn/modules/tensor_quantizer.py +1293 -0
  176. modelopt/torch/quantization/plugins/__init__.py +73 -0
  177. modelopt/torch/quantization/plugins/accelerate.py +214 -0
  178. modelopt/torch/quantization/plugins/apex.py +52 -0
  179. modelopt/torch/quantization/plugins/attention.py +312 -0
  180. modelopt/torch/quantization/plugins/custom.py +192 -0
  181. modelopt/torch/quantization/plugins/diffusers.py +237 -0
  182. modelopt/torch/quantization/plugins/fairscale.py +45 -0
  183. modelopt/torch/quantization/plugins/huggingface.py +736 -0
  184. modelopt/torch/quantization/plugins/megatron.py +462 -0
  185. modelopt/torch/quantization/plugins/peft.py +148 -0
  186. modelopt/torch/quantization/plugins/transformer_engine.py +60 -0
  187. modelopt/torch/quantization/plugins/transformers.py +52 -0
  188. modelopt/torch/quantization/plugins/transformers_trainer.py +426 -0
  189. modelopt/torch/quantization/plugins/trl.py +27 -0
  190. modelopt/torch/quantization/plugins/vllm.py +199 -0
  191. modelopt/torch/quantization/qtensor/__init__.py +24 -0
  192. modelopt/torch/quantization/qtensor/base_qtensor.py +370 -0
  193. modelopt/torch/quantization/qtensor/fp8_tensor.py +151 -0
  194. modelopt/torch/quantization/qtensor/int4_tensor.py +130 -0
  195. modelopt/torch/quantization/qtensor/int8_tensor.py +124 -0
  196. modelopt/torch/quantization/qtensor/mxfp4_tensor.py +144 -0
  197. modelopt/torch/quantization/qtensor/nf4_tensor.py +199 -0
  198. modelopt/torch/quantization/qtensor/nvfp4_tensor.py +295 -0
  199. modelopt/torch/quantization/src/tensor_quant.cpp +77 -0
  200. modelopt/torch/quantization/src/tensor_quant.h +69 -0
  201. modelopt/torch/quantization/src/tensor_quant_fp8.cpp +48 -0
  202. modelopt/torch/quantization/src/tensor_quant_gpu.cu +361 -0
  203. modelopt/torch/quantization/src/tensor_quant_gpu_fp8.cu +122 -0
  204. modelopt/torch/quantization/src/tensor_quant_mx.cu +411 -0
  205. modelopt/torch/quantization/src/tensor_quant_mx.h +247 -0
  206. modelopt/torch/quantization/tensor_quant.py +816 -0
  207. modelopt/torch/quantization/triton/__init__.py +36 -0
  208. modelopt/torch/quantization/triton/fp4_kernel.py +303 -0
  209. modelopt/torch/quantization/utils.py +443 -0
  210. modelopt/torch/sparsity/__init__.py +19 -0
  211. modelopt/torch/sparsity/config.py +51 -0
  212. modelopt/torch/sparsity/magnitude.py +148 -0
  213. modelopt/torch/sparsity/mode.py +203 -0
  214. modelopt/torch/sparsity/module.py +87 -0
  215. modelopt/torch/sparsity/plugins/__init__.py +27 -0
  216. modelopt/torch/sparsity/plugins/megatron.py +93 -0
  217. modelopt/torch/sparsity/searcher.py +84 -0
  218. modelopt/torch/sparsity/sparsegpt.py +276 -0
  219. modelopt/torch/sparsity/sparsification.py +123 -0
  220. modelopt/torch/speculative/__init__.py +20 -0
  221. modelopt/torch/speculative/config.py +100 -0
  222. modelopt/torch/speculative/eagle/__init__.py +20 -0
  223. modelopt/torch/speculative/eagle/conversion.py +67 -0
  224. modelopt/torch/speculative/eagle/default_config.py +50 -0
  225. modelopt/torch/speculative/eagle/eagle_model.py +51 -0
  226. modelopt/torch/speculative/eagle/utils.py +91 -0
  227. modelopt/torch/speculative/medusa/__init__.py +19 -0
  228. modelopt/torch/speculative/medusa/conversion.py +59 -0
  229. modelopt/torch/speculative/medusa/medusa_model.py +32 -0
  230. modelopt/torch/speculative/mode.py +86 -0
  231. modelopt/torch/speculative/plugins/__init__.py +33 -0
  232. modelopt/torch/speculative/plugins/megatron_eagle.py +2119 -0
  233. modelopt/torch/speculative/plugins/megatron_medusa.py +312 -0
  234. modelopt/torch/speculative/plugins/transformers.py +1138 -0
  235. modelopt/torch/speculative/speculative_decoding.py +59 -0
  236. modelopt/torch/speculative/utils.py +364 -0
  237. modelopt/torch/trace/__init__.py +25 -0
  238. modelopt/torch/trace/analyzer.py +1368 -0
  239. modelopt/torch/trace/modules/__init__.py +19 -0
  240. modelopt/torch/trace/modules/concat.py +384 -0
  241. modelopt/torch/trace/modules/nn.py +153 -0
  242. modelopt/torch/trace/plugins/__init__.py +34 -0
  243. modelopt/torch/trace/plugins/megatron.py +36 -0
  244. modelopt/torch/trace/plugins/transformers.py +58 -0
  245. modelopt/torch/trace/symbols.py +545 -0
  246. modelopt/torch/trace/tracer.py +331 -0
  247. modelopt/torch/utils/__init__.py +28 -0
  248. modelopt/torch/utils/_pytree.py +133 -0
  249. modelopt/torch/utils/cpp_extension.py +91 -0
  250. modelopt/torch/utils/dataset_utils.py +484 -0
  251. modelopt/torch/utils/distributed.py +311 -0
  252. modelopt/torch/utils/graph.py +123 -0
  253. modelopt/torch/utils/image_processor.py +112 -0
  254. modelopt/torch/utils/import_utils.py +35 -0
  255. modelopt/torch/utils/list.py +54 -0
  256. modelopt/torch/utils/logging.py +127 -0
  257. modelopt/torch/utils/memory_monitor.py +146 -0
  258. modelopt/torch/utils/network.py +658 -0
  259. modelopt/torch/utils/perf.py +174 -0
  260. modelopt/torch/utils/plugins/__init__.py +27 -0
  261. modelopt/torch/utils/plugins/megatron_generate.py +299 -0
  262. modelopt/torch/utils/plugins/megatron_mmlu.py +152 -0
  263. modelopt/torch/utils/plugins/megatron_preprocess_data.py +200 -0
  264. modelopt/torch/utils/random.py +163 -0
  265. modelopt/torch/utils/speech_dataset_utils.py +134 -0
  266. modelopt/torch/utils/tensor.py +87 -0
  267. modelopt/torch/utils/vlm_dataset_utils.py +110 -0
  268. nvidia_modelopt-0.35.0.dist-info/METADATA +171 -0
  269. nvidia_modelopt-0.35.0.dist-info/RECORD +272 -0
  270. nvidia_modelopt-0.35.0.dist-info/WHEEL +5 -0
  271. nvidia_modelopt-0.35.0.dist-info/licenses/LICENSE +14 -0
  272. nvidia_modelopt-0.35.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,78 @@
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
+ """Logging configuration module for AutoCast.
17
+
18
+ This module provides centralized logging configuration for all AutoCast components,
19
+ including console and file-based logging with customizable log levels. It ensures
20
+ consistent logging behavior across all components of the AutoCast tool.
21
+ """
22
+
23
+ import logging
24
+ import os
25
+
26
+ # Create a parent logger for all AutoCast components
27
+ logger = logging.getLogger("autocast")
28
+
29
+
30
+ def configure_logging(level=logging.INFO, log_file=None):
31
+ """Configure logging for all AutoCast components.
32
+
33
+ Args:
34
+ level: The logging level to use (default: logging.INFO).
35
+ log_file: Optional path to a log file. If provided, logs will be written to this file
36
+ in addition to stdout (default: None).
37
+ """
38
+ # Set level for the parent logger and all child loggers
39
+ logger.setLevel(level)
40
+
41
+ # Remove any existing handlers to ensure clean configuration
42
+ for handler in logger.handlers[:]:
43
+ logger.removeHandler(handler)
44
+
45
+ # Create console handler with formatting
46
+ console_handler = logging.StreamHandler()
47
+ formatter = logging.Formatter(
48
+ "%(asctime)s - %(name)s - %(levelname)s - %(filename)s - %(message)s"
49
+ )
50
+ console_handler.setFormatter(formatter)
51
+ logger.addHandler(console_handler)
52
+
53
+ # Add file handler if log_file is specified
54
+ if log_file:
55
+ try:
56
+ # Create directory if it doesn't exist
57
+ log_dir = os.path.dirname(log_file)
58
+ if log_dir:
59
+ os.makedirs(log_dir, exist_ok=True)
60
+
61
+ file_handler = logging.FileHandler(log_file)
62
+ file_handler.setFormatter(formatter)
63
+ logger.addHandler(file_handler)
64
+ logger.info(f"Logging configured to write to file: {log_file}")
65
+ except Exception as e:
66
+ logger.error(f"Failed to setup file logging to {log_file}: {e!s}")
67
+
68
+ # Prevent log messages from propagating to the root logger
69
+ logger.propagate = False
70
+
71
+ # Ensure all child loggers inherit the level setting
72
+ for name in logging.root.manager.loggerDict:
73
+ if name.startswith("autocast"):
74
+ logging.getLogger(name).setLevel(level)
75
+
76
+
77
+ # Configure with default settings if not already configured
78
+ configure_logging()
@@ -0,0 +1,416 @@
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
+ """Node classification module for AutoCast.
17
+
18
+ This module provides classes for classifying ONNX nodes based on various rules
19
+ to determine which nodes should be converted to lower precision and which should
20
+ remain in high precision. It includes rules for handling node names, operation types,
21
+ initializer ranges, and I/O value ranges.
22
+ """
23
+
24
+ import abc
25
+ import re
26
+
27
+ import numpy as np
28
+ import onnx
29
+
30
+ from modelopt.onnx.autocast.logging_config import configure_logging, logger
31
+
32
+ configure_logging()
33
+
34
+
35
+ class NodeRuleBase:
36
+ """Base class for node classification rules.
37
+
38
+ This class defines the interface for rules that determine whether a node
39
+ should be kept in high precision or converted to low precision.
40
+ """
41
+
42
+ @abc.abstractmethod
43
+ def _check_inner(self, node):
44
+ """Implement this method to check if node conversion should be skipped based on rule criteria."""
45
+
46
+ def _log_skipped(self, node, **kwargs):
47
+ """Log information about skipped nodes."""
48
+ logger.info(f"Skipping node {node.name}: {self.__class__.__name__}")
49
+
50
+ def check(self, node):
51
+ """Check if a node should be skipped based on the rule.
52
+
53
+ Args:
54
+ node: The ONNX node to check.
55
+
56
+ Returns:
57
+ bool: True if the node should be kept in high precision, False otherwise.
58
+ """
59
+ result = self._check_inner(node)
60
+ if result:
61
+ self._log_skipped(node)
62
+ return True
63
+ return False
64
+
65
+
66
+ class DisabledNodeNameRegexRule(NodeRuleBase):
67
+ """Rule for keeping nodes with matching names in high precision."""
68
+
69
+ def __init__(self, disabled_node_name_regex):
70
+ """Initialize the rule.
71
+
72
+ Args:
73
+ disabled_node_name_regex: List of regex patterns for node names to keep in high precision.
74
+ """
75
+ self.disabled_node_name_regex = disabled_node_name_regex
76
+
77
+ def _check_inner(self, node):
78
+ return any(re.match(regex, node.name) for regex in self.disabled_node_name_regex)
79
+
80
+
81
+ class DisabledOpTypes(NodeRuleBase):
82
+ """Rule for keeping nodes with specific operation types in high precision."""
83
+
84
+ def __init__(self, op_types_to_exclude):
85
+ """Initialize the rule.
86
+
87
+ Args:
88
+ op_types_to_exclude: List of operation types to keep in high precision.
89
+ """
90
+ self.op_types_to_exclude = op_types_to_exclude
91
+
92
+ def _check_inner(self, node):
93
+ return node.op_type in self.op_types_to_exclude
94
+
95
+
96
+ class InitializerRangeRule(NodeRuleBase):
97
+ """Rule for keeping nodes with out-of-range initializers in high precision."""
98
+
99
+ def __init__(self, init_max, node_to_init_map):
100
+ """Initialize the rule.
101
+
102
+ Args:
103
+ init_max: Maximum absolute value allowed for initializers.
104
+ node_to_init_map: Mapping from node names to their initializers.
105
+ """
106
+ self.init_max = init_max
107
+ self.node_to_init_map = node_to_init_map
108
+ self.init_data = (None, None)
109
+
110
+ def _check_inner(self, node):
111
+ for init in self.node_to_init_map[node.name]:
112
+ np_array = onnx.numpy_helper.to_array(init)
113
+ if np_array.dtype == np.float32 and np.any(np.abs(np_array) > self.init_max):
114
+ self.init_data = init.name, np_array
115
+ return True
116
+ return False
117
+
118
+ def _log_skipped(self, node, **kwargs):
119
+ """Log information about skipped nodes with initializer range violations."""
120
+ if self.init_data[1] is not None:
121
+ logger.info(
122
+ f"Skipping node {node.name}: initializer {self.init_data[0]} out of range: "
123
+ f"min={self.init_data[1].min()}, max={self.init_data[1].max()}, range=[{-self.init_max},"
124
+ f"{self.init_max}]"
125
+ )
126
+ else:
127
+ super()._log_skipped(node, **kwargs)
128
+
129
+
130
+ class IORangeRule(NodeRuleBase):
131
+ """Rule for keeping nodes with out-of-range inputs/outputs in high precision."""
132
+
133
+ def __init__(self, data_max, reference_data, node_to_init_map):
134
+ """Initialize the rule.
135
+
136
+ Args:
137
+ data_max: Maximum absolute value allowed for node I/O.
138
+ reference_data: Reference data for checking I/O ranges.
139
+ node_to_init_map: Mapping from node names to their initializers.
140
+ """
141
+ self.data_max = data_max
142
+ self.reference_data = reference_data
143
+ self.node_to_init_map = node_to_init_map
144
+ self.output_data = None
145
+
146
+ def _check_inner(self, node):
147
+ def is_io_out_of_range(node, tensor_name):
148
+ if tensor_name not in self.reference_data:
149
+ # Issue a warning only if the tensor is not an initializer/network input
150
+ init_names = [init.name for init in self.node_to_init_map.get(node.name, [])]
151
+ init_names.extend(node.input)
152
+ if tensor_name not in init_names:
153
+ logger.warning(
154
+ f"Node {node.name}: Tensor {tensor_name} not found in reference data."
155
+ )
156
+ return False
157
+ ref_data = self.reference_data[tensor_name]
158
+ if ref_data.size == 0:
159
+ logger.debug(
160
+ f"Node {node.name}: Tensor {tensor_name} has size 0. Skipping I/O range check."
161
+ )
162
+ return False
163
+ logger.debug(
164
+ f"Node {node.name}: reference data: min={np.min(ref_data)}, max={np.max(ref_data)}"
165
+ )
166
+ if np.any(np.abs(ref_data) > self.data_max):
167
+ self.output_data = ref_data
168
+ return True
169
+
170
+ if node.op_type == "Constant":
171
+ return False
172
+ if self.reference_data:
173
+ for in_name in node.input:
174
+ if is_io_out_of_range(node, in_name):
175
+ return True
176
+ for out_name in node.output:
177
+ if is_io_out_of_range(node, out_name):
178
+ return True
179
+ return False
180
+
181
+ def _log_skipped(self, node, **kwargs):
182
+ """Log information about skipped nodes with I/O range violations."""
183
+ if self.output_data is not None:
184
+ logger.info(
185
+ f"Skipping node {node.name}: reference IO out of range: min={np.min(self.output_data)}, "
186
+ f"max={np.max(self.output_data)}, range=[{-self.data_max}, {self.data_max}]"
187
+ )
188
+ else:
189
+ super()._log_skipped(node, **kwargs)
190
+
191
+
192
+ class DepthOfReductionRule(NodeRuleBase):
193
+ """Rule for keeping nodes with high depth of reduction in high precision."""
194
+
195
+ def __init__(self, max_depth_of_reduction, reference_data, node_to_init_map, initializer_map):
196
+ """Initialize the rule.
197
+
198
+ Args:
199
+ max_depth_of_reduction: Maximum depth of reduction allowed in low precision.
200
+ reference_data: Reference data for checking I/O ranges.
201
+ node_to_init_map: Mapping from node names to their initializers.
202
+ initializer_map: Mapping from initializer names to initializers.
203
+ """
204
+ self.max_depth_of_reduction = max_depth_of_reduction
205
+ self.reference_data = reference_data
206
+ self.node_to_init_map = node_to_init_map
207
+ self.initializer_map = initializer_map
208
+ self.reduction_depth = 0
209
+
210
+ def _get_tensor_shape(self, tensor_name):
211
+ """Get tensor shape from reference data."""
212
+ if tensor_name in self.reference_data:
213
+ return self.reference_data[tensor_name].shape
214
+ if tensor_name in self.initializer_map:
215
+ return self.initializer_map[tensor_name].dims
216
+ return None
217
+
218
+ def _log_skipped(self, node, **kwargs):
219
+ """Log information about skipped nodes with depth of reduction violations."""
220
+ if self.reduction_depth > 0:
221
+ logger.info(
222
+ f"Skipping node {node.name}: depth of reduction {self.reduction_depth} exceeds "
223
+ f"{self.max_depth_of_reduction}."
224
+ )
225
+ else:
226
+ super()._log_skipped(node, **kwargs)
227
+
228
+ def _check_inner(self, node):
229
+ # All reduction ops rely on shape of input[0]
230
+ input_0_dims = self._get_tensor_shape(node.input[0]) if len(node.input) > 0 else None
231
+ if input_0_dims is None:
232
+ return False
233
+ self.reduction_depth = 0
234
+ if node.op_type == "Attention":
235
+ # Attention: input (batch_size, sequence_length, hidden_size)
236
+ # or (batch_size, kv_num_heads, total_sequence_length, head_size)
237
+ assert len(input_0_dims) == 3 or len(input_0_dims) == 4
238
+ hidden_size = (
239
+ input_0_dims[2] if len(input_0_dims) == 3 else input_0_dims[1] * input_0_dims[3]
240
+ )
241
+ self.reduction_depth = hidden_size
242
+ elif node.op_type == "Conv":
243
+ # Conv: input (N x C x D1 x D2 ... x Dn)
244
+ # weight (out_channels, in_channels, kD1, kD2, ... kDn)
245
+ # Reduction depth = in_channels * kernel_volume
246
+ weight_shape = self._get_tensor_shape(node.input[1]) if len(node.input) > 1 else None
247
+ if weight_shape is None:
248
+ return False
249
+ in_channels = weight_shape[1]
250
+ kernel_volume = np.prod(weight_shape[2:])
251
+ self.reduction_depth = in_channels * kernel_volume
252
+ elif node.op_type == "CumSum":
253
+ axis_name = node.input[1] if len(node.input) > 1 else None
254
+ if axis_name is None:
255
+ return False
256
+ # Find the axis initializer
257
+ axis_init = None
258
+ for init in self.node_to_init_map.get(node.name, []):
259
+ if init.name == axis_name:
260
+ axis_init = init
261
+ break
262
+ if axis_init is None:
263
+ return False
264
+ axis_array = onnx.numpy_helper.to_array(axis_init)
265
+ assert axis_array.ndim == 0 or (axis_array.ndim == 1 and axis_array.size == 1)
266
+ axis = int(axis_array.item())
267
+ if input_0_dims[axis] > self.max_depth_of_reduction:
268
+ self.reduction_depth = input_0_dims[axis]
269
+
270
+ elif node.op_type == "Gemm":
271
+ # GEMM: A (M, K) @ B (K, N) = C (M, N)
272
+ # Check for transpose attributes
273
+ trans_a = False
274
+ for attr in node.attribute:
275
+ if attr.name == "transA":
276
+ trans_a = bool(attr.i)
277
+
278
+ # Get K dimension based on transpose flag
279
+ self.reduction_depth = (
280
+ input_0_dims[0] if trans_a else input_0_dims[1]
281
+ ) # A is (K, M) when transposed
282
+
283
+ elif node.op_type == "MatMul":
284
+ # MatMul: (..., M, K) @ (..., K, N) = (..., M, N)
285
+ # K is the last dimension of first input
286
+ if len(input_0_dims) >= 2:
287
+ self.reduction_depth = input_0_dims[-1]
288
+ elif node.op_type == "Mean":
289
+ self.reduction_depth = len(input_0_dims)
290
+ elif node.op_type in [
291
+ "ReduceL1",
292
+ "ReduceL2",
293
+ "ReduceLogSum",
294
+ "ReduceLogSumExp",
295
+ "ReduceMean",
296
+ "ReduceProd",
297
+ "ReduceSum",
298
+ "ReduceSumSquare",
299
+ ]:
300
+ if len(node.input) > 1:
301
+ axes_name = node.input[1]
302
+ # Find the axes initializer
303
+ axes_init = None
304
+ for init in self.node_to_init_map.get(node.name, []):
305
+ if init.name == axes_name:
306
+ axes_init = init
307
+ break
308
+ if axes_init is None:
309
+ return False
310
+ axes_array = onnx.numpy_helper.to_array(axes_init)
311
+ if axes_array.ndim == 0:
312
+ axes_array = [int(axes_array.item())]
313
+ else:
314
+ assert axes_array.ndim == 1
315
+ axes_array = axes_array.astype(np.int64)
316
+ else:
317
+ axes_array = range(len(input_0_dims))
318
+ for axis in axes_array:
319
+ if input_0_dims[axis] > self.max_depth_of_reduction:
320
+ self.reduction_depth = input_0_dims[axis]
321
+ return True
322
+ return self.reduction_depth > self.max_depth_of_reduction
323
+
324
+
325
+ class NodeClassifier:
326
+ """Main class for classifying nodes into high and low precision groups."""
327
+
328
+ def __init__(
329
+ self,
330
+ model: onnx.ModelProto,
331
+ node_to_init_map: dict[str, list[onnx.TensorProto]] | None = None,
332
+ initializer_map: dict[str, onnx.TensorProto] | None = None,
333
+ nodes_to_exclude: list[str] | None = None,
334
+ op_types_to_exclude: list[str] | None = None,
335
+ custom_rule: NodeRuleBase | None = None,
336
+ data_max: float | None = 1000.0,
337
+ init_max: float | None = np.finfo(np.float16).max,
338
+ max_depth_of_reduction: int | None = None,
339
+ ):
340
+ """Initialize the node classifier.
341
+
342
+ Args:
343
+ model: The ONNX model to classify nodes for.
344
+ node_to_init_map: Mapping from node names to their initializers.
345
+ initializer_map: Mapping from initializer names to their tensors.
346
+ nodes_to_exclude: List of regex patterns for node names to keep in high precision.
347
+ op_types_to_exclude: List of operation types to keep in high precision.
348
+ custom_rule: Optional custom classification rule.
349
+ data_max: Maximum absolute value allowed for node I/O.
350
+ init_max: Maximum absolute value allowed for initializers.
351
+ max_depth_of_reduction: Maximum depth of reduction allowed in low precision.
352
+ """
353
+ self.model = model
354
+ self.node_to_init_map = node_to_init_map
355
+ self.initializer_map = initializer_map
356
+ self.nodes_to_exclude = nodes_to_exclude
357
+ self.op_types_to_exclude = op_types_to_exclude
358
+ self.custom_rule = custom_rule
359
+ self.data_max = data_max
360
+ self.init_max = init_max
361
+ self.max_depth_of_reduction = max_depth_of_reduction
362
+
363
+ def _gen_block_node_rules(self, reference_data):
364
+ """Generate list of rules for blocking nodes from precision conversion.
365
+
366
+ Args:
367
+ reference_data: Reference data for checking I/O ranges.
368
+
369
+ Returns:
370
+ list[NodeRuleBase]: List of rules to apply.
371
+ """
372
+ block_node_rules: list[NodeRuleBase] = []
373
+ if self.nodes_to_exclude:
374
+ block_node_rules.append(DisabledNodeNameRegexRule(self.nodes_to_exclude))
375
+ if self.op_types_to_exclude:
376
+ block_node_rules.append(DisabledOpTypes(self.op_types_to_exclude))
377
+ if self.init_max is not None:
378
+ block_node_rules.append(InitializerRangeRule(self.init_max, self.node_to_init_map))
379
+ if reference_data:
380
+ block_node_rules.append(
381
+ IORangeRule(self.data_max, reference_data, self.node_to_init_map)
382
+ )
383
+ if self.max_depth_of_reduction is not None:
384
+ block_node_rules.append(
385
+ DepthOfReductionRule(
386
+ self.max_depth_of_reduction,
387
+ reference_data,
388
+ self.node_to_init_map,
389
+ self.initializer_map,
390
+ )
391
+ )
392
+ if self.custom_rule:
393
+ block_node_rules.append(self.custom_rule)
394
+ return block_node_rules
395
+
396
+ def run(self, ref_outputs_dict=None):
397
+ """Run node classification.
398
+
399
+ Args:
400
+ ref_outputs_dict: Optional tensors' reference data.
401
+
402
+ Returns:
403
+ tuple: Lists of node names (low_precision_nodes, high_precision_nodes).
404
+ """
405
+ block_node_rules = self._gen_block_node_rules(ref_outputs_dict)
406
+ low_precision_nodes = []
407
+ high_precision_nodes = []
408
+ for node in self.model.graph.node:
409
+ # If any condition is met - node will be executed in high precision
410
+ if any(rule.check(node) for rule in block_node_rules):
411
+ high_precision_nodes.append(node.name)
412
+ else:
413
+ low_precision_nodes.append(node.name)
414
+ logger.debug(f"Low Precision Nodes: {low_precision_nodes}")
415
+ logger.debug(f"High Precision Nodes: {high_precision_nodes}")
416
+ return low_precision_nodes, high_precision_nodes