-
Notifications
You must be signed in to change notification settings - Fork 10
Singleton device allocator #17
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
Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
3ecbf9f
feat: GlobalDeviceAllocator for SYCL.
highperformancecoder 97109af
bugfix: revert switch to standard seqential code in generate.
highperformancecoder 62d6910
bugfix: Fix SYCL version of EcolabPoint::mutate
highperformancecoder 9d68311
This commit crashes NUC:
highperformancecoder 113482d
Working on 12x12 on GPU. Need to check non-SYCL build.
highperformancecoder e02ad4e
bugfix: LocalAllocator working now - 12x12.
highperformancecoder 0699765
bugfix: Fix non-sycl build
highperformancecoder a2e4571
chore: deadcode removal
highperformancecoder 3d8753a
bugfix: Increase working group size to 256. arrays assignment from ex…
highperformancecoder e456e3f
bugfix: Added a groupBarrier to array::asgV, but left commented out.
highperformancecoder 74a953e
bugfix: Remove conditional expression around a groupBarrier.
highperformancecoder 0b876c8
bugfix: more attempts to fix crashing problem. Reference counter upda…
highperformancecoder a14c382
bugfix: Change binop/unop and RVIndex expressions to copies rather th…
highperformancecoder 7db3664
bugfix - reverted back tom how things were before the last lot of bra…
highperformancecoder 03f614b
bugfix: For code running on device, we need a slightly different allo…
highperformancecoder 454ceab
chore: optimise array(expr)
highperformancecoder b8bc64a
bugfix: deallocating pointers in DeviceAllocator was incorrectly shif…
highperformancecoder b9f37fc
refactor: Optimise generate step by precomputing row offsets
highperformancecoder 11fa6a8
chore: dead code removal
highperformancecoder c1cd4de
bugfix: missing oDiagIdx calculation in condense
highperformancecoder cbdab62
bugfix: printf format string needs a %zu for size_t
highperformancecoder 8342fad
bugfix: update pred-prey test to current libs
highperformancecoder fb9b5d8
Fix Queue enqueue/dequeue with bounded MPMC per-slot sequencing
highperformancecoder c9eabc0
Fix groupedForAll fatal-error tracking with atomic fetch_or and post-…
highperformancecoder 5e1b872
chore: address code review comments.
highperformancecoder b088deb
bugfix: fixed refactor of DeviceAllocator
highperformancecoder ae4e344
bugfix: synchronous update was corrupted by prefious refactor.
highperformancecoder 2d45497
Revert "bugfix: update pred-prey test to current libs"
highperformancecoder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| /* | ||
| @copyright Russell Standish 2026 | ||
| @author Russell Standish | ||
| This file is part of EcoLab | ||
|
|
||
| Open source licensed under the MIT license. See LICENSE for details. | ||
| */ | ||
|
|
||
| #ifndef DEVICE_ALLOCATOR_H | ||
| #define DEVICE_ALLOCATOR_H | ||
| #include "sycl.h" | ||
| #include "graphcode.h" | ||
|
|
||
| namespace ecolab | ||
| { | ||
| /// minimum allocation size = 2^minOrder, maximum object size = 2^(maxOrder-1) | ||
| constexpr unsigned minOrder=4, maxOrder=20; | ||
| /// memory allocated to each order, total memory allocated on device=poolSize*(maxOrder-minOrder) | ||
| constexpr unsigned poolSize=32*1024*1024; | ||
|
|
||
| struct FatalErrorFlag | ||
| { | ||
| bool flag; | ||
| }; | ||
|
|
||
| inline __attribute__((noinline)) bool& fatalErrorFlag() { | ||
| return sycl::ext::oneapi::group_local_memory<FatalErrorFlag>(syclGroup(),false)->flag; | ||
| } | ||
|
|
||
| // Bounded MPMC circular buffer queue for SYCL using per-slot sequence numbers. | ||
| // dequeue() returns ~0U when queue appears empty (non-blocking empty signal). | ||
| template <unsigned size> | ||
| class Queue | ||
| { | ||
| static_assert((size&(size-1))==0,"size must be power of two"); | ||
| constexpr static unsigned mask=size-1; | ||
|
|
||
| struct Slot | ||
| { | ||
| unsigned seq; | ||
| unsigned value; | ||
| }; | ||
|
|
||
| Slot slots[size]; | ||
| unsigned head=size, tail=0; | ||
|
|
||
| using Atomic=sycl::atomic_ref<unsigned,sycl::memory_order::relaxed,sycl::memory_scope::device>; | ||
|
|
||
| public: | ||
| Queue() | ||
| { | ||
| for (unsigned i=0; i<size; ++i) { | ||
| slots[i].value=i; | ||
| slots[i].seq=i+1; | ||
| } | ||
| } | ||
|
|
||
| void enqueue(unsigned x) | ||
| { | ||
| while (true) | ||
| { | ||
| Atomic headAtomic(head); | ||
| unsigned pos=headAtomic.load(); | ||
| Slot& slot=slots[pos & mask]; | ||
| Atomic seqAtomic(slot.seq); | ||
| unsigned seq=seqAtomic.load(sycl::memory_order::acquire); | ||
| int diff=int(seq)-int(pos); | ||
|
|
||
| if (diff==0) | ||
| { | ||
| if (headAtomic.compare_exchange_strong(pos,pos+1)) | ||
| { | ||
| slot.value=x; | ||
| Atomic publish(slot.seq); | ||
| publish.store(pos+1,sycl::memory_order::release); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| unsigned dequeue() | ||
| { | ||
| while (true) | ||
| { | ||
| Atomic tailAtomic(tail); | ||
| unsigned pos=tailAtomic.load(); | ||
| Slot& slot=slots[pos & mask]; | ||
| Atomic seqAtomic(slot.seq); | ||
| unsigned seq=seqAtomic.load(sycl::memory_order::acquire); | ||
| int diff=int(seq)-int(pos+1); | ||
|
|
||
| if (diff==0) | ||
| { | ||
| if (tailAtomic.compare_exchange_strong(pos,pos+1)) | ||
| { | ||
| unsigned v=slot.value; | ||
| Atomic release(slot.seq); | ||
| release.store(pos+size,sycl::memory_order::release); | ||
| return v; | ||
| } | ||
| } | ||
| else if (diff<0) | ||
| { | ||
| return ~0U; // signal buffer empty, don't wait | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| template <unsigned order> class DeviceAllocator; | ||
| /// empty allocator to terminate template recursion | ||
| template <> class DeviceAllocator<maxOrder> { | ||
| public: | ||
| void* allocate(size_t sz) { | ||
| if (groupLeader()) | ||
| sycl::ext::oneapi::experimental::printf("failed to allocate %zu bytes\n",sz); | ||
| #ifdef __SYCL_DEVICE_ONLY__ | ||
| fatalErrorFlag()=true; | ||
| #else | ||
| throw std::bad_alloc(); | ||
| #endif | ||
| return nullptr; | ||
| } | ||
| void deallocate(void* p, size_t) {sycl::ext::oneapi::experimental::printf("%p leaked on device\n",p);} | ||
| }; | ||
|
|
||
| template <unsigned order=minOrder> class DeviceAllocator | ||
| { | ||
| constexpr static unsigned pageSize=1<<order; | ||
| constexpr static unsigned numPages=poolSize/pageSize; | ||
| Queue<numPages> queue; | ||
| char memory[poolSize]; | ||
| DeviceAllocator<order+1> nextAllocator; // next size up allocator | ||
| public: | ||
| // all members of group get the same pointer | ||
| void* allocate(size_t size) { | ||
| if (size==0) return nullptr; | ||
| if (size<=pageSize) { | ||
| unsigned offs; | ||
| if (groupLeader()) offs=queue.dequeue(); | ||
| #ifdef __SYCL_DEVICE_ONLY__ | ||
| offs=sycl::group_broadcast(syclGroup(),offs); | ||
| #endif | ||
| if (offs!=~0U) | ||
| return memory+(offs<<order); | ||
| } | ||
| return nextAllocator.allocate(size); | ||
| } | ||
| void deallocate(void* p, size_t size) { | ||
| if (!p) return; | ||
| if (p>=memory && p<memory+poolSize) { | ||
| groupBarrier(); | ||
| if (groupLeader()) | ||
| queue.enqueue((reinterpret_cast<char*>(p)-memory)>>order); | ||
| return; | ||
| } | ||
| nextAllocator.deallocate(p,size); | ||
| } | ||
|
|
||
| bool inAllocator(void* p) const {return p>=this && p<this+1;} | ||
| }; | ||
|
|
||
| inline DeviceAllocator<>& deviceAllocator() { | ||
| static DeviceType<DeviceAllocator<>> deviceAllocator; | ||
| return *deviceAllocator; | ||
| } | ||
|
|
||
| /// Allocator wrapping the DeviceAllocator singleton | ||
| template <class T> | ||
| struct GlobalDeviceAllocator | ||
| { | ||
| using value_type=T; | ||
| using pointer=T*; | ||
| using reference=T&; | ||
| using difference_type=std::ptrdiff_t; | ||
| using propagate_on_container_move_assignment=std::true_type; | ||
|
|
||
| DeviceAllocator<>* allocator=&deviceAllocator(); | ||
|
|
||
| GlobalDeviceAllocator() = default; // note: default constructor must be called on host | ||
| template <class U> | ||
| GlobalDeviceAllocator(const GlobalDeviceAllocator<U>& other): | ||
| allocator(other.allocator) {} | ||
|
|
||
| T* allocate(size_t n) | ||
| {return reinterpret_cast<T*>(allocator->allocate(n*sizeof(T)));} | ||
| void deallocate(T* p, size_t n){allocator->deallocate(p,n*sizeof(T));} | ||
| template<class U> struct rebind {using other=GlobalDeviceAllocator<U>;}; | ||
| }; | ||
|
|
||
| template <class T> | ||
| struct HostSharedAllocator: public graphcode::Allocator<T> | ||
| { | ||
| HostSharedAllocator(): graphcode::Allocator<T>(syclQ(), sycl::usm::alloc::shared) {} | ||
| template<class U> struct rebind {using other=HostSharedAllocator<U>;}; | ||
| }; | ||
|
|
||
| constexpr static unsigned LocalAllocatorSize=30*1024; // 32KiB = half typical local storage | ||
|
|
||
| struct LocalAllocatorBuffer | ||
| { | ||
| unsigned next; | ||
| char buffer[LocalAllocatorSize]; | ||
| }; | ||
|
|
||
| /* | ||
| group_local_memory is a weird beast. The address returned is tied to the line of code in which it instantiated, so we need to specify noinline to prevent it from being inlined, and inline to ensure single definition | ||
| */ | ||
| inline __attribute__((noinline)) LocalAllocatorBuffer& localAllocatorBuffer() | ||
| {return *sycl::ext::oneapi::group_local_memory_for_overwrite<LocalAllocatorBuffer>(syclGroup());} | ||
|
|
||
|
|
||
| #ifdef __SYCL_DEVICE_ONLY__ | ||
|
|
||
| /** | ||
| A Local allocator allocates memory from device local memory, | ||
| which is shared between threads of a work group, and has the same | ||
| lifetime as the kernel | ||
| */ | ||
| template <class T> | ||
| class LocalAllocator | ||
| { | ||
| public: | ||
| using value_type=T; | ||
| using pointer=T*; | ||
| using reference=T&; | ||
| using difference_type=std::ptrdiff_t; | ||
| using propagate_on_container_move_assignment=std::true_type; | ||
|
|
||
| // no need for destructor, as Impl has nothing to tear down | ||
| T* allocate(size_t n) { | ||
| auto& b=localAllocatorBuffer(); | ||
| unsigned offs=b.next; | ||
| if (offs+n*sizeof(T)>LocalAllocatorSize) | ||
| { | ||
| fatalErrorFlag()=true; | ||
| return nullptr; | ||
| } | ||
| sycl::group_barrier(syclGroup()); | ||
| if (syclGroup().leader()) b.next+=n*sizeof(T); | ||
| sycl::group_barrier(syclGroup()); | ||
| char* alloc=b.buffer+offs; | ||
| return reinterpret_cast<T*>(alloc); | ||
| } | ||
| void deallocate(T*,size_t) {} // cleaned up when group exits | ||
| template<class U> struct rebind {using other=LocalAllocator<U>;}; | ||
| }; | ||
| #endif | ||
|
|
||
| } | ||
| #endif | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.