libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]

模型部署方式有很多,libtorch和TorchScript也是部署的一种方式,由pytorch官方提供
将pytorch模型转换为TorchScript方法:

  • Tracing an existing module
  • Using scripting to directly compile a module
  • How to compose both approaches
  • Saving and loading TorchScript modules
翻译过来就是
  • 跟踪现有模块
  • 使用脚本直接编译模块
  • 如何组合这两种方法
  • 保存和加载 TorchScript 模块
目录
跟踪模型
使用脚本编译模块
VGG16猫狗分类
报错问题
1.问题:无法定位程序输入点。。于动态库链接..exe
2.问题:std不明确的符合
3.问题:找不到xx.dll
4.问题:libtorch无法使用GPU
5.问题:无法加载模型
windows 10
VS 2017
cuda 10.2
pytorch 1.7.0
libtorch 1.7 cuda Debug版【尽量和自己pytorch版本一致】
附上代码直接来分析(这个例子是pytorch官网自带的,如果已经学过这部分内容可以略去),Introduction to TorchScript — PyTorch Tutorials 1.11.0+cu102 documentation
建立了一个很简单的模型,一个全连接层,和一个tanh激活函数
跟踪模型
import torch import torch.nn as nnclass MyCell(torch.nn.Module): def __init__(self): super(MyCell,self).__init__() self.liner = nn.Linear(4, 4) def forward(self, x, h): new_h = self.liner(x) + h new_h = torch.tanh(new_h)return new_h, new_h

my_cell = MyCell() x = torch.randn(3,4) h = torch.randn(3,4) my_trace = torch.jit.trace(my_cell, (x, h)) print(my_trace)

用trace对模型进行跟踪,其实就是用一个我们人为设置的变量,输入到模型中,然后利用trace对模型进行跟踪,获得模型的图结构。可以打印一下跟踪后的模型
MyCell(
original_name=MyCell
(liner): Linear(original_name=Linear)
)
然后可以用model.code来打印一下获得的结构(其实可以用model.graph来获得图结构的,但打印的结果不太好看懂,可以用code的方式增加可解释性)。打印后,我们可以看到对于模型是这样跟踪的,即模型调用了forward函数,输入类型都为张量,返回值是元组形式,元组中的元素是张量,然后用调用了全连接层的forward函数,传入输入张量后和h相加赋值给new_h,再给激活函数tanh,_0即为返回值。那么这里就是用跟踪模型
【libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]】
print(my_trace.code)

def forward(self,
input: Tensor,
h: Tensor) -> Tuple[Tensor, Tensor]:
new_h = torch.add((self.liner).forward(input, ), h, alpha=1)
_0 = torch.tanh(new_h)
return (_0, _0)
使用脚本编译模块
import torch import torch.nn as nnclass MyDecisionGate(torch.nn.Module): def forward(self, x): if x.sum() > 0: return x else: return -xclass MyCell(torch.nn.Module): def __init__(self, dg): super(MyCell, self).__init__() self.dg = dg self.linear = torch.nn.Linear(4, 4)def forward(self, x, h): new_h = torch.tanh(self.dg(self.linear(x)) + h) return new_h, new_hx,h = torch.randn(3,4),torch.randn(3,4) my_cell = MyCell(MyDecisionGate()) traced_cell = torch.jit.trace(my_cell, (x, h))print(traced_cell.dg.code) print(traced_cell.code)

