为什么 sklearn 在 CPU 上比 GPU 上的 Theano 更快?
Why is sklearn faster on CPU than Theano on GPU?
我使用 Python 比较了 theano(CPU)、theano(GPU) 和 Scikit-learn(CPU) 的处理时间。
但是,我得到了奇怪的结果。
看我绘制的图表。
处理时间比较:
你可以看到scikit-learn的结果比theano(GPU)更快。
我检查其经过时间的程序是从具有 n * 40 个元素的矩阵计算欧氏距离矩阵。
这是部分代码。
points = T.fmatrix("points")
edm = T.zeros_like(points)
def get_point_to_points_euclidean_distances(point_id):
euclideans = (T.sqrt((T.sqr(points- points[point_id, : ])).sum(axis=1)))
return euclideans
def get_EDM_CPU(points):
EDM = np.zeros((points.shape[0], points.shape[0])).astype(np.float32)
for row in range(points.shape[0]):
EDM[row, :] = np.sqrt(np.sum((points - points[row, :])**2, axis=1))
return EDM
def get_sk(points):
EDM = sk.pairwise_distances(a, metric='l2')
return EDM
seq = T.arange(T.shape(points)[0])
(result, _) = theano.scan(fn = get_point_to_points_euclidean_distances, \
outputs_info = None , \
sequences = seq)
get_EDM_GPU = theano.function(inputs = [points], outputs = result, allow_input_downcast = True)
我认为 GPU 比 sci-kit 学习慢的原因可能是传输时间。所以我用 nvprof 命令分析了 GPU。然后我明白了。
==27105== NVPROF is profiling process 27105, command: python ./EDM_test.py
Using gpu device 0: GeForce GTX 580 (CNMeM is disabled, cuDNN not available)
data shape : (10000, 40)
get_EDM_GPU elapsed time : 1.84863090515 (s)
get_EDM_CPU elapsed time : 8.09937691689 (s)
get_EDM_sk elapsed time : 1.10968112946 (s)
ratio : 4.38128395145
==27105== Profiling application: python ./EDM_test.py
==27105== Warning: Found 9 invalid records in the result.
==27105== Warning: This could be because device ran out of memory when profiling.
==27105== Profiling result:
Time(%) Time Calls Avg Min Max Name
71.34% 1.28028s 9998 128.05us 127.65us 128.78us kernel_reduce_01_node_316e2e1cbfbe8cfb8e4a101f329ffeec_0(int, int, float const *, int, int, float*, int)
19.95% 357.97ms 9997 35.807us 35.068us 36.948us kernel_Sub_node_bc41b3f8f12c93d29f2c4360ad445d80_0_2(unsigned int, int, int, float const *, int, int, float const *, int, int, float*, int, int)
7.32% 131.38ms 2 65.690ms 1.2480us 131.38ms [CUDA memcpy DtoH]
1.25% 22.456ms 9996 2.2460us 2.1140us 2.8420us kernel_Sqrt_node_23508f8f49d12f3e8369d543f5620c15_0_Ccontiguous(unsigned int, float const *, float*)
0.12% 2.1847ms 1 2.1847ms 2.1847ms 2.1847ms [CUDA memset]
0.01% 259.73us 5 51.946us 640ns 250.36us [CUDA memcpy HtoD]
0.00% 17.086us 1 17.086us 17.086us 17.086us kernel_reduce_ccontig_node_97496c4d3cf9a06dc4082cc141f918d2_0(unsigned int, float const *, float*)
0.00% 2.0090us 1 2.0090us 2.0090us 2.0090us void copy_kernel<float, int=0>(cublasCopyParams<float>)
传输 [CUDA memcpy DtoH] 执行了两次 { 1.248 [us], 131.38 [ms] }
传输 [CUDA memcpy HtoD] 执行了 5 次 { min: 640 [ns], max: 250.36 [us] }
传输时间约为 131.639 毫秒(131.88 毫秒 + 259.73 微秒)。
但是 GPU 和 scikit-learn 之间的差距大约是 700 毫秒(1.8 秒 - 1.1 秒)所以,差距超过了传输时间。
是否只计算对称矩阵的上三角矩阵?
是什么让 scikit-learn 如此之快?
是什么让 scikit-learn(在纯 CPU 端)如此之快?
我最初的候选人是:
- 高效使用可用的 CPU-核心' L1-/ L2- 大小在最快的 [ns]-距离内
- 智能
numpy
矢量化执行对CPU缓存行友好
- 数据集如此小,它可以完全保持不从缓存中逐出(测试将正在审查的数据集扩展到 L2-/L3-cache 大小以上以查看DDRx 内存成本对观察到的性能的影响(详细信息在下面的 URL 中))
- 如果避免
.astype()
次转化(测试) ,numpy
可能会享受更好的时机
GPU 方面的事实
- 与手动调整的内核设计相比,自动生成的 GPU 内核没有太多机会获得最终级别的全局内存延迟屏蔽,以适应各自的 GPU 硅架构/体内观察到的延迟
- 大于几 KB 的数据结构仍然需要支付 GPU-SM/GDDR-MEM ~ 数百 [ns] 的距离,接近 [us] -v/s- 与小单位相比~小数十[ns] 在 CPU/L1/L2/L3/DDRx ) 参考。 >>>
中的计时详情
- 无法享受 GPU/SMX 的大部分功能,由于此任务明显的数据点重用率低且数据集大小超出 GPU/SM-silicon 限制,这导致并且必须导致 GPU/SM-register 任何类型的 GPU 内核设计尝试和调整中的容量溢出
- 全局任务没有最小合理数量的异步、孤立(非通信孤岛)数学密集,但 SMX 本地、GPU 内核处理步骤(没有太多计算来调整对于附加开销和昂贵的 SMX/GDDR 内存成本)
GPU-s 可以可爱地展示它的最佳性能,如果足够多的密集卷积再处理操作发生——就像在 large-scale/high-resolution 图像处理中——在 [m,n,o]
卷积上- 内核矩阵如此之小,以至于所有这些 m*n*o
常量值都可以驻留在 SM 的本地,在一组可用的 SMX-SM_registers 中,并且如果 GPU 内核启动器通过 3D-tblock/grid 处理布局几何形状,以便全局内存访问延迟处于最佳掩蔽性能,所有 GPU 线程都在硬件内强制执行 WARP 对齐 SMx:WarpScheduler RoundRobin 线程调度功能(第一个如果 GPU 内核代码中的执行路径不同,则从 Round-Robin 切换到 Greedy-WarpSchedule 模式会输掉整场战斗。
我使用 Python 比较了 theano(CPU)、theano(GPU) 和 Scikit-learn(CPU) 的处理时间。 但是,我得到了奇怪的结果。 看我绘制的图表。
处理时间比较:
你可以看到scikit-learn的结果比theano(GPU)更快。 我检查其经过时间的程序是从具有 n * 40 个元素的矩阵计算欧氏距离矩阵。
这是部分代码。
points = T.fmatrix("points")
edm = T.zeros_like(points)
def get_point_to_points_euclidean_distances(point_id):
euclideans = (T.sqrt((T.sqr(points- points[point_id, : ])).sum(axis=1)))
return euclideans
def get_EDM_CPU(points):
EDM = np.zeros((points.shape[0], points.shape[0])).astype(np.float32)
for row in range(points.shape[0]):
EDM[row, :] = np.sqrt(np.sum((points - points[row, :])**2, axis=1))
return EDM
def get_sk(points):
EDM = sk.pairwise_distances(a, metric='l2')
return EDM
seq = T.arange(T.shape(points)[0])
(result, _) = theano.scan(fn = get_point_to_points_euclidean_distances, \
outputs_info = None , \
sequences = seq)
get_EDM_GPU = theano.function(inputs = [points], outputs = result, allow_input_downcast = True)
我认为 GPU 比 sci-kit 学习慢的原因可能是传输时间。所以我用 nvprof 命令分析了 GPU。然后我明白了。
==27105== NVPROF is profiling process 27105, command: python ./EDM_test.py
Using gpu device 0: GeForce GTX 580 (CNMeM is disabled, cuDNN not available)
data shape : (10000, 40)
get_EDM_GPU elapsed time : 1.84863090515 (s)
get_EDM_CPU elapsed time : 8.09937691689 (s)
get_EDM_sk elapsed time : 1.10968112946 (s)
ratio : 4.38128395145
==27105== Profiling application: python ./EDM_test.py
==27105== Warning: Found 9 invalid records in the result.
==27105== Warning: This could be because device ran out of memory when profiling.
==27105== Profiling result:
Time(%) Time Calls Avg Min Max Name
71.34% 1.28028s 9998 128.05us 127.65us 128.78us kernel_reduce_01_node_316e2e1cbfbe8cfb8e4a101f329ffeec_0(int, int, float const *, int, int, float*, int)
19.95% 357.97ms 9997 35.807us 35.068us 36.948us kernel_Sub_node_bc41b3f8f12c93d29f2c4360ad445d80_0_2(unsigned int, int, int, float const *, int, int, float const *, int, int, float*, int, int)
7.32% 131.38ms 2 65.690ms 1.2480us 131.38ms [CUDA memcpy DtoH]
1.25% 22.456ms 9996 2.2460us 2.1140us 2.8420us kernel_Sqrt_node_23508f8f49d12f3e8369d543f5620c15_0_Ccontiguous(unsigned int, float const *, float*)
0.12% 2.1847ms 1 2.1847ms 2.1847ms 2.1847ms [CUDA memset]
0.01% 259.73us 5 51.946us 640ns 250.36us [CUDA memcpy HtoD]
0.00% 17.086us 1 17.086us 17.086us 17.086us kernel_reduce_ccontig_node_97496c4d3cf9a06dc4082cc141f918d2_0(unsigned int, float const *, float*)
0.00% 2.0090us 1 2.0090us 2.0090us 2.0090us void copy_kernel<float, int=0>(cublasCopyParams<float>)
传输 [CUDA memcpy DtoH] 执行了两次 { 1.248 [us], 131.38 [ms] }
传输 [CUDA memcpy HtoD] 执行了 5 次 { min: 640 [ns], max: 250.36 [us] }
传输时间约为 131.639 毫秒(131.88 毫秒 + 259.73 微秒)。 但是 GPU 和 scikit-learn 之间的差距大约是 700 毫秒(1.8 秒 - 1.1 秒)所以,差距超过了传输时间。
是否只计算对称矩阵的上三角矩阵?
是什么让 scikit-learn 如此之快?
是什么让 scikit-learn(在纯 CPU 端)如此之快?
我最初的候选人是:
- 高效使用可用的 CPU-核心' L1-/ L2- 大小在最快的 [ns]-距离内
- 智能
numpy
矢量化执行对CPU缓存行友好 - 数据集如此小,它可以完全保持不从缓存中逐出(测试将正在审查的数据集扩展到 L2-/L3-cache 大小以上以查看DDRx 内存成本对观察到的性能的影响(详细信息在下面的 URL 中))
- 如果避免
.astype()
次转化(测试) ,
numpy
可能会享受更好的时机
GPU 方面的事实
- 与手动调整的内核设计相比,自动生成的 GPU 内核没有太多机会获得最终级别的全局内存延迟屏蔽,以适应各自的 GPU 硅架构/体内观察到的延迟
- 大于几 KB 的数据结构仍然需要支付 GPU-SM/GDDR-MEM ~ 数百 [ns] 的距离,接近 [us] -v/s- 与小单位相比~小数十[ns] 在 CPU/L1/L2/L3/DDRx ) 参考。 >>> 中的计时详情
- 无法享受 GPU/SMX 的大部分功能,由于此任务明显的数据点重用率低且数据集大小超出 GPU/SM-silicon 限制,这导致并且必须导致 GPU/SM-register 任何类型的 GPU 内核设计尝试和调整中的容量溢出
- 全局任务没有最小合理数量的异步、孤立(非通信孤岛)数学密集,但 SMX 本地、GPU 内核处理步骤(没有太多计算来调整对于附加开销和昂贵的 SMX/GDDR 内存成本)
GPU-s 可以可爱地展示它的最佳性能,如果足够多的密集卷积再处理操作发生——就像在 large-scale/high-resolution 图像处理中——在 [m,n,o]
卷积上- 内核矩阵如此之小,以至于所有这些 m*n*o
常量值都可以驻留在 SM 的本地,在一组可用的 SMX-SM_registers 中,并且如果 GPU 内核启动器通过 3D-tblock/grid 处理布局几何形状,以便全局内存访问延迟处于最佳掩蔽性能,所有 GPU 线程都在硬件内强制执行 WARP 对齐 SMx:WarpScheduler RoundRobin 线程调度功能(第一个如果 GPU 内核代码中的执行路径不同,则从 Round-Robin 切换到 Greedy-WarpSchedule 模式会输掉整场战斗。