doc22_074.md 43.9 KB
Newer Older
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
1 2
# 量化

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
3 4 5 6 7 8
> 原文:[`pytorch.org/docs/stable/quantization.html`](https://pytorch.org/docs/stable/quantization.html)> 
>
> 译者:[飞龙](https://github.com/wizardforcel)
>
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
9 10 11 12 13

警告

量化处于测试阶段,可能会有变化。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
14
## 量化简介
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
15

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
16
量化是指在执行计算和存储张量时使用比浮点精度更低的比特宽度的技术。量化模型在张量上执行一些或所有操作时,使用降低的精度而不是完整精度(浮点)值。这样可以实现更紧凑的模型表示,并在许多硬件平台上使用高性能的矢量化操作。PyTorch 支持 INT8 量化,相比典型的 FP32 模型,可以将模型大小减小 4 倍,内存带宽需求减小 4 倍。与 FP32 计算相比,硬件对 INT8 计算的支持通常快 2 到 4 倍。量化主要是一种加速推断的技术,只支持量化操作符的前向传播。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
17

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
18
PyTorch 支持多种量化深度学习模型的方法。在大多数情况下,模型在 FP32 中训练,然后将模型转换为 INT8。此外,PyTorch 还支持量化感知训练,该训练模型在前向和后向传递中使用伪量化模块模拟量化误差。请注意,整个计算过程都是在浮点数中进行的。在量化感知训练结束时,PyTorch 提供转换函数将训练好的模型转换为较低精度。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
19

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
20
在较低级别,PyTorch 提供了一种表示量化张量并对其进行操作的方法。它们可以用于直接构建在较低精度中执行全部或部分计算的模型。还提供了更高级别的 API,其中包含将 FP32 模型转换为较低精度的典型工作流程,以最小化精度损失。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
21

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
22
## 量化 API 摘要
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
23

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
24
PyTorch 提供三种不同的量化模式:Eager 模式量化,FX 图模式量化(维护)和 PyTorch 2 导出量化。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
25

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
26
Eager 模式量化是一个测试功能。用户需要手动进行融合并指定量化和去量化发生的位置,它只支持模块而不支持功能。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
27

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
28
FX 图模式量化是 PyTorch 中的自动量化工作流程,目前是一个原型功能,自从我们有了 PyTorch 2 导出量化以来,它处于维护模式。它通过添加对功能的支持和自动化量化过程来改进 Eager 模式量化,尽管人们可能需要重构模型以使其与 FX 图模式量化兼容(使用`torch.fx`进行符号跟踪)。请注意,FX 图模式量化不适用于任意模型,因为模型可能无法进行符号跟踪,我们将把它集成到领域库中,如 torchvision,用户将能够使用 FX 图模式量化对支持的领域库中的模型进行量化。对于任意模型,我们将提供一般性指导,但要使其正常工作,用户可能需要熟悉`torch.fx`,特别是如何使模型进行符号跟踪。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
29

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
30
PyTorch 2 导出量化是新的完整图模式量化工作流程,作为 PyTorch 2.1 原型功能发布。随着 PyTorch 2 的推出,我们正在转向更好的解决方案,用于完整程序捕获(torch.export),因为与 FX 图模式量化使用的程序捕获解决方案(torch.fx.symbolic_trace)相比,它可以捕获更高比例(14K 模型上的 88.8%比 14K 模型上的 72.7%)。torch.export 仍然存在一些限制,涉及到一些 Python 结构,并需要用户参与以支持导出模型中的动态性,但总体而言,它是对以前的程序捕获解决方案的改进。PyTorch 2 导出量化是为 torch.export 捕获的模型构建的,考虑到建模用户和后端开发人员的灵活性和生产力。其主要特点是(1)可编程 API,用于配置如何对模型进行量化,可以扩展到更多用例(2)简化的用户体验,用于建模用户和后端开发人员,因为他们只需要与一个对象(量化器)交互,表达用户关于如何量化模型以及后端支持的意图(3)可选的参考量化模型表示,可以用整数操作表示量化计算,更接近硬件中实际发生的量化计算。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
31

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
32
鼓励新用户首先尝试 PyTorch 2 导出量化,如果效果不佳,用户可以尝试急切模式量化。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
33

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
34
以下表格比较了急切模式量化、FX 图模式量化和 PyTorch 2 导出量化之间的区别:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
35

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
36
|  | 急切模式量化 | FX 图模式量化 | PyTorch 2 导出量化 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
37 38 39 40 41
| --- | --- | --- | --- |
| 发布状态 | beta | 原型(维护) | 原型 |
| 运算符融合 | 手动 | 自动 | 自动 |
| 量化/去量化放置 | 手动 | 自动 | 自动 |
| 量化模块 | 支持 | 支持 | 支持 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
42
| 量化功能/Torch 操作 | 手动 | 自动 | 支持 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
43 44
| 自定义支持 | 有限支持 | 完全支持 | 完全支持 |
| 量化模式支持 | 训练后量化:静态、动态、仅权重量化感知训练:静态 | 训练后量化:静态、动态、仅权重量化感知训练:静态 | 由后端特定量化器定义 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
45
| 输入/输出模型类型 | `torch.nn.Module` | `torch.nn.Module`(可能需要一些重构以使模型与 FX 图模式量化兼容) | `torch.fx.GraphModule`(由`torch.export`捕获) |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
46 47 48 49 50 51 52 53 54

支持三种类型的量化:

1.  动态量化(权重量化,激活以浮点数读取/存储并量化计算)

1.  静态量化(权重量化,激活量化,训练后需要校准)

1.  静态量化感知训练(权重量化,激活量化,训练期间建模量化数值)

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
55
请查看我们的[PyTorch 量化介绍](https://pytorch.org/blog/introduction-to-quantization-on-pytorch/)博客文章,了解这些量化类型之间的权衡更全面的概述。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
56 57 58 59 60 61 62 63

动态量化和静态量化的运算符覆盖范围不同,详见下表。

|  | 静态量化 | 动态量化 |
| --- | --- | --- |
| nn.Linearnn.Conv1d/2d/3d | YY | YN |
| nn.LSTMnn.GRU | Y(通过自定义模块)N | YY |
| nn.RNNCellnn.GRUCellnn.LSTMCell | NNN | YYY |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
64
| nn.EmbeddingBag | Y(激活为 fp32) | Y |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
65 66
| nn.Embedding | Y | Y |
| nn.MultiheadAttention | Y(通过自定义模块) | 不支持 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
67
| 激活 | 广泛支持 | 保持不变,计算保持在 fp32 中 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
68 69 70

### 急切模式量化

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
71
有关量化流程的一般介绍,包括不同类型的量化,请参阅一般量化流程。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
72

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
73
#### 训练后动态量化
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
74

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
75
这是最简单的量化形式,其中权重在预先量化,但激活在推断期间动态量化。这适用于模型执行时间主要由从内存加载权重而不是计算矩阵乘法所主导的情况。这对于批量大小较小的 LSTM 和 Transformer 类型模型是真实的。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

图表:

```py
# original model
# all tensors and computations are in floating point
previous_layer_fp32 -- linear_fp32 -- activation_fp32 -- next_layer_fp32
                 /
linear_weight_fp32

# dynamically quantized model
# linear and LSTM weights are in int8
previous_layer_fp32 -- linear_int8_w_fp32_inp -- activation_fp32 -- next_layer_fp32
                     /
   linear_weight_int8 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
93
PTDQ API 示例:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

```py
import torch

# define a floating point model
class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.fc = torch.nn.Linear(4, 4)

    def forward(self, x):
        x = self.fc(x)
        return x

# create a model instance
model_fp32 = M()
# create a quantized model instance
model_int8 = torch.ao.quantization.quantize_dynamic(
    model_fp32,  # the original model
    {torch.nn.Linear},  # a set of layers to dynamically quantize
    dtype=torch.qint8)  # the target dtype for quantized weights

# run the model
input_fp32 = torch.randn(4, 4, 4, 4)
res = model_int8(input_fp32) 
```

要了解更多关于动态量化的信息,请参阅我们的[动态量化教程](https://pytorch.org/tutorials/recipes/recipes/dynamic_quantization.html)

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
123
#### 后训练静态量化
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
124

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
125
后训练静态量化(PTQ 静态)量化模型的权重和激活。它将激活融合到可能的前置层中。它需要使用代表性数据集进行校准,以确定激活的最佳量化参数。后训练静态量化通常用于 CNN 是典型用例的情况下,其中内存带宽和计算节省都很重要。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
126

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
127
在应用后训练静态量化之前,我们可能需要修改模型。请参阅急切模式静态量化的模型准备。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

图表:

```py
# original model
# all tensors and computations are in floating point
previous_layer_fp32 -- linear_fp32 -- activation_fp32 -- next_layer_fp32
                    /
    linear_weight_fp32

# statically quantized model
# weights and activations are in int8
previous_layer_int8 -- linear_with_activation_int8 -- next_layer_int8
                    /
  linear_weight_int8 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
145
PTSQ API 示例:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

```py
import torch

# define a floating point model where some layers could be statically quantized
class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        # QuantStub converts tensors from floating point to quantized
        self.quant = torch.ao.quantization.QuantStub()
        self.conv = torch.nn.Conv2d(1, 1, 1)
        self.relu = torch.nn.ReLU()
        # DeQuantStub converts tensors from quantized to floating point
        self.dequant = torch.ao.quantization.DeQuantStub()

    def forward(self, x):
        # manually specify where tensors will be converted from floating
        # point to quantized in the quantized model
        x = self.quant(x)
        x = self.conv(x)
        x = self.relu(x)
        # manually specify where tensors will be converted from quantized
        # to floating point in the quantized model
        x = self.dequant(x)
        return x

# create a model instance
model_fp32 = M()

# model must be set to eval mode for static quantization logic to work
model_fp32.eval()

# attach a global qconfig, which contains information about what kind
# of observers to attach. Use 'x86' for server inference and 'qnnpack'
# for mobile inference. Other quantization configurations such as selecting
# symmetric or asymmetric quantization and MinMax or L2Norm calibration techniques
# can be specified here.
# Note: the old 'fbgemm' is still available but 'x86' is the recommended default
# for server inference.
# model_fp32.qconfig = torch.ao.quantization.get_default_qconfig('fbgemm')
model_fp32.qconfig = torch.ao.quantization.get_default_qconfig('x86')

# Fuse the activations to preceding layers, where applicable.
# This needs to be done manually depending on the model architecture.
# Common fusions include `conv + relu` and `conv + batchnorm + relu`
model_fp32_fused = torch.ao.quantization.fuse_modules(model_fp32, [['conv', 'relu']])

# Prepare the model for static quantization. This inserts observers in
# the model that will observe activation tensors during calibration.
model_fp32_prepared = torch.ao.quantization.prepare(model_fp32_fused)

# calibrate the prepared model to determine quantization parameters for activations
# in a real world setting, the calibration would be done with a representative dataset
input_fp32 = torch.randn(4, 1, 4, 4)
model_fp32_prepared(input_fp32)

# Convert the observed model to a quantized model. This does several things:
# quantizes the weights, computes and stores the scale and bias value to be
# used with each activation tensor, and replaces key operators with quantized
# implementations.
model_int8 = torch.ao.quantization.convert(model_fp32_prepared)

# run the model, relevant calculations will happen in int8
res = model_int8(input_fp32) 
```

要了解更多关于静态量化的信息,请参阅[静态量化教程](https://pytorch.org/tutorials/advanced/static_quantization_tutorial.html)

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
214
#### 静态量化的量化感知训练
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
215

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
216
量化感知训练(QAT)在训练期间模拟量化的效果,从而使准确性比其他量化方法更高。我们可以对静态、动态或仅权重量化进行 QAT。在训练期间,所有计算都是在浮点数中进行的,通过 fake_quant 模块模拟量化的效果,通过夹紧和四舍五入来模拟 INT8 的效果。在模型转换后,权重和激活被量化,并且激活被融合到可能的前置层中。它通常与 CNN 一起使用,并且与静态量化相比,准确性更高。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
217

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
218
在应用后训练静态量化之前,我们可能需要修改模型。请参阅急切模式静态量化的模型准备。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240

图表:

```py
# original model
# all tensors and computations are in floating point
previous_layer_fp32 -- linear_fp32 -- activation_fp32 -- next_layer_fp32
                      /
    linear_weight_fp32

# model with fake_quants for modeling quantization numerics during training
previous_layer_fp32 -- fq -- linear_fp32 -- activation_fp32 -- fq -- next_layer_fp32
                           /
   linear_weight_fp32 -- fq

# quantized model
# weights and activations are in int8
previous_layer_int8 -- linear_with_activation_int8 -- next_layer_int8
                     /
   linear_weight_int8 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
241
QAT API 示例:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305

```py
import torch

# define a floating point model where some layers could benefit from QAT
class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        # QuantStub converts tensors from floating point to quantized
        self.quant = torch.ao.quantization.QuantStub()
        self.conv = torch.nn.Conv2d(1, 1, 1)
        self.bn = torch.nn.BatchNorm2d(1)
        self.relu = torch.nn.ReLU()
        # DeQuantStub converts tensors from quantized to floating point
        self.dequant = torch.ao.quantization.DeQuantStub()

    def forward(self, x):
        x = self.quant(x)
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        x = self.dequant(x)
        return x

# create a model instance
model_fp32 = M()

# model must be set to eval for fusion to work
model_fp32.eval()

# attach a global qconfig, which contains information about what kind
# of observers to attach. Use 'x86' for server inference and 'qnnpack'
# for mobile inference. Other quantization configurations such as selecting
# symmetric or asymmetric quantization and MinMax or L2Norm calibration techniques
# can be specified here.
# Note: the old 'fbgemm' is still available but 'x86' is the recommended default
# for server inference.
# model_fp32.qconfig = torch.ao.quantization.get_default_qconfig('fbgemm')
model_fp32.qconfig = torch.ao.quantization.get_default_qat_qconfig('x86')

# fuse the activations to preceding layers, where applicable
# this needs to be done manually depending on the model architecture
model_fp32_fused = torch.ao.quantization.fuse_modules(model_fp32,
    [['conv', 'bn', 'relu']])

# Prepare the model for QAT. This inserts observers and fake_quants in
# the model needs to be set to train for QAT logic to work
# the model that will observe weight and activation tensors during calibration.
model_fp32_prepared = torch.ao.quantization.prepare_qat(model_fp32_fused.train())

# run the training loop (not shown)
training_loop(model_fp32_prepared)

# Convert the observed model to a quantized model. This does several things:
# quantizes the weights, computes and stores the scale and bias value to be
# used with each activation tensor, fuses modules where appropriate,
# and replaces key operators with quantized implementations.
model_fp32_prepared.eval()
model_int8 = torch.ao.quantization.convert(model_fp32_prepared)

# run the model, relevant calculations will happen in int8
res = model_int8(input_fp32) 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
306
要了解更多关于量化感知训练,请参阅[QAT 教程](https://pytorch.org/tutorials/advanced/static_quantization_tutorial.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
307

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
308
#### 急切模式静态量化的模型准备
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
309 310 311 312 313

目前在进行急切模式量化之前需要对模型定义进行一些修改。这是因为目前量化是基于模块的。具体来说,对于所有量化技术,用户需要:

1.  将需要输出重新量化的操作(因此具有额外参数)从功能形式转换为模块形式(例如,使用`torch.nn.ReLU`而不是`torch.nn.functional.relu`)。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
314
1.  指定模型的哪些部分需要量化,可以通过在子模块上分配`.qconfig`属性或通过指定`qconfig_mapping`来实现。例如,设置`model.conv1.qconfig = None`意味着`model.conv`层不会被量化,设置`model.linear1.qconfig = custom_qconfig`意味着`model.linear1`的量化设置将使用`custom_qconfig`而不是全局 qconfig。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
315 316 317

对于量化激活的静态量化技术,用户需要额外执行以下操作:

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
318
1.  指定激活量化和去量化的位置。这是使用`QuantStub``DeQuantStub`模块完成的。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
319

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
320
1.  使用`FloatFunctional`将需要特殊处理以进行量化的张量操作包装成模块。例如,需要特殊处理以确定输出量化参数的操作如`add``cat`
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
321

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
322
1.  融合模块:将操作/模块组合成单个模块以获得更高的准确性和性能。这是使用`fuse_modules()`API 完成的,该 API 接受要融合的模块列表。我们目前支持以下融合:[Conv, Relu],[Conv, BatchNorm],[Conv, BatchNorm, Relu],[Linear, Relu]
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
323

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
324
### (原型-维护模式)FX 图模式量化[](#prototype-maintaince-mode-fx-graph-mode-quantization“跳转到此标题”)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
325

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
326
后训练量化中有多种量化类型(仅权重、动态和静态),配置通过 qconfig_mapping(prepare_fx 函数的参数)完成。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
327

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
328
FXPTQ API 示例:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390

```py
import torch
from torch.ao.quantization import (
  get_default_qconfig_mapping,
  get_default_qat_qconfig_mapping,
  QConfigMapping,
)
import torch.ao.quantization.quantize_fx as quantize_fx
import copy

model_fp = UserModel()

#
# post training dynamic/weight_only quantization
#

# we need to deepcopy if we still want to keep model_fp unchanged after quantization since quantization apis change the input model
model_to_quantize = copy.deepcopy(model_fp)
model_to_quantize.eval()
qconfig_mapping = QConfigMapping().set_global(torch.ao.quantization.default_dynamic_qconfig)
# a tuple of one or more example inputs are needed to trace the model
example_inputs = (input_fp32)
# prepare
model_prepared = quantize_fx.prepare_fx(model_to_quantize, qconfig_mapping, example_inputs)
# no calibration needed when we only have dynamic/weight_only quantization
# quantize
model_quantized = quantize_fx.convert_fx(model_prepared)

#
# post training static quantization
#

model_to_quantize = copy.deepcopy(model_fp)
qconfig_mapping = get_default_qconfig_mapping("qnnpack")
model_to_quantize.eval()
# prepare
model_prepared = quantize_fx.prepare_fx(model_to_quantize, qconfig_mapping, example_inputs)
# calibrate (not shown)
# quantize
model_quantized = quantize_fx.convert_fx(model_prepared)

#
# quantization aware training for static quantization
#

model_to_quantize = copy.deepcopy(model_fp)
qconfig_mapping = get_default_qat_qconfig_mapping("qnnpack")
model_to_quantize.train()
# prepare
model_prepared = quantize_fx.prepare_qat_fx(model_to_quantize, qconfig_mapping, example_inputs)
# training loop (not shown)
# quantize
model_quantized = quantize_fx.convert_fx(model_prepared)

#
# fusion
#
model_to_quantize = copy.deepcopy(model_fp)
model_fused = quantize_fx.fuse_fx(model_to_quantize) 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
391
请按照以下教程了解有关 FX 图模式量化的更多信息:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
392

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
393
+   [使用 FX 图模式量化的用户指南](https://pytorch.org/tutorials/prototype/fx_graph_mode_quant_guide.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
394

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
395
+   [FX 图模式后训练静态量化](https://pytorch.org/tutorials/prototype/fx_graph_mode_ptq_static.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
396

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
397
+   [FX 图模式后训练动态量化](https://pytorch.org/tutorials/prototype/fx_graph_mode_ptq_dynamic.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
398

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
399
### (原型)PyTorch 2 导出量化[](#prototype-pytorch-2-export-quantization“跳转到此标题”)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
400

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
401
API 示例:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451

```py
import torch
from torch.ao.quantization.quantize_pt2e import prepare_pt2e
from torch._export import capture_pre_autograd_graph
from torch.ao.quantization.quantizer import (
    XNNPACKQuantizer,
    get_symmetric_quantization_config,
)

class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(5, 10)

   def forward(self, x):
       return self.linear(x)

# initialize a floating point model
float_model = M().eval()

# define calibration function
def calibrate(model, data_loader):
    model.eval()
    with torch.no_grad():
        for image, target in data_loader:
            model(image)

# Step 1\. program capture
# NOTE: this API will be updated to torch.export API in the future, but the captured
# result shoud mostly stay the same
m = capture_pre_autograd_graph(m, *example_inputs)
# we get a model with aten ops

# Step 2\. quantization
# backend developer will write their own Quantizer and expose methods to allow
# users to express how they
# want the model to be quantized
quantizer = XNNPACKQuantizer().set_global(get_symmetric_quantization_config())
# or prepare_qat_pt2e for Quantization Aware Training
m = prepare_pt2e(m, quantizer)

# run calibration
# calibrate(m, sample_inference_data)
m = convert_pt2e(m)

# Step 3\. lowering
# lower to target backend 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
452
请按照以下教程开始 PyTorch 2 导出量化:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
453 454 455

建模用户:

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
456
+   [PyTorch 2 导出后训练量化](https://pytorch.org/tutorials/prototype/pt2e_quant_ptq.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
457

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
458
+   [通过电感器在 X86 后端进行 PyTorch 2 导出后训练量化](https://pytorch.org/tutorials/prototype/pt2e_quant_ptq_x86_inductor.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
459

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
460
+   [PyTorch 2 导出量化感知训练](https://pytorch.org/tutorials/prototype/pt2e_quant_qat.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
461 462 463

后端开发人员(请查看所有建模用户文档):

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
464
+   [如何为 PyTorch 2 导出量化器编写量化器](https://pytorch.org/tutorials/prototype/pt2e_quantizer.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
465 466 467 468 469 470 471 472 473

## 量化堆栈

量化是将浮点模型转换为量化模型的过程。因此,在高层次上,量化堆栈可以分为两部分:1)用于量化模型的构建块或抽象 2)用于将浮点模型转换为量化模型的量化流的构建块或抽象

### 量化模型

#### 量化张量

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
474
为了在 PyTorch 中进行量化,我们需要能够在张量中表示量化数据。量化张量允许存储量化数据(表示为 int8/uint8/int32)以及量化参数,如比例和零点。量化张量允许进行许多有用的操作,使量化算术变得容易,同时允许以量化格式序列化数据。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
475

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
476
PyTorch 支持对称和非对称的每个张量和每个通道量化。每个张量意味着张量中的所有值以相同的方式使用相同的量化参数进行量化。每个通道意味着对于每个维度,通常是张量的通道维度,张量中的值使用不同的量化参数进行量化。这样可以减少将张量转换为量化值时的误差,因为异常值只会影响它所在的通道,而不是整个张量。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505

通过将浮点张量转换为

[![_images/math-quantizer-equation.png](../Images/161907b1eaa52e48f126f8041595a277.png)](_images/math-quantizer-equation.png)

请注意,我们确保在量化后浮点数中的零不会出现错误,从而确保像填充这样的操作不会导致额外的量化误差。

以下是量化张量的一些关键属性:

+   QScheme(torch.qscheme):指定我们量化张量的方式的枚举

    +   torch.per_tensor_affine

    +   torch.per_tensor_symmetric

    +   torch.per_channel_affine

    +   torch.per_channel_symmetric

+   dtype(torch.dtype):量化张量的数据类型

    +   torch.quint8

    +   torch.qint8

    +   torch.qint32

    +   torch.float16

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
506
+   量化参数(根据 QScheme 的不同而变化):所选量化方式的参数
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
507

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
508
    +   torch.per_tensor_affine 将具有量化参数
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
509 510 511 512 513

        +   比例(浮点数)

        +   零点(整数)

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
514
    +   torch.per_channel_affine 将具有量化参数
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537

        +   每通道比例(浮点数列表)

        +   每通道零点(整数列表)

        +   轴(整数)

#### 量化和去量化

模型的输入和输出是浮点张量,但量化模型中的激活是量化的,因此我们需要运算符在浮点张量和量化张量之间进行转换。

+   量化(浮点->量化)

    +   torch.quantize_per_tensor(x,scale,zero_point,dtype)

    +   torch.quantize_per_channel(x,scales,zero_points,axis,dtype)

    +   torch.quantize_per_tensor_dynamic(x,dtype,reduce_range)

    +   to(torch.float16)

+   去量化(量化->浮点)

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
538
    +   quantized_tensor.dequantize() - 在 torch.float16 张量上调用 dequantize 将张量转换回 torch.float
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
539 540 541 542 543 544 545

    +   torch.dequantize(x)

#### 量化运算符/模块[](#量化运算符模块“到此标题的永久链接”)

+   量化运算符是将量化张量作为输入的运算符,并输出量化张量。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
546
+   量化模块是执行量化操作的 PyTorch 模块。它们通常用于加权操作,如线性和卷积。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
547 548 549

#### 量化引擎

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
550
当执行量化模型时,qengine(torch.backends.quantized.engine)指定要用于执行的后端。重要的是要确保 qengine 与量化模型在量化激活和权重的值范围方面是兼容的。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
551 552 553

### 量化流程

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
554
#### 观察器和 FakeQuantize
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
555

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
556
+   观察器是 PyTorch 模块,用于:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
557 558 559 560 561

    +   收集张量统计信息,如通过观察器传递的张量的最小值和最大值

    +   并根据收集的张量统计数据计算量化参数

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
562
+   FakeQuantize 是 PyTorch 模块,用于:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
563 564 565 566 567 568 569

    +   在网络中为张量模拟量化(执行量化/去量化)

    +   它可以根据观察器收集的统计数据计算量化参数,也可以学习量化参数

#### QConfig

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
570
+   QConfig 是 Observer 或 FakeQuantize 模块类的命名元组,可以配置 qscheme、dtype 等。它用于配置如何观察运算符
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
571 572 573 574 575 576 577 578 579 580 581 582 583

    +   运算符/模块的量化配置

        +   不同类型的观察器/FakeQuantize

        +   dtype

        +   qscheme

        +   quant_min/quant_max:可用于模拟低精度张量

    +   当前支持激活和权重的配置

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
584
    +   我们根据为给定运算符或模块配置的 qconfig 插入输入/权重/输出观察器
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619

#### 一般量化流程

一般来说,流程如下

+   准备

    +   基于用户指定的 qconfig 插入 Observer/FakeQuantize 模块

+   校准/训练(取决于后训练量化或量化感知训练)

    +   允许 Observer 收集统计信息或 FakeQuantize 模块学习量化参数

+   转换

    +   将校准/训练好的模型转换为量化模型

有不同的量化模式,它们可以按两种方式分类:

在我们应用量化流程的位置方面,我们有:

1.  后训练量化(在训练后应用量化,量化参数是基于样本校准数据计算的)

1.  量化感知训练(在训练过程中模拟量化,以便量化参数可以与使用训练数据训练的模型一起学习)

在我们量化运算符的方式方面,我们可以有:

+   仅权重量化(仅权重是静态量化的)

+   动态量化(权重静态量化,激活动态量化)

+   静态量化(权重和激活均为静态量化)

我们可以在同一量化流程中混合不同的量化运算符方式。例如,我们可以有后训练量化,其中既有静态量化的运算符,也有动态量化的运算符。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
620
## 量化支持矩阵
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640

### 量化模式支持

|  | 量化模式 | 数据集要求 | 最适用于 | 精度 | 注释 |
| --- | --- | --- | --- | --- | --- |
| 后训练量化 | 动态/仅权重量化 | 激活动态量化(fp16,int8)或未量化,权重静态量化(fp16,int8,in4) | 无 | LSTM,MLP,嵌入,Transformer | 良好 | 在性能受计算或内存限制的情况下,易于使用,接近静态量化时的性能 |
| 静态量化 | 激活和权重静态量化(int8) | 校准数据集 | CNN | 良好 | 提供最佳性能,可能对精度有很大影响,适用于仅支持 int8 计算的硬件 |
| 量化感知训练 | 动态量化 | 激活和权重均为虚假量化 | 微调数据集 | MLP,嵌入 | 最佳 | 目前支持有限 |
| 静态量化 | 激活和权重均为虚假量化 | 微调数据集 | CNN,MLP,嵌入 | 最佳 | 通常在静态量化导致精度下降时使用,并用于缩小精度差距 |

请查看我们的[Pytorch 量化简介](https://pytorch.org/blog/introduction-to-quantization-on-pytorch/)博客文章,以获取更全面的这些量化类型之间权衡的概述。

### 量化流程支持

PyTorch 提供两种量化模式:Eager Mode Quantization 和 FX Graph Mode Quantization。

Eager Mode Quantization 是一个测试功能。用户需要手动进行融合并指定量化和去量化发生的位置,它仅支持模块而不支持功能。

FX Graph Mode Quantization 是 PyTorch 中的自动量化框架,目前是一个原型功能。它通过添加对功能的支持和自动化量化过程来改进 Eager Mode Quantization,尽管人们可能需要重构模型以使其与 FX Graph Mode Quantization 兼容(通过 `torch.fx` 进行符号跟踪)。请注意,FX Graph Mode Quantization 不适用于任意模型,因为模型可能无法进行符号跟踪,我们将把它集成到领域库中,如 torchvision,并且用户将能够使用 FX Graph Mode Quantization 对类似于受支持领域库中的模型进行量化。对于任意模型,我们将提供一般性指导,但要使其正常工作,用户可能需要熟悉 `torch.fx`,特别是如何使模型符号可跟踪。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
641
鼓励量化的新用户首先尝试 FX 图模式量化,如果不起作用,用户可以尝试遵循[使用 FX 图模式量化](https://pytorch.org/tutorials/prototype/fx_graph_mode_quant_guide.html)的指南或回退到急切模式量化。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
642

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
643
以下表格比较了急切模式量化和 FX 图模式量化之间的差异。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
644

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
645
|  | 急切模式量化 | FX 图模式量化 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
646 647 648 649 650 651 652 653
| --- | --- | --- |
| 发布状态 | beta | 原型 |
| 运算符融合 | 手动 | 自动 |
| 量化/去量化放置 | 手动 | 自动 |
| 量化模块 | 支持 | 支持 |
| 量化功能/火炬操作 | 手动 | 自动 |
| 定制支持 | 有限支持 | 完全支持 |
| 量化模式支持 | 后训练量化:静态,动态,仅权重量化感知训练:静态 | 后训练量化:静态,动态,仅权重量化感知训练:静态 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
654
| 输入/输出模型类型 | `torch.nn.Module` | `torch.nn.Module`(可能需要一些重构以使模型与 FX 图模式量化兼容) |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
655 656 657

### 后端/硬件支持

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
658
| 硬件 | 内核库 | 急切模式量化 | FX 图模式量化 | 量化模式支持 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
659
| --- | --- | --- | --- | --- |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
660 661 662
| 服务器 CPU | fbgemm/onednn | 支持 | 所有支持 |
| 移动 CPU | qnnpack/xnnpack |
| 服务器 GPU | TensorRT(早期原型) | 不支持,需要图形 | 支持 | 静态量化 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
663

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
664
今天,PyTorch 支持以下用于高效运行量化运算符的后端:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
665

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
666
+   具有 AVX2 支持或更高版本的 x86 CPU(没有 AVX2,某些操作具有低效的实现),通过由[fbgemm](https://github.com/pytorch/FBGEMM)[onednn](https://github.com/oneapi-src/oneDNN)优化的 x86(请参阅[RFC](https://github.com/pytorch/pytorch/issues/83888)中的详细信息)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
667 668 669

+   ARM CPU(通常在移动/嵌入式设备中找到),通过[qnnpack](https://github.com/pytorch/pytorch/tree/main/aten/src/ATen/native/quantized/cpu/qnnpack)

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
670
+   (早期原型)通过[fx2trt](https://developer.nvidia.com/tensorrt)支持 NVidia GPU
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
671

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
672
#### 本机 CPU 后端注意事项
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
673

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
674
我们同时暴露了 x86 和 qnnpack,使用相同的本机 pytorch 量化运算符,因此我们需要额外的标志来区分它们。根据 PyTorch 构建模式自动选择 x86 和 qnnpack 的相应实现,尽管用户可以通过将 torch.backends.quantization.engine 设置为 x86 或 qnnpack 来覆盖此设置。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
675

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
676
在准备量化模型时,必须确保 qconfig 和用于量化计算的引擎与将执行模型的后端匹配。qconfig 控制量化过程中使用的观察者类型。qengine 控制在为线性和卷积函数和模块打包权重时使用 x86 还是 qnnpack 特定的打包函数。例如:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
677

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
678
x86 的默认设置:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
679 680 681 682 683 684 685 686 687 688 689

```py
# set the qconfig for PTQ
# Note: the old 'fbgemm' is still available but 'x86' is the recommended default on x86 CPUs
qconfig = torch.ao.quantization.get_default_qconfig('x86')
# or, set the qconfig for QAT
qconfig = torch.ao.quantization.get_default_qat_qconfig('x86')
# set the qengine to control weight packing
torch.backends.quantized.engine = 'x86' 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
690
qnnpack 的默认设置:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
691 692 693 694 695 696 697 698 699 700 701 702

```py
# set the qconfig for PTQ
qconfig = torch.ao.quantization.get_default_qconfig('qnnpack')
# or, set the qconfig for QAT
qconfig = torch.ao.quantization.get_default_qat_qconfig('qnnpack')
# set the qengine to control weight packing
torch.backends.quantized.engine = 'qnnpack' 
```

### 运算符支持

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
703
动态和静态量化之间的运算符覆盖范围在下表中捕获。请注意,对于 FX 图模式量化,相应的功能也受支持。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
704 705 706 707 708 709

|  | 静态量化 | 动态量化 |
| --- | --- | --- |
| nn.Linear | YY | YN |
| nn.LSTMnn.GRU | NN | YY |
| nn.RNNCellnn.GRUCellnn.LSTMCell | NNN | YYY |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
710
| nn.EmbeddingBag | Y(激活在 fp32 中) | Y |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
711 712
| nn.Embedding | Y | Y |
| nn.MultiheadAttention | 不支持 | 不支持 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
713
| 激活 | 广泛支持 | 保持不变,计算保持在 fp32 中 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
714

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
715
注意:这将很快更新一些从本机 backend_config_dict 生成的信息。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
716

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
717
## 量化 API 参考
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
718

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
719
量化 API 参考包含有关量化 API 的文档,例如量化传递、量化张量操作以及支持的量化模块和函数。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
720 721 722

## 量化后端配置

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
723
量化后端配置包含有关如何为各种后端配置量化工作流程的文档。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
724 725 726

## 量化精度调试

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
727
量化精度调试包含有关如何调试量化精度的文档。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
728 729 730 731 732 733 734

## 量化定制

尽管提供了默认的观察者实现来根据观察到的张量数据选择比例因子和偏差,但开发人员可以提供自己的量化函数。量化可以选择性地应用于模型的不同部分,或者为模型的不同部分进行不同的配置。

我们还为**conv1d()****conv2d()****conv3d()****linear()**提供了通道量化支持。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
735
量化工作流程通过向模型的模块层次结构添加(例如,将观察者添加为`.observer`子模块)或替换(例如,将`nn.Conv2d`转换为`nn.quantized.Conv2d`)子模块来实现。这意味着模型在整个过程中保持常规的基于`nn.Module`的实例,因此可以与 PyTorch 的其余 API 一起使用。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
736

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
737
### 量化定制模块 API
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
738

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
739
Eager 模式和 FX 图模式的量化 API 提供了一个钩子,供用户以自定义方式指定模块的量化,具有用户定义的观察和量化逻辑。用户需要指定:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
740

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
741
1.  源 fp32 模块的 Python 类型(存在于模型中)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
742

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
743
1.  观察模块的 Python 类型(由用户提供)。该模块需要定义一个`from_float`函数,该函数定义了如何从原始 fp32 模块创建观察模块。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
744

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
745
1.  量化模块的 Python 类型(由用户提供)。该模块需要定义一个`from_observed`函数,该函数定义了如何从观察到的模块创建量化模块。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
746

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
747
1.  描述(1)、(2)、(3)的配置,传递给量化 API。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
748 749 750 751 752 753 754

然后框架将执行以下操作:

1.  在准备模块交换期间,它将使用(2)中类的`from_float`函数,将(1)中指定类型的每个模块转换为(2)中指定类型。

1.  在转换模块交换期间,它将使用(3)中的类的`from_observed`函数,将(2)中指定类型的每个模块转换为(3)中指定类型。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
755
目前,ObservedCustomModule 将具有单个张量输出的要求,并且观察者将由框架(而不是用户)添加到该输出上。观察者将作为自定义模块实例的属性存储在`activation_post_process`键下。在未来可能会放宽这些限制。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
756

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
757
自定义 API 示例:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856

```py
import torch
import torch.ao.nn.quantized as nnq
from torch.ao.quantization import QConfigMapping
import torch.ao.quantization.quantize_fx

# original fp32 module to replace
class CustomModule(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = torch.nn.Linear(3, 3)

    def forward(self, x):
        return self.linear(x)

# custom observed module, provided by user
class ObservedCustomModule(torch.nn.Module):
    def __init__(self, linear):
        super().__init__()
        self.linear = linear

    def forward(self, x):
        return self.linear(x)

    @classmethod
    def from_float(cls, float_module):
        assert hasattr(float_module, 'qconfig')
        observed = cls(float_module.linear)
        observed.qconfig = float_module.qconfig
        return observed

# custom quantized module, provided by user
class StaticQuantCustomModule(torch.nn.Module):
    def __init__(self, linear):
        super().__init__()
        self.linear = linear

    def forward(self, x):
        return self.linear(x)

    @classmethod
    def from_observed(cls, observed_module):
        assert hasattr(observed_module, 'qconfig')
        assert hasattr(observed_module, 'activation_post_process')
        observed_module.linear.activation_post_process = \
            observed_module.activation_post_process
        quantized = cls(nnq.Linear.from_float(observed_module.linear))
        return quantized

#
# example API call (Eager mode quantization)
#

m = torch.nn.Sequential(CustomModule()).eval()
prepare_custom_config_dict = {
    "float_to_observed_custom_module_class": {
        CustomModule: ObservedCustomModule
    }
}
convert_custom_config_dict = {
    "observed_to_quantized_custom_module_class": {
        ObservedCustomModule: StaticQuantCustomModule
    }
}
m.qconfig = torch.ao.quantization.default_qconfig
mp = torch.ao.quantization.prepare(
    m, prepare_custom_config_dict=prepare_custom_config_dict)
# calibration (not shown)
mq = torch.ao.quantization.convert(
    mp, convert_custom_config_dict=convert_custom_config_dict)
#
# example API call (FX graph mode quantization)
#
m = torch.nn.Sequential(CustomModule()).eval()
qconfig_mapping = QConfigMapping().set_global(torch.ao.quantization.default_qconfig)
prepare_custom_config_dict = {
    "float_to_observed_custom_module_class": {
        "static": {
            CustomModule: ObservedCustomModule,
        }
    }
}
convert_custom_config_dict = {
    "observed_to_quantized_custom_module_class": {
        "static": {
            ObservedCustomModule: StaticQuantCustomModule,
        }
    }
}
mp = torch.ao.quantization.quantize_fx.prepare_fx(
    m, qconfig_mapping, torch.randn(3,3), prepare_custom_config=prepare_custom_config_dict)
# calibration (not shown)
mq = torch.ao.quantization.quantize_fx.convert_fx(
    mp, convert_custom_config=convert_custom_config_dict) 
```

## 最佳实践

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
857
1. 如果您正在使用`x86`后端,我们需要使用 7 位而不是 8 位。确保您减少`quant_min``quant_max`的范围,例如,如果`dtype``torch.quint8`,请确保将自定义的`quant_min`设置为`0``quant_max`设置为`127``255` / `2`);如果`dtype``torch.qint8`,请确保将自定义的`quant_min`设置为`-64``-128` / `2`),`quant_max`设置为`63``127` / `2`),如果您调用`torch.ao.quantization.get_default_qconfig(backend)``torch.ao.quantization.get_default_qat_qconfig(backend)`函数来获取`x86``qnnpack`后端的默认`qconfig`,我们已经正确设置了这些。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
858

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
859
2. 如果选择了`onednn`后端,将在默认的 qconfig 映射`torch.ao.quantization.get_default_qconfig_mapping('onednn')`和默认的 qconfig`torch.ao.quantization.get_default_qconfig('onednn')`中使用 8 位激活。建议在支持向量神经网络指令(VNNI)的 CPU 上使用。否则,将激活的观察者的`reduce_range`设置为 True,以在没有 VNNI 支持的 CPU 上获得更好的准确性。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
860

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
861
## 常见问题
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
862

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
863
1.  如何在 GPU 上进行量化推断?:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
864

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
865
    我们目前还没有官方的 GPU 支持,但这是一个积极开发的领域,您可以在[这里](https://github.com/pytorch/pytorch/issues/87395)找到更多信息
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
866

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
867
1.  我如何为我的量化模型获得 ONNX 支持?
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
868

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
869
    如果在导出模型时出现错误(使用`torch.onnx`下的 API),您可以在 PyTorch 存储库中打开一个问题。在问题标题前加上`[ONNX]`并将问题标记为`module: onnx`。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
870

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
871
    如果您在 ONNX Runtime 中遇到问题,请在[GitHub - microsoft/onnxruntime](https://github.com/microsoft/onnxruntime/issues/)上打开一个问题。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
872

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
873
1.  如何在 LSTM 中使用量化?:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
874

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
875
    LSTM 通过我们的自定义模块 API 在急切模式和 fx 图模式量化中得到支持。示例可以在急切模式中找到:[pytorch/test_quantized_op.py TestQuantizedOps.test_custom_module_lstm](https://github.com/pytorch/pytorch/blob/9b88dcf248e717ca6c3f8c5e11f600825547a561/test/quantization/core/test_quantized_op.py#L2782) FX 图模式中:[pytorch/test_quantize_fx.py TestQuantizeFx.test_static_lstm](https://github.com/pytorch/pytorch/blob/9b88dcf248e717ca6c3f8c5e11f600825547a561/test/quantization/fx/test_quantize_fx.py#L4116)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
876 877 878

## 常见错误

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
879
### 将一个非量化的张量传递给一个量化的内核
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
880 881 882 883 884 885 886

如果您看到类似以下错误:

```py
RuntimeError: Could not run 'quantized::some_operator' with arguments from the 'CPU' backend... 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
887
这意味着您正在尝试将一个非量化的张量传递给一个量化的内核。一个常见的解决方法是使用`torch.ao.quantization.QuantStub`来对张量进行量化。这在 Eager 模式量化中需要手动完成。一个端到端的例子:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903

```py
class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.quant = torch.ao.quantization.QuantStub()
        self.conv = torch.nn.Conv2d(1, 1, 1)

    def forward(self, x):
        # during the convert step, this will be replaced with a
        # `quantize_per_tensor` call
        x = self.quant(x)
        x = self.conv(x)
        return x 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
904
### 将一个量化的张量传递给一个非量化的内核
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
905 906 907 908 909 910 911

如果您看到类似以下错误:

```py
RuntimeError: Could not run 'aten::thnn_conv2d_forward' with arguments from the 'QuantizedCPU' backend. 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
912
这意味着您正在尝试将一个量化的张量传递给一个非量化的内核。一个常见的解决方法是使用`torch.ao.quantization.DeQuantStub`来对张量进行去量化。这在 Eager 模式量化中需要手动完成。一个端到端的例子:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940

```py
class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.quant = torch.ao.quantization.QuantStub()
        self.conv1 = torch.nn.Conv2d(1, 1, 1)
        # this module will not be quantized (see `qconfig = None` logic below)
        self.conv2 = torch.nn.Conv2d(1, 1, 1)
        self.dequant = torch.ao.quantization.DeQuantStub()

    def forward(self, x):
        # during the convert step, this will be replaced with a
        # `quantize_per_tensor` call
        x = self.quant(x)
        x = self.conv1(x)
        # during the convert step, this will be replaced with a
        # `dequantize` call
        x = self.dequant(x)
        x = self.conv2(x)
        return x

m = M()
m.qconfig = some_qconfig
# turn off quantization for conv2
m.conv2.qconfig = None 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
941
### 保存和加载量化模型
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
942 943 944 945 946 947 948 949 950

在对一个量化模型调用`torch.load`时,如果出现类似以下错误:

```py
AttributeError: 'LinearPackedParams' object has no attribute '_modules' 
```

这是因为直接使用`torch.save``torch.load`保存和加载一个量化模型是不受支持的。要保存/加载量化模型,可以使用以下方法:

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
951
1.  保存/加载量化模型的 state_dict
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001

一个例子:

```py
class M(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(5, 5)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.linear(x)
        x = self.relu(x)
        return x

m = M().eval()
prepare_orig = prepare_fx(m, {'' : default_qconfig})
prepare_orig(torch.rand(5, 5))
quantized_orig = convert_fx(prepare_orig)

# Save/load using state_dict
b = io.BytesIO()
torch.save(quantized_orig.state_dict(), b)

m2 = M().eval()
prepared = prepare_fx(m2, {'' : default_qconfig})
quantized = convert_fx(prepared)
b.seek(0)
quantized.load_state_dict(torch.load(b)) 
```

1.  使用`torch.jit.save``torch.jit.load`保存/加载脚本化的量化模型

一个例子:

```py
# Note: using the same model M from previous example
m = M().eval()
prepare_orig = prepare_fx(m, {'' : default_qconfig})
prepare_orig(torch.rand(5, 5))
quantized_orig = convert_fx(prepare_orig)

# save/load using scripted model
scripted = torch.jit.script(quantized_orig)
b = io.BytesIO()
torch.jit.save(scripted, b)
b.seek(0)
scripted_quantized = torch.jit.load(b) 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
1002
### 在使用 FX 图模式量化时出现符号跟踪错误
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
1003

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
1004
符号跟踪是(原型-维护模式) FX 图模式量化的要求,因此如果您传递一个不能被符号跟踪的 PyTorch 模型到 torch.ao.quantization.prepare_fx 或 torch.ao.quantization.prepare_qat_fx,我们可能会看到以下类似的错误:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
1005 1006 1007 1008 1009

```py
torch.fx.proxy.TraceError: symbolically traced variables cannot be used as inputs to control flow 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
1010
请查看[符号跟踪的限制](https://pytorch.org/docs/2.0/fx.html#limitations-of-symbolic-tracing)并使用-[使用 FX 图模式量化的用户指南](https://pytorch.org/tutorials/prototype/fx_graph_mode_quant_guide.html)来解决问题。