【问题标题】:sparse x dense matrix multiplication performance under-efficient稀疏 x 密集矩阵乘法性能低效
【发布时间】:2016-09-17 12:55:04
【问题描述】:

上下文:我将 Eigen 用于人工神经网络,其典型尺寸约为每层 1000 个节点。因此,大多数操作是将大小为 ~(1000,1000) 的矩阵 M 与大小为 1000 的向量或一批 B 向量相乘,它们表示为大小为 Bx1000 的矩阵

训练神经网络后,我正在使用修剪 - 这是一种常见的压缩技术,最终得到稀疏矩阵(非空参数的密度在 10% 到 50% 之间)。

目标:我想将稀疏矩阵用于压缩目的,其次用于性能优化,但这不是主要目标

问题: 我正在比较不同批量大小的稀疏和密集矩阵乘法(仅计算乘法时间)的性能,并且我观察到以下内容(使用 Eigen 3.2.8、MacBook Pro 64 位、没有 open_mp 并使用标准 g++):

  • 当 B=1(矩阵 x 向量)时 - 密度为 10% 或 30% 的稀疏矩阵运算比密集矩阵运算更有效 - 这似乎是预期的结果:执行的运算要少得多
  • 对于 B=32:
    • 密集矩阵运算所需的时间仅为 B=1 所需时间的 10 倍左右 - 这很酷 - 是否显示了一些矢量化效果?
    • 稀疏矩阵运算所需的时间是 B=1 所需时间的 67 倍 - 这意味着它的效率低于独立处理 32 个向量

MxN multiplication time (ms) for M sparse/dense, and N of size 1000xB

Same numbers but showing the time per vector in a batch of different size for sparse and dense matrix. We see clearly the decrease of time for dense matrix when batch size increase, and the augmentation for sparse matrix showing some wrong. Normalized with time for B=1

代码: 我将以下类型用于稀疏和密集矩阵:

typedef SparseMatrix<float> spMatFloat;
typedef Matrix<float, Dynamic, Dynamic, RowMajor> deMatRowFloat;

我进行基准测试的操作如下:

o.noalias()=m*in.transpose();

其中o是稠密矩阵(1000xB),m是稠密矩阵(1000x1000)或对应的用m.sparseView()得到的稀疏矩阵,in是稠密矩阵(Bx1000)

下面是完整代码(20 个不同随机矩阵的平均时间,每次乘法运行 50 次)- B=32 和 B=1 的时间如下。

欢迎任何反馈/直觉!


batch   1   ratio   0.3 dense   0.32    sparse  0.29
batch   32  ratio   0.3 dense   2.75    sparse  15.01

#include <Eigen/Sparse>
#include <Eigen/Dense>
#include <stdlib.h>
#include <boost/timer/timer.hpp>

using namespace Eigen;
using namespace boost::timer;

typedef SparseMatrix<float> spMatFloat;
typedef Matrix<float, Dynamic, Dynamic, RowMajor> deMatRowFloat;

void bench_Sparse(const spMatFloat &m, const deMatRowFloat &in, deMatRowFloat &o) {
  o.noalias()=m*in.transpose();
}

void bench_Dense(const deMatRowFloat &m, const deMatRowFloat &in, deMatRowFloat &o) {
  o.noalias()=m*in.transpose();
}

