Skip to content

Commit

Permalink
[openmp] parallel learning using OpenMP
Browse files Browse the repository at this point in the history
  • Loading branch information
Anastasios Zouzias committed Oct 21, 2019
1 parent d2bbfb2 commit 0aff6c2
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 19 deletions.
47 changes: 46 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

PROJECT(microgbt CXX)

OPTION(USE_OPENMP "Enable OpenMP" ON)

# project version
set(VERSION_MAJOR 0)
set(VERSION_MINOR 1)
Expand All @@ -17,6 +19,40 @@ SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
FIND_PACKAGE (Eigen3 3.3 REQUIRED NO_MODULE)


# Based on https://iscinumpy.gitlab.io/post/omp-on-high-sierra/
# and for Mojave 10.14: see https://github.com/microsoft/LightGBM/pull/1919
if(USE_OPENMP)
find_package(OpenMP)

# If OpenMP wasn't found, try if we can find it in the default Homebrew location
if((NOT OPENMP_FOUND) AND (NOT OPENMP_CXX_FOUND) AND EXISTS "/usr/local/opt/libomp/lib/libomp.dylib")
set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp -I/usr/local/opt/libomp/include")
set(OpenMP_C_LIB_NAMES omp)
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp -I/usr/local/opt/libomp/include")
set(OpenMP_CXX_LIB_NAMES omp)
set(OpenMP_omp_LIBRARY /usr/local/opt/libomp/lib/libomp.dylib)

find_package(OpenMP)
if (OPENMP_FOUND OR OPENMP_CXX_FOUND)
message(STATUS "Found libomp in homebrew default location.")
else()
message(FATAL_ERROR "Didn't find libomp. Tried homebrew default location but also didn't find it.")
endif()
endif()

if((NOT OPENMP_FOUND) AND (NOT OPENMP_CXX_FOUND))
message(FATAL_ERROR "Did not find OpenMP.")
endif()

SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
else()
# Ignore unknown #pragma warning
if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"))
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas")
endif()
endif(USE_OPENMP)

# place binaries and libraries according to GNU standards
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
Expand Down Expand Up @@ -72,3 +108,12 @@ pybind11_add_module(microgbtpy src/metrics/metric.h src/trees/tree.h src/GBT.h s
src/utils.h
src/python_api.cpp)
#########################


