Skip to content

Commit 6118e24

Browse files
feat(python): Implement array from buffer for non-CPU arrays (#550)
Requires building with (e.g.) `export NANOARROW_PYTHON_CUDA=/usr/local/cuda` and a `cupy` install: ```python import nanoarrow as na from nanoarrow import device import cupy as cp device.c_device_array(cp.array([1, 2, 3])) #> <nanoarrow.device.CDeviceArray> #> - device_type: CUDA <2> #> - device_id: 0 #> - array: <nanoarrow.c_array.CArray int64> #> - length: 3 #> - offset: 0 #> - null_count: 0 #> - buffers: (0, 133980798058496) #> - dictionary: NULL #> - children[0]: # Also roundtrips darray = device.c_device_array(cp.array([1, 2, 3])) cp.from_dlpack(darray.array.view().buffer(1)) #> array([1, 2, 3]) ``` --------- Co-authored-by: Dane Pitkin <[email protected]>
1 parent 48168e0 commit 6118e24

17 files changed

+588
-72
lines changed

python/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def get_version(pkg_path):
8585

8686
device_include_dirs.append(str(include_dir))
8787
device_libraries.append("cuda")
88-
device_define_macros.append(("NANOARROW_DEVICE_WITH_CUDA", 1))
88+
extra_define_macros.append(("NANOARROW_DEVICE_WITH_CUDA", 1))
8989

9090
# Library might be already in a system library directory such that no -L flag
9191
# is needed

python/src/nanoarrow/_array.pxd

+4-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ from nanoarrow_device_c cimport (
2929
ArrowDeviceType
3030
)
3131

32-
from nanoarrow._device cimport Device
32+
from nanoarrow._device cimport CSharedSyncEvent
3333
from nanoarrow._schema cimport CSchema
3434

3535

@@ -39,15 +39,16 @@ cdef class CArray:
3939
cdef CSchema _schema
4040
cdef ArrowDeviceType _device_type
4141
cdef int _device_id
42+
cdef void* _sync_event
4243

43-
cdef _set_device(self, ArrowDeviceType device_type, int64_t device_id)
44+
cdef _set_device(self, ArrowDeviceType device_type, int64_t device_id, void* sync_event)
4445

4546

4647
cdef class CArrayView:
4748
cdef object _base
4849
cdef object _array_base
4950
cdef ArrowArrayView* _ptr
50-
cdef Device _device
51+
cdef CSharedSyncEvent _event
5152

5253
cdef class CDeviceArray:
5354
cdef object _base

python/src/nanoarrow/_array.pyx

+85-25
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,14 @@ from nanoarrow_c cimport (
6767
NANOARROW_OK,
6868
)
6969

70-
7170
from nanoarrow_device_c cimport (
7271
ARROW_DEVICE_CPU,
7372
ArrowDeviceType,
7473
ArrowDeviceArray,
7574
ArrowDeviceArrayInit,
7675
)
7776

78-
from nanoarrow._device cimport Device
77+
from nanoarrow._device cimport Device, CSharedSyncEvent
7978

8079
from nanoarrow._buffer cimport CBuffer, CBufferView
8180
from nanoarrow._schema cimport CSchema, CLayout
@@ -107,7 +106,7 @@ cdef class CArrayView:
107106
def __cinit__(self, object base, uintptr_t addr):
108107
self._base = base
109108
self._ptr = <ArrowArrayView*>addr
110-
self._device = DEVICE_CPU
109+
self._event = CSharedSyncEvent(DEVICE_CPU)
111110

112111
def _set_array(self, CArray array, Device device=DEVICE_CPU):
113112
cdef Error error = Error()
@@ -120,7 +119,8 @@ cdef class CArrayView:
120119

121120
error.raise_message_not_ok("ArrowArrayViewSetArray()", code)
122121
self._array_base = array._base
123-
self._device = device
122+
self._event = CSharedSyncEvent(device, <uintptr_t>array._sync_event)
123+
124124
return self
125125

126126
@property
@@ -160,7 +160,7 @@ cdef class CArrayView:
160160
self._ptr.null_count = 0
161161
elif validity_bits == NULL:
162162
self._ptr.null_count = 0
163-
elif self._device is DEVICE_CPU:
163+
elif self._event.device is DEVICE_CPU:
164164
self._ptr.null_count = ArrowArrayViewComputeNullCount(self._ptr)
165165

166166
return self._ptr.null_count
@@ -178,7 +178,8 @@ cdef class CArrayView:
178178
<uintptr_t>self._ptr.children[i]
179179
)
180180

181-
child._device = self._device
181+
child._event = self._event
182+
182183
return child
183184

