tf計算矩陣維度_【tf.matmul 致命錯誤】請謹慎使用tensorflow 2.0
技術標籤:tf計算矩陣維度
2020/1/11更新:
在 tensorflow 2.1 及以上版本中,該bug已解決。
如何升級到 2.1 及以上版本,請移步:
Kevin:Anaconda 搭建 Tensorflow 2 開發環境zhuanlan.zhihu.comA fatal error is a bug that does not report any errors, so we cannot locate or resolve it.
已經有許多博文介紹了tensorflow 2.0先進的設計理念和人性化的新特性:相較於tf1.0,tf2.0 刪去了很多反直覺的概念和方法,並且使用動態圖機制,與python完美融合等等等。而且近半年tf2.0版本不斷迭代,終於從測試版的α、β、c一路更新到穩定版的stable,是時候盡情擁抱tf2.0了......了嗎?
穩定版 tf2.0 stable 真的 stable 嗎?
tensorflow 是一個用於數值計算的庫(廢話),矩陣運算是它進行數值計算的基礎操作(廢話),那麼如果矩陣運算,即常用的tf.matmul操作出現了一個詭異的、不會報錯的bug呢? (;° ロ°)
github 上關於這個 bug 的 issue:A puzzling & fatal error occurred in the tf.matmal()
該 bug 在colab上的復現:here
高維張量的乘法
首先再複習一下,高維張量的乘法:
''' 對於兩個張量: a.shape = [dim_1,...,dim_n, l, k] b.shape = [dim_1,...,dim_n, k, m] 只要最後的兩個維度滿足矩陣乘法的要求,而其他維度(並行維度)大小相等,則可以進行如下乘法操作: ''' c = tf.matmul(a, b) ''' 結果: c.shape = [dim_1,...,dim_n, l, m] '''
展開來相當於平行計算多組矩陣乘法,因此這種操作非常適合於在gpu上執行:
顯然地,平行計算的結果應該等於分開來計算的結果,亦即tf.matmul(a[i], b[i])
應該等於tf.matmul(a, b)[i]
。但是當tensor的維度較大時,這個顯然的性質竟然不成立,bug來了。
bug初體驗
你可以通過下面的例子來體會這個bug:
當使用gpu計算較大維度tensor的tf.matmul
時,將會出現如下錯誤:
j = np.random.rand(10, 6, 1130, 16, 8) k = np.random.rand(10, 6, 1130, 8, 1) j = tf.cast(j, dtype=tf.float32) k = tf.cast(k, dtype=tf.float32) a = tf.matmul(j, k)[9, 3] # 大維度tensor相乘 b = tf.matmul(j[9], k[9])[3] c = tf.matmul(j[9, 3], k[9, 3]) print(tf.reduce_all(tf.equal(a, b))) print(tf.reduce_sum(a-b)) print(tf.reduce_all(tf.equal(b, c))) ''' tf.Tensor(False, shape=(), dtype=bool) # 正確的值應該是 True tf.Tensor(-1.3804454e+38, shape=(), dtype=float32) # 比較了a和b的具體差異,發現造成錯誤的並不是細微的差異 tf.Tensor(True, shape=(), dtype=bool) '''
而使用cpu則不會出現該錯誤:
...
with tf.device("CPU:0"):
a = tf.matmul(j, k)[9, 3]
b = tf.matmul(j[9], k[9])[3]
c = tf.matmul(j[9, 3], k[9, 3])
print(tf.reduce_all(tf.equal(a, b)))
print(tf.reduce_sum(a-b))
print(tf.reduce_all(tf.equal(b, c)))
'''
tf.Tensor(True, shape=(), dtype=bool)
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor(True, shape=(), dtype=bool)
'''
有趣的是,只要稍微將其中一個維度變小,比如將 1130 減一,同樣使用gpu,也不會發生錯誤。
# j = np.random.rand(10, 6, 1130, 16, 8)
# k = np.random.rand(10, 6, 1130, 8, 1)
j = np.random.rand(10, 6, 1129, 16, 8) # 1130 --> 1129
k = np.random.rand(10, 6, 1129, 8, 1)
j = tf.cast(j, dtype=tf.float32)
k = tf.cast(k, dtype=tf.float32)
a = tf.matmul(j, k)[9, 3]
b = tf.matmul(j[9], k[9])[3]
c = tf.matmul(j[9, 3], k[9, 3])
print(tf.reduce_all(tf.equal(a, b)))
print(tf.reduce_sum(a-b))
print(tf.reduce_all(tf.equal(b, c)))
'''
tf.Tensor(True, shape=(), dtype=bool)
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor(True, shape=(), dtype=bool)
'''
進一步地,我們猜想對於使用gpu的tf.matmul
操作,輸入tensor的並行維度有一個上限大小,超過它就會出現bug。我進行了如下的測試,發現對於
offset = 0
while True:
j = np.random.rand(*(65530+offset*1 , 16, 8))
k = np.random.rand(*(65530+offset*1 , 8, 1))
# with tf.device("CPU:0"):
j = tf.cast(j, dtype=tf.float32)
k = tf.cast(k, dtype=tf.float32)
a = tf.matmul(j, k)[-1]
b = tf.matmul(j[-1], k[-1])
print(offset)
if not tf.reduce_all(tf.equal(a, b)).numpy():
break
offset += 1
print(65530+offset*1)
'''
65536 (is 2^16)
'''
但是很遺憾的是,對於不同大小的矩陣相乘,並行維度的上限並不確定,似乎是矩陣越大、並行維度上限越小,但並沒有確切的規律。
bug特點
首先:雖然我們知道並行維度過大將會產生bug,但是我們並不知道過大的標準是什麼,而且這個標準似乎隨著矩陣的大小在不規律變化。
並且:這個 bug 不會報錯,你不知道它是否發生、什麼時候發生。
因此:這是一個不可控的bug(除非你不使用gpu,但那樣你為什麼還要用tensorflow呢)。
tf.matmul
的應用廣泛,比如卷積的底層實現就用到了,因此這種bug會影響網路的深層執行,你又一時半會發現不了,排查也非常困難。
其他
這個bug是否產生自顯示卡記憶體的限制?
不是,無論是在僅有2gb記憶體的gtx 850,還是8g記憶體的rtx 2070,bug都在矩陣大小超過相同值時產生。
bug與系統有關嗎?
無論是 Linux Ubuntu 18.04 還是 win 10 都存在這個bug。
bug與tf2.0的版本有關嗎?
在tf2.0 α、β、c 和 stable 上都存在這個bug。