C++调用Python文件,TensorFlow和PyTorch构建的深度学习模型,无法使用GPU的情况分析。

本文主要分析,C++调用Python深度学习模型,模型加载到GPU,然后GPU内存中的模型释放,以及调用了GPU,GPU内存不够,内存占用过多,Utilities利用率低,GPU跑不起来等各种疑难杂症!

(注:关于GPU利用率低的问题,如何提高深度学习GPU使用效率的解决方案,请查看我的这一篇文章:深度学习PyTorch,TensorFlow中GPU利用率较低,CPU利用率很低,且模型训练速度很慢的问题总结与分析

1. C++调Python所构建的深度学习模型

1.1 项目描述

最近在帮忙做一个项目,上层用C++写的一个QT软件,是人脸识别分析的一个软件。C++负责界面,多线程,实时显示等各种应用的业务。Python主要负责深度学习这一方面。Python构建了几个深度学习模型,包含ResNet101, LSTM, VGG16等网络,用于特征提取,表情分类,时序分析等算法方面的逻辑。
C++这一块,有一个选项是进行人脸检测,这个时候,为了加速,需要调用GPU来进行计算。由于算法这边提供的是Python写好的,没有进行TensorFlow或者PyTorch转换成C++的这一种思路。况且,我们的深度学习模型,由于是不同人负责不同的块,包含了TensorFlow,Keras,PyTorch,如果用一个转换包,也不好将三种不同框架转换为1个可用的C++代码。
为了方便,就在Python下写成单独的函数,C++直接通过Python的接口,来调用Python代码。换句话说就是,将你用Python写的深度学习代码,比如,文件名为:DNN_algorithm.py给导入到C++里面,调用相关函数就可以像运行python一样通过C++运行深度学习代码。

1.2 C++和Python代码的构建

Python写好深度学习的代码,就可以用C++将你DNN_algorithm.py 里面的各个函数,类,使用起来了。我的python代码,首先是加载训练好的模型,函数名称是load_model();然后,使用model_predict(image)函数,来进行图像的人脸检测,也叫做预测。

2. Python深度学习模型的构建代码,Python文件名为DNN_algorithm.py

按照常规的方式,在Python下构建模型,训练和保存好模型。当进行预测的时候,直接加载训练好的权重文件就可以。这一部分到处都可以查到相关的指导文件,就不多说这一块。

	class DNN_model():
		def __init__(self):
			self.vgg_model = None
			self.LSTM_model = None
		
		def load_model(self):
			VGG_net = # construct the model, you can use tensorflow, keras, PyTorch.
			LSTM_net = # construct the LSTM model.
			# load the trained weights to the constructed model architecture.
			self.vgg_model = load_wights(VGG_net, 'vgg_net.h5')
			self.LSTM_model = load_wights(LSTM_net , 'lstm_net.h5')
		
		def model_predict(self, image):
			class_out = self.vgg_model.predict(image)
			temporal_out = self.LSTM_model.predict(image)
			return class_out, temporal_out

3. C++调用所构建的Python代码的相关函数

C++只讲怎么调用你上面构建的Python的几个函数。先是导入python文件名,然后导入相关函数就可以。具体C++怎么调用Python的,可以搜一下py.h。是Python官方自带的,供C++使用的接口函数。

    //创建代码文件模块:将你用Python写的深度学习代码DNN_algorithm.py给导入到C++里面,方便调用
    m_pModule = PyImport_ImportModule("DNN_algorithm");
    //下面就可以,将你DNN_algorithm.py 里面的各个函数,类,使用起来了。
    //我的python代码,首先是加载训练好的模型,函数名称是load_model();
    //然后,使用model_predict(image)函数,来进行图像的人脸检测。
    PyObject* pResult = NULL;
    //调用python加载模型的函数load_model。
    pResult = PyObject_CallMethod(m_pInstanceME, "load_model", NULL);
    PyObject* pFunc = NULL;
    //调用python模型预测的函数model_predict。
    pFunc = PyObject_GetAttrString(m_pInstanceME, "model_predict");
    //这句话,就是将python函数,与C++这边采集到的图像argList,给模型拿去预测。
    pResult2 = PyEval_CallObject(pFunc, argList); 
    

4. 加载模型到GPU,然后使用GPU进行计算

4.1 C++加载TensorFlow和Keras模型到GPU

对于这一块,由于我遇到的问题是Tensorflow 和Keras这部分的,PyTorch下加载没问题。所以就说一下TensorFlow和Keras,在C++调用模型的时候,如何加载模型到GPU,如何run起来。

其实只需要在调用Python的文件那边,主动加入这些函数,你的模型,就自动加载到GPU上的。PyTorch不是这个用法,PyTorch需要显示的将模型加到device上 :

model=model.to(device) #这是PyTorch的加载方法

	# 这是Keras的加载方法
	import os
	os.environ['KMP_DUPLICATE_LIB_OK']='TRUE'
	os.environ['CUDA_VISIBLE_DEVICES']='0'
	os.environ["TF_CPP_MIN_LOG_LEVEL"]='3'
	
	## 如果你的GPU内存不够,不允许TF和Keras开辟很大的内存,下面的也可以来进行限制。
	config = tf.ConfigProto()
	config.gpu_options.per_process_gpu_memory_fraction = 0.5  #程序最多只能占用指定gpu50%的显存
	config.gpu_options.allow_growth = True	#程序按需申请内存
	sess = tf.Session(config = config)
  • 这个时候,查看你的任务管理器的GPU的情况,包括内存和cuda的使用率等。当C++调用了load_model的函数之后,查看你的GPU的内存和使用率是否上去。
  1. 如果内存利用率没有上去,就是检查你的模型是否load上去。这种情况下,先在python下面运行,看你的模型load上去GPU没有,如果没有,那就是python代码的问题。
  2. 如果Python能够load上GPU去,但是C++调用代码之后,没有load上GPU上,那就是C++调用Python的问题。你检查你的C++代码是否正确调用Python。如果不确定,先写一个简单的print函数,然后用C++调用一下,如果行,就按照这个调用方式调用函数。
  • 我的Python代码下面是一个类,你就要在C++这边,先实例化这个类,才能调用下面的类的成员函数。

4.2 C++加载模型到GPU之后,神经网络运行完预测函数,GPU的内存一直被占用,没释放的问题。

在Python下面,我们run完模型的预测函数,也就是model_predict()完成之后,或者代码运行完,GPU的内存直接被释放掉。因此,python下,无需考虑模型占用内存的问题。
当我们使用C++,来调用Python所写的深度学习模型的时候,如上面所述的流程,先构建模型,加载权重文件,然后模型预测,阶段性的处理完了采集的图像。随后,我们的界面,可以做其他的业务,比如,浏览,报表分析,等等。但此时,GPU还被占着,只有你关闭这个exe,或者退掉整个程序,才释放了由于使用GPU进行神经网络预测(推理)所加载的模型及其占用的GPU内存。

  • 清除缓存

在Python下面,可以采用以下的方法,来清理缓存,收集垃圾数据,(PS:只是暂时的清除一些临时变量,作用其实不大,GPU内存占用一样的无法减少。

    def delete_model(self):
        del self.vgg_model #删除模型
        del self.LSTM_model #删除模型
        gc.collect() #回收一些临时变量和垃圾数据
        K.clear_session() #清除session
        tf.reset_default_graph() # 重置 graph。

请注意:如果你只是暂时的没有接收到数据,GPU暂时没有需要处理的图像数据(也许十几秒之后,就采集到新的图像,所以有可能随时要用)。此时,不用释放和删除内存。如果删除和释放了GPU内存,如果新的图像数据来了,你还要重新加载数据到GPU,这个过程是很耗时的。

  • 强制释放GPU所占用内存
    当完成了人脸检测任务,由于当前程序尚在执行,GPU内存一直被占着。
    如果你的业务下面,有其他算法需要GPU来进行处理,或者GPU用于其他的处理线程,此时,可以关闭掉你人脸检测任务,彻底清除掉GPU缓存和内存占用。个人感觉有点kill的意思。执行了下面的这个代码,你的GPU内存瞬间释放,因为前面整个加载的模型,全部被close了。这是强制性的。
	from numba import cuda
	
    cuda.select_device(0) #选择你的device id。在上面我们指定了那一块GPU用来处理,这里就指定那块。
    cuda.close() # 然后,关闭掉这个cuda线程。

下面是简要的描述一下Numba这个库。

cuda.close()
Explicitly close all contexts in the current thread.
Compiled functions are associated with the CUDA context. This makes it not very useful to close and create new devices, though it is certainly useful for choosing which device to use when the machine has multiple GPUs.

Numba 是一个利用CUDA核在GPU上进行快速计算的Python库,主要用于高性能计算。特点如下:
1. Numba: High Productivity for High-Performance Computing
2. GPU-Accelerated Libraries for Python
3. Massive Parallelism with CUDA Python]

当你的代码,执行上述的close。此时,如果你还想加载模型,然后进行预测,会出问题的。因为你的cuda被强制close掉了。要想重新运行起来,只有关闭程序,重新运行代码。如果想在这个程序里面,再次检测人脸。。。。。这个时候,就报错了。。。。
!!!因此,cuda.close()只适合于强制关闭GPU,留给其他任务。本任务是不可能再次使用的。

4.3 关于上一轮预测完成(也就是深度学习模型使用完成),下一次加载模型到GPU失败的问题的解决方法。

如果你的C++写的应用,比如QT界面,需要执行完本次深度学习预测任务,然后继续去收集图像或者其他数据,再次进行人脸检测。这时候,如果你加载模型到GPU失败了,应该是上一次执行的session未清空,或者这些缓存变量,没有给清除掉。因此,你需要在每次执行完深度学习预测 model_predict()之后,clear某些session。因此,delete_model()就可以再次加载模型到GPU了。如果使用cuda.close()。你不能再次加载到GPU的。

5. GPU一直占着,加载成功,但是GPU的利用率为0,或者很低的情况。

这个时候,点开你的资源管理器,如果GPU内存被占,然后上面的利用率,cuda这个栏目,总是为0。你可以看一下,你的模型代码,是否正在执行预测的前向计算,也就是是否正在进行model_predict。或者是检查模型是否读入图像数据,正在输出结果。这个时候,如果真的是在预测阶段,那么GPU的利用率,一定有50%,或者80%,不可能是0。最大的原因是:你的模型,大部分时间花在了等待数据预处理阶段,包括了图像resize,人脸对齐,convert color space,还有就是特征检测,滤波,(我遇到的问题是,大部分时间在花在光流法处理图像)。因此,感觉非常慢,而且感觉GPU没有利用上。一度怀疑是不是深度学习代码的问题。最后是opencv图像预处理的问题。你的GPU利用率就是有一个小的尖峰脉冲形式的抖动。其实代表你的模型正在预测,GPU正在被使用,只是速度极快,实时利用率这一栏只有一个小的脉冲抖动。
解决方法:采用CUDA来进行图像预处理的加速,opencv-python 4.几及其以上版本,已经完全支持某些特定函数的CUDA开发了,在python上就可以调用CUDA实现的的GPU加速图像处理函数了。

  • TODO:下一个博文,我就讲我如何使用cuda和GPU进行图像预处理的加速。平时大家总是觉得GPU主要用来深度学习的加速,其实某些耗时的图像算法,CUDA也有对应版本。

参考资料

1. Numba: High-Performance Python with CUDA Acceleration
2. Numba for device management
3. CUDA Device Management
4. C++ call python neural network model, the model was loaded on GPU, but can’t run on the GPU, the CPU run the model.

猜你喜欢

转载自blog.csdn.net/qq_32998593/article/details/107465671