Introduce Execution Time structure (#5162)
authorАндрей Шедько/AI Tools Lab /SRR/Engineer/삼성전자 <a.shedko@samsung.com>
Tue, 21 May 2019 09:36:13 +0000 (12:36 +0300)
committer오형석/On-Device Lab(SR)/Staff Engineer/삼성전자 <hseok82.oh@samsung.com>
Tue, 21 May 2019 09:36:13 +0000 (18:36 +0900)
This adds a structure to store/update/persist
runtime information for different ops

Signed-off-by: Andrei Shedko <a.shedko@samsung.com>
runtimes/neurun/core/include/backend/ExecTime.h [new file with mode: 0644]
runtimes/neurun/core/src/backend/ExecTime.cc [new file with mode: 0644]
runtimes/neurun/test/core/backend/ExecTime.test.cc [new file with mode: 0644]

diff --git a/runtimes/neurun/core/include/backend/ExecTime.h b/runtimes/neurun/core/include/backend/ExecTime.h
new file mode 100644 (file)
index 0000000..90c641b
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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.
+ */
+
+#ifndef __NEURUN_BACKEND_EXEC_TIME_H__
+#define __NEURUN_BACKEND_EXEC_TIME_H__
+
+#include "backend/Backend.h"
+#include "backend/IConfig.h"
+#include "JSONExecTime.h"
+#include <memory>
+#include <limits>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+namespace neurun
+{
+namespace backend
+{
+class ExecTime
+{
+public:
+  explicit ExecTime(const std::vector<Backend *> &backends) : _json(backends, _measurements) {}
+
+public:
+  /**
+   * @brief Get exec time of an operation with input size
+   *        or linearly interpolated value based on size if there is no record for given size
+   *
+   * @param[in] backend id of a backend
+   * @param[in] operation name of an operation
+   * @param[in] quant if input type quantized
+   * @param[in] op_size sum of operation's flattened sizes of inputs and outputs
+   * @return execution time for given input sizes
+   *         -1 if there are no records for given parameters (backend, op, quantization).
+   */
+  int64_t getOperationExecTime(Backend *backend, const std::string &operation, bool quant,
+                               uint32_t op_size) const;
+  /**
+   * @brief Update exec time of the operation on a backend with given input size or
+   *        add new entity if there is no one.
+   *
+   * @param[in] backend id of a backend
+   * @param[in] operation name of an operation
+   * @param[in] quant if input type quantized
+   * @param[in] op_size sum of operation's flattened sizes of inputs and outputs
+   * @param[in] time real measured value
+   */
+  void updateOperationExecTime(Backend *backend, const std::string &operation, bool quant,
+                               uint32_t op_size, int64_t time);
+  /**
+   * @brief Get the permute time from one backend to another
+   *
+   * @param[in] from_backend
+   * @param[in] to_backend
+   * @param[in] quant if input type quantized
+   * @param[in] op_size sum of operation's flattened sizes of inputs and outputs
+   * @return permutation time for operation size
+   */
+  int64_t getPermuteTime(Backend *from_backend, Backend *to_backend, bool quant,
+                         uint32_t op_size) const;
+  /**
+   * @brief Update permute time from one backend to another
+   *
+   * @param[in] from_backend
+   * @param[in] to_backend
+   * @param[in] quant if input type quantized
+   * @param[in] time measured permutation time
+   * @param[in] op_size sum of operation's flattened sizes of inputs and outputs
+   */
+  void updatePermuteTime(Backend *from_backend, Backend *to_backend, bool quant, uint32_t op_size,
+                         int64_t time);
+  /**
+   * @brief Get the max value of int32_t in int64_t
+   * @return max value
+   */
+  static int64_t getMax() { return _MAX; }
+
+private:
+  /// @brief Measurement data, which is shared with serializer
+  MeasurementData _measurements;
+  // int64_t::max may cause integer overflow
+  static const int64_t _MAX = std::numeric_limits<int32_t>::max();
+  /// @brief Serializer
+  JSON _json;
+};
+
+} // namespace backend
+} // namespace neurun
+
+#endif // __NEURUN_BACKEND_EXEC_TIME_H__
diff --git a/runtimes/neurun/core/src/backend/ExecTime.cc b/runtimes/neurun/core/src/backend/ExecTime.cc
new file mode 100644 (file)
index 0000000..fdb5edf
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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 "backend/ExecTime.h"
+
+#include <fstream>
+#include <cassert>
+#include <limits>
+
+namespace neurun
+{
+namespace backend
+{
+
+int64_t ExecTime::getOperationExecTime(Backend *backend, const std::string &operation, bool quant,
+                                       uint32_t op_size) const
+{
+  auto found_backend = _measurements.find(backend);
+  if (found_backend == _measurements.end())
+    return -1; // no execution time for this backend
+
+  auto found_operation_with_type = found_backend->second.find(operation);
+  if (found_operation_with_type == found_backend->second.end())
+    // no execution time for this operation
+    return -1;
+
+  auto found_operation = found_operation_with_type->second.find(quant);
+  if (found_operation == found_operation_with_type->second.end())
+    // no execution time for this operation
+    return -1;
+
+  auto found_size = found_operation->second.find(op_size);
+  if (found_size != found_operation->second.end())
+    return found_size->second; // found execution time
+
+  // Try to interpolate
+  if (found_operation->second.size() < 2)
+    // not possible to do linear interpolation
+    return found_operation->second.begin()->second;
+
+  // if we reach here, then this means, that there is no record, that is equal to op_size
+  auto upper_bound = found_operation->second.upper_bound(op_size); // > op_size
+  auto lower_bound = upper_bound;
+
+  if (upper_bound == found_operation->second.end()) // all values <= op_size
+  {
+    upper_bound--;
+    lower_bound = upper_bound;
+    lower_bound--;
+  }
+  else if (upper_bound == found_operation->second.begin()) // all values > op_size
+  {
+    upper_bound++;
+  }
+  else // op_size between
+  {
+    lower_bound--;
+  }
+
+  // Linear interpolation
+  const auto x0 = static_cast<int64_t>(lower_bound->first); // size
+  const auto x1 = static_cast<int64_t>(upper_bound->first); // size
+  const int64_t y0 = lower_bound->second;                   // time
+  const int64_t y1 = upper_bound->second;                   // time
+  const auto x = static_cast<int64_t>(op_size);
+
+  int64_t interpolated_value = y0 + (x - x0) * (y1 - y0) / (x1 - x0);
+
+  // execution time must be non-negative
+  return std::max<int64_t>(interpolated_value, 1);
+}
+
+void ExecTime::updateOperationExecTime(Backend *backend, const std::string &operation, bool quant,
+                                       uint32_t op_size, int64_t time)
+{
+  _measurements[backend][operation][quant][op_size] = time;
+}
+
+void ExecTime::updatePermuteTime(Backend *from_backend, Backend *to_backend, bool quant,
+                                 uint32_t op_size, int64_t time)
+{
+  updateOperationExecTime(from_backend, to_backend->config()->id(), quant, op_size, time);
+}
+
+int64_t ExecTime::getPermuteTime(Backend *from_backend, Backend *to_backend, bool quant,
+                                 uint32_t op_size) const
+{
+  return getOperationExecTime(from_backend, to_backend->config()->id(), quant, op_size);
+}
+
+} // namespace backend
+} // namespace neurun
diff --git a/runtimes/neurun/test/core/backend/ExecTime.test.cc b/runtimes/neurun/test/core/backend/ExecTime.test.cc
new file mode 100644 (file)
index 0000000..5b67c42
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd. 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 "backend/ExecTime.h"
+#include "backend/IConfig.h"
+#include "backend/IStageGenerator.h"
+#include "backend/Backend.h"
+#include <gtest/gtest.h>
+#include <fstream>
+#include <string>
+
+namespace
+{
+using namespace neurun;
+using namespace backend;
+
+struct MockConfig : public IConfig
+{
+  std::string id() override { return "b1"; }
+  void initialize() override{};
+  bool SupportSubTensorAlloc() override { return false; }
+};
+
+struct MockStageGenerator : IStageGenerator
+{
+  std::shared_ptr<ITensorBuilder> tensor_builder() final { return nullptr; }
+};
+
+struct MockBackend : public ::neurun::backend::Backend
+{
+  /** @brief Stub backend for testing.
+   *    Required because we use pointers to backends instead of string identifiers.
+   */
+  MockBackend()
+      : Backend{std::make_shared<MockConfig>(), std::make_shared<MockStageGenerator>()} {};
+};
+
+TEST(ExecTime, roundtrip_ok)
+{
+  auto b = new MockBackend();
+  std::vector<Backend *> bs = {b};
+  {
+    ExecTime et(bs);
+    et.updateOperationExecTime(b, "op1", true, 100, 100);
+    et.updateOperationExecTime(b, "op1", true, 200, 200);
+    et.updateOperationExecTime(b, "op1", false, 100, 888);
+  }
+  {
+    ExecTime et(bs);
+    auto time = et.getOperationExecTime(b, "op1", true, 100);
+    ASSERT_EQ(time, 100);
+    // Check interpolation
+    time = et.getOperationExecTime(b, "op1", true, 150);
+    ASSERT_EQ(time, 150);
+    time = et.getOperationExecTime(b, "op1", false, 100);
+    ASSERT_EQ(time, 888);
+  }
+  // clean up
+  remove("execution_time.json");
+}
+
+TEST(ExecTime, structure)
+{
+
+  auto b = new MockBackend();
+  std::vector<Backend *> bs = {b};
+  {
+    ExecTime et(bs);
+    et.updateOperationExecTime(b, "op1", true, 100, 100);
+    et.updateOperationExecTime(b, "op1", true, 200, 200);
+  }
+  {
+    ExecTime et(bs);
+    auto time = et.getOperationExecTime(b, "op1", true, 100);
+    ASSERT_EQ(time, 100);
+    // Check interpolation
+    time = et.getOperationExecTime(b, "op1", true, 200);
+    ASSERT_EQ(time, 200);
+  }
+  // clean up
+  remove("execution_time.json");
+}
+} // unnamed namespace