You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mxnet.apache.org by jx...@apache.org on 2017/11/07 05:43:41 UTC

[incubator-mxnet] branch master updated: Engine reserves cores from OMP. Set some defaults for dynamic and recursion (#8553)

This is an automated email from the ASF dual-hosted git repository.

jxie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-mxnet.git


The following commit(s) were added to refs/heads/master by this push:
     new 5c84c30  Engine reserves cores from OMP.  Set some defaults for dynamic and recursion (#8553)
5c84c30 is described below

commit 5c84c30cfffbee1fdb70d6544743b55775ecf1e6
Author: Chris Olivier <cj...@gmail.com>
AuthorDate: Mon Nov 6 21:43:38 2017 -0800

    Engine reserves cores from OMP.  Set some defaults for dynamic and recursion (#8553)
    
    * Engine reserves cores from OMP.  Set some defaults for dynamic and recursion unless environment variables are set.
    
    * Pull some generic unit testing stuff from tuner branch
    Also, something with gperftools got missed in a CMakeLists.txt merge at some point.
    
    * lint
---
 CMakeLists.txt                                |  5 +++
 src/engine/openmp.cc                          | 51 ++++++++++++++++++++-------
 src/engine/openmp.h                           | 19 ++++++++--
 src/engine/threaded_engine_perdevice.cc       | 29 ++++++++++++---
 src/initialize.cc                             |  5 +++
 src/operator/dropout-inl.h                    | 22 ++++++------
 src/operator/mxnet_op.h                       |  1 +
 src/operator/operator_common.h                |  6 +++-
 src/operator/slice_channel-inl.h              |  1 -
 tests/cpp/include/test_util.h                 | 22 ++++++++++--
 tests/cpp/misc/memory_test.cc                 | 29 +++++----------
 tests/cpp/unittest.mk                         |  2 +-
 tests/python/unittest/test_sparse_operator.py | 14 +++++++-
 13 files changed, 150 insertions(+), 56 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 63bc8d7..539515b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -185,10 +185,15 @@ endif()
 
 # ---[ jemalloc
 if(USE_JEMALLOC)
+  if(USE_GPERFTOOLS)
+    message(ERROR "Only one of USE_JEMALLOC and USE_GPERFTOOLS can be defined at once")
+  endif()
   find_package(JeMalloc)
   if(JEMALLOC_FOUND)
     message(STATUS "Using JEMalloc malloc")
     add_definitions(-DUSE_JEMALLOC)
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ALT_MALLOC_FLAGS}")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ALT_MALLOC_FLAGS}")
     include_directories(${JEMALLOC_INCLUDE_DIRS})
     set(mxnet_LINKER_LIBS ${mxnet_LINKER_LIBS} ${JEMALLOC_LIBRARIES})
   endif()
diff --git a/src/engine/openmp.cc b/src/engine/openmp.cc
index a605f97..be7885b 100644
--- a/src/engine/openmp.cc
+++ b/src/engine/openmp.cc
@@ -29,44 +29,67 @@ namespace engine {
 #define ARCH_IS_INTEL_X86
 #endif
 
+static inline bool is_env_set(const char *var) {
+  return dmlc::GetEnv(var, INT_MIN) == INT_MIN;
+}
+
 OpenMP *OpenMP::Get() {
   static OpenMP openMP;
   return &openMP;
 }
 
 OpenMP::OpenMP()
-  : omp_num_threads_set_in_environment(dmlc::GetEnv("OMP_NUM_THREADS", INT_MIN) == INT_MIN) {
+  : omp_num_threads_set_in_environment(is_env_set("OMP_NUM_THREADS")) {
 #ifdef _OPENMP
-  if (!omp_num_threads_set_in_environment) {
-    omp_set_nested(true);
-    omp_set_dynamic(false);
-  }
   const int max = dmlc::GetEnv("MXNET_OMP_MAX_THREADS", INT_MIN);
   if (max != INT_MIN) {
     omp_thread_max_ = max;
   } else {
+    if (!omp_num_threads_set_in_environment) {
+      omp_thread_max_ = omp_get_num_procs();
 #ifdef ARCH_IS_INTEL_X86
-    omp_thread_max_ = omp_get_num_procs() >> 1;
+      omp_thread_max_ >>= 1;
 #endif
+      omp_set_num_threads(omp_thread_max_);
+    } else {
+      omp_thread_max_ = omp_get_max_threads();
+  }
   }
+  omp_set_nested(dmlc::GetEnv("OMP_NESTED", false));
+  omp_set_dynamic(dmlc::GetEnv("OMP_DYNAMIC", false));
 #else
   enabled_ = false;
   omp_thread_max_ = 1;
 #endif
 }
 
-int OpenMP::GetRecommendedOMPThreadCount() const {
+void OpenMP::set_reserve_cores(int cores) {
+  CHECK_GE(cores, 0);
+  reserve_cores_ = cores;
+#ifdef _OPENMP
+  if (reserve_cores_ >= omp_thread_max_) {
+    omp_set_num_threads(1);
+  } else {
+    omp_set_num_threads(omp_thread_max_ - reserve_cores_);
+  }
+#endif
+}
+
+int OpenMP::GetRecommendedOMPThreadCount(bool exclude_reserved) const {
 #ifdef _OPENMP
   if (omp_num_threads_set_in_environment) {
     return omp_get_max_threads();
   }
   if (enabled_) {
-#ifdef ARCH_IS_INTEL_X86
-    // x86 does hyperthreading, but do to cache issues, it's faster to only use # true CPUs
-    const int thread_count = omp_get_max_threads() >> 1;
-#else
-    const int thread_count = omp_get_max_threads();
-#endif
+    int thread_count = omp_get_max_threads();
+    if (exclude_reserved) {
+      if (reserve_cores_ >= thread_count) {
+        thread_count = 1;
+      } else {
+        thread_count -= reserve_cores_;
+      }
+    }
+    // Check that OMP doesn't suggest more than our 'omp_thread_max_' value
     if (!omp_thread_max_ || thread_count < omp_thread_max_) {
       return thread_count;
     }
@@ -78,6 +101,8 @@ int OpenMP::GetRecommendedOMPThreadCount() const {
 #endif
 }
 
+OpenMP *__init_omp__ = OpenMP::Get();
+
 }  // namespace engine
 }  // namespace mxnet
 
diff --git a/src/engine/openmp.h b/src/engine/openmp.h
index 8b995a6..02e73c0 100644
--- a/src/engine/openmp.h
+++ b/src/engine/openmp.h
@@ -36,7 +36,7 @@ class OpenMP {
    * \brief Get the recommended number of OMP threads to use given the current context
    * \return Recommended number of OMP threads to use in a parallel operation
    */
-  int GetRecommendedOMPThreadCount() const;
+  int GetRecommendedOMPThreadCount(bool exclude_reserved = true) const;
 
   /*!
    * \brief Set whether clients of this class receive pro-OMP behavior guidance
@@ -57,8 +57,19 @@ class OpenMP {
   int thread_max() const { return omp_thread_max_; }
 
   /*!
+   * \brief Reserve cores to be excluded from OMP regions
+   * \param cores Number of cores to be excluded from OMP region usage
+   */
+  void set_reserve_cores(int cores);
+  /*!
+   * \brief Get number of cores to be excluded from OMP regions
+   * \return Number of cores to be excluded from OMP regions
+   */
+  int reserve_cores() const { return reserve_cores_; }
+
+  /*!
    * \brief Get the OpenMP object's singleton pointer
-   * \return
+   * \return Singleton OpenMP object pointer
    */
   static OpenMP *Get();
 
@@ -73,6 +84,10 @@ class OpenMP {
    */
   volatile int omp_thread_max_ = 0;
   /*!
+   * \brief Number of cores to reserve for non-OMP regions
+   */
+  volatile int reserve_cores_ = 0;
+  /*!
    * \brief Whether OMP_NUM_THREADS was set in the environment.  If it is, we fall back to
    *        the OMP's implementation's handling of that environment variable
    */
diff --git a/src/engine/threaded_engine_perdevice.cc b/src/engine/threaded_engine_perdevice.cc
index 6086879..e01dd4e 100644
--- a/src/engine/threaded_engine_perdevice.cc
+++ b/src/engine/threaded_engine_perdevice.cc
@@ -113,8 +113,8 @@ class ThreadedEnginePerDevice : public ThreadedEngine {
         if (is_copy) {
           auto ptr =
           gpu_copy_workers_.Get(ctx.dev_id, [this, ctx, is_copy, nthread]() {
-            // Signify to kernel that GPU is being used,  no Kernel Launch OMP (temporary behavior)
-            OpenMP::Get()->set_enabled(false);
+            // Signify to kernel that GPU is being used, so reserve cores as necessary
+            OpenMP::Get()->set_reserve_cores(GetReserveCoreCount(true));
             auto blk = new ThreadWorkerBlock<kCopyQueue>();
               blk->pool.reset(new ThreadPool(
                 nthread,
@@ -133,8 +133,8 @@ class ThreadedEnginePerDevice : public ThreadedEngine {
           }
         } else {
           auto ptr = gpu_normal_workers_.Get(ctx.dev_id, [this, ctx, is_copy, nthread]() {
-            // Signify to kernel that GPU is being used,  no Kernel Launch OMP (temporary behavior)
-            OpenMP::Get()->set_enabled(false);
+            // Signify to kernel that GPU is being used, so reserve cores as necessary
+            OpenMP::Get()->set_reserve_cores(GetReserveCoreCount(true));
               auto blk = new ThreadWorkerBlock<kWorkerQueue>();
               blk->pool.reset(new ThreadPool(
                 nthread,
@@ -234,6 +234,27 @@ class ThreadedEnginePerDevice : public ThreadedEngine {
     }
   }
 
+  /*!
+   * \brief Get number of cores this engine should reserve for its own use
+   * \param using_gpu Whether there is GPU usage
+   * \return number of cores that this engine wishes to be reserved
+   * \note Testing found no degradation of performance using these values
+   *       running cifar10 with resnet50 on various GPU systems,
+   *       including AWS p2.16xlarge, which has 16 GPU's
+   */
+  int GetReserveCoreCount(const bool using_gpu) const {
+    int reserve = 0;
+    if (using_gpu) {
+      // Save at least one for GPU tasks
+      ++reserve;
+      // If we have 8 or more real cores, reserve another core for GPU tasks
+      if (OpenMP::Get()->GetRecommendedOMPThreadCount(true) >= 8) {
+        ++reserve;
+      }
+    }
+    return reserve;
+  }
+
   /*! \brief Signal a single queue for shutdown */
   template<typename Object>
   static inline void SignalQueueForKill(common::LazyAllocArray<Object> *array) {
diff --git a/src/initialize.cc b/src/initialize.cc
index ca78a76..a3cc116 100644
--- a/src/initialize.cc
+++ b/src/initialize.cc
@@ -77,5 +77,10 @@ LibraryInitializer* LibraryInitializer::Get() {
   return &inst;
 }
 
+#ifdef __GNUC__
+// Don't print an unused variable message since this is intentional
+#pragma GCC diagnostic ignored "-Wunused-variable"
+#endif
+
 static LibraryInitializer* __library_init = LibraryInitializer::Get();
 }  // namespace mxnet
diff --git a/src/operator/dropout-inl.h b/src/operator/dropout-inl.h
index b2fb782..7fcd7ad 100644
--- a/src/operator/dropout-inl.h
+++ b/src/operator/dropout-inl.h
@@ -35,6 +35,7 @@
 #include <algorithm>
 #include "./operator_common.h"
 #include "./mshadow_op.h"
+#include "../engine/openmp.h"
 
 #if defined(USE_MKL) && defined(_OPENMP)
 #include <omp.h>
@@ -55,8 +56,8 @@ namespace op {
 
 #if defined(USE_MKL) && defined(_OPENMP)
 static void bernoulli_generate(int n, double p, int* r) {
-  int seed = 17 + rand() % 4096;  // NOLINT(runtime/threadsafe_fn)
-  int nthr = omp_get_max_threads();
+  const int seed = 17 + rand() % 4096;  // NOLINT(runtime/threadsafe_fn)
+  const int nthr = engine::OpenMP::Get()->GetRecommendedOMPThreadCount();
 # pragma omp parallel num_threads(nthr)
   {
     const int ithr = omp_get_thread_num();
@@ -117,12 +118,13 @@ class DropoutOp : public Operator {
 #if !defined(__CUDACC__) && defined(USE_MKL) && defined(_OPENMP)
       DType* outptr = out.dptr_;
       DType* dataptr = data.dptr_;
-      int* maskptr = reinterpret_cast<int*>(mask.dptr_);
+      auto maskptr = reinterpret_cast<int*>(mask.dptr_);
       int count = mask.shape_[0]*mask.shape_[1];
       bernoulli_generate(count, this->pkeep_, maskptr);
-  #pragma omp parallel for
+      const float pk_1 = 1.0f / pkeep_;
+      #pragma omp parallel for num_threads(engine::OpenMP::Get()->GetRecommendedOMPThreadCount())
       for (int i = 0; i < count; ++i) {
-        outptr[i] = dataptr[i] * maskptr[i] * (1.0f / pkeep_);
+        outptr[i] = dataptr[i] * maskptr[i] * pk_1;
       }
 #else
       Random<xpu> *prnd = ctx.requested[dropout::kRandom].get_random<xpu, real_t>(s);
@@ -154,15 +156,15 @@ class DropoutOp : public Operator {
 #if !defined(__CUDACC__) && defined(USE_MKL) && defined(_OPENMP)
       DType* ingradptr = gdata.dptr_;
       DType* outgradptr = grad.dptr_;
-      int* maskptr = reinterpret_cast<int*>(mask.dptr_);
-
+      auto maskptr = reinterpret_cast<int*>(mask.dptr_);
       int count = mask.shape_[0]*mask.shape_[1];
-
-      #pragma omp parallel for
+      const float pk_1 = 1.0f / pkeep_;
+      #pragma omp parallel for num_threads(engine::OpenMP::Get()->GetRecommendedOMPThreadCount())
       for (int i = 0; i < count; ++i) {
-        ingradptr[i] = outgradptr[i] * maskptr[i] * (1.0f / pkeep_);
+        ingradptr[i] = outgradptr[i] * maskptr[i] * pk_1;
       }
 #else  // USE_MKL && _OPENMP
+      CHECK_EQ(grad.shape_.Size(), mask.shape_.Size());
       Assign(gdata, req[dropout::kData], grad * mask);
 #endif  // USE_MKL && _OPENMP
     } else {
diff --git a/src/operator/mxnet_op.h b/src/operator/mxnet_op.h
index c74b216..06e2393 100644
--- a/src/operator/mxnet_op.h
+++ b/src/operator/mxnet_op.h
@@ -77,6 +77,7 @@ inline int get_num_threads<gpu>(const int N) {
   using namespace mshadow::cuda;
   return kBaseThreadNum * cuda_get_num_blocks(N);
 }
+
 #endif  // __CUDACC__
 
 template<>
diff --git a/src/operator/operator_common.h b/src/operator/operator_common.h
index 875c79c..d036355 100644
--- a/src/operator/operator_common.h
+++ b/src/operator/operator_common.h
@@ -129,7 +129,7 @@ inline std::string shape_string(const TShape& x) {
   return os.str();
 }
 
-/*! \brief get string representation of shape */
+/*! \brief get string representation of data type */
 inline std::string type_string(const int& x) {
   switch (x) {
     case mshadow::kFloat32:
@@ -138,10 +138,14 @@ inline std::string type_string(const int& x) {
       return "float64";
     case mshadow::kFloat16:
       return "float16";
+    case mshadow::kInt8:
+      return "int8";
     case mshadow::kUint8:
       return "uint8";
     case mshadow::kInt32:
       return "int32";
+    case mshadow::kInt64:
+      return "int64";
   }
   return "unknown";
 }
diff --git a/src/operator/slice_channel-inl.h b/src/operator/slice_channel-inl.h
index a48c52f..791b90e 100644
--- a/src/operator/slice_channel-inl.h
+++ b/src/operator/slice_channel-inl.h
@@ -41,7 +41,6 @@ namespace op {
 
 namespace slice_enum {
 enum SliceChannelOpInputs {kData};
-enum SliceChannelOpOutputs {kOut0, kOut1, kOut2, kOut3, kOut4};
 }  // namespace slice_enum
 
 struct SliceChannelParam : public dmlc::Parameter<SliceChannelParam> {
diff --git a/tests/cpp/include/test_util.h b/tests/cpp/include/test_util.h
index 2926055..95ab141 100644
--- a/tests/cpp/include/test_util.h
+++ b/tests/cpp/include/test_util.h
@@ -518,14 +518,17 @@ inline std::string demangle(const char *name) {
   return status ? name : res.get();
 }
 
+template<typename T>
+inline std::string type_name() { return demangle(typeid(T).name()); }
+
 #define PRINT_NDARRAYS(__ctx$, __var)  test::print(__ctx$, __FUNCTION__, #__var, __var)
 #define PRINT_OP_AND_ARRAYS(__ctx$, __op, __var)  test::print(__ctx$, __FUNCTION__, \
   static_cast<std::stringstream *>(&(std::stringstream() << #__var << \
-  "<" << test::demangle(typeid(__op).name()) << ">"))->str(), __var)
+  "<" << type_name<__op>() << ">"))->str(), __var)
 #define PRINT_OP2_AND_ARRAYS(__ctx$, __op1, __op2, __var)  test::print(__ctx$, __FUNCTION__, \
   static_cast<std::stringstream *>(&(std::stringstream() << #__var << \
-  "<" << test::demangle(typeid(__op1).name()) << ", " \
-  << test::demangle(typeid(__op2).name()) << ">"))->str(), __var)
+  "<" << type_name<__op1>().name()) << ", " \
+  << type_name<__op2>() << ">"))->str(), __var)
 
 /*! \brief Fill blob with some pattern defined by the getNextData() callback
  * Pattern fill in the defined order (important for analysis):
@@ -606,6 +609,19 @@ inline ScalarType rangedRand(const ScalarType min, const ScalarType max) {
   return static_cast<ScalarType>(x / bin_size + min);
 }
 
+inline std::string pretty_num(uint64_t val) {
+  std::string res, s = std::to_string(val);
+  size_t ctr = 0;
+  for (int i = static_cast<int>(s.size()) - 1; i >= 0; --i, ++ctr) {
+    if (ctr && (ctr % 3) == 0) {
+      res += ",";
+    }
+    res.push_back(s[i]);
+  }
+  std::reverse(res.begin(), res.end());
+  return res;
+}
+
 /*! \brief Change a value during the scope of this declaration */
 template<typename T>
 struct ScopeSet {
diff --git a/tests/cpp/misc/memory_test.cc b/tests/cpp/misc/memory_test.cc
index e39399d..a36f7f9 100644
--- a/tests/cpp/misc/memory_test.cc
+++ b/tests/cpp/misc/memory_test.cc
@@ -42,19 +42,6 @@ static typename Container::value_type average(const Container& cont) {
   return avg;
 }
 
-static std::string pretty_num(uint64_t val) {
-  std::string res, s = std::to_string(val);
-  size_t ctr = 0;
-  for (int i = static_cast<int>(s.size()) - 1; i >= 0; --i, ++ctr) {
-    if (ctr && (ctr % 3) == 0) {
-      res += ",";
-    }
-    res.push_back(s[i]);
-  }
-  std::reverse(res.begin(), res.end());
-  return res;
-}
-
 static int GetOMPThreadCount() {
   return omp_get_max_threads() >> 1;
 }
@@ -75,7 +62,7 @@ TEST(MEMORY_TEST, MemsetAndMemcopyPerformance) {
 
     const size_t test_size = 2 * base;
     std::cout << "====================================" << std::endl
-              << "Data size: " << pretty_num(test_size) << std::endl << std::flush;
+              << "Data size: " << test::pretty_num(test_size) << std::endl << std::flush;
 
     std::unique_ptr<uint8_t> buffer_1(new uint8_t[test_size]), buffer_2(new uint8_t[test_size]);
     uint8_t *src = buffer_1.get(), *dest = buffer_2.get();
@@ -117,19 +104,21 @@ TEST(MEMORY_TEST, MemsetAndMemcopyPerformance) {
       memcpy_times.push_back(memcpy_time);
       omp_copy_times.push_back(omp_copy_time);
 
-      std::cout << "memset time:   " << pretty_num(memcpy_time) << " ns" << std::endl
-                << "omp set time:  " << pretty_num(omp_set_time) << " ns" << std::endl
+      std::cout << "memset time:   " << test::pretty_num(memcpy_time) << " ns" << std::endl
+                << "omp set time:  " << test::pretty_num(omp_set_time) << " ns" << std::endl
                 << std::endl;
-      std::cout << "memcpy time:   " << pretty_num(memcpy_time) << " ns" << std::endl
-                << "omp copy time: " << pretty_num(omp_copy_time) << " ns" << std::endl
+      std::cout << "memcpy time:   " << test::pretty_num(memcpy_time) << " ns" << std::endl
+                << "omp copy time: " << test::pretty_num(omp_copy_time) << " ns" << std::endl
                 << std::endl;
     }
     std::cout << "------------------------------------" << std::endl;
     if (average(memset_times) > average(omp_set_times)) {
-      std::cout << "<< MEMSET SLOWER FOR " << pretty_num(test_size) << " items >>" << std::endl;
+      std::cout << "<< MEMSET SLOWER FOR " << test::pretty_num(test_size)
+                << " items >>" << std::endl;
     }
     if (average(memcpy_times) > average(omp_copy_times)) {
-      std::cout << "<< MEMCPY SLOWER FOR " << pretty_num(test_size) << " items >>" << std::endl;
+      std::cout << "<< MEMCPY SLOWER FOR " << test::pretty_num(test_size)
+                << " items >>" << std::endl;
     }
     if (!pass) {
       GTEST_ASSERT_LE(average(memset_times), average(omp_set_times));
diff --git a/tests/cpp/unittest.mk b/tests/cpp/unittest.mk
index 11ea6d1..030b240 100644
--- a/tests/cpp/unittest.mk
+++ b/tests/cpp/unittest.mk
@@ -1,6 +1,6 @@
 TEST_SRC = $(shell find tests/cpp/ -name "*.cc")
 TEST_OBJ = $(patsubst %.cc, build/%.o, $(TEST_SRC))
-TEST = build/tests/cpp/mxnet_test
+TEST = build/tests/cpp/mxnet_unit_tests
 
 GTEST_LIB=$(GTEST_PATH)/lib/
 GTEST_INC=$(GTEST_PATH)/include/
diff --git a/tests/python/unittest/test_sparse_operator.py b/tests/python/unittest/test_sparse_operator.py
index d440553..2f0ebd8 100644
--- a/tests/python/unittest/test_sparse_operator.py
+++ b/tests/python/unittest/test_sparse_operator.py
@@ -727,6 +727,19 @@ def test_sparse_mathematical_core():
                                        force_overlap=force_overlap,
                                        verbose=False)
 
+        # minus_scalar
+        check_sparse_mathematical_core("minus_scalar", stype,
+                                       lambda x, y: x - y,
+                                       lambda x, y: x - y,
+                                       lambda input, rhs: 1,
+                                       rhs_arg=5.0,
+                                       data_init=2, grad_init=3,
+                                       output_grad_stype=output_grad_stype,
+                                       input_grad_stype=input_grad_stype,
+                                       density=density, ograd_density=ograd_density,
+                                       force_overlap=force_overlap,
+                                       verbose=False)
+
     # Check many basic unary operators
     def check_mathematical_core(stype, output_grad_stype=None,
                                 input_grad_stype=None, force_overlap=False,
@@ -1677,4 +1690,3 @@ def test_scatter_ops():
 if __name__ == '__main__':
     import nose
     nose.runmodule()
-

-- 
To stop receiving notification emails like this one, please contact
['"commits@mxnet.apache.org" <co...@mxnet.apache.org>'].