-
Notifications
You must be signed in to change notification settings - Fork 19
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
A datatype representing an allocation #505
Comments
I think comparing only against the stable APIs is misguided. The first thing we're going to ask is "what about the
|
We discussed this during today's libs-api meeting and we're inclined to reject this on the ground that that this doesn't implement anything necessary to use the allocator APIs and can exist as a safer abstraction in a crate. One of the concerns was that most containers types, especially collections, but also Though we were also unsure which properties exactly you want from an allocation type. E.g. would it be be ok to make the alignment a generic const argument instead of a runtime value?
This does not seem obvious, some open questions around the allocator API - such as the Storage proposal - would change it in deep ways that would also mean that We agree that it's unfortunate that not much progress has been made on the Allocator API, but adding even more unstable API surface is unlikely to improve that situation. The difficulty here is that the exploration of the flexibility-complexity frontier requires a lot of work and is littered with dead proposals, which can be quite demotivating for the participants. At the same time the libs-team has quite limited review bandwidth for complex proposals. |
I'm mainly interested in a stable way of changing the alignment of an allocation that avoids memcopying everything over to new memory. The above proposal would allow one to freely change both the requested
A bit of context on the why. The popular neural net libraries store the weights/tensor elements as a series of bytes and a datatype descriptor on disk. When deserializing, this datatype is thus not statically known. Having one code path per supported datatype (matching on the dynamic dtype from the file and forwarding to a monomorphized version of each method) leads to a lot of code duplication. Most of the time, the data is not even on the CPU in the first place, so no need to actually statically know it. In any case, a second method of constructing a tensor would be the user passing in a So, how to actually support the above? The main pain point is connected to the deserialization protocol. In this example we would like to take the allocation of a |
Why does a user want an owned
I implore you to try out the unstable struct Overaligned<A, const ALIGN: usize>(A);
impl<A: Allocator, const ALIGN: usize> Allocator for Overaligned<A, ALIGN> {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
self.0.allocate(layout.align_to(ALIGN).map_err(|_| AllocError)?)
}
...
}
// Then
#[derive(serve::Deserialize)]
struct YourData {
vec: Vec<u8, Overaligned<Global>>
} I don't see why we'd need a separate type for |
For any case where the alignment is statically known, rather than dynamically, there's Maybe a smaller thing that could be done here, if people want to reuse those allocations, would be to have something like the inverse to https://doc.rust-lang.org/std/boxed/struct.Box.html#method.write for separating a value and the allocation again, for easy re-use of that allocation later?
No, because there's nowhere for the Box to store that alignment, and thus it wouldn't be able to pass the correct alignment in But overall, the mentioned direction of "close because allocation stored with its |
Why not. I don't think it's exotic to expect to take ownership of the tensor data if a user knows the datatype that their tensor contains, yet the framework needs to be more versatile around it.
The docs are not 100% clear on this, but very close. You can though allocate the storage of a So what I'm saying is that I'm pretty sure you can legally:
The interesting step is in (2) which, as I said, can currently only be achieved unstably and in a bit of a roundabout way. The allocator might (and for my use-cases most likely should) be able to avoid a memcopy by making a reasonable effort to do so. If the passed-in pointer already is aligned to whatever alignment
The alignment is (potentially) different per datatype and "leaking" that statically in the type information á la |
It's not customary in Rust to pass around raw data by value. It's idiomatic to pass through the least permissive type possible, and there is even a clippy lint
Just use the maximum foreseeable alignment of any datatype in use. That's what you're hoping the global allocator is doing anyways.
No, because size must be a multiple of alignment. Each element of a hypothetical
Not if you wrap it in your own type, which you probably should anyways for convenience. Or you could just provide slice references with the requested element type and everything. |
Proposal
Problem statement
The allocator API is currently unstable. Going through the global
{re}alloc
functions is possible, but not as general. Still, all these methods areunsafe
and require careful use. Most of the complexity comes from providing a consistent layout to the different methods for the associated pointer representing the allocation, checking for nullptr and not causing accidental double-free or leakage.Further, while there is a way to manage an allocation of typed memory through
Box<T>
, there is no equivalent for the underlying untyped memory.While the Allocator API is understandably still unstable and the exact contract with implementations is a bit of a moving target that needs to get stabilized, from a user persective only the unsatisfying
alloc
,realloc
anddealloc
methods can be used, which will most likely get deprecated (to my understanding, in favor of the Allocator API) either way in the forseeable future. The biggest pain points on stable are that:alloc::allocate
realloc
can't change the alignment of the allocationCopy
and express no ownership, making it necessary to carefully argue about duplication and leaking in panic pathsrealloc_zeroed
Most of these are solvable on unstable, though
allocator_api
is and was a moving target for the past 8 years with no particular stabilization date in sight. Additionally, the API is stillunsafe
. The proposed API is safe and fairly small, trading that for more explicitly tracked data in the addedAllocation
type.Motivating examples or use cases
This PR manually implements buffer management that can't be done with a
Vec
due to working with heterogeneous datatypes with potentially different alignment. Similar datastructures areBoxBytes
inbytemuck
(which has conversion from aBox
/Vec
and deallocates but does not support resizing) and multiple questions on stackoverflow et al about over-aligned vectors.Solution sketch
A new datatype
Allocation
inalloc::alloc
that tracks all necessary information to safely use the allocation interface. While this often has a small overhead of some bits of information that is available externally (such as the datatype's alignment and capacity in the case ofVec
), the unsafety requirements can be tracked and checked making this worth it.Alternatives
All downsides mentioned in the motivation can in theory be worked around with existing API, but an unstable-enabled library like the
std
could provide a better and more performant interface than a crate working against stable rust ever could by internally using the yet unstable allocator API. This ACP could be stabilized without commiting to the exact details of the underlying Allocator API.Very little of the above would be
unsafe
API, the alternative for consumers ofstd
at the moment is writing a lot of unsafe code (which is possible, albeit probably error prone).Links and related work
MVP implementation of some of the above interface
The text was updated successfully, but these errors were encountered: