本文已参加「新人创造礼」活动,一起开启创造之路。

本文首发于CSDN。

诸神缄默不语-个人CSDN博文目录

本文是PyTorch的教程Dynamic Quantization — PyTorch Tutorials 1.11.0+cu102 documentation的学习笔记。事实上由于我对该领域的不了解,本篇笔记基本上便是翻译+一点点我对其的了解。
本文介绍如何用Dynamic Quantization加快一个LSTM1模型的推理进程。Dynamic Quantization削减了模型权重的尺度,加快了模型处理进程。
本文代码及悉数输出已以jupyter notebook格局展示在GitHub上,能够直接下载运转:all-notes-in-one/dynamicquantization.ipynb at main PolarisRisingWar/all-notes-in-one(我所运用的环境是Python3.8+PyTorch1.8.2+cuda11.1(cudatoolkit),源是pytorch-lts,可是据我所知,别的大多数PyTorch版别也都支撑这套代码的运转)

@[toc]

1. 介绍

规划模型时需求权衡一些特征,如调整模型层数、RNN参数量,在准确率和performance(如模型尺度 和/或 model latency或throughput2)之间进行权衡。这样的改动可能会非常浪费时刻和核算资源,由于你需求重复迭代训练进程以得到试验成果。Quantization能够让你一个训练好的模型上做推理任务时,在performance和模型准确率之间完成相似的权衡。
它能够使你的模型尺度大幅下降,latency显著削减,可是模型准确率下降很少。

2. 什么是Dynamic Quantization?

Quantizing一个网络,是对网络本身进行转换,使其权重和/或激活函数运用更低精度的整数表明。这使model size减小,让CPU/GPU上能够用更高的throughput math operations(这句话我没看懂什么意思,总之应该是表明模型运转更快了)。
在从浮点数转换成整型时,你需求将浮点数乘以某一scale factor,将成果round到一个整数上。不同quantization办法的区别就在于选择scale factor的办法。
正如将在本文中描述的,dynamic quantization的中心思维,便是根据运转时观察到的数据规模来动态地(dynamically)决议激活函数的scale factor。这保证了scale factor是经过调整的,能够保存被观察到数据集尽可能多的信息。
模型参数在转换时已知,它们会被提前转换为INT8格局。
quantized model中的核算运用vectorized INT8 instructions执行。数据累积时一般运用INT16或INT32以防溢出。鄙人一层被量化、或为输出转化为FP32后,这个高精度值就会被scale回INT8。
Dynamic quantization调整参数的进程是相对自在的,这使它很适宜于被添加到production pipelines,作为布置LSTM模型的一个规范环节。
(留意:本文的介绍有所简化)

  1. 你需求一个小LSTM模型。
  2. 你用随机的hidden state初始化网络。
  3. 你在随机输入上测验网络。
  4. 你要在这个教程中训练网络。
  5. 你将会看到这个网络的quantized版别比咱们一开始的floating point版别更小、运算更快。
  6. 你会看到这个网络的输出值简直和FP32网络的输出值适当,可是咱们并不保证在实在的训练好的模型上希望的accuracy loss。(我觉得这个应该是说本教程没有证明在实在网络上,准确率、损失函数这些量化评价目标在改动后仍然能够坚持原水平)
    (对这一点的证明,教程中给出了另一个参阅材料,我还没看,今后也预备写相应教程的笔记:(beta) Dynamic Quantization on an LSTM Word Language Model — PyTorch Tutorials 1.11.0+cu102 documentation)

3. 代码进程

  1. Set Up:界说一个简略的LSTM,导入模块,创立一些随机输入张量
  2. Do the Quantization:初始化一个浮点数的模型,并创立一个它的quantized版。
  3. Look at Model Size:在这一步能够看到模型尺度有所变小。
  4. Look at Latency:在这一步运转2个模型,比较模型运转时刻(latency)。
  5. Look at Accuracy:在这一步运转2个模型,比较模型输出。

3.1 Set Up

# import the modules used here in this recipe
import torch
import torch.quantization
import torch.nn as nn
import copy
import os
import time
# define a very, very simple LSTM for demonstration purposes
# in this case, we are wrapping nn.LSTM, one layer, no pre or post processing
# inspired by
# https://pytorch.org/tutorials/beginner/nlp/sequence_models_tutorial.html, by Robert Guthrie
# and https://pytorch.org/tutorials/advanced/dynamic_quantization_tutorial.html
class lstm_for_demonstration(nn.Module):
  """Elementary Long Short Term Memory style model which simply wraps nn.LSTM
     Not to be used for anything other than demonstration.
  """
  def __init__(self,in_dim,out_dim,depth):
     super(lstm_for_demonstration,self).__init__()
     self.lstm = nn.LSTM(in_dim,out_dim,depth)
  def forward(self,inputs,hidden):
     out,hidden = self.lstm(inputs,hidden)
     return out, hidden
torch.manual_seed(29592)  # set the seed for reproducibility
#shape parameters
model_dimension=8
sequence_length=20
batch_size=1
lstm_depth=1
# random data for input
inputs = torch.randn(sequence_length,batch_size,model_dimension)
# hidden is actually is a tuple of the initial hidden state and the initial cell state
hidden = (torch.randn(lstm_depth,batch_size,model_dimension), torch.randn(lstm_depth,batch_size,model_dimension))

3.2 Do the Quantization

在这一部分,咱们将运用torch.quantization.quantize_dynamic()函数。
其入参为模型,想要quantize的submodules(如果出现的话),目标datatype,返回一个原模型的quantized版别(nn.Module类)。

 # here is our floating point instance
