Матричное умножение на CPU (numpy) и GPU (gnumpy) дает разные результаты

Я использую gnumpy, чтобы ускорить некоторые вычисления в обучении нейронной сети, выполняя их на GPU. Я получаю желаемое ускорение, но немного беспокоюсь о различиях в результатах numpy (cpu) vs gnumpy (gpu).

У меня есть следующий тест script, чтобы проиллюстрировать проблему:

import gnumpy as gpu
import numpy as np

n = 400

a = np.random.uniform(low=0., high=1., size=(n, n)).astype(np.float32)
b = np.random.uniform(low=0., high=1., size=(n, n)).astype(np.float32)

ga = gpu.garray(a)
gb = gpu.garray(b)

ga = ga.dot(gb)
a  = a.dot(b)

print ga.as_numpy_array(dtype=np.float32) - a

который обеспечивает вывод:

[[  1.52587891e-05  -2.28881836e-05   2.28881836e-05 ...,  -1.52587891e-05
    3.81469727e-05   1.52587891e-05]
 [ -5.34057617e-05  -1.52587891e-05   0.00000000e+00 ...,   1.52587891e-05
    0.00000000e+00   1.52587891e-05]
 [ -1.52587891e-05  -2.28881836e-05   5.34057617e-05 ...,   2.28881836e-05
    0.00000000e+00  -7.62939453e-06]
 ..., 
 [  0.00000000e+00   1.52587891e-05   3.81469727e-05 ...,   3.05175781e-05
    0.00000000e+00  -2.28881836e-05]
 [  7.62939453e-06  -7.62939453e-06  -2.28881836e-05 ...,   1.52587891e-05
    7.62939453e-06   1.52587891e-05]
 [  1.52587891e-05   7.62939453e-06   2.28881836e-05 ...,  -1.52587891e-05
    7.62939453e-06   3.05175781e-05]]

Как вы можете видеть, различия составляют величину 10 ^ -5.

Итак, вопрос: следует ли беспокоиться об этих различиях или это ожидаемое поведение?

Дополнительная информация:

  • GPU: GeForce GTX 770;
  • numpy version: 1.6.1

Я заметил проблему, когда я использовал проверку градиента (с конечной разностной аппроксимацией), чтобы проверить, что небольшие изменения, которые я сделал для переключения с numpy на gnumpy, не нарушали ничего. Поскольку можно ожидать, что проверка градиента не будет работать с 32-разрядной точностью (gnumpy не поддерживает float64), но, к моему удивлению, ошибки различались между CPU и GPU при использовании той же точности.

Ниже приведены ошибки в CPU и GPU на небольшой тестовой нейронной сети: gradient checking errors

Поскольку значения ошибок аналогичны, я думаю, что эти различия в порядке?

После прочтения статьи упомянутой в комментарии BenC, я вполне уверен, что различия могут быть в основном объяснены одним из устройства с использованием команды плавного умножения (FMA), а другая нет.

Я применил пример из статьи:

import gnumpy as gpu
import numpy as np

a=np.array([1.907607,-.7862027, 1.147311, .9604002], dtype=np.float32)
b=np.array([-.9355000, -.6915108, 1.724470, -.7097529], dtype=np.float32)

ga = gpu.garray(a)
gb = gpu.garray(b)

ga = ga.dot(gb)
a  = a.dot(b)

print "CPU", a
print "GPU", ga
print "DIFF", ga - a

>>>CPU 0.0559577
>>>GPU 0.0559577569366
>>>DIFF 8.19563865662e-08

... и разница похожа на FMA по сравнению с последовательным алгоритмом (хотя по некоторым причинам оба результата отличаются от точного результата больше, чем в статье).

Графический процессор, который я использую (GeForce GTX 770), поддерживает инструкцию FMA, пока процессор не работает (у меня есть Ivy Bridge Intel® Xeon (R) CPU E3-1225 V2, но Intel представила инструкцию FMA3 в своих продуктах с Хасуэлл).

Другие возможные объяснения включают в себя различные математические библиотеки, используемые в фоновом режиме, или различия в последовательности операций, вызванные, например, разным уровнем распараллеливания на CPU и GPU.

Ответ 1

Я бы рекомендовал использовать np.allclose для проверки того, являются ли два массива с плавающей точкой почти равными.

В то время как вы смотрите только на абсолютную разницу между значениями в ваших двух массивах результатов, np.allclose также учитывает их относительные различия. Предположим, например, что значения в ваших входных массивах были на 1000 раз больше - тогда абсолютные различия между этими двумя результатами также будут на 1000 раз больше, но это не означает, что два точечных продукта были менее точными.

np.allclose вернет True только в том случае, если для каждой соответствующей пары элементов в ваших двух тестовых массивах выполнено следующее условие: a и b:

abs(a - b) <= (atol + rtol * abs(b))

По умолчанию rtol=1e-5 и atol=1e-8. Эти допуски являются хорошим "правилом", но достаточно ли они в вашем случае будут зависеть от вашего конкретного приложения. Например, если вы имеете дело со значениями < 1e-8, то абсолютная разница 1e-8 будет полной катастрофой!

Если вы попробуете призывать np.allclose к вашим двум результатам с допусками по умолчанию, вы обнаружите, что np.allclose возвращает True. Я предполагаю, что эти различия, вероятно, достаточно малы, что их не стоит беспокоиться. Это действительно зависит от того, что вы делаете с результатами.

Ответ 2

Карты RTX имеют плавающую точку с половинной точностью, потому что это быстрее для рендеринга изображений. Вы должны указать GPU использовать полную точность при умножении числа с плавающей запятой на AI. Точность чрезвычайно важна при выполнении ИИ.

Я испытал ту же разницу с плавающей точкой, что и вы, когда пытались использовать Cuda с RTX 2080 Ti.