184185
@property
@@ -227,7 +228,7 @@ cdef class CArrayView:
227228
buffer_view.size_bytes,
228229
self._ptr.layout.buffer_data_type[i],
229230
self._ptr.layout.element_size_bits[i],
230-
self._device
231+
self._event
231232
)
232233

233234
@property
@@ -239,11 +240,14 @@ cdef class CArrayView:
239240
def dictionary(self):
240241
if self._ptr.dictionary == NULL:
241242
return None
242-
else:
243-
return CArrayView(
244-
self,
245-
<uintptr_t>self._ptr.dictionary
246-
)
243+
244+
cdef CArrayView dictionary = CArrayView(
245+
self,
246+
<uintptr_t>self._ptr.dictionary
247+
)
248+
dictionary._event = self._event
249+
250+
return dictionary
247251

248252
def __repr__(self):
249253
return _repr_utils.array_view_repr(self)
@@ -288,11 +292,13 @@ cdef class CArray:
288292
self._ptr = <ArrowArray*>addr
289293
self._schema = schema
290294
self._device_type = ARROW_DEVICE_CPU
291-
self._device_id = 0
295+
self._device_id = -1
296+
self._sync_event = NULL
292297

293-
cdef _set_device(self, ArrowDeviceType device_type, int64_t device_id):
298+
cdef _set_device(self, ArrowDeviceType device_type, int64_t device_id, void* sync_event):
294299
self._device_type = device_type
295300
self._device_id = device_id
301+
self._sync_event = sync_event
296302

297303
@staticmethod
298304
def _import_from_c_capsule(schema_capsule, array_capsule) -> CArray:
@@ -350,7 +356,8 @@ cdef class CArray:
350356
c_array_out.offset = c_array_out.offset + start
351357
c_array_out.length = stop - start
352358
cdef CArray out = CArray(base, <uintptr_t>c_array_out, self._schema)
353-
out._set_device(self._device_type, self._device_id)
359+
out._set_device(self._device_type, self._device_id, self._sync_event)
360+
354361
return out
355362

356363
def __arrow_c_array__(self, requested_schema=None):
@@ -466,7 +473,7 @@ cdef class CArray:
466473
<uintptr_t>self._ptr.children[i],
467474
self._schema.child(i)
468475
)
469-
out._set_device(self._device_type, self._device_id)
476+
out._set_device(self._device_type, self._device_id, self._sync_event)
470477
return out
471478

472479
@property
@@ -480,7 +487,7 @@ cdef class CArray:
480487
cdef CArray out
481488
if self._ptr.dictionary != NULL:
482489
out = CArray(self, <uintptr_t>self._ptr.dictionary, self._schema.dictionary)
483-
out._set_device(self._device_type, self._device_id)
490+
out._set_device(self._device_type, self._device_id, self._sync_event)
484491
return out
485492
else:
486493
return None
@@ -497,22 +504,24 @@ cdef class CArrayBuilder:
497504
"""
498505
cdef CArray c_array
499506
cdef ArrowArray* _ptr
507+
cdef Device _device
500508
cdef bint _can_validate
501509

502-
def __cinit__(self, CArray array):
510+
def __cinit__(self, CArray array, Device device=DEVICE_CPU):
503511
self.c_array = array
504512
self._ptr = array._ptr
505-
self._can_validate = True
513+
self._device = device
514+
self._can_validate = device is DEVICE_CPU
506515

507516
@staticmethod
508-
def allocate():
517+
def allocate(Device device=DEVICE_CPU):
509518
"""Create a CArrayBuilder
510519
511520
Allocates memory for an ArrowArray and populates it with nanoarrow's
512521
ArrowArray private_data/release callback implementation. This should
513522
usually be followed by :meth:`init_from_type` or :meth:`init_from_schema`.
514523
"""
515-
return CArrayBuilder(CArray.allocate(CSchema.allocate()))
524+
return CArrayBuilder(CArray.allocate(CSchema.allocate()), device)
516525

517526
def is_empty(self) -> bool:
518527
"""Check if any items have been appended to this builder"""
@@ -550,6 +559,9 @@ cdef class CArrayBuilder:
550559
Calling this method is required to produce a valid array prior to calling
551560
:meth:`append_strings` or `append_bytes`.
552561
"""
562+
if self._device != DEVICE_CPU:
563+
raise ValueError("Can't append to non-CPU array")
564+
553565
cdef int code = ArrowArrayStartAppending(self._ptr)
554566
Error.raise_error_not_ok("ArrowArrayStartAppending()", code)
555567
return self
@@ -617,7 +629,11 @@ cdef class CArrayBuilder:
617629
return self
618630

