×

签到

分享到微信

打开微信,使用扫一扫进入页面后,点击右上角菜单,

点击“发送给朋友”或“分享到朋友圈”完成分享

【经验总结】LPRNet maxpool3d性能优化 欧式鹏2021-08-03 16:26:42 回复 1 查看 经验交流
【经验总结】LPRNet maxpool3d性能优化
分享到:

本文是作者在适配LPRNet过程中遇到的性能问题,以部分代码为引导,介绍如何做模型等价替换性能提升,有将的不到位的地方还请见谅。

首先LPRNet是一个轻量级的车牌识别网络,下面是论文和代码链接。

附上论文地址:https://arxiv.org/abs/1806.10447?context=cs

代码链接:https://github.com/sirius-ai/LPRNet_Pytorch

由于LPRNet模型较为简单,适配起来不难,此文写在适配过LPRNet的基础上,并对性能做一定的优化。

如果模型适配完后在mlu上的性能较差,我们是可以利用cnperf工具来分析性能的瓶颈,下面是一张cnperf出来的图。

youdu图片20210803152822.png

可以看到整个网络的性能被MAXPOOL3D所占用。

这个时候我想能不能用maxpool2d来代替目前版本性能不佳的maxpool3d。于是有了下面的测试代码,

import torch
import torch.nn as nn

class Pool3d(nn.Module):
    def __init__(self):
        super(Pool3d, self).__init__()
        self.pool3d = nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1))
    def forward(self, x):
        return self.pool3d(x)

class Pool2d(nn.Module):
    def __init__(self):
        super(Pool2d, self).__init__()
        self.pool2d = nn.MaxPool2d(kernel_size=(3, 3), stride=(1, 1))
    def forward(self, x):
        return self.pool2d(x)

net_3d = Pool3d()
net_2d = Pool2d()

input_data = torch.randn(1, 64, 24, 92,dtype=torch.float)

output_3d = net_3d(input_data)
output_2d = net_2d(input_data)
print(torch.equal(output_3d, output_2d))

LPRNet中有3个maxpool3d,这是第一个maxpool3d,并且第一个kernel_size 和stride都是1,所以对channel方向来说,输入输出是一样的,所以该操作理应可以用maxpool2d来代替。

于是测试net_3d和net_2d在cpu上运行的结果,并通过torch.equal来比较,发现两个结果是一模一样,所以第一个maxpool3d就这样被替换了。替换之前我们也需要测试一下mlu上的性能,

import torch
import torch.nn as nn
import torch_mlu
import torch_mlu.core.mlu_model as ct
import torch_mlu.core.mlu_quantize as mlu_quantize
torch.set_grad_enabled(False)

class Pool3d(nn.Module):
    def __init__(self):
        super(Pool3d, self).__init__()
        #self.pool3d = nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1))
        self.pool3d = nn.MaxPool2d(kernel_size=(3, 3), stride=(1, 1))
    def forward(self, x):
        return self.pool3d(x)

net = Pool3d()

input_data = torch.randn(1, 64, 24, 92,dtype=torch.float)
input_data_mlu = input_data.type(torch.HalfTensor).to(ct.mlu_device())

ct.set_core_number(4)

quantized_net = torch_mlu.core.mlu_quantize.quantize_dynamic_mlu(net)
quantized_net.eval()
quantized_net.to(ct.mlu_device())
ct.save_as_cambricon("pool3d")

quantized_net = torch.jit.trace(quantized_net, input_data_mlu, check_trace = False)
output = quantized_net(input_data_mlu)

在Pool3d中self.pool3d分别用maxpool3d和maxpool2d来运行,测试mlu上的性能可以发现,maxpool2d的性能是us级别,而maxpool3d的性能是15ms级别。


下面我们考虑第二个maxpool3d,nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2))

可以发现stride第一个值不是1了,所以我们不能把maxpool3d直接改成maxpool2d,但是第二个stride是1,所以我考虑通过两次maxpool2d来达到一次maxpool3d的效果。

下面同样附上相关的测试代码:

import torch
import torch.nn as nn

class maxpool_3d(nn.Module):
    def __init__(self, kernel_size1, kernel_size2, stride1, stride2):
        super(maxpool_3d, self).__init__()
        self.maxpool1 = nn.MaxPool2d(kernel_size=kernel_size1, stride=stride1)
        self.maxpool2 = nn.MaxPool2d(kernel_size=kernel_size2, stride=stride2)
    def forward(self, x):
        x = self.maxpool1(x)
        x = x.transpose(1,3)
        x = self.maxpool2(x)
        x = x.transpose(1,3)
        return x

class Pool3d(nn.Module):
    def __init__(self):
        super(Pool3d, self).__init__()
        self.pool3d = nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2))
    def forward(self, x):
        return self.pool3d(x)

net = Pool3d()
net_3d = Pool3d()
net_2d = maxpool_3d(kernel_size1=(3,3), stride1=(1,2), kernel_size2=(1,1), stride2=(1,4))

input_data = torch.randn(1, 64, 24, 92,dtype=torch.float)

output_3d = net_3d(input_data)
output_2d = net_2d(input_data)
print(torch.equal(output_3d, output_2d))

上面的做法主要是先将后两维做maxpool2d,作完一次maxpool2d后通过transpose将后两维中的某一位transpose到channel的维度上,继而可以继续将channel维度做maxpool2d,运行后同样输出True,说明替换是等价的。同样测试mlu上的性能maxpool2d的收益较大。

下面将这种方法应用到网络中,再输出cnperf后的结果,发现提升真的非常大。

youdu图片20.png

好了,本次分享到此结束。

版权所有 © 2022 寒武纪 Cambricon.com 备案/许可证号:京ICP备17003415号-1
关闭