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

Exposure Tracked Buffer (first step towards unifying copy-on-write and spilling) #13307

Merged
merged 32 commits into from
Jul 3, 2023

Conversation

madsbk
Copy link
Member

@madsbk madsbk commented May 8, 2023

The first step towards unifying copy-on-write and spillable buffers.

This PR re-implement copy-on-write by introducing a ExposureTrackedBuffer and BufferSlice. The idea is that when copy-on-write (and in a follow-up PR later, when spill) is enabled, we use BufferSlice throughout cudf.
BufferSlice is a view of a ExposureTrackedBuffer that implements copy-on-write semantics by tracking the number of BufferSlice that points to the same ExposureTrackedBuffer.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

cc. @shwina, @vyasr, @galipremsagar, @wence-

@madsbk madsbk added 2 - In Progress Currently a work in progress improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels May 8, 2023
@github-actions github-actions bot added the Python Affects Python cuDF API. label May 8, 2023
@madsbk madsbk force-pushed the tenable_buffer branch from 11de74c to c2269eb Compare May 9, 2023 09:23
@madsbk madsbk force-pushed the tenable_buffer branch from e56a3b6 to b5027ff Compare May 9, 2023 15:15
@madsbk madsbk marked this pull request as ready for review May 9, 2023 16:07
@madsbk madsbk requested a review from a team as a code owner May 9, 2023 16:07
@madsbk madsbk requested review from wence- and galipremsagar May 9, 2023 16:07
@madsbk madsbk removed the 2 - In Progress Currently a work in progress label May 9, 2023
Copy link
Contributor

@wence- wence- left a comment

Choose a reason for hiding this comment

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

A first pass, haven't fully managed to go through all the logic yet

docs/cudf/source/developer_guide/library_design.md Outdated Show resolved Hide resolved
docs/cudf/source/developer_guide/library_design.md Outdated Show resolved Hide resolved

