From 2fa2f74e5e8a9dea76e4136b34050ae9d4cdff9c Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Tue, 11 Jun 2024 17:29:45 -0400 Subject: [PATCH 1/9] [Python] Array gracefully fails on non-cpu device --- python/pyarrow/array.pxi | 74 +++++++++++++++++++++--- python/pyarrow/includes/libarrow.pxd | 1 + python/pyarrow/tests/test_array.py | 85 ++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 8 deletions(-) diff --git a/python/pyarrow/array.pxi b/python/pyarrow/array.pxi index 64a6ceaa6ea..f6722be3b0c 100644 --- a/python/pyarrow/array.pxi +++ b/python/pyarrow/array.pxi @@ -956,6 +956,7 @@ cdef class Array(_PandasConvertible): +"two-and-a-half" """ + self._assert_cpu() cdef c_string result with nogil: result = self.ap.Diff(deref(other.ap)) @@ -982,6 +983,7 @@ cdef class Array(_PandasConvertible): ------- cast : Array """ + self._assert_cpu() return _pc().cast(self, target_type, safe=safe, options=options, memory_pool=memory_pool) @@ -1000,6 +1002,7 @@ cdef class Array(_PandasConvertible): ------- view : Array """ + self._assert_cpu() cdef DataType type = ensure_type(target_type) cdef shared_ptr[CArray] result with nogil: @@ -1022,6 +1025,7 @@ cdef class Array(_PandasConvertible): sum : Scalar A scalar containing the sum value. """ + self._assert_cpu() options = _pc().ScalarAggregateOptions(**kwargs) return _pc().call_function('sum', [self], options) @@ -1034,6 +1038,7 @@ cdef class Array(_PandasConvertible): unique : Array An array of the same data type, with deduplicated elements. """ + self._assert_cpu() return _pc().call_function('unique', [self]) def dictionary_encode(self, null_encoding='mask'): @@ -1052,6 +1057,7 @@ cdef class Array(_PandasConvertible): encoded : DictionaryArray A dictionary-encoded version of this array. """ + self._assert_cpu() options = _pc().DictionaryEncodeOptions(null_encoding) return _pc().call_function('dictionary_encode', [self], options) @@ -1064,6 +1070,7 @@ cdef class Array(_PandasConvertible): StructArray An array of structs """ + self._assert_cpu() return _pc().call_function('value_counts', [self]) @staticmethod @@ -1101,10 +1108,12 @@ cdef class Array(_PandasConvertible): array : pyarrow.Array or pyarrow.ChunkedArray ChunkedArray is returned if object data overflows binary buffer. """ + self._assert_cpu() return array(obj, mask=mask, type=type, safe=safe, from_pandas=True, memory_pool=memory_pool) def __reduce__(self): + self._assert_cpu() return _restore_array, \ (_reduce_array_data(self.sp_array.get().data().get()),) @@ -1172,6 +1181,7 @@ cdef class Array(_PandasConvertible): @property def null_count(self): + self._assert_cpu() return self.sp_array.get().null_count() @property @@ -1191,9 +1201,8 @@ cdef class Array(_PandasConvertible): The dictionary of dictionary arrays will always be counted in their entirety even if the array only references a portion of the dictionary. """ - cdef: - CResult[int64_t] c_size_res - + self._assert_cpu() + cdef CResult[int64_t] c_size_res with nogil: c_size_res = ReferencedBufferSize(deref(self.ap)) size = GetResultValue(c_size_res) @@ -1210,20 +1219,22 @@ cdef class Array(_PandasConvertible): If a buffer is referenced multiple times then it will only be counted once. """ - cdef: - int64_t total_buffer_size - + self._assert_cpu() + cdef int64_t total_buffer_size total_buffer_size = TotalBufferSize(deref(self.ap)) return total_buffer_size def __sizeof__(self): + self._assert_cpu() return super(Array, self).__sizeof__() + self.nbytes def __iter__(self): + self._assert_cpu() for i in range(len(self)): yield self.getitem(i) def __repr__(self): + self._assert_cpu() type_format = object.__repr__(self) return '{0}\n{1}'.format(type_format, str(self)) @@ -1252,6 +1263,8 @@ cdef class Array(_PandasConvertible): If the array should be rendered as a single line of text or if each element should be on its own line. """ + self._assert_cpu() + cdef: c_string result PrettyPrintOptions options @@ -1287,6 +1300,7 @@ cdef class Array(_PandasConvertible): return self.to_string(**kwargs) def __str__(self): + self._assert_cpu() return self.to_string() def __eq__(self, other): @@ -1307,6 +1321,8 @@ cdef class Array(_PandasConvertible): ------- bool """ + self._assert_cpu() + self.other._assert_cpu() return self.ap.Equals(deref(other.ap)) def __len__(self): @@ -1331,6 +1347,7 @@ cdef class Array(_PandasConvertible): ------- array : boolean Array """ + self._assert_cpu() options = _pc().NullOptions(nan_is_null=nan_is_null) return _pc().call_function('is_null', [self], options) @@ -1342,12 +1359,14 @@ cdef class Array(_PandasConvertible): ------- array : boolean Array """ + self._assert_cpu() return _pc().call_function('is_nan', [self]) def is_valid(self): """ Return BooleanArray indicating the non-null values. """ + self._assert_cpu() return _pc().is_valid(self) def fill_null(self, fill_value): @@ -1364,6 +1383,7 @@ cdef class Array(_PandasConvertible): result : Array A new array with nulls replaced by the given value. """ + self._assert_cpu() return _pc().fill_null(self, fill_value) def __getitem__(self, key): @@ -1380,12 +1400,14 @@ cdef class Array(_PandasConvertible): ------- value : Scalar (index) or Array (slice) """ + self._assert_cpu() if isinstance(key, slice): return _normalize_slice(self, key) return self.getitem(_normalize_index(key, self.length())) cdef getitem(self, int64_t i): + self._assert_cpu() return Scalar.wrap(GetResultValue(self.ap.GetScalar(i))) def slice(self, offset=0, length=None): @@ -1404,8 +1426,9 @@ cdef class Array(_PandasConvertible): ------- sliced : RecordBatch """ - cdef: - shared_ptr[CArray] result + self._assert_cpu() + + cdef shared_ptr[CArray] result if offset < 0: raise IndexError('Offset must be non-negative') @@ -1436,12 +1459,14 @@ cdef class Array(_PandasConvertible): taken : Array An array with the same datatype, containing the taken values. """ + self._assert_cpu() return _pc().take(self, indices) def drop_null(self): """ Remove missing values from an array. """ + self._assert_cpu() return _pc().drop_null(self) def filter(self, object mask, *, null_selection_behavior='drop'): @@ -1463,6 +1488,7 @@ cdef class Array(_PandasConvertible): An array of the same type, with only the elements selected by the boolean mask. """ + self._assert_cpu() return _pc().filter(self, mask, null_selection_behavior=null_selection_behavior) @@ -1488,6 +1514,7 @@ cdef class Array(_PandasConvertible): index : Int64Scalar The index of the value in the array (-1 if not found). """ + self._assert_cpu() return _pc().index(self, value, start, end, memory_pool=memory_pool) def sort(self, order="ascending", **kwargs): @@ -1507,6 +1534,7 @@ cdef class Array(_PandasConvertible): ------- result : Array """ + self._assert_cpu() indices = _pc().sort_indices( self, options=_pc().SortOptions(sort_keys=[("", order)], **kwargs) @@ -1514,9 +1542,12 @@ cdef class Array(_PandasConvertible): return self.take(indices) def _to_pandas(self, options, types_mapper=None, **kwargs): + self._assert_cpu() return _array_like_to_pandas(self, options, types_mapper=types_mapper) def __array__(self, dtype=None, copy=None): + self._assert_cpu() + if copy is False: try: values = self.to_numpy(zero_copy_only=True) @@ -1566,6 +1597,8 @@ cdef class Array(_PandasConvertible): ------- array : numpy.ndarray """ + self._assert_cpu() + cdef: PyObject* out PandasOptions c_options @@ -1604,6 +1637,7 @@ cdef class Array(_PandasConvertible): ------- lst : list """ + self._assert_cpu() return [x.as_py() for x in self] def tolist(self): @@ -1737,6 +1771,8 @@ cdef class Array(_PandasConvertible): A pair of PyCapsules containing a C ArrowSchema and ArrowArray, respectively. """ + self._assert_cpu() + cdef: ArrowArray* c_array ArrowSchema* c_schema @@ -1885,6 +1921,28 @@ cdef class Array(_PandasConvertible): device = GetResultValue(ExportDevice(self.sp_array)) return device.device_type, device.device_id + @property + def device_type(self): + """ + The device type where the array resides. + + Returns + ------- + DeviceAllocationType + """ + return _wrap_device_allocation_type(self.sp_array.get().device_type()) + + @property + def is_cpu(self): + """ + Whether the array is CPU-accessible. + """ + return self.device_type == DeviceAllocationType.CPU + + def _assert_cpu(self): + if not self.is_cpu: + raise NotImplementedError("Implemented only for data on CPU device") + cdef _array_like_to_pandas(obj, options, types_mapper): cdef: diff --git a/python/pyarrow/includes/libarrow.pxd b/python/pyarrow/includes/libarrow.pxd index 53ad95f2430..5d9e7fcaeb2 100644 --- a/python/pyarrow/includes/libarrow.pxd +++ b/python/pyarrow/includes/libarrow.pxd @@ -234,6 +234,7 @@ cdef extern from "arrow/api.h" namespace "arrow" nogil: CStatus Validate() const CStatus ValidateFull() const CResult[shared_ptr[CArray]] View(const shared_ptr[CDataType]& type) + CDeviceAllocationType device_type() shared_ptr[CArray] MakeArray(const shared_ptr[CArrayData]& data) CResult[shared_ptr[CArray]] MakeArrayOfNull( diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index becf00ead82..ba06cdd6daf 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -4012,3 +4012,88 @@ def test_swapped_byte_order_fails(numpy_native_dtype): # Struct type array with pytest.raises(pa.ArrowNotImplementedError): pa.StructArray.from_arrays([np_arr], names=['a']) + + +def test_non_cpu_array(): + cuda = pytest.importorskip("pyarrow.cuda") + ctx = cuda.Context(0) + + data = np.arange(4, dtype=np.int32) + validity = np.array([True, False, True, False], dtype=np.bool_) + cuda_data_buf = ctx.buffer_from_data(data) + cuda_validity_buf = ctx.buffer_from_data(validity) + arr = pa.Array.from_buffers(pa.int32(), 4, [None, cuda_data_buf]) + arr2 = pa.Array.from_buffers(pa.int32(), 4, [None, cuda_data_buf]) + arr_with_nulls = pa.Array.from_buffers(pa.int32(), 4, [cuda_validity_buf, cuda_data_buf]) + + # Supported + arr.validate() + arr.validate(full=True) + assert arr.offset() == 0 + assert arr.buffers() == [None, cuda_data_buf] + assert arr.device_type == pa.DeviceAllocationType.CUDA + assert arr.is_cpu is False + + # Not Supported + with pytest.raises(NotImplementedError): + arr.diff(arr2) + with pytest.raises(NotImplementedError): + arr.cast(pa.int64()) + with pytest.raises(NotImplementedError): + arr.view(pa.int64()) + with pytest.raises(NotImplementedError): + arr.sum() + with pytest.raises(NotImplementedError): + arr.unique() + with pytest.raises(NotImplementedError): + arr.dictionary_encode() + with pytest.raises(NotImplementedError): + arr.value_counts() + with pytest.raises(NotImplementedError): + arr.null_count + with pytest.raises(NotImplementedError): + arr.nbytes + with pytest.raises(NotImplementedError): + arr.get_total_buffer_size() + with pytest.raises(NotImplementedError): + [i for i in iter(arr)] + with pytest.raises(NotImplementedError): + repr(arr) + with pytest.raises(NotImplementedError): + str(arr) + with pytest.raises(NotImplementedError): + arr == arr2 + with pytest.raises(NotImplementedError): + arr.is_null() + with pytest.raises(NotImplementedError): + arr.is_nan() + with pytest.raises(NotImplementedError): + arr.is_valid() + with pytest.raises(NotImplementedError): + arr.fill_null(0) + with pytest.raises(NotImplementedError): + arr.__getitem__(0) + with pytest.raises(NotImplementedError): + arr.getitem(0) + with pytest.raises(NotImplementedError): + arr.slice() + with pytest.raises(NotImplementedError): + arr.take([0]) + with pytest.raises(NotImplementedError): + arr.drop_null() + with pytest.raises(NotImplementedError): + arr.filter([True, True, False, False]) + with pytest.raises(NotImplementedError): + arr.index(0) + with pytest.raises(NotImplementedError): + arr.sort() + with pytest.raises(NotImplementedError): + arr.__array__() + with pytest.raises(NotImplementedError): + arr.to_numpy() + with pytest.raises(NotImplementedError): + arr.to_list() + with pytest.raises(NotImplementedError): + arr.__dlpack__() + with pytest.raises(NotImplementedError): + arr.__dlpack_device__() From e6d81817b0be778cb33260a1b34fb08494159c21 Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Tue, 11 Jun 2024 19:01:20 -0400 Subject: [PATCH 2/9] Lint --- python/pyarrow/tests/test_array.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index ba06cdd6daf..bfce73b3c4a 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -4024,7 +4024,8 @@ def test_non_cpu_array(): cuda_validity_buf = ctx.buffer_from_data(validity) arr = pa.Array.from_buffers(pa.int32(), 4, [None, cuda_data_buf]) arr2 = pa.Array.from_buffers(pa.int32(), 4, [None, cuda_data_buf]) - arr_with_nulls = pa.Array.from_buffers(pa.int32(), 4, [cuda_validity_buf, cuda_data_buf]) + arr_with_nulls = pa.Array.from_buffers( + pa.int32(), 4, [cuda_validity_buf, cuda_data_buf]) # Supported arr.validate() @@ -4050,7 +4051,7 @@ def test_non_cpu_array(): with pytest.raises(NotImplementedError): arr.value_counts() with pytest.raises(NotImplementedError): - arr.null_count + arr_with_nulls.null_count with pytest.raises(NotImplementedError): arr.nbytes with pytest.raises(NotImplementedError): From b5ecd313f3e892f88867e3308a175d6f2f165f57 Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Tue, 11 Jun 2024 21:33:07 -0400 Subject: [PATCH 3/9] Remove from staticmethod --- python/pyarrow/array.pxi | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/pyarrow/array.pxi b/python/pyarrow/array.pxi index f6722be3b0c..7ed7243af67 100644 --- a/python/pyarrow/array.pxi +++ b/python/pyarrow/array.pxi @@ -1108,7 +1108,6 @@ cdef class Array(_PandasConvertible): array : pyarrow.Array or pyarrow.ChunkedArray ChunkedArray is returned if object data overflows binary buffer. """ - self._assert_cpu() return array(obj, mask=mask, type=type, safe=safe, from_pandas=True, memory_pool=memory_pool) @@ -1300,7 +1299,6 @@ cdef class Array(_PandasConvertible): return self.to_string(**kwargs) def __str__(self): - self._assert_cpu() return self.to_string() def __eq__(self, other): From 7e217460bafb6db5f05edeec279c2510f5d0e3da Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Mon, 24 Jun 2024 14:52:14 -0700 Subject: [PATCH 4/9] Cleanup --- python/pyarrow/array.pxi | 9 +++------ python/pyarrow/tests/test_array.py | 20 +++++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/python/pyarrow/array.pxi b/python/pyarrow/array.pxi index 7ed7243af67..2448d8f9738 100644 --- a/python/pyarrow/array.pxi +++ b/python/pyarrow/array.pxi @@ -1233,7 +1233,6 @@ cdef class Array(_PandasConvertible): yield self.getitem(i) def __repr__(self): - self._assert_cpu() type_format = object.__repr__(self) return '{0}\n{1}'.format(type_format, str(self)) @@ -1320,7 +1319,7 @@ cdef class Array(_PandasConvertible): bool """ self._assert_cpu() - self.other._assert_cpu() + other._assert_cpu() return self.ap.Equals(deref(other.ap)) def __len__(self): @@ -1424,8 +1423,6 @@ cdef class Array(_PandasConvertible): ------- sliced : RecordBatch """ - self._assert_cpu() - cdef shared_ptr[CArray] result if offset < 0: @@ -1937,8 +1934,8 @@ cdef class Array(_PandasConvertible): """ return self.device_type == DeviceAllocationType.CPU - def _assert_cpu(self): - if not self.is_cpu: + cdef _assert_cpu(self) except -1: + if self.sp_array.get().device_type() != CDeviceAllocationType_kCPU: raise NotImplementedError("Implemented only for data on CPU device") diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index bfce73b3c4a..6e1e168b4b4 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -4034,6 +4034,16 @@ def test_non_cpu_array(): assert arr.buffers() == [None, cuda_data_buf] assert arr.device_type == pa.DeviceAllocationType.CUDA assert arr.is_cpu is False + assert len(arr) == 4 + assert arr.slice(2, 2) == pa.RecordBatch.from_arrays([ + ctx.buffer_from_data(data[2:]) + ]) + + # TODO support DLPack for CUDA + with pytest.raises(NotImplementedError): + arr.__dlpack__() + with pytest.raises(NotImplementedError): + arr.__dlpack_device__() # Not Supported with pytest.raises(NotImplementedError): @@ -4073,11 +4083,7 @@ def test_non_cpu_array(): with pytest.raises(NotImplementedError): arr.fill_null(0) with pytest.raises(NotImplementedError): - arr.__getitem__(0) - with pytest.raises(NotImplementedError): - arr.getitem(0) - with pytest.raises(NotImplementedError): - arr.slice() + arr[0] with pytest.raises(NotImplementedError): arr.take([0]) with pytest.raises(NotImplementedError): @@ -4094,7 +4100,3 @@ def test_non_cpu_array(): arr.to_numpy() with pytest.raises(NotImplementedError): arr.to_list() - with pytest.raises(NotImplementedError): - arr.__dlpack__() - with pytest.raises(NotImplementedError): - arr.__dlpack_device__() From 55640d149056e30e531361a946b90072e3fbedaf Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Mon, 24 Jun 2024 15:44:45 -0700 Subject: [PATCH 5/9] Fix cdef function --- python/pyarrow/array.pxi | 2 +- python/pyarrow/lib.pxd | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/pyarrow/array.pxi b/python/pyarrow/array.pxi index 2448d8f9738..9cff3e21d6b 100644 --- a/python/pyarrow/array.pxi +++ b/python/pyarrow/array.pxi @@ -1934,7 +1934,7 @@ cdef class Array(_PandasConvertible): """ return self.device_type == DeviceAllocationType.CPU - cdef _assert_cpu(self) except -1: + cdef void _assert_cpu(self) except *: if self.sp_array.get().device_type() != CDeviceAllocationType_kCPU: raise NotImplementedError("Implemented only for data on CPU device") diff --git a/python/pyarrow/lib.pxd b/python/pyarrow/lib.pxd index 1bc639cc8d2..082d8470cdb 100644 --- a/python/pyarrow/lib.pxd +++ b/python/pyarrow/lib.pxd @@ -286,6 +286,7 @@ cdef class Array(_PandasConvertible): cdef void init(self, const shared_ptr[CArray]& sp_array) except * cdef getitem(self, int64_t i) cdef int64_t length(self) + cdef void _assert_cpu(self) except * cdef class Tensor(_Weakrefable): From d5a547fd3eaeee656f63650d877e1188854a0c56 Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Mon, 24 Jun 2024 16:24:30 -0700 Subject: [PATCH 6/9] Fix test case --- python/pyarrow/tests/test_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index 6e1e168b4b4..a4e6296a15e 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -4030,7 +4030,7 @@ def test_non_cpu_array(): # Supported arr.validate() arr.validate(full=True) - assert arr.offset() == 0 + assert arr.offset == 0 assert arr.buffers() == [None, cuda_data_buf] assert arr.device_type == pa.DeviceAllocationType.CUDA assert arr.is_cpu is False From 1c903e23355828d8e07df8d582f91a56038810f5 Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Tue, 25 Jun 2024 16:28:42 -0400 Subject: [PATCH 7/9] Update python/pyarrow/tests/test_array.py Co-authored-by: Joris Van den Bossche --- python/pyarrow/tests/test_array.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index a4e6296a15e..6b86867fe6d 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -4035,9 +4035,7 @@ def test_non_cpu_array(): assert arr.device_type == pa.DeviceAllocationType.CUDA assert arr.is_cpu is False assert len(arr) == 4 - assert arr.slice(2, 2) == pa.RecordBatch.from_arrays([ - ctx.buffer_from_data(data[2:]) - ]) + assert arr.slice(2, 2).offset == 2 # TODO support DLPack for CUDA with pytest.raises(NotImplementedError): From c12765a5cff5000f96345b044047fefbd02cb563 Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Tue, 25 Jun 2024 14:31:34 -0700 Subject: [PATCH 8/9] Fix test case --- python/pyarrow/tests/test_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index 6b86867fe6d..95991829be8 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -4097,4 +4097,4 @@ def test_non_cpu_array(): with pytest.raises(NotImplementedError): arr.to_numpy() with pytest.raises(NotImplementedError): - arr.to_list() + arr.tolist() From 9740a90c64735f280255ae02adeb325e65874775 Mon Sep 17 00:00:00 2001 From: Dane Pitkin Date: Tue, 25 Jun 2024 14:54:05 -0700 Subject: [PATCH 9/9] Check for CPU if validate(full=True) --- python/pyarrow/array.pxi | 1 + python/pyarrow/tests/test_array.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/python/pyarrow/array.pxi b/python/pyarrow/array.pxi index 9cff3e21d6b..a9af1419ae4 100644 --- a/python/pyarrow/array.pxi +++ b/python/pyarrow/array.pxi @@ -1658,6 +1658,7 @@ cdef class Array(_PandasConvertible): ArrowInvalid """ if full: + self._assert_cpu() with nogil: check_status(self.ap.ValidateFull()) else: diff --git a/python/pyarrow/tests/test_array.py b/python/pyarrow/tests/test_array.py index 95991829be8..62a3eebb8c0 100644 --- a/python/pyarrow/tests/test_array.py +++ b/python/pyarrow/tests/test_array.py @@ -4029,7 +4029,6 @@ def test_non_cpu_array(): # Supported arr.validate() - arr.validate(full=True) assert arr.offset == 0 assert arr.buffers() == [None, cuda_data_buf] assert arr.device_type == pa.DeviceAllocationType.CUDA @@ -4098,3 +4097,5 @@ def test_non_cpu_array(): arr.to_numpy() with pytest.raises(NotImplementedError): arr.tolist() + with pytest.raises(NotImplementedError): + arr.validate(full=True)