@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / d0502b4928
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 11, 11:15 UTC

[MoE][Refactor 1/N] Separate Online Quantization (#30627)

Signed-off-by: Robert Shaw <robshaw@redhat.com> Co-authored-by: Robert Shaw <robshaw@redhat.com>

Robert Shaw committed Dec 15, 2025 at 09:54 UTC d0502b4928fb683491952c6cd4f31b3d63e6d25c
1 file changed +154 -89
vllm/model_executor/layers/quantization/fp8.py
+154 -89
@@ -332,7 +332,10 @@ class Fp8Config(QuantizationConfig):
332 fused_mapping=self.packed_modules_mapping,
333 ):
334 return UnquantizedFusedMoEMethod(layer.moe_config)
335 - moe_quant_method = Fp8MoEMethod(self, layer)
335 + if self.is_checkpoint_fp8_serialized:
336 + moe_quant_method = Fp8MoEMethod(self, layer)
337 + else:
338 + moe_quant_method = Fp8OnlineMoEMethod(self, layer)
339 moe_quant_method.marlin_input_dtype = get_marlin_input_dtype(prefix)
340 return moe_quant_method
341 elif isinstance(layer, Attention):
@@ -745,8 +748,9 @@ class Fp8MoEMethod(FusedMoEMethodBase):
748 layer.orig_dtype = params_dtype
749 layer.weight_block_size = None
750
748 - if self.quant_config.is_checkpoint_fp8_serialized:
749 - params_dtype = torch.float8_e4m3fn
751 + assert self.quant_config.is_checkpoint_fp8_serialized
752 + params_dtype = torch.float8_e4m3fn
753 +
754 if self.block_quant:
755 assert self.weight_block_size is not None
756 layer.weight_block_size = self.weight_block_size
@@ -773,41 +777,6 @@ class Fp8MoEMethod(FusedMoEMethodBase):
777 f"weight quantization block_k = {block_k}."
778 )
779
776 - # if we are doing online quantization, patch the weight
777 - # loaded to call `process_weights_after_loading` in a streaming fashion
778 - # as soon as the last weight chunk is loaded
779 - if not self.quant_config.is_checkpoint_fp8_serialized:
780 - weight_loader = extra_weight_attrs["weight_loader"]
781 - # create a new holder to prevent modifying behavior of any other
782 - # objects which might depend on the old one
783 - new_extra_weight_attrs = extra_weight_attrs
784 -
785 - def patched_weight_loader(param, loaded_weight, *args, **kwargs):
786 - # load the current weight chunk
787 - res = weight_loader(param, loaded_weight, *args, **kwargs) # type: ignore[misc]
788 -
789 - # add a counter to track how many elements we have updated
790 - if not hasattr(layer, "_loaded_numel"):
791 - layer._loaded_numel = 0
792 - layer._loaded_numel += loaded_weight.numel()
793 -
794 - # if we have loaded all of the elements, call
795 - # process_weights_after_loading
796 - target_loaded_numel = layer.w13_weight.numel() + layer.w2_weight.numel()
797 - if layer._loaded_numel == target_loaded_numel:
798 - self.process_weights_after_loading(layer)
799 -
800 - # Delete the bookkeeping
801 - del layer._loaded_numel
802 - # Prevent the usual `process_weights_after_loading` call
803 - # from doing anything
804 - layer._already_called_process_weights_after_loading = True
805 -
806 - return res
807 -
808 - new_extra_weight_attrs["weight_loader"] = patched_weight_loader
809 - extra_weight_attrs = new_extra_weight_attrs
810 -
780 # WEIGHTS
781 w13_weight = torch.nn.Parameter(
782 torch.empty(
@@ -875,21 +844,11 @@ class Fp8MoEMethod(FusedMoEMethodBase):
844 if self.block_quant
845 else {"quant_method": FusedMoeWeightScaleSupported.TENSOR.value}
846 )
878 - # If loading fp8 checkpoint, pass the weight loaders.
879 - # If loading an fp16 checkpoint, do not (we will quantize in
880 - # process_weights_after_loading()
881 - if self.quant_config.is_checkpoint_fp8_serialized:
882 - set_weight_attrs(w13_weight_scale, extra_weight_attrs)
883 - set_weight_attrs(w2_weight_scale, extra_weight_attrs)
847 + set_weight_attrs(w13_weight_scale, extra_weight_attrs)
848 + set_weight_attrs(w2_weight_scale, extra_weight_attrs)
849
850 # INPUT_SCALES
851 if self.quant_config.activation_scheme == "static":
887 - if not self.quant_config.is_checkpoint_fp8_serialized:
888 - raise ValueError(
889 - "Found static activation scheme for checkpoint that "
890 - "was not serialized fp8."
891 - )
892 -
852 w13_input_scale = torch.nn.Parameter(
853 torch.ones(num_experts, dtype=torch.float32), requires_grad=False
854 )
@@ -986,45 +945,6 @@ class Fp8MoEMethod(FusedMoEMethodBase):
945 layer.w2_weight_scale_inv = Parameter(
946 dg_w2_weight_scale_inv, requires_grad=False
947 )
989 -
990 - # If checkpoint is fp16, quantize in place.
991 - elif not self.quant_config.is_checkpoint_fp8_serialized:
992 - fp8_dtype = current_platform.fp8_dtype()
993 - w13_weight = torch.empty_like(layer.w13_weight.data, dtype=fp8_dtype)
994 - w2_weight = torch.empty_like(layer.w2_weight.data, dtype=fp8_dtype)
995 -
996 - # Re-initialize w13_scale because we directly quantize
997 - # merged w13 weights and generate a single scaling factor.
998 - replace_parameter(
999 - layer,
1000 - "w13_weight_scale",
1001 - torch.ones(
1002 - layer.local_num_experts,
1003 - dtype=torch.float32,
1004 - device=w13_weight.device,
1005 - ),
1006 - )
1007 - for expert in range(layer.local_num_experts):
1008 - w13_weight[expert, :, :], layer.w13_weight_scale[expert] = (
1009 - ops.scaled_fp8_quant(layer.w13_weight.data[expert, :, :])
1010 - )
1011 - w2_weight[expert, :, :], layer.w2_weight_scale[expert] = (
1012 - ops.scaled_fp8_quant(layer.w2_weight.data[expert, :, :])
1013 - )
1014 - replace_parameter(layer, "w13_weight", w13_weight)
1015 - replace_parameter(layer, "w2_weight", w2_weight)
1016 -
1017 - if self.rocm_aiter_moe_enabled:
1018 - # reshaping weights is required for aiter moe kernel.
1019 - shuffled_w13, shuffled_w2 = rocm_aiter_ops.shuffle_weights(
1020 - layer.w13_weight, layer.w2_weight
1021 - )
1022 -
1023 - replace_parameter(layer, "w13_weight", shuffled_w13)
1024 - replace_parameter(layer, "w2_weight", shuffled_w2)
1025 - # If checkpoint is fp8, we need to handle that the
1026 - # MoE kernels require single activation scale and single weight
1027 - # scale for w13 per expert.
948 else:
949 # Fp8 moe kernels require a single activation scale.
950 # We take the max of all the scales in case they differ.
@@ -1387,6 +1307,151 @@ class Fp8MoEMethod(FusedMoEMethodBase):
1307 return result
1308
1309
1310 +class Fp8OnlineMoEMethod(Fp8MoEMethod):
1311 + """MoE method for online FP8 quantization.
1312 + Supports loading quantized FP16/BF16 model checkpoints with dynamic
1313 + activation scaling. The weight scaling factor will be initialized after
1314 + the model weights are loaded.
1315 +
1316 + Args:
1317 + quant_config: The quantization config.
1318 + """
1319 +
1320 + def __init__(self, quant_config: Fp8Config, layer: torch.nn.Module):
1321 + super().__init__(quant_config, layer)
1322 + assert not quant_config.is_checkpoint_fp8_serialized
1323 + assert quant_config.activation_scheme == "dynamic"
1324 + assert quant_config.weight_block_size is None
1325 + assert self.flashinfer_moe_backend is None
1326 +
1327 + def create_weights(
1328 + self,
1329 + layer: Module,
1330 + num_experts: int,
1331 + hidden_size: int,
1332 + intermediate_size_per_partition: int,
1333 + params_dtype: torch.dtype,
1334 + **extra_weight_attrs,
1335 + ):
1336 + layer.intermediate_size_per_partition = intermediate_size_per_partition
1337 + layer.hidden_size = hidden_size
1338 + layer.num_experts = num_experts
1339 + layer.orig_dtype = params_dtype
1340 + layer.weight_block_size = None
1341 +
1342 + # We are doing online quantization, patch the weight loaded
1343 + # to call `process_weights_after_loading` in a streaming fashion
1344 + # as soon as the last weight chunk is loaded.
1345 + weight_loader = extra_weight_attrs["weight_loader"]
1346 + # create a new holder to prevent modifying behavior of any other
1347 + # objects which might depend on the old one
1348 + new_extra_weight_attrs = extra_weight_attrs
1349 +
1350 + def patched_weight_loader(param, loaded_weight, *args, **kwargs):
1351 + # load the current weight chunk
1352 + res = weight_loader(param, loaded_weight, *args, **kwargs) # type: ignore[misc]
1353 +
1354 + # add a counter to track how many elements we have updated
1355 + if not hasattr(layer, "_loaded_numel"):
1356 + layer._loaded_numel = 0
1357 + layer._loaded_numel += loaded_weight.numel()
1358 +
1359 + # if we have loaded all of the elements, call
1360 + # process_weights_after_loading
1361 + target_loaded_numel = layer.w13_weight.numel() + layer.w2_weight.numel()
1362 + if layer._loaded_numel == target_loaded_numel:
1363 + self.process_weights_after_loading(layer)
1364 +
1365 + # Delete the bookkeeping
1366 + del layer._loaded_numel
1367 + # Prevent the usual `process_weights_after_loading` call
1368 + # from doing anything
1369 + layer._already_called_process_weights_after_loading = True
1370 +
1371 + return res
1372 +
1373 + new_extra_weight_attrs["weight_loader"] = patched_weight_loader
1374 + extra_weight_attrs = new_extra_weight_attrs
1375 +
1376 + # WEIGHTS
1377 + w13_weight = torch.nn.Parameter(
1378 + torch.empty(
1379 + num_experts,
1380 + 2 * intermediate_size_per_partition,
1381 + hidden_size,
1382 + dtype=params_dtype,
1383 + ),
1384 + requires_grad=False,
1385 + )
1386 + layer.register_parameter("w13_weight", w13_weight)
1387 + set_weight_attrs(w13_weight, extra_weight_attrs)
1388 +
1389 + w2_weight = torch.nn.Parameter(
1390 + torch.empty(
1391 + num_experts,
1392 + hidden_size,
1393 + intermediate_size_per_partition,
1394 + dtype=params_dtype,
1395 + ),
1396 + requires_grad=False,
1397 + )
1398 + layer.register_parameter("w2_weight", w2_weight)
1399 + set_weight_attrs(w2_weight, extra_weight_attrs)
1400 +
1401 + # WEIGHT_SCALES
1402 + # Allocate 2 scales for w1 and w3 respectively.
1403 + # They will be combined to a single scale after weight loading.
1404 + w13_weight_scale = torch.nn.Parameter(
1405 + torch.ones(num_experts, dtype=torch.float32), requires_grad=False
1406 + )
1407 + w2_weight_scale = torch.nn.Parameter(
1408 + torch.ones(num_experts, dtype=torch.float32), requires_grad=False
1409 + )
1410 + layer.register_parameter("w13_weight_scale", w13_weight_scale)
1411 + layer.register_parameter("w2_weight_scale", w2_weight_scale)
1412 +
1413 + layer.w13_input_scale = None
1414 + layer.w2_input_scale = None
1415 +
1416 + self.rocm_aiter_moe_enabled = False
1417 +
1418 + def process_weights_after_loading(self, layer: Module) -> None:
1419 + if getattr(layer, "_already_called_process_weights_after_loading", False):
1420 + return
1421 +
1422 + # Lazy import to avoid importing triton too early.
1423 + self.rocm_aiter_moe_enabled = rocm_aiter_ops.is_fused_moe_enabled()
1424 +
1425 + # If checkpoint is fp16, quantize in place.
1426 + fp8_dtype = current_platform.fp8_dtype()
1427 + w13_weight = torch.empty_like(layer.w13_weight.data, dtype=fp8_dtype)
1428 + w2_weight = torch.empty_like(layer.w2_weight.data, dtype=fp8_dtype)
1429 +
1430 + for expert in range(layer.local_num_experts):
1431 + w13_weight[expert, :, :], layer.w13_weight_scale[expert] = (
1432 + ops.scaled_fp8_quant(layer.w13_weight.data[expert, :, :])
1433 + )
1434 + w2_weight[expert, :, :], layer.w2_weight_scale[expert] = (
1435 + ops.scaled_fp8_quant(layer.w2_weight.data[expert, :, :])
1436 + )
1437 + replace_parameter(layer, "w13_weight", w13_weight)
1438 + replace_parameter(layer, "w2_weight", w2_weight)
1439 +
1440 + # Reshuffle weights for AITER if needed.
1441 + if self.rocm_aiter_moe_enabled:
1442 + shuffled_w13, shuffled_w2 = rocm_aiter_ops.shuffle_weights(
1443 + layer.w13_weight, layer.w2_weight
1444 + )
1445 + replace_parameter(layer, "w13_weight", shuffled_w13)
1446 + replace_parameter(layer, "w2_weight", shuffled_w2)
1447 +
1448 + # Rushuffle weights for MARLIN if needed.
1449 + if self.use_marlin:
1450 + prepare_moe_fp8_layer_for_marlin(
1451 + layer, False, input_dtype=self.marlin_input_dtype
1452 + )
1453 +
1454 +
1455 class Fp8KVCacheMethod(BaseKVCacheMethod):
1456 """
1457 Supports loading kv-cache scaling factors from FP8 checkpoints.