Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REVIEW] Change ways to access ptr in Buffer #12587

Merged
merged 29 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
15cbedf
get_ptr & _array_view refactor
galipremsagar Jan 20, 2023
3697367
Apply suggestions from code review
galipremsagar Jan 20, 2023
2b1b48d
address reviews
galipremsagar Jan 20, 2023
0bba2a5
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 20, 2023
9824471
Apply suggestions from code review
galipremsagar Jan 23, 2023
7868437
Merge remote-tracking branch 'upstream/branch-23.02' into ptr_refactor_1
galipremsagar Jan 23, 2023
07a34b4
drop internal_write
galipremsagar Jan 23, 2023
afd3588
add docstring
galipremsagar Jan 23, 2023
91a9b60
Apply suggestions from code review
galipremsagar Jan 23, 2023
e53a5b7
address reviews
galipremsagar Jan 23, 2023
360ce3f
Merge branch 'ptr_refactor_1' of https://github.com/galipremsagar/cud…
galipremsagar Jan 23, 2023
1c44bd2
address reviews
galipremsagar Jan 23, 2023
8292ed7
add locks
galipremsagar Jan 23, 2023
01e8883
Apply suggestions from code review
galipremsagar Jan 24, 2023
e399211
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 24, 2023
04897ef
Apply suggestions from code review
galipremsagar Jan 24, 2023
8f0f017
make mode a required key-arg
galipremsagar Jan 24, 2023
af0e264
rename to _readonly_proxy_cai_obj
galipremsagar Jan 24, 2023
f04c2a1
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 24, 2023
046025a
Update python/cudf/cudf/core/column/column.py
galipremsagar Jan 24, 2023
bf18485
drop default
galipremsagar Jan 25, 2023
517c2ef
Merge branch 'branch-23.02' into ptr_refactor_1
galipremsagar Jan 25, 2023
9619201
Update python/cudf/cudf/core/buffer/buffer.py
galipremsagar Jan 25, 2023
98c3892
Update python/strings_udf/strings_udf/tests/test_string_udfs.py
galipremsagar Jan 25, 2023
1cf727b
Update python/strings_udf/strings_udf/tests/test_string_udfs.py
galipremsagar Jan 25, 2023
535128c
Update python/strings_udf/strings_udf/tests/test_string_udfs.py
galipremsagar Jan 25, 2023
f1bb022
Fix
galipremsagar Jan 26, 2023
22418fb
Merge remote-tracking branch 'upstream/branch-23.02' into ptr_refactor_1
galipremsagar Jan 26, 2023
810d921
Fix
galipremsagar Jan 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions docs/cudf/source/developer_guide/library_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,13 +236,11 @@ Spilling consists of two components:
- A spill manager that tracks all instances of `SpillableBuffer` and spills them on demand.
A global spill manager is used throughout cudf when spilling is enabled, which makes `as_buffer()` return `SpillableBuffer` instead of the default `Buffer` instances.

Accessing `Buffer.ptr`, we get the device memory pointer of the buffer. This is unproblematic in the case of `Buffer` but what happens when accessing `SpillableBuffer.ptr`, which might have spilled its device memory. In this case, `SpillableBuffer` needs to unspill the memory before returning its device memory pointer. Furthermore, while this device memory pointer is being used (or could be used), `SpillableBuffer` cannot spill its memory back to host memory because doing so would invalidate the device pointer.
Accessing `Buffer.get_ptr(...)`, we get the device memory pointer of the buffer. This is unproblematic in the case of `Buffer` but what happens when accessing `SpillableBuffer.get_ptr(...)`, which might have spilled its device memory. In this case, `SpillableBuffer` needs to unspill the memory before returning its device memory pointer. Furthermore, while this device memory pointer is being used (or could be used), `SpillableBuffer` cannot spill its memory back to host memory because doing so would invalidate the device pointer.

To address this, we mark the `SpillableBuffer` as unspillable, we say that the buffer has been _exposed_. This can either be permanent if the device pointer is exposed to external projects or temporary while `libcudf` accesses the device memory.

