nvidia-modelopt 0.35.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modelopt/__init__.py +20 -0
- modelopt/deploy/__init__.py +16 -0
- modelopt/deploy/llm/__init__.py +30 -0
- modelopt/deploy/llm/generate.py +345 -0
- modelopt/onnx/__init__.py +34 -0
- modelopt/onnx/autocast/__init__.py +19 -0
- modelopt/onnx/autocast/__main__.py +187 -0
- modelopt/onnx/autocast/convert.py +202 -0
- modelopt/onnx/autocast/graphsanitizer.py +422 -0
- modelopt/onnx/autocast/logging_config.py +78 -0
- modelopt/onnx/autocast/nodeclassifier.py +416 -0
- modelopt/onnx/autocast/precisionconverter.py +1032 -0
- modelopt/onnx/autocast/referencerunner.py +159 -0
- modelopt/onnx/autocast/utils.py +117 -0
- modelopt/onnx/llm_export_utils/__init__.py +16 -0
- modelopt/onnx/llm_export_utils/export_utils.py +162 -0
- modelopt/onnx/llm_export_utils/quantization_utils.py +122 -0
- modelopt/onnx/llm_export_utils/surgeon_utils.py +120 -0
- modelopt/onnx/logging_config.py +77 -0
- modelopt/onnx/op_types.py +300 -0
- modelopt/onnx/quantization/__init__.py +20 -0
- modelopt/onnx/quantization/__main__.py +291 -0
- modelopt/onnx/quantization/calib_utils.py +178 -0
- modelopt/onnx/quantization/extensions.py +35 -0
- modelopt/onnx/quantization/fp8.py +375 -0
- modelopt/onnx/quantization/graph_utils.py +1479 -0
- modelopt/onnx/quantization/gs_patching.py +112 -0
- modelopt/onnx/quantization/int4.py +1347 -0
- modelopt/onnx/quantization/int8.py +283 -0
- modelopt/onnx/quantization/operators.py +121 -0
- modelopt/onnx/quantization/ort_patching.py +1766 -0
- modelopt/onnx/quantization/ort_utils.py +346 -0
- modelopt/onnx/quantization/partitioning.py +439 -0
- modelopt/onnx/quantization/qdq_utils.py +1349 -0
- modelopt/onnx/quantization/quant_utils.py +283 -0
- modelopt/onnx/quantization/quantize.py +528 -0
- modelopt/onnx/quantization/src/modelopt_round_and_pack_ext.cpp +127 -0
- modelopt/onnx/trt_utils.py +424 -0
- modelopt/onnx/utils.py +753 -0
- modelopt/torch/__init__.py +41 -0
- modelopt/torch/_deploy/__init__.py +23 -0
- modelopt/torch/_deploy/_runtime/__init__.py +29 -0
- modelopt/torch/_deploy/_runtime/common.py +72 -0
- modelopt/torch/_deploy/_runtime/ort_client.py +193 -0
- modelopt/torch/_deploy/_runtime/registry.py +83 -0
- modelopt/torch/_deploy/_runtime/runtime_client.py +154 -0
- modelopt/torch/_deploy/_runtime/tensorrt/constants.py +108 -0
- modelopt/torch/_deploy/_runtime/tensorrt/engine_builder.py +324 -0
- modelopt/torch/_deploy/_runtime/tensorrt/hw_param_config.py +51 -0
- modelopt/torch/_deploy/_runtime/tensorrt/layerwise_profiling.py +178 -0
- modelopt/torch/_deploy/_runtime/tensorrt/parse_trtexec_log.py +151 -0
- modelopt/torch/_deploy/_runtime/tensorrt/tensorrt_utils.py +200 -0
- modelopt/torch/_deploy/_runtime/trt_client.py +219 -0
- modelopt/torch/_deploy/compilation.py +142 -0
- modelopt/torch/_deploy/device_model.py +157 -0
- modelopt/torch/_deploy/profiling.py +193 -0
- modelopt/torch/_deploy/utils/__init__.py +18 -0
- modelopt/torch/_deploy/utils/onnx_optimizer.py +120 -0
- modelopt/torch/_deploy/utils/onnx_utils.py +58 -0
- modelopt/torch/_deploy/utils/torch_onnx.py +593 -0
- modelopt/torch/distill/__init__.py +28 -0
- modelopt/torch/distill/config.py +130 -0
- modelopt/torch/distill/distillation.py +84 -0
- modelopt/torch/distill/distillation_model.py +318 -0
- modelopt/torch/distill/loss_balancers.py +140 -0
- modelopt/torch/distill/losses.py +275 -0
- modelopt/torch/distill/mode.py +223 -0
- modelopt/torch/distill/plugins/__init__.py +24 -0
- modelopt/torch/distill/plugins/huggingface.py +110 -0
- modelopt/torch/distill/plugins/megatron.py +476 -0
- modelopt/torch/distill/registry.py +23 -0
- modelopt/torch/export/__init__.py +24 -0
- modelopt/torch/export/convert_hf_config.py +117 -0
- modelopt/torch/export/distribute.py +300 -0
- modelopt/torch/export/hf_config_map.py +84 -0
- modelopt/torch/export/layer_utils.py +1893 -0
- modelopt/torch/export/mcore_config_map.py +25 -0
- modelopt/torch/export/model_config.py +623 -0
- modelopt/torch/export/model_config_export.py +552 -0
- modelopt/torch/export/model_config_utils.py +392 -0
- modelopt/torch/export/model_utils.py +71 -0
- modelopt/torch/export/plugins/__init__.py +21 -0
- modelopt/torch/export/plugins/mcore_common.py +67 -0
- modelopt/torch/export/plugins/mcore_custom.py +530 -0
- modelopt/torch/export/plugins/mcore_deepseek.py +143 -0
- modelopt/torch/export/plugins/mcore_gptoss.py +71 -0
- modelopt/torch/export/plugins/mcore_llama.py +164 -0
- modelopt/torch/export/plugins/mcore_nemotron.py +90 -0
- modelopt/torch/export/plugins/mcore_qwen.py +99 -0
- modelopt/torch/export/plugins/megatron_importer.py +647 -0
- modelopt/torch/export/postprocess.py +847 -0
- modelopt/torch/export/quant_utils.py +1069 -0
- modelopt/torch/export/tensorrt_llm_type.py +42 -0
- modelopt/torch/export/tensorrt_llm_utils.py +405 -0
- modelopt/torch/export/transformer_engine.py +94 -0
- modelopt/torch/export/unified_export_hf.py +537 -0
- modelopt/torch/export/unified_export_megatron.py +1164 -0
- modelopt/torch/nas/__init__.py +27 -0
- modelopt/torch/nas/algorithms.py +856 -0
- modelopt/torch/nas/autonas.py +808 -0
- modelopt/torch/nas/conversion.py +119 -0
- modelopt/torch/nas/hparams/__init__.py +19 -0
- modelopt/torch/nas/hparams/concat.py +319 -0
- modelopt/torch/nas/hparams/container.py +48 -0
- modelopt/torch/nas/modules/__init__.py +21 -0
- modelopt/torch/nas/modules/container.py +99 -0
- modelopt/torch/nas/modules/conv.py +254 -0
- modelopt/torch/nas/modules/linear.py +76 -0
- modelopt/torch/nas/modules/norm.py +193 -0
- modelopt/torch/nas/modules/utils.py +61 -0
- modelopt/torch/nas/patch.py +261 -0
- modelopt/torch/nas/plugins/__init__.py +29 -0
- modelopt/torch/nas/plugins/megatron.py +1497 -0
- modelopt/torch/nas/plugins/torch.py +71 -0
- modelopt/torch/nas/plugins/transformer_engine.py +42 -0
- modelopt/torch/nas/plugins/transformers.py +210 -0
- modelopt/torch/nas/registry.py +23 -0
- modelopt/torch/nas/search_space.py +260 -0
- modelopt/torch/nas/traced_hp.py +149 -0
- modelopt/torch/nas/utils.py +408 -0
- modelopt/torch/opt/__init__.py +41 -0
- modelopt/torch/opt/_hooks.py +98 -0
- modelopt/torch/opt/config.py +383 -0
- modelopt/torch/opt/conversion.py +620 -0
- modelopt/torch/opt/dynamic.py +1367 -0
- modelopt/torch/opt/hparam.py +275 -0
- modelopt/torch/opt/mode.py +353 -0
- modelopt/torch/opt/plugins/__init__.py +38 -0
- modelopt/torch/opt/plugins/diffusers.py +40 -0
- modelopt/torch/opt/plugins/huggingface.py +162 -0
- modelopt/torch/opt/plugins/mcore_dist_checkpointing.py +211 -0
- modelopt/torch/opt/plugins/megatron.py +142 -0
- modelopt/torch/opt/plugins/peft.py +108 -0
- modelopt/torch/opt/plugins/transformers.py +160 -0
- modelopt/torch/opt/searcher.py +363 -0
- modelopt/torch/opt/utils.py +125 -0
- modelopt/torch/prune/__init__.py +26 -0
- modelopt/torch/prune/fastnas.py +376 -0
- modelopt/torch/prune/gradnas.py +349 -0
- modelopt/torch/prune/plugins/__init__.py +24 -0
- modelopt/torch/prune/plugins/mcore_minitron.py +263 -0
- modelopt/torch/prune/plugins/transformers.py +41 -0
- modelopt/torch/prune/pruning.py +211 -0
- modelopt/torch/quantization/__init__.py +26 -0
- modelopt/torch/quantization/algorithms.py +671 -0
- modelopt/torch/quantization/backends/__init__.py +20 -0
- modelopt/torch/quantization/backends/fp8_per_tensor_gemm.py +204 -0
- modelopt/torch/quantization/backends/gemm_registry.py +156 -0
- modelopt/torch/quantization/backends/nvfp4_gemm.py +201 -0
- modelopt/torch/quantization/backends/utils.py +28 -0
- modelopt/torch/quantization/calib/__init__.py +25 -0
- modelopt/torch/quantization/calib/bias.py +172 -0
- modelopt/torch/quantization/calib/calibrator.py +63 -0
- modelopt/torch/quantization/calib/histogram.py +434 -0
- modelopt/torch/quantization/calib/max.py +110 -0
- modelopt/torch/quantization/compress.py +227 -0
- modelopt/torch/quantization/config.py +1180 -0
- modelopt/torch/quantization/conversion.py +389 -0
- modelopt/torch/quantization/export_onnx.py +639 -0
- modelopt/torch/quantization/extensions.py +89 -0
- modelopt/torch/quantization/mode.py +428 -0
- modelopt/torch/quantization/model_calib.py +949 -0
- modelopt/torch/quantization/model_quant.py +477 -0
- modelopt/torch/quantization/nn/__init__.py +26 -0
- modelopt/torch/quantization/nn/functional.py +109 -0
- modelopt/torch/quantization/nn/modules/__init__.py +16 -0
- modelopt/torch/quantization/nn/modules/quant_activations.py +22 -0
- modelopt/torch/quantization/nn/modules/quant_batchnorm.py +24 -0
- modelopt/torch/quantization/nn/modules/quant_conv.py +123 -0
- modelopt/torch/quantization/nn/modules/quant_instancenorm.py +39 -0
- modelopt/torch/quantization/nn/modules/quant_linear.py +258 -0
- modelopt/torch/quantization/nn/modules/quant_module.py +211 -0
- modelopt/torch/quantization/nn/modules/quant_pooling.py +99 -0
- modelopt/torch/quantization/nn/modules/quant_rnn.py +527 -0
- modelopt/torch/quantization/nn/modules/tensor_quantizer.py +1293 -0
- modelopt/torch/quantization/plugins/__init__.py +73 -0
- modelopt/torch/quantization/plugins/accelerate.py +214 -0
- modelopt/torch/quantization/plugins/apex.py +52 -0
- modelopt/torch/quantization/plugins/attention.py +312 -0
- modelopt/torch/quantization/plugins/custom.py +192 -0
- modelopt/torch/quantization/plugins/diffusers.py +237 -0
- modelopt/torch/quantization/plugins/fairscale.py +45 -0
- modelopt/torch/quantization/plugins/huggingface.py +736 -0
- modelopt/torch/quantization/plugins/megatron.py +462 -0
- modelopt/torch/quantization/plugins/peft.py +148 -0
- modelopt/torch/quantization/plugins/transformer_engine.py +60 -0
- modelopt/torch/quantization/plugins/transformers.py +52 -0
- modelopt/torch/quantization/plugins/transformers_trainer.py +426 -0
- modelopt/torch/quantization/plugins/trl.py +27 -0
- modelopt/torch/quantization/plugins/vllm.py +199 -0
- modelopt/torch/quantization/qtensor/__init__.py +24 -0
- modelopt/torch/quantization/qtensor/base_qtensor.py +370 -0
- modelopt/torch/quantization/qtensor/fp8_tensor.py +151 -0
- modelopt/torch/quantization/qtensor/int4_tensor.py +130 -0
- modelopt/torch/quantization/qtensor/int8_tensor.py +124 -0
- modelopt/torch/quantization/qtensor/mxfp4_tensor.py +144 -0
- modelopt/torch/quantization/qtensor/nf4_tensor.py +199 -0
- modelopt/torch/quantization/qtensor/nvfp4_tensor.py +295 -0
- modelopt/torch/quantization/src/tensor_quant.cpp +77 -0
- modelopt/torch/quantization/src/tensor_quant.h +69 -0
- modelopt/torch/quantization/src/tensor_quant_fp8.cpp +48 -0
- modelopt/torch/quantization/src/tensor_quant_gpu.cu +361 -0
- modelopt/torch/quantization/src/tensor_quant_gpu_fp8.cu +122 -0
- modelopt/torch/quantization/src/tensor_quant_mx.cu +411 -0
- modelopt/torch/quantization/src/tensor_quant_mx.h +247 -0
- modelopt/torch/quantization/tensor_quant.py +816 -0
- modelopt/torch/quantization/triton/__init__.py +36 -0
- modelopt/torch/quantization/triton/fp4_kernel.py +303 -0
- modelopt/torch/quantization/utils.py +443 -0
- modelopt/torch/sparsity/__init__.py +19 -0
- modelopt/torch/sparsity/config.py +51 -0
- modelopt/torch/sparsity/magnitude.py +148 -0
- modelopt/torch/sparsity/mode.py +203 -0
- modelopt/torch/sparsity/module.py +87 -0
- modelopt/torch/sparsity/plugins/__init__.py +27 -0
- modelopt/torch/sparsity/plugins/megatron.py +93 -0
- modelopt/torch/sparsity/searcher.py +84 -0
- modelopt/torch/sparsity/sparsegpt.py +276 -0
- modelopt/torch/sparsity/sparsification.py +123 -0
- modelopt/torch/speculative/__init__.py +20 -0
- modelopt/torch/speculative/config.py +100 -0
- modelopt/torch/speculative/eagle/__init__.py +20 -0
- modelopt/torch/speculative/eagle/conversion.py +67 -0
- modelopt/torch/speculative/eagle/default_config.py +50 -0
- modelopt/torch/speculative/eagle/eagle_model.py +51 -0
- modelopt/torch/speculative/eagle/utils.py +91 -0
- modelopt/torch/speculative/medusa/__init__.py +19 -0
- modelopt/torch/speculative/medusa/conversion.py +59 -0
- modelopt/torch/speculative/medusa/medusa_model.py +32 -0
- modelopt/torch/speculative/mode.py +86 -0
- modelopt/torch/speculative/plugins/__init__.py +33 -0
- modelopt/torch/speculative/plugins/megatron_eagle.py +2119 -0
- modelopt/torch/speculative/plugins/megatron_medusa.py +312 -0
- modelopt/torch/speculative/plugins/transformers.py +1138 -0
- modelopt/torch/speculative/speculative_decoding.py +59 -0
- modelopt/torch/speculative/utils.py +364 -0
- modelopt/torch/trace/__init__.py +25 -0
- modelopt/torch/trace/analyzer.py +1368 -0
- modelopt/torch/trace/modules/__init__.py +19 -0
- modelopt/torch/trace/modules/concat.py +384 -0
- modelopt/torch/trace/modules/nn.py +153 -0
- modelopt/torch/trace/plugins/__init__.py +34 -0
- modelopt/torch/trace/plugins/megatron.py +36 -0
- modelopt/torch/trace/plugins/transformers.py +58 -0
- modelopt/torch/trace/symbols.py +545 -0
- modelopt/torch/trace/tracer.py +331 -0
- modelopt/torch/utils/__init__.py +28 -0
- modelopt/torch/utils/_pytree.py +133 -0
- modelopt/torch/utils/cpp_extension.py +91 -0
- modelopt/torch/utils/dataset_utils.py +484 -0
- modelopt/torch/utils/distributed.py +311 -0
- modelopt/torch/utils/graph.py +123 -0
- modelopt/torch/utils/image_processor.py +112 -0
- modelopt/torch/utils/import_utils.py +35 -0
- modelopt/torch/utils/list.py +54 -0
- modelopt/torch/utils/logging.py +127 -0
- modelopt/torch/utils/memory_monitor.py +146 -0
- modelopt/torch/utils/network.py +658 -0
- modelopt/torch/utils/perf.py +174 -0
- modelopt/torch/utils/plugins/__init__.py +27 -0
- modelopt/torch/utils/plugins/megatron_generate.py +299 -0
- modelopt/torch/utils/plugins/megatron_mmlu.py +152 -0
- modelopt/torch/utils/plugins/megatron_preprocess_data.py +200 -0
- modelopt/torch/utils/random.py +163 -0
- modelopt/torch/utils/speech_dataset_utils.py +134 -0
- modelopt/torch/utils/tensor.py +87 -0
- modelopt/torch/utils/vlm_dataset_utils.py +110 -0
- nvidia_modelopt-0.35.0.dist-info/METADATA +171 -0
- nvidia_modelopt-0.35.0.dist-info/RECORD +272 -0
- nvidia_modelopt-0.35.0.dist-info/WHEEL +5 -0
- nvidia_modelopt-0.35.0.dist-info/licenses/LICENSE +14 -0
- nvidia_modelopt-0.35.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1293 @@
|
|
|
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
|
+
"""TensorQuantizer Module."""
|
|
17
|
+
|
|
18
|
+
import contextlib
|
|
19
|
+
import math
|
|
20
|
+
import warnings
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
import torch
|
|
24
|
+
import torch.distributed as dist
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from torch.distributed.tensor import DTensor
|
|
28
|
+
except ImportError:
|
|
29
|
+
DTensor = None
|
|
30
|
+
|
|
31
|
+
import torch.nn.functional as F
|
|
32
|
+
from packaging.version import Version
|
|
33
|
+
from torch import nn
|
|
34
|
+
from torch.onnx._globals import GLOBALS
|
|
35
|
+
|
|
36
|
+
from modelopt.torch.utils import standardize_constructor_args
|
|
37
|
+
from modelopt.torch.utils.distributed import DistributedProcessGroup
|
|
38
|
+
|
|
39
|
+
from ... import calib
|
|
40
|
+
from ... import utils as quant_utils
|
|
41
|
+
from ...config import QuantizerAttributeConfig
|
|
42
|
+
from ...qtensor import (
|
|
43
|
+
BaseQuantizedTensor,
|
|
44
|
+
FP8QTensor,
|
|
45
|
+
INT4QTensor,
|
|
46
|
+
INT8QTensor,
|
|
47
|
+
MXFP4QTensor,
|
|
48
|
+
NF4QTensor,
|
|
49
|
+
NVFP4QTensor,
|
|
50
|
+
QTensorWrapper,
|
|
51
|
+
)
|
|
52
|
+
from ...tensor_quant import dynamic_block_quant, fake_tensor_quant, scaled_e4m3
|
|
53
|
+
from ...utils import is_torch_export_mode
|
|
54
|
+
from ..functional import normalized_hadamard_transform
|
|
55
|
+
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
from collections.abc import Callable
|
|
58
|
+
|
|
59
|
+
__all__ = ["SequentialQuantizer", "TensorQuantizer"]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TensorQuantizer(nn.Module):
|
|
63
|
+
"""Tensor quantizer module.
|
|
64
|
+
|
|
65
|
+
This module manages quantization and calibration of input tensor. It can perform fake (simulated quantization)
|
|
66
|
+
or real quantization for various precisions and formats such as FP8 per-tensor, INT8 per-channel,
|
|
67
|
+
INT4 per-block etc.
|
|
68
|
+
|
|
69
|
+
If quantization is enabled, it calls the appropriate quantization functional and
|
|
70
|
+
returns the quantized tensor. The quantized tensor data type will be same as the input tensor data type for
|
|
71
|
+
fake quantization. During calibration mode, the module collects the statistics using its calibrator.
|
|
72
|
+
|
|
73
|
+
The quantization parameters are as described in
|
|
74
|
+
:class:`QuantizerAttributeConfig <modelopt.torch.quantization.config.QuantizerAttributeConfig>`. They can be set
|
|
75
|
+
at initialization using ``quant_attribute_cfg`` or later by calling :meth:`set_from_attribute_config`.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
quant_attribute_cfg: An instance of
|
|
79
|
+
:class:`QuantizerAttributeConfig <modelopt.torch.quantization.config.QuantizerAttributeConfig>` or None.
|
|
80
|
+
If None, default values are used.
|
|
81
|
+
if_quant: A boolean. If True, quantization is enabled in the forward path.
|
|
82
|
+
if_calib: A boolean. If True, calibration is enabled in the forward path.
|
|
83
|
+
amax: None or an array like object such as list, tuple, numpy array, scalar
|
|
84
|
+
which can be used to construct amax tensor.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
_skip_properties_for_save_restore = {
|
|
88
|
+
"_calibrator",
|
|
89
|
+
"_bias_calibrator",
|
|
90
|
+
"_original_shape",
|
|
91
|
+
"_block_reshape_size",
|
|
92
|
+
"_padding",
|
|
93
|
+
# Extra flags added by huggingface
|
|
94
|
+
"_is_hf_initialized",
|
|
95
|
+
# Extra flags added by deepspeed
|
|
96
|
+
"ds_external_parameters",
|
|
97
|
+
"all_parameters",
|
|
98
|
+
"_external_params",
|
|
99
|
+
"_original_parameters",
|
|
100
|
+
"post_bwd_fn",
|
|
101
|
+
"ds_grads_remaining",
|
|
102
|
+
"ds_id",
|
|
103
|
+
"pre_bwd_fn",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
quant_attribute_cfg=None,
|
|
109
|
+
if_quant=True,
|
|
110
|
+
if_calib=False,
|
|
111
|
+
amax=None,
|
|
112
|
+
):
|
|
113
|
+
"""Initialize quantizer and set up required variables."""
|
|
114
|
+
super().__init__()
|
|
115
|
+
quant_attribute_cfg = (
|
|
116
|
+
quant_attribute_cfg if quant_attribute_cfg is not None else QuantizerAttributeConfig()
|
|
117
|
+
)
|
|
118
|
+
if amax is not None:
|
|
119
|
+
self.amax = amax
|
|
120
|
+
|
|
121
|
+
self.set_from_attribute_config(quant_attribute_cfg)
|
|
122
|
+
|
|
123
|
+
self._if_quant = if_quant
|
|
124
|
+
self._if_calib = if_calib
|
|
125
|
+
self._enable_pre_quant_scale = True
|
|
126
|
+
self._dequantize = False
|
|
127
|
+
self._input_dtype = None
|
|
128
|
+
|
|
129
|
+
# Lazy initialize the bias calibrator for KV cache quantization
|
|
130
|
+
self._bias_calibrator = None
|
|
131
|
+
|
|
132
|
+
def set_from_attribute_config(self, attribute_cfg: QuantizerAttributeConfig | dict):
|
|
133
|
+
"""Set quantizer attributes from attribute_dict.
|
|
134
|
+
|
|
135
|
+
The attributes are defined in
|
|
136
|
+
:class:`QuantizerAttributeConfig <modelopt.torch.quantization.config.QuantizerAttributeConfig>`.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def _calibrator_setter(val):
|
|
140
|
+
if val in ["max", "histogram"]:
|
|
141
|
+
calib_cls = calib.MaxCalibrator if val == "max" else calib.HistogramCalibrator
|
|
142
|
+
args, kwargs = (self._num_bits, self._axis, self._unsigned), {}
|
|
143
|
+
else:
|
|
144
|
+
calib_cls, args, kwargs = standardize_constructor_args(val)
|
|
145
|
+
return calib_cls(*args, **kwargs)
|
|
146
|
+
|
|
147
|
+
# Some attributes need custom handling.
|
|
148
|
+
# By default, attributes from config are mapped to a name ``f"_{attribute}"``
|
|
149
|
+
_custom_setters: dict[str, tuple[str, Callable]] = {
|
|
150
|
+
"enable": ("_disabled", lambda val: val is False),
|
|
151
|
+
"type": ("_dynamic", lambda val: val == "dynamic"),
|
|
152
|
+
"calibrator": ("_calibrator", _calibrator_setter),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for attribute, val in attribute_cfg.items():
|
|
156
|
+
assert attribute in QuantizerAttributeConfig.model_fields, (
|
|
157
|
+
f"{attribute} is not a valid `TensorQuantizer` attribute"
|
|
158
|
+
)
|
|
159
|
+
_tq_attribute_name, _setter = _custom_setters.get(
|
|
160
|
+
attribute, (f"_{attribute}", lambda v: v)
|
|
161
|
+
)
|
|
162
|
+
setattr(self, _tq_attribute_name, _setter(val))
|
|
163
|
+
|
|
164
|
+
if self.is_mx_format:
|
|
165
|
+
self._pass_through_bwd = True
|
|
166
|
+
|
|
167
|
+
def dequantize(self, inputs: BaseQuantizedTensor | QTensorWrapper):
|
|
168
|
+
"""De-quantize a real quantized tensor to a given dtype."""
|
|
169
|
+
qtensor = inputs.get_qtensor() if isinstance(inputs, QTensorWrapper) else inputs
|
|
170
|
+
assert isinstance(qtensor, BaseQuantizedTensor), "Expected input as real quantized tensors."
|
|
171
|
+
kwarg = {
|
|
172
|
+
"scale": self._scale,
|
|
173
|
+
"block_sizes": self.block_sizes,
|
|
174
|
+
"double_scale": getattr(self, "_double_scale", None),
|
|
175
|
+
"scale_zeros": getattr(self, "_scale_zeros", None),
|
|
176
|
+
}
|
|
177
|
+
return qtensor.dequantize(**kwarg)
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def num_bits(self):
|
|
181
|
+
"""Return num_bits for quantization."""
|
|
182
|
+
return self._num_bits
|
|
183
|
+
|
|
184
|
+
@num_bits.setter
|
|
185
|
+
def num_bits(self, value):
|
|
186
|
+
self._num_bits = value
|
|
187
|
+
self._calibrator._num_bits = value
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def maxbound(self):
|
|
191
|
+
"""Return maxbound for quantization."""
|
|
192
|
+
if self._num_bits == (4, 3):
|
|
193
|
+
return 448.0
|
|
194
|
+
if self._num_bits == (2, 1) and self._block_sizes.get("scale_bits") == (4, 3):
|
|
195
|
+
return 6.0
|
|
196
|
+
return (1 << (self._num_bits - 1 + int(self._unsigned))) - 1
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def unsigned(self):
|
|
200
|
+
"""Return True if unsigned quantization is used."""
|
|
201
|
+
return self._unsigned
|
|
202
|
+
|
|
203
|
+
@unsigned.setter
|
|
204
|
+
def unsigned(self, value):
|
|
205
|
+
self._unsigned = value
|
|
206
|
+
self._calibrator._unsigned = value
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def pre_quant_scale(self):
|
|
210
|
+
"""Return pre_quant_scale used for smoothquant."""
|
|
211
|
+
if not hasattr(self, "_pre_quant_scale") or not self._enable_pre_quant_scale:
|
|
212
|
+
return None
|
|
213
|
+
return self._pre_quant_scale
|
|
214
|
+
|
|
215
|
+
@pre_quant_scale.setter
|
|
216
|
+
def pre_quant_scale(self, value):
|
|
217
|
+
assert value is not None, "pre_quant_scale cannot be set to None."
|
|
218
|
+
assert self._enable_pre_quant_scale, (
|
|
219
|
+
"pre_quant_scale cannot be set when forward_with_pre_quant_scale is False."
|
|
220
|
+
)
|
|
221
|
+
if not isinstance(value, torch.Tensor):
|
|
222
|
+
value = torch.tensor(value)
|
|
223
|
+
if not hasattr(self, "_pre_quant_scale"):
|
|
224
|
+
self.register_buffer("_pre_quant_scale", value.clone().detach())
|
|
225
|
+
else:
|
|
226
|
+
if self._pre_quant_scale.shape != value.shape:
|
|
227
|
+
raise RuntimeError("Changing shape when setting pre_quant_scale is not allowed.")
|
|
228
|
+
self._pre_quant_scale.data.copy_(
|
|
229
|
+
value.clone().detach().to(self._pre_quant_scale.device)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def amax(self):
|
|
234
|
+
"""Return amax for quantization."""
|
|
235
|
+
if not hasattr(self, "_amax") or self.is_mx_format:
|
|
236
|
+
return None
|
|
237
|
+
assert not self._dynamic, "Dynamic quantization does not have fixed amax"
|
|
238
|
+
return self._amax
|
|
239
|
+
|
|
240
|
+
@amax.setter
|
|
241
|
+
def amax(self, value):
|
|
242
|
+
assert value is not None, "amax cannot be set to None."
|
|
243
|
+
|
|
244
|
+
if not isinstance(value, torch.Tensor):
|
|
245
|
+
value = torch.tensor(value)
|
|
246
|
+
|
|
247
|
+
if not hasattr(self, "_amax"):
|
|
248
|
+
self.register_buffer("_amax", value.clone().detach())
|
|
249
|
+
else:
|
|
250
|
+
if self._amax.shape != value.shape:
|
|
251
|
+
raise RuntimeError("Changing shape when setting amax is not allowed.")
|
|
252
|
+
self._amax.data.copy_(value.clone().detach().to(self._amax.device))
|
|
253
|
+
|
|
254
|
+
def reset_amax(self):
|
|
255
|
+
"""Reset amax to None."""
|
|
256
|
+
if hasattr(self, "_amax"):
|
|
257
|
+
delattr(self, "_amax")
|
|
258
|
+
self._calibrator.reset()
|
|
259
|
+
|
|
260
|
+
def reset_bias(self):
|
|
261
|
+
"""Reset bias to None."""
|
|
262
|
+
if hasattr(self, "_bias_value"):
|
|
263
|
+
delattr(self, "_bias_value")
|
|
264
|
+
if self._bias_calibrator is not None:
|
|
265
|
+
self._bias_calibrator.reset()
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def step_size(self):
|
|
269
|
+
"""Return step size for integer quantization."""
|
|
270
|
+
if not hasattr(self, "_amax"):
|
|
271
|
+
warnings.warn("step_size is undefined under dynamic amax mode!")
|
|
272
|
+
return None
|
|
273
|
+
assert isinstance(self._num_bits, int), (
|
|
274
|
+
"Step size is not defined for non-integer quantization."
|
|
275
|
+
)
|
|
276
|
+
return self._amax / (2.0 ** (self._num_bits - 1 + int(self._unsigned)) - 1.0)
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def axis(self):
|
|
280
|
+
"""Return axis for quantization."""
|
|
281
|
+
return self._axis
|
|
282
|
+
|
|
283
|
+
@axis.setter
|
|
284
|
+
def axis(self, value):
|
|
285
|
+
self._axis = value
|
|
286
|
+
self._calibrator._axis = value
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def block_sizes(self):
|
|
290
|
+
"""Return block_sizes for quantization."""
|
|
291
|
+
return self._block_sizes
|
|
292
|
+
|
|
293
|
+
@block_sizes.setter
|
|
294
|
+
def block_sizes(self, value):
|
|
295
|
+
self._axis = None
|
|
296
|
+
self._block_sizes = value
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def bias(self):
|
|
300
|
+
"""Return bias for quantization."""
|
|
301
|
+
if not hasattr(self, "_bias"):
|
|
302
|
+
return None
|
|
303
|
+
return self._bias
|
|
304
|
+
|
|
305
|
+
@property
|
|
306
|
+
def bias_axis(self):
|
|
307
|
+
"""Return bias_axis for quantization."""
|
|
308
|
+
if not hasattr(self, "_bias_axis"):
|
|
309
|
+
return None
|
|
310
|
+
return self._bias_axis
|
|
311
|
+
|
|
312
|
+
@bias_axis.setter
|
|
313
|
+
def bias_axis(self, value):
|
|
314
|
+
assert value is not None, "bias_axis cannot be set to None."
|
|
315
|
+
assert isinstance(value, (tuple, list)), "bias_axis must be a tuple or a list."
|
|
316
|
+
self._bias_axis = value
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def bias_method(self):
|
|
320
|
+
"""Return bias_method for quantization."""
|
|
321
|
+
if self._bias is None:
|
|
322
|
+
return None
|
|
323
|
+
return self._bias.get("method", "mean")
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def bias_type(self):
|
|
327
|
+
"""Return bias_type for quantization."""
|
|
328
|
+
if self._bias is None:
|
|
329
|
+
return None
|
|
330
|
+
return self._bias.get("type", "static")
|
|
331
|
+
|
|
332
|
+
@bias_type.setter
|
|
333
|
+
def bias_type(self, value):
|
|
334
|
+
assert value in [
|
|
335
|
+
"static",
|
|
336
|
+
"dynamic",
|
|
337
|
+
], "bias_type must be either 'static' or 'dynamic'."
|
|
338
|
+
self._bias["type"] = value
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def bias_value(self):
|
|
342
|
+
"""Return bias for quantization."""
|
|
343
|
+
if not hasattr(self, "_bias_value"):
|
|
344
|
+
return None
|
|
345
|
+
return self._bias_value
|
|
346
|
+
|
|
347
|
+
@bias_value.setter
|
|
348
|
+
def bias_value(self, value):
|
|
349
|
+
assert value is not None, "bias cannot be set to None."
|
|
350
|
+
|
|
351
|
+
if not isinstance(value, torch.Tensor):
|
|
352
|
+
value = torch.tensor(value)
|
|
353
|
+
|
|
354
|
+
if not hasattr(self, "_bias_value"):
|
|
355
|
+
self.register_buffer("_bias_value", value.clone().detach())
|
|
356
|
+
else:
|
|
357
|
+
if self._bias_value.shape != value.shape:
|
|
358
|
+
raise RuntimeError("Changing shape when setting bias is not allowed.")
|
|
359
|
+
self._bias_value.data.copy_(value.clone().detach().to(self._bias_value.device))
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def bias_calibrator(self):
|
|
363
|
+
"""Return bias_calibrator for quantization."""
|
|
364
|
+
# Get reduce_axis from bias config
|
|
365
|
+
# Bias calibration supports per-channel and per-token quantization
|
|
366
|
+
if self._bias_calibrator is None and self.bias is not None:
|
|
367
|
+
self.bias_axis = tuple(k for k in self.bias if isinstance(k, int))
|
|
368
|
+
if self._bias is not None:
|
|
369
|
+
self._bias_calibrator = calib.BiasCalibrator(
|
|
370
|
+
method=self.bias_method,
|
|
371
|
+
axis=self.bias_axis,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
return self._bias_calibrator
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def fake_quant(self):
|
|
378
|
+
"""Return True if fake quantization is used."""
|
|
379
|
+
return self._fake_quant
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def narrow_range(self):
|
|
383
|
+
"""Return True if symmetric integer range for signed quantization is used."""
|
|
384
|
+
return self._narrow_range
|
|
385
|
+
|
|
386
|
+
@narrow_range.setter
|
|
387
|
+
def narrow_range(self, value):
|
|
388
|
+
self._narrow_range = value
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def is_enabled(self):
|
|
392
|
+
"""Return true if the modules is not disabled."""
|
|
393
|
+
return not self._disabled
|
|
394
|
+
|
|
395
|
+
def disable(self):
|
|
396
|
+
"""Bypass the module.
|
|
397
|
+
|
|
398
|
+
Neither of calibration, clipping and quantization will be performed if the module is disabled.
|
|
399
|
+
"""
|
|
400
|
+
self._disabled = True
|
|
401
|
+
|
|
402
|
+
def enable(self):
|
|
403
|
+
"""Enable the module."""
|
|
404
|
+
self._disabled = False
|
|
405
|
+
|
|
406
|
+
@property
|
|
407
|
+
def trt_high_precision_dtype(self):
|
|
408
|
+
"""Return True if FP16 AMAX is used when exporting the model."""
|
|
409
|
+
return self._trt_high_precision_dtype
|
|
410
|
+
|
|
411
|
+
@trt_high_precision_dtype.setter
|
|
412
|
+
def trt_high_precision_dtype(self, value):
|
|
413
|
+
self._trt_high_precision_dtype = value
|
|
414
|
+
|
|
415
|
+
@property
|
|
416
|
+
def is_mx_format(self):
|
|
417
|
+
"""Check if is MX formats."""
|
|
418
|
+
return (
|
|
419
|
+
self.block_sizes is not None
|
|
420
|
+
and self.block_sizes.get("type", None) == "dynamic"
|
|
421
|
+
and self.block_sizes.get("scale_bits", None) == (8, 0)
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def svdquant_lora_a(self):
|
|
426
|
+
"""Lora a weights for svdquant."""
|
|
427
|
+
if not hasattr(self, "_svdquant_lora_a"):
|
|
428
|
+
return None
|
|
429
|
+
return self._svdquant_lora_a
|
|
430
|
+
|
|
431
|
+
@svdquant_lora_a.setter
|
|
432
|
+
def svdquant_lora_a(self, value):
|
|
433
|
+
"""Lora a weights for svdquant."""
|
|
434
|
+
assert value is not None, "svdquant_lora_a cannot be set to None."
|
|
435
|
+
|
|
436
|
+
if not isinstance(value, torch.Tensor):
|
|
437
|
+
value = torch.tensor(value)
|
|
438
|
+
|
|
439
|
+
if not hasattr(self, "_svdquant_lora_a"):
|
|
440
|
+
self.register_buffer("_svdquant_lora_a", value.clone().detach())
|
|
441
|
+
else:
|
|
442
|
+
if self._svdquant_lora_a.shape != value.shape:
|
|
443
|
+
raise RuntimeError("Changing shape when setting svdquant_lora_a is not allowed.")
|
|
444
|
+
self._svdquant_lora_a.data.copy_(
|
|
445
|
+
value.clone().detach().to(self._svdquant_lora_a.device)
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def svdquant_lora_b(self):
|
|
450
|
+
"""Lora b weights for svdquant."""
|
|
451
|
+
if not hasattr(self, "_svdquant_lora_b"):
|
|
452
|
+
return None
|
|
453
|
+
return self._svdquant_lora_b
|
|
454
|
+
|
|
455
|
+
@svdquant_lora_b.setter
|
|
456
|
+
def svdquant_lora_b(self, value):
|
|
457
|
+
"""Lora b weights for svdquant."""
|
|
458
|
+
assert value is not None, "svdquant_lora_b cannot be set to None."
|
|
459
|
+
|
|
460
|
+
if not isinstance(value, torch.Tensor):
|
|
461
|
+
value = torch.tensor(value)
|
|
462
|
+
|
|
463
|
+
if not hasattr(self, "_svdquant_lora_b"):
|
|
464
|
+
self.register_buffer("_svdquant_lora_b", value.clone().detach())
|
|
465
|
+
else:
|
|
466
|
+
if self._svdquant_lora_b.shape != value.shape:
|
|
467
|
+
raise RuntimeError("Changing shape when setting svdquant_lora_b is not allowed.")
|
|
468
|
+
self._svdquant_lora_b.data.copy_(
|
|
469
|
+
value.clone().detach().to(self._svdquant_lora_b.device)
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
def disable_calib(self):
|
|
473
|
+
"""Disable calibration."""
|
|
474
|
+
self._if_calib = False
|
|
475
|
+
|
|
476
|
+
def enable_calib(self):
|
|
477
|
+
"""Enable calibration."""
|
|
478
|
+
if self._calibrator is None:
|
|
479
|
+
raise ValueError("Calibrator was not created, cannot enable calibration.")
|
|
480
|
+
|
|
481
|
+
# Dynamic quantization does not need calibration.
|
|
482
|
+
if self._dynamic:
|
|
483
|
+
return
|
|
484
|
+
self._if_calib = True
|
|
485
|
+
|
|
486
|
+
def disable_quant(self):
|
|
487
|
+
"""Disable quantization."""
|
|
488
|
+
self._if_quant = False
|
|
489
|
+
|
|
490
|
+
def enable_quant(self):
|
|
491
|
+
"""Enable quantization."""
|
|
492
|
+
self._if_quant = True
|
|
493
|
+
|
|
494
|
+
def load_calib_amax(self, *args, **kwargs):
|
|
495
|
+
"""Load amax from calibrator.
|
|
496
|
+
|
|
497
|
+
Updates the amax buffer with value computed by the calibrator, creating it if necessary.
|
|
498
|
+
``*args`` and ``**kwargs`` are directly passed to ``compute_amax``, except ``"strict"`` in
|
|
499
|
+
``kwargs``. Refer to ``compute_amax`` for more details.
|
|
500
|
+
"""
|
|
501
|
+
assert not self._dynamic, "Dynamic quantization does not need calibration."
|
|
502
|
+
|
|
503
|
+
strict = kwargs.pop("strict", True)
|
|
504
|
+
if getattr(self, "_calibrator", None) is None:
|
|
505
|
+
raise RuntimeError("Calibrator not created.")
|
|
506
|
+
calib_amax = self._calibrator.compute_amax(*args, **kwargs)
|
|
507
|
+
if calib_amax is None:
|
|
508
|
+
err_msg = (
|
|
509
|
+
"Calibrator returned None. This usually happens when calibrator hasn't seen any"
|
|
510
|
+
" tensor."
|
|
511
|
+
)
|
|
512
|
+
if not strict:
|
|
513
|
+
warnings.warn(err_msg)
|
|
514
|
+
warnings.warn("Set amax to NaN!")
|
|
515
|
+
calib_amax = torch.tensor(math.nan)
|
|
516
|
+
else:
|
|
517
|
+
raise RuntimeError(
|
|
518
|
+
err_msg
|
|
519
|
+
+ " Passing 'strict=False' to `load_calib_amax()` will ignore the error."
|
|
520
|
+
)
|
|
521
|
+
if not hasattr(self, "_amax"):
|
|
522
|
+
self.register_buffer("_amax", calib_amax.clone().detach())
|
|
523
|
+
else:
|
|
524
|
+
self._amax.data.copy_(calib_amax.clone().detach())
|
|
525
|
+
|
|
526
|
+
def load_calib_bias(self, *args, **kwargs):
|
|
527
|
+
"""Load affine bias for quantization."""
|
|
528
|
+
assert not self._dynamic, "Dynamic quantization does not need calibration."
|
|
529
|
+
calib_bias = self.bias_calibrator.compute_bias(*args, **kwargs)
|
|
530
|
+
if calib_bias is None:
|
|
531
|
+
raise RuntimeError(
|
|
532
|
+
"Calibrator returned None. This usually happens when calibrator hasn't seen any tensor."
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
if not hasattr(self, "_bias_value"):
|
|
536
|
+
self.register_buffer("_bias_value", calib_bias.clone().detach())
|
|
537
|
+
else:
|
|
538
|
+
self._bias_value.data.copy_(calib_bias.clone().detach())
|
|
539
|
+
|
|
540
|
+
def _get_amax(self, inputs):
|
|
541
|
+
"""Get amax from buffer or compute it dynamically."""
|
|
542
|
+
if hasattr(self, "_amax"):
|
|
543
|
+
amax = self._amax
|
|
544
|
+
else:
|
|
545
|
+
reduce_axis = quant_utils.convert_quantization_axis_to_reduce_axis(inputs, self._axis)
|
|
546
|
+
amax = quant_utils.reduce_amax(inputs, axis=reduce_axis, keepdims=True).detach()
|
|
547
|
+
|
|
548
|
+
amax = amax.detach() if is_torch_export_mode() else amax.data
|
|
549
|
+
return amax
|
|
550
|
+
|
|
551
|
+
def _validate_amax(self, amax):
|
|
552
|
+
# Dynamic control flow is not supported by torch dynamo
|
|
553
|
+
if not is_torch_export_mode() and not torch._dynamo.is_compiling():
|
|
554
|
+
assert torch.all(amax >= 0) and not torch.any(torch.isinf(amax)), (
|
|
555
|
+
f"Got invalid amax: {amax}"
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
def _get_bias(self, inputs):
|
|
559
|
+
"""Get bias from buffer or compute it dynamically."""
|
|
560
|
+
if self.bias_calibrator is None:
|
|
561
|
+
return None
|
|
562
|
+
|
|
563
|
+
if self.bias_type == "static":
|
|
564
|
+
bias = self._bias_value
|
|
565
|
+
elif self.bias_type == "dynamic":
|
|
566
|
+
bias = self.bias_calibrator.compute_dynamic_bias(inputs)
|
|
567
|
+
else:
|
|
568
|
+
raise ValueError(f"Unsupported bias type: {self.bias_type}")
|
|
569
|
+
return bias
|
|
570
|
+
|
|
571
|
+
def _is_real_quantize_support(self):
|
|
572
|
+
"""Check if real quantization is supported for this quant config."""
|
|
573
|
+
return (
|
|
574
|
+
(self._num_bits == 4 and self._block_sizes)
|
|
575
|
+
or (self._num_bits == (2, 1) and self._block_sizes)
|
|
576
|
+
or self._num_bits in ((4, 3), 8) # Int8
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
def _real_quantize(self, inputs):
|
|
580
|
+
assert self._is_real_quantize_support(), "Real quantization not supported for this format."
|
|
581
|
+
|
|
582
|
+
buffer_to_register = {}
|
|
583
|
+
if self._num_bits == (4, 3):
|
|
584
|
+
# FP8 quantization
|
|
585
|
+
# For per-tensor/per-channel quantization, we might need amax which is synced across all ranks
|
|
586
|
+
outputs, _scale = FP8QTensor.quantize(
|
|
587
|
+
inputs,
|
|
588
|
+
axis=self._axis,
|
|
589
|
+
block_sizes=self._block_sizes,
|
|
590
|
+
scales=self.amax / 448.0 if self.amax is not None else None,
|
|
591
|
+
)
|
|
592
|
+
buffer_to_register["_scale"] = _scale
|
|
593
|
+
elif self._num_bits == 8:
|
|
594
|
+
outputs, _scale = INT8QTensor.quantize(
|
|
595
|
+
inputs, axis=self._axis, block_sizes=self._block_sizes
|
|
596
|
+
)
|
|
597
|
+
buffer_to_register["_scale"] = _scale
|
|
598
|
+
elif self._block_sizes.get("scale_bits", 0) == 8 and self._block_sizes.get(
|
|
599
|
+
"scale_block_sizes", None
|
|
600
|
+
):
|
|
601
|
+
# NF4 double quantization
|
|
602
|
+
# Return real quantized tensor class and store scales inside the TensorQuantizer
|
|
603
|
+
outputs, scales = NF4QTensor.quantize(
|
|
604
|
+
inputs, self._block_sizes[-1], self._block_sizes["scale_block_sizes"][-1]
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
_scale, _double_scale, _scale_zeros = NF4QTensor.double_quantization(
|
|
608
|
+
scales,
|
|
609
|
+
self._block_sizes["scale_block_sizes"][-1],
|
|
610
|
+
self._block_sizes["scale_bits"],
|
|
611
|
+
)
|
|
612
|
+
buffer_to_register["_scale"] = _scale
|
|
613
|
+
buffer_to_register["_double_scale"] = _double_scale
|
|
614
|
+
buffer_to_register["_scale_zeros"] = _scale_zeros
|
|
615
|
+
elif (
|
|
616
|
+
self._block_sizes.get("scale_bits") == (8, 0)
|
|
617
|
+
and self._block_sizes.get("type") == "dynamic"
|
|
618
|
+
):
|
|
619
|
+
# MX quantization
|
|
620
|
+
if self._num_bits == (2, 1):
|
|
621
|
+
outputs, scales = MXFP4QTensor.quantize(inputs, self._block_sizes[-1])
|
|
622
|
+
buffer_to_register["_scale"] = scales
|
|
623
|
+
else:
|
|
624
|
+
raise ValueError(
|
|
625
|
+
f"Real quantization for MX {self._num_bits} format is not supported."
|
|
626
|
+
)
|
|
627
|
+
elif self._block_sizes.get("scale_bits") == (4, 3):
|
|
628
|
+
# NVFP4 default quantization
|
|
629
|
+
# Return real quantized tensor and store scales inside TensorQuantizer
|
|
630
|
+
outputs, _weights_scaling_factor, _weights_scaling_factor_2 = NVFP4QTensor.quantize(
|
|
631
|
+
inputs,
|
|
632
|
+
self._block_sizes[-1],
|
|
633
|
+
weights_scaling_factor_2=self.amax.float() / (448.0 * 6.0)
|
|
634
|
+
if self.amax is not None
|
|
635
|
+
else None,
|
|
636
|
+
try_tensorrt=True,
|
|
637
|
+
)
|
|
638
|
+
buffer_to_register["_scale"] = _weights_scaling_factor
|
|
639
|
+
buffer_to_register["_double_scale"] = _weights_scaling_factor_2
|
|
640
|
+
else:
|
|
641
|
+
outputs, _scale = INT4QTensor.quantize(inputs, self._block_sizes[-1])
|
|
642
|
+
buffer_to_register["_scale"] = _scale
|
|
643
|
+
for k, v in buffer_to_register.items():
|
|
644
|
+
self._set_buffer(k, v)
|
|
645
|
+
|
|
646
|
+
# We assume _real_quantize is called when compress the model weights, and we set
|
|
647
|
+
# self._dequantize to True so that future forward call will only do dequantize.
|
|
648
|
+
self._dequantize = True
|
|
649
|
+
return outputs
|
|
650
|
+
|
|
651
|
+
def _fake_quantize(self, inputs):
|
|
652
|
+
"""Fake quantization."""
|
|
653
|
+
amax = None
|
|
654
|
+
if not self.is_mx_format:
|
|
655
|
+
amax = self._get_amax(inputs)
|
|
656
|
+
self._validate_amax(amax)
|
|
657
|
+
|
|
658
|
+
if self.block_sizes is not None and self.block_sizes.get("type", "static") == "dynamic":
|
|
659
|
+
# Block quantization, including dynamic and static block quantization
|
|
660
|
+
block_size = self.block_sizes.get(-1, None) or self.block_sizes.get(
|
|
661
|
+
inputs.dim() - 1, None
|
|
662
|
+
)
|
|
663
|
+
assert block_size is not None, "block size for dynamic quantization not found."
|
|
664
|
+
|
|
665
|
+
outputs = dynamic_block_quant(
|
|
666
|
+
inputs,
|
|
667
|
+
block_size,
|
|
668
|
+
amax,
|
|
669
|
+
self._get_bias(inputs),
|
|
670
|
+
self._num_bits,
|
|
671
|
+
self.block_sizes.get("scale_bits", None),
|
|
672
|
+
getattr(self, "_trt_high_precision_dtype", None),
|
|
673
|
+
getattr(self, "_onnx_quantizer_type", None),
|
|
674
|
+
self._pass_through_bwd,
|
|
675
|
+
)
|
|
676
|
+
elif isinstance(self._num_bits, tuple):
|
|
677
|
+
# Float-point quantization, e.g., FP8
|
|
678
|
+
E, M = self._num_bits # noqa: N806
|
|
679
|
+
|
|
680
|
+
outputs = scaled_e4m3(
|
|
681
|
+
inputs,
|
|
682
|
+
amax,
|
|
683
|
+
self._get_bias(inputs),
|
|
684
|
+
E,
|
|
685
|
+
M,
|
|
686
|
+
self._trt_high_precision_dtype,
|
|
687
|
+
self._pass_through_bwd,
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
else:
|
|
691
|
+
# Integer quantization, e.g., INT8
|
|
692
|
+
outputs = fake_tensor_quant(
|
|
693
|
+
inputs,
|
|
694
|
+
amax,
|
|
695
|
+
self._get_bias(inputs),
|
|
696
|
+
self._num_bits,
|
|
697
|
+
self._unsigned,
|
|
698
|
+
self._narrow_range,
|
|
699
|
+
self._trt_high_precision_dtype,
|
|
700
|
+
self._pass_through_bwd,
|
|
701
|
+
self.block_sizes.get(-1) if self.block_sizes else None,
|
|
702
|
+
self.axis[0] if isinstance(self.axis, tuple) else self.axis,
|
|
703
|
+
)
|
|
704
|
+
return outputs
|
|
705
|
+
|
|
706
|
+
def _check_onnx_readiness(self, inputs):
|
|
707
|
+
"""Check if quantizer is ready for ONNX export."""
|
|
708
|
+
if not self.block_sizes or self.block_sizes.get("scale_bits", None) != (8, 0):
|
|
709
|
+
assert hasattr(self, "_amax"), (
|
|
710
|
+
"Quantizer has not been calibrated. ONNX export requires the quantizer to be"
|
|
711
|
+
" calibrated. Calibrate and load amax before exporting to ONNX."
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
if self._if_calib:
|
|
715
|
+
warnings.warn(
|
|
716
|
+
"Quantizer is in calibration mode. "
|
|
717
|
+
"Please complete calibration before exporting to ONNX for correct results."
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
amax = self._get_amax(inputs)
|
|
721
|
+
|
|
722
|
+
# We only support scalar amax for E4M3 ONNX export
|
|
723
|
+
if isinstance(self.num_bits, tuple):
|
|
724
|
+
assert amax.numel() == 1, (
|
|
725
|
+
"E4M3 supports ONNX export only for per-tensor quantization."
|
|
726
|
+
" Per-tensor quantization requires scalar amax. "
|
|
727
|
+
f"Received non-scalar amax of shape: {amax.shape}"
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
def _setup_for_blockquant(self, inputs: torch.Tensor):
|
|
731
|
+
# Get reshape sizes and paddings for block-quantization
|
|
732
|
+
def get_axis_quant_params(ax):
|
|
733
|
+
ax = ax if ax in self.block_sizes else ax - inputs.dim()
|
|
734
|
+
bsize = self.block_sizes.get(ax, None)
|
|
735
|
+
padding, ax_slice = None, None
|
|
736
|
+
if bsize is not None and inputs.shape[ax] % bsize != 0:
|
|
737
|
+
padding = (bsize - (inputs.shape[ax] % bsize), 0)
|
|
738
|
+
ax_slice = slice(inputs.shape[ax])
|
|
739
|
+
return bsize, padding, ax_slice
|
|
740
|
+
|
|
741
|
+
def set_quant_params(axis, block_reshape_size, padding, slices, amax_shape=None):
|
|
742
|
+
self._axis = tuple(axis)
|
|
743
|
+
if hasattr(self, "_calibrator"):
|
|
744
|
+
self._calibrator._axis = self._axis
|
|
745
|
+
self._original_shape = inputs.shape
|
|
746
|
+
self._block_reshape_size = torch.Size(block_reshape_size)
|
|
747
|
+
if padding is not None:
|
|
748
|
+
self._padding = tuple(padding)
|
|
749
|
+
self._original_shape = F.pad(inputs, self._padding, "constant", 0).shape
|
|
750
|
+
if slices is not None:
|
|
751
|
+
self._slices = slices
|
|
752
|
+
if amax_shape:
|
|
753
|
+
self._amax_shape_for_export = amax_shape
|
|
754
|
+
|
|
755
|
+
# Reshape size have already been set
|
|
756
|
+
if hasattr(self, "_block_reshape_size"):
|
|
757
|
+
return
|
|
758
|
+
|
|
759
|
+
reshape_size, quantize_axis, paddings, slices = [], [], [], []
|
|
760
|
+
|
|
761
|
+
# special handling for block-quantization along the last axis:
|
|
762
|
+
# flatten the input for faster execution
|
|
763
|
+
if (self.block_sizes.get(inputs.dim() - 1, None) or self.block_sizes.get(-1, None)) and len(
|
|
764
|
+
QuantizerAttributeConfig._get_block_quant_axes_and_sizes(self.block_sizes)
|
|
765
|
+
) == 1:
|
|
766
|
+
bsize, padding, ax_slice = get_axis_quant_params(inputs.dim() - 1)
|
|
767
|
+
slices = None if ax_slice is None else (*(slice(None),) * (inputs.dim() - 1), ax_slice)
|
|
768
|
+
padding = padding if not padding else tuple(reversed(padding))
|
|
769
|
+
amax_shape_for_export = (*(inputs.shape[:-1]), -1)
|
|
770
|
+
set_quant_params((0,), (-1, bsize), padding, slices, amax_shape_for_export)
|
|
771
|
+
return
|
|
772
|
+
|
|
773
|
+
for ax in range(inputs.dim()):
|
|
774
|
+
bsize, padding, ax_slice = get_axis_quant_params(ax)
|
|
775
|
+
paddings.append(padding)
|
|
776
|
+
slices.append(ax_slice)
|
|
777
|
+
if bsize is not None:
|
|
778
|
+
reshape_size.extend([math.ceil(inputs.shape[ax] / bsize), bsize])
|
|
779
|
+
quantize_axis.extend([True, False])
|
|
780
|
+
else:
|
|
781
|
+
reshape_size.append(inputs.shape[ax])
|
|
782
|
+
quantize_axis.append(True)
|
|
783
|
+
|
|
784
|
+
quant_axis = [i for i in range(len(quantize_axis)) if quantize_axis[i]]
|
|
785
|
+
|
|
786
|
+
slices = (
|
|
787
|
+
None if all(s is None for s in slices) else [s if s else slice(None) for s in slices]
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
if all(p is None for p in paddings):
|
|
791
|
+
paddings = None
|
|
792
|
+
else:
|
|
793
|
+
new_paddings = []
|
|
794
|
+
for padding in paddings:
|
|
795
|
+
if not (new_paddings or padding):
|
|
796
|
+
continue
|
|
797
|
+
new_paddings.extend(padding if padding else (0, 0))
|
|
798
|
+
paddings = tuple(reversed(new_paddings))
|
|
799
|
+
|
|
800
|
+
set_quant_params(quant_axis, reshape_size, paddings, slices)
|
|
801
|
+
|
|
802
|
+
def _process_for_blockquant(self, inputs: torch.Tensor):
|
|
803
|
+
if hasattr(self, "_padding"):
|
|
804
|
+
inputs = F.pad(inputs, self._padding, "constant", 0)
|
|
805
|
+
assert inputs.shape == self._original_shape, (
|
|
806
|
+
f"Input shape has changed from {self._original_shape} to {inputs.shape}."
|
|
807
|
+
" Block-quantization requires a fixed input shape."
|
|
808
|
+
)
|
|
809
|
+
inputs = inputs.reshape(self._block_reshape_size)
|
|
810
|
+
return inputs
|
|
811
|
+
|
|
812
|
+
def _reset_to_original_shape(self, outputs: torch.Tensor):
|
|
813
|
+
outputs = outputs.reshape(self._original_shape)
|
|
814
|
+
if hasattr(self, "_slices"):
|
|
815
|
+
outputs = outputs[self._slices]
|
|
816
|
+
return outputs
|
|
817
|
+
|
|
818
|
+
def _block_sizes_to_axis(self, x: torch.Tensor):
|
|
819
|
+
"""Convert block_sizes to axis in per-channel/tensor quantization.
|
|
820
|
+
|
|
821
|
+
For example, for input tensor with shape (B, T, H),
|
|
822
|
+
{"block_sizes": {-1: None, -3: None}} equals to {axis: (-2)}, amax shape: (1, T, 1),
|
|
823
|
+
{"block_sizes": {-1: None, -2: None, -3: None}} equals to {axis: None}, amax shape: (1, T, 1)
|
|
824
|
+
"""
|
|
825
|
+
block_sizes = self._block_sizes
|
|
826
|
+
if block_sizes is None:
|
|
827
|
+
return
|
|
828
|
+
|
|
829
|
+
def _check_per_channel_block_sizes(block_sizes):
|
|
830
|
+
# Check per-channel/block quant
|
|
831
|
+
return all(v is None for k, v in block_sizes.items() if isinstance(k, int))
|
|
832
|
+
|
|
833
|
+
if _check_per_channel_block_sizes(block_sizes):
|
|
834
|
+
# Convert block_sizes to axis
|
|
835
|
+
assert self.axis is None, "Axis and block_sizes are both set."
|
|
836
|
+
axis = tuple(k if k >= 0 else k + x.dim() for k in block_sizes if isinstance(k, int))
|
|
837
|
+
self.axis = tuple(i for i in range(x.dim()) if i not in axis) or None
|
|
838
|
+
|
|
839
|
+
# remove block_sizes
|
|
840
|
+
self._block_sizes = None
|
|
841
|
+
|
|
842
|
+
def export_amax(self) -> torch.Tensor | None:
|
|
843
|
+
"""Export correctly formatted/shaped amax."""
|
|
844
|
+
if self.block_sizes is not None and self.block_sizes.get("type", None) == "dynamic":
|
|
845
|
+
return self.amax
|
|
846
|
+
|
|
847
|
+
if self.amax is None:
|
|
848
|
+
return None
|
|
849
|
+
|
|
850
|
+
if not hasattr(self, "_amax_shape_for_export"):
|
|
851
|
+
amax = self.amax
|
|
852
|
+
else:
|
|
853
|
+
amax = self.amax.reshape(self._amax_shape_for_export)
|
|
854
|
+
amax[amax == 0] = self.maxbound
|
|
855
|
+
amax = torch.nan_to_num(amax, nan=self.maxbound)
|
|
856
|
+
clamp_min, clamp_max = torch.finfo(amax.dtype).tiny, torch.finfo(amax.dtype).max
|
|
857
|
+
amax = amax.clamp(min=clamp_min, max=clamp_max)
|
|
858
|
+
|
|
859
|
+
self._validate_amax(amax)
|
|
860
|
+
|
|
861
|
+
if self.block_sizes is None:
|
|
862
|
+
# tensorrt_llm assumes the scaling_factor dim >= 1 for per-tensor.
|
|
863
|
+
if self.axis is None:
|
|
864
|
+
amax = amax.unsqueeze(0)
|
|
865
|
+
|
|
866
|
+
# If single-axis quantization, squeeze amax
|
|
867
|
+
elif isinstance(self.axis, int) or (
|
|
868
|
+
isinstance(self.axis, (list, tuple)) and len(self.axis) == 1
|
|
869
|
+
):
|
|
870
|
+
amax = amax.squeeze()
|
|
871
|
+
|
|
872
|
+
return amax
|
|
873
|
+
|
|
874
|
+
def forward(self, inputs):
|
|
875
|
+
"""Apply tensor_quant function to inputs.
|
|
876
|
+
|
|
877
|
+
Args:
|
|
878
|
+
inputs: A Tensor of type float32/float16/bfloat16.
|
|
879
|
+
|
|
880
|
+
Returns:
|
|
881
|
+
outputs: A Tensor of type output_dtype
|
|
882
|
+
"""
|
|
883
|
+
if DTensor is not None and isinstance(inputs, DTensor):
|
|
884
|
+
# TensorQuantizer only handles regular non-DTensor inputs
|
|
885
|
+
device_mesh, placements = inputs.device_mesh, inputs.placements
|
|
886
|
+
outputs = self.forward(inputs.to_local())
|
|
887
|
+
return DTensor.from_local(outputs, device_mesh, placements)
|
|
888
|
+
|
|
889
|
+
if isinstance(inputs, (BaseQuantizedTensor, QTensorWrapper)):
|
|
890
|
+
assert self._dequantize, "No dequantization stats in the tensor quantizer."
|
|
891
|
+
return self.dequantize(inputs)
|
|
892
|
+
|
|
893
|
+
# Early return if nothing is collected during the forward (e.g. MoE)
|
|
894
|
+
if inputs.numel() == 0:
|
|
895
|
+
return inputs
|
|
896
|
+
|
|
897
|
+
# Activation scaling for smoothquant
|
|
898
|
+
if self.pre_quant_scale is not None:
|
|
899
|
+
inputs = inputs * self.pre_quant_scale
|
|
900
|
+
|
|
901
|
+
# Rotating the input
|
|
902
|
+
if self._rotate:
|
|
903
|
+
inputs = normalized_hadamard_transform(inputs)
|
|
904
|
+
|
|
905
|
+
if self._disabled:
|
|
906
|
+
# if quantizer is disabled, we still need to track the input dtype for saving the model
|
|
907
|
+
# TODO: This is a temporary solution and needs to be removed once megatron supports
|
|
908
|
+
# non-homogeneous layers
|
|
909
|
+
self._input_dtype = inputs.dtype if hasattr(inputs, "dtype") else None
|
|
910
|
+
return inputs
|
|
911
|
+
|
|
912
|
+
if (
|
|
913
|
+
not is_torch_export_mode()
|
|
914
|
+
and not torch._dynamo.is_compiling()
|
|
915
|
+
and GLOBALS.in_onnx_export
|
|
916
|
+
):
|
|
917
|
+
# GLOBALS could break TorchDynamo for some Pytorch versions (i.e., 2.3.0)
|
|
918
|
+
self._check_onnx_readiness(inputs)
|
|
919
|
+
|
|
920
|
+
if self.block_sizes is not None and self._fake_quant:
|
|
921
|
+
# To support the new block_sizes representation for per-channel quantization,
|
|
922
|
+
# convert the dim dict in block_sizes to axis.
|
|
923
|
+
# The axis attribute is still preserved for backward compatibility.
|
|
924
|
+
self._block_sizes_to_axis(inputs)
|
|
925
|
+
|
|
926
|
+
if (
|
|
927
|
+
self.block_sizes is not None
|
|
928
|
+
and self.block_sizes.get("type", None) != "dynamic"
|
|
929
|
+
and self._fake_quant
|
|
930
|
+
):
|
|
931
|
+
# Tensor reshaping is required for static block quantization
|
|
932
|
+
# Tensor shapes are handled separately by the quantization kernels for dynamic block quantization
|
|
933
|
+
self._setup_for_blockquant(inputs)
|
|
934
|
+
inputs = self._process_for_blockquant(inputs)
|
|
935
|
+
|
|
936
|
+
outputs = inputs
|
|
937
|
+
|
|
938
|
+
block_size = None
|
|
939
|
+
if self._if_calib and not self._dynamic:
|
|
940
|
+
if self._calibrator is None:
|
|
941
|
+
raise RuntimeError("Calibrator was not created.")
|
|
942
|
+
# Shape is only known when it sees the first tensor
|
|
943
|
+
if self.block_sizes is not None and self.block_sizes.get("type", None) == "dynamic":
|
|
944
|
+
block_size = self.block_sizes.get(-1, None) or self.block_sizes.get(
|
|
945
|
+
inputs.dim() - 1, None
|
|
946
|
+
)
|
|
947
|
+
assert block_size is not None, "block size for dynamic quantization not found."
|
|
948
|
+
|
|
949
|
+
# Collect calibration data for bias
|
|
950
|
+
self.collect(inputs)
|
|
951
|
+
|
|
952
|
+
if self._if_quant:
|
|
953
|
+
# Check if the input tensor is contiguous
|
|
954
|
+
# Non-contiguous tensors will generate incorrect FP4 quantization results
|
|
955
|
+
if hasattr(inputs, "is_contiguous") and not inputs.is_contiguous():
|
|
956
|
+
inputs.data = inputs.data.contiguous()
|
|
957
|
+
if self.fake_quant:
|
|
958
|
+
outputs = self._fake_quantize(inputs)
|
|
959
|
+
elif not self._dequantize:
|
|
960
|
+
outputs = self._real_quantize(inputs)
|
|
961
|
+
else:
|
|
962
|
+
raise ValueError(
|
|
963
|
+
"self._dequantize is True and self.fake_quant is False. "
|
|
964
|
+
"This case should have been handled."
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
if (
|
|
968
|
+
self.block_sizes is not None
|
|
969
|
+
and self.block_sizes.get("type", None) != "dynamic"
|
|
970
|
+
and self._fake_quant
|
|
971
|
+
):
|
|
972
|
+
outputs = self._reset_to_original_shape(outputs)
|
|
973
|
+
|
|
974
|
+
return outputs
|
|
975
|
+
|
|
976
|
+
def _short_amax(self, fmt=".4f"):
|
|
977
|
+
"""Short description of amax.
|
|
978
|
+
|
|
979
|
+
Returns:
|
|
980
|
+
'dynamic': if _amax is not registered
|
|
981
|
+
'amax': if _amax is per-tensor
|
|
982
|
+
'[min, max](size)': if _amax is per-channel
|
|
983
|
+
"""
|
|
984
|
+
if self.is_mx_format:
|
|
985
|
+
return "None"
|
|
986
|
+
if not hasattr(self, "_amax"):
|
|
987
|
+
return "dynamic"
|
|
988
|
+
if self._amax is None:
|
|
989
|
+
return "None"
|
|
990
|
+
if self._amax.is_meta:
|
|
991
|
+
return "meta"
|
|
992
|
+
if self._amax.numel() == 1:
|
|
993
|
+
return f"{self._amax.item():{fmt}}"
|
|
994
|
+
return (
|
|
995
|
+
f"[{self._amax.min().item():{fmt}},"
|
|
996
|
+
f" {self._amax.max().item():{fmt}}]({self._amax.numel()})"
|
|
997
|
+
)
|
|
998
|
+
|
|
999
|
+
def extra_repr(self):
|
|
1000
|
+
"""Set the extra information about this module."""
|
|
1001
|
+
if self._disabled:
|
|
1002
|
+
return "disabled"
|
|
1003
|
+
s = f"{'unsigned ' if self._unsigned else ''}{self._num_bits} bit"
|
|
1004
|
+
s += " narrow" if (self._narrow_range) else ""
|
|
1005
|
+
s += " fake" if (self._fake_quant) else ""
|
|
1006
|
+
if self.block_sizes is not None:
|
|
1007
|
+
s += f" block_sizes={self._block_sizes},"
|
|
1008
|
+
else:
|
|
1009
|
+
s += f" axis={self._axis}" if self._axis is not None else " per-tensor"
|
|
1010
|
+
s += f" amax={self._short_amax()}"
|
|
1011
|
+
s += " pre_quant_scale" if self.pre_quant_scale is not None else ""
|
|
1012
|
+
s += " svdquant" if self.svdquant_lora_a is not None else ""
|
|
1013
|
+
s += " rotated" if self._rotate else ""
|
|
1014
|
+
s += (
|
|
1015
|
+
f" calibrator={self._calibrator.__class__.__name__}"
|
|
1016
|
+
if (self._calibrator is not None)
|
|
1017
|
+
else ""
|
|
1018
|
+
)
|
|
1019
|
+
if self._bias:
|
|
1020
|
+
s += f" bias={self._bias}"
|
|
1021
|
+
|
|
1022
|
+
s += " quant" if (self._if_quant) else ""
|
|
1023
|
+
s += " calib" if (self._if_calib) else ""
|
|
1024
|
+
return s
|
|
1025
|
+
|
|
1026
|
+
@property
|
|
1027
|
+
def mopt_ckpt_versn(self):
|
|
1028
|
+
"""Version of the checkpoint if it is restored from a checkpoint."""
|
|
1029
|
+
return getattr(self, "_mopt_ckpt_versn", None)
|
|
1030
|
+
|
|
1031
|
+
@mopt_ckpt_versn.setter
|
|
1032
|
+
def mopt_ckpt_versn(self, version: str):
|
|
1033
|
+
self._mopt_ckpt_versn = str(version)
|
|
1034
|
+
|
|
1035
|
+
def _load_from_state_dict(self, state_dict, prefix, *args, **kwargs):
|
|
1036
|
+
"""Special handling for loading older checkpoints.
|
|
1037
|
+
|
|
1038
|
+
This implementation is for backward compatibility and can be deprecated in future versions.
|
|
1039
|
+
|
|
1040
|
+
Args:
|
|
1041
|
+
state_dict: A dict containing the state of the top level module
|
|
1042
|
+
prefix: A string that prefixes all of this modules state in state_dict, e.g. 'model.conv1.'
|
|
1043
|
+
"""
|
|
1044
|
+
if self.mopt_ckpt_versn is None or Version(self.mopt_ckpt_versn) >= Version("0.29"):
|
|
1045
|
+
# Warnings below are raised if users use partial state dictionary intentionally (eg:- HF ckpts)
|
|
1046
|
+
# For ModelOpt >= 0.29, the buffers will be correctly created, So lets skip the warnings
|
|
1047
|
+
return super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
|
1048
|
+
|
|
1049
|
+
_attrs = ["_amax", "_pre_quant_scale", "_svdquant_lora_a", "_svdquant_lora_b"]
|
|
1050
|
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
1051
|
+
|
|
1052
|
+
for attr in _attrs:
|
|
1053
|
+
has_dst = attr in self._buffers
|
|
1054
|
+
has_src = prefix + attr in state_dict
|
|
1055
|
+
|
|
1056
|
+
if not has_src and has_dst:
|
|
1057
|
+
warnings.warn(f"{prefix[:-1]}: No {attr} in state_dict.")
|
|
1058
|
+
elif has_src and not has_dst:
|
|
1059
|
+
warnings.warn(
|
|
1060
|
+
f"{prefix[:-1]}: No '{attr}' buffer to load {attr} into."
|
|
1061
|
+
f" '{attr}` is created as a buffer for now. Please move the model to the correct device and "
|
|
1062
|
+
"dtype after this by calling `model.to(device, dtype)`."
|
|
1063
|
+
)
|
|
1064
|
+
self.register_buffer(attr, state_dict[prefix + attr].clone().detach().to(device))
|
|
1065
|
+
|
|
1066
|
+
super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
|
1067
|
+
|
|
1068
|
+
def _get_properties_for_modelopt_state(self):
|
|
1069
|
+
return (
|
|
1070
|
+
self.__dict__.keys()
|
|
1071
|
+
- nn.Module().__dict__.keys()
|
|
1072
|
+
- self._skip_properties_for_save_restore
|
|
1073
|
+
)
|
|
1074
|
+
|
|
1075
|
+
def _get_pytorch_state_metadata(self):
|
|
1076
|
+
"""Get Pytorch state metadata.
|
|
1077
|
+
|
|
1078
|
+
Current we only store the shape of the state_dict values.
|
|
1079
|
+
"""
|
|
1080
|
+
metadata = {"params": {}, "buffers": {}}
|
|
1081
|
+
for k, v in self._parameters.items():
|
|
1082
|
+
metadata["params"][k] = {"shape": v.shape, "dtype": v.dtype}
|
|
1083
|
+
for k, v in self._buffers.items():
|
|
1084
|
+
if k in self._non_persistent_buffers_set:
|
|
1085
|
+
continue
|
|
1086
|
+
metadata["buffers"][k] = {"shape": v.shape, "dtype": v.dtype}
|
|
1087
|
+
return metadata
|
|
1088
|
+
|
|
1089
|
+
def _del_pytorch_state(self):
|
|
1090
|
+
# Lets delete the parameters and buffers
|
|
1091
|
+
self._parameters.clear()
|
|
1092
|
+
self._buffers.clear()
|
|
1093
|
+
self._non_persistent_buffers_set.clear()
|
|
1094
|
+
|
|
1095
|
+
def _reset_pytorch_state_from_metadata(self, metadata: dict[str, Any]):
|
|
1096
|
+
# Lets delete existing parameters and buffers and create fresh ones
|
|
1097
|
+
self._del_pytorch_state()
|
|
1098
|
+
for k, v in metadata.get("params", {}).items():
|
|
1099
|
+
dtype = v.get("dtype", None)
|
|
1100
|
+
self.register_parameter(k, nn.Parameter(torch.empty(v["shape"], dtype=dtype)))
|
|
1101
|
+
for k, v in metadata.get("buffers", {}).items():
|
|
1102
|
+
dtype = v.get("dtype", None)
|
|
1103
|
+
self.register_buffer(k, torch.empty(v["shape"], dtype=dtype))
|
|
1104
|
+
|
|
1105
|
+
def get_modelopt_state(self, properties_only: bool = False) -> dict[str, Any]:
|
|
1106
|
+
"""Get meta state to be saved in checkpoint.
|
|
1107
|
+
|
|
1108
|
+
If `properties_only` is True, only the quantizer properties such as `num_bits`, `axis` etc are included.
|
|
1109
|
+
For restoring the quantizer fully including the parameters and buffers, use `properties_only=False`.
|
|
1110
|
+
"""
|
|
1111
|
+
modelopt_state = {}
|
|
1112
|
+
for k in self._get_properties_for_modelopt_state():
|
|
1113
|
+
modelopt_state[k] = getattr(self, k)
|
|
1114
|
+
|
|
1115
|
+
if properties_only:
|
|
1116
|
+
return modelopt_state
|
|
1117
|
+
|
|
1118
|
+
modelopt_state["_pytorch_state_metadata"] = self._get_pytorch_state_metadata()
|
|
1119
|
+
|
|
1120
|
+
return modelopt_state
|
|
1121
|
+
|
|
1122
|
+
def set_from_modelopt_state(self, modelopt_state, properties_only: bool = False):
|
|
1123
|
+
"""Set meta state from checkpoint.
|
|
1124
|
+
|
|
1125
|
+
If `properties_only` is True, only the quantizer properties such as `num_bits`, `axis` etc are included.
|
|
1126
|
+
For restoring the quantizer fully including the parameters and buffers, use `properties_only=False`.
|
|
1127
|
+
"""
|
|
1128
|
+
# Set all properties except the skip properties; this is done for backward compatibility
|
|
1129
|
+
for key in modelopt_state.keys() - self._skip_properties_for_save_restore:
|
|
1130
|
+
setattr(self, key, modelopt_state[key])
|
|
1131
|
+
|
|
1132
|
+
# Set the calibrator properties
|
|
1133
|
+
# TODO: This might not be sufficient for the custom calibrators - however there is no use-case for it yet
|
|
1134
|
+
for key in ["_num_bits", "_axis", "_unsigned"]:
|
|
1135
|
+
setattr(self._calibrator, key, getattr(self, key))
|
|
1136
|
+
|
|
1137
|
+
if not properties_only:
|
|
1138
|
+
self._reset_pytorch_state_from_metadata(
|
|
1139
|
+
modelopt_state.get("_pytorch_state_metadata", {})
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
def sync_amax_across_distributed_group(self, parallel_group: DistributedProcessGroup):
|
|
1143
|
+
"""Synchronize the amax across all ranks in the given group."""
|
|
1144
|
+
if parallel_group.is_initialized() and getattr(self, "_amax", None) is not None:
|
|
1145
|
+
try:
|
|
1146
|
+
dist.all_reduce(self._amax, op=dist.ReduceOp.MAX, group=parallel_group.group)
|
|
1147
|
+
except RuntimeError as e:
|
|
1148
|
+
# This error happens if the distributed backend is using GPU and
|
|
1149
|
+
# the tensor is not on GPU (or vice versa).
|
|
1150
|
+
warnings.warn(
|
|
1151
|
+
f"Failed to synchronize amax: {e}, probably because the tensor is on a device which is not"
|
|
1152
|
+
"supported by the current distributed backend. This warning can be ignored"
|
|
1153
|
+
"if happening during modelopt restore."
|
|
1154
|
+
)
|
|
1155
|
+
|
|
1156
|
+
@contextlib.contextmanager
|
|
1157
|
+
def disable_pre_quant_scale(self):
|
|
1158
|
+
"""Context manager to turn off pre_quant_scale inside this quantizer."""
|
|
1159
|
+
was_enabled = self._enable_pre_quant_scale
|
|
1160
|
+
self._enable_pre_quant_scale = False
|
|
1161
|
+
try:
|
|
1162
|
+
yield
|
|
1163
|
+
finally:
|
|
1164
|
+
self._enable_pre_quant_scale = was_enabled
|
|
1165
|
+
|
|
1166
|
+
def collect(self, inputs) -> None:
|
|
1167
|
+
"""Collect calibration data."""
|
|
1168
|
+
if not self._if_calib or self._dynamic:
|
|
1169
|
+
return
|
|
1170
|
+
|
|
1171
|
+
# Collect bias data if bias calibration is enabled
|
|
1172
|
+
if self.bias_calibrator is not None and self.bias_type == "static":
|
|
1173
|
+
self.bias_calibrator.collect(inputs)
|
|
1174
|
+
inputs = inputs - self.bias_calibrator.compute_bias()
|
|
1175
|
+
|
|
1176
|
+
self._calibrator.collect(inputs)
|
|
1177
|
+
|
|
1178
|
+
def _set_buffer(self, key, value):
|
|
1179
|
+
if hasattr(self, key):
|
|
1180
|
+
setattr(self, key, value)
|
|
1181
|
+
else:
|
|
1182
|
+
self.register_buffer(key, value)
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
class SequentialQuantizer(nn.Sequential):
|
|
1186
|
+
"""A sequential container for :class:`TensorQuantizer` modules.
|
|
1187
|
+
|
|
1188
|
+
This modules is used to quantize a tensor in multiple formats sequentially. It takes as input
|
|
1189
|
+
:class:`TensorQuantizer` modules and containerize them similar to :class:`torch.nn.Sequential`.
|
|
1190
|
+
|
|
1191
|
+
We delegate certain properties and methods to all contained quantizers.
|
|
1192
|
+
In the case of conflicts, the first quantizer's property or method takes priority.
|
|
1193
|
+
|
|
1194
|
+
`SequentialQuantizer` is useful in cases like INT4 weights, FP8 activations where weight quantization is not the
|
|
1195
|
+
same as the gemm quantization. It allows for applying multiple quantization formats to the same tensor in sequence.
|
|
1196
|
+
|
|
1197
|
+
Use `SequentialQuantizer` methods in lower level implementations for better code organization and readability.
|
|
1198
|
+
|
|
1199
|
+
Args:
|
|
1200
|
+
quantizers (TensorQuantizer): :class:`TensorQuantizer` modules to be added to the container.
|
|
1201
|
+
|
|
1202
|
+
"""
|
|
1203
|
+
|
|
1204
|
+
_delegated_properties = ["fake_quant", "is_enabled"]
|
|
1205
|
+
_delegated_methods = [
|
|
1206
|
+
"reset_amax",
|
|
1207
|
+
"disable",
|
|
1208
|
+
"enable",
|
|
1209
|
+
"load_calib_amax",
|
|
1210
|
+
"load_calib_bias",
|
|
1211
|
+
]
|
|
1212
|
+
|
|
1213
|
+
def __init__(self, *quantizers: TensorQuantizer):
|
|
1214
|
+
"""Initialize SequentialQuantizer module."""
|
|
1215
|
+
super().__init__(*quantizers)
|
|
1216
|
+
assert all(isinstance(q, TensorQuantizer) for q in self), (
|
|
1217
|
+
"All quantizers must be a TensorQuantizer."
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
def __getattr__(self, name):
|
|
1221
|
+
"""Delegate properties and methods to all contained quantizers."""
|
|
1222
|
+
if name in self._delegated_properties:
|
|
1223
|
+
# Return the property of the first quantizer
|
|
1224
|
+
return getattr(self[0], name)
|
|
1225
|
+
|
|
1226
|
+
if name in self._delegated_methods:
|
|
1227
|
+
|
|
1228
|
+
def method_wrapper(*args, **kwargs):
|
|
1229
|
+
outputs = getattr(self[0], name)(*args, **kwargs)
|
|
1230
|
+
for quantizer in self[1:]:
|
|
1231
|
+
outputs = getattr(quantizer, name)(*args, **kwargs)
|
|
1232
|
+
return outputs
|
|
1233
|
+
|
|
1234
|
+
return method_wrapper
|
|
1235
|
+
|
|
1236
|
+
# Defer to super class for attributes not handled here
|
|
1237
|
+
return super().__getattr__(name)
|
|
1238
|
+
|
|
1239
|
+
def __setattr__(self, name, value):
|
|
1240
|
+
if name in self._delegated_properties:
|
|
1241
|
+
for quantizer in self:
|
|
1242
|
+
setattr(quantizer, name, value)
|
|
1243
|
+
else:
|
|
1244
|
+
super().__setattr__(name, value)
|
|
1245
|
+
|
|
1246
|
+
def get_modelopt_state(self) -> dict[str, Any]:
|
|
1247
|
+
"""Get meta state to be saved in checkpoint."""
|
|
1248
|
+
return {"num_quantizers": len(self), "is_sequential_quantizer": True}
|
|
1249
|
+
|
|
1250
|
+
def set_from_attribute_config(
|
|
1251
|
+
self,
|
|
1252
|
+
attributes: list[dict[str, Any] | QuantizerAttributeConfig]
|
|
1253
|
+
| dict[str, Any]
|
|
1254
|
+
| QuantizerAttributeConfig,
|
|
1255
|
+
):
|
|
1256
|
+
"""Set the attributes of contained quantizers from a list of attribute_dicts."""
|
|
1257
|
+
if not isinstance(attributes, (list, tuple)):
|
|
1258
|
+
assert isinstance(attributes, (dict, QuantizerAttributeConfig)), (
|
|
1259
|
+
"attributes must be a list or a dict."
|
|
1260
|
+
)
|
|
1261
|
+
attributes = [attributes] * len(self)
|
|
1262
|
+
|
|
1263
|
+
for attribute, quantizer in zip(attributes, self):
|
|
1264
|
+
quantizer.set_from_attribute_config(attribute)
|
|
1265
|
+
|
|
1266
|
+
@staticmethod
|
|
1267
|
+
@contextlib.contextmanager
|
|
1268
|
+
def convert_to_single_quantizer(model, indx: int = 0):
|
|
1269
|
+
"""Replace instances of :class:`SequentialQuantizer` in the model with single `TensorQuantizer` quantizer.
|
|
1270
|
+
|
|
1271
|
+
The quantizer indexed by ``indx`` from the sequential quantizer is used to replace it.
|
|
1272
|
+
This method is useful for individually calibrating the quantizers in a sequential quantizer.
|
|
1273
|
+
"""
|
|
1274
|
+
original_sequential_quantizers: dict[nn.Module, list] = {}
|
|
1275
|
+
for name, module in list(model.named_modules()):
|
|
1276
|
+
if isinstance(module, SequentialQuantizer):
|
|
1277
|
+
assert len(module) > indx
|
|
1278
|
+
parent_module = model.get_submodule(name.rpartition(".")[0])
|
|
1279
|
+
if parent_module not in original_sequential_quantizers:
|
|
1280
|
+
original_sequential_quantizers[parent_module] = []
|
|
1281
|
+
original_sequential_quantizers[parent_module].append(
|
|
1282
|
+
(name.rpartition(".")[-1], module)
|
|
1283
|
+
)
|
|
1284
|
+
setattr(parent_module, name.rpartition(".")[-1], module[indx])
|
|
1285
|
+
|
|
1286
|
+
yield
|
|
1287
|
+
|
|
1288
|
+
for (
|
|
1289
|
+
parent_module,
|
|
1290
|
+
sequential_quantizers_list,
|
|
1291
|
+
) in original_sequential_quantizers.items():
|
|
1292
|
+
for name, sequential_quantizer in sequential_quantizers_list:
|
|
1293
|
+
setattr(parent_module, name, sequential_quantizer)
|