myl7/fss 2.0.0
Function secret sharing (FSS) primitives including distributed point/comparison function (DPF/DCF)
Loading...
Searching...
No Matches
packed_half_tree_dpf.cuh
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
64
65#pragma once
66#include <cuda_runtime.h>
67#include <type_traits>
68#include <cstddef>
69#include <cassert>
70#include <vector>
71#ifdef _OPENMP
72#include <omp.h>
73#endif
74#include <fss/hash.cuh>
75#include <fss/util.cuh>
76#include <fss/constraints.cuh>
77#include <fss/compat.cuh>
78#include <cuda/std/span>
79#include <limits>
80
81#include <fss/half_tree_dpf.cuh>
82#include <stdexcept>
83
84namespace fss {
85
86namespace detail {
87class SchemeAccess;
88
89// Split Cws view shared by the host and device split paths. The packed
90// construction carries no control bits, so layer l is just cws[l*stride] and the
91// terminal entry holds the packed-output correction word at cws[kDepth*stride].
92// operator[] recovers the Cw the shared algorithms consume, so their bodies stay
93// identical on the Cw and split paths.
94template <typename Cw>
95struct PackedHalfTreeCwsView {
96 const int4 *cws;
97 size_t stride;
98
99 __host__ __device__ inline Cw operator[](int l) const {
100 return Cw{cws[static_cast<size_t>(l) * stride]};
101 }
102};
103
104} // namespace detail
105
106template <int in_bits, typename Hash, typename In>
107 requires(ValidForInputBits<In, in_bits> && CcrHashable<Hash>)
109
126template <int in_bits, int out_bits, typename Hash, typename In = uint>
128 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
130public:
134 static constexpr int kLanes = 128 / out_bits;
135
139 static constexpr int kDepth = in_bits - [] {
140 int r = 0;
141 int v = kLanes;
142 while (v > 1) {
143 v >>= 1;
144 ++r;
145 }
146 return r;
147 }();
148 static_assert(kDepth >= 1, "in_bits must exceed log2(kLanes) so that the tree has at least one level");
149
159 struct __align__(16) Cw {
160 int4 s;
161 };
162 static_assert(sizeof(Cw) == 16);
163
165 static constexpr int kCwCount = kDepth + 1;
166
167 using Input = In;
168
175 __host__ __device__ explicit PackedHalfTreeDpf(Hash hash, int4 instance_seed);
176
205 __host__ __device__ void Gen(cuda::std::span<int4> cws,
206 cuda::std::span<const int4, 2> s0s, In a, uint64_t beta, size_t stride = 1);
207
232 __host__ __device__ int4 Eval(bool b, int4 s0, cuda::std::span<const int4> cws,
233 In x, size_t stride = 1);
234
249 static __host__ __device__ uint64_t Extract(int4 block, In x);
250
275 void EvalAll(bool b, int4 s0, cuda::std::span<const int4> cws, int4 ys[],
276 int par_depth = 0, size_t stride = 1);
277
306 void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws,
307 cuda::std::span<int4> ys, int par_depth = 0, size_t stride = 1);
308
330 template <int frontier_depth = 16, int threads_per_block = 256>
331 __host__ cudaError_t EvalAllGpu(bool b, int4 s0, cuda::std::span<const int4> cws,
332 cuda::std::span<int4> ys, cudaStream_t stream = 0, size_t stride = 1) const
333 requires(kDepth < sizeof(size_t) * 8);
334
363 template <int frontier_depth = 16, int threads_per_block = 256>
364 __host__ cudaError_t EvalAllGpuBatch(bool b, cuda::std::span<const int4> s0s,
365 cuda::std::span<const int4> cws, cuda::std::span<int4> ys,
366 cudaStream_t stream = 0, size_t stride = 0) const
367 requires(kDepth < sizeof(size_t) * 8);
368
369#if FSS_ENABLE_V1_COMPAT
370 // Deprecated v1.3.0 aggregate-Key entries and public constants; see
371 // fss/compat.cuh. The Key alias exposes the compat record under the legacy
372 // nested name, while the method declarations spell the record type so a
373 // deprecated name never enters a member signature. The constants restore the
374 // legacy public names over the renamed private spellings; kTopDepth was a
375 // constant of the old implementation and does not steer the current
376 // scheduling. EvalAll() runs sequentially, as the current split-component
377 // default does; the old automatic thread-count split is not restored.
378
380 using Key [[deprecated("use the split-component Gen/Eval/EvalAll with the packed-output terminal in the cws array")]] =
381 compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth>;
382
384 [[deprecated("beta must fit in out_bits bits; express the bound from out_bits instead")]]
385 static constexpr uint64_t kBetaMax = out_bits == 64 ? ~0ULL : (1ULL << out_bits) - 1;
386
388 [[deprecated("the constant described the removed scalar-top implementation and does not steer the current scheduling")]]
389 static constexpr int kTopDepth = 3;
390
392 [[deprecated("use the split-component Gen(cuda::std::span<int4>, cuda::std::span<const int4, 2>, In, uint64_t, size_t) overload")]]
393 __host__ __device__ void Gen(compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &k0,
394 compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &k1,
395 cuda::std::span<const int4, 2> s0s, In a, uint64_t beta);
396
398 [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, size_t) overload")]]
399 __host__ __device__ int4 Eval(bool b, const compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &key, In x);
400
402 [[deprecated("use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, int4[], int, size_t) overload")]]
403 void EvalAll(bool b, const compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &key,
404 cuda::std::span<int4> ys);
405#endif // FSS_ENABLE_V1_COMPAT
406
407private:
408 friend class detail::SchemeAccess;
409
410 template <int frontier_depth, int threads_per_block>
411 struct GpuLaunchShape;
412
413 static __host__ __device__ void ExpandNode(Hash &hash, int4 instance_seed,
414 int4 node, int4 cw, int4 &left, int4 &right);
415 static __host__ __device__ int4 PathStep(Hash &hash, int4 instance_seed,
416 int4 node, int4 cw, bool take_right);
417 static __host__ __device__ int4 ConvertLeaf(Hash &hash, int4 instance_seed,
418 int4 node, int4 output_cw);
419
420 Hash hash_;
421 int4 instance_seed_;
422
423 // Split Cws view over the caller's array. The host methods and the
424 // split GPU kernels share this one view; see detail::PackedHalfTreeCwsView.
425 using CwsView = detail::PackedHalfTreeCwsView<Cw>;
426
427 // Writes the strided Cw slots.
428 struct CwsWriter {
429 int4 *cws;
430 size_t stride;
431
432 __host__ __device__ void Write(int i, int4 s_cw) {
433 cws[static_cast<size_t>(i) * stride] = s_cw;
434 }
435
436 __host__ __device__ void Finish(int4 terminal) {
437 cws[static_cast<size_t>(kDepth) * stride] = terminal;
438 }
439 };
440
441 // Shared Cw generation loop. The writer is passed by value and owns
442 // only output access, so the existing algorithm and backend use stay unchanged.
443 template <typename Writer>
444 __host__ __device__ void Generate(Writer writer, const int4 *s0s, In a, uint64_t beta);
445
446 // Shared point evaluation over Cw arrays and split Cws views.
447 template <typename Cws>
448 __host__ __device__ int4 EvalCws(bool b, int4 s0, Cws cws, In x);
449
450 // Shared full-domain evaluation over Cw arrays and split Cws views.
451 template <typename Cws>
452 void EvalAllCws(bool b, int4 s0, Cws cws, int4 ys[], int par_depth);
453
454 // Shared interval evaluation. Cws is a Cw pointer on the array form
455 // and a CwsView on the split form.
456 template <typename Cws>
457 void EvalRangeCws(bool b, In first, In last, int4 s0, Cws cws,
458 cuda::std::span<int4> ys, int par_depth);
459
460 template <typename Cws>
461 void EvalRangeTree(int4 node, Cws cws, int4 ys[], In first,
462 In last, In output_first, In low, In high, int level);
463
464 template <int other_bits, typename OtherHash, typename OtherIn>
465 requires(ValidForInputBits<OtherIn, other_bits> && CcrHashable<OtherHash>)
466 friend class HalfTreeGrottoDcf;
467
468 // The private spellings below back the deprecated public aliases kBetaMax
469 // and kTopDepth of the compatibility section; the values and every use are
470 // unchanged by the rename.
471 static constexpr uint64_t kBetaLimit = out_bits == 64 ? ~0ULL : (1ULL << out_bits) - 1;
472 // Three scalar levels produce the eight parents consumed by Expand8().
473 static constexpr int kBatchTopDepth = 3;
474
475 static __host__ __device__ int4 PackBeta(In a, uint64_t beta);
476
477 // Walks x's path to its level-kDepth node. visit(t, x_bit) sees the control bit of every
478 // expanded node and the branch x takes from it.
479 template <typename Cws, typename Visit>
480 __host__ __device__ int4 Walk(bool b, int4 s0, Cws cws, In x, Visit &&visit);
481
482 // lhs ^ (rhs & mask) with mask being 0 or -1 in every lane, so the per-node correction words
483 // controlled by the seed LSB apply branchlessly.
484 static __host__ __device__ int4 MaskXor(int4 lhs, int4 rhs, int mask);
485
486 // Expands one node with a scalar hash call: left = H_S(node) ^ (t ? cw : 0), right = left ^ node.
487 __host__ __device__ void ExpandNode(int4 node, int4 cw, int4 &left, int4 &right);
488
489 // Level-order full-domain evaluation over a batched frontier. The frontier of
490 // level i lives in bufs[i & 1]; the final expansion fuses the leaf conversion and writes
491 // each ys block exactly once, in the same order the DFS walk produces. The level-order
492 // node index equals the in-order leaf index, so the ys layout is unchanged.
493 template <typename Cws>
494 void EvalAllFrontier(bool b, int4 s0, Cws cws, int4 fcw, int4 ys[], int par_depth);
495
496 // Expands the 8 frontier parents src[8k..8k+8) into their 16 children with one batch.
497 // Parent p has children 2p and 2p + 1, so the children of a batch interleave in pairs.
498 void Expand8(const int4 *src, int4 *dst, size_t k, int4 cw);
499
500 // Last-level Expand8() with the leaf conversion fused in: the children are the level
501 // kDepth nodes, so they are hashed straight from registers into two more batches
502 // (even and odd children), fcw applies through the child LSB mask, and each final block
503 // is stored exactly once.
504 void ExpandConvert8(const int4 *src, int4 *ys, size_t k, int4 cw, int4 fcw);
505
506 __host__ __device__ int4 ConvertLeaf(int4 node, int4 fcw);
507};
508
509// -----------------------------------------------------------------------------
510
511template <int in_bits, int out_bits, typename Hash, typename In>
513 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
514__host__ __device__ inline PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::PackedHalfTreeDpf(Hash hash, int4 instance_seed)
515 : hash_(hash), instance_seed_(instance_seed) {}
516
517template <int in_bits, int out_bits, typename Hash, typename In>
519 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
520__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::PackBeta(
521 In a, uint64_t beta) {
522 int lane = static_cast<int>(a) & (kLanes - 1);
523 int4 packed = {0, 0, 0, 0};
524 if constexpr (out_bits == 64) {
525 packed = lane == 0 ? int4{static_cast<int>(beta), static_cast<int>(beta >> 32), 0, 0}
526 : int4{0, 0, static_cast<int>(beta), static_cast<int>(beta >> 32)};
527 } else {
528 constexpr int lanes_per_word = 32 / out_bits;
529 int word = lane / lanes_per_word;
530 int off = (lane % lanes_per_word) * out_bits;
531 unsigned int v = static_cast<unsigned int>(beta) << off;
532 if (word == 0) packed.x = static_cast<int>(v);
533 else if (word == 1) packed.y = static_cast<int>(v);
534 else if (word == 2) packed.z = static_cast<int>(v);
535 else packed.w = static_cast<int>(v);
536 }
537 return packed;
538}
539
540template <int in_bits, int out_bits, typename Hash, typename In>
542 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
544 int4 block, In x) {
545 int lane = static_cast<int>(x) & (kLanes - 1);
546 if constexpr (out_bits == 64) {
547 uint64_t lo = lane == 0 ? static_cast<unsigned int>(block.x) : static_cast<unsigned int>(block.z);
548 uint64_t hi = lane == 0 ? static_cast<unsigned int>(block.y) : static_cast<unsigned int>(block.w);
549 return lo | (hi << 32);
550 } else {
551 constexpr int lanes_per_word = 32 / out_bits;
552 int word = lane / lanes_per_word;
553 int off = (lane % lanes_per_word) * out_bits;
554 unsigned int w = word == 0 ? static_cast<unsigned int>(block.x)
555 : word == 1 ? static_cast<unsigned int>(block.y)
556 : word == 2 ? static_cast<unsigned int>(block.z)
557 : static_cast<unsigned int>(block.w);
558 return (w >> off) & static_cast<unsigned int>((1ULL << out_bits) - 1);
559 }
560}
561
562template <int in_bits, int out_bits, typename Hash, typename In>
564 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
566 cuda::std::span<int4> cws, cuda::std::span<const int4, 2> s0s, In a, uint64_t beta,
567 size_t stride) {
568 util::RequireArg(stride > 0, "PackedHalfTreeDpf::Gen: stride must be positive");
569 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
570 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(kDepth),
571 "PackedHalfTreeDpf::Gen: stride exceeds the correction array limit");
572 const size_t required = static_cast<size_t>(kDepth) * stride + 1;
573 util::RequireArg(cws.size() >= required,
574 "PackedHalfTreeDpf::Gen: cws must hold kDepth*stride+1 elements");
575 Generate(CwsWriter{cws.data(), stride}, s0s.data(), a, beta);
576}
577
578template <int in_bits, int out_bits, typename Hash, typename In>
580 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
581template <typename Writer>
582__host__ __device__ inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::Generate(
583 Writer writer, const int4 *s0s, In a, uint64_t beta) {
584 util::RequireArg(beta <= kBetaLimit, "PackedHalfTreeDpf::Gen: beta exceeds kBetaMax");
585
586 int4 packed_beta = PackBeta(a, beta);
587
588 // node1 starts with the control bit set, so the path difference delta keeps LSB 1 and
589 // exactly one party has t=1 on the path at every level.
590 int4 node0 = util::SetLsb(s0s[0], false);
591 int4 node1 = util::SetLsb(s0s[1], true);
592 int4 delta = util::Xor(node0, node1); // LSB = 0^1 = 1
593
594 for (int i = 0; i < kDepth; ++i) {
595 int4 h0 = hash_.Hash(util::Xor(instance_seed_, node0));
596 int4 h1 = hash_.Hash(util::Xor(instance_seed_, node1));
597
598 bool a_bit = (a >> (in_bits - 1 - i)) & 1;
599
600 // CW = h0 ^ h1 ^ (!a_bit ? delta : 0)
601 // When a_bit=0 (go left): non-alpha is right, CW = h0^h1^delta makes right0=right1
602 // When a_bit=1 (go right): non-alpha is left, CW = h0^h1 makes left0=left1
603 int4 cw = util::Xor(h0, h1);
604 if (!a_bit) cw = util::Xor(cw, delta);
605
606 writer.Write(i, cw);
607
608 bool t0 = util::GetLsb(node0);
609 bool t1 = util::GetLsb(node1);
610
611 // node_b = h_b ^ (a_bit ? node_b : 0) ^ (t_b ? cw : 0)
612 int4 zero4 = {0, 0, 0, 0};
613 int4 ab_mask0 = a_bit ? node0 : zero4;
614 int4 ab_mask1 = a_bit ? node1 : zero4;
615 int4 t0_mask = t0 ? cw : zero4;
616 int4 t1_mask = t1 ? cw : zero4;
617
618 node0 = util::Xor(util::Xor(h0, ab_mask0), t0_mask);
619 node1 = util::Xor(util::Xor(h1, ab_mask1), t1_mask);
620
621 // delta = node0 ^ node1 for the next level; stays the root seed XOR
622 delta = util::Xor(node0, node1);
623 }
624
625 // The leaf hash masks the constant path difference, see the file-level details.
626 int4 leaf0 = hash_.Hash(util::Xor(instance_seed_, node0));
627 int4 leaf1 = hash_.Hash(util::Xor(instance_seed_, node1));
628 int4 fcw = util::Xor(util::Xor(packed_beta, leaf0), leaf1);
629 writer.Finish(fcw);
630}
631
632template <int in_bits, int out_bits, typename Hash, typename In>
634 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
636 bool b, int4 s0, cuda::std::span<const int4> cws, In x, size_t stride) {
637 util::RequireArg(stride > 0, "PackedHalfTreeDpf::Eval: stride must be positive");
638 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
639 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(kDepth),
640 "PackedHalfTreeDpf::Eval: stride exceeds the correction array limit");
641 const size_t required = static_cast<size_t>(kDepth) * stride + 1;
642 util::RequireArg(cws.size() >= required,
643 "PackedHalfTreeDpf::Eval: cws must hold kDepth*stride+1 elements");
644 return EvalCws(b, s0, CwsView{cws.data(), stride}, x);
645}
646
647template <int in_bits, int out_bits, typename Hash, typename In>
649 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
650template <typename Cws>
651__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::EvalCws(
652 bool b, int4 s0, Cws cws, In x) {
653 return ConvertLeaf(Walk(b, s0, cws, x, [](bool, bool) {}), cws[kDepth].s);
654}
655
656template <int in_bits, int out_bits, typename Hash, typename In>
658 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
659template <typename Cws, typename Visit>
660__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::Walk(
661 bool b, int4 s0, Cws cws, In x, Visit &&visit) {
662 int4 node = util::SetLsb(s0, b);
663
664 for (int i = 0; i < kDepth; ++i) {
665 bool x_bit = (x >> (in_bits - 1 - i)) & 1;
666 bool t = util::GetLsb(node);
667 visit(t, x_bit);
668
669 node = PathStep(hash_, instance_seed_, node, cws[i].s, x_bit);
670 }
671
672 return node;
673}
674
675template <int in_bits, int out_bits, typename Hash, typename In>
677 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
679 bool b, int4 s0, cuda::std::span<const int4> cws, int4 ys[], int par_depth,
680 size_t stride) {
681 if (stride == 0) throw std::invalid_argument("PackedHalfTreeDpf::EvalAll: stride must be positive");
682 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
683 if (stride > (kMaxElements - 1) / static_cast<size_t>(kDepth))
684 throw std::invalid_argument("PackedHalfTreeDpf::EvalAll: stride exceeds the correction array limit");
685 const size_t required = static_cast<size_t>(kDepth) * stride + 1;
686 if (cws.size() < required)
687 throw std::invalid_argument("PackedHalfTreeDpf::EvalAll: cws must hold kDepth*stride+1 elements");
688 EvalAllCws(b, s0, CwsView{cws.data(), stride}, ys, par_depth);
689}
690
691template <int in_bits, int out_bits, typename Hash, typename In>
693 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
694template <typename Cws>
695inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::EvalAllCws(
696 bool b, int4 s0, Cws cws, int4 ys[], int par_depth) {
697 assert(par_depth >= 0);
698 static_assert(detail::FitsFullDomain<int4, kDepth>(),
699 "packed output count and byte size must fit in size_t");
700 int4 fcw = cws[kDepth].s;
701 if constexpr (kDepth > kBatchTopDepth) {
702 EvalAllFrontier(b, s0, cws, fcw, ys, par_depth);
703 } else {
704 // Domains too shallow to fill an 8-wide frontier evaluate one point per
705 // block through the tested point walk.
706 constexpr size_t blocks = size_t{1} << kDepth;
707 fss::detail::ParallelFor(par_depth, blocks, [&](size_t j) {
708 ys[j] = EvalCws(b, s0, cws, static_cast<In>(j * kLanes));
709 });
710 }
711}
712
713template <int in_bits, int out_bits, typename Hash, typename In>
715 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
717 In last, int4 s0, cuda::std::span<const int4> cws, cuda::std::span<int4> ys,
718 int par_depth, size_t stride) {
719 if (stride == 0)
720 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: stride must be positive");
721 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
722 if (stride > (kMaxElements - 1) / static_cast<size_t>(kDepth))
723 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: stride exceeds the correction array limit");
724 const size_t required = static_cast<size_t>(kDepth) * stride + 1;
725 if (cws.size() < required || cws.data() == nullptr)
726 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: cws must hold kDepth*stride+1 elements");
727 if (ys.data() == nullptr)
728 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: ys must not be null");
729 EvalRangeCws(b, first, last, s0, CwsView{cws.data(), stride}, ys, par_depth);
730}
731
732template <int in_bits, int out_bits, typename Hash, typename In>
734 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
735template <typename Cws>
736inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::EvalRangeCws(
737 bool b, In first, In last, int4 s0, Cws cws,
738 cuda::std::span<int4> ys, int par_depth) {
739 assert(par_depth >= 0);
740 In domain_last = static_cast<In>(~In{0});
741 if constexpr (in_bits < sizeof(In) * 8) {
742 domain_last = static_cast<In>((In{1} << in_bits) - 1);
743 }
744 if (first > last)
745 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: first must not exceed last");
746 if (last > domain_last)
747 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: last must stay inside the input domain");
748 const __uint128_t distance = static_cast<__uint128_t>(last) - first;
749 const __uint128_t block_distance = distance / kLanes;
750 // The caller controls the interval, so this is an argument check, not an
751 // invariant: with a 128-bit input domain the block distance can exceed
752 // size_t, and narrowing it first would shrink the output-size check.
753 if (block_distance >= std::numeric_limits<size_t>::max() / sizeof(int4))
754 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: the interval is too large for size_t");
755 const size_t blocks = static_cast<size_t>(block_distance) + 1;
756 if (ys.size() < blocks || ys.data() == nullptr)
757 throw std::invalid_argument("PackedHalfTreeDpf::EvalRange: ys must hold the covered blocks");
758
759 const size_t partitions = detail::ResolveParPartitions(par_depth, blocks);
760 detail::ParallelFor(par_depth, partitions, [&](size_t partition) {
761 const size_t begin = partition * (blocks / partitions) +
762 (partition < blocks % partitions ? partition : blocks % partitions);
763 const size_t end = begin + blocks / partitions + (partition < blocks % partitions);
764 for (size_t index = begin; index < end; ++index) ys[index] = {0, 0, 0, 0};
765 const __uint128_t begin_offset = static_cast<__uint128_t>(begin) * kLanes;
766 const __uint128_t end_offset = end == blocks ? distance :
767 static_cast<__uint128_t>(end) * kLanes - 1;
768 EvalRangeTree(util::SetLsb(s0, b), cws, ys.data(),
769 static_cast<In>(first + begin_offset), static_cast<In>(first + end_offset),
770 first, In{0}, domain_last, 0);
771 });
772}
773
774template <int in_bits, int out_bits, typename Hash, typename In>
776 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
777template <typename Cws>
778inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::EvalRangeTree(
779 int4 node, Cws cws, int4 ys[], In first, In last,
780 In output_first, In low, In high, int level) {
781 if (level == kDepth) {
782 const int4 block = ConvertLeaf(node, cws[kDepth].s);
783 const In begin = first > low ? first : low;
784 const In end = last < high ? last : high;
785 const size_t lanes = static_cast<size_t>(end - begin) + 1;
786 for (size_t lane = 0; lane < lanes; ++lane) {
787 const In x = static_cast<In>(begin + lane);
788 const In offset = static_cast<In>(x - output_first);
789 const size_t index = static_cast<size_t>(offset / kLanes);
790 ys[index] = util::Xor(ys[index], PackBeta(offset, Extract(block, x)));
791 }
792 return;
793 }
794 const In middle = static_cast<In>(low + ((high - low) >> 1));
795 int4 left, right;
796 ExpandNode(node, cws[level].s, left, right);
797 if (first <= middle) {
798 EvalRangeTree(left, cws, ys, first, last, output_first, low, middle, level + 1);
799 }
800 if (last > middle) {
801 EvalRangeTree(right, cws, ys, first, last, output_first,
802 static_cast<In>(middle + 1), high, level + 1);
803 }
804}
805
806template <int in_bits, int out_bits, typename Hash, typename In>
808 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
809__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::MaskXor(
810 int4 lhs, int4 rhs, int mask) {
811 return {lhs.x ^ (rhs.x & mask), lhs.y ^ (rhs.y & mask), lhs.z ^ (rhs.z & mask), lhs.w ^ (rhs.w & mask)};
812}
813
814template <int in_bits, int out_bits, typename Hash, typename In>
816 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
817__host__ __device__ inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::ExpandNode(
818 int4 node, int4 cw, int4 &left, int4 &right) {
819 ExpandNode(hash_, instance_seed_, node, cw, left, right);
820}
821
822template <int in_bits, int out_bits, typename Hash, typename In>
824 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
825template <typename Cws>
826inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::EvalAllFrontier(
827 bool b, int4 s0, Cws cws, int4 fcw, int4 ys[], int par_depth) {
828 constexpr size_t num_blocks = detail::FullDomainSize<int4, kDepth>();
829
830 // Frontier ping-pong scratch, grown on demand and reused across calls of this thread.
831 // Two halves of num_blocks / 2 each: no frontier before the last level exceeds that size.
832 // thread_local isolates concurrent calls: each thread pays the first-call allocation and
833 // retains the peak 16 * 2 ** kDepth bytes, about one packed output buffer, until it exits.
834 static thread_local std::vector<int4> scratch;
835 if (scratch.size() < num_blocks) scratch.resize(num_blocks);
836 int4 *bufs[2] = {scratch.data(), scratch.data() + num_blocks / 2};
837
838 // Scalar top: the root becomes the 2^kBatchTopDepth level-kBatchTopDepth frontier in bufs[kBatchTopDepth & 1].
839 bufs[0][0] = util::SetLsb(s0, b);
840 for (int i = 0; i < kBatchTopDepth; ++i) {
841 size_t count = size_t{1} << i;
842 for (size_t j = 0; j < count; ++j) {
843 ExpandNode(bufs[i & 1][j], cws[i].s, bufs[(i + 1) & 1][2 * j], bufs[(i + 1) & 1][2 * j + 1]);
844 }
845 }
846
847 // Each level bounds its independent partitions by 2^par_depth. The
848 // sequential branch avoids an inactive OpenMP region as well as workers.
849 int depth = fss::detail::ResolveParDepth(par_depth, kDepth);
850 if (depth == 0) {
851 for (int i = kBatchTopDepth; i < kDepth; ++i) {
852 const int4 *src = bufs[i & 1];
853 int4 *dst = i + 1 == kDepth ? ys : bufs[(i + 1) & 1];
854 size_t batches = (size_t{1} << i) / 8;
855 for (size_t k = 0; k < batches; ++k) {
856 if (i + 1 == kDepth) ExpandConvert8(src, dst, k, cws[i].s, fcw);
857 else Expand8(src, dst, k, cws[i].s);
858 }
859 }
860 return;
861 }
862 int threads = fss::detail::ResolveParThreads(depth, num_blocks / 16);
863#pragma omp parallel num_threads(threads)
864 {
865 for (int i = kBatchTopDepth; i < kDepth; ++i) {
866 const int4 *src = bufs[i & 1];
867 int4 *dst = i + 1 == kDepth ? ys : bufs[(i + 1) & 1];
868 size_t batches = (size_t{1} << i) / 8;
869 size_t partitions = fss::detail::ResolveParPartitions(depth, batches);
870#pragma omp for schedule(static)
871 for (size_t partition = 0; partition < partitions; ++partition) {
872 size_t begin = partition * (batches / partitions) +
873 (partition < batches % partitions ? partition : batches % partitions);
874 size_t end = begin + batches / partitions + (partition < batches % partitions);
875 for (size_t k = begin; k < end; ++k) {
876 if (i + 1 == kDepth) ExpandConvert8(src, dst, k, cws[i].s, fcw);
877 else Expand8(src, dst, k, cws[i].s);
878 }
879 }
880 }
881 }
882}
883
884template <int in_bits, int out_bits, typename Hash, typename In>
886 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
887inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::Expand8(
888 const int4 *src, int4 *dst, size_t k, int4 cw) {
889 int4 parents[8];
890 cuda::std::array<int4, 8> seeds;
891 for (int j = 0; j < 8; ++j) {
892 parents[j] = src[8 * k + j];
893 seeds[j] = util::Xor(instance_seed_, parents[j]);
894 }
895 auto hs = detail::HashBatch(hash_, seeds);
896 for (int j = 0; j < 8; ++j) {
897 int4 left = MaskXor(hs[j], cw, -static_cast<int>(util::GetLsb(parents[j])));
898 dst[16 * k + 2 * j] = left;
899 dst[16 * k + 2 * j + 1] = util::Xor(left, parents[j]);
900 }
901}
902
903template <int in_bits, int out_bits, typename Hash, typename In>
905 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
906inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::ExpandConvert8(
907 const int4 *src, int4 *ys, size_t k, int4 cw, int4 fcw) {
908 int4 parents[8];
909 cuda::std::array<int4, 8> seeds;
910 for (int j = 0; j < 8; ++j) {
911 parents[j] = src[8 * k + j];
912 seeds[j] = util::Xor(instance_seed_, parents[j]);
913 }
914 auto hs = detail::HashBatch(hash_, seeds);
915
916 int4 children[16];
917 cuda::std::array<int4, 8> conv_seeds[2];
918 for (int j = 0; j < 8; ++j) {
919 int4 left = MaskXor(hs[j], cw, -static_cast<int>(util::GetLsb(parents[j])));
920 children[2 * j] = left;
921 children[2 * j + 1] = util::Xor(left, parents[j]);
922 conv_seeds[0][j] = util::Xor(instance_seed_, children[2 * j]);
923 conv_seeds[1][j] = util::Xor(instance_seed_, children[2 * j + 1]);
924 }
925 auto leaves_even = detail::HashBatch(hash_, conv_seeds[0]);
926 auto leaves_odd = detail::HashBatch(hash_, conv_seeds[1]);
927 for (int j = 0; j < 8; ++j) {
928 ys[16 * k + 2 * j] = MaskXor(leaves_even[j], fcw, -static_cast<int>(util::GetLsb(children[2 * j])));
929 ys[16 * k + 2 * j + 1] =
930 MaskXor(leaves_odd[j], fcw, -static_cast<int>(util::GetLsb(children[2 * j + 1])));
931 }
932}
933
934template <int in_bits, int out_bits, typename Hash, typename In>
936 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
937__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::ConvertLeaf(
938 int4 node, int4 fcw) {
939 return ConvertLeaf(hash_, instance_seed_, node, fcw);
940}
941
942template <int in_bits, int out_bits, typename Hash, typename In>
944 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
945__host__ __device__ inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::ExpandNode(
946 Hash &hash, int4 instance_seed, int4 node, int4 cw, int4 &left, int4 &right) {
947 int4 hashed = hash.Hash(util::Xor(instance_seed, node));
948 left = MaskXor(hashed, cw, -static_cast<int>(util::GetLsb(node)));
949 right = util::Xor(left, node);
950}
951
952template <int in_bits, int out_bits, typename Hash, typename In>
954 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
955__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::PathStep(
956 Hash &hash, int4 instance_seed, int4 node, int4 cw, bool take_right) {
957 const int4 zero = {0, 0, 0, 0};
958 int4 hashed = hash.Hash(util::Xor(instance_seed, node));
959 return util::Xor(util::Xor(hashed, take_right ? node : zero),
960 util::GetLsb(node) ? cw : zero);
961}
962
963template <int in_bits, int out_bits, typename Hash, typename In>
965 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
966__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::ConvertLeaf(
967 Hash &hash, int4 instance_seed, int4 node, int4 output_cw) {
968 int4 leaf = hash.Hash(util::Xor(instance_seed, node));
969 if (util::GetLsb(node)) leaf = util::Xor(leaf, output_cw);
970 return leaf;
971}
972
973namespace gpu::detail {
974
975template <int in_bits, int out_bits, typename Hash, typename In>
976 requires(ValidForInputBits<In, in_bits> &&
977 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
978struct HalfTreeGpuTraits<PackedHalfTreeDpf<in_bits, out_bits, Hash, In>> {
979 using Backend = Hash;
980 static constexpr int kExpansionDepth = PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::kDepth;
981 static constexpr int kOutputBits = PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::kDepth;
982 static constexpr bool kPacked = true;
983};
984
985#if defined(__CUDACC__)
986
987// Split-layout counterparts of the shared HalfTree kernels. The Cw
988// buffer arrives type-erased and the boundary view reconstructs the per-layer
989// Cw entries, so the shared traversal stays identical on both paths.
990template <typename Scheme, int frontier_depth, int block_root_depth>
991__global__ void PackedHalfTreeSplitEvalAllKernel(bool b, int4 s0, const void *cws_buffer,
992 size_t stride, int4 *ys, int4 instance_seed, typename HalfTreeGpuTraits<Scheme>::Backend hash) {
993 using Cws = fss::detail::PackedHalfTreeCwsView<typename Scheme::Cw>;
994 const auto *cws = static_cast<const int4 *>(cws_buffer);
995 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
996 if (thread_index >= (1LL << frontier_depth)) return;
997 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
998 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0,
999 Cws{cws, stride}, ys, instance_seed, hash, blockIdx.x, thread_index,
1000 shared_frontier);
1001}
1002
1003template <typename Scheme, int frontier_depth, int block_root_depth>
1004__global__ void PackedHalfTreeSplitEvalAllBatchKernel(bool b, const int4 *s0s,
1005 const void *cws_buffer, size_t stride, int4 *ys, int4 instance_seed,
1006 typename HalfTreeGpuTraits<Scheme>::Backend hash) {
1007 using Cws = fss::detail::PackedHalfTreeCwsView<typename Scheme::Cw>;
1008 const auto *cws = static_cast<const int4 *>(cws_buffer);
1009 constexpr int kBlocksPerKey = 1 << block_root_depth;
1010 constexpr size_t kOutputsPerKey = size_t{1} << HalfTreeGpuTraits<Scheme>::kOutputBits;
1011 const int key_index = blockIdx.x / kBlocksPerKey;
1012 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
1013 const int64_t thread_index = static_cast<int64_t>(subtree_block_index) * blockDim.x + threadIdx.x;
1014 if (thread_index >= (1LL << frontier_depth)) return;
1015 int4 *key_outputs = ys + static_cast<size_t>(key_index) * kOutputsPerKey;
1016 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
1017 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
1018 Cws{cws + key_index, stride}, key_outputs, instance_seed, hash,
1019 subtree_block_index, thread_index, shared_frontier);
1020}
1021
1022// Key-major batch counterpart: key k's components occupy its whole block of
1023// records with a unit stride.
1024template <typename Scheme, int frontier_depth, int block_root_depth>
1025__global__ void PackedHalfTreeSplitEvalAllBatchDeviceKeyKernel(bool b, const int4 *s0s,
1026 const void *cws_buffer, size_t key_records, size_t key_count, int4 *ys, int4 instance_seed,
1027 typename HalfTreeGpuTraits<Scheme>::Backend hash) {
1028 using Cws = fss::detail::PackedHalfTreeCwsView<typename Scheme::Cw>;
1029 const auto *cws = static_cast<const int4 *>(cws_buffer);
1030 constexpr int kBlocksPerKey = 1 << block_root_depth;
1031 constexpr size_t kOutputsPerKey = size_t{1} << HalfTreeGpuTraits<Scheme>::kOutputBits;
1032 const int key_index = blockIdx.x / kBlocksPerKey;
1033 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
1034 const int64_t thread_index = static_cast<int64_t>(subtree_block_index) * blockDim.x + threadIdx.x;
1035 if (thread_index >= (1LL << frontier_depth)) return;
1036 const int4 *key = cws + static_cast<size_t>(key_index) * key_records;
1037 int4 *key_outputs = ys + static_cast<size_t>(key_index) * kOutputsPerKey;
1038 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
1039 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
1040 Cws{key, 1}, key_outputs, instance_seed, hash,
1041 subtree_block_index, thread_index, shared_frontier);
1042}
1043
1044// Host launcher for the key-major batch kernel; the hash backend and instance
1045// seed arrive by value from a host mirror of the device scheme.
1046template <int in_bits, int out_bits, typename Hash, typename In, int frontier_depth = 16,
1047 int threads_per_block = 256>
1048__host__ inline cudaError_t PackedHalfTreeDpfEvalAllGpuBatchDeviceKey(bool b, const int4 *s0s,
1049 size_t key_count, const int4 *cws, size_t cws_count, size_t key_records, int4 *ys,
1050 size_t ys_count, int4 instance_seed, const Hash &hash, cudaStream_t stream) {
1051 using Scheme = PackedHalfTreeDpf<in_bits, out_bits, Hash, In>;
1052 constexpr int kDepth = Scheme::kDepth;
1053 constexpr size_t kOutputsPerKey = size_t{1} << kDepth;
1054 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1055 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
1056 constexpr int kFrontierDepth = frontier_depth < kDepth ? frontier_depth : kDepth;
1057 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
1058 (threads_per_block & (threads_per_block - 1)) == 0,
1059 "gpu block size must be a power of two in [1,1024]");
1060 static_assert(kDepth - kFrontierDepth <= 8,
1061 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
1062 constexpr int kThreadsPerBlockLog2 = [] {
1063 int requested_log2 = 0;
1064 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
1065 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
1066 }();
1067 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
1068 constexpr int kBlocksPerKey = 1 << kBlockRootDepth;
1069 if (key_count == 0) return cudaSuccess;
1070 if (key_count > kMaxElements ||
1071 key_count > kMaxAddressableSize / kOutputsPerKey / sizeof(int4) ||
1072 key_count > kMaxAddressableSize / key_records / sizeof(int4) ||
1073 cws_count < key_count * key_records || ys_count < key_count * kOutputsPerKey ||
1074 s0s == nullptr || cws == nullptr || ys == nullptr) return cudaErrorInvalidValue;
1075 if (key_count > static_cast<size_t>(std::numeric_limits<int>::max()) / kBlocksPerKey)
1076 return cudaErrorInvalidConfiguration;
1077 PackedHalfTreeSplitEvalAllBatchDeviceKeyKernel<Scheme, kFrontierDepth, kBlockRootDepth>
1078 <<<key_count * kBlocksPerKey, 1 << kThreadsPerBlockLog2, 0, stream>>>(
1079 b, s0s, cws, key_records, key_count, ys, instance_seed, hash);
1080 return cudaGetLastError();
1081}
1082
1083#endif // defined(__CUDACC__)
1084
1085#if defined(__CUDACC__)
1086
1087// Point-evaluate packed HalfTree keys, one thread per key: ys[tid] is x's output
1088// block, or with cmp the 1[alpha <= x] share of HalfTreeGrottoDcf::Eval(). cws
1089// holds dense level-major correction words, the layout the split form of Gen()
1090// writes: layer i of key k is cws[i * nkeys + k] and the final correction word
1091// is the tail row, cws[kDepth * nkeys + k].
1092template <int in_bits, int unroll, bool b, bool cmp, typename Scheme, typename In, typename Query, typename Y>
1093__global__ void PackedHalfTreeDpfEvalPointKernel(Y *ys, const int4 *seeds, const int4 *cws, const Query *xs,
1094 int nkeys, typename HalfTreeGpuTraits<Scheme>::Backend hash, int4 instance_seed) {
1095 int tid = blockIdx.x * blockDim.x + threadIdx.x;
1096 if (tid >= nkeys) return;
1097 int4 node = fss::util::SetLsb(seeds[tid], b);
1098 In x = static_cast<In>(xs[tid]);
1099 fss::detail::GrottoTurns turns;
1100
1101#pragma unroll unroll
1102 for (int i = 0; i < Scheme::kDepth; ++i) {
1103 bool x_bit = GetBit(x, in_bits - 1 - i);
1104 bool t = fss::util::GetLsb(node);
1105 if constexpr (cmp) turns(t, x_bit);
1106 int4 h = hash.Hash(fss::util::Xor(instance_seed, node));
1107 int4 cw = cws[(size_t)i * nkeys + tid];
1108 node = fss::util::Xor(fss::util::Xor(h, x_bit ? node : Zero4()), t ? cw : Zero4());
1109 }
1110
1111 int4 leaf = hash.Hash(fss::util::Xor(instance_seed, node));
1112 if (fss::util::GetLsb(node)) leaf = fss::util::Xor(leaf, cws[(size_t)Scheme::kDepth * nkeys + tid]);
1113 if constexpr (cmp) {
1114 ys[tid] = turns.share ^ fss::detail::SchemeAccess::HalfTreeGrottoLeafParity<Scheme>(leaf, x);
1115 } else {
1116 ys[tid] = leaf;
1117 }
1118}
1119
1120template <int unroll, bool cmp, int in_bits, typename Scheme, typename In, typename Query, typename Y>
1121void PackedHalfTreeDpfEvalPointLaunch(bool b, const int4 *seeds, const int4 *cws, const Query *xs, Y *ys,
1122 int nkeys, typename HalfTreeGpuTraits<Scheme>::Backend hash, int4 instance_seed, cudaStream_t stream) {
1123 if (nkeys == 0) return;
1124 constexpr int kBs = 256;
1125 int blocks = (nkeys + kBs - 1) / kBs;
1126 if (b) {
1127 PackedHalfTreeDpfEvalPointKernel<in_bits, unroll, true, cmp, Scheme, In, Query, Y>
1128 <<<blocks, kBs, 0, stream>>>(ys, seeds, cws, xs, nkeys, hash, instance_seed);
1129 } else {
1130 PackedHalfTreeDpfEvalPointKernel<in_bits, unroll, false, cmp, Scheme, In, Query, Y>
1131 <<<blocks, kBs, 0, stream>>>(ys, seeds, cws, xs, nkeys, hash, instance_seed);
1132 }
1133}
1134
1151template <int unroll = 4, int in_bits, int out_bits, typename Hash, typename In, typename Query = In>
1152 requires QueryFor<Query, in_bits>
1153void PackedHalfTreeDpfEvalPointGpu(bool b, cuda::std::span<const int4> seeds, cuda::std::span<const int4> cws,
1154 cuda::std::span<const Query> xs, int4 *ys, int nkeys, const PackedHalfTreeDpf<in_bits, out_bits, Hash, In> &dpf,
1155 cudaStream_t stream = 0) {
1156 if (nkeys < 0)
1157 throw std::invalid_argument("PackedHalfTreeDpfEvalPointGpu: nkeys must be nonnegative");
1158 if (nkeys == 0) return;
1159 const size_t count = static_cast<size_t>(nkeys);
1160 constexpr size_t kLevels = static_cast<size_t>(PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::kDepth) + 1;
1161 if (count > SIZE_MAX / kLevels || cws.size() < count * kLevels)
1162 throw std::invalid_argument("PackedHalfTreeDpfEvalPointGpu: cws must hold (kDepth+1)*nkeys elements");
1163 if (seeds.size() < count || xs.size() < count || ys == nullptr)
1164 throw std::invalid_argument("PackedHalfTreeDpfEvalPointGpu: seeds, xs, and ys must cover nkeys entries");
1165 using Scheme = PackedHalfTreeDpf<in_bits, out_bits, Hash, In>;
1166 PackedHalfTreeDpfEvalPointLaunch<unroll, false, in_bits, Scheme, In, Query, int4>(b, seeds.data(), cws.data(),
1167 xs.data(), ys, nkeys, fss::detail::SchemeAccess::GetHash(dpf),
1168 fss::detail::SchemeAccess::GetInstanceSeed(dpf), stream);
1169}
1170
1171#endif // defined(__CUDACC__)
1172
1173} // namespace gpu::detail
1174
1175template <int in_bits, int out_bits, typename Hash, typename In>
1177 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
1178template <int frontier_depth, int threads_per_block>
1180struct PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::GpuLaunchShape {
1181 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
1182 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
1183 (threads_per_block & (threads_per_block - 1)) == 0,
1184 "gpu block size must be a power of two in [1,1024]");
1185 static_assert(kDepth >= 0, "gpu tree must have a nonnegative expansion depth");
1186 static constexpr int kFrontierDepth = frontier_depth < kDepth ? frontier_depth : kDepth;
1187 static_assert(kDepth - kFrontierDepth <= 8,
1188 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
1189 static constexpr int kThreadsPerBlockLog2 = [] {
1190 int depth = 0;
1191 for (int count = threads_per_block; count > 1; count >>= 1) ++depth;
1192 return depth < kFrontierDepth ? depth : kFrontierDepth;
1193 }();
1194 static constexpr int kThreadsPerBlock = 1 << kThreadsPerBlockLog2;
1195 static constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
1196 static_assert(kBlockRootDepth <= 30, "gpu block index exceeds the signed-int range");
1197 static constexpr size_t kBlocksPerKey = size_t{1} << (kBlockRootDepth <= 30 ? kBlockRootDepth : 0);
1198};
1199
1200template <int in_bits, int out_bits, typename Hash, typename In>
1202 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
1203template <int frontier_depth, int threads_per_block>
1205 bool b, int4 s0, cuda::std::span<const int4> cws, cuda::std::span<int4> ys,
1206 cudaStream_t stream, size_t stride) const
1207 requires(kDepth < sizeof(size_t) * 8) {
1208#if defined(__CUDACC__)
1209 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1211 constexpr size_t kOutputsPerKey = size_t{1} << kDepth;
1212 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1213 if (stride == 0 || stride > (kMaxElements - 1) / static_cast<size_t>(kDepth))
1214 return cudaErrorInvalidValue;
1215 const size_t required = static_cast<size_t>(kDepth) * stride + 1;
1216 if (cws.size() < required || ys.size() < kOutputsPerKey ||
1217 cws.data() == nullptr || ys.data() == nullptr ||
1218 kOutputsPerKey > std::numeric_limits<size_t>::max() / sizeof(int4))
1219 return cudaErrorInvalidValue;
1220 gpu::detail::PackedHalfTreeSplitEvalAllKernel<Scheme, LaunchShape::kFrontierDepth,
1221 LaunchShape::kBlockRootDepth>
1222 <<<LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1223 b, s0, cws.data(), stride, ys.data(), instance_seed_, hash_);
1224 return cudaGetLastError();
1225#else
1226 static_assert(sizeof(Hash) == 0, "gpu launch requires a CUDA compiler");
1227 return cudaErrorNotSupported;
1228#endif
1229}
1230
1231template <int in_bits, int out_bits, typename Hash, typename In>
1233 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
1234template <int frontier_depth, int threads_per_block>
1236 bool b, cuda::std::span<const int4> s0s, cuda::std::span<const int4> cws,
1237 cuda::std::span<int4> ys, cudaStream_t stream, size_t stride) const
1238 requires(kDepth < sizeof(size_t) * 8) {
1239#if defined(__CUDACC__)
1240 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1242 constexpr size_t kOutputsPerKey = size_t{1} << kDepth;
1243 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1244 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
1245 const size_t key_count = s0s.size();
1246 if (key_count == 0) return cudaSuccess;
1247 // The output guard bounds key_count * kOutputsPerKey * sizeof(int4) so the
1248 // product in the size check below cannot wrap.
1249 if (key_count > kMaxElements ||
1250 key_count > kMaxAddressableSize / kOutputsPerKey / sizeof(int4) ||
1251 (stride != 0 && (stride < key_count || stride > kMaxElements)))
1252 return cudaErrorInvalidValue;
1253 // A stride of 0 resolves to the key count, which is the dense level-major layout.
1254 const size_t resolved_stride = stride == 0 ? key_count : stride;
1255 if (resolved_stride > (kMaxElements - key_count) / static_cast<size_t>(kDepth))
1256 return cudaErrorInvalidValue;
1257 const size_t required = static_cast<size_t>(kDepth) * resolved_stride + key_count;
1258 if (cws.size() < required || ys.size() < key_count * kOutputsPerKey ||
1259 s0s.data() == nullptr || cws.data() == nullptr || ys.data() == nullptr)
1260 return cudaErrorInvalidValue;
1261 if (key_count > static_cast<size_t>(std::numeric_limits<int>::max()) / LaunchShape::kBlocksPerKey)
1262 return cudaErrorInvalidConfiguration;
1263 gpu::detail::PackedHalfTreeSplitEvalAllBatchKernel<Scheme, LaunchShape::kFrontierDepth,
1264 LaunchShape::kBlockRootDepth>
1265 <<<key_count * LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1266 b, s0s.data(), cws.data(), resolved_stride, ys.data(), instance_seed_, hash_);
1267 return cudaGetLastError();
1268#else
1269 static_assert(sizeof(Hash) == 0, "gpu launch requires a CUDA compiler");
1270 return cudaErrorNotSupported;
1271#endif
1272}
1273
1274#if FSS_ENABLE_V1_COMPAT
1275
1276// Deprecated v1.3.0 aggregate-Key entries. Every wrapper copies between the
1277// caller's Key layout and a compile-time-sized split-component scratch buffer,
1278// then runs the current public method at stride 1. The old layout kept the
1279// packed-output correction word in the caller's separate fcw.
1280template <int in_bits, int out_bits, typename Hash, typename In>
1282 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
1283__host__ __device__ inline void PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::Gen(
1284 compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &k0,
1285 compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &k1,
1286 cuda::std::span<const int4, 2> s0s, In a, uint64_t beta) {
1287 int4 seed[kDepth + 1];
1288 Gen(cuda::std::span<int4>(seed, kDepth + 1), s0s, a, beta);
1289 for (int i = 0; i < kDepth; ++i) {
1290 const Cw layer = {seed[i]};
1291 k0.cws[i] = layer;
1292 k1.cws[i] = layer;
1293 }
1294 k0.fcw = seed[kDepth];
1295 k1.fcw = seed[kDepth];
1296 k0.s0 = s0s[0];
1297 k1.s0 = s0s[1];
1298}
1299
1300template <int in_bits, int out_bits, typename Hash, typename In>
1302 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
1303__host__ __device__ inline int4 PackedHalfTreeDpf<in_bits, out_bits, Hash, In>::Eval(
1304 bool b, const compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &key, In x) {
1305 int4 seed[kDepth + 1];
1306 for (int i = 0; i < kDepth; ++i) seed[i] = key.cws[i].s;
1307 seed[kDepth] = key.fcw;
1308 return Eval(b, key.s0, cuda::std::span<const int4>(seed, kDepth + 1), x);
1309}
1310
1311template <int in_bits, int out_bits, typename Hash, typename In>
1313 CcrHashable<Hash> && out_bits >= 1 && out_bits <= 64 && (out_bits & (out_bits - 1)) == 0)
1315 bool b, const compat::detail::FcwKeyRecord<PackedHalfTreeDpf, Cw, kDepth> &key,
1316 cuda::std::span<int4> ys) {
1317 int4 seed[kDepth + 1];
1318 for (int i = 0; i < kDepth; ++i) seed[i] = key.cws[i].s;
1319 seed[kDepth] = key.fcw;
1320 EvalAll(b, key.s0, cuda::std::span<const int4>(seed, kDepth + 1), ys.data());
1321}
1322
1323#endif // FSS_ENABLE_V1_COMPAT
1324
1325} // namespace fss
2-party DCF scheme over F2 from the packed Half-Tree DPF (Grotto construction).
Definition half_tree_grotto_dcf.cuh:83
void Gen(cuda::std::span< int4 > cws, cuda::std::span< const int4, 2 > s0s, In a, uint64_t beta, size_t stride=1)
Generate split Cws for the packed point function.
Definition packed_half_tree_dpf.cuh:565
int4 Eval(bool b, int4 s0, cuda::std::span< const int4 > cws, In x, size_t stride=1)
Evaluate the packed output block containing x from split Cws.
Definition packed_half_tree_dpf.cuh:635
void EvalAll(bool b, int4 s0, cuda::std::span< const int4 > cws, int4 ys[], int par_depth=0, size_t stride=1)
Evaluate all packed output blocks from split Cws on the host.
Definition packed_half_tree_dpf.cuh:678
cudaError_t EvalAllGpu(bool b, int4 s0, cuda::std::span< const int4 > cws, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=1) const
Launch full-domain CUDA evaluation from split device Cws.
Definition packed_half_tree_dpf.cuh:1204
static constexpr int kLanes
Definition packed_half_tree_dpf.cuh:134
void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span< const int4 > cws, cuda::std::span< int4 > ys, int par_depth=0, size_t stride=1)
Evaluate the closed input interval [first, last] from split Cws.
Definition packed_half_tree_dpf.cuh:716
PackedHalfTreeDpf(Hash hash, int4 instance_seed)
Configure the CCR hash and public instance seed S.
Definition packed_half_tree_dpf.cuh:514
cudaError_t EvalAllGpuBatch(bool b, cuda::std::span< const int4 > s0s, cuda::std::span< const int4 > cws, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=0) const
static constexpr int kDepth
Definition packed_half_tree_dpf.cuh:139
static uint64_t Extract(int4 block, In x)
Extracts the out_bits-bit output share for input x from a packed block.
Definition packed_half_tree_dpf.cuh:543
In Input
Definition packed_half_tree_dpf.cuh:167
static constexpr int kCwCount
Definition packed_half_tree_dpf.cuh:165
Single build-time switch for the deprecated v1 Cw-form compatibility entries.
Circular-correlation-robust (CCR) hash interface for HalfTree schemes.
Definition hash.cuh:86
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
2-party distributed point function (DPF) using the Half-Tree scheme.
Correction word for a tree level or the final packed-output correction word.
Definition packed_half_tree_dpf.cuh:159
int4 s
Tree seed correction word, or packed-output correction word in the final entry.
Definition packed_half_tree_dpf.cuh:160