float_lstm = lstm_for_demonstration(model_dimension, model_dimension,lstm_depth)
# this is the call that does the work
quantized_lstm = torch.quantization.quantize_dynamic(
    float_lstm, {nn.LSTM, nn.Linear}, dtype=torch.qint8
)
# show the changes that were made
print('Here is the floating point version of this module:')
print(float_lstm)
print('')
print('and now the quantized version:')
print(quantized_lstm)

输出:

Here is the floating point version of this module:
lstm_for_demonstration(
  (lstm): LSTM(8, 8)
)
and now the quantized version:
lstm_for_demonstration(
  (lstm): DynamicQuantizedLSTM(8, 8)
)

3.3 Look at Model Size

现在咱们现已quantize了模型,将FP32的模型参数替换为INT8(和一些被记录的scale factors),这意味着咱们削减了75%左右的模型贮存空间。在本文所运用的默认值上的削减会小于75%,但如果你添加模型尺度(如设置model dimension到80),这个紧缩程度就会趋近于25%,由于此时模型尺度受参数值的影响更大。

#临时贮存模型,核算贮存空间,然后删除
def print_size_of_model(model, label=""):
    torch.save(model.state_dict(), "temp.p")
    size=os.path.getsize("temp.p")
    print("model: ",label,' \t','Size (KB):', size/1e3)
    os.remove('temp.p')
    return size
# compare the sizes
f=print_size_of_model(float_lstm,"fp32")
q=print_size_of_model(quantized_lstm,"int8")
print("{0:.2f} times smaller".format(f/q))

输出:

model:  fp32  	 Size (KB): 4.051
model:  int8  	 Size (KB): 2.963
1.37 times smaller

能够看到正如本节前文所说,这个紧缩程度是大于25%的。

3.4 Look at Latency

quantized模型也会运转更快。这是由于:

  1. 搬运参数花费的时刻更少。
  2. INT8的运算操作更快。

在这个简易版模型上你就能看到速度的提高(这是文档原话,但其实试验成果是原模型运转更快……),在复杂模型上一般会提高更多。但影响latency的原因很多。

原模型:

print("Floating point FP32")
%timeit float_lstm.forward(inputs, hidden)

输出:

Floating point FP32
830 s  9.39 s per loop (mean  std. dev. of 7 runs, 1000 loops each)

quantized模型:

print("Quantized INT8")
%timeit quantized_lstm.forward(inputs,hidden)

输出:

Quantized INT8
913 s  13.2 s per loop (mean  std. dev. of 7 runs, 1000 loops each)

3.5 Look at Accuracy

由于模型是随机初始化而非经过训练的,所以咱们就不严厉核算准确率的改动程度了(由于没有意义)。可是咱们能够迅速、简略看一下quantized网络能够输出跟原网络差不多的成果。
更多剖析请参阅本文末说到的进阶教程。

核算输出的平均值,经比较后发现差异很小:

# run the float model
out1, hidden1 = float_lstm(inputs, hidden)
mag1 = torch.mean(abs(out1)).item()
print('mean absolute value of output tensor values in the FP32 model is {0:.5f} '.format(mag1))
# run the quantized model
out2, hidden2 = quantized_lstm(inputs, hidden)
mag2 = torch.mean(abs(out2)).item()
print('mean absolute value of output tensor values in the INT8 model is {0:.5f}'.format(mag2))
# compare them
mag3 = torch.mean(abs(out1-out2)).item()
print('mean absolute value of the difference between the output tensors is {0:.5f} or {1:.2f} percent'.format(mag3,mag3/mag1*100))

输出:

mean absolute value of output tensor values in the FP32 model is 0.12887
mean absolute value of output tensor values in the INT8 model is 0.12912
mean absolute value of the difference between the output tensors is 0.00156 or 1.21 percent

6. 教程中供给的其他参阅材料

  1. (beta) Dynamic Quantization on an LSTM Word Language Model Tutorial:这一篇我现已方案要编撰学习笔记博文
  2. Quantization API Documentaion
  3. (beta) Dynamic Quantization on BERT:这一篇我现已方案要编撰学习笔记博文
  4. Introduction to Quantization on PyTorch | PyTorch

7. 其他我自己找的参阅材料

  1. 闲话模型紧缩之量化(Quantization)篇_ariesjzj的博客-CSDN博客_模型紧缩量化
  2. 模型量化综述及使用-云社区-华为云

这两篇都写得很好,由于我不太懂所以看不太出哪种更好,看起来第一篇要更精准、深入一些,第二篇更篇科普。如果我今后对模型量化这一领域需求进行更深了解的话,我会来阅览更多材料、了解更多信息的。

Footnotes

  1. 我后期方案编撰LSTM模型相关、尤其是在PyTorch上使用的博文,包含后文代码注释中的PyTorch官方教程。此处先留下位置,今后等我写了来补一下作为扩充阅览材料。 ↩

  2. model latency是模型每次处理一个单位的数据所需的时刻,单位为秒(时刻单位)。
    举例来说,在图画分类任务上,GoogleNet模型在Intel CascadeLake上分类一张图画需求0.057秒。
    throughput是模型在单位时刻内能够处理的数据量。如每秒图画数。
    此处仅对这两个概念做简略了解,对其更多了解请参阅其他参阅材料,本文编撰进程中所参阅的材料为:
    What is Latency in Machine Learning (ML)?
    PLASTER: A Framework for Deep Learning Performance ↩