为什么在blas gemm函数系列中不允许非正向步长?

时间:2017-07-02 21:10:56

标签: c++ c arrays blas stride

The netlib documentation of sgemm声明数组步幅LDALDB必须为>= 1,并且足够大以使列不重叠。实际上,Apple的Accelerate / veclib框架中的实现会检查这些条件,并且如果它们被违反则存在。

我真的不明白为什么存在这种限制。 BLAS不能简单地相信我,我真的想要一个零步或消极步幅吗?据我了解,默认情况下Fortran整数是签名的,所以参数类型似乎不是起源(免责声明:我不太了解Fortan)。

实际上,非正数组步幅存在非常合理的用例:

  • 零步幅:在多维数组类中,零步幅可以实现numpy式广播。
  • 负步幅:否定步幅允许沿任意轴以相反顺序查看数组而无需复制。这可能是有用的,例如翻转卷积内核时,and convolutions can be implemented efficiently using gemm。或者,可以翻转图像的垂直轴,这是很方便的,因为存在不同的约定:在postscript / pdf中向上指向轴,在png格式中向下指向(以及许多其他)。

我对两个方面感兴趣:

  1. 我想了解限制存在的原因。是否真的只是因为BLAS的设计者没有考虑过这样的用例?我是否有人试图捕捉到一个确实是一个功能的错误?或者限制是否会带来更好的性能?我很难想象后者。
  2. 有没有办法在不牺牲(太多)性能的情况下解决限制问题?现在我需要一些可以在Mac上用C ++工作的东西,但从长远来看它应该仍然基于BLAS,所以它可以跨平台工作。

1 个答案:

答案 0 :(得分:0)

我最近发现自己有效地做到了这一点:

double s[4] = {10., 2., 3.1, 4.1};
dscal_(4, 3., s, -1);
assert( s[1] == 2.*3. );

dscal_是最简单的BLAS函数,将数组乘以标量,其签名为:

void sscal(int, double, double*, int); // doesn't seem to return anything useful

在我的BLAS特定发行版(Fedora 28附带)中,这给出了一个无声错误,因为该函数似乎没有任何作用。 此外,dscal_甚至似乎都没有返回错误代码,因此没有包装函数就无法捕获此错误(我的数组在运行时具有正向或负向的步幅,但是我无法在所有情况下都控制该值)。

所有这些情况均失败:

double s[4] = {10., 2., 3.1, 4.1};
dscal_(4, 3., s, -1); // negative incx does nothing
dscal_(4, 3., &s[4], -1); // negative incx does nothing
dscal_(-4, 3., &s[4], 1); // negative n does nothing
dscal_(-4, 3., &s[4], -1); // negative n or incx does nothing
assert( s[1] == 2. );

这告诉我,尽管步幅(incx)可能必须为正数(以及大小),但可能没有记录在案。 幸运的是,对于许多BLAS函数,可以将调用转换为积极的步伐。 我需要一个包装器函数以任何方式进行调试,因此编写了以下包装器函数:

void signed_dscal(int n, double alpha, double* x, int incx){
    int prod = incx*n;
    if(prod > 0) dscal(abs(n), alpha, x, abs(incx));
    else         dscal(abs(n), alpha, x + prod, abs(incx));
}

通过这种方式,我可以以积极或消极的步伐和大小来呼叫signed_dscal

int main(){
{
    double d[4] = {10., 2., 3.1, 4.1};
    signed_dscal(4, 3., d, 1);
    assert( d[1] == 6. );
}
{
    double d[4] = {10., 2., 3.1, 4.1};
    signed_dscal(4, 3., &d[4], -1);
    assert( d[1] == 6. );
}
{
    double d[4] = {10., 2., 3.1, 4.1};
    signed_dscal(-4, 3., &d[4], 1);
    assert( d[1] == 6. );
}
{
    double d[4] = {10., 2., 3.1, 4.1};
    signed_dscal(-4, 3., d, -1);
    assert( d[1] == 6. );
}    
    return 0;
}

(请注意,incx=0仍然是无法修改的情况。)

我仍然不明白这背后的逻辑是什么。也许BLAS的某些实现将默认允许您执行此操作,而另一些实现将尝试防止无限循环(其副作用是假定步幅为正)。