cuDF 与 CuPy 之间的互操作性#
本 Jupyter Notebook 提供了如何结合使用 cuDF 和 CuPy 的入门示例,以利用 CuPy 数组功能(例如高级线性代数运算)。
import cudf
import cupy as cp
from packaging import version
if version.parse(cp.__version__) >= version.parse("10.0.0"):
cupy_from_dlpack = cp.from_dlpack
else:
cupy_from_dlpack = cp.fromDlpack
将 cuDF DataFrame 转换为 CuPy Array#
如果我们要将 cuDF DataFrame 转换为 CuPy ndarray,有多种方法可以实现
我们可以使用 dlpack 接口。
我们也可以使用
DataFrame.values
。我们还可以通过使用 cuDF 的
to_cupy
功能,借助 CUDA array interface 进行转换。
nelem = 10000
df = cudf.DataFrame(
{
"a": range(nelem),
"b": range(500, nelem + 500),
"c": range(1000, nelem + 1000),
}
)
%timeit arr_cupy = cupy_from_dlpack(df.to_dlpack())
%timeit arr_cupy = df.values
%timeit arr_cupy = df.to_cupy()
190 μs ± 4.13 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
384 μs ± 4.5 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
379 μs ± 3.68 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
arr_cupy = cupy_from_dlpack(df.to_dlpack())
arr_cupy
array([[ 0, 500, 1000],
[ 1, 501, 1001],
[ 2, 502, 1002],
...,
[ 9997, 10497, 10997],
[ 9998, 10498, 10998],
[ 9999, 10499, 10999]])
将 cuDF Series 转换为 CuPy Array#
将 cuDF Series 转换为 CuPy array 也有多种方法
我们可以将 Series 传递给
cupy.asarray
,因为 cuDF Series 公开了__cuda_array_interface__
。我们可以利用 dlpack 接口
to_dlpack()
。我们也可以使用
Series.values
col = "a"
%timeit cola_cupy = cp.asarray(df[col])
%timeit cola_cupy = cupy_from_dlpack(df[col].to_dlpack())
%timeit cola_cupy = df[col].values
171 μs ± 1.95 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
346 μs ± 4.11 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
239 μs ± 3.4 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
cola_cupy = cp.asarray(df[col])
cola_cupy
array([ 0, 1, 2, ..., 9997, 9998, 9999])
从这里,我们可以继续正常的 CuPy 工作流程,例如重塑数组、获取对角线或计算范数。
reshaped_arr = cola_cupy.reshape(50, 200)
reshaped_arr
array([[ 0, 1, 2, ..., 197, 198, 199],
[ 200, 201, 202, ..., 397, 398, 399],
[ 400, 401, 402, ..., 597, 598, 599],
...,
[9400, 9401, 9402, ..., 9597, 9598, 9599],
[9600, 9601, 9602, ..., 9797, 9798, 9799],
[9800, 9801, 9802, ..., 9997, 9998, 9999]])
reshaped_arr.diagonal()
array([ 0, 201, 402, 603, 804, 1005, 1206, 1407, 1608, 1809, 2010,
2211, 2412, 2613, 2814, 3015, 3216, 3417, 3618, 3819, 4020, 4221,
4422, 4623, 4824, 5025, 5226, 5427, 5628, 5829, 6030, 6231, 6432,
6633, 6834, 7035, 7236, 7437, 7638, 7839, 8040, 8241, 8442, 8643,
8844, 9045, 9246, 9447, 9648, 9849])
cp.linalg.norm(reshaped_arr)
array(577306.967739)
将 CuPy Array 转换为 cuDF DataFrame#
我们也可以将 CuPy ndarray 转换为 cuDF DataFrame。和之前一样,有多种方法可以实现
最简单的方法; 我们可以直接使用
DataFrame
构造函数。我们可以使用 CUDA array interface 配合
DataFrame
构造函数。我们也可以使用 dlpack 接口。
对于后两种情况,我们需要确保 CuPy 数组在内存中是 Fortran 连续的(如果它尚未如此)。我们可以转置数组或者简单地在操作前将其强制转换为 Fortran 连续。
%timeit reshaped_df = cudf.DataFrame(reshaped_arr)
5.13 ms ± 24.1 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
reshaped_df = cudf.DataFrame(reshaped_arr)
reshaped_df.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 |
1 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | ... | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 |
2 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | ... | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 |
3 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | ... | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 |
4 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | ... | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 |
5 行 × 200 列
我们可以通过使用 cupy.isfortran 或查看数组的 flags 来检查数组是否为 Fortran 连续。
cp.isfortran(reshaped_arr)
False
在这种情况下,我们需要在转换为 cuDF DataFrame 之前对其进行转换。在接下来的两个单元格中,我们将分别利用 dlpack 和 CUDA array interface 创建 DataFrame。
%%timeit
fortran_arr = cp.asfortranarray(reshaped_arr)
reshaped_df = cudf.DataFrame(fortran_arr)
5.09 ms ± 16 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%%timeit
fortran_arr = cp.asfortranarray(reshaped_arr)
reshaped_df = cudf.from_dlpack(fortran_arr.__dlpack__())
5.11 ms ± 30.3 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
fortran_arr = cp.asfortranarray(reshaped_arr)
reshaped_df = cudf.DataFrame(fortran_arr)
reshaped_df.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 |
1 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | ... | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 |
2 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | ... | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 |
3 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | ... | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 |
4 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | ... | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 |
5 行 × 200 列
将 CuPy Array 转换为 cuDF Series#
要将数组转换为 Series,我们可以直接将数组传递给 Series
构造函数。
cudf.Series(reshaped_arr.diagonal()).head()
0 0
1 201
2 402
3 603
4 804
dtype: int64
巧妙结合 CuDF 和 CuPy 实现流畅的 PyData 工作流程#
RAPIDS 库和整个 GPU PyData 生态系统正在快速发展,但有时某个库可能不具备您需要的功能。一个例子是计算 Pandas DataFrame 的行级总和(或均值)。cuDF 对行级操作的支持尚不成熟,因此您需要转置 DataFrame 或编写 UDF 并显式计算每行的总和。根据数据形状,转置可能导致数十万列(cuDF 对此性能不佳),而编写 UDF 可能会耗费大量时间。
通过利用 GPU PyData 生态系统的互操作性,此操作变得非常简单。让我们计算之前重塑的 cuDF DataFrame 的行级总和。
reshaped_df.head()
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ... | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 |
1 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | ... | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 |
2 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | ... | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 |
3 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | ... | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 |
4 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | ... | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 |
5 行 × 200 列
我们可以将其转换为 CuPy 数组,然后使用 sum
的 axis
参数。
new_arr = cupy_from_dlpack(reshaped_df.to_dlpack())
new_arr.sum(axis=1)
array([ 19900, 59900, 99900, 139900, 179900, 219900, 259900,
299900, 339900, 379900, 419900, 459900, 499900, 539900,
579900, 619900, 659900, 699900, 739900, 779900, 819900,
859900, 899900, 939900, 979900, 1019900, 1059900, 1099900,
1139900, 1179900, 1219900, 1259900, 1299900, 1339900, 1379900,
1419900, 1459900, 1499900, 1539900, 1579900, 1619900, 1659900,
1699900, 1739900, 1779900, 1819900, 1859900, 1899900, 1939900,
1979900])
仅用这一行代码,我们就可以在这个生态系统中的数据结构之间无缝切换,这为我们带来了巨大的灵活性,而不会牺牲速度。
将 cuDF DataFrame 转换为 CuPy Sparse Matrix#
我们也可以将 DataFrame 或 Series 转换为 CuPy 稀疏矩阵。如果下游流程需要 CuPy 稀疏矩阵作为输入,我们可能需要这样做。
稀疏矩阵数据结构由三个密集数组定义。我们将定义一个小的辅助函数以保持代码整洁。
def cudf_to_cupy_sparse_matrix(data, sparseformat="column"):
"""Converts a cuDF object to a CuPy Sparse Column matrix."""
if sparseformat not in (
"row",
"column",
):
raise ValueError("Let's focus on column and row formats for now.")
_sparse_constructor = cp.sparse.csc_matrix
if sparseformat == "row":
_sparse_constructor = cp.sparse.csr_matrix
return _sparse_constructor(cupy_from_dlpack(data.to_dlpack()))
我们可以定义一个稀疏填充的 DataFrame 来演示如何将其转换为任意一种稀疏矩阵格式。
df = cudf.DataFrame()
nelem = 10000
nonzero = 1000
for i in range(20):
arr = cp.random.normal(5, 5, nelem)
arr[cp.random.choice(arr.shape[0], nelem - nonzero, replace=False)] = 0
df["a" + str(i)] = arr
df.head()
a0 | a1 | a2 | a3 | a4 | a5 | a6 | a7 | a8 | a9 | a10 | a11 | a12 | a13 | a14 | a15 | a16 | a17 | a18 | a19 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | -6.169778 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 |
1 | 0.0 | -4.541529 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 |
2 | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 |
3 | 0.0 | 0.000000 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 |
4 | 0.0 | 0.000000 | 1.968797 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.931168 | 0.0 | 0.0 | 0.0 | 0.000000 | 0.0 | 0.0 | 0.0 | 13.904802 | 0.0 | 0.0 |
sparse_data = cudf_to_cupy_sparse_matrix(df)
print(sparse_data)
<Compressed Sparse Column sparse matrix of dtype 'float64'
with 20000 stored elements and shape (10000, 20)>
Coords Values
(1536, 0) -1.588673097447183
(513, 0) 9.970849097280857
(1537, 0) 10.919748570706547
(514, 0) 15.406238081371376
(133, 0) 14.244409906640747
(391, 0) 14.902704249607675
(903, 0) 0.5230024581417142
(1031, 0) 8.70026662105173
(520, 0) 10.749146426975798
(521, 0) -0.3975292543165676
(138, 0) 12.426856080125647
(395, 0) 8.916467351123888
(12, 0) -2.8616338036589584
(524, 0) 4.681955023757494
(652, 0) 12.246118348445732
(781, 0) -2.8507967809171832
(1041, 0) 6.995574359377683
(274, 0) 2.3309249317612797
(147, 0) 12.359227470864713
(1171, 0) 10.093092378619225
(660, 0) 8.154601665233967
(1174, 0) 6.3087799395907735
(1175, 0) 4.032828405884995
(1431, 0) 12.646691935293564
(280, 0) 5.906604494435242
: :
(9455, 19) 7.558948666478769
(8304, 19) 9.961580742164827
(9328, 19) -4.291674396726237
(9456, 19) 10.757362989980543
(9840, 19) 7.538590667108517
(8689, 19) 6.044390744662792
(8306, 19) 11.117171906013688
(9586, 19) -1.6244139065599927
(8563, 19) 2.462342095564601
(9076, 19) 13.118104961154485
(9077, 19) -0.8453695473258904
(8312, 19) 4.134135117931522
(8696, 19) 17.381880180943515
(9337, 19) 3.6189164595357495
(9465, 19) 2.4950134371873016
(9338, 19) 5.2247938993269765
(8316, 19) 7.9226341254972095
(8061, 19) 5.225457778998734
(9214, 19) 1.028785069440696
(9726, 19) -4.226047690994142
(9957, 19) 8.546192901635248
(9963, 19) 4.229201318842815
(9968, 19) 4.735706306709329
(9974, 19) 5.79253803092277
(9976, 19) 6.973675422848798
从这里,我们可以继续使用 CuPy 稀疏矩阵进行工作。