W4A16量化
大模型推理中,模型权重数据类型(weight),推理计算时的数据类型(activation)和kvcache一般使用半精度浮点FP16或BF16。量化指将高比特的浮点转换为更低比特的数据类型的过程。例如int4、int8等。
模型量化分为weight-only量化,weight-activation量化和kvcache量化。
量化的一般步骤是:1、对浮点类型的权重镜像量化并保存量化完的权重;2、使用量化完的权重进行推理部署。
什么是W4A16量化
W4A16量化是一种大模型压缩优化技术。其中“W4”表示将模型的权重量化为4位整数(int4),“A16”表示激活(或输入/输出)保持16位浮点数(FP16或BF16)。
这种量化方式只对参数进行4bit量化,激活值仍维持FP16精度。其优势在于能显著降低模型显存占用以及需要部署的卡数(约75%)。大幅降低小batch下的增量推理时延。
约束限制
- 支持使用llm-compressor工具进行 W4A16、 per-group(group-size=128)量化,不支持指定actorder参数为“group”;
- 当前只支持Qwen系列相关模型的W4A16量化,支持量化的模型列表请参见表1。
- W4A16量化的Qwen系列模型只支持使用图模式启动,且不支持设置Qwen系列性能优化环境变量,具体配置会在下文详细描述。
获取量化后的模型权重
有两种方式获取量化模型权重:
方式一:可以在Huggingface开源社区获取量化后的模型权重,具体参考从开源社区下载发布的llm-compressor量化模型。
方式二:获取FP16/BF16的模型权重之后,通过llm-compressor工具进行量化,具体参考使用llm-compressor量化工具进行量化。
使用llm-compressor工具量化模型
本章节介绍如何在NPU的机器上使用开源量化工具llm-compressor量化模型权重,然后在NPU的机器上实现推理量化。了解更多开源量化工具信息,请参考llm-compressor。
- 使用该量化工具,需要切换conda环境,运行以下命令。
conda create --name llmcompressor --clone PyTorch-2.5.1 conda activate llmcompressor
- 安装llm-compressor。
pip install llmcompressor==0.6.0 pip install transformers==4.51.3 pip install zstandard
- 创建量化脚本,完整的llm_compressor_W4A16.py脚本请参见llm_compressor_W4A16.py脚本。
- 配置校准数据集。W4A16量化脚本中默认使用"mit-han-lab/pile-val-backup"数据集作为校准数据集,如需指定数据集,请修改"llm_compressor_W4A16.py"脚本中如下字段。
DATASET_ID = "mit-han-lab/pile-val-backup" DATASET_SPLIT = "validation" NUM_CALIBRATION_SAMPLES = 256 MAX_SEQUENCE_LENGTH = 512
如果当前环境无法访问HuggingFace网站获取数据集,可以先将数据集通过浏览器下载到本地后上传至服务器,并将DATASET_ID指定到服务器路径,数据集下载地址:https://huggingface.co/datasets/mit-han-lab/pile-val-backup/tree/main
下载后将val.jsonl.zst文件上传至服务器自定义目录,例如<local-dir>,并解压:
cd <local-dir> zstd -d val.jsonl.zst
解压后的文件名为val.jsonl,解压成功后需要删除原始文件,避免影响后续量化时读取数据:
rm -f val.jsonl.zst
DATASET_ID指定到val.jsonl文件所在的文件夹目录名称即可:
DATASET_ID = "<local-dir>"
- 配置量化算法。W4A16量化脚本中默认提供AWQ非对称量化算法,如下所示:
# Configure the quantization algorithm to run. recipe = [ AWQModifier(ignore=["lm_head"], scheme="W4A16_ASYM", targets=["Linear"]), ]
如果使用对称量化算法,可以配置scheme="W4A16"。
- 运行"llm_compressor_W4A16.py"文件进行模型量化,模型权重路径需要根据实际情况修改,量化时间和模型大小有关,预计30分钟~3小时。
# 根据机器卡的空闲情况指定量化使用的卡号,不设置则默认使用0号卡 export ASCEND_RT_VISIBLE_DEVICES=0 python llm_compressor_W4A16.py --model-path /home/ma-user/Qwen3-32B/ --quant-path /home/ma-user/Qwen3-32B-quant/
参数说明:
- --model-path:原始模型权重路径
- --quant-path:转换后保存权重路径
- 参考启动在线推理服务,在启动服务时添加如下量化参数:
-q compressed-tensors 或者--quantization compressed-tensors
W4A16模型当前不支持eager或acl-graph模式,服务启动时需要开启ascend_turbo图模式,配置如下:
--additional-config: {"ascend_turbo_graph_config": {"enabled": true}}
W4A16模型不支持配置Qwen系列性能优化环境变量,服务启动时不支持设置如下环境变量:
unset ENABLE_QWEN_HYPERDRIVE_OPT unset ENABLE_QWEN_MICROBATCH unset ENABLE_PHASE_AWARE_QKVO_QUANT unset DISABLE_QWEN_DP_PROJ
针对Qwen2.5-72B-instruct模型和Qwen2-72B-instruct模型,在量化完成后启动服务之前,需要先修改量化后的W4A16模型配置文件config.json,将intermediate_size的值从29568改为29696,原因是Qwen2.5-72B-instruct模型的FFN blocks为29568,W4A16量化group size为128,29568/128=231,该数字无法被大于1的TP整除,因此在TP大于1的场景下无法正常运行服务。此处将intermediate_size改为29696,29696/128=232,该数可以被TP=4或者TP=8整除,因此可以进行多卡推理(参考社区说明)。修改配置后,服务启动时会自动将权重padding到指定shape。修改方式如下:
vim config.json # 修改如下配置项 "intermediate_size": 29696,
import argparse import os from functools import partial import torch import torch_npu from torch_npu.contrib import transfer_to_npu from datasets import load_dataset from llmcompressor import oneshot from llmcompressor.modifiers.awq import AWQModifier from transformers import AutoModelForCausalLM, AutoTokenizer os.environ['PYTORCH_NPU_ALLOC_CONF'] = 'expandable_segments:False' # Select calibration dataset. DATASET_ID = "mit-han-lab/pile-val-backup" DATASET_SPLIT = "validation" # Select number of samples. 256 samples is a good place to start. # Increasing the number of samples can improve accuracy. NUM_CALIBRATION_SAMPLES = 256 MAX_SEQUENCE_LENGTH = 512 def preprocess(example, tokenizer): return { "text": tokenizer.apply_chat_template( [{"role": "user", "content": example["text"]}], tokenize=False, ) } def quantize(model_id, quantized_path): # Select model and load it model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype="auto") tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True) ds = load_dataset(DATASET_ID, split=f"{DATASET_SPLIT}[:{NUM_CALIBRATION_SAMPLES}]") ds = ds.shuffle(seed=42) preprocess_func = partial(preprocess, tokenizer=tokenizer) ds = ds.map(preprocess_func) # Configure the quantization algorithm to run. recipe = [ AWQModifier(ignore=["lm_head"], scheme="W4A16_ASYM", targets=["Linear"]), ] oneshot( model=model, dataset=ds, recipe=recipe, max_seq_length=MAX_SEQUENCE_LENGTH, num_calibration_samples=NUM_CALIBRATION_SAMPLES, ) # Save to disk compressed. model.save_pretrained(quantized_path, save_compressed=True) tokenizer.save_pretrained(quantized_path) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("--model-path", type=str, required=True, help="Path to the input model") parser.add_argument("--quant-path", type=str, required=True, help="Path to save the compressed model") args = parser.parse_args() quantize(args.model_path, args.quant_path)