Files
Model-Optimizer/modelopt/torch/quantization/plugins/vllm.py
T
2025-07-31 22:48:09 +05:30

200 lines
8.2 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 VLLM layers."""
import importlib
import torch
import vllm.model_executor.layers.fused_moe.layer as vllm_fused_moe_layer
import vllm.model_executor.layers.linear as vllm_linear
from ...utils.distributed import ParallelState
from ..nn import QuantLinearConvBase, QuantModule, QuantModuleRegistry, TensorQuantizer
vllm_fused_moe_package = importlib.import_module("vllm.model_executor.layers.fused_moe.fused_moe")
class FakeQuantMethod:
"""A class that implements fake quantization methods for vLLM models.
This class provides functionality to apply quantization methods to model layers
in a way that's compatible with vLLM's architecture.
"""
def __init__(self, quant_method):
"""Initialize the FakeQuantMethod.
Args:
quant_method: The quantization method to be applied to the model layers.
"""
self.quant_method = quant_method
def apply(
self,
layer: torch.nn.Module,
x: torch.Tensor,
bias: torch.Tensor | None = None,
) -> torch.Tensor:
"""Apply the quantization method to a given layer.
Args:
layer (torch.nn.Module): The neural network layer to be quantized.
x (torch.Tensor): The input tensor to the layer.
bias (torch.Tensor | None, optional): The bias tensor to the layer. Defaults to None.
Returns:
torch.Tensor: The quantized output tensor.
"""
x = layer.input_quantizer(x)
if layer.weight_quantizer.is_enabled:
original_weight = layer.weight
layer.weight = layer.weight_quantizer(layer.weight)
output = self.quant_method.apply(layer, x, bias)
layer.weight = original_weight
else:
output = self.quant_method.apply(layer, x, bias)
output = layer.output_quantizer(output)
return output
class _VLLMParallelLinear(QuantModule):
def _setup(self):
self.input_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_input)
self.weight_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_weight)
self.output_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_output)
self.output_quantizer.disable()
assert type(self.quant_method) is vllm_linear.UnquantizedLinearMethod, (
f"quant_method is {type(self.quant_method)}"
)
self.fake_quant_method = FakeQuantMethod(self.quant_method)
self.parallel_state = ParallelState(-1, -1)
def forward(self, input_):
# This context manager will conflict with torch.compile
# with replace_function(self, "quant_method", self.fake_quant_method):
# Manually replace quant_method instead
self._quant_method = self.quant_method
self.quant_method = self.fake_quant_method
output = super().forward(input_)
self.quant_method = self._quant_method
return output
@QuantModuleRegistry.register({vllm_linear.RowParallelLinear: "vllm_RowParallelLinear"})
class _QuantVLLMRowParallelLinear(_VLLMParallelLinear):
pass
@QuantModuleRegistry.register({vllm_linear.ColumnParallelLinear: "vllm_ColumnParallelLinear"})
class _QuantVLLMColumnParallelLinear(_VLLMParallelLinear):
pass
@QuantModuleRegistry.register(
{vllm_linear.MergedColumnParallelLinear: "vllm_MergedColumnParallelLinear"}
)
class _QuantVLLMMergedColumnParallelLinear(_VLLMParallelLinear):
pass
@QuantModuleRegistry.register({vllm_linear.QKVParallelLinear: "vllm_QKVParallelLinear"})
class _QuantVLLMQKVParallelLinear(_VLLMParallelLinear):
pass
# ReplicatedLinear is for MoE router and should not be quantized
# FusedMoE layer requires handling for UnquantizedFusedMoEMethod
@QuantModuleRegistry.register({vllm_fused_moe_layer.FusedMoE: "vllm_FusedMoE"})
class _QuantVLLMFusedMoE(QuantModule):
def _setup(self):
self.w13_input_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_input)
self.w2_input_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_input)
self.w13_weight_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_weight)
self.w2_weight_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_weight)
self.w13_output_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_output)
self.w2_output_quantizer = TensorQuantizer(QuantLinearConvBase.default_quant_desc_output)
self.w13_output_quantizer.disable()
self.w2_output_quantizer.disable()
assert type(self.quant_method) is vllm_fused_moe_layer.UnquantizedFusedMoEMethod, (
f"quant_method is {type(self.quant_method)}"
)
self.parallel_state = ParallelState(-1, -1)
def invoke_fused_moe_quantized(
self,
A: torch.Tensor, # noqa: N803
B: torch.Tensor, # noqa: N803
C: torch.Tensor, # noqa: N803
*args,
**kwargs,
):
if B is self.w13_weight:
# First layer of expert
A = self.w13_input_quantizer(A) # noqa: N806
if self.w13_weight_quantizer.is_enabled:
original_weight = self.w13_weight
self.w13_weight = self.w13_weight_quantizer(self.w13_weight)
vllm_fused_moe_package._invoke_fused_moe_kernel(A, B, C, *args, **kwargs)
self.w13_weight = original_weight
else:
vllm_fused_moe_package._invoke_fused_moe_kernel(A, B, C, *args, **kwargs)
if self.w13_output_quantizer.is_enabled:
C[:] = self.w13_output_quantizer(C)
elif B is self.w2_weight:
A = self.w2_input_quantizer(A) # noqa: N806
if self.w2_weight_quantizer.is_enabled:
original_weight = self.w2_weight
self.w2_weight = self.w2_weight_quantizer(self.w2_weight)
vllm_fused_moe_package._invoke_fused_moe_kernel(A, B, C, *args, **kwargs)
self.w2_weight = original_weight
else:
vllm_fused_moe_package._invoke_fused_moe_kernel(A, B, C, *args, **kwargs)
if self.w2_output_quantizer.is_enabled:
C[:] = self.w2_output_quantizer(C)
else:
raise ValueError("Cannot determine first or second layer of expert")
def forward(self, hidden_states: torch.Tensor, router_logits: torch.Tensor):
# This is again due to the bad coding of vLLM
# fused_moe submodule is overwritten by the fused_moe function
# so we need to import the fused_moe module explicitly
assert vllm_fused_moe_package.invoke_fused_moe_kernel is not None
# This context manager will conflict with torch.compile
# with replace_function(
# vllm_fused_moe_package,
# "invoke_fused_moe_kernel",
# self.invoke_fused_moe_quantized,
# ):
self._invoke_fused_moe_quantized = self.invoke_fused_moe_quantized
self.invoke_fused_moe_quantized = self.invoke_fused_moe_quantized
output = super().forward(hidden_states, router_logits)
self.invoke_fused_moe_quantized = self._invoke_fused_moe_quantized
return output
@property
def mopt_ckpt_versn(self):
"""Checkpoint version of the modelopt."""
return None
@mopt_ckpt_versn.setter
def mopt_ckpt_versn(self, version: str):
"""Set the checkpoint version for the TensorQuantizer states."""
# vLLM defined an apply method that overwrites nn.Module.apply
# To avoid conflicting, disable the apply call here
# self.apply(_set_ckpt_version)