• 教程 >
  • (原型) PyTorch BackendConfig 教程
快捷方式

(原型) PyTorch BackendConfig 教程

創建於: 2023年1月3日 | 最後更新於: 2023年1月18日 | 最後驗證: 未驗證

作者: Andrew Or

BackendConfig API 使開發者能夠將其後端與 PyTorch 量化整合。目前它僅支援 FX 圖模式量化,但未來可能會擴充套件到其他量化模式。在本教程中,我們將演示如何使用此 API 為特定後端定製量化支援。有關 BackendConfig 動機和實現細節的更多資訊,請參閱此README

假設我們是一名後端開發者,希望將我們的後端與 PyTorch 的量化 API 整合。我們的後端僅包含兩個運算元:量化 Linear 和量化 Conv-ReLU。在本節中,我們將透過使用自定義 BackendConfig,透過 prepare_fxconvert_fx 量化示例模型來演示如何實現這一點。

import torch
from torch.ao.quantization import (
    default_weight_observer,
    get_default_qconfig_mapping,
    MinMaxObserver,
    QConfig,
    QConfigMapping,
)
from torch.ao.quantization.backend_config import (
    BackendConfig,
    BackendPatternConfig,
    DTypeConfig,
    DTypeWithConstraints,
    ObservationType,
)
from torch.ao.quantization.quantize_fx import prepare_fx, convert_fx

1. 推導每個量化運算元的參考模式

對於量化 Linear,假設我們的後端期望參考模式 [dequant - fp32_linear - quant] 並將其轉換為單個量化 Linear 運算元。實現此目的的方法是,首先在浮點 Linear 運算元之前和之後插入 quant-dequant 運算元,從而生成以下參考模型

quant1 - [dequant1 - fp32_linear - quant2] - dequant2

類似地,對於量化 Conv-ReLU,我們希望生成以下參考模型,其中方括號中的參考模式將被轉換為單個量化 Conv-ReLU 運算元

quant1 - [dequant1 - fp32_conv_relu - quant2] - dequant2

2. 設定具有後端約束的 DTypeConfigs

在上面的參考模式中,DTypeConfig 中指定的輸入資料型別將作為資料型別引數傳遞給 quant1,而輸出資料型別將作為資料型別引數傳遞給 quant2。如果輸出資料型別是 fp32(如動態量化的情況),則不會插入輸出 quant-dequant 對。此示例還展示瞭如何指定對特定資料型別的量化和比例範圍的限制。

quint8_with_constraints = DTypeWithConstraints(
    dtype=torch.quint8,
    quant_min_lower_bound=0,
    quant_max_upper_bound=255,
    scale_min_lower_bound=2 ** -12,
)

# Specify the dtypes passed to the quantized ops in the reference model spec
weighted_int8_dtype_config = DTypeConfig(
    input_dtype=quint8_with_constraints,
    output_dtype=quint8_with_constraints,
    weight_dtype=torch.qint8,
    bias_dtype=torch.float)

3. 為 Conv-ReLU 設定融合

請注意,原始使用者模型包含單獨的 conv 和 relu 運算元,因此我們需要先將 conv 和 relu 運算元融合為單個 conv-relu 運算元 (fp32_conv_relu),然後像量化 Linear 運算元一樣量化此運算元。我們可以透過定義一個接受 3 個引數的函式來設定融合,其中第一個引數表示是否用於 QAT,其餘引數指向融合模式的單個項。

def fuse_conv2d_relu(is_qat, conv, relu):
    """Return a fused ConvReLU2d from individual conv and relu modules."""
    return torch.ao.nn.intrinsic.ConvReLU2d(conv, relu)

4. 定義 BackendConfig

現在我們已經具備了所有必要的元件,可以繼續定義我們的 BackendConfig。此處我們為 Linear 運算元的輸入和輸出使用不同的觀察者(將被重新命名),因此傳遞給兩個量化運算元(quant1 和 quant2)的量化引數將不同。對於 Linear 和 Conv 等帶權運算元,這通常是常見情況。

