Skip to content

Commit 0bb4a82

Browse files
huanzhang12guolinke
authored andcommitted
Initial GPU acceleration support for LightGBM (#368)
* add dummy gpu solver code * initial GPU code * fix crash bug * first working version * use asynchronous copy * use a better kernel for root * parallel read histogram * sparse features now works, but no acceleration, compute on CPU * compute sparse feature on CPU simultaneously * fix big bug; add gpu selection; add kernel selection * better debugging * clean up * add feature scatter * Add sparse_threshold control * fix a bug in feature scatter * clean up debug * temporarily add OpenCL kernels for k=64,256 * fix up CMakeList and definition USE_GPU * add OpenCL kernels as string literals * Add boost.compute as a submodule * add boost dependency into CMakeList * fix opencl pragma * use pinned memory for histogram * use pinned buffer for gradients and hessians * better debugging message * add double precision support on GPU * fix boost version in CMakeList * Add a README * reconstruct GPU initialization code for ResetTrainingData * move data to GPU in parallel * fix a bug during feature copy * update gpu kernels * update gpu code * initial port to LightGBM v2 * speedup GPU data loading process * Add 4-bit bin support to GPU * re-add sparse_threshold parameter * remove kMaxNumWorkgroups and allows an unlimited number of features * add feature mask support for skipping unused features * enable kernel cache * use GPU kernels withoug feature masks when all features are used * REAdme. * REAdme. * update README * fix typos (#349) * change compile to gcc on Apple as default * clean vscode related file * refine api of constructing from sampling data. * fix bug in the last commit. * more efficient algorithm to sample k from n. * fix bug in filter bin * change to boost from average output. * fix tests. * only stop training when all classes are finshed in multi-class. * limit the max tree output. change hessian in multi-class objective. * robust tree model loading. * fix test. * convert the probabilities to raw score in boost_from_average of classification. * fix the average label for binary classification. * Add boost_from_average to docs (#354) * don't use "ConvertToRawScore" for self-defined objective function. * boost_from_average seems doesn't work well in binary classification. remove it. * For a better jump link (#355) * Update Python-API.md * for a better jump in page A space is needed between `#` and the headers content according to Github's markdown format [guideline](https://guides.github.com/features/mastering-markdown/) After adding the spaces, we can jump to the exact position in page by click the link. * fixed something mentioned by @wxchan * Update Python-API.md * add FitByExistingTree. * adapt GPU tree learner for FitByExistingTree * avoid NaN output. * update boost.compute * fix typos (#361) * fix broken links (#359) * update README * disable GPU acceleration by default * fix image url * cleanup debug macro * remove old README * do not save sparse_threshold_ in FeatureGroup * add details for new GPU settings * ignore submodule when doing pep8 check * allocate workspace for at least one thread during builing Feature4 * move sparse_threshold to class Dataset * remove duplicated code in GPUTreeLearner::Split * Remove duplicated code in FindBestThresholds and BeforeFindBestSplit * do not rebuild ordered gradients and hessians for sparse features * support feature groups in GPUTreeLearner * Initial parallel learners with GPU support * add option device, cleanup code * clean up FindBestThresholds; add some omp parallel * constant hessian optimization for GPU * Fix GPUTreeLearner crash when there is zero feature * use np.testing.assert_almost_equal() to compare lists of floats in tests * travis for GPU
1 parent db3d1f8 commit 0bb4a82

30 files changed

+4163
-246
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "include/boost/compute"]
2+
path = compute
3+
url = https://github.com/boostorg/compute

.travis.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,48 @@ before_install:
1111
- export PATH="$HOME/miniconda/bin:$PATH"
1212
- conda config --set always_yes yes --set changeps1 no
1313
- conda update -q conda
14+
- sudo add-apt-repository ppa:george-edison55/cmake-3.x -y
15+
- sudo apt-get update -q
16+
- bash .travis/amd_sdk.sh;
17+
- tar -xjf AMD-SDK.tar.bz2;
18+
- AMDAPPSDK=${HOME}/AMDAPPSDK;
19+
- export OPENCL_VENDOR_PATH=${AMDAPPSDK}/etc/OpenCL/vendors;
20+
- mkdir -p ${OPENCL_VENDOR_PATH};
21+
- sh AMD-APP-SDK*.sh --tar -xf -C ${AMDAPPSDK};
22+
- echo libamdocl64.so > ${OPENCL_VENDOR_PATH}/amdocl64.icd;
23+
- export LD_LIBRARY_PATH=${AMDAPPSDK}/lib/x86_64:${LD_LIBRARY_PATH};
24+
- chmod +x ${AMDAPPSDK}/bin/x86_64/clinfo;
25+
- ${AMDAPPSDK}/bin/x86_64/clinfo;
26+
- export LIBRARY_PATH="$HOME/miniconda/lib:$LIBRARY_PATH"
27+
- export LD_RUN_PATH="$HOME/miniconda/lib:$LD_RUN_PATH"
28+
- export CPLUS_INCLUDE_PATH="$HOME/miniconda/include:$AMDAPPSDK/include/:$CPLUS_INCLUDE_PATH"
1429

1530
install:
1631
- sudo apt-get install -y libopenmpi-dev openmpi-bin build-essential
32+
- sudo apt-get install -y cmake
1733
- conda install --yes atlas numpy scipy scikit-learn pandas matplotlib
34+
- conda install --yes -c conda-forge boost=1.63.0
1835
- pip install pep8
1936

20-
2137
script:
2238
- cd $TRAVIS_BUILD_DIR
2339
- mkdir build && cd build && cmake .. && make -j
2440
- cd $TRAVIS_BUILD_DIR/tests/c_api_test && python test.py
2541
- cd $TRAVIS_BUILD_DIR/python-package && python setup.py install
2642
- cd $TRAVIS_BUILD_DIR/tests/python_package_test && python test_basic.py && python test_engine.py && python test_sklearn.py && python test_plotting.py
27-
- cd $TRAVIS_BUILD_DIR && pep8 --ignore=E501 .
43+
- cd $TRAVIS_BUILD_DIR && pep8 --ignore=E501 --exclude=./compute .
2844
- rm -rf build && mkdir build && cd build && cmake -DUSE_MPI=ON ..&& make -j
2945
- cd $TRAVIS_BUILD_DIR/tests/c_api_test && python test.py
3046
- cd $TRAVIS_BUILD_DIR/python-package && python setup.py install
3147
- cd $TRAVIS_BUILD_DIR/tests/python_package_test && python test_basic.py && python test_engine.py && python test_sklearn.py && python test_plotting.py
48+
- cd $TRAVIS_BUILD_DIR
49+
- rm -rf build && mkdir build && cd build && cmake -DUSE_GPU=ON -DBOOST_ROOT="$HOME/miniconda/" -DOpenCL_INCLUDE_DIR=$AMDAPPSDK/include/ ..
50+
- sed -i 's/std::string device_type = "cpu";/std::string device_type = "gpu";/' ../include/LightGBM/config.h
51+
- make -j$(nproc)
52+
- sed -i 's/std::string device_type = "gpu";/std::string device_type = "cpu";/' ../include/LightGBM/config.h
53+
- cd $TRAVIS_BUILD_DIR/tests/c_api_test && python test.py
54+
- cd $TRAVIS_BUILD_DIR/python-package && python setup.py install
55+
- cd $TRAVIS_BUILD_DIR/tests/python_package_test && python test_basic.py && python test_engine.py && python test_sklearn.py && python test_plotting.py
3256

3357
notifications:
3458
email: false

.travis/amd_sdk.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
3+
# Original script from https://github.com/gregvw/amd_sdk/
4+
5+
# Location from which get nonce and file name from
6+
URL="http://developer.amd.com/tools-and-sdks/opencl-zone/opencl-tools-sdks/amd-accelerated-parallel-processing-app-sdk/"
7+
URLDOWN="http://developer.amd.com/amd-license-agreement-appsdk/"
8+
9+
NONCE1_STRING='name="amd_developer_central_downloads_page_nonce"'
10+
FILE_STRING='name="f"'
11+
POSTID_STRING='name="post_id"'
12+
NONCE2_STRING='name="amd_developer_central_nonce"'
13+
14+
#For newest FORM=`wget -qO - $URL | sed -n '/download-2/,/64-bit/p'`
15+
FORM=`wget -qO - $URL | sed -n '/download-5/,/64-bit/p'`
16+
17+
# Get nonce from form
18+
NONCE1=`echo $FORM | awk -F ${NONCE1_STRING} '{print $2}'`
19+
NONCE1=`echo $NONCE1 | awk -F'"' '{print $2}'`
20+
echo $NONCE1
21+
22+
# get the postid
23+
POSTID=`echo $FORM | awk -F ${POSTID_STRING} '{print $2}'`
24+
POSTID=`echo $POSTID | awk -F'"' '{print $2}'`
25+
echo $POSTID
26+
27+
# get file name
28+
FILE=`echo $FORM | awk -F ${FILE_STRING} '{print $2}'`
29+
FILE=`echo $FILE | awk -F'"' '{print $2}'`
30+
echo $FILE
31+
32+
FORM=`wget -qO - $URLDOWN --post-data "amd_developer_central_downloads_page_nonce=${NONCE1}&f=${FILE}&post_id=${POSTID}"`
33+
34+
NONCE2=`echo $FORM | awk -F ${NONCE2_STRING} '{print $2}'`
35+
NONCE2=`echo $NONCE2 | awk -F'"' '{print $2}'`
36+
echo $NONCE2
37+
38+
wget --content-disposition --trust-server-names $URLDOWN --post-data "amd_developer_central_nonce=${NONCE2}&f=${FILE}" -O AMD-SDK.tar.bz2;

CMakeLists.txt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ PROJECT(lightgbm)
99

1010
OPTION(USE_MPI "MPI based parallel learning" OFF)
1111
OPTION(USE_OPENMP "Enable OpenMP" ON)
12+
OPTION(USE_GPU "Enable GPU-acclerated training (EXPERIMENTAL)" OFF)
1213

1314
if(APPLE)
1415
OPTION(APPLE_OUTPUT_DYLIB "Output dylib shared library" OFF)
@@ -34,8 +35,17 @@ else()
3435
endif()
3536
endif(USE_OPENMP)
3637

38+
if(USE_GPU)
39+
find_package(OpenCL REQUIRED)
40+
include_directories(${OpenCL_INCLUDE_DIRS})
41+
MESSAGE(STATUS "OpenCL include directory:" ${OpenCL_INCLUDE_DIRS})
42+
find_package(Boost 1.56.0 COMPONENTS filesystem system REQUIRED)
43+
include_directories(${Boost_INCLUDE_DIRS})
44+
ADD_DEFINITIONS(-DUSE_GPU)
45+
endif(USE_GPU)
46+
3747
if(UNIX OR MINGW OR CYGWIN)
38-
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -O3 -Wall -std=c++11")
48+
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -O3 -Wall -std=c++11 -Wno-ignored-attributes")
3949
endif()
4050

4151
if(MSVC)
@@ -65,11 +75,13 @@ endif()
6575

6676

6777
SET(LightGBM_HEADER_DIR ${PROJECT_SOURCE_DIR}/include)
78+
SET(BOOST_COMPUTE_HEADER_DIR ${PROJECT_SOURCE_DIR}/compute/include)
6879

6980
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
7081
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR})
7182

