华为Asend NPU 大模型W8A8量化调优

目前华为显卡上支持的量化方案比较少,只能选取它自己的量化框架msmodelslim进行量化。

1 安装msmodelslim

下载一个vllm-ascend的docker

然后下载msmodelslim的gitee 项目 https://gitee.com/ascend/msit.git

然后开始安装,需要什么pip,就自己离线拷贝进去安装。

我们这里使用的是arrch64的cpu。

复制代码
easydict==1.13
einops
pydantic>=2.0.3

安装好了以后。开始量化校准。

python 复制代码
"""
1、导入相关依赖
"""
import os
import json
import torch
import torch_npu # 如果需要使用npu进行量化
from transformers import AutoTokenizer, AutoModelForCausalLM
from msmodelslim.pytorch.llm_ptq.anti_outlier import AntiOutlierConfig, AntiOutlier
from msmodelslim.pytorch.llm_ptq.llm_ptq_tools import Calibrator, QuantConfig
from precision_tool.precision_tool import PrecisionTest # precision_tool用于伪量化测精度

SEQ_LEN_OUT = 100
device_id = 4
batch_size = 5

# 如果使用npu进行量化需开启二进制编译,避免在线编译算子
torch.npu.set_compile_mode(jit_compile=False)
option = {}
option["NPU_FUZZY_COMPILE_BLACKLIST"] = "ReduceProd"
torch.npu.set_option(option)


"""
2、导入相关模型
"""
fp16_path = '/data/chatglm2-6b' # 原始浮点模型路径

tokenizer = AutoTokenizer.from_pretrained(
    pretrained_model_name_or_path=fp16_path,
    local_files_only=True
)

model = AutoModelForCausalLM.from_pretrained(
    pretrained_model_name_or_path=fp16_path,
    local_files_only=True,
    device_map="auto",
    torch_dtype="auto"
).eval()

"""
数据集测原始模型浮点精度(此示例中选择的是boolq)
"""
precision_test = PrecisionTest(model, tokenizer, "boolq", batch_size, "npu")
precision_test.test()


"""
3、获取校准数据
"""
# 一般数据都在cpu上,用npu进行量化的时候都需要指定数据到npu设备上
def build_prompt(title, text, passage):
    prompt = f"{title} -- {passage}\nQuestion:{text}?\nAnswer:"
    return prompt

def get_calib_dataset(tokenizer, calib_list, device=f"npu:{device_id}"):
    calib_dataset = []
    for calib_data in calib_list:
        title = calib_data["title"]
        text = calib_data["question"]
        passage = calib_data["passage"]
        queries = build_prompt(title, text, passage)
        inputs = tokenizer(queries, return_tensors='pt')
        calib_dataset.append([inputs.data['input_ids'].to(device), inputs.data['attention_mask'].to(device)])     
    return calib_dataset

entry = "/path/to/calib_dataset" # 此示例中校准数据选取"precision_tool/dataset/boolq/dev.jsonl"
calib_set = []
i = 0
with open(entry, encoding="utf-8") as file:
    for line in file:
        data =json.loads(line) # 将字符串转换为字典
        while i < 50: # 获取50条校准数据
            calib_set.append(data)
            i += 1

dataset_calib = get_calib_dataset(tokenizer, calib_set)


"""
4、离群值抑制AntiOutlier(W8A8)
"""
anti_config = AntiOutlierConfig(anti_method="m2", dev_type="npu", dev_id=device_id)
anti_outlier = AntiOutlier(model, calib_data=dataset_calib, cfg=anti_config)
anti_outlier.process()


"""
5、回退层设置
"""
"""
因为一些量化后的网络层对精度影响太大了,所以需要让这些网络层使用浮点权重进行计算, disable_names中为需要进行回退的网络层。
"""
disable_names = []


"""
6、执行PTQ量化校准 + 存储量化参数用于部署
"""
quant_config = QuantConfig(
    a_bit=8,
    w_bit=8,
    disable_names=disable_names,
    dev_type='npu',
    dev_id=device_id,
    act_method=3,
    pr=1.0,
    w_sym=True,
    mm_tensor=False
)

calibrator = Calibrator(model, quant_config, calib_data=dataset_calib, disable_level='L0')  # disable_level: 自动回退n个linear
calibrator.run()  # 执行PTQ量化校准
calibrator.save('/save/path', save_type=["safe_tensor", "numpy"]) # "safe_tensor"对应safetensors格式权重,"numpy"对应npy格式权重


"""
数据集测伪量化模型精度(此示例中选择的是boolq)
"""
precision_test = PrecisionTest(model, tokenizer, "boolq", batch_size, "npu")
precision_test.test()


"""
7、伪量化验证一轮推理(可选)
"""
print("testing quantized weights...")
test_prompt = "Common sense questions and answers\n\nQuestion: How to learn a new language\nFactual answer:"
test_input = tokenizer(test_prompt, return_tensors="pt")
print("model is inferring...")
model = model.to(f"npu:{device_id}")
model.eval()
generate_ids = model.generate(
    test_input.input_ids.to(f"npu:{device_id}"), 
    attention_mask=test_input.attention_mask.to(f"npu:{device_id}"), 
    max_new_tokens=SEQ_LEN_OUT
)