619631
def resolve_null_count(self) -> CArrayBuilder:
620-
"""Ensure the output null count is synchronized with existing buffers"""
632+
"""Ensure the output null count is synchronized with existing buffers
633+
634+
Note that this will not attempt to access non-CPU buffers such that
635+
:attr:`null_count` might still be -1 after calling this method.
636+
"""
621637
self.c_array._assert_valid()
622638

623639
# This doesn't apply to unions. We currently don't have a schema view
@@ -636,6 +652,10 @@ cdef class CArrayBuilder:
636652
self._ptr.null_count = 0
637653
return self
638654

655+
# Don't attempt to access a non-cpu buffer
656+
if self._device != DEVICE_CPU:
657+
return self
658+
639659
# From _ArrowBytesForBits(), which is not included in nanoarrow_c.pxd
640660
# because it's an internal inline function.
641661
cdef int64_t bits = self._ptr.offset + self._ptr.length
@@ -669,6 +689,14 @@ cdef class CArrayBuilder:
669689
if i < 0 or i > 3:
670690
raise IndexError("i must be >= 0 and <= 3")
671691

692+
if buffer._device != self._device:
693+
raise ValueError(
694+
f"Builder device ({self._device.device_type}/{self._device.device_id})"
695+
" and buffer device "
696+
f"({buffer._device.device_type}/{buffer._device.device_id})"
697+
" are not identical"
698+
)
699+
672700
self.c_array._assert_valid()
673701
if not move:
674702
buffer = CBuffer.from_pybuffer(buffer)
@@ -694,6 +722,26 @@ cdef class CArrayBuilder:
694722
if child._ptr.release != NULL:
695723
ArrowArrayRelease(child._ptr)
696724

725+
if (
726+
self._device.device_type_id != c_array.device_type_id
727+
or self._device.device_id != c_array.device_id
728+
):
729+
raise ValueError(
730+
f"Builder device ({self._device.device_type}/{self._device.device_id})"
731+
" and child device "
732+
f"({c_array.device_type}/{c_array.device_id}) are not identical"
733+
)
734+
735+
# There is probably a way to avoid a full synchronize for each child
736+
# (e.g., perhaps the ArrayBuilder could allocate a stream to use such
737+
# that an event can be allocated on finish_device() and synchronization
738+
# could be avoided entirely). Including this for now for safety.
739+
cdef CSharedSyncEvent sync = CSharedSyncEvent(
740+
self._device,
741+
<uintptr_t>c_array._sync_event
742+
)
743+
sync.synchronize()
744+
697745
if not move:
698746
c_array_shallow_copy(c_array._base, c_array._ptr, child._ptr)
699747
else:
@@ -747,6 +795,20 @@ cdef class CArrayBuilder:
747795

748796
return out
749797

798+
def finish_device(self):
799+
"""Finish building this array and export to an ArrowDeviceArray
800+
801+
Calls :meth:`finish`, propagating device information into an ArrowDeviceArray.
802+
"""
803+
cdef CArray array = self.finish()
804+
805+
cdef ArrowDeviceArray* device_array_ptr
806+
holder = alloc_c_device_array(&device_array_ptr)
807+
cdef int code = ArrowDeviceArrayInit(self._device._ptr, device_array_ptr, array._ptr, NULL)
808+
Error.raise_error_not_ok("ArrowDeviceArrayInit", code)
809+
810+
return CDeviceArray(holder, <uintptr_t>device_array_ptr, array._schema)
811+
750812

751813
cdef class CDeviceArray:
752814
"""Low-level ArrowDeviceArray wrapper
@@ -792,10 +854,8 @@ cdef class CDeviceArray:
792854

793855
@property
794856
def array(self) -> CArray:
795-
# TODO: We lose access to the sync_event here, so we probably need to
796-
# synchronize (or propagate it, or somehow prevent data access downstream)
797857
cdef CArray array = CArray(self, <uintptr_t>&self._ptr.array, self._schema)
798-
array._set_device(self._ptr.device_type, self._ptr.device_id)
858+
array._set_device(self._ptr.device_type, self._ptr.device_id, self._ptr.sync_event)
799859
return array
800860

801861
def view(self) -> CArrayView:

python/src/nanoarrow/_buffer.pxd

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ from nanoarrow_c cimport (
2525
ArrowType,
2626
)
2727

28-
from nanoarrow._device cimport Device
28+
from nanoarrow._device cimport Device, CSharedSyncEvent
2929

3030

3131
cdef class CBufferView:
3232
cdef object _base
3333
cdef ArrowBufferView _ptr
3434
cdef ArrowType _data_type
35-
cdef Device _device
35+
cdef CSharedSyncEvent _event
3636
cdef Py_ssize_t _element_size_bits
3737
cdef Py_ssize_t _shape
3838
cdef Py_ssize_t _strides

0 commit comments

Comments
 (0)