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,119 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024 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
+ """Main APIs+entrypoints for model pruning."""
17
+
18
+ from torch import nn
19
+
20
+ from modelopt.torch.opt.conversion import apply_mode
21
+ from modelopt.torch.opt.mode import ModeLike, _ModeRegistryCls
22
+ from modelopt.torch.utils import ModelLike, unwrap_model
23
+
24
+ __all__ = ["convert", "export"]
25
+
26
+ NASModeRegistry = _ModeRegistryCls("nas")
27
+
28
+
29
+ def convert(model: ModelLike, mode: ModeLike) -> nn.Module:
30
+ """Convert a regular PyTorch model into a model that supports design space optimization.
31
+
32
+ Args:
33
+ model: A model-like object. Can be an nn.Module, a model class type, or a tuple.
34
+ Tuple must be of the form ``(model_cls,)`` or ``(model_cls, args)`` or ``(model_cls, args, kwargs)``.
35
+ Model will be initialized as ``model_cls(*args, **kwargs)``.
36
+ mode: A (list of) string(s) or Mode(s) or a list of tuples containing the mode and its
37
+ config indicating the desired mode(s) (and configurations) for the convert
38
+ process. Modes set up the model for different algorithms for model optimization. The
39
+ following modes are available:
40
+
41
+ * :class:`"autonas"<modelopt.torch.nas.autonas.AutoNASModeDescriptor>`: The ``model`` will
42
+ be converted into a search space and set up to automatically perform operations
43
+ required for AutoNAS-based model training, evaluation, and search. The mode's config
44
+ is described in :class:`AutoNASConfig<modelopt.torch.nas.autonas.AutoNASConfig>`.
45
+
46
+ If the mode argument is specified as a dictionary, the keys should indicate the mode and
47
+ the values specify the per-mode configuration. If not provided, then default
48
+ configuration would be used.
49
+
50
+ Returns:
51
+ A converted model with the original weights preserved that can be used for model
52
+ optimization.
53
+
54
+ .. note::
55
+
56
+ Note that model wrappers (such as DataParallel/DistributedDataParallel) are `not` supported
57
+ during the convert process. Please wrap the model after the convert process.
58
+
59
+ .. note::
60
+ ``convert()`` relies on `monkey patching <https://en.wikipedia.org/wiki/Monkey_patch>`_ to
61
+ augment the ``forward()``, ``eval()``, and ``train()`` methods of ``model`` as well as
62
+ augment individual modules to make them dynamic. This renders the conversion incompatible
63
+ with other monkey patches to those methods and modules! Note that ``convert()`` is still
64
+ fully compatible with inheritance.
65
+
66
+ .. note::
67
+
68
+ #. Configs can be customized for individual layers using
69
+ `glob expressions <https://docs.python.org/3/library/fnmatch.html#fnmatch.fnmatch>`_ on
70
+ qualified submodule names, e.g., as shown for ``nn.Conv2d`` in the above example.
71
+
72
+ #. Keys in the config that appear earlier in a dict have lower priority, e.g.,
73
+ ``backbone.stages.1.0.spatial_conv`` will have ``out_channels_ratio``
74
+ ``[0.334, 0.5, 0.667, 1.0]``, not ``[1.0]``.
75
+
76
+ #. Config entries without layer qualifiers are also supported, e.g., as shown for
77
+ ``nn.Sequential`` in the above example.
78
+
79
+ #. Mixed usage of configurations with and without layer qualifiers is supported for
80
+ *different* layers, e.g., as shown for ``nn.Conv2d`` and ``nn.Sequential`` in the above
81
+ example. For a specific layer type, only configurations with *or* without layer
82
+ qualifiers are supported.
83
+
84
+ #. Use ``*`` as a wildcard matching any layer.
85
+ """
86
+ # apply mode and handle model-like object with wrapper
87
+ return apply_mode(model, mode, registry=NASModeRegistry)
88
+
89
+
90
+ def export(model: nn.Module, strict: bool = True, calib: bool = False) -> nn.Module:
91
+ """Export a pruned subnet to a regular model.
92
+
93
+ Args:
94
+ model: The pruned subnet to be exported.
95
+ strict: Raise an error when the config does not contain all necessary keys.
96
+ calib: Whether to calibrate the subnet to be exported.
97
+
98
+ Returns:
99
+ The current active subnet in regular PyTorch model format.
100
+
101
+ .. note::
102
+
103
+ if model is a wrapper such as DistributedDataParallel, it will be unwrapped, e.g.,
104
+ ``model.module`` will be returned.
105
+ """
106
+ # unwrap a DP/DDP model
107
+ model = unwrap_model(
108
+ model,
109
+ warn=True,
110
+ msg=(
111
+ f"Unwrapping a {type(model).__name__} model for export! Note that the export is"
112
+ " in-place and the model wrapper should be re-created after export since the wrapper"
113
+ " might not support changing parameters after initialization."
114
+ ),
115
+ )
116
+
117
+ # apply export mode and return model
118
+ config = {"strict": strict, "calib": calib}
119
+ return apply_mode(model, [("export", config)], registry=NASModeRegistry)
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024 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
+ """Module containing implementations for various types of hyperparameters."""
17
+
18
+ from .concat import *
19
+ from .container import *
@@ -0,0 +1,319 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024 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
+ """Hparam that represent concat."""
17
+
18
+ from collections import defaultdict
19
+ from collections.abc import Callable, Iterator
20
+ from itertools import product
21
+ from math import prod
22
+ from warnings import warn
23
+
24
+ import numpy as np
25
+ import torch
26
+
27
+ from modelopt.torch.trace import ConcatSymbol, Symbol
28
+
29
+ from ..traced_hp import TracedHp, TracedHpRegistry
30
+
31
+ __all__ = ["ConcatTracedHp"]
32
+
33
+
34
+ @TracedHpRegistry.register(ConcatSymbol)
35
+ class ConcatTracedHp(TracedHp):
36
+ """Concat hparam that can stitch together input hparams."""
37
+
38
+ _inputs: list[TracedHp]
39
+ _sum_to_combo: dict[int, tuple[int]]
40
+ _hp_start_idx: torch.LongTensor
41
+
42
+ @staticmethod
43
+ def _choice_combos(*all_choices) -> Iterator[tuple[int, ...]]:
44
+ # compute total number of combinations
45
+ n_combos = prod(len(c_list) for c_list in all_choices)
46
+
47
+ # we don't wanna iterate over more than that to keep it fast
48
+ n_max = 5e7 # takes 4s
49
+
50
+ # if we have less than n_max combinations, we can iterate over all of them
51
+ if n_combos <= n_max:
52
+ # use itertools.product and iterate over all combinations
53
+ yield from product(*all_choices)
54
+ return
55
+ else:
56
+ warn(f"ConcatTracedHp: {n_combos=} is larger than {n_max=}. Pruning combinations.")
57
+
58
+ # otherwise, we use a pruned set of combinations based on immediate vicinity of each value
59
+
60
+ # range of choices
61
+ c_min = min(min(c_list) for c_list in all_choices)
62
+ c_max = max(max(c_list) for c_list in all_choices)
63
+
64
+ # figure out for each list what's the closest index for each value in the choice range
65
+ idx_lookup = {
66
+ c: [min(range(len(c_list)), key=lambda i: abs(c_list[i] - c)) for c_list in all_choices]
67
+ for c in range(c_min, c_max + 1)
68
+ }
69
+
70
+ # solve equation for r such that n_combos = (c_max - c_min) * (2r+1)^n \approx n_max
71
+ # use r = max(1, r) to avoid edge cases
72
+ r = max(int((n_max / (c_max - c_min)) ** (1 / len(all_choices)) - 1) // 2, 1)
73
+
74
+ # yield reduced number of combinations (note that we might have duplicates; hence we filter)
75
+ # each combination is based on the product of choices centered around the value closest to
76
+ # the target value c. We use r to control the size of the neighborhood.
77
+ processed = set()
78
+ for c in range(c_min, c_max + 1):
79
+ all_choices_reduced = []
80
+ for i, c_list in enumerate(all_choices):
81
+ idx_closest = idx_lookup[c][i]
82
+ all_choices_reduced.append(c_list[max(0, idx_closest - r) : idx_closest + r + 1])
83
+ for combo in product(*all_choices_reduced):
84
+ if combo not in processed:
85
+ processed.add(combo)
86
+ yield combo
87
+
88
+ def _get_sum_to_combo(self) -> dict[int, tuple[int, ...]]:
89
+ """Return dict mapping sum to individual hparam active values."""
90
+ # If two hparams are the same, we need their values to be the same and we need to track that
91
+ hps_unique = list(set(self._inputs))
92
+ idxs_unique = [hps_unique.index(hp) for hp in self._inputs]
93
+
94
+ # Keep the combination with the smallest difference between the individual values
95
+ # e.g. for sum 48, prefer [24, 24] over [16, 32] or [32, 16]
96
+ sum_to_combo = {}
97
+ sum_to_cost = defaultdict(lambda: float("inf"))
98
+ for combo in self._choice_combos(*[hp.choices for hp in hps_unique]):
99
+ # get full combo and sum
100
+ combo_full = tuple(combo[idx] for idx in idxs_unique)
101
+ s = sum(combo_full)
102
+ # check cost (sum of absolute differences between average and individual values)
103
+ cost = sum(abs(c - s / len(combo_full)) for c in combo_full)
104
+ # update if better
105
+ if cost < sum_to_cost[s]:
106
+ sum_to_cost[s] = cost
107
+ sum_to_combo[s] = combo_full
108
+ return sum_to_combo
109
+
110
+ def _set_hp_start_idx(self) -> None:
111
+ """Compute start indices for input hps."""
112
+ # NOTE: the last index is the length of the concatenated hp
113
+ hp_start_idx = np.concatenate(([0], np.cumsum([hp.max for hp in self._inputs])))
114
+ self._hp_start_idx = torch.asarray(hp_start_idx, dtype=torch.long)
115
+
116
+ @property
117
+ def active(self) -> int:
118
+ """Return the sum of active values of all hparams."""
119
+ active = 0
120
+ for hp in self._inputs:
121
+ active += hp.active
122
+ return active
123
+
124
+ @active.setter # type: ignore[override]
125
+ def active(self, val: int | None):
126
+ """Set the active value with a sanity check for choices and dynamic hparams."""
127
+ val = self.original if val is None else val
128
+ assert val in self._choices, f"val = {val}, choices = {self.choices}"
129
+ assert val in self._sum_to_combo, f"Invalid value: {val} not in {self._sum_to_combo.keys()}"
130
+ for hp_in, v in zip(self._inputs, self._sum_to_combo[val]):
131
+ assert v in hp_in.choices
132
+ hp_in._active = v
133
+
134
+ @property
135
+ def active_slice(self) -> TracedHp.ActiveSlice:
136
+ """Return the currently active sorted indices or slice corresponding to the active value.
137
+
138
+ Example:
139
+ hp1(max=4, active=2), hp2(max=4, active=4)
140
+ Then the active indices in the concatenated hp are [0, 1, 4, 5, 6, 7]
141
+ """
142
+ # get vanilla slice first (with sanity check)
143
+ as_simple = super().active_slice
144
+ assert isinstance(as_simple, slice), "ConcatTracedHp does not support its own slice order!"
145
+
146
+ # get the list of slices as active indices in the concatenated hp
147
+ as_hps = [hp.active_slice for hp in self._inputs]
148
+ as_hps = [torch.arange(_as.stop)[_as] if isinstance(_as, slice) else _as for _as in as_hps]
149
+
150
+ # concatenate the slices with correct offsets
151
+ active_slice = torch.cat([_as + self._hp_start_idx[i] for i, _as in enumerate(as_hps)])
152
+
153
+ # check if active_slice corresponds to the vanilla slice
154
+ if torch.equal(active_slice, torch.arange(as_simple.stop)[as_simple]):
155
+ return as_simple
156
+
157
+ return active_slice
158
+
159
+ def _get_importance(self) -> TracedHp.Importance:
160
+ # compute the regular importance (this hparam + registered dependencies)
161
+ imp_total = super()._get_importance()
162
+
163
+ # now we add in the importance from the individual hparams contributing to the concat node
164
+ # concat inputs are not registered as dependencies, so we need to add them here
165
+ # note that we use ``hp.importance`` instead of the private API ``hp._get_importance()``
166
+ # here. Why? We can have the situation where only one of the concat inputs is searchable.
167
+ # We need to account for that and compute the "partial" importances hence!
168
+ imps_cat = [hp.importance if hp.is_configurable else None for hp in self._inputs]
169
+
170
+ # clean up importances that are None
171
+ imps_cat = [imp or torch.zeros(hp.max) for hp, imp in zip(self._inputs, imps_cat)]
172
+
173
+ # Let's split imp_total
174
+ imps_split = torch.tensor_split(imp_total, self._hp_start_idx[1:-1])
175
+
176
+ # Added split imps
177
+ imps = [imp + imp_cat.to(imp) for imp, imp_cat in zip(imps_split, imps_cat)] # type: ignore[union-attr]
178
+
179
+ # We need to aggregate between split importances when the come from the same hparam!
180
+ imps = [
181
+ sum([imp_ for imp_, hp in zip(imps, self._inputs) if hp is self._inputs[i]])
182
+ for i, imp in enumerate(imps)
183
+ ]
184
+
185
+ # concatenate and return
186
+ return torch.cat(imps)
187
+
188
+ def _split_order(self, order: torch.Tensor) -> list[torch.Tensor]:
189
+ """Split the order tensor into the individual order tensors for each input hparam."""
190
+ return [
191
+ (
192
+ order[torch.logical_and(order >= start, order < start + hp.max)] - start
193
+ if hp.is_configurable and hp.is_sortable
194
+ else torch.arange(hp.max, device=order.device)
195
+ )
196
+ for hp, start in zip(self._inputs, self._hp_start_idx[:-1])
197
+ ]
198
+
199
+ @torch.no_grad()
200
+ def _enforce_order(self, order: torch.Tensor | None = None) -> None:
201
+ """Enforcing the order.
202
+
203
+ Note that we only support intra-group sorting but **not** inter-group sorting. Inter-group
204
+ sorting would not be compatible with the concat operation and is thus not well-defined.
205
+
206
+ E.g., consider a concat operation with multiple inputs from convs. Then a channel that is
207
+ currently assigned to the first group (i.e. first conv) cannot be resorted to appear in any
208
+ other group/conv. Hence, we need to ensure that we maintain inter-group consistency.
209
+ """
210
+ # vanilla case
211
+ if order is None:
212
+ for hp in self._inputs:
213
+ hp._enforce_order(order) # private API is fine here since it is just None
214
+ return super()._enforce_order(order)
215
+
216
+ # we have to group the order by the hparams to enforce order on each group separately with
217
+ # the correct offset subtracted
218
+ orders_split = self._split_order(order)
219
+
220
+ # ensure that orders are consistent if they come from the same hparam
221
+ for hp, _order in zip(self._inputs, orders_split):
222
+ assert all(
223
+ torch.equal(_order, order_)
224
+ for hp_, order_ in zip(self._inputs, orders_split)
225
+ if hp is hp_
226
+ )
227
+
228
+ # enforce order now ...
229
+ # note that we use the public API here for extra sanity checks
230
+ for hp, partial_order in zip(self._inputs, orders_split):
231
+ if hp.is_configurable and hp.is_sortable:
232
+ hp.enforce_order(partial_order)
233
+
234
+ # recombine the order into a single tensor without inter-group sorting for dependencies
235
+ order_intra_only = torch.cat(
236
+ [partial + start for partial, start in zip(orders_split, self._hp_start_idx[:-1])]
237
+ )
238
+
239
+ # sort dependencies now
240
+ super()._enforce_order(order_intra_only)
241
+ self._slice_order = None # we don't store slice_order in a ConcatTracedHp
242
+
243
+ def _resolve_dependencies(
244
+ self, sym: Symbol, get_hp: Callable[[Symbol], TracedHp]
245
+ ) -> dict[Symbol, TracedHp]:
246
+ # check that we have the correct symbol
247
+ assert type(sym) is ConcatSymbol, f"Sym should be {type(self)}: {sym}."
248
+
249
+ # construct input hparams
250
+ # NOTE: input syms are hidden and corresponding registered symbol should be last dependency!
251
+ # --> if that's not the case we cannot retrieve the hps needed.
252
+ assert all(isinstance(s, ConcatSymbol.Input) for s in sym.input_syms)
253
+ assert all(s._dependencies[-1]._parent == s for s in sym.input_syms)
254
+ self._inputs = [get_hp(s._dependencies[-1]) for s in sym.input_syms]
255
+
256
+ def _get_hp_with_input(s: Symbol) -> TracedHp:
257
+ assert type(sym) is ConcatSymbol
258
+ for s_, hp in zip(sym.input_syms, self._inputs):
259
+ if s is s_:
260
+ return hp
261
+ return get_hp(s)
262
+
263
+ # resolve regular dependencies of all input hparams
264
+ mapping: dict[Symbol, TracedHp] = {}
265
+ for hp_in, sym_in in zip(self._inputs, sym.input_syms):
266
+ mapping.update(hp_in._resolve_dependencies(sym_in, _get_hp_with_input))
267
+
268
+ # remove input syms from mapping...
269
+ for sym_in in set(sym.input_syms):
270
+ mapping.pop(sym_in)
271
+
272
+ # resolve regular dependencies
273
+ mapping.update(super()._resolve_dependencies(sym, get_hp))
274
+
275
+ # compute sum_to_combo values and make sure they are consistent with choices
276
+ sum_to_combo = {s: val for s, val in self._get_sum_to_combo().items() if s in self.choices}
277
+ self._sum_to_combo = sum_to_combo
278
+ self.choices = list(self._sum_to_combo)
279
+
280
+ # update choices for inputs from sum_to_combo and make them non-configurable
281
+ for i, hp_in in enumerate(self._inputs):
282
+ hp_in.choices = [combo[i] for combo in sum_to_combo.values()]
283
+ hp_in._is_configurable = False
284
+
285
+ self._set_hp_start_idx()
286
+
287
+ return mapping
288
+
289
+ def reset_choices(self) -> None:
290
+ """Reset the choices of the concat hparam.
291
+
292
+ Useful if we want to reset choices after input hparam choices are changed during modify().
293
+ """
294
+ self._sum_to_combo = self._get_sum_to_combo()
295
+ self._set_hp_start_idx()
296
+ with self._force_configurable():
297
+ self.choices = list(self._sum_to_combo)
298
+
299
+
300
+ def build_concat_hp(inputs: list[TracedHp]):
301
+ """Initialize a non-configurable concat hparam from a list of input hparams.
302
+
303
+ One key difference from ConcatTracedHp via tracing is that in ConcatTracedHp, the input hparams
304
+ are not configurable, and only the concatenated hparam is configurable. In build_concat_hp, its the opposite.
305
+
306
+ This is useful for building concat hparams from a list of configurable input hparams instead of
307
+ tracing (e.g. for megatron language model DynamicModule).
308
+ """
309
+ concat_hp = object.__new__(ConcatTracedHp)
310
+ concat_hp._inputs = inputs
311
+ concat_hp._sum_to_combo = concat_hp._get_sum_to_combo()
312
+ concat_hp._set_hp_start_idx()
313
+
314
+ choices = list(concat_hp._sum_to_combo)
315
+ concat_hp.__init__(choices) # type: ignore[misc]
316
+ concat_hp._is_configurable = False
317
+ concat_hp._importance_estimators = None
318
+
319
+ return concat_hp
@@ -0,0 +1,48 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024 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
+ """Hparam for Depth."""
17
+
18
+ from collections.abc import Callable
19
+
20
+ from modelopt.torch.trace import Symbol, SymDepth
21
+
22
+ from ..traced_hp import TracedHp, TracedHpRegistry
23
+
24
+ __all__ = ["DepthHparam"]
25
+
26
+
27
+ @TracedHpRegistry.register(SymDepth)
28
+ class DepthHparam(TracedHp):
29
+ """Hparam describing depth."""
30
+
31
+ def _resolve_dependencies(
32
+ self, sym: Symbol, get_hp: Callable[[Symbol], TracedHp]
33
+ ) -> dict[Symbol, TracedHp]:
34
+ assert isinstance(sym, SymDepth), f"Unexpected type {type(sym)} for {sym}!"
35
+ assert not sym._dependencies, "Depth should not have any dependencies!"
36
+ assert not sym._parent, "Depth should not have any parents!"
37
+
38
+ # we can only skip layers when all layers after are skippable
39
+ choices = {sym.max_depth}
40
+ for i in range(sym.max_depth - 1, -1, -1):
41
+ if not sym.is_skippable(i):
42
+ break
43
+ choices.add(i)
44
+
45
+ # record choices and skippable layers
46
+ self.choices = list(choices & set(self.choices))
47
+
48
+ return super()._resolve_dependencies(sym, get_hp)
@@ -0,0 +1,21 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024 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
+ """The dynamic equivalents for torch.nn modules."""
17
+
18
+ from .container import *
19
+ from .conv import *
20
+ from .linear import *
21
+ from .norm import *
@@ -0,0 +1,99 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2024 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
+ """Dynamic sequential implementation (variable depth based on torch.nn.modules.container)."""
17
+
18
+ from collections import OrderedDict
19
+ from collections.abc import Callable
20
+ from typing import Any
21
+
22
+ from torch import nn
23
+
24
+ from modelopt.torch.opt.dynamic import DynamicModule
25
+
26
+ from ..registry import DMRegistry
27
+ from ..traced_hp import TracedHp
28
+
29
+ __all__ = ["_DynamicSequential"]
30
+
31
+
32
+ def _activate_depth(func: Callable) -> Callable:
33
+ """A decorator for enabling dynamic depth within methods in _DynamicSequential.
34
+
35
+ The decorated method (and methods within the decorated method) will see any attributes with
36
+ dynamic depth enabled.
37
+ """
38
+
39
+ def func_with_dynamic_depth(self: "_DynamicSequential", *args, **kwargs) -> Any:
40
+ """Call func and return value wrapped with temporarily enabling dynamic depth."""
41
+ val = self._dynamic_depth
42
+ self._dynamic_depth = True
43
+ ret = func(self, *args, **kwargs)
44
+ self._dynamic_depth = val
45
+ return ret
46
+
47
+ return func_with_dynamic_depth
48
+
49
+
50
+ @DMRegistry.register({nn.Sequential: "nn.Sequential"})
51
+ class _DynamicSequential(DynamicModule):
52
+ """An ``nn.Sequential`` layer with dynamic hyperparams and variable ``depth``."""
53
+
54
+ _dynamic_depth: bool
55
+
56
+ @_activate_depth
57
+ def forward(self, input):
58
+ """Forward with activated variable depth."""
59
+ return super().forward(input)
60
+
61
+ @_activate_depth
62
+ def export(self) -> nn.Module:
63
+ """Export with activated variable depth."""
64
+ return super().export()
65
+
66
+ @_activate_depth
67
+ def __repr__(self):
68
+ """__repr__ with dynamic depth enabled -> str(self) will only show active subnet."""
69
+ return super().__repr__()
70
+
71
+ @_activate_depth
72
+ def extra_repr(self):
73
+ """extra_repr with dynamic depth enabled -> str(self) will only show active subnet."""
74
+ return super().extra_repr()
75
+
76
+ @staticmethod
77
+ def _get_modules(mod: "_DynamicSequential", modules: OrderedDict) -> OrderedDict:
78
+ if mod.depth < len(modules) and mod._dynamic_depth:
79
+ return OrderedDict(list(modules.items())[: mod.depth])
80
+ return modules
81
+
82
+ def _setup(self):
83
+ # register temp attribute to keep track of whether dynamic depth is on
84
+ self._register_temp_attribute("_dynamic_depth", False)
85
+
86
+ # register hyperparameters
87
+ self._register_hparam("depth", TracedHp(list(range(len(self) + 1))))
88
+
89
+ # register _modules as a dynamic attribute
90
+ self._register_dynamic_attribute("_modules", self._get_modules)
91
+
92
+ def modify(self, *, min_depth: int = 0):
93
+ """Modify the dynamic choices of the module according to provided keyword arguments.
94
+
95
+ Args:
96
+ min_depth: The minimum depth of the module.
97
+ """
98
+ hp = self.get_hparam("depth")
99
+ hp.choices = [d for d in hp.choices if d >= min_depth]