You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When Predictor::Predictor is invoked for a shared Booster between multiple threads, it causes data races for shared data in Boosting object(GBDT)
Here is an API i.e LGBM_BoosterPredictForMatSingleRowFastInit() which if called from 2 separate threads concurrently using the same booster would cause data race(see the fully reproducible code below)
which writes concurrently i.e data race to variables(atleast) num_iteration_for_pred_ and start_iteration_for_pred_ of Boosting object(concretely GBDT) src/boosting/gbdt.h:422
Here is another/alternate API i.e LGBM_BoosterPredictForMat() which when invoked concurrently for the same Booster, would cause data race
Below is the code which when ran with Thread Sanitizer should reproduce/prove the race
I am trying to share a BoosterHandle between multiple threads only for inference/prediction. I intend to use the API LGBM_BoosterPredictForMatSingleRowFast and hence need to use LGBM_BoosterPredictForMatSingleRowFastInit to create/initialize a FastConfigHandle.
Reproducible example
#include<array>
#include<fstream>
#include<iostream>
#include<sstream>
#include<thread>
#include<vector>
#include"LightGBM/c_api.h"constintkFeatures = 13;
std::vector<std::array<double, kFeatures>> readFile(const std::string& file) {
std::vector<std::array<double, kFeatures>> ans;
std::ifstream f(file);
if (!f.is_open()) {
std::cout << "Could not open file " << file << '\n';
return ans;
}
bool is_header = true;
std::string temp;
int nline = 0;
while (std::getline(f, temp)) {
++nline;
if (is_header) {
is_header = false;
continue;
}
std::istringstream s(temp);
std::string field;
int idx = 0;
std::array<double, kFeatures> row;
while (std::getline(s, field, ',')) {
row[idx++] = std::stod(field);
}
if (idx != kFeatures) {
ans.clear();
std::cout << "Incorrect # of cols in line " << nline << '\n';
return ans;
}
ans.emplace_back(row);
}
return ans;
}
// shared booster handle for all threads
BoosterHandle handle;
// Input data
std::vector<std::array<double, kFeatures>> data;
// Final result or all predictions
std::vector<double> result;
voidpredict(ssize_t beg, ssize_t end) {
FastConfigHandle config;
int rc = LGBM_BoosterPredictForMatSingleRowFastInit(
handle, C_API_PREDICT_NORMAL, 0, 0, C_API_DTYPE_FLOAT64, kFeatures, "",
&config);
if (rc != 0) {
abort();
}
for (ssize_t i = beg; i < end; i++) {
int64_t len = 0;
rc = LGBM_BoosterPredictForMatSingleRowFast(config, &data[i], &len,
&result[i]);
if (rc != 0) {
abort();
}
}
rc = LGBM_FastConfigFree(config);
if (rc != 0) {
abort();
}
}
intmain(int argc, char* argv[]) {
if (argc != 4) {
std::cout
<< "Usage a.out <model_file> <input_file> <nworkers> ...exiting\n";
return1;
}
int nworkers = std::stoi(argv[3]);
int num_iterations;
std::cout << "Loading the Model from file\n";
int rc = LGBM_BoosterCreateFromModelfile(argv[1], &num_iterations, &handle);
if (rc != 0) {
std::cout << "LGBM_BoosterCreateFromModelfile() returned " << rc << '\n';
return1;
}
data = readFile(argv[2]);
ssize_t nrows = ssize(data);
result.resize(nrows);
std::vector<std::thread> workers(nworkers);
ssize_t rows_per_thread = nrows / nworkers;
for (ssize_t i = 0; i < nworkers; i++) {
if (i != nworkers - 1) {
workers[i] =
std::thread(predict, rows_per_thread * i, rows_per_thread * (i + 1));
} else {
workers[i] = std::thread(predict, rows_per_thread * i, nrows);
}
}
for (std::thread& t : workers) {
t.join();
}
rc = LGBM_BoosterFree(handle);
if (rc != 0) {
abort();
}
return0;
}
Environment info
LightGBM version or commit hash:
git log --oneline
8ed371c (HEAD -> master, origin/master, origin/HEAD) set explicit number of threads in every OpenMP parallel region (#6135)
Command(s) you used to install LightGBM
# this is part of a Makefile
mkdir -p LightGBM/build
env CC=$(CC) CXX=$(CXX) cmake -DUSE_DEBUG=ON -DUSE_SANITIZER=ON -DENABLED_SANITIZERS="thread" -DUSE_OPENMP=OFF -S LightGBM -B LightGBM/build
env CC=$(CC) CXX=$(CXX) VERBOSE=1 $(MAKE) -C LightGBM/build
Description
When
Predictor::Predictor
is invoked for a shared Booster between multiple threads, it causes data races for shared data in Boosting object(GBDT
)Here is an API i.e LGBM_BoosterPredictForMatSingleRowFastInit() which if called from 2 separate threads concurrently using the same booster would cause data race(see the fully reproducible code below)
LGBM_BoosterPredictForMatSingleRowFastInit()-> Booster::SetSingleRowPredictor -> SingleRowPredictor::SingleRowPredictor() ->Predictor::Predictor() -> GBDT::InitPredict()
which writes concurrently i.e data race to variables(atleast)
num_iteration_for_pred_
andstart_iteration_for_pred_
of Boosting object(concretelyGBDT
)src/boosting/gbdt.h:422
Here is another/alternate API i.e LGBM_BoosterPredictForMat() which when invoked concurrently for the same Booster, would cause data race
LGBM_BoosterPredictForMat() -> Booster::Predict() -> Booster::CreatePredictor() -> Predictor::Predictor()
See Issue 6024 comments here and here
#6024
Below is the code which when ran with Thread Sanitizer should reproduce/prove the race
I am trying to share a
BoosterHandle
between multiple threads only for inference/prediction. I intend to use the APILGBM_BoosterPredictForMatSingleRowFast
and hence need to useLGBM_BoosterPredictForMatSingleRowFastInit
to create/initialize aFastConfigHandle
.Reproducible example
Environment info
LightGBM version or commit hash:
git log --oneline
8ed371c (HEAD -> master, origin/master, origin/HEAD) set explicit number of threads in every OpenMP
parallel
region (#6135)Command(s) you used to install LightGBM
Additional Comments
The text was updated successfully, but these errors were encountered: