pytorch动态量化bert实验

本文介绍了如何使用PyTorch动态量化技术对预训练的BERT模型进行压缩,以减小模型大小并提高推理速度。通过下载GLUE数据集、微调BERT模型、加载微调模型并进行动态量化,实验结果显示量化后模型的精度略有下降,但模型大小显著减小,推理速度得到提升。此外,通过调整线程数,进一步加速了量化模型的运行。实验还利用Tensorboard观察量化前后模型的参数分布,以理解量化对模型性能的影响。
摘要由CSDN通过智能技术生成

参考资料:

(BETA) DYNAMIC QUANTIZATION ON BERT:https://pytorch.org/tutorials/intermediate/dynamic_quantization_bert_tutorial.html

主要参考该教程,本文已指出源代码报错之处。

本实验的地址:https://github.com/zoetu/DynamicQuantization_Bert

实验目录:

在这里插入图片描述

1. 下载数据集

新建download_glue_data.py,代码参考如下链接内容:

参考:
https://gist.github.com/W4ngatang/60c2bdb54d156a41194446737ce03e2e
报错MRPC下载失败,将try部分注释,自行导入dev_ids.tsv映射文件。

!pwd
!ls
!python download_glue_data.py --data_dir='glue_data' --tasks='MRPC'
!ls glue_data/MRPC

下载的数据集目录:
在这里插入图片描述

2. 微调的BERT模型

下载地址:https://download.pytorch.org/tutorial/MRPC.zip

%wget https://download.pytorch.org/tutorial/MRPC.zip
%unzip MRPC.zip
%ls
%pwd

这里导入了教程训好的BERT模型,如果想自己微调的话,可以参考如下的参数:

export GLUE_DIR=./glue_data
export TASK_NAME=MRPC
export OUT_DIR=./$TASK_NAME/
python ./run_glue.py \
    --model_type bert \
    --model_name_or_path bert-base-uncased \
    --task_name $TASK_NAME \
    --do_train \
    --do_eval \
    --do_lower_case \
    --data_dir $GLUE_DIR/$TASK_NAME \
    --max_seq_length 128 \
    --per_gpu_eval_batch_size=8   \
    --per_gpu_train_batch_size=8   \
    --learning_rate 2e-5 \
    --num_train_epochs 3.0 \
    --save_steps 100000 \
    --output_dir $OUT_DIR

3. 加载微调的BERT模型

tokenizer = BertTokenizer.from_pretrained(
   configs.output_dir, do_lower_case=configs.do_lower_case)

model = BertForSequenceClassification.from_pretrained(configs.output_dir)
model.to(configs.device)

在这里插入图片描述

4. 使用动态量化

quantized_model = torch.quantization.quantize_dynamic(
    model, {torch.nn.Linear}, dtype=torch.qint8
)
print(quantized_model)

或者 dyte = torch.qfloat16

5. 评估

5.1 模型大小

def print_size_of_model(model):
    torch.save(model.state_dict(), "temp.p")
    print('Size (MB):', os.path.getsize("temp.p")/1e6)
    os.remove('temp.p')

print_size_of_model(model)
print_size_of_model(quantized_model)

结果:
在这里插入图片描述

5.2 正确率和时间

def time_model_evaluation(model, configs, tokenizer):
    eval_start_time = time.time()
    result = evaluate(configs, model, tokenizer, prefix="")
    eval_end_time = time.time()
    eval_duration_time = eval_end_time - eval_start_time
    print(result)
    print("Evaluate total time (seconds): {0:.1f}".format(eval_duration_time))

FP32 BERT model

time_model_evaluation(model, configs, tokenizer)

结果:在这里插入图片描述

INT8 BERT model after the dynamic quantization

time_model_evaluation(quantized_model, configs, tokenizer)

结果:
在这里插入图片描述

总结:模型压缩,时间减少,但是正确率变化不大

补充(增加线程加速)

另外,通过设置线程数可以进一步加速。
在这里插入图片描述

增加线程数=4

在这里插入图片描述

实验结果

FP32:
在这里插入图片描述
INT8:
在这里插入图片描述

实验结果总结

| Prec | F1 score | Model Size | 1 thread   | 4 threads | 
| FP32 |  0.9019  |   438 MB   | 214.2 sec  | 60.8 sec  |
| INT8 |  0.8977  |   181 MB   |  81.8 sec  | 24.4 sec  |

作者给出的压缩性能如下:
在这里插入图片描述

  • 1 thread:我评估FP32模型时间比原作者要长,但是压缩后的模型在我的实验环境下跑出来要快。
  • 4 thread:增加线程数,对同意模型的速度提升效果也比较好,INT8从81s --> 24s

补充:参数分布

使用Tensorboard查看曲线图和直方图

参考资料:【Pytorch】Tensorboard用法:标量曲线图、直方图、模型结构图

  1. load quant_model file
y = torch.load("./bert_quant.pt")
print(y)

在这里插入图片描述

  1. read parameters’ name and data of quant_model
for name, param in quantized_model.named_parameters():
    print(name,param.data)

在这里插入图片描述

读取指定参数数据

print(model.state_dict()['bert.encoder.layer.0.attention.self.key.weight'])
print(model.state_dict()['bert.encoder.layer.0.attention.self.key.bias'])
print(quantized_model.state_dict()['bert.encoder.layer.0.attention.self.key._packed_params._packed_params'])

在这里插入图片描述

  1. using TensorBoard
from torch.utils.tensorboard import SummaryWriter
# strat training
writer = SummaryWriter(log_dir='./log')
# for epoch in range(3):
    # 【关键代码】
for name, param in quantized_model.named_parameters():
    # writer.add_histogram(tag=name+'_grad', values=param.grad, global_step=epoch)
    writer.add_histogram(tag=name+'_data', values=param.data)   
writer.close()

Embedding词嵌入层权重值

在这里插入图片描述

Encoder attention层的权重值分布

在这里插入图片描述

Encoder output层的权重分布

在这里插入图片描述

补充知识:

如何使用Pytorch的量化功能?——https://cloud.tencent.com/developer/article/1785781

Dynamic Quantization 使用下面的 API 来完成模型的量化:

torch.quantization.quantize_dynamic(model, qconfig_spec=None, dtype=torch.qint8, mapping=None, inplace=False)

quantize_dynamic 这个 API 把一个 float model 转换为 dynamic quantized model,也就是只有权重被量化的 model,dtype 参数可以取值 float16 或者 qint8。当对整个模型进行转换时,默认只对以下的 op 进行转换:

  • Linear
  • LSTM
  • LSTMCell
  • RNNCell
  • GRUCell

因为 dynamic quantization只是把权重参数进行量化,而这些 layer 一般参数数量很大,在整个模型中参数量占比极高,因此边际效益高。对其它 layer进行 dynamic quantization 几乎没有实际的意义。
再来说说这个 API 的第二个参数:qconfig_spec:

  • qconfig_spec 指定了一组 qconfig,具体就是哪个 op 对应哪个 qconfig;

  • 每个 qconfig 是 QConfig 类的实例,封装了两个 observer;

  • 这两个 observer 分别是 activation 的 observer 和 weight 的 observer;

  • 但是动态量化使用的是 QConfig 子类 QConfigDynamic 的实例,该实例实际上只封装了 weight 的 observer;

  • activate 就是 post process,就是 op forward 之后的后处理,但在动态量化中不包含;

  • observer 用来根据四元组(min_val,max_val,qmin, qmax)来计算 2 个量化的参数:scale 和 zero_point;

  • qmin、qmax 是算法提前确定好的,min_val 和 max_val 是从输入数据中观察到的,所以起名叫 observer。

当 qconfig_spec 为 None 的时候就是默认行为,如果想要改变默认行为,则可以:

  • qconfig_spec 赋值为一个 set,比如:{nn.LSTM, nn.Linear},意思是指定当前模型中的哪些 layer 要被 dynamic quantization;
  • qconfig_spec 赋值为一个 dict,key 为 submodule 的 name 或 type,value 为 QConfigDynamic 实例(其包含了特定的 Observer,比如 MinMaxObserver、MovingAverageMinMaxObserver、PerChannelMinMaxObserver、MovingAveragePerChannelMinMaxObserver、HistogramObserver)。

事实上,当 qconfig_spec 为 None 的时候,quantize_dynamic API 就会使用如下的默认值:

qconfig_spec = {
                nn.Linear : default_dynamic_qconfig,
                nn.LSTM : default_dynamic_qconfig,
                nn.GRU : default_dynamic_qconfig,
                nn.LSTMCell : default_dynamic_qconfig,
                nn.RNNCell : default_dynamic_qconfig,
                nn.GRUCell : default_dynamic_qconfig,
            }

这就是 Gemfield 刚才提到的动态量化只量化 Linear 和 RNN 变种的真相。而default_dynamic_qconfig 是 QConfigDynamic 的一个实例,使用如下的参数进行构造:

default_dynamic_qconfig = QConfigDynamic(activation=default_dynamic_quant_observer, weight=default_weight_observer)
default_dynamic_quant_observer = PlaceholderObserver.with_args(dtype=torch.float, compute_dtype=torch.quint8)
default_weight_observer = MinMaxObserver.with_args(dtype=torch.qint8, qscheme=torch.per_tensor_symmetric)

其中,用于 activation 的 PlaceholderObserver 就是个占位符,啥也不做;
而用于 weight 的 MinMaxObserver 就是记录输入 tensor 中的最大值和最小值,用来计算 scale 和 zp。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zoetu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值