数据中心/云端

掌握 cudf.pandas Profiler 实现 GPU 加速

在 Python 数据科学领域,pandas 长期以来一直是直观数据操作和分析的首选库。但是,随着数据量的增长,CPU 受限的 pandas 工作流可能会成为瓶颈。

这就是 cuDF 及其 pandas 加速器模式 cudf.pandas 的用武之地。此模式可尽可能使用 GPU 加速操作,并无缝回退至 CPU 以执行不受支持的操作。此方法的基本支柱是 cudf.pandas 分析器,该分析器可用于了解与 CPU 相比,GPU 上执行的代码量。

在本文中,我们将讨论 cudf.pandas 分析器是什么,如何使用它,以及为什么它对于理解和优化加速 pandas 工作负载至关重要。

cudf.pandas 分析器概述 

Jupyter 和 IPython 中提供的 cudf.pandas.profile magic 命令是一种分析工具,可实时分析 pandas 式代码。启用 cudf.pandas 扩展后,分析器将报告每个操作的执行设备 (GPU 或 CPU),并统计特定函数或方法的触发次数。

通过捕获这些数据,您可以快速确定以下内容:

  • 哪些操作在 GPU 上完全加速。
  • 哪些操作返回到 CPU 上的 pandas。
  • 潜在瓶颈或不必要的数据传输可能潜伏在何处。

启用分析器 

首先,在 Notebook 中加载 cudf.pandas 扩展程序,就像加载其他 IPython 魔术一样:

%load_ext cudf.pandas
import pandas as pd

从这里开始,您可以开始编写 pandas 代码,例如读取 CSV、合并 DataFrames 或运行 groupbys,并让 cudf.pandas 自动决定如何在 GPU 上加速这些代码,或者在需要时使用 CPU。

分析的实际应用 

使用 cudf.pandas 分析器的方法有以下几种:

  • 使用单元级分析器
  • 使用线条分析器
  • 使用命令行中的 profiler

使用单元级分析器 

在 Jupyter 或 IPython 中,您可以逐单元激活分析:

%%cudf.pandas.profile

df = pd.DataFrame({'a': [0, 1, 2], 'b': [3, 4, 3]})
df.min(axis=1)
out = df.groupby('a').filter(lambda group: len(group) > 1)

单元完成后,您将看到一个输出,其中分解了与 CPU 相比在 GPU 上运行的操作、每个操作的调用次数,以及用于发现性能瓶颈的便捷摘要。

                                          Total time elapsed: 0.256 seconds                                  
                                        3 GPU function calls in 0.170 seconds                                
                                        1 CPU function calls in 0.009 seconds                                
                                                                                                             
                                                        Stats                                                
                                                                                                             
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Function                ┃ GPU ncalls ┃ GPU cumtime ┃ GPU percall ┃ CPU ncalls ┃ CPU cumtime ┃ CPU percall ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ DataFrame               │ 1          │ 0.031       │ 0.031       │ 0          │ 0.000       │ 0.000       │
│ DataFrame.min           │ 1          │ 0.137       │ 0.137       │ 0          │ 0.000       │ 0.000       │
│ DataFrame.groupby       │ 1          │ 0.001       │ 0.001       │ 0          │ 0.000       │ 0.000       │
│ DataFrameGroupBy.filter │ 0          │ 0.000       │ 0.000       │ 1          │ 0.009       │ 0.009       │
└─────────────────────────┴────────────┴─────────────┴─────────────┴────────────┴─────────────┴─────────────┘
Not all pandas operations ran on the GPU. The following functions required CPU fallback:

- DataFrameGroupBy.filter

To request GPU support for any of these functions, please file a Github issue here: https://github.com/rapidsai/cudf/issues/new/choose.

DataFrameGroupBy.filter 未在 GPU 上加速,因此您可以看到表中的一次 CPU 调用需要 0.025 秒。

使用线条分析器 

要更深入地了解单元中每一行的执行情况,请运行以下代码示例:

%%cudf.pandas.line_profile

df = pd.DataFrame({'a': [0, 1, 2], 'b': [3, 4, 3]})
df.min(axis=1)
out = df.groupby('a').filter(lambda group: len(group) > 1)

