tensorflow1.x——如何在C++多线程中调用同一个session会话
tensorflow1.x——如何在python多线程中调用同一个session会话
=================================================
从前文tensorflow1.x——如何在python多线程中调用同一个session会话 可以知道,使用python多线程调用同一个session中的计算图并不能有显著的性能提升,虽然有小幅度的提升但是该提升更像是一个python线程发送cuda计算指令的间隔期间另一个python线程发送cuda计算指令,从而填补了空闲,有了小幅度的提升,但是总体来看python多线程调用通过session并不能实现多个计算图的并行执行,当然这样可以用python线程的GIL来解释,因此本文就使用C++线程来调用通过session,以此来判断TensorFlow1.x中是否可以有效的实现多线程并发执行同一个session中的同个计算图的计算。
给出代码:TensorFlow1.x
一个线程的情况:
import tensorflow as tf from tensorflow import keras import numpy as np import threading import time def build(): n = 8 with tf.device("/gpu:0"): x = tf.random_normal([n, 10]) x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1") x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2") x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3") y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer') enqueue_ops = [] for _ in range(1): enqueue_ops.append(queue.enqueue(y)) tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default(): if __name__ == '__main__': queue = build() dequeued = queue.dequeue_many(4) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) tf.train.start_queue_runners() a_time = time.time() print(a_time) for _ in range(100000): sess.run(dequeued) b_time = time.time() print(b_time) print(b_time-a_time) time.sleep(11111)
用时:
两个线程的情况:
import tensorflow as tf from tensorflow import keras import numpy as np import threading import time def build(): n = 8 with tf.device("/gpu:0"): x = tf.random_normal([n, 10]) x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1") x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2") x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3") y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer') enqueue_ops = [] for _ in range(2): enqueue_ops.append(queue.enqueue(y)) tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default(): if __name__ == '__main__': queue = build() dequeued = queue.dequeue_many(4) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) tf.train.start_queue_runners() a_time = time.time() print(a_time) for _ in range(100000): sess.run(dequeued) b_time = time.time() print(b_time) print(b_time-a_time) time.sleep(11111)
用时:
四个线程的情况:
import tensorflow as tf from tensorflow import keras import numpy as np import threading import time def build(): n = 8 with tf.device("/gpu:0"): x = tf.random_normal([n, 10]) x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1") x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2") x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3") y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer') enqueue_ops = [] for _ in range(4): enqueue_ops.append(queue.enqueue(y)) tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default(): if __name__ == '__main__': queue = build() dequeued = queue.dequeue_many(4) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) tf.train.start_queue_runners() a_time = time.time() print(a_time) for _ in range(100000): sess.run(dequeued) b_time = time.time() print(b_time) print(b_time-a_time) time.sleep(11111)
用时:
八个线程的情况:
import tensorflow as tf from tensorflow import keras import numpy as np import threading import time def build(): n = 8 with tf.device("/gpu:0"): x = tf.random_normal([n, 10]) x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1") x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2") x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3") y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer') enqueue_ops = [] for _ in range(8): enqueue_ops.append(queue.enqueue(y)) tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default(): if __name__ == '__main__': queue = build() dequeued = queue.dequeue_many(4) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) tf.train.start_queue_runners() a_time = time.time() print(a_time) for _ in range(100000): sess.run(dequeued) b_time = time.time() print(b_time) print(b_time-a_time) time.sleep(11111)
用时:
================================================
可以看到使用C++多线程调用TensorFlow1.x中的同一个session下的同一个计算图也没有得到线性的加速,大致情况和python多线程的情况类似,确实开多线程调用同个session中的同个计算图性能会得到一定的提升,但是这个提升幅度很小,远不是和线程数成正比关系的,对于这种多线程与单线程相比较小幅度的提升更可能是在同个session的同个计算图中对cuda的调用都是使用一个命令队列的,之所以多线程会有一定性能提升是因为弥补上了cpu端对gpu端cuda发送命令的间隔上的空隙。
那么我们使用同一个session的两个计算分支,然后分别用两个线程来运行,那么效果如何呢?
给出代码:
import tensorflow as tf from tensorflow import keras import numpy as np import threading import time def build(): n = 8 with tf.device("/gpu:0"): x = tf.random_normal([n, 10]) x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1") x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2") x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3") y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") _x = tf.random_normal([n, 10]) _x1 = tf.layers.dense(_x, 10, activation=tf.nn.elu, name="fc1x") _x2 = tf.layers.dense(_x1, 10, activation=tf.nn.elu, name="fc2x") _x3 = tf.layers.dense(_x2, 10, activation=tf.nn.elu, name="fc3x") _y = tf.layers.dense(_x3, 10, activation=tf.nn.elu, name="fc4x") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer') enqueue_ops = [] for _ in range(1): enqueue_ops.append(queue.enqueue(y)) enqueue_ops.append(queue.enqueue(_y)) tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default(): if __name__ == '__main__': queue = build() dequeued = queue.dequeue_many(4) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) tf.train.start_queue_runners() a_time = time.time() print(a_time) for _ in range(100000): sess.run(dequeued) b_time = time.time() print(b_time) print(b_time-a_time) time.sleep(11111)
运算时间:
可以看到这个效果其实和同一个session下两个线程调用同个计算分支是相同的效果,那么这个问题会不会是出现在GPU上呢,如果我们的这两个计算分支分别在两个GPU上呢,给出代码:
import tensorflow as tf from tensorflow import keras import numpy as np import threading import time def build(): n = 8 with tf.device("/gpu:0"): x = tf.random_normal([n, 10]) x1 = tf.layers.dense(x, 10, activation=tf.nn.elu, name="fc1") x2 = tf.layers.dense(x1, 10, activation=tf.nn.elu, name="fc2") x3 = tf.layers.dense(x2, 10, activation=tf.nn.elu, name="fc3") y = tf.layers.dense(x3, 10, activation=tf.nn.elu, name="fc4") with tf.device("/gpu:1"): _x = tf.random_normal([n, 10]) _x1 = tf.layers.dense(_x, 10, activation=tf.nn.elu, name="fc1x") _x2 = tf.layers.dense(_x1, 10, activation=tf.nn.elu, name="fc2x") _x3 = tf.layers.dense(_x2, 10, activation=tf.nn.elu, name="fc3x") _y = tf.layers.dense(_x3, 10, activation=tf.nn.elu, name="fc4x") queue = tf.FIFOQueue(10000, y.dtype, y.shape, shared_name='buffer') enqueue_ops = [] for _ in range(1): enqueue_ops.append(queue.enqueue(y)) enqueue_ops.append(queue.enqueue(_y)) tf.train.add_queue_runner(tf.train.QueueRunner(queue, enqueue_ops)) return queue # with sess.graph.as_default(): if __name__ == '__main__': queue = build() dequeued = queue.dequeue_many(4) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) tf.train.start_queue_runners() a_time = time.time() print(a_time) for _ in range(100000): sess.run(dequeued) b_time = time.time() print(b_time) print(b_time-a_time) time.sleep(11111)
0号显卡由于还在运行Firefox上的电影播放任务因此比一号卡使用率高了些,不过这个代码对两个显卡的利用率应该都是在32%左右。
运行时间:
可以看到对结果影响最多的还是使用两个线程分别调用两个显卡上两个不同的计算分支,从这里我们可以给出一个粗略的结论,那就是在TensorFlow中多线程调用session中的计算分支并不能有显著的性能提升,但是使用多线程调用同一个session中的不同GPU上的计算分支却可以极大的提升计算效率,不过这样的话和TensorFlow的多进程运行就比较像了,同时考虑到多线程编程的复杂性因此除了强化学习以外的机器学习代码如果想多线程加速运算那还不如使用单机多进程加速了。
其实,即使是深度学习框架中性能最强的TensorFlow在设计的最初也是针对单线程调用设计的,这里的单线程是只CPU端的单线程,如果CPU端是多线程调用同一个显卡上的计算图往往会由于cuda的stream默认队列的限制导致并不会有显著性能提升,当然从技术上来说完全深度学习框架完全可以在设计时就考虑到cuda指令执行的stream默认队列问题,或许是设计难度和适用面较窄的问题,即使是TensorFlow也没有提供多线程调用cuda kernel的多个stream队列,或许从目前来看多进程加速深度学习框架计算确实还是最优性价比的解决方法,虽然多进程的同步开销较大、用户编写代码的逻辑变得复杂,但是也完全可以弥补上深度学习框架提供该功能的厂家方的花销代价。
-----------------------------------
至少从目前来看,多线程调用深度学习框架其实还不如使用多进程调用深度学习框架来的合适,不过多进程调用深度学习框架必然要面对进程之间网络模型的同步问题,这又成了一个提高用户编码难度的一个点了。TensorFlow是属于少数提供多线程封装调用的深度学习框架,即使对于TensorFlow来说使用C++多线程调用不同cuda计算分支的性能也没有多进程调用不同cuda计算分支的性能高,再加上使用TensorFlow中的多线程本就是小众特征,难以切换到其他深度学习框架上使用,因此目前来看多进程调用cuda相比与多线程调用cuda才更是深度学习框架的正解,当然如果未来深度学习框架可以提高C++多线程调用不同计算分支的性能,那么或许以后有一天C++多线程调用深度框架的性能会优于多进程调用的。
---------------------------------------------
posted on 2022-11-03 00:13 Angry_Panda 阅读(337) 评论(0) 编辑 收藏 举报