2525import torch .nn as nn
2626
2727from modelopt import __version__
28- from modelopt .torch .quantization .model_calib import enable_stats_collection , finish_stats_collection
28+ from modelopt .torch .quantization .model_calib import (
29+ enable_stats_collection ,
30+ finish_stats_collection ,
31+ svd ,
32+ )
2933from modelopt .torch .quantization .nn .modules .quant_linear import RealQuantLinear
3034from modelopt .torch .quantization .qtensor import (
3135 FP8QTensor ,
5761 QUANTIZATION_NONE ,
5862 QUANTIZATION_NVFP4 ,
5963 QUANTIZATION_NVFP4_AWQ ,
64+ QUANTIZATION_NVFP4_SVDQUANT ,
6065 QUANTIZATION_W4A8_AWQ ,
6166 QUANTIZATION_W4A8_MXFP4_FP8 ,
6267 QUANTIZATION_W4A8_NVFP4_FP8 ,
@@ -165,7 +170,7 @@ def resmooth_and_get_scale(
165170 )
166171 new_weights .append (weight )
167172 # If NVFP4_AWQ then we view the scales as uint8 to allow for cat later
168- if quantization == QUANTIZATION_NVFP4_AWQ :
173+ if quantization in [ QUANTIZATION_NVFP4_AWQ , QUANTIZATION_NVFP4_SVDQUANT ] :
169174 scale , _ = NVFP4QTensor .get_weights_scaling_factor (weight , group_size ).view (torch .uint8 )
170175 else :
171176 scale = get_scaling_factor_from_weight (weight , group_size )
@@ -176,7 +181,7 @@ def resmooth_and_get_scale(
176181 return (
177182 torch .cat (new_weights , dim = 0 ),
178183 resmoothed_scales .view (torch .float8_e4m3fn )
179- if quantization == QUANTIZATION_NVFP4_AWQ
184+ if quantization in [ QUANTIZATION_NVFP4_AWQ , QUANTIZATION_NVFP4_SVDQUANT ]
180185 else resmoothed_scales , # if NVFP4_AWQ we view the scales back as float8_e4m3fn after cat
181186 new_pre_quant_scale ,
182187 )
@@ -243,6 +248,7 @@ def get_activation_scaling_factor(
243248 if get_quantization_format (module ) in [
244249 QUANTIZATION_NVFP4 ,
245250 QUANTIZATION_NVFP4_AWQ ,
251+ QUANTIZATION_NVFP4_SVDQUANT ,
246252 ]:
247253 return NVFP4QTensor .get_activation_scaling_factor (input_quantizer )
248254 return get_scaling_factor (input_quantizer )
@@ -270,6 +276,7 @@ def get_weight_scaling_factor(module: nn.Module, weight_name: str = "weight") ->
270276 if quantization_format in [
271277 QUANTIZATION_NVFP4 ,
272278 QUANTIZATION_NVFP4_AWQ ,
279+ QUANTIZATION_NVFP4_SVDQUANT ,
273280 QUANTIZATION_W4A8_NVFP4_FP8 ,
274281 ]:
275282 if quantization_format == QUANTIZATION_W4A8_NVFP4_FP8 :
@@ -303,6 +310,7 @@ def get_weight_scaling_factor_2(module: nn.Module, weight_name: str = "weight")
303310 if get_quantization_format (module ) in [
304311 QUANTIZATION_NVFP4 ,
305312 QUANTIZATION_NVFP4_AWQ ,
313+ QUANTIZATION_NVFP4_SVDQUANT ,
306314 ]:
307315 return NVFP4QTensor .get_weights_scaling_factor_2_from_quantizer (weight_quantizer )
308316 elif get_quantization_format (module ) == QUANTIZATION_W4A8_NVFP4_FP8 :
@@ -487,6 +495,8 @@ def _get_quantization_from_layer(layer, quantizer_attr_names: QuantizerAttrNames
487495 block_sizes = getattr (weight_quantizer , "block_sizes" )
488496 scale_bits = block_sizes .get ("scale_bits" )
489497
498+ if input_quantizer is not None and hasattr (weight_quantizer , "svdquant_lora_a" ):
499+ return QUANTIZATION_NVFP4_SVDQUANT
490500 if input_quantizer is not None and hasattr (input_quantizer , "_pre_quant_scale" ):
491501 return QUANTIZATION_NVFP4_AWQ
492502 if getattr (layer , "fused_with_prequant" , False ):
@@ -660,15 +670,18 @@ def process_layer_quant_config(layer_config_dict):
660670 elif v == "w4a8_nvfp4_fp8" :
661671 layer_config = {
662672 "quant_algo" : "W4A8_NVFP4_FP8" ,
663- "group_size" : layer_config_dict [prefix + ".awq_block_size" ],
664- "has_zero_point" : False ,
665- "pre_quant_scale" : True ,
673+ "group_size" : block_size_value ,
666674 }
667675 elif v == "w4a8_mxfp4_fp8" :
668676 layer_config = {
669677 "quant_algo" : "W4A8_MXFP4_FP8" ,
670678 "group_size" : block_size_value ,
671679 }
680+ elif v == "nvfp4_svdquant" :
681+ layer_config = {
682+ "quant_algo" : "NVFP4_SVD" ,
683+ "group_size" : block_size_value ,
684+ }
672685 else :
673686 layer_config = {"quant_algo" : v }
674687
@@ -813,7 +826,12 @@ def to_quantized_weight(
813826 if quantization in [QUANTIZATION_INT4_AWQ , QUANTIZATION_W4A8_AWQ ]:
814827 return pack_int4_in_uint8 (weight , weights_scaling_factor )
815828
816- if quantization in [QUANTIZATION_NVFP4 , QUANTIZATION_NVFP4_AWQ , QUANTIZATION_W4A8_NVFP4_FP8 ]:
829+ if quantization in [
830+ QUANTIZATION_NVFP4 ,
831+ QUANTIZATION_NVFP4_AWQ ,
832+ QUANTIZATION_W4A8_NVFP4_FP8 ,
833+ QUANTIZATION_NVFP4_SVDQUANT ,
834+ ]:
817835 assert block_size is not None , "Block size not passed. Unable to quantize to NVFP4 format."
818836 assert weights_scaling_factor2 is not None , (
819837 "Weights scaling factor 2 not passed. Unable to quantize to NVFP4 format"
@@ -1014,6 +1032,40 @@ def _update_pre_quant_scale(module, new_pre_quant_scale):
10141032 finish_stats_collection (module .weight_quantizer )
10151033
10161034
1035+ def _update_svdquant (modules , new_pre_quant_scale ):
1036+ """Updates the pre_quant_scale, svdquant_lora_a and svdquant_lora_b matrices when pre_quant_scale is changed."""
1037+ new_pre_quant_scale = new_pre_quant_scale .to (torch .float32 )
1038+ lora_a = [m .weight_quantizer .svdquant_lora_a .to (torch .float32 ) for m in modules ]
1039+ lora_b = [m .weight_quantizer .svdquant_lora_b .to (torch .float32 ) for m in modules ]
1040+ weight = [m .weight .to (torch .float32 ) for m in modules ]
1041+ old_pre_quant_scale = [m .input_quantizer ._pre_quant_scale .to (torch .float32 ) for m in modules ]
1042+ weight = [
1043+ (w + (lb @ la )) * (s / new_pre_quant_scale )
1044+ for w , la , lb , s in zip (weight , lora_a , lora_b , old_pre_quant_scale )
1045+ ]
1046+ weight_concatenated = torch .cat (weight , dim = 0 )
1047+ lb , la = svd (weight_concatenated , rank = lora_a [0 ].shape [0 ])
1048+ weight_concatenated -= lb @ la
1049+ weight_concatenated = weight_concatenated .to (modules [0 ].weight .dtype )
1050+ la = la .to (modules [0 ].weight_quantizer .svdquant_lora_a .dtype )
1051+ lb = lb .to (modules [0 ].weight_quantizer .svdquant_lora_b .dtype )
1052+ new_pre_quant_scale = new_pre_quant_scale .to (modules [0 ].input_quantizer .pre_quant_scale .dtype )
1053+
1054+ index = 0
1055+ for i , module in enumerate (modules ):
1056+ module .input_quantizer .pre_quant_scale = new_pre_quant_scale
1057+ module .weight_quantizer .svdquant_lora_a = la
1058+ assert lora_b [i ].shape [0 ] == module .weight .shape [0 ]
1059+ module .weight_quantizer .svdquant_lora_b = lb [index : index + lora_b [i ].shape [0 ], :]
1060+ module .weight = nn .Parameter (weight_concatenated [index : index + lora_b [i ].shape [0 ], :])
1061+ index += lora_b [i ].shape [0 ]
1062+ # Redo weights collection
1063+ module .weight_quantizer .reset_amax ()
1064+ enable_stats_collection (module .weight_quantizer )
1065+ module .weight_quantizer (module .weight )
1066+ finish_stats_collection (module .weight_quantizer )
1067+
1068+
10171069# Format: (list of target modules, tuple of (linear_to_fuse_into, linear_from_with_scale))
10181070PQS_FUSE_MODULE_MAPPING = [
10191071 # Attention: Fuse o_proj's pre_quant_scale into v_proj's output dimension
@@ -1166,9 +1218,14 @@ def preprocess_linear_fusion(modules: list[torch.nn.Module], resmooth_only=False
11661218 dim = 0 ,
11671219 )
11681220
1169- for module in modules :
1170- if not torch .equal (module .input_quantizer .pre_quant_scale , avg_prequant_scale ):
1171- _update_pre_quant_scale (module , avg_prequant_scale )
1221+ if all (
1222+ getattr (m .weight_quantizer , "svdquant_lora_a" , None ) is not None for m in modules
1223+ ):
1224+ _update_svdquant (modules , avg_prequant_scale )
1225+ else :
1226+ for module in modules :
1227+ if not torch .equal (module .input_quantizer .pre_quant_scale , avg_prequant_scale ):
1228+ _update_pre_quant_scale (module , avg_prequant_scale )
11721229
11731230 if resmooth_only :
11741231 return
0 commit comments