Quantized layer loading: AWQ / GPTQ / Marlin / fp8
Responsibilities
vLLM does not treat quantization as a post-training "compress after training" step. Instead, each quantization scheme is implemented as a pair of QuantizationConfig + LinearMethodBase (and, for MoE, FusedMoEMethodBase). The Config reads the quantization_config field from the Hugging Face checkpoint and decides which LinearMethod to use; the LinearMethod is responsible for three things: create_weights builds the qweight / scales / qzeros parameter objects at layer construction time, process_weights_after_loading performs a one-shot reorder once all weights have been loaded, and apply invokes the actual quantized GEMM kernel during the forward pass. This abstraction lets the same ColumnParallelLinear run unquantized as well as AWQ / GPTQ / fp8 / Marlin, differing only in quant_method.
Both AWQ and GPTQ go through the Marlin kernel family in vLLM: AutoAWQMarlinLinearMethod(auto_awq.py:414-426) and AutoGPTQLinearMethod(auto_gptq.py:306-324), selecting the best among Conch / Exllama / Marlin / Machete via choose_mp_linear_kernel. fp8 takes a different route: Fp8Config(fp8.py:95-101) supports per-tensor / per-block / static / dynamic activation quantization, and picks a cutlass / Marlin / triton backend via init_fp8_linear_kernel.
Design motivation
Why split quantization into three layers: Config + Method + Kernel?
- Uniform interface:
LinearMethodBase.create_weights(layer, input_size_per_partition, output_partition_sizes, input_size, output_size, ...)(linear.py:144-168) has the same signature across quantization schemes, soColumnParallelLineardoes not need to know whether it is AWQ or fp8; it only needsquant_config.get_quant_method(layer)to return aLinearMethodBaseinstance. - Kernel selection is deferred to runtime:
create_weightsfor AWQ / GPTQ first builds anMPLinearLayerConfigdescribing weight_type / group_size / zero_points / has_g_idx, then callschoose_mp_linear_kernel(...)to pick a concrete kernel(auto_gptq.py:341-354), and the kernel itself decides whether to reorder viaprocess_weights_after_loading. - AWQ -> GPTQ format conversion: AWQ checkpoints use a non-standard packing order packed along the output dimension, while the Marlin kernel only understands the GPTQ style (packed along the input dimension, standard bit order), so
process_weights_after_loadingfirst calls_convert_awq_to_standard_formatbefore handing off to the kernel(auto_awq.py:528-536). - GPTQ desc_act / g_idx:
AutoGPTQLinearMethod.create_weightsdecides whether to build theg_idxparameter based ondesc_act, and decides whether scales are replicated or sharded under TP based onmarlin_repeat_scales_on_all_ranks(auto_gptq.py:366-440). - fp8 checkpoints that are not fp8 are quantized online: if the checkpoint is bf16,
process_weights_after_loadingcallsprocess_fp8_weight_tensor_strategyto quantize it to fp8 online, also handling per-tensor reshaping(fp8.py:416-437). - Marlin reorder: when fp8 picks
MarlinFP8ScaledMMLinearKernel,process_weights_after_loadingtransposes the weight, rewrites it in the layout Marlin expects, and setsmarlin_input_dtype(fp8.py:398-408). - Parameters go through the v2 loader: AWQ / GPTQ use v2 parameter classes like
PackedvLLMParameter/GroupQuantScaleParameter/PackedColumnParameter, which carrypacked_dim/packed_factor/input_dim/output_dimmetadata, andweight_loader_v2splits them correctly based on this(auto_awq.py:478-519).
Key files
QuantizationConfig:87-101— abstract base class definingget_quant_method,get_cache_method, and related interfaces.LinearMethodBase:141-180— quantization method base class:create_weights/apply/process_weights_after_loading.AutoAWQConfig:171-180— parses the AWQ checkpoint'squantization_config.AutoAWQMarlinLinearMethod.create_weights:439-526— buildsqweight/qzeros/scalesand selects the Marlin kernel.AutoAWQMarlinLinearMethod.process_weights_after_loading:528-536—_convert_awq_to_standard_format+ kernel's own reorder._convert_awq_to_standard_format:93-100— function that converts AWQ packing to GPTQ packing.AutoGPTQConfig:97-103— parses the GPTQ checkpoint, handlingbits/group_size/desc_act.AutoGPTQLinearMethod.create_weights:326-453— buildsqweight/g_idx/scales/qzeros, choosingChannelQuantScaleParameterorGroupQuantScaleParameterbased ondesc_act.AutoGPTQLinearMethod.process+apply:455-464—kernel.process_weights_after_loading(layer)+kernel.apply_weights(layer, x, bias).Fp8Config:95-101— fp8 config, distinguishing per-tensor / block / static-dynamic activation.Fp8LinearMethod.create_weights:322-394— buildsweight/weight_scale/ optionalinput_scale, selecting the kernel viainit_fp8_linear_kernel.Fp8LinearMethod.process_weights_after_loading:398-441— transpose + kernel reorder for Marlin; otherwise quantizes non-fp8 checkpoints online.choose_mp_linear_kernel:685-687— picks among Conch / Exllama / Marlin / Machete based on platform and shape.MarlinLinearKernel.process_weights_after_loading:88-137—ops.gptq_marlin_repackrepacks qweight into the layout Marlin expects.
Data flow
Taking AWQ Marlin as an example, ColumnParallelLinear.__init__ calls self.quant_method.create_weights(...), AutoAWQMarlinLinearMethod.create_weights first builds an MPLinearLayerConfig, then calls choose_mp_linear_kernel to pick a MarlinLinearKernel instance, then builds three v2 parameter objects:
qweight = PackedvLLMParameter(
data=torch.empty(
input_size_per_partition,
output_size_per_partition // self.quant_config.pack_factor,
dtype=torch.int32,
),
input_dim=0,
output_dim=1,
packed_dim=1,
packed_factor=self.quant_config.pack_factor,
weight_loader=weight_loader,
)
num_groups = input_size_per_partition // group_size
qzeros = PackedvLLMParameter(
data=torch.empty(
num_groups,
output_size_per_partition // self.quant_config.pack_factor,
dtype=torch.int32,
),
input_dim=0,
output_dim=1,
packed_dim=1,
packed_factor=self.quant_config.pack_factor,
weight_loader=weight_loader,
)
scales = GroupQuantScaleParameter(
data=torch.empty(
num_groups,
output_size_per_partition,
dtype=params_dtype,
),
input_dim=0,
output_dim=1,
weight_loader=weight_loader,
)(auto_awq.py:478-515)Afterwards DefaultModelLoader drives model.load_weights to split and write qweight / scales / qzeros from disk through the v2 loader. Once everything is loaded, ColumnParallelLinear calls quant_method.process_weights_after_loading, AutoAWQMarlinLinearMethod first converts AWQ packing to GPTQ style, then hands off to MarlinLinearKernel.process_weights_after_loading, which uses ops.gptq_marlin_repack to repack the weight into the layout expected by the Marlin kernel(marlin.py:124-137). During forward, apply calls self.kernel.apply_weights(layer, x, bias)(auto_awq.py:538-545), ultimately landing in Marlin's CUDA / Triton kernels.
Boundaries and failure modes
- Platform does not support Marlin:
AutoAWQMarlinLinearMethod.__init__callsverify_marlin_supported(quant_type, group_size, has_zp=...)on non-CPU platforms and raises if the check fails(auto_awq.py:431-437). - group_size=-1:
create_weightsdegenerates group_size toinput_size, i.e. per-channel quantization(auto_awq.py:452-455). - fp8 block_quant + act_q_static: block quantization strictly requires dynamic activation (
assert not self.act_q_static), otherwise it raises(fp8.py:367-368). - fp8 non-fp8 checkpoint:
process_weights_after_loadinggoes throughprocess_fp8_weight_tensor_tensor_strategyto quantize bf16 online, and per-tensor also has to reshape fused modules(fp8.py:416-429). - GPTQ row-parallel scale replication:
marlin_repeat_scales_on_all_ranksdecides whether scales areChannelQuantScaleParameter(scale_dim=None, replicated on all ranks) orGroupQuantScaleParameter(scale_dim=0, sharded)(auto_gptq.py:367-440). - Phi-3 fused QKV: when QKV is already fused on disk there is no shard id, so
QKVParallelLinear._load_fused_module_from_checkpointsplits it into three segments itself(linear.py:1049-1097), and quantization has to handle the packed_dim offset adjustment. - Marlin act_type is fp8:
MarlinLinearKernel.process_weights_after_loadingcallsops.marlin_int4_fp8_preprocessand multiplies scales by 512 whenc.act_type == torch.float8_e4m3fn(marlin.py:98-103).
Summary
The quantization abstraction splits Config (reading checkpoint metadata) -> Method (building params + post-processing + delegating apply) -> Kernel (the actual computation) into three layers. AWQ / GPTQ / Marlin share MPLinearLayerConfig + choose_mp_linear_kernel, while fp8 goes through its own init_fp8_linear_kernel. It relies on tensor-parallel linear layers to inject the v2 loader into parameters at create_weights time, and the load flow is driven by DefaultModelLoader; the post-processed weights are ultimately consumed on the forward pass by the attention layers and the KV cache described by block tables.