7283
include_directories (${LightGBM_HEADER_DIR})
84+
include_directories (${BOOST_COMPUTE_HEADER_DIR})
7385

7486
if(APPLE)
7587
if (APPLE_OUTPUT_DYLIB)
@@ -105,6 +117,11 @@ if(USE_MPI)
105117
TARGET_LINK_LIBRARIES(_lightgbm ${MPI_CXX_LIBRARIES})
106118
endif(USE_MPI)
107119

120+
if(USE_GPU)
121+
TARGET_LINK_LIBRARIES(lightgbm ${OpenCL_LIBRARY} ${Boost_LIBRARIES})
122+
TARGET_LINK_LIBRARIES(_lightgbm ${OpenCL_LIBRARY} ${Boost_LIBRARIES})
123+
endif(USE_GPU)
124+
108125
if(WIN32 AND (MINGW OR CYGWIN))
109126
TARGET_LINK_LIBRARIES(lightgbm Ws2_32)
110127
TARGET_LINK_LIBRARIES(_lightgbm Ws2_32)

compute

Submodule compute added at 1380a04

include/LightGBM/bin.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class BinMapper {
5959
explicit BinMapper(const void* memory);
6060
~BinMapper();
6161

62-
static double kSparseThreshold;
6362
bool CheckAlign(const BinMapper& other) const {
6463
if (num_bin_ != other.num_bin_) {
6564
return false;
@@ -258,6 +257,7 @@ class BinIterator {
258257
* \return Bin data
259258
*/
260259
virtual uint32_t Get(data_size_t idx) = 0;
260+
virtual uint32_t RawGet(data_size_t idx) = 0;
261261
virtual void Reset(data_size_t idx) = 0;
262262
virtual ~BinIterator() = default;
263263
};
@@ -383,12 +383,13 @@ class Bin {
383383
* \param num_bin Number of bin
384384
* \param sparse_rate Sparse rate of this bins( num_bin0/num_data )
385385
* \param is_enable_sparse True if enable sparse feature
386+
* \param sparse_threshold Threshold for treating a feature as a sparse feature
386387
* \param is_sparse Will set to true if this bin is sparse
387388
* \param default_bin Default bin for zeros value
388389
* \return The bin data object
389390
*/
390391
static Bin* CreateBin(data_size_t num_data, int num_bin,
391-
double sparse_rate, bool is_enable_sparse, bool* is_sparse);
392+
double sparse_rate, bool is_enable_sparse, double sparse_threshold, bool* is_sparse);
392393

393394
/*!
394395
* \brief Create object for bin data of one feature, used for dense feature

include/LightGBM/config.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ struct IOConfig: public ConfigBase {
9797
int num_iteration_predict = -1;
9898
bool is_pre_partition = false;
9999
bool is_enable_sparse = true;
100+
/*! \brief The threshold of zero elements precentage for treating a feature as a sparse feature.
101+
* Default is 0.8, where a feature is treated as a sparse feature when there are over 80% zeros.
102+
* When setting to 1.0, all features are processed as dense features.
103+
*/
104+
double sparse_threshold = 0.8;
100105
bool use_two_round_loading = false;
101106
bool is_save_binary_file = false;
102107
bool enable_load_from_binary_file = true;
@@ -188,6 +193,16 @@ struct TreeConfig: public ConfigBase {
188193
// max_depth < 0 means no limit
189194
int max_depth = -1;
190195
int top_k = 20;
196+
/*! \brief OpenCL platform ID. Usually each GPU vendor exposes one OpenCL platform.
197+
* Default value is -1, using the system-wide default platform
198+
*/
199+
int gpu_platform_id = -1;
200+
/*! \brief OpenCL device ID in the specified platform. Each GPU in the selected platform has a
201+
* unique device ID. Default value is -1, using the default device in the selected platform
202+
*/
203+
int gpu_device_id = -1;
204+
/*! \brief Set to true to use double precision math on GPU (default using single precision) */
205+
bool gpu_use_dp = false;
191206
LIGHTGBM_EXPORT void Set(const std::unordered_map<std::string, std::string>& params) override;
192207
};
193208

@@ -216,11 +231,14 @@ struct BoostingConfig: public ConfigBase {
216231
// only used for the regression. Will boost from the average labels.
217232
bool boost_from_average = true;
218233
std::string tree_learner_type = "serial";
234+
std::string device_type = "cpu";
219235
TreeConfig tree_config;
220236
LIGHTGBM_EXPORT void Set(const std::unordered_map<std::string, std::string>& params) override;
221237
private:
222238
void GetTreeLearnerType(const std::unordered_map<std::string,
223239
std::string>& params);
240+
void GetDeviceType(const std::unordered_map<std::string,
241+
std::string>& params);
224242
};
225243

226244
/*! \brief Config for Network */

include/LightGBM/dataset.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ class Dataset {
355355
inline int Feture2SubFeature(int feature_idx) const {
356356
return feature2subfeature_[feature_idx];
357357
}
358+
inline uint64_t GroupBinBoundary(int group_idx) const {
359+
return group_bin_boundaries_[group_idx];
360+
}
358361
inline uint64_t NumTotalBin() const {
359362
return group_bin_boundaries_.back();
360363
}
@@ -421,19 +424,36 @@ class Dataset {
421424
const int sub_feature = feature2subfeature_[i];
422425
return feature_groups_[group]->bin_mappers_[sub_feature]->num_bin();
423426
}
427+
428+
inline int FeatureGroupNumBin(int group) const {
429+
return feature_groups_[group]->num_total_bin_;
430+
}
424431

425432
inline const BinMapper* FeatureBinMapper(int i) const {
426433
const int group = feature2group_[i];
427434
const int sub_feature = feature2subfeature_[i];
428435
return feature_groups_[group]->bin_mappers_[sub_feature].get();
429436
}
430437

438+
inline const Bin* FeatureBin(int i) const {
439+
const int group = feature2group_[i];
440+
return feature_groups_[group]->bin_data_.get();
441+
}
442+
443+
inline const Bin* FeatureGroupBin(int group) const {
444+
return feature_groups_[group]->bin_data_.get();
445+
}
446+
431447
inline BinIterator* FeatureIterator(int i) const {
432448
const int group = feature2group_[i];
433449
const int sub_feature = feature2subfeature_[i];
434450
return feature_groups_[group]->SubFeatureIterator(sub_feature);
435451
}
436452

453+
inline BinIterator* FeatureGroupIterator(int group) const {
454+
return feature_groups_[group]->FeatureGroupIterator();
455+
}
456+
437457
inline double RealThreshold(int i, uint32_t threshold) const {
438458
const int group = feature2group_[i];
439459
const int sub_feature = feature2subfeature_[i];
@@ -461,6 +481,9 @@ class Dataset {
461481
/*! \brief Get Number of used features */
462482
inline int num_features() const { return num_features_; }
463483

484+
/*! \brief Get Number of feature groups */
485+
inline int num_feature_groups() const { return num_groups_;}
486+
464487
/*! \brief Get Number of total features */
465488
inline int num_total_features() const { return num_total_features_; }
466489

@@ -516,6 +539,8 @@ class Dataset {
516539
Metadata metadata_;
517540
/*! \brief index of label column */
518541
int label_idx_ = 0;
542+
/*! \brief Threshold for treating a feature as a sparse feature */
543+
double sparse_threshold_;
519544
/*! \brief store feature names */
520545
std::vector<std::string> feature_names_;
521546
/*! \brief store feature names */

include/LightGBM/feature_group.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ class FeatureGroup {
2525
* \param bin_mappers Bin mapper for features
2626
* \param num_data Total number of data
2727
* \param is_enable_sparse True if enable sparse feature
28+
* \param sparse_threshold Threshold for treating a feature as a sparse feature
2829
*/
2930
FeatureGroup(int num_feature,
3031
std::vector<std::unique_ptr<BinMapper>>& bin_mappers,
31-
data_size_t num_data, bool is_enable_sparse) : num_feature_(num_feature) {
32+
data_size_t num_data, double sparse_threshold, bool is_enable_sparse) : num_feature_(num_feature) {
3233
CHECK(static_cast<int>(bin_mappers.size()) == num_feature);
3334
// use bin at zero to store default_bin
3435
num_total_bin_ = 1;
@@ -46,7 +47,7 @@ class FeatureGroup {
4647
}
4748
double sparse_rate = 1.0f - static_cast<double>(cnt_non_zero) / (num_data);
4849
bin_data_.reset(Bin::CreateBin(num_data, num_total_bin_,
49-
sparse_rate, is_enable_sparse, &is_sparse_));
50+
sparse_rate, is_enable_sparse, sparse_threshold, &is_sparse_));
5051
}
5152
/*!
5253
* \brief Constructor from memory
@@ -120,6 +121,18 @@ class FeatureGroup {
120121
uint32_t default_bin = bin_mappers_[sub_feature]->GetDefaultBin();
121122
return bin_data_->GetIterator(min_bin, max_bin, default_bin);
122123
}
124+
125+
/*!
126+
* \brief Returns a BinIterator that can access the entire feature group's raw data.
127+
* The RawGet() function of the iterator should be called for best efficiency.
128+
* \return A pointer to the BinIterator object
129+
*/
130+
inline BinIterator* FeatureGroupIterator() {
131+
uint32_t min_bin = bin_offsets_[0];
132+
uint32_t max_bin = bin_offsets_.back() - 1;
133+
uint32_t default_bin = 0;
134+
return bin_data_->GetIterator(min_bin, max_bin, default_bin);
135+
}
123136

124137
inline data_size_t Split(
125138
int sub_feature,

include/LightGBM/tree_learner.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ class TreeLearner {
2424
/*!
2525
* \brief Initialize tree learner with training dataset
2626
* \param train_data The used training data
27+
* \param is_constant_hessian True if all hessians share the same value
2728
*/
28-
virtual void Init(const Dataset* train_data) = 0;
29+
virtual void Init(const Dataset* train_data, bool is_constant_hessian) = 0;
2930

3031
virtual void ResetTrainingData(const Dataset* train_data) = 0;
3132

@@ -71,10 +72,12 @@ class TreeLearner {
7172

7273
/*!
7374
* \brief Create object of tree learner
74-
* \param type Type of tree learner
75+
* \param learner_type Type of tree learner
76+
* \param device_type Type of tree learner
7577
* \param tree_config config of tree
7678
*/
77-
static TreeLearner* CreateTreeLearner(const std::string& type,
79+
static TreeLearner* CreateTreeLearner(const std::string& learner_type,
80+
const std::string& device_type,
7881
const TreeConfig* tree_config);
7982
};
8083

src/boosting/gbdt.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ void GBDT::ResetTrainingData(const BoostingConfig* config, const Dataset* train_
9292

9393
if (train_data_ != train_data && train_data != nullptr) {
9494
if (tree_learner_ == nullptr) {
95-
tree_learner_ = std::unique_ptr<TreeLearner>(TreeLearner::CreateTreeLearner(new_config->tree_learner_type, &new_config->tree_config));
95+
tree_learner_ = std::unique_ptr<TreeLearner>(TreeLearner::CreateTreeLearner(new_config->tree_learner_type, new_config->device_type, &new_config->tree_config));
9696
}
9797
// init tree learner
98-
tree_learner_->Init(train_data);
98+
tree_learner_->Init(train_data, is_constant_hessian_);
9999

100100
// push training metrics
101101
training_metrics_.clear();

src/io/bin.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -339,12 +339,10 @@ template class OrderedSparseBin<uint8_t>;
339339
template class OrderedSparseBin<uint16_t>;
340340
template class OrderedSparseBin<uint32_t>;
341341

342-
double BinMapper::kSparseThreshold = 0.8f;
343-
344342
Bin* Bin::CreateBin(data_size_t num_data, int num_bin, double sparse_rate,
345-
bool is_enable_sparse, bool* is_sparse) {
343+
bool is_enable_sparse, double sparse_threshold, bool* is_sparse) {
346344
// sparse threshold
347-
if (sparse_rate >= BinMapper::kSparseThreshold && is_enable_sparse) {
345+
if (sparse_rate >= sparse_threshold && is_enable_sparse) {
348346
*is_sparse = true;
349347
return CreateSparseBin(num_data, num_bin);
350348
} else {

0 commit comments

Comments
 (0)