The `SpillableBuffer.get_ptr()` returns the device pointer of the buffer memory just like `.ptr` but if given an instance of `SpillLock`, the buffer is only unspillable as long as the instance of `SpillLock` is alive.

For convenience, one can use the decorator/context `acquire_spill_lock` to associate a `SpillLock` with a lifetime bound to the context automatically.
The `SpillableBuffer.get_ptr(...)` returns the device pointer of the buffer memory but if called within an `acquire_spill_lock` decorator/context, the buffer is only marked unspillable while running within the decorator/context.

#### Statistics
cuDF supports spilling statistics, which can be very useful for performance profiling and to identify code that renders buffers unspillable.
Expand Down
2 changes: 0 additions & 2 deletions python/cudf/cudf/_lib/column.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ class Column:
@property
def base_mask(self) -> Optional[Buffer]: ...
@property
def base_mask_ptr(self) -> int: ...
@property
def mask(self) -> Optional[Buffer]: ...
@property
def mask_ptr(self) -> int: ...
Expand Down
34 changes: 15 additions & 19 deletions python/cudf/cudf/_lib/column.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ cdef class Column:
if self.data is None:
return 0
else:
return self.data.ptr
return self.data.get_ptr(mode="write")

def set_base_data(self, value):
if value is not None and not isinstance(value, Buffer):
Expand All @@ -124,13 +124,6 @@ cdef class Column:
def base_mask(self):
return self._base_mask

@property
def base_mask_ptr(self):
if self.base_mask is None:
return 0
else:
return self.base_mask.ptr

@property
def mask(self):
if self._mask is None:
Expand All @@ -145,7 +138,7 @@ cdef class Column:
if self.mask is None:
return 0
else:
return self.mask.ptr
return self.mask.get_ptr(mode="write")

