在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]
为什么会这样?
更新:
- 如果我将 prod = torch.mm(a,b) 替换为 torch.mm(prod,a,b ),这表明内存分配有问题。
- 我的 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_++;
}
评论是我的。
所以:
- with
torch.mm(a,b)
:在每次迭代中,都会创建一个新的结果张量而不进行初始化(它可能包含 NaN-s)。 所以每次迭代都存在 returning NaN-s 的风险(见上面的警告),
- 与
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)。
我在 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]
为什么会这样?
更新:
- 如果我将 prod = torch.mm(a,b) 替换为 torch.mm(prod,a,b ),这表明内存分配有问题。
- 我的 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_++;
}
评论是我的。
所以:
- with
torch.mm(a,b)
:在每次迭代中,都会创建一个新的结果张量而不进行初始化(它可能包含 NaN-s)。 所以每次迭代都存在 returning NaN-s 的风险(见上面的警告), - 与
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)。