1. 程式人生 > 其它 >tf計算矩陣維度_【tf.matmul 致命錯誤】請謹慎使用tensorflow 2.0

tf計算矩陣維度_【tf.matmul 致命錯誤】請謹慎使用tensorflow 2.0

技術標籤:tf計算矩陣維度

90b64d2ec82c90df7cebf2e5066d1429.png

2020/1/11更新:

在 tensorflow 2.1 及以上版本中,該bug已解決。

如何升級到 2.1 及以上版本,請移步:

Kevin:Anaconda 搭建 Tensorflow 2 開發環境​zhuanlan.zhihu.com dc9d2d2e881b518901c44fdb3ac51308.png
A 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。我進行了如下的測試,發現對於

的矩陣相乘,並行維度超過 將會產生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。