在这里,profiler 逐行显示执行细节,以便更轻松地发现导致 CPU 回退或性能降低的特定代码行。

                                        Total time elapsed: 0.244 seconds                                
                                                                                                         
                                                      Stats                                              
                                                                                                         
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Line no. ┃ Line                                                           ┃ GPU TIME(s) ┃ CPU TIME(s) ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ 2        │     df = pd.DataFrame({'a': [0, 1, 2], 'b': [3, 4, 3]})        │ 0.004833249 │             │
│          │                                                                │             │             │
│ 3        │     df.min(axis=1)                                             │ 0.006497159 │             │
│          │                                                                │             │             │
│ 4        │     out = df.groupby('a').filter(lambda group: len(group) > 1) │ 0.000599624 │ 0.000347643 │
│          │                                                                │             │             │
└──────────┴────────────────────────────────────────────────────────────────┴─────────────┴─────────────┘

使用命令行中的 profiler

您还可以通过传递 --profile 参数从命令行运行 cudf.pandas 分析器。

以下是 demo.py 的内容:

import pandas as pd
s = pd.Series([1, 2, 3])
s = (s * 10) + 2
print(s)

运行以下命令:

python -m cudf.pandas –-profile demo.py

您将看到 profile stats 输出:

                                      Total time elapsed: 0.852 seconds                               
                                    4 GPU function calls in 0.029 seconds                             
                                    0 CPU function calls in 0.000 seconds                             
                                                                                                      
                                                    Stats                                             
                                                                                                      
┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ Function         ┃ GPU ncalls ┃ GPU cumtime ┃ GPU percall ┃ CPU ncalls ┃ CPU cumtime ┃ CPU percall ┃
┡━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ Series           │ 1          │ 0.002       │ 0.002       │ 0          │ 0.000       │ 0.000       │
│ OpsMixin.__mul__ │ 1          │ 0.011       │ 0.011       │ 0          │ 0.000       │ 0.000       │
│ OpsMixin.__add__ │ 1          │ 0.008       │ 0.008       │ 0          │ 0.000       │ 0.000       │
│ object.__str__   │ 1          │ 0.008       │ 0.008       │ 0          │ 0.000       │ 0.000       │
└──────────────────┴────────────┴─────────────┴─────────────┴────────────┴─────────────┴─────────────┘

分析为何重要 

在 GPU 加速方面,并非所有功能都能自动获得支持。某些操作 (例如某些尚未在 GPU 上实现的 Python 级函数、自定义 lambdas 或 DataFrame 方法) 可能会触发 CPU 回退。

分析器可帮助您精确查看这些回退的出现位置。借助这些知识,您可以调整工作流程、重写某些函数,或调查需要改进的潜在领域:

  • 重写受 CPU 限制的运算: 某些用户定义的函数或运算可能会进行修改,使其对 GPU 更加友好。
  • 留意频繁的数据传输 :CPU 和 GPU 之间的过多数据传输可能会抵消加速所带来的收益。识别这些对更大限度地提高速度至关重要,因为重复的 GPU 操作序列和 CPU 操作通常会导致昂贵的数据传输。
  • 及时了解最新动态:cuDF 不断添加功能 (并弥合与 pandas 之间的差距)。

了解哪些方法目前受 CPU 限制,有助于您跟踪未来的改进。

结束语 

pandas 用户体验是现代数据科学的核心,但扩展到大型数据集通常会对 CPU 性能造成压力。

借助 cudf.pandas,您可以尽可能使用 GPU 的速度获得相同的直观 API,并为不受支持的操作提供自动 CPU 回退。cudf.pandas 分析器是了解此混合 CPU/GPU 生态系统的关键,可突出显示加速机会,并帮助您优化代码以实现最佳性能。

在您的数据项目中尝试一下。通过分析代码并识别 CPU 回退,您可以快速突破 pandas 的极限,而无需离开其熟悉的 API 的舒适性。

如果您遇到希望通过 GPU 加速的 API,请在 /rapidsai/cudf GitHub 存储库中 打开 GitHub 问题

 

标签