對於 Conv-ReLU 運算元,觀察型別相同。但是,我們需要兩個 BackendPatternConfig 來支援此運算元,一個用於融合,一個用於量化。對於 Conv-ReLU 和 Linear,我們都使用上面定義的 DTypeConfig。

linear_config = BackendPatternConfig() \
    .set_pattern(torch.nn.Linear) \
    .set_observation_type(ObservationType.OUTPUT_USE_DIFFERENT_OBSERVER_AS_INPUT) \
    .add_dtype_config(weighted_int8_dtype_config) \
    .set_root_module(torch.nn.Linear) \
    .set_qat_module(torch.nn.qat.Linear) \
    .set_reference_quantized_module(torch.ao.nn.quantized.reference.Linear)

# For fusing Conv2d + ReLU into ConvReLU2d
# No need to set observation type and dtype config here, since we are not
# inserting quant-dequant ops in this step yet
conv_relu_config = BackendPatternConfig() \
    .set_pattern((torch.nn.Conv2d, torch.nn.ReLU)) \
    .set_fused_module(torch.ao.nn.intrinsic.ConvReLU2d) \
    .set_fuser_method(fuse_conv2d_relu)

# For quantizing ConvReLU2d
fused_conv_relu_config = BackendPatternConfig() \
    .set_pattern(torch.ao.nn.intrinsic.ConvReLU2d) \
    .set_observation_type(ObservationType.OUTPUT_USE_DIFFERENT_OBSERVER_AS_INPUT) \
    .add_dtype_config(weighted_int8_dtype_config) \
    .set_root_module(torch.nn.Conv2d) \
    .set_qat_module(torch.ao.nn.intrinsic.qat.ConvReLU2d) \
    .set_reference_quantized_module(torch.ao.nn.quantized.reference.Conv2d)

backend_config = BackendConfig("my_backend") \
    .set_backend_pattern_config(linear_config) \
    .set_backend_pattern_config(conv_relu_config) \
    .set_backend_pattern_config(fused_conv_relu_config)

5. 設定滿足後端約束的 QConfigMapping

為了使用上面定義的運算元,使用者必須定義一個滿足 DTypeConfig 中指定約束的 QConfig。有關更多詳細資訊,請參閱 DTypeConfig 的文件。然後,我們將對希望量化的模式中使用的所有模組使用此 QConfig。

# Note: Here we use a quant_max of 127, but this could be up to 255 (see `quint8_with_constraints`)
activation_observer = MinMaxObserver.with_args(quant_min=0, quant_max=127, eps=2 ** -12)
qconfig = QConfig(activation=activation_observer, weight=default_weight_observer)

# Note: All individual items of a fused pattern, e.g. Conv2d and ReLU in
# (Conv2d, ReLU), must have the same QConfig
qconfig_mapping = QConfigMapping() \
    .set_object_type(torch.nn.Linear, qconfig) \
    .set_object_type(torch.nn.Conv2d, qconfig) \
    .set_object_type(torch.nn.BatchNorm2d, qconfig) \
    .set_object_type(torch.nn.ReLU, qconfig)

6. 透過 prepare 和 convert 量化模型

最後,我們透過將定義的 BackendConfig 傳遞給 prepare 和 convert 來量化模型。這將生成一個量化 Linear 模組和一個融合的量化 Conv-ReLU 模組。

class MyModel(torch.nn.Module):
    def __init__(self, use_bn: bool):
        super().__init__()
        self.linear = torch.nn.Linear(10, 3)
        self.conv = torch.nn.Conv2d(3, 3, 3)
        self.bn = torch.nn.BatchNorm2d(3)
        self.relu = torch.nn.ReLU()
        self.sigmoid = torch.nn.Sigmoid()
        self.use_bn = use_bn

    def forward(self, x):
        x = self.linear(x)
        x = self.conv(x)
        if self.use_bn:
            x = self.bn(x)
        x = self.relu(x)
        x = self.sigmoid(x)
        return x

