Repository navigation
ENH: (almost) full structured dtype support - #9650
Conversation
c10ee65 to
cc1fa6b
Compare
|
|
||
| // Hack to replace std::false_type and std::true_type | ||
| template<bool Value> | ||
| struct my_integral_constant { |
There was a problem hiding this comment.
FYI std::integral_constant is available through cupy/workaround.h
| buf.format = dtype_format[dtype] | ||
|
|
||
|
|
||
| def make_gpu_aligned_dtype( |
There was a problem hiding this comment.
Two questions:
- How does this relate to GPU, other than that we ensure alignment? I assume
make_aligned_dtypeis fine too? - How about moving this to
cuda.core? We need this function for libraries lower than CuPy in the stack (Addcuda.core.utils.get_aligned_dtypeNVIDIA/cuda-python#734).
There was a problem hiding this comment.
Moving sounds good. CPU alignment is smaller than GPU one often. While GPU is (typically?) itemsize aligned. An example that always works is complex numbers, which CUDA uses the full itemsize alignment for copies, I think, while for the CPU the compiler will only require the floats alignment.
FWIW, I am not sure that encoding a custom larger alignment is super useful. It seems mainly needed for nested structs which dould maybe be solve directly by passing field alignment constraints offset (that are only used to compute offsets).
There was a problem hiding this comment.
Yeah for 1 I mainly wanted to clarify for the function name. It seems calling it make_aligned_dtype() (removing "gpu") is fine. There is nothing GPU-specific.
For 2, my suggestion is
- move it to cuda.core (I can take over this if you like, or if you have time to check it there it's also fine 🙂 )
- try-import it in CuPy, if cuda.core is new enough to offer it, use it
- otherwise, use a private copy vendored in CuPy (and we can remove it in 1-2 major releases)
There was a problem hiding this comment.
Sorry yeah, I'll work on moving it. Thought I may have an idea about how to signal alignment, but likely this is pretty good (and we only embed if users asked for somdthing special).
(I suppose we cannot move the C++ type construction, might befun one day, but not a priority.)
cc1fa6b to
9d9fbd7
Compare
|
Ok, this grew to 1k lines, so maybe should split it up. But while it still needs some polishing, I think this should be good for review. The basic changes are:
I am sure there will be places with some holes (if just due to an The main questions here may well be around |
|
This pull request is now in conflicts. Could you fix it @seberg? 🙏 |
|
/test mini |
| # is the case, this always passes. | ||
| # with pytest.raises(ValueError): | ||
| # func(ByteSwapped(base)) |
There was a problem hiding this comment.
Yeah, I hadn't fixed that yet here. Let's see, it might not be much more than a 2 line change to make it work well enough.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
OK, I added support for this, but with slightly extra logic to work with the necessary padding more reliably (NumPy doesn't do this well, but also doesn't pad by default, I need to think about what API to add in NumPy to improve this).
The asarray() path still doesn't raise, because CAI doesn't strictly check the dtype compatibility right now. (tried enabling it, but I forgot about datetime)
| class TestFieldCasting: | ||
| @testing.numpy_cupy_array_equal() | ||
| def test_casting_simple(self, xp): | ||
| a = xp.array([(1, 2.), (3, 4.)], dtype="i8,f8") |
There was a problem hiding this comment.
Q: if we use "i4,f8" instead, will we get to test the padding behavior?
There was a problem hiding this comment.
I am not auto-padding for xp.array() right now, so it would just fail on this test. There are some tests below (mainly using complex types, because they are always float aligned in NumPy/C and itemsize aligned in CUDA) that test the padding paths.
There was a problem hiding this comment.
Ah. What if it's np.dtype([('f1', np.int32), ('f2', np.float64)], align=True) instead? Would it also fail?
There was a problem hiding this comment.
In principle yes, float64 CPU alignment can be 4 bytes. That said, it may be that it isn't on any hardware we support in practice (I am not sure it is on any 64bit platforms).
| # When the data can be defined as a normal struct, then | ||
| # we allow access via `.data`. | ||
| # TODO/NOTE(seberg): Is this even desired?! | ||
| kernel = cupy.ElementwiseKernel( | ||
| 'T in', 'T out', 'out.data.f1 = -in.data.f0;', |
There was a problem hiding this comment.
Q: How do we determine the field names on the device side?
There was a problem hiding this comment.
They come directly from the dtype field names, if the dtype can be represented by a C-struct, then I generate the typedef struct {} and use it for .data to make them available.
We could allow something like out.get<field> but it seems C++20 or so and we don't actually need field names for our kernels, it is only interesting for custom kernels.
Commenting also because we should decide whether we want this at all. We could just delete the .data attribute (as we don't use it).
So this is a convenience feature for custom kernels avoiding careful dtype checking + reinterpret casts in the kernel.
There was a problem hiding this comment.
We could just delete the .data attribute (as we don't use it).
So instead of
'out.data.f1 = -in.data.f0;'we write
'out.f1 = -in.f0;'
? If so, it sounds reasonable to me.
This C++ feature seems very internal-ish and I expect only the team (+ contributors) would be using it within the codebase for now, so I am not too worried about it. I am aware some users reaching into CArray stuff, but it's really undocumented and offers no guarantee.
There was a problem hiding this comment.
Ah, right now it is out.data.f1, I could maybe subclass the structured dtype to achieve writing out.f1. I.e. .data is the normal struct. I had a subclassing solution initially, but then thought if we don't need it much, subclassing may add a bit much complexity.
The alternative would be out.get<f1>() = -in.get<f0>() if C++20 is available.
would be using it within the codebase for now, so I am not too worried about it.
So maybe it is just a bit "experimental" which seems fine. But not sure if we are likely to need it internally (maybe in cupyx).
There was a problem hiding this comment.
yeah let's keep it c++17 compatible for now
| return alignment | ||
|
|
||
|
|
||
| def _build_struct_typename(dtype, type_decls): |
There was a problem hiding this comment.
Q: Should we use functool.cache or the internal _utils.cache here?
There was a problem hiding this comment.
Unfortunately, caching needs to use the identity, so I didn't add it yet. But yeah, maybe I should (it needs to be a custom caching, unfortunately, which may mean cache misses are pretty common), since this is going to be slowish.
|
(Let me merge with main to catch up with recent CI cache update) |
|
/test mini |
|
/test mini |
|
This pull request is now in conflicts. Could you fix it @seberg? 🙏 |
The full thing is too suggestive IMO. E.g. recurse=... would have to actually recurse in that case, but currently doesn't.
|
Erik had given me some claude red-team tasks. I applied most of them (the largest diff being that it suggested to re-organize the header to allow more than 250 fields; not really vital but sounds good). A few of the suggestion seemed wrong, or way to niche to do something about (biggest niche one is support for /test mini |
eriknw
left a comment
There was a problem hiding this comment.
LGTM, primarily from a user perspective.
Thanks for applying the appropriate updates from my Claude's suggestions. I agree with your judgement about which to ignore.
The one other comment I left is more about technical compliance with CAI v3 that I think we should be aware of (and these comments leave a paper trail for). Probably fine as is though, so I'm approving the PR 👍
|
This causes a full rebuild, so unfortunately needs at least one more iteration. /test mini |
|
(Was fine before except one test not finishing yet, so skipping for the doc change.) /test skip |
|
Let me put this in then, thanks for the reviews! I think The doc addition is rather trivial and renders fine but we can follow-up of course. |
|
Ah, silly me, I can't actually merge, need someone to do a final approve as the one got stale :). |
leofang
left a comment
There was a problem hiding this comment.
A few observations from reading through the PR.
| template<size_t Size> | ||
| class UnstructuredVoid { | ||
| public: | ||
| char data[Size]; |
There was a problem hiding this comment.
UnstructuredVoid<Size> inherits the alignment of char (i.e. 1). That means CArray<UnstructuredVoid<8>> will also get alignment 1, so device accesses to multi-byte voids won't be naturally aligned. Consider adding alignas(...) — e.g. the largest power-of-two ≤ min(Size, 16) — to match the alignment CUDA would give an equivalent-sized trivial type.
There was a problem hiding this comment.
It would be nice to use a higher alignment when possible, but in principle there is nothing stopping you from having a ?,V4 dtype or so, where the V4 is random data that has no alignment guarantees.
Maybe we'll have to either allow users to override it explicitly, or use actual runtime alignment for the kernel building (I like the latter if it works).
|
/test mini |
1 similar comment
|
/test mini |
|
/test mini,windows |
|
/test cuda120,windows |
ENH: (almost) full structured dtype support
This is quite far along, but needs cleaning up (and is based on the other PR). So it is a WIP only, just for visibility.Based on gh-9494, this adds the missing support for structured dtypes, that is, allowing casts and multi-field access/assignments.
Major open points:
dtypemuch more. This is OK, but we need to refactor things to make use of the NumPy C-API to reduce overheads again.alignof()through the JIT to implement this? (plus memoization).It needs tests of course!Smaller points:
To implement this I added a
resolve_dtypes()function. This is a concept that NumPy also has, although I made it a bit more raw here. (The divergence has two reasons. First casts as a ufunc don't work well without it, although a cast could maybe be a simpleElementwiseKernel. But also the functionality used in NumPy to actual implement it nicely isn't public, oops.)Addresses gh-2031 (but one could argue that subarray support is missing)