From e86e2abd5a28671c64f70b5ad1633c289d1e842d Mon Sep 17 00:00:00 2001 From: Xiyou Zhou Date: Tue, 3 Aug 2021 18:42:18 -0700 Subject: [PATCH 1/7] Add linear congruential engine. --- include/tvm/support/random_engine.h | 115 ++++++++++++++++++++++++++++ tests/cpp/random_engine_test.cc | 71 +++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 include/tvm/support/random_engine.h create mode 100644 tests/cpp/random_engine_test.cc diff --git a/include/tvm/support/random_engine.h b/include/tvm/support/random_engine.h new file mode 100644 index 000000000000..dfbeab406f81 --- /dev/null +++ b/include/tvm/support/random_engine.h @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/*! + * \file random_engine.h + * \brief Random number generator, for Sampler and Sampling functions. + */ + +#ifndef TVM_SUPPORT_RANDOM_ENGINE_H_ +#define TVM_SUPPORT_RANDOM_ENGINE_H_ + +#include + +#include // for int64_t + +namespace tvm { +namespace support { + +/*! + * \brief This linear congruential engine is a drop-in replacement for and stricly corresponds to + * std::minstd_rand but designed to be serializable and strictly reproducible. Specifically + * implemented for meta schedule but also reusable for other purposes. + * \note Part of std::linear_congruential_engine's member functions are not included, for full + * member functions of std::minstd_rand, please check out the following link: + * https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine + */ +class LinearCongruentialEngine { + public: + /*! + * \brief The result type is defined as int64_t here for meta_schedule sampler usage. + * \note The type name is not in Google style because it is used in STL's distribution inferface. + */ + using result_type = int64_t; + + /*! \brief The multiplier */ + static constexpr result_type multiplier = 48271; + + /*! \brief The increment */ + static constexpr result_type increment = 0; + + /*! \brief The modulus */ + static constexpr result_type modulus = 2147483647; + + /*! + * \brief The minimum possible value of random state here. + * \note The function name is uncapilized because it is used in STL's distribution inferface. + */ + result_type min() { return 0; } + + /*! + * \brief The maximum possible value of random state here. + * \note The function name is uncapilized because it is used in STL's distribution inferface. + */ + result_type max() { return modulus - 1; } + + /*! + * \brief Operator to move the random state to the next and return the new random state. According + * to definition of linear congruential engine, the new random state value is computed as + * new_random_state = (current_random_state * multiplier + increment) % modulus. + * \return The next current random state value in the type of result_type. + * \note In case of potential overflow, please use Schrage multiplication algorithm to implement. + * We also assume the given rand state is not nullptr here. + */ + result_type operator()() { + // Avoid getting all 0 given the current parameter set. + if (increment == 0 && *rand_state_ptr_ == 0) *rand_state_ptr_ = 1; + (*rand_state_ptr_) = ((*rand_state_ptr_) * multiplier + increment) % modulus; + return *rand_state_ptr_; + } + + /*! + * \brief Change the start random state of RNG with the seed of a new random state value. + * \param rand_state The random state given in result_type. + */ + void Seed(result_type rand_state = 1) { + rand_state %= modulus; // Make sure the seed is within the range of modulus. + if (rand_state < 0) rand_state += modulus; // The congruential engine is always non-negative. + ICHECK(rand_state_ptr_ != nullptr); // Make sure the pointer is not null. + *rand_state_ptr_ = rand_state; // Change pointed random state to given random state value. + }; + + /*! + * \brief Construct a random number generator with a random state pointer. + * \param rand_state_ptr The random state pointer given in result_type*. + * \note The random state is not checked for whether it's nullptr and whether it's in the range of + * [0, modulus-1]. We assume the given random state is valid or the Seed function would be called. + */ + explicit LinearCongruentialEngine(result_type* rand_state_ptr) { + rand_state_ptr_ = rand_state_ptr; + } + + private: + result_type* rand_state_ptr_; +}; + +} // namespace support +} // namespace tvm + +#endif // TVM_SUPPORT_RANDOM_ENGINE_H_ diff --git a/tests/cpp/random_engine_test.cc b/tests/cpp/random_engine_test.cc new file mode 100644 index 000000000000..b962f86e4ba1 --- /dev/null +++ b/tests/cpp/random_engine_test.cc @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 +#include +#include + +TEST(RandomEngine, Randomness) { + int64_t rand_state = 0; + + tvm::support::LinearCongruentialEngine rng(&rand_state); + rng.Seed(0x114514); + + bool covered[100]; + memset(covered, 0, sizeof(covered)); + for (int i = 0; i < 100000; i++) { + covered[rng() % 100] = true; + } + for (int i = 0; i < 100; i++) { + ICHECK(covered[i]); + } +} + +TEST(RandomEngine, Reproducibility) { + int64_t rand_state_a = 0, rand_state_b = 0; + tvm::support::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b); + + rng_a.Seed(0x23456789); + rng_b.Seed(0x23456789); + + for (int i = 0; i < 100000; i++) { + ICHECK(rng_a() == rng_b()); + } +} + +TEST(RandomEngine, Serialization) { + int64_t rand_state_a = 0, rand_state_b = 0; + tvm::support::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b); + + rng_a.Seed(0x56728); + + rand_state_b = rand_state_a; + for (int i = 0; i < 100000; i++) ICHECK(rng_a() == rng_b()); + + for (int i = 0; i < 123456; i++) rng_a(); + + rand_state_b = rand_state_a; + for (int i = 0; i < 100000; i++) ICHECK(rng_a() == rng_b()); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + testing::FLAGS_gtest_death_test_style = "threadsafe"; + return RUN_ALL_TESTS(); +} \ No newline at end of file From 839a886dfa224ef0f0f12af4327d69f0f37e3a1f Mon Sep 17 00:00:00 2001 From: Xiyou Zhou Date: Tue, 3 Aug 2021 18:48:11 -0700 Subject: [PATCH 2/7] Fix typo. --- include/tvm/support/random_engine.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/tvm/support/random_engine.h b/include/tvm/support/random_engine.h index dfbeab406f81..a546cdf47c46 100644 --- a/include/tvm/support/random_engine.h +++ b/include/tvm/support/random_engine.h @@ -33,7 +33,7 @@ namespace tvm { namespace support { /*! - * \brief This linear congruential engine is a drop-in replacement for and stricly corresponds to + * \brief This linear congruential engine is a drop-in replacement for and strictly corresponds to * std::minstd_rand but designed to be serializable and strictly reproducible. Specifically * implemented for meta schedule but also reusable for other purposes. * \note Part of std::linear_congruential_engine's member functions are not included, for full From 6965242976bfd4e9ef114f0e479880414c9d315a Mon Sep 17 00:00:00 2001 From: Xiyou Zhou Date: Tue, 3 Aug 2021 21:43:54 -0700 Subject: [PATCH 3/7] Minor fix. --- include/tvm/support/random_engine.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/include/tvm/support/random_engine.h b/include/tvm/support/random_engine.h index a546cdf47c46..9c973c26f5d1 100644 --- a/include/tvm/support/random_engine.h +++ b/include/tvm/support/random_engine.h @@ -78,8 +78,6 @@ class LinearCongruentialEngine { * We also assume the given rand state is not nullptr here. */ result_type operator()() { - // Avoid getting all 0 given the current parameter set. - if (increment == 0 && *rand_state_ptr_ == 0) *rand_state_ptr_ = 1; (*rand_state_ptr_) = ((*rand_state_ptr_) * multiplier + increment) % modulus; return *rand_state_ptr_; } @@ -90,10 +88,13 @@ class LinearCongruentialEngine { */ void Seed(result_type rand_state = 1) { rand_state %= modulus; // Make sure the seed is within the range of modulus. - if (rand_state < 0) rand_state += modulus; // The congruential engine is always non-negative. - ICHECK(rand_state_ptr_ != nullptr); // Make sure the pointer is not null. - *rand_state_ptr_ = rand_state; // Change pointed random state to given random state value. - }; + if (rand_state == 0) + rand_state = 1; // Avoid getting all 0 given the current parameter set. + else if (rand_state < 0) + rand_state += modulus; // The congruential engine is always non-negative. + ICHECK(rand_state_ptr_ != nullptr); // Make sure the pointer is not null. + *rand_state_ptr_ = rand_state; // Change pointed random state to given random state value. + } /*! * \brief Construct a random number generator with a random state pointer. From 38191beeff8aaf76a2183b8e4f7aeb3efee76986 Mon Sep 17 00:00:00 2001 From: Xiyou Zhou Date: Wed, 4 Aug 2021 15:01:48 -0700 Subject: [PATCH 4/7] Fix comments and intros. --- include/tvm/support/random_engine.h | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/include/tvm/support/random_engine.h b/include/tvm/support/random_engine.h index 9c973c26f5d1..b2812eae16d1 100644 --- a/include/tvm/support/random_engine.h +++ b/include/tvm/support/random_engine.h @@ -33,12 +33,13 @@ namespace tvm { namespace support { /*! - * \brief This linear congruential engine is a drop-in replacement for and strictly corresponds to - * std::minstd_rand but designed to be serializable and strictly reproducible. Specifically - * implemented for meta schedule but also reusable for other purposes. - * \note Part of std::linear_congruential_engine's member functions are not included, for full - * member functions of std::minstd_rand, please check out the following link: - * https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine + * \brief This linear congruential engine is a drop-in replacement for std::minstd_rand. It strictly + * corresponds to std::minstd_rand and is designed to be platform-independent. + * \note Our linear congruential engine is a complete implementation of + * std::uniform_random_bit_generator so it can be used as generator for any STL random number + * distribution. However, parts of std::linear_congruential_engine's member functions are not + * included. For full member functions of std::minstd_rand, please check out the following link: + * https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine */ class LinearCongruentialEngine { public: @@ -71,11 +72,13 @@ class LinearCongruentialEngine { /*! * \brief Operator to move the random state to the next and return the new random state. According - * to definition of linear congruential engine, the new random state value is computed as + * to definition of linear congruential engine, the new random state value is computed as * new_random_state = (current_random_state * multiplier + increment) % modulus. * \return The next current random state value in the type of result_type. - * \note In case of potential overflow, please use Schrage multiplication algorithm to implement. - * We also assume the given rand state is not nullptr here. + * \note In order for better efficiency, the implementation here has a few assumptions: + * 1. The multiplication and addition won't overflow. + * 2. The given random state pointer `rand_state_ptr` is not nullptr. + * 3. The given random state *(rand_state_ptr) is in the range of [1, modulus - 1]. */ result_type operator()() { (*rand_state_ptr_) = ((*rand_state_ptr_) * multiplier + increment) % modulus; @@ -100,7 +103,8 @@ class LinearCongruentialEngine { * \brief Construct a random number generator with a random state pointer. * \param rand_state_ptr The random state pointer given in result_type*. * \note The random state is not checked for whether it's nullptr and whether it's in the range of - * [0, modulus-1]. We assume the given random state is valid or the Seed function would be called. + * [0, modulus-1]. We assume the given random state is valid or the Seed function would be + * called right after the constructor before any usage. */ explicit LinearCongruentialEngine(result_type* rand_state_ptr) { rand_state_ptr_ = rand_state_ptr; From 27fb4a23fcb33cade341465d8daeb691d56d8b93 Mon Sep 17 00:00:00 2001 From: Xiyou Zhou Date: Wed, 4 Aug 2021 15:20:30 -0700 Subject: [PATCH 5/7] Change to unsigned. --- include/tvm/support/random_engine.h | 17 +++++++---------- tests/cpp/random_engine_test.cc | 12 ++++++------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/include/tvm/support/random_engine.h b/include/tvm/support/random_engine.h index b2812eae16d1..376cbf4fef2b 100644 --- a/include/tvm/support/random_engine.h +++ b/include/tvm/support/random_engine.h @@ -27,7 +27,7 @@ #include -#include // for int64_t +#include // for uint64_t namespace tvm { namespace support { @@ -47,7 +47,7 @@ class LinearCongruentialEngine { * \brief The result type is defined as int64_t here for meta_schedule sampler usage. * \note The type name is not in Google style because it is used in STL's distribution inferface. */ - using result_type = int64_t; + using result_type = uint64_t; /*! \brief The multiplier */ static constexpr result_type multiplier = 48271; @@ -78,7 +78,7 @@ class LinearCongruentialEngine { * \note In order for better efficiency, the implementation here has a few assumptions: * 1. The multiplication and addition won't overflow. * 2. The given random state pointer `rand_state_ptr` is not nullptr. - * 3. The given random state *(rand_state_ptr) is in the range of [1, modulus - 1]. + * 3. The given random state *(rand_state_ptr) is in the range of [0, modulus - 1]. */ result_type operator()() { (*rand_state_ptr_) = ((*rand_state_ptr_) * multiplier + increment) % modulus; @@ -90,13 +90,10 @@ class LinearCongruentialEngine { * \param rand_state The random state given in result_type. */ void Seed(result_type rand_state = 1) { - rand_state %= modulus; // Make sure the seed is within the range of modulus. - if (rand_state == 0) - rand_state = 1; // Avoid getting all 0 given the current parameter set. - else if (rand_state < 0) - rand_state += modulus; // The congruential engine is always non-negative. - ICHECK(rand_state_ptr_ != nullptr); // Make sure the pointer is not null. - *rand_state_ptr_ = rand_state; // Change pointed random state to given random state value. + rand_state %= modulus; // Make sure the seed is within the range of modulus. + if (rand_state == 0) rand_state = 1; // Avoid getting all 0 given the current parameter set. + ICHECK(rand_state_ptr_ != nullptr); // Make sure the pointer is not null. + *rand_state_ptr_ = rand_state; // Change pointed random state to given random state value. } /*! diff --git a/tests/cpp/random_engine_test.cc b/tests/cpp/random_engine_test.cc index b962f86e4ba1..9540bcd38ee3 100644 --- a/tests/cpp/random_engine_test.cc +++ b/tests/cpp/random_engine_test.cc @@ -22,7 +22,7 @@ #include TEST(RandomEngine, Randomness) { - int64_t rand_state = 0; + uint64_t rand_state = 0; tvm::support::LinearCongruentialEngine rng(&rand_state); rng.Seed(0x114514); @@ -38,30 +38,30 @@ TEST(RandomEngine, Randomness) { } TEST(RandomEngine, Reproducibility) { - int64_t rand_state_a = 0, rand_state_b = 0; + uint64_t rand_state_a = 0, rand_state_b = 0; tvm::support::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b); rng_a.Seed(0x23456789); rng_b.Seed(0x23456789); for (int i = 0; i < 100000; i++) { - ICHECK(rng_a() == rng_b()); + ICHECK_EQ(rng_a(), rng_b()); } } TEST(RandomEngine, Serialization) { - int64_t rand_state_a = 0, rand_state_b = 0; + uint64_t rand_state_a = 0, rand_state_b = 0; tvm::support::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b); rng_a.Seed(0x56728); rand_state_b = rand_state_a; - for (int i = 0; i < 100000; i++) ICHECK(rng_a() == rng_b()); + for (int i = 0; i < 100000; i++) ICHECK_EQ(rng_a(), rng_b()); for (int i = 0; i < 123456; i++) rng_a(); rand_state_b = rand_state_a; - for (int i = 0; i < 100000; i++) ICHECK(rng_a() == rng_b()); + for (int i = 0; i < 100000; i++) ICHECK_EQ(rng_a(), rng_b()); } int main(int argc, char** argv) { From 8e3f46db539410e8beb99b10b89d02e6c4ed593b Mon Sep 17 00:00:00 2001 From: Xiyou Zhou Date: Wed, 4 Aug 2021 15:22:52 -0700 Subject: [PATCH 6/7] Minor comment fix. --- include/tvm/support/random_engine.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/tvm/support/random_engine.h b/include/tvm/support/random_engine.h index 376cbf4fef2b..3b7c8db0cd95 100644 --- a/include/tvm/support/random_engine.h +++ b/include/tvm/support/random_engine.h @@ -38,8 +38,8 @@ namespace support { * \note Our linear congruential engine is a complete implementation of * std::uniform_random_bit_generator so it can be used as generator for any STL random number * distribution. However, parts of std::linear_congruential_engine's member functions are not - * included. For full member functions of std::minstd_rand, please check out the following link: - * https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine + * included for simplification. For full member functions of std::minstd_rand, please check out the + * following link: https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine */ class LinearCongruentialEngine { public: From 288ee4b47fe27513bd10a3b435d337e7ad0778ff Mon Sep 17 00:00:00 2001 From: Xiyou Zhou Date: Wed, 4 Aug 2021 16:08:29 -0700 Subject: [PATCH 7/7] Fix unsigned rand state to signed. --- include/tvm/support/random_engine.h | 26 +++++++++++++++----------- tests/cpp/random_engine_test.cc | 6 +++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/include/tvm/support/random_engine.h b/include/tvm/support/random_engine.h index 3b7c8db0cd95..e73c1193f4c3 100644 --- a/include/tvm/support/random_engine.h +++ b/include/tvm/support/random_engine.h @@ -48,15 +48,16 @@ class LinearCongruentialEngine { * \note The type name is not in Google style because it is used in STL's distribution inferface. */ using result_type = uint64_t; + using TRandState = int64_t; /*! \brief The multiplier */ - static constexpr result_type multiplier = 48271; + static constexpr TRandState multiplier = 48271; /*! \brief The increment */ - static constexpr result_type increment = 0; + static constexpr TRandState increment = 0; /*! \brief The modulus */ - static constexpr result_type modulus = 2147483647; + static constexpr TRandState modulus = 2147483647; /*! * \brief The minimum possible value of random state here. @@ -78,7 +79,7 @@ class LinearCongruentialEngine { * \note In order for better efficiency, the implementation here has a few assumptions: * 1. The multiplication and addition won't overflow. * 2. The given random state pointer `rand_state_ptr` is not nullptr. - * 3. The given random state *(rand_state_ptr) is in the range of [0, modulus - 1]. + * 3. The given random state `*(rand_state_ptr)` is in the range of [0, modulus - 1]. */ result_type operator()() { (*rand_state_ptr_) = ((*rand_state_ptr_) * multiplier + increment) % modulus; @@ -89,11 +90,14 @@ class LinearCongruentialEngine { * \brief Change the start random state of RNG with the seed of a new random state value. * \param rand_state The random state given in result_type. */ - void Seed(result_type rand_state = 1) { - rand_state %= modulus; // Make sure the seed is within the range of modulus. - if (rand_state == 0) rand_state = 1; // Avoid getting all 0 given the current parameter set. - ICHECK(rand_state_ptr_ != nullptr); // Make sure the pointer is not null. - *rand_state_ptr_ = rand_state; // Change pointed random state to given random state value. + void Seed(TRandState rand_state = 1) { + rand_state %= modulus; // Make sure the seed is within the range of modulus. + if (rand_state == 0) + rand_state = 1; // Avoid getting all 0 given the current parameter set. + else if (rand_state < 0) + rand_state += modulus; // Make sure the rand state is non-negative. + ICHECK(rand_state_ptr_ != nullptr); // Make sure the pointer is not null. + *rand_state_ptr_ = rand_state; // Change pointed random state to given random state value. } /*! @@ -103,12 +107,12 @@ class LinearCongruentialEngine { * [0, modulus-1]. We assume the given random state is valid or the Seed function would be * called right after the constructor before any usage. */ - explicit LinearCongruentialEngine(result_type* rand_state_ptr) { + explicit LinearCongruentialEngine(TRandState* rand_state_ptr) { rand_state_ptr_ = rand_state_ptr; } private: - result_type* rand_state_ptr_; + TRandState* rand_state_ptr_; }; } // namespace support diff --git a/tests/cpp/random_engine_test.cc b/tests/cpp/random_engine_test.cc index 9540bcd38ee3..6435d5dc3c1c 100644 --- a/tests/cpp/random_engine_test.cc +++ b/tests/cpp/random_engine_test.cc @@ -22,7 +22,7 @@ #include TEST(RandomEngine, Randomness) { - uint64_t rand_state = 0; + int64_t rand_state = 0; tvm::support::LinearCongruentialEngine rng(&rand_state); rng.Seed(0x114514); @@ -38,7 +38,7 @@ TEST(RandomEngine, Randomness) { } TEST(RandomEngine, Reproducibility) { - uint64_t rand_state_a = 0, rand_state_b = 0; + int64_t rand_state_a = 0, rand_state_b = 0; tvm::support::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b); rng_a.Seed(0x23456789); @@ -50,7 +50,7 @@ TEST(RandomEngine, Reproducibility) { } TEST(RandomEngine, Serialization) { - uint64_t rand_state_a = 0, rand_state_b = 0; + int64_t rand_state_a = 0, rand_state_b = 0; tvm::support::LinearCongruentialEngine rng_a(&rand_state_a), rng_b(&rand_state_b); rng_a.Seed(0x56728);