本文主要分析,C++调用Python深度学习模型,模型加载到GPU,然后GPU内存中的模型释放,以及调用了GPU,GPU内存不够,内存占用过多,Utilities利用率低,GPU跑不起来等各种疑难杂症!
(注:关于GPU利用率低的问题,如何提高深度学习GPU使用效率的解决方案,请查看我的这一篇文章:深度学习PyTorch,TensorFlow中GPU利用率较低,CPU利用率很低,且模型训练速度很慢的问题总结与分析)
最近在帮忙做一个项目,上层用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++运行深度学习代码。
Python写好深度学习的代码,就可以用C++将你DNN_algorithm.py 里面的各个函数,类,使用起来了。我的python代码,首先是加载训练好的模型,函数名称是load_model();然后,使用model_predict(image)函数,来进行图像的人脸检测,也叫做预测。
按照常规的方式,在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
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);
对于这一块,由于我遇到的问题是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)
在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,这个过程是很耗时的。
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,留给其他任务。本任务是不可能再次使用的。
如果你的C++写的应用,比如QT界面,需要执行完本次深度学习预测任务,然后继续去收集图像或者其他数据,再次进行人脸检测。这时候,如果你加载模型到GPU失败了,应该是上一次执行的session未清空,或者这些缓存变量,没有给清除掉。因此,你需要在每次执行完深度学习预测 model_predict()之后,clear某些session。因此,delete_model()就可以再次加载模型到GPU了。如果使用cuda.close()。你不能再次加载到GPU的。
这个时候,点开你的资源管理器,如果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加速图像处理函数了。
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.
如果有用,记得点赞加收藏哦。!!!!