diff --git a/MindIE/MultiModal/example/convert_quant_weight.py b/MindIE/MultiModal/example/convert_quant_weight.py deleted file mode 100644 index f311245d8db5fc42015ef01b3db3cee1d190fa53..0000000000000000000000000000000000000000 --- a/MindIE/MultiModal/example/convert_quant_weight.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 -import os -import torch -from torch import distributed as dist -from msmodelslim.pytorch.llm_ptq.llm_ptq_tools import Calibrator, QuantConfig -from opensora.models.diffusion.opensora.modeling_opensora import OpenSoraT2V -from utils.parallel_mgr import ParallelConfig, init_parallel_env, finalize_parallel_env, get_sequence_parallel_rank - -# 如果使用npu进行量化需开启二进制编译,避免在线编译算子 -torch.npu.set_compile_mode(jit_compile=False) - -use_fa3 = True -world_size = int(os.getenv('WORLD_SIZE', 1)) -if world_size > 1: - sp_degree = world_size // 2 - parallel_config = ParallelConfig(sp_degree=sp_degree, use_cfg_parallel=True, - world_size=world_size) - init_parallel_env(parallel_config) -rank = dist.get_rank() - -model_path = '/home/Open-Sora-Plan-v1.2.0/93x720p' # 原始浮点模型路径 -dev_id = 8 -model = OpenSoraT2V.from_pretrained(model_path, cache_dir="../cache_dir", - low_cpu_mem_usage=False, device_map=None, - torch_dtype=torch.bfloat16).to("npu") -calib_datas = torch.load(f"/home/quant_model/calib_datas.pt", map_location='cpu') -for calib_data in calib_datas: - for i, data in enumerate(calib_data): - if torch.is_tensor(data): - calib_data[i] = data.npu() - -# 调用fa_quant之后默认开启FA量化,fa_amp可设置自动回退层数 -quant_config = QuantConfig( - a_bit=8, - w_bit=8, - disable_names=None, - dev_type='npu', - dev_id=rank, - act_method=3, - pr=1.0, - w_sym=True, - mm_tensor=False, - is_dynamic=True, -).fa_quant(fa_amp=0) - -calibrator = Calibrator(model, quant_config, calib_data=calib_datas, disable_level='L0', - torch_dtype=torch.bfloat16) -calibrator.run() - -# fa3需要按不同卡去保存权重,不用fa3则使用默认保存 -if use_fa3: - calibrator.save('/home/quant_model', safetensors_name=f'quant_model_weight_w8a8_dynamic_{rank}.safetensors', - save_type=["safe_tensor"], - json_name=f'quant_model_description_w8a8_dynamic_{rank}.json') # "safe_tensor"对应safetensors格式权重,"numpy"对应npy格式权重 -elif rank == 0: - calibrator.save('/home/quant_model', save_type=["safe_tensor"]) diff --git a/MindIE/MultiModal/example/sd_quant_readme.md b/MindIE/MultiModal/example/sd_quant_readme.md new file mode 100644 index 0000000000000000000000000000000000000000..62ce384cfcc3c5322e05fb8fd82b6592ec3ce77b --- /dev/null +++ b/MindIE/MultiModal/example/sd_quant_readme.md @@ -0,0 +1,400 @@ +# SD量化说明 + +# 依赖 + +参考以下文档完成工具使用前准备工作 +[大模型量化工具使用前的开发环境的部署](https://gitee.com/ascend/msit/tree/master/msmodelslim) +从cmc获取链接,安装atb包 +```python +./Ascend-cann-nnal_8.1.RC1_linux-x86_64.run --install --torch_atb +``` +# 接口说明 +1.[工具接口](https://gitee.com/ascend/msit/tree/master/msmodelslim/docs/Python-API%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%8E%8B%E7%BC%A9%E6%8E%A5%E5%8F%A3/%E5%A4%A7%E6%A8%A1%E5%9E%8B%E9%87%8F%E5%8C%96%E6%8E%A5%E5%8F%A3/PyTorch) + +2.MindIE-SD量化推理接口 + +用在整个量化模型上,根据匹配规则自动改图和加载量化层的权重 + +``` +quantize(model, quant_des_path) +``` +model输入要量化的模型实例,如某种dit,需要传入nn.Module + +quant_des_path输入导出后的权重描述文件路径,如果是多卡导出的场景,输入任意一个, + +根据权重描述文件会寻找一起导出的量化权重文件,量化权重文件包含整个模型的权重,包括浮点层和量化层的权重, + +quantize只要生成了模型实例就可以调用,会自动加载量化层权重, + +其他层权重可自行通过原有的权重加载逻辑从浮点或量化权重中加载,量化权重和其他层权重的加载顺序不限 + +# 导出脚本注意的点 +注意的点是不同场景需要关注的,同时在脚本样例中也用序号在注释中标记 + +1.如果有的模型单卡运行较慢或者单卡不能运行,并且工具当前不支持多模态模型多卡运行,需要在脚本侧手动使用多卡运行,添加相关初始化并行环境逻辑,并且在保存权重时按rank保存,每个rank上量化权重不同 + +2.如果模型不需要校准激活值和fa,如W8A8_DYNAMIC, W8A16,FA_QUANT等算法,不需要多卡运行和校准数据的逻辑以及多卡保存权重 + +3.由于SD实现是通过标准文件名去寻找文件,保存的权重文件只能按照标准文件名命名 + +``` +多卡场景命名规则 +f'quant_model_weight_{quant_algo.lower()}_{rank}.safetensors' +单卡场景命名规则 +f'quant_model_weight_{quant_algo.lower()}.safetensors' + +当前quant_algo支持"w8a8", "w8a8_dynamic" +``` +# 导出脚本样例 +```python +import json +import os +from types import SimpleNamespace +import torch +from torch import distributed as dist +import torch_npu +from msmodelslim.pytorch.llm_ptq.llm_ptq_tools import Calibrator, QuantConfig + +from opensora.models.diffusion.opensora.modeling_opensora import OpenSoraT2V # 导入的量化模型 +from utils.parallel_mgr import ParallelConfig, init_parallel_env, finalize_parallel_env, get_sequence_parallel_rank # 1.多卡环境需要 + + +# 如果使用npu进行量化需开启二进制编译,避免在线编译算子 +torch.npu.set_compile_mode(jit_compile=False) + +# 1.多卡环境需要 +world_size = int(os.getenv('WORLD_SIZE', 1)) +if world_size > 1: + sp_degree = world_size // 2 + parallel_config = ParallelConfig(sp_degree=sp_degree, use_cfg_parallel=True, + world_size=world_size) + init_parallel_env(parallel_config) +rank = dist.get_rank() + + +# 导入相关模型 +model_path = '/data/Open-Sora-Plan-v1.2.0/93x720p' # 原始浮点模型路径 +model = OpenSoraT2V.from_pretrained(model_path, cache_dir="../cache_dir", + low_cpu_mem_usage=False, device_map=None, + torch_dtype=torch.bfloat16).to("npu") + +# 由于llm ptq接口限制,模型补充dtype属性 +if not hasattr(model, 'config'): + model.config = SimpleNamespace() # 使用轻量级命名空间 +if not hasattr(model.config, 'torch_dtype'): + model.config.torch_dtype = torch.bfloat16 +if not hasattr(model, "dtype"): + model.dtype = torch.bfloat16 + +# 2.导入校准数据,不涉及激活值量化和异常值抑制则不需要 +calib_datas = torch.load(f"/data/calib_datas.pt", map_location='cpu') +for calib_data in calib_datas: + for i, data in enumerate(calib_data): + if torch.is_tensor(data): + calib_data[i] = data.npu() + + +#填写量化配置 +quant_config = QuantConfig( + a_bit=8, + w_bit=8, + disable_names=None, + dev_type='npu', + dev_id=rank, + act_method=3, + pr=1.0, + w_sym=True, + mm_tensor=False, + is_dynamic = True, +).fa_quant(fa_amp=0) # fa_quant只有fa量化才需要,fa_amp可设置自动回退层数 + +# 2.执行校准,不需要校准数据的场景不需要传calib_data +calibrator = Calibrator(model, quant_config, calib_data=calib_datas, disable_level='L0', torch_dtype=torch.bfloat16) # disable_level: 自动回退n个linear +calibrator.run() # 执行PTQ量化校准 +# 3.多卡场景需要保存多个权重,按当前rank去区分名称 +calibrator.save('/data', safetensors_name=f'quant_model_weight_w8a8_dynamic_{rank}.safetensors',save_type=["safe_tensor"], json_name=f'quant_model_description_w8a8_dynamic_{rank}.json') + + +``` + +在调用Calibrator.run()方法后,构建Calibrator时传入的model会被替换为伪量化模型,可以直接调用进行前向推理,用来测试效果。 +如果伪量化结果不理想,可以参考以下方法进行调优: + + + +# w8a8量化模型的调参配置步骤: +0.离群值抑制(AntiOutlier) -- 当前多模态生成的抑制算法和模型绑定,暂不开放 + +1.量化参数(QuantConfig) + +2.校准数据(calib_set) + +3.量化回退(disable_names) + + + +## 1 量化参数选择 +```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' +) +``` + + +可优化参数——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。 + +## 2 校准集调整 +1.当算法层面无法提升精度时,可以增大校准数据集,增加时间步和提示词的数量。 +(正常情况下,可以增加数据得到精度提升,但是到一定数据后,提高数据对精度影响有限。 +2.针对特定场景切换成应用场景的数据作为校准集。 +(在选取时需要考虑模型部署时的具体推理场景,例如中文模型需要使用中文输入作为校准集;英文模型使用英文输入;代码生成类模型则使用代码生成类任务;中英文兼顾的模型考虑使用中英文混合的校准集合)。 +3.剔除量化前后模型输出变化较大的数据作为校准集。 +4.[校准集如何获取和使用](https://gitee.com/ascend/ModelZoo-PyTorch/blob/master/MindIE/MultiModal/example/calib_datas_save.py)使用@save_args_decorator添加需要量化的类的推理入口,会自动dump每一个时间步的数据 + +```python + @save_args_decorator() + def forward( + self, + hidden_states: torch.Tensor, + timestep: Optional[torch.LongTensor] = None, + encoder_hidden_states: Optional[torch.Tensor] = None, + added_cond_kwargs: Dict[str, torch.Tensor] = None, + class_labels: Optional[torch.LongTensor] = None, + cross_attention_kwargs: Dict[str, Any] = None, + attention_mask: Optional[torch.Tensor] = None, + encoder_attention_mask: Optional[torch.Tensor] = None, + step_id: int = 0, + use_image_num: Optional[int] = 0, + return_dict: bool = True, + ): +``` + +## 3 量化回退 +大模型需要量化的原因:模型量化可以降低模型大小,减少计算量,降低内存占用,提升推理速度。 + +大模型量化线性层的原因:大模型中的线性层层数多、权重数量庞大且存在矩阵相乘(计算量大),通过量化线性层的权重和激活值,可以达到降低模型大小,减少计算量,降低内存占用,提升推理速度。 + +量化回退的原因:某些线性层对于量化比较敏感,量化后会带来一定的精度损失,这些层是不太适合量化的,应该使用浮点数进行计算,这个过程称之为回退,可以通过设置disable_names控制哪些线性层应该被回退。 + +怎么判定敏感:终端的打印日志中会显示每一层算子激活量化输入的range_parm数值,range_parm数值越大越敏感。 +终端打印日志示例: +```python +时间戳 - 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_feaTrues, 数值小的就是下采样层)。 +2、回退o_proj层(通常精度敏感):是self_attention中调的最后一个线性层,(model中打出来的只是初始化时的顺序,要去模型代码里看实际调用顺序) 。 +3、根据理论经验或终端打印日志中的range_parm数值大小找出量化敏感层进行回退。 + +如下示例为手动回退chatglm2-6b的所有down_proj层: + +```python +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设置的数量超过模型层数就是全部回退,并且也不报错。 + + +# FA3量化 +## fa3量化 + +**Flash Attention 3(FA)**,增强了在硬件设备上的利用率,提升了整体在推理场景中的计算效率,以低精度的数据格式完成更快的处理和更少的内存占用。 + +## 前提条件 + +说明:仅Atlas 800I A2推理产品支持fa3量化功能 + +## 功能实现流程 + +关键步骤说明如下: + +### 1.工具导出权重时需要在模型手动插入fa伪量化算子修改modeling文件: +(1)确定Attention是否满足工具限制: + +需确定模型基于哪一个Attention进行实现,以open sora1.2为例,Attention模块为Attention类,由于当前工具实现限制,需确认类是否满足类名中包含Attention,以及Attention中包含类似diffusor库的heads,inner_dim属性 + +(2)保存校准数据 +fa3量化需要校准数据 + +(3)修改modeling文件: + +- 添加伪量化算子定义: + +```python +class Attention(Attention_): + def __init__(self, downsampler, attention_mode, use_rope, interpolation_scale_thw, **kwags): + # ---定义伪量化算子 + from msmodelslim.pytorch.llm_ptq.llm_ptq_tools.fa_quant import FAQuantizer + from msmodelslim import logger + self.fa_quantizer = FAQuantizer(logger=logger) + # ---定义伪量化算子 + processor = AttnProcessor2_0(attention_mode=attention_mode, use_rope=use_rope, + interpolation_scale_thw=interpolation_scale_thw) +``` +- 在浮点fa前插入伪量化算子,需要输入考虑并行算法后的num_head和head_dim +```python +query = attn.fa_quantizer.quant(query, qkv="q", num_head=attn.heads // sp_size, head_dim=head_dim, num_kv_head=attn.heads // sp_size) + +key = attn.fa_quantizer.quant(key, qkv="k", num_head=attn.heads // sp_size, head_dim=head_dim, num_kv_head=attn.heads // sp_size) + +value = attn.fa_quantizer.quant(value, qkv="v",num_head=attn.heads // sp_size, head_dim=head_dim, num_kv_head=attn.heads // sp_size) + +hidden_states = torch_npu.npu_fusion_attention(query, key, value,... +``` + +### 2.配置导出量化权重的config: + +`config = QuantConfig().fa_quant()` + +在QuantConfig初始化中完成核心参数`(w_bit,a_bit,disable_names,disable_last_linear,dev_type,dev_id)`的配置后,如果需要使用FA量化的新特性,通过调用QuantConfig的`fa_quant` 函数完成配置。 + +具体的参数说明如下: + +| **量化类型** | **需要配置的参数列表** | **调用示例** | +| ------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| fa_quant(fa_amp=5) | fa_amp用于配置自动精度回退,根据想要回退的layer的数量来设置。
数据类型为int,默认值为0。数据取值范围是大于等于0,并且小于等于模型layer数量,如果超出模型的layer数量将会取模型的最大layer数量为回退层数。 | quant_config=QuantConfig(w_bit=8, a_bit=8, disable_names=disable_names,dev_type='npu',dev_id=0).fa_quant(fa_amp=5)| + +- **safetensors格式** + +当save_type设置为['safe_tensor']时,量化权重会保存为safetensors文件和json描述文件。 + +- safetensors中储存格式为字典,包含量化权重和量化不修改的浮点权重。其中量化权重的key值为各层Linear的名字加上对应权重的名字,module.weight和module.bias对应anti_fp_norm.npy,weight对应quant_weight.npy,quant_bias对应quant_bias.npy等以此类推。例如Qwen2.5-7B模型的model.layers.0.self_attn.q_proj.deq_scale对应npy格式权重中deq_scale.npy中的model.layers.0.self_attn.q_proj; + +```python +# qwen模型量化生成的权重文件部分内容 +{ + "model.embed_tokens.weight": tensor([...]), + "model.layers.0.self_attn.q_proj.weight": tensor([...]), + "model.layers.0.self_attn.q_proj.input_scale": tensor([...]), + "model.layers.0.self_attn.q_proj.input_offset": tensor([...]), + "model.layers.0.self_attn.q_proj.quant_bias": tensor([...]), + "model.layers.0.self_attn.q_proj.deq_scale": tensor([...]), + "model.layers.0.self_attn.k_proj.weight": tensor([...]), + ... + "model.layers.0.self_attn.fa_q.scale": tensor([...]), + "model.layers.0.self_attn.fa_q.offset": tensor([...]), + "model.layers.0.self_attn.fa_k.scale": tensor([...]), + "model.layers.0.self_attn.fa_k.offset": tensor([...]), + "model.layers.0.self_attn.fa_v.scale": tensor([...]), + "model.layers.0.self_attn.fa_v.offset": tensor([...]), + ... +} +``` + + +- json描述文件中储存的量化权重的总体类型model_quant_type,是否启用fa3量化fa_quant_type,和其中各个权重的类型,来自原始浮点权重则为FLOAT,来自W8A8量化则为W8A8。 + +```python +{ + "model_quant_type": "W8A8", # 整体量化类型为w8a8量化 + "fa_quant_type": "FAQuant", # 量化过程开启了fa3量化 + "model.embed_tokens.weight": "FLOAT", # 来自原始浮点模型的embed_tokens权重 + "model.layers.0.self_attn.q_proj.weight": "W8A8", # 量化新增的第0层self_attn.q_proj的quant_weight + "model.layers.0.self_attn.q_proj.input_scale": "W8A8", # 量化新增的第0层self_attn.q_proj的input_scale + "model.layers.0.self_attn.q_proj.input_offset": "W8A8", # 量化新增的第0层self_attn.q_proj的input_offset + "model.layers.0.self_attn.q_proj.quant_bias": "W8A8", # 量化新增的第0层self_attn.q_proj的quant_bias + "model.layers.0.self_attn.q_proj.deq_scale": "W8A8", # 量化新增的第0层self_attn.q_proj的deq_scale + "model.layers.0.self_attn.k_proj.weight": "W8A8", # 量化新增的第0层self_attn.k_proj的quant_weight + ... + "model.layers.0.self_attn.fa_q.scale": "FAQuant", # 量化新增的第0层self_attn的query_states的scale + "model.layers.0.self_attn.fa_q.offset": "FAQuant", # 量化新增的第0层self_attn的query_states的offset + "model.layers.0.self_attn.fa_k.scale": "FAQuant", # 量化新增的第0层self_attn的key_states的scale + "model.layers.0.self_attn.fa_k.offset": "FAQuant", # 量化新增的第0层self_attn的key_states的offset + "model.layers.0.self_attn.fa_v.scale": "FAQuant", # 量化新增的第0层self_attn的key_states的scale + "model.layers.0.self_attn.fa_v.offset": "FAQuant", # 量化新增的第0层self_attn的key_states的offset + ... +} +``` +### 3.推理时添加量化算子逻辑 +删除之前校准时用的伪量化算子,准备添加量化算子 + +当前fa3只支持self attention,在attrention forward逻辑中添加fa3分支,并在生成模型实例的地方添加统一的量化接口quantize, +可以根据判断是否回退自动添加fa3属性 + +fa3算子接收(ntoken n d)格式的qkv,以及一个seq_len标量,nTokens在Atlas 800I A2推理产品上为各batch上seq_len之和 +```python +if hasattr(attn, "fa3") and query.shape[0] == key.shape[0]: + seq_len = query.shape[0] + query = rearrange(query, "s b (n d) -> (s b) n d", n=6) # ntoken n d + key = rearrange(key, "s b (n d) -> (s b) n d", n=6) # ntoken n d + value = rearrange(value, "s b (n d) -> (s b) n d", n=6) # ntoken n d + hidden_states = attn.fa3(query, key, value, seq_len) + hidden_states = rearrange(hidden_states, "(s b) n d -> s b (n d)", n=6, b=1) + +else: + 浮点推理逻辑 +``` +### FA3精度调优 +可设置fa层回退,其余调优与matmul相同 + + +# 模型量化后的部署推理 +模型量化后,可基于MindIESD的量化接口进行推理,在模型实例获取后,调用接口加载。 \ No newline at end of file