Files
paddlepaddle--paddle/paddle/phi/kernels/funcs/shuffle_batch.cu.h
T
2026-07-13 12:40:42 +08:00

201 lines
6.9 KiB
C++

// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// #include <curand.h>
// #include "paddle/phi/core/enforce.h"
// #include <curand_kernel.h>
#pragma once
#include <thrust/detail/config.h>
#include <thrust/detail/temporary_array.h>
#include <thrust/iterator/discard_iterator.h>
#include <thrust/iterator/transform_iterator.h>
#include <thrust/iterator/transform_output_iterator.h>
#include <thrust/random.h>
#include <thrust/scan.h>
#include <cstdint>
namespace phi {
namespace funcs {
// Note: all this code is from thrust:shuffle_copy,
// but we aim to maintain alignment in model accuracy > CUDA 11.2,
// thus we have to control the randomness code.
// An implementation of a Feistel cipher for operating on 64 bit keys
class feistel_bijection_fixed {
struct round_state {
std::uint32_t left;
std::uint32_t right;
};
public:
template <class URBG>
__host__ __device__ feistel_bijection_fixed(std::uint64_t m, URBG&& g) {
std::uint64_t total_bits = get_cipher_bits_fixed(m);
// Half bits rounded down
left_side_bits = total_bits / 2;
left_side_mask = (1ull << left_side_bits) - 1;
// Half the bits rounded up
right_side_bits = total_bits - left_side_bits;
right_side_mask = (1ull << right_side_bits) - 1;
for (std::uint64_t i = 0; i < num_rounds; i++) {
key[i] = g();
}
}
__host__ __device__ std::uint64_t nearest_power_of_two_fixed() const {
return 1ull << (left_side_bits + right_side_bits);
}
__host__ __device__ std::uint64_t operator()(const std::uint64_t val) const {
// Extract the right and left sides of the input
auto left = static_cast<std::uint32_t>(val >> right_side_bits);
auto right = static_cast<std::uint32_t>(val & right_side_mask);
round_state state = {left, right};
for (std::uint64_t i = 0; i < num_rounds; i++) {
state = do_round_fixed(state, i);
}
// Check we have the correct number of bits on each side
assert((state.left >> left_side_bits) == 0);
assert((state.right >> right_side_bits) == 0);
// Combine the left and right sides together to get result
return state.left << right_side_bits | state.right;
}
private:
// Find the nearest power of two
__host__ __device__ std::uint64_t get_cipher_bits_fixed(std::uint64_t m) {
std::uint64_t i = 0;
while (m != 0) {
i++;
m >>= 1;
}
return i;
}
// Round function, a 'pseudorandom function' who's output is indistinguishable
// from random for each key value input. This is not cryptographically secure
// but sufficient for generating permutations.
__host__ __device__ std::uint32_t round_function_fixed(
std::uint64_t value, const std::uint64_t key_) const {
std::uint64_t value_hash =
thrust::random::taus88(static_cast<std::uint32_t>(value))();
return (value_hash ^ key_) & left_side_mask;
}
__host__ __device__ round_state
do_round_fixed(const round_state state, const std::uint64_t round) const {
const std::uint32_t new_left = state.right & left_side_mask;
const std::uint32_t round_function_res =
state.left ^ round_function_fixed(state.right, key[round]);
if (right_side_bits != left_side_bits) {
// Upper bit of the old right becomes lower bit of new right if we have
// odd length feistel
const std::uint32_t new_right =
(round_function_res << 1ull) | state.right >> left_side_bits;
return {new_left, new_right};
}
return {new_left, round_function_res};
}
static constexpr std::uint64_t num_rounds = 8;
std::uint64_t right_side_bits;
std::uint64_t left_side_bits;
std::uint64_t right_side_mask;
std::uint64_t left_side_mask;
std::uint64_t key[8];
};
struct key_flag_tuple_fixed {
std::uint64_t key;
std::uint64_t flag;
};
// scan only flags
struct key_flag_scan_op {
__host__ __device__ key_flag_tuple_fixed
operator()(const key_flag_tuple_fixed& a, const key_flag_tuple_fixed& b) {
return {b.key, a.flag + b.flag};
}
};
struct construct_key_flag_op {
std::uint64_t m;
feistel_bijection_fixed bijection;
__host__ __device__ construct_key_flag_op(std::uint64_t m,
feistel_bijection_fixed bijection)
: m(m), bijection(bijection) {}
__host__ __device__ key_flag_tuple_fixed operator()(std::uint64_t idx) {
auto gather_key = bijection(idx);
return key_flag_tuple_fixed{gather_key, (gather_key < m) ? 1ull : 0ull};
}
};
template <typename InputIterT, typename OutputIterT>
struct write_output_op_fixed {
std::uint64_t m;
InputIterT in;
OutputIterT out;
// flag contains inclusive scan of valid keys
// perform gather using valid keys
__thrust_exec_check_disable__ __host__ __device__ std::size_t operator()(
key_flag_tuple_fixed x) {
if (x.key < m) {
// -1 because inclusive scan
out[x.flag - 1] = in[x.key];
}
return 0; // Discarded
}
};
template <typename ExecutionPolicy,
typename RandomIterator,
typename OutputIterator,
typename URBG>
__host__ __device__ void shuffle_copy_fixed(
const thrust::execution_policy<ExecutionPolicy>& exec,
RandomIterator first,
RandomIterator last,
OutputIterator result,
URBG&& g) {
// m is the length of the input
// we have an available bijection of length n via a feistel cipher
std::size_t m = last - first;
feistel_bijection_fixed bijection(m, g);
std::uint64_t n = bijection.nearest_power_of_two_fixed();
// perform stream compaction over length n bijection to get length m
// pseudorandom bijection over the original input
thrust::counting_iterator<std::uint64_t> indices(0);
thrust::transform_iterator<construct_key_flag_op,
decltype(indices),
key_flag_tuple_fixed>
key_flag_it(indices, construct_key_flag_op(m, bijection));
write_output_op_fixed<RandomIterator, decltype(result)> write_functor{
m, first, result};
auto gather_output_it = thrust::make_transform_output_iterator(
thrust::discard_iterator<std::size_t>(), write_functor);
// the feistel_bijection_fixed outputs a stream of permuted indices in range
// [0,n) flag each value < m and compact it, so we have a set of permuted
// indices in range [0,m) each thread gathers an input element according to
// its pseudorandom permuted index
thrust::inclusive_scan(
exec, key_flag_it, key_flag_it + n, gather_output_it, key_flag_scan_op());
}
} // namespace funcs
} // namespace phi