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 3, 16:30 UTC
[Quantization][Autoround][Toolkit] Add W4A16 Support (#39778)
Signed-off-by: Zhenzhong1 <zhenzhong.xu@intel.com> Signed-off-by: Zhenzhong Xu <zhenzhong.xu@intel.com>
Zhenzhong Xu committed
May 14, 2026 at 19:18 UTC
1ea94013644558f83e134f0c6f53d6b261f7c824
2 files changed
+209
-31
requirements/xpu.txt
+1
@@ -15,4 +15,5 @@ torch==2.11.0+xpu
15
torchaudio
16
torchvision
17
18
+auto_round_lib>=0.13.0
19
vllm_xpu_kernels @ https://github.com/vllm-project/vllm-xpu-kernels/releases/download/v0.1.8/vllm_xpu_kernels-0.1.8-cp38-abi3-manylinux_2_28_x86_64.whl
vllm/model_executor/layers/quantization/inc.py
+208
-31
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
from fractions import Fraction
5
+from functools import lru_cache
6
from typing import TYPE_CHECKING, Any
7
8
import regex as re
@@ -429,7 +430,23 @@ class INCConfig(QuantizationConfig):
430
raise NotImplementedError(
431
"INC W4A16 on XPU only supports symmetric quantization for now."
432
)
433
+
434
if isinstance(layer, (LinearBase, ParallelLMHead)):
435
+ is_ark_available, ark_error, _, _ = get_ark_state()
436
+ if is_ark_available:
437
+ return INCARKLinearMethod(
438
+ weight_bits=weight_bits,
439
+ group_size=group_size,
440
+ sym=sym,
441
+ )
442
+
443
+ logger.debug(
444
+ "ARK backend is unavailable for layer %s; "
445
+ "falling back to the default XPU INC path. Error: %s",
446
+ prefix,
447
+ ark_error or "unknown error",
448
+ )
449
+
450
return INCXPULinearMethod(
451
weight_bits=weight_bits,
452
group_size=group_size,
@@ -455,6 +472,21 @@ class INCConfig(QuantizationConfig):
472
"INC W4A16 on CPU only supports symmetric quantization for now."
473
)
474
if isinstance(layer, (LinearBase, ParallelLMHead)):
475
+ is_ark_available, ark_error, _, _ = get_ark_state()
476
+ if is_ark_available:
477
+ return INCARKLinearMethod(
478
+ weight_bits=weight_bits,
479
+ group_size=group_size,
480
+ sym=sym,
481
+ )
482
+
483
+ logger.debug(
484
+ "ARK backend is unavailable for layer %s; "
485
+ "falling back to the default CPU INC path. Error: %s",
486
+ prefix,
487
+ ark_error or "unknown error",
488
+ )
489
+
490
return self.apply_gptq_quant_layer(layer, prefix)
491
return None
492
@@ -465,6 +497,7 @@ class INCConfig(QuantizationConfig):
497
layer_name == prefix or layer_name == f"model.{prefix}"
498
) and self.extra_config[layer_name].get("bits", 16) >= 16:
499
return UnquantizedLinearMethod()
500
+
501
if current_platform.is_xpu():
502
return self.apply_xpu_w4a16_quant_layer(layer, prefix)
503
is_gptq = "gptq" in self.packing_format or "gptq" in self.backend
@@ -493,54 +526,39 @@ class INCConfig(QuantizationConfig):
526
return None
527
528
496
-class INCXPULinearMethod(LinearMethodBase):
497
- """XPU linear method for INC w4a16 GPTQ quantization (symmetric only).
498
-
499
- Repacks GPTQ weights from [in_packed, out] to oneDNN [out, in_packed]
500
- layout and calls torch.ops._xpu_C.int4_gemm_w4a16.
501
-
502
- GPTQ format: qweight [in_packed, out] with sequential nibble order.
503
-
504
- Note: Asymmetric quantization (sym=false) is not for now.
505
-
506
- FIXME(yiliu30): Refine the implementation to reuse XPUwNa16LinearKernel.
507
- """
508
-
529
+class INCXPULinearBase(LinearMethodBase):
530
def __init__(self, weight_bits: int, group_size: int, sym: bool):
531
self.weight_bits = weight_bits
532
self.group_size = group_size
533
self.sym = sym
534
self.pack_factor = 32 // weight_bits
535
515
- def create_weights(
536
+ def _create_inc_weights(
537
self,
538
layer: torch.nn.Module,
539
input_size_per_partition: int,
540
output_partition_sizes: list[int],
520
- input_size: int,
521
- output_size: int,
541
params_dtype: torch.dtype,
523
- **extra_weight_attrs,
524
- ):
525
- del output_size # Unused.
542
+ weight_loader: Any,
543
+ group_size: int,
544
+ pack_factor: int,
545
+ ) -> None:
546
output_size_per_partition = sum(output_partition_sizes)
527
- weight_loader = extra_weight_attrs.get("weight_loader")
528
- scales_and_zp_size = input_size_per_partition // self.group_size
547
+ scales_and_zp_size = input_size_per_partition // group_size
548
530
- # GPTQ: qweight [in // pack_factor, out] packed along input dim
549
qweight = PackedvLLMParameter(
550
data=torch.empty(
533
- input_size_per_partition // self.pack_factor,
551
+ input_size_per_partition // pack_factor,
552
output_size_per_partition,
553
dtype=torch.int32,
554
),
555
input_dim=0,
556
output_dim=1,
557
packed_dim=0,
540
- packed_factor=self.pack_factor,
558
+ packed_factor=pack_factor,
559
weight_loader=weight_loader,
560
)
543
- # scales: [num_groups, out] params_dtype
561
+
562
scales = GroupQuantScaleParameter(
563
data=torch.empty(
564
scales_and_zp_size,
@@ -551,17 +569,17 @@ class INCXPULinearMethod(LinearMethodBase):
569
output_dim=1,
570
weight_loader=weight_loader,
571
)
554
- # qzeros: [num_groups, out // pack_factor] int32
572
+
573
qzeros = PackedvLLMParameter(
574
data=torch.empty(
575
scales_and_zp_size,
558
- output_size_per_partition // self.pack_factor,
576
+ output_size_per_partition // pack_factor,
577
dtype=torch.int32,
578
),
579
input_dim=0,
580
output_dim=1,
581
packed_dim=1,
564
- packed_factor=self.pack_factor,
582
+ packed_factor=pack_factor,
583
weight_loader=weight_loader,
584
)
585
@@ -569,11 +587,9 @@ class INCXPULinearMethod(LinearMethodBase):
587
layer.register_parameter("scales", scales)
588
layer.register_parameter("qzeros", qzeros)
589
572
- # GPTQ checkpoints may include g_idx for activation reordering.
573
- # Register it so the weight loader doesn't error on unexpected keys.
590
g_idx = RowvLLMParameter(
591
data=torch.tensor(
576
- [i // self.group_size for i in range(input_size_per_partition)],
592
+ [i // group_size for i in range(input_size_per_partition)],
593
dtype=torch.int32,
594
),
595
input_dim=0,
@@ -581,6 +597,66 @@ class INCXPULinearMethod(LinearMethodBase):
597
)
598
layer.register_parameter("g_idx", g_idx)
599
600
+ def create_weights(
601
+ self,
602
+ layer: torch.nn.Module,
603
+ input_size_per_partition: int,
604
+ output_partition_sizes: list[int],
605
+ input_size: int,
606
+ output_size: int,
607
+ params_dtype: torch.dtype,
608
+ **extra_weight_attrs,
609
+ ):
610
+ self._create_inc_weights(
611
+ layer=layer,
612
+ input_size_per_partition=input_size_per_partition,
613
+ output_partition_sizes=output_partition_sizes,
614
+ params_dtype=params_dtype,
615
+ weight_loader=extra_weight_attrs.get("weight_loader"),
616
+ group_size=self.group_size,
617
+ pack_factor=self.pack_factor,
618
+ )
619
+
620
+
621
+@lru_cache(maxsize=1)
622
+def get_ark_state() -> tuple[bool, str | None, Any | None, Any | None]:
623
+ """Return ARK availability, error details, cached instance, and QuantLinear."""
624
+ try:
625
+ import auto_round_kernel
626
+ from auto_round_kernel.qlinear import QuantLinear
627
+
628
+ logger.info("Successfully imported auto_round_kernel.")
629
+ except ImportError as error:
630
+ return False, str(error), None, None
631
+
632
+ ark_loader = getattr(auto_round_kernel, "_ark_instance", None)
633
+ if not callable(ark_loader):
634
+ return False, "auto_round_kernel does not expose _ark_instance().", None, None
635
+
636
+ try:
637
+ ark_instance = ark_loader()
638
+ except Exception as error:
639
+ return False, str(error), None, None
640
+
641
+ if ark_instance is None:
642
+ return False, "auto_round_kernel._ark_instance() returned None.", None, None
643
+
644
+ return True, None, ark_instance, QuantLinear
645
+
646
+
647
+class INCXPULinearMethod(INCXPULinearBase):
648
+ """XPU linear method for INC w4a16 GPTQ quantization (symmetric only).
649
+
650
+ Repacks GPTQ weights from [in_packed, out] to oneDNN [out, in_packed]
651
+ layout and calls torch.ops._xpu_C.int4_gemm_w4a16.
652
+
653
+ GPTQ format: qweight [in_packed, out] with sequential nibble order.
654
+
655
+ Note: Asymmetric quantization (sym=false) is not for now.
656
+
657
+ FIXME(yiliu30): Refine the implementation to reuse XPUwNa16LinearKernel.
658
+ """
659
+
660
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
661
"""Repack GPTQ weights into kernel-ready NT layout."""
662
device = layer.qweight.data.device
@@ -624,3 +700,104 @@ class INCXPULinearMethod(LinearMethodBase):
700
None, # g_idx not needed: desc_act is always False for INC models
701
)
702
return out.reshape(out_shape)
703
+
704
+
705
+class INCARKLinearMethod(INCXPULinearBase):
706
+ """XPU & CPU w4a16 linear method for INC quantization utilizing the ARK backend.
707
+
708
+ See: https://github.com/intel/auto-round/blob/main/auto_round_extension/ark/README.md
709
+
710
+ Repacks GPTQ/INC weights into ARK's layout.
711
+ """
712
+
713
+ def __init__(self, weight_bits: int, group_size: int, sym: bool):
714
+ super().__init__(weight_bits=weight_bits, group_size=group_size, sym=sym)
715
+
716
+ is_available, error_str, _, quant_linear_cls = get_ark_state()
717
+ if not is_available or quant_linear_cls is None:
718
+ reason = error_str or "unknown error"
719
+ raise ImportError(f"Failed to import auto_round_kernel. {reason}")
720
+
721
+ self.QuantLinear = quant_linear_cls
722
+
723
+ def create_weights(
724
+ self,
725
+ layer: torch.nn.Module,
726
+ input_size_per_partition: int,
727
+ output_partition_sizes: list[int],
728
+ input_size: int,
729
+ output_size: int,
730
+ params_dtype: torch.dtype,
731
+ **extra_weight_attrs,
732
+ ):
733
+ super().create_weights(
734
+ layer=layer,
735
+ input_size_per_partition=input_size_per_partition,
736
+ output_partition_sizes=output_partition_sizes,
737
+ input_size=input_size,
738
+ output_size=output_size,
739
+ params_dtype=params_dtype,
740
+ **extra_weight_attrs,
741
+ )
742
+ layer.in_features = input_size_per_partition
743
+ layer.out_features = sum(output_partition_sizes)
744
+ layer.params_dtype = params_dtype
745
+
746
+ def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
747
+ if hasattr(layer, "input_size_per_partition"):
748
+ in_features = layer.input_size_per_partition
749
+ elif hasattr(layer, "input_size"):
750
+ in_features = layer.input_size
751
+ else:
752
+ raise AttributeError("Cannot determine in_features for layer.")
753
+
754
+ if hasattr(layer, "output_partition_sizes"):
755
+ out_features = sum(layer.output_partition_sizes)
756
+ elif hasattr(layer, "output_size_per_partition"):
757
+ out_features = layer.output_size_per_partition
758
+ elif hasattr(layer, "output_size"):
759
+ out_features = layer.output_size
760
+ else:
761
+ out_features = layer.scales.shape[-1]
762
+
763
+ ark_linear = self.QuantLinear(
764
+ bits=self.weight_bits,
765
+ group_size=self.group_size,
766
+ sym=self.sym,
767
+ in_features=in_features,
768
+ out_features=out_features,
769
+ bias=layer.bias is not None,
770
+ weight_dtype=layer.params_dtype,
771
+ )
772
+
773
+ ark_linear.to(layer.qweight.device)
774
+
775
+ with torch.no_grad():
776
+ ark_linear.qweight.copy_(layer.qweight.detach())
777
+
778
+ if hasattr(layer, "qzeros") and layer.qzeros is not None:
779
+ ark_linear.qzeros.copy_(layer.qzeros.detach())
780
+ else:
781
+ ark_linear.qzeros = None
782
+
783
+ ark_linear.scales.copy_(layer.scales.detach())
784
+
785
+ if hasattr(layer, "bias") and layer.bias is not None:
786
+ ark_linear.bias.copy_(layer.bias.detach())
787
+
788
+ ark_linear.post_init()
789
+
790
+ layer.ark_linear = ark_linear
791
+
792
+ del layer.qweight
793
+ if hasattr(layer, "qzeros"):
794
+ del layer.qzeros
795
+ del layer.scales
796
+
797
+ def apply(
798
+ self,
799
+ layer: torch.nn.Module,
800
+ x: torch.Tensor,
801
+ bias: torch.Tensor | None = None,
802
+ ) -> torch.Tensor:
803
+ return layer.ark_linear.forward(x)