[Dataset] Add iteration queue tests
authorJihoon Lee <jhoon.it.lee@samsung.com>
Thu, 12 Aug 2021 11:58:36 +0000 (20:58 +0900)
committerJijoong Moon <jijoong.moon@samsung.com>
Thu, 26 Aug 2021 09:35:25 +0000 (18:35 +0900)
**Changes proposed in this PR:**
- Add iteration queue tests within sync (async will be followed)
- Add `slots` and `batch` query method to batch_queue
- Fix some trivial issues like uninitialized variables

**Self evaluation:**
1. Build test: [X]Passed [ ]Failed [ ]Skipped
2. Run test: [X]Passed [ ]Failed [ ]Skipped

Signed-off-by: Jihoon Lee <jhoon.it.lee@samsung.com>
nntrainer/dataset/batch_queue.cpp
nntrainer/dataset/batch_queue.h
nntrainer/dataset/data_iteration.h
test/unittest/datasets/unittest_batch_queue.cpp

index 8fe4bf7e06e5ac9ce3f838a839476c1681c343dc..7a64e13ef11dc6eb5515ddf1a0eae6ee1e008b42 100644 (file)
@@ -70,7 +70,11 @@ bool BatchQueue::isEmpty() const {
 
 IterationQueue::IterationQueue(
   unsigned int num_slots, const std::vector<ml::train::TensorDim> &input_dims,
-  const std::vector<ml::train::TensorDim> &label_dims) {
+  const std::vector<ml::train::TensorDim> &label_dims) :
+  being_filled(nullptr) {
+  NNTR_THROW_IF(num_slots == 0, std::invalid_argument)
+    << "number of slots must be more then zero";
+
   iterations.reserve(num_slots);
   for (decltype(num_slots) i = 0; i < num_slots; ++i) {
     iterations.emplace_back(input_dims, label_dims, this);
@@ -80,6 +84,10 @@ IterationQueue::IterationQueue(
 
 ScopedView<Sample> IterationQueue::requestEmpty() {
   if (being_filled == nullptr) {
+    if (empty_q.empty()) {
+      throw std::invalid_argument(
+        "empty_q empty"); /// this is temporary measure
+    }
     being_filled = empty_q.front();
     empty_q.pop();
     current_iterator = being_filled->get().begin();
@@ -88,7 +96,9 @@ ScopedView<Sample> IterationQueue::requestEmpty() {
   }
 
   auto view = ScopedView<Sample>(&(*current_iterator),
-                                 [this] { being_filled->markSampleFilled(); });
+                                 [current_being_filed = this->being_filled] {
+                                   current_being_filed->markSampleFilled();
+                                 });
 
   if (current_iterator + 1 == being_filled->get().end()) {
     being_filled = nullptr;
@@ -98,6 +108,10 @@ ScopedView<Sample> IterationQueue::requestEmpty() {
 }
 
 ScopedView<Iteration> IterationQueue::requestFilled() {
+  if (filled_q.empty()) {
+    throw std::invalid_argument("filled_q empty"); /// this is temporary measure
+  }
+
   auto iteration = filled_q.front();
   filled_q.pop();
   return ScopedView<Iteration>(&iteration->get(),
index 0a48c94f622296a457391977c4e9a4f7804bcabd..0bd476762cd5e8392f37e597b95bd79e89325eff 100644 (file)
@@ -204,6 +204,20 @@ public:
    */
   ScopedView<Iteration> requestFilled();
 
+  /**
+   * @brief get slot size, slot size is number of batches inside the queue
+   *
+   * @return unsigned int num slot
+   */
+  unsigned int slots() { return iterations.size(); }
+
+  /**
+   * @brief get size of batch for one iteration
+   *
+   * @return unsigned int size of batch
+   */
+  unsigned int batch() { return iterations.front().get().batch(); }
+
 private:
   /**
    * @brief A wrapper object around @c Iteration which marks filled when filling
index 5458673323aff28ff2ed043a4b47dd7b66db7c96..47e08393dd88c0925dc288437b7628540f414d98 100644 (file)
@@ -132,6 +132,11 @@ public:
    */
   Sample(const Iteration &iter, unsigned int batch);
 
+  Sample(const Sample &rhs) = delete;
+  Sample &operator=(const Sample &rhs) = delete;
+  Sample(Sample &&rhs) = default;
+  Sample &operator=(Sample &&rhs) = default;
+
   /**
    * @brief Get the Input Reference object
    *
index f0b2882d2984326514268bd1d8c5256d8968df6e..0c76d7092cdd6dd005e6d10d73c7f134d7dc1a75 100644 (file)
 #include <gtest/gtest.h>
 
 #include <batch_queue.h>
+#include <random_data_producers.h>
 #include <tensor.h>
 
+#include <algorithm>
 #include <future>
 #include <thread>
 #include <tuple>
@@ -110,3 +112,208 @@ TEST(BatchQueue, threadedPushPops_p) {
     }
   }
 }
+
+using IterQueueTestParamType =
+  std::tuple<unsigned int /**< queue size */,
+             std::vector<nntrainer::TensorDim> /**< input dimensions */,
+             std::vector<nntrainer::TensorDim> /**< label dimensions */>;
+
+/**
+ * @brief Iteration Queue Test
+ */
+class IterQueueScenarios
+  : public ::testing::TestWithParam<IterQueueTestParamType> {
+public:
+  /**
+   * @brief SetUp test cases here
+   *
+   */
+  virtual void SetUp() {
+    auto &[q_size, input_dims, label_dims] = GetParam();
+    iq = std::make_unique<nntrainer::IterationQueue>(q_size, input_dims,
+                                                     label_dims);
+    auto producer = std::make_unique<nntrainer::RandomDataOneHotProducer>();
+    producer->setProperty({"num_samples=10000"});
+    sample_getter = producer->finalize_sample(input_dims, label_dims);
+    sum_from_producer = 0;
+    sum_from_consumer = 0;
+  }
+
+  virtual void produceSample(unsigned int size) {
+    auto sample_view = iq->requestEmpty();
+    auto &sample = sample_view.get();
+    auto &inputs = sample.getInputsRef();
+    auto &labels = sample.getLabelsRef();
+    sample_getter(size, inputs, labels);
+    sum_from_producer += getSum(inputs, labels);
+  }
+
+  virtual void consumeIteration() {
+    auto iter_view = iq->requestFilled();
+    auto &iter = iter_view.get();
+    auto &inputs = iter.getInputsRef();
+    auto &labels = iter.getLabelsRef();
+    sum_from_consumer += getSum(inputs, labels);
+  }
+
+  /**
+   * @brief do here if any memory needs to be released
+   *
+   */
+  virtual void TearDown(){};
+
+protected:
+  /**
+   * @brief Get a single value (sum) for the given inputs and outputs, this is
+   * to effectively reduce a tensor to a single value
+   *
+   * @param inputs inputs
+   * @param labels labels
+   * @return long double single value which sums everything
+   */
+  long double getSum(const std::vector<nntrainer::Tensor> &inputs,
+                     const std::vector<nntrainer::Tensor> &labels) {
+    auto accumulator = [](long double old_sum, const nntrainer::Tensor &t) {
+      return old_sum + (long double)t.sum({0, 1, 2, 3}).getValue(0, 0, 0, 0);
+    };
+
+    long double sum =
+      std::accumulate(inputs.begin(), inputs.end(), 0.0l, accumulator);
+    return std::accumulate(labels.begin(), labels.end(), sum, accumulator);
+  }
+
+  long double sum_from_producer;
+  long double sum_from_consumer;
+  nntrainer::DataProducer::Generator_sample sample_getter;
+  std::unique_ptr<nntrainer::IterationQueue> iq;
+  std::vector<nntrainer::TensorDim> input_dims; /**< input dims */
+  std::vector<nntrainer::TensorDim> label_dims; /**< output dims */
+};
+
+TEST_P(IterQueueScenarios, produceAndConsumeSingle_p) {
+  auto batch_size = iq->batch();
+
+  for (unsigned int i = 0; i < batch_size; ++i) {
+    produceSample(i);
+  }
+  consumeIteration();
+
+  EXPECT_FLOAT_EQ(sum_from_producer, sum_from_consumer);
+}
+
+TEST_P(IterQueueScenarios, produceAndConsumeOnce_p) {
+  auto q_size = iq->slots();
+  auto q_size_in_sample = q_size * iq->batch();
+  /// step1: fill buffer to the queue (filling 0 ~ 11th samples)
+  for (unsigned int i = 0; i < q_size_in_sample; ++i) {
+    produceSample(i);
+  }
+
+  /// step2: consume the filled buffer from the queue
+  for (unsigned int i = 0; i < q_size; ++i) {
+    consumeIteration();
+  }
+
+  EXPECT_FLOAT_EQ(sum_from_producer, sum_from_consumer);
+}
+
+TEST_P(IterQueueScenarios, produceAndConsumeSyncTwice_p) {
+  auto q_size = iq->slots();
+  auto q_size_in_sample = q_size * iq->batch();
+  /// step1: fill buffer to the queue (filling full queue)
+  for (unsigned int i = 0; i < q_size_in_sample; ++i) {
+    produceSample(i);
+  }
+
+  /// step2: consume the filled buffer from the queue
+  for (unsigned int i = 0; i < q_size; ++i) {
+    consumeIteration();
+  }
+
+  /// step3: fill buffer to the queue (filling full queue)
+  for (unsigned int i = q_size_in_sample; i < q_size_in_sample * 2; ++i) {
+    produceSample(i);
+  }
+
+  /// step4: consume the filled buffer from the queue
+  for (unsigned int i = 0; i < q_size; ++i) {
+    consumeIteration();
+  }
+
+  EXPECT_FLOAT_EQ(sum_from_producer, sum_from_consumer);
+}
+
+TEST_P(IterQueueScenarios, produceAndConsumeSyncMixed_p) {
+  auto q_size = iq->slots();
+  auto q_size_in_sample = q_size * iq->batch();
+  /// step1: fill buffer to the queue (filling half samples)
+  for (unsigned int i = 0; i < q_size_in_sample / 2; ++i) {
+    produceSample(i);
+  }
+
+  /// step2: consume the filled buffer from the queue
+  for (unsigned int i = 0; i < q_size / 2; ++i) {
+    consumeIteration();
+  }
+
+  /// step3: fill buffer to the queue (filling rest half samples)
+  for (unsigned int i = q_size_in_sample / 2; i < q_size_in_sample; ++i) {
+    produceSample(i);
+  }
+
+  /// step4: consume the filled buffer from the queue
+  for (unsigned int i = q_size / 2; i < q_size; ++i) {
+    consumeIteration();
+  }
+
+  EXPECT_FLOAT_EQ(sum_from_producer, sum_from_consumer);
+}
+
+IterQueueTestParamType multi_slot_multi_batch = {
+  4 /** queue size */,
+  {{3, 2, 4, 5}, {3, 4, 5, 7}} /** input_dims*/,
+  {{3, 1, 1, 8}, {3, 1, 1, 2}} /** label_dims */};
+
+IterQueueTestParamType single_slot_multi_batch = {
+  1 /** queue size */,
+  {{3, 2, 4, 5}, {3, 4, 5, 7}} /** input_dims*/,
+  {{3, 1, 1, 8}, {3, 1, 1, 2}} /** label_dims */};
+
+IterQueueTestParamType multi_slot_single_batch = {
+  3 /** queue size */,
+  {{1, 2, 4, 5}, {1, 4, 5, 7}} /** input_dims*/,
+  {{1, 1, 1, 8}, {1, 1, 1, 2}} /** label_dims */};
+
+IterQueueTestParamType single_slot_single_batch = {
+  1 /** queue size */,
+  {{1, 2, 4, 5}, {1, 4, 5, 7}} /** input_dims*/,
+  {{1, 1, 1, 8}, {1, 1, 1, 2}} /** label_dims */};
+
+INSTANTIATE_TEST_CASE_P(IterQueue, IterQueueScenarios,
+                        ::testing::Values(multi_slot_multi_batch,
+                                          single_slot_multi_batch,
+                                          multi_slot_single_batch,
+                                          single_slot_single_batch));
+
+TEST(IterQueue, constructEmptySlots_n) {
+  EXPECT_ANY_THROW(nntrainer::IterationQueue(0, {{1}}, {{1}}));
+}
+
+TEST(IterQueue, constructEmptyInput_n) {
+  EXPECT_ANY_THROW(nntrainer::IterationQueue(1, {}, {{1}}));
+}
+
+TEST(IterQueue, constructNotConsistentBatchSizeBetweenInputs_n) {
+  EXPECT_ANY_THROW(
+    nntrainer::IterationQueue(1, {{3, 1, 1, 10}, {2, 1, 1, 10}}, {}));
+}
+
+TEST(IterQueue, constructNotConsistentBatchSizeInLabel_n) {
+  EXPECT_ANY_THROW(nntrainer::IterationQueue(1, {{3, 1, 1, 10}, {3, 1, 1, 10}},
+                                             {{2, 1, 1, 10}}));
+}
+
+TEST(IterQueue, constructNotConsistentBatchSizeInLabel2_n) {
+  EXPECT_ANY_THROW(nntrainer::IterationQueue(1, {{3, 1, 1, 10}, {3, 1, 1, 10}},
+                                             {{3, 1, 1, 10}, {2, 1, 1, 10}}));
+}