res = tokenizer.batch_decode(
    generate_ids, 
    skip_special_tokens=True, 
    clean_up_tokenization_spaces=False
)
for idx, item in enumerate(res):
    print(item)

在调用Calibrator.run()方法后,构建Calibrator时传入的model会被替换为伪量化模型,可以直接调用进行前向推理,用来测试对话效果。

如果伪量化结果不理想,可以参考以下方法进行调优:

主要有下面四个方法。

1.离群值抑制(AntiOutlier)

可优化参数------anti_method

m1:SmoothQuant算法

m2:SmoothQuant升级版

m3:AWQ算法(适用于W8A16/W4A16)

m4:SmoothQuant优化算法

m5:CBQ算法

m6:Flex smooth量化算法

w8a8适用m1、m2、m4、m5、m6 建议从m1尝试到m6,因为不同模型对不同离群抑制算法表现不一样,当前m2已适配qwen-vl和llava-v1.5-7b多模态模型

复制代码
anti_config = AntiOutlierConfig(
    anti_method="m2",
    dev_type="npu",
    dev_id=device_id
)

2 量化参数选择

可优化参数------disable_names、disable_level、act_method

【增加回退层(建议最后进行调整),可以按照一定经验,通过disable_names手动设置回退层,或使用disable_level自动回退功能按照一定的标准自动回退对精度影响比较大的Linear层】

disable_names: 手动指定回退层(根据理论经验和日志信息) disable_level='L0': 自动回退

act_method:激活值量化方法

act_method默认值为1,该参数可选1、2、3

1代表min-max量化方式;

2代表histogram量化方式;

3.代表min-max和histogram混合的量化的方式。

LLM大模型场景下建议使用3

python 复制代码
quant_config = QuantConfig(
    a_bit=8,
    w_bit=8,
    disable_names=disable_names,
    dev_type='npu',
    dev_id=device_id,
    act_method=3,
    pr=1.0,
    w_sym=True,
    mm_tensor=False
)

calibrator = Calibrator(
    model, 
    quant_config, 
    calib_data=dataset_calib, 
    disable_level='L0'
)  

3 校准集调整

可以使用官方数据集,也可以使用自己的数据集,针对自己的场景进行专门的量化调优。

数据集格式:

然后读取数据:

python 复制代码
def get_calib_dataset(tokenizer, calib_list, device=f"npu:{device_id}"):
    calib_dataset = []
    for calib_data in calib_list:
        title = calib_data["title"]
        text = calib_data["question"] 
        passage = calib_data["passage"]
        queries = build_prompt(title, text, passage)
        inputs = tokenizer(queries, return_tensors='pt')
        calib_dataset.append([inputs.data['input_ids'].to(device), inputs.data['attention_mask'].to(device)])       
    return calib_dataset

4 量化回退

大模型需要量化的原因:模型量化可以降低模型大小,减少计算量,降低内存占用,提升推理速度。

大模型量化线性层的原因:大模型中的线性层层数多、权重数量庞大且存在矩阵相乘(计算量大),通过量化线性层的权重和激活值,可以达到降低模型大小,减少计算量,降低内存占用,提升推理速度。

量化回退的原因:某些线性层对于量化比较敏感,量化后会带来一定的精度损失,这些层是不太适合量化的,应该使用浮点数进行计算,这个过程称之为回退,可以通过设置disable_names控制哪些线性层应该被回退。

怎么判定敏感:终端的打印日志中会显示每一层算子激活量化输入的range_parm数值,range_parm数值越大越敏感。

终端打印日志示例:

bash 复制代码
时间戳 - msmodelslim-logger - INFO - use histogram observer:transformer.encoder.layers.27.mlp.dense_h_to_4h.quant_input, range_parm:41.21875

此示例中的 range_parm:41.21875 数值就很大(和日志中其他层的range_parm相比),说明该层敏感,需要回退。

量化回退的方法:分为手动回退和自动回退两个方法(可叠加使用),建议先手动回退,如果不清楚该回退哪些模型层或者手动回退精度不好的话,再自动回退。

注:量化回退会造成一定的性能损失。

手动回退------disable_names

disable_names=[]: []手动回退层名称,如果不添加则不回退

按以下顺序进行回退:

1、回退down_proj层(精度敏感):mlp的采样层,(如果没有标识出down_proj就看out_features, 数值小的就是下采样层)。

2、回退o_proj层(通常精度敏感):是self_attention中调的最后一个线性层,(model中打出来的只是初始化时的顺序,要去模型代码里看实际调用顺序) 。

3、根据理论经验或终端打印日志中的range_parm数值大小找出量化敏感层进行回退。

如下示例为手动回退chatglm2-6b的所有down_proj层:

