myl7/fss 2.0.0
Function secret sharing (FSS) primitives including distributed point/comparison function (DPF/DCF)
Loading...
Searching...
No Matches
dpf.cuh
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
38
39#pragma once
40#include <cuda_runtime.h>
41#include <type_traits>
42#include <cstddef>
43#include <cassert>
44#ifdef _OPENMP
45#include <omp.h>
46#endif
47#include <vector>
48#include <cuda/std/array>
49#include <fss/group.cuh>
50#include <fss/prg.cuh>
51#include <fss/util.cuh>
52#include <cuda/std/span>
53#include <limits>
54#include <fss/constraints.cuh>
55#include <fss/compat.cuh>
56
57#include <cstdint>
58#include <stdexcept>
59
60namespace fss {
61
62namespace detail {
63class SchemeAccess;
64} // namespace detail
65
66namespace detail {
67
68// Split correction-word view shared by the host and device split paths. Layer i is
69// read from cws[i*stride] and the right-control correction is bit i of
70// packed_tr. The terminal is rebuilt as a plain Cw with no control bit, so bits of
71// packed_tr at or above in_bits are never read; the shift by in_bits would be
72// undefined when in_bits equals the width of In. The layer LSB keeps the
73// left-control correction exactly as the Cw layout does. operator[] recovers the Cw
74// the shared algorithms consume, so their bodies stay identical on the Cw and split
75// paths.
76template <typename Cw, typename In, int in_bits>
77struct DpfCwsView {
78 const int4 *s;
79 In packed_tr;
80 size_t stride;
81
82 __host__ __device__ inline Cw operator[](int i) const {
83 const int4 s_cw = s[static_cast<size_t>(i) * stride];
84 if (i >= in_bits) return Cw{s_cw, false};
85 return Cw{s_cw, ((packed_tr >> i) & 1) != 0};
86 }
87};
88
89} // namespace detail
90
91template <int in_bits, typename Prg, typename In>
92 requires(ValidForInputBits<In, in_bits> && Prgable<Prg, 2>)
93class GrottoDcf;
94
109template <int in_bits, typename Group, typename Prg, typename In = uint>
111 Groupable<Group> && Prgable<Prg, 2>)
112class Dpf {
113public:
123 struct __align__(32) Cw {
124 int4 s;
125 bool tr;
126 };
127 // For only 1 and aligned memory access on GPU
128 static_assert(sizeof(Cw) == 32);
129
137 struct Key {
138 Cw cws[in_bits + 1];
139 int4 s0;
140#if FSS_ENABLE_V1_COMPAT
144 [[deprecated("the par_depth template parameter is gone; pass par_depth to EvalAll() instead")]]
145 static constexpr int kParDepth = -1;
146#endif // FSS_ENABLE_V1_COMPAT
147 };
148
150 static constexpr int kCwCount = in_bits + 1;
151
152 using Input = In;
153
159 __host__ __device__ explicit Dpf(Prg prg);
160
187 __host__ __device__ void Gen(cuda::std::span<int4> cws, In &tr,
188 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf, size_t stride = 1);
189
217 __host__ __device__ int4 Eval(bool b, int4 s0, cuda::std::span<const int4> cws,
218 In tr, In x, size_t stride = 1);
219
252 void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws,
253 In tr, cuda::std::span<int4> ys, int par_depth = 0, size_t stride = 1);
254
283 void EvalAll(bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
284 int4 ys[], int par_depth = 0, size_t stride = 1);
285
313 template <int frontier_depth = 16, int threads_per_block = 256>
314 __host__ cudaError_t EvalRangeGpu(bool b, In first, In last, int4 s0,
315 cuda::std::span<const int4> cws, In tr, cuda::std::span<int4> ys,
316 cudaStream_t stream = 0, size_t stride = 1) const;
317
341 template <int frontier_depth = 16, int threads_per_block = 256>
342 __host__ cudaError_t EvalAllGpu(bool b, int4 s0, cuda::std::span<const int4> cws,
343 In tr, cuda::std::span<int4> ys, cudaStream_t stream = 0, size_t stride = 1) const
344 requires(in_bits < sizeof(size_t) * 8);
345
378 template <int frontier_depth = 16, int threads_per_block = 256>
379 __host__ cudaError_t EvalAllGpuBatch(bool b, cuda::std::span<const int4> s0s,
380 cuda::std::span<const int4> cws, cuda::std::span<const In> tr,
381 cuda::std::span<int4> ys, cudaStream_t stream = 0, size_t stride = 0) const
382 requires(in_bits < sizeof(size_t) * 8);
383
384#if FSS_ENABLE_V1_COMPAT
385 // Deprecated v1.3.0 aggregate-Cw compatibility entries; see fss/compat.cuh.
386 // Each converts through a compile-time-sized scratch buffer and the current
387 // split-component method above at stride 1. cws has in_bits + 1 entries.
388
390 [[deprecated("use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, int4, size_t) overload")]]
391 __host__ __device__ void Gen(Cw cws[], const int4 s0s[2], In a, int4 b_buf);
392
394 [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, In, size_t) overload")]]
395 __host__ __device__ int4 Eval(bool b, int4 s0, const Cw cws[], In x);
396
398 [[deprecated("use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, int4[], int, size_t) overload")]]
399 void EvalAll(bool b, int4 s0, const Cw cws[], int4 ys[]);
400
401 // Deprecated v1.3.0 aggregate-Key entries. They keep the current public Key
402 // type, which the modern bucket APIs still use, and convert through the same
403 // compile-time scratch buffer as the Cw entries above. EvalAll() runs
404 // sequentially, as the current split-component default does; the old
405 // automatic thread-count split is not restored.
406
408 [[deprecated("use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, int4, size_t) overload")]]
409 __host__ __device__ void Gen(Key &k0, Key &k1, cuda::std::span<const int4, 2> s0s,
410 In a, int4 b_buf);
411
413 [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, In, size_t) overload")]]
414 __host__ __device__ int4 Eval(bool b, const Key &key, In x);
415
417 [[deprecated("use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, int4[], int, size_t) overload")]]
418 void EvalAll(bool b, const Key &key, cuda::std::span<int4> ys);
419#endif // FSS_ENABLE_V1_COMPAT
420
421private:
422 friend class detail::SchemeAccess;
423
424 Prg prg_;
425
426 template <int frontier_depth, int threads_per_block>
427 struct GpuLaunchShape;
428
429 // EvalAll() shape: a scalar prefix of kBatchPrefix levels grows the frontier to
430 // 8 nodes, detail::GenBatch() expands 8 parents per level (pipelined through
431 // Gen8() when the PRG offers a batched entry), and a kBatchTail-level tail
432 // converts each 8-subtree group of 8 << kBatchTail leaves in local memory.
433 static constexpr int kBatchPrefix = 3;
434 static constexpr int kBatchTail = 3;
435
436 friend class GrottoDcf<in_bits, Prg, In>;
437
438 // Packed states store the control bit in their least significant bit.
439 // correction retains the left control bit in its LSB; right_control is Cw::tr.
440 // A backend reference lets CPU and GPU callers reuse the same calculation
441 // without reconstructing a Scheme or copying its backend at every node.
442 __host__ __device__ static void ExpandNode(Prg &prg, int4 state, int4 correction,
443 bool right_control, int4 &left, int4 &right);
444
445 __host__ __device__ static int4 PathStep(Prg &prg, int4 state, int4 correction,
446 bool right_control, bool take_right);
447
448 // Correction is a raw int4, or the Group already converted by the CPU batch
449 // tail. Raw correction values are converted only when the leaf control bit is set.
450 template <typename Correction>
451 __host__ __device__ static int4 ConvertLeaf(bool party, int4 state, Correction &correction);
452
453 // Split correction-word view over the caller's arrays. The host methods and the
454 // split GPU kernels share this one view; see detail::DpfCwsView.
455 using CwsView = detail::DpfCwsView<Cw, In, in_bits>;
456
457 // Writes Cw slots in the legacy AoS layout, covering each full 32B slot.
458 struct AosWriter {
459 Cw *cws;
460
461 __host__ __device__ void Write(int i, int4 s_cw, bool tr_cw) const {
462 // Write both 16B halves so the full 32B slot is covered by unmasked stores.
463 // {s_cw, tr_cw} alone leaves the padding undefined: NVCC emits a masked
464 // 256-bit store, and the partial-sector write forces a write-allocate
465 // round trip per level on GPU (measured ~2x Gen time).
466 int4 *cw_slot = reinterpret_cast<int4 *>(&cws[i]);
467 cw_slot[0] = s_cw;
468 cw_slot[1] = make_int4(tr_cw, 0, 0, 0);
469 }
470
471 __host__ __device__ void Finish(int4 terminal) const {
472 cws[in_bits] = {terminal, false};
473 }
474 };
475
476 // Writes strided seed components and accumulates packed right-control bits locally.
477 struct CwsWriter {
478 int4 *cws;
479 size_t stride;
480 In &tr;
481 In packed_tr = 0;
482
483 __host__ __device__ void Write(int i, int4 s_cw, bool tr_cw) {
484 cws[static_cast<size_t>(i) * stride] = s_cw;
485 packed_tr |= static_cast<In>(tr_cw) << i;
486 }
487
488 __host__ __device__ void Finish(int4 terminal) {
489 cws[static_cast<size_t>(in_bits) * stride] = terminal;
490 tr = packed_tr;
491 }
492 };
493
494 // Shared correction generation loop. The writer is passed by value and owns
495 // only output access, so the existing algorithm and backend use stay unchanged.
496 template <typename Writer>
497 __host__ __device__ void Generate(Writer writer, const int4 *s0s, In a, int4 b_buf);
498
499 // Walks x's path from the root and returns the leaf seed with its control bit in the LSB.
500 // visit(t, x_bit) sees the control bit of every expanded node and the branch x takes from it.
501 template <typename Cws, typename Visit>
502 __host__ __device__ int4 Walk(bool b, int4 s0, Cws cws, In x, Visit &&visit);
503
504 // Shared scalar evaluation over the Cw array or the split view.
505 template <typename Cws>
506 __host__ __device__ int4 EvalCws(bool b, int4 s0, Cws cws, In x);
507
508 // Shared interval evaluation over the Cw array or the split view.
509 template <typename Cws>
510 void EvalRangeCws(bool b, In first, In last, int4 s0, Cws cws,
511 cuda::std::span<int4> ys, int par_depth);
512
513 // Shared full-domain evaluation over the Cw array or the split view.
514 template <typename Cws>
515 void EvalAllCws(bool b, int4 s0, Cws cws, int4 ys[], int par_depth);
516
517 // Full-domain batch GPU launch over key-major rows of Cw, the storage
518 // contract of DMPF bucket keys. Reached through detail::SchemeAccess only;
519 // public callers use the level-major split batch API.
520 template <int frontier_depth, int threads_per_block>
521 __host__ cudaError_t EvalAllGpuBatchCw(bool b, cuda::std::span<const int4> s0s,
522 cuda::std::span<const Cw> cws, cuda::std::span<int4> ys, cudaStream_t stream) const;
523
524 // Closed node bounds avoid representing an unaddressable full-domain end.
525 template <typename Cws>
526 void EvalRangeSerial(bool b, In first, In last, int4 s0, Cws cws, int4 ys[]);
527
528 // GCC 12.4.0 can discard these writes before the OpenMP walk after IPA
529 // effect analysis. Keep this boundary opaque on GCC 12 releases before 12.5.
530 // GCC PR115033 describes a related closure escape-analysis defect.
531 template <typename Cws>
532#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 12 && __GNUC_MINOR__ <= 4
533 __attribute__((noipa))
534#endif
535 std::vector<int4> BuildParallelFrontier(int4 st, Cws cws, int split_depth);
536
537 // Expands w scalar frontier nodes (t in LSB) by one level with cws[lv].
538 // The children overwrite the frontier in place: iteration is in reverse so
539 // the writes at 2j, 2j+1 never clobber nodes still to be read.
540 template <typename Cws>
541 void ExpandFrontier(int4 frontier[], size_t w, Cws cws, int lv);
542
543 // Applies the level correction to one expanded child branchlessly.
544 // m is 0 or -1 from the parent control bit t, and t_cw is the cw control bit
545 // masked by t. The cw seed is clamped, so the XOR leaves the child LSB free
546 // for the packed control bit.
547 __host__ __device__ static int4 CorrectChild(int4 child, int4 s_cw, int m, int t_cw);
548
549 // Full-domain evaluation of the subtree rooted at st (t in LSB) at level i through an
550 // 8-wide batched frontier: a kBatchPrefix-level scalar prefix, batched expansions, and
551 // a kBatchTail-level tail converted in local memory.
552 // Requires in_bits - i >= kBatchPrefix + kBatchTail.
553 template <typename Cws>
554 void EvalBatched8(bool b, int4 st, Cws cws, int4 ys[], size_t l, int i);
555
556 // Evaluates a group of 8 consecutive frontier nodes (t in LSB) at level i, covering the
557 // leaves [l, l + 8 << (in_bits - i)) in order. Requires in_bits - i >= kBatchTail.
558 template <typename Cws>
559 void EvalBatched8Group(bool b, const int4 (&frontier)[8], Cws cws, int4 ys[], size_t l, int i);
560};
561
562// -----------------------------------------------------------------------------
563
564namespace gpu::detail {
565
566template <typename Scheme>
567struct DpfGpuSchemeTraits;
568
569#if defined(__CUDACC__)
570
571// The single-key and batch kernels share the same block-local frontier and tail.
572// Each node keeps the former full-domain helper's by-value backend lifetime.
573// Cws is a Cw pointer on the array path and a kernel-boundary split view
574// on the split path, so the traversal body does not depend on the layout.
575template <typename Scheme, int frontier_depth, int block_root_depth, bool range = false, typename Cws>
576__device__ __forceinline__ void DpfEvalAllBlock(bool b, int4 s0, Cws cws,
577 int4 *ys, typename DpfGpuSchemeTraits<Scheme>::Backend &prg, int subtree_block_index, int64_t thread_index, int4 *shared_frontier,
578 __uint128_t first = 0, int subtree_depth = DpfGpuSchemeTraits<Scheme>::kInputBits,
579 int range_frontier = frontier_depth, int range_block_root = block_root_depth) {
580 constexpr int kInputBits = DpfGpuSchemeTraits<Scheme>::kInputBits;
581 constexpr int kTailCapacity = range ? 8 : kInputBits - frontier_depth;
582 const int start_depth = range ? kInputBits - subtree_depth : 0;
583 const int frontier = range ? start_depth + range_frontier : frontier_depth;
584 const int root_depth = range ? start_depth + range_block_root : block_root_depth;
585 const int tail_depth = kInputBits - frontier;
586 const int block_subtree_depth = frontier - root_depth;
587 // Phase 0: block root walk (top block_root_depth levels) by thread 0.
588 if (threadIdx.x == 0) {
589 int4 st = util::SetLsb(s0, b);
590#pragma unroll
591 for (int i = 0; i < root_depth; ++i) {
592 bool x_bit = i < start_depth
593 ? ((first >> (kInputBits - 1 - i)) & 1) != 0
594 : ((subtree_block_index >> (root_depth - 1 - i)) & 1) != 0;
595 {
596 // Preserve the old by-value path helper's copy and destruction boundary.
597 auto node_prg = prg;
598 st = fss::detail::SchemeAccess::DpfPathStep<Scheme>(node_prg, st, cws[i].s, cws[i].tr, x_bit);
599 }
600 }
601 shared_frontier[0] = st;
602 }
603 __syncthreads();
604
605 // Phase 1: breadth-parallel expansion of the block's subtree, levels block_root_depth..frontier_depth.
606#pragma unroll
607 for (int i = 0; i < block_subtree_depth; ++i) {
608 const bool active = threadIdx.x < (1 << (i + 1));
609 int4 n = {0, 0, 0, 0};
610 if (active) n = shared_frontier[threadIdx.x >> 1];
611 // All parent reads must finish before any child overwrites the frontier.
612 __syncthreads();
613 if (active) {
614 int4 left, right;
615 {
616 // Preserve the old by-value expansion helper's copy and destruction boundary.
617 auto node_prg = prg;
618 fss::detail::SchemeAccess::DpfExpandNode<Scheme>(node_prg, n, cws[root_depth + i].s, cws[root_depth + i].tr, left, right);
619 }
620 shared_frontier[threadIdx.x] = (threadIdx.x & 1) ? right : left;
621 }
622 __syncthreads();
623 }
624
625 if constexpr (range) {
626 int4 pending[8];
627 int4 node = shared_frontier[threadIdx.x];
628 int level = frontier;
629 const int count = 1 << tail_depth;
630 const size_t offset = static_cast<size_t>(thread_index) << tail_depth;
631 for (int index = 0; index < count; ++index) {
632 while (level < kInputBits) {
633 int4 left, right;
634 {
635 auto node_prg = prg;
636 fss::detail::SchemeAccess::DpfExpandNode<Scheme>(node_prg, node,
637 cws[level].s, cws[level].tr, left, right);
638 }
639 pending[level - frontier] = right;
640 node = left;
641 ++level;
642 }
643 ys[offset + index] = fss::detail::SchemeAccess::DpfConvertLeaf<Scheme>(
644 b, node, cws[kInputBits].s);
645 if (index + 1 < count) {
646 int next = index + 1;
647 level = kInputBits - 1;
648 while ((next & 1) == 0) {
649 --level;
650 next >>= 1;
651 }
652 node = pending[level - frontier];
653 ++level;
654 }
655 }
656 } else {
657 // Phase 2: expand the subtree below depth frontier_depth, then convert to leaves.
658 int4 nodes[1 << kTailCapacity];
659 nodes[0] = shared_frontier[threadIdx.x];
660 int count = 1;
661#pragma unroll
662 for (int lev = frontier; lev < kInputBits; ++lev) {
663#pragma unroll
664 for (int k = count - 1; k >= 0; --k) {
665 int4 left, right;
666 {
667 // Preserve the old by-value expansion helper's copy and destruction boundary.
668 auto node_prg = prg;
669 fss::detail::SchemeAccess::DpfExpandNode<Scheme>(node_prg, nodes[k], cws[lev].s, cws[lev].tr, left, right);
670 }
671 nodes[2 * k] = left;
672 nodes[2 * k + 1] = right;
673 }
674 count *= 2;
675 }
676
677 int4 v_cw_np1 = cws[kInputBits].s;
678 const size_t output_offset = static_cast<size_t>(thread_index) << tail_depth;
679#pragma unroll
680 for (int k = 0; k < count; ++k) {
681 ys[output_offset + k] = fss::detail::SchemeAccess::DpfConvertLeaf<Scheme>(b, nodes[k], v_cw_np1);
682 }
683 }
684}
685
686template <typename Scheme, int frontier_depth, int block_root_depth>
687__global__ void DpfEvalAllBatchKernel(bool b, const int4 *s0s, const void *cws_buf,
688 int4 *ys, typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
689 // The caller passes the raw int4 correction records; the array layout is one
690 // Cw per record, so the typed view is a plain cast.
691 const auto *cws = static_cast<const typename Scheme::Cw *>(cws_buf);
692 constexpr int kInputBits = DpfGpuSchemeTraits<Scheme>::kInputBits;
693 constexpr int kBlocksPerKey = 1 << block_root_depth;
694 const int key_index = blockIdx.x / kBlocksPerKey;
695 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
696 const int64_t thread_index = static_cast<int64_t>(subtree_block_index) * blockDim.x + threadIdx.x;
697 if (thread_index >= (1LL << frontier_depth)) return;
698 const auto *key_cws = cws + static_cast<size_t>(key_index) * (kInputBits + 1);
699 int4 *key_outputs = ys + static_cast<size_t>(key_index) * (size_t{1} << kInputBits);
700 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
701 DpfEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index], key_cws, key_outputs, prg, subtree_block_index, thread_index, shared_frontier);
702}
703
704// Split-layout counterparts of the three kernels above. The correction buffers
705// arrive as raw records and the boundary view reconstructs the per-layer Cw
706// entries, so the shared traversal stays identical on both paths.
707template <typename Scheme, int threads_per_block>
708__global__ void DpfSplitEvalRangeKernel(bool b, int4 s0,
709 const int4 *cws, typename DpfGpuSchemeTraits<Scheme>::Input tr,
710 size_t stride, int4 *ys, __uint128_t first,
711 int depth, int frontier, int root, typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
712 using Cws = fss::detail::DpfCwsView<typename Scheme::Cw,
713 typename DpfGpuSchemeTraits<Scheme>::Input,
714 DpfGpuSchemeTraits<Scheme>::kInputBits>;
715 __shared__ int4 states[threads_per_block];
716 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
717 DpfEvalAllBlock<Scheme, 0, 0, true>(b, s0, Cws{cws, tr, stride}, ys, prg,
718 blockIdx.x, thread_index, states, first, depth, frontier, root);
719}
720
721template <typename Scheme, int frontier_depth, int block_root_depth>
722__global__ void DpfSplitEvalAllKernel(bool b, int4 s0, const int4 *cws,
723 typename DpfGpuSchemeTraits<Scheme>::Input tr, size_t stride,
724 int4 *ys, typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
725 using Cws = fss::detail::DpfCwsView<typename Scheme::Cw,
726 typename DpfGpuSchemeTraits<Scheme>::Input,
727 DpfGpuSchemeTraits<Scheme>::kInputBits>;
728 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
729 if (thread_index >= (1LL << frontier_depth)) return;
730 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
731 DpfEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0, Cws{cws, tr, stride},
732 ys, prg, blockIdx.x, thread_index, shared_frontier);
733}
734
735template <typename Scheme, int frontier_depth, int block_root_depth>
736__global__ void DpfSplitEvalAllBatchKernel(bool b, const int4 *s0s, const int4 *cws,
737 const typename DpfGpuSchemeTraits<Scheme>::Input *trs, size_t stride,
738 int4 *ys, typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
739 using Cws = fss::detail::DpfCwsView<typename Scheme::Cw,
740 typename DpfGpuSchemeTraits<Scheme>::Input,
741 DpfGpuSchemeTraits<Scheme>::kInputBits>;
742 constexpr int kInputBits = DpfGpuSchemeTraits<Scheme>::kInputBits;
743 constexpr int kBlocksPerKey = 1 << block_root_depth;
744 const int key_index = blockIdx.x / kBlocksPerKey;
745 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
746 const int64_t thread_index = static_cast<int64_t>(subtree_block_index) * blockDim.x + threadIdx.x;
747 if (thread_index >= (1LL << frontier_depth)) return;
748 int4 *key_outputs = ys + static_cast<size_t>(key_index) * (size_t{1} << kInputBits);
749 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
750 DpfEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
751 Cws{cws + key_index, trs[key_index], stride}, key_outputs, prg,
752 subtree_block_index, thread_index, shared_frontier);
753}
754
755// Device-key counterparts of the split kernels above: the party seed and the
756// packed control live in caller device storage and are loaded here, so no host
757// value of the key material exists. They feed the same shared traversal.
758template <typename Scheme, int frontier_depth, int block_root_depth>
759__global__ void DpfSplitEvalAllDeviceKeyKernel(bool b, const int4 *s0,
760 const int4 *cws, const typename DpfGpuSchemeTraits<Scheme>::Input *tr,
761 size_t stride, int4 *ys, typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
762 using Cws = fss::detail::DpfCwsView<typename Scheme::Cw,
763 typename DpfGpuSchemeTraits<Scheme>::Input,
764 DpfGpuSchemeTraits<Scheme>::kInputBits>;
765 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
766 if (thread_index >= (1LL << frontier_depth)) return;
767 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
768 DpfEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0[0],
769 Cws{cws, tr[0], stride}, ys, prg, blockIdx.x, thread_index, shared_frontier);
770}
771
772template <typename Scheme, int threads_per_block>
773__global__ void DpfSplitEvalRangeDeviceKeyKernel(bool b, const int4 *s0,
774 const int4 *cws, const typename DpfGpuSchemeTraits<Scheme>::Input *tr,
775 size_t stride, int4 *ys, __uint128_t first,
776 int depth, int frontier, int root, typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
777 using Cws = fss::detail::DpfCwsView<typename Scheme::Cw,
778 typename DpfGpuSchemeTraits<Scheme>::Input,
779 DpfGpuSchemeTraits<Scheme>::kInputBits>;
780 __shared__ int4 states[threads_per_block];
781 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
782 DpfEvalAllBlock<Scheme, 0, 0, true>(b, s0[0], Cws{cws, tr[0], stride}, ys, prg,
783 blockIdx.x, thread_index, states, first, depth, frontier, root);
784}
785
786// Key-major batch counterpart: key k's components occupy records
787// [k*key_records, k*key_records + in_bits] and its control record follows, so
788// the per-key view uses a unit stride.
789template <typename Scheme, int frontier_depth, int block_root_depth>
790__global__ void DpfSplitEvalAllBatchDeviceKeyKernel(bool b, const int4 *s0s,
791 const int4 *cws, size_t key_records, size_t control_record,
792 size_t key_count, int4 *ys, typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
793 using In = typename DpfGpuSchemeTraits<Scheme>::Input;
794 using Cws = fss::detail::DpfCwsView<typename Scheme::Cw, In,
795 DpfGpuSchemeTraits<Scheme>::kInputBits>;
796 constexpr int kInputBits = DpfGpuSchemeTraits<Scheme>::kInputBits;
797 constexpr int kBlocksPerKey = 1 << block_root_depth;
798 const int key_index = blockIdx.x / kBlocksPerKey;
799 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
800 const int64_t thread_index = static_cast<int64_t>(subtree_block_index) * blockDim.x + threadIdx.x;
801 if (thread_index >= (1LL << frontier_depth)) return;
802 const int4 *key = cws + static_cast<size_t>(key_index) * key_records;
803 const In *tr = reinterpret_cast<const In *>(key + control_record);
804 int4 *key_outputs = ys + static_cast<size_t>(key_index) * (size_t{1} << kInputBits);
805 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
806 DpfEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
807 Cws{key, tr[0], 1}, key_outputs, prg, subtree_block_index, thread_index, shared_frontier);
808}
809
810// Host launchers for the device-key kernels. The Prg is passed by value from a
811// host mirror of the device scheme, matching the public launch methods.
812template <int in_bits, typename Group, typename Prg, typename In, int frontier_depth = 16,
813 int threads_per_block = 256>
814__host__ inline cudaError_t DpfEvalAllGpuDeviceKey(bool b, const int4 *s0,
815 const int4 *cws, size_t cws_count, const In *tr, size_t stride, int4 *ys, size_t ys_count,
816 const Prg &prg, cudaStream_t stream) {
817 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
818 constexpr size_t kOutputsPerKey = size_t{1} << in_bits;
819 constexpr int kFrontierDepth = frontier_depth < in_bits ? frontier_depth : in_bits;
820 if (stride == 0 || stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
821 cws_count < static_cast<size_t>(in_bits) * stride + 1 || ys_count < kOutputsPerKey ||
822 s0 == nullptr || cws == nullptr || tr == nullptr || ys == nullptr ||
823 kOutputsPerKey > kMaxElements)
824 return cudaErrorInvalidValue;
825 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
826 (threads_per_block & (threads_per_block - 1)) == 0,
827 "gpu block size must be a power of two in [1,1024]");
828 static_assert(in_bits - kFrontierDepth <= 8,
829 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
830 constexpr int kThreadsPerBlockLog2 = [] {
831 int requested_log2 = 0;
832 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
833 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
834 }();
835 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
836 using Scheme = Dpf<in_bits, Group, Prg, In>;
837 DpfSplitEvalAllDeviceKeyKernel<Scheme, kFrontierDepth, kBlockRootDepth>
838 <<<size_t{1} << kBlockRootDepth, 1 << kThreadsPerBlockLog2, 0, stream>>>(
839 b, s0, cws, tr, stride, ys, prg);
840 return cudaGetLastError();
841}
842
843template <int in_bits, typename Group, typename Prg, typename In, int frontier_depth = 16,
844 int threads_per_block = 256>
845__host__ inline cudaError_t DpfEvalRangeGpuDeviceKey(bool b, In first, In last,
846 const int4 *s0, const int4 *cws, size_t cws_count, const In *tr, size_t stride,
847 int4 *ys, size_t ys_count, const Prg &prg, cudaStream_t stream) {
848 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
849 if (stride == 0 || stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
850 cws_count < static_cast<size_t>(in_bits) * stride + 1 || cws == nullptr ||
851 s0 == nullptr || tr == nullptr || ys == nullptr) return cudaErrorInvalidValue;
852 using Scheme = Dpf<in_bits, Group, Prg, In>;
853 return fss::detail::SplitRangeChunks<in_bits, frontier_depth, threads_per_block>(
854 first, last, ys_count,
855 [&](In chunk_first, int depth, size_t offset, int frontier, int thread_bits, int root,
856 size_t blocks) {
857 DpfSplitEvalRangeDeviceKeyKernel<Scheme, threads_per_block>
858 <<<blocks, 1 << thread_bits, 0, stream>>>(b, s0, cws, tr, stride,
859 ys + offset, chunk_first, depth, frontier, root, prg);
860 return cudaGetLastError();
861 });
862}
863
864template <int in_bits, typename Group, typename Prg, typename In, int frontier_depth = 16,
865 int threads_per_block = 256>
866__host__ inline cudaError_t DpfEvalAllGpuBatchDeviceKey(bool b, const int4 *s0s,
867 size_t key_count, const int4 *cws, size_t cws_count, size_t key_records,
868 size_t control_record, int4 *ys, size_t ys_count, const Prg &prg, cudaStream_t stream) {
869 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
870 constexpr size_t kOutputsPerKey = size_t{1} << in_bits;
871 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
872 constexpr int kFrontierDepth = frontier_depth < in_bits ? frontier_depth : in_bits;
873 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
874 (threads_per_block & (threads_per_block - 1)) == 0,
875 "gpu block size must be a power of two in [1,1024]");
876 static_assert(in_bits - kFrontierDepth <= 8,
877 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
878 constexpr int kThreadsPerBlockLog2 = [] {
879 int requested_log2 = 0;
880 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
881 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
882 }();
883 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
884 constexpr int kBlocksPerKey = 1 << kBlockRootDepth;
885 if (key_count == 0) return cudaSuccess;
886 if (key_count > kMaxAddressableSize / kOutputsPerKey / sizeof(int4) ||
887 key_count > kMaxAddressableSize / key_records / sizeof(int4) ||
888 cws_count < key_count * key_records || ys_count < key_count * kOutputsPerKey ||
889 s0s == nullptr || cws == nullptr || ys == nullptr ||
890 kOutputsPerKey > kMaxElements) return cudaErrorInvalidValue;
891 if (key_count > static_cast<size_t>(std::numeric_limits<int>::max()) / kBlocksPerKey)
892 return cudaErrorInvalidConfiguration;
893 using Scheme = Dpf<in_bits, Group, Prg, In>;
894 DpfSplitEvalAllBatchDeviceKeyKernel<Scheme, kFrontierDepth, kBlockRootDepth>
895 <<<key_count * kBlocksPerKey, 1 << kThreadsPerBlockLog2, 0, stream>>>(
896 b, s0s, cws, key_records, control_record, key_count, ys, prg);
897 return cudaGetLastError();
898}
899
900// Correction words arrive in the dense level-major split layout: layer i of
901// key k is cws[i * key_count + k], the terminal layer is
902// cws[in_bits * key_count + k], and tr[k] holds key k's packed right-control
903// bits, bit i for layer i.
904template <typename Scheme, int unroll, bool b, typename Control, typename Query>
905__global__ void DpfCoreEvalPointKernel(int4 *ys, const int4 *seeds, const int4 *cws,
906 const Control *tr, const int4 *out_cw,
907 const Query *xs, int key_count,
908 typename DpfGpuSchemeTraits<Scheme>::Backend prg) {
909 using In = typename DpfGpuSchemeTraits<Scheme>::Input;
910 constexpr int kInputBits = DpfGpuSchemeTraits<Scheme>::kInputBits;
911 static_assert(kInputBits <= sizeof(In) * 8, "In too narrow for kInputBits");
912 int thread_index = blockIdx.x * blockDim.x + threadIdx.x;
913 if (thread_index >= key_count) return;
914 int4 state = util::SetLsb(seeds[thread_index], b);
915 In x = static_cast<In>(xs[thread_index]);
916 Control ex = tr[thread_index];
917#pragma unroll unroll
918 for (int i = 0; i < kInputBits; ++i) {
919 bool take_right = ((x >> (kInputBits - 1 - i)) & 1) != 0;
920 bool right_control = ((ex >> i) & 1) != 0;
921 state = fss::detail::SchemeAccess::DpfPathStep<Scheme>(
922 prg, state, cws[static_cast<size_t>(i) * key_count + thread_index], right_control, take_right);
923 }
924 ys[thread_index] = fss::detail::SchemeAccess::DpfConvertLeaf<Scheme>(b, state, out_cw[thread_index]);
925}
926
927template <typename Scheme, typename Control, typename Query>
928struct DpfPointGpuLauncher;
929
930template <int n, typename Group, typename Prg, typename In, typename Control, typename Query>
931struct DpfPointGpuLauncher<Dpf<n, Group, Prg, In>, Control, Query> {
932 template <int unroll>
933 static void Launch(bool b, const int4 *seeds, const int4 *cws, const Control *tr,
934 const int4 *out_cw, const Query *xs, int4 *ys, int key_count, Prg prg,
935 cudaStream_t stream) {
936 using Scheme = Dpf<n, Group, Prg, In>;
937 constexpr int kThreadsPerBlock = 256;
938 int block_count = (key_count + kThreadsPerBlock - 1) / kThreadsPerBlock;
939 if (b) {
940 DpfCoreEvalPointKernel<Scheme, unroll, true, Control, Query>
941 <<<block_count, kThreadsPerBlock, 0, stream>>>(ys, seeds, cws, tr, out_cw, xs, key_count, prg);
942 } else {
943 DpfCoreEvalPointKernel<Scheme, unroll, false, Control, Query>
944 <<<block_count, kThreadsPerBlock, 0, stream>>>(ys, seeds, cws, tr, out_cw, xs, key_count, prg);
945 }
946 }
947};
948
949template <int unroll, typename Scheme, typename Control, typename Query>
950void DpfCoreEvalPointLaunch(bool b, const int4 *seeds, const int4 *cws, const Control *tr,
951 const int4 *out_cw, const Query *xs, int4 *ys, int key_count,
952 typename DpfGpuSchemeTraits<Scheme>::Backend prg, cudaStream_t stream) {
953 if (key_count == 0) return;
954 DpfPointGpuLauncher<Scheme, Control, Query>::template Launch<unroll>(
955 b, seeds, cws, tr, out_cw, xs, ys, key_count, prg, stream);
956}
957
976template <int unroll = 4, int in_bits, typename Group, typename Prg, typename In, typename Query = In>
977 requires QueryFor<Query, in_bits>
978void DpfEvalPointGpu(bool b, cuda::std::span<const int4> seeds, cuda::std::span<const int4> cws,
979 cuda::std::span<const In> tr, cuda::std::span<const Query> xs, int4 *ys, int nkeys,
980 const Dpf<in_bits, Group, Prg, In> &dpf, cudaStream_t stream = 0) {
981 if (nkeys < 0)
982 throw std::invalid_argument("DpfEvalPointGpu: nkeys must be nonnegative");
983 if (nkeys == 0) return;
984 const size_t count = static_cast<size_t>(nkeys);
985 constexpr size_t kLevels = static_cast<size_t>(in_bits) + 1;
986 if (count > SIZE_MAX / kLevels || cws.size() < count * kLevels)
987 throw std::invalid_argument("DpfEvalPointGpu: cws must hold (in_bits+1)*nkeys elements");
988 if (seeds.size() < count || tr.size() < count || xs.size() < count || ys == nullptr)
989 throw std::invalid_argument("DpfEvalPointGpu: seeds, tr, xs, and ys must cover nkeys entries");
990 const int4 *out_cw = cws.data() + static_cast<size_t>(in_bits) * static_cast<size_t>(nkeys);
991 DpfCoreEvalPointLaunch<unroll, Dpf<in_bits, Group, Prg, In>, In, Query>(b, seeds.data(), cws.data(), tr.data(),
992 out_cw, xs.data(), ys, nkeys, fss::detail::SchemeAccess::GetPrg(dpf), stream);
993}
994
995#endif // defined(__CUDACC__)
996
997} // namespace gpu::detail
998
999namespace gpu::detail {
1000
1001template <int n, typename Group, typename Prg, typename In>
1002struct DpfGpuSchemeTraits<Dpf<n, Group, Prg, In>> {
1003 using Backend = Prg;
1004 using Input = In;
1005 static constexpr int kInputBits = n;
1006};
1007
1008} // namespace gpu::detail
1009
1010template <int in_bits, typename Group, typename Prg, typename In>
1012 Groupable<Group> && Prgable<Prg, 2>)
1013__host__ __device__ inline Dpf<in_bits, Group, Prg, In>::Dpf(Prg prg)
1014 : prg_(prg) {}
1015
1016template <int in_bits, typename Group, typename Prg, typename In>
1019__host__ __device__ inline void Dpf<in_bits, Group, Prg, In>::Gen(
1020 cuda::std::span<int4> cws, In &tr, cuda::std::span<const int4, 2> s0s,
1021 In a, int4 b_buf, size_t stride) {
1022 util::RequireArg(stride > 0, "Dpf::Gen: stride must be positive");
1023 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1024 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(in_bits),
1025 "Dpf::Gen: stride exceeds the correction array limit");
1026 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1027 util::RequireArg(cws.size() >= required,
1028 "Dpf::Gen: cws must hold in_bits*stride+1 elements");
1029 Generate(CwsWriter{cws.data(), stride, tr}, s0s.data(), a, b_buf);
1030}
1031
1032template <int in_bits, typename Group, typename Prg, typename In>
1035template <typename Writer>
1036__host__ __device__ inline void Dpf<in_bits, Group, Prg, In>::Generate(
1037 Writer writer, const int4 *s0s, In a, int4 b_buf) {
1038 int4 s0 = s0s[0];
1039 s0 = util::SetLsb(s0, false);
1040 int4 s1 = s0s[1];
1041 s1 = util::SetLsb(s1, false);
1042 bool t0 = false;
1043 bool t1 = true;
1044 b_buf = util::SetLsb(b_buf, false);
1045
1046 for (int i = 0; i < in_bits; ++i) {
1047 auto [s0l, s0r] = prg_.Gen(s0);
1048 auto [s1l, s1r] = prg_.Gen(s1);
1049
1050 bool t0l = util::GetLsb(s0l);
1051 s0l = util::SetLsb(s0l, false);
1052 bool t0r = util::GetLsb(s0r);
1053 s0r = util::SetLsb(s0r, false);
1054 bool t1l = util::GetLsb(s1l);
1055 s1l = util::SetLsb(s1l, false);
1056 bool t1r = util::GetLsb(s1r);
1057 s1r = util::SetLsb(s1r, false);
1058
1059 bool a_bit = (a >> (in_bits - 1 - i)) & 1;
1060
1061 int4 s_cw;
1062 if (!a_bit) s_cw = util::Xor(s0r, s1r);
1063 else s_cw = util::Xor(s0l, s1l);
1064
1065 bool tl_cw = t0l ^ t1l ^ a_bit ^ 1;
1066 bool tr_cw = t0r ^ t1r ^ a_bit;
1067
1068 if (!a_bit) {
1069 s0 = s0l;
1070 if (t0) s0 = util::Xor(s0, s_cw);
1071 s1 = s1l;
1072 if (t1) s1 = util::Xor(s1, s_cw);
1073
1074 if (t0) t0 = t0l ^ tl_cw;
1075 else t0 = t0l;
1076 if (t1) t1 = t1l ^ tl_cw;
1077 else t1 = t1l;
1078 } else {
1079 s0 = s0r;
1080 if (t0) s0 = util::Xor(s0, s_cw);
1081 s1 = s1r;
1082 if (t1) s1 = util::Xor(s1, s_cw);
1083
1084 if (t0) t0 = t0r ^ tr_cw;
1085 else t0 = t0r;
1086 if (t1) t1 = t1r ^ tr_cw;
1087 else t1 = t1r;
1088 }
1089
1090 s_cw = util::SetLsb(s_cw, tl_cw);
1091 writer.Write(i, s_cw, tr_cw);
1092 }
1093
1094 auto v_cw_np1 = Group::From(b_buf) + (-Group::From(s0)) + Group::From(s1);
1095 if (t1) v_cw_np1 = -v_cw_np1;
1096 writer.Finish(v_cw_np1.Into());
1097}
1098
1099template <int in_bits, typename Group, typename Prg, typename In>
1101 Groupable<Group> && Prgable<Prg, 2>)
1102__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::Eval(bool b, int4 s0,
1103 cuda::std::span<const int4> cws, In tr, In x, size_t stride) {
1104 util::RequireArg(stride > 0, "Dpf::Eval: stride must be positive");
1105 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1106 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(in_bits),
1107 "Dpf::Eval: stride exceeds the correction array limit");
1108 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1109 util::RequireArg(cws.size() >= required,
1110 "Dpf::Eval: cws must hold in_bits*stride+1 elements");
1111 return EvalCws(b, s0, CwsView{cws.data(), tr, stride}, x);
1112}
1113
1114template <int in_bits, typename Group, typename Prg, typename In>
1117template <typename Cws>
1118__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::EvalCws(
1119 bool b, int4 s0, Cws cws, In x) {
1120 int4 state = Walk(b, s0, cws, x, [](bool, bool) {});
1121 const int4 terminal = cws[in_bits].s;
1122 util::RequireArg((terminal.w & 1) == 0,
1123 "Dpf::Eval: the terminal group value must have a zero LSB");
1124 return ConvertLeaf(b, state, terminal);
1125}
1126
1127template <int in_bits, typename Group, typename Prg, typename In>
1129 Groupable<Group> && Prgable<Prg, 2>)
1130inline void Dpf<in_bits, Group, Prg, In>::EvalRange(bool b, In first, In last,
1131 int4 s0, cuda::std::span<const int4> cws, In tr, cuda::std::span<int4> ys,
1132 int par_depth, size_t stride) {
1133 if (stride == 0) throw std::invalid_argument("Dpf::EvalRange: stride must be positive");
1134 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1135 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1136 throw std::invalid_argument("Dpf::EvalRange: stride exceeds the correction array limit");
1137 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1138 if (cws.size() < required || cws.data() == nullptr)
1139 throw std::invalid_argument("Dpf::EvalRange: cws must hold in_bits*stride+1 elements");
1140 EvalRangeCws(b, first, last, s0, CwsView{cws.data(), tr, stride},
1141 ys, par_depth);
1142}
1143
1144template <int in_bits, typename Group, typename Prg, typename In>
1147template <typename Cws>
1148inline void Dpf<in_bits, Group, Prg, In>::EvalRangeCws(bool b, In first, In last,
1149 int4 s0, Cws cws, cuda::std::span<int4> ys, int par_depth) {
1150 assert(par_depth >= 0);
1151 if (first > last)
1152 throw std::invalid_argument("Dpf::EvalRange: first must not exceed last");
1153 if constexpr (in_bits < sizeof(In) * 8) {
1154 if ((last >> in_bits) != 0)
1155 throw std::invalid_argument("Dpf::EvalRange: last must stay inside the input domain");
1156 }
1157 const __uint128_t difference = static_cast<__uint128_t>(last) - first;
1158 if (difference >= std::numeric_limits<size_t>::max() / sizeof(int4))
1159 throw std::invalid_argument("Dpf::EvalRange: the interval is too large for size_t");
1160 const size_t count = static_cast<size_t>(difference) + 1;
1161 if (ys.size() < count || ys.data() == nullptr)
1162 throw std::invalid_argument("Dpf::EvalRange: ys must hold last-first+1 entries");
1163 util::RequireArg((cws[in_bits].s.w & 1) == 0,
1164 "Dpf::EvalRange: the terminal group value must have a zero LSB");
1165 const size_t partitions = detail::ResolveParPartitions(par_depth, count);
1166 detail::ParallelFor(par_depth, partitions, [&](size_t partition) {
1167 const size_t remainder = count % partitions;
1168 const size_t begin = partition * (count / partitions) +
1169 (partition < remainder ? partition : remainder);
1170 const size_t length = count / partitions + (partition < remainder);
1171 const In partition_first = first + static_cast<In>(begin);
1172 const In partition_last = partition_first + static_cast<In>(length - 1);
1173 EvalRangeSerial(b, partition_first, partition_last, s0, cws, ys.data() + begin);
1174 });
1175}
1176
1177template <int in_bits, typename Group, typename Prg, typename In>
1179 Groupable<Group> && Prgable<Prg, 2>)
1180template <typename Cws>
1181inline void Dpf<in_bits, Group, Prg, In>::EvalRangeSerial(bool b, In first, In last,
1182 int4 s0, Cws cws, int4 ys[]) {
1183 constexpr In domain_last = [] {
1184 if constexpr (in_bits == sizeof(In) * 8) return static_cast<In>(~In{0});
1185 else return static_cast<In>((In{1} << in_bits) - 1);
1186 }();
1187 struct Frame {
1188 int4 state;
1189 In low;
1190 In high;
1191 int level;
1192 };
1193 cuda::std::array<Frame, in_bits + 1> stack;
1194 size_t pending = 1;
1195 stack[0] = {util::SetLsb(s0, b), In{0}, domain_last, 0};
1196 while (pending != 0) {
1197 const Frame node = stack[--pending];
1198 if (node.level == in_bits) {
1199 const int4 terminal = cws[in_bits].s;
1200 ys[static_cast<size_t>(node.low - first)] = ConvertLeaf(b, node.state, terminal);
1201 continue;
1202 }
1203 const In middle = node.low + ((node.high - node.low) >> 1);
1204 int4 left;
1205 int4 right;
1206 ExpandNode(prg_, node.state, cws[node.level].s, cws[node.level].tr, left, right);
1207 if (last > middle) {
1208 assert(pending < stack.size());
1209 stack[pending++] = {right, static_cast<In>(middle + 1), node.high, node.level + 1};
1210 }
1211 if (first <= middle) {
1212 assert(pending < stack.size());
1213 stack[pending++] = {left, node.low, middle, node.level + 1};
1214 }
1215 }
1216}
1217
1218template <int in_bits, typename Group, typename Prg, typename In>
1220 Groupable<Group> && Prgable<Prg, 2>)
1221inline void Dpf<in_bits, Group, Prg, In>::EvalAll(bool b, int4 s0,
1222 cuda::std::span<const int4> cws, In tr, int4 ys[], int par_depth, size_t stride) {
1223 if (stride == 0) throw std::invalid_argument("Dpf::EvalAll: stride must be positive");
1224 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1225 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1226 throw std::invalid_argument("Dpf::EvalAll: stride exceeds the correction array limit");
1227 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1228 if (cws.size() < required)
1229 throw std::invalid_argument("Dpf::EvalAll: cws must hold in_bits*stride+1 elements");
1230 EvalAllCws(b, s0, CwsView{cws.data(), tr, stride}, ys, par_depth);
1231}
1232
1233template <int in_bits, typename Group, typename Prg, typename In>
1236template <typename Cws>
1237inline void Dpf<in_bits, Group, Prg, In>::EvalAllCws(
1238 bool b, int4 s0, Cws cws, int4 ys[], int par_depth) {
1239 assert(par_depth >= 0);
1240 static_assert(detail::FitsFullDomain<int4, in_bits>(),
1241 "full-domain output count and byte size must fit in size_t");
1242
1243 // The terminal LSB is a caller precondition. Check it on the calling thread,
1244 // so a Debug violation aborts there instead of inside an OpenMP worker;
1245 // Release builds do not check it.
1246 util::RequireArg((cws[in_bits].s.w & 1) == 0,
1247 "Dpf::EvalAll: the terminal group value must have a zero LSB");
1248
1249 if constexpr (in_bits < kBatchPrefix + kBatchTail) {
1250 // Domains too small to fill an 8-wide frontier evaluate per input; the
1251 // point walk is cheaper than the batched machinery at this size.
1252 // The Walk call is host-only (it runs inside EvalAllCws's host ParallelFor),
1253 // so nvcc's #20013-D on the host lambda passed to the host/device Walk
1254 // template below is a known false alarm for this instantiation.
1255 constexpr size_t n = size_t{1} << in_bits;
1256 const int4 terminal = cws[in_bits].s;
1257 fss::detail::ParallelFor(par_depth, n, [&](size_t x) {
1258 const int4 state = Walk(b, s0, cws, static_cast<In>(x), [](bool, bool) {});
1259 ys[x] = ConvertLeaf(b, state, terminal);
1260 });
1261 return;
1262 }
1263
1264 int4 st = s0;
1265 st = util::SetLsb(st, b);
1266
1267 int par_depth_ = fss::detail::ResolveParDepth(par_depth, in_bits);
1268
1269 int split_depth = par_depth_;
1270 if (split_depth > in_bits - kBatchPrefix - kBatchTail) split_depth = in_bits - kBatchPrefix - kBatchTail;
1271 if (split_depth > 0) {
1272 // Split at a frontier of 2 ** split_depth subtree roots instead of per-node tasks,
1273 // so the sequential batched walk stays the fast default path.
1274 size_t num_tasks = size_t{1} << split_depth;
1275 std::vector<int4> frontier = BuildParallelFrontier(st, cws, split_depth);
1276 size_t span = (size_t{1} << in_bits) / num_tasks;
1277 fss::detail::ParallelFor(par_depth_, num_tasks, [&](size_t task) {
1278 EvalBatched8(b, frontier[task], cws, ys, task * span, split_depth);
1279 });
1280 } else {
1281 EvalBatched8(b, st, cws, ys, 0, 0);
1282 }
1283}
1284
1285template <int in_bits, typename Group, typename Prg, typename In>
1287 Groupable<Group> && Prgable<Prg, 2>)
1288__host__ __device__ inline void Dpf<in_bits, Group, Prg, In>::ExpandNode(
1289 Prg &prg, int4 state, int4 correction, bool right_control, int4 &left, int4 &right) {
1290 int control = static_cast<int>(util::GetLsb(state));
1291 auto [raw_left, raw_right] = prg.Gen(util::SetLsb(state, false));
1292 int left_control = static_cast<int>(util::GetLsb(correction));
1293 int4 seed_correction = util::SetLsb(correction, false);
1294 left = CorrectChild(raw_left, seed_correction, -control, control & left_control);
1295 right = CorrectChild(raw_right, seed_correction, -control, control & static_cast<int>(right_control));
1296}
1297
1298template <int in_bits, typename Group, typename Prg, typename In>
1300 Groupable<Group> && Prgable<Prg, 2>)
1301__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::PathStep(
1302 Prg &prg, int4 state, int4 correction, bool right_control, bool take_right) {
1303 int control = static_cast<int>(util::GetLsb(state));
1304 auto [raw_child] = detail::GenHalf<2>(prg, util::SetLsb(state, false), take_right);
1305 int child_control = static_cast<int>(take_right ? right_control : util::GetLsb(correction));
1306 return CorrectChild(raw_child, util::SetLsb(correction, false),
1307 -control, control & child_control);
1308}
1309
1310template <int in_bits, typename Group, typename Prg, typename In>
1312 Groupable<Group> && Prgable<Prg, 2>)
1313template <typename Correction>
1314__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::ConvertLeaf(
1315 bool party, int4 state, Correction &correction) {
1316 auto value = Group::From(util::SetLsb(state, false));
1317 if (util::GetLsb(state)) {
1318 if constexpr (std::is_same_v<std::remove_cv_t<Correction>, int4>) value = value + Group::From(correction);
1319 else value = value + correction;
1320 }
1321 if (party) value = -value;
1322 return value.Into();
1323}
1324
1325template <int in_bits, typename Group, typename Prg, typename In>
1327 Groupable<Group> && Prgable<Prg, 2>)
1328template <typename Cws, typename Visit>
1329__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::Walk(
1330 bool b, int4 s0, Cws cws, In x, Visit &&visit) {
1331 int4 state = util::SetLsb(s0, b);
1332 for (int i = 0; i < in_bits; ++i) {
1333 bool x_bit = (x >> (in_bits - 1 - i)) & 1;
1334 visit(util::GetLsb(state), x_bit);
1335 state = PathStep(prg_, state, cws[i].s, cws[i].tr, x_bit);
1336 }
1337 return state;
1338}
1339
1340template <int in_bits, typename Group, typename Prg, typename In>
1342 Groupable<Group> && Prgable<Prg, 2>)
1343template <typename Cws>
1344inline std::vector<int4> Dpf<in_bits, Group, Prg, In>::BuildParallelFrontier(
1345 int4 st, Cws cws, int split_depth) {
1346 std::vector<int4> frontier(size_t{1} << split_depth);
1347 frontier[0] = st;
1348 for (int lv = 0; lv < split_depth; ++lv) ExpandFrontier(frontier.data(), size_t{1} << lv, cws, lv);
1349 return frontier;
1350}
1351
1352template <int in_bits, typename Group, typename Prg, typename In>
1354 Groupable<Group> && Prgable<Prg, 2>)
1355template <typename Cws>
1356inline void Dpf<in_bits, Group, Prg, In>::ExpandFrontier(int4 frontier[], size_t w, Cws cws, int lv) {
1357 int4 correction = cws[lv].s;
1358 bool right_control = cws[lv].tr;
1359 for (size_t j = w; j-- > 0;) {
1360 int4 left, right;
1361 ExpandNode(prg_, frontier[j], correction, right_control, left, right);
1362 frontier[2 * j] = left;
1363 frontier[2 * j + 1] = right;
1364 }
1365}
1366
1367template <int in_bits, typename Group, typename Prg, typename In>
1369 Groupable<Group> && Prgable<Prg, 2>)
1370__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::CorrectChild(int4 child, int4 s_cw, int m, int t_cw) {
1371 int4 res = {child.x ^ (s_cw.x & m), child.y ^ (s_cw.y & m), child.z ^ (s_cw.z & m), child.w ^ (s_cw.w & m)};
1372 res.w = (res.w & ~1) | ((child.w ^ t_cw) & 1);
1373 return res;
1374}
1375
1376template <int in_bits, typename Group, typename Prg, typename In>
1378 Groupable<Group> && Prgable<Prg, 2>)
1379template <typename Cws>
1380inline void Dpf<in_bits, Group, Prg, In>::EvalBatched8(
1381 bool b, int4 st, Cws cws, int4 ys[], size_t l, int i) {
1382 int4 frontier[8];
1383 frontier[0] = st;
1384 for (int lv = i; lv < i + kBatchPrefix; ++lv) ExpandFrontier(frontier, size_t{1} << (lv - i), cws, lv);
1385 EvalBatched8Group(b, frontier, cws, ys, l, i + kBatchPrefix);
1386}
1387
1388template <int in_bits, typename Group, typename Prg, typename In>
1390 Groupable<Group> && Prgable<Prg, 2>)
1391template <typename Cws>
1392inline void Dpf<in_bits, Group, Prg, In>::EvalBatched8Group(
1393 bool b, const int4 (&frontier)[8], Cws cws, int4 ys[], size_t l, int i) {
1394 if (i == in_bits - kBatchTail) {
1395 // Expand the group to its 8 << kBatchTail leaves in local memory, then convert.
1396 int4 bufs[2][8 << kBatchTail];
1397 int4 *cur = bufs[0];
1398 int4 *next = bufs[1];
1399 for (int j = 0; j < 8; ++j) cur[j] = frontier[j];
1400 int w = 8;
1401 for (int lv = i; lv < in_bits; ++lv) {
1402 int4 s_cw = util::SetLsb(cws[lv].s, false);
1403 int tl_cw = util::GetLsb(cws[lv].s);
1404 int tr_cw = cws[lv].tr;
1405 for (int g = 0; g < w / 8; ++g) {
1406 cuda::std::array<int4, 8> seeds;
1407 int ts[8];
1408 for (int j = 0; j < 8; ++j) {
1409 ts[j] = static_cast<int>(util::GetLsb(cur[8 * g + j]));
1410 seeds[j] = util::SetLsb(cur[8 * g + j], false);
1411 }
1412 auto out = detail::GenBatch<2>(prg_, seeds);
1413 for (int j = 0; j < 8; ++j) {
1414 int m = -ts[j];
1415 next[16 * g + 2 * j] = CorrectChild(out[j][0], s_cw, m, ts[j] & tl_cw);
1416 next[16 * g + 2 * j + 1] = CorrectChild(out[j][1], s_cw, m, ts[j] & tr_cw);
1417 }
1418 }
1419 int4 *swap = cur;
1420 cur = next;
1421 next = swap;
1422 w *= 2;
1423 }
1424
1425 int4 v_cw_np1 = cws[in_bits].s;
1426 // The shared entry validated the terminal before parallel dispatch.
1427 assert((v_cw_np1.w & 1) == 0);
1428 Group v_cw = Group::From(v_cw_np1);
1429 for (int j = 0; j < w; ++j) ys[l + j] = ConvertLeaf(b, cur[j], v_cw);
1430 return;
1431 }
1432
1433 int4 s_cw = util::SetLsb(cws[i].s, false);
1434 int tl_cw = util::GetLsb(cws[i].s);
1435 int tr_cw = cws[i].tr;
1436
1437 cuda::std::array<int4, 8> seeds;
1438 int ts[8];
1439 for (int j = 0; j < 8; ++j) {
1440 ts[j] = static_cast<int>(util::GetLsb(frontier[j]));
1441 seeds[j] = util::SetLsb(frontier[j], false);
1442 }
1443 auto out = detail::GenBatch<2>(prg_, seeds);
1444 // The 16 children interleave in leaf order: node j's children land at 2j, 2j+1 of the
1445 // next level. De-interleave them into two consecutive groups of 8.
1446 int4 low[8];
1447 int4 high[8];
1448 for (int j = 0; j < 4; ++j) {
1449 int m = -ts[j];
1450 low[2 * j] = CorrectChild(out[j][0], s_cw, m, ts[j] & tl_cw);
1451 low[2 * j + 1] = CorrectChild(out[j][1], s_cw, m, ts[j] & tr_cw);
1452 int m4 = -ts[j + 4];
1453 high[2 * j] = CorrectChild(out[j + 4][0], s_cw, m4, ts[j + 4] & tl_cw);
1454 high[2 * j + 1] = CorrectChild(out[j + 4][1], s_cw, m4, ts[j + 4] & tr_cw);
1455 }
1456
1457 size_t half = size_t{8} << (in_bits - i - 1);
1458 EvalBatched8Group(b, low, cws, ys, l, i + 1);
1459 EvalBatched8Group(b, high, cws, ys, l + half, i + 1);
1460}
1461
1462template <int in_bits, typename Group, typename Prg, typename In>
1464 Groupable<Group> && Prgable<Prg, 2>)
1465template <int frontier_depth, int threads_per_block>
1467struct Dpf<in_bits, Group, Prg, In>::GpuLaunchShape {
1468 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
1469 static_assert(1 <= threads_per_block && threads_per_block <= 1024 && (threads_per_block & (threads_per_block - 1)) == 0,
1470 "gpu block size must be a power of two in [1,1024]");
1471 static constexpr int kFrontierDepth = frontier_depth < in_bits ? frontier_depth : in_bits;
1472 static_assert(in_bits - kFrontierDepth <= 8,
1473 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
1474 static constexpr int kThreadsPerBlockLog2 = [] {
1475 int threads_per_block_log2 = 0;
1476 for (int thread_count = threads_per_block; thread_count > 1; thread_count >>= 1) ++threads_per_block_log2;
1477 return threads_per_block_log2 < kFrontierDepth ? threads_per_block_log2 : kFrontierDepth;
1478 }();
1479 static constexpr int kThreadsPerBlock = 1 << kThreadsPerBlockLog2;
1480 static constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
1481 static_assert(kBlockRootDepth <= 30, "gpu block index exceeds the signed-int range");
1482 static constexpr size_t kBlocksPerKey = size_t{1} << (kBlockRootDepth <= 30 ? kBlockRootDepth : 0);
1483};
1484
1485template <int in_bits, typename Group, typename Prg, typename In>
1487 Groupable<Group> && Prgable<Prg, 2>)
1488template <int frontier_depth, int threads_per_block>
1489__host__ inline cudaError_t Dpf<in_bits, Group, Prg, In>::EvalAllGpuBatchCw(
1490 bool b, cuda::std::span<const int4> s0s, cuda::std::span<const Cw> cws,
1491 cuda::std::span<int4> ys, cudaStream_t stream) const {
1492#if defined(__CUDACC__)
1493 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1494 using Scheme = Dpf<in_bits, Group, Prg, In>;
1495 constexpr size_t kOutputsPerKey = size_t{1} << in_bits;
1496 constexpr size_t kCwPerKey = in_bits + 1;
1497 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
1498 size_t key_count = s0s.size();
1499 if (key_count == 0) return cudaSuccess;
1500 if (key_count > kMaxAddressableSize / kCwPerKey / sizeof(Cw) || key_count > kMaxAddressableSize / kOutputsPerKey / sizeof(int4) ||
1501 cws.size() < key_count * kCwPerKey || ys.size() < key_count * kOutputsPerKey ||
1502 s0s.data() == nullptr || cws.data() == nullptr || ys.data() == nullptr) return cudaErrorInvalidValue;
1503 if (key_count > static_cast<size_t>(std::numeric_limits<int>::max()) / LaunchShape::kBlocksPerKey)
1504 return cudaErrorInvalidConfiguration;
1505 gpu::detail::DpfEvalAllBatchKernel<Scheme, LaunchShape::kFrontierDepth, LaunchShape::kBlockRootDepth>
1506 <<<key_count * LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1507 b, s0s.data(), cws.data(), ys.data(), prg_);
1508 return cudaGetLastError();
1509#else
1510 static_assert(sizeof(Prg) == 0, "gpu launch requires a CUDA compiler");
1511 return cudaErrorNotSupported;
1512#endif
1513}
1514
1515template <int in_bits, typename Group, typename Prg, typename In>
1517 Groupable<Group> && Prgable<Prg, 2>)
1518template <int frontier_depth, int threads_per_block>
1520 bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws, In tr,
1521 cuda::std::span<int4> ys, cudaStream_t stream, size_t stride) const {
1522#if defined(__CUDACC__)
1523 using Scheme = Dpf<in_bits, Group, Prg, In>;
1524 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
1525 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
1526 (threads_per_block & (threads_per_block - 1)) == 0,
1527 "gpu block size must be a power of two in [1,1024]");
1528 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1529 if (stride == 0 || (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits)) ||
1530 cws.size() < static_cast<size_t>(in_bits) * stride + 1 ||
1531 cws.data() == nullptr || ys.data() == nullptr)
1532 return cudaErrorInvalidValue;
1533 return detail::SplitRangeChunks<in_bits, frontier_depth, threads_per_block>(
1534 first, last, ys.size(),
1535 [&](In chunk_first, int depth, size_t offset, int frontier, int thread_bits, int root,
1536 size_t blocks) {
1537 gpu::detail::DpfSplitEvalRangeKernel<Scheme, threads_per_block>
1538 <<<blocks, 1 << thread_bits, 0, stream>>>(b, s0, cws.data(), tr, stride,
1539 ys.data() + offset, chunk_first, depth, frontier, root, prg_);
1540 return cudaGetLastError();
1541 });
1542#else
1543 static_assert(sizeof(Prg) == 0, "gpu launch requires a CUDA compiler");
1544 return cudaErrorNotSupported;
1545#endif
1546}
1547
1548template <int in_bits, typename Group, typename Prg, typename In>
1551template <int frontier_depth, int threads_per_block>
1553 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
1554 cuda::std::span<int4> ys, cudaStream_t stream, size_t stride) const
1555 requires(in_bits < sizeof(size_t) * 8) {
1556#if defined(__CUDACC__)
1557 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1558 using Scheme = Dpf<in_bits, Group, Prg, In>;
1559 constexpr size_t kOutputsPerKey = size_t{1} << in_bits;
1560 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1561 if (stride == 0 ||
1562 (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits)))
1563 return cudaErrorInvalidValue;
1564 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1565 if (cws.size() < required || ys.size() < kOutputsPerKey ||
1566 cws.data() == nullptr || ys.data() == nullptr ||
1567 kOutputsPerKey > std::numeric_limits<size_t>::max() / sizeof(int4)) return cudaErrorInvalidValue;
1568 gpu::detail::DpfSplitEvalAllKernel<Scheme, LaunchShape::kFrontierDepth, LaunchShape::kBlockRootDepth>
1569 <<<LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1570 b, s0, cws.data(), tr, stride, ys.data(), prg_);
1571 return cudaGetLastError();
1572#else
1573 static_assert(sizeof(Prg) == 0, "gpu launch requires a CUDA compiler");
1574 return cudaErrorNotSupported;
1575#endif
1576}
1577
1578template <int in_bits, typename Group, typename Prg, typename In>
1581template <int frontier_depth, int threads_per_block>
1583 bool b, cuda::std::span<const int4> s0s, cuda::std::span<const int4> cws,
1584 cuda::std::span<const In> tr, cuda::std::span<int4> ys,
1585 cudaStream_t stream, size_t stride) const
1586 requires(in_bits < sizeof(size_t) * 8) {
1587#if defined(__CUDACC__)
1588 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1589 using Scheme = Dpf<in_bits, Group, Prg, In>;
1590 constexpr size_t kOutputsPerKey = size_t{1} << in_bits;
1591 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1592 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
1593 size_t key_count = s0s.size();
1594 if (key_count == 0) return cudaSuccess;
1595 // The output guard bounds key_count * kOutputsPerKey * sizeof(int4) so the
1596 // product in the size check below cannot wrap.
1597 if (key_count > kMaxElements ||
1598 key_count > kMaxAddressableSize / kOutputsPerKey / sizeof(int4) ||
1599 (stride != 0 && (stride < key_count || stride > kMaxElements)))
1600 return cudaErrorInvalidValue;
1601 // A stride of 0 resolves to the key count, which is the dense level-major layout.
1602 const size_t resolved_stride = stride == 0 ? key_count : stride;
1603 if (resolved_stride > (kMaxElements - key_count) / static_cast<size_t>(in_bits))
1604 return cudaErrorInvalidValue;
1605 const size_t required = static_cast<size_t>(in_bits) * resolved_stride + key_count;
1606 if (cws.size() < required || tr.size() < key_count ||
1607 ys.size() < key_count * kOutputsPerKey ||
1608 s0s.data() == nullptr || cws.data() == nullptr || tr.data() == nullptr ||
1609 ys.data() == nullptr) return cudaErrorInvalidValue;
1610 if (key_count > static_cast<size_t>(std::numeric_limits<int>::max()) / LaunchShape::kBlocksPerKey)
1611 return cudaErrorInvalidConfiguration;
1612 gpu::detail::DpfSplitEvalAllBatchKernel<Scheme, LaunchShape::kFrontierDepth, LaunchShape::kBlockRootDepth>
1613 <<<key_count * LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1614 b, s0s.data(), cws.data(), tr.data(), resolved_stride, ys.data(), prg_);
1615 return cudaGetLastError();
1616#else
1617 static_assert(sizeof(Prg) == 0, "gpu launch requires a CUDA compiler");
1618 return cudaErrorNotSupported;
1619#endif
1620}
1621
1622#if FSS_ENABLE_V1_COMPAT
1623
1624// Deprecated v1.3.0 aggregate-Cw entries. Every wrapper copies between the
1625// caller's Cw layout and a compile-time-sized split-component scratch buffer,
1626// then runs the current public method at stride 1. The scratch copies are the
1627// compatibility cost and never enter the core algorithms.
1628template <int in_bits, typename Group, typename Prg, typename In>
1631__host__ __device__ inline void Dpf<in_bits, Group, Prg, In>::Gen(
1632 Cw cws[], const int4 s0s[2], In a, int4 b_buf) {
1633 int4 seed[in_bits + 1];
1634 In tr;
1635 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr,
1636 cuda::std::span<const int4, 2>(s0s, 2), a, b_buf);
1637 for (int i = 0; i < in_bits; ++i) cws[i] = {seed[i], ((tr >> i) & 1) != 0};
1638 // The terminal is a plain group value; its unused tr bit is false.
1639 cws[in_bits] = {seed[in_bits], false};
1640}
1641
1642template <int in_bits, typename Group, typename Prg, typename In>
1644 Groupable<Group> && Prgable<Prg, 2>)
1645__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::Eval(
1646 bool b, int4 s0, const Cw cws[], In x) {
1647 int4 seed[in_bits + 1];
1648 In tr = 0;
1649 for (int i = 0; i <= in_bits; ++i) seed[i] = cws[i].s;
1650 for (int i = 0; i < in_bits; ++i) tr |= static_cast<In>(cws[i].tr) << i;
1651 return Eval(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, x);
1652}
1653
1654template <int in_bits, typename Group, typename Prg, typename In>
1656 Groupable<Group> && Prgable<Prg, 2>)
1658 bool b, int4 s0, const Cw cws[], int4 ys[]) {
1659 int4 seed[in_bits + 1];
1660 In tr = 0;
1661 for (int i = 0; i <= in_bits; ++i) seed[i] = cws[i].s;
1662 for (int i = 0; i < in_bits; ++i) tr |= static_cast<In>(cws[i].tr) << i;
1663 EvalAll(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys);
1664}
1665
1666// Deprecated v1.3.0 aggregate-Key entries. Same conversion as the Cw entries
1667// above; the Key entries additionally spread the shared correction words over
1668// both keys and take the party seeds from s0s. The bodies spell the current
1669// public Key type, which stays undeprecated.
1670template <int in_bits, typename Group, typename Prg, typename In>
1672 Groupable<Group> && Prgable<Prg, 2>)
1673__host__ __device__ inline void Dpf<in_bits, Group, Prg, In>::Gen(
1674 Key &k0, Key &k1, cuda::std::span<const int4, 2> s0s, In a, int4 b_buf) {
1675 int4 seed[in_bits + 1];
1676 In tr;
1677 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr, s0s, a, b_buf);
1678 for (int i = 0; i < in_bits; ++i) {
1679 k0.cws[i] = {seed[i], ((tr >> i) & 1) != 0};
1680 k1.cws[i] = k0.cws[i];
1681 }
1682 // The terminal is a plain group value; its unused tr bit is false.
1683 k0.cws[in_bits] = {seed[in_bits], false};
1684 k1.cws[in_bits] = k0.cws[in_bits];
1685 k0.s0 = s0s[0];
1686 k1.s0 = s0s[1];
1687}
1688
1689template <int in_bits, typename Group, typename Prg, typename In>
1691 Groupable<Group> && Prgable<Prg, 2>)
1692__host__ __device__ inline int4 Dpf<in_bits, Group, Prg, In>::Eval(
1693 bool b, const Key &key, In x) {
1694 int4 seed[in_bits + 1];
1695 In tr = 0;
1696 for (int i = 0; i <= in_bits; ++i) seed[i] = key.cws[i].s;
1697 for (int i = 0; i < in_bits; ++i) tr |= static_cast<In>(key.cws[i].tr) << i;
1698 return Eval(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, x);
1699}
1700
1701template <int in_bits, typename Group, typename Prg, typename In>
1703 Groupable<Group> && Prgable<Prg, 2>)
1705 bool b, const Key &key, cuda::std::span<int4> ys) {
1706 int4 seed[in_bits + 1];
1707 In tr = 0;
1708 for (int i = 0; i <= in_bits; ++i) seed[i] = key.cws[i].s;
1709 for (int i = 0; i < in_bits; ++i) tr |= static_cast<In>(key.cws[i].tr) << i;
1710 EvalAll(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys.data());
1711}
1712
1713#endif // FSS_ENABLE_V1_COMPAT
1714
1715} // namespace fss
2-party DPF scheme.
Definition dpf.cuh:112
static constexpr int kCwCount
Definition dpf.cuh:150
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 dpf.cuh:1552
Dpf(Prg prg)
Configure the pseudorandom generator.
Definition dpf.cuh:1013
int4 Eval(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, In x, size_t stride=1)
Evaluate one point using separate correction components.
Definition dpf.cuh:1102
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 arrays.
Definition dpf.cuh:1519
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 on the host from split correction words.
Definition dpf.cuh:1221
__uint128_t Input
Definition dpf.cuh:152
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 correction words in a caller-strided layout and packed control bits.
Definition dpf.cuh:1019
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] on the host from split correction words.
Definition dpf.cuh:1130
cudaError_t EvalAllGpuBatch(bool b, cuda::std::span< const int4 > s0s, cuda::std::span< const int4 > cws, cuda::std::span< const __uint128_t > tr, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=0) const
2-party DCF scheme over F2 from standard DPF (Grotto construction).
Definition grotto_dcf.cuh:103
Single build-time switch for the deprecated v1 Cw-form compatibility entries.
Group interface.
Definition group.cuh:49
Pseudorandom generator (PRG) interface.
Definition prg.cuh:24
Unsigned input storage wide enough for an input domain of in_bits bits.
Definition constraints.cuh:29
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 dpf.cuh:123
int4 s
Seed correction, with the left control correction in its least significant bit.
Definition dpf.cuh:124
bool tr
Right control correction.
Definition dpf.cuh:125
One party's key in the aggregate Cw layout: the correction words plus the party's initial seed.
Definition dpf.cuh:137
int4 s0
This party's initial seed.
Definition dpf.cuh:139
Cw cws[in_bits+1]
Tree correction words followed by the output correction word.
Definition dpf.cuh:138