#########################
# Link openMP
#########################
if(USE_OPENMP)
target_link_libraries(microgbtpy PRIVATE OpenMP::OpenMP_CXX)
endif(USE_OPENMP)
#########################
4 changes: 4 additions & 0 deletions src/GBT.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <vector>
#include<iostream>
#include <memory>
#include <omp.h>
#include "dataset.h"
#include "trees/tree.h"
#include "metrics/metric.h"
Expand Down Expand Up @@ -117,6 +118,9 @@ namespace microgbt {
*/
void train(const Dataset &trainSet, const Dataset &validSet, int numBoostRound, int earlyStoppingRounds) {

// Allow nested threading in OpenMP
omp_set_max_active_levels(_maxDepth + 1);

long bestIteration = 0;
double learningRate = _shrinkageRate;
double bestValidationLoss = std::numeric_limits<double>::max();
Expand Down
1 change: 1 addition & 0 deletions src/dataset.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ namespace microgbt {
// By default, all rows are included in the dataset
std::iota(_rowIndices.begin(), _rowIndices.end(), 0);

#pragma omp parallel for schedule(static)
for ( long j = 0; j < X.cols(); j++) {
_sortedMatrixIdx.col(j) = sortIndices(j);
}
Expand Down
3 changes: 3 additions & 0 deletions src/metrics/logloss.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ namespace microgbt {
unsigned long sz = predictions.size();
Vector gradients(sz);

#pragma omp parallel for schedule(static)
for (unsigned long i = 0 ; i < sz; i++){
gradients[i] = predictions[i] - labels[i];
}
Expand All @@ -62,6 +63,7 @@ namespace microgbt {
unsigned long sz = predictions.size();
Vector hessians(sz);

#pragma omp parallel for schedule(static)
for (unsigned long i = 0 ; i < sz; i++){
hessians[i] = predictions[i] * (1 - predictions[i]);
}
Expand All @@ -73,6 +75,7 @@ namespace microgbt {
size_t n = predictions.size();
double loss = 0.0;

#pragma omp parallel for shared(labels, predictions) reduction(+: loss)
for (size_t i = 0; i < n; i ++){
loss += labels[i] * log(predictions[i]) + (1 - labels[i]) * log(1 - predictions[i]);
}
Expand Down
1 change: 1 addition & 0 deletions src/trees/numerical_splliter.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ namespace microgbt {

// For each feature, compute split gain and keep the split index with maximum gain
Vector gainPerOrderedSampleIndex(dataset.nRows());
#pragma omp parallel for schedule(static, 1024)
for (size_t i = 0 ; i < dataset.nRows(); i++){
gainPerOrderedSampleIndex[i] = calc_split_gain(cum_sum_g, cum_sum_h, cum_sum_G[i], cum_sum_H[i]);
}
Expand Down
45 changes: 28 additions & 17 deletions src/trees/treenode.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,23 +123,34 @@ namespace microgbt {
this->splitFeatureIndex = bestGain.getBestFeatureId();
this->splitNumericValue = bestGain.splitValue();


Dataset leftDataset(trainSet, bestGain, SplitInfo::Side::Left);
Vector leftGradient = bestGain.split(gradient, SplitInfo::Side::Left);
Vector leftHessian = bestGain.split(hessian, SplitInfo::Side::Left);
Vector leftPreviousPreds = bestGain.split(previousPreds, SplitInfo::Side::Left);
this->leftSubTree = std::unique_ptr<TreeNode>(
new TreeNode(_lambda, _minSplitGain, _minTreeSize, _maxDepth));
leftSubTree->build(leftDataset, leftPreviousPreds, leftGradient, leftHessian, shrinkage, depth + 1);

Dataset rightDataset(trainSet, bestGain, SplitInfo::Side::Right);
Vector rightGradient = bestGain.split(gradient, SplitInfo::Side::Right);
Vector rightHessian = bestGain.split(hessian, SplitInfo::Side::Right);
Vector rightPreviousPreds = bestGain.split(previousPreds, SplitInfo::Side::Right);

this->rightSubTree = std::unique_ptr<TreeNode>(
new TreeNode(_lambda, _minSplitGain, _minTreeSize, _maxDepth));
rightSubTree->build(rightDataset, rightPreviousPreds, rightGradient, rightHessian, shrinkage, depth + 1);
#pragma omp parallel sections
{
// Recurse on the left subtree
#pragma omp section
{
Dataset leftDataset(trainSet, bestGain, SplitInfo::Side::Left);
Vector leftGradient = bestGain.split(gradient, SplitInfo::Side::Left);
Vector leftHessian = bestGain.split(hessian, SplitInfo::Side::Left);
Vector leftPreviousPreds = bestGain.split(previousPreds, SplitInfo::Side::Left);
this->leftSubTree = std::unique_ptr<TreeNode>(
new TreeNode(_lambda, _minSplitGain, _minTreeSize, _maxDepth));
leftSubTree->build(leftDataset, leftPreviousPreds, leftGradient, leftHessian, shrinkage, depth + 1);
}


// Recurse on the right subtree
#pragma omp section
{
Dataset rightDataset(trainSet, bestGain, SplitInfo::Side::Right);
Vector rightGradient = bestGain.split(gradient, SplitInfo::Side::Right);
Vector rightHessian = bestGain.split(hessian, SplitInfo::Side::Right);
Vector rightPreviousPreds = bestGain.split(previousPreds, SplitInfo::Side::Right);

this->rightSubTree = std::unique_ptr<TreeNode>(
new TreeNode(_lambda, _minSplitGain, _minTreeSize, _maxDepth));
rightSubTree->build(rightDataset, rightPreviousPreds, rightGradient, rightHessian, shrinkage, depth + 1);
}
}
}

/**
Expand Down
4 changes: 3 additions & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ add_executable(
target_link_libraries(
unit_tests
gtest_main
microgbt)
microgbt
OpenMP::OpenMP_CXX
)

add_test(
NAME
Expand Down

0 comments on commit 0aff6c2

Please sign in to comment.