=================================================
從前文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)
View Code

用時:
兩個線程的情況:
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)
View Code
用時:
四個線程的情況:
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)
View Code
用時:
八個線程的情況:
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)
View Code
用時:
================================================
可以看到使用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)
View Code
運算時間:
可以看到這個效果其實和同一個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)
View Code
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++多線程調用深度架構的性能會優于多程序調用的。