mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-11-13 02:17:13 +08:00
312 lines
12 KiB
Python
312 lines
12 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Support quantization for huggingface layers."""
|
|
|
|
import warnings
|
|
from contextlib import contextmanager
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
import transformers
|
|
|
|
from modelopt.core.torch.quantization.algorithms import AutoQuantizeSearcher
|
|
from modelopt.torch.opt.dynamic import DynamicModule
|
|
|
|
from ..nn import QuantModuleRegistry, TensorQuantizer
|
|
from ..nn.modules.quant_linear import _QuantLinear
|
|
from .attention import register_attention_for_kv_quant, register_hf_attention_for_kv_quant
|
|
|
|
__all__ = ["register_hf_attentions_on_the_fly"]
|
|
|
|
if transformers.modeling_utils.Conv1D not in QuantModuleRegistry:
|
|
# transformers.modeling_utils.Conv1D used in HF-GPT2 is not a real Conv1D
|
|
# It is actually a Linear layer where weight is transposed and torch.addmm is used
|
|
@QuantModuleRegistry.register({transformers.modeling_utils.Conv1D: "Conv1D"})
|
|
class _QuantConv1D(_QuantLinear):
|
|
@classmethod
|
|
@torch.no_grad()
|
|
def convert(cls, module: nn.Module) -> "_QuantConv1D":
|
|
module.weight = nn.Parameter(module.weight.T.contiguous())
|
|
module.out_features, module.in_features = module.weight.shape
|
|
# We want the forward method of nn.Linear to be called instead of the forward method of Conv1D
|
|
dyn_cls: DynamicModule = QuantModuleRegistry.get(nn.Linear)
|
|
return dyn_cls.convert(module)
|
|
|
|
|
|
class _QuantLlama4TextExperts(DynamicModule):
|
|
def _setup(self):
|
|
self.gate_up_proj_input_quantizer = TensorQuantizer()
|
|
self.gate_up_proj_weight_quantizer = TensorQuantizer()
|
|
self.down_proj_input_quantizer = TensorQuantizer()
|
|
self.down_proj_weight_quantizer = TensorQuantizer()
|
|
|
|
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
|
hidden_states = hidden_states.view(self.num_experts, -1, self.hidden_size)
|
|
gate_up = torch.bmm(
|
|
self.gate_up_proj_input_quantizer(hidden_states),
|
|
self.gate_up_proj_weight_quantizer(self.gate_up_proj),
|
|
)
|
|
gate, up = gate_up.chunk(2, dim=-1) # not supported for DTensors
|
|
next_states = torch.bmm(
|
|
self.down_proj_input_quantizer(up * self.act_fn(gate)),
|
|
self.down_proj_weight_quantizer(self.down_proj),
|
|
)
|
|
next_states = next_states.view(-1, self.hidden_size)
|
|
return next_states
|
|
|
|
|
|
# For more information on DbrxExpert, see https://github.com/huggingface/transformers/blob/dcdda532/src/transformers/models/dbrx/modeling_dbrx.py#L756
|
|
class _QuantDbrxExperts(DynamicModule):
|
|
def _setup(self):
|
|
"""Modify the DbrxExpert."""
|
|
# No setup is needed for DbrxExpert, we only need to update DbrxExpertGLU
|
|
pass
|
|
|
|
# forward method copied from the original dbrx repo - https://github.com/databricks/dbrx/blob/a3200393/model/modeling_dbrx.py#L795
|
|
def forward(
|
|
self,
|
|
x: torch.Tensor,
|
|
weights: torch.Tensor,
|
|
top_weights: torch.Tensor,
|
|
top_experts: torch.LongTensor,
|
|
) -> torch.Tensor:
|
|
bsz, q_len, hidden_size = x.shape
|
|
x = x.view(-1, hidden_size)
|
|
out = torch.zeros_like(x)
|
|
|
|
expert_mask = nn.functional.one_hot(top_experts, num_classes=self.moe_num_experts).permute(
|
|
2, 1, 0
|
|
)
|
|
for expert_idx in range(0, self.moe_num_experts):
|
|
topk_idx, token_idx = torch.where(expert_mask[expert_idx])
|
|
if token_idx.shape[0] == 0:
|
|
continue
|
|
|
|
token_list = token_idx.tolist()
|
|
topk_list = topk_idx.tolist()
|
|
|
|
expert_tokens = x[None, token_list].reshape(-1, hidden_size)
|
|
expert_out = (
|
|
self.mlp(expert_tokens, expert_idx) * top_weights[token_list, topk_list, None]
|
|
)
|
|
|
|
out.index_add_(0, token_idx, expert_out)
|
|
|
|
out = out.reshape(bsz, q_len, hidden_size)
|
|
return out
|
|
|
|
|
|
class _QuantDbrxExpertGLU(DynamicModule):
|
|
def _setup(self):
|
|
"""Modify the DbrxExpertGLU by using nn.Linear layers."""
|
|
dtype, device = self.w1.dtype, self.w1.device
|
|
|
|
def _copy_weights(modules, weights):
|
|
modules.to(dtype=dtype, device=device)
|
|
for expert_idx, module in enumerate(modules):
|
|
with torch.no_grad():
|
|
module.weight.copy_(weights[expert_idx].detach())
|
|
|
|
self.w1_linear = nn.ModuleList(
|
|
[
|
|
nn.Linear(self.hidden_size, self.ffn_hidden_size, bias=False)
|
|
for _ in range(self.moe_num_experts)
|
|
]
|
|
)
|
|
_copy_weights(
|
|
self.w1_linear,
|
|
self.w1.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size),
|
|
)
|
|
delattr(self, "w1")
|
|
|
|
self.v1_linear = nn.ModuleList(
|
|
[
|
|
nn.Linear(self.hidden_size, self.ffn_hidden_size, bias=False)
|
|
for _ in range(self.moe_num_experts)
|
|
]
|
|
)
|
|
_copy_weights(
|
|
self.v1_linear,
|
|
self.v1.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size),
|
|
)
|
|
delattr(self, "v1")
|
|
|
|
self.w2_linear = nn.ModuleList(
|
|
[
|
|
nn.Linear(self.ffn_hidden_size, self.hidden_size, bias=False)
|
|
for _ in range(self.moe_num_experts)
|
|
]
|
|
)
|
|
_copy_weights(
|
|
self.w2_linear,
|
|
self.w2.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size).transpose(
|
|
1, 2
|
|
),
|
|
)
|
|
delattr(self, "w2")
|
|
|
|
def forward(self, x: torch.Tensor, expert_idx: int) -> torch.Tensor:
|
|
x1 = self.w1_linear[expert_idx](x)
|
|
x2 = self.v1_linear[expert_idx](x)
|
|
x1 = self.activation_fn(x1)
|
|
x1 = x1 * x2
|
|
return self.w2_linear[expert_idx](x1)
|
|
|
|
|
|
try:
|
|
from transformers.models.llama4.modeling_llama4 import Llama4TextExperts
|
|
|
|
if Llama4TextExperts not in QuantModuleRegistry:
|
|
QuantModuleRegistry.register({Llama4TextExperts: "hf.Llama4TextExperts"})(
|
|
_QuantLlama4TextExperts
|
|
)
|
|
except ImportError:
|
|
pass
|
|
|
|
try:
|
|
from transformers.models.dbrx.modeling_dbrx import DbrxExpertGLU, DbrxExperts
|
|
|
|
if DbrxExperts not in QuantModuleRegistry:
|
|
QuantModuleRegistry.register({DbrxExperts: "hf.DbrxExperts"})(_QuantDbrxExperts)
|
|
|
|
if DbrxExpertGLU not in QuantModuleRegistry:
|
|
QuantModuleRegistry.register({DbrxExpertGLU: "hf.DbrxExpertGLU"})(_QuantDbrxExpertGLU)
|
|
except ImportError:
|
|
pass
|
|
|
|
try:
|
|
from transformers.models.falcon.modeling_falcon import FalconLinear
|
|
|
|
if FalconLinear not in QuantModuleRegistry:
|
|
QuantModuleRegistry.register({FalconLinear: "hf.FalconLinear"})(_QuantLinear)
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
def register_dbrx_moe_on_the_fly(model):
|
|
"""Register DBRX MoE modules as QUANT_MODULE.
|
|
|
|
The MoE class in DBRX is `transformers_modules.modeling_dbrx.DbrxExpertGLU`, which loads dynamically.
|
|
"""
|
|
if type(model).__name__ in ["DbrxForCausalLM"]:
|
|
moe_type = type(model.transformer.blocks[0].ffn.experts.mlp)
|
|
# Create a QuantDbrxExpertGLU class on the fly
|
|
if QuantModuleRegistry.get(moe_type) is None:
|
|
QuantModuleRegistry.register({moe_type: moe_type.__name__})(_QuantDbrxExpertGLU)
|
|
|
|
|
|
def register_falcon_linears_on_the_fly(model):
|
|
"""Register Falcon linear modules as a QUANT_MODULE.
|
|
|
|
Certain falcon models (for example, falcon 40b) use remote code, which are loaded dynamically, to build their model.
|
|
Therefore, we need to register the linear on the fly before quantization.
|
|
"""
|
|
if type(model).__name__ in ["RWForCausalLM", "FalconForCausalLM"]:
|
|
linear_type = type(model.transformer.h[0].self_attention.dense)
|
|
# Create a QuantFalconLinear class on the fly
|
|
if QuantModuleRegistry.get(linear_type) is None:
|
|
QuantModuleRegistry.register({linear_type: linear_type.__name__})(_QuantLinear)
|
|
|
|
|
|
def register_hf_attentions_on_the_fly(model):
|
|
"""Find HF Attention modules in the model and register them for KV Cache quantization.
|
|
|
|
This function attempts to find child modules ending with "Attention" in the name.
|
|
If such child modules are not found, or the corresponding class does not contain
|
|
identifiable attention patterns, the function will not register any new modules.
|
|
"""
|
|
attention_cls = {}
|
|
for name, module in model.named_modules():
|
|
# Only register attention classes that are from Huggingface transformers
|
|
if type(module).__name__.endswith("Attention"):
|
|
attention_type = type(module)
|
|
# Add modules to be registered only if they arent already registered
|
|
if QuantModuleRegistry.get(attention_type) is None:
|
|
attention_cls[attention_type] = type(module).__name__
|
|
|
|
# Check if the attention class is already registered
|
|
for cls in list(attention_cls.keys()):
|
|
if QuantModuleRegistry.get(cls) is not None:
|
|
print(f"Attention class {cls} already registered")
|
|
del attention_cls[cls]
|
|
|
|
# Check if there is any attention class to register
|
|
if not attention_cls:
|
|
print("No module ending with Attention found")
|
|
return
|
|
|
|
if not _is_supported_hf_model(model):
|
|
return
|
|
|
|
# Register the attention class for KV Cache quantization
|
|
success = any([register_hf_attention_for_kv_quant(cls) for cls in attention_cls])
|
|
if not success:
|
|
success = any([register_attention_for_kv_quant(cls) for cls in attention_cls])
|
|
if not success:
|
|
warnings.warn(
|
|
f"Could not create a quantized attention class for {attention_cls} from this model. "
|
|
"To enable KV Cache quantization, please create a custom quantized attention class for this model and "
|
|
"register it to ModelOpt using `mtq.register` "
|
|
"(see https://nvidia.github.io/TensorRT-Model-Optimizer/guides/_pytorch_quantization.html#custom-quantized-module-and-quantizer-placement)"
|
|
)
|
|
|
|
|
|
def _is_supported_hf_model(model):
|
|
"""Check if the model a valid model for transformers quantization specific support."""
|
|
return isinstance(model, transformers.PreTrainedModel)
|
|
|
|
|
|
@contextmanager
|
|
def setup_model_for_gradient_checkpointing(model: nn.Module):
|
|
use_cache = None
|
|
if hasattr(model, "config") and hasattr(model.config, "use_cache"):
|
|
# Disable use_cache explicitly before forward is called
|
|
use_cache = model.config.use_cache
|
|
model.config.use_cache = False
|
|
|
|
if not hasattr(model, "gradient_checkpointing_enable") or not (
|
|
hasattr(model, "supports_gradient_checkpointing") and model.supports_gradient_checkpointing
|
|
):
|
|
warnings.warn(
|
|
"AutoQuantize: Huggingface model without gradient checkpointing support detected. "
|
|
"AutoQuantize will consume more memory."
|
|
)
|
|
else:
|
|
try:
|
|
warnings.warn(
|
|
"AutoQuantize: Huggingface model detected - Enabling gradient checkpointing. "
|
|
"Disable gradient checkpointing after AutoQuantize if this is not desired!"
|
|
)
|
|
model.gradient_checkpointing_enable({"use_reentrant": True})
|
|
model.train() # Model needs to be in training mode to enable gradient checkpointing
|
|
# Set all dropout layers to eval mode for deterministic auto-quantize scores
|
|
for name, module in model.named_modules():
|
|
if isinstance(model, torch.nn.Dropout):
|
|
module.eval()
|
|
except Exception as e:
|
|
warnings.warn(
|
|
f"AutoQuantize: Error enabling gradient checkpointing for huggingface model due to: {e}, "
|
|
"AutoQuantize will consume more memory."
|
|
)
|
|
yield
|
|
if use_cache is not None:
|
|
model.config.use_cache = use_cache
|
|
|
|
|
|
AutoQuantizeSearcher.register_gradient_checkpointing_enable_context(
|
|
_is_supported_hf_model, setup_model_for_gradient_checkpointing
|
|
)
|