Redo add_transform! for GPU backed Tensors to be much, much faster - #509
Redo add_transform! for GPU backed Tensors to be much, much faster #509kshyatt wants to merge 3 commits into
add_transform! for GPU backed Tensors to be much, much faster #509Conversation
|
I'm also running the GPU branch of PEPSKit.jl against this to see how much/if it helps |
|
Well, the short story for now is it doesn't help much because nearly all the time in our PEPSKit runs is spent on the CPU (GPU utilization is quite low). I'll look into that separately but I still think this is a nice bit of progress :) |
Codecov Report✅ All modified and coverable lines are covered by tests.
🚀 New features to boost your workflow:
|
|
OK I've got one more thing to push here to make it play nicely with PEPSKit (cache-related) but most of this is ready to go I think |
32a46c9 to
ec253b6
Compare
|
OK!!! I modified stuff a bit for the |
lkdvos
left a comment
There was a problem hiding this comment.
have only looked at the abelian part for now, looks really cool! I didn't really realize that indeed we can just spawn a thread per element. I am definitely wondering how this affects the case where the tensors are a bit larger and the number of blocks not so severe, for example what would effectively be the case of more MPS-like contractions, for which it might be reasonable to try and generalize/run the benchmarks that are in this repository on GPU as well to get a sense about overall performance implications?
Given that every element is read/written exactly once, it could also be cool to just express these numbers in terms of actual read/write bandwidth percentages, for which 🤖 might be able to help? Not that I'm expecting or requiring any huge results, but I'd be very interested to know how much is left on the table
|
|
||
| # largest `i` with `offsets[i] <= w`. This corresponds to the | ||
| # block which this kernel thread will work on. | ||
| @inline function _searchblock(offsets, w) |
There was a problem hiding this comment.
Is this Base.searchsortedlast or Base.searchsortedfirst?
There was a problem hiding this comment.
edit: I see now that this is used inside the kernel, which I assume is why that has to be like this
There was a problem hiding this comment.
Should I add a comment about this to make it clearer?
|
|
||
| # Cartesian coordinates of the `w`-th (0-based) entry of a dense subblock of shape `sz`. | ||
| # Computed once per thread and then reused for every strided view of that subblock. | ||
| # This avoids `StridedView` redoing these divisions on every single element access. |
There was a problem hiding this comment.
This is slightly confusing to me, isn't this still computed in each kernel call below (line 371)? Keep in mind I might be completely misreading kernels here.
There was a problem hiding this comment.
It's computed just once per kernel thread, though, which becomes more important for the generic case
Yeah, it's a really good question. I focused on the cases I looked at in the linked issue, but if we have some others I can run I will certainly do that to get a clearer picture. |
I did do a bit of testing with these kernels on CPU arrays and there's no real benefit there, this approach is really GPU specific I guess. |
Should close #508
The short version of what I've done here is rework
add_transform!into two kernels with associated helper structures, one for the Abelian case (which is quite simple) and one for the more generic case. Instead of parallelizing over the blocks, which are of pretty different sizes, I parallelize over the output data indata_dst. This means a lot more annoying bookkeeping but also a much more uniform workload across the GPU and better usage of the large number of available threads. There are still probably some performance optimizations to be done but I thought this was a) already hard enough to understand and b) pretty compelling!Here are the results for the sample script I linked in the issue above, on an AMD MI210:
GPU permute time (ms)
mainksh/add_transformCost per fusion tree (GPU, µs/tree)
mainksh/add_transformSo this should hopefully finally let people doing complicated stuff (e.g. anything involving SU(2)) really benefit from the GPU. All the tests passed for me locally.
I still have some lingering questions about where stuff should live. I added some new caches of GPU objects as well to avoid sending things back to the GPU that don't need to go, but maybe those should live in the main package?