cudf.pandas#
用户指南中解释了 cuDF pandas 加速模式 (cudf.pandas
) 的使用方法。本文档旨在解释快慢代理机制的工作原理,并记录可用于调试 cudf.pandas
本身的内部环境变量。
快慢代理机制#
cudf.pandas
的核心是通过 fast_slow_proxy.py
中定义的代理类型实现的,这些代理类型链接了一对“快”库和“慢”库。cudf.pandas
的工作原理是将每种“慢”类型及其对应的“快”类型封装在一个新的代理类型中,也称为快慢代理类型。这些代理类型的目的是使我们能够首先尝试在“快”对象上进行计算,如果“快”版本失败,则回退到“慢”对象。虽然核心包装功能是通用的,但当前的主要用法是使用 cuDF 和 Pandas 提供一对代理。在本文档的其余部分,为了方便思考具体的库对,我们将 cuDF 和 Pandas 分别用作“快”库和“慢”库的名称,但需要理解可以使用任何一对 API 匹配的库。例如,未来的支持可能包括 CuPy(作为“快”库)和 NumPy(作为“慢”库)等库对。
注意
我们目前没有包装整个 NumPy 库,因为它暴露了 C API。但我们确实将 NumPy 的
numpy.ndarray
和 CuPy 的cupy.ndarray
包装在代理类型中。定义了一个名为
custom_iter
的方法,该方法始终利用“慢”对象的iter
方法,这样我们就不会将对象移动到 GPU 并触发错误,然后再将对象移动到 CPU 以成功执行迭代。
类型:#
被包装的类型和代理类型#
“被包装”的类型/类是已经包装成代理类型的 Pandas 和 cuDF 特定类型。被包装对象和代理对象分别是被包装类型和代理类型的实例。在下面的代码片段中,s1
和 s2
是被包装对象,而 s3
是快慢代理对象。另请注意,模块 xpd
是一个被包装的模块,并包含 cuDF 和 Pandas 模块作为属性。要检查对象是否为代理类型,我们可以使用 cudf.pandas.is_proxy_object
。
import cudf.pandas
cudf.pandas.install()
import pandas as xpd
cudf = xpd._fsproxy_fast
pd = xpd._fsproxy_slow
s1 = cudf.Series([1,2])
s2 = pd.Series([1,2])
s3 = xpd.Series([1,2])
from cudf.pandas import is_proxy_object
is_proxy_object(s1) # returns False
is_proxy_object(s2) # returns False
is_proxy_object(s3) # returns True
注意
请注意,用户绝不应该以这种方式直接与被包装的对象进行交互。此代码仅用于演示目的。
不同类型的代理类型#
在 cudf.pandas
中,主要有两种代理类型:最终类型和中间类型。
最终代理类型和中间代理类型#
最终类型是指已知存在操作可以将“快”类型的对象转换为“慢”类型,反之亦然的类型。例如,可以使用 to_pandas
方法将 cudf.DataFrame
转换为 Pandas,可以使用 cudf.from_pandas
函数将 pd.DataFrame
转换为 cuDF。中间类型是在最终类型上调用操作的结果的类型。例如,xpd.DataFrameGroupBy
是一种中间类型,将在最终类型 xpd.DataFrame
上执行分组操作时创建。
属性和可调用代理类型#
最终代理类型通常是类或模块,它们都有属性。类还有方法。这些属性和方法也必须被包装以支持快慢代理方案。
创建新的代理类型#
_FinalProxy
和 _IntermediateProxy
类型分别使用函数 make_final_proxy_type
和 make_intermediate_proxy
创建。创建新的最终类型如下所示。
DataFrame = make_final_proxy_type(
"DataFrame",
cudf.DataFrame,
pd.DataFrame,
fast_to_slow=lambda fast: fast.to_pandas(),
slow_to_fast=cudf.from_pandas,
)
回退机制#
代理调用通过 _fast_slow_function_call
实现带回退的功能。这实现了我们先尝试使用“快”方式 (使用 cuDF) 执行操作,失败时回退到“慢”方式 (使用 Pandas) 的机制。该函数如下所示
def _fast_slow_function_call(func: Callable, *args, **kwargs):
try:
...
fast_args, fast_kwargs = _fast_arg(args), _fast_arg(kwargs)
result = func(*fast_args, **fast_kwargs)
...
except Exception:
...
slow_args, slow_kwargs = _slow_arg(args), _slow_arg(kwargs)
result = func(*slow_args, **slow_kwargs)
...
return _maybe_wrap_result(result, func, *args, **kwargs), fast
正如我们所见,该函数尝试使用 cuDF 以“快”方式调用 func
,如果发生任何 Exception
,则使用 Pandas 调用该函数。本质上,这个 try-except
块使得 cudf.pandas
能够支持大部分 Pandas API。
最后,如果需要,该函数会将来自任一路径的结果包装在快慢代理对象中。
转换代理对象#
请注意,在调用 func
之前,代理对象及其属性需要转换为其 cuDF 或 Pandas 实现。此转换在函数 _transform_arg
中处理,_fast_arg
和 _slow_arg
都会调用此函数。
_transform_arg
是一个递归函数,它将根据传递给它的类型或参数调用自身(例如,对参数列表中的每个元素调用 _transform_arg
)。
使用元类#
cudf.pandas
使用一个名为 (_FastSlowProxyMeta
) 的元类来查找快慢代理类型的类属性和类方法。例如,在下面的代码片段中,xpd.Series
类型是 _FastSlowProxyMeta
的一个实例。因此我们可以访问在元类中定义的属性 _fsproxy_fast
。
import cudf.pandas
cudf.pandas.install()
import pandas as xpd
print(xpd.Series._fsproxy_fast) # output is cudf.core.series.Series
调试 cudf.pandas
#
可以使用几个环境变量进行调试。
设置环境变量 CUDF_PANDAS_DEBUGGING
会在 cuDF 和 Pandas 的结果不同时产生警告。例如,下面的代码片段会产生如下警告。
import cudf.pandas
cudf.pandas.install()
import pandas as pd
import numpy as np
setattr(pd.Series.mean, "_fsproxy_slow", lambda self, *args, **kwargs: np.float64(1))
s = pd.Series([1,2,3])
s.mean()
UserWarning: The results from cudf and pandas were different. The exception was
Arrays are not almost equal to 7 decimals
ACTUAL: 1.0
DESIRED: 2.0.
设置环境变量 CUDF_PANDAS_FAIL_ON_FALLBACK
会导致 cudf.pandas
在从 cuDF 回退到 Pandas 时失败。例如,
import cudf.pandas
cudf.pandas.install()
import pandas as pd
import numpy as np
df = pd.DataFrame({
'complex_col': [1 + 2j, 3 + 4j, 5 + 6j]
})
print(df)
ProxyFallbackError: The operation failed with cuDF, the reason was <class 'NotImplementedError'>: Series with Complex128DType is not supported.