华为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等进行部署推理。

相关推荐
程序猿追1 小时前
【鸿蒙PC桌面端实战】从零构建 ArkTS 高性能图像展示器:DevEco Studio 调试与 HDC 命令行验证全流程
华为·harmonyos
前端世界2 小时前
设备找不到、Ability 启不动?一次讲清 DevEco Studio 调试鸿蒙分布式应用
华为·harmonyos
小雨下雨的雨6 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨6 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨7 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨7 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
小雨下雨的雨8 小时前
Flutter 框架跨平台鸿蒙开发 —— Padding 控件之空间呼吸艺术
flutter·ui·华为·harmonyos·鸿蒙系统
小雨下雨的雨9 小时前
Flutter 框架跨平台鸿蒙开发 —— Align 控件之精准定位美学
flutter·ui·华为·harmonyos·鸿蒙
C雨后彩虹16 小时前
任务最优调度
java·数据结构·算法·华为·面试
盐焗西兰花19 小时前
鸿蒙学习实战之路-蓝牙设置完全指南
学习·华为·harmonyos