在模型跟踪的时候,是调用了两个示例,一个MyCell,一个MyDecisionGate(注意里面有条件语句,一会儿要和输出做对比的)。
在第一次输出调用dg.code的时候,是没有参数参数【即argument_1】
在调用traced_cell.code的时候,trace路径-->MyCell的forward函数,传入两个张量,返回类型是元组,_0是定义的self.dg成员变量,_1是调用了全连接层的forward函数,_2是调用了_0[即我们定义的MyDecisionGate]的forward,将_1结果传入,_2是MyDecisionGate返回结果,再将返回结果和h相加后用tanh激活得到_3变量,最后返回。[把这个过程捋清楚]注意,再_2这一过程中,是不是没有发现if之类的条件语句,这是怎么回事?跟踪完全按照:运行代码,记录发生的操作,并构造一个可以做到这一点的 ScriptModule。 但是!!诸如控制流之类的东西被抹去了。【即如果我们的网络模型含有这一类的判断语句,会被直接删掉】
def forward(self,
argument_1: Tensor) -> None:
return None
def forward(self,
input: Tensor,
h: Tensor) -> Tuple[Tensor, Tensor]:
_0 = self.dg
_1 = (self.linear).forward(input, )
_2 = (_0).forward(_1, )
_3 = torch.tanh(torch.add(_1, h, alpha=1))
return (_3, _3
但我们有时候是需要将这控制语句也包含在TorchScript 里面的,则我们可以使用脚本编译器(script compiler) 可以直接分析我们的python源码后在转TorchScript
class MyDecisionGate(torch.nn.Module): def forward(self, x): if x.sum() > 0: return x else: return -xclass MyCell(torch.nn.Module): def __init__(self, dg): super(MyCell, self).__init__() self.dg = dg self.linear = torch.nn.Linear(4, 4)def forward(self, x, h): new_h = torch.tanh(self.dg(self.linear(x)) + h) return new_h, new_hscripted_gate = torch.jit.script(MyDecisionGate()) my_cell = MyCell(scripted_gate) scripted_cell = torch.jit.script(my_cell)print(scripted_gate.code) print(scripted_cell.code)

可以看到用torch.jit.script后【不是用的torch.jit.trace】,可以捕获控制信息了,还有要注意的一点,看最后的返回值是new_h与前面跟踪模型对比,前面的返回值是_0,前者只是名字而言[里面没有具体的值],这是script和trace的一个区别,script只是可以对python语言进行一个解析。
def forward(self,
x: Tensor) -> Tensor:
_0 = bool(torch.gt(torch.sum(x, dtype=None), 0))
if _0:
_1 = x
else:
_1 = torch.neg(x)
return _1
def forward(self,
x: Tensor,
h: Tensor) -> Tuple[Tensor, Tensor]:
_0 = (self.dg).forward((self.linear).forward(x, ), )
new_h = torch.tanh(torch.add(_0, h, alpha=1))
return (new_h, new_h)
我们可以对解析后的再用trace传入具体的参数看看效果,根据输出可以看出,现在不仅有控制语句,还能通过trace进行模型的捕获【如果你的代码里有控制语句,先用script编译一下,再用trace跟踪模型】
scripted_gate = torch.jit.script(MyDecisionGate()) x,h = torch.randn(3,4),torch.randn(3,4) my_cell = MyCell(scripted_gate) #scripted_cell = torch.jit.script(my_cell) trace_cell = torch.jit.trace(my_cell,(x,h))print(scripted_gate.code) print(trace_cell.code)


def forward(self,
x: Tensor) -> Tensor:
_0 = bool(torch.gt(torch.sum(x, dtype=None), 0))
if _0:
_1 = x
else:
_1 = torch.neg(x)
return _1
def forward(self,
input: Tensor,
h: Tensor) -> Tuple[Tensor, Tensor]:
_0 = (self.dg).forward((self.linear).forward(input, ), )
_1 = torch.tanh(torch.add(_0, h, alpha=1))
return (_1, _1)

然后我们就可以对模型进行保存和加载了,官网上提供的是RNN的保存和加载方式,然后我这里想用VGG16试一下,发现也是可以的,将会生成一个vgg16.zip文件 【其实也可以保存成其他权重格式,比如pt文件,只需要save('vgg16.pt')即可】
import torch from torchvision.models import vgg16model = vgg16(pretrained=False) script_model = torch.jit.script(model) x = torch.ones(1,3,224,224) trace_model = torch.jit.trace(script_model,x) print(trace_model)trace_model.save('vgg16.zip')

RecursiveScriptModule(
original_name=VGG
(features): RecursiveScriptModule(
original_name=Sequential
(0): RecursiveScriptModule(original_name=Conv2d)
(1): RecursiveScriptModule(original_name=ReLU)
(2): RecursiveScriptModule(original_name=Conv2d)
(3): RecursiveScriptModule(original_name=ReLU)
(4): RecursiveScriptModule(original_name=MaxPool2d)
(5): RecursiveScriptModule(original_name=Conv2d)
(6): RecursiveScriptModule(original_name=ReLU)
(7): RecursiveScriptModule(original_name=Conv2d)
(8): RecursiveScriptModule(original_name=ReLU)
(9): RecursiveScriptModule(original_name=MaxPool2d)
(10): RecursiveScriptModule(original_name=Conv2d)
(11): RecursiveScriptModule(original_name=ReLU)
(12): RecursiveScriptModule(original_name=Conv2d)
(13): RecursiveScriptModule(original_name=ReLU)
(14): RecursiveScriptModule(original_name=Conv2d)
(15): RecursiveScriptModule(original_name=ReLU)
(16): RecursiveScriptModule(original_name=MaxPool2d)
(17): RecursiveScriptModule(original_name=Conv2d)
(18): RecursiveScriptModule(original_name=ReLU)
(19): RecursiveScriptModule(original_name=Conv2d)
(20): RecursiveScriptModule(original_name=ReLU)
(21): RecursiveScriptModule(original_name=Conv2d)
(22): RecursiveScriptModule(original_name=ReLU)
(23): RecursiveScriptModule(original_name=MaxPool2d)
(24): RecursiveScriptModule(original_name=Conv2d)
(25): RecursiveScriptModule(original_name=ReLU)
(26): RecursiveScriptModule(original_name=Conv2d)
(27): RecursiveScriptModule(original_name=ReLU)
(28): RecursiveScriptModule(original_name=Conv2d)
(29): RecursiveScriptModule(original_name=ReLU)
(30): RecursiveScriptModule(original_name=MaxPool2d)
)
(avgpool): RecursiveScriptModule(original_name=AdaptiveAvgPool2d)
(classifier): RecursiveScriptModule(
original_name=Sequential
(0): RecursiveScriptModule(original_name=Linear)
(1): RecursiveScriptModule(original_name=ReLU)
(2): RecursiveScriptModule(original_name=Dropout)
(3): RecursiveScriptModule(original_name=Linear)
(4): RecursiveScriptModule(original_name=ReLU)
(5): RecursiveScriptModule(original_name=Dropout)
(6): RecursiveScriptModule(original_name=Linear)
)
)

libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片

然后我们可以加载一下这个文件,同时用code输出一下图结构
loaded = torch.jit.load('vgg16.zip') print(loaded) print(loaded.code)


RecursiveScriptModule(
original_name=VGG
(features): RecursiveScriptModule(
original_name=Sequential
(0): RecursiveScriptModule(original_name=Conv2d)
(1): RecursiveScriptModule(original_name=ReLU)
(2): RecursiveScriptModule(original_name=Conv2d)
(3): RecursiveScriptModule(original_name=ReLU)
(4): RecursiveScriptModule(original_name=MaxPool2d)
(5): RecursiveScriptModule(original_name=Conv2d)
(6): RecursiveScriptModule(original_name=ReLU)
(7): RecursiveScriptModule(original_name=Conv2d)
(8): RecursiveScriptModule(original_name=ReLU)
(9): RecursiveScriptModule(original_name=MaxPool2d)
(10): RecursiveScriptModule(original_name=Conv2d)
(11): RecursiveScriptModule(original_name=ReLU)
(12): RecursiveScriptModule(original_name=Conv2d)
(13): RecursiveScriptModule(original_name=ReLU)
(14): RecursiveScriptModule(original_name=Conv2d)
(15): RecursiveScriptModule(original_name=ReLU)
(16): RecursiveScriptModule(original_name=MaxPool2d)
(17): RecursiveScriptModule(original_name=Conv2d)
(18): RecursiveScriptModule(original_name=ReLU)
(19): RecursiveScriptModule(original_name=Conv2d)
(20): RecursiveScriptModule(original_name=ReLU)
(21): RecursiveScriptModule(original_name=Conv2d)
(22): RecursiveScriptModule(original_name=ReLU)
(23): RecursiveScriptModule(original_name=MaxPool2d)
(24): RecursiveScriptModule(original_name=Conv2d)
(25): RecursiveScriptModule(original_name=ReLU)
(26): RecursiveScriptModule(original_name=Conv2d)
(27): RecursiveScriptModule(original_name=ReLU)
(28): RecursiveScriptModule(original_name=Conv2d)
(29): RecursiveScriptModule(original_name=ReLU)
(30): RecursiveScriptModule(original_name=MaxPool2d)
)
(avgpool): RecursiveScriptModule(original_name=AdaptiveAvgPool2d)
(classifier): RecursiveScriptModule(
original_name=Sequential
(0): RecursiveScriptModule(original_name=Linear)
(1): RecursiveScriptModule(original_name=ReLU)
(2): RecursiveScriptModule(original_name=Dropout)
(3): RecursiveScriptModule(original_name=Linear)
(4): RecursiveScriptModule(original_name=ReLU)
(5): RecursiveScriptModule(original_name=Dropout)
(6): RecursiveScriptModule(original_name=Linear)
)
)
下面是用code的输出
def forward(self,
x: Tensor) -> Tensor:
x0 = (self.features).forward(x, )
x1 = (self.avgpool).forward(x0, )
x2 = torch.flatten(x1, 1, -1)
return (self.classifier).forward(x2, )

我们可以进入vgg16的官方代码看看对不对,可以看到在VGG16 forward()函数中如下所示,发现和我们用code获取的结果是一致的。
def forward(self, x): x = self.features(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.classifier(x) return x

保存以后,以后可以用C++调用
C++调用模型
需要下载libtorch。
将libtorch/lib和libtorch/bin路径放在你电脑的环境变量中
新建C++项目
项目》属性》C/C++目录》附加包含库目录添加include配置torch头文件路径
我这里是E:\libtorch\include\torch\csrc\api\include和E:\libtorch\include
这两个include分别是导入torch.h和script.h
libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片


设置链接库

链接器>输入>附加依赖项 ,添加所需要的lib文件
libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片

libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片

所需要的lib文件如下【这里的opencv是我以前的添加的,后面也会用到】 (因为我是在Debug模式下,所以lib文件后面都是有d的,如果你的是在release下就没有加d这种文件)
opencv_world410d.lib
c10.lib
libprotobufd.lib
libprotobuf-lited.lib
libprotocd.lib
mkldnn.lib
torch.lib
torch_cpu.lib
torch_cuda.lib
链接器>常规>附加库目录,将libtorch中的lib路径填入
libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片


将libtorch>lib下的所有dll文件复制到你项目x64/Debug/下,即和你的exe同级文件下【同样,注意你的libtorch是Debug还是release】【如果没有这一步,你可能会遇到无法定位程序输入点。。于动态库链接..exe】
接下来可以测试一下
#include #include #includeint main() { torch::Tensor output = torch::randn({ 3,4 }); std::cout << output << std::endl; system("pause"); return 0; }

-0.4466 -0.6320 -0.37151.0210
-2.18601.1954 -0.15090.0185
-2.49540.9098 -1.1754 -1.0180
[ CPUFloatType{3,4} ]
请按任意键继续. . .
可以输出一个3行4列的张量
然后我们再看看能不能用cuda【如果你的不能用cuda,情况报错问题>问题4】
#include #include #includevoid IsCuda() { if (torch::cuda::is_available()) { std::cout << "支持GPU" << std::endl; } else { std::cout << "不支持GPU" << std::endl; } }; int main() { IsCuda(); system("pause"); return 0; }

支持GPU
请按任意键继续. . .
然后我们可以加载一下上面我们转换的模型
// Myrenet.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include #include #includevoid IsCuda() { if (torch::cuda::is_available()) { std::cout << "支持GPU" << std::endl; } else { std::cout << "不支持GPU" << std::endl; } }; int main() { IsCuda(); torch::jit::script::Module module; try { // Deserialize the ScriptModule from a file using torch::jit::load(). module = torch::jit::load("E:\\模型部署学习\\Learning_TorchScript\\vgg16.pt"); } catch (const c10::Error& e) { std::cerr << "error loading the model\n"; return -1; } std::cout << "模型加载成功" << std::endl; system("pause"); return 0; }


支持GPU
模型加载成功
请按任意键继续. . .
如果你的模型无法加载,看报错问题>问题5
VGG16猫狗分类 接下来用就可以利用libtorch进行预测了,进行前向推理主要会调用forward()函数,tensor_image是进过预处理后的图像:
module.eval(); //进行推理 torch::Tensor output = module.forward({ tensor_image }).toTensor();

然后我利用pytorchVGG16训练了一个猫狗分类【训练完以后和上面一样,把模型进行转换】
#include #include #include #include #include #include #includevoid IsCuda() { if (torch::cuda::is_available()) { std::cout << "支持GPU" << std::endl; } else { std::cout << "不支持GPU" << std::endl; } }; int main() { std::string classes_names[] = { "cat","dog" }; //IsCuda(); torch::jit::script::Module module; try { // Deserialize the ScriptModule from a file using torch::jit::load(). module = torch::jit::load("E:\\模型部署学习\\Learning_TorchScript\\vgg16dog.pt"); } catch (const c10::Error& e) { std::cerr << "error loading the model\n"; return -1; } std::cout << "模型加载成功" << std::endl; torch::DeviceType device_type; device_type = torch::kCUDA; torch::Device device(device_type); module.to(device); std::string image_path = "E:\\模型部署学习\\Learning_TorchScript\\cat.jpg"; cv::Mat image = cv::imread(image_path); cv::Mat input; cv::resize(image, image, cv::Size(224, 224)); cv::cvtColor(image, input, cv::COLOR_BGR2RGB); //from_blob Mat转Tensor {batchsize,w,h,channles} torch::Tensor tensor_image = torch::from_blob(input.data, { 1,input.rows, input.cols,3 }, torch::kByte); //shape->(batchsize,channles,w,h) tensor_image = tensor_image.permute({ 0,3,1,2 }); tensor_image = tensor_image.toType(torch::kFloat); //image/255.0图像的归一化处理 tensor_image = tensor_image.div(255); //tensor_image[0]指的是取出第一个batch 然后对每个通道进行处理 tensor_image[0][0] = tensor_image[0][0].sub_(0.4914).div_(0.2023); tensor_image[0][1] = tensor_image[0][1].sub_(0.4822).div_(0.1994); tensor_image[0][2] = tensor_image[0][2].sub_(0.4465).div_(0.2010); //将处理后的tensor送入cuda tensor_image = tensor_image.to(device); module.eval(); //进行推理 torch::Tensor output = module.forward({ tensor_image }).toTensor(); //可以获得输出的shape output的shape是[batchsize,num_classes] //std::cout << "输出的shape:"<< output.sizes() << std::endl; //output.view(-1) shape=num_classes auto tmp = output.view(-1); auto pred = torch::softmax(tmp, -1); int res = torch::argmax(pred).item(); std::cout << "概率值为 :" << (pred[res]*100) << std::endl; std::cout << "预测的类为 :" << classes_names[res] << std::endl; cv::Point origin; origin.x = 5; origin.y = 20; cv::putText(image, classes_names[res],origin, cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, 255, 255),2); cv::namedWindow("预测结果图", CV_WINDOW_NORMAL); cv::imshow("预测结果图",image); cv::imwrite("分类结果.jpg", image); cv::waitKey(0); system("pause"); return 0; }

libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片


libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片


报错问题 1.问题:无法定位程序输入点。。于动态库链接..exe 解决办法:
将libtorch>lib下的所有dll文件复制到你项目x64/Debug/下,即和你的exe同级文件下【同样,注意你的libtorch是Debug还是release】
2.问题:std不明确的符合 解决办法:在C/C++>语言>符合模式改为否
3.问题:找不到xx.dll 解决办法:项目属性>调试>环境中把包含dll的lib路径添加进去
如果你是遇到的VCRUNTIME140_1D.dll无法找到,可以去c:/windows/System32/下找找有没有这个文件,如果没有,但你看到有一个vcruntime140_1.dll,你可以把这个文件复制一份,然后改名为vcruntime140_1D.dll
libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片


4.问题:libtorch无法使用GPU 解决办法:如果确定libtorch下载版本正确且是cuda版,同时在pytorch环境下可以使用cuda,但libtorch无法使用。在项目属性中>链接器>命令行在右侧【其他选项下方】填入:
/INCLUDE:?warp_size@cuda@at@@YAHXZ
libtorch|使用TorchScript和libtorch进行模型推理[附C++代码]
文章图片


5.问题:无法加载模型 解决办法:通过torch::jit::load()无法加载模型,先检测一下你的模型路径对不对,可以改成绝对路径。如果发现路径对,pytorch中可以用torch.jit.load加载,那么检查一下你的pytorch和libtorch版本是不是对应,比如我用的pytorch是1.7.0,所以libtorch也应该是1.7.0 【之前我试过别的版本,就发现不能加载模型,然后把版本对应以后就可以了】
如果大家在实践过程中遇到了什么报错并解决了的话,可在评论区写出来,方便大家的一起学习

    推荐阅读