int main(int argc, const char **argv) {
  float ratio=0.3;
  int iter=20;
  int batch=32;
  float t_dense=0;
  float t_sparse=0;

  deMatRowFloat d_o1(batch,1000);
  deMatRowFloat d_o2(batch,1000);
  for(int k=0; k<iter; k++) {
    deMatRowFloat d_m=deMatRowFloat::Zero(1000,1000);
    deMatRowFloat d_b=deMatRowFloat::Random(batch,1000);
    for(int h=0;h<ratio*1000000;h++) {
      int i=rand()%1000;
      int j=rand()%1000;
      d_m(i,j)=(rand()%1000)/500.-1;
    }
    spMatFloat s_m=d_m.sparseView();
    {
      cpu_timer timer;
      for(int k=0;k<50;k++) bench_Dense(d_m,d_b,d_o1);
      cpu_times const elapsed_times(timer.elapsed());
      nanosecond_type const elapsed(elapsed_times.system+elapsed_times.user);
      t_dense+=elapsed/1000000.;
    }
    {
      cpu_timer timer;
      for(int k=0;k<50;k++) bench_Sparse(s_m,d_b,d_o2);
      cpu_times const elapsed_times(timer.elapsed());
      nanosecond_type const elapsed(elapsed_times.system+elapsed_times.user);
      t_sparse+=elapsed/1000000.;
    }
  }
  std::cout<<"batch\t"<<batch<<"\tratio\t"<<ratio<<"\tdense\t"<<t_dense/50/iter<<"\tsparse\t"<<t_sparse/50/iter<<std::endl;
}

ggael 建议后的新结果:我尝试了不同的可能组合,发现在更改MB RowMajor/ColMajor 时性能确实存在巨大差异。

总而言之,我有兴趣做M*B,其中M 是(1000,1000),B 是(1000,batch):我有兴趣比较M sparse/dense 和batch 何时增长的性能.

我测试了 3 种配置:

  • M 密集,B 密集
  • M 稀疏,B 密集
  • M 稀疏,B 密集,但 M*B 的乘法是逐列手动完成的

结果如下 - 其中数字是 B=32 时每列时间/B=1 时每列时间的比率,矩阵 M 密度为 0.3:

最初报告的问题是最坏的情况(M ColMajor,B RowMajor)。对于 (M RowMajor, B ColMajor),B=32 和 B=1 之间有 5 倍的加速,稀疏矩阵的性能几乎等同于密集矩阵。

【问题讨论】:

  • 你在使用编译器优化吗?
  • 是的,使用 -O2 -msse4

标签: c++ eigen


【解决方案1】:

在 Eigen 中,对于稠密代数,矩阵-向量和矩阵-矩阵乘积都经过高度优化,并充分利用了向量化。正如您所观察到的,矩阵-矩阵产品表现出更高的效率。这是因为矩阵-矩阵乘积可以通过增加算术运算次数和内存访问次数之间的比率以及利用内存缓存来进一步优化。

那么对于稀疏-密集产品,有两种策略:

  1. 一次处理密集的右侧一列,从而多次扫描稀疏矩阵。对于此策略,最好对密集矩阵(右侧和结果)使用以列为主的存储。在 Eigen 3.2 中,已通过手动扫描列来模拟此策略。
  2. 只扫描稀疏矩阵一次,并处理密集右侧的行并导致最嵌套的循环。这是 Eigen 3.2 中的默认策略。在这种情况下,最好对密集矩阵使用行优先存储 (Matrix&lt;float,Dynamic,32,RowMajor&gt;)。

最后,无论哪种情况,您都可以尝试对稀疏矩阵同时使用行优先和列优先存储,并找出最适合您的情况的稀疏矩阵的策略和存储顺序组合。

p>

【讨论】:

  • 确实有效!非常感谢,我为所有配置添加了新结果。仍然 - 初始情况对于本征来说是有问题的,因为(独立于 DensexDense) - B(1000,32)的(M = SparsexB = Dense)的性能比32次B(1000,1)更差(慢两倍) -这是不对的,因为每个列产品的“手动”列可以提供更好的性能。是否不可能处理这种特殊模式 Sparse ColMajorxDense RowMajor - 至少使用“手动”方法:我的代码很简单:for(int i=0; i
猜你喜欢
  • 2021-01-31
  • 1970-01-01
  • 2014-03-06
  • 2013-05-20
  • 1970-01-01
  • 2013-09-06
  • 2021-04-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多