ArrayFire CUDA 应用程序在第一分钟内非常慢
ArrayFire CUDA application is extremely slow in the first minute
我正在 windows 10 + Nvidia Gtx 970 上使用 ArrayFire 运行 编写测试程序。该程序是用 SGD 求解器训练神经网络。因此,主要计算是更新网络参数的迭代。迭代在一个名为 step()
.
的函数中进行
除了在第一分钟执行速度极慢之外,该程序符合预期。以下是程序的输出。第一列是经过的时间。
ArrayFire v3.5.1 (CUDA, 64-bit Windows, build 0a675e8)
Platform: CUDA Toolkit 8, Driver: CUDA Driver Version: 8000
[0] GeForce GTX 970, 4096 MB, CUDA Compute 5.2
time epochs training error
5 0.002 5.6124567
6 0.007 5.5981609
7 0.010 5.3560046
8 0.015 5.2485286
9 0.020 5.1370633
10 0.022 5.1081303
....
52 0.148 3.2528560
53 0.150 3.2425120
54 0.153 3.2180901
55 0.155 3.2048657
56 0.157 3.1949191
57 0.158 3.1816899
58 0.160 3.1717312
59 0.162 3.1597322
60 0.165 3.1370639
60 0.498 2.1359600
61 0.548 2.0685355
61 0.882 1.7098215
62 0.943 1.6575973
62 1.277 1.4156345
63 1.343 1.3845720
63 1.677 1.1789854
64 1.733 1.1549067
64 2.067 1.0162785
....
71 4.517 0.4732214
71 4.850 0.4522045
72 4.910 0.4501807
72 5.243 0.4355422
73 5.305 0.4307187
如你所见,在第一分钟,它连一个epoch的1/5都没有完成。但一分钟后,它突然加速,在大约 4 秒内完成一个 epoch。
分析数据也说明了同样的事情:在第一分钟,函数 step()
的平均执行时间约为 500 毫秒,但在第一分钟后,它下降到 6 毫秒。
Nvidia visual profiler 显示内核在第一分钟内几乎一直处于空闲状态。
我不知道是什么导致了第一分钟之前|之后的性能变化。感谢任何帮助。
ArrayFire 在运行时使用 JIT 编译来融合对函数的多次调用。因此,当您执行加法或任何其他元素方面的操作时,ArrayFire 将创建一个自定义内核并执行该内核。当您第一次生成这个内核时,这会产生一些开销,但这些内核会被缓存,不需要编译额外的调用。通常,在不需要额外的编译之前,它应该只需要几次迭代。奇怪的是,即使经过 60 次左右的迭代,内核仍然很慢。
JIT 内核是使用基于内存和内核大小的内部试探法进行评估的。也许您的应用程序没有以最佳方式触发内核并导致额外的内核编译。您可以通过对变量调用 eval 函数来强制执行评估来解决此问题。这是一个人为的例子:
array a = randu(10, 10);
array b = randu(10, 10);
for(int i = 0; i < 100; i++) {
a += b / 4;
b *= i;
eval(a, b);
}
在这里,您将在每次迭代中评估变量 a 和 b 的 JIT 树。这将在每次迭代中重用相同的内核,而不是为不同的迭代倍数创建内核。
需要注意的一件事是元素方面,一些条件函数如 select 和 shift 是 JIT 的。其他函数在使用之前强制评估它们的参数。此外,如果您过于频繁地评估,您将降低应用程序的性能。
我正在 windows 10 + Nvidia Gtx 970 上使用 ArrayFire 运行 编写测试程序。该程序是用 SGD 求解器训练神经网络。因此,主要计算是更新网络参数的迭代。迭代在一个名为 step()
.
除了在第一分钟执行速度极慢之外,该程序符合预期。以下是程序的输出。第一列是经过的时间。
ArrayFire v3.5.1 (CUDA, 64-bit Windows, build 0a675e8) Platform: CUDA Toolkit 8, Driver: CUDA Driver Version: 8000 [0] GeForce GTX 970, 4096 MB, CUDA Compute 5.2 time epochs training error 5 0.002 5.6124567 6 0.007 5.5981609 7 0.010 5.3560046 8 0.015 5.2485286 9 0.020 5.1370633 10 0.022 5.1081303 .... 52 0.148 3.2528560 53 0.150 3.2425120 54 0.153 3.2180901 55 0.155 3.2048657 56 0.157 3.1949191 57 0.158 3.1816899 58 0.160 3.1717312 59 0.162 3.1597322 60 0.165 3.1370639 60 0.498 2.1359600 61 0.548 2.0685355 61 0.882 1.7098215 62 0.943 1.6575973 62 1.277 1.4156345 63 1.343 1.3845720 63 1.677 1.1789854 64 1.733 1.1549067 64 2.067 1.0162785 .... 71 4.517 0.4732214 71 4.850 0.4522045 72 4.910 0.4501807 72 5.243 0.4355422 73 5.305 0.4307187
如你所见,在第一分钟,它连一个epoch的1/5都没有完成。但一分钟后,它突然加速,在大约 4 秒内完成一个 epoch。
分析数据也说明了同样的事情:在第一分钟,函数 step()
的平均执行时间约为 500 毫秒,但在第一分钟后,它下降到 6 毫秒。
Nvidia visual profiler 显示内核在第一分钟内几乎一直处于空闲状态。
我不知道是什么导致了第一分钟之前|之后的性能变化。感谢任何帮助。
ArrayFire 在运行时使用 JIT 编译来融合对函数的多次调用。因此,当您执行加法或任何其他元素方面的操作时,ArrayFire 将创建一个自定义内核并执行该内核。当您第一次生成这个内核时,这会产生一些开销,但这些内核会被缓存,不需要编译额外的调用。通常,在不需要额外的编译之前,它应该只需要几次迭代。奇怪的是,即使经过 60 次左右的迭代,内核仍然很慢。
JIT 内核是使用基于内存和内核大小的内部试探法进行评估的。也许您的应用程序没有以最佳方式触发内核并导致额外的内核编译。您可以通过对变量调用 eval 函数来强制执行评估来解决此问题。这是一个人为的例子:
array a = randu(10, 10);
array b = randu(10, 10);
for(int i = 0; i < 100; i++) {
a += b / 4;
b *= i;
eval(a, b);
}
在这里,您将在每次迭代中评估变量 a 和 b 的 JIT 树。这将在每次迭代中重用相同的内核,而不是为不同的迭代倍数创建内核。
需要注意的一件事是元素方面,一些条件函数如 select 和 shift 是 JIT 的。其他函数在使用之前强制评估它们的参数。此外,如果您过于频繁地评估,您将降低应用程序的性能。