-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
TASK: Potential fixup/followups for the allocator changes #20193
Comments
An example might be the best way. Do we know of code in the wild that assigns |
Ah, I had done a github search: https://github.com/Seynen/egfrd/blob/e0ed0916797fe105f90e38e83b6375d340f94caf/peer/numpy/ndarray_converters.hpp#L60 (I guess it is good that the top 4000 PyPI packages are clean, means that this seems really only used in a fiew small "home grown" libs.) |
It'd be nice to have Python APIs for the allocator, but I doubt it can be done in time for 1.22? |
Whoops. Searching for |
Ah, that explains why my search felt like it only turned up fringe examples this time ;).
@leofang I am not sure what you are looking for? Is there a use that is not just as well addressed by an example in the documentation, or, maybe even better, a dedicated small repository (which could live in the NumPy org)? |
@seberg I need a Python API to switch the allocator in a Python session, as we likely cannot afford to make NumPy a build time dependency. Something similar to what @eliaskoromilas did with his numpy-allocator would do: >>> import numpy as np
>>>
>>> # let downstream libraries/users worry about how to prepare the necessary interface objects
>>> my_allocator = np.create_allocator(
>>> malloc=...,
>>> calloc=...,
>>> realloc=...,
>>> free=...)
>>> with my_allocator: # change the allocator locally
... a = np.array([1, 2, 3])
>>>
>>> # or, change the allocator globally
>>> curr_allocator = np.get_allocator()
>>> np.set_allocator(my_allocator)
>>> b = np.array([4, 5, 6]) If it has to be in a separate repo, we might be able to live with it, but the preference is to have it in NumPy because it's really just a small interface. |
@seberg Another orthogonal thing I've been pondering is how to make the allocator interact with the DLPack support (#19083). Ideally, for example, if I set the allocator to CUDA pinned memory or managed memory, when exporting through DLPack it should be able to set the corresponding |
Uffffff... I personally don't really like encoding arbitrary stuff in names :(. Maybe we do need some "reserved" space to allow putting in feature-flags/version or just some function pointer slots (easy enough)? If you are already proposing to extend the API/ABI. Or even just bite the bullet and consider a I don't like getting hands dirty in API discussion, but if this is impeding anyway better now than after release. EDIT: Of course, you could write your own dlpack exporter already, but that cannot not work implicitly... |
Just to add to Leo's point about hooking allocators into Python, there are a few use cases that stick out to me:
There are probably others I'm overlooking, but these come up a fair bit. Admittedly there is some C code in all of these, but it can be handy to switch to a different allocator (especially in particular contexts). |
cc @pentschev @madsbk @quasiben (for awareness) |
My NumPy Allocator API actually fulfills all these requirements through: a) The allocator metaclass (
|
This is an example of how an aligned allocator can be written in Python, utilizing the NumPy Allocator API.
This is an example of how we (InAccel) use this API to integrate our shared memory architecture with NumPy. As you will notice, in this case we just open |
@eliaskoromilas just wondering if you have a thought on how/whether we should have some versioning (e.g. a version number stored in the struct)? |
Maybe there could be a method for getting that version? Agree having a versioned API is important (this API may well change in the future) |
#17582 introduced the following API:
with A I think it's important to mark this as Stable ABI (promising backwards compatibility, deprecation period, etc.). Fortunately, there is a way to make this happen through the use of capsule names. I've already proposed this in a comment, but let me explain here in more detail. Let's assume that in the future there is a need to introduce a typedef struct {
void *ctx;
void* (*malloc) (void *ctx, size_t size);
void* (*calloc) (void *ctx, size_t nelem, size_t elsize);
void* (*realloc) (void *ctx, void *ptr, size_t new_size);
void* (*memcpy) (void *ctx, void *dest, void *src, size_t n); /* this is the new func */
void (*free) (void *ctx, void *ptr, size_t size);
} PyDataMemAllocator2;
typedef struct {
char name[128]; /* multiple of 64 to keep the struct aligned */
PyDataMemAllocator2 allocator;
} PyDataMem_Handler2; In order to allow both the old and the new struct to co-exist, we need to be able to distinguish them. The capsule names come in handy here and can play the role of the version identifier. Since capsules are the way to pass handlers around, we can just use a different capsule name (e.g. Now, Benefits:
>>> my_allocator.handler()
<capsule object "mem_handler2" at 0x7f1326654c90>
# let's also assume that the library wants to support both versions,
# but also set the newest supported as default one (my_allocator)
# my_allocator_v1 contains a "mem_handler" capsule
# my_allocator_v2 contains a "mem_handler2" capsule
if version.parse(numpy.__version__) < version.parse('X.Y.Z'):
my_allocator = my_allocator_v1
else:
my_allocator = my_allocator_v2
/* handler here is a capsule provided by the user */
if (PyCapsule_IsValid(handler, "mem_handler") {
PyDataMem_Handler *mem_handler = (PyDataMem_Handler *) PyCapsule_GetPointer(handler, "mem_handler");
/* allocator actions, for memcpy use the default function */
} else if (PyCapsule_IsValid(handler, "mem_handler2") {
PyDataMem_Handler2 *mem_handler2 = (PyDataMem_Handler2 *) PyCapsule_GetPointer(handler, "mem_handler2");
/* allocator actions */
} else {
/* unknown version */
}
/*
or to avoid running these checks multiple times,
only PyDataMem_SetHandler could perform them,
(if needed) transforming the handler to a v2 handler
(in this case, setting a default value for the missing `memcpy` field),
and then storing the handler in the context-local var.
*/ To summarize, the capsule API that the ENH introduced can support versioning through capsule names. In my opinion it would be redundant to have a version field in the handler structs, since there is already a way to "label" the handler capsules. |
Hmm, means we need to do |
IIRC @seberg isn't a fan of using the capsule name to contain operational information 😄 But I can live with that. One nitpick on @eliaskoromilas's example is that |
Yeah, but I can be convinced. I don't like that And I suppose... we could even "deprecate" versions effectively, by making a very quick check in the EDIT: And yes, I assume any newer version is ABI compatible with all older ones. |
I'm not a huge fan either. In the capsule name solution there is no compatibility between the different handler structs. In other words, we don't have "versions" but "identifiers".
Intentionally I messed with the existing fields to note this functionality. If we choose to go with this solution, "mem_handler2" capsules won't be supported by NumPy versions that only know how to handle "mem_handler" capsules, etc. User libraries need to make sure that their allocator is compatible with the installed ΝumPy version.
There is an alternate solution, of course, that focuses exactly on that. In this solution:
This means that user libraries may set 1.22.0 as their minimum required NumPy version, but still update their handler structs to match the latest NumPy release.
I agree. TLDR; If we think that future versions (most probably) are going to be extensions to the current struct (e.g. extra functions), a |
Matti added PR ( #20343 ) to include versioning. Would be great if others here took a look 🙂 |
Is it OK to push this off? We now have versioning, and I don't think all the tasks here will be finished in time. |
Yeah, should be good now. |
Closing, I think we have resolved enough of these problems, please reopen or open a new issue. |
by any chance can you point us to the documentation for the new api? |
The documentation added is https://numpy.org/devdocs/reference/c-api/data_memory.html, and there is also the NEP https://numpy.org/neps/nep-0049.html |
@jakirkham did you want to help maintain? I noticed you were active in the discussion referenced below. @eliaskoromilas do you have any interest in helping maintain conda-forge packages? numpy/numpy#20193 (comment)
These is a list of potential followups for gh-17582
PyCapsule_GetPointer
technically can set an error and may need the GIL? I am not worried enough about this to delay, but the functions look like they are meant to be callable without the GIL (I doubt we use it, and I doubt it really can error/need the GIL, but maybe we can make it a bit nicer anyway).VOID_compare
)Anyone else have anything that might make sense to look into?
The text was updated successfully, but these errors were encountered: