quantizer.py — 这个文件实现了 QATQuantizer -- 用于将 QAT 训练后的 FP16/BF16...¶
模块路径: verl.utils.qat.quantizer
文件概述¶
这个文件实现了 QATQuantizer -- 用于将 QAT 训练后的 FP16/BF16 权重真正量化为 NVFP4 格式的量化器。与 linear.py 的假量化不同,这里是真正的量化,产生打包后的低精度权重和 scale 参数。
使用 compressed_tensors 库的 API 进行实际的量化和打包。
关键代码讲解¶
1. Blockwise Scale 计算¶
def compute_blockwise_scale(weight, global_scale, group_size=16):
"""计算分块 scale(FP8 E4M3 格式)"""
out_features, in_features = weight.shape
weight_reshaped = weight.view(out_features, num_groups, group_size)
# 每个分块的最大绝对值
block_max = torch.amax(torch.abs(weight_reshaped), dim=-1)
# 计算 local scale 并用 global scale 归一化
local_scale = block_max / FP4_E2M1_DATA.max
blockwise_scale = torch.clamp(
global_scale * local_scale,
min=-FP8_E4M3_DATA.max, max=FP8_E4M3_DATA.max
).to(torch.float8_e4m3fn)
return blockwise_scale
NVFP4 的两级 scale 体系:global_scale(整个权重矩阵)+ blockwise_scale(每个分组),确保量化精度。
2. Scale 融合¶
FUSE_PATTERNS = {
"qkv": ["q_proj", "k_proj", "v_proj"],
"gate_up": ["gate_proj", "up_proj"],
}
def fuse_global_scales(layer_global_scales, strategy="min"):
"""将 QKV/GateUp 组的 global scale 融合(取最小值)"""
for _, patterns in FUSE_PATTERNS.items():
matched = [children[p] for p in patterns if p in children]
if len(matched) == len(patterns):
group_scales = [layer_global_scales[n] for n in matched]
fused_scale = torch.min(torch.cat(group_scales)).reshape([1])
for layer_name in matched:
fused_scales[layer_name] = fused_scale.clone()
取最小值(min)策略确保量化范围足以覆盖所有融合层。
3. QATQuantizer 类¶
class QATQuantizer:
def __init__(self, mode="w4a16", group_size=16, ignore_patterns=None, device=None):
self._compressor = NVFP4PackedCompressor()
self._quant_args = QuantizationArgs(
num_bits=4, type=QuantizationType.FLOAT, symmetric=True,
strategy=QuantizationStrategy.TENSOR_GROUP, group_size=group_size
)
4. 流式量化¶
def quantize_with_fusion(self, params, target_device=None):
"""流式量化:逐层消费输入,逐层产出量化结果"""
current_layer_idx = _sentinel
layer_buffer = {}
for name, tensor in params:
layer_idx = self._extract_layer_idx(name)
# 层边界:刷新上一层的缓冲
if layer_idx != current_layer_idx and layer_buffer:
yield from self._process_layer_group(current_layer_idx, layer_buffer, ...)
layer_buffer = {}
layer_buffer[name] = tensor_cpu
# 刷新最后一层
if layer_buffer:
yield from self._process_layer_group(...)
流式处理的优势:不需要将所有参数同时加载到内存,适合大模型。每次只处理一个 decoder layer 的参数。
5. 层级量化处理¶
def _process_layer_group(self, layer_idx, layer_params, input_global_scales, output_device):
# 1. 将权重移到 GPU
weight_gpu = tensor.to(device=self.device, dtype=self.param_dtype)
# 2. 计算 global scale
amax = torch.amax(torch.abs(weight_gpu))
layer_global_scales[layer_name] = generate_gparam(...)
# 3. 融合 global scale
fused_global_scales = fuse_global_scales(layer_global_scales)
# 4. 计算 blockwise scale
weight_scale = compute_blockwise_scale(weight_gpu, fused_global_scale)
# 5. 压缩打包
weight_packed = self._compressor.compress_weight(weight=weight_gpu, scale=weight_scale, ...)
results.append((f"{layer_name}.weight_packed", weight_packed))
results.append((f"{layer_name}.weight_scale", weight_scale))
results.append((f"{layer_name}.weight_global_scale", fused_global_scale))
核心类/函数列表¶
| 类/函数名 | 作用 |
|---|---|
QATQuantizer |
主量化器类 |
compute_blockwise_scale |
计算分块 scale |
fuse_global_scales |
融合相关层的 global scale |
quantize_with_fusion |
流式量化(generator) |
_process_layer_group |
处理一层的量化 |
与其他模块的关系¶
- 在训练完成后使用,将训练好的权重转换为量化格式
- 使用
compressed_tensors库的NVFP4PackedCompressor - 产出的量化权重可以被 vLLM 加载(通过
vllm_patch.py的补丁)
小结¶
QATQuantizer 是训练到推理的桥梁。它将 QAT 训练后的全精度权重转换为 NVFP4 打包格式,同时计算两级 scale(global + blockwise)并执行 QKV/GateUp 融合。流式处理设计使其能够处理超大模型。