Simd Matmul程序给出不同的数值结果

时间:2019-04-02 14:51:51

标签: c floating-point vectorization simd avx

我正在尝试使用simd内在函数在C中编程矩阵乘法。我非常确定自己的实现方式,但是执行时,会从所得矩阵系数的第5位开始出现一些数字错误。

REAL_T只是具有typedef的浮点数

InventoryJob
SubJob
InventoryJob

测试的矩阵尺寸为512 * 512。 对于顺序版本,结果矩阵的左上角正方形为:

/* This is my matmul Version with simd, using floating simple precision*/
void matmul(int n, REAL_T *A, REAL_T *B, REAL_T *C){
  int i,j,k;
  __m256 vA, vB, vC, vRes;
  for (i=0; i<n; i++){
    for (j=0; j<n; j++){  
      for (k=0; k<n; k= k+8){
        vA = _mm256_load_ps(&A[i*n+k]);
        vB = _mm256_loadu_ps(&B[k*n+j]);
        vC = _mm256_mul_ps(vA, vB);
        vC = _mm256_hadd_ps(vC, vC);
        vC = _mm256_hadd_ps(vC, vC);
        /*To get the resulting coefficient, after doing 2 hadds,
        I have to get the first and the last element of the resulting
        Vector vC*/
        C[i*n+j] += ((float )(vC[0])) + ((float )(vC[7]));
      } /* for k */
    } /* for j */
  } /* for i */
}
*/End of program

但是,对于simd版本,正方形为:

/*And this is the sequential Version*/
void matmul(int n, REAL_T *A, REAL_T *B, REAL_T *C){
  int i,j,k;
  for (i=0; i<n; i++){ 
    for (j=0; j<n; j++){
      for (k=0; k<n; k++){
        C[i*n+j] +=  A[i*n+k] *  B[k*n+j];  
      } /* for k */
    } /* for j */
  } /* for i */  
}
/*End of program*/

如图所示,两个版本之间存在数值错误。 任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:9)

这看起来很正常;按不同的顺序添加数字会在临时中产生不同的舍入。

FP数学不是关联的; 1 Is Floating point addition and multiplication associative? / Are floating point operations in C associative?

更改量取决于数据。对于float,仅在小数点后第五位的差异似乎是合理的。


除非您采取特殊的数字预防措施(例如先将小数相加),否则顺序结果并不“更正确”,它们只是具有不同的错误。

实际上,假设您的数字都具有相似的数量级,使用多个累加器通常会提高精度。 (理想情况下,每个SIMD向量都由多个元素组成,以隐藏FP-add或FMA延迟)。   https://en.wikipedia.org/wiki/Pairwise_summation 是一种数字技术,可将其带入一个新的水平:将树中列表的子集求和,以避免将单个数组元素添加到更大的值。例如,参见How to avoid less precise sum for numpy-arrays with multiple columns

使用固定数量的累加器(例如8个__m256 = 64个float累加器)可以将预期误差减少64倍,而不是从N到对数N进行完整的成对求和。


脚注1:关联对于并行化,SIMD和多个累加器是必需的。 Associativity gives us parallelizability. But what does commutativity give?

在具有4个周期延迟,每时钟2个吞吐量的FMA的机器上,SIMD宽度为8个浮点数,即使用AVX2的Skylake系统,多个累加器的潜在加速比为4 * 2 = 8,* SIMD宽度,内核数乘以纯顺序版本可以得出8,即使对于可能精度较低而不是完全不同的问题。

大多数人认为值得8*8 = 64的因素值得! (并且在理论上,假设对大型矩阵进行完美缩放,您也可以在四核上并行化另外一个可能为4的因数。)

您已经在使用float而不是double来提高性能。

另请参阅Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables?,以了解更多有关使用多个累加器隐藏FMA延迟的信息,以了解其他8倍加速的因素。

也请不要在最内层的循环中使用hadd。垂直求和,并在循环结束时使用有效的归约。 (Fastest way to do horizontal float vector sum on x86)。您真的很想避免编译器在每一步都将向量提取到标量,这会抵消SIMD的大部分优势!除了hadd不适合用于1个向量的水平和的事实之外,在所有现有的CPU上花费2次混洗+常规的add