def set_base_mask(self, value):
"""
Expand Down Expand Up @@ -206,7 +199,7 @@ cdef class Column:
elif hasattr(value, "__cuda_array_interface__"):
if value.__cuda_array_interface__["typestr"] not in ("|i1", "|u1"):
if isinstance(value, Column):
value = value.data_array_view
value = value.data_array_view(mode="write")
value = cp.asarray(value).view('|u1')
mask = as_buffer(value)
if mask.size < required_num_bytes:
Expand Down Expand Up @@ -329,10 +322,10 @@ cdef class Column:

if col.base_data is None:
data = NULL
elif isinstance(col.base_data, SpillableBuffer):
data = <void*><uintptr_t>(col.base_data).get_ptr()
else:
data = <void*><uintptr_t>(col.base_data.ptr)
data = <void*><uintptr_t>(col.base_data.get_ptr(
mode="write")
)

cdef Column child_column
if col.base_children:
Expand All @@ -341,7 +334,9 @@ cdef class Column:

cdef libcudf_types.bitmask_type* mask
if self.nullable:
mask = <libcudf_types.bitmask_type*><uintptr_t>(self.base_mask_ptr)
mask = <libcudf_types.bitmask_type*><uintptr_t>(
self.base_mask.get_ptr(mode="write")
)
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
else:
mask = NULL

Expand Down Expand Up @@ -387,10 +382,8 @@ cdef class Column:

if col.base_data is None:
data = NULL
elif isinstance(col.base_data, SpillableBuffer):
data = <void*><uintptr_t>(col.base_data).get_ptr()
else:
data = <void*><uintptr_t>(col.base_data.ptr)
data = <void*><uintptr_t>(col.base_data.get_ptr(mode="read"))

cdef Column child_column
if col.base_children:
Expand All @@ -399,7 +392,9 @@ cdef class Column:

cdef libcudf_types.bitmask_type* mask
if self.nullable:
mask = <libcudf_types.bitmask_type*><uintptr_t>(self.base_mask_ptr)
mask = <libcudf_types.bitmask_type*><uintptr_t>(
self.base_mask.get_ptr(mode="read")
)
else:
mask = NULL

Expand Down Expand Up @@ -549,7 +544,8 @@ cdef class Column:
f"{data_owner} is spilled, which invalidates "
f"the exposed data_ptr ({hex(data_ptr)})"
)
data_owner.ptr # accessing the pointer marks it exposed.
# accessing the pointer marks it exposed permanently.
data_owner.mark_exposed()
else:
data = as_buffer(
rmm.DeviceBuffer(ptr=data_ptr, size=0)
Expand Down
4 changes: 2 additions & 2 deletions python/cudf/cudf/_lib/copying.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020-2022, NVIDIA CORPORATION.
# Copyright (c) 2020-2023, NVIDIA CORPORATION.

import pickle

Expand Down Expand Up @@ -765,7 +765,7 @@ cdef class _CPackedColumns:
gpu_data = Buffer.deserialize(header["data"], frames)

dbuf = DeviceBuffer(
ptr=gpu_data.ptr,
ptr=gpu_data.get_ptr(mode="write"),
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
size=gpu_data.nbytes
)

Expand Down
4 changes: 3 additions & 1 deletion python/cudf/cudf/_lib/transform.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ def mask_to_bools(object mask_buffer, size_type begin_bit, size_type end_bit):
if not isinstance(mask_buffer, cudf.core.buffer.Buffer):
raise TypeError("mask_buffer is not an instance of "
"cudf.core.buffer.Buffer")
cdef bitmask_type* bit_mask = <bitmask_type*><uintptr_t>(mask_buffer.ptr)
cdef bitmask_type* bit_mask = <bitmask_type*><uintptr_t>(
mask_buffer.get_ptr(mode="read")
)

cdef unique_ptr[column] result
with nogil:
Expand Down
69 changes: 61 additions & 8 deletions python/cudf/cudf/core/buffer/buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ def _getitem(self, offset: int, size: int) -> Buffer:
"""
return self._from_device_memory(
cuda_array_interface_wrapper(
ptr=self.ptr + offset, size=size, owner=self.owner
ptr=self.get_ptr(mode="read") + offset,
size=size,
owner=self.owner,
)
)

Expand All @@ -202,11 +204,6 @@ def nbytes(self) -> int:
"""Size of the buffer in bytes."""
return self._size

@property
def ptr(self) -> int:
"""Device pointer to the start of the buffer."""
return self._ptr

@property
def owner(self) -> Any:
"""Object owning the memory of the buffer."""
Expand All @@ -215,18 +212,74 @@ def owner(self) -> Any:
@property
def __cuda_array_interface__(self) -> Mapping:
"""Implementation of the CUDA Array Interface."""
return self._get_cuda_array_interface(readonly=False)

def _get_cuda_array_interface(self, readonly=False):
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
"""Helper function to create a CUDA Array Interface.

Parameters
----------
readonly : bool, default False
If True, returns a CUDA Array Interface with
readonly flag set to True.
If False, returns a CUDA Array Interface with
readonly flag set to False.

Returns
-------
dict
"""
return {
"data": (self.ptr, False),
"data": (
self.get_ptr(mode="read" if readonly else "write"),
readonly,
),
"shape": (self.size,),
"strides": None,
"typestr": "|u1",
"version": 0,
}

