myl7/fss 2.0.0
Function secret sharing (FSS) primitives including distributed point/comparison function (DPF/DCF)
Loading...
Searching...
No Matches
half_tree_dpf.cuh
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
16
17#pragma once
18#include <cuda_runtime.h>
19#include <type_traits>
20#include <cstddef>
21#include <cassert>
22#ifdef _OPENMP
23#include <omp.h>
24#endif
25#include <vector>
26#include <cuda/std/array>
27#include <fss/group.cuh>
28#include <fss/hash.cuh>
29#include <fss/util.cuh>
30#include <fss/constraints.cuh>
31#include <fss/compat.cuh>
32#include <cuda/std/span>
33#include <limits>
34
35#include <cstdint>
36#include <stdexcept>
37
38namespace fss {
39
40namespace detail {
41class SchemeAccess;
42
43// Split Cws view shared by the host and device split paths. Layer l is
44// read from cws[l*stride]. Only the final tree level (l == in_bits - 1) carries a
45// control component, and it is bit in_bits - 1 of packed_tr; every other bit is
46// ignored. Each other Cw entry is rebuilt with extra = false, so the terminal
47// entry holds the plain output correction word. operator[] recovers the Cw the shared
48// algorithms consume, so their bodies stay identical on the Cw and split paths.
49template <typename Cw, typename In, int in_bits>
50struct HalfTreeDpfCwsView {
51 const int4 *cws;
52 In packed_tr;
53 size_t stride;
54
55 __host__ __device__ inline Cw operator[](int l) const {
56 const int4 s_cw = cws[static_cast<size_t>(l) * stride];
57 if (l == in_bits - 1) return Cw{s_cw, ((packed_tr >> (in_bits - 1)) & 1) != 0};
58 return Cw{s_cw, false};
59 }
60};
61
62} // namespace detail
63
78template <int in_bits, typename Group, typename Hash, typename In = uint>
80 Groupable<Group> && CcrHashable<Hash>)
82public:
92 struct __align__(32) Cw {
93 int4 s;
94 bool extra;
95 };
96 static_assert(sizeof(Cw) == 32);
97
99 static constexpr int kCwCount = in_bits + 1;
100
101 using Input = In;
102
109 __host__ __device__ explicit HalfTreeDpf(Hash hash, int4 instance_seed);
110
140 __host__ __device__ void Gen(cuda::std::span<int4> cws, In &tr,
141 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf, size_t stride = 1);
142
170 __host__ __device__ int4 Eval(bool b, int4 s0, cuda::std::span<const int4> cws,
171 In tr, In x, size_t stride = 1);
172
201 void EvalAll(bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
202 int4 ys[], int par_depth = 0, size_t stride = 1);
203
237 void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws,
238 In tr, cuda::std::span<int4> ys, int par_depth = 0, size_t stride = 1);
239
266 template <int frontier_depth = 16, int threads_per_block = 256>
267 __host__ cudaError_t EvalRangeGpu(bool b, In first, In last, int4 s0,
268 cuda::std::span<const int4> cws, In tr, cuda::std::span<int4> ys,
269 cudaStream_t stream = 0, size_t stride = 1) const;
270
295 template <int frontier_depth = 16, int threads_per_block = 256>
296 __host__ cudaError_t EvalAllGpu(bool b, int4 s0, cuda::std::span<const int4> cws,
297 In tr, cuda::std::span<int4> ys, cudaStream_t stream = 0, size_t stride = 1) const
298 requires(in_bits < sizeof(size_t) * 8);
299
332 template <int frontier_depth = 16, int threads_per_block = 256>
333 __host__ cudaError_t EvalAllGpuBatch(bool b, cuda::std::span<const int4> s0s,
334 cuda::std::span<const int4> cws, cuda::std::span<const In> tr,
335 cuda::std::span<int4> ys, cudaStream_t stream = 0,
336 size_t stride = 0) const
337 requires(in_bits < sizeof(size_t) * 8);
338
339#if FSS_ENABLE_V1_COMPAT
340 // Deprecated v1.3.0 aggregate-Cw compatibility entries; see fss/compat.cuh.
341 // Each converts through a compile-time-sized scratch buffer and the current
342 // split-component method above at stride 1. cws has in_bits entries, and the
343 // output correction word leaves through the separate ocw parameter.
344
346 [[deprecated("use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, int4, size_t) overload")]]
347 __host__ __device__ void Gen(Cw cws[], int4 &ocw, const int4 s0s[2], In a, int4 b_buf);
348
350 [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, In, size_t) overload")]]
351 __host__ __device__ int4 Eval(bool b, int4 s0, const Cw cws[], int4 ocw, In x);
352
354 [[deprecated("use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, int4[], int, size_t) overload")]]
355 void EvalAll(bool b, int4 s0, const Cw cws[], int4 ocw, int4 ys[]);
356
357 // Deprecated v1.3.0 aggregate-Key entries; see fss/compat.cuh. The Key alias
358 // exposes the compat record under the legacy nested name, while the method
359 // declarations spell the record type so a deprecated name never enters a
360 // member signature. EvalAll() runs sequentially, as the current
361 // split-component default does; the old automatic thread-count split is not
362 // restored.
363
365 using Key [[deprecated("use the split-component Gen/Eval/EvalAll with separate cws and tr arrays")]] =
366 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits>;
367
369 [[deprecated("use the split-component Gen(cuda::std::span<int4>, In&, cuda::std::span<const int4, 2>, In, int4, size_t) overload")]]
370 __host__ __device__ void Gen(compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k0,
371 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k1,
372 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf);
373
375 [[deprecated("use the split-component Eval(bool, int4, cuda::std::span<const int4>, In, In, size_t) overload")]]
376 __host__ __device__ int4 Eval(bool b, const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key, In x);
377
379 [[deprecated("use the split-component EvalAll(bool, int4, cuda::std::span<const int4>, In, int4[], int, size_t) overload")]]
380 void EvalAll(bool b, const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key,
381 cuda::std::span<int4> ys);
382#endif // FSS_ENABLE_V1_COMPAT
383
384private:
385 friend class detail::SchemeAccess;
386
387 template <int frontier_depth, int threads_per_block>
388 struct GpuLaunchShape;
389
390 static __host__ __device__ void ExpandNode(Hash &hash, int4 instance_seed,
391 int4 node, int4 cw, int4 &left, int4 &right);
392 static __host__ __device__ int4 PathStep(Hash &hash, int4 instance_seed,
393 int4 node, int4 cw, bool take_right);
394 template <typename OutputCw>
395 static __host__ __device__ int4 ConvertLeaf(bool party, Hash &hash,
396 int4 instance_seed, int4 node, bool take_right, const Cw &last_cw,
397 OutputCw &output_cw);
398 template <typename OutputCw>
399 static __host__ __device__ int4 ConvertHashedLeaf(bool party, int4 hashed,
400 bool node_control, bool take_right, const Cw &last_cw,
401 OutputCw &output_cw);
402 template <typename OutputCw>
403 static __host__ __device__ void ConvertLeafPair(bool party, Hash &hash,
404 int4 instance_seed, int4 node, const Cw &last_cw,
405 OutputCw &output_cw, int4 &left, int4 &right);
406
407 // Split Cws view over the caller's array. The host methods and the
408 // split GPU kernels share this one view; see detail::HalfTreeDpfCwsView.
409 using CwsView = detail::HalfTreeDpfCwsView<Cw, In, in_bits>;
410
411 // Writes the strided components and accumulates the packed control bit locally.
412 struct CwsWriter {
413 int4 *cws;
414 size_t stride;
415 In &tr;
416 In packed_tr = 0;
417
418 __host__ __device__ void Write(int i, int4 s_cw) {
419 cws[static_cast<size_t>(i) * stride] = s_cw;
420 }
421
422 __host__ __device__ void WriteLast(int4 s_cw, bool extra_cw) {
423 cws[static_cast<size_t>(in_bits - 1) * stride] = s_cw;
424 packed_tr |= static_cast<In>(extra_cw) << (in_bits - 1);
425 }
426
427 __host__ __device__ void Finish(int4 terminal) {
428 cws[static_cast<size_t>(in_bits) * stride] = terminal;
429 tr = packed_tr;
430 }
431 };
432
433 // Shared Cw generation loop. The writer is passed by value and owns
434 // only output access, so the existing algorithm and backend use stay unchanged.
435 template <typename Writer>
436 __host__ __device__ void Generate(Writer writer, const int4 *s0s, In a, int4 b_buf);
437
438 // Shared point evaluation over Cw arrays and split Cws views.
439 template <typename Cws>
440 __host__ __device__ int4 EvalWalk(bool b, int4 s0, Cws cws, In x);
441
442 // Shared full-domain evaluation over Cw arrays and split Cws views.
443 template <typename Cws>
444 void EvalAllCws(bool b, int4 s0, Cws cws, int4 ys[], int par_depth);
445
446 // Shared interval evaluation. Cws is a Cw pointer on the array form
447 // and a CwsView on the split form.
448 template <typename Cws>
449 void EvalRangeCws(bool b, In first, In last, int4 s0, Cws cws,
450 cuda::std::span<int4> ys, int par_depth);
451
452 Hash hash_;
453 int4 instance_seed_;
454
455 template <typename Cws>
456 void EvalRangeTree(bool b, int4 node, Cws cws, int4 ys[],
457 In first, In last, In output_first, In low, In high, int level);
458
459 // EvalAll() shape: a scalar prefix of kBatchPrefix levels grows the frontier
460 // to 8 nodes and detail::HashBatch() hashes 8 parents per level. The tail fuses the
461 // last-level conversion: each group of 8 parents emits its 16 leaves
462 // directly, so no backward in-place pass over ys is needed.
463 static constexpr int kBatchPrefix = 3;
464
465 // Expands w scalar frontier nodes by one level with cws[lv].
466 // The children overwrite the frontier in place: iteration is in reverse so
467 // the writes at 2j, 2j+1 never clobber nodes still to be read.
468 template <typename Cws>
469 void ExpandFrontier(int4 frontier[], size_t w, Cws cws, int lv);
470
471 // Branchless XOR of a correction word into 16B under a 0/-1 mask from the control bit.
472 static int4 MaskXor(int4 val, int4 cw, int m);
473
474 // Full-domain evaluation of the subtree rooted at node at level i through an
475 // 8-wide batched frontier: a kBatchPrefix-level scalar prefix, batched
476 // expansions, and a fused tail that converts each group of 8 parents to its
477 // 16 leaves. Requires in_bits - i >= kBatchPrefix + 1.
478 template <typename Cws>
479 void EvalBatched8(bool b, int4 node, Cws cws, Group ocw_group, int4 ys[],
480 size_t l, int i);
481
482 // Evaluates a group of 8 consecutive frontier nodes at level i, covering the leaves
483 // [l, l + 8 << (in_bits - i)) in order. Requires in_bits - i >= 1.
484 template <typename Cws>
485 void EvalBatched8Group(bool b, const int4 (&frontier)[8], Cws cws,
486 Group ocw_group, int4 ys[], size_t l, int i);
487};
488
489// -----------------------------------------------------------------------------
490
491namespace gpu::detail {
492
493template <typename Scheme>
494struct HalfTreeGpuTraits;
495
496#if defined(__CUDACC__)
497
498// One block-local traversal serves the Cw and split kernels. Cws is a Cw
499// pointer on the array path and a kernel-boundary split view on the split path,
500// so the traversal body does not depend on the layout.
501template <typename Scheme, int frontier_depth, int block_root_depth, bool range = false,
502 typename Cws>
503__device__ __forceinline__ void HalfTreeEvalAllBlock(bool b, int4 s0,
504 Cws cws, int4 *ys, int4 instance_seed,
505 typename HalfTreeGpuTraits<Scheme>::Backend &hash, int subtree_block_index,
506 int64_t thread_index, int4 *shared_frontier,
507 __uint128_t first = 0, int subtree_depth = HalfTreeGpuTraits<Scheme>::kOutputBits,
508 int range_frontier = frontier_depth, int range_block_root = block_root_depth) {
509 using Traits = HalfTreeGpuTraits<Scheme>;
510 constexpr int kTailCapacity = range ? 8 : Traits::kOutputBits - frontier_depth;
511 const int start_depth = range ? Traits::kOutputBits - subtree_depth : 0;
512 const int frontier = range ? start_depth + range_frontier : frontier_depth;
513 const int root_depth = range ? start_depth + range_block_root : block_root_depth;
514 const int tail_depth = Traits::kOutputBits - frontier;
515 const int block_subtree_depth = frontier - root_depth;
516 if (threadIdx.x == 0) {
517 int4 node = util::SetLsb(s0, b);
518#pragma unroll
519 for (int level = 0; level < root_depth; ++level) {
520 bool take_right = level < start_depth
521 ? ((first >> (Traits::kOutputBits - 1 - level)) & 1) != 0
522 : ((subtree_block_index >> (root_depth - 1 - level)) & 1) != 0;
523 // Preserve the former by-value helper's backend lifetime per path step.
524 auto node_hash = hash;
525 node = fss::detail::SchemeAccess::HalfTreePathStep<Scheme>(
526 node_hash, instance_seed, node, cws[level].s, take_right);
527 }
528 shared_frontier[0] = node;
529 }
530 __syncthreads();
531
532#pragma unroll
533 for (int level = 0; level < block_subtree_depth; ++level) {
534 const bool active = threadIdx.x < (1 << (level + 1));
535 int4 node = {0, 0, 0, 0};
536 if (active) node = shared_frontier[threadIdx.x >> 1];
537 // Finish all parent reads before children overwrite the same frontier.
538 __syncthreads();
539 if (active) {
540 int4 left, right;
541 auto node_hash = hash;
542 fss::detail::SchemeAccess::HalfTreeExpandNode<Scheme>(node_hash, instance_seed,
543 node, cws[root_depth + level].s, left, right);
544 shared_frontier[threadIdx.x] = (threadIdx.x & 1) ? right : left;
545 }
546 __syncthreads();
547 }
548
549 if constexpr (range && !Traits::kPacked) {
550 int4 pending[8];
551 int4 node = shared_frontier[threadIdx.x];
552 int level = frontier;
553 const int count = 1 << (Traits::kExpansionDepth - frontier);
554 const size_t offset = static_cast<size_t>(thread_index) << tail_depth;
555 using OutputGroup = typename Traits::OutputGroup;
556 auto cw = OutputGroup::From(cws[Traits::kOutputBits].s);
557 for (int index = 0; index < count; ++index) {
558 while (level < Traits::kExpansionDepth) {
559 int4 left, right;
560 fss::detail::SchemeAccess::HalfTreeExpandNode<Scheme>(hash, instance_seed,
561 node, cws[level].s, left, right);
562 pending[level - frontier] = right;
563 node = left;
564 ++level;
565 }
566 int4 left, right;
567 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash,
568 instance_seed, node, cws[Traits::kExpansionDepth], cw, left, right);
569 ys[offset + 2 * index] = left;
570 ys[offset + 2 * index + 1] = right;
571 if (index + 1 < count) {
572 int next = index + 1;
573 level = Traits::kExpansionDepth - 1;
574 while ((next & 1) == 0) {
575 --level;
576 next >>= 1;
577 }
578 node = pending[level - frontier];
579 ++level;
580 }
581 }
582 } else {
583 int4 nodes[1 << kTailCapacity];
584 nodes[0] = shared_frontier[threadIdx.x];
585 int count = 1;
586#pragma unroll
587 for (int level = frontier; level < Traits::kExpansionDepth; ++level) {
588#pragma unroll
589 for (int index = count - 1; index >= 0; --index) {
590 int4 left, right;
591 fss::detail::SchemeAccess::HalfTreeExpandNode<Scheme>(
592 hash, instance_seed, nodes[index], cws[level].s, left, right);
593 nodes[2 * index] = left;
594 nodes[2 * index + 1] = right;
595 }
596 count *= 2;
597 }
598
599 const size_t output_offset = static_cast<size_t>(thread_index) << tail_depth;
600 if constexpr (Traits::kPacked) {
601#pragma unroll
602 for (int index = 0; index < count; ++index) {
603 ys[output_offset + index] = fss::detail::SchemeAccess::PackedConvertLeaf<Scheme>(
604 hash, instance_seed, nodes[index], cws[Traits::kExpansionDepth].s);
605 }
606 } else {
607 using OutputGroup = typename Traits::OutputGroup;
608 auto output_cw = OutputGroup::From(cws[Traits::kOutputBits].s);
609#pragma unroll
610 for (int index = 0; index < count; ++index) {
611 int4 left, right;
612 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash, instance_seed,
613 nodes[index], cws[Traits::kExpansionDepth], output_cw, left, right);
614 ys[output_offset + 2 * index] = left;
615 ys[output_offset + 2 * index + 1] = right;
616 }
617 }
618 }
619}
620
621// Split-layout counterparts of the three kernels above. The Cw buffer
622// arrives type-erased and the boundary view reconstructs the per-layer Cw
623// entries, so the shared traversal stays identical on both paths.
624template <typename Scheme, int threads_per_block>
625__global__ void HalfTreeSplitDpfEvalRangeKernel(bool b, int4 s0,
626 const void *cws_buffer,
627 typename HalfTreeGpuTraits<Scheme>::Input packed_tr, size_t stride,
628 int4 *ys, __uint128_t first, int depth, int frontier, int root,
629 int4 instance_seed, typename HalfTreeGpuTraits<Scheme>::Backend hash) {
630 using Traits = HalfTreeGpuTraits<Scheme>;
631 using Cws = fss::detail::HalfTreeDpfCwsView<typename Scheme::Cw,
632 typename Traits::Input, Traits::kOutputBits>;
633 const auto *cws = static_cast<const int4 *>(cws_buffer);
634 const Cws view{cws, packed_tr, stride};
635 __shared__ int4 states[threads_per_block];
636 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
637 if (depth == 0) {
638 int4 node = util::SetLsb(s0, b);
639 for (int level = 0; level < HalfTreeGpuTraits<Scheme>::kExpansionDepth; ++level) {
640 auto node_hash = hash;
641 node = fss::detail::SchemeAccess::HalfTreePathStep<Scheme>(node_hash,
642 instance_seed, node, view[level].s,
643 ((first >> (HalfTreeGpuTraits<Scheme>::kOutputBits - 1 - level)) & 1) != 0);
644 }
645 int4 left, right;
646 using OutputGroup = typename HalfTreeGpuTraits<Scheme>::OutputGroup;
647 auto cw = OutputGroup::From(view[HalfTreeGpuTraits<Scheme>::kOutputBits].s);
648 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash, instance_seed,
649 node, view[HalfTreeGpuTraits<Scheme>::kExpansionDepth], cw, left, right);
650 ys[0] = (first & 1) ? right : left;
651 return;
652 }
653 HalfTreeEvalAllBlock<Scheme, 0, 0, true>(b, s0, view, ys, instance_seed, hash,
654 blockIdx.x, thread_index, states, first, depth, frontier, root);
655}
656
657template <typename Scheme, int frontier_depth, int block_root_depth>
658__global__ void HalfTreeSplitEvalAllKernel(bool b, int4 s0, const void *cws_buffer,
659 typename HalfTreeGpuTraits<Scheme>::Input packed_tr, size_t stride,
660 int4 *ys, int4 instance_seed, typename HalfTreeGpuTraits<Scheme>::Backend hash) {
661 using Traits = HalfTreeGpuTraits<Scheme>;
662 using Cws = fss::detail::HalfTreeDpfCwsView<typename Scheme::Cw,
663 typename Traits::Input, Traits::kOutputBits>;
664 const auto *cws = static_cast<const int4 *>(cws_buffer);
665 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
666 if (thread_index >= (1LL << frontier_depth)) return;
667 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
668 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0,
669 Cws{cws, packed_tr, stride}, ys, instance_seed, hash, blockIdx.x,
670 thread_index, shared_frontier);
671}
672
673template <typename Scheme, int frontier_depth, int block_root_depth>
674__global__ void HalfTreeSplitEvalAllBatchKernel(bool b, const int4 *s0s,
675 const void *cws_buffer,
676 const typename HalfTreeGpuTraits<Scheme>::Input *trs, size_t stride,
677 int4 *ys, int4 instance_seed, typename HalfTreeGpuTraits<Scheme>::Backend hash) {
678 using Traits = HalfTreeGpuTraits<Scheme>;
679 using Cws = fss::detail::HalfTreeDpfCwsView<typename Scheme::Cw,
680 typename Traits::Input, Traits::kOutputBits>;
681 const auto *cws = static_cast<const int4 *>(cws_buffer);
682 constexpr int kBlocksPerKey = 1 << block_root_depth;
683 constexpr size_t kOutputsPerKey = size_t{1} << HalfTreeGpuTraits<Scheme>::kOutputBits;
684 const int key_index = blockIdx.x / kBlocksPerKey;
685 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
686 const int64_t thread_index = static_cast<int64_t>(subtree_block_index) * blockDim.x + threadIdx.x;
687 if (thread_index >= (1LL << frontier_depth)) return;
688 int4 *key_outputs = ys + static_cast<size_t>(key_index) * kOutputsPerKey;
689 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
690 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
691 Cws{cws + key_index, trs[key_index], stride}, key_outputs, instance_seed,
692 hash, subtree_block_index, thread_index, shared_frontier);
693}
694
695// Device-key counterparts of the split kernels above: the party seed and the
696// packed control are loaded from caller device storage. The shared traversal
697// and the shallow-range fast path are unchanged.
698template <typename Scheme, int frontier_depth, int block_root_depth>
699__global__ void HalfTreeSplitEvalAllDeviceKeyKernel(bool b, const int4 *s0,
700 const void *cws_buffer, const typename HalfTreeGpuTraits<Scheme>::Input *tr,
701 size_t stride, int4 *ys, int4 instance_seed, typename HalfTreeGpuTraits<Scheme>::Backend hash) {
702 using Traits = HalfTreeGpuTraits<Scheme>;
703 using Cws = fss::detail::HalfTreeDpfCwsView<typename Scheme::Cw,
704 typename Traits::Input, Traits::kOutputBits>;
705 const auto *cws = static_cast<const int4 *>(cws_buffer);
706 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
707 if (thread_index >= (1LL << frontier_depth)) return;
708 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
709 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0[0],
710 Cws{cws, tr[0], stride}, ys, instance_seed, hash, blockIdx.x,
711 thread_index, shared_frontier);
712}
713
714template <typename Scheme, int threads_per_block>
715__global__ void HalfTreeSplitEvalRangeDeviceKeyKernel(bool b, const int4 *s0,
716 const void *cws_buffer,
717 const typename HalfTreeGpuTraits<Scheme>::Input *tr, size_t stride,
718 int4 *ys, __uint128_t first, int depth, int frontier, int root,
719 int4 instance_seed, typename HalfTreeGpuTraits<Scheme>::Backend hash) {
720 using Traits = HalfTreeGpuTraits<Scheme>;
721 using Cws = fss::detail::HalfTreeDpfCwsView<typename Scheme::Cw,
722 typename Traits::Input, Traits::kOutputBits>;
723 const auto *cws = static_cast<const int4 *>(cws_buffer);
724 const Cws view{cws, tr[0], stride};
725 __shared__ int4 states[threads_per_block];
726 const int64_t thread_index = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
727 if (depth == 0) {
728 int4 node = util::SetLsb(s0[0], b);
729 for (int level = 0; level < HalfTreeGpuTraits<Scheme>::kExpansionDepth; ++level) {
730 auto node_hash = hash;
731 node = fss::detail::SchemeAccess::HalfTreePathStep<Scheme>(node_hash,
732 instance_seed, node, view[level].s,
733 ((first >> (HalfTreeGpuTraits<Scheme>::kOutputBits - 1 - level)) & 1) != 0);
734 }
735 int4 left, right;
736 using OutputGroup = typename HalfTreeGpuTraits<Scheme>::OutputGroup;
737 auto cw = OutputGroup::From(view[HalfTreeGpuTraits<Scheme>::kOutputBits].s);
738 fss::detail::SchemeAccess::HalfTreeConvertLeafPair<Scheme>(b, hash, instance_seed,
739 node, view[HalfTreeGpuTraits<Scheme>::kExpansionDepth], cw, left, right);
740 ys[0] = (first & 1) ? right : left;
741 return;
742 }
743 HalfTreeEvalAllBlock<Scheme, 0, 0, true>(b, s0[0], view, ys, instance_seed, hash,
744 blockIdx.x, thread_index, states, first, depth, frontier, root);
745}
746
747// Key-major batch counterpart: key k's components occupy records
748// [k*key_records, k*key_records + output_bits] and its control record follows.
749template <typename Scheme, int frontier_depth, int block_root_depth>
750__global__ void HalfTreeSplitEvalAllBatchDeviceKeyKernel(bool b, const int4 *s0s,
751 const void *cws_buffer, size_t key_records, size_t control_record, size_t key_count,
752 int4 *ys, int4 instance_seed, typename HalfTreeGpuTraits<Scheme>::Backend hash) {
753 using In = typename HalfTreeGpuTraits<Scheme>::Input;
754 using Cws = fss::detail::HalfTreeDpfCwsView<typename Scheme::Cw, In,
755 HalfTreeGpuTraits<Scheme>::kOutputBits>;
756 constexpr int kBlocksPerKey = 1 << block_root_depth;
757 constexpr size_t kOutputsPerKey = size_t{1} << HalfTreeGpuTraits<Scheme>::kOutputBits;
758 const auto *cws = static_cast<const int4 *>(cws_buffer);
759 const int key_index = blockIdx.x / kBlocksPerKey;
760 const int subtree_block_index = blockIdx.x - key_index * kBlocksPerKey;
761 const int64_t thread_index = static_cast<int64_t>(subtree_block_index) * blockDim.x + threadIdx.x;
762 if (thread_index >= (1LL << frontier_depth)) return;
763 const int4 *key = cws + static_cast<size_t>(key_index) * key_records;
764 const In *tr = reinterpret_cast<const In *>(key + control_record);
765 int4 *key_outputs = ys + static_cast<size_t>(key_index) * kOutputsPerKey;
766 __shared__ int4 shared_frontier[1 << (frontier_depth - block_root_depth)];
767 HalfTreeEvalAllBlock<Scheme, frontier_depth, block_root_depth>(b, s0s[key_index],
768 Cws{key, tr[0], 1}, key_outputs, instance_seed,
769 hash, subtree_block_index, thread_index, shared_frontier);
770}
771
772// Host launchers for the device-key kernels. The hash backend and instance
773// seed arrive by value from a host mirror of the device scheme, matching the
774// public launch methods.
775template <int in_bits, typename Group, typename Hash, typename In, int frontier_depth = 16,
776 int threads_per_block = 256>
777__host__ inline cudaError_t HalfTreeDpfEvalAllGpuDeviceKey(bool b, const int4 *s0,
778 const int4 *cws, size_t cws_count, const In *tr, size_t stride, int4 *ys,
779 size_t ys_count, int4 instance_seed, const Hash &hash, cudaStream_t stream) {
780 constexpr int kOutputBits = in_bits;
781 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
782 constexpr size_t kOutputsPerKey = size_t{1} << kOutputBits;
783 // Cap the frontier below the leaf level, exactly like the public launcher:
784 // the tail converts leaf pairs, so a frontier at the full depth would emit
785 // two outputs per frontier thread and double the domain.
786 constexpr int kFrontierDepth = frontier_depth < kOutputBits - 1
787 ? frontier_depth : kOutputBits - 1;
788 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
789 (threads_per_block & (threads_per_block - 1)) == 0,
790 "gpu block size must be a power of two in [1,1024]");
791 static_assert(kOutputBits - kFrontierDepth <= 8,
792 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
793 constexpr int kThreadsPerBlockLog2 = [] {
794 int requested_log2 = 0;
795 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
796 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
797 }();
798 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
799 if (stride == 0 || stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
800 cws_count < static_cast<size_t>(in_bits) * stride + 1 || ys_count < kOutputsPerKey ||
801 s0 == nullptr || cws == nullptr || tr == nullptr || ys == nullptr ||
802 kOutputsPerKey > kMaxElements) return cudaErrorInvalidValue;
803 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
804 HalfTreeSplitEvalAllDeviceKeyKernel<Scheme, kFrontierDepth, kBlockRootDepth>
805 <<<size_t{1} << kBlockRootDepth, 1 << kThreadsPerBlockLog2, 0, stream>>>(
806 b, s0, cws, tr, stride, ys, instance_seed, hash);
807 return cudaGetLastError();
808}
809
810template <int in_bits, typename Group, typename Hash, typename In, int frontier_depth = 16,
811 int threads_per_block = 256>
812__host__ inline cudaError_t HalfTreeDpfEvalRangeGpuDeviceKey(bool b, In first, In last,
813 const int4 *s0, const int4 *cws, size_t cws_count, const In *tr, size_t stride,
814 int4 *ys, size_t ys_count, int4 instance_seed, const Hash &hash, cudaStream_t stream) {
815 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
816 if (stride == 0 || stride > (kMaxElements - 1) / static_cast<size_t>(in_bits) ||
817 cws_count < static_cast<size_t>(in_bits) * stride + 1 || s0 == nullptr ||
818 cws == nullptr || tr == nullptr || ys == nullptr) return cudaErrorInvalidValue;
819 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
820 return fss::detail::SplitRangeChunks<in_bits, frontier_depth, threads_per_block, 1>(
821 first, last, ys_count,
822 [&](In chunk_first, int depth, size_t offset, int frontier, int thread_bits, int root,
823 size_t blocks) {
824 HalfTreeSplitEvalRangeDeviceKeyKernel<Scheme, threads_per_block>
825 <<<blocks, 1 << thread_bits, 0, stream>>>(b, s0, cws, tr, stride,
826 ys + offset, chunk_first, depth, frontier, root, instance_seed, hash);
827 return cudaGetLastError();
828 });
829}
830
831template <int in_bits, typename Group, typename Hash, typename In, int frontier_depth = 16,
832 int threads_per_block = 256>
833__host__ inline cudaError_t HalfTreeDpfEvalAllGpuBatchDeviceKey(bool b, const int4 *s0s,
834 size_t key_count, const int4 *cws, size_t cws_count, size_t key_records,
835 size_t control_record, int4 *ys, size_t ys_count, int4 instance_seed, const Hash &hash,
836 cudaStream_t stream) {
837 constexpr int kOutputBits = in_bits;
838 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
839 constexpr size_t kOutputsPerKey = size_t{1} << kOutputBits;
840 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
841 // Cap the frontier below the leaf level like the public and single-key
842 // launchers; the tail converts leaf pairs, so a frontier at the full depth
843 // would emit two outputs per frontier thread and double each key's rows.
844 constexpr int kFrontierDepth = frontier_depth < kOutputBits - 1
845 ? frontier_depth : kOutputBits - 1;
846 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
847 (threads_per_block & (threads_per_block - 1)) == 0,
848 "gpu block size must be a power of two in [1,1024]");
849 static_assert(kOutputBits - kFrontierDepth <= 8,
850 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
851 constexpr int kThreadsPerBlockLog2 = [] {
852 int requested_log2 = 0;
853 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++requested_log2;
854 return requested_log2 < kFrontierDepth ? requested_log2 : kFrontierDepth;
855 }();
856 constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
857 constexpr int kBlocksPerKey = 1 << kBlockRootDepth;
858 if (key_count == 0) return cudaSuccess;
859 if (key_count > kMaxAddressableSize / kOutputsPerKey / sizeof(int4) ||
860 key_count > kMaxAddressableSize / key_records / sizeof(int4) ||
861 cws_count < key_count * key_records || ys_count < key_count * kOutputsPerKey ||
862 s0s == nullptr || cws == nullptr || ys == nullptr ||
863 kOutputsPerKey > kMaxElements) return cudaErrorInvalidValue;
864 if (key_count > static_cast<size_t>(std::numeric_limits<int>::max()) / kBlocksPerKey)
865 return cudaErrorInvalidConfiguration;
866 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
867 HalfTreeSplitEvalAllBatchDeviceKeyKernel<Scheme, kFrontierDepth, kBlockRootDepth>
868 <<<key_count * kBlocksPerKey, 1 << kThreadsPerBlockLog2, 0, stream>>>(
869 b, s0s, cws, key_records, control_record, key_count, ys, instance_seed, hash);
870 return cudaGetLastError();
871}
872
873#endif // defined(__CUDACC__)
874
875} // namespace gpu::detail
876
877template <int in_bits, typename Group, typename Hash, typename In>
879 Groupable<Group> && CcrHashable<Hash>)
880__host__ __device__ inline HalfTreeDpf<in_bits, Group, Hash, In>::HalfTreeDpf(Hash hash, int4 instance_seed)
881 : hash_(hash), instance_seed_(instance_seed) {}
882
883template <int in_bits, typename Group, typename Hash, typename In>
886__host__ __device__ inline void HalfTreeDpf<in_bits, Group, Hash, In>::Gen(
887 cuda::std::span<int4> cws, In &tr, cuda::std::span<const int4, 2> s0s,
888 In a, int4 b_buf, size_t stride) {
889 util::RequireArg(stride > 0, "HalfTreeDpf::Gen: stride must be positive");
890 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
891 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(in_bits),
892 "HalfTreeDpf::Gen: stride exceeds the correction array limit");
893 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
894 util::RequireArg(cws.size() >= required,
895 "HalfTreeDpf::Gen: cws must hold in_bits*stride+1 elements");
896 Generate(CwsWriter{cws.data(), stride, tr}, s0s.data(), a, b_buf);
897}
898
899template <int in_bits, typename Group, typename Hash, typename In>
902template <typename Writer>
903__host__ __device__ inline void HalfTreeDpf<in_bits, Group, Hash, In>::Generate(
904 Writer writer, const int4 *s0s, In a, int4 b_buf) {
905 b_buf = util::SetLsb(b_buf, false);
906
907 // Initialize: node0 has t=0, node1 has t=1
908 int4 node0 = util::SetLsb(s0s[0], false);
909 int4 node1 = util::SetLsb(s0s[1], true);
910 int4 delta = util::Xor(node0, node1); // LSB = 0^1 = 1
911
912 // Levels 1 to n-1 (index i = 0 to in_bits-2)
913 for (int i = 0; i < in_bits - 1; ++i) {
914 int4 h0 = hash_.Hash(util::Xor(instance_seed_, node0));
915 int4 h1 = hash_.Hash(util::Xor(instance_seed_, node1));
916
917 bool a_bit = (a >> (in_bits - 1 - i)) & 1;
918
919 // CW = h0 ^ h1 ^ (!a_bit ? delta : 0)
920 // When a_bit=0 (go left): non-alpha is right, CW = h0^h1^delta makes right0=right1
921 // When a_bit=1 (go right): non-alpha is left, CW = h0^h1 makes left0=left1
922 int4 cw = util::Xor(h0, h1);
923 if (!a_bit) cw = util::Xor(cw, delta);
924
925 writer.Write(i, cw);
926
927 bool t0 = util::GetLsb(node0);
928 bool t1 = util::GetLsb(node1);
929
930 // node_b = h_b ^ (a_bit ? node_b : 0) ^ (t_b ? cw : 0)
931 int4 zero4 = {0, 0, 0, 0};
932 int4 ab_mask0 = a_bit ? node0 : zero4;
933 int4 ab_mask1 = a_bit ? node1 : zero4;
934 int4 t0_mask = t0 ? cw : zero4;
935 int4 t1_mask = t1 ? cw : zero4;
936
937 node0 = util::Xor(util::Xor(h0, ab_mask0), t0_mask);
938 node1 = util::Xor(util::Xor(h1, ab_mask1), t1_mask);
939
940 // delta = node0 ^ node1 for next level
941 delta = util::Xor(node0, node1);
942 }
943
944 // Level n (last level, index i = in_bits-1)
945 {
946 bool a_n = (a >> 0) & 1; // last bit of alpha
947 bool t0 = util::GetLsb(node0);
948 bool t1 = util::GetLsb(node1);
949
950 // Hash with sigma in {0, 1}
951 int4 h0_0 = hash_.Hash(util::Xor(instance_seed_, util::SetLsb(node0, false)));
952 int4 h0_1 = hash_.Hash(util::Xor(instance_seed_, util::SetLsb(node0, true)));
953 int4 h1_0 = hash_.Hash(util::Xor(instance_seed_, util::SetLsb(node1, false)));
954 int4 h1_1 = hash_.Hash(util::Xor(instance_seed_, util::SetLsb(node1, true)));
955
956 // Extract high (s) and low (t) parts
957 int4 high0_0 = util::SetLsb(h0_0, false);
958 bool low0_0 = util::GetLsb(h0_0);
959 int4 high0_1 = util::SetLsb(h0_1, false);
960 bool low0_1 = util::GetLsb(h0_1);
961 int4 high1_0 = util::SetLsb(h1_0, false);
962 bool low1_0 = util::GetLsb(h1_0);
963 int4 high1_1 = util::SetLsb(h1_1, false);
964 bool low1_1 = util::GetLsb(h1_1);
965
966 // HCW corrects the non-alpha direction so both parties converge.
967 // HCW = high{!a_n}_0 ^ high{!a_n}_1
968 int4 HCW;
969 if (a_n) HCW = util::Xor(high0_0, high1_0);
970 else HCW = util::Xor(high0_1, high1_1);
971
972 // LCW ensures:
973 // Alpha direction (sigma=a_n): low0 ^ low1 = 1 (exactly one adds ocw)
974 // Non-alpha direction (sigma=!a_n): low0 ^ low1 = 0 (cancels)
975 // LCW_0 = low{0}_0 ^ low{0}_1 ^ !a_n
976 // LCW_1 = low{1}_0 ^ low{1}_1 ^ a_n
977 bool LCW_0 = low0_0 ^ low1_0 ^ !a_n;
978 bool LCW_1 = low0_1 ^ low1_1 ^ a_n;
979
980 // Store CW_n. The writer's WriteLast() covers the whole final slot.
981 writer.WriteLast(util::SetLsb(HCW, LCW_0), LCW_1);
982
983 // Compute leaf for each party
984 // leaf_b = (a_n ? high{1}_b||low{1}_b : high{0}_b||low{0}_b)
985 int4 leaf0, leaf1;
986 if (a_n) {
987 leaf0 = util::SetLsb(high0_1, low0_1);
988 leaf1 = util::SetLsb(high1_1, low1_1);
989 } else {
990 leaf0 = util::SetLsb(high0_0, low0_0);
991 leaf1 = util::SetLsb(high1_0, low1_0);
992 }
993
994 // Apply the CW: if t_b: leaf_b ^= SetLsb(HCW, lcw_an)
995 bool lcw_an = a_n ? LCW_1 : LCW_0;
996 int4 leaf_cw = util::SetLsb(HCW, lcw_an);
997 if (t0) leaf0 = util::Xor(leaf0, leaf_cw);
998 if (t1) leaf1 = util::Xor(leaf1, leaf_cw);
999
1000 // Output CW: v_cw = Group::From(b_buf) + (-Group::From(SetLsb(leaf0,false))) + Group::From(SetLsb(leaf1,false))
1001 auto v_cw =
1002 Group::From(b_buf) + (-Group::From(util::SetLsb(leaf0, false))) + Group::From(util::SetLsb(leaf1, false));
1003 if (util::GetLsb(leaf1)) v_cw = -v_cw;
1004 writer.Finish(v_cw.Into());
1005 }
1006}
1007
1008template <int in_bits, typename Group, typename Hash, typename In>
1010 Groupable<Group> && CcrHashable<Hash>)
1011__host__ __device__ inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::Eval(
1012 bool b, int4 s0, cuda::std::span<const int4> cws, In tr, In x, size_t stride) {
1013 util::RequireArg(stride > 0, "HalfTreeDpf::Eval: stride must be positive");
1014 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1015 util::RequireArg(stride <= (kMaxElements - 1) / static_cast<size_t>(in_bits),
1016 "HalfTreeDpf::Eval: stride exceeds the correction array limit");
1017 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1018 util::RequireArg(cws.size() >= required,
1019 "HalfTreeDpf::Eval: cws must hold in_bits*stride+1 elements");
1020 const int4 terminal = cws[static_cast<size_t>(in_bits) * stride];
1021 util::RequireArg((terminal.w & 1) == 0,
1022 "HalfTreeDpf::Eval: the terminal group value must have a zero LSB");
1023 return EvalWalk(b, s0, CwsView{cws.data(), tr, stride}, x);
1024}
1025
1026template <int in_bits, typename Group, typename Hash, typename In>
1029template <typename Cws>
1030__host__ __device__ inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::EvalWalk(
1031 bool b, int4 s0, Cws cws, In x) {
1032 int4 node = util::SetLsb(s0, b);
1033
1034 for (int i = 0; i < in_bits - 1; ++i) {
1035 bool take_right = (x >> (in_bits - 1 - i)) & 1;
1036 node = PathStep(hash_, instance_seed_, node, cws[i].s, take_right);
1037 }
1038 // The split view returns the final entries by value, so the shared leaf
1039 // conversion takes them as locals.
1040 const Cw last_cw = cws[in_bits - 1];
1041 const int4 terminal = cws[in_bits].s;
1042 return ConvertLeaf(b, hash_, instance_seed_, node, (x & 1) != 0,
1043 last_cw, terminal);
1044}
1045
1046template <int in_bits, typename Group, typename Hash, typename In>
1048 Groupable<Group> && CcrHashable<Hash>)
1050 cuda::std::span<const int4> cws, In tr, int4 ys[], int par_depth, size_t stride) {
1051 if (stride == 0) throw std::invalid_argument("HalfTreeDpf::EvalAll: stride must be positive");
1052 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1053 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1054 throw std::invalid_argument("HalfTreeDpf::EvalAll: stride exceeds the correction array limit");
1055 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1056 if (cws.size() < required)
1057 throw std::invalid_argument("HalfTreeDpf::EvalAll: cws must hold in_bits*stride+1 elements");
1058 EvalAllCws(b, s0, CwsView{cws.data(), tr, stride}, ys, par_depth);
1059}
1060
1061template <int in_bits, typename Group, typename Hash, typename In>
1064template <typename Cws>
1065inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalAllCws(
1066 bool b, int4 s0, Cws cws, int4 ys[], int par_depth) {
1067 assert(par_depth >= 0);
1068 static_assert(detail::FitsFullDomain<int4, in_bits>(),
1069 "full-domain output count and byte size must fit in size_t");
1070
1071 // The terminal LSB is a caller precondition. Check it on the calling thread,
1072 // so a Debug violation aborts there instead of inside an OpenMP worker;
1073 // Release builds do not check it.
1074 util::RequireArg((cws[in_bits].s.w & 1) == 0,
1075 "HalfTreeDpf::EvalAll: the terminal group value must have a zero LSB");
1076
1077 if constexpr (in_bits < kBatchPrefix + 1) {
1078 // Domains too small to fill an 8-wide frontier evaluate per input; the
1079 // point walk is cheaper than the batched machinery at this size.
1080 constexpr size_t n = size_t{1} << in_bits;
1081 fss::detail::ParallelFor(par_depth, n,
1082 [&](size_t x) { ys[x] = EvalWalk(b, s0, cws, static_cast<In>(x)); });
1083 return;
1084 }
1085
1086 int4 node = util::SetLsb(s0, b);
1087
1088 int par_depth_ = fss::detail::ResolveParDepth(par_depth, in_bits);
1089
1090 int split_depth = par_depth_;
1091 if (split_depth > in_bits - kBatchPrefix - 1) split_depth = in_bits - kBatchPrefix - 1;
1092 if (split_depth > 0) {
1093 // Split at a frontier of 2 ** split_depth subtree roots instead of per-node tasks,
1094 // so the sequential batched walk stays the fast default path.
1095 size_t num_tasks = size_t{1} << split_depth;
1096 std::vector<int4> frontier(num_tasks);
1097 frontier[0] = node;
1098 for (int lv = 0; lv < split_depth; ++lv) ExpandFrontier(frontier.data(), size_t{1} << lv, cws, lv);
1099 size_t span = size_t{1} << (in_bits - split_depth);
1100 fss::detail::ParallelFor(par_depth_, num_tasks, [&](size_t task) {
1101 EvalBatched8(b, frontier[task], cws, Group::From(cws[in_bits].s), ys, task * span, split_depth);
1102 });
1103 } else {
1104 EvalBatched8(b, node, cws, Group::From(cws[in_bits].s), ys, 0, 0);
1105 }
1106}
1107
1108template <int in_bits, typename Group, typename Hash, typename In>
1110 Groupable<Group> && CcrHashable<Hash>)
1111inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalRange(bool b, In first, In last,
1112 int4 s0, cuda::std::span<const int4> cws, In tr, cuda::std::span<int4> ys,
1113 int par_depth, size_t stride) {
1114 if (stride == 0) throw std::invalid_argument("HalfTreeDpf::EvalRange: stride must be positive");
1115 constexpr size_t kMaxElements = SIZE_MAX / sizeof(int4);
1116 if (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits))
1117 throw std::invalid_argument("HalfTreeDpf::EvalRange: stride exceeds the correction array limit");
1118 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1119 if (cws.size() < required || cws.data() == nullptr)
1120 throw std::invalid_argument("HalfTreeDpf::EvalRange: cws must hold in_bits*stride+1 elements");
1121 if (ys.data() == nullptr)
1122 throw std::invalid_argument("HalfTreeDpf::EvalRange: ys must not be null");
1123 EvalRangeCws(b, first, last, s0,
1124 CwsView{cws.data(), tr, stride}, ys, par_depth);
1125}
1126
1127template <int in_bits, typename Group, typename Hash, typename In>
1130template <typename Cws>
1131inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalRangeCws(
1132 bool b, In first, In last, int4 s0, Cws cws,
1133 cuda::std::span<int4> ys, int par_depth) {
1134 assert(par_depth >= 0);
1135 In domain_last = static_cast<In>(~In{0});
1136 if constexpr (in_bits < sizeof(In) * 8) {
1137 domain_last = static_cast<In>((In{1} << in_bits) - 1);
1138 }
1139 if (first > last)
1140 throw std::invalid_argument("HalfTreeDpf::EvalRange: first must not exceed last");
1141 if (last > domain_last)
1142 throw std::invalid_argument("HalfTreeDpf::EvalRange: last must stay inside the input domain");
1143 const __uint128_t distance = static_cast<__uint128_t>(last) - first;
1144 if (distance >= std::numeric_limits<size_t>::max() / sizeof(int4))
1145 throw std::invalid_argument("HalfTreeDpf::EvalRange: the interval is too large for size_t");
1146 const size_t count = static_cast<size_t>(distance) + 1;
1147 if (ys.size() < count || ys.data() == nullptr)
1148 throw std::invalid_argument("HalfTreeDpf::EvalRange: ys must hold last-first+1 entries");
1149 util::RequireArg((cws[in_bits].s.w & 1) == 0,
1150 "HalfTreeDpf::EvalRange: the terminal group value must have a zero LSB");
1151
1152 const size_t partitions = detail::ResolveParPartitions(par_depth, count);
1153 detail::ParallelFor(par_depth, partitions, [&](size_t partition) {
1154 const size_t begin = partition * (count / partitions) +
1155 (partition < count % partitions ? partition : count % partitions);
1156 const size_t end = begin + count / partitions + (partition < count % partitions);
1157 EvalRangeTree(b, util::SetLsb(s0, b), cws, ys.data(),
1158 static_cast<In>(first + begin), static_cast<In>(first + (end - 1)),
1159 first, In{0}, domain_last, 0);
1160 });
1161}
1162
1163template <int in_bits, typename Group, typename Hash, typename In>
1165 Groupable<Group> && CcrHashable<Hash>)
1166template <typename Cws>
1167inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalRangeTree(
1168 bool b, int4 node, Cws cws, int4 ys[], In first, In last,
1169 In output_first, In low, In high, int level) {
1170 if (level == in_bits - 1) {
1171 // The split view returns the final entries by value, so the shared leaf
1172 // conversion takes them as locals.
1173 const Cw last_cw = cws[level];
1174 const int4 terminal = cws[in_bits].s;
1175 if (first <= low) {
1176 ys[static_cast<size_t>(low - output_first)] = ConvertLeaf(
1177 b, hash_, instance_seed_, node, false, last_cw, terminal);
1178 }
1179 if (last >= high) {
1180 ys[static_cast<size_t>(high - output_first)] = ConvertLeaf(
1181 b, hash_, instance_seed_, node, true, last_cw, terminal);
1182 }
1183 return;
1184 }
1185 const In middle = static_cast<In>(low + ((high - low) >> 1));
1186 int4 left, right;
1187 ExpandNode(hash_, instance_seed_, node, cws[level].s, left, right);
1188 if (first <= middle) {
1189 EvalRangeTree(b, left, cws, ys, first, last, output_first, low, middle, level + 1);
1190 }
1191 if (last > middle) {
1192 EvalRangeTree(b, right, cws, ys, first, last, output_first,
1193 static_cast<In>(middle + 1), high, level + 1);
1194 }
1195}
1196
1197template <int in_bits, typename Group, typename Hash, typename In>
1199 Groupable<Group> && CcrHashable<Hash>)
1200template <typename Cws>
1201inline void HalfTreeDpf<in_bits, Group, Hash, In>::ExpandFrontier(
1202 int4 frontier[], size_t w, Cws cws, int lv) {
1203 int4 cw = cws[lv].s;
1204 for (size_t j = w; j-- > 0;) {
1205 int4 node = frontier[j];
1206 int4 left, right;
1207 ExpandNode(hash_, instance_seed_, node, cw, left, right);
1208 frontier[2 * j] = left;
1209 frontier[2 * j + 1] = right;
1210 }
1211}
1212
1213template <int in_bits, typename Group, typename Hash, typename In>
1215 Groupable<Group> && CcrHashable<Hash>)
1216inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::MaskXor(int4 val, int4 cw, int m) {
1217 return {val.x ^ (cw.x & m), val.y ^ (cw.y & m), val.z ^ (cw.z & m), val.w ^ (cw.w & m)};
1218}
1219
1220template <int in_bits, typename Group, typename Hash, typename In>
1222 Groupable<Group> && CcrHashable<Hash>)
1223template <typename Cws>
1224inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalBatched8(
1225 bool b, int4 node, Cws cws, Group ocw_group, int4 ys[], size_t l, int i) {
1226 int4 frontier[8];
1227 frontier[0] = node;
1228 for (int lv = i; lv < i + kBatchPrefix; ++lv) ExpandFrontier(frontier, size_t{1} << (lv - i), cws, lv);
1229 EvalBatched8Group(b, frontier, cws, ocw_group, ys, l, i + kBatchPrefix);
1230}
1231
1232template <int in_bits, typename Group, typename Hash, typename In>
1234 Groupable<Group> && CcrHashable<Hash>)
1235template <typename Cws>
1236inline void HalfTreeDpf<in_bits, Group, Hash, In>::EvalBatched8Group(
1237 bool b, const int4 (&frontier)[8], Cws cws, Group ocw_group, int4 ys[],
1238 size_t l, int i) {
1239 if (i == in_bits - 1) {
1240 // 8 parents -> 16 leaves with two batches of eight hashes (sigma = 0, 1).
1241 int4 hcw = util::SetLsb(cws[in_bits - 1].s, false);
1242 int lcw_0 = util::GetLsb(cws[in_bits - 1].s);
1243 int lcw_1 = cws[in_bits - 1].extra;
1244
1245 cuda::std::array<int4, 8> seeds0;
1246 cuda::std::array<int4, 8> seeds1;
1247 int ts[8];
1248 for (int j = 0; j < 8; ++j) {
1249 ts[j] = static_cast<int>(util::GetLsb(frontier[j]));
1250 seeds0[j] = util::Xor(instance_seed_, util::SetLsb(frontier[j], false));
1251 seeds1[j] = util::Xor(instance_seed_, util::SetLsb(frontier[j], true));
1252 }
1253 auto h0s = detail::HashBatch(hash_, seeds0);
1254 auto h1s = detail::HashBatch(hash_, seeds1);
1255 for (int j = 0; j < 8; ++j) {
1256 int m = -ts[j];
1257 int4 h0 = h0s[j];
1258 int4 high0 = MaskXor(h0, hcw, m);
1259 high0.w &= ~1;
1260 int low0 = (h0.w & 1) ^ (ts[j] & lcw_0);
1261 int4 h1 = h1s[j];
1262 int4 high1 = MaskXor(h1, hcw, m);
1263 high1.w &= ~1;
1264 int low1 = (h1.w & 1) ^ (ts[j] & lcw_1);
1265
1266 auto y0 = Group::From(high0);
1267 if (low0) y0 = y0 + ocw_group;
1268 if (b) y0 = -y0;
1269 ys[l + 2 * j] = y0.Into();
1270
1271 auto y1 = Group::From(high1);
1272 if (low1) y1 = y1 + ocw_group;
1273 if (b) y1 = -y1;
1274 ys[l + 2 * j + 1] = y1.Into();
1275 }
1276 return;
1277 }
1278
1279 int4 cw = cws[i].s;
1280 cuda::std::array<int4, 8> seeds;
1281 int ts[8];
1282 for (int j = 0; j < 8; ++j) {
1283 ts[j] = static_cast<int>(util::GetLsb(frontier[j]));
1284 seeds[j] = util::Xor(instance_seed_, frontier[j]);
1285 }
1286 auto out = detail::HashBatch(hash_, seeds);
1287 // The 16 children interleave in leaf order: node j's children land at 2j, 2j+1 of the
1288 // next level. De-interleave them into two consecutive groups of 8.
1289 int4 low[8];
1290 int4 high[8];
1291 for (int j = 0; j < 4; ++j) {
1292 int m = -ts[j];
1293 int4 left = MaskXor(out[j], cw, m);
1294 low[2 * j] = left;
1295 low[2 * j + 1] = util::Xor(left, frontier[j]);
1296 int m4 = -ts[j + 4];
1297 int4 left4 = MaskXor(out[j + 4], cw, m4);
1298 high[2 * j] = left4;
1299 high[2 * j + 1] = util::Xor(left4, frontier[j + 4]);
1300 }
1301
1302 size_t half = size_t{8} << (in_bits - i - 1);
1303 EvalBatched8Group(b, low, cws, ocw_group, ys, l, i + 1);
1304 EvalBatched8Group(b, high, cws, ocw_group, ys, l + half, i + 1);
1305}
1306
1307template <int in_bits, typename Group, typename Hash, typename In>
1309 Groupable<Group> && CcrHashable<Hash>)
1310__host__ __device__ inline void HalfTreeDpf<in_bits, Group, Hash, In>::ExpandNode(
1311 Hash &hash, int4 instance_seed, int4 node, int4 cw, int4 &left, int4 &right) {
1312 int4 hashed = hash.Hash(util::Xor(instance_seed, node));
1313 const int4 zero = {0, 0, 0, 0};
1314 left = util::Xor(hashed, util::GetLsb(node) ? cw : zero);
1315 right = util::Xor(left, node);
1316}
1317
1318template <int in_bits, typename Group, typename Hash, typename In>
1320 Groupable<Group> && CcrHashable<Hash>)
1321__host__ __device__ inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::PathStep(
1322 Hash &hash, int4 instance_seed, int4 node, int4 cw, bool take_right) {
1323 const int4 zero = {0, 0, 0, 0};
1324 int4 hashed = hash.Hash(util::Xor(instance_seed, node));
1325 return util::Xor(util::Xor(hashed, take_right ? node : zero),
1326 util::GetLsb(node) ? cw : zero);
1327}
1328
1329template <int in_bits, typename Group, typename Hash, typename In>
1331 Groupable<Group> && CcrHashable<Hash>)
1332template <typename OutputCw>
1333__host__ __device__ inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::ConvertLeaf(bool party, Hash &hash,
1334 int4 instance_seed, int4 node, bool take_right, const Cw &last_cw,
1335 OutputCw &output_cw) {
1336 int4 hashed = hash.Hash(util::Xor(instance_seed, util::SetLsb(node, take_right)));
1337 return ConvertHashedLeaf(party, hashed, util::GetLsb(node), take_right,
1338 last_cw, output_cw);
1339}
1340
1341template <int in_bits, typename Group, typename Hash, typename In>
1343 Groupable<Group> && CcrHashable<Hash>)
1344template <typename OutputCw>
1345__host__ __device__ inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::ConvertHashedLeaf(bool party, int4 hashed,
1346 bool node_control, bool take_right, const Cw &last_cw,
1347 OutputCw &output_cw) {
1348 int4 high = util::SetLsb(hashed, false);
1349 bool low = util::GetLsb(hashed);
1350 if (node_control) {
1351 high = util::Xor(high, util::SetLsb(last_cw.s, false));
1352 low ^= take_right ? last_cw.extra : util::GetLsb(last_cw.s);
1353 }
1354 auto value = Group::From(high);
1355 if (low) {
1356 if constexpr (std::is_same_v<std::remove_cv_t<OutputCw>, int4>) {
1357 value = value + Group::From(output_cw);
1358 } else {
1359 value = value + output_cw;
1360 }
1361 }
1362 if (party) value = -value;
1363 return value.Into();
1364}
1365
1366template <int in_bits, typename Group, typename Hash, typename In>
1368 Groupable<Group> && CcrHashable<Hash>)
1369template <typename OutputCw>
1370__host__ __device__ inline void HalfTreeDpf<in_bits, Group, Hash, In>::ConvertLeafPair(bool party, Hash &hash,
1371 int4 instance_seed, int4 node, const Cw &last_cw,
1372 OutputCw &output_cw, int4 &left, int4 &right) {
1373 int4 left_hash = hash.Hash(util::Xor(instance_seed, util::SetLsb(node, false)));
1374 int4 right_hash = hash.Hash(util::Xor(instance_seed, util::SetLsb(node, true)));
1375 bool control = util::GetLsb(node);
1376 left = ConvertHashedLeaf(party, left_hash, control, false, last_cw, output_cw);
1377 right = ConvertHashedLeaf(party, right_hash, control, true, last_cw, output_cw);
1378}
1379
1380namespace gpu::detail {
1381
1382template <int in_bits, typename Group, typename Hash, typename In>
1383 requires(ValidForInputBits<In, in_bits> &&
1384 Groupable<Group> && CcrHashable<Hash>)
1385struct HalfTreeGpuTraits<HalfTreeDpf<in_bits, Group, Hash, In>> {
1386 using Backend = Hash;
1387 using OutputGroup = Group;
1388 using Input = In;
1389 static constexpr int kExpansionDepth = in_bits - 1;
1390 static constexpr int kOutputBits = in_bits;
1391 static constexpr bool kPacked = false;
1392};
1393
1394#if defined(__CUDACC__)
1395
1396// Point-evaluate in_bits-level HalfTree DPF keys: one thread per key, one output
1397// value per key at xs[tid]. cws holds dense level-major correction words, the
1398// layout the split form of Gen() writes: layer i of key k is cws[i * nkeys + k]
1399// and the terminal layer is cws[in_bits * nkeys + k]. tr[k] carries key k's
1400// packed control bits; its bit in_bits - 1 is the last level's extra bit.
1401template <int in_bits, int unroll, bool b, typename Scheme, typename In, typename Query = In>
1402__global__ void HalfTreeDpfEvalPointKernel(int4 *ys, const int4 *seeds, const int4 *cws, const In *tr,
1403 const Query *xs, int nkeys, typename HalfTreeGpuTraits<Scheme>::Backend hash, int4 instance_seed) {
1404 using Group = typename HalfTreeGpuTraits<Scheme>::OutputGroup;
1405 static_assert(in_bits <= sizeof(In) * 8, "In too narrow for in_bits");
1406 int tid = blockIdx.x * blockDim.x + threadIdx.x;
1407 if (tid >= nkeys) return;
1408 int4 node = fss::util::SetLsb(seeds[tid], b);
1409 In x = static_cast<In>(xs[tid]);
1410 In tr_k = tr[tid];
1411
1412#pragma unroll unroll
1413 for (int i = 0; i < in_bits - 1; ++i) {
1414 bool x_bit = GetBit(x, in_bits - 1 - i);
1415 bool t = fss::util::GetLsb(node);
1416 int4 h = hash.Hash(fss::util::Xor(instance_seed, node));
1417 int4 cw = cws[(size_t)i * nkeys + tid];
1418 node = fss::util::Xor(fss::util::Xor(h, x_bit ? node : Zero4()), t ? cw : Zero4());
1419 }
1420
1421 bool x_n = GetBit(x, 0);
1422 bool t = fss::util::GetLsb(node);
1423 int4 h = hash.Hash(fss::util::Xor(instance_seed, fss::util::SetLsb(node, x_n)));
1424 int4 cw = cws[(size_t)(in_bits - 1) * nkeys + tid];
1425 int4 hcw = fss::util::SetLsb(cw, false);
1426 bool lcw_xn = x_n ? GetBit(tr_k, in_bits - 1) : fss::util::GetLsb(cw);
1427 int4 high = fss::util::SetLsb(h, false);
1428 bool low = fss::util::GetLsb(h);
1429 if (t) {
1430 high = fss::util::Xor(high, hcw);
1431 low = low ^ lcw_xn;
1432 }
1433 auto y = Group::From(high);
1434 if (low) y = y + Group::From(cws[static_cast<size_t>(in_bits) * nkeys + tid]);
1435 if constexpr (b) y = -y;
1436 ys[tid] = y.Into();
1437}
1438
1456template <int unroll = 4, int in_bits, typename Group, typename Hash, typename In, typename Query = In>
1457 requires QueryFor<Query, in_bits>
1458void HalfTreeDpfEvalPointGpu(bool b, cuda::std::span<const int4> seeds, cuda::std::span<const int4> cws,
1459 cuda::std::span<const In> tr, cuda::std::span<const Query> xs, int4 *ys, int nkeys,
1460 const HalfTreeDpf<in_bits, Group, Hash, In> &dpf, cudaStream_t stream = 0) {
1461 if (nkeys < 0)
1462 throw std::invalid_argument("HalfTreeDpfEvalPointGpu: nkeys must be nonnegative");
1463 if (nkeys == 0) return;
1464 const size_t count = static_cast<size_t>(nkeys);
1465 constexpr size_t kLevels = static_cast<size_t>(in_bits) + 1;
1466 if (count > SIZE_MAX / kLevels || cws.size() < count * kLevels)
1467 throw std::invalid_argument("HalfTreeDpfEvalPointGpu: cws must hold (in_bits+1)*nkeys elements");
1468 if (seeds.size() < count || tr.size() < count || xs.size() < count || ys == nullptr)
1469 throw std::invalid_argument("HalfTreeDpfEvalPointGpu: seeds, tr, xs, and ys must cover nkeys entries");
1470 using Scheme = HalfTreeDpf<in_bits, Group, Hash, In>;
1471 constexpr int kBs = 256;
1472 int blocks = (nkeys + kBs - 1) / kBs;
1473 if (b) {
1474 HalfTreeDpfEvalPointKernel<in_bits, unroll, true, Scheme, In, Query>
1475 <<<blocks, kBs, 0, stream>>>(ys, seeds.data(), cws.data(), tr.data(), xs.data(), nkeys,
1476 fss::detail::SchemeAccess::GetHash(dpf), fss::detail::SchemeAccess::GetInstanceSeed(dpf));
1477 } else {
1478 HalfTreeDpfEvalPointKernel<in_bits, unroll, false, Scheme, In, Query>
1479 <<<blocks, kBs, 0, stream>>>(ys, seeds.data(), cws.data(), tr.data(), xs.data(), nkeys,
1480 fss::detail::SchemeAccess::GetHash(dpf), fss::detail::SchemeAccess::GetInstanceSeed(dpf));
1481 }
1482}
1483
1484#endif // defined(__CUDACC__)
1485
1486} // namespace gpu::detail
1487
1488template <int in_bits, typename Group, typename Hash, typename In>
1490 Groupable<Group> && CcrHashable<Hash>)
1491template <int frontier_depth, int threads_per_block>
1493struct HalfTreeDpf<in_bits, Group, Hash, In>::GpuLaunchShape {
1494 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
1495 static_assert(1 <= threads_per_block && threads_per_block <= 1024 &&
1496 (threads_per_block & (threads_per_block - 1)) == 0,
1497 "gpu block size must be a power of two in [1,1024]");
1498 static constexpr int kFrontierDepth = frontier_depth < in_bits - 1 ? frontier_depth : in_bits - 1;
1499 static_assert(in_bits - kFrontierDepth <= 8,
1500 "gpu frontier leaves more than eight tail levels; specify a larger frontier_depth");
1501 static constexpr int kThreadsPerBlockLog2 = [] {
1502 int depth = 0;
1503 for (int count = threads_per_block; count > 1; count >>= 1) ++depth;
1504 return depth < kFrontierDepth ? depth : kFrontierDepth;
1505 }();
1506 static constexpr int kThreadsPerBlock = 1 << kThreadsPerBlockLog2;
1507 static constexpr int kBlockRootDepth = kFrontierDepth - kThreadsPerBlockLog2;
1508 static_assert(kBlockRootDepth <= 30, "gpu block index exceeds the signed-int range");
1509 static constexpr size_t kBlocksPerKey = size_t{1} << (kBlockRootDepth <= 30 ? kBlockRootDepth : 0);
1510};
1511
1512template <int in_bits, typename Group, typename Hash, typename In>
1514 Groupable<Group> && CcrHashable<Hash>)
1515template <int frontier_depth, int threads_per_block>
1517 bool b, In first, In last, int4 s0, cuda::std::span<const int4> cws, In tr,
1518 cuda::std::span<int4> ys, cudaStream_t stream, size_t stride) const {
1519#if defined(__CUDACC__)
1521 static_assert(frontier_depth >= 0, "gpu frontier depth must be nonnegative");
1522 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
1523 (threads_per_block & (threads_per_block - 1)) == 0,
1524 "gpu block size must be a power of two in [1,1024]");
1525 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1526 size_t count;
1527 if (!detail::RangeOutputCount<in_bits>(first, last, ys.size(), count) || stride == 0 ||
1528 (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits)) ||
1529 cws.size() < static_cast<size_t>(in_bits) * stride + 1 ||
1530 cws.data() == nullptr || ys.data() == nullptr)
1531 return cudaErrorInvalidValue;
1532 return detail::SplitRangeChunks<in_bits, frontier_depth, threads_per_block, 1>(
1533 first, last, ys.size(),
1534 [&](In chunk_first, int depth, size_t offset, int frontier, int thread_bits, int root,
1535 size_t blocks) {
1536 gpu::detail::HalfTreeSplitDpfEvalRangeKernel<Scheme, threads_per_block>
1537 <<<blocks, 1 << thread_bits, 0, stream>>>(b, s0, cws.data(), tr, stride,
1538 ys.data() + offset, chunk_first, depth, frontier, root, instance_seed_, hash_);
1539 return cudaGetLastError();
1540 });
1541#else
1542 static_assert(sizeof(Hash) == 0, "gpu launch requires a CUDA compiler");
1543 return cudaErrorNotSupported;
1544#endif
1545}
1546
1547template <int in_bits, typename Group, typename Hash, typename In>
1550template <int frontier_depth, int threads_per_block>
1552 bool b, int4 s0, cuda::std::span<const int4> cws, In tr,
1553 cuda::std::span<int4> ys, cudaStream_t stream, size_t stride) const
1554 requires(in_bits < sizeof(size_t) * 8) {
1555#if defined(__CUDACC__)
1556 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1558 constexpr size_t kOutputsPerKey = size_t{1} << in_bits;
1559 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1560 if (stride == 0 ||
1561 (stride > (kMaxElements - 1) / static_cast<size_t>(in_bits)))
1562 return cudaErrorInvalidValue;
1563 const size_t required = static_cast<size_t>(in_bits) * stride + 1;
1564 if (cws.size() < required || ys.size() < kOutputsPerKey ||
1565 cws.data() == nullptr || ys.data() == nullptr ||
1566 kOutputsPerKey > std::numeric_limits<size_t>::max() / sizeof(int4))
1567 return cudaErrorInvalidValue;
1568 gpu::detail::HalfTreeSplitEvalAllKernel<Scheme, LaunchShape::kFrontierDepth,
1569 LaunchShape::kBlockRootDepth>
1570 <<<LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1571 b, s0, cws.data(), tr, stride, ys.data(), instance_seed_, hash_);
1572 return cudaGetLastError();
1573#else
1574 static_assert(sizeof(Hash) == 0, "gpu launch requires a CUDA compiler");
1575 return cudaErrorNotSupported;
1576#endif
1577}
1578
1579template <int in_bits, typename Group, typename Hash, typename In>
1582template <int frontier_depth, int threads_per_block>
1584 bool b, cuda::std::span<const int4> s0s, cuda::std::span<const int4> cws,
1585 cuda::std::span<const In> tr, cuda::std::span<int4> ys,
1586 cudaStream_t stream, size_t stride) const
1587 requires(in_bits < sizeof(size_t) * 8) {
1588#if defined(__CUDACC__)
1589 using LaunchShape = GpuLaunchShape<frontier_depth, threads_per_block>;
1591 constexpr size_t kOutputsPerKey = size_t{1} << in_bits;
1592 constexpr size_t kMaxElements = std::numeric_limits<size_t>::max() / sizeof(int4);
1593 constexpr size_t kMaxAddressableSize = std::numeric_limits<size_t>::max();
1594 const size_t key_count = s0s.size();
1595 if (key_count == 0) return cudaSuccess;
1596 // The output guard bounds key_count * kOutputsPerKey * sizeof(int4) so the
1597 // product in the size check below cannot wrap.
1598 if (key_count > kMaxElements ||
1599 key_count > kMaxAddressableSize / kOutputsPerKey / sizeof(int4) ||
1600 (stride != 0 && (stride < key_count || stride > kMaxElements)))
1601 return cudaErrorInvalidValue;
1602 // A stride of 0 resolves to the key count, which is the dense level-major layout.
1603 const size_t resolved_stride = stride == 0 ? key_count : stride;
1604 if (resolved_stride > (kMaxElements - key_count) / static_cast<size_t>(in_bits))
1605 return cudaErrorInvalidValue;
1606 const size_t required = static_cast<size_t>(in_bits) * resolved_stride + key_count;
1607 if (cws.size() < required || tr.size() < key_count || ys.size() < key_count * kOutputsPerKey ||
1608 s0s.data() == nullptr || cws.data() == nullptr || tr.data() == nullptr ||
1609 ys.data() == nullptr)
1610 return cudaErrorInvalidValue;
1611 if (key_count > static_cast<size_t>(std::numeric_limits<int>::max()) / LaunchShape::kBlocksPerKey)
1612 return cudaErrorInvalidConfiguration;
1613 gpu::detail::HalfTreeSplitEvalAllBatchKernel<Scheme, LaunchShape::kFrontierDepth,
1614 LaunchShape::kBlockRootDepth>
1615 <<<key_count * LaunchShape::kBlocksPerKey, LaunchShape::kThreadsPerBlock, 0, stream>>>(
1616 b, s0s.data(), cws.data(), tr.data(), resolved_stride, ys.data(), instance_seed_, hash_);
1617 return cudaGetLastError();
1618#else
1619 static_assert(sizeof(Hash) == 0, "gpu launch requires a CUDA compiler");
1620 return cudaErrorNotSupported;
1621#endif
1622}
1623
1624#if FSS_ENABLE_V1_COMPAT
1625
1626// Deprecated v1.3.0 aggregate-Cw entries. Every wrapper copies between the
1627// caller's Cw layout and a compile-time-sized split-component scratch buffer,
1628// then runs the current public method at stride 1. The old layout kept the
1629// output correction word in the caller's separate ocw and the last tree
1630// level's LCW_1 in Cw::extra, which is bit in_bits - 1 of the split control.
1631template <int in_bits, typename Group, typename Hash, typename In>
1634__host__ __device__ inline void HalfTreeDpf<in_bits, Group, Hash, In>::Gen(
1635 Cw cws[], int4 &ocw, const int4 s0s[2], In a, int4 b_buf) {
1636 int4 seed[in_bits + 1];
1637 In tr;
1638 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr,
1639 cuda::std::span<const int4, 2>(s0s, 2), a, b_buf);
1640 for (int i = 0; i < in_bits - 1; ++i) cws[i] = {seed[i], false};
1641 cws[in_bits - 1] = {seed[in_bits - 1], ((tr >> (in_bits - 1)) & 1) != 0};
1642 ocw = seed[in_bits];
1643}
1644
1645template <int in_bits, typename Group, typename Hash, typename In>
1647 Groupable<Group> && CcrHashable<Hash>)
1648__host__ __device__ inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::Eval(
1649 bool b, int4 s0, const Cw cws[], int4 ocw, In x) {
1650 int4 seed[in_bits + 1];
1651 In tr = 0;
1652 for (int i = 0; i < in_bits; ++i) seed[i] = cws[i].s;
1653 seed[in_bits] = ocw;
1654 tr |= static_cast<In>(cws[in_bits - 1].extra) << (in_bits - 1);
1655 return Eval(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, x);
1656}
1657
1658template <int in_bits, typename Group, typename Hash, typename In>
1660 Groupable<Group> && CcrHashable<Hash>)
1662 bool b, int4 s0, const Cw cws[], int4 ocw, int4 ys[]) {
1663 int4 seed[in_bits + 1];
1664 In tr = 0;
1665 for (int i = 0; i < in_bits; ++i) seed[i] = cws[i].s;
1666 seed[in_bits] = ocw;
1667 tr |= static_cast<In>(cws[in_bits - 1].extra) << (in_bits - 1);
1668 EvalAll(b, s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys);
1669}
1670
1671// Deprecated v1.3.0 aggregate-Key entries. Same conversion as the Cw entries
1672// above; the Key entries additionally spread the shared correction words and
1673// output correction word over both keys and take the party seeds from s0s.
1674template <int in_bits, typename Group, typename Hash, typename In>
1676 Groupable<Group> && CcrHashable<Hash>)
1677__host__ __device__ inline void HalfTreeDpf<in_bits, Group, Hash, In>::Gen(
1678 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k0,
1679 compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &k1,
1680 cuda::std::span<const int4, 2> s0s, In a, int4 b_buf) {
1681 int4 seed[in_bits + 1];
1682 In tr;
1683 Gen(cuda::std::span<int4>(seed, in_bits + 1), tr, s0s, a, b_buf);
1684 for (int i = 0; i < in_bits - 1; ++i) {
1685 const Cw layer = {seed[i], false};
1686 k0.cws[i] = layer;
1687 k1.cws[i] = layer;
1688 }
1689 const Cw last = {seed[in_bits - 1], ((tr >> (in_bits - 1)) & 1) != 0};
1690 k0.cws[in_bits - 1] = last;
1691 k1.cws[in_bits - 1] = last;
1692 k0.ocw = seed[in_bits];
1693 k1.ocw = seed[in_bits];
1694 k0.s0 = s0s[0];
1695 k1.s0 = s0s[1];
1696}
1697
1698template <int in_bits, typename Group, typename Hash, typename In>
1700 Groupable<Group> && CcrHashable<Hash>)
1701__host__ __device__ inline int4 HalfTreeDpf<in_bits, Group, Hash, In>::Eval(
1702 bool b, const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key, In x) {
1703 int4 seed[in_bits + 1];
1704 In tr = 0;
1705 for (int i = 0; i < in_bits; ++i) seed[i] = key.cws[i].s;
1706 seed[in_bits] = key.ocw;
1707 tr |= static_cast<In>(key.cws[in_bits - 1].extra) << (in_bits - 1);
1708 return Eval(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, x);
1709}
1710
1711template <int in_bits, typename Group, typename Hash, typename In>
1713 Groupable<Group> && CcrHashable<Hash>)
1715 bool b, const compat::detail::OcwKeyRecord<HalfTreeDpf, Cw, in_bits> &key,
1716 cuda::std::span<int4> ys) {
1717 int4 seed[in_bits + 1];
1718 In tr = 0;
1719 for (int i = 0; i < in_bits; ++i) seed[i] = key.cws[i].s;
1720 seed[in_bits] = key.ocw;
1721 tr |= static_cast<In>(key.cws[in_bits - 1].extra) << (in_bits - 1);
1722 EvalAll(b, key.s0, cuda::std::span<const int4>(seed, in_bits + 1), tr, ys.data());
1723}
1724
1725#endif // FSS_ENABLE_V1_COMPAT
1726
1727} // namespace fss
cudaError_t EvalAllGpu(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=1) const
Launch full-domain CUDA evaluation from split device arrays.
Definition half_tree_dpf.cuh:1551
int4 Eval(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, In x, size_t stride=1)
Evaluate one point from split Cws.
Definition half_tree_dpf.cuh:1011
void EvalAll(bool b, int4 s0, cuda::std::span< const int4 > cws, In tr, int4 ys[], int par_depth=0, size_t stride=1)
Evaluate the full domain from split Cws.
Definition half_tree_dpf.cuh:1049
void Gen(cuda::std::span< int4 > cws, In &tr, cuda::std::span< const int4, 2 > s0s, In a, int4 b_buf, size_t stride=1)
Generate split Cws from caller-owned initial seeds.
Definition half_tree_dpf.cuh:886
void EvalRange(bool b, In first, In last, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< int4 > ys, int par_depth=0, size_t stride=1)
Evaluate the closed input interval [first, last] from split Cws.
Definition half_tree_dpf.cuh:1111
cudaError_t EvalRangeGpu(bool b, In first, In last, int4 s0, cuda::std::span< const int4 > cws, In tr, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=1) const
Evaluate the closed interval [first, last] on the given CUDA stream from split device arrays.
Definition half_tree_dpf.cuh:1516
cudaError_t EvalAllGpuBatch(bool b, cuda::std::span< const int4 > s0s, cuda::std::span< const int4 > cws, cuda::std::span< const In > tr, cuda::std::span< int4 > ys, cudaStream_t stream=0, size_t stride=0) const
Launch full-domain CUDA evaluation for s0s.size() keys from split device arrays.
Definition half_tree_dpf.cuh:1583
In Input
The caller-facing input-domain type.
Definition half_tree_dpf.cuh:101
HalfTreeDpf(Hash hash, int4 instance_seed)
Configure the CCR hash and public instance seed S.
Definition half_tree_dpf.cuh:880
static constexpr int kCwCount
Seed-component count Gen() writes at stride 1: in_bits levels plus the terminal.
Definition half_tree_dpf.cuh:99
Single build-time switch for the deprecated v1 Cw-form compatibility entries.
Circular-correlation-robust (CCR) hash interface for HalfTree schemes.
Definition hash.cuh:86
Group interface.
Definition group.cuh:49
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 half_tree_dpf.cuh:92
int4 s
Seed correction word, or final HCW with LCW_0 in its least significant bit.
Definition half_tree_dpf.cuh:93
bool extra
Final tree-level LCW_1. Ignored in earlier levels and the output correction word.
Definition half_tree_dpf.cuh:94