如何将带有 grid_sample 的模型转换为带有 INT8 量化的 TensorRT?

Posted

技术标签:

【中文标题】如何将带有 grid_sample 的模型转换为带有 INT8 量化的 TensorRT?【英文标题】:How to convert the model with grid_sample to TensorRT with INT8 quantization? 【发布时间】:2021-09-13 11:52:49 【问题描述】:

我正在尝试将带有torch.nn.functional.grid_sample 的模型从Pytorch (1.9) 转换为TensorRT (7),并通过ONNX (opset 11) 进行INT8 量化。 Opset 11 不支持将 grid_sample 转换为 ONNX。因此,我将 ONNX graphsurgeon 与外部 GridSamplePlugin 一起使用,因为它是 proposed here。有了它,转换到 TensorRT(有和没有 INT8 量化)是成功的。 没有 INT8 量化的 Pytorch 和 TRT 模型提供的结果接近相同(MSE 为 e-10 阶)。但是对于带有 INT8 量化的 TensorRT,MSE 更高(185)。

grid_sample 运算符有两个输入:输入信号和采样网格。它们都应该是同一类型。在 GridSamplePlugin 中,只实现了 kFLOAT 和 kHALF 的处理。 在我的情况下,绝对采样网格中的 X 坐标(在转换为 grid_sample 所需的相对坐标之前)在 [-d; W+d]和[-d; H+d] 为 Y 坐标。 W 的最大值为 640,H 的最大值为 360。并且坐标在此范围内可能具有非整数值。 出于测试目的,我创建了仅包含 grid_sample 层的测试模型。在这种情况下,使用和不使用 INT8 量化的 TensorRT 结果是相同的。

这是测试模型的代码:

import torch
import numpy as np
import cv2

BATCH_SIZE = 1
WIDTH = 640
HEIGHT = 360

def calculate_grid(B, H, W, dtype, device='cuda'):
    xx = torch.arange(0, W, device=device).view(1, -1).repeat(H, 1).type(dtype)
    yy = torch.arange(0, H, device=device).view(-1, 1).repeat(1, W).type(dtype)
    xx = xx + yy * 0.25
    if B > 1:
        xx = xx.view(1, 1, H, W).repeat(B, 1, 1, 1)
        yy = yy.view(1, 1, H, W).repeat(B, 1, 1, 1)
    else:
        xx = xx.view(1, 1, H, W)
        yy = yy.view(1, 1, H, W)
    vgrid = torch.cat((xx, yy), 1).type(dtype)
    return vgrid.type(dtype)

def modify_grid(vgrid, H, W):
    vgrid = torch.cat([
        torch.sub(2.0 * vgrid[:, :1, :, :].clone() / max(W - 1, 1), 1.0),
        torch.sub(2.0 * vgrid[:, 1:2, :, :].clone() / max(H - 1, 1), 1.0),
        vgrid[:, 2:, :, :]], dim=1)
    vgrid = vgrid.permute(0, 2, 3, 1)
    return vgrid

class GridSamplingBlock(torch.nn.Module):

    def __init__(self):
        super(GridSamplingBlock, self).__init__()

    def forward(self, input, vgrid):
        output = torch.nn.functional.grid_sample(input, vgrid)
        return output

if __name__ == '__main__':
    model = torch.nn.DataParallel(GridSamplingBlock())
    model.cuda()
    print("Reading inputs")
    img = cv2.imread("result/left_frame_rect_0373.png")
    img = cv2.resize(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), (WIDTH, HEIGHT))
    img_in = torch.from_numpy(img.astype(float)).view(1, 1, HEIGHT, WIDTH).cuda()
    vgrid = calculate_grid(BATCH_SIZE, HEIGHT, WIDTH, img_in.dtype)
    vgrid = modify_grid(vgrid, HEIGHT, WIDTH)
    np.save("result/grid", vgrid.cpu().detach().numpy())
    print("Getting output")
    with torch.no_grad():
        model.module.eval()
        img_out = model.module(img_in, vgrid)
        img = img_out.cpu().detach().numpy().squeeze()
        cv2.imwrite("result/grid_sample_test_output.png", img.astype(np.uint8))

保存的网格用于 TensorRT 模型的校准和推理。

所以问题是:

将 INT8 量化应用于具有至少一个的函数是否有效 索引输入(如grid_sample)?这种量化不会导致 结果的显着变化(如果我们将 INT8 量化应用于 例如,范围为 [0..640) 的输入)? 如果在此插件代码中仅实现 FP32 和 FP16,INT8 量化如何与自定义插件一起使用? 由于 grid_sample 输入实际上是网络输入这一事实,在 TensorRT 中使用和不使用 INT8 量化获得的测试网络的结果是否相同?

我的环境:

TensorRT 版本:7 GPU 类型:NVidia GeForce GTX 1050 Ti Nvidia 驱动程序版本:470.63.01 CUDA 版本:10.2.89 CUDNN 版本:8.1.1 操作系统+版本:Ubuntu 18.04 Python 版本(如果适用):3.7 PyTorch 版本(如果适用):1.9

重现步骤:

运行测试代码保存网格并获取 Torch 结果。使用任何 用于测试的输入图像。 根据自定义插件构建 TensorRT OSS 这个sample。最新的 TRT OSS 版本需要对 GridSamplePlugin 进行一些适配,所以 最好使用推荐的 TensorRT OSS 版本。 根据code example创建ONNX模型。 创建带有或不带有 INT8 量化的 TensorRT 引擎并运行推理。在我的 C++ 代码中,我使用 https://github.com/llohse/libnpy 来读取 grid.npy 文件。

【问题讨论】:

【参考方案1】:

您可以将模型分成两部分,一个在网格样本之前,另一个在它之后,分别进行 int8 量化。在 INT8 中使用 grid_sample 会极大地影响您的模型性能。 这将导致您的网络结构发生变化,因此可能会改变图的优化。

【讨论】:

以上是关于如何将带有 grid_sample 的模型转换为带有 INT8 量化的 TensorRT?的主要内容,如果未能解决你的问题,请参考以下文章

将 .HBM 模型转换为带注释的 pojos

帮助将子查询转换为带连接的查询

使用Java将String中的大写文本转换为带重音的小写

将 MySQL 中的值列表转换为带方括号的数组

如何使用光流和 grid_sample 扭曲图像?

AFNetworking 在格式化 JSON 时将我的双打转换为带引号的字符串