example_inputs = (torch.rand(1, 3, 10, 10, dtype=torch.float),)
model = MyModel(use_bn=False)
prepared = prepare_fx(model, qconfig_mapping, example_inputs, backend_config=backend_config)
prepared(*example_inputs)  # calibrate
converted = convert_fx(prepared, backend_config=backend_config)
>>> print(converted)

GraphModule(
  (linear): QuantizedLinear(in_features=10, out_features=3, scale=0.012136868201196194, zero_point=67, qscheme=torch.per_tensor_affine)
  (conv): QuantizedConvReLU2d(3, 3, kernel_size=(3, 3), stride=(1, 1), scale=0.0029353597201406956, zero_point=0)
  (sigmoid): Sigmoid()
)

def forward(self, x):
    linear_input_scale_0 = self.linear_input_scale_0
    linear_input_zero_point_0 = self.linear_input_zero_point_0
    quantize_per_tensor = torch.quantize_per_tensor(x, linear_input_scale_0, linear_input_zero_point_0, torch.quint8);  x = linear_input_scale_0 = linear_input_zero_point_0 = None
    linear = self.linear(quantize_per_tensor);  quantize_per_tensor = None
    conv = self.conv(linear);  linear = None
    dequantize_2 = conv.dequantize();  conv = None
    sigmoid = self.sigmoid(dequantize_2);  dequantize_2 = None
    return sigmoid

(7. 嘗試有問題的 BackendConfig 設定)

作為實驗,這裡我們將模型修改為使用 Conv-BN-ReLU 而不是 Conv-ReLU,但使用相同的 BackendConfig,該 BackendConfig 不知道如何量化 Conv-BN-ReLU。因此,只有 Linear 被量化,而 Conv-BN-ReLU 既未融合也未量化。

>>> print(converted)

GraphModule(
  (linear): QuantizedLinear(in_features=10, out_features=3, scale=0.015307803638279438, zero_point=95, qscheme=torch.per_tensor_affine)
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (bn): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU()
  (sigmoid): Sigmoid()
)

def forward(self, x):
    linear_input_scale_0 = self.linear_input_scale_0
    linear_input_zero_point_0 = self.linear_input_zero_point_0
    quantize_per_tensor = torch.quantize_per_tensor(x, linear_input_scale_0, linear_input_zero_point_0, torch.quint8);  x = linear_input_scale_0 = linear_input_zero_point_0 = None
    linear = self.linear(quantize_per_tensor);  quantize_per_tensor = None
    dequantize_1 = linear.dequantize();  linear = None
    conv = self.conv(dequantize_1);  dequantize_1 = None
    bn = self.bn(conv);  conv = None
    relu = self.relu(bn);  bn = None
    sigmoid = self.sigmoid(relu);  relu = None
    return sigmoid

作為另一個實驗,此處我們使用預設的 QConfigMapping,它不滿足後端中指定的 dtype 約束。因此,沒有任何內容被量化,因為 QConfigs 被簡單地忽略了。

>>> print(converted)

GraphModule(
  (linear): Linear(in_features=10, out_features=3, bias=True)
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (bn): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (relu): ReLU()
  (sigmoid): Sigmoid()
)

def forward(self, x):
    linear = self.linear(x);  x = None
    conv = self.conv(linear);  linear = None
    bn = self.bn(conv);  conv = None
    relu = self.relu(bn);  bn = None
    sigmoid = self.sigmoid(relu);  relu = None
    return sigmoid

內建 BackendConfigs

PyTorch 量化支援 torch.ao.quantization.backend_config 名稱空間下的幾個內建原生 BackendConfigs

目前還有其他正在開發的 BackendConfig(例如用於 TensorRT 和 x86),但這些大部分仍處於實驗階段。如果使用者希望將新的自定義後端與 PyTorch 的量化 API 整合,他們可以使用與上面示例中用於定義原生支援的 BackendConfig 相同的 API 集來定義自己的 BackendConfig。

文件

查閱 PyTorch 的全面開發者文件

檢視文件

教程

獲取針對初學者和高階開發者的深度教程

檢視教程

資源

查詢開發資源並獲得問題解答

檢視資源