坦克需要多少FLOP?

Mar*_*oma 8 python flops tensorflow

我想计算每层LeNet-5(纸张)需要多少翻牌.一些论文给触发器其他架构共(1,2,3)但是,这些文件没有就如何计算FLOPS的数量细节,我不知道有多少触发器的非线性激活功能是必要的.例如,计算需要多少FLOP tanh(x)

我想这将是实现,也可能是硬件特定的.但是,我主要感兴趣的是获得一个数量级.我们在谈论10个FLOP吗?100 FLOP?1000 FLOP?因此,选择您想要的任何架构/实现来获得答案.(虽然我很欣赏接近"常见"设置的答案,例如Intel i5/nvidia GPU/Tensorflow)

Run*_*ith 6

注意:这个答案不是特定于python的,但我认为tanh之类的东西在语言上根本不同.

Tanh通常通过定义上限和下限来实现,其中分别返回1和-1.中间部分用不同的函数近似如下:

 Interval 0  x_small               x_medium               x_large 
  tanh(x) |  x  |  polynomial approx.  |  1-(2/(1+exp(2x)))  |  1
Run Code Online (Sandbox Code Playgroud)

存在精确到多个精确浮点的多项式,并且还存在双精度.该算法称为Cody-Waite算法.

引用这个描述(你可以在那里找到关于数学的更多信息,例如如何确定x_medium),Cody和Waite的理性形式需要四次乘法,三次加法,一次单精度除法,七次乘法,六次加法,以及双精度的一个师.

对于负x,您可以计算| x | 并翻转标志.因此,您需要比较x所在的区间,并评估相应的近似值.这总共:

  1. 取x的绝对值
  2. 3间隔的比较
  3. 根据间隔和浮点精度,指数的0到几个FLOPS,检查如何计算指数的这个问题.
  4. 一个比较,以决定是否翻转标志.

现在,这是1993年的一份报告,但我认为这里没有太大的改变.


ead*_*ead 5

如果我们看一下glibc实现tanh(x),我们看到:

  1. 对于 x值大于22.0和双精度的值,tanh(x)可以安全地假设为1.0,因此几乎没有成本.
  2. 对于非常小的x(假设x<2^(-55))另一种廉价的近似是可能的:tanh(x)=x(1+x)因此只需要两个浮点运算.
  3. 对于beetween中的值,可以重写tanh(x)=(1-exp(-2x))/(1+exp(-2x)).然而,一个必须是准确的,因为1-exp(t)由于重要性的损失而对于小的t值是非常有问题的,因此人们使用expm(x)=exp(x)-1并计算tanh(x)=-expm1(-2x)/(expm1(-2x)+2).

所以基本上,最坏的情况大约是所需操作次数的2倍expm1,这是一个相当复杂的功能.最好的方法可能只是测量计算tanh(x)所需的时间与两个双精度乘法所需的时间相比较.

我在英特尔处理器上的(草率)实验产生了以下结果,这给出了一个粗略的想法:

在此输入图像描述

因此,对于非常小且数量> 22的几乎没有成本,对于0.1我们支付6 FLOPS的数字,然后成本上升到大约20 FLOPS每个tanh计算.

关键点:计算成本tanh(x)取决于参数x,最大成本介于10到100 FLOP之间.


有称为Intel的指令F2XM1,其计算2^x-1-1.0<x<1.0,其可用于计算tanh,至少对于一些范围.但是,如果要相信agner的表格,这项操作的成本约为60 FLOP.


另一个问题是矢量化 - 就我所见,正常的glibc实现没有矢量化.因此,如果您的程序使用向量化并且必须使用非向量化tanh实现,则会进一步减慢程序的速度.为此,intel编译器具有mkl库,该库可以在其他库中进行矢量化tanh.

正如您在表中所看到的,每次操作的最大成本约为10个时钟(浮点运算的成本约为1个时钟).


我猜你有可能通过使用-ffast-math编译器选项赢得一些FLOP ,这会导致程序更快但更不精确(这是Cuda或c/c ++的一个选项,不确定是否可以为python/numpy完成).


产生图形数据的c ++代码(用g ++ -std = c ++ 11 -O2编译).它的目的不是给出确切的数字,而是关于成本的第一印象:

#include <chrono>
#include <iostream>
#include <vector>
#include <math.h>

int main(){
   const std::vector<double> starts={1e-30, 1e-18, 1e-16, 1e-10, 1e-5, 1e-2, 1e-1, 0.5, 0.7, 0.9, 1.0, 2.0, 10, 20, 23, 100,1e3, 1e4};
   const double FACTOR=1.0+1e-11;
   const size_t ITER=100000000; 


   //warm-up:
   double res=1.0;
      for(size_t i=0;i<4*ITER;i++){
      res*=FACTOR;
   }
   //overhead:
   auto begin = std::chrono::high_resolution_clock::now();
   for(size_t i=0;i<ITER;i++){
      res*=FACTOR;
   }
   auto end = std::chrono::high_resolution_clock::now();
   auto overhead=std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin).count(); 
   //std::cout<<"overhead: "<<overhead<<"\n";


   //experiments:
   for(auto start : starts){
       begin=std::chrono::high_resolution_clock::now();
       for(size_t i=0;i<ITER;i++){
           res*=tanh(start);
           start*=FACTOR;
       }
       auto end = std::chrono::high_resolution_clock::now();
       auto time_needed=std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin).count();
       std::cout<<start<<" "<<time_needed/overhead<<"\n"; 
   }

   //overhead check:
   begin = std::chrono::high_resolution_clock::now();
   for(size_t i=0;i<ITER;i++){
      res*=FACTOR;
   }
   end = std::chrono::high_resolution_clock::now();
   auto overhead_new=std::chrono::duration_cast<std::chrono::nanoseconds>(end-begin).count(); 
   std::cerr<<"overhead check: "<<overhead/overhead_new<<"\n";
   std::cerr<<res;//don't optimize anything out...
}
Run Code Online (Sandbox Code Playgroud)