- 数据分析
- 数据工程
- 机器学习
【免费下载链接】cudf
cuDF - GPU DataFrame Library
cuDF 的文档页 interop_dlpack.rst 通过 Doxygen 组interop_dlpack渲染了 libcudf 中 DLPack 互操作的核心 API:cudf::from_dlpack与cudf::to_dlpack。这两个函数是 cuDF 表(cudf::table/cudf::table_view)与 DLPack 张量(DLManagedTensor)之间的标准转换边界,使 cuDF 能够与 PyTorch、CuPy 等任何支持__dlpack__协议的 GPU 张量库交换数据。读完本文,你将掌握这两个 API 的完整签名、约束条件(设备类型、维度、内存布局、数据类型映射)以及底层拷贝语义,并能用 C++ 或 Python(pylibcudf)在实际工程中完成双向转换。
文档页面与 API 声明
原始文档页只有两行有效内容:
.. doxygengroup:: interop_dlpack :members:即通过doxygengroup指令自动渲染 Doxygen 组interop_dlpack的全部成员文档。该组在头文件 interop.hpp 中声明,组的正式成员就是from_dlpack与to_dlpack两个函数:
- 入站:
std::unique_ptr<table> from_dlpack(DLManagedTensor const* managed_tensor, ...),将外部张量导入为 cuDF 表; - 出站:
DLManagedTensor* to_dlpack(table_view const& input, ...),将 cuDF 表导出为张量。
头文件声明(interop.hpp)如下,两个函数都接受可选的cuda::stream_ref stream与rmm::device_async_resource_ref mr,默认分别为cudf::get_default_stream()和cudf::get_current_device_resource_ref():
std::unique_ptr<table> from_dlpack( DLManagedTensor const* managed_tensor, cuda::stream_ref stream = cudf::get_default_stream(), rmm::device_async_resource_ref mr = cudf::get_current_device_resource_ref()); DLManagedTensor* to_dlpack( table_view const& input, cuda::stream_ref stream = cudf::get_default_stream(), rmm::device_async_resource_ref mr = cudf::get_current_device_resource_ref());注意 DLPack 头文件在 libcudf 中是以构建期第三方依赖方式引入的,见 get_dlpack.cmake;interop.hpp只前向声明了struct DLManagedTensor,避免把 DLPack 头泄漏进所有消费者(interop.hpp)。
数据类型映射
DLPack 与 libcudf 的类型系统并不一一对应。从源码 dlpack.cpp 的DLDataType_to_data_type可以确认完整的映射规则,也是from_dlpack唯一接受的类型集合:
| DLPack 类型 | bits | 对应 cudf data_type |
|---|---|---|
kDLInt | 8 / 16 / 32 / 64 | INT8/INT16/INT32/INT64 |
kDLUInt | 8 / 16 / 32 / 64 | UINT8/UINT16/UINT32/UINT64 |
kDLFloat | 32 / 64 | FLOAT32/FLOAT64 |
硬性约束(违反即抛cudf::logic_error):
lanes必须为 1,即不支持复数或向量类型(源码中的CUDF_EXPECTS(type.lanes == 1, ...));- 其他
code(如kDLOpaque、kBfloat)直接CUDF_FAIL; - 整数位宽超出 {8, 16, 32, 64} 或浮点位宽超出 {32, 64} 也不支持。
反向映射由data_type_to_DLDataType(dlpack.cpp)完成:浮点映射到kDLFloat,有符号整数映射到kDLInt,无符号映射到kDLUInt,bits = sizeof(T) * 8;非数值类型(字符串、时间戳、列表、结构、布尔等)一律拒绝,抛cudf::logic_error。这一点与文档注释一致:"All columns must have the same data type and this type must be numeric"。
from_dlpack:约束与实现细节
from_dlpack的文档注释列出了顶层约束:device_type必须是kDLCPU、kDLCuda或kDLCUDAHost,device_id必须匹配当前设备,ndim必须为 1 或 2,且该函数不会删除传入的 managed tensor(所有权仍归调用方)。实现位于 detail::from_dlpack,逐条校验并执行数据拷贝:
- 设备校验(L134-L143):接受 CPU、CUDA、CUDAHost 三类指针;若为非 CPU 设备,调用
cudaGetDevice确认tensor.device.device_id与当前设备一致。 - 维度与布局校验(L146-L165):
- 1D:必须是紧凑布局(
strides == nullptr或strides[0] == 1),空张量(shape[0] == 0)例外; - 2D:只接受**列主序(column-major / Fortran order)**数据,即
strides[0] == 1且strides[1] >= shape[0],或退化的(N, 1)紧凑形状。行主序(C order)张量会被直接拒绝——测试 dlpack_test.cpp 中UnsupportedImplicitRowMajor2DTensorFromDlpack与UnsupportedExplicitRowMajor2DTensorFromDlpack正是覆盖这两个场景; - 广播张量(stride-0)与任意非单位步进的 1D 张量同样被拒绝。
- 1D:必须是紧凑布局(
- 尺寸上限(L166-L177):
shape[0]与shape[1]不得超过size_type最大值,否则抛std::overflow_error(即列大小上限,通常为 2^31-1)。 - 逐列拷贝(L193-L206):计算
tensor.data + byte_offset作为起点,对每一列make_numeric_column后执行detail::memcpy_async(支持 host→device、device→device 等方向),列与列之间按col_stride = byte_width * strides[1]步进(1D 或无 strides 时退化为byte_width * num_rows)。
由此可得出几个实用结论:from_dlpack总是产生数据拷贝(结果表拥有自己的设备内存);byte_offset被正确支持,测试FromDlpackCpu(dlpack_test.cpp)就验证了带byte_offset与稀疏 strides 的 host 2D 张量导入;空表无法表达类型信息,to_dlpack对空表返回nullptr,from_dlpack(nullptr)则抛cudf::logic_error。
to_dlpack:约束、拷贝语义与所有权
to_dlpack的文档注释(interop.hpp)给出了三条核心规则,实现 detail::to_dlpack 与之严格对应:
- 所有列必须同类型且为数值类型(L222-L224,
all_have_same_types+data_type_to_DLDataType,否则抛data_type_error); - 列可以带空值掩码,但 null 计数必须为 0(L227-L229)——DLPack 本身没有 null 语义,因此"可空但实际无空值"的列可以通过,含空值的列抛错;
- 空表(0 行且 0 列)返回
nullptr(L213-L215),因为无法为没有类型信息的空表构造合法的 DLPack 对象。
其余实现要点:
- 形状与步长:单列导出为 1D(
strides = nullptr),多列导出为 2D 列主序张量,strides[0] = 1、strides[1] = num_rows(dlpack.cpp); - 设备字段:
device_type = kDLCUDA,device_id通过cudaGetDevice取得; - 总是执行数据拷贝:源码注释(L250-L257)明确说明,即使是单列也始终把每列数据拷贝到一块新分配的
rmm::device_buffer中,以保证导出的张量独立于源列的后续修改; - 所有权与释放:返回的
DLManagedTensor所有权移交给调用方,其manager_ctx指向内部dltensor_context(持有 shape/strides 数组与 device buffer),必须调用deleter(manager_ctx)释放,否则会泄漏(dlpack.cpp); - 同步语义:函数返回前会对 stream 执行
cudf::detail::sync_stream(L276-L278),因为返回后数据可能被 host 端立即访问(例如 pinned memory 场景),必须保证异步拷贝完成。
C++ 侧最小使用模式(参考测试 dlpack_test.cpp 的所有权包装写法):
#include <cudf/interop.hpp> #include <dlpack/dlpack.h> struct dlpack_deleter { void operator()(DLManagedTensor* t) { t->deleter(t); } }; using unique_managed_tensor = std::unique_ptr<DLManagedTensor, dlpack_deleter>; // 导出:table_view -> DLPack 张量 unique_managed_tensor tensor{cudf::to_dlpack(table_view_of_numeric_cols)}; // 使用 tensor->dl_tensor(data/shape/strides/dtype...) // 导入:DLPack 张量 -> cudf::table(总是拷贝,不消费输入) std::unique_ptr<cudf::table> result = cudf::from_dlpack(tensor.get());Python 层:pylibcudf 的 interop 封装
cuDF 的 Python 栈通过 pylibcudf/interop.pyx 将上述 C++ API 暴露为plc.interop.from_dlpack与plc.interop.to_dlpack,桥接层额外处理了 PyCapsule 协议:
plc.interop.from_dlpack接收任何实现了__dlpack__()的 Python 对象(NumPy 数组、CuPy 数组等),用PyCapsule_GetPointer取出DLManagedTensor*;由于 C++ 侧from_dlpack不删除输入张量,PyXLL 封装层在完成转换后会主动调用dlpack_tensor.deleter释放 capsule 指向的对象(interop.pyx 中的注释与代码明确了这一点);plc.interop.to_dlpack则把 C++ 返回的DLManagedTensor重新封装回 PyCapsule 交给下游张量库,由消费方按 DLPack 协议负责释放。
因此 Python 侧的完整往返非常直接,test_interop.py 给出了两个可复现的示例:
import pylibcudf as plc # cudf 表 -> DLPack capsule -> cudf 表 往返 plc_table = plc.Table.from_arrow(pa.table({"a": [1, 2, 3], "b": [5, 6, 7]})) result = plc.interop.from_dlpack(plc.interop.to_dlpack(plc_table))import cupy as cp import numpy as np # 直接从 NumPy / CuPy 数组经 __dlpack__ 进入 cudf arr = cp.array([1, 2, 3]) plc.interop.from_dlpack(arr.__dlpack__())同文件中的边界测试也印证了 C++ 层约束在 Python 侧的行为:to_dlpack遇到含 null 的表会抛出ValueError("Cannot create a DLPack tensor"),from_dlpack传入非 capsule 对象抛出 "Invalid PyCapsule object"(test_interop.py)。
边界情况与测试矩阵
C++ 测试 cpp/tests/interop/dlpack_test.cpp 对全部约束做了系统化覆盖,可作为接入前自检清单:
| 测试用例 | 场景 | 预期 |
|---|---|---|
EmptyTableToDlpack | 0 列 0 行空表 | to_dlpack返回nullptr |
EmptyColsToDlpack | 0 行的两列 int32 表 | 合法 2D 空张量,strides全 0,可往返 |
NullTensorFromDlpack | 传nullptr给from_dlpack | 抛cudf::logic_error |
MultipleTypesToDlpack | 列类型不一致(int16 + int32) | 抛cudf::data_type_error |
InvalidNullsToDlpack | 含 null 的列 | 抛cudf::logic_error |
StringTypeToDlpack | 字符串列 | 抛cudf::logic_error |
ChronoTypesToDlpack | 时间戳列 | 抛cudf::logic_error |
UnsupportedDeviceTypeFromDlpack/InvalidDeviceIdFromDlpack | 伪造设备类型/设备 ID | 抛cudf::logic_error |
TooManyRowsFromDlpack/TooManyColsFromDlpack | 维度超过 size_type 上限 | 抛std::overflow_error |
InvalidTypeFromDlpack/UnsupportedIntBitsizeFromDlpack/UnsupportedLanesFromDlpack | 非法 dtype code / 位宽 / lanes | 抛cudf::logic_error |
UnsupportedBroadcast1DTensorFromDlpack/UnsupportedStrided1DTensorFromDlpack | stride-0 或任意步进的 1D 张量 | 抛cudf::logic_error |
UnsupportedImplicit/ExplicitRowMajor2DTensorFromDlpack、UnsupportedStridedColMajor2DTensorFromDlpack | 行主序 2D 或列内带步进的 2D 张量 | 抛cudf::logic_error |
ToDlpack1D/ToDlpack2D/FromDlpack1D/FromDlpack2D/FromDlpackCpu | 数值类型的往返正确性(含 host 源、byte_offset) | 表内容与输入一致 |
另外,streams/interop_test.cpp 中还包含针对 stream 使用行为的 DLPack 相关测试,说明这两个 API 遵循 libcudf 的 stream 语义(所有拷贝与 kernel 均在指定 stream 上执行)。
使用建议与限制小结
- 适用前提:交换的数据必须是纯数值、无空值的同类型多列数据;维度上限 2;2D 输入必须列主序(行主序需先在源端转置);
- 性能特征:
from_dlpack与to_dlpack均为拷贝式转换(to_dlpack还额外sync_stream),不做零拷贝共享。若需要与 Arrow 生态(而非张量生态)做零拷贝 GPU 数据交换,应使用同一头文件中interop_arrow组的to_arrow_device/from_arrow_device等 API(见 interop.hpp),它们可以零拷贝地包装 GPU 数据并遵循 Arrow C Data Interface; - 所有权纪律:C++ 侧记得对返回的
DLManagedTensor调用deleter;Python 侧由 pylibcudf 自动管理 capsule 生命周期,只需保证__dlpack__()的对象在转换期间存活; - 与文档页的对应关系:本文所有 API 语义、参数默认值与异常说明均直接来自 interop.hpp 中
interop_dlpack组的 Doxygen 注释(即文档页渲染的原始内容),实现细节以 dlpack.cpp 为准,行为边界以 dlpack_test.cpp 与 test_interop.py 的测试为准。
- 数据分析
- 数据工程
- 机器学习
【免费下载链接】cudf
cuDF - GPU DataFrame Library
相关推荐
cuDF libcudf Column Interop 完全指南:DLPack 与 Apache Arrow 数据互操作 API 详解
cuDF libcudf Column Interop 完全指南:DLPack 与 Apache Arrow 数据互操作 API 详解 导读 本文围绕 libc
数据分析数据工程机器学习JAX 与 DLPack:跨框架零拷贝张量互操作的协议实现与实战指南
JAX 与 DLPack:跨框架零拷贝张量互操作的协议实现与实战指南 本文围绕 docs/jax.dlpack.rst https://link.gitcode
人工智能机器学习深度学习编译器高性能计算如何用 DLPack 协议在 PyArrow 与张量框架之间交换数据
如何用 DLPack 协议在 PyArrow 与张量框架之间交换数据 如果你的数据管道一端是 PyArrow(例如从 Parquet、CSV 或 Flight
大数据数据分析数据工程序列化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考