注意
点击此处下载完整的示例代码
动态量化¶
创建日期:2020 年 4 月 14 日 | 最后更新:2024 年 9 月 30 日 | 最后验证:2024 年 11 月 5 日
在此 recipe 中,您将了解如何利用动态量化来加速 LSTM 风格循环神经网络的推理。这可以减小模型权重的大小并加快模型执行速度。
引言¶
在设计神经网络时,可以做出许多权衡。在模型开发和训练期间,您可以更改循环神经网络的层数和参数数量,并在准确性与模型大小和/或模型延迟或吞吐量之间进行权衡。这种更改可能需要大量时间和计算资源,因为您需要迭代模型训练。量化为您提供了一种在训练完成后对已知模型进行性能和模型准确性之间类似权衡的方法。
您可以在单个会话中尝试一下,肯定会显著减小模型大小,并且可能获得显著的延迟降低,而不会损失太多准确性。
什么是动态量化?¶
量化网络意味着将其转换为使用权重和/或激活的降低精度整数表示。这可以节省模型大小,并允许在 CPU 或 GPU 上使用更高吞吐量的数学运算。
当从浮点数转换为整数值时,本质上是将浮点数值乘以某个比例因子并将结果四舍五入为整数。不同的量化方法在确定该比例因子的方式上有所不同。
此处描述的动态量化的关键思想是,我们将根据运行时观察到的数据范围动态确定激活的比例因子。这确保了比例因子是经过“调整”的,以便尽可能保留每个观察到的数据集的信号。
另一方面,模型参数在模型转换期间是已知的,它们会提前转换并以 INT8 形式存储。
量化模型中的算术运算使用向量化 INT8 指令完成。累积通常使用 INT16 或 INT32 完成以避免溢出。如果下一层是量化的,则将此更高精度的值缩放回 INT8;如果用于输出,则转换为 FP32。
动态量化相对没有调整参数,这使得它非常适合作为将 LSTM 模型转换为部署的标准化部分添加到生产流水线中。
注意
此处采用方法的局限性
本 recipe 提供了 PyTorch 中动态量化功能及其工作流程的快速介绍。我们的重点是解释用于转换模型的特定函数。为了简洁和清晰起见,我们将进行一些重大的简化
您将从一个最小的 LSTM 网络开始
您将简单地使用随机隐藏状态初始化网络
您将使用随机输入测试网络
您不会在此教程中训练网络
您将看到此网络的量化形式比我们开始使用的浮点网络更小且运行更快
您将看到输出值与 FP32 网络的输出值大致在同一范围,但我们在此不演示在真实训练网络上的预期准确性损失
您将看到动态量化是如何完成的,并且能够看到内存使用和延迟时间的显著减少。关于该技术可以在训练后的 LSTM 上保持高水平模型准确性的演示留待更高级的教程。如果您想立即转向更严格的处理方法,请参阅高级动态量化教程。
步骤¶
此 recipe 包含 5 个步骤。
设置 - 在此您定义一个非常简单的 LSTM,导入模块,并建立一些随机输入张量。
执行量化 - 在此您实例化一个浮点模型,然后创建它的量化版本。
查看模型大小 - 在此您展示模型大小变小了。
查看延迟 - 在此您运行这两个模型并比较模型运行时(延迟)。
查看准确性 - 在此您运行这两个模型并比较输出。
1: 设置¶
这是一段直接的代码,用于设置 recipe 的其余部分。
我们在此导入的唯一模块是 torch.quantization,其中包含 PyTorch 的量化操作符和转换函数。我们还定义了一个非常简单的 LSTM 模型并设置了一些输入。
# 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 preprocessing or postprocessing
# inspired by
# `Sequence Models and Long Short-Term Memory Networks tutorial <https://pytorch.ac.cn/tutorials/beginner/nlp/sequence_models_tutorial.html`_, by Robert Guthrie
# and `Dynamic Quanitzation tutorial <https://pytorch.ac.cn/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))
2: 执行量化¶
现在我们来到有趣的部分。首先我们创建一个名为 float_lstm
的模型实例,然后我们将其量化。我们将使用 torch.quantization.quantize_dynamic 函数,该函数接收模型,然后是要量化的子模块列表(如果存在),然后是目标数据类型。此函数将原始模型的量化版本作为新模块返回。
就这么简单。
# 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)
3. 查看模型大小¶
我们已经量化了模型。这给我们带来了什么?首先,我们将 FP32 模型参数替换为 INT8 值(以及一些记录的比例因子)。这意味着存储和移动的数据量减少约 75%。使用默认值时,此处所示的减少量将小于 75%,但如果您增加模型大小(例如,可以将模型维度设置为 80),则随着存储模型大小越来越由参数值主导,减少量将趋向于 4 倍。
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))
4. 查看延迟¶
第二个好处是量化模型通常运行得更快。这是由于多种效应的组合,包括至少:
移动参数数据所需时间更少
更快的 INT8 操作
正如您将看到的那样,这个超简单网络的量化版本运行得更快。对于更复杂的网络通常也是如此,但俗话说“您的体验可能会有所不同”,具体取决于许多因素,包括模型的结构和您运行的硬件。
# compare the performance
print("Floating point FP32")
%timeit float_lstm.forward(inputs, hidden)
print("Quantized INT8")
%timeit quantized_lstm.forward(inputs,hidden)
5: 查看准确性¶
我们不会在此仔细查看准确性,因为我们正在使用随机初始化的网络而不是经过适当训练的网络。但是,我认为快速展示量化网络确实能产生与原始网络“大致相同”的输出张量是值得的。
如需更详细的分析,请参阅本 recipe 末尾引用的更高级教程。
# 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))
了解更多¶
我们解释了什么是动态量化,它带来了什么好处,并且您使用了 torch.quantization.quantize_dynamic()
函数快速量化了一个简单的 LSTM 模型。
这是对此材料的快速和高层次处理;如需更多详细信息,请继续学习 (Beta) 在 LSTM 词语言模型上进行动态量化教程。
其他资源¶
脚本总运行时间: ( 0 分钟 0.000 秒)