91#include <sycl/sycl.hpp>
115constexpr int64_t kHashWgSize = 1024;
117constexpr int64_t kHashBucketCountMultiplier = 2;
120inline uint64_t PackSlot(uint32_t state,
buf_index_t bi, uint32_t fingerprint) {
121 return (
static_cast<uint64_t
>(fingerprint) << 36) |
122 (
static_cast<uint64_t
>(state) << 32) |
static_cast<uint64_t
>(bi);
125inline void UnpackSlot(uint64_t packed,
128 uint32_t& fingerprint) {
129 bi =
static_cast<buf_index_t>(packed & 0xffffffffULL);
130 state =
static_cast<uint32_t
>((packed >> 32) & 0xfULL);
131 fingerprint =
static_cast<uint32_t
>(packed >> 36);
135inline int64_t NextPowerOfTwo(int64_t n) {
136 if (n <= 0)
return 1;
150inline uint64_t HashMix(uint64_t h) {
152 h *= 0xff51afd7ed558ccdULL;
154 h *= 0xc4ceb9fe1a85ec53ULL;
162template <
typename Key,
typename Hash,
typename Eq>
173 const uint64_t hash = HashMix(
hash_fn(key));
174 const int64_t home =
static_cast<int64_t
>(hash & mask);
175 const uint32_t my_fingerprint =
176 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
178 for (int64_t probe = 0; probe <
bucket_count; ++probe) {
179 const int64_t idx = (home + probe) & mask;
184 UnpackSlot(packed, s, bi, fp);
189 const Key* slot_key =
191 if (
eq_fn(*slot_key, key)) {
201template <
typename Key,
typename Hash,
typename Eq>
206 const std::vector<int64_t>& value_dsizes,
208 int64_t wg_size = kHashWgSize);
214 void Insert(
const void* input_keys,
215 const std::vector<const void*>& input_values_soa,
218 int64_t
count)
override;
220 void Find(
const void* input_keys,
223 int64_t
count)
override;
225 void Erase(
const void* input_keys,
227 int64_t
count)
override;
231 void Clear()
override;
233 int64_t
Size()
const override;
240 void Allocate(int64_t capacity)
override;
241 void Free()
override;
264template <
typename Key,
typename Hash,
typename Eq>
266 int64_t init_capacity,
268 const std::vector<int64_t>& value_dsizes,
273 queue_(sy::GetQueue(device)) {
274 const int64_t device_max_wg_size =
static_cast<int64_t
>(
276 .get_info<sycl::info::device::max_work_group_size>());
281template <
typename Key,
typename Hash,
typename Eq>
286template <
typename Key,
typename Hash,
typename Eq>
288 if (!occupied_count_) {
294 return static_cast<int64_t
>(
count);
297template <
typename Key,
typename Hash,
typename Eq>
299 if (!non_empty_count_) {
305 return static_cast<int64_t
>(
count);
308template <
typename Key,
typename Hash,
typename Eq>
310 return bucket_count_;
313template <
typename Key,
typename Hash,
typename Eq>
315 utility::LogError(
"Unimplemented");
318template <
typename Key,
typename Hash,
typename Eq>
320 return float(Size()) / float(bucket_count_);
323template <
typename Key,
typename Hash,
typename Eq>
325 const void* input_keys,
326 const std::vector<const void*>& input_values_soa,
330 if (
count == 0)
return;
332 const Key* keys =
static_cast<const Key*
>(input_keys);
333 const int n_values =
static_cast<int>(input_values_soa.size());
337 "SYCL hashmap supports up to 16 value arrays, but got {}.",
343 const void* ptrs[16];
345 for (
int i = 0; i < n_values; ++i) {
346 values_soa.ptrs[i] = input_values_soa[i];
354 const int prev_heap_top = this->buffer_->GetHeapTopIndex();
355 const int64_t capacity = buffer_accessor_.capacity_;
356 if (
count > capacity - prev_heap_top) {
358 "SYCL hashmap insertion requires {} free buffer slots, but "
359 "only {} are available.",
360 count, capacity - prev_heap_top);
363 queue_.memcpy(output_buf_indices,
364 buffer_accessor_.heap_ + prev_heap_top,
367 const int new_heap_top = prev_heap_top +
static_cast<int>(
count);
368 queue_.memcpy(buffer_accessor_.heap_top_, &new_heap_top,
sizeof(
int))
373 uint64_t* slot_data = slot_data_;
374 const int64_t bucket_count = bucket_count_;
375 int* occupied_count = occupied_count_;
376 int* non_empty_count = non_empty_count_;
377 constexpr int kMaxOuterIter = 1 << 20;
381 const int64_t common_block_size = buffer_accessor_.common_block_size_;
383 auto insert_kernel = [=](sycl::nd_item<1>
384 item) [[intel::kernel_args_restrict]] {
385 const int64_t tid = item.get_global_id(0);
386 int my_new_occupied = 0;
387 int my_new_nonempty = 0;
390 sycl::atomic_fence(sycl::memory_order::seq_cst,
391 sycl::memory_scope::device);
394 const Key key = keys[tid];
395 output_buf_indices[tid] = 0;
396 output_masks[tid] =
false;
398 const int64_t mask = bucket_count - 1;
399 const uint64_t hash = HashMix(hash_fn(key));
400 const int64_t home =
static_cast<int64_t
>(hash & mask);
401 const uint32_t my_fingerprint =
402 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
404 bool key_published =
false;
412 Key* slot_key =
static_cast<Key*
>(accessor.
GetKeyPtr(my_bi));
415 for (
int j = 0; j < n_values; ++j) {
416 const int64_t blocks =
418 DISPATCH_DIVISOR_SIZE_TO_BLOCK_T_SYCL(
419 common_block_size, [&]() {
420 using val_block_t = block_t;
422 reinterpret_cast<val_block_t*
>(
424 const val_block_t* src =
425 reinterpret_cast<const val_block_t*
>(
426 values_soa.ptrs[j]) +
428 for (int64_t b = 0; b < blocks; ++b) {
434 sycl::atomic_fence(sycl::memory_order::seq_cst,
435 sycl::memory_scope::device);
436 key_published =
true;
439 bool finished =
false;
442 if (++outer_iter > kMaxOuterIter) {
445 int64_t first_deleted = -1;
446 bool restart =
false;
448 for (int64_t probe = 0; probe < bucket_count; ++probe) {
449 const int64_t idx = (home + probe) & mask;
450 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
451 sycl::memory_scope::device>
454 uint64_t packed = st.load(sycl::memory_order::acquire);
458 UnpackSlot(packed, s, bi, fp);
461 if (fp == my_fingerprint) {
462 sycl::atomic_fence(sycl::memory_order::seq_cst,
463 sycl::memory_scope::device);
464 const Key* slot_key =
static_cast<const Key*
>(
466 if (eq_fn(*slot_key, key)) {
467 output_buf_indices[tid] = bi;
468 output_masks[tid] =
false;
477 if (first_deleted < 0) first_deleted = idx;
481 if (!key_published) {
488 (first_deleted >= 0) ? first_deleted : idx;
489 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
490 sycl::memory_scope::device>
492 uint64_t expected_packed =
494 ? tst.load(sycl::memory_order::acquire)
496 const uint32_t prev_state =
static_cast<uint32_t
>(
497 (expected_packed >> 32) & 0xfULL);
500 tst.compare_exchange_strong(
503 sycl::memory_order::acq_rel,
504 sycl::memory_order::relaxed)) {
505 output_buf_indices[tid] = my_bi;
506 output_masks[tid] =
true;
519 if (finished || !restart) {
528 if (key_published && !output_masks[tid]) {
533 const int wg_occupied = sycl::reduce_over_group(
534 item.get_group(), my_new_occupied, sycl::plus<int>{});
535 const int wg_nonempty = sycl::reduce_over_group(
536 item.get_group(), my_new_nonempty, sycl::plus<int>{});
537 if (item.get_local_id(0) == 0) {
538 if (wg_occupied > 0) {
539 sycl::atomic_ref<int, sycl::memory_order::relaxed,
540 sycl::memory_scope::device>
542 oc.fetch_add(wg_occupied);
544 if (wg_nonempty > 0) {
545 sycl::atomic_ref<int, sycl::memory_order::relaxed,
546 sycl::memory_scope::device>
547 nec(*non_empty_count);
548 nec.fetch_add(wg_nonempty);
553 const int64_t wg_size = wg_size_;
554 const int64_t global_size = ((
count + wg_size - 1) / wg_size) * wg_size;
555 queue_.submit([&](sycl::handler& cgh) {
556 cgh.parallel_for(sycl::nd_range<1>(global_size, wg_size),
561template <
typename Key,
typename Hash,
typename Eq>
566 if (
count == 0)
return;
568 const Key* keys =
static_cast<const Key*
>(input_keys);
570 uint64_t* slot_data = slot_data_;
571 const int64_t bucket_count = bucket_count_;
576 [=](sycl::nd_item<1> item) [[intel::kernel_args_restrict]] {
577 const int64_t tid = item.get_global_id(0);
578 if (tid >=
count)
return;
579 const Key key = keys[tid];
580 const int64_t mask = bucket_count - 1;
581 const uint64_t hash = HashMix(hash_fn(key));
582 const int64_t home =
static_cast<int64_t
>(hash & mask);
583 const uint32_t my_fingerprint =
584 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
588 for (int64_t probe = 0; probe < bucket_count; ++probe) {
589 const int64_t idx = (home + probe) & mask;
590 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
591 sycl::memory_scope::device>
593 uint64_t packed = st.load(sycl::memory_order::acquire);
597 UnpackSlot(packed, s, bi, fp);
603 const Key* slot_key =
604 static_cast<const Key*
>(accessor.
GetKeyPtr(bi));
605 if (eq_fn(*slot_key, key)) {
612 output_masks[tid] = found;
613 output_buf_indices[tid] = found ?
result : 0;
616 const int64_t wg_size = wg_size_;
617 const int64_t global_size = ((
count + wg_size - 1) / wg_size) * wg_size;
618 queue_.submit([&](sycl::handler& cgh) {
619 cgh.parallel_for(sycl::nd_range<1>(global_size, wg_size),
624template <
typename Key,
typename Hash,
typename Eq>
628 if (
count == 0)
return;
630 const Key* keys =
static_cast<const Key*
>(input_keys);
632 uint64_t* slot_data = slot_data_;
633 const int64_t bucket_count = bucket_count_;
634 int* occupied_count = occupied_count_;
638 auto erase_kernel = [=](sycl::nd_item<1>
639 item) [[intel::kernel_args_restrict]] {
640 const int64_t tid = item.get_global_id(0);
641 if (tid >=
count)
return;
642 const Key key = keys[tid];
643 const int64_t mask = bucket_count - 1;
644 const uint64_t hash = HashMix(hash_fn(key));
645 const int64_t home =
static_cast<int64_t
>(hash & mask);
646 const uint32_t my_fingerprint =
647 static_cast<uint32_t
>((hash >> 16) & 0xfffffffULL);
650 for (int64_t probe = 0; probe < bucket_count; ++probe) {
651 const int64_t idx = (home + probe) & mask;
652 sycl::atomic_ref<uint64_t, sycl::memory_order::relaxed,
653 sycl::memory_scope::device>
655 uint64_t packed = st.load(sycl::memory_order::acquire);
659 UnpackSlot(packed, s, bi, fp);
665 const Key* slot_key =
666 static_cast<const Key*
>(accessor.
GetKeyPtr(bi));
667 if (eq_fn(*slot_key, key)) {
668 uint64_t expected_packed = packed;
670 if (st.compare_exchange_strong(
671 expected_packed, deleted_val,
672 sycl::memory_order::acq_rel,
673 sycl::memory_order::relaxed)) {
676 sycl::atomic_ref<int, sycl::memory_order::relaxed,
677 sycl::memory_scope::device>
685 output_masks[tid] = erased;
688 const int64_t wg_size = wg_size_;
689 const int64_t global_size = ((
count + wg_size - 1) / wg_size) * wg_size;
690 queue_.submit([&](sycl::handler& cgh) {
691 cgh.parallel_for(sycl::nd_range<1>(global_size, wg_size),
696template <
typename Key,
typename Hash,
typename Eq>
699 uint64_t* slot_data = slot_data_;
700 const int64_t bucket_count = bucket_count_;
702 int* d_count =
static_cast<int*
>(
708 sycl::event memset_event =
queue_.memset(d_count, 0,
sizeof(
int));
710 const int64_t kWgSize = wg_size_;
712 auto scan_kernel = [=](sycl::nd_item<1> item) {
713 int64_t idx = item.get_global_id(0);
714 auto group = item.get_group();
716 bool is_occupied =
false;
718 if (idx < bucket_count) {
719 const uint64_t packed = slot_data[idx];
720 uint32_t s =
static_cast<uint32_t
>((packed >> 32) & 0xfULL);
723 bi =
static_cast<buf_index_t>(packed & 0xffffffffULL);
727 int local_val = is_occupied ? 1 : 0;
728 int local_offset = sycl::exclusive_scan_over_group(group, local_val,
731 sycl::reduce_over_group(group, local_val, sycl::plus<int>{});
734 if (item.get_local_id(0) == 0 && group_total > 0) {
735 sycl::atomic_ref<int, sycl::memory_order::relaxed,
736 sycl::memory_scope::device>
738 group_start = counter.fetch_add(group_total);
740 group_start = sycl::group_broadcast(group, group_start, 0);
743 output_indices[group_start + local_offset] = bi;
747 int64_t global_size = ((bucket_count + kWgSize - 1) / kWgSize) * kWgSize;
748 queue_.submit([&](sycl::handler& cgh) {
749 cgh.depends_on(memset_event);
750 cgh.parallel_for(sycl::nd_range<1>(global_size, kWgSize),
757 return static_cast<int64_t
>(
count);
760template <
typename Key,
typename Hash,
typename Eq>
762 this->buffer_->ResetHeap();
766 queue_.memset(slot_data_, 0, bucket_count_ *
sizeof(uint64_t));
767 if (occupied_count_) {
768 queue_.memset(occupied_count_, 0,
sizeof(
int));
770 if (non_empty_count_) {
771 queue_.memset(non_empty_count_, 0,
sizeof(
int));
776template <
typename Key,
typename Hash,
typename Eq>
778 this->capacity_ = capacity;
779 bucket_count_ = NextPowerOfTwo(
780 std::max<int64_t>(capacity * kHashBucketCountMultiplier, 1));
782 this->buffer_ = std::make_shared<HashBackendBuffer>(
783 this->capacity_, this->key_dsize_, this->value_dsizes_,
785 buffer_accessor_.Setup(*this->buffer_);
788 bucket_count_ *
sizeof(uint64_t), this->device_));
789 queue_.memset(slot_data_, 0, bucket_count_ *
sizeof(uint64_t));
791 occupied_count_ =
static_cast<int*
>(
793 queue_.memset(occupied_count_, 0,
sizeof(
int));
795 non_empty_count_ =
static_cast<int*
>(
800 queue_.memset(non_empty_count_, 0,
sizeof(
int)).wait_and_throw();
803template <
typename Key,
typename Hash,
typename Eq>
805 buffer_accessor_.Shutdown(this->device_);
808 slot_data_ =
nullptr;
810 if (occupied_count_) {
812 occupied_count_ =
nullptr;
814 if (non_empty_count_) {
816 non_empty_count_ =
nullptr;
BitmapEventQueue * queue_
Definition BitmapWindowSystem.cpp:53
Vectorized trivial-object copy block sizes (CUDA and SYCL).
SYCL device properties and (when built) queue manager.
Device-side accessor for SYCL hash-map key/value buffers.
Real target
Definition SurfaceReconstructionPoisson.cpp:270
core::Tensor result
Definition VtkUtils.cpp:76
Definition DeviceHashBackend.h:20
static void MemcpyToHost(void *host_ptr, const void *src_ptr, const Device &src_device, size_t num_bytes)
Same as Memcpy, but with host (CPU:0) as default dst_device.
Definition MemoryManager.cpp:85
static void * Malloc(size_t byte_size, const Device &device)
Definition MemoryManager.cpp:22
static void Free(void *ptr, const Device &device)
Frees previously allocated memory at address ptr on device device.
Definition MemoryManager.cpp:28
Definition SYCLHashBackendBufferAccessor.h:33
void * GetKeyPtr(buf_index_t buf_index) const
Device: USM pointer to the key at buf_index.
Definition SYCLHashBackendBufferAccessor.h:133
void * GetValuePtr(buf_index_t buf_index, int value_idx=0) const
Device: USM pointer to value value_idx at buf_index.
Definition SYCLHashBackendBufferAccessor.h:137
int64_t * value_blocks_per_element_
Blocks per value for vector copy.
Definition SYCLHashBackendBufferAccessor.h:154
static constexpr buf_index_t kInvalidBufIndex
Definition SYCLHashBackendBufferAccessor.h:35
void DeviceFree(buf_index_t buf_index) const
Definition SYCLHashBackendBufferAccessor.h:122
DeviceHashBackend for SYCL devices (algorithm in file header).
Definition SYCLHashBackend.h:202
SYCLHashDeviceLookup< Key, Hash, Eq > GetDeviceLookup() const
Snapshot for device kernels; table must not be mutated while in use.
Definition SYCLHashBackend.h:244
SYCLHashBackend(int64_t init_capacity, int64_t key_dsize, const std::vector< int64_t > &value_dsizes, const Device &device, int64_t wg_size=kHashWgSize)
Definition SYCLHashBackend.h:265
~SYCLHashBackend()
Definition SYCLHashBackend.h:282
void Find(const void *input_keys, buf_index_t *output_buf_indices, bool *output_masks, int64_t count) override
Parallel find a contiguous array of keys.
Definition SYCLHashBackend.h:562
void Erase(const void *input_keys, bool *output_masks, int64_t count) override
Parallel erase a contiguous array of keys.
Definition SYCLHashBackend.h:625
int64_t GetNonEmptyCount() const override
Occupied + deleted slots (rehash guard; see file header).
Definition SYCLHashBackend.h:298
float LoadFactor() const override
Get the current load factor, defined as size / bucket count.
Definition SYCLHashBackend.h:319
void Clear() override
Clear stored map without reallocating memory.
Definition SYCLHashBackend.h:761
int64_t wg_size_
SYCL work-group size for kernels.
Definition SYCLHashBackend.h:259
std::vector< int64_t > BucketSizes() const override
Get the number of entries per bucket.
Definition SYCLHashBackend.h:314
void Free() override
Definition SYCLHashBackend.h:804
sycl::queue queue_
Definition SYCLHashBackend.h:261
void Reserve(int64_t capacity) override
No-op; use HashMap::Reserve for capacity growth.
Definition SYCLHashBackend.h:212
int * occupied_count_
Device live entry count.
Definition SYCLHashBackend.h:256
int64_t bucket_count_
Definition SYCLHashBackend.h:258
void Insert(const void *input_keys, const std::vector< const void * > &input_values_soa, buf_index_t *output_buf_indices, bool *output_masks, int64_t count) override
Parallel insert contiguous arrays of keys and values.
Definition SYCLHashBackend.h:324
SYCLHashBackendBufferAccessor buffer_accessor_
Definition SYCLHashBackend.h:253
int64_t GetActiveIndices(buf_index_t *output_indices) override
Parallel collect all iterators in the hash table.
Definition SYCLHashBackend.h:697
int64_t Size() const override
Get the size (number of valid entries) of the hash map.
Definition SYCLHashBackend.h:287
int64_t GetBucketCount() const override
Get the number of buckets of the hash map.
Definition SYCLHashBackend.h:309
int * non_empty_count_
Device occupied + tombstone count.
Definition SYCLHashBackend.h:257
void Allocate(int64_t capacity) override
Definition SYCLHashBackend.h:777
uint64_t * slot_data_
USM packed slots.
Definition SYCLHashBackend.h:255
uint32_t buf_index_t
Definition HashBackendBuffer.h:49
HashSlotState
Definition SYCLHashBackend.h:107
@ kSlotOccupied
Definition SYCLHashBackend.h:109
@ kSlotDeleted
Definition SYCLHashBackend.h:110
@ kSlotEmpty
Definition SYCLHashBackend.h:108
const char const char value recording_handle imu_sample recording_handle uint8_t size_t data_size k4a_record_configuration_t config target_format k4a_capture_t capture_handle k4a_imu_sample_t imu_sample uint64_t
Definition K4aPlugin.cpp:343
Definition PinholeCameraIntrinsic.cpp:16
Read-only table view for device kernels (see file header).
Definition SYCLHashBackend.h:163
uint64_t * slot_data
USM packed slot array.
Definition SYCLHashBackend.h:164
Hash hash_fn
Key hash functor.
Definition SYCLHashBackend.h:167
Eq eq_fn
Key equality functor.
Definition SYCLHashBackend.h:168
int64_t bucket_count
Power-of-two bucket count.
Definition SYCLHashBackend.h:165
SYCLHashBackendBufferAccessor accessor
Key/value buffer accessor.
Definition SYCLHashBackend.h:166
buf_index_t Find(const Key &key) const
Linear-probe lookup; returns buffer index or -1 if not found.
Definition SYCLHashBackend.h:171