在Lua Torch中,两个零矩阵的乘积有nan项

In Lua Torch, the product of two zero matrices has nan entries

我在 Lua/Torch 中遇到了 torch.mm 函数的奇怪行为。这是一个演示问题的简单程序。

iteration = 0;
a = torch.Tensor(2, 2);
b = torch.Tensor(2, 2);
prod = torch.Tensor(2,2);

a:zero();
b:zero();

repeat
   prod = torch.mm(a,b);
   ent  = prod[{2,1}]; 
   iteration = iteration + 1;
until ent ~= ent

print ("error at iteration " .. iteration);
print (prod);

该程序由一个循环组成,其中程序将两个零 2x2 矩阵相乘并测试乘积矩阵的条目 ent 是否等于 nan。似乎程序应该永远 运行 因为产品应该总是等于 0,因此 ent 应该是 0。但是,程序打印:

error at iteration 548   
0.000000 0.000000
nan nan
[torch.DoubleTensor of size 2x2]

为什么会这样?

更新:

  1. 如果我将 prod = torch.mm(a,b) 替换为 torch.mm(prod,a,b ),这表明内存分配有问题。
  2. 我的 Torch 版本是在没有 BLAS 和 LAPACK 库的情况下编译的。我用 OpenBLAS 重新编译 torch 后,问题就消失了。不过,我还是对它的成因很感兴趣。

可以找到为 torch.mm 自动生成 Lua 包装的代码部分 here

当您在循环中编写 prod = torch.mm(a,b) 时,它对应于幕后的以下 C 代码(由于 cwrap 而由该包装器生成):

/* this is the tensor that will hold the results */
arg1 = THDoubleTensor_new(); 
THDoubleTensor_resize2d(arg1, arg5->size[0], arg6->size[1]);
arg3 = arg1;
/* .... */
luaT_pushudata(L, arg1, "torch.DoubleTensor");
/* effective matrix multiplication operation that will fill arg1 */
THDoubleTensor_addmm(arg1,arg2,arg3,arg4,arg5,arg6);

所以:

  • 创建了一个新的结果张量并使用适当的尺寸调整了大小,
  • 但是这个新张量没有初始化,即这里没有calloc或显式填充所以它指向垃圾内存并且可能包含NaN-s,
  • 这个张量被压入堆栈,以便在 Lua 端作为 return 值可用。

最后一点意味着这个 returned 张量不同于初始的 prod 张量(即在循环中,prod 隐藏了初始值)。

另一方面,调用 torch.mm(prod,a,b) 使用您的初始 prod 张量来存储结果(在幕后不需要创建在这种情况下专用张量)。由于在您的代码片段中您没有使用给定值初始化/填充它,因此它也可能包含垃圾。

在这两种情况下,核心运算都是 gemm 乘法,例如 C = beta * C + alpha * A * B,其中 beta=0 和 alpha=1。 naive implementation 看起来像这样:

  real *a_ = a;
  for(i = 0; i < m; i++)
  {
    real *b_ = b;
    for(j = 0; j < n; j++)
    {
      real sum = 0;
      for(l = 0; l < k; l++)
        sum += a_[l*lda]*b_[l];
      b_ += ldb;
      /*
       * WARNING: beta*c[j*ldc+i] could give NaN even if beta=0
       *          if the other operand c[j*ldc+i] is NaN!
       */
      c[j*ldc+i] = beta*c[j*ldc+i]+alpha*sum;
    }
    a_++;
  }

评论是我的。

所以:

  1. with torch.mm(a,b):在每次迭代中,都会创建一个新的结果张量而不进行初始化(它可能包含 NaN-s)。 所以每次迭代都存在 returning NaN-s 的风险(见上面的警告),
  2. torch.mm(prod,a,b):存在相同的风险,因为您没有初始化prod张量。但是:此风险仅存在于 repeat / until 循环的第一次迭代中,因为在 prod 之后立即填充 0-s 并重新用于后续迭代。

所以这就是为什么您在这里没有观察到问题(发生频率较低)。

情况 1:这应该在 Torch 级别进行改进,即确保包装器初始化输出(例如 THDoubleTensor_fill(arg1, 0);)。

情况 2:您应该首先初始化 prod 并使用 torch.mm(prod,a,b) 构造来避免任何 NaN 问题。

--

编辑:这个问题现在已经解决(见pull request)。