【问题标题】:Why does pytorch matmul get different results when executed on cpu and gpu?为什么在cpu和gpu上执行pytorch matmul会得到不同的结果?
【发布时间】:2022-01-11 12:41:04
【问题描述】:

我正在尝试找出 numpy/pytorch、gpu/cpu、float16/float32 数字之间的舍入差异,而我的发现让我感到困惑。

基本版是:

a = torch.rand(3, 4, dtype=torch.float32)
b = torch.rand(4, 5, dtype=torch.float32)
print(a.numpy()@b.numpy() - a@b)

结果如预期的那样全为零,但是

print((a.cuda()@b.cuda()).cpu() - a@b)

得到非零结果。 为什么 Pytorch float32 matmul 在 gpu 和 cpu 上的执行方式不同?

一个更令人困惑的实验涉及float16,如下:


a = torch.rand(3, 4, dtype=torch.float16)
b = torch.rand(4, 5, dtype=torch.float16)
print(a.numpy()@b.numpy() - a@b)
print((a.cuda()@b.cuda()).cpu() - a@b)

这两个结果都是非零的。 为什么numpy和torch处理float16数字的方式不同?我知道cpu只能做float32操作,numpy在计算前将float16转换为float32,但是torch计算也是在cpu上执行的。

你猜怎么着,print((a.cuda()@b.cuda()).cpu() - a.numpy()@b.numpy()) 得到一个全为零的结果!这对我来说纯粹是幻想......

环境如下:

  • 蟒蛇:3.8.5
  • 火炬:1.7.0
  • numpy:1.21.2
  • cuda:11.1
  • GPU:GeForce RTX 3090

根据一些评论者的建议,我添加了以下相等测试

(a.numpy()@b.numpy() - (a@b).numpy()).any()
((a.cuda()@b.cuda()).cpu() - a@b).numpy().any()
(a.numpy()@b.numpy() - (a@b).numpy()).any()
((a.cuda()@b.cuda()).cpu() - a@b).numpy().any()
((a.cuda()@b.cuda()).cpu().numpy() - a.numpy()@b.numpy()).any()

分别直接跟上面五个打印函数,结果分别是:

False
True
True
True
False

而对于最后一个,我试了好几次,我想我可以排除运气。

【问题讨论】:

  • “非零结果”是什么意思?
  • 据我所知,使用浮点类型获得可重现结果的唯一方法是使用严格模式,我不知道 Python 或这些库中的任何一个使用什么。您可以尝试将您的差异测试更改为等于测试以排除引入错误的减法,但我希望 x-x 像您所做的那样无论模式如何都会变为 0.0
  • @talonmies 这意味着返回的张量的某些元素具有非零值,例如上面的第一个 print((a.cuda()@b.cuda()).cpu() - a@b) 返回:张量([[-1.6999e-04, 7.8678e-06, -1.6534e-04, -1.2589e-04, -1.5211e-04], [ 3.4809e-05, 1.1599e-04 , 9.6798e-05, 2.5213e-05, 1.9252e-05], [-1.6284e-04, 4.0352e-05, -2.1398e-04, -7.8559e-05, -2.4378e-04]])
  • @talonmies 而对于全零结果,我的意思是每个术语都有 0.0 的张量
  • 它们代表什么是产品数量的相对误差?您确定零点不仅仅是表示问题。 np.all_zeronp.all_close 是非常有用的工具,试试看你会得到什么

标签: numpy pytorch rounding


【解决方案1】:

如@talonmies 所述,差异主要是数字。 CPU/GPU 及其各自的 BLAS 库的实现方式不同,使用不同的操作/操作顺序,因此存在数值差异。

一个可能的原因是顺序操作与缩减 (https://discuss.pytorch.org/t/why-different-results-when-multiplying-in-cpu-than-in-gpu/1356/3),例如(((a+b)+c)+d) 与 ((a+b)+(c+d)) 相比具有不同的数值属性。

This question 还谈到了可能导致数值差异的融合操作(乘加)。

我做了一点测试,发现如果我们在计算前将数据类型提升为float32,然后将其降级,可以匹配GPU在float16模式下的输出。这可能是由内部中间转换或融合操作的更好的数值稳定性引起的(torch.backends.cudnn.enabled 无关紧要)。但这并不能解决 float32 中的情况。

import torch

def test(L, M, N):
    # test (L*M) @ (M*N)
    for _ in range(5000):
        a = torch.rand(L, M, dtype=torch.float16)
        b = torch.rand(M, N, dtype=torch.float16)

        cpu_result = a@b
        gpu_result = (a.cuda()@b.cuda()).cpu()
        if (cpu_result-gpu_result).any():
            print(f'({L}x{M}) @ ({M}x{N}) failed')
            return
    else:
        print(f'({L}x{M}) @ ({M}x{N}) passed')


test(1, 1, 1)
test(1, 2, 1)
test(4, 1, 4)
test(4, 4, 4)

def test2():
    for _ in range(5000):
        a = torch.rand(1, 2, dtype=torch.float16)
        b = torch.rand(2, 1, dtype=torch.float16)

        cpu_result = a@b
        gpu_result = (a.cuda()@b.cuda()).cpu()

        half_result = a[0,0]*b[0,0] + a[0,1]*b[1,0]
        convert_result = (a[0,0].float()*b[0,0].float() + a[0,1].float()*b[1,0].float()).half()

        if ((cpu_result-half_result).any()):
            print('CPU != half')
            return
        if (gpu_result-convert_result).any():
            print('GPU != convert')
            return
    else:
        print('All passed')

test2()

输出:

(1x1) @ (1x1) passed
(1x2) @ (2x1) failed
(4x1) @ (1x4) passed
(4x4) @ (4x4) failed
All passed

你可以看出,当内部维度为1时,它通过了检查(不需要乘加/减)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-24
    • 2016-07-16
    • 1970-01-01
    • 2015-10-01
    • 1970-01-01
    • 2019-03-08
    • 2015-12-24
    • 1970-01-01
    相关资源
    最近更新 更多