@property
def _readonly_proxy_cai_obj(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't think we should add extra property here given that it's used only in one other function (do we expect more uses for it?)

If not, I think we can just use cuda_array_interface_wrapper in those places instead.

Copy link
Contributor Author

@galipremsagar galipremsagar Jan 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is only being used in data_array_view & mask_array_view, and one more place for copy-on-write scenarios: https://github.com/rapidsai/cudf/pull/12556/files#diff-e23cdfbd2c7e4be6f1d08ea3cec7e075e42851641068fe318985775f6b9efd99R214.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK -- let's leave as is now, and we can take stock in the merge of copy-on-write.

"""
Returns a proxy object with a read-only CUDA Array Interface.
"""
return cuda_array_interface_wrapper(
ptr=self.get_ptr(mode="read"),
size=self.size,
owner=self,
readonly=True,
typestr="|u1",
version=0,
)

def get_ptr(self, *, mode) -> int:
"""Device pointer to the start of the buffer.

Parameters
----------
mode : str
Supported values are {"read", "write"}
galipremsagar marked this conversation as resolved.
Show resolved Hide resolved
If "write", the data pointed to may be modified
by the caller. If "read", the data pointed to
must not be modified by the caller.
Failure to fulfill this contract will cause
incorrect behavior.


See Also
--------
SpillableBuffer.get_ptr
"""
return self._ptr

def memoryview(self) -> memoryview:
"""Read-only access to the buffer through host memory."""
host_buf = host_memory_allocation(self.size)
rmm._lib.device_buffer.copy_ptr_to_host(self.ptr, host_buf)
rmm._lib.device_buffer.copy_ptr_to_host(
self.get_ptr(mode="read"), host_buf
)
return memoryview(host_buf).toreadonly()

def serialize(self) -> Tuple[dict, list]:
Expand Down
32 changes: 10 additions & 22 deletions python/cudf/cudf/core/buffer/spillable_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def __len__(self):

def __getitem__(self, i):
if i == 0:
return self._buf.ptr
return self._buf.get_ptr(mode="write")
elif i == 1:
return False
raise IndexError("tuple index out of range")
Expand Down Expand Up @@ -359,7 +359,7 @@ def spill_lock(self, spill_lock: SpillLock) -> None:
self.spill(target="gpu")
self._spill_locks.add(spill_lock)

def get_ptr(self) -> int:
def get_ptr(self, *, mode) -> int:
"""Get a device pointer to the memory of the buffer.

If this is called within an `acquire_spill_lock` context,
Expand All @@ -369,8 +369,8 @@ def get_ptr(self) -> int:
If this is *not* called within a `acquire_spill_lock` context,
this buffer is marked as unspillable permanently.

Return
------
Returns
-------
int
The device pointer as an integer
"""
Expand Down Expand Up @@ -409,18 +409,6 @@ def memory_info(self) -> Tuple[int, int, str]:
).__array_interface__["data"][0]
return (ptr, self.nbytes, self._ptr_desc["type"])

@property
def ptr(self) -> int:
"""Access the memory directly

Notice, this will mark the buffer as "exposed" and make
it unspillable permanently.

Consider using `.get_ptr()` instead.
"""
self.mark_exposed()
return self._ptr

@property
def owner(self) -> Any:
return self._owner
Expand Down Expand Up @@ -559,12 +547,12 @@ def __init__(self, base: SpillableBuffer, offset: int, size: int) -> None:
self._owner = base
self.lock = base.lock

@property
def ptr(self) -> int:
return self._base.ptr + self._offset

def get_ptr(self) -> int:
return self._base.get_ptr() + self._offset
def get_ptr(self, *, mode) -> int:
"""
A passthrough method to `SpillableBuffer.get_ptr`
with factoring in the `offset`.
"""
return self._base.get_ptr(mode=mode) + self._offset

def _getitem(self, offset: int, size: int) -> Buffer:
return SpillableBufferSlice(
Expand Down
7 changes: 4 additions & 3 deletions python/cudf/cudf/core/column/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,9 +956,10 @@ def clip(self, lo: ScalarLike, hi: ScalarLike) -> "column.ColumnBase":
self.astype(self.categories.dtype).clip(lo, hi).astype(self.dtype)
)

@property
def data_array_view(self) -> cuda.devicearray.DeviceNDArray:
return self.codes.data_array_view
def data_array_view(
self, *, mode="write"
) -> cuda.devicearray.DeviceNDArray:
return self.codes.data_array_view(mode=mode)

def unique(self, preserve_order=False) -> CategoricalColumn:
codes = self.as_numerical.unique(preserve_order=preserve_order)
Expand Down
Loading