1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 // Implement the Philox algorithm to generate random numbers in parallel. 17 // Salmon et al. SC 2011. Parallel random numbers: as easy as 1, 2, 3. 18 // http://www.thesalmons.org/john/random123/papers/random123sc11.pdf 19 20 #ifndef TENSORFLOW_CORE_LIB_RANDOM_PHILOX_RANDOM_H_ 21 #define TENSORFLOW_CORE_LIB_RANDOM_PHILOX_RANDOM_H_ 22 23 #include <stdlib.h> 24 25 // Function qualifiers that need to work on both CPU and GPU. 26 #if defined(__CUDACC__) 27 // For nvcc. 28 #define PHILOX_DEVICE_FUNC __host__ __device__ 29 #define PHILOX_INLINE __inline__ 30 #else 31 // For non-nvcc. 32 #define PHILOX_DEVICE_FUNC 33 #define PHILOX_INLINE inline 34 #endif 35 #define PHILOX_DEVICE_INLINE PHILOX_DEVICE_FUNC PHILOX_INLINE 36 37 #include <math.h> 38 39 namespace tensorflow { 40 namespace random { 41 42 typedef int16_t int16; 43 typedef uint16_t uint16; 44 typedef int32_t int32; 45 typedef uint32_t uint32; 46 typedef int64_t int64; 47 typedef uint64_t uint64; 48 49 // A class that represents an inline array. It can be used on both CPU and GPU, 50 // and also trivially copyable between CPU and GPU. 51 // Arguments: 52 // T: the array element type; 53 // ElementCount: the fixed size of the array; 54 template <typename T, int ElementCount> 55 class Array { 56 public: Array()57 PHILOX_DEVICE_INLINE Array() { 58 for (int i = 0; i < ElementCount; ++i) { 59 data_[i] = T(0); 60 } 61 } 62 63 PHILOX_DEVICE_INLINE const T& operator[](int index) const { return data_[index]; } 64 65 PHILOX_DEVICE_INLINE T& operator[](int index) { return data_[index]; } 66 size()67 size_t size() const { return ElementCount; } 68 69 private: 70 T data_[ElementCount]; 71 }; 72 73 // A class that encapsulates all the states for a random number generator using 74 // the philox_4x32_10 algorithm. Each invocation returns a 128-bit random bits 75 // in the form of four uint32. 76 // There are multiple variants of this algorithm, we picked the 4x32_10 version 77 // that is most suited for our applications. 78 // Since this class is meant to be copied between CPU to GPU, it maintains a 79 // value semantics. 80 // 81 // For example: To use this class and populate an array of 1024 randoms on CPU 82 // with two threads, 83 // 84 // void Fill(PhiloxRandom rnd, uint32* output, int start, int limit) { 85 // assert(start % 4 == 0); 86 // assert(limit % 4 == 0); 87 // rnd.Skip(start / 4); 88 // for (int i = start; i < limit; i += 4) { 89 // auto sample = rnd(); 90 // ... copy sample[0..3] to output[i..i+3] 91 // } 92 // } 93 // 94 // PhiloxRandom rng(seed); 95 // PhiloxRandom rng_copy = rng; 96 // rng.Skip(1000/4); 97 // 98 // ... schedule Fill(rng_copy, output, 0, 512) in thread 1; 99 // ... schedule Fill(rng_copy, output, 512, 1024) in thread 2; 100 // ... wait for thread 1 & 2 to finish executing Fill(). 101 // 102 // NOTE: 103 // 1. PhiloxRandom is trivially copyable. 104 // 2. PhiloxRandom is compilable by gcc and nvcc. 105 class PhiloxRandom { 106 public: 107 using ResultType = Array<uint32, 4>; 108 using ResultElementType = uint32; 109 // The number of elements that will be returned. 110 static const int kResultElementCount = 4; 111 // Cost of generation of a single element (in cycles). 112 static const int kElementCost = 10; 113 // The type for the 64-bit key stored in the form of two 32-bit uint 114 // that are used in the diffusion process. 115 using Key = Array<uint32, 2>; 116 117 PHILOX_DEVICE_INLINE PhiloxRandom()118 PhiloxRandom() {} 119 120 PHILOX_DEVICE_INLINE PhiloxRandom(uint64 seed)121 explicit PhiloxRandom(uint64 seed) { 122 key_[0] = static_cast<uint32>(seed); 123 key_[1] = static_cast<uint32>(seed >> 32); 124 } 125 126 PHILOX_DEVICE_INLINE PhiloxRandom(uint64 seed_lo,uint64 seed_hi)127 explicit PhiloxRandom(uint64 seed_lo, uint64 seed_hi) { 128 key_[0] = static_cast<uint32>(seed_lo); 129 key_[1] = static_cast<uint32>(seed_lo >> 32); 130 counter_[2] = static_cast<uint32>(seed_hi); 131 counter_[3] = static_cast<uint32>(seed_hi >> 32); 132 } 133 134 PHILOX_DEVICE_INLINE PhiloxRandom(ResultType counter,Key key)135 PhiloxRandom(ResultType counter, Key key) : counter_(counter), key_(key) {} 136 137 // Skip the specified number of samples of 128-bits in the current stream. 138 PHILOX_DEVICE_INLINE Skip(uint64 count)139 void Skip(uint64 count) { 140 const uint32 count_lo = static_cast<uint32>(count); 141 uint32 count_hi = static_cast<uint32>(count >> 32); 142 143 counter_[0] += count_lo; 144 if (counter_[0] < count_lo) { 145 ++count_hi; 146 } 147 148 counter_[1] += count_hi; 149 if (counter_[1] < count_hi) { 150 if (++counter_[2] == 0) { 151 ++counter_[3]; 152 } 153 } 154 } 155 156 // Returns a group of four random numbers using the underlying Philox 157 // algorithm. operator()158 PHILOX_DEVICE_INLINE ResultType operator()() { 159 ResultType counter = counter_; 160 Key key = key_; 161 162 // Run the single rounds for ten times. Manually unrolling the loop 163 // for better performance. 164 counter = ComputeSingleRound(counter, key); 165 RaiseKey(&key); 166 counter = ComputeSingleRound(counter, key); 167 RaiseKey(&key); 168 counter = ComputeSingleRound(counter, key); 169 RaiseKey(&key); 170 counter = ComputeSingleRound(counter, key); 171 RaiseKey(&key); 172 counter = ComputeSingleRound(counter, key); 173 RaiseKey(&key); 174 counter = ComputeSingleRound(counter, key); 175 RaiseKey(&key); 176 counter = ComputeSingleRound(counter, key); 177 RaiseKey(&key); 178 counter = ComputeSingleRound(counter, key); 179 RaiseKey(&key); 180 counter = ComputeSingleRound(counter, key); 181 RaiseKey(&key); 182 counter = ComputeSingleRound(counter, key); 183 184 SkipOne(); 185 186 return counter; 187 } 188 189 private: 190 // We use the same constants as recommended by the original paper. 191 static const uint32 kPhiloxW32A = 0x9E3779B9; 192 static const uint32 kPhiloxW32B = 0xBB67AE85; 193 static const uint32 kPhiloxM4x32A = 0xD2511F53; 194 static const uint32 kPhiloxM4x32B = 0xCD9E8D57; 195 196 // Helper function to skip the next sample of 128-bits in the current stream. SkipOne()197 PHILOX_DEVICE_INLINE void SkipOne() { 198 if (++counter_[0] == 0) { 199 if (++counter_[1] == 0) { 200 if (++counter_[2] == 0) { 201 ++counter_[3]; 202 } 203 } 204 } 205 } 206 207 // Helper function to return the lower and higher 32-bits from two 32-bit 208 // integer multiplications. 209 PHILOX_DEVICE_INLINE MultiplyHighLow(uint32 a,uint32 b,uint32 * result_low,uint32 * result_high)210 static void MultiplyHighLow(uint32 a, uint32 b, uint32* result_low, uint32* result_high) { 211 #ifndef __CUDA_ARCH__ 212 const uint64 product = static_cast<uint64>(a) * b; 213 *result_low = static_cast<uint32>(product); 214 *result_high = static_cast<uint32>(product >> 32); 215 #else 216 *result_low = a * b; 217 *result_high = __umulhi(a, b); 218 #endif 219 } 220 221 // Helper function for a single round of the underlying Philox algorithm. ComputeSingleRound(const ResultType & counter,const Key & key)222 PHILOX_DEVICE_INLINE static ResultType ComputeSingleRound(const ResultType& counter, 223 const Key& key) { 224 uint32 lo0; 225 uint32 hi0; 226 MultiplyHighLow(kPhiloxM4x32A, counter[0], &lo0, &hi0); 227 228 uint32 lo1; 229 uint32 hi1; 230 MultiplyHighLow(kPhiloxM4x32B, counter[2], &lo1, &hi1); 231 232 ResultType result; 233 result[0] = hi1 ^ counter[1] ^ key[0]; 234 result[1] = lo1; 235 result[2] = hi0 ^ counter[3] ^ key[1]; 236 result[3] = lo0; 237 return result; 238 } 239 RaiseKey(Key * key)240 PHILOX_DEVICE_INLINE void RaiseKey(Key* key) { 241 (*key)[0] += kPhiloxW32A; 242 (*key)[1] += kPhiloxW32B; 243 } 244 245 private: 246 ResultType counter_; 247 Key key_; 248 }; 249 250 } // namespace random 251 } // namespace tensorflow 252 253 #endif // TENSORFLOW_CORE_LIB_RANDOM_PHILOX_RANDOM_H_ 254