bash 复制代码
disable_names=[
    'transformer.encoder.layers.0.mlp.dense_4h_to_h',
    'transformer.encoder.layers.1.mlp.dense_4h_to_h',
    'transformer.encoder.layers.2.mlp.dense_4h_to_h',
    'transformer.encoder.layers.3.mlp.dense_4h_to_h',
    'transformer.encoder.layers.4.mlp.dense_4h_to_h',
    'transformer.encoder.layers.5.mlp.dense_4h_to_h',
    'transformer.encoder.layers.6.mlp.dense_4h_to_h',
    'transformer.encoder.layers.7.mlp.dense_4h_to_h',
    'transformer.encoder.layers.8.mlp.dense_4h_to_h',
    'transformer.encoder.layers.9.mlp.dense_4h_to_h',
    'transformer.encoder.layers.10.mlp.dense_4h_to_h',
    'transformer.encoder.layers.11.mlp.dense_4h_to_h',
    'transformer.encoder.layers.12.mlp.dense_4h_to_h',
    'transformer.encoder.layers.13.mlp.dense_4h_to_h',
    'transformer.encoder.layers.14.mlp.dense_4h_to_h',
    'transformer.encoder.layers.15.mlp.dense_4h_to_h',
    'transformer.encoder.layers.16.mlp.dense_4h_to_h',
    'transformer.encoder.layers.17.mlp.dense_4h_to_h',
    'transformer.encoder.layers.18.mlp.dense_4h_to_h',
    'transformer.encoder.layers.19.mlp.dense_4h_to_h',
    'transformer.encoder.layers.20.mlp.dense_4h_to_h',
    'transformer.encoder.layers.21.mlp.dense_4h_to_h',
    'transformer.encoder.layers.22.mlp.dense_4h_to_h',
    'transformer.encoder.layers.23.mlp.dense_4h_to_h',
    'transformer.encoder.layers.24.mlp.dense_4h_to_h',
    'transformer.encoder.layers.25.mlp.dense_4h_to_h',
    'transformer.encoder.layers.26.mlp.dense_4h_to_h',
    'transformer.encoder.layers.27.mlp.dense_4h_to_h',
]

自动回退------disable_level

自动回退会根据range_parm参数由大到小排序回退对精度影响比较大的Linear层。 设置disable_level='Lx',x为自动回退的linear层数量,会在终端显示回退的层名称,diable_level='L0'即为不进行回退,x设置的数量超过模型层数就是全部回退,并且也不报错。

5 KV Cache int8量化

可在QuantConfig后调用kv_quant函数来开启KV Cache int8量化。

python 复制代码
quant_config = QuantConfig(
    a_bit=8,
    w_bit=8,
    disable_names=disable_names,
    dev_type='npu',
    dev_id=device_id
).kv_quant()

长序列场景下KV Cache占用显存空间较大,通过KV Cache量化可以节约显存占用,增加并发数。

调用kv_quant函数会自动将QuantConfig中use_kvcache_quant设置为True。

use_kvcache_quant=True启用KV Cache量化,支持与W8A8、W8A16和稀疏量化同时使用。

以下是量化chatglm-6b,每个操作的精度损失情况,可以作为参考。

步骤 参数更改描述 精度
原浮点模型精度 0.794
添加QuantConfig量化参数 disable_names = [] ,act_method = 3, disable_level = "L0", dataset_calib 数据量为2条 0.519
添加离群值抑制 anti_method = "m2" 0.497
增加boolq校准数据 从2条增加到50条 0.505
增加量化回退(手动回退) 手动回退所有layer中的mlp.down_proj 0.793
增加量化回退(自动回退) disable_names = [],设置 disable_level = "L28" 0.791
增加量化回退(手动回退+自动回退) disable_names手动回退10层,disable_level = "L28" 0.795

模型量化后,可基于MindIE、vLLM等进行部署推理。

相关推荐
文火冰糖的硅基工坊6 小时前
[嵌入式系统-114]:华为的操作系统:鸿蒙操作系统(HarmonyOS)和欧拉操作系统(openEuler)
科技·华为·架构·harmonyos
大雷神7 小时前
windows开发中使用flutter开发鸿蒙
华为·harmonyos
Swift社区7 小时前
HarmonyOS 用 attributeModifier 修改按钮背景但按压态不生效
华为·harmonyos
特立独行的猫a9 小时前
HarmonyOS鸿蒙中的NES游戏模拟器的完整实现
游戏·华为·harmonyos·fc·nes
小雨青年9 小时前
创建你的第一个 HarmonyOS 6 鸿蒙应用 Hello HarmonyOS
华为·harmonyos
猫林老师10 小时前
OpenHarmony内核基础:LiteOS-M内核与POSIX/CMSIS接口
华为·harmonyos
Bert丶seven13 小时前
鸿蒙Harmony实战开发教学Day1-IDE新版本安装篇
华为·harmonyos·arkts·鸿蒙·鸿蒙系统·arkui·开发教学
2501_919749031 天前
鸿蒙:用Toggle组件实现选择框、开关样式
华为·harmonyos