18#include <cuda_runtime.h>
26#include <cuda/std/array>
32#include <cuda/std/span>
49template <
typename Cw,
typename In,
int in_bits>
50struct HalfTreeDpfCwsView {
55 __host__ __device__
inline Cw operator[](
int l)
const {
56 const int4 s_cw = cws[
static_cast<size_t>(l) * stride];
57 if (l == in_bits - 1)
return Cw{s_cw, ((packed_tr >> (in_bits - 1)) & 1) != 0};
58 return Cw{s_cw,
false};
78template <
int in_bits,
typename Group,
typename Hash,
typename In = u
int>
80 Groupable<Group> && CcrHashable<Hash>)
92 struct __align__(32)
Cw {
96 static_assert(
sizeof(
Cw) == 32);
109 __host__ __device__
explicit HalfTreeDpf(Hash hash, int4 instance_seed);
140 __host__ __device__
void Gen(cuda::std::span<int4> cws, In &tr,
141 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf,
size_t stride = 1);
170 __host__ __device__ int4
Eval(
bool b, int4 s0, cuda::std::span<const int4> cws,
171 In tr, In x,
size_t stride = 1);
201 void EvalAll(
bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
202 int4 ys[],
int par_depth = 0,
size_t stride = 1);
237 void EvalRange(
bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws,
238 In tr, cuda::std::span<int4> ys,
int par_depth = 0,
size_t stride = 1);
266 template <
int frontier_depth = 16,
int threads_per_block = 256>
267 __host__ cudaError_t
EvalRangeGpu(
bool b, In first, In last, int4 s0,
268 cuda::std::span<const int4> cws, In tr, cuda::std::span<int4> ys,
269 cudaStream_t stream = 0,
size_t stride = 1)
const;
295 template <
int frontier_depth = 16,
int threads_per_block = 256>
296 __host__ cudaError_t
EvalAllGpu(
bool b, int4 s0, cuda::std::span<const int4> cws,
297 In tr, cuda::std::span<int4> ys, cudaStream_t stream = 0,
size_t stride = 1) const
298 requires(in_bits < sizeof(
size_t) * 8);
332 template <
int frontier_depth = 16,
int threads_per_block = 256>
333 __host__ cudaError_t
EvalAllGpuBatch(
bool b, cuda::std::span<const int4> s0s,
334 cuda::std::span<const int4> cws, cuda::std::span<const In> tr,
335 cuda::std::span<int4> ys, cudaStream_t stream = 0,
336 size_t stride = 0) const
337 requires(in_bits < sizeof(
size_t) * 8);
339#if FSS_ENABLE_V1_COMPAT
346 [[deprecated(
"use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, int4, size_t) overload")]]
347 __host__ __device__
void Gen(
Cw cws[], int4 &ocw,
const int4 s0s[2], In a, int4 b_buf);
350 [[deprecated(
"use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, In, size_t) overload")]]
351 __host__ __device__ int4
Eval(
bool b, int4 s0,
const Cw cws[], int4 ocw, In x);
354 [[deprecated(
"use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, int4[], int, size_t) overload")]]
355 void EvalAll(
bool b, int4 s0,
const Cw cws[], int4 ocw, int4 ys[]);
365 using Key [[deprecated(
"use the split-component Gen/Eval/EvalAll with separate cws and tr arrays")]] =
366 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits>;
369 [[deprecated(
"use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, int4, size_t) overload")]]
370 __host__ __device__
void Gen(compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k0,
371 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k1,
372 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf);
375 [[deprecated(
"use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, In, size_t) overload")]]
376 __host__ __device__ int4
Eval(
bool b,
const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key, In x);
379 [[deprecated(
"use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, int4[], int, size_t) overload")]]
380 void EvalAll(
bool b,
const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key,
381 cuda::std::span<int4> ys);
385 friend class detail::SchemeAccess;
387 template <
int frontier_depth,
int threads_per_block>
388 struct GpuLaunchShape;
390 static __host__ __device__
void ExpandNode(Hash &hash, int4 instance_seed,
391 int4 node, int4 cw, int4 &left, int4 &right);
392 static __host__ __device__ int4 PathStep(Hash &hash, int4 instance_seed,
393 int4 node, int4 cw,
bool take_right);
394 template <
typename OutputCw>
395 static __host__ __device__ int4 ConvertLeaf(
bool party, Hash &hash,
396 int4 instance_seed, int4 node,
bool take_right,
const Cw &last_cw,
397 OutputCw &output_cw);
398 template <
typename OutputCw>
399 static __host__ __device__ int4 ConvertHashedLeaf(
bool party, int4 hashed,
400 bool node_control,
bool take_right,
const Cw &last_cw,
401 OutputCw &output_cw);
402 template <
typename OutputCw>
403 static __host__ __device__
void ConvertLeafPair(
bool party, Hash &hash,
404 int4 instance_seed, int4 node,
const Cw &last_cw,
405 OutputCw &output_cw, int4 &left, int4 &right);
409 using CwsView = detail::HalfTreeDpfCwsView<Cw, In, in_bits>;
418 __host__ __device__
void Write(
int i, int4 s_cw) {
419 cws[
static_cast<size_t>(i) * stride] = s_cw;
422 __host__ __device__
void WriteLast(int4 s_cw,
bool extra_cw) {
423 cws[
static_cast<size_t>(in_bits - 1) * stride] = s_cw;
424 packed_tr |=
static_cast<In
>(extra_cw) << (in_bits - 1);
427 __host__ __device__
void Finish(int4 terminal) {
428 cws[
static_cast<size_t>(in_bits) * stride] = terminal;
435 template <
typename Writer>
436 __host__ __device__
void Generate(Writer writer,
const int4 *s0s, In a, int4 b_buf);
439 template <
typename Cws>
440 __host__ __device__ int4 EvalWalk(
bool b, int4 s0, Cws cws, In x);
443 template <
typename Cws>
444 void EvalAllCws(
bool b, int4 s0, Cws cws, int4 ys[],
int par_depth);
448 template <
typename Cws>
449 void EvalRangeCws(
bool b, In first, In last, int4 s0, Cws cws,
450 cuda::std::span<int4> ys,
int par_depth);
455 template <
typename Cws>
456 void EvalRangeTree(
bool b, int4 node, Cws cws, int4 ys[],
457 In first, In last, In output_first, In low, In high,
int level);
463 static constexpr int kBatchPrefix = 3;
468 template <
typename Cws>
469 void ExpandFrontier(int4 frontier[],
size_t w, Cws cws,
int lv);
472 static int4 MaskXor(int4 val, int4 cw,
int m);
478 template <
typename Cws>
479 void EvalBatched8(
bool b, int4 node, Cws cws, Group ocw_group, int4 ys[],
484 template <
typename Cws>
485 void EvalBatched8Group(
bool b,
const int4 (&frontier)[8], Cws cws,
486 Group ocw_group, int4 ys[],
size_t l,
int i);
491namespace gpu::detail {
493template <
typename Scheme>
494struct HalfTreeGpuTraits;
496#if defined(__CUDACC__)
501template <
typename Scheme,
int frontier_depth,
int block_root_depth,
bool range =
false,
503__device__ __forceinline__
void HalfTreeEvalAllBlock(
bool b, int4 s0,
504 Cws cws, int4 *ys, int4 instance_seed,
505 typename HalfTreeGpuTraits<Scheme>::Backend &hash,
int subtree_block_index,
506 int64_t thread_index, int4 *shared_frontier,
507 __uint128_t first = 0,
int subtree_depth = HalfTreeGpuTraits<Scheme>::kOutputBits,
508 int range_frontier = frontier_depth,
int range_block_root = block_root_depth) {
509 using Traits = HalfTreeGpuTraits<Scheme>;
510 constexpr int kTailCapacity = range ? 8 : Traits::kOutputBits - frontier_depth;
511 const int start_depth = range ? Traits::kOutputBits - subtree_depth : 0;
512 const int frontier = range ? start_depth + range_frontier : frontier_depth;
513 const int root_depth = range ? start_depth + range_block_root : block_root_depth;
514 const int tail_depth = Traits::kOutputBits - frontier;
515 const int block_subtree_depth = frontier - root_depth;
516 if (threadIdx.x == 0) {
519 for (
int level = 0; level < root_depth; ++level) {
520 bool take_right = level < start_depth
521 ? ((first >> (Traits::kOutputBits - 1 - level)) & 1) != 0
522 : ((subtree_block_index >> (root_depth - 1 - level)) & 1) != 0;
524 auto node_hash = hash;
525 node = fss::detail::SchemeAccess::HalfTreePathStep<Scheme>(
526 node_hash, instance_seed, node, cws[level].s, take_right);
528 shared_frontier[0] = node;
533 for (
int level = 0; level < block_subtree_depth; ++level) {
534 const bool active = threadIdx.x < (1 << (level + 1));
535 int4 node = {0, 0, 0, 0};
536 if (active) node = shared_frontier[threadIdx.x >> 1];
541 auto node_hash = hash;
542 fss::detail::SchemeAccess::HalfTreeExpandNode<Scheme>(node_hash, instance_seed,
543 node, cws[root_depth + level].s, left, right);
544 shared_frontier[threadIdx.x] = (threadIdx.x & 1) ? right : left;
549 if constexpr (range && !Traits::kPacked) {
551 int4 node = shared_frontier[threadIdx.x];
552 int level = frontier;
553 const int count = 1 << (Traits::kExpansionDepth - frontier);
554 const size_t offset =
static_cast<size_t>(thread_index) << tail_depth;
555 using OutputGroup =
typename Traits::OutputGroup;
556 auto cw = OutputGroup::From(cws[Traits::kOutputBits].s);
557 for (
int index = 0; index < count; ++index) {
558 while (level < Traits::kExpansionDepth) {
560 fss::detail::SchemeAccess::HalfTreeExpandNode<Scheme>(hash, instance_seed,
561 node, cws[level].s, left, right);
562 pending[level - frontier] = right;
567 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash,
568 instance_seed, node, cws[Traits::kExpansionDepth], cw, left, right);
569 ys[offset + 2 * index] = left;
570 ys[offset + 2 * index + 1] = right;
571 if (index + 1 < count) {
572 int next = index + 1;
573 level = Traits::kExpansionDepth - 1;
574 while ((next & 1) == 0) {
578 node = pending[level - frontier];
583 int4 nodes[1 << kTailCapacity];
584 nodes[0] = shared_frontier[threadIdx.x];
587 for (
int level = frontier; level < Traits::kExpansionDepth; ++level) {
589 for (
int index = count - 1; index >= 0; --index) {
591 fss::detail::SchemeAccess::HalfTreeExpandNode<Scheme>(
592 hash, instance_seed, nodes[index], cws[level].s, left, right);
593 nodes[2 * index] = left;
594 nodes[2 * index + 1] = right;
599 const size_t output_offset =
static_cast<size_t>(thread_index) << tail_depth;
600 if constexpr (Traits::kPacked) {
602 for (
int index = 0; index < count; ++index) {
603 ys[output_offset + index] = fss::detail::SchemeAccess::PackedConvertLeaf<Scheme>(
604 hash, instance_seed, nodes[index], cws[Traits::kExpansionDepth].s);
607 using OutputGroup =
typename Traits::OutputGroup;
608 auto output_cw = OutputGroup::From(cws[Traits::kOutputBits].s);
610 for (
int index = 0; index < count; ++index) {
612 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash, instance_seed,
613 nodes[index], cws[Traits::kExpansionDepth], output_cw, left, right);
614 ys[output_offset + 2 * index] = left;
615 ys[output_offset + 2 * index + 1] = right;
624template <
typename Scheme,
int threads_per_block>
625__global__
void HalfTreeSplitDpfEvalRangeKernel(
bool b, int4 s0,
626 const void *cws_buffer,
627 typename HalfTreeGpuTraits<Scheme>::Input packed_tr,
size_t stride,
628 int4 *ys, __uint128_t first,
int depth,
int frontier,
int root,
629 int4 instance_seed,
typename HalfTreeGpuTraits<Scheme>::Backend hash) {
630 using Traits = HalfTreeGpuTraits<Scheme>;
631 using Cws = fss::detail::HalfTreeDpfCwsView<
typename Scheme::Cw,
632 typename Traits::Input, Traits::kOutputBits>;
633 const auto *cws =
static_cast<const int4 *
>(cws_buffer);
634 const Cws view{cws, packed_tr, stride};
635 __shared__ int4 states[threads_per_block];
636 const int64_t thread_index =
static_cast<int64_t
>(blockIdx.x) * blockDim.x + threadIdx.x;
639 for (
int level = 0; level < HalfTreeGpuTraits<Scheme>::kExpansionDepth; ++level) {
640 auto node_hash = hash;
641 node = fss::detail::SchemeAccess::HalfTreePathStep<Scheme>(node_hash,
642 instance_seed, node, view[level].s,
643 ((first >> (HalfTreeGpuTraits<Scheme>::kOutputBits - 1 - level)) & 1) != 0);
646 using OutputGroup =
typename HalfTreeGpuTraits<Scheme>::OutputGroup;
647 auto cw = OutputGroup::From(view[HalfTreeGpuTraits<Scheme>::kOutputBits].s);
648 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash, instance_seed,
649 node, view[HalfTreeGpuTraits<Scheme>::kExpansionDepth], cw, left, right);
650 ys[0] = (first & 1) ? right : left;
653 HalfTreeEvalAllBlock<Scheme, 0, 0, true>(b, s0, view, ys, instance_seed, hash,
654 blockIdx.x, thread_index, states, first, depth, frontier, root);
657template <
typename Scheme,
int frontier_depth,
int block_root_depth>
658__global__
void HalfTreeSplitEvalAllKernel(
bool b, int4 s0,
const void *cws_buffer,
659 typename HalfTreeGpuTraits<Scheme>::Input packed_tr,
size_t stride,
660 int4 *ys, int4 instance_seed,
typename HalfTreeGpuTraits<Scheme>::Backend hash) {
661 using Traits = HalfTreeGpuTraits<Scheme>;
662 using Cws = fss::detail::HalfTreeDpfCwsView<
typename Scheme::Cw,
663 typename Traits::Input, Traits::kOutputBits>;
664 const auto *cws =
static_cast<const int4 *
>(cws_buffer);
665 const int64_t thread_index =
static_cast<int64_t
>(blockIdx.x) * blockDim.x + threadIdx.x;
666 if (thread_index >= (1LL << frontier_depth))
return;
667 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
668 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0,
669 Cws{cws, packed_tr, stride}, ys, instance_seed, hash, blockIdx.x,
670 thread_index, shared_frontier);
673template <
typename Scheme,
int frontier_depth,
int block_root_depth>
674__global__
void HalfTreeSplitEvalAllBatchKernel(
bool b,
const int4 *s0s,
675 const void *cws_buffer,
676 const typename HalfTreeGpuTraits<Scheme>::Input *trs,
size_t stride,
677 int4 *ys, int4 instance_seed,
typename HalfTreeGpuTraits<Scheme>::Backend hash) {
678 using Traits = HalfTreeGpuTraits<Scheme>;
679 using Cws = fss::detail::HalfTreeDpfCwsView<
typename Scheme::Cw,
680 typename Traits::Input, Traits::kOutputBits>;
681 const auto *cws =
static_cast<const int4 *
>(cws_buffer);
682 constexpr int kBlocksPerKey = 1 << block_root_depth;
683 constexpr size_t kOutputsPerKey =
size_t{1} << HalfTreeGpuTraits<Scheme>::kOutputBits;
684 const int key_index = blockIdx.x / kBlocksPerKey;
685 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
686 const int64_t thread_index =
static_cast<int64_t
>(subtree_block_index) * blockDim.x + threadIdx.x;
687 if (thread_index >= (1LL << frontier_depth))
return;
688 int4 *key_outputs = ys +
static_cast<size_t>(key_index) * kOutputsPerKey;
689 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
690 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
691 Cws{cws + key_index, trs[key_index], stride}, key_outputs, instance_seed,
692 hash, subtree_block_index, thread_index, shared_frontier);
698template <
typename Scheme,
int frontier_depth,
int block_root_depth>
699__global__
void HalfTreeSplitEvalAllDeviceKeyKernel(
bool b,
const int4 *s0,
700 const void *cws_buffer,
const typename HalfTreeGpuTraits<Scheme>::Input *tr,
701 size_t stride, int4 *ys, int4 instance_seed,
typename HalfTreeGpuTraits<Scheme>::Backend hash) {
702 using Traits = HalfTreeGpuTraits<Scheme>;
703 using Cws = fss::detail::HalfTreeDpfCwsView<
typename Scheme::Cw,
704 typename Traits::Input, Traits::kOutputBits>;
705 const auto *cws =
static_cast<const int4 *
>(cws_buffer);
706 const int64_t thread_index =
static_cast<int64_t
>(blockIdx.x) * blockDim.x + threadIdx.x;
707 if (thread_index >= (1LL << frontier_depth))
return;
708 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
709 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0[0],
710 Cws{cws, tr[0], stride}, ys, instance_seed, hash, blockIdx.x,
711 thread_index, shared_frontier);
714template <
typename Scheme,
int threads_per_block>
715__global__
void HalfTreeSplitEvalRangeDeviceKeyKernel(
bool b,
const int4 *s0,
716 const void *cws_buffer,
717 const typename HalfTreeGpuTraits<Scheme>::Input *tr,
size_t stride,
718 int4 *ys, __uint128_t first,
int depth,
int frontier,
int root,
719 int4 instance_seed,
typename HalfTreeGpuTraits<Scheme>::Backend hash) {
720 using Traits = HalfTreeGpuTraits<Scheme>;
721 using Cws = fss::detail::HalfTreeDpfCwsView<
typename Scheme::Cw,
722 typename Traits::Input, Traits::kOutputBits>;
723 const auto *cws =
static_cast<const int4 *
>(cws_buffer);
724 const Cws view{cws, tr[0], stride};
725 __shared__ int4 states[threads_per_block];
726 const int64_t thread_index =
static_cast<int64_t
>(blockIdx.x) * blockDim.x + threadIdx.x;
729 for (
int level = 0; level < HalfTreeGpuTraits<Scheme>::kExpansionDepth; ++level) {
730 auto node_hash = hash;
731 node = fss::detail::SchemeAccess::HalfTreePathStep<Scheme>(node_hash,
732 instance_seed, node, view[level].s,
733 ((first >> (HalfTreeGpuTraits<Scheme>::kOutputBits - 1 - level)) & 1) != 0);
736 using OutputGroup =
typename HalfTreeGpuTraits<Scheme>::OutputGroup;
737 auto cw = OutputGroup::From(view[HalfTreeGpuTraits<Scheme>::kOutputBits].s);
738 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash, instance_seed,
739 node, view[HalfTreeGpuTraits<Scheme>::kExpansionDepth], cw, left, right);
740 ys[0] = (first & 1) ? right : left;
743 HalfTreeEvalAllBlock<Scheme, 0, 0, true>(b, s0[0], view, ys, instance_seed, hash,
744 blockIdx.x, thread_index, states, first, depth, frontier, root);
749template <
typename Scheme,
int frontier_depth,
int block_root_depth>
750__global__
void HalfTreeSplitEvalAllBatchDeviceKeyKernel(
bool b,
const int4 *s0s,
751 const void *cws_buffer,
size_t key_records,
size_t control_record,
size_t key_count,
752 int4 *ys, int4 instance_seed,
typename HalfTreeGpuTraits<Scheme>::Backend hash) {
753 using In =
typename HalfTreeGpuTraits<Scheme>::Input;
754 using Cws = fss::detail::HalfTreeDpfCwsView<
typename Scheme::Cw, In,
755 HalfTreeGpuTraits<Scheme>::kOutputBits>;
756 constexpr int kBlocksPerKey = 1 << block_root_depth;
757 constexpr size_t kOutputsPerKey =
size_t{1} << HalfTreeGpuTraits<Scheme>::kOutputBits;
758 const auto *cws =
static_cast<const int4 *
>(cws_buffer);
759 const int key_index = blockIdx.x / kBlocksPerKey;
760 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
761 const int64_t thread_index =
static_cast<int64_t
>(subtree_block_index) * blockDim.x + threadIdx.x;
762 if (thread_index >= (1LL << frontier_depth))
return;
763 const int4 *key = cws +
static_cast<size_t>(key_index) * key_records;
764 const In *tr =
reinterpret_cast<const In *
>(key + control_record);
765 int4 *key_outputs = ys +
static_cast<size_t>(key_index) * kOutputsPerKey;
766 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
767 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
768 Cws{key, tr[0], 1}, key_outputs, instance_seed,
769 hash, subtree_block_index, thread_index, shared_frontier);
775template <
int in_bits,
typename Group,
typename Hash,
typename In,
int frontier_depth = 16,
776 int threads_per_block = 256>
777__host__
inline cudaError_t HalfTreeDpfEvalAllGpuDeviceKey(
bool b,
const int4 *s0,
778 const int4 *cws,
size_t cws_count,
const In *tr,
size_t stride, int4 *ys,
779 size_t ys_count, int4 instance_seed,
const Hash &hash, cudaStream_t stream) {
780 constexpr int kOutputBits = in_bits;
781 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
782 constexpr size_t kOutputsPerKey =
size_t{1} << kOutputBits;
786 constexpr int kFrontierDepth = frontier_depth < kOutputBits - 1
787 ? frontier_depth : kOutputBits - 1;
788 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
789 (threads_per_block & (threads_per_block - 1)) == 0,
790 "gpu block size must be a power of two in [1,1024]");
791 static_assert(kOutputBits - kFrontierDepth <= 8,
792 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
793 constexpr int kThreadsPerBlockLog2 = [] {
794 int requested_log2 = 0;
795 for (
int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
796 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
798 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
799 if (stride == 0 || stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits) ||
800 cws_count <
static_cast<size_t>(in_bits) * stride + 1 || ys_count < kOutputsPerKey ||
801 s0 ==
nullptr || cws ==
nullptr || tr ==
nullptr || ys ==
nullptr ||
802 kOutputsPerKey > kMaxElements)
return cudaErrorInvalidValue;
803 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
804 HalfTreeSplitEvalAllDeviceKeyKernel<Scheme, kFrontierDepth, kBlockRootDepth>
805 <<<
size_t{1} << kBlockRootDepth, 1 << kThreadsPerBlockLog2, 0, stream>>>(
806 b, s0, cws, tr, stride, ys, instance_seed, hash);
807 return cudaGetLastError();
810template <
int in_bits,
typename Group,
typename Hash,
typename In,
int frontier_depth = 16,
811 int threads_per_block = 256>
812__host__
inline cudaError_t HalfTreeDpfEvalRangeGpuDeviceKey(
bool b, In first, In last,
813 const int4 *s0,
const int4 *cws,
size_t cws_count,
const In *tr,
size_t stride,
814 int4 *ys,
size_t ys_count, int4 instance_seed,
const Hash &hash, cudaStream_t stream) {
815 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
816 if (stride == 0 || stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits) ||
817 cws_count <
static_cast<size_t>(in_bits) * stride + 1 || s0 ==
nullptr ||
818 cws ==
nullptr || tr ==
nullptr || ys ==
nullptr)
return cudaErrorInvalidValue;
819 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
820 return fss::detail::SplitRangeChunks<in_bits, frontier_depth, threads_per_block, 1>(
821 first, last, ys_count,
822 [&](In chunk_first,
int depth,
size_t offset,
int frontier,
int thread_bits,
int root,
824 HalfTreeSplitEvalRangeDeviceKeyKernel<Scheme, threads_per_block>
825 <<<blocks, 1 << thread_bits, 0, stream>>>(b, s0, cws, tr, stride,
826 ys + offset, chunk_first, depth, frontier, root, instance_seed, hash);
827 return cudaGetLastError();
831template <
int in_bits,
typename Group,
typename Hash,
typename In,
int frontier_depth = 16,
832 int threads_per_block = 256>
833__host__
inline cudaError_t HalfTreeDpfEvalAllGpuBatchDeviceKey(
bool b,
const int4 *s0s,
834 size_t key_count,
const int4 *cws,
size_t cws_count,
size_t key_records,
835 size_t control_record, int4 *ys,
size_t ys_count, int4 instance_seed,
const Hash &hash,
836 cudaStream_t stream) {
837 constexpr int kOutputBits = in_bits;
838 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
839 constexpr size_t kOutputsPerKey =
size_t{1} << kOutputBits;
840 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
844 constexpr int kFrontierDepth = frontier_depth < kOutputBits - 1
845 ? frontier_depth : kOutputBits - 1;
846 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
847 (threads_per_block & (threads_per_block - 1)) == 0,
848 "gpu block size must be a power of two in [1,1024]");
849 static_assert(kOutputBits - kFrontierDepth <= 8,
850 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
851 constexpr int kThreadsPerBlockLog2 = [] {
852 int requested_log2 = 0;
853 for (
int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
854 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
856 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
857 constexpr int kBlocksPerKey = 1 << kBlockRootDepth;
858 if (key_count == 0)
return cudaSuccess;
859 if (key_count > kMaxAddressableSize / kOutputsPerKey /
sizeof(int4) ||
860 key_count > kMaxAddressableSize / key_records /
sizeof(int4) ||
861 cws_count < key_count * key_records || ys_count < key_count * kOutputsPerKey ||
862 s0s ==
nullptr || cws ==
nullptr || ys ==
nullptr ||
863 kOutputsPerKey > kMaxElements)
return cudaErrorInvalidValue;
864 if (key_count >
static_cast<size_t>(std::numeric_limits<int>::max()) / kBlocksPerKey)
865 return cudaErrorInvalidConfiguration;
866 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
867 HalfTreeSplitEvalAllBatchDeviceKeyKernel<Scheme, kFrontierDepth, kBlockRootDepth>
868 <<<key_count * kBlocksPerKey, 1 << kThreadsPerBlockLog2, 0, stream>>>(
869 b, s0s, cws, key_records, control_record, key_count, ys, instance_seed, hash);
870 return cudaGetLastError();
877template <
int in_bits,
typename Group,
typename Hash,
typename In>
879 Groupable<Group> && CcrHashable<Hash>)
881 : hash_(hash), instance_seed_(instance_seed) {}
883template <
int in_bits,
typename Group,
typename Hash,
typename In>
887 cuda::std::span<int4> cws, In &tr, cuda::std::span<const int4, 2> s0s,
888 In a, int4 b_buf,
size_t stride) {
890 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
891 util::RequireArg(stride <= (kMaxElements - 1) /
static_cast<size_t>(in_bits),
892 "HalfTreeDpf::Gen: stride exceeds the correction array limit");
893 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
895 "HalfTreeDpf::Gen: cws must hold in_bits*stride+1 elements");
896 Generate(CwsWriter{cws.data(), stride, tr}, s0s.data(), a, b_buf);
899template <
int in_bits,
typename Group,
typename Hash,
typename In>
902template <typename Writer>
903__host__ __device__
inline void HalfTreeDpf<in_bits, Group, Hash, In>::Generate(
904 Writer writer,
const int4 *s0s, In a, int4 b_buf) {
913 for (
int i = 0; i < in_bits - 1; ++i) {
914 int4 h0 = hash_.Hash(
util::Xor(instance_seed_, node0));
915 int4 h1 = hash_.Hash(
util::Xor(instance_seed_, node1));
917 bool a_bit = (a >> (in_bits - 1 - i)) & 1;
931 int4 zero4 = {0, 0, 0, 0};
932 int4 ab_mask0 = a_bit ? node0 : zero4;
933 int4 ab_mask1 = a_bit ? node1 : zero4;
934 int4 t0_mask = t0 ? cw : zero4;
935 int4 t1_mask = t1 ? cw : zero4;
946 bool a_n = (a >> 0) & 1;
969 if (a_n) HCW =
util::Xor(high0_0, high1_0);
977 bool LCW_0 = low0_0 ^ low1_0 ^ !a_n;
978 bool LCW_1 = low0_1 ^ low1_1 ^ a_n;
995 bool lcw_an = a_n ? LCW_1 : LCW_0;
997 if (t0) leaf0 =
util::Xor(leaf0, leaf_cw);
998 if (t1) leaf1 =
util::Xor(leaf1, leaf_cw);
1004 writer.Finish(v_cw.Into());
1008template <
int in_bits,
typename Group,
typename Hash,
typename In>
1010 Groupable<Group> && CcrHashable<Hash>)
1012 bool b, int4 s0, cuda::std::span<const int4> cws, In tr, In x,
size_t stride) {
1013 util::RequireArg(stride > 0,
"HalfTreeDpf::Eval: stride must be positive");
1014 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
1015 util::RequireArg(stride <= (kMaxElements - 1) /
static_cast<size_t>(in_bits),
1016 "HalfTreeDpf::Eval: stride exceeds the correction array limit");
1017 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
1019 "HalfTreeDpf::Eval: cws must hold in_bits*stride+1 elements");
1020 const int4 terminal = cws[
static_cast<size_t>(in_bits) * stride];
1022 "HalfTreeDpf::Eval: the terminal group value must have a zero LSB");
1023 return EvalWalk(b, s0, CwsView{cws.data(), tr, stride}, x);
1026template <
int in_bits,
typename Group,
typename Hash,
typename In>
1029template <typename Cws>
1030__host__ __device__
inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::EvalWalk(
1031 bool b, int4 s0, Cws cws, In x) {
1034 for (
int i = 0; i < in_bits - 1; ++i) {
1035 bool take_right = (x >> (in_bits - 1 - i)) & 1;
1036 node = PathStep(hash_, instance_seed_, node, cws[i].s, take_right);
1040 const Cw last_cw = cws[in_bits - 1];
1041 const int4 terminal = cws[in_bits].s;
1042 return ConvertLeaf(b, hash_, instance_seed_, node, (x & 1) != 0,
1046template <
int in_bits,
typename Group,
typename Hash,
typename In>
1048 Groupable<Group> && CcrHashable<Hash>)
1050 cuda::std::span<const int4> cws, In tr, int4 ys[],
int par_depth,
size_t stride) {
1051 if (stride == 0)
throw std::invalid_argument(
"HalfTreeDpf::EvalAll: stride must be positive");
1052 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
1053 if (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits))
1054 throw std::invalid_argument(
"HalfTreeDpf::EvalAll: stride exceeds the correction array limit");
1055 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
1056 if (cws.size() < required)
1057 throw std::invalid_argument(
"HalfTreeDpf::EvalAll: cws must hold in_bits*stride+1 elements");
1058 EvalAllCws(b, s0, CwsView{cws.data(), tr, stride}, ys, par_depth);
1061template <
int in_bits,
typename Group,
typename Hash,
typename In>
1064template <typename Cws>
1065inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalAllCws(
1066 bool b, int4 s0, Cws cws, int4 ys[],
int par_depth) {
1067 assert(par_depth >= 0);
1068 static_assert(detail::FitsFullDomain<int4, in_bits>(),
1069 "full-domain output count and byte size must fit in size_t");
1075 "HalfTreeDpf::EvalAll: the terminal group value must have a zero LSB");
1077 if constexpr (in_bits < kBatchPrefix + 1) {
1080 constexpr size_t n =
size_t{1} << in_bits;
1081 fss::detail::ParallelFor(par_depth, n,
1082 [&](
size_t x) { ys[x] = EvalWalk(b, s0, cws,
static_cast<In
>(x)); });
1088 int par_depth_ = fss::detail::ResolveParDepth(par_depth, in_bits);
1090 int split_depth = par_depth_;
1091 if (split_depth > in_bits - kBatchPrefix - 1) split_depth = in_bits - kBatchPrefix - 1;
1092 if (split_depth > 0) {
1095 size_t num_tasks =
size_t{1} << split_depth;
1096 std::vector<int4> frontier(num_tasks);
1098 for (
int lv = 0; lv < split_depth; ++lv) ExpandFrontier(frontier.data(),
size_t{1} << lv, cws, lv);
1099 size_t span =
size_t{1} << (in_bits - split_depth);
1100 fss::detail::ParallelFor(par_depth_, num_tasks, [&](
size_t task) {
1101 EvalBatched8(b, frontier[task], cws, Group::From(cws[in_bits].s), ys, task * span, split_depth);
1104 EvalBatched8(b, node, cws, Group::From(cws[in_bits].s), ys, 0, 0);
1108template <
int in_bits,
typename Group,
typename Hash,
typename In>
1110 Groupable<Group> && CcrHashable<Hash>)
1112 int4 s0, cuda::std::span<const int4> cws, In tr, cuda::std::span<int4> ys,
1113 int par_depth,
size_t stride) {
1114 if (stride == 0)
throw std::invalid_argument(
"HalfTreeDpf::EvalRange: stride must be positive");
1115 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
1116 if (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits))
1117 throw std::invalid_argument(
"HalfTreeDpf::EvalRange: stride exceeds the correction array limit");
1118 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
1119 if (cws.size() < required || cws.data() ==
nullptr)
1120 throw std::invalid_argument(
"HalfTreeDpf::EvalRange: cws must hold in_bits*stride+1 elements");
1121 if (ys.data() ==
nullptr)
1122 throw std::invalid_argument(
"HalfTreeDpf::EvalRange: ys must not be null");
1123 EvalRangeCws(b, first, last, s0,
1124 CwsView{cws.data(), tr, stride}, ys, par_depth);
1127template <
int in_bits,
typename Group,
typename Hash,
typename In>
1130template <typename Cws>
1131inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalRangeCws(
1132 bool b, In first, In last, int4 s0, Cws cws,
1133 cuda::std::span<int4> ys,
int par_depth) {
1134 assert(par_depth >= 0);
1135 In domain_last =
static_cast<In
>(~In{0});
1136 if constexpr (in_bits <
sizeof(In) * 8) {
1137 domain_last =
static_cast<In
>((In{1} << in_bits) - 1);
1140 throw std::invalid_argument(
"HalfTreeDpf::EvalRange: first must not exceed last");
1141 if (last > domain_last)
1142 throw std::invalid_argument(
"HalfTreeDpf::EvalRange: last must stay inside the input domain");
1143 const __uint128_t distance =
static_cast<__uint128_t
>(last) - first;
1144 if (distance >= std::numeric_limits<size_t>::max() /
sizeof(int4))
1145 throw std::invalid_argument(
"HalfTreeDpf::EvalRange: the interval is too large for size_t");
1146 const size_t count =
static_cast<size_t>(distance) + 1;
1147 if (ys.size() < count || ys.data() ==
nullptr)
1148 throw std::invalid_argument(
"HalfTreeDpf::EvalRange: ys must hold last-first+1 entries");
1150 "HalfTreeDpf::EvalRange: the terminal group value must have a zero LSB");
1152 const size_t partitions = detail::ResolveParPartitions(par_depth, count);
1153 detail::ParallelFor(par_depth, partitions, [&](
size_t partition) {
1154 const size_t begin = partition * (count / partitions) +
1155 (partition < count % partitions ? partition : count % partitions);
1156 const size_t end = begin + count / partitions + (partition < count % partitions);
1158 static_cast<In
>(first + begin),
static_cast<In
>(first + (end - 1)),
1159 first, In{0}, domain_last, 0);
1163template <
int in_bits,
typename Group,
typename Hash,
typename In>
1165 Groupable<Group> && CcrHashable<Hash>)
1166template <typename Cws>
1167inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalRangeTree(
1168 bool b, int4 node, Cws cws, int4 ys[], In first, In last,
1169 In output_first, In low, In high,
int level) {
1170 if (level == in_bits - 1) {
1173 const Cw last_cw = cws[level];
1174 const int4 terminal = cws[in_bits].s;
1176 ys[
static_cast<size_t>(low - output_first)] = ConvertLeaf(
1177 b, hash_, instance_seed_, node,
false, last_cw, terminal);
1180 ys[
static_cast<size_t>(high - output_first)] = ConvertLeaf(
1181 b, hash_, instance_seed_, node,
true, last_cw, terminal);
1185 const In middle =
static_cast<In
>(low + ((high - low) >> 1));
1187 ExpandNode(hash_, instance_seed_, node, cws[level].s, left, right);
1188 if (first <= middle) {
1189 EvalRangeTree(b, left, cws, ys, first, last, output_first, low, middle, level + 1);
1191 if (last > middle) {
1192 EvalRangeTree(b, right, cws, ys, first, last, output_first,
1193 static_cast<In
>(middle + 1), high, level + 1);
1197template <
int in_bits,
typename Group,
typename Hash,
typename In>
1199 Groupable<Group> && CcrHashable<Hash>)
1200template <typename Cws>
1201inline void HalfTreeDpf<in_bits, Group, Hash, In>::ExpandFrontier(
1202 int4 frontier[],
size_t w, Cws cws,
int lv) {
1203 int4 cw = cws[lv].s;
1204 for (
size_t j = w; j-- > 0;) {
1205 int4 node = frontier[j];
1207 ExpandNode(hash_, instance_seed_, node, cw, left, right);
1208 frontier[2 * j] = left;
1209 frontier[2 * j + 1] = right;
1213template <
int in_bits,
typename Group,
typename Hash,
typename In>
1215 Groupable<Group> && CcrHashable<Hash>)
1216inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::MaskXor(int4 val, int4 cw,
int m) {
1217 return {val.x ^ (cw.x & m), val.y ^ (cw.y & m), val.z ^ (cw.z & m), val.w ^ (cw.w & m)};
1220template <
int in_bits,
typename Group,
typename Hash,
typename In>
1222 Groupable<Group> && CcrHashable<Hash>)
1223template <typename Cws>
1224inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalBatched8(
1225 bool b, int4 node, Cws cws, Group ocw_group, int4 ys[],
size_t l,
int i) {
1228 for (
int lv = i; lv < i + kBatchPrefix; ++lv) ExpandFrontier(frontier,
size_t{1} << (lv - i), cws, lv);
1229 EvalBatched8Group(b, frontier, cws, ocw_group, ys, l, i + kBatchPrefix);
1232template <
int in_bits,
typename Group,
typename Hash,
typename In>
1234 Groupable<Group> && CcrHashable<Hash>)
1235template <typename Cws>
1236inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalBatched8Group(
1237 bool b,
const int4 (&frontier)[8], Cws cws, Group ocw_group, int4 ys[],
1239 if (i == in_bits - 1) {
1243 int lcw_1 = cws[in_bits - 1].extra;
1245 cuda::std::array<int4, 8> seeds0;
1246 cuda::std::array<int4, 8> seeds1;
1248 for (
int j = 0; j < 8; ++j) {
1253 auto h0s = detail::HashBatch(hash_, seeds0);
1254 auto h1s = detail::HashBatch(hash_, seeds1);
1255 for (
int j = 0; j < 8; ++j) {
1258 int4 high0 = MaskXor(h0, hcw, m);
1260 int low0 = (h0.w & 1) ^ (ts[j] & lcw_0);
1262 int4 high1 = MaskXor(h1, hcw, m);
1264 int low1 = (h1.w & 1) ^ (ts[j] & lcw_1);
1266 auto y0 = Group::From(high0);
1267 if (low0) y0 = y0 + ocw_group;
1269 ys[l + 2 * j] = y0.Into();
1271 auto y1 = Group::From(high1);
1272 if (low1) y1 = y1 + ocw_group;
1274 ys[l + 2 * j + 1] = y1.Into();
1280 cuda::std::array<int4, 8> seeds;
1282 for (
int j = 0; j < 8; ++j) {
1284 seeds[j] =
util::Xor(instance_seed_, frontier[j]);
1286 auto out = detail::HashBatch(hash_, seeds);
1291 for (
int j = 0; j < 4; ++j) {
1293 int4 left = MaskXor(out[j], cw, m);
1295 low[2 * j + 1] =
util::Xor(left, frontier[j]);
1296 int m4 = -ts[j + 4];
1297 int4 left4 = MaskXor(out[j + 4], cw, m4);
1298 high[2 * j] = left4;
1299 high[2 * j + 1] =
util::Xor(left4, frontier[j + 4]);
1302 size_t half =
size_t{8} << (in_bits - i - 1);
1303 EvalBatched8Group(b, low, cws, ocw_group, ys, l, i + 1);
1304 EvalBatched8Group(b, high, cws, ocw_group, ys, l + half, i + 1);
1307template <
int in_bits,
typename Group,
typename Hash,
typename In>
1309 Groupable<Group> && CcrHashable<Hash>)
1310__host__ __device__
inline void HalfTreeDpf<in_bits, Group, Hash, In>::ExpandNode(
1311 Hash &hash, int4 instance_seed, int4 node, int4 cw, int4 &left, int4 &right) {
1312 int4 hashed = hash.Hash(
util::Xor(instance_seed, node));
1313 const int4 zero = {0, 0, 0, 0};
1318template <
int in_bits,
typename Group,
typename Hash,
typename In>
1320 Groupable<Group> && CcrHashable<Hash>)
1321__host__ __device__
inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::PathStep(
1322 Hash &hash, int4 instance_seed, int4 node, int4 cw,
bool take_right) {
1323 const int4 zero = {0, 0, 0, 0};
1324 int4 hashed = hash.Hash(
util::Xor(instance_seed, node));
1329template <
int in_bits,
typename Group,
typename Hash,
typename In>
1331 Groupable<Group> && CcrHashable<Hash>)
1332template <typename OutputCw>
1333__host__ __device__
inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::ConvertLeaf(
bool party, Hash &hash,
1334 int4 instance_seed, int4 node,
bool take_right,
const Cw &last_cw,
1335 OutputCw &output_cw) {
1337 return ConvertHashedLeaf(party, hashed,
util::GetLsb(node), take_right,
1338 last_cw, output_cw);
1341template <
int in_bits,
typename Group,
typename Hash,
typename In>
1343 Groupable<Group> && CcrHashable<Hash>)
1344template <typename OutputCw>
1345__host__ __device__
inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::ConvertHashedLeaf(
bool party, int4 hashed,
1346 bool node_control,
bool take_right,
const Cw &last_cw,
1347 OutputCw &output_cw) {
1352 low ^= take_right ? last_cw.extra :
util::GetLsb(last_cw.s);
1354 auto value = Group::From(high);
1356 if constexpr (std::is_same_v<std::remove_cv_t<OutputCw>, int4>) {
1357 value = value + Group::From(output_cw);
1359 value = value + output_cw;
1362 if (party) value = -value;
1363 return value.Into();
1366template <
int in_bits,
typename Group,
typename Hash,
typename In>
1368 Groupable<Group> && CcrHashable<Hash>)
1369template <typename OutputCw>
1370__host__ __device__
inline void HalfTreeDpf<in_bits, Group, Hash, In>::ConvertLeafPair(
bool party, Hash &hash,
1371 int4 instance_seed, int4 node,
const Cw &last_cw,
1372 OutputCw &output_cw, int4 &left, int4 &right) {
1376 left = ConvertHashedLeaf(party, left_hash, control,
false, last_cw, output_cw);
1377 right = ConvertHashedLeaf(party, right_hash, control,
true, last_cw, output_cw);
1380namespace gpu::detail {
1382template <
int in_bits,
typename Group,
typename Hash,
typename In>
1383 requires(ValidForInputBits<In, in_bits> &&
1384 Groupable<Group> && CcrHashable<Hash>)
1385struct HalfTreeGpuTraits<HalfTreeDpf<in_bits, Group, Hash, In>> {
1386 using Backend = Hash;
1387 using OutputGroup = Group;
1389 static constexpr int kExpansionDepth = in_bits - 1;
1390 static constexpr int kOutputBits = in_bits;
1391 static constexpr bool kPacked =
false;
1394#if defined(__CUDACC__)
1401template <
int in_bits,
int unroll,
bool b,
typename Scheme,
typename In,
typename Query = In>
1402__global__
void HalfTreeDpfEvalPointKernel(int4 *ys,
const int4 *seeds,
const int4 *cws,
const In *tr,
1403 const Query *xs,
int nkeys,
typename HalfTreeGpuTraits<Scheme>::Backend hash, int4 instance_seed) {
1404 using Group =
typename HalfTreeGpuTraits<Scheme>::OutputGroup;
1405 static_assert(in_bits <=
sizeof(In) * 8,
"In too narrow for in_bits");
1406 int tid = blockIdx.x * blockDim.x + threadIdx.x;
1407 if (tid >= nkeys)
return;
1409 In x =
static_cast<In
>(xs[tid]);
1412#pragma unroll unroll
1413 for (
int i = 0; i < in_bits - 1; ++i) {
1414 bool x_bit = GetBit(x, in_bits - 1 - i);
1417 int4 cw = cws[(size_t)i * nkeys + tid];
1421 bool x_n = GetBit(x, 0);
1424 int4 cw = cws[(size_t)(in_bits - 1) * nkeys + tid];
1426 bool lcw_xn = x_n ? GetBit(tr_k, in_bits - 1) : fss::util::
GetLsb(cw);
1433 auto y = Group::From(high);
1434 if (low) y = y + Group::From(cws[
static_cast<size_t>(in_bits) * nkeys + tid]);
1435 if constexpr (b) y = -y;
1456template <
int unroll = 4,
int in_bits,
typename Group,
typename Hash,
typename In,
typename Query = In>
1457 requires QueryFor<Query, in_bits>
1458void HalfTreeDpfEvalPointGpu(
bool b, cuda::std::span<const int4> seeds, cuda::std::span<const int4> cws,
1459 cuda::std::span<const In> tr, cuda::std::span<const Query> xs, int4 *ys,
int nkeys,
1460 const HalfTreeDpf<in_bits, Group, Hash, In> &dpf, cudaStream_t stream = 0) {
1462 throw std::invalid_argument(
"HalfTreeDpfEvalPointGpu: nkeys must be nonnegative");
1463 if (nkeys == 0)
return;
1464 const size_t count =
static_cast<size_t>(nkeys);
1465 constexpr size_t kLevels =
static_cast<size_t>(in_bits) + 1;
1466 if (count > SIZE_MAX / kLevels || cws.size() < count * kLevels)
1467 throw std::invalid_argument(
"HalfTreeDpfEvalPointGpu: cws must hold (in_bits+1)*nkeys elements");
1468 if (seeds.size() < count || tr.size() < count || xs.size() < count || ys ==
nullptr)
1469 throw std::invalid_argument(
"HalfTreeDpfEvalPointGpu: seeds, tr, xs, and ys must cover nkeys entries");
1470 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
1471 constexpr int kBs = 256;
1472 int blocks = (nkeys + kBs - 1) / kBs;
1474 HalfTreeDpfEvalPointKernel<in_bits, unroll, true, Scheme, In, Query>
1475 <<<blocks, kBs, 0, stream>>>(ys, seeds.data(), cws.data(), tr.data(), xs.data(), nkeys,
1476 fss::detail::SchemeAccess::GetHash(dpf), fss::detail::SchemeAccess::GetInstanceSeed(dpf));
1478 HalfTreeDpfEvalPointKernel<in_bits, unroll, false, Scheme, In, Query>
1479 <<<blocks, kBs, 0, stream>>>(ys, seeds.data(), cws.data(), tr.data(), xs.data(), nkeys,
1480 fss::detail::SchemeAccess::GetHash(dpf), fss::detail::SchemeAccess::GetInstanceSeed(dpf));
1488template <
int in_bits,
typename Group,
typename Hash,
typename In>
1490 Groupable<Group> && CcrHashable<Hash>)
1491template <int frontier_depth, int threads_per_block>
1493struct HalfTreeDpf<in_bits, Group, Hash, In>::GpuLaunchShape {
1494 static_assert(frontier_depth >= 0,
"gpu frontier depth must be nonnegative");
1495 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
1496 (threads_per_block & (threads_per_block - 1)) == 0,
1497 "gpu block size must be a power of two in [1,1024]");
1498 static constexpr int kFrontierDepth = frontier_depth < in_bits - 1 ? frontier_depth : in_bits - 1;
1499 static_assert(in_bits - kFrontierDepth <= 8,
1500 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
1501 static constexpr int kThreadsPerBlockLog2 = [] {
1503 for (
int count = threads_per_block; count > 1; count >>= 1) ++depth;
1504 return depth < kFrontierDepth ? depth : kFrontierDepth;
1506 static constexpr int kThreadsPerBlock = 1 << kThreadsPerBlockLog2;
1507 static constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
1508 static_assert(kBlockRootDepth <= 30,
"gpu block index exceeds the signed-int range");
1509 static constexpr size_t kBlocksPerKey =
size_t{1} << (kBlockRootDepth <= 30 ? kBlockRootDepth : 0);
1512template <
int in_bits,
typename Group,
typename Hash,
typename In>
1514 Groupable<Group> && CcrHashable<Hash>)
1515template <int frontier_depth, int threads_per_block>
1517 bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws, In tr,
1518 cuda::std::span<int4> ys, cudaStream_t stream,
size_t stride)
const {
1519#if defined(__CUDACC__)
1521 static_assert(frontier_depth >= 0,
"gpu frontier depth must be nonnegative");
1522 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
1523 (threads_per_block & (threads_per_block - 1)) == 0,
1524 "gpu block size must be a power of two in [1,1024]");
1525 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
1527 if (!detail::RangeOutputCount<in_bits>(first, last, ys.size(), count) || stride == 0 ||
1528 (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits)) ||
1529 cws.size() <
static_cast<size_t>(in_bits) * stride + 1 ||
1530 cws.data() ==
nullptr || ys.data() ==
nullptr)
1531 return cudaErrorInvalidValue;
1532 return detail::SplitRangeChunks<in_bits, frontier_depth, threads_per_block, 1>(
1533 first, last, ys.size(),
1534 [&](In chunk_first,
int depth,
size_t offset,
int frontier,
int thread_bits,
int root,
1536 gpu::detail::HalfTreeSplitDpfEvalRangeKernel<Scheme, threads_per_block>
1537 <<<blocks, 1 << thread_bits, 0, stream>>>(b, s0, cws.data(), tr, stride,
1538 ys.data() + offset, chunk_first, depth, frontier, root, instance_seed_, hash_);
1539 return cudaGetLastError();
1542 static_assert(
sizeof(Hash) == 0,
"gpu launch requires a CUDA compiler");
1543 return cudaErrorNotSupported;
1547template <
int in_bits,
typename Group,
typename Hash,
typename In>
1550template <int frontier_depth, int threads_per_block>
1552 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
1553 cuda::std::span<int4> ys, cudaStream_t stream,
size_t stride)
const
1554 requires(in_bits <
sizeof(size_t) * 8) {
1555#if defined(__CUDACC__)
1556 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1558 constexpr size_t kOutputsPerKey =
size_t{1} << in_bits;
1559 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
1561 (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits)))
1562 return cudaErrorInvalidValue;
1563 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
1564 if (cws.size() < required || ys.size() < kOutputsPerKey ||
1565 cws.data() ==
nullptr || ys.data() ==
nullptr ||
1566 kOutputsPerKey > std::numeric_limits<size_t>::max() /
sizeof(int4))
1567 return cudaErrorInvalidValue;
1568 gpu::detail::HalfTreeSplitEvalAllKernel<Scheme, LaunchShape::kFrontierDepth,
1569 LaunchShape::kBlockRootDepth>
1570 <<<LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1571 b, s0, cws.data(), tr, stride, ys.data(), instance_seed_, hash_);
1572 return cudaGetLastError();
1574 static_assert(
sizeof(Hash) == 0,
"gpu launch requires a CUDA compiler");
1575 return cudaErrorNotSupported;
1579template <
int in_bits,
typename Group,
typename Hash,
typename In>
1582template <int frontier_depth, int threads_per_block>
1584 bool b, cuda::std::span<const int4> s0s, cuda::std::span<const int4> cws,
1585 cuda::std::span<const In> tr, cuda::std::span<int4> ys,
1586 cudaStream_t stream,
size_t stride)
const
1587 requires(in_bits <
sizeof(size_t) * 8) {
1588#if defined(__CUDACC__)
1589 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1591 constexpr size_t kOutputsPerKey =
size_t{1} << in_bits;
1592 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
1593 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
1594 const size_t key_count = s0s.size();
1595 if (key_count == 0)
return cudaSuccess;
1598 if (key_count > kMaxElements ||
1599 key_count > kMaxAddressableSize / kOutputsPerKey /
sizeof(int4) ||
1600 (stride != 0 && (stride < key_count || stride > kMaxElements)))
1601 return cudaErrorInvalidValue;
1603 const size_t resolved_stride = stride == 0 ? key_count : stride;
1604 if (resolved_stride > (kMaxElements - key_count) /
static_cast<size_t>(in_bits))
1605 return cudaErrorInvalidValue;
1606 const size_t required =
static_cast<size_t>(in_bits) * resolved_stride + key_count;
1607 if (cws.size() < required || tr.size() < key_count || ys.size() < key_count * kOutputsPerKey ||
1608 s0s.data() ==
nullptr || cws.data() ==
nullptr || tr.data() ==
nullptr ||
1609 ys.data() ==
nullptr)
1610 return cudaErrorInvalidValue;
1611 if (key_count >
static_cast<size_t>(std::numeric_limits<int>::max()) / LaunchShape::kBlocksPerKey)
1612 return cudaErrorInvalidConfiguration;
1613 gpu::detail::HalfTreeSplitEvalAllBatchKernel<Scheme, LaunchShape::kFrontierDepth,
1614 LaunchShape::kBlockRootDepth>
1615 <<<key_count * LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1616 b, s0s.data(), cws.data(), tr.data(), resolved_stride, ys.data(), instance_seed_, hash_);
1617 return cudaGetLastError();
1619 static_assert(
sizeof(Hash) == 0,
"gpu launch requires a CUDA compiler");
1620 return cudaErrorNotSupported;
1624#if FSS_ENABLE_V1_COMPAT
1631template <
int in_bits,
typename Group,
typename Hash,
typename In>
1635 Cw cws[], int4 &ocw,
const int4 s0s[2], In a, int4 b_buf) {
1636 int4 seed[in_bits + 1];
1638 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr,
1639 cuda::std::span<const int4, 2>(s0s, 2), a, b_buf);
1640 for (
int i = 0; i < in_bits - 1; ++i) cws[i] = {seed[i],
false};
1641 cws[in_bits - 1] = {seed[in_bits - 1], ((tr >> (in_bits - 1)) & 1) != 0};
1642 ocw = seed[in_bits];
1645template <
int in_bits,
typename Group,
typename Hash,
typename In>
1647 Groupable<Group> && CcrHashable<Hash>)
1649 bool b, int4 s0,
const Cw cws[], int4 ocw, In x) {
1650 int4 seed[in_bits + 1];
1652 for (
int i = 0; i < in_bits; ++i) seed[i] = cws[i].s;
1653 seed[in_bits] = ocw;
1654 tr |=
static_cast<In
>(cws[in_bits - 1].extra) << (in_bits - 1);
1655 return Eval(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, x);
1658template <
int in_bits,
typename Group,
typename Hash,
typename In>
1660 Groupable<Group> && CcrHashable<Hash>)
1662 bool b, int4 s0,
const Cw cws[], int4 ocw, int4 ys[]) {
1663 int4 seed[in_bits + 1];
1665 for (
int i = 0; i < in_bits; ++i) seed[i] = cws[i].s;
1666 seed[in_bits] = ocw;
1667 tr |=
static_cast<In
>(cws[in_bits - 1].extra) << (in_bits - 1);
1668 EvalAll(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys);
1674template <
int in_bits,
typename Group,
typename Hash,
typename In>
1676 Groupable<Group> && CcrHashable<Hash>)
1678 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k0,
1679 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k1,
1680 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf) {
1681 int4 seed[in_bits + 1];
1683 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr, s0s, a, b_buf);
1684 for (
int i = 0; i < in_bits - 1; ++i) {
1685 const Cw layer = {seed[i],
false};
1689 const Cw last = {seed[in_bits - 1], ((tr >> (in_bits - 1)) & 1) != 0};
1690 k0.cws[in_bits - 1] = last;
1691 k1.cws[in_bits - 1] = last;
1692 k0.ocw = seed[in_bits];
1693 k1.ocw = seed[in_bits];
1698template <
int in_bits,
typename Group,
typename Hash,
typename In>
1700 Groupable<Group> && CcrHashable<Hash>)
1702 bool b,
const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key, In x) {
1703 int4 seed[in_bits + 1];
1705 for (
int i = 0; i < in_bits; ++i) seed[i] = key.cws[i].s;
1706 seed[in_bits] = key.ocw;
1707 tr |=
static_cast<In
>(key.cws[in_bits - 1].extra) << (in_bits - 1);
1708 return Eval(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, x);
1711template <
int in_bits,
typename Group,
typename Hash,
typename In>
1713 Groupable<Group> && CcrHashable<Hash>)
1715 bool b,
const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key,
1716 cuda::std::span<int4> ys) {
1717 int4 seed[in_bits + 1];
1719 for (
int i = 0; i < in_bits; ++i) seed[i] = key.cws[i].s;
1720 seed[in_bits] = key.ocw;
1721 tr |=
static_cast<In
>(key.cws[in_bits - 1].extra) << (in_bits - 1);
1722 EvalAll(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys.data());
cudaError_t EvalAllGpu(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=1) const
Launch full-domain CUDA evaluation from split device arrays.
Definition half_tree_dpf.cuh:1551
int4 Eval(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, In x, size_t stride=1)
Evaluate one point from split Cws.
Definition half_tree_dpf.cuh:1011
void EvalAll(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, int4 ys[], int par_depth=0, size_t stride=1)
Evaluate the full domain from split Cws.
Definition half_tree_dpf.cuh:1049
void Gen(cuda::std::span< int4 > cws, In &tr, cuda::std::span< const int4, 2 > s0s, In a, int4 b_buf, size_t stride=1)
Generate split Cws from caller-owned initial seeds.
Definition half_tree_dpf.cuh:886
void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< int4 > ys, int par_depth=0, size_t stride=1)
Evaluate the closed input interval [first, last] from split Cws.
Definition half_tree_dpf.cuh:1111
cudaError_t EvalRangeGpu(bool b, In first, In last, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=1) const
Evaluate the closed interval [first, last] on the given CUDA stream from split device arrays.
Definition half_tree_dpf.cuh:1516
cudaError_t EvalAllGpuBatch(bool b, cuda::std::span< const int4 > s0s, cuda::std::span< const int4 > cws, cuda::std::span< const In > tr, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=0) const
Launch full-domain CUDA evaluation for s0s.size() keys from split device arrays.
Definition half_tree_dpf.cuh:1583
In Input
The caller-facing input-domain type.
Definition half_tree_dpf.cuh:101
HalfTreeDpf(Hash hash, int4 instance_seed)
Configure the CCR hash and public instance seed S.
Definition half_tree_dpf.cuh:880
static constexpr int kCwCount
Seed-component count Gen() writes at stride 1: in_bits levels plus the terminal.
Definition half_tree_dpf.cuh:99
Single build-time switch for the deprecated v1 Cw-form compatibility entries.
Circular-correlation-robust (CCR) hash interface for HalfTree schemes.
Definition hash.cuh:86
Group interface.
Definition group.cuh:49
bool GetLsb(int4 val)
Return bit 0 of the fourth word of val.
Definition util.cuh:58
void RequireArg(bool ok, const char *msg)
Check a caller precondition.
Definition util.cuh:73
int4 SetLsb(int4 val, bool bit)
Return val with bit 0 of its fourth word set to bit.
Definition util.cuh:51
int4 Xor(int4 lhs, int4 rhs)
Return the bitwise XOR of two 128-bit blocks.
Definition util.cuh:29
Correction word.
Definition half_tree_dpf.cuh:92
int4 s
Seed correction word, or final HCW with LCW_0 in its least significant bit.
Definition half_tree_dpf.cuh:93
bool extra
Final tree-level LCW_1. Ignored in earlier levels and the output correction word.
Definition half_tree_dpf.cuh:94