36#include <cuda_runtime.h>
49#include <cuda/std/span>
55#if defined(__CUDACC__)
56#include <cub/device/device_scan.cuh>
62template <
typename Scheme>
74template <
typename Cw,
typename In>
75struct GrottoDcfCwsView {
82 __host__ __device__
inline Cw operator[](
int l)
const {
83 return Cw{cws[
static_cast<size_t>(l) * stride], ((packed_tr >> l) & 1) != 0};
101template <
int in_bits,
typename Prg,
typename In = u
int>
107 using Cw =
typename DpfType::Cw;
119 __host__ __device__
explicit GrottoDcf(Prg prg);
144 __host__ __device__
void Gen(cuda::std::span<int4> cws, In &tr,
145 cuda::std::span<const int4, 2> s0s, In a,
size_t stride = 1);
172 __host__ __device__
bool Eval(
bool b, int4 s0, cuda::std::span<const int4> cws,
173 In tr, In x,
size_t stride = 1);
217 void Preprocess(
bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
218 bool p[],
int par_depth = 0,
size_t stride = 1);
242 cuda::std::span<const int4> cws, In tr,
int par_depth = 0,
size_t stride = 1);
290 void EvalAll(
bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
291 bool ys[],
int par_depth = 0,
size_t stride = 1);
315 template <
int frontier_depth = 16,
int threads_per_block = 256>
316 __host__ cudaError_t
PreprocessGpu(
bool b, int4 s0, cuda::std::span<const int4> cws,
317 In tr, cuda::std::span<bool> tree, cudaStream_t stream = 0,
size_t stride = 1) const
318 requires(in_bits < sizeof(
size_t) * 8);
344 template <
int frontier_depth = 16,
int threads_per_block = 256>
345 __host__ cudaError_t
EvalAllGpu(
bool b, int4 s0, cuda::std::span<const int4> cws,
346 In tr, cuda::std::span<
bool> ys, cudaStream_t stream = 0,
size_t stride = 1) const
347 requires(in_bits < sizeof(
size_t) * 8);
362 cuda::std::span<
bool> ys, cudaStream_t stream = 0)
363 requires(in_bits < sizeof(
size_t) * 8);
365#if FSS_ENABLE_V1_COMPAT
377 [[deprecated(
"use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, size_t) overload")]]
378 __host__ __device__
void Gen(
Cw cws[],
const int4 s0s[2], In a);
385 using ParityTree [[deprecated(
"use ParityTreeView, which drops the party tag")]] =
386 compat::detail::ParityTreeRecord<GrottoDcf>;
393 using Key [[deprecated(
"use the split-component Gen/EvalAll/Preprocess with separate cws and tr arrays")]] =
394 compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1>;
401 using PreprocessedTree [[deprecated(
"use PreprocessOwned() and OwnedParityTree::View()")]] =
402 compat::detail::PreprocessedTreeRecord<GrottoDcf>;
405 [[deprecated(
"use the split-component Preprocess(bool, int4, cuda::std::span<const int4>, In, bool*, int, size_t) overload")]]
406 void Preprocess(compat::detail::ParityTreeRecord<GrottoDcf> &pt, int4 s0,
const Cw cws[]);
409 [[deprecated(
"use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, size_t) overload")]]
410 __host__ __device__
void Gen(compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &k0,
411 compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &k1,
412 cuda::std::span<const int4, 2> s0s, In a);
415 [[deprecated(
"use the split-component PreprocessOwned(bool, int4, cuda::std::span<const int4>, In, int, size_t)")]]
416 compat::detail::PreprocessedTreeRecord<GrottoDcf>
Preprocess(
417 bool b,
const compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &key);
420 [[deprecated(
"use the ParityTreeView Eval(ParityTreeView, In) overload")]]
421 __host__ __device__
static bool Eval(
const compat::detail::ParityTreeRecord<GrottoDcf> &pt, In x);
424 [[deprecated(
"use OwnedParityTree::View() and the ParityTreeView Eval(ParityTreeView, In) overload")]]
425 static bool Eval(
const compat::detail::PreprocessedTreeRecord<GrottoDcf> &tree, In x);
428 [[deprecated(
"use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, bool[], int, size_t) overload")]]
429 void EvalAll(
bool b, int4 s0,
const Cw cws[],
bool ys[]);
432 [[deprecated(
"use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, bool[], int, size_t) overload")]]
433 void EvalAll(
bool b,
const compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &key,
434 cuda::std::span<bool> ys);
438 friend class detail::SchemeAccess;
444 using CwsView = detail::GrottoDcfCwsView<Cw, In>;
447 template <
typename Cws>
448 __host__ __device__
bool EvalCws(
bool b, int4 s0, Cws cws, In x);
451 template <
typename Cws>
452 void PreprocessCws(
bool b, int4 s0, Cws cws,
bool p[],
int par_depth);
455 template <
typename Cws>
456 void EvalAllCws(
bool b, int4 s0, Cws cws,
bool ys[],
int par_depth);
459 template <
typename Cws>
461 bool b, int4 s0, Cws cws,
int par_depth);
464 template <
typename Cws>
465 void ExpandTree(
bool b, int4 s0, Cws cws,
bool t[],
int par_depth);
467 template <
typename Cws>
468 void ExpandTreeRec(int4 st, Cws cws,
bool t[],
size_t l,
size_t r,
int i,
int par_depth_);
479template <
typename Scheme>
489 typename Scheme::ParityTreeView
View()
const &;
492 typename Scheme::ParityTreeView
View() const && = delete;
495 std::unique_ptr<
bool[]> p_;
500namespace gpu::detail {
504template <
int tree_depth,
int frontier_depth,
int threads_per_block>
505struct GrottoGpuLaunchShape {
506 static_assert(frontier_depth >= 0,
"gpu frontier depth must be nonnegative");
507 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
508 (threads_per_block & (threads_per_block - 1)) == 0,
509 "gpu block size must be a power of two in [1,1024]");
510 static constexpr int kFrontierDepth = frontier_depth < tree_depth ? frontier_depth : tree_depth;
511 static_assert(tree_depth - kFrontierDepth <= 8,
512 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
513 static constexpr int kThreadsPerBlockLog2 = [] {
515 for (
int count = threads_per_block; count > 1; count >>= 1) ++log2;
516 return log2 < kFrontierDepth ? log2 : kFrontierDepth;
518 static constexpr int kThreadsPerBlock = 1 << kThreadsPerBlockLog2;
519 static constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
520 static_assert(kBlockRootDepth <= 30,
"gpu block index exceeds the signed-int range");
521 static constexpr size_t kBlocks =
size_t{1} << (kBlockRootDepth <= 30 ? kBlockRootDepth : 0);
524#if defined(__CUDACC__)
526struct GrottoGpuBoolXor {
527 __host__ __device__
bool operator()(
bool lhs,
bool rhs)
const {
return lhs != rhs; }
530static __global__
void GrottoUnpackBitsKernel(
const int4 *blocks,
bool *leaves,
size_t count) {
531 size_t i =
static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
532 if (i >= count)
return;
533 int4 block = blocks[i / 128];
534 unsigned word = (i % 128) / 32;
535 unsigned value = word == 0 ?
static_cast<unsigned>(block.x) :
536 word == 1 ?
static_cast<unsigned>(block.y) : word == 2 ?
static_cast<unsigned>(block.z) :
static_cast<unsigned>(block.w);
537 leaves[i] = ((value >> (i % 32)) & 1u) != 0;
540inline cudaError_t GrottoUnpackGrid(
size_t count,
int threads,
int &grid) {
541 const size_t blocks = count / threads + (count % threads != 0);
542 if (blocks >
static_cast<size_t>(std::numeric_limits<int>::max()))
543 return cudaErrorInvalidConfiguration;
544 grid =
static_cast<int>(blocks);
551template <
int input_bits,
int frontier_depth,
int block_root_depth,
typename Dpf,
typename Prg,
553__device__
void GrottoSplitLeafBitsWork(
bool party, int4 s0,
const Cws &view,
bool *leaves,
555 constexpr int kTailDepth = input_bits - frontier_depth;
556 constexpr int kBlockSubtreeDepth = frontier_depth - block_root_depth;
557 const int64_t thread_index =
static_cast<int64_t
>(blockIdx.x) * blockDim.x + threadIdx.x;
558 __shared__ int4 shared_frontier[1 << kBlockSubtreeDepth];
559 if (threadIdx.x == 0) {
562 for (
int level = 0; level < block_root_depth; ++level) {
563 bool take_right = (blockIdx.x >> (block_root_depth - 1 - level)) & 1;
565 const auto cw = view[level];
566 state = fss::detail::SchemeAccess::DpfPathStep<Dpf>(
567 node_prg, state, cw.s, cw.tr, take_right);
569 shared_frontier[0] = state;
574 for (
int level = 0; level < kBlockSubtreeDepth; ++level) {
575 const bool active = threadIdx.x < (1 << (level + 1));
576 int4 state = {0, 0, 0, 0};
577 if (active) state = shared_frontier[threadIdx.x >> 1];
583 const auto cw = view[block_root_depth + level];
584 fss::detail::SchemeAccess::DpfExpandNode<Dpf>(node_prg, state, cw.s, cw.tr, left, right);
586 shared_frontier[threadIdx.x] = (threadIdx.x & 1) ? right : left;
591 int4 nodes[1 << kTailDepth];
592 nodes[0] = shared_frontier[threadIdx.x];
595 for (
int level = frontier_depth; level < input_bits; ++level) {
597 for (
int i = count - 1; i >= 0; --i) {
601 const auto cw = view[level];
602 fss::detail::SchemeAccess::DpfExpandNode<Dpf>(node_prg, nodes[i], cw.s, cw.tr, left, right);
605 nodes[2 * i + 1] = right;
610 const size_t output_offset =
static_cast<size_t>(thread_index) << kTailDepth;
612 for (
int i = 0; i < count; ++i) leaves[output_offset + i] =
util::GetLsb(nodes[i]);
614template <
int input_bits,
int frontier_depth,
int block_root_depth,
typename Dpf,
typename Prg,
616__global__
void GrottoSplitLeafBitsKernel(
bool party, int4 s0,
const void *cws_buf,
617 typename Cws::Control packed_tr,
size_t stride,
bool *leaves, Prg prg) {
618 const auto *cws =
static_cast<const int4 *
>(cws_buf);
619 GrottoSplitLeafBitsWork<input_bits, frontier_depth, block_root_depth, Dpf, Prg, Cws>(
620 party, s0, Cws{cws, packed_tr, stride}, leaves, prg);
625template <
int input_bits,
int frontier_depth,
int block_root_depth,
typename Dpf,
typename Prg,
627__global__
void GrottoSplitLeafBitsDeviceKeyKernel(
bool party,
const int4 *s0,
628 const void *cws_buf,
const typename Cws::Control *tr,
size_t stride,
bool *leaves, Prg prg) {
629 const auto *cws =
static_cast<const int4 *
>(cws_buf);
630 GrottoSplitLeafBitsWork<input_bits, frontier_depth, block_root_depth, Dpf, Prg, Cws>(
631 party, s0[0], Cws{cws, tr[0], stride}, leaves, prg);
635static __global__
void GrottoParityLevelKernel(
bool *tree,
size_t count) {
636 size_t i =
static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
637 if (i >= count)
return;
638 size_t index = count - 1 + i;
639 tree[index] = tree[2 * index + 1] ^ tree[2 * index + 2];
642template <
int top_level>
643__global__
void GrottoParityTopKernel(
bool *tree) {
645 for (
int level = top_level; level >= 0; --level) {
646 int count = 1 << level;
647 if (threadIdx.x < count) {
648 int index = count - 1 + threadIdx.x;
649 tree[index] = tree[2 * index + 1] ^ tree[2 * index + 2];
655static __global__
void GrottoPackedParityKernel(
const int4 *blocks,
bool *leaves,
size_t count) {
656 size_t i =
static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
657 if (i >= count)
return;
658 int4 block = blocks[i];
659 unsigned value =
static_cast<unsigned>(block.x) ^
static_cast<unsigned>(block.y) ^
660 static_cast<unsigned>(block.z) ^
static_cast<unsigned>(block.w);
661 leaves[i] = (__popc(value) & 1) != 0;
664template <
int leaf_bits,
typename LaunchShape>
665inline cudaError_t BuildGrottoParityTree(
bool *tree, cudaStream_t stream) {
666 constexpr int kThreads = LaunchShape::kThreadsPerBlock;
667 constexpr int kTop = LaunchShape::kThreadsPerBlockLog2 < leaf_bits - 1
668 ? LaunchShape::kThreadsPerBlockLog2 : leaf_bits - 1;
669 for (
int level = leaf_bits - 1; level > kTop; --level) {
670 size_t count =
size_t{1} << level;
671 size_t grid = count / kThreads + (count % kThreads != 0);
672 GrottoParityLevelKernel<<<grid, kThreads, 0, stream>>>(tree, count);
673 cudaError_t status = cudaGetLastError();
674 if (status != cudaSuccess)
return status;
676 if constexpr (leaf_bits > 0) {
677 GrottoParityTopKernel<kTop><<<1, kThreads, 0, stream>>>(tree);
678 return cudaGetLastError();
687template <
int in_bits,
typename Prg,
typename In>
692template <
int in_bits,
typename Prg,
typename In>
695 In &tr, cuda::std::span<const int4, 2> s0s, In a,
size_t stride) {
698 DpfType{prg_}.Gen(cws, tr, s0s, a, int4{0, 0, 0, 0}, stride);
701template <
int in_bits,
typename Prg,
typename In>
704 cuda::std::span<const int4> cws, In tr, In x,
size_t stride) {
706 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
707 util::RequireArg(stride <= (kMaxElements - 1) /
static_cast<size_t>(in_bits),
708 "GrottoDcf::Eval: stride exceeds the correction array limit");
709 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
711 "GrottoDcf::Eval: cws must hold in_bits*stride+1 elements");
712 return EvalCws(b, s0, CwsView{cws.data(), tr, stride}, x);
715template <
int in_bits,
typename Prg,
typename In>
717template <typename Cws>
718__host__ __device__
inline bool GrottoDcf<in_bits, Prg, In>::EvalCws(
719 bool b, int4 s0, Cws cws, In x) {
720 detail::GrottoTurns turns;
721 int4 leaf = DpfType{prg_}.Walk(b, s0, cws, x, turns);
725template <
int in_bits,
typename Prg,
typename In>
726 requires(ValidForInputBits<In, in_bits> && Prgable<Prg, 2>)
728 cuda::std::span<const int4> cws, In tr,
bool p[],
int par_depth,
size_t stride) {
729 if (stride == 0)
throw std::invalid_argument(
"GrottoDcf::Preprocess: stride must be positive");
730 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
731 if (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits))
732 throw std::invalid_argument(
"GrottoDcf::Preprocess: stride exceeds the correction array limit");
733 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
734 if (cws.size() < required)
735 throw std::invalid_argument(
"GrottoDcf::Preprocess: cws must hold in_bits*stride+1 elements");
736 PreprocessCws(b, s0, CwsView{cws.data(), tr, stride}, p, par_depth);
739template <
int in_bits,
typename Prg,
typename In>
741template <typename Cws>
742inline void GrottoDcf<in_bits, Prg, In>::PreprocessCws(
743 bool b, int4 s0, Cws cws,
bool p[],
int par_depth) {
744 assert(par_depth >= 0);
745 static_assert(detail::FitsBinaryTree<bool, in_bits>(),
746 "parity-tree count and byte size must fit in size_t");
747 constexpr size_t n = detail::FullDomainSize<bool, in_bits>();
748 ExpandTree(b, s0, cws, p + n - 1, par_depth);
749 for (
size_t j = n - 2; j < n - 1; --j) {
750 p[j] = p[2 * j + 1] ^ p[2 * j + 2];
754template <
int in_bits,
typename Prg,
typename In>
755 requires(ValidForInputBits<In, in_bits> && Prgable<Prg, 2>)
757 static_assert(detail::FitsBinaryTree<bool, in_bits>(),
758 "parity-tree count and byte size must fit in size_t");
759 constexpr size_t N = detail::FullDomainSize<bool, in_bits>();
760 In e =
static_cast<In
>(x) + 1;
763 if (e == 0 || e == N)
return pt.
p[0];
767 for (
int i = 0; i < in_bits; ++i) {
768 bool e_bit = (e >> (in_bits - 1 - i)) & 1;
770 pi ^= pt.
p[2 * cur + 1];
779template <
int in_bits,
typename Prg,
typename In>
782 static_assert(detail::FitsBinaryTree<bool, in_bits>(),
783 "parity-tree count and byte size must fit in size_t");
784 constexpr size_t n = detail::FullDomainSize<bool, in_bits>();
786 for (
size_t x = 0; x < n; ++x) {
787 prefix ^= pt.
p[n - 1 + x];
792template <
int in_bits,
typename Prg,
typename In>
795 cuda::std::span<const int4> cws, In tr,
bool ys[],
int par_depth,
size_t stride) {
796 if (stride == 0)
throw std::invalid_argument(
"GrottoDcf::EvalAll: stride must be positive");
797 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
798 if (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits))
799 throw std::invalid_argument(
"GrottoDcf::EvalAll: stride exceeds the correction array limit");
800 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
801 if (cws.size() < required)
802 throw std::invalid_argument(
"GrottoDcf::EvalAll: cws must hold in_bits*stride+1 elements");
803 EvalAllCws(b, s0, CwsView{cws.data(), tr, stride}, ys, par_depth);
806template <
int in_bits,
typename Prg,
typename In>
808template <typename Cws>
809inline void GrottoDcf<in_bits, Prg, In>::EvalAllCws(
810 bool b, int4 s0, Cws cws,
bool ys[],
int par_depth) {
811 assert(par_depth >= 0);
812 constexpr size_t N = detail::FullDomainSize<bool, in_bits>();
815 ExpandTree(b, s0, cws, ys, par_depth);
820 for (
size_t x = 1; x < N; ++x) {
821 ys[x] = ys[x] ^ ys[x - 1];
825template <
int in_bits,
typename Prg,
typename In>
826 requires(ValidForInputBits<In, in_bits> && Prgable<Prg, 2>)
827template <typename Cws>
828inline void GrottoDcf<in_bits, Prg, In>::ExpandTree(
829 bool b, int4 s0, Cws cws,
bool t[],
int par_depth) {
834 constexpr size_t r = detail::FullDomainSize<bool, in_bits>();
837 int par_depth_ = fss::detail::ResolveParDepth(par_depth, in_bits);
839 if (par_depth_ == 0) {
840 ExpandTreeRec(st, cws, t, l, r, i, 0);
843 int threads = fss::detail::ResolveParThreads(par_depth_, r);
844#pragma omp parallel num_threads(threads)
846 ExpandTreeRec(st, cws, t, l, r, i, par_depth_);
849template <
int in_bits,
typename Prg,
typename In>
850 requires(ValidForInputBits<In, in_bits> && Prgable<Prg, 2>)
851template <typename Cws>
852inline void GrottoDcf<in_bits, Prg, In>::ExpandTreeRec(
853 int4 st, Cws cws,
bool t[],
size_t l,
size_t r,
int i,
int par_depth_) {
864 const Cw cw = cws[i];
870 auto [sl, sr] = prg_.Gen(s);
889 size_t mid = (l + r) / 2;
891 if (i < par_depth_) {
893 ExpandTreeRec(stl, cws, t, l, mid, i + 1, par_depth_);
895 ExpandTreeRec(str, cws, t, mid, r, i + 1, par_depth_);
898 ExpandTreeRec(stl, cws, t, l, mid, i + 1, par_depth_);
899 ExpandTreeRec(str, cws, t, mid, r, i + 1, par_depth_);
903template <
int in_bits,
typename Prg,
typename In>
904 requires(ValidForInputBits<In, in_bits> && Prgable<Prg, 2>)
905template <int frontier_depth, int threads_per_block>
907 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
908 cuda::std::span<bool> tree, cudaStream_t stream,
size_t stride)
const
909 requires(in_bits <
sizeof(size_t) * 8) {
910#if defined(__CUDACC__)
911 using LaunchShape = gpu::detail::GrottoGpuLaunchShape<in_bits, frontier_depth, threads_per_block>;
912 constexpr size_t kLeaves =
size_t{1} << in_bits;
913 constexpr size_t kTreeSize = kLeaves + (kLeaves - 1);
914 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
915 if (!detail::FitsBinaryTree<bool, in_bits>() || stride == 0 ||
916 (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits)) ||
917 cws.size() <
static_cast<size_t>(in_bits) * stride + 1 ||
918 tree.size() < kTreeSize || cws.data() ==
nullptr || tree.data() ==
nullptr)
919 return cudaErrorInvalidValue;
920 constexpr size_t kParityWidth = kLeaves / 2;
921 constexpr size_t kParityBlocks = kParityWidth / LaunchShape::kThreadsPerBlock +
922 (kParityWidth % LaunchShape::kThreadsPerBlock != 0);
923 if (kParityBlocks >
static_cast<size_t>(std::numeric_limits<int>::max()))
924 return cudaErrorInvalidConfiguration;
925 gpu::detail::GrottoSplitLeafBitsKernel<in_bits, LaunchShape::kFrontierDepth,
926 LaunchShape::kBlockRootDepth, DpfType, Prg, CwsView>
927 <<<LaunchShape::kBlocks, LaunchShape::kThreadsPerBlock, 0, stream>>>(
928 b, s0, cws.data(), tr, stride, tree.data() + kLeaves - 1, prg_);
929 cudaError_t status = cudaGetLastError();
930 if (status != cudaSuccess)
return status;
931 return gpu::detail::BuildGrottoParityTree<in_bits, LaunchShape>(tree.data(), stream);
933 static_assert(
sizeof(Prg) == 0,
"gpu launch requires a CUDA compiler");
934 return cudaErrorNotSupported;
938template <
int in_bits,
typename Prg,
typename In>
940template <int frontier_depth, int threads_per_block>
942 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
943 cuda::std::span<bool> ys, cudaStream_t stream,
size_t stride)
const
944 requires(in_bits <
sizeof(size_t) * 8) {
945#if defined(__CUDACC__)
946 using LaunchShape = gpu::detail::GrottoGpuLaunchShape<in_bits, frontier_depth, threads_per_block>;
947 constexpr size_t kOutputs =
size_t{1} << in_bits;
948 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
949 if (!detail::FitsFullDomain<bool, in_bits>() || stride == 0 ||
950 (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits)) ||
951 kOutputs >
static_cast<size_t>(std::numeric_limits<int64_t>::max()) ||
952 cws.size() <
static_cast<size_t>(in_bits) * stride + 1 || ys.size() < kOutputs ||
953 cws.data() ==
nullptr || ys.data() ==
nullptr)
return cudaErrorInvalidValue;
955 cudaError_t status = cub::DeviceScan::InclusiveScan(
nullptr, bytes,
956 ys.data(), ys.data(), gpu::detail::GrottoGpuBoolXor{},
static_cast<int64_t
>(kOutputs), stream);
957 if (status != cudaSuccess)
return status;
958 gpu::detail::DeviceScratch scan_storage(stream);
959 status = scan_storage.Allocate<
unsigned char>(bytes == 0 ? 1 : bytes);
960 if (status != cudaSuccess)
return status;
961 gpu::detail::GrottoSplitLeafBitsKernel<in_bits, LaunchShape::kFrontierDepth,
962 LaunchShape::kBlockRootDepth, DpfType, Prg, CwsView>
963 <<<LaunchShape::kBlocks, LaunchShape::kThreadsPerBlock, 0, stream>>>(
964 b, s0, cws.data(), tr, stride, ys.data(), prg_);
965 status = cudaGetLastError();
966 if (status != cudaSuccess)
return status;
967 status = cub::DeviceScan::InclusiveScan(scan_storage.Get<
unsigned char>(), bytes,
968 ys.data(), ys.data(), gpu::detail::GrottoGpuBoolXor{},
static_cast<int64_t
>(kOutputs), stream);
969 return gpu::detail::ReleaseScratch(status, {&scan_storage});
971 static_assert(
sizeof(Prg) == 0,
"gpu launch requires a CUDA compiler");
972 return cudaErrorNotSupported;
976template <
int in_bits,
typename Prg,
typename In>
980 requires(in_bits <
sizeof(size_t) * 8) {
981#if defined(__CUDACC__)
982 constexpr size_t kOutputs =
size_t{1} << in_bits;
983 if (!detail::FitsBinaryTree<bool, in_bits>() ||
984 kOutputs >
static_cast<size_t>(std::numeric_limits<int64_t>::max()) ||
985 ys.size() < kOutputs || ys.data() ==
nullptr || pt.
p ==
nullptr)
return cudaErrorInvalidValue;
986 const bool *leaves = pt.
p + kOutputs - 1;
988 cudaError_t status = cub::DeviceScan::InclusiveScan(
nullptr, bytes,
989 leaves, ys.data(), gpu::detail::GrottoGpuBoolXor{},
static_cast<int64_t
>(kOutputs), stream);
990 if (status != cudaSuccess)
return status;
991 gpu::detail::DeviceScratch scan_storage(stream);
992 status = scan_storage.Allocate<
unsigned char>(bytes == 0 ? 1 : bytes);
993 if (status != cudaSuccess)
return status;
994 status = cub::DeviceScan::InclusiveScan(scan_storage.Get<
unsigned char>(), bytes,
995 leaves, ys.data(), gpu::detail::GrottoGpuBoolXor{},
static_cast<int64_t
>(kOutputs), stream);
996 return gpu::detail::ReleaseScratch(status, {&scan_storage});
998 static_assert(
sizeof(Prg) == 0,
"gpu launch requires a CUDA compiler");
999 return cudaErrorNotSupported;
1003template <
typename Scheme>
1005 : p_(std::move(p)) {}
1007template <
typename Scheme>
1012template <
int in_bits,
typename Prg,
typename In>
1015 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
int par_depth,
size_t stride) {
1017 throw std::invalid_argument(
"GrottoDcf::PreprocessOwned: stride must be positive");
1018 constexpr size_t kMaxElements = SIZE_MAX /
sizeof(int4);
1019 if (stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits))
1020 throw std::invalid_argument(
"GrottoDcf::PreprocessOwned: stride exceeds the correction array limit");
1021 const size_t required =
static_cast<size_t>(in_bits) * stride + 1;
1022 if (cws.size() < required)
1023 throw std::invalid_argument(
"GrottoDcf::PreprocessOwned: cws must hold in_bits*stride+1 elements");
1024 return PreprocessOwnedCws(b, s0,
1025 CwsView{cws.data(), tr, stride}, par_depth);
1028template <
int in_bits,
typename Prg,
typename In>
1030template <typename Cws>
1032GrottoDcf<in_bits, Prg, In>::PreprocessOwnedCws(
1033 bool b, int4 s0, Cws cws,
int par_depth) {
1034 constexpr size_t tree_size = detail::FullBinaryTreeSize<bool, in_bits>();
1035 auto p = std::make_unique<bool[]>(tree_size);
1036 PreprocessCws(b, s0, cws, p.get(), par_depth);
1040namespace gpu::detail {
1042#if defined(__CUDACC__)
1050template <
int in_bits,
int unroll,
bool b,
typename Prg,
typename In,
typename Query>
1051__global__
void GrottoDcfEvalPointKernel(
bool *ys,
const int4 *seeds,
const int4 *cws,
const In *tr,
1052 const Query *xs,
int nkeys, Prg prg) {
1053 static_assert(in_bits <=
sizeof(In) * 8,
"In too narrow for in_bits");
1054 int tid = blockIdx.x * blockDim.x + threadIdx.x;
1055 if (tid >= nkeys)
return;
1058 In x =
static_cast<In
>(xs[tid]);
1060 fss::detail::GrottoTurns turns;
1062#pragma unroll unroll
1063 for (
int i = 0; i < in_bits; ++i) {
1064 bool x_bit = GetBit(x, in_bits - 1 - i);
1067 int4 s_cw = cws[(size_t)i * nkeys + tid];
1068 bool tr_cw = GetBit(tr_k, i);
1072 auto g = prg.Gen(s);
1073 int4 sl = g[0], sr = g[1];
1095 ys[tid] = turns.PointLeaf(t);
1098template <
int unroll,
int in_bits,
typename Prg,
typename In,
typename Query>
1099void GrottoDcfEvalPointLaunch(
bool b,
const int4 *seeds,
const int4 *cws,
const In *tr,
const Query *xs,
1100 bool *ys,
int nkeys, Prg prg, cudaStream_t stream) {
1101 if (nkeys == 0)
return;
1102 constexpr int kBs = 256;
1103 int blocks = (nkeys + kBs - 1) / kBs;
1105 GrottoDcfEvalPointKernel<in_bits, unroll, true, Prg, In, Query>
1106 <<<blocks, kBs, 0, stream>>>(ys, seeds, cws, tr, xs, nkeys, prg);
1108 GrottoDcfEvalPointKernel<in_bits, unroll, false, Prg, In, Query>
1109 <<<blocks, kBs, 0, stream>>>(ys, seeds, cws, tr, xs, nkeys, prg);
1131template <
int unroll = 4,
int in_bits,
typename Prg,
typename In,
typename Query = In>
1132 requires QueryFor<Query, in_bits>
1133void GrottoDcfEvalPointGpu(
bool b, cuda::std::span<const int4> seeds, cuda::std::span<const int4> cws,
1134 cuda::std::span<const In> tr, cuda::std::span<const Query> xs,
bool *ys,
int nkeys,
1135 const GrottoDcf<in_bits, Prg, In> &dcf, cudaStream_t stream = 0) {
1137 throw std::invalid_argument(
"GrottoDcfEvalPointGpu: nkeys must be nonnegative");
1138 if (nkeys == 0)
return;
1139 const size_t count =
static_cast<size_t>(nkeys);
1140 constexpr size_t kLevels =
static_cast<size_t>(in_bits) + 1;
1141 if (count > SIZE_MAX / kLevels || cws.size() < count * kLevels)
1142 throw std::invalid_argument(
"GrottoDcfEvalPointGpu: cws must hold (in_bits+1)*nkeys elements");
1143 if (seeds.size() < count || tr.size() < count || xs.size() < count || ys ==
nullptr)
1144 throw std::invalid_argument(
"GrottoDcfEvalPointGpu: seeds, tr, xs, and ys must cover nkeys entries");
1145 GrottoDcfEvalPointLaunch<unroll, in_bits, Prg, In, Query>(b, seeds.data(), cws.data(), tr.data(), xs.data(), ys,
1146 nkeys, fss::detail::SchemeAccess::GetPrg(dcf), stream);
1151template <
int in_bits,
typename Prg,
typename In,
int frontier_depth = 16,
1152 int threads_per_block = 256>
1153__host__
inline cudaError_t GrottoPreprocessGpuDeviceKey(
bool b,
const int4 *s0,
1154 const int4 *cws,
size_t cws_count,
const In *tr,
size_t stride,
bool *tree,
1155 size_t tree_count,
const Prg &prg, cudaStream_t stream) {
1156 using LaunchShape = GrottoGpuLaunchShape<in_bits, frontier_depth, threads_per_block>;
1157 using DpfType = fss::Dpf<in_bits, fss::group::Bytes, Prg, In>;
1158 using CwsView = fss::detail::GrottoDcfCwsView<typename DpfType::Cw, In>;
1159 constexpr size_t kLeaves =
size_t{1} << in_bits;
1160 constexpr size_t kTreeSize = kLeaves + (kLeaves - 1);
1161 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
1162 if (!fss::detail::FitsBinaryTree<bool, in_bits>() || stride == 0 ||
1163 stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits) ||
1164 cws_count <
static_cast<size_t>(in_bits) * stride + 1 ||
1165 tree_count < kTreeSize || s0 ==
nullptr || cws ==
nullptr || tr ==
nullptr ||
1166 tree ==
nullptr)
return cudaErrorInvalidValue;
1167 constexpr size_t kParityWidth = kLeaves / 2;
1168 constexpr size_t kParityBlocks = kParityWidth / LaunchShape::kThreadsPerBlock +
1169 (kParityWidth % LaunchShape::kThreadsPerBlock != 0);
1170 if (kParityBlocks >
static_cast<size_t>(std::numeric_limits<int>::max()))
1171 return cudaErrorInvalidConfiguration;
1172 GrottoSplitLeafBitsDeviceKeyKernel<in_bits, LaunchShape::kFrontierDepth,
1173 LaunchShape::kBlockRootDepth, DpfType, Prg, CwsView>
1174 <<<LaunchShape::kBlocks, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1175 b, s0, cws, tr, stride, tree + kLeaves - 1, prg);
1176 cudaError_t status = cudaGetLastError();
1177 if (status != cudaSuccess)
return status;
1178 return BuildGrottoParityTree<in_bits, LaunchShape>(tree, stream);
1181template <
int in_bits,
typename Prg,
typename In,
int frontier_depth = 16,
1182 int threads_per_block = 256>
1183__host__
inline cudaError_t GrottoEvalAllGpuDeviceKey(
bool b,
const int4 *s0,
1184 const int4 *cws,
size_t cws_count,
const In *tr,
size_t stride,
bool *ys,
1185 size_t ys_count,
const Prg &prg, cudaStream_t stream) {
1186 using LaunchShape = GrottoGpuLaunchShape<in_bits, frontier_depth, threads_per_block>;
1187 using DpfType = fss::Dpf<in_bits, fss::group::Bytes, Prg, In>;
1188 using CwsView = fss::detail::GrottoDcfCwsView<typename DpfType::Cw, In>;
1189 constexpr size_t kOutputs =
size_t{1} << in_bits;
1190 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() /
sizeof(int4);
1191 if (!fss::detail::FitsFullDomain<bool, in_bits>() || stride == 0 ||
1192 stride > (kMaxElements - 1) /
static_cast<size_t>(in_bits) ||
1193 kOutputs >
static_cast<size_t>(std::numeric_limits<int64_t>::max()) ||
1194 cws_count <
static_cast<size_t>(in_bits) * stride + 1 || ys_count < kOutputs ||
1195 s0 ==
nullptr || cws ==
nullptr || tr ==
nullptr || ys ==
nullptr)
1196 return cudaErrorInvalidValue;
1198 cudaError_t status = cub::DeviceScan::InclusiveScan(
nullptr, bytes,
1199 ys, ys, gpu::detail::GrottoGpuBoolXor{},
static_cast<int64_t
>(kOutputs), stream);
1200 if (status != cudaSuccess)
return status;
1201 DeviceScratch scan_storage(stream);
1202 status = scan_storage.Allocate<
unsigned char>(bytes == 0 ? 1 : bytes);
1203 if (status != cudaSuccess)
return status;
1204 GrottoSplitLeafBitsDeviceKeyKernel<in_bits, LaunchShape::kFrontierDepth,
1205 LaunchShape::kBlockRootDepth, DpfType, Prg, CwsView>
1206 <<<LaunchShape::kBlocks, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1207 b, s0, cws, tr, stride, ys, prg);
1208 status = cudaGetLastError();
1209 if (status != cudaSuccess)
return status;
1210 status = cub::DeviceScan::InclusiveScan(scan_storage.Get<
unsigned char>(), bytes,
1211 ys, ys, gpu::detail::GrottoGpuBoolXor{},
static_cast<int64_t
>(kOutputs), stream);
1212 return ReleaseScratch(status, {&scan_storage});
1219#if FSS_ENABLE_V1_COMPAT
1225template <
int in_bits,
typename Prg,
typename In>
1228 Cw cws[],
const int4 s0s[2], In a) {
1229 int4 seed[in_bits + 1];
1231 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr,
1232 cuda::std::span<const int4, 2>(s0s, 2), a);
1233 for (
int i = 0; i < in_bits; ++i) cws[i] = {seed[i], ((tr >> i) & 1) != 0};
1234 cws[in_bits] = {seed[in_bits],
false};
1237template <
int in_bits,
typename Prg,
typename In>
1240 compat::detail::ParityTreeRecord<GrottoDcf> &pt, int4 s0,
const Cw cws[]) {
1241 int4 seed[in_bits + 1];
1243 for (
int i = 0; i <= in_bits; ++i) seed[i] = cws[i].s;
1244 for (
int i = 0; i < in_bits; ++i) tr |= static_cast<In>(cws[i].tr) << i;
1245 Preprocess(pt.b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, pt.p);
1254template <
int in_bits,
typename Prg,
typename In>
1257 compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &k0,
1258 compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &k1,
1259 cuda::std::span<const int4, 2> s0s, In a) {
1260 int4 seed[in_bits + 1];
1262 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr, s0s, a);
1263 for (
int i = 0; i < in_bits; ++i) {
1264 const Cw layer = {seed[i], ((tr >> i) & 1) != 0};
1268 const Cw terminal = {seed[in_bits],
false};
1269 k0.cws[in_bits] = terminal;
1270 k1.cws[in_bits] = terminal;
1275template <
int in_bits,
typename Prg,
typename In>
1279 bool b,
const compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &key) {
1280 int4 seed[in_bits + 1];
1282 for (
int i = 0; i <= in_bits; ++i) seed[i] = key.cws[i].s;
1283 for (
int i = 0; i < in_bits; ++i) tr |= static_cast<In>(key.cws[i].tr) << i;
1285 compat::detail::PreprocessedTreeRecord<GrottoDcf> pt;
1286 constexpr size_t kTreeSize = (
size_t{2} << in_bits) - 1;
1287 pt.p = std::make_unique<bool[]>(kTreeSize);
1289 Preprocess(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, pt.p.get());
1293template <
int in_bits,
typename Prg,
typename In>
1296 const compat::detail::ParityTreeRecord<GrottoDcf> &pt, In x) {
1297 return Eval(ParityTreeView{pt.p}, x);
1300template <
int in_bits,
typename Prg,
typename In>
1303 const compat::detail::PreprocessedTreeRecord<GrottoDcf> &tree, In x) {
1304 return Eval(ParityTreeView{tree.p.get()}, x);
1307template <
int in_bits,
typename Prg,
typename In>
1310 bool b, int4 s0,
const Cw cws[],
bool ys[]) {
1311 int4 seed[in_bits + 1];
1313 for (
int i = 0; i <= in_bits; ++i) seed[i] = cws[i].s;
1314 for (
int i = 0; i < in_bits; ++i) tr |= static_cast<In>(cws[i].tr) << i;
1315 EvalAll(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys);
1318template <
int in_bits,
typename Prg,
typename In>
1321 bool b,
const compat::detail::CwsKeyRecord<GrottoDcf, Cw, in_bits + 1> &key,
1322 cuda::std::span<bool> ys) {
1323 int4 seed[in_bits + 1];
1325 for (
int i = 0; i <= in_bits; ++i) seed[i] = key.cws[i].s;
1326 for (
int i = 0; i < in_bits; ++i) tr |= static_cast<In>(key.cws[i].tr) << i;
1327 EvalAll(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys.data());
2-party DPF scheme.
Definition dpf.cuh:112
2-party DCF scheme over F2 from standard DPF (Grotto construction).
Definition grotto_dcf.cuh:103
bool Eval(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, In x, size_t stride=1)
Evaluate a comparison share along x's path from split correction words.
Definition grotto_dcf.cuh:703
In Input
The caller-facing input-domain type.
Definition grotto_dcf.cuh:112
GrottoDcf(Prg prg)
Configure the pseudorandom generator.
Definition grotto_dcf.cuh:689
void Gen(cuda::std::span< int4 > cws, In &tr, cuda::std::span< const int4, 2 > s0s, In a, size_t stride=1)
Generate split DPF correction words with beta equal to zero.
Definition grotto_dcf.cuh:694
cudaError_t EvalAllGpu(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< bool > ys, cudaStream_t stream=0, size_t stride=1) const
Evaluate all comparison shares on the GPU from split device correction words.
Definition grotto_dcf.cuh:941
cudaError_t PreprocessGpu(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< bool > tree, cudaStream_t stream=0, size_t stride=1) const
Build a parity tree on the GPU from split device correction words.
Definition grotto_dcf.cuh:906
static void EvalAll(ParityTreeView pt, bool ys[])
Evaluate the full domain from a host parity tree without PRG calls.
Definition grotto_dcf.cuh:781
OwnedParityTree< GrottoDcf > PreprocessOwned(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, int par_depth=0, size_t stride=1)
Allocate and populate a parity tree from split correction words in host storage.
Definition grotto_dcf.cuh:1014
static constexpr int kCwCount
Seed-component count Gen() writes at stride 1: in_bits layers plus the terminal.
Definition grotto_dcf.cuh:110
void Preprocess(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, bool p[], int par_depth=0, size_t stride=1)
Expand split leaf control bits and build a parity tree in caller-owned host storage.
Definition grotto_dcf.cuh:727
typename DpfType::Cw Cw
Correction-word type of the underlying DPF.
Definition grotto_dcf.cuh:107
Own a host parity-tree allocation and expose a borrowed, read-only view.
Definition grotto_dcf.cuh:480
Scheme::ParityTreeView View() const &
Return a view borrowing this live owner's host allocation.
Definition grotto_dcf.cuh:1008
Scheme::ParityTreeView View() const &&=delete
Reject a temporary owner whose borrowed view would immediately dangle.
OwnedParityTree(std::unique_ptr< bool[]> p)
Take ownership of an already populated host parity tree.
Definition grotto_dcf.cuh:1004
Single build-time switch for the deprecated v1 Cw-form compatibility entries.
Pseudorandom generator (PRG) interface.
Definition prg.cuh:24
2-party distributed point function (DPF).
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
Borrowed, read-only parity tree in level order.
Definition grotto_dcf.cuh:189
const bool * p
Borrowed parity entries, including leaves.
Definition grotto_dcf.cuh:190