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,1164 @@
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
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
17
+
18
+
19
+ """Code that export quantized Megatron Core models for deployment."""
20
+
21
+ import json
22
+ import os
23
+ import tempfile
24
+ from collections import OrderedDict
25
+ from pathlib import Path
26
+ from typing import Any
27
+ from warnings import warn
28
+
29
+ import torch
30
+ import torch.distributed
31
+ import torch.nn as nn
32
+ from huggingface_hub import snapshot_download
33
+ from safetensors.torch import safe_open, save_file
34
+ from tqdm import tqdm
35
+
36
+ from modelopt import __version__
37
+ from modelopt.torch.utils import import_plugin
38
+
39
+ from .model_config import (
40
+ KV_CACHE_FP8,
41
+ QUANTIZATION_FP8,
42
+ QUANTIZATION_FP8_PB_REAL,
43
+ QUANTIZATION_FP8_PB_WO,
44
+ QUANTIZATION_NVFP4,
45
+ )
46
+ from .plugins.mcore_common import all_mcore_hf_export_mapping
47
+ from .plugins.mcore_custom import CustomModuleMapping, save_safetensors
48
+ from .plugins.megatron_importer import GPTModelImporter
49
+ from .quant_utils import (
50
+ get_activation_scaling_factor,
51
+ get_kv_cache_dtype,
52
+ get_quantization_format,
53
+ get_scaling_factor,
54
+ get_weight_block_size,
55
+ get_weight_scaling_factor,
56
+ get_weight_scaling_factor_2,
57
+ to_quantized_weight,
58
+ )
59
+
60
+ with import_plugin("transformers", verbose=False):
61
+ import transformers
62
+ from transformers import AutoProcessor
63
+
64
+ has_mcore = False
65
+ with import_plugin("megatron"):
66
+ from megatron.core.models.gpt import GPTModel
67
+ from megatron.core.models.mamba import MambaModel
68
+ from megatron.core.models.multimodal.llava_model import LLaVAModel
69
+ from megatron.core.parallel_state import (
70
+ get_pipeline_model_parallel_rank,
71
+ get_pipeline_model_parallel_world_size,
72
+ )
73
+ from megatron.core.ssm.mamba_layer import MambaLayer
74
+ from megatron.core.transformer.identity_op import IdentityOp
75
+ from megatron.core.transformer.torch_norm import L2Norm
76
+ from megatron.core.transformer.transformer_layer import TransformerLayer
77
+
78
+ has_mcore = True
79
+
80
+ __all__ = ["export_mcore_gpt_to_hf", "import_mcore_gpt_from_hf"]
81
+
82
+
83
+ # This path uses output_quantizer for KV cache quantization.
84
+ # The function below is the old version of get_kv_cache_scaling_factor which is now refactored to handle bmm_quantizer.
85
+ def get_kv_cache_scaling_factor(kv_module: nn.Module) -> torch.Tensor:
86
+ """Returns the kv_cache scaling factor if output quantizer is set. Else returns None by default."""
87
+ scaling_factor = (
88
+ get_scaling_factor(kv_module.output_quantizer)
89
+ if hasattr(kv_module, "output_quantizer")
90
+ else None
91
+ )
92
+
93
+ if not scaling_factor:
94
+ return None
95
+
96
+ # For FP8, we recommend default kv cache scaling factor to be 1.
97
+ if get_kv_cache_dtype(kv_module) == KV_CACHE_FP8:
98
+ if scaling_factor.item() > 0.5:
99
+ warn(
100
+ f"!!!!Large KV activations detected: {scaling_factor.item()}, "
101
+ "Quantized KV cache may lead to higher accuracy drop.\n!!!!"
102
+ )
103
+ scaling_factor = torch.max(
104
+ scaling_factor,
105
+ torch.tensor([1.0], dtype=torch.float, device=scaling_factor.device),
106
+ )
107
+ return scaling_factor
108
+
109
+
110
+ def get_quantized_state(
111
+ module: torch.nn.Module,
112
+ dtype: torch.dtype = torch.float16,
113
+ ) -> tuple[dict[str, torch.Tensor], str, int]:
114
+ """Return a state_dict, quantization format, and block_size of the module.
115
+
116
+ Args:
117
+ module: The target module to perform real quantization.
118
+ dtype: The default data type.
119
+
120
+ Returns:
121
+ Tuple: state_dict, quantization format, and block_size of the module.
122
+ """
123
+ name_to_value = {}
124
+ qformat: str = get_quantization_format(module)
125
+ block_size = get_weight_block_size(module)
126
+
127
+ if hasattr(module, "weight") and module.weight is not None:
128
+ weight = module.weight.to(dtype).cpu()
129
+ name_to_value["weight"] = weight
130
+ else:
131
+ return name_to_value, qformat, block_size
132
+
133
+ if hasattr(module, "bias") and module.bias is not None:
134
+ name_to_value["bias"] = module.bias.to(dtype).cpu()
135
+
136
+ if hasattr(module, "expert_bias") and module.expert_bias is not None:
137
+ name_to_value["expert_bias"] = module.expert_bias.to(dtype).cpu()
138
+
139
+ # Getting the weight scales
140
+ weight_scale = get_weight_scaling_factor(module)
141
+ weight_scale_2 = get_weight_scaling_factor_2(module)
142
+ if weight_scale is not None:
143
+ name_to_value["weight_scale"] = weight_scale
144
+
145
+ if weight_scale_2 is not None:
146
+ name_to_value["weight_scale_2"] = weight_scale_2
147
+
148
+ # Getting the input scale
149
+ input_scale = get_activation_scaling_factor(module)
150
+ if input_scale is not None:
151
+ name_to_value["input_scale"] = input_scale
152
+ # TODO (chenhany): support AWQ with pre_quant_scale
153
+ if hasattr(module.input_quantizer, "_pre_quant_scale"):
154
+ raise ValueError("Detect pre_quant_scale! SmoothQuant/AWQ are not yet supported!")
155
+
156
+ if hasattr(module, "output_quantizer"):
157
+ output_scale = get_kv_cache_scaling_factor(module)
158
+ if output_scale is not None:
159
+ name_to_value["output_scale"] = output_scale
160
+
161
+ return name_to_value, qformat, block_size
162
+
163
+
164
+ class GPTModelExporter:
165
+ """Megatron Core GPTModel Exporter.
166
+
167
+ The Exporter is created by `export_mcore_gpt_to_hf` to host attributes
168
+ and methods that export a quantized Megatron Core GPTModel to the Hugging
169
+ Face unified checkpoint.
170
+
171
+ Args:
172
+ model: The Megatron Core GPTModel instance.
173
+ pretrained_model_name_or_path: Can be either: the *model id* of a
174
+ pretrained model hosted inside a model repo on huggingface.co; or
175
+ a *directory* containing model weights saved using
176
+ [`~PreTrainedModel.save_pretrained`], e.g., `./my_model_directory/`.
177
+ export_extra_modules: If True, export extra modules like medusa_heads or
178
+ eagle_module. Otherwise, only export the base model.
179
+ dtype: The weights data type to export the unquantized layers.
180
+ """
181
+
182
+ def __init__(
183
+ self,
184
+ model: torch.nn.Module,
185
+ pretrained_model_name_or_path: str | os.PathLike | None = None,
186
+ export_extra_modules: bool = False,
187
+ dtype=torch.bfloat16,
188
+ trust_remote_code: bool = True,
189
+ ):
190
+ """Create a GPTModel exporter instance."""
191
+ if not isinstance(model, (GPTModel, MambaModel, LLaVAModel)):
192
+ raise ValueError("Input to GPTModelExport must be a megatron.core.models.GPTModel!")
193
+
194
+ self._state_dict = OrderedDict()
195
+ self._hf_pretrained_model_name = pretrained_model_name_or_path
196
+ self._hf_config = transformers.AutoConfig.from_pretrained(
197
+ pretrained_model_name_or_path, trust_remote_code=trust_remote_code
198
+ )
199
+ # If multimodal, extra the text_config
200
+ self._hf_text_config = getattr(self._hf_config, "text_config", self._hf_config)
201
+
202
+ # Update hf_config
203
+ self._hf_text_config.num_hidden_layers = model.config.num_layers
204
+ self._hf_text_config.hidden_size = model.config.hidden_size
205
+ self._hf_text_config.head_dim = model.config.kv_channels
206
+ self._hf_text_config.num_attention_heads = model.config.num_attention_heads
207
+ self._hf_text_config.num_key_value_heads = model.config.num_query_groups
208
+ self.is_multimodal = isinstance(model, LLaVAModel)
209
+ if not self.is_multimodal:
210
+ self._hf_text_config.intermediate_size = model.config.ffn_hidden_size
211
+ self._hf_quant_config = None
212
+ self._hf_extra_config = None
213
+ self.export_extra_modules = export_extra_modules
214
+ self.is_multimodal = isinstance(model, LLaVAModel)
215
+ self.model = model.language_model if self.is_multimodal else model
216
+ self.dtype = dtype
217
+ self.trust_remote_code = trust_remote_code
218
+ self.arch = self._hf_config.architectures[0]
219
+ # TODO: May modify this later according to what quantization exported ckpt is, currently only support BF16.
220
+ if self.arch == "GptOssForCausalLM":
221
+ if hasattr(self._hf_config, "quantization_config"):
222
+ del self._hf_config.quantization_config
223
+ self.all_rules = self._populate_rule_book()
224
+ self.rules = self.all_rules[self.arch]
225
+
226
+ if not hasattr(model, "_modelopt_state"):
227
+ return
228
+
229
+ for mode, mode_cfg in model._modelopt_state:
230
+ if mode == "medusa" and export_extra_modules:
231
+ medusa_config = {
232
+ "num_medusa_heads": mode_cfg["config"]["medusa_num_heads"],
233
+ "num_medusa_layers": mode_cfg["config"]["medusa_num_layers"],
234
+ }
235
+ self._hf_config.medusa = medusa_config
236
+ self.rules = self.all_rules["MedusaLlamaForCausalLM"]
237
+
238
+ if mode == "eagle" and export_extra_modules:
239
+ is_eagle3 = mode_cfg["config"]["eagle_architecture_config"]["use_aux_hidden_state"]
240
+
241
+ architectures = "LlamaForCausalLMEagle3" if is_eagle3 else "LlamaForCausalLMEagle"
242
+
243
+ self.rules = self.all_rules[architectures]
244
+
245
+ if torch.distributed.get_rank() == torch.distributed.get_world_size() - 1:
246
+ # By default, we use Llama-3.1
247
+ self._hf_extra_config = transformers.AutoConfig.from_pretrained(
248
+ "nvidia/Llama-3.1-8B-Instruct-FP8", trust_remote_code=self.trust_remote_code
249
+ )
250
+
251
+ eagle_config = {
252
+ "use_input_layernorm_in_first_layer": mode_cfg["config"][
253
+ "eagle_architecture_config"
254
+ ]["use_input_layernorm_in_first_layer"],
255
+ "use_last_layernorm": mode_cfg["config"]["eagle_architecture_config"][
256
+ "use_last_layernorm"
257
+ ],
258
+ "use_mtp_layernorm": mode_cfg["config"]["eagle_architecture_config"][
259
+ "use_mtp_layernorm"
260
+ ],
261
+ "use_aux_hidden_state": mode_cfg["config"]["eagle_architecture_config"][
262
+ "use_aux_hidden_state"
263
+ ],
264
+ "eagle_aux_hidden_state_layer_ids": model.eagle_config.eagle_aux_hidden_state_layer_ids,
265
+ }
266
+
267
+ eagle_config_update = {
268
+ "architectures": [architectures],
269
+ "head_dim": model.eagle_module.config.kv_channels,
270
+ "hidden_act": self._hf_text_config.hidden_act,
271
+ "hidden_size": self._hf_text_config.hidden_size,
272
+ "intermediate_size": model.eagle_module.config.ffn_hidden_size,
273
+ "max_position_embeddings": self._hf_text_config.max_position_embeddings,
274
+ "num_attention_heads": model.eagle_module.config.num_attention_heads,
275
+ "num_key_value_heads": model.eagle_module.config.num_query_groups,
276
+ "num_hidden_layers": mode_cfg["config"]["eagle_architecture_config"][
277
+ "num_hidden_layers"
278
+ ],
279
+ "vocab_size": self._hf_text_config.vocab_size,
280
+ # Unset any special token ids given that the tokenizer can change here.
281
+ "bos_token_id": None,
282
+ "eos_token_id": None,
283
+ "pad_token_id": None,
284
+ "sep_token_id": None,
285
+ # The following attributes are EAGLE specific
286
+ "eagle_config": eagle_config,
287
+ "draft_vocab_size": mode_cfg["config"]["eagle_architecture_config"][
288
+ "draft_vocab_size"
289
+ ],
290
+ }
291
+
292
+ self._hf_extra_config.update(eagle_config_update)
293
+
294
+ def save_pretrained(
295
+ self,
296
+ save_directory: str | os.PathLike,
297
+ pretrained_model_name_or_path: str | os.PathLike | None = None,
298
+ ):
299
+ """Save a unified checkpoint which can be deployed by vLLM and TensorRT-LLM.
300
+
301
+ Args:
302
+ save_directory: Directory to which to save. Will be created if it doesn't exist.
303
+ """
304
+ pp_rank = get_pipeline_model_parallel_rank()
305
+ pp_size = get_pipeline_model_parallel_world_size()
306
+
307
+ # We use the 1st PP rank to handle VLM because vision_models
308
+ # and vision_proj only exist in the first stage.
309
+ is_first_stage_main_rank = pp_rank == 0
310
+ # We use the last PP rank to write the config because
311
+ # medusa_heads and eagle_module only exist in the last stage.
312
+ is_last_stage_main_rank = pp_rank == pp_size - 1
313
+
314
+ # Main export process
315
+ state_dict = self.extra_state_dict if self.export_extra_modules else self.state_dict
316
+ quantization_format = get_quantization_format(self.model)
317
+ quantization = None
318
+ kv_cache_quantization = None
319
+
320
+ if quantization_format in (
321
+ QUANTIZATION_FP8_PB_REAL,
322
+ QUANTIZATION_FP8_PB_WO,
323
+ ):
324
+ quantization = quantization_format
325
+ elif quantization_format == QUANTIZATION_FP8:
326
+ quantization = "FP8"
327
+ elif quantization_format == QUANTIZATION_NVFP4:
328
+ quantization = "NVFP4"
329
+
330
+ # We use the last PP rank and the 1st EP rank to write the config because
331
+ # medusa_heads and eagle_module only exist in the last stage.
332
+ if is_last_stage_main_rank:
333
+ if self.export_extra_modules and self._hf_extra_config is not None:
334
+ self._hf_extra_config.save_pretrained(save_directory)
335
+ else:
336
+ self._hf_config.save_pretrained(save_directory)
337
+ try:
338
+ generation_config = transformers.GenerationConfig.from_pretrained(
339
+ self._hf_pretrained_model_name
340
+ )
341
+ generation_config.save_pretrained(save_directory)
342
+ except OSError:
343
+ pass
344
+ try:
345
+ tokenizer = transformers.AutoTokenizer.from_pretrained(
346
+ self._hf_pretrained_model_name
347
+ )
348
+ tokenizer.save_pretrained(save_directory)
349
+ except OSError:
350
+ pass
351
+ except TypeError:
352
+ pass
353
+ try:
354
+ # Load and save preprocessor config from the original model
355
+ processor = AutoProcessor.from_pretrained(
356
+ self._hf_pretrained_model_name, trust_remote_code=self.trust_remote_code
357
+ )
358
+ if hasattr(processor, "image_processor"):
359
+ processor.image_processor.save_pretrained(save_directory)
360
+ except (OSError, ValueError, ImportError):
361
+ pass
362
+
363
+ if is_last_stage_main_rank:
364
+ hf_quant_config = {
365
+ "producer": {
366
+ "name": "modelopt",
367
+ "version": __version__,
368
+ },
369
+ "quantization": {
370
+ "quant_algo": quantization,
371
+ "kv_cache_quant_algo": kv_cache_quantization,
372
+ "exclude_modules": ["lm_head"],
373
+ },
374
+ }
375
+ with open(save_directory + "/hf_quant_config.json", "w") as f:
376
+ json.dump(hf_quant_config, f, indent=4)
377
+
378
+ if (
379
+ is_first_stage_main_rank
380
+ and self.is_multimodal
381
+ and pretrained_model_name_or_path is not None
382
+ ):
383
+ hf_checkpoint_path = Path(pretrained_model_name_or_path)
384
+ if not hf_checkpoint_path.is_dir():
385
+ hf_checkpoint_path = tempfile.gettempdir() + "/" + pretrained_model_name_or_path
386
+ if not Path(hf_checkpoint_path).exists():
387
+ snapshot_download(
388
+ repo_id=pretrained_model_name_or_path,
389
+ local_dir=hf_checkpoint_path,
390
+ )
391
+
392
+ safetensors_file = Path(hf_checkpoint_path) / "model.safetensors"
393
+ safetensors_index_file = Path(hf_checkpoint_path) / "model.safetensors.index.json"
394
+
395
+ multimodal_state_dict = {}
396
+
397
+ if safetensors_file.is_file():
398
+ print(f"Loading multimodal components from single file: {safetensors_file}")
399
+ with safe_open(safetensors_file, framework="pt") as f:
400
+ multimodal_keys = [
401
+ key
402
+ for key in f.keys() # noqa: SIM118
403
+ if key.startswith(("multi_modal_projector", "vision_model"))
404
+ ]
405
+ for key in tqdm(multimodal_keys, desc="Loading multimodal tensors"):
406
+ multimodal_state_dict[key] = f.get_tensor(key)
407
+
408
+ elif safetensors_index_file.is_file():
409
+ print(f"Loading multimodal components from sharded model: {hf_checkpoint_path}")
410
+ with open(safetensors_index_file) as f:
411
+ safetensors_index = json.load(f)
412
+
413
+ # For multimodal models, vision_model and multi_modal_projector are in the first shard
414
+ all_shard_files = sorted(set(safetensors_index["weight_map"].values()))
415
+ first_shard_file = all_shard_files[0] # e.g., "model-00001-of-00050.safetensors"
416
+
417
+ # Load multimodal components from the first shard file
418
+ safetensors_filepath = Path(hf_checkpoint_path) / first_shard_file
419
+ print(f"Loading multimodal components from {first_shard_file}")
420
+
421
+ with safe_open(safetensors_filepath, framework="pt") as f:
422
+ shard_keys = list(f.keys())
423
+ multimodal_keys_in_shard = [
424
+ k
425
+ for k in shard_keys
426
+ if k.startswith(("multi_modal_projector", "vision_model"))
427
+ ]
428
+
429
+ if multimodal_keys_in_shard:
430
+ print(
431
+ f"Found {len(multimodal_keys_in_shard)} multimodal tensors in {first_shard_file}"
432
+ )
433
+ for key in tqdm(
434
+ multimodal_keys_in_shard, desc="Loading multimodal tensors"
435
+ ):
436
+ multimodal_state_dict[key] = f.get_tensor(key)
437
+ else:
438
+ print(f"No multimodal components found in {first_shard_file}")
439
+
440
+ else:
441
+ print(f"Warning: No safetensors files found in {hf_checkpoint_path}")
442
+
443
+ print(f"Successfully loaded {len(multimodal_state_dict)} multimodal tensors")
444
+ # Add multimodal components to state_dict
445
+ state_dict.update(multimodal_state_dict)
446
+
447
+ # Barrier to ensure the export_dir has been created.
448
+ torch.distributed.barrier()
449
+
450
+ if self.export_extra_modules:
451
+ if is_last_stage_main_rank:
452
+ save_file(
453
+ state_dict, save_directory + "/model.safetensors", metadata={"format": "pt"}
454
+ )
455
+ torch.distributed.barrier()
456
+ return
457
+
458
+ save_safetensors(state_dict, save_directory)
459
+
460
+ @property
461
+ def state_dict(self):
462
+ """Return the real quantized state_dict of the base model."""
463
+ if len(self._state_dict) == 0:
464
+ self._get_state_dict()
465
+ return self._state_dict
466
+
467
+ @property
468
+ def extra_state_dict(self):
469
+ if len(self._state_dict) == 0:
470
+ self._get_medusa_heads_state_dict()
471
+ self._get_eagle_module_state_dict()
472
+ return self._state_dict
473
+
474
+ def _populate_rule_book(self):
475
+ all_rules = {}
476
+
477
+ def _custom_mapping_to_lambda(mapping):
478
+ method_map = {
479
+ "name_remapping": self._name_remapping,
480
+ "qkv_slicing": self._qkv_slicing,
481
+ "gated_mlp_slicing": self._gated_mlp_slicing,
482
+ "pack_name_remapping": self._pack_name_remapping,
483
+ "pack_name_remapping_gpt_oss": self._pack_name_remapping_gpt_oss,
484
+ }
485
+ func = method_map[mapping.func_name]
486
+ prefix = mapping.target_name_or_prefix
487
+ func_kwargs = mapping.func_kwargs
488
+ return lambda m, *args: func(m, prefix.format(*args), **func_kwargs)
489
+
490
+ for arch, mappings in all_mcore_hf_export_mapping.items():
491
+ all_rules[arch] = {
492
+ k: _custom_mapping_to_lambda(v) if isinstance(v, CustomModuleMapping) else v
493
+ for (k, v) in mappings.items()
494
+ if isinstance(v, (CustomModuleMapping, bool))
495
+ }
496
+
497
+ return all_rules
498
+
499
+ def _get_weight_scales(self, quantized_state: dict[str, Any], qformat: str):
500
+ weight_scale = quantized_state.pop("weight_scale", None)
501
+ weight_scale_2 = quantized_state.pop("weight_scale_2", None)
502
+
503
+ if weight_scale is not None:
504
+ weight_scale = weight_scale.clone().detach()
505
+ if qformat == QUANTIZATION_FP8 and weight_scale.numel() == 1:
506
+ weight_scale = weight_scale.squeeze()
507
+ if weight_scale_2 is not None:
508
+ weight_scale_2 = weight_scale_2.clone().detach()
509
+
510
+ return weight_scale, weight_scale_2
511
+
512
+ def _name_remapping(
513
+ self,
514
+ module: torch.nn.Module | torch.Tensor,
515
+ prefix: str,
516
+ skip_output_scale: bool = True,
517
+ mapping={},
518
+ ):
519
+ if isinstance(module, torch.Tensor):
520
+ self._state_dict[prefix] = module
521
+ return
522
+
523
+ name_to_value, qformat, block_size = get_quantized_state(module, self.dtype)
524
+
525
+ weight = name_to_value.pop("weight")
526
+ weight_scale, weight_scale_2 = self._get_weight_scales(name_to_value, qformat)
527
+
528
+ if weight_scale is None:
529
+ self._state_dict[prefix + "weight"] = weight
530
+ else:
531
+ self._state_dict[prefix + "weight"] = to_quantized_weight(
532
+ weight,
533
+ weight_scale,
534
+ qformat,
535
+ weight_scale_2,
536
+ block_size,
537
+ )
538
+ self._state_dict[prefix + "weight_scale"] = weight_scale.detach().clone()
539
+
540
+ if weight_scale_2 is not None:
541
+ if len(weight_scale_2.shape) > 0:
542
+ raise ValueError("weight_scale_2 must be a scalar!")
543
+ self._state_dict[prefix + "weight_scale_2"] = weight_scale_2.detach().clone()
544
+
545
+ for key, val in name_to_value.items():
546
+ if key == "output_scale" and skip_output_scale:
547
+ continue
548
+ else:
549
+ source_key = mapping.get(key, key)
550
+ self._state_dict[prefix + source_key] = val
551
+
552
+ def _gated_mlp_slicing(
553
+ self, module, prefix, gate_proj_name="gate_proj", up_proj_name="up_proj"
554
+ ):
555
+ name_to_value, qformat, block_size = get_quantized_state(module, self.dtype)
556
+
557
+ weight = name_to_value.pop("weight")
558
+ weight_scale, weight_scale_2 = self._get_weight_scales(name_to_value, qformat)
559
+
560
+ gate_proj_prefix = prefix + gate_proj_name + "."
561
+ up_proj_prefix = prefix + up_proj_name + "."
562
+
563
+ ffn_hidden_size = module.config.ffn_hidden_size
564
+ gate_proj_weight = weight[:ffn_hidden_size, :]
565
+ up_proj_weight = weight[ffn_hidden_size:, :]
566
+
567
+ if weight_scale is None:
568
+ self._state_dict[gate_proj_prefix + "weight"] = gate_proj_weight
569
+ self._state_dict[up_proj_prefix + "weight"] = up_proj_weight
570
+ else:
571
+ if len(weight_scale.shape) == 0:
572
+ gate_proj_weight_scale = weight_scale.detach().clone()
573
+ up_proj_weight_scale = weight_scale.detach().clone()
574
+ else:
575
+ gate_proj_weight_scale = weight_scale[:ffn_hidden_size]
576
+ up_proj_weight_scale = weight_scale[ffn_hidden_size:]
577
+ self._state_dict[gate_proj_prefix + "weight"] = to_quantized_weight(
578
+ gate_proj_weight,
579
+ gate_proj_weight_scale,
580
+ qformat,
581
+ weight_scale_2,
582
+ block_size,
583
+ )
584
+ self._state_dict[up_proj_prefix + "weight"] = to_quantized_weight(
585
+ up_proj_weight,
586
+ up_proj_weight_scale,
587
+ qformat,
588
+ weight_scale_2,
589
+ block_size,
590
+ )
591
+ self._state_dict[gate_proj_prefix + "weight_scale"] = gate_proj_weight_scale
592
+ self._state_dict[up_proj_prefix + "weight_scale"] = up_proj_weight_scale
593
+
594
+ if weight_scale_2 is not None:
595
+ if len(weight_scale_2.shape) > 0:
596
+ raise ValueError("weight_scale_2 must be a scalar!")
597
+ self._state_dict[gate_proj_prefix + "weight_scale_2"] = weight_scale_2.detach().clone()
598
+ self._state_dict[up_proj_prefix + "weight_scale_2"] = weight_scale_2.detach().clone()
599
+
600
+ # weight and weight_scale have been pop out.
601
+ for key, val in name_to_value.items():
602
+ gate_proj_key = gate_proj_prefix + key
603
+ up_proj_key = up_proj_prefix + key
604
+ if key == "output_scale":
605
+ continue
606
+ else:
607
+ self._state_dict[gate_proj_key] = val.detach().clone()
608
+ self._state_dict[up_proj_key] = val.detach().clone()
609
+
610
+ def _qkv_slicing(
611
+ self,
612
+ module,
613
+ prefix,
614
+ q_proj_name="q_proj",
615
+ k_proj_name="k_proj",
616
+ v_proj_name="v_proj",
617
+ k_scale_name="k_scale",
618
+ v_scale_name="v_scale",
619
+ ):
620
+ name_to_value, qformat, block_size = get_quantized_state(module, self.dtype)
621
+
622
+ q_proj_prefix = prefix + q_proj_name + "."
623
+ k_proj_prefix = prefix + k_proj_name + "."
624
+ v_proj_prefix = prefix + v_proj_name + "."
625
+
626
+ config = module.config
627
+ hidden_size = config.hidden_size
628
+ num_query_groups = config.num_query_groups
629
+ head_num = config.num_attention_heads
630
+ head_size = config.kv_channels
631
+ heads_per_group = head_num // num_query_groups
632
+ qkv_total_dim = head_num + 2 * num_query_groups
633
+
634
+ weight = name_to_value.pop("weight")
635
+
636
+ if weight.shape[-1] == 2 * hidden_size:
637
+ print(
638
+ "Parameter linear_qkv.weight has 2x the hidden_size."
639
+ "Set hidden_size to 2x the hidden_size. EAGLE3 is the only known"
640
+ "use case which has this behavior."
641
+ )
642
+ hidden_size = 2 * hidden_size
643
+
644
+ weight = weight.reshape([qkv_total_dim, head_size, hidden_size])
645
+ weight_scale, weight_scale_2 = self._get_weight_scales(name_to_value, qformat)
646
+
647
+ q_slice = torch.cat(
648
+ [
649
+ torch.arange((heads_per_group + 2) * i, (heads_per_group + 2) * i + heads_per_group)
650
+ for i in range(num_query_groups)
651
+ ]
652
+ )
653
+ k_slice = torch.arange(heads_per_group, qkv_total_dim, (heads_per_group + 2))
654
+ v_slice = torch.arange(heads_per_group + 1, qkv_total_dim, (heads_per_group + 2))
655
+ ## Example of slices
656
+ ## 7b: num_query_groups = head_num = 32,
657
+ ## q_slice = [0, 3, 6, 9 , ... 90, 93]
658
+ ## k_slice = [1, 4, 7, 10, ... 91, 94]
659
+ ## v_slice = [2, 5, 8, 11, ... 92, 95]
660
+ ## 70b (with GQA): num_query_groups = 8, head_num = 64
661
+ ## q_slice = [0, 1, .. 6, 7, 10, 11, .. 16, 17, 20, 21, .. 67, 70, ... 76, 77]
662
+ ## k_slice = [8, 18, 28, ... 68, 78]
663
+ ## v_slice = [9, 19, 29, ... 69, 79]
664
+ slices = [q_slice, k_slice, v_slice]
665
+ prefixes = [q_proj_prefix, k_proj_prefix, v_proj_prefix]
666
+
667
+ proj_weights = [weight[s].reshape(-1, hidden_size) for s in slices]
668
+ proj_keys = [p + "weight" for p in prefixes]
669
+
670
+ if weight_scale is None:
671
+ for key, weight in zip(proj_keys, proj_weights):
672
+ self._state_dict[key] = weight
673
+ else:
674
+ if len(weight_scale.shape) > 0:
675
+ # AWQ per-block or per-channel scaling
676
+ weight_scale_dtype = weight_scale.dtype
677
+ weight_scale_hidden_size = weight_scale.shape[-1]
678
+ weight_scale = weight_scale.to(dtype=float).reshape(
679
+ [qkv_total_dim, head_size, weight_scale_hidden_size]
680
+ )
681
+ proj_weight_scales = [
682
+ weight_scale[s]
683
+ .reshape(-1, weight_scale_hidden_size)
684
+ .to(dtype=weight_scale_dtype)
685
+ for s in slices
686
+ ]
687
+ else:
688
+ # per-tensor scaling
689
+ proj_weight_scales = [
690
+ weight_scale.detach().clone(),
691
+ weight_scale.detach().clone(),
692
+ weight_scale.detach().clone(),
693
+ ]
694
+
695
+ for weight, scale, key in zip(proj_weights, proj_weight_scales, proj_keys):
696
+ quantized_weight = to_quantized_weight(
697
+ weight,
698
+ scale,
699
+ qformat,
700
+ weight_scale_2,
701
+ block_size,
702
+ )
703
+ self._state_dict[key] = quantized_weight
704
+ self._state_dict[key + "_scale"] = scale
705
+
706
+ if weight_scale_2 is not None:
707
+ if len(weight_scale_2.shape) > 0:
708
+ raise ValueError("weight_scale_2 must be a scalar!")
709
+ for weight, scale, key in zip(proj_weights, proj_weight_scales, proj_keys):
710
+ self._state_dict[key + "_scale_2"] = weight_scale_2.detach().clone()
711
+
712
+ # weight and weight_scale have been pop out.
713
+ for key, val in name_to_value.items():
714
+ q_proj_key = q_proj_prefix + key
715
+ k_proj_key = k_proj_prefix + key
716
+ v_proj_key = v_proj_prefix + key
717
+ if key == "output_scale":
718
+ self._state_dict[prefix + k_scale_name] = val.detach().clone()
719
+ self._state_dict[prefix + v_scale_name] = val.detach().clone()
720
+ elif key == "bias":
721
+ # Slice bias similar to weight
722
+ bias = val.detach().clone()
723
+ bias = bias.reshape([qkv_total_dim, head_size])
724
+ proj_biases = [bias[s].reshape(-1) for s in slices]
725
+ proj_bias_keys = [q_proj_prefix + key, k_proj_prefix + key, v_proj_prefix + key]
726
+ for bias_tensor, bias_key in zip(proj_biases, proj_bias_keys):
727
+ self._state_dict[bias_key] = bias_tensor
728
+ else:
729
+ self._state_dict[q_proj_key] = val.detach().clone()
730
+ self._state_dict[k_proj_key] = val.detach().clone()
731
+ self._state_dict[v_proj_key] = val.detach().clone()
732
+
733
+ def _pack_name_remapping(self, module, prefix, layer_type=None):
734
+ """Pack name remapping into one tensor."""
735
+ weight_list = []
736
+ weight_scale_list = []
737
+ weight_scale_2_list = []
738
+ input_scale_list = []
739
+
740
+ for expert in module:
741
+ assert layer_type is not None, "layer_type is required for pack_name_remapping"
742
+ name_to_value, qformat, block_size = get_quantized_state(
743
+ getattr(expert, layer_type), self.dtype
744
+ )
745
+ weight = name_to_value.pop("weight")
746
+ weight_scale, weight_scale_2 = self._get_weight_scales(name_to_value, qformat)
747
+ input_scale = (
748
+ name_to_value.pop("input_scale") if "input_scale" in name_to_value else None
749
+ )
750
+
751
+ weight_list.append(weight)
752
+ weight_scale_list.append(weight_scale)
753
+ weight_scale_2_list.append(weight_scale_2)
754
+ input_scale_list.append(input_scale)
755
+
756
+ merged_weight = torch.stack(weight_list, dim=0)
757
+
758
+ # Transpose the last two dimensions to match HuggingFace format
759
+ # NeMo format: [num_experts, out_features, in_features]
760
+ # HF format: [num_experts, in_features, out_features]
761
+ merged_weight = merged_weight.transpose(-2, -1).contiguous()
762
+
763
+ if weight_scale_2_list[0] is None:
764
+ merged_weight_scale_2 = None
765
+ if weight_scale_list[0] is not None:
766
+ merged_weight_scale = torch.max(torch.stack(weight_scale_list, dim=0), dim=0)[0]
767
+ else:
768
+ merged_weight_scale = None
769
+ else:
770
+ # NVFP4
771
+ merged_weight_scale_2 = torch.max(torch.stack(weight_scale_2_list, dim=0), dim=0)[0]
772
+ merged_weight_scale = torch.stack(weight_scale_list, dim=0)
773
+ # Transpose the scaling factors to match the transposed weights
774
+ merged_weight_scale = merged_weight_scale.transpose(-2, -1).contiguous()
775
+
776
+ if input_scale_list[0] is not None:
777
+ merged_input_scale = torch.max(torch.stack(input_scale_list, dim=0), dim=0)[0]
778
+ else:
779
+ merged_input_scale = None
780
+
781
+ # Save the merged weights
782
+ if merged_weight_scale is None:
783
+ self._state_dict[prefix] = merged_weight
784
+ else:
785
+ self._state_dict[prefix] = to_quantized_weight(
786
+ merged_weight,
787
+ merged_weight_scale,
788
+ qformat,
789
+ merged_weight_scale_2,
790
+ block_size,
791
+ )
792
+ self._state_dict[prefix + "_weight_scale"] = merged_weight_scale
793
+ if merged_weight_scale_2 is not None:
794
+ self._state_dict[prefix + "_weight_scale_2"] = merged_weight_scale_2
795
+ if merged_input_scale is not None:
796
+ self._state_dict[prefix + "_input_scale"] = merged_input_scale
797
+
798
+ def _pack_name_remapping_gpt_oss(self, module, prefix, layer_type=None):
799
+ """Pack name remapping into one tensor."""
800
+ weight_list = []
801
+ weight_scale_list = []
802
+ weight_scale_2_list = []
803
+ input_scale_list = []
804
+ bias_list = []
805
+
806
+ for expert in module:
807
+ assert layer_type is not None, "layer_type is required for pack_name_remapping"
808
+ name_to_value, qformat, block_size = get_quantized_state(
809
+ getattr(expert, layer_type), self.dtype
810
+ )
811
+ weight = name_to_value.pop("weight")
812
+ bias = name_to_value.pop("bias", None)
813
+ weight_scale, weight_scale_2 = self._get_weight_scales(name_to_value, qformat)
814
+ input_scale = (
815
+ name_to_value.pop("input_scale") if "input_scale" in name_to_value else None
816
+ )
817
+
818
+ weight_list.append(weight)
819
+ weight_scale_list.append(weight_scale)
820
+ weight_scale_2_list.append(weight_scale_2)
821
+ input_scale_list.append(input_scale)
822
+ bias_list.append(bias)
823
+
824
+ merged_weight = torch.stack(weight_list, dim=0)
825
+
826
+ # Transpose the last two dimensions to match HuggingFace format (except for GptOssForCausalLM)
827
+ # NeMo format: [num_experts, out_features, in_features]
828
+ # HF format: [num_experts, in_features, out_features]
829
+
830
+ # TODO: Need to decide if we want to transpose the weight or not.
831
+ merged_weight = merged_weight.transpose(-2, -1).contiguous()
832
+
833
+ # Apply interleaving for GptOssForCausalLM linear_fc1 to match HF format
834
+ if layer_type == "linear_fc1":
835
+ # Megatron has de-interleaved format, need to interleave for HF
836
+ # Pattern: first half -> even indices, second half -> odd indices
837
+ num_experts, in_features, out_features = merged_weight.shape
838
+ half_out = out_features // 2
839
+
840
+ # Create interleaved tensor
841
+ interleaved_weight = torch.zeros_like(merged_weight)
842
+ interleaved_weight[:, :, ::2] = merged_weight[
843
+ :, :, :half_out
844
+ ] # First half -> even indices
845
+ interleaved_weight[:, :, 1::2] = merged_weight[
846
+ :, :, half_out:
847
+ ] # Second half -> odd indices
848
+ merged_weight = interleaved_weight
849
+
850
+ # Handle bias tensors
851
+ merged_bias = None
852
+ if bias_list[0] is not None:
853
+ merged_bias = torch.stack(bias_list, dim=0)
854
+
855
+ # Apply interleaving for GptOssForCausalLM linear_fc1 bias to match HF format
856
+ if layer_type == "linear_fc1":
857
+ num_experts, bias_len = merged_bias.shape
858
+ half_bias_len = bias_len // 2
859
+
860
+ # Create interleaved bias tensor
861
+ interleaved_bias = torch.zeros_like(merged_bias)
862
+ interleaved_bias[:, ::2] = merged_bias[
863
+ :, :half_bias_len
864
+ ] # First half -> even indices
865
+ interleaved_bias[:, 1::2] = merged_bias[
866
+ :, half_bias_len:
867
+ ] # Second half -> odd indices
868
+ merged_bias = interleaved_bias
869
+
870
+ if weight_scale_2_list[0] is None:
871
+ merged_weight_scale_2 = None
872
+ if weight_scale_list[0] is not None:
873
+ merged_weight_scale = torch.max(torch.stack(weight_scale_list, dim=0), dim=0)[0]
874
+ else:
875
+ merged_weight_scale = None
876
+ else:
877
+ # NVFP4
878
+ merged_weight_scale_2 = torch.max(torch.stack(weight_scale_2_list, dim=0), dim=0)[0]
879
+ merged_weight_scale = torch.stack(weight_scale_list, dim=0)
880
+ # Transpose the scaling factors to match the transposed weights
881
+ # TODO: Need to decide if we want to transpose the weight or not.
882
+ merged_weight_scale = merged_weight_scale.transpose(-2, -1).contiguous()
883
+
884
+ if input_scale_list[0] is not None:
885
+ merged_input_scale = torch.max(torch.stack(input_scale_list, dim=0), dim=0)[0]
886
+ else:
887
+ merged_input_scale = None
888
+
889
+ # Save the merged weights
890
+ if merged_weight_scale is None:
891
+ # TODO: May need to modify the key name later.
892
+ self._state_dict[prefix] = merged_weight
893
+ else:
894
+ self._state_dict[prefix] = to_quantized_weight(
895
+ merged_weight,
896
+ merged_weight_scale,
897
+ qformat,
898
+ merged_weight_scale_2,
899
+ block_size,
900
+ )
901
+ self._state_dict[prefix + "_weight_scale"] = merged_weight_scale
902
+ if merged_weight_scale_2 is not None:
903
+ self._state_dict[prefix + "_weight_scale_2"] = merged_weight_scale_2
904
+ if merged_input_scale is not None:
905
+ self._state_dict[prefix + "_input_scale"] = merged_input_scale
906
+
907
+ # Save bias tensors if they exist
908
+ if merged_bias is not None:
909
+ # TODO: May need to modify the key name later.
910
+ self._state_dict[prefix + "_bias"] = merged_bias
911
+
912
+ def _get_medusa_heads_state_dict(self):
913
+ medusa_heads = getattr(self.model, "medusa_heads", None)
914
+ if medusa_heads is None:
915
+ return
916
+
917
+ for head_id, head in enumerate(medusa_heads):
918
+ self.rules["medusa_heads.lm_head"](head.lm_head, head_id)
919
+ for layer_id, layer in enumerate(head.medusa_layers):
920
+ self.rules["medusa_heads.medusa_layers.linear"](layer.linear, head_id, layer_id)
921
+
922
+ def _get_eagle_module_state_dict(self):
923
+ eagle_module = getattr(self.model, "eagle_module", None)
924
+
925
+ if eagle_module is None:
926
+ return
927
+
928
+ # if hasattr(self.model, "embedding"):
929
+ # self.rules["word_embeddings"](self.model.embedding.word_embeddings)
930
+
931
+ self.rules["fc"](eagle_module.fc)
932
+ if self.model.eagle_config.use_aux_hidden_state:
933
+ self.rules["enorm"](eagle_module.enorm)
934
+ elif self.model.eagle_config.use_mtp_layernorm:
935
+ self.rules["enorm"](eagle_module.enorm)
936
+ self.rules["hnorm"](eagle_module.hnorm)
937
+
938
+ if self.model.eagle_config.use_last_layernorm:
939
+ self.rules["final_layernorm"](eagle_module.decoder.final_layernorm)
940
+
941
+ if hasattr(self.model.eagle_module, "eagle_output_layer"):
942
+ self.rules["output_layer"](eagle_module.eagle_output_layer)
943
+ if hasattr(self.model.eagle_module, "dt2"):
944
+ self.rules["d2t"](eagle_module.d2t)
945
+
946
+ for layer in eagle_module.decoder.layers:
947
+ layer_id = layer.layer_number - 1
948
+
949
+ if layer_id > 0 or self.model.eagle_config.use_input_layernorm_in_first_layer:
950
+ self.rules["input_layernorm"](layer.input_layernorm, layer_id)
951
+
952
+ if "MLASelfAttention" in str(type(layer.self_attention)):
953
+ if hasattr(layer.self_attention, "linear_q_proj"):
954
+ self.rules["eagle_module.linear_q_proj"](
955
+ layer.self_attention.linear_q_proj, layer_id
956
+ )
957
+ else:
958
+ self.rules["eagle_module.linear_q_down_proj"](
959
+ layer.self_attention.linear_q_down_proj, layer_id
960
+ )
961
+ self.rules["eagle_module.linear_q_layernorm"](
962
+ layer.self_attention.q_layernorm, layer_id
963
+ )
964
+ self.rules["eagle_module.linear_q_up_proj"](
965
+ layer.self_attention.linear_q_up_proj, layer_id
966
+ )
967
+
968
+ self.rules["eagle_module.linear_kv_down_proj"](
969
+ layer.self_attention.linear_kv_down_proj, layer_id
970
+ )
971
+ self.rules["eagle_module.linear_kv_layernorm"](
972
+ layer.self_attention.kv_layernorm, layer_id
973
+ )
974
+ self.rules["eagle_module.linear_kv_up_proj"](
975
+ layer.self_attention.linear_kv_up_proj, layer_id
976
+ )
977
+ else:
978
+ self.rules["linear_qkv"](layer.self_attention.linear_qkv, layer_id)
979
+
980
+ self.rules["linear_proj"](layer.self_attention.linear_proj, layer_id)
981
+ self.rules["pre_mlp_layernorm"](layer.pre_mlp_layernorm, layer_id)
982
+
983
+ if "MoE" in str(type(layer.mlp)):
984
+ self.rules["eagle_module.router"](layer.mlp.router, layer_id)
985
+ if hasattr(layer.mlp, "shared_experts") and layer.mlp.shared_experts is not None:
986
+ self.rules["eagle_module.shared_experts.linear_fc1"](
987
+ layer.mlp.shared_experts.linear_fc1, layer_id
988
+ )
989
+ self.rules["eagle_module.shared_experts.linear_fc2"](
990
+ layer.mlp.shared_experts.linear_fc2, layer_id
991
+ )
992
+ for expert_id, expert in enumerate(layer.mlp.experts.local_experts):
993
+ self.rules["eagle_module.local_experts.linear_fc1"](
994
+ expert.linear_fc1, layer_id, expert_id
995
+ )
996
+ self.rules["eagle_module.local_experts.linear_fc2"](
997
+ expert.linear_fc2, layer_id, expert_id
998
+ )
999
+ else:
1000
+ self.rules["linear_fc1"](layer.mlp.linear_fc1, layer_id)
1001
+ self.rules["linear_fc2"](layer.mlp.linear_fc2, layer_id)
1002
+
1003
+ def _get_state_dict(self):
1004
+ model = self.model
1005
+
1006
+ # Embedding
1007
+ if hasattr(model, "embedding"):
1008
+ self.rules["word_embeddings"](model.embedding.word_embeddings)
1009
+
1010
+ # Final layernorm
1011
+ if hasattr(model.decoder, "final_layernorm") and model.decoder.final_layernorm:
1012
+ self.rules["final_layernorm"](model.decoder.final_layernorm)
1013
+
1014
+ if hasattr(model.decoder, "final_norm") and model.decoder.final_norm:
1015
+ self.rules["final_norm"](model.decoder.final_norm)
1016
+
1017
+ # Output layer
1018
+ if hasattr(model, "output_layer") and not model.share_embeddings_and_output_weights:
1019
+ self.rules["output_layer"](model.output_layer)
1020
+
1021
+ # Decoder layers
1022
+ for layer in model.decoder.layers:
1023
+ layer_id = layer.layer_number - 1
1024
+
1025
+ if isinstance(layer, MambaLayer):
1026
+ if not isinstance(layer.norm, IdentityOp):
1027
+ self.rules["norm"](layer.norm, layer_id)
1028
+
1029
+ self.rules["mixer_norm"](layer.mixer.norm, layer_id)
1030
+ self.rules["A_log"](layer.mixer.A_log, layer_id)
1031
+ self.rules["D"](layer.mixer.D, layer_id)
1032
+ self.rules["dt_bias"](layer.mixer.dt_bias, layer_id)
1033
+
1034
+ self.rules["conv1d"](layer.mixer.conv1d, layer_id)
1035
+ self.rules["in_proj"](layer.mixer.in_proj, layer_id)
1036
+ self.rules["out_proj"](layer.mixer.out_proj, layer_id)
1037
+
1038
+ elif isinstance(layer, TransformerLayer):
1039
+ if not isinstance(layer.input_layernorm, IdentityOp):
1040
+ self.rules["input_layernorm"](layer.input_layernorm, layer_id)
1041
+
1042
+ if not isinstance(layer.self_attention, IdentityOp):
1043
+ if "MLASelfAttention" in str(type(layer.self_attention)):
1044
+ if hasattr(layer.self_attention, "linear_q_proj"):
1045
+ self.rules["linear_q_proj"](
1046
+ layer.self_attention.linear_q_proj, layer_id
1047
+ )
1048
+ else:
1049
+ self.rules["linear_q_down_proj"](
1050
+ layer.self_attention.linear_q_down_proj, layer_id
1051
+ )
1052
+ self.rules["linear_q_layernorm"](
1053
+ layer.self_attention.q_layernorm, layer_id
1054
+ )
1055
+ self.rules["linear_q_up_proj"](
1056
+ layer.self_attention.linear_q_up_proj, layer_id
1057
+ )
1058
+
1059
+ self.rules["linear_kv_down_proj"](
1060
+ layer.self_attention.linear_kv_down_proj, layer_id
1061
+ )
1062
+ self.rules["linear_kv_layernorm"](
1063
+ layer.self_attention.kv_layernorm, layer_id
1064
+ )
1065
+ self.rules["linear_kv_up_proj"](
1066
+ layer.self_attention.linear_kv_up_proj, layer_id
1067
+ )
1068
+ self.rules["linear_proj"](layer.self_attention.linear_proj, layer_id)
1069
+ else:
1070
+ if layer.self_attention.q_layernorm is not None and not isinstance(
1071
+ layer.self_attention.q_layernorm, (IdentityOp, L2Norm)
1072
+ ):
1073
+ self.rules["q_layernorm"](layer.self_attention.q_layernorm, layer_id)
1074
+ self.rules["k_layernorm"](layer.self_attention.k_layernorm, layer_id)
1075
+ self.rules["linear_qkv"](layer.self_attention.linear_qkv, layer_id)
1076
+ self.rules["linear_proj"](layer.self_attention.linear_proj, layer_id)
1077
+ if hasattr(layer.self_attention.core_attention, "softmax_offset"):
1078
+ self.rules["softmax_offset"](
1079
+ layer.self_attention.core_attention.softmax_offset, layer_id
1080
+ )
1081
+
1082
+ if not isinstance(layer.pre_mlp_layernorm, IdentityOp):
1083
+ self.rules["pre_mlp_layernorm"](layer.pre_mlp_layernorm, layer_id)
1084
+
1085
+ if not isinstance(layer.mlp, IdentityOp):
1086
+ if "MoE" in str(type(layer.mlp)):
1087
+ self.rules["router"](layer.mlp.router, layer_id)
1088
+ if (
1089
+ hasattr(layer.mlp, "shared_experts")
1090
+ and layer.mlp.shared_experts is not None
1091
+ ):
1092
+ self.rules["shared_experts.linear_fc1"](
1093
+ layer.mlp.shared_experts.linear_fc1, layer_id
1094
+ )
1095
+ self.rules["shared_experts.linear_fc2"](
1096
+ layer.mlp.shared_experts.linear_fc2, layer_id
1097
+ )
1098
+ if not self.rules.get("use_packed_local_experts", False):
1099
+ for expert_id, expert in enumerate(layer.mlp.experts.local_experts):
1100
+ self.rules["local_experts.linear_fc1"](
1101
+ expert.linear_fc1, layer_id, expert_id
1102
+ )
1103
+ self.rules["local_experts.linear_fc2"](
1104
+ expert.linear_fc2, layer_id, expert_id
1105
+ )
1106
+ else:
1107
+ # For llama 4, in hf unified checkpoint, all local experts share one scale
1108
+ self.rules["local_experts.linear_fc1"](
1109
+ layer.mlp.experts.local_experts, layer_id
1110
+ )
1111
+ self.rules["local_experts.linear_fc2"](
1112
+ layer.mlp.experts.local_experts, layer_id
1113
+ )
1114
+ else:
1115
+ self.rules["linear_fc1"](layer.mlp.linear_fc1, layer_id)
1116
+ self.rules["linear_fc2"](layer.mlp.linear_fc2, layer_id)
1117
+ else:
1118
+ raise ValueError("Only TransformerLayer or MambaLayer are supported.")
1119
+
1120
+
1121
+ def export_mcore_gpt_to_hf(
1122
+ model: torch.nn.Module,
1123
+ pretrained_model_name_or_path: str | os.PathLike | None = None,
1124
+ export_extra_modules: bool = False,
1125
+ dtype: torch.dtype = torch.float16,
1126
+ export_dir: Path | str = tempfile.gettempdir(),
1127
+ ):
1128
+ """Export Megatron Core GPTModel to unified checkpoint and save to export_dir.
1129
+
1130
+ Args:
1131
+ model: The Megatron Core GPTModel instance.
1132
+ pretrained_model_name_or_path: Can be either: the *model id* of a
1133
+ pretrained model hosted inside a model repo on huggingface.co; or
1134
+ a *directory* containing model weights saved using
1135
+ [`~PreTrainedModel.save_pretrained`], e.g., `./my_model_directory/`.
1136
+ export_extra_modules: If True, export extra modules like medusa_heads or
1137
+ eagle_module. Otherwise, only export the base model.
1138
+ dtype: The weights data type to export the unquantized layers.
1139
+ export_dir: The target export path.
1140
+ """
1141
+ exporter = GPTModelExporter(
1142
+ model, pretrained_model_name_or_path, export_extra_modules=export_extra_modules, dtype=dtype
1143
+ )
1144
+ exporter.save_pretrained(export_dir, pretrained_model_name_or_path)
1145
+
1146
+
1147
+ def import_mcore_gpt_from_hf(
1148
+ model: torch.nn.Module,
1149
+ pretrained_model_path: str,
1150
+ workspace_dir: str | None = None,
1151
+ dtype: torch.dtype = torch.float16,
1152
+ ):
1153
+ """Import GPTModel state_dict from supported HuggingFace pretrained model path.
1154
+
1155
+ Args:
1156
+ model: The Megatron Core GPTModel instance.
1157
+ pretrained_model_path: A path to a *directory* containing model weights saved using
1158
+ [`~PreTrainedModel.save_pretrained`], e.g., `./my_model_directory/`.
1159
+ dtype: The weights data type to import.
1160
+ """
1161
+ importer = GPTModelImporter(
1162
+ model, pretrained_model_path, workspace_dir=workspace_dir, dtype=dtype
1163
+ )
1164
+ importer._import_state_dict()