`TenableBuffer` is a subclass of the regular `Buffer` that tracks its "expose" status of its underlying memory. We say that the buffer has been exposed if the device pointer (integer or void*) has been accessed outside of cudf, in which case we have no control over knowing if the data is being modified by a third-party. Additionally, `TenableBuffer` also maintains [weak references](https://docs.python.org/3/library/weakref.html) to every existing `BufferSlice` that points to its underlying memory.

`BufferSlice` is a subclass of `TenableBuffer` that represents a _slice_ of the memory underlying a tenable buffer.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is subclassing the right thing? (I haven't yet read the implementation). It seems like every BufferSlice has-a TenableBuffer, but that doesn't necessarily imply an is-a relationship.

If it really is a subclass, do we actually need both classes, or can there just be TenableBuffer objects that either own data, or are views of existing data.

Copy link
Member Author

Choose a reason for hiding this comment

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

In a follow-up PR, SpillableBuffer will inherent from TenableBuffer and BufferSlice._base will point to SpillableBuffer.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe it is better if BufferSlice inherent from Buffer ?

Copy link
Contributor

Choose a reason for hiding this comment

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

It kind of seems like what we want is ad-hoc, rather than subtype, polymorphism (BufferSlices are not Liskov-substitutable for TenableBuffers everywhere [in particular when constructing new buffer slices]). In which case, is this the time to introduce a buffer Protocol?

Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose this is similar to the column vs. column_view idea in libcudf. A Buffer (TenableBuffer, SpillableBuffer) is the concrete owning object, and then the BufferSlice is a non-owning view? Or how does the ownership work? Does a BufferSlice own the Buffer it slices (or share ownership with multiple slices?)?

Copy link
Member Author

Choose a reason for hiding this comment

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

A buffer Protocol might make sense, but then I think we should do it in a follow up PR

Copy link
Member Author

@madsbk madsbk May 11, 2023

Choose a reason for hiding this comment

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

Alternatively, we could have Buffer use BufferSlice so that BufferSlice is the only buffer object used in the rest of cudf.
In any case, I think we should wait until SpillableBuffer also uses BufferSlice and TenableBuffer so we have a better picture of the exact use cases.

docs/cudf/source/developer_guide/library_design.md Outdated Show resolved Hide resolved
python/cudf/cudf/_lib/column.pyx Show resolved Hide resolved
python/cudf/cudf/core/buffer/tenable_buffer.py Outdated Show resolved Hide resolved
python/cudf/cudf/core/buffer/tenable_buffer.py Outdated Show resolved Hide resolved
python/cudf/cudf/core/buffer/tenable_buffer.py Outdated Show resolved Hide resolved
python/cudf/cudf/core/buffer/tenable_buffer.py Outdated Show resolved Hide resolved
python/cudf/cudf/core/buffer/tenable_buffer.py Outdated Show resolved Hide resolved
@madsbk madsbk requested a review from wence- May 15, 2023 09:59
@madsbk madsbk changed the base branch from branch-23.06 to branch-23.08 June 19, 2023 07:17
@madsbk madsbk changed the title Tenable Buffer (first step towards unifying copy-on-write and spilling) Exposure Tracked Buffer (first step towards unifying copy-on-write and spilling) Jun 19, 2023
Copy link
Contributor

@vyasr vyasr left a comment

Choose a reason for hiding this comment

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

A few small change requests, but in general this looks like the right direction. Thanks @madsbk! I can see from this PR how you would integrate spilling, but also appreciate you splitting up the work this way to make the changes incrementally.

python/cudf/cudf/_lib/column.pyx Show resolved Hide resolved
python/cudf/cudf/core/buffer/exposure_tracked_buffer.py Outdated Show resolved Hide resolved
python/cudf/cudf/core/buffer/exposure_tracked_buffer.py Outdated Show resolved Hide resolved
if exposed:
raise ValueError("cannot created exposed host memory")
return cast(
BufferSlice, ExposureTrackedBuffer._from_host_memory(data)[:]
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the eventual plan for Buffer.getitem to return a BufferSlice? If not, it might be cleaner to override the method in ExposureTrackedBuffer. I know the whole point of the _getitem/__getitem__ split is to help share some functionality, but the typing confusion here indicates that there are potentially incorrect types that could result from that approach (obviously we can coerce the code into behaving correctly, but it makes it much harder to write intrinsically type-safe code if the type annotations aren't sufficiently valid).

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, this is a very valid point!

I think the clean design is to make cudf always work on BufferSlice even when COW and spilling is disabled. Then we get a clean class hierarchy:

  • COW & Spilling disable:
    BufferSlice -> Buffer
  • COW enabled:
    BufferSlice -> ExposureTrackedBuffer -> Buffer
  • Spilling enabled (when is has been unified with COW in a follow-up PR):
    BufferSlice -> SpillableBuffer -> ExposureTrackedBuffer -> Buffer

The downside is that this approach is a bit more intrusive in the default case where COW and spilling is disabled. I think it is worth it but what do you guys think?

cc. @wence- @shwina

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that it is probably worth it. I would stage that as work to be done after the COW and spilling unification is complete and we can reevaluate in the context of a cleaner architecture.

python/cudf/cudf/tests/test_copying.py Show resolved Hide resolved
Co-authored-by: Vyas Ramasubramani <[email protected]>
Copy link
Contributor

@wence- wence- left a comment

Choose a reason for hiding this comment

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

Very minor typo fix, thanks!

docs/cudf/source/developer_guide/library_design.md Outdated Show resolved Hide resolved
"shape": (self.size,),
"strides": None,
"typestr": "|u1",
"version": 0,
}

def get_ptr(self, *, mode) -> int:
def get_ptr(self, *, mode: Literal["read", "write"]) -> int:
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to change this, but just to note that Literal doesn't get handled very well by type-checkers if the argument comes from a variable, rather than a literal value (unless the variable is marked with : Final). Since they don't do dataflow analysis

https://mypy-play.net/?mypy=latest&python=3.11&gist=6541ee12e80daeb4b0837563d98dc442

@madsbk
Copy link
Member Author

madsbk commented Jul 3, 2023

/merge

@rapids-bot rapids-bot bot merged commit d078cff into rapidsai:branch-23.08 Jul 3, 2023
@madsbk
Copy link
Member Author

madsbk commented Jul 3, 2023

Thanks all for the reviews

@madsbk madsbk deleted the tenable_buffer branch July 31, 2023 06:22
rapids-bot bot pushed a commit that referenced this pull request Jan 15, 2024
…13801)

This PR de-couples buffer slices/views from owning buffers. As it is now, all buffer classes (`ExposureTrackedBuffer`, `BufferSlice`, `SpillableBuffer`, `SpillableBufferSlice`) inherent from `Buffer`, however they are not Liskov substitutable as pointed by @wence- and @vyasr ([here](#13307 (comment)) and [here](#13307 (comment))). 

To fix this, we now have a `Buffer` and a `BufferOwner` class. We still use the `Buffer` throughout cuDF but it now points to an `BufferOwner`.  

We have the following class hierarchy: 
``` 
ExposureTrackedBufferOwner -> BufferOwner 
SpillableBufferOwner -> BufferOwner 
ExposureTrackedBuffer -> Buffer 
SpillableBuffer -> Buffer 
``` 

With the following relationship: 
``` 
Buffer -> BufferOwner 
ExposureTrackedBuffer -> ExposureTrackedBufferOwner 
SpillableBuffer -> SpillableBufferOwner 
``` 

#### Unify COW and Spilling 

In a follow-up PR, the spilling buffer classes will inherent from the exposure tracked buffer classes so we get the following hierarchy: 
``` 
SpillableBufferOwner -> ExposureTrackedBufferOwner -> BufferOwner 
SpillableBuffer -> ExposureTrackedBuffer -> Buffer 
```

Authors:
  - Mads R. B. Kristensen (https://github.com/madsbk)

Approvers:
  - Lawrence Mitchell (https://github.com/wence-)
  - Vyas Ramasubramani (https://github.com/vyasr)

URL: #13801
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
improvement Improvement / enhancement to an existing function non-breaking Non-breaking change Python Affects Python cuDF API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants