chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/application.h>
|
||||
|
||||
#include <LightGBM/boosting.h>
|
||||
#include <LightGBM/dataset.h>
|
||||
#include <LightGBM/dataset_loader.h>
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/network.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/prediction_early_stop.h>
|
||||
#include <LightGBM/cuda/vector_cudahost.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
#include <LightGBM/utils/text_reader.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "predictor.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
Application::Application(int argc, char** argv) {
|
||||
LoadParameters(argc, argv);
|
||||
// set number of threads for openmp
|
||||
OMP_SET_NUM_THREADS(config_.num_threads);
|
||||
if (config_.data.size() == 0 && config_.task != TaskType::kConvertModel) {
|
||||
Log::Fatal("No training/prediction data, application quit");
|
||||
}
|
||||
|
||||
if (config_.device_type == std::string("cuda")) {
|
||||
LGBM_config_::current_device = lgbm_device_cuda;
|
||||
}
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
if (config_.is_parallel) {
|
||||
Network::Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::LoadParameters(int argc, char** argv) {
|
||||
std::unordered_map<std::string, std::vector<std::string>> all_params;
|
||||
std::unordered_map<std::string, std::string> params;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
Config::KV2Map(&all_params, argv[i]);
|
||||
}
|
||||
// read parameters from config file
|
||||
bool config_file_ok = true;
|
||||
if (all_params.count("config") > 0) {
|
||||
TextReader<size_t> config_reader(all_params["config"][0].c_str(), false);
|
||||
config_reader.ReadAllLines();
|
||||
if (!config_reader.Lines().empty()) {
|
||||
for (auto& line : config_reader.Lines()) {
|
||||
// remove str after "#"
|
||||
if (line.size() > 0 && std::string::npos != line.find_first_of("#")) {
|
||||
line.erase(line.find_first_of("#"));
|
||||
}
|
||||
line = Common::Trim(line);
|
||||
if (line.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
Config::KV2Map(&all_params, line.c_str());
|
||||
}
|
||||
} else {
|
||||
config_file_ok = false;
|
||||
}
|
||||
}
|
||||
Config::SetVerbosity(all_params);
|
||||
// de-duplicate params
|
||||
Config::KeepFirstValues(all_params, ¶ms);
|
||||
if (!config_file_ok) {
|
||||
Log::Warning("Config file %s doesn't exist, will ignore", params["config"].c_str());
|
||||
}
|
||||
ParameterAlias::KeyAliasTransform(¶ms);
|
||||
config_.Set(params);
|
||||
Log::Info("Finished loading parameters");
|
||||
}
|
||||
|
||||
void Application::LoadData() {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
std::unique_ptr<Predictor> predictor;
|
||||
// prediction is needed if using input initial model(continued train)
|
||||
PredictFunction predict_fun = nullptr;
|
||||
// need to continue training
|
||||
if (boosting_->NumberOfTotalModel() > 0 && config_.task != TaskType::KRefitTree) {
|
||||
predictor.reset(new Predictor(boosting_.get(), 0, -1, true, false, false, false, -1, -1));
|
||||
predict_fun = predictor->GetPredictFunction();
|
||||
}
|
||||
|
||||
// sync up random seed for data partition
|
||||
if (config_.is_data_based_parallel) {
|
||||
config_.data_random_seed = Network::GlobalSyncUpByMin(config_.data_random_seed);
|
||||
}
|
||||
|
||||
Log::Debug("Loading train file...");
|
||||
DatasetLoader dataset_loader(config_, predict_fun,
|
||||
config_.num_class, config_.data.c_str());
|
||||
// load Training data
|
||||
if (config_.is_data_based_parallel) {
|
||||
// load data for distributed training
|
||||
train_data_.reset(dataset_loader.LoadFromFile(config_.data.c_str(),
|
||||
Network::rank(), Network::num_machines()));
|
||||
} else {
|
||||
// load data for single machine
|
||||
train_data_.reset(dataset_loader.LoadFromFile(config_.data.c_str(), 0, 1));
|
||||
}
|
||||
// need save binary file
|
||||
if (config_.save_binary) {
|
||||
train_data_->SaveBinaryFile(nullptr);
|
||||
}
|
||||
// create training metric
|
||||
if (config_.is_provide_training_metric) {
|
||||
for (auto metric_type : config_.metric) {
|
||||
auto metric = std::unique_ptr<Metric>(Metric::CreateMetric(metric_type, config_));
|
||||
if (metric == nullptr) {
|
||||
continue;
|
||||
}
|
||||
metric->Init(train_data_->metadata(), train_data_->num_data());
|
||||
train_metric_.push_back(std::move(metric));
|
||||
}
|
||||
}
|
||||
train_metric_.shrink_to_fit();
|
||||
|
||||
if (!config_.metric.empty()) {
|
||||
// only when have metrics then need to construct validation data
|
||||
|
||||
// Add validation data, if it exists
|
||||
for (size_t i = 0; i < config_.valid.size(); ++i) {
|
||||
Log::Debug("Loading validation file #%zu...", (i + 1));
|
||||
// add
|
||||
auto new_dataset = std::unique_ptr<Dataset>(
|
||||
dataset_loader.LoadFromFileAlignWithOtherDataset(
|
||||
config_.valid[i].c_str(),
|
||||
train_data_.get()));
|
||||
valid_datas_.push_back(std::move(new_dataset));
|
||||
// need save binary file
|
||||
if (config_.save_binary) {
|
||||
valid_datas_.back()->SaveBinaryFile(nullptr);
|
||||
}
|
||||
|
||||
// add metric for validation data
|
||||
valid_metrics_.emplace_back();
|
||||
for (auto metric_type : config_.metric) {
|
||||
auto metric = std::unique_ptr<Metric>(Metric::CreateMetric(metric_type, config_));
|
||||
if (metric == nullptr) {
|
||||
continue;
|
||||
}
|
||||
metric->Init(valid_datas_.back()->metadata(),
|
||||
valid_datas_.back()->num_data());
|
||||
valid_metrics_.back().push_back(std::move(metric));
|
||||
}
|
||||
valid_metrics_.back().shrink_to_fit();
|
||||
}
|
||||
valid_datas_.shrink_to_fit();
|
||||
valid_metrics_.shrink_to_fit();
|
||||
}
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
// output used time on each iteration
|
||||
Log::Info("Finished loading data in %f seconds",
|
||||
std::chrono::duration<double, std::milli>(end_time - start_time) * 1e-3);
|
||||
}
|
||||
|
||||
void Application::InitTrain() {
|
||||
if (config_.is_parallel) {
|
||||
// need init network
|
||||
Network::Init(config_);
|
||||
Log::Info("Finished initializing network");
|
||||
config_.feature_fraction_seed =
|
||||
Network::GlobalSyncUpByMin(config_.feature_fraction_seed);
|
||||
config_.feature_fraction =
|
||||
Network::GlobalSyncUpByMin(config_.feature_fraction);
|
||||
config_.drop_seed =
|
||||
Network::GlobalSyncUpByMin(config_.drop_seed);
|
||||
}
|
||||
|
||||
// create boosting
|
||||
boosting_.reset(
|
||||
Boosting::CreateBoosting(config_.boosting,
|
||||
config_.input_model.c_str(), config_.device_type, config_.num_gpu));
|
||||
// create objective function
|
||||
objective_fun_.reset(
|
||||
ObjectiveFunction::CreateObjectiveFunction(config_.objective,
|
||||
config_));
|
||||
// load training data
|
||||
LoadData();
|
||||
if (config_.task == TaskType::kSaveBinary) {
|
||||
Log::Info("Save data as binary finished, exit");
|
||||
exit(0);
|
||||
}
|
||||
// initialize the objective function
|
||||
objective_fun_->Init(train_data_->metadata(), train_data_->num_data());
|
||||
// initialize the boosting
|
||||
boosting_->Init(&config_, train_data_.get(), objective_fun_.get(),
|
||||
Common::ConstPtrInVectorWrapper<Metric>(train_metric_));
|
||||
// add validation data into boosting
|
||||
for (size_t i = 0; i < valid_datas_.size(); ++i) {
|
||||
boosting_->AddValidDataset(valid_datas_[i].get(),
|
||||
Common::ConstPtrInVectorWrapper<Metric>(valid_metrics_[i]));
|
||||
Log::Debug("Number of data points in validation set #%zu: %d", i + 1, valid_datas_[i]->num_data());
|
||||
}
|
||||
Log::Info("Finished initializing training");
|
||||
}
|
||||
|
||||
void Application::Train() {
|
||||
Log::Info("Started training...");
|
||||
boosting_->Train(config_.snapshot_freq, config_.output_model);
|
||||
boosting_->SaveModelToFile(0, -1, config_.saved_feature_importance_type,
|
||||
config_.output_model.c_str());
|
||||
// convert model to if-else statement code
|
||||
if (config_.convert_model_language == std::string("cpp")) {
|
||||
boosting_->SaveModelToIfElse(-1, config_.convert_model.c_str());
|
||||
}
|
||||
Log::Info("Finished training");
|
||||
}
|
||||
|
||||
void Application::Predict() {
|
||||
if (config_.task == TaskType::KRefitTree) {
|
||||
// create predictor
|
||||
Predictor predictor(boosting_.get(), 0, -1, false, true, false, false, 1, 1);
|
||||
predictor.Predict(config_.data.c_str(), config_.output_result.c_str(), config_.header, config_.predict_disable_shape_check,
|
||||
config_.precise_float_parser);
|
||||
TextReader<int> result_reader(config_.output_result.c_str(), false);
|
||||
result_reader.ReadAllLines();
|
||||
|
||||
size_t nrow = result_reader.Lines().size();
|
||||
size_t ncol = 0;
|
||||
if (nrow > 0) {
|
||||
ncol = Common::StringToArray<int>(result_reader.Lines()[0], '\t').size();
|
||||
}
|
||||
std::vector<int> pred_leaf;
|
||||
pred_leaf.resize(nrow * ncol);
|
||||
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int irow = 0; irow < static_cast<int>(nrow); ++irow) {
|
||||
auto line_vec = Common::StringToArray<int>(result_reader.Lines()[irow], '\t');
|
||||
CHECK_EQ(line_vec.size(), ncol);
|
||||
for (int i_row_item = 0; i_row_item < static_cast<int>(ncol); ++i_row_item) {
|
||||
pred_leaf[irow * ncol + i_row_item] = line_vec[i_row_item];
|
||||
}
|
||||
// Free memory
|
||||
result_reader.Lines()[irow].clear();
|
||||
}
|
||||
DatasetLoader dataset_loader(config_, nullptr,
|
||||
config_.num_class, config_.data.c_str());
|
||||
train_data_.reset(dataset_loader.LoadFromFile(config_.data.c_str(), 0, 1));
|
||||
train_metric_.clear();
|
||||
objective_fun_.reset(ObjectiveFunction::CreateObjectiveFunction(config_.objective,
|
||||
config_));
|
||||
objective_fun_->Init(train_data_->metadata(), train_data_->num_data());
|
||||
boosting_->Init(&config_, train_data_.get(), objective_fun_.get(),
|
||||
Common::ConstPtrInVectorWrapper<Metric>(train_metric_));
|
||||
|
||||
boosting_->RefitTree(pred_leaf.data(), nrow, ncol);
|
||||
boosting_->SaveModelToFile(0, -1, config_.saved_feature_importance_type,
|
||||
config_.output_model.c_str());
|
||||
Log::Info("Finished RefitTree");
|
||||
} else {
|
||||
// create predictor
|
||||
Predictor predictor(boosting_.get(), config_.start_iteration_predict, config_.num_iteration_predict, config_.predict_raw_score,
|
||||
config_.predict_leaf_index, config_.predict_contrib,
|
||||
config_.pred_early_stop, config_.pred_early_stop_freq,
|
||||
config_.pred_early_stop_margin);
|
||||
predictor.Predict(config_.data.c_str(),
|
||||
config_.output_result.c_str(), config_.header, config_.predict_disable_shape_check,
|
||||
config_.precise_float_parser);
|
||||
Log::Info("Finished prediction");
|
||||
}
|
||||
}
|
||||
|
||||
void Application::InitPredict() {
|
||||
boosting_.reset(
|
||||
Boosting::CreateBoosting("gbdt", config_.input_model.c_str(), config_.device_type, config_.num_gpu));
|
||||
Log::Info("Finished initializing prediction, total used %d iterations", boosting_->GetCurrentIteration());
|
||||
}
|
||||
|
||||
void Application::ConvertModel() {
|
||||
boosting_.reset(
|
||||
Boosting::CreateBoosting(config_.boosting, config_.input_model.c_str(), config_.device_type, config_.num_gpu));
|
||||
boosting_->SaveModelToIfElse(-1, config_.convert_model.c_str());
|
||||
}
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,303 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_APPLICATION_PREDICTOR_HPP_
|
||||
#define LIGHTGBM_SRC_APPLICATION_PREDICTOR_HPP_
|
||||
|
||||
#include <LightGBM/boosting.h>
|
||||
#include <LightGBM/dataset.h>
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
#include <LightGBM/utils/text_reader.h>
|
||||
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
/*!
|
||||
* \brief Used to predict data with input model
|
||||
*/
|
||||
class Predictor {
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructor
|
||||
* \param boosting Input boosting model
|
||||
* \param start_iteration Start index of the iteration to predict
|
||||
* \param num_iteration Number of boosting round
|
||||
* \param is_raw_score True if need to predict result with raw score
|
||||
* \param predict_leaf_index True to output leaf index instead of prediction score
|
||||
* \param predict_contrib True to output feature contributions instead of prediction score
|
||||
*/
|
||||
Predictor(Boosting* boosting, int start_iteration, int num_iteration, bool is_raw_score,
|
||||
bool predict_leaf_index, bool predict_contrib, bool early_stop,
|
||||
int early_stop_freq, double early_stop_margin) {
|
||||
early_stop_ = CreatePredictionEarlyStopInstance(
|
||||
"none", LightGBM::PredictionEarlyStopConfig());
|
||||
if (early_stop && !boosting->NeedAccuratePrediction()) {
|
||||
PredictionEarlyStopConfig pred_early_stop_config;
|
||||
CHECK_GT(early_stop_freq, 0);
|
||||
CHECK_GE(early_stop_margin, 0);
|
||||
pred_early_stop_config.margin_threshold = early_stop_margin;
|
||||
pred_early_stop_config.round_period = early_stop_freq;
|
||||
if (boosting->NumberOfClasses() == 1) {
|
||||
early_stop_ =
|
||||
CreatePredictionEarlyStopInstance("binary", pred_early_stop_config);
|
||||
} else {
|
||||
early_stop_ = CreatePredictionEarlyStopInstance("multiclass",
|
||||
pred_early_stop_config);
|
||||
}
|
||||
}
|
||||
|
||||
boosting->InitPredict(start_iteration, num_iteration, predict_contrib);
|
||||
boosting_ = boosting;
|
||||
num_pred_one_row_ = boosting_->NumPredictOneRow(start_iteration,
|
||||
num_iteration, predict_leaf_index, predict_contrib);
|
||||
num_feature_ = boosting_->MaxFeatureIdx() + 1;
|
||||
predict_buf_.resize(
|
||||
OMP_NUM_THREADS(),
|
||||
std::vector<double, Common::AlignmentAllocator<double, kAlignedSize>>(
|
||||
num_feature_, 0.0f));
|
||||
const int kFeatureThreshold = 100000;
|
||||
const size_t KSparseThreshold = static_cast<size_t>(0.01 * num_feature_);
|
||||
if (predict_leaf_index) {
|
||||
predict_fun_ = [=](const std::vector<std::pair<int, double>>& features,
|
||||
double* output) {
|
||||
int tid = omp_get_thread_num();
|
||||
if (num_feature_ > kFeatureThreshold &&
|
||||
features.size() < KSparseThreshold) {
|
||||
auto buf = CopyToPredictMap(features);
|
||||
boosting_->PredictLeafIndexByMap(buf, output);
|
||||
} else {
|
||||
CopyToPredictBuffer(predict_buf_[tid].data(), features);
|
||||
// get result for leaf index
|
||||
boosting_->PredictLeafIndex(predict_buf_[tid].data(), output);
|
||||
ClearPredictBuffer(predict_buf_[tid].data(), predict_buf_[tid].size(),
|
||||
features);
|
||||
}
|
||||
};
|
||||
} else if (predict_contrib) {
|
||||
if (boosting_->IsLinear()) {
|
||||
Log::Fatal("Predicting SHAP feature contributions is not implemented for linear trees.");
|
||||
}
|
||||
predict_fun_ = [=](const std::vector<std::pair<int, double>>& features,
|
||||
double* output) {
|
||||
int tid = omp_get_thread_num();
|
||||
CopyToPredictBuffer(predict_buf_[tid].data(), features);
|
||||
// get feature importances
|
||||
boosting_->PredictContrib(predict_buf_[tid].data(), output);
|
||||
ClearPredictBuffer(predict_buf_[tid].data(), predict_buf_[tid].size(),
|
||||
features);
|
||||
};
|
||||
predict_sparse_fun_ = [=](const std::vector<std::pair<int, double>>& features,
|
||||
std::vector<std::unordered_map<int, double>>* output) {
|
||||
auto buf = CopyToPredictMap(features);
|
||||
// get sparse feature importances
|
||||
boosting_->PredictContribByMap(buf, output);
|
||||
};
|
||||
|
||||
} else {
|
||||
if (is_raw_score) {
|
||||
predict_fun_ = [=](const std::vector<std::pair<int, double>>& features,
|
||||
double* output) {
|
||||
int tid = omp_get_thread_num();
|
||||
if (num_feature_ > kFeatureThreshold &&
|
||||
features.size() < KSparseThreshold) {
|
||||
auto buf = CopyToPredictMap(features);
|
||||
boosting_->PredictRawByMap(buf, output, &early_stop_);
|
||||
} else {
|
||||
CopyToPredictBuffer(predict_buf_[tid].data(), features);
|
||||
boosting_->PredictRaw(predict_buf_[tid].data(), output,
|
||||
&early_stop_);
|
||||
ClearPredictBuffer(predict_buf_[tid].data(),
|
||||
predict_buf_[tid].size(), features);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
predict_fun_ = [=](const std::vector<std::pair<int, double>>& features,
|
||||
double* output) {
|
||||
int tid = omp_get_thread_num();
|
||||
if (num_feature_ > kFeatureThreshold &&
|
||||
features.size() < KSparseThreshold) {
|
||||
auto buf = CopyToPredictMap(features);
|
||||
boosting_->PredictByMap(buf, output, &early_stop_);
|
||||
} else {
|
||||
CopyToPredictBuffer(predict_buf_[tid].data(), features);
|
||||
boosting_->Predict(predict_buf_[tid].data(), output, &early_stop_);
|
||||
ClearPredictBuffer(predict_buf_[tid].data(),
|
||||
predict_buf_[tid].size(), features);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Destructor
|
||||
*/
|
||||
~Predictor() {
|
||||
}
|
||||
|
||||
inline const PredictFunction& GetPredictFunction() const {
|
||||
return predict_fun_;
|
||||
}
|
||||
|
||||
|
||||
inline const PredictSparseFunction& GetPredictSparseFunction() const {
|
||||
return predict_sparse_fun_;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief predicting on data, then saving result to disk
|
||||
* \param data_filename Filename of data
|
||||
* \param result_filename Filename of output result
|
||||
*/
|
||||
void Predict(const char* data_filename, const char* result_filename, bool header, bool disable_shape_check, bool precise_float_parser) {
|
||||
auto writer = VirtualFileWriter::Make(result_filename);
|
||||
if (!writer->Init()) {
|
||||
Log::Fatal("Prediction results file %s cannot be created", result_filename);
|
||||
}
|
||||
auto label_idx = header ? -1 : boosting_->LabelIdx();
|
||||
auto parser = std::unique_ptr<Parser>(Parser::CreateParser(data_filename, header, boosting_->MaxFeatureIdx() + 1, label_idx,
|
||||
precise_float_parser, boosting_->ParserConfigStr()));
|
||||
|
||||
if (parser == nullptr) {
|
||||
Log::Fatal("Could not recognize the data format of data file %s", data_filename);
|
||||
}
|
||||
if (!header && !disable_shape_check && parser->NumFeatures() != boosting_->MaxFeatureIdx() + 1) {
|
||||
Log::Fatal("The number of features in data (%d) is not the same as it was in training data (%d).\n" \
|
||||
"You can set ``predict_disable_shape_check=true`` to discard this error, but please be aware what you are doing.", parser->NumFeatures(), boosting_->MaxFeatureIdx() + 1);
|
||||
}
|
||||
TextReader<data_size_t> predict_data_reader(data_filename, header);
|
||||
std::vector<int> feature_remapper(parser->NumFeatures(), -1);
|
||||
bool need_adjust = false;
|
||||
// skip raw feature remapping if trained model has parser config str which may contain actual feature names.
|
||||
if (header && boosting_->ParserConfigStr().empty()) {
|
||||
std::string first_line = predict_data_reader.first_line();
|
||||
std::vector<std::string> header_words = Common::Split(first_line.c_str(), "\t,");
|
||||
std::unordered_map<std::string, int> header_mapper;
|
||||
for (int i = 0; i < static_cast<int>(header_words.size()); ++i) {
|
||||
if (header_mapper.count(header_words[i]) > 0) {
|
||||
Log::Fatal("Feature (%s) appears more than one time.", header_words[i].c_str());
|
||||
}
|
||||
header_mapper[header_words[i]] = i;
|
||||
}
|
||||
const auto& fnames = boosting_->FeatureNames();
|
||||
for (int i = 0; i < static_cast<int>(fnames.size()); ++i) {
|
||||
if (header_mapper.count(fnames[i]) <= 0) {
|
||||
Log::Warning("Feature (%s) is missed in data file. If it is weight/query/group/ignore_column, you can ignore this warning.", fnames[i].c_str());
|
||||
} else {
|
||||
feature_remapper[header_mapper.at(fnames[i])] = i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(feature_remapper.size()); ++i) {
|
||||
if (feature_remapper[i] >= 0 && i != feature_remapper[i]) {
|
||||
need_adjust = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// function for parse data
|
||||
std::function<void(const char*, std::vector<std::pair<int, double>>*)> parser_fun;
|
||||
double tmp_label;
|
||||
parser_fun = [&parser, &feature_remapper, &tmp_label, need_adjust]
|
||||
(const char* buffer, std::vector<std::pair<int, double>>* feature) {
|
||||
parser->ParseOneLine(buffer, feature, &tmp_label);
|
||||
if (need_adjust) {
|
||||
int i = 0, j = static_cast<int>(feature->size());
|
||||
while (i < j) {
|
||||
if (feature_remapper[(*feature)[i].first] >= 0) {
|
||||
(*feature)[i].first = feature_remapper[(*feature)[i].first];
|
||||
++i;
|
||||
} else {
|
||||
// move the non-used features to the end of the feature vector
|
||||
std::swap((*feature)[i], (*feature)[--j]);
|
||||
}
|
||||
}
|
||||
feature->resize(i);
|
||||
}
|
||||
};
|
||||
|
||||
std::function<void(data_size_t, const std::vector<std::string>&)>
|
||||
process_fun = [&parser_fun, &writer, this](
|
||||
data_size_t, const std::vector<std::string>& lines) {
|
||||
std::vector<std::pair<int, double>> oneline_features;
|
||||
std::vector<std::string> result_to_write(lines.size());
|
||||
OMP_INIT_EX();
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(oneline_features)
|
||||
for (data_size_t i = 0; i < static_cast<data_size_t>(lines.size()); ++i) {
|
||||
OMP_LOOP_EX_BEGIN();
|
||||
oneline_features.clear();
|
||||
// parser
|
||||
parser_fun(lines[i].c_str(), &oneline_features);
|
||||
// predict
|
||||
std::vector<double> result(num_pred_one_row_);
|
||||
predict_fun_(oneline_features, result.data());
|
||||
auto str_result = Common::Join<double>(result, "\t");
|
||||
result_to_write[i] = str_result;
|
||||
OMP_LOOP_EX_END();
|
||||
}
|
||||
OMP_THROW_EX();
|
||||
for (data_size_t i = 0; i < static_cast<data_size_t>(result_to_write.size()); ++i) {
|
||||
writer->Write(result_to_write[i].c_str(), result_to_write[i].size());
|
||||
writer->Write("\n", 1);
|
||||
}
|
||||
};
|
||||
predict_data_reader.ReadAllAndProcessParallel(process_fun);
|
||||
}
|
||||
|
||||
private:
|
||||
void CopyToPredictBuffer(double* pred_buf, const std::vector<std::pair<int, double>>& features) {
|
||||
for (const auto &feature : features) {
|
||||
if (feature.first < num_feature_) {
|
||||
pred_buf[feature.first] = feature.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClearPredictBuffer(double* pred_buf, size_t buf_size, const std::vector<std::pair<int, double>>& features) {
|
||||
if (features.size() > static_cast<size_t>(buf_size / 2)) {
|
||||
std::memset(pred_buf, 0, sizeof(double)*(buf_size));
|
||||
} else {
|
||||
for (const auto &feature : features) {
|
||||
if (feature.first < num_feature_) {
|
||||
pred_buf[feature.first] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<int, double> CopyToPredictMap(const std::vector<std::pair<int, double>>& features) {
|
||||
std::unordered_map<int, double> buf;
|
||||
for (const auto &feature : features) {
|
||||
if (feature.first < num_feature_) {
|
||||
buf[feature.first] = feature.second;
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*! \brief Boosting model */
|
||||
const Boosting* boosting_;
|
||||
/*! \brief function for prediction */
|
||||
PredictFunction predict_fun_;
|
||||
PredictSparseFunction predict_sparse_fun_;
|
||||
PredictionEarlyStopInstance early_stop_;
|
||||
int num_feature_;
|
||||
int num_pred_one_row_;
|
||||
std::vector<std::vector<double, Common::AlignmentAllocator<double, kAlignedSize>>> predict_buf_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_APPLICATION_PREDICTOR_HPP_
|
||||
@@ -0,0 +1,413 @@
|
||||
/*!
|
||||
* Copyright (c) 2026-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*
|
||||
* Author: Oliver Borchert
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_ARROW_ARRAY_HPP_
|
||||
#define LIGHTGBM_SRC_ARROW_ARRAY_HPP_
|
||||
|
||||
#include <LightGBM/arrow.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <nanoarrow/nanoarrow.hpp>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
/* ------------------------------------------- UTILS ------------------------------------------- */
|
||||
|
||||
inline struct ArrowSchemaView MakeSchemaView(const struct ArrowSchema* schema) {
|
||||
struct ArrowSchemaView view;
|
||||
struct ArrowError error;
|
||||
auto ret = ArrowSchemaViewInit(&view, schema, &error);
|
||||
if (ret != NANOARROW_OK) {
|
||||
throw nanoarrow::Exception("Failed to initialize ArrowSchemaView: " +
|
||||
std::string(ArrowErrorMessage(&error)));
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
/* CHUNKED ARRAY */
|
||||
/* --------------------------------------------------------------------------------------------- */
|
||||
|
||||
class ArrowChunkedArray {
|
||||
enum ArrowType type_;
|
||||
nanoarrow::UniqueSchema schema_;
|
||||
std::vector<nanoarrow::UniqueArray> chunks_;
|
||||
|
||||
public:
|
||||
class View;
|
||||
template <typename ArrowT, typename OutputT>
|
||||
class Visitor;
|
||||
template <typename ArrowT, typename OutputT>
|
||||
class Iterator;
|
||||
|
||||
/**
|
||||
* @brief Construct a new chunked Arrow array from an array stream.
|
||||
* The stream is consumed and the chunked Arrow array takes ownership of the schema and all
|
||||
* chunks. Upon destruction, the release callback is called for the schema and all chunks.
|
||||
*
|
||||
* @param stream The Arrow array stream to consume.
|
||||
*/
|
||||
explicit ArrowChunkedArray(ArrowArrayStream* stream) {
|
||||
nanoarrow::UniqueArrayStream stream_(stream);
|
||||
|
||||
// Extract the schema
|
||||
auto ret = stream_->get_schema(stream_.get(), schema_.get());
|
||||
if (ret != NANOARROW_OK) {
|
||||
throw nanoarrow::Exception("Failed to get schema from Arrow array stream: " +
|
||||
std::string(stream_->get_last_error(stream_.get())));
|
||||
}
|
||||
|
||||
// Turn the schema into a type
|
||||
type_ = MakeSchemaView(schema_.get()).type;
|
||||
|
||||
// Extract all chunks
|
||||
while (true) {
|
||||
nanoarrow::UniqueArray chunk;
|
||||
auto ret = stream_->get_next(stream_.get(), chunk.get());
|
||||
if (ret != NANOARROW_OK) {
|
||||
throw nanoarrow::Exception("Failed to get next chunk from Arrow array stream: " +
|
||||
std::string(stream_->get_last_error(stream_.get())));
|
||||
}
|
||||
if (chunk->release == nullptr) break;
|
||||
chunks_.emplace_back(std::move(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a new chunked Arrow array from a list of Arrow arrays and a schema.
|
||||
* The chunked Arrow array takes ownership of the schema and all chunks. Upon destruction,
|
||||
* the release callback is called for the schema and all chunks.
|
||||
*
|
||||
* @param n_chunks The number of Arrow arrays.
|
||||
* @param chunks Pointer to the list of Arrow arrays.
|
||||
* @param schema Pointer to the schema of all Arrow arrays.
|
||||
*/
|
||||
explicit ArrowChunkedArray(int64_t n_chunks, struct ArrowArray* chunks,
|
||||
struct ArrowSchema* schema) {
|
||||
// Take ownership of schema
|
||||
schema_ = nanoarrow::UniqueSchema(schema);
|
||||
type_ = MakeSchemaView(schema_.get()).type;
|
||||
|
||||
// Take ownership of chunks
|
||||
chunks_.reserve(n_chunks);
|
||||
for (int64_t i = 0; i < n_chunks; ++i) {
|
||||
chunks_.emplace_back(&chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Whether the chunked array is a struct and has multiple fields.
|
||||
* A struct chunked array is typically interpreted as a table.
|
||||
*/
|
||||
bool is_struct() const { return type_ == NANOARROW_TYPE_STRUCT; }
|
||||
|
||||
/**
|
||||
* @brief Get the length of the chunked array as the sum of all chunk lengths.
|
||||
*
|
||||
* @return int64_t The total number of elements.
|
||||
*/
|
||||
int64_t get_length() const {
|
||||
int64_t length = 0;
|
||||
for (const auto& chunk : chunks_) {
|
||||
length += chunk->length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the number of fields in the chunked array.
|
||||
*
|
||||
* @return int64_t The number of fields.
|
||||
*/
|
||||
int64_t get_num_fields() const {
|
||||
if (!is_struct()) {
|
||||
Log::Fatal("Expected struct type for array, got %s", ArrowTypeString(type_));
|
||||
}
|
||||
return schema_.get()->n_children;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtain a view on the chunked array to visit its values.
|
||||
*
|
||||
* @return View The view on the array.
|
||||
*/
|
||||
View view() const {
|
||||
std::vector<const struct ArrowArray*> chunk_ptrs;
|
||||
chunk_ptrs.reserve(chunks_.size());
|
||||
for (const auto& chunk : chunks_) {
|
||||
// Skip empty chunks to avoid additional complexity in the iterator
|
||||
if (chunk->length == 0) continue;
|
||||
chunk_ptrs.push_back(chunk.get());
|
||||
}
|
||||
return View(type_, schema_.get(), std::move(chunk_ptrs));
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
/* VIEW */
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
|
||||
class View {
|
||||
friend class ArrowChunkedArray;
|
||||
|
||||
enum ArrowType type_;
|
||||
const struct ArrowSchema* schema_;
|
||||
std::vector<const struct ArrowArray*> chunks_;
|
||||
|
||||
View(enum ArrowType type, const struct ArrowSchema* schema,
|
||||
std::vector<const struct ArrowArray*> chunks)
|
||||
: type_(type), schema_(schema), chunks_(std::move(chunks)) {}
|
||||
|
||||
public:
|
||||
explicit View(std::vector<View> views) {
|
||||
type_ = views[0].type_;
|
||||
schema_ = views[0].schema_;
|
||||
for (auto it = views.begin(), end = views.end(); it != end; ++it) {
|
||||
if (it->type_ != type_) {
|
||||
Log::Fatal("All views must have the same type, but got %s and %s",
|
||||
ArrowTypeString(it->type_), ArrowTypeString(type_));
|
||||
}
|
||||
chunks_.insert(chunks_.end(), it->chunks_.begin(), it->chunks_.end());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Obtain a view on the field at the given index.
|
||||
* This method assumes that the view has a type "struct".
|
||||
*
|
||||
* @param field_idx The index of the field to view.
|
||||
* @return View A view on the field at the given index.
|
||||
*/
|
||||
View field(int64_t field_idx) const {
|
||||
if (type_ != NANOARROW_TYPE_STRUCT) {
|
||||
Log::Fatal("Expected struct type for array, got %s", ArrowTypeString(type_));
|
||||
}
|
||||
|
||||
std::vector<const struct ArrowArray*> chunk_ptrs;
|
||||
chunk_ptrs.reserve(chunks_.size());
|
||||
for (const auto& chunk : chunks_) {
|
||||
chunk_ptrs.push_back(chunk->children[field_idx]);
|
||||
}
|
||||
|
||||
auto type = MakeSchemaView(schema_->children[field_idx]).type;
|
||||
return View(type, schema_->children[field_idx], std::move(chunk_ptrs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Visit the chunked array with a visitor created from the schema type.
|
||||
* The visitor allows accessing all values from the chunked array, casting to the desired
|
||||
* output type.
|
||||
*
|
||||
* @tparam OutputT The desired output type.
|
||||
* @tparam F The type of the visitor function, which must be invocable with a
|
||||
* `Visitor<ArrowT, OutputT>`
|
||||
* @param f The visitor function to invoke with the created visitor.
|
||||
* @return decltype(auto) The result of invoking the visitor function with the created visitor.
|
||||
*/
|
||||
template <typename OutputT, typename F>
|
||||
decltype(auto) visit(F&& f) const {
|
||||
// Switch on the schema type and construct the appropriate visitor
|
||||
switch (type_) {
|
||||
case NANOARROW_TYPE_INT8:
|
||||
return f(Visitor<int8_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_INT16:
|
||||
return f(Visitor<int16_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_INT32:
|
||||
return f(Visitor<int32_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_INT64:
|
||||
return f(Visitor<int64_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_UINT8:
|
||||
return f(Visitor<uint8_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_UINT16:
|
||||
return f(Visitor<uint16_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_UINT32:
|
||||
return f(Visitor<uint32_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_UINT64:
|
||||
return f(Visitor<uint64_t, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_FLOAT:
|
||||
return f(Visitor<float, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_DOUBLE:
|
||||
return f(Visitor<double, OutputT>(chunks_));
|
||||
case NANOARROW_TYPE_BOOL:
|
||||
return f(Visitor<bool, OutputT>(chunks_));
|
||||
default:
|
||||
Log::Fatal("Unsupported Arrow type: %s", ArrowTypeString(type_));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
/* VISITOR */
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
|
||||
template <typename ArrowT, typename OutputT>
|
||||
class Visitor {
|
||||
friend class View;
|
||||
friend class Iterator<ArrowT, OutputT>;
|
||||
|
||||
std::vector<const struct ArrowArray*> chunks_;
|
||||
std::vector<int64_t> chunk_offsets_;
|
||||
|
||||
explicit Visitor(std::vector<const struct ArrowArray*> chunks) : chunks_(chunks) {
|
||||
// Derive chunk offsets
|
||||
chunk_offsets_.reserve(chunks.size() + 1);
|
||||
chunk_offsets_.push_back(0);
|
||||
for (const auto& chunk : chunks) {
|
||||
chunk_offsets_.push_back(chunk_offsets_.back() + chunk->length);
|
||||
}
|
||||
}
|
||||
|
||||
ArrowT is_valid(int64_t chunk_idx, int64_t element_idx) const {
|
||||
auto arr = chunks_[chunk_idx];
|
||||
return arr->buffers[0] == nullptr ||
|
||||
ArrowBitGet(static_cast<const uint8_t*>(arr->buffers[0]), arr->offset + element_idx);
|
||||
}
|
||||
|
||||
ArrowT get(int64_t chunk_idx, int64_t element_idx) const {
|
||||
auto arr = chunks_[chunk_idx];
|
||||
auto idx = arr->offset + element_idx;
|
||||
if constexpr (std::is_same_v<ArrowT, bool>) {
|
||||
return ArrowBitGet(static_cast<const uint8_t*>(arr->buffers[1]), idx);
|
||||
} else {
|
||||
return static_cast<const ArrowT*>(arr->buffers[1])[idx];
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
Iterator<ArrowT, OutputT> begin() const { return Iterator<ArrowT, OutputT>(*this, 0, 0); }
|
||||
Iterator<ArrowT, OutputT> end() const {
|
||||
return Iterator<ArrowT, OutputT>(*this, chunks_.size(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
/* ITERATOR */
|
||||
/* ------------------------------------------------------------------------------------------- */
|
||||
|
||||
template <typename ArrowT, typename OutputT>
|
||||
class Iterator {
|
||||
friend class Visitor<ArrowT, OutputT>;
|
||||
|
||||
const Visitor<ArrowT, OutputT>& visitor_;
|
||||
int64_t chunk_idx_;
|
||||
int64_t element_idx_;
|
||||
|
||||
Iterator(const Visitor<ArrowT, OutputT>& visitor, int64_t chunk_idx, int64_t element_idx)
|
||||
: visitor_(visitor), chunk_idx_(chunk_idx), element_idx_(element_idx) {}
|
||||
|
||||
int64_t full_offset() const { return visitor_.chunk_offsets_[chunk_idx_] + element_idx_; }
|
||||
|
||||
public:
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
using difference_type = int64_t;
|
||||
using value_type = OutputT;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
|
||||
/* --------------------------------------- OPERATORS --------------------------------------- */
|
||||
|
||||
public:
|
||||
Iterator<ArrowT, OutputT>& operator++() {
|
||||
if (element_idx_ + 1 >= visitor_.chunks_[chunk_idx_]->length) {
|
||||
element_idx_ = 0;
|
||||
chunk_idx_++;
|
||||
} else {
|
||||
element_idx_++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator<ArrowT, OutputT>& operator+=(int64_t c) {
|
||||
while (element_idx_ + c >= visitor_.chunks_[chunk_idx_]->length) {
|
||||
c -= visitor_.chunks_[chunk_idx_]->length - element_idx_;
|
||||
element_idx_ = 0;
|
||||
chunk_idx_++;
|
||||
}
|
||||
element_idx_ += c;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator<ArrowT, OutputT>& operator--() {
|
||||
if (element_idx_ == 0) {
|
||||
chunk_idx_--;
|
||||
element_idx_ = visitor_.chunks_[chunk_idx_]->length - 1;
|
||||
} else {
|
||||
element_idx_--;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Iterator<ArrowT, OutputT>& operator-=(int64_t c) {
|
||||
while (c > element_idx_) {
|
||||
c -= element_idx_ + 1;
|
||||
chunk_idx_--;
|
||||
element_idx_ = visitor_.chunks_[chunk_idx_]->length - 1;
|
||||
}
|
||||
element_idx_ -= c;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend int64_t operator-(const Iterator<ArrowT, OutputT>& a,
|
||||
const Iterator<ArrowT, OutputT>& b) {
|
||||
auto full_offset_a = a.full_offset();
|
||||
auto full_offset_b = b.full_offset();
|
||||
return full_offset_a - full_offset_b;
|
||||
}
|
||||
|
||||
friend bool operator==(const Iterator<ArrowT, OutputT>& a,
|
||||
const Iterator<ArrowT, OutputT>& b) {
|
||||
return a.chunk_idx_ == b.chunk_idx_ && a.element_idx_ == b.element_idx_;
|
||||
}
|
||||
|
||||
friend bool operator!=(const Iterator<ArrowT, OutputT>& a,
|
||||
const Iterator<ArrowT, OutputT>& b) {
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/* ----------------------------------------- VALUE ----------------------------------------- */
|
||||
|
||||
private:
|
||||
static constexpr OutputT null_default() {
|
||||
if constexpr (std::is_floating_point_v<OutputT>) {
|
||||
return std::numeric_limits<OutputT>::quiet_NaN();
|
||||
} else {
|
||||
return OutputT{0};
|
||||
}
|
||||
}
|
||||
|
||||
OutputT get(int64_t chunk_idx, int64_t element_idx) const {
|
||||
if (visitor_.is_valid(chunk_idx, element_idx)) {
|
||||
return static_cast<OutputT>(visitor_.get(chunk_idx, element_idx));
|
||||
} else {
|
||||
return null_default();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
OutputT operator*() const { return this->get(chunk_idx_, element_idx_); }
|
||||
|
||||
OutputT operator[](int64_t c) const {
|
||||
if (visitor_.chunks_.size() == 1) {
|
||||
return this->get(0, c);
|
||||
}
|
||||
auto it =
|
||||
std::upper_bound(visitor_.chunk_offsets_.begin(), visitor_.chunk_offsets_.end(), c);
|
||||
auto chunk_idx = std::distance(visitor_.chunk_offsets_.begin(), it) - 1;
|
||||
auto element_idx = c - visitor_.chunk_offsets_[chunk_idx];
|
||||
return this->get(chunk_idx, element_idx);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
}; // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_ARROW_ARRAY_HPP_
|
||||
@@ -0,0 +1,297 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_BAGGING_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_BAGGING_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class BaggingSampleStrategy : public SampleStrategy {
|
||||
public:
|
||||
BaggingSampleStrategy(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function, int num_tree_per_iteration)
|
||||
: need_re_bagging_(false) {
|
||||
config_ = config;
|
||||
train_data_ = train_data;
|
||||
num_data_ = train_data->num_data();
|
||||
num_queries_ = train_data->metadata().num_queries();
|
||||
query_boundaries_ = train_data->metadata().query_boundaries();
|
||||
objective_function_ = objective_function;
|
||||
num_tree_per_iteration_ = num_tree_per_iteration;
|
||||
num_threads_ = OMP_NUM_THREADS();
|
||||
}
|
||||
|
||||
~BaggingSampleStrategy() {}
|
||||
|
||||
void Bagging(int iter, TreeLearner* tree_learner, score_t* /*gradients*/, score_t* /*hessians*/) override {
|
||||
Common::FunctionTimer fun_timer("GBDT::Bagging", global_timer);
|
||||
// if need bagging
|
||||
if ((bag_data_cnt_ < num_data_ && iter % config_->bagging_freq == 0) ||
|
||||
need_re_bagging_) {
|
||||
need_re_bagging_ = false;
|
||||
if (!config_->bagging_by_query) {
|
||||
auto left_cnt = bagging_runner_.Run<true>(
|
||||
num_data_,
|
||||
[=](int, data_size_t cur_start, data_size_t cur_cnt, data_size_t* left,
|
||||
data_size_t*) {
|
||||
data_size_t cur_left_count = 0;
|
||||
if (balanced_bagging_) {
|
||||
cur_left_count =
|
||||
BalancedBaggingHelper(cur_start, cur_cnt, left);
|
||||
} else {
|
||||
cur_left_count = BaggingHelper(cur_start, cur_cnt, left);
|
||||
}
|
||||
return cur_left_count;
|
||||
},
|
||||
bag_data_indices_.data());
|
||||
bag_data_cnt_ = left_cnt;
|
||||
} else {
|
||||
num_sampled_queries_ = bagging_runner_.Run<true>(
|
||||
num_queries_,
|
||||
[=](int, data_size_t cur_start, data_size_t cur_cnt, data_size_t* left,
|
||||
data_size_t*) {
|
||||
data_size_t cur_left_count = 0;
|
||||
cur_left_count = BaggingHelper(cur_start, cur_cnt, left);
|
||||
return cur_left_count;
|
||||
}, bag_query_indices_.data());
|
||||
|
||||
sampled_query_boundaries_[0] = 0;
|
||||
OMP_INIT_EX();
|
||||
#pragma omp parallel for schedule(static) num_threads(num_threads_)
|
||||
for (data_size_t i = 0; i < num_sampled_queries_; ++i) {
|
||||
OMP_LOOP_EX_BEGIN();
|
||||
sampled_query_boundaries_[i + 1] = query_boundaries_[bag_query_indices_[i] + 1] - query_boundaries_[bag_query_indices_[i]];
|
||||
OMP_LOOP_EX_END();
|
||||
}
|
||||
OMP_THROW_EX();
|
||||
|
||||
const int num_blocks = Threading::For<data_size_t>(0, num_sampled_queries_ + 1, 128, [this](int thread_index, data_size_t start_index, data_size_t end_index) {
|
||||
for (data_size_t i = start_index + 1; i < end_index; ++i) {
|
||||
sampled_query_boundaries_[i] += sampled_query_boundaries_[i - 1];
|
||||
}
|
||||
sampled_query_boundaries_thread_buffer_[thread_index] = sampled_query_boundaries_[end_index - 1];
|
||||
});
|
||||
|
||||
for (int thread_index = 1; thread_index < num_blocks; ++thread_index) {
|
||||
sampled_query_boundaries_thread_buffer_[thread_index] += sampled_query_boundaries_thread_buffer_[thread_index - 1];
|
||||
}
|
||||
|
||||
Threading::For<data_size_t>(0, num_sampled_queries_ + 1, 128, [this](int thread_index, data_size_t start_index, data_size_t end_index) {
|
||||
if (thread_index > 0) {
|
||||
for (data_size_t i = start_index; i < end_index; ++i) {
|
||||
sampled_query_boundaries_[i] += sampled_query_boundaries_thread_buffer_[thread_index - 1];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bag_data_cnt_ = sampled_query_boundaries_[num_sampled_queries_];
|
||||
|
||||
Threading::For<data_size_t>(0, num_sampled_queries_, 1, [this](int /*thread_index*/, data_size_t start_index, data_size_t end_index) {
|
||||
for (data_size_t sampled_query_id = start_index; sampled_query_id < end_index; ++sampled_query_id) {
|
||||
const data_size_t query_index = bag_query_indices_[sampled_query_id];
|
||||
const data_size_t data_index_start = query_boundaries_[query_index];
|
||||
const data_size_t data_index_end = query_boundaries_[query_index + 1];
|
||||
const data_size_t sampled_query_start = sampled_query_boundaries_[sampled_query_id];
|
||||
for (data_size_t i = data_index_start; i < data_index_end; ++i) {
|
||||
bag_data_indices_[sampled_query_start + i - data_index_start] = i;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Log::Debug("Re-bagging, using %d data to train", bag_data_cnt_);
|
||||
// set bagging data to tree learner
|
||||
if (!is_use_subset_) {
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
CopyFromHostToCUDADevice<data_size_t>(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
tree_learner->SetBaggingData(nullptr, cuda_bag_data_indices_.RawData(), bag_data_cnt_);
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
tree_learner->SetBaggingData(nullptr, bag_data_indices_.data(), bag_data_cnt_);
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
} else {
|
||||
// get subset
|
||||
tmp_subset_->ReSize(bag_data_cnt_);
|
||||
tmp_subset_->CopySubrow(train_data_, bag_data_indices_.data(),
|
||||
bag_data_cnt_, false);
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
CopyFromHostToCUDADevice<data_size_t>(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
tree_learner->SetBaggingData(tmp_subset_.get(), cuda_bag_data_indices_.RawData(),
|
||||
bag_data_cnt_);
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
tree_learner->SetBaggingData(tmp_subset_.get(), bag_data_indices_.data(),
|
||||
bag_data_cnt_);
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResetSampleConfig(const Config* config, bool is_change_dataset) override {
|
||||
need_resize_gradients_ = false;
|
||||
// if need bagging, create buffer
|
||||
data_size_t num_pos_data = 0;
|
||||
if (objective_function_ != nullptr) {
|
||||
num_pos_data = objective_function_->NumPositiveData();
|
||||
}
|
||||
bool balance_bagging_cond = (config->pos_bagging_fraction < 1.0 || config->neg_bagging_fraction < 1.0) && (num_pos_data > 0);
|
||||
if ((config->bagging_fraction < 1.0 || balance_bagging_cond) && config->bagging_freq > 0) {
|
||||
need_re_bagging_ = false;
|
||||
if (!is_change_dataset &&
|
||||
config_ != nullptr && config_->bagging_fraction == config->bagging_fraction && config_->bagging_freq == config->bagging_freq
|
||||
&& config_->pos_bagging_fraction == config->pos_bagging_fraction && config_->neg_bagging_fraction == config->neg_bagging_fraction) {
|
||||
config_ = config;
|
||||
return;
|
||||
}
|
||||
config_ = config;
|
||||
if (balance_bagging_cond) {
|
||||
balanced_bagging_ = true;
|
||||
bag_data_cnt_ = static_cast<data_size_t>(num_pos_data * config_->pos_bagging_fraction)
|
||||
+ static_cast<data_size_t>((num_data_ - num_pos_data) * config_->neg_bagging_fraction);
|
||||
} else {
|
||||
bag_data_cnt_ = static_cast<data_size_t>(config_->bagging_fraction * num_data_);
|
||||
}
|
||||
bag_data_indices_.resize(num_data_);
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
cuda_bag_data_indices_.Resize(num_data_);
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
if (!config_->bagging_by_query) {
|
||||
bagging_runner_.ReSize(num_data_);
|
||||
} else {
|
||||
bagging_runner_.ReSize(num_queries_);
|
||||
sampled_query_boundaries_.resize(num_queries_ + 1, 0);
|
||||
sampled_query_boundaries_thread_buffer_.resize(num_threads_, 0);
|
||||
bag_query_indices_.resize(num_data_);
|
||||
}
|
||||
bagging_rands_.clear();
|
||||
for (int i = 0;
|
||||
i < (num_data_ + bagging_rand_block_ - 1) / bagging_rand_block_; ++i) {
|
||||
bagging_rands_.emplace_back(config_->bagging_seed + i);
|
||||
}
|
||||
|
||||
double average_bag_rate =
|
||||
(static_cast<double>(bag_data_cnt_) / num_data_) / config_->bagging_freq;
|
||||
is_use_subset_ = false;
|
||||
if (config_->device_type != std::string("cuda")) {
|
||||
const int group_threshold_usesubset = 100;
|
||||
const double average_bag_rate_threshold = 0.5;
|
||||
if (average_bag_rate <= average_bag_rate_threshold
|
||||
&& (train_data_->num_feature_groups() < group_threshold_usesubset)) {
|
||||
if (tmp_subset_ == nullptr || is_change_dataset) {
|
||||
tmp_subset_.reset(new Dataset(bag_data_cnt_));
|
||||
tmp_subset_->CopyFeatureMapperFrom(train_data_);
|
||||
}
|
||||
is_use_subset_ = true;
|
||||
Log::Debug("Use subset for bagging");
|
||||
}
|
||||
}
|
||||
|
||||
need_re_bagging_ = true;
|
||||
|
||||
if (is_use_subset_ && bag_data_cnt_ < num_data_) {
|
||||
// resize gradient vectors to copy the customized gradients for using subset data
|
||||
need_resize_gradients_ = true;
|
||||
}
|
||||
} else {
|
||||
bag_data_cnt_ = num_data_;
|
||||
bag_data_indices_.clear();
|
||||
#ifdef USE_CUDA
|
||||
cuda_bag_data_indices_.Clear();
|
||||
#endif // USE_CUDA
|
||||
bagging_runner_.ReSize(0);
|
||||
is_use_subset_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsHessianChange() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_size_t num_sampled_queries() const override {
|
||||
return num_sampled_queries_;
|
||||
}
|
||||
|
||||
const data_size_t* sampled_query_indices() const override {
|
||||
return bag_query_indices_.data();
|
||||
}
|
||||
|
||||
private:
|
||||
data_size_t BaggingHelper(data_size_t start, data_size_t cnt, data_size_t* buffer) {
|
||||
if (cnt <= 0) {
|
||||
return 0;
|
||||
}
|
||||
data_size_t cur_left_cnt = 0;
|
||||
data_size_t cur_right_pos = cnt;
|
||||
// random bagging, minimal unit is one record
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
auto cur_idx = start + i;
|
||||
if (bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < config_->bagging_fraction) {
|
||||
buffer[cur_left_cnt++] = cur_idx;
|
||||
} else {
|
||||
buffer[--cur_right_pos] = cur_idx;
|
||||
}
|
||||
}
|
||||
return cur_left_cnt;
|
||||
}
|
||||
|
||||
data_size_t BalancedBaggingHelper(data_size_t start, data_size_t cnt, data_size_t* buffer) {
|
||||
if (cnt <= 0) {
|
||||
return 0;
|
||||
}
|
||||
auto label_ptr = train_data_->metadata().label();
|
||||
data_size_t cur_left_cnt = 0;
|
||||
data_size_t cur_right_pos = cnt;
|
||||
// random bagging, minimal unit is one record
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
auto cur_idx = start + i;
|
||||
bool is_pos = label_ptr[start + i] > 0;
|
||||
bool is_in_bag = false;
|
||||
if (is_pos) {
|
||||
is_in_bag = bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() <
|
||||
config_->pos_bagging_fraction;
|
||||
} else {
|
||||
is_in_bag = bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() <
|
||||
config_->neg_bagging_fraction;
|
||||
}
|
||||
if (is_in_bag) {
|
||||
buffer[cur_left_cnt++] = cur_idx;
|
||||
} else {
|
||||
buffer[--cur_right_pos] = cur_idx;
|
||||
}
|
||||
}
|
||||
return cur_left_cnt;
|
||||
}
|
||||
|
||||
/*! \brief whether need restart bagging in continued training */
|
||||
bool need_re_bagging_;
|
||||
/*! \brief number of threads */
|
||||
int num_threads_;
|
||||
/*! \brief query boundaries of the in-bag queries */
|
||||
std::vector<data_size_t> sampled_query_boundaries_;
|
||||
/*! \brief buffer for calculating sampled_query_boundaries_ */
|
||||
std::vector<data_size_t> sampled_query_boundaries_thread_buffer_;
|
||||
/*! \brief in-bag query indices */
|
||||
std::vector<data_size_t, Common::AlignmentAllocator<data_size_t, kAlignedSize>> bag_query_indices_;
|
||||
/*! \brief number of queries in the training dataset */
|
||||
data_size_t num_queries_;
|
||||
/*! \brief number of in-bag queries */
|
||||
data_size_t num_sampled_queries_;
|
||||
/*! \brief query boundaries of the whole training dataset */
|
||||
const data_size_t* query_boundaries_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_BAGGING_HPP_
|
||||
@@ -0,0 +1,102 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/boosting.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "dart.hpp"
|
||||
#include "gbdt.h"
|
||||
#include "rf.hpp"
|
||||
|
||||
#ifdef USE_CUDA
|
||||
#include "cuda/nccl_gbdt.hpp"
|
||||
#endif // USE_CUDA
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
std::string GetBoostingTypeFromModelFile(const char* filename) {
|
||||
TextReader<size_t> model_reader(filename, true);
|
||||
std::string type = model_reader.first_line();
|
||||
return type;
|
||||
}
|
||||
|
||||
bool Boosting::LoadFileToBoosting(Boosting* boosting, const char* filename) {
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
if (boosting != nullptr) {
|
||||
TextReader<size_t> model_reader(filename, true);
|
||||
size_t buffer_len = 0;
|
||||
auto buffer = model_reader.ReadContent(&buffer_len);
|
||||
if (!boosting->LoadModelFromString(buffer.data(), buffer_len)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::chrono::duration<double, std::milli> delta = (std::chrono::steady_clock::now() - start_time);
|
||||
Log::Debug("Time for loading model: %f seconds", 1e-3*delta);
|
||||
return true;
|
||||
}
|
||||
|
||||
Boosting* Boosting::CreateBoosting(const std::string& type, const char* filename,
|
||||
const std::string&
|
||||
#ifdef USE_CUDA
|
||||
device_type
|
||||
#endif // USE_CUDA
|
||||
, const int
|
||||
#ifdef USE_CUDA
|
||||
num_gpu
|
||||
#endif // USE_CUDA
|
||||
) {
|
||||
if (filename == nullptr || filename[0] == '\0') {
|
||||
if (type == std::string("gbdt")) {
|
||||
#ifdef USE_CUDA
|
||||
if (device_type == std::string("cuda") && num_gpu > 1) {
|
||||
return new NCCLGBDT<GBDT>();
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
return new GBDT();
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
} else if (type == std::string("dart")) {
|
||||
return new DART();
|
||||
} else if (type == std::string("goss")) {
|
||||
return new GBDT();
|
||||
} else if (type == std::string("rf")) {
|
||||
return new RF();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
std::unique_ptr<Boosting> ret;
|
||||
if (GetBoostingTypeFromModelFile(filename) == std::string("tree")) {
|
||||
if (type == std::string("gbdt")) {
|
||||
#ifdef USE_CUDA
|
||||
if (device_type == std::string("cuda") && num_gpu > 1) {
|
||||
ret.reset(new NCCLGBDT<GBDT>());
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
ret.reset(new GBDT());
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
} else if (type == std::string("dart")) {
|
||||
ret.reset(new DART());
|
||||
} else if (type == std::string("goss")) {
|
||||
ret.reset(new GBDT());
|
||||
} else if (type == std::string("rf")) {
|
||||
ret.reset(new RF());
|
||||
} else {
|
||||
Log::Fatal("Unknown boosting type %s", type.c_str());
|
||||
}
|
||||
LoadFileToBoosting(ret.get(), filename);
|
||||
} else {
|
||||
Log::Fatal("Unknown model format or submodel type in model file %s", filename);
|
||||
}
|
||||
return ret.release();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,93 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#include "cuda_score_updater.hpp"
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDAScoreUpdater::CUDAScoreUpdater(const Dataset* data, int num_tree_per_iteration, const bool boosting_on_cuda):
|
||||
ScoreUpdater(data, num_tree_per_iteration), num_threads_per_block_(1024), boosting_on_cuda_(boosting_on_cuda) {
|
||||
num_data_ = data->num_data();
|
||||
int64_t total_size = static_cast<int64_t>(num_data_) * num_tree_per_iteration;
|
||||
InitCUDA(total_size);
|
||||
has_init_score_ = false;
|
||||
const double* init_score = data->metadata().init_score();
|
||||
// if exists initial score, will start from it
|
||||
if (init_score != nullptr) {
|
||||
if ((data->metadata().num_init_score() % num_data_) != 0
|
||||
|| (data->metadata().num_init_score() / num_data_) != num_tree_per_iteration) {
|
||||
Log::Fatal("Number of class for initial score error");
|
||||
}
|
||||
has_init_score_ = true;
|
||||
CopyFromHostToCUDADevice<double>(cuda_score_.RawData(), init_score, total_size, __FILE__, __LINE__);
|
||||
} else {
|
||||
SetCUDAMemory<double>(cuda_score_.RawData(), 0, static_cast<size_t>(total_size), __FILE__, __LINE__);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
if (boosting_on_cuda_) {
|
||||
// clear host score buffer
|
||||
score_.clear();
|
||||
score_.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAScoreUpdater::InitCUDA(const size_t total_size) {
|
||||
cuda_score_.Resize(total_size);
|
||||
}
|
||||
|
||||
CUDAScoreUpdater::~CUDAScoreUpdater() {}
|
||||
|
||||
inline void CUDAScoreUpdater::AddScore(double val, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("CUDAScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
LaunchAddScoreConstantKernel(val, offset);
|
||||
if (!boosting_on_cuda_) {
|
||||
CopyFromCUDADeviceToHost<double>(score_.data() + offset, cuda_score_.RawData() + offset, static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
inline void CUDAScoreUpdater::AddScore(const Tree* tree, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
tree->AddPredictionToScore(data_, num_data_, cuda_score_.RawData() + offset);
|
||||
if (!boosting_on_cuda_) {
|
||||
CopyFromCUDADeviceToHost<double>(score_.data() + offset, cuda_score_.RawData() + offset, static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
inline void CUDAScoreUpdater::AddScore(const TreeLearner* tree_learner, const Tree* tree, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
tree_learner->AddPredictionToScore(tree, cuda_score_.RawData() + offset);
|
||||
if (!boosting_on_cuda_) {
|
||||
CopyFromCUDADeviceToHost<double>(score_.data() + offset, cuda_score_.RawData() + offset, static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
inline void CUDAScoreUpdater::AddScore(const Tree* tree, const data_size_t* data_indices,
|
||||
data_size_t data_cnt, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
tree->AddPredictionToScore(data_, data_indices, data_cnt, cuda_score_.RawData() + offset);
|
||||
if (!boosting_on_cuda_) {
|
||||
CopyFromCUDADeviceToHost<double>(score_.data() + offset, cuda_score_.RawData() + offset, static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
inline void CUDAScoreUpdater::MultiplyScore(double val, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("CUDAScoreUpdater::MultiplyScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
LaunchMultiplyScoreConstantKernel(val, offset);
|
||||
if (!boosting_on_cuda_) {
|
||||
CopyFromCUDADeviceToHost<double>(score_.data() + offset, cuda_score_.RawData() + offset, static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#include "cuda_score_updater.hpp"
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
__global__ void AddScoreConstantKernel(
|
||||
const double val,
|
||||
const data_size_t num_data,
|
||||
double* score) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (data_index < num_data) {
|
||||
score[data_index] += val;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAScoreUpdater::LaunchAddScoreConstantKernel(const double val, const size_t offset) {
|
||||
const int num_blocks = (num_data_ + num_threads_per_block_) / num_threads_per_block_;
|
||||
Log::Debug("Adding init score = %lf", val);
|
||||
AddScoreConstantKernel<<<num_blocks, num_threads_per_block_>>>(val, num_data_, cuda_score_.RawData() + offset);
|
||||
}
|
||||
|
||||
__global__ void MultiplyScoreConstantKernel(
|
||||
const double val,
|
||||
const data_size_t num_data,
|
||||
double* score) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (data_index < num_data) {
|
||||
score[data_index] *= val;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAScoreUpdater::LaunchMultiplyScoreConstantKernel(const double val, const size_t offset) {
|
||||
const int num_blocks = (num_data_ + num_threads_per_block_) / num_threads_per_block_;
|
||||
MultiplyScoreConstantKernel<<<num_blocks, num_threads_per_block_>>>(val, num_data_, cuda_score_.RawData() + offset);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,66 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_CUDA_CUDA_SCORE_UPDATER_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_CUDA_CUDA_SCORE_UPDATER_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_utils.hu>
|
||||
|
||||
#include "../score_updater.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class CUDAScoreUpdater: public ScoreUpdater {
|
||||
public:
|
||||
CUDAScoreUpdater(const Dataset* data, int num_tree_per_iteration, const bool boosting_on_cuda);
|
||||
|
||||
~CUDAScoreUpdater();
|
||||
|
||||
void AddScore(double val, int cur_tree_id) override;
|
||||
|
||||
inline void AddScore(const Tree* tree, int cur_tree_id) override;
|
||||
|
||||
void AddScore(const TreeLearner* tree_learner, const Tree* tree, int cur_tree_id) override;
|
||||
|
||||
inline void AddScore(const Tree* tree, const data_size_t* data_indices,
|
||||
data_size_t data_cnt, int cur_tree_id) override;
|
||||
|
||||
inline void MultiplyScore(double val, int cur_tree_id) override;
|
||||
|
||||
inline const double* score() const override {
|
||||
if (boosting_on_cuda_) {
|
||||
return cuda_score_.RawData();
|
||||
} else {
|
||||
return score_.data();
|
||||
}
|
||||
}
|
||||
|
||||
/*! \brief Disable copy */
|
||||
CUDAScoreUpdater& operator=(const CUDAScoreUpdater&) = delete;
|
||||
|
||||
CUDAScoreUpdater(const CUDAScoreUpdater&) = delete;
|
||||
|
||||
private:
|
||||
void InitCUDA(const size_t total_size);
|
||||
|
||||
void LaunchAddScoreConstantKernel(const double val, const size_t offset);
|
||||
|
||||
void LaunchMultiplyScoreConstantKernel(const double val, const size_t offset);
|
||||
|
||||
CUDAVector<double> cuda_score_;
|
||||
|
||||
const int num_threads_per_block_;
|
||||
|
||||
const bool boosting_on_cuda_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_CUDA_CUDA_SCORE_UPDATER_HPP_
|
||||
@@ -0,0 +1,210 @@
|
||||
/*!
|
||||
* Copyright (c) 2023-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2023-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "nccl_gbdt.hpp"
|
||||
#include "nccl_gbdt_component.hpp"
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename GBDT_T>
|
||||
NCCLGBDT<GBDT_T>::NCCLGBDT(): GBDT_T() {}
|
||||
|
||||
template <typename GBDT_T>
|
||||
NCCLGBDT<GBDT_T>::~NCCLGBDT() {}
|
||||
|
||||
template <typename GBDT_T>
|
||||
void NCCLGBDT<GBDT_T>::Init(
|
||||
const Config* gbdt_config, const Dataset* train_data,
|
||||
const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) {
|
||||
GBDT_T::Init(gbdt_config, train_data, objective_function, training_metrics);
|
||||
|
||||
this->tree_learner_.reset();
|
||||
|
||||
nccl_topology_.reset(new NCCLTopology(this->config_->gpu_device_id, this->config_->num_gpu, this->config_->gpu_device_id_list, train_data->num_data()));
|
||||
|
||||
nccl_topology_->InitNCCL();
|
||||
|
||||
nccl_topology_->InitPerDevice<NCCLGBDTComponent>(&nccl_gbdt_components_);
|
||||
nccl_topology_->RunPerDevice<NCCLGBDTComponent, void>(nccl_gbdt_components_, [this, gbdt_config, train_data]
|
||||
(NCCLGBDTComponent* nccl_gbdt_component) { nccl_gbdt_component->Init(
|
||||
gbdt_config, train_data, this->num_tree_per_iteration_, this->boosting_on_gpu_, this->is_constant_hessian_);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename GBDT_T>
|
||||
void NCCLGBDT<GBDT_T>::BoostingThread(NCCLGBDTComponent* thread_data) {
|
||||
const ObjectiveFunction* objective_function = thread_data->objective_function();
|
||||
score_t* gradients = thread_data->gradients();
|
||||
score_t* hessians = thread_data->hessians();
|
||||
const double* score = thread_data->train_score_updater()->score();
|
||||
objective_function->GetGradients(score, gradients, hessians);
|
||||
}
|
||||
|
||||
template <typename GBDT_T>
|
||||
void NCCLGBDT<GBDT_T>::Boosting() {
|
||||
Common::FunctionTimer fun_timer("NCCLGBDT::Boosting", global_timer);
|
||||
if (this->objective_function_ == nullptr) {
|
||||
Log::Fatal("No object function provided");
|
||||
}
|
||||
nccl_topology_->DispatchPerDevice<NCCLGBDTComponent>(&nccl_gbdt_components_, BoostingThread);
|
||||
}
|
||||
|
||||
template <typename GBDT_T>
|
||||
double NCCLGBDT<GBDT_T>::BoostFromAverage(int class_id, bool update_scorer) {
|
||||
double init_score = GBDT_T::BoostFromAverage(class_id, update_scorer);
|
||||
|
||||
if (init_score != 0.0) {
|
||||
nccl_topology_->RunPerDevice<NCCLGBDTComponent, void>(nccl_gbdt_components_, [init_score, class_id] (NCCLGBDTComponent* thread_data) {
|
||||
thread_data->train_score_updater()->AddScore(init_score, class_id);
|
||||
});
|
||||
}
|
||||
|
||||
return init_score;
|
||||
}
|
||||
|
||||
template <typename GBDT_T>
|
||||
void NCCLGBDT<GBDT_T>::TrainTreeLearnerThread(NCCLGBDTComponent* thread_data, const int class_id, const bool is_first_tree) {
|
||||
const data_size_t num_data_in_gpu = thread_data->num_data_in_gpu();
|
||||
const score_t* gradients = thread_data->gradients() + class_id * num_data_in_gpu;
|
||||
const score_t* hessians = thread_data->hessians() + class_id * num_data_in_gpu;
|
||||
thread_data->SetTree(thread_data->tree_learner()->Train(gradients, hessians, is_first_tree));
|
||||
}
|
||||
|
||||
template <typename GBDT_T>
|
||||
bool NCCLGBDT<GBDT_T>::TrainOneIter(const score_t* gradients, const score_t* hessians) {
|
||||
Common::FunctionTimer fun_timer("NCCLGBDT::TrainOneIter", global_timer);
|
||||
std::vector<double> init_scores(this->num_tree_per_iteration_, 0.0);
|
||||
// boosting first
|
||||
if (gradients == nullptr || hessians == nullptr) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < this->num_tree_per_iteration_; ++cur_tree_id) {
|
||||
init_scores[cur_tree_id] = BoostFromAverage(cur_tree_id, true);
|
||||
}
|
||||
Boosting();
|
||||
} else {
|
||||
nccl_topology_->RunPerDevice<NCCLGBDTComponent, void>(nccl_gbdt_components_, [this, gradients, hessians] (NCCLGBDTComponent* thread_data) {
|
||||
const data_size_t data_start_index = thread_data->data_start_index();
|
||||
const data_size_t num_data_in_gpu = thread_data->num_data_in_gpu();
|
||||
|
||||
for (int class_id = 0; class_id < this->num_class_; ++class_id) {
|
||||
CopyFromHostToCUDADevice<score_t>(
|
||||
thread_data->gradients() + class_id * num_data_in_gpu,
|
||||
gradients + class_id * this->num_data_ + data_start_index, num_data_in_gpu, __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<score_t>(
|
||||
thread_data->hessians() + class_id * num_data_in_gpu,
|
||||
hessians + class_id * this->num_data_ + data_start_index, num_data_in_gpu, __FILE__, __LINE__);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool should_continue = false;
|
||||
for (int cur_tree_id = 0; cur_tree_id < this->num_tree_per_iteration_; ++cur_tree_id) {
|
||||
if (this->class_need_train_[cur_tree_id] && this->train_data_->num_features() > 0) {
|
||||
if (this->data_sample_strategy_->is_use_subset() && this->data_sample_strategy_->bag_data_cnt() < this->num_data_) {
|
||||
Log::Fatal("Bagging is not supported for NCCLGBDT");
|
||||
}
|
||||
bool is_first_tree = this->models_.size() < static_cast<size_t>(this->num_tree_per_iteration_);
|
||||
nccl_topology_->DispatchPerDevice<NCCLGBDTComponent>(&nccl_gbdt_components_,
|
||||
[is_first_tree, cur_tree_id] (NCCLGBDTComponent* thread_data) -> void {
|
||||
TrainTreeLearnerThread(thread_data, cur_tree_id, is_first_tree);
|
||||
});
|
||||
}
|
||||
|
||||
nccl_topology_->DispatchPerDevice<NCCLGBDTComponent>(&nccl_gbdt_components_, [cur_tree_id, this, init_scores] (NCCLGBDTComponent* thread_data) -> void {
|
||||
this->UpdateScoreThread(thread_data, cur_tree_id, this->config_->learning_rate, init_scores[cur_tree_id]);
|
||||
});
|
||||
|
||||
nccl_topology_->RunOnMasterDevice<NCCLGBDTComponent, void>(nccl_gbdt_components_, [&should_continue, this, cur_tree_id] (NCCLGBDTComponent* thread_data) -> void {
|
||||
if (thread_data->new_tree()->num_leaves() > 1) {
|
||||
should_continue = true;
|
||||
}
|
||||
for (auto& score_updater : this->valid_score_updater_) {
|
||||
score_updater->AddScore(thread_data->new_tree(), cur_tree_id);
|
||||
}
|
||||
});
|
||||
|
||||
if (!should_continue) {
|
||||
if (this->models_.size() < static_cast<size_t>(this->num_tree_per_iteration_)) {
|
||||
Log::Warning("Training stopped with no splits.");
|
||||
}
|
||||
}
|
||||
|
||||
// add model
|
||||
nccl_topology_->RunOnMasterDevice<NCCLGBDTComponent, void>(nccl_gbdt_components_, [this] (NCCLGBDTComponent* thread_data) -> void {
|
||||
this->models_.emplace_back(thread_data->release_new_tree());
|
||||
});
|
||||
|
||||
nccl_topology_->RunOnNonMasterDevice<NCCLGBDTComponent, void>(nccl_gbdt_components_, [this] (NCCLGBDTComponent* thread_data) -> void {
|
||||
thread_data->clear_new_tree();
|
||||
});
|
||||
}
|
||||
|
||||
if (!should_continue) {
|
||||
Log::Warning("Stopped training because there are no more leaves that meet the split requirements");
|
||||
if (this->models_.size() > static_cast<size_t>(this->num_tree_per_iteration_)) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < this->num_tree_per_iteration_; ++cur_tree_id) {
|
||||
this->models_.pop_back();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
++this->iter_;
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename GBDT_T>
|
||||
void NCCLGBDT<GBDT_T>::UpdateScoreThread(NCCLGBDTComponent* thread_data, const int cur_tree_id, const double shrinkage_rate, const double init_score) {
|
||||
if (thread_data->new_tree()->num_leaves() > 1) {
|
||||
// TODO(shiyu1994): implement bagging
|
||||
if (thread_data->objective_function() != nullptr && thread_data->objective_function()->IsRenewTreeOutput()) {
|
||||
// TODO(shiyu1994): implement renewing
|
||||
}
|
||||
thread_data->new_tree()->Shrinkage(shrinkage_rate);
|
||||
thread_data->train_score_updater()->AddScore(
|
||||
thread_data->tree_learner(),
|
||||
thread_data->new_tree(),
|
||||
cur_tree_id);
|
||||
if (std::fabs(init_score) > kEpsilon) {
|
||||
thread_data->new_tree()->AddBias(init_score);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename GBDT_T>
|
||||
std::vector<double> NCCLGBDT<GBDT_T>::EvalOneMetric(const Metric* metric, const double* score, const data_size_t num_data) const {
|
||||
if (score == this->train_score_updater_->score()) {
|
||||
// delegate to per gpu train score updater
|
||||
std::vector<double> tmp_score(num_data * this->num_class_, 0.0f);
|
||||
|
||||
nccl_topology_->RunPerDevice<NCCLGBDTComponent, void>(nccl_gbdt_components_, [this, &tmp_score] (NCCLGBDTComponent* thread_data) {
|
||||
const data_size_t data_start = thread_data->data_start_index();
|
||||
const data_size_t num_data_in_gpu = thread_data->num_data_in_gpu();
|
||||
for (int class_id = 0; class_id < this->num_class_; ++class_id) {
|
||||
CopyFromCUDADeviceToHost<double>(tmp_score.data() + class_id * this->num_data_ + data_start,
|
||||
thread_data->train_score_updater()->score() + class_id * num_data_in_gpu,
|
||||
static_cast<size_t>(num_data_in_gpu), __FILE__, __LINE__);
|
||||
}
|
||||
});
|
||||
|
||||
return metric->Eval(tmp_score.data(), this->objective_function_);
|
||||
} else {
|
||||
return GBDT_T::EvalOneMetric(metric, score, num_data);
|
||||
}
|
||||
}
|
||||
|
||||
template class NCCLGBDT<GBDT>;
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,146 @@
|
||||
/*!
|
||||
* Copyright (c) 2023-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2023-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_CUDA_NCCL_GBDT_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_CUDA_NCCL_GBDT_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/network.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <LightGBM/cuda/cuda_nccl_topology.hpp>
|
||||
|
||||
#include "cuda_score_updater.hpp"
|
||||
#include "nccl_gbdt_component.hpp"
|
||||
|
||||
#include "../gbdt.h"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename GBDT_T>
|
||||
class NCCLGBDT: public GBDT_T {
|
||||
public:
|
||||
NCCLGBDT();
|
||||
|
||||
~NCCLGBDT();
|
||||
|
||||
void Init(const Config* gbdt_config, const Dataset* train_data,
|
||||
const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) override;
|
||||
|
||||
void Boosting() override;
|
||||
|
||||
void RefitTree(const int* /*tree_leaf_prediction*/, const size_t /*nrow*/, const size_t /*ncol*/) override {
|
||||
Log::Fatal("RefitTree is not supported for NCCLGBDT.");
|
||||
}
|
||||
|
||||
bool TrainOneIter(const score_t* gradients, const score_t* hessians) override;
|
||||
|
||||
const double* GetTrainingScore(int64_t* /*out_len*/) override {
|
||||
Log::Fatal("GetTrainingScore is not supported for NCCLGBDT.");
|
||||
}
|
||||
|
||||
void ResetTrainingData(const Dataset* /*train_data*/, const ObjectiveFunction* /*objective_function*/,
|
||||
const std::vector<const Metric*>& /*training_metrics*/) override {
|
||||
Log::Fatal("ResetTrainingData is not supported for NCCLGBDT.");
|
||||
}
|
||||
|
||||
void ResetConfig(const Config* /*gbdt_config*/) override {
|
||||
Log::Fatal("ResetConfig is not supported for NCCLGBDT.");
|
||||
}
|
||||
|
||||
private:
|
||||
struct BoostingThreadData {
|
||||
int gpu_index;
|
||||
ObjectiveFunction* gpu_objective_function;
|
||||
score_t* gradients;
|
||||
score_t* hessians;
|
||||
const double* score;
|
||||
|
||||
BoostingThreadData() {
|
||||
gpu_index = 0;
|
||||
gpu_objective_function = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct TrainTreeLearnerThreadData {
|
||||
int gpu_index;
|
||||
TreeLearner* gpu_tree_learner;
|
||||
const score_t* gradients;
|
||||
const score_t* hessians;
|
||||
bool is_first_time;
|
||||
int class_id;
|
||||
data_size_t num_data_in_gpu;
|
||||
std::unique_ptr<Tree> tree;
|
||||
|
||||
TrainTreeLearnerThreadData() {
|
||||
gpu_index = 0;
|
||||
gpu_tree_learner = nullptr;
|
||||
gradients = nullptr;
|
||||
hessians = nullptr;
|
||||
is_first_time = false;
|
||||
class_id = 0;
|
||||
num_data_in_gpu = 0;
|
||||
tree.reset(nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateScoreThreadData {
|
||||
int gpu_index;
|
||||
ScoreUpdater* gpu_score_updater;
|
||||
TreeLearner* gpu_tree_learner;
|
||||
Tree* tree;
|
||||
int cur_tree_id;
|
||||
|
||||
UpdateScoreThreadData() {
|
||||
gpu_index = 0;
|
||||
gpu_score_updater = nullptr;
|
||||
gpu_tree_learner = nullptr;
|
||||
tree = nullptr;
|
||||
cur_tree_id = 0;
|
||||
}
|
||||
};
|
||||
|
||||
static void BoostingThread(NCCLGBDTComponent* thread_data);
|
||||
|
||||
static void TrainTreeLearnerThread(NCCLGBDTComponent* thread_data, const int class_id, const bool is_first_tree);
|
||||
|
||||
static void UpdateScoreThread(NCCLGBDTComponent* thread_data, const int cur_tree_id, const double shrinkage_rate, const double init_score);
|
||||
|
||||
double BoostFromAverage(int class_id, bool update_scorer) override;
|
||||
|
||||
void UpdateScore(const std::vector<std::unique_ptr<Tree>>& tree, const int cur_tree_id);
|
||||
|
||||
void UpdateScore(const Tree* /*tree*/, const int /*cur_tree_id*/) {
|
||||
Log::Fatal("UpdateScore is not supported for NCCLGBDT.");
|
||||
}
|
||||
|
||||
void RollbackOneIter() override {
|
||||
Log::Fatal("RollbackOneIter is not supported for NCCLGBDT.");
|
||||
}
|
||||
|
||||
std::vector<double> EvalOneMetric(const Metric* metric, const double* score, const data_size_t num_data) const override;
|
||||
|
||||
|
||||
int num_threads_;
|
||||
std::unique_ptr<NCCLTopology> nccl_topology_;
|
||||
|
||||
std::vector<int> nccl_gpu_rank_;
|
||||
std::vector<ncclComm_t> nccl_communicators_;
|
||||
|
||||
std::vector<std::unique_ptr<NCCLGBDTComponent>> nccl_gbdt_components_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_CUDA_NCCL_GBDT_HPP_
|
||||
@@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* Copyright (c) 2023-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2023-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_CUDA_NCCL_GBDT_COMPONENT_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_CUDA_NCCL_GBDT_COMPONENT_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/tree.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <LightGBM/cuda/cuda_objective_function.hpp>
|
||||
#include "cuda_score_updater.hpp"
|
||||
#include "../../treelearner/cuda/cuda_single_gpu_tree_learner.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class NCCLGBDTComponent: public NCCLInfo {
|
||||
public:
|
||||
NCCLGBDTComponent() {}
|
||||
|
||||
~NCCLGBDTComponent() {}
|
||||
|
||||
void Init(const Config* config, const Dataset* train_data, const int num_tree_per_iteration, const bool boosting_on_gpu, const bool is_constant_hessian) {
|
||||
CUDASUCCESS_OR_FATAL(cudaGetDeviceCount(&num_gpu_in_node_));
|
||||
const data_size_t num_data_per_gpu = (train_data->num_data() + num_gpu_in_node_ - 1) / num_gpu_in_node_;
|
||||
data_start_index_ = num_data_per_gpu * local_gpu_rank_;
|
||||
data_end_index_ = std::min<data_size_t>(data_start_index_ + num_data_per_gpu, train_data->num_data());
|
||||
num_data_in_gpu_ = data_end_index_ - data_start_index_;
|
||||
|
||||
dataset_.reset(new Dataset(num_data_in_gpu_));
|
||||
dataset_->ReSize(num_data_in_gpu_);
|
||||
dataset_->CopyFeatureMapperFrom(train_data);
|
||||
std::vector<data_size_t> used_indices(num_data_in_gpu_);
|
||||
for (data_size_t data_index = data_start_index_; data_index < data_end_index_; ++data_index) {
|
||||
used_indices[data_index - data_start_index_] = data_index;
|
||||
}
|
||||
dataset_->CopySubrowToDevice(train_data, used_indices.data(), num_data_in_gpu_, true, gpu_device_id_);
|
||||
|
||||
objective_function_.reset(ObjectiveFunction::CreateObjectiveFunctionCUDA(config->objective, *config));
|
||||
objective_function_->SetNCCLInfo(nccl_communicator_, nccl_gpu_rank_, local_gpu_rank_, gpu_device_id_, train_data->num_data());
|
||||
train_score_updater_.reset(new CUDAScoreUpdater(dataset_.get(), num_tree_per_iteration, boosting_on_gpu));
|
||||
gradients_.reset(new CUDAVector<score_t>(num_data_in_gpu_));
|
||||
hessians_.reset(new CUDAVector<score_t>(num_data_in_gpu_));
|
||||
tree_learner_.reset(new CUDASingleGPUTreeLearner(config, boosting_on_gpu));
|
||||
|
||||
tree_learner_->SetNCCLInfo(nccl_communicator_, nccl_gpu_rank_, local_gpu_rank_, gpu_device_id_, train_data->num_data());
|
||||
|
||||
objective_function_->Init(dataset_->metadata(), dataset_->num_data());
|
||||
tree_learner_->Init(dataset_.get(), is_constant_hessian);
|
||||
}
|
||||
|
||||
ObjectiveFunction* objective_function() { return objective_function_.get(); }
|
||||
|
||||
ScoreUpdater* train_score_updater() { return train_score_updater_.get(); }
|
||||
|
||||
score_t* gradients() { return gradients_->RawData(); }
|
||||
|
||||
score_t* hessians() { return hessians_->RawData(); }
|
||||
|
||||
data_size_t num_data_in_gpu() const { return num_data_in_gpu_; }
|
||||
|
||||
CUDASingleGPUTreeLearner* tree_learner() { return tree_learner_.get(); }
|
||||
|
||||
void SetTree(Tree* tree) {
|
||||
new_tree_.reset(tree);
|
||||
}
|
||||
|
||||
data_size_t data_start_index() const { return data_start_index_; }
|
||||
|
||||
data_size_t data_end_index() const { return data_end_index_; }
|
||||
|
||||
Tree* new_tree() { return new_tree_.get(); }
|
||||
|
||||
Tree* release_new_tree() { return new_tree_.release(); }
|
||||
|
||||
void clear_new_tree() { new_tree_.reset(nullptr); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<ObjectiveFunction> objective_function_;
|
||||
std::unique_ptr<ScoreUpdater> train_score_updater_;
|
||||
std::unique_ptr<CUDAVector<score_t>> gradients_;
|
||||
std::unique_ptr<CUDAVector<score_t>> hessians_;
|
||||
std::unique_ptr<Dataset> dataset_;
|
||||
std::unique_ptr<CUDASingleGPUTreeLearner> tree_learner_;
|
||||
std::unique_ptr<Tree> new_tree_;
|
||||
|
||||
data_size_t data_start_index_;
|
||||
data_size_t data_end_index_;
|
||||
data_size_t num_data_in_gpu_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_CUDA_NCCL_GBDT_COMPONENT_HPP_
|
||||
@@ -0,0 +1,212 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_DART_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_DART_HPP_
|
||||
|
||||
#include <LightGBM/boosting.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
#include "gbdt.h"
|
||||
#include "score_updater.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief DART algorithm implementation. including Training, prediction, bagging.
|
||||
*/
|
||||
class DART: public GBDT {
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructor
|
||||
*/
|
||||
DART() : GBDT() { }
|
||||
/*!
|
||||
* \brief Destructor
|
||||
*/
|
||||
~DART() { }
|
||||
/*!
|
||||
* \brief Initialization logic
|
||||
* \param config Config for boosting
|
||||
* \param train_data Training data
|
||||
* \param objective_function Training objective function
|
||||
* \param training_metrics Training metrics
|
||||
* \param output_model_filename Filename of output model
|
||||
*/
|
||||
void Init(const Config* config, const Dataset* train_data,
|
||||
const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) override {
|
||||
GBDT::Init(config, train_data, objective_function, training_metrics);
|
||||
random_for_drop_ = Random(config_->drop_seed);
|
||||
sum_weight_ = 0.0f;
|
||||
}
|
||||
|
||||
void ResetConfig(const Config* config) override {
|
||||
GBDT::ResetConfig(config);
|
||||
random_for_drop_ = Random(config_->drop_seed);
|
||||
sum_weight_ = 0.0f;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief one training iteration
|
||||
*/
|
||||
bool TrainOneIter(const score_t* gradient, const score_t* hessian) override {
|
||||
is_update_score_cur_iter_ = false;
|
||||
bool ret = GBDT::TrainOneIter(gradient, hessian);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
// normalize
|
||||
Normalize();
|
||||
if (!config_->uniform_drop) {
|
||||
tree_weight_.push_back(shrinkage_rate_);
|
||||
sum_weight_ += shrinkage_rate_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Get current training score
|
||||
* \param out_len length of returned score
|
||||
* \return training score
|
||||
*/
|
||||
const double* GetTrainingScore(int64_t* out_len) override {
|
||||
if (!is_update_score_cur_iter_) {
|
||||
// only drop one time in one iteration
|
||||
DroppingTrees();
|
||||
is_update_score_cur_iter_ = true;
|
||||
}
|
||||
*out_len = static_cast<int64_t>(train_score_updater_->num_data()) * num_class_;
|
||||
return train_score_updater_->score();
|
||||
}
|
||||
|
||||
bool EvalAndCheckEarlyStopping() override {
|
||||
GBDT::OutputMetric(iter_);
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief drop trees based on drop_rate
|
||||
*/
|
||||
void DroppingTrees() {
|
||||
drop_index_.clear();
|
||||
bool is_skip = random_for_drop_.NextFloat() < config_->skip_drop;
|
||||
// select dropping tree indices based on drop_rate and tree weights
|
||||
if (!is_skip) {
|
||||
double drop_rate = config_->drop_rate;
|
||||
if (!config_->uniform_drop) {
|
||||
double inv_average_weight = static_cast<double>(tree_weight_.size()) / sum_weight_;
|
||||
if (config_->max_drop > 0) {
|
||||
drop_rate = std::min(drop_rate, config_->max_drop * inv_average_weight / sum_weight_);
|
||||
}
|
||||
for (int i = 0; i < iter_; ++i) {
|
||||
if (random_for_drop_.NextFloat() < drop_rate * tree_weight_[i] * inv_average_weight) {
|
||||
drop_index_.push_back(num_init_iteration_ + i);
|
||||
if (drop_index_.size() >= static_cast<size_t>(config_->max_drop)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (config_->max_drop > 0) {
|
||||
drop_rate = std::min(drop_rate, config_->max_drop / static_cast<double>(iter_));
|
||||
}
|
||||
for (int i = 0; i < iter_; ++i) {
|
||||
if (random_for_drop_.NextFloat() < drop_rate) {
|
||||
drop_index_.push_back(num_init_iteration_ + i);
|
||||
if (drop_index_.size() >= static_cast<size_t>(config_->max_drop)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// drop trees
|
||||
for (auto i : drop_index_) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
auto curr_tree = i * num_tree_per_iteration_ + cur_tree_id;
|
||||
models_[curr_tree]->Shrinkage(-1.0);
|
||||
train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
}
|
||||
if (!config_->xgboost_dart_mode) {
|
||||
shrinkage_rate_ = config_->learning_rate / (1.0f + static_cast<double>(drop_index_.size()));
|
||||
} else {
|
||||
if (drop_index_.empty()) {
|
||||
shrinkage_rate_ = config_->learning_rate;
|
||||
} else {
|
||||
shrinkage_rate_ = config_->learning_rate / (config_->learning_rate + static_cast<double>(drop_index_.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
/*!
|
||||
* \brief normalize dropped trees
|
||||
* NOTE: num_drop_tree(k), learning_rate(lr), shrinkage_rate_ = lr / (k + 1)
|
||||
* step 1: shrink tree to -1 -> drop tree
|
||||
* step 2: shrink tree to k / (k + 1) - 1 from -1, by 1/(k+1)
|
||||
* -> normalize for valid data
|
||||
* step 3: shrink tree to k / (k + 1) from k / (k + 1) - 1, by -k
|
||||
* -> normalize for train data
|
||||
* end with tree weight = (k / (k + 1)) * old_weight
|
||||
*/
|
||||
void Normalize() {
|
||||
double k = static_cast<double>(drop_index_.size());
|
||||
if (!config_->xgboost_dart_mode) {
|
||||
for (auto i : drop_index_) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
auto curr_tree = i * num_tree_per_iteration_ + cur_tree_id;
|
||||
// update validation score
|
||||
models_[curr_tree]->Shrinkage(1.0f / (k + 1.0f));
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
// update training score
|
||||
models_[curr_tree]->Shrinkage(-k);
|
||||
train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
if (!config_->uniform_drop) {
|
||||
sum_weight_ -= tree_weight_[i - num_init_iteration_] * (1.0f / (k + 1.0f));
|
||||
tree_weight_[i - num_init_iteration_] *= (k / (k + 1.0f));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto i : drop_index_) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
auto curr_tree = i * num_tree_per_iteration_ + cur_tree_id;
|
||||
// update validation score
|
||||
models_[curr_tree]->Shrinkage(shrinkage_rate_);
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
// update training score
|
||||
models_[curr_tree]->Shrinkage(-k / config_->learning_rate);
|
||||
train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
if (!config_->uniform_drop) {
|
||||
sum_weight_ -= tree_weight_[i - num_init_iteration_] * (1.0f / (k + config_->learning_rate));;
|
||||
tree_weight_[i - num_init_iteration_] *= (k / (k + config_->learning_rate));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*! \brief The weights of all trees, used to choose drop trees */
|
||||
std::vector<double> tree_weight_;
|
||||
/*! \brief sum weights of all trees */
|
||||
double sum_weight_;
|
||||
/*! \brief The indices of dropping trees */
|
||||
std::vector<int> drop_index_;
|
||||
/*! \brief Random generator, used to select dropping trees */
|
||||
Random random_for_drop_;
|
||||
/*! \brief Flag that the score is update on current iter or not*/
|
||||
bool is_update_score_cur_iter_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_DART_HPP_
|
||||
@@ -0,0 +1,890 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include "gbdt.h"
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/network.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/prediction_early_stop.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
#include <LightGBM/sample_strategy.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
Common::Timer global_timer;
|
||||
|
||||
int LGBM_config_::current_device = lgbm_device_cpu;
|
||||
int LGBM_config_::current_learner = use_cpu_learner;
|
||||
|
||||
GBDT::GBDT()
|
||||
: iter_(0),
|
||||
train_data_(nullptr),
|
||||
config_(nullptr),
|
||||
objective_function_(nullptr),
|
||||
early_stopping_round_(0),
|
||||
early_stopping_min_delta_(0.0),
|
||||
es_first_metric_only_(false),
|
||||
max_feature_idx_(0),
|
||||
num_tree_per_iteration_(1),
|
||||
num_class_(1),
|
||||
num_iteration_for_pred_(0),
|
||||
shrinkage_rate_(0.1f),
|
||||
num_init_iteration_(0) {
|
||||
average_output_ = false;
|
||||
tree_learner_ = nullptr;
|
||||
linear_tree_ = false;
|
||||
data_sample_strategy_.reset(nullptr);
|
||||
gradients_pointer_ = nullptr;
|
||||
hessians_pointer_ = nullptr;
|
||||
boosting_on_gpu_ = false;
|
||||
}
|
||||
|
||||
GBDT::~GBDT() {
|
||||
}
|
||||
|
||||
void GBDT::Init(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) {
|
||||
CHECK_NOTNULL(train_data);
|
||||
train_data_ = train_data;
|
||||
if (!config->monotone_constraints.empty()) {
|
||||
CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->monotone_constraints.size());
|
||||
}
|
||||
if (!config->feature_contri.empty()) {
|
||||
CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->feature_contri.size());
|
||||
}
|
||||
iter_ = 0;
|
||||
num_iteration_for_pred_ = 0;
|
||||
max_feature_idx_ = 0;
|
||||
num_class_ = config->num_class;
|
||||
config_ = std::unique_ptr<Config>(new Config(*config));
|
||||
early_stopping_round_ = config_->early_stopping_round;
|
||||
early_stopping_min_delta_ = config->early_stopping_min_delta;
|
||||
es_first_metric_only_ = config_->first_metric_only;
|
||||
shrinkage_rate_ = config_->learning_rate;
|
||||
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
LGBM_config_::current_learner = use_cuda_learner;
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
const int gpu_device_id = config_->gpu_device_id >= 0 ? config_->gpu_device_id : 0;
|
||||
CUDASUCCESS_OR_FATAL(cudaSetDevice(gpu_device_id));
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
// load forced_splits file
|
||||
if (!config->forcedsplits_filename.empty()) {
|
||||
std::ifstream forced_splits_file(config->forcedsplits_filename.c_str());
|
||||
std::stringstream buffer;
|
||||
buffer << forced_splits_file.rdbuf();
|
||||
std::string err;
|
||||
forced_splits_json_ = Json::parse(buffer.str(), &err);
|
||||
}
|
||||
|
||||
objective_function_ = objective_function;
|
||||
num_tree_per_iteration_ = num_class_;
|
||||
if (objective_function_ != nullptr) {
|
||||
num_tree_per_iteration_ = objective_function_->NumModelPerIteration();
|
||||
if (objective_function_->IsRenewTreeOutput() && !config->monotone_constraints.empty()) {
|
||||
Log::Fatal("Cannot use ``monotone_constraints`` in %s objective, please disable it.", objective_function_->GetName());
|
||||
}
|
||||
}
|
||||
|
||||
data_sample_strategy_.reset(SampleStrategy::CreateSampleStrategy(config_.get(), train_data_, objective_function_, num_tree_per_iteration_));
|
||||
is_constant_hessian_ = GetIsConstHessian(objective_function);
|
||||
|
||||
boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() &&
|
||||
!data_sample_strategy_->IsHessianChange(); // for sample strategy with Hessian change, fall back to boosting on CPU
|
||||
|
||||
tree_learner_ = std::unique_ptr<TreeLearner>(TreeLearner::CreateTreeLearner(config_->tree_learner, config_->device_type,
|
||||
config_.get(), boosting_on_gpu_));
|
||||
|
||||
// init tree learner
|
||||
tree_learner_->Init(train_data_, is_constant_hessian_);
|
||||
tree_learner_->SetForcedSplit(&forced_splits_json_);
|
||||
|
||||
// push training metrics
|
||||
training_metrics_.clear();
|
||||
for (const auto& metric : training_metrics) {
|
||||
training_metrics_.push_back(metric);
|
||||
}
|
||||
training_metrics_.shrink_to_fit();
|
||||
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
train_score_updater_.reset(new CUDAScoreUpdater(train_data_, num_tree_per_iteration_, boosting_on_gpu_));
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
train_score_updater_.reset(new ScoreUpdater(train_data_, num_tree_per_iteration_));
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
|
||||
num_data_ = train_data_->num_data();
|
||||
|
||||
// get max feature index
|
||||
max_feature_idx_ = train_data_->num_total_features() - 1;
|
||||
// get label index
|
||||
label_idx_ = train_data_->label_idx();
|
||||
// get feature names
|
||||
feature_names_ = train_data_->feature_names();
|
||||
feature_infos_ = train_data_->feature_infos();
|
||||
monotone_constraints_ = config->monotone_constraints;
|
||||
// get parser config file content
|
||||
parser_config_str_ = train_data_->parser_config_str();
|
||||
|
||||
// check that forced splits does not use feature indices larger than dataset size
|
||||
CheckForcedSplitFeatures();
|
||||
|
||||
// if need bagging, create buffer
|
||||
data_sample_strategy_->ResetSampleConfig(config_.get(), true);
|
||||
ResetGradientBuffers();
|
||||
|
||||
class_need_train_ = std::vector<bool>(num_tree_per_iteration_, true);
|
||||
if (objective_function_ != nullptr && objective_function_->SkipEmptyClass()) {
|
||||
CHECK_EQ(num_tree_per_iteration_, num_class_);
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
class_need_train_[i] = objective_function_->ClassNeedTrain(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (config_->linear_tree) {
|
||||
linear_tree_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::CheckForcedSplitFeatures() {
|
||||
std::queue<Json> forced_split_nodes;
|
||||
forced_split_nodes.push(forced_splits_json_);
|
||||
while (!forced_split_nodes.empty()) {
|
||||
Json node = forced_split_nodes.front();
|
||||
forced_split_nodes.pop();
|
||||
const int feature_index = node["feature"].int_value();
|
||||
if (feature_index > max_feature_idx_) {
|
||||
Log::Fatal("Forced splits file includes feature index %d, but maximum feature index in dataset is %d",
|
||||
feature_index, max_feature_idx_);
|
||||
}
|
||||
if (node.object_items().count("left") > 0) {
|
||||
forced_split_nodes.push(node["left"]);
|
||||
}
|
||||
if (node.object_items().count("right") > 0) {
|
||||
forced_split_nodes.push(node["right"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::AddValidDataset(const Dataset* valid_data,
|
||||
const std::vector<const Metric*>& valid_metrics) {
|
||||
if (!train_data_->CheckAlign(*valid_data)) {
|
||||
Log::Fatal("Cannot add validation data, since it has different bin mappers with training data");
|
||||
}
|
||||
// for a validation dataset, we need its score and metric
|
||||
auto new_score_updater =
|
||||
#ifdef USE_CUDA
|
||||
config_->device_type == std::string("cuda") ?
|
||||
std::unique_ptr<CUDAScoreUpdater>(new CUDAScoreUpdater(valid_data, num_tree_per_iteration_,
|
||||
objective_function_ != nullptr && objective_function_->IsCUDAObjective())) :
|
||||
#endif // USE_CUDA
|
||||
std::unique_ptr<ScoreUpdater>(new ScoreUpdater(valid_data, num_tree_per_iteration_));
|
||||
// update score
|
||||
for (int i = 0; i < iter_; ++i) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
auto curr_tree = (i + num_init_iteration_) * num_tree_per_iteration_ + cur_tree_id;
|
||||
new_score_updater->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
}
|
||||
valid_score_updater_.push_back(std::move(new_score_updater));
|
||||
valid_metrics_.emplace_back();
|
||||
for (const auto& metric : valid_metrics) {
|
||||
valid_metrics_.back().push_back(metric);
|
||||
}
|
||||
valid_metrics_.back().shrink_to_fit();
|
||||
|
||||
if (early_stopping_round_ > 0) {
|
||||
auto num_metrics = valid_metrics.size();
|
||||
if (es_first_metric_only_) {
|
||||
num_metrics = 1;
|
||||
}
|
||||
best_iter_.emplace_back(num_metrics, 0);
|
||||
best_score_.emplace_back(num_metrics, kMinScore);
|
||||
best_msg_.emplace_back(num_metrics);
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::Boosting() {
|
||||
Common::FunctionTimer fun_timer("GBDT::Boosting", global_timer);
|
||||
if (objective_function_ == nullptr) {
|
||||
Log::Fatal("No objective function provided");
|
||||
}
|
||||
// objective function will calculate gradients and hessians
|
||||
int64_t num_score = 0;
|
||||
if (config_->bagging_by_query) {
|
||||
data_sample_strategy_->Bagging(iter_, tree_learner_.get(), gradients_.data(), hessians_.data());
|
||||
objective_function_->
|
||||
GetGradientsWithSampledQueries(GetTrainingScore(&num_score), data_sample_strategy_->num_sampled_queries(), data_sample_strategy_->sampled_query_indices(), gradients_pointer_, hessians_pointer_);
|
||||
} else {
|
||||
objective_function_->
|
||||
GetGradients(GetTrainingScore(&num_score), gradients_pointer_, hessians_pointer_);
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::Train(int snapshot_freq, const std::string& model_output_path) {
|
||||
Common::FunctionTimer fun_timer("GBDT::Train", global_timer);
|
||||
bool is_finished = false;
|
||||
auto start_time = std::chrono::steady_clock::now();
|
||||
for (int iter = 0; iter < config_->num_iterations && !is_finished; ++iter) {
|
||||
is_finished = TrainOneIter(nullptr, nullptr);
|
||||
if (!is_finished) {
|
||||
is_finished = EvalAndCheckEarlyStopping();
|
||||
}
|
||||
auto end_time = std::chrono::steady_clock::now();
|
||||
// output used time per iteration
|
||||
Log::Info("%f seconds elapsed, finished iteration %d", std::chrono::duration<double,
|
||||
std::milli>(end_time - start_time) * 1e-3, iter + 1);
|
||||
if (snapshot_freq > 0
|
||||
&& (iter + 1) % snapshot_freq == 0) {
|
||||
std::string snapshot_out = model_output_path + ".snapshot_iter_" + std::to_string(iter + 1);
|
||||
SaveModelToFile(0, -1, config_->saved_feature_importance_type, snapshot_out.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::RefitTree(const int* tree_leaf_prediction, const size_t nrow, const size_t ncol) {
|
||||
CHECK_GT(nrow * ncol, 0);
|
||||
CHECK_EQ(static_cast<size_t>(num_data_), nrow);
|
||||
CHECK_EQ(models_.size(), ncol);
|
||||
|
||||
int num_iterations = static_cast<int>(models_.size() / num_tree_per_iteration_);
|
||||
std::vector<int> leaf_pred(num_data_);
|
||||
if (linear_tree_) {
|
||||
std::vector<int> max_leaves_by_thread = std::vector<int>(OMP_NUM_THREADS(), 0);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int i = 0; i < static_cast<int>(nrow); ++i) {
|
||||
int tid = omp_get_thread_num();
|
||||
for (size_t j = 0; j < ncol; ++j) {
|
||||
max_leaves_by_thread[tid] = std::max(max_leaves_by_thread[tid], tree_leaf_prediction[i * ncol + j]);
|
||||
}
|
||||
}
|
||||
int max_leaves = *std::max_element(max_leaves_by_thread.begin(), max_leaves_by_thread.end());
|
||||
max_leaves += 1;
|
||||
tree_learner_->InitLinear(train_data_, max_leaves);
|
||||
}
|
||||
|
||||
for (int iter = 0; iter < num_iterations; ++iter) {
|
||||
Boosting();
|
||||
for (int tree_id = 0; tree_id < num_tree_per_iteration_; ++tree_id) {
|
||||
int model_index = iter * num_tree_per_iteration_ + tree_id;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int i = 0; i < num_data_; ++i) {
|
||||
leaf_pred[i] = tree_leaf_prediction[i * ncol + model_index];
|
||||
CHECK_LT(leaf_pred[i], models_[model_index]->num_leaves());
|
||||
}
|
||||
size_t offset = static_cast<size_t>(tree_id) * num_data_;
|
||||
auto grad = gradients_pointer_ + offset;
|
||||
auto hess = hessians_pointer_ + offset;
|
||||
auto new_tree = tree_learner_->FitByExistingTree(models_[model_index].get(), leaf_pred, grad, hess);
|
||||
train_score_updater_->AddScore(tree_learner_.get(), new_tree, tree_id);
|
||||
models_[model_index].reset(new_tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the custom "average" is implemented it will be used in place of the label average (if enabled)
|
||||
*
|
||||
* An improvement to this is to have options to explicitly choose
|
||||
* (i) standard average
|
||||
* (ii) custom average if available
|
||||
* (iii) any user defined scalar bias (e.g. using a new option "init_score" that overrides (i) and (ii) )
|
||||
*
|
||||
* (i) and (ii) could be selected as say "auto_init_score" = 0 or 1 etc..
|
||||
*
|
||||
*/
|
||||
double ObtainAutomaticInitialScore(const ObjectiveFunction* fobj, int class_id) {
|
||||
double init_score = 0.0;
|
||||
if (fobj != nullptr) {
|
||||
init_score = fobj->BoostFromScore(class_id);
|
||||
}
|
||||
if (Network::num_machines() > 1) {
|
||||
init_score = Network::GlobalSyncUpByMean(init_score);
|
||||
}
|
||||
return init_score;
|
||||
}
|
||||
|
||||
double GBDT::BoostFromAverage(int class_id, bool update_scorer) {
|
||||
Common::FunctionTimer fun_timer("GBDT::BoostFromAverage", global_timer);
|
||||
// boosting from average label; or customized "average" if implemented for the current objective
|
||||
if (models_.empty() && !train_score_updater_->has_init_score() && objective_function_ != nullptr) {
|
||||
if (config_->boost_from_average || (train_data_ != nullptr && train_data_->num_features() == 0)) {
|
||||
double init_score = ObtainAutomaticInitialScore(objective_function_, class_id);
|
||||
if (std::fabs(init_score) > kEpsilon) {
|
||||
if (update_scorer) {
|
||||
train_score_updater_->AddScore(init_score, class_id);
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->AddScore(init_score, class_id);
|
||||
}
|
||||
}
|
||||
Log::Info("Start training from score %lf", init_score);
|
||||
return init_score;
|
||||
}
|
||||
} else if (std::string(objective_function_->GetName()) == std::string("regression_l1")
|
||||
|| std::string(objective_function_->GetName()) == std::string("quantile")
|
||||
|| std::string(objective_function_->GetName()) == std::string("mape")) {
|
||||
Log::Warning("Disabling boost_from_average in %s may cause the slow convergence", objective_function_->GetName());
|
||||
}
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
bool GBDT::TrainOneIter(const score_t* gradients, const score_t* hessians) {
|
||||
Common::FunctionTimer fun_timer("GBDT::TrainOneIter", global_timer);
|
||||
std::vector<double> init_scores(num_tree_per_iteration_, 0.0);
|
||||
// boosting first
|
||||
if (gradients == nullptr || hessians == nullptr) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
init_scores[cur_tree_id] = BoostFromAverage(cur_tree_id, true);
|
||||
}
|
||||
Boosting();
|
||||
gradients = gradients_pointer_;
|
||||
hessians = hessians_pointer_;
|
||||
} else {
|
||||
// use customized objective function
|
||||
// the check below fails unless objective=custom is provided in the parameters on Booster creation
|
||||
CHECK(objective_function_ == nullptr);
|
||||
if (data_sample_strategy_->IsHessianChange()) {
|
||||
// need to copy customized gradients when using GOSS
|
||||
int64_t total_size = static_cast<int64_t>(num_data_) * num_tree_per_iteration_;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int64_t i = 0; i < total_size; ++i) {
|
||||
gradients_[i] = gradients[i];
|
||||
hessians_[i] = hessians[i];
|
||||
}
|
||||
CHECK_EQ(gradients_pointer_, gradients_.data());
|
||||
CHECK_EQ(hessians_pointer_, hessians_.data());
|
||||
gradients = gradients_pointer_;
|
||||
hessians = hessians_pointer_;
|
||||
}
|
||||
}
|
||||
|
||||
// bagging logic
|
||||
if (!config_->bagging_by_query) {
|
||||
data_sample_strategy_->Bagging(iter_, tree_learner_.get(), gradients_.data(), hessians_.data());
|
||||
}
|
||||
const bool is_use_subset = data_sample_strategy_->is_use_subset();
|
||||
const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt();
|
||||
const std::vector<data_size_t, Common::AlignmentAllocator<data_size_t, kAlignedSize>>& bag_data_indices = data_sample_strategy_->bag_data_indices();
|
||||
|
||||
if (objective_function_ == nullptr && is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_ && !data_sample_strategy_->IsHessianChange()) {
|
||||
ResetGradientBuffers();
|
||||
}
|
||||
|
||||
bool should_continue = false;
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
const size_t offset = static_cast<size_t>(cur_tree_id) * num_data_;
|
||||
std::unique_ptr<Tree> new_tree(new Tree(2, false, false));
|
||||
if (class_need_train_[cur_tree_id] && train_data_->num_features() > 0) {
|
||||
auto grad = gradients + offset;
|
||||
auto hess = hessians + offset;
|
||||
// need to copy gradients for bagging subset.
|
||||
if (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_) {
|
||||
for (int i = 0; i < bag_data_cnt; ++i) {
|
||||
gradients_pointer_[offset + i] = grad[bag_data_indices[i]];
|
||||
hessians_pointer_[offset + i] = hess[bag_data_indices[i]];
|
||||
}
|
||||
grad = gradients_pointer_ + offset;
|
||||
hess = hessians_pointer_ + offset;
|
||||
}
|
||||
bool is_first_tree = models_.size() < static_cast<size_t>(num_tree_per_iteration_);
|
||||
new_tree.reset(tree_learner_->Train(grad, hess, is_first_tree));
|
||||
}
|
||||
|
||||
if (new_tree->num_leaves() > 1) {
|
||||
should_continue = true;
|
||||
auto score_ptr = train_score_updater_->score() + offset;
|
||||
auto residual_getter = [score_ptr](const label_t* label, int i) {return static_cast<double>(label[i]) - score_ptr[i]; };
|
||||
tree_learner_->RenewTreeOutput(new_tree.get(), objective_function_, residual_getter,
|
||||
num_data_, bag_data_indices.data(), bag_data_cnt, train_score_updater_->score());
|
||||
// shrinkage by learning rate
|
||||
new_tree->Shrinkage(shrinkage_rate_);
|
||||
// update score
|
||||
UpdateScore(new_tree.get(), cur_tree_id);
|
||||
if (std::fabs(init_scores[cur_tree_id]) > kEpsilon) {
|
||||
new_tree->AddBias(init_scores[cur_tree_id]);
|
||||
}
|
||||
} else {
|
||||
// only add default score one-time
|
||||
if (models_.size() < static_cast<size_t>(num_tree_per_iteration_)) {
|
||||
if (objective_function_ != nullptr && !config_->boost_from_average && !train_score_updater_->has_init_score()) {
|
||||
init_scores[cur_tree_id] = ObtainAutomaticInitialScore(objective_function_, cur_tree_id);
|
||||
// updates scores
|
||||
train_score_updater_->AddScore(init_scores[cur_tree_id], cur_tree_id);
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->AddScore(init_scores[cur_tree_id], cur_tree_id);
|
||||
}
|
||||
}
|
||||
new_tree->AsConstantTree(init_scores[cur_tree_id], num_data_);
|
||||
} else {
|
||||
// extend init_scores with zeros
|
||||
new_tree->AsConstantTree(0, num_data_);
|
||||
}
|
||||
}
|
||||
// add model
|
||||
models_.push_back(std::move(new_tree));
|
||||
}
|
||||
|
||||
if (!should_continue) {
|
||||
Log::Warning("Stopped training because there are no more leaves that meet the split requirements");
|
||||
if (models_.size() > static_cast<size_t>(num_tree_per_iteration_)) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
models_.pop_back();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
++iter_;
|
||||
return false;
|
||||
}
|
||||
|
||||
void GBDT::RollbackOneIter() {
|
||||
if (iter_ <= 0) {
|
||||
return;
|
||||
}
|
||||
// reset score
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
auto curr_tree = models_.size() - num_tree_per_iteration_ + cur_tree_id;
|
||||
models_[curr_tree]->Shrinkage(-1.0);
|
||||
train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
}
|
||||
// remove model
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
models_.pop_back();
|
||||
}
|
||||
--iter_;
|
||||
}
|
||||
|
||||
bool GBDT::EvalAndCheckEarlyStopping() {
|
||||
bool is_met_early_stopping = false;
|
||||
// print message for metric
|
||||
auto best_msg = OutputMetric(iter_);
|
||||
|
||||
|
||||
is_met_early_stopping = !best_msg.empty();
|
||||
if (is_met_early_stopping) {
|
||||
Log::Info("Early stopping at iteration %d, the best iteration round is %d",
|
||||
iter_, iter_ - early_stopping_round_);
|
||||
Log::Info("Output of best iteration round:\n%s", best_msg.c_str());
|
||||
// pop last early_stopping_round_ models
|
||||
for (int i = 0; i < early_stopping_round_ * num_tree_per_iteration_; ++i) {
|
||||
models_.pop_back();
|
||||
}
|
||||
}
|
||||
return is_met_early_stopping;
|
||||
}
|
||||
|
||||
void GBDT::UpdateScore(const Tree* tree, const int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("GBDT::UpdateScore", global_timer);
|
||||
// update training score
|
||||
if (!data_sample_strategy_->is_use_subset()) {
|
||||
train_score_updater_->AddScore(tree_learner_.get(), tree, cur_tree_id);
|
||||
|
||||
const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt();
|
||||
// we need to predict out-of-bag scores of data for boosting
|
||||
if (num_data_ - bag_data_cnt > 0) {
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
train_score_updater_->AddScore(tree, data_sample_strategy_->cuda_bag_data_indices().RawData() + bag_data_cnt, num_data_ - bag_data_cnt, cur_tree_id);
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
train_score_updater_->AddScore(tree, data_sample_strategy_->bag_data_indices().data() + bag_data_cnt, num_data_ - bag_data_cnt, cur_tree_id);
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
} else {
|
||||
train_score_updater_->AddScore(tree, cur_tree_id);
|
||||
}
|
||||
|
||||
|
||||
// update validation score
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->AddScore(tree, cur_tree_id);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_CUDA
|
||||
std::vector<double> GBDT::EvalOneMetric(const Metric* metric, const double* score, const data_size_t num_data) const {
|
||||
#else
|
||||
std::vector<double> GBDT::EvalOneMetric(const Metric* metric, const double* score, const data_size_t /*num_data*/) const {
|
||||
#endif // USE_CUDA
|
||||
#ifdef USE_CUDA
|
||||
const bool evaluation_on_cuda = metric->IsCUDAMetric();
|
||||
if ((boosting_on_gpu_ && evaluation_on_cuda) || (!boosting_on_gpu_ && !evaluation_on_cuda)) {
|
||||
#endif // USE_CUDA
|
||||
return metric->Eval(score, objective_function_);
|
||||
#ifdef USE_CUDA
|
||||
} else if (boosting_on_gpu_ && !evaluation_on_cuda) {
|
||||
const size_t total_size = static_cast<size_t>(num_data) * static_cast<size_t>(num_tree_per_iteration_);
|
||||
if (total_size > host_score_.size()) {
|
||||
host_score_.resize(total_size, 0.0f);
|
||||
}
|
||||
CopyFromCUDADeviceToHost<double>(host_score_.data(), score, total_size, __FILE__, __LINE__);
|
||||
return metric->Eval(host_score_.data(), objective_function_);
|
||||
} else {
|
||||
const size_t total_size = static_cast<size_t>(num_data) * static_cast<size_t>(num_tree_per_iteration_);
|
||||
if (total_size > cuda_score_.Size()) {
|
||||
cuda_score_.Resize(total_size);
|
||||
}
|
||||
CopyFromHostToCUDADevice<double>(cuda_score_.RawData(), score, total_size, __FILE__, __LINE__);
|
||||
return metric->Eval(cuda_score_.RawData(), objective_function_);
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
std::string GBDT::OutputMetric(int iter) {
|
||||
bool need_output = (iter % config_->metric_freq) == 0;
|
||||
std::string ret = "";
|
||||
std::stringstream msg_buf;
|
||||
std::vector<std::pair<size_t, size_t>> meet_early_stopping_pairs;
|
||||
// print training metric
|
||||
if (need_output) {
|
||||
for (auto& sub_metric : training_metrics_) {
|
||||
auto name = sub_metric->GetName();
|
||||
auto scores = EvalOneMetric(sub_metric, train_score_updater_->score(), train_score_updater_->num_data());
|
||||
for (size_t k = 0; k < name.size(); ++k) {
|
||||
std::stringstream tmp_buf;
|
||||
tmp_buf << "Iteration:" << iter
|
||||
<< ", training " << name[k]
|
||||
<< " : " << scores[k];
|
||||
Log::Info(tmp_buf.str().c_str());
|
||||
if (early_stopping_round_ > 0) {
|
||||
msg_buf << tmp_buf.str() << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// print validation metric
|
||||
if (need_output || early_stopping_round_ > 0) {
|
||||
for (size_t i = 0; i < valid_metrics_.size(); ++i) {
|
||||
for (size_t j = 0; j < valid_metrics_[i].size(); ++j) {
|
||||
auto test_scores = EvalOneMetric(valid_metrics_[i][j], valid_score_updater_[i]->score(), valid_score_updater_[i]->num_data());
|
||||
auto name = valid_metrics_[i][j]->GetName();
|
||||
for (size_t k = 0; k < name.size(); ++k) {
|
||||
std::stringstream tmp_buf;
|
||||
tmp_buf << "Iteration:" << iter
|
||||
<< ", valid_" << i + 1 << " " << name[k]
|
||||
<< " : " << test_scores[k];
|
||||
if (need_output) {
|
||||
Log::Info(tmp_buf.str().c_str());
|
||||
}
|
||||
if (early_stopping_round_ > 0) {
|
||||
msg_buf << tmp_buf.str() << '\n';
|
||||
}
|
||||
}
|
||||
if (es_first_metric_only_ && j > 0) {
|
||||
continue;
|
||||
}
|
||||
if (ret.empty() && early_stopping_round_ > 0) {
|
||||
auto cur_score = valid_metrics_[i][j]->factor_to_bigger_better() * test_scores.back();
|
||||
if (cur_score - best_score_[i][j] > early_stopping_min_delta_) {
|
||||
best_score_[i][j] = cur_score;
|
||||
best_iter_[i][j] = iter;
|
||||
meet_early_stopping_pairs.emplace_back(i, j);
|
||||
} else {
|
||||
if (iter - best_iter_[i][j] >= early_stopping_round_) {
|
||||
ret = best_msg_[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& pair : meet_early_stopping_pairs) {
|
||||
best_msg_[pair.first][pair.second] = msg_buf.str();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! \brief Get eval result */
|
||||
std::vector<double> GBDT::GetEvalAt(int data_idx) const {
|
||||
CHECK(data_idx >= 0 && data_idx <= static_cast<int>(valid_score_updater_.size()));
|
||||
std::vector<double> ret;
|
||||
if (data_idx == 0) {
|
||||
for (auto& sub_metric : training_metrics_) {
|
||||
auto scores = EvalOneMetric(sub_metric, train_score_updater_->score(), train_score_updater_->num_data());
|
||||
for (auto score : scores) {
|
||||
ret.push_back(score);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto used_idx = data_idx - 1;
|
||||
for (size_t j = 0; j < valid_metrics_[used_idx].size(); ++j) {
|
||||
auto test_scores = EvalOneMetric(valid_metrics_[used_idx][j], valid_score_updater_[used_idx]->score(), valid_score_updater_[used_idx]->num_data());
|
||||
for (auto score : test_scores) {
|
||||
ret.push_back(score);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*! \brief Get training scores result */
|
||||
const double* GBDT::GetTrainingScore(int64_t* out_len) {
|
||||
*out_len = static_cast<int64_t>(train_score_updater_->num_data()) * num_class_;
|
||||
return train_score_updater_->score();
|
||||
}
|
||||
|
||||
void GBDT::PredictContrib(const double* features, double* output) const {
|
||||
// set zero
|
||||
const int num_features = max_feature_idx_ + 1;
|
||||
std::memset(output, 0, sizeof(double) * num_tree_per_iteration_ * (num_features + 1));
|
||||
const int end_iteration_for_pred = start_iteration_for_pred_ + num_iteration_for_pred_;
|
||||
for (int i = start_iteration_for_pred_; i < end_iteration_for_pred; ++i) {
|
||||
// predict all the trees for one iteration
|
||||
for (int k = 0; k < num_tree_per_iteration_; ++k) {
|
||||
models_[i * num_tree_per_iteration_ + k]->PredictContrib(features, num_features, output + k*(num_features + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::PredictContribByMap(const std::unordered_map<int, double>& features,
|
||||
std::vector<std::unordered_map<int, double>>* output) const {
|
||||
const int num_features = max_feature_idx_ + 1;
|
||||
const int end_iteration_for_pred = start_iteration_for_pred_ + num_iteration_for_pred_;
|
||||
for (int i = start_iteration_for_pred_; i < end_iteration_for_pred; ++i) {
|
||||
// predict all the trees for one iteration
|
||||
for (int k = 0; k < num_tree_per_iteration_; ++k) {
|
||||
models_[i * num_tree_per_iteration_ + k]->PredictContribByMap(features, num_features, &((*output)[k]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::GetPredictAt(int data_idx, double* out_result, int64_t* out_len) {
|
||||
CHECK(data_idx >= 0 && data_idx <= static_cast<int>(valid_score_updater_.size()));
|
||||
|
||||
const double* raw_scores = nullptr;
|
||||
data_size_t num_data = 0;
|
||||
if (data_idx == 0) {
|
||||
raw_scores = GetTrainingScore(out_len);
|
||||
num_data = train_score_updater_->num_data();
|
||||
} else {
|
||||
auto used_idx = data_idx - 1;
|
||||
raw_scores = valid_score_updater_[used_idx]->score();
|
||||
num_data = valid_score_updater_[used_idx]->num_data();
|
||||
*out_len = static_cast<int64_t>(num_data) * num_class_;
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
std::vector<double> host_raw_scores;
|
||||
if (boosting_on_gpu_) {
|
||||
host_raw_scores.resize(static_cast<size_t>(*out_len), 0.0);
|
||||
CopyFromCUDADeviceToHost<double>(host_raw_scores.data(), raw_scores, static_cast<size_t>(*out_len), __FILE__, __LINE__);
|
||||
raw_scores = host_raw_scores.data();
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
if (objective_function_ != nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
std::vector<double> tree_pred(num_tree_per_iteration_);
|
||||
for (int j = 0; j < num_tree_per_iteration_; ++j) {
|
||||
tree_pred[j] = raw_scores[j * num_data + i];
|
||||
}
|
||||
std::vector<double> tmp_result(num_class_);
|
||||
objective_function_->ConvertOutput(tree_pred.data(), tmp_result.data());
|
||||
for (int j = 0; j < num_class_; ++j) {
|
||||
out_result[j * num_data + i] = static_cast<double>(tmp_result[j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
for (int j = 0; j < num_tree_per_iteration_; ++j) {
|
||||
out_result[j * num_data + i] = static_cast<double>(raw_scores[j * num_data + i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double GBDT::GetUpperBoundValue() const {
|
||||
double max_value = 0.0;
|
||||
for (const auto &tree : models_) {
|
||||
max_value += tree->GetUpperBoundValue();
|
||||
}
|
||||
return max_value;
|
||||
}
|
||||
|
||||
double GBDT::GetLowerBoundValue() const {
|
||||
double min_value = 0.0;
|
||||
for (const auto &tree : models_) {
|
||||
min_value += tree->GetLowerBoundValue();
|
||||
}
|
||||
return min_value;
|
||||
}
|
||||
|
||||
void GBDT::ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) {
|
||||
if (train_data != train_data_ && !train_data_->CheckAlign(*train_data)) {
|
||||
Log::Fatal("Cannot reset training data, since new training data has different bin mappers");
|
||||
}
|
||||
|
||||
objective_function_ = objective_function;
|
||||
data_sample_strategy_->UpdateObjectiveFunction(objective_function);
|
||||
if (objective_function_ != nullptr) {
|
||||
CHECK_EQ(num_tree_per_iteration_, objective_function_->NumModelPerIteration());
|
||||
if (objective_function_->IsRenewTreeOutput() && !config_->monotone_constraints.empty()) {
|
||||
Log::Fatal("Cannot use ``monotone_constraints`` in %s objective, please disable it.", objective_function_->GetName());
|
||||
}
|
||||
}
|
||||
is_constant_hessian_ = GetIsConstHessian(objective_function);
|
||||
|
||||
// push training metrics
|
||||
training_metrics_.clear();
|
||||
for (const auto& metric : training_metrics) {
|
||||
training_metrics_.push_back(metric);
|
||||
}
|
||||
training_metrics_.shrink_to_fit();
|
||||
|
||||
#ifdef USE_CUDA
|
||||
boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() &&
|
||||
!data_sample_strategy_->IsHessianChange(); // for sample strategy with Hessian change, fall back to boosting on CPU
|
||||
tree_learner_->ResetBoostingOnGPU(boosting_on_gpu_);
|
||||
#endif // USE_CUDA
|
||||
|
||||
if (train_data != train_data_) {
|
||||
train_data_ = train_data;
|
||||
data_sample_strategy_->UpdateTrainingData(train_data);
|
||||
// not same training data, need reset score and others
|
||||
// create score tracker
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
train_score_updater_.reset(new CUDAScoreUpdater(train_data_, num_tree_per_iteration_, boosting_on_gpu_));
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
train_score_updater_.reset(new ScoreUpdater(train_data_, num_tree_per_iteration_));
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
|
||||
// update score
|
||||
for (int i = 0; i < iter_; ++i) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
auto curr_tree = (i + num_init_iteration_) * num_tree_per_iteration_ + cur_tree_id;
|
||||
train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
}
|
||||
|
||||
num_data_ = train_data_->num_data();
|
||||
|
||||
ResetGradientBuffers();
|
||||
|
||||
max_feature_idx_ = train_data_->num_total_features() - 1;
|
||||
label_idx_ = train_data_->label_idx();
|
||||
feature_names_ = train_data_->feature_names();
|
||||
feature_infos_ = train_data_->feature_infos();
|
||||
parser_config_str_ = train_data_->parser_config_str();
|
||||
|
||||
tree_learner_->ResetTrainingData(train_data, is_constant_hessian_);
|
||||
data_sample_strategy_->ResetSampleConfig(config_.get(), true);
|
||||
} else {
|
||||
tree_learner_->ResetIsConstantHessian(is_constant_hessian_);
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::ResetConfig(const Config* config) {
|
||||
auto new_config = std::unique_ptr<Config>(new Config(*config));
|
||||
if (!config->monotone_constraints.empty()) {
|
||||
CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->monotone_constraints.size());
|
||||
}
|
||||
if (!config->feature_contri.empty()) {
|
||||
CHECK_EQ(static_cast<size_t>(train_data_->num_total_features()), config->feature_contri.size());
|
||||
}
|
||||
if (objective_function_ != nullptr && objective_function_->IsRenewTreeOutput() && !config->monotone_constraints.empty()) {
|
||||
Log::Fatal("Cannot use ``monotone_constraints`` in %s objective, please disable it.", objective_function_->GetName());
|
||||
}
|
||||
early_stopping_round_ = new_config->early_stopping_round;
|
||||
shrinkage_rate_ = new_config->learning_rate;
|
||||
if (tree_learner_ != nullptr) {
|
||||
tree_learner_->ResetConfig(new_config.get());
|
||||
}
|
||||
|
||||
boosting_on_gpu_ = objective_function_ != nullptr && objective_function_->IsCUDAObjective() &&
|
||||
!data_sample_strategy_->IsHessianChange(); // for sample strategy with Hessian change, fall back to boosting on CPU
|
||||
tree_learner_->ResetBoostingOnGPU(boosting_on_gpu_);
|
||||
|
||||
if (train_data_ != nullptr) {
|
||||
data_sample_strategy_->ResetSampleConfig(new_config.get(), false);
|
||||
if (data_sample_strategy_->NeedResizeGradients()) {
|
||||
// resize gradient vectors to copy the customized gradients for goss or bagging with subset
|
||||
ResetGradientBuffers();
|
||||
}
|
||||
}
|
||||
if (config_.get() != nullptr && config_->forcedsplits_filename != new_config->forcedsplits_filename) {
|
||||
// load forced_splits file
|
||||
if (!new_config->forcedsplits_filename.empty()) {
|
||||
std::ifstream forced_splits_file(
|
||||
new_config->forcedsplits_filename.c_str());
|
||||
std::stringstream buffer;
|
||||
buffer << forced_splits_file.rdbuf();
|
||||
std::string err;
|
||||
forced_splits_json_ = Json::parse(buffer.str(), &err);
|
||||
tree_learner_->SetForcedSplit(&forced_splits_json_);
|
||||
} else {
|
||||
forced_splits_json_ = Json();
|
||||
tree_learner_->SetForcedSplit(nullptr);
|
||||
}
|
||||
}
|
||||
config_.reset(new_config.release());
|
||||
}
|
||||
|
||||
void GBDT::ResetGradientBuffers() {
|
||||
const size_t total_size = static_cast<size_t>(num_data_) * num_tree_per_iteration_;
|
||||
const bool is_use_subset = data_sample_strategy_->is_use_subset();
|
||||
const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt();
|
||||
if (objective_function_ != nullptr) {
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda") && boosting_on_gpu_) {
|
||||
if (cuda_gradients_.Size() < total_size) {
|
||||
cuda_gradients_.Resize(total_size);
|
||||
cuda_hessians_.Resize(total_size);
|
||||
}
|
||||
gradients_pointer_ = cuda_gradients_.RawData();
|
||||
hessians_pointer_ = cuda_hessians_.RawData();
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
if (gradients_.size() < total_size) {
|
||||
gradients_.resize(total_size);
|
||||
hessians_.resize(total_size);
|
||||
}
|
||||
gradients_pointer_ = gradients_.data();
|
||||
hessians_pointer_ = hessians_.data();
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
} else if (data_sample_strategy_->IsHessianChange() || (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_)) {
|
||||
if (gradients_.size() < total_size) {
|
||||
gradients_.resize(total_size);
|
||||
hessians_.resize(total_size);
|
||||
}
|
||||
gradients_pointer_ = gradients_.data();
|
||||
hessians_pointer_ = hessians_.data();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,625 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_GBDT_H_
|
||||
#define LIGHTGBM_SRC_BOOSTING_GBDT_H_
|
||||
|
||||
#include <LightGBM/boosting.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/prediction_early_stop.h>
|
||||
#include <LightGBM/cuda/vector_cudahost.h>
|
||||
#include <LightGBM/utils/json11.h>
|
||||
#include <LightGBM/utils/threading.h>
|
||||
#include <LightGBM/sample_strategy.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "cuda/cuda_score_updater.hpp"
|
||||
#include "score_updater.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
using json11_internal_lightgbm::Json;
|
||||
|
||||
/*!
|
||||
* \brief GBDT algorithm implementation. including Training, prediction, bagging.
|
||||
*/
|
||||
class GBDT : public GBDTBase {
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructor
|
||||
*/
|
||||
GBDT();
|
||||
|
||||
/*!
|
||||
* \brief Destructor
|
||||
*/
|
||||
~GBDT();
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Initialization logic
|
||||
* \param gbdt_config Config for boosting
|
||||
* \param train_data Training data
|
||||
* \param objective_function Training objective function
|
||||
* \param training_metrics Training metrics
|
||||
*/
|
||||
void Init(const Config* gbdt_config, const Dataset* train_data,
|
||||
const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) override;
|
||||
|
||||
/*!
|
||||
* \brief Traverse the tree of forced splits and check that all indices are less than the number of features.
|
||||
*/
|
||||
void CheckForcedSplitFeatures();
|
||||
|
||||
/*!
|
||||
* \brief Merge model from other boosting object. Will insert to the front of current boosting object
|
||||
* \param other
|
||||
*/
|
||||
void MergeFrom(const Boosting* other) override {
|
||||
auto other_gbdt = reinterpret_cast<const GBDT*>(other);
|
||||
// tmp move to other vector
|
||||
auto original_models = std::move(models_);
|
||||
models_ = std::vector<std::unique_ptr<Tree>>();
|
||||
// push model from other first
|
||||
for (const auto& tree : other_gbdt->models_) {
|
||||
auto new_tree = std::unique_ptr<Tree>(new Tree(*(tree.get())));
|
||||
models_.push_back(std::move(new_tree));
|
||||
}
|
||||
num_init_iteration_ = static_cast<int>(models_.size()) / num_tree_per_iteration_;
|
||||
// push model in current object
|
||||
for (const auto& tree : original_models) {
|
||||
auto new_tree = std::unique_ptr<Tree>(new Tree(*(tree.get())));
|
||||
models_.push_back(std::move(new_tree));
|
||||
}
|
||||
num_iteration_for_pred_ = static_cast<int>(models_.size()) / num_tree_per_iteration_;
|
||||
}
|
||||
|
||||
void ShuffleModels(int start_iter, int end_iter) override {
|
||||
int total_iter = static_cast<int>(models_.size()) / num_tree_per_iteration_;
|
||||
start_iter = std::max(0, start_iter);
|
||||
if (end_iter <= 0) {
|
||||
end_iter = total_iter;
|
||||
}
|
||||
end_iter = std::min(total_iter, end_iter);
|
||||
auto original_models = std::move(models_);
|
||||
std::vector<int> indices(total_iter);
|
||||
for (int i = 0; i < total_iter; ++i) {
|
||||
indices[i] = i;
|
||||
}
|
||||
Random tmp_rand(17);
|
||||
for (int i = start_iter; i < end_iter - 1; ++i) {
|
||||
int j = tmp_rand.NextShort(i + 1, end_iter);
|
||||
std::swap(indices[i], indices[j]);
|
||||
}
|
||||
models_ = std::vector<std::unique_ptr<Tree>>();
|
||||
for (int i = 0; i < total_iter; ++i) {
|
||||
for (int j = 0; j < num_tree_per_iteration_; ++j) {
|
||||
int tree_idx = indices[i] * num_tree_per_iteration_ + j;
|
||||
auto new_tree = std::unique_ptr<Tree>(new Tree(*(original_models[tree_idx].get())));
|
||||
models_.push_back(std::move(new_tree));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Reset the training data
|
||||
* \param train_data New Training data
|
||||
* \param objective_function Training objective function
|
||||
* \param training_metrics Training metrics
|
||||
*/
|
||||
void ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) override;
|
||||
|
||||
/*!
|
||||
* \brief Reset Boosting Config
|
||||
* \param gbdt_config Config for boosting
|
||||
*/
|
||||
void ResetConfig(const Config* gbdt_config) override;
|
||||
|
||||
/*!
|
||||
* \brief Adding a validation dataset
|
||||
* \param valid_data Validation dataset
|
||||
* \param valid_metrics Metrics for validation dataset
|
||||
*/
|
||||
void AddValidDataset(const Dataset* valid_data,
|
||||
const std::vector<const Metric*>& valid_metrics) override;
|
||||
|
||||
/*!
|
||||
* \brief Perform a full training procedure
|
||||
* \param snapshot_freq frequency of snapshot
|
||||
* \param model_output_path path of model file
|
||||
*/
|
||||
void Train(int snapshot_freq, const std::string& model_output_path) override;
|
||||
|
||||
void RefitTree(const int* tree_leaf_prediction, const size_t nrow, const size_t ncol) override;
|
||||
|
||||
/*!
|
||||
* \brief Training logic
|
||||
* \param gradients nullptr for using default objective, otherwise use self-defined boosting
|
||||
* \param hessians nullptr for using default objective, otherwise use self-defined boosting
|
||||
* \return True if cannot train any more
|
||||
*/
|
||||
bool TrainOneIter(const score_t* gradients, const score_t* hessians) override;
|
||||
|
||||
/*!
|
||||
* \brief Rollback one iteration
|
||||
*/
|
||||
void RollbackOneIter() override;
|
||||
|
||||
/*!
|
||||
* \brief Get current iteration
|
||||
*/
|
||||
int GetCurrentIteration() const override { return static_cast<int>(models_.size()) / num_tree_per_iteration_; }
|
||||
|
||||
/*!
|
||||
* \brief Get parameters as a JSON string
|
||||
*/
|
||||
std::string GetLoadedParam() const override {
|
||||
if (loaded_parameter_.empty()) {
|
||||
return std::string("{}");
|
||||
}
|
||||
const auto param_types = Config::ParameterTypes();
|
||||
const auto lines = Common::Split(loaded_parameter_.c_str(), "\n");
|
||||
bool first = true;
|
||||
std::stringstream str_buf;
|
||||
str_buf << "{";
|
||||
for (const auto& line : lines) {
|
||||
const auto pair = Common::Split(line.c_str(), ":");
|
||||
if (pair[1] == " ]")
|
||||
continue;
|
||||
const auto param = pair[0].substr(1);
|
||||
const auto value_str = pair[1].substr(1, pair[1].size() - 2);
|
||||
auto iter = param_types.find(param);
|
||||
if (iter == param_types.end()) {
|
||||
Log::Warning("Ignoring unrecognized parameter '%s' found in model string.", param.c_str());
|
||||
continue;
|
||||
}
|
||||
std::string param_type = iter->second;
|
||||
if (first) {
|
||||
first = false;
|
||||
str_buf << "\"";
|
||||
} else {
|
||||
str_buf << ",\"";
|
||||
}
|
||||
str_buf << param << "\": ";
|
||||
if (param_type == "string") {
|
||||
str_buf << "\"" << value_str << "\"";
|
||||
} else if (param_type == "int") {
|
||||
int value;
|
||||
Common::Atoi(value_str.c_str(), &value);
|
||||
str_buf << value;
|
||||
} else if (param_type == "double") {
|
||||
double value;
|
||||
Common::Atof(value_str.c_str(), &value);
|
||||
str_buf << value;
|
||||
} else if (param_type == "bool") {
|
||||
bool value = value_str == "1";
|
||||
str_buf << std::boolalpha << value;
|
||||
} else if (param_type.substr(0, 6) == "vector") {
|
||||
str_buf << "[";
|
||||
if (param_type.substr(7, 6) == "string") {
|
||||
const auto parts = Common::Split(value_str.c_str(), ",");
|
||||
str_buf << "\"" << Common::Join(parts, "\",\"") << "\"";
|
||||
} else {
|
||||
str_buf << value_str;
|
||||
}
|
||||
str_buf << "]";
|
||||
}
|
||||
}
|
||||
str_buf << "}";
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Can use early stopping for prediction or not
|
||||
* \return True if cannot use early stopping for prediction
|
||||
*/
|
||||
bool NeedAccuratePrediction() const override {
|
||||
if (objective_function_ == nullptr) {
|
||||
return true;
|
||||
} else {
|
||||
return objective_function_->NeedAccuratePrediction();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Get evaluation result at data_idx data
|
||||
* \param data_idx 0: training data, 1: 1st validation data
|
||||
* \return evaluation result
|
||||
*/
|
||||
std::vector<double> GetEvalAt(int data_idx) const override;
|
||||
|
||||
/*!
|
||||
* \brief Get current training score
|
||||
* \param out_len length of returned score
|
||||
* \return training score
|
||||
*/
|
||||
const double* GetTrainingScore(int64_t* out_len) override;
|
||||
|
||||
/*!
|
||||
* \brief Get size of prediction at data_idx data
|
||||
* \param data_idx 0: training data, 1: 1st validation data
|
||||
* \return The size of prediction
|
||||
*/
|
||||
int64_t GetNumPredictAt(int data_idx) const override {
|
||||
CHECK(data_idx >= 0 && data_idx <= static_cast<int>(valid_score_updater_.size()));
|
||||
data_size_t num_data = train_data_->num_data();
|
||||
if (data_idx > 0) {
|
||||
num_data = valid_score_updater_[data_idx - 1]->num_data();
|
||||
}
|
||||
return static_cast<int64_t>(num_data) * num_class_;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Get prediction result at data_idx data
|
||||
* \param data_idx 0: training data, 1: 1st validation data
|
||||
* \param result used to store prediction result, should allocate memory before call this function
|
||||
* \param out_len length of returned score
|
||||
*/
|
||||
void GetPredictAt(int data_idx, double* out_result, int64_t* out_len) override;
|
||||
|
||||
/*!
|
||||
* \brief Get number of prediction for one data
|
||||
* \param start_iteration Start index of the iteration to predict
|
||||
* \param num_iteration number of used iterations
|
||||
* \param is_pred_leaf True if predicting leaf index
|
||||
* \param is_pred_contrib True if predicting feature contribution
|
||||
* \return number of prediction
|
||||
*/
|
||||
inline int NumPredictOneRow(int start_iteration, int num_iteration, bool is_pred_leaf, bool is_pred_contrib) const override {
|
||||
int num_pred_in_one_row = num_class_;
|
||||
if (is_pred_leaf) {
|
||||
int max_iteration = GetCurrentIteration();
|
||||
start_iteration = std::max(start_iteration, 0);
|
||||
start_iteration = std::min(start_iteration, max_iteration);
|
||||
if (num_iteration > 0) {
|
||||
num_pred_in_one_row *= static_cast<int>(std::min(max_iteration - start_iteration, num_iteration));
|
||||
} else {
|
||||
num_pred_in_one_row *= (max_iteration - start_iteration);
|
||||
}
|
||||
} else if (is_pred_contrib) {
|
||||
num_pred_in_one_row = num_tree_per_iteration_ * (max_feature_idx_ + 2); // +1 for 0-based indexing, +1 for baseline
|
||||
}
|
||||
return num_pred_in_one_row;
|
||||
}
|
||||
|
||||
void PredictRaw(const double* features, double* output,
|
||||
const PredictionEarlyStopInstance* earlyStop) const override;
|
||||
|
||||
void PredictRawByMap(const std::unordered_map<int, double>& features, double* output,
|
||||
const PredictionEarlyStopInstance* early_stop) const override;
|
||||
|
||||
void Predict(const double* features, double* output,
|
||||
const PredictionEarlyStopInstance* earlyStop) const override;
|
||||
|
||||
void PredictByMap(const std::unordered_map<int, double>& features, double* output,
|
||||
const PredictionEarlyStopInstance* early_stop) const override;
|
||||
|
||||
void PredictLeafIndex(const double* features, double* output) const override;
|
||||
|
||||
void PredictLeafIndexByMap(const std::unordered_map<int, double>& features, double* output) const override;
|
||||
|
||||
void PredictContrib(const double* features, double* output) const override;
|
||||
|
||||
void PredictContribByMap(const std::unordered_map<int, double>& features,
|
||||
std::vector<std::unordered_map<int, double>>* output) const override;
|
||||
|
||||
/*!
|
||||
* \brief Dump model to json format string
|
||||
* \param start_iteration The model will be saved start from
|
||||
* \param num_iteration Number of iterations that want to dump, -1 means dump all
|
||||
* \param feature_importance_type Type of feature importance, 0: split, 1: gain
|
||||
* \return Json format string of model
|
||||
*/
|
||||
std::string DumpModel(int start_iteration, int num_iteration,
|
||||
int feature_importance_type) const override;
|
||||
|
||||
/*!
|
||||
* \brief Translate model to if-else statement
|
||||
* \param num_iteration Number of iterations that want to translate, -1 means translate all
|
||||
* \return if-else format codes of model
|
||||
*/
|
||||
std::string ModelToIfElse(int num_iteration) const override;
|
||||
|
||||
/*!
|
||||
* \brief Translate model to if-else statement
|
||||
* \param num_iteration Number of iterations that want to translate, -1 means translate all
|
||||
* \param filename Filename that want to save to
|
||||
* \return is_finish Is training finished or not
|
||||
*/
|
||||
bool SaveModelToIfElse(int num_iteration, const char* filename) const override;
|
||||
|
||||
/*!
|
||||
* \brief Save model to file
|
||||
* \param start_iteration The model will be saved start from
|
||||
* \param num_iterations Number of model that want to save, -1 means save all
|
||||
* \param feature_importance_type Type of feature importance, 0: split, 1: gain
|
||||
* \param filename Filename that want to save to
|
||||
* \return is_finish Is training finished or not
|
||||
*/
|
||||
bool SaveModelToFile(int start_iteration, int num_iterations,
|
||||
int feature_importance_type,
|
||||
const char* filename) const override;
|
||||
|
||||
/*!
|
||||
* \brief Save model to string
|
||||
* \param start_iteration The model will be saved start from
|
||||
* \param num_iterations Number of model that want to save, -1 means save all
|
||||
* \param feature_importance_type Type of feature importance, 0: split, 1: gain
|
||||
* \return Non-empty string if succeeded
|
||||
*/
|
||||
std::string SaveModelToString(int start_iteration, int num_iterations, int feature_importance_type) const override;
|
||||
|
||||
/*!
|
||||
* \brief Restore from a serialized buffer
|
||||
*/
|
||||
bool LoadModelFromString(const char* buffer, size_t len) override;
|
||||
|
||||
/*!
|
||||
* \brief Calculate feature importances
|
||||
* \param num_iteration Number of model that want to use for feature importance, -1 means use all
|
||||
* \param importance_type: 0 for split, 1 for gain
|
||||
* \return vector of feature_importance
|
||||
*/
|
||||
std::vector<double> FeatureImportance(int num_iteration, int importance_type) const override;
|
||||
|
||||
/*!
|
||||
* \brief Calculate upper bound value
|
||||
* \return upper bound value
|
||||
*/
|
||||
double GetUpperBoundValue() const override;
|
||||
|
||||
/*!
|
||||
* \brief Calculate lower bound value
|
||||
* \return lower bound value
|
||||
*/
|
||||
double GetLowerBoundValue() const override;
|
||||
|
||||
/*!
|
||||
* \brief Get max feature index of this model
|
||||
* \return Max feature index of this model
|
||||
*/
|
||||
inline int MaxFeatureIdx() const override { return max_feature_idx_; }
|
||||
|
||||
/*!
|
||||
* \brief Get feature names of this model
|
||||
* \return Feature names of this model
|
||||
*/
|
||||
inline std::vector<std::string> FeatureNames() const override { return feature_names_; }
|
||||
|
||||
/*!
|
||||
* \brief Get index of label column
|
||||
* \return index of label column
|
||||
*/
|
||||
inline int LabelIdx() const override { return label_idx_; }
|
||||
|
||||
/*!
|
||||
* \brief Get number of weak sub-models
|
||||
* \return Number of weak sub-models
|
||||
*/
|
||||
inline int NumberOfTotalModel() const override { return static_cast<int>(models_.size()); }
|
||||
|
||||
/*!
|
||||
* \brief Get number of tree per iteration
|
||||
* \return number of tree per iteration
|
||||
*/
|
||||
inline int NumModelPerIteration() const override { return num_tree_per_iteration_; }
|
||||
|
||||
/*!
|
||||
* \brief Get number of classes
|
||||
* \return Number of classes
|
||||
*/
|
||||
inline int NumberOfClasses() const override { return num_class_; }
|
||||
|
||||
inline void InitPredict(int start_iteration, int num_iteration, bool is_pred_contrib) override {
|
||||
num_iteration_for_pred_ = static_cast<int>(models_.size()) / num_tree_per_iteration_;
|
||||
start_iteration = std::max(start_iteration, 0);
|
||||
start_iteration = std::min(start_iteration, num_iteration_for_pred_);
|
||||
if (num_iteration > 0) {
|
||||
num_iteration_for_pred_ = std::min(num_iteration, num_iteration_for_pred_ - start_iteration);
|
||||
} else {
|
||||
num_iteration_for_pred_ = num_iteration_for_pred_ - start_iteration;
|
||||
}
|
||||
start_iteration_for_pred_ = start_iteration;
|
||||
|
||||
if (is_pred_contrib && !models_initialized_) {
|
||||
std::lock_guard<std::mutex> lock(instance_mutex_);
|
||||
if (models_initialized_)
|
||||
return;
|
||||
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int i = 0; i < static_cast<int>(models_.size()); ++i) {
|
||||
models_[i]->RecomputeMaxDepth();
|
||||
}
|
||||
|
||||
models_initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
inline double GetLeafValue(int tree_idx, int leaf_idx) const override {
|
||||
CHECK(tree_idx >= 0 && static_cast<size_t>(tree_idx) < models_.size());
|
||||
CHECK(leaf_idx >= 0 && leaf_idx < models_[tree_idx]->num_leaves());
|
||||
return models_[tree_idx]->LeafOutput(leaf_idx);
|
||||
}
|
||||
|
||||
inline void SetLeafValue(int tree_idx, int leaf_idx, double val) override {
|
||||
CHECK(tree_idx >= 0 && static_cast<size_t>(tree_idx) < models_.size());
|
||||
CHECK(leaf_idx >= 0 && leaf_idx < models_[tree_idx]->num_leaves());
|
||||
models_[tree_idx]->SetLeafOutput(leaf_idx, val);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Get Type name of this boosting object
|
||||
*/
|
||||
const char* SubModelName() const override { return "tree"; }
|
||||
|
||||
bool IsLinear() const override { return linear_tree_; }
|
||||
|
||||
inline std::string ParserConfigStr() const override {return parser_config_str_;}
|
||||
|
||||
protected:
|
||||
virtual bool GetIsConstHessian(const ObjectiveFunction* objective_function) {
|
||||
if (objective_function != nullptr && !data_sample_strategy_->IsHessianChange()) {
|
||||
return objective_function->IsConstantHessian();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/*!
|
||||
* \brief Print eval result and check early stopping
|
||||
*/
|
||||
virtual bool EvalAndCheckEarlyStopping();
|
||||
|
||||
/*!
|
||||
* \brief reset config for bagging
|
||||
*/
|
||||
void ResetBaggingConfig(const Config* config, bool is_change_dataset);
|
||||
|
||||
/*!
|
||||
* \brief calculate the objective function
|
||||
*/
|
||||
virtual void Boosting();
|
||||
|
||||
/*!
|
||||
* \brief updating score after tree was trained
|
||||
* \param tree Trained tree of this iteration
|
||||
* \param cur_tree_id Current tree for multiclass training
|
||||
*/
|
||||
virtual void UpdateScore(const Tree* tree, const int cur_tree_id);
|
||||
|
||||
/*!
|
||||
* \brief eval results for one metric
|
||||
|
||||
*/
|
||||
virtual std::vector<double> EvalOneMetric(const Metric* metric, const double* score, const data_size_t num_data) const;
|
||||
|
||||
/*!
|
||||
* \brief Print metric result of current iteration
|
||||
* \param iter Current iteration
|
||||
* \return best_msg if met early_stopping
|
||||
*/
|
||||
std::string OutputMetric(int iter);
|
||||
|
||||
virtual double BoostFromAverage(int class_id, bool update_scorer);
|
||||
|
||||
/*!
|
||||
* \brief Reset gradient buffers, must be called after sample strategy is reset
|
||||
*/
|
||||
void ResetGradientBuffers();
|
||||
|
||||
/*! \brief current iteration */
|
||||
int iter_;
|
||||
/*! \brief Pointer to training data */
|
||||
const Dataset* train_data_;
|
||||
/*! \brief Config of gbdt */
|
||||
std::unique_ptr<Config> config_;
|
||||
/*! \brief Tree learner, will use this class to learn trees */
|
||||
std::unique_ptr<TreeLearner> tree_learner_;
|
||||
/*! \brief Objective function */
|
||||
const ObjectiveFunction* objective_function_;
|
||||
/*! \brief Store and update training data's score */
|
||||
std::unique_ptr<ScoreUpdater> train_score_updater_;
|
||||
/*! \brief Metrics for training data */
|
||||
std::vector<const Metric*> training_metrics_;
|
||||
/*! \brief Store and update validation data's scores */
|
||||
std::vector<std::unique_ptr<ScoreUpdater>> valid_score_updater_;
|
||||
/*! \brief Metric for validation data */
|
||||
std::vector<std::vector<const Metric*>> valid_metrics_;
|
||||
/*! \brief Number of rounds for early stopping */
|
||||
int early_stopping_round_;
|
||||
/*! \brief Minimum improvement for early stopping */
|
||||
double early_stopping_min_delta_;
|
||||
/*! \brief Only use first metric for early stopping */
|
||||
bool es_first_metric_only_;
|
||||
/*! \brief Best iteration(s) for early stopping */
|
||||
std::vector<std::vector<int>> best_iter_;
|
||||
/*! \brief Best score(s) for early stopping */
|
||||
std::vector<std::vector<double>> best_score_;
|
||||
/*! \brief output message of best iteration */
|
||||
std::vector<std::vector<std::string>> best_msg_;
|
||||
/*! \brief Trained models(trees) */
|
||||
std::vector<std::unique_ptr<Tree>> models_;
|
||||
/*! \brief Max feature index of training data*/
|
||||
int max_feature_idx_;
|
||||
/*! \brief Parser config file content */
|
||||
std::string parser_config_str_ = "";
|
||||
/*! \brief Are the models initialized (passed RecomputeMaxDepth phase) */
|
||||
bool models_initialized_ = false;
|
||||
/*! \brief Mutex for exclusive models initialization */
|
||||
std::mutex instance_mutex_;
|
||||
|
||||
#ifdef USE_CUDA
|
||||
/*! \brief First order derivative of training data */
|
||||
std::vector<score_t, CHAllocator<score_t>> gradients_;
|
||||
/*! \brief Second order derivative of training data */
|
||||
std::vector<score_t, CHAllocator<score_t>> hessians_;
|
||||
#else
|
||||
/*! \brief First order derivative of training data */
|
||||
std::vector<score_t, Common::AlignmentAllocator<score_t, kAlignedSize>> gradients_;
|
||||
/*! \brief Second order derivative of training data */
|
||||
std::vector<score_t, Common::AlignmentAllocator<score_t, kAlignedSize>> hessians_;
|
||||
#endif
|
||||
/*! \brief Pointer to gradient vector, can be on CPU or GPU */
|
||||
score_t* gradients_pointer_;
|
||||
/*! \brief Pointer to hessian vector, can be on CPU or GPU */
|
||||
score_t* hessians_pointer_;
|
||||
/*! \brief Whether boosting is done on GPU, used for device_type=cuda */
|
||||
bool boosting_on_gpu_;
|
||||
#ifdef USE_CUDA
|
||||
/*! \brief Gradient vector on GPU */
|
||||
CUDAVector<score_t> cuda_gradients_;
|
||||
/*! \brief Hessian vector on GPU */
|
||||
CUDAVector<score_t> cuda_hessians_;
|
||||
/*! \brief Buffer for scores when boosting is on GPU but evaluation is not, used only with device_type=cuda */
|
||||
mutable std::vector<double> host_score_;
|
||||
/*! \brief Buffer for scores when boosting is not on GPU but evaluation is, used only with device_type=cuda */
|
||||
mutable CUDAVector<double> cuda_score_;
|
||||
#endif // USE_CUDA
|
||||
|
||||
/*! \brief Number of training data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Number of trees per iterations */
|
||||
int num_tree_per_iteration_;
|
||||
/*! \brief Number of class */
|
||||
int num_class_;
|
||||
/*! \brief Index of label column */
|
||||
data_size_t label_idx_;
|
||||
/*! \brief number of used model */
|
||||
int num_iteration_for_pred_;
|
||||
/*! \brief Start iteration of used model */
|
||||
int start_iteration_for_pred_;
|
||||
/*! \brief Shrinkage rate for one iteration */
|
||||
double shrinkage_rate_;
|
||||
/*! \brief Number of loaded initial models */
|
||||
int num_init_iteration_;
|
||||
/*! \brief Feature names */
|
||||
std::vector<std::string> feature_names_;
|
||||
std::vector<std::string> feature_infos_;
|
||||
std::vector<bool> class_need_train_;
|
||||
bool is_constant_hessian_;
|
||||
std::unique_ptr<ObjectiveFunction> loaded_objective_;
|
||||
bool average_output_;
|
||||
bool need_re_bagging_;
|
||||
bool balanced_bagging_;
|
||||
std::string loaded_parameter_;
|
||||
std::vector<int8_t> monotone_constraints_;
|
||||
Json forced_splits_json_;
|
||||
bool linear_tree_;
|
||||
std::unique_ptr<SampleStrategy> data_sample_strategy_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_GBDT_H_
|
||||
@@ -0,0 +1,667 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/config.h>
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/utils/array_args.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gbdt.h"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
const char* kModelVersion = "v4";
|
||||
|
||||
std::string GBDT::DumpModel(int start_iteration, int num_iteration, int feature_importance_type) const {
|
||||
std::stringstream str_buf;
|
||||
Common::C_stringstream(str_buf);
|
||||
|
||||
str_buf << "{";
|
||||
str_buf << "\"name\":\"" << SubModelName() << "\"," << '\n';
|
||||
str_buf << "\"version\":\"" << kModelVersion << "\"," << '\n';
|
||||
str_buf << "\"num_class\":" << num_class_ << "," << '\n';
|
||||
str_buf << "\"num_tree_per_iteration\":" << num_tree_per_iteration_ << "," << '\n';
|
||||
str_buf << "\"label_index\":" << label_idx_ << "," << '\n';
|
||||
str_buf << "\"max_feature_idx\":" << max_feature_idx_ << "," << '\n';
|
||||
if (objective_function_ != nullptr) {
|
||||
str_buf << "\"objective\":\"" << objective_function_->ToString() << "\",\n";
|
||||
}
|
||||
|
||||
str_buf << "\"average_output\":" << (average_output_ ? "true" : "false") << ",\n";
|
||||
|
||||
str_buf << "\"feature_names\":[\"" << CommonC::Join(feature_names_, "\",\"")
|
||||
<< "\"]," << '\n';
|
||||
|
||||
str_buf << "\"monotone_constraints\":["
|
||||
<< CommonC::Join(monotone_constraints_, ",") << "]," << '\n';
|
||||
|
||||
str_buf << "\"feature_infos\":" << "{";
|
||||
bool first_obj = true;
|
||||
for (size_t i = 0; i < feature_infos_.size(); ++i) {
|
||||
std::stringstream json_str_buf;
|
||||
Common::C_stringstream(json_str_buf);
|
||||
auto strs = Common::Split(feature_infos_[i].c_str(), ":");
|
||||
if (strs[0][0] == '[') {
|
||||
strs[0].erase(0, 1); // remove '['
|
||||
strs[1].erase(strs[1].size() - 1); // remove ']'
|
||||
double max_, min_;
|
||||
Common::Atof(strs[0].c_str(), &min_);
|
||||
Common::Atof(strs[1].c_str(), &max_);
|
||||
json_str_buf << std::setprecision(std::numeric_limits<double>::digits10 + 2);
|
||||
json_str_buf << "{\"min_value\":" << Common::AvoidInf(min_) << ",";
|
||||
json_str_buf << "\"max_value\":" << Common::AvoidInf(max_) << ",";
|
||||
json_str_buf << "\"values\":[]}";
|
||||
} else if (strs[0] != "none") { // categorical feature
|
||||
auto vals = CommonC::StringToArray<int>(feature_infos_[i], ':');
|
||||
auto max_idx = ArrayArgs<int>::ArgMax(vals);
|
||||
auto min_idx = ArrayArgs<int>::ArgMin(vals);
|
||||
json_str_buf << "{\"min_value\":" << vals[min_idx] << ",";
|
||||
json_str_buf << "\"max_value\":" << vals[max_idx] << ",";
|
||||
json_str_buf << "\"values\":[" << CommonC::Join(vals, ",") << "]}";
|
||||
} else { // unused feature
|
||||
continue;
|
||||
}
|
||||
if (!first_obj) {
|
||||
str_buf << ",";
|
||||
}
|
||||
str_buf << "\"" << feature_names_[i] << "\":";
|
||||
str_buf << json_str_buf.str();
|
||||
first_obj = false;
|
||||
}
|
||||
str_buf << "}," << '\n';
|
||||
|
||||
str_buf << "\"tree_info\":[";
|
||||
int num_used_model = static_cast<int>(models_.size());
|
||||
int total_iteration = num_used_model / num_tree_per_iteration_;
|
||||
start_iteration = std::max(start_iteration, 0);
|
||||
start_iteration = std::min(start_iteration, total_iteration);
|
||||
if (num_iteration > 0) {
|
||||
int end_iteration = start_iteration + num_iteration;
|
||||
num_used_model = std::min(end_iteration * num_tree_per_iteration_ , num_used_model);
|
||||
}
|
||||
int start_model = start_iteration * num_tree_per_iteration_;
|
||||
for (int i = start_model; i < num_used_model; ++i) {
|
||||
if (i > start_model) {
|
||||
str_buf << ",";
|
||||
}
|
||||
str_buf << "{";
|
||||
str_buf << "\"tree_index\":" << i << ",";
|
||||
str_buf << models_[i]->ToJSON();
|
||||
str_buf << "}";
|
||||
}
|
||||
str_buf << "]," << '\n';
|
||||
|
||||
std::vector<double> feature_importances = FeatureImportance(
|
||||
num_iteration, feature_importance_type);
|
||||
// store the importance first
|
||||
std::vector<std::pair<size_t, std::string>> pairs;
|
||||
for (size_t i = 0; i < feature_importances.size(); ++i) {
|
||||
size_t feature_importances_int = static_cast<size_t>(feature_importances[i]);
|
||||
if (feature_importances_int > 0) {
|
||||
pairs.emplace_back(feature_importances_int, feature_names_[i]);
|
||||
}
|
||||
}
|
||||
str_buf << '\n' << "\"feature_importances\":" << "{";
|
||||
for (size_t i = 0; i < pairs.size(); ++i) {
|
||||
if (i > 0) {
|
||||
str_buf << ",";
|
||||
}
|
||||
str_buf << "\"" << pairs[i].second << "\":" << std::to_string(pairs[i].first);
|
||||
}
|
||||
str_buf << "}" << '\n';
|
||||
|
||||
str_buf << "}" << '\n';
|
||||
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
std::string GBDT::ModelToIfElse(int num_iteration) const {
|
||||
std::stringstream str_buf;
|
||||
Common::C_stringstream(str_buf);
|
||||
|
||||
str_buf << "#include \"gbdt.h\"" << '\n';
|
||||
str_buf << "#include <LightGBM/utils/common.h>" << '\n';
|
||||
str_buf << "#include <LightGBM/objective_function.h>" << '\n';
|
||||
str_buf << "#include <LightGBM/metric.h>" << '\n';
|
||||
str_buf << "#include <LightGBM/prediction_early_stop.h>" << '\n';
|
||||
str_buf << "#include <ctime>" << '\n';
|
||||
str_buf << "#include <sstream>" << '\n';
|
||||
str_buf << "#include <chrono>" << '\n';
|
||||
str_buf << "#include <string>" << '\n';
|
||||
str_buf << "#include <vector>" << '\n';
|
||||
str_buf << "#include <utility>" << '\n';
|
||||
str_buf << "namespace LightGBM {" << '\n';
|
||||
|
||||
int num_used_model = static_cast<int>(models_.size());
|
||||
if (num_iteration > 0) {
|
||||
num_used_model = std::min(num_iteration * num_tree_per_iteration_, num_used_model);
|
||||
}
|
||||
|
||||
// PredictRaw
|
||||
for (int i = 0; i < num_used_model; ++i) {
|
||||
str_buf << models_[i]->ToIfElse(i, false) << '\n';
|
||||
}
|
||||
|
||||
str_buf << "double (*PredictTreePtr[])(const double*) = { ";
|
||||
for (int i = 0; i < num_used_model; ++i) {
|
||||
if (i > 0) {
|
||||
str_buf << " , ";
|
||||
}
|
||||
str_buf << "PredictTree" << i;
|
||||
}
|
||||
str_buf << " };" << '\n' << '\n';
|
||||
|
||||
std::stringstream pred_str_buf;
|
||||
Common::C_stringstream(pred_str_buf);
|
||||
|
||||
pred_str_buf << "\t" << "int early_stop_round_counter = 0;" << '\n';
|
||||
pred_str_buf << "\t" << "std::memset(output, 0, sizeof(double) * num_tree_per_iteration_);" << '\n';
|
||||
pred_str_buf << "\t" << "for (int i = 0; i < num_iteration_for_pred_; ++i) {" << '\n';
|
||||
pred_str_buf << "\t\t" << "for (int k = 0; k < num_tree_per_iteration_; ++k) {" << '\n';
|
||||
pred_str_buf << "\t\t\t" << "output[k] += (*PredictTreePtr[i * num_tree_per_iteration_ + k])(features);" << '\n';
|
||||
pred_str_buf << "\t\t" << "}" << '\n';
|
||||
pred_str_buf << "\t\t" << "++early_stop_round_counter;" << '\n';
|
||||
pred_str_buf << "\t\t" << "if (early_stop->round_period == early_stop_round_counter) {" << '\n';
|
||||
pred_str_buf << "\t\t\t" << "if (early_stop->callback_function(output, num_tree_per_iteration_))" << '\n';
|
||||
pred_str_buf << "\t\t\t\t" << "return;" << '\n';
|
||||
pred_str_buf << "\t\t\t" << "early_stop_round_counter = 0;" << '\n';
|
||||
pred_str_buf << "\t\t" << "}" << '\n';
|
||||
pred_str_buf << "\t" << "}" << '\n';
|
||||
|
||||
str_buf << "void GBDT::PredictRaw(const double* features, double *output, const PredictionEarlyStopInstance* early_stop) const {" << '\n';
|
||||
str_buf << pred_str_buf.str();
|
||||
str_buf << "}" << '\n';
|
||||
str_buf << '\n';
|
||||
|
||||
// PredictRawByMap
|
||||
str_buf << "double (*PredictTreeByMapPtr[])(const std::unordered_map<int, double>&) = { ";
|
||||
for (int i = 0; i < num_used_model; ++i) {
|
||||
if (i > 0) {
|
||||
str_buf << " , ";
|
||||
}
|
||||
str_buf << "PredictTree" << i << "ByMap";
|
||||
}
|
||||
str_buf << " };" << '\n' << '\n';
|
||||
|
||||
std::stringstream pred_str_buf_map;
|
||||
Common::C_stringstream(pred_str_buf_map);
|
||||
|
||||
pred_str_buf_map << "\t" << "int early_stop_round_counter = 0;" << '\n';
|
||||
pred_str_buf_map << "\t" << "std::memset(output, 0, sizeof(double) * num_tree_per_iteration_);" << '\n';
|
||||
pred_str_buf_map << "\t" << "for (int i = 0; i < num_iteration_for_pred_; ++i) {" << '\n';
|
||||
pred_str_buf_map << "\t\t" << "for (int k = 0; k < num_tree_per_iteration_; ++k) {" << '\n';
|
||||
pred_str_buf_map << "\t\t\t" << "output[k] += (*PredictTreeByMapPtr[i * num_tree_per_iteration_ + k])(features);" << '\n';
|
||||
pred_str_buf_map << "\t\t" << "}" << '\n';
|
||||
pred_str_buf_map << "\t\t" << "++early_stop_round_counter;" << '\n';
|
||||
pred_str_buf_map << "\t\t" << "if (early_stop->round_period == early_stop_round_counter) {" << '\n';
|
||||
pred_str_buf_map << "\t\t\t" << "if (early_stop->callback_function(output, num_tree_per_iteration_))" << '\n';
|
||||
pred_str_buf_map << "\t\t\t\t" << "return;" << '\n';
|
||||
pred_str_buf_map << "\t\t\t" << "early_stop_round_counter = 0;" << '\n';
|
||||
pred_str_buf_map << "\t\t" << "}" << '\n';
|
||||
pred_str_buf_map << "\t" << "}" << '\n';
|
||||
|
||||
str_buf << "void GBDT::PredictRawByMap(const std::unordered_map<int, double>& features, double* output, const PredictionEarlyStopInstance* early_stop) const {" << '\n';
|
||||
str_buf << pred_str_buf_map.str();
|
||||
str_buf << "}" << '\n';
|
||||
str_buf << '\n';
|
||||
|
||||
// Predict
|
||||
str_buf << "void GBDT::Predict(const double* features, double *output, const PredictionEarlyStopInstance* early_stop) const {" << '\n';
|
||||
str_buf << "\t" << "PredictRaw(features, output, early_stop);" << '\n';
|
||||
str_buf << "\t" << "if (average_output_) {" << '\n';
|
||||
str_buf << "\t\t" << "for (int k = 0; k < num_tree_per_iteration_; ++k) {" << '\n';
|
||||
str_buf << "\t\t\t" << "output[k] /= num_iteration_for_pred_;" << '\n';
|
||||
str_buf << "\t\t" << "}" << '\n';
|
||||
str_buf << "\t" << "}" << '\n';
|
||||
str_buf << "\t" << "if (objective_function_ != nullptr) {" << '\n';
|
||||
str_buf << "\t\t" << "objective_function_->ConvertOutput(output, output);" << '\n';
|
||||
str_buf << "\t" << "}" << '\n';
|
||||
str_buf << "}" << '\n';
|
||||
str_buf << '\n';
|
||||
|
||||
// PredictByMap
|
||||
str_buf << "void GBDT::PredictByMap(const std::unordered_map<int, double>& features, double* output, const PredictionEarlyStopInstance* early_stop) const {" << '\n';
|
||||
str_buf << "\t" << "PredictRawByMap(features, output, early_stop);" << '\n';
|
||||
str_buf << "\t" << "if (average_output_) {" << '\n';
|
||||
str_buf << "\t\t" << "for (int k = 0; k < num_tree_per_iteration_; ++k) {" << '\n';
|
||||
str_buf << "\t\t\t" << "output[k] /= num_iteration_for_pred_;" << '\n';
|
||||
str_buf << "\t\t" << "}" << '\n';
|
||||
str_buf << "\t" << "}" << '\n';
|
||||
str_buf << "\t" << "if (objective_function_ != nullptr) {" << '\n';
|
||||
str_buf << "\t\t" << "objective_function_->ConvertOutput(output, output);" << '\n';
|
||||
str_buf << "\t" << "}" << '\n';
|
||||
str_buf << "}" << '\n';
|
||||
str_buf << '\n';
|
||||
|
||||
|
||||
// PredictLeafIndex
|
||||
for (int i = 0; i < num_used_model; ++i) {
|
||||
str_buf << models_[i]->ToIfElse(i, true) << '\n';
|
||||
}
|
||||
|
||||
str_buf << "double (*PredictTreeLeafPtr[])(const double*) = { ";
|
||||
for (int i = 0; i < num_used_model; ++i) {
|
||||
if (i > 0) {
|
||||
str_buf << " , ";
|
||||
}
|
||||
str_buf << "PredictTree" << i << "Leaf";
|
||||
}
|
||||
str_buf << " };" << '\n' << '\n';
|
||||
|
||||
str_buf << "void GBDT::PredictLeafIndex(const double* features, double *output) const {" << '\n';
|
||||
str_buf << "\t" << "int total_tree = num_iteration_for_pred_ * num_tree_per_iteration_;" << '\n';
|
||||
str_buf << "\t" << "for (int i = 0; i < total_tree; ++i) {" << '\n';
|
||||
str_buf << "\t\t" << "output[i] = (*PredictTreeLeafPtr[i])(features);" << '\n';
|
||||
str_buf << "\t" << "}" << '\n';
|
||||
str_buf << "}" << '\n';
|
||||
|
||||
// PredictLeafIndexByMap
|
||||
str_buf << "double (*PredictTreeLeafByMapPtr[])(const std::unordered_map<int, double>&) = { ";
|
||||
for (int i = 0; i < num_used_model; ++i) {
|
||||
if (i > 0) {
|
||||
str_buf << " , ";
|
||||
}
|
||||
str_buf << "PredictTree" << i << "LeafByMap";
|
||||
}
|
||||
str_buf << " };" << '\n' << '\n';
|
||||
|
||||
str_buf << "void GBDT::PredictLeafIndexByMap(const std::unordered_map<int, double>& features, double* output) const {" << '\n';
|
||||
str_buf << "\t" << "int total_tree = num_iteration_for_pred_ * num_tree_per_iteration_;" << '\n';
|
||||
str_buf << "\t" << "for (int i = 0; i < total_tree; ++i) {" << '\n';
|
||||
str_buf << "\t\t" << "output[i] = (*PredictTreeLeafByMapPtr[i])(features);" << '\n';
|
||||
str_buf << "\t" << "}" << '\n';
|
||||
str_buf << "}" << '\n';
|
||||
|
||||
str_buf << "} // namespace LightGBM" << '\n';
|
||||
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
bool GBDT::SaveModelToIfElse(int num_iteration, const char* filename) const {
|
||||
/*! \brief File to write models */
|
||||
std::ofstream output_file;
|
||||
std::ifstream ifs(filename);
|
||||
if (ifs.good()) {
|
||||
std::string origin((std::istreambuf_iterator<char>(ifs)),
|
||||
(std::istreambuf_iterator<char>()));
|
||||
output_file.open(filename);
|
||||
output_file << "#define USE_HARD_CODE 0" << '\n';
|
||||
output_file << "#ifndef USE_HARD_CODE" << '\n';
|
||||
output_file << origin << '\n';
|
||||
output_file << "#else" << '\n';
|
||||
output_file << ModelToIfElse(num_iteration);
|
||||
output_file << "#endif" << '\n';
|
||||
} else {
|
||||
output_file.open(filename);
|
||||
output_file << ModelToIfElse(num_iteration);
|
||||
}
|
||||
|
||||
ifs.close();
|
||||
output_file.close();
|
||||
|
||||
return static_cast<bool>(output_file);
|
||||
}
|
||||
|
||||
std::string GBDT::SaveModelToString(int start_iteration, int num_iteration, int feature_importance_type) const {
|
||||
std::stringstream ss;
|
||||
Common::C_stringstream(ss);
|
||||
|
||||
// output model type
|
||||
ss << SubModelName() << '\n';
|
||||
ss << "version=" << kModelVersion << '\n';
|
||||
// output number of class
|
||||
ss << "num_class=" << num_class_ << '\n';
|
||||
ss << "num_tree_per_iteration=" << num_tree_per_iteration_ << '\n';
|
||||
// output label index
|
||||
ss << "label_index=" << label_idx_ << '\n';
|
||||
// output max_feature_idx
|
||||
ss << "max_feature_idx=" << max_feature_idx_ << '\n';
|
||||
// output objective
|
||||
if (objective_function_ != nullptr) {
|
||||
ss << "objective=" << objective_function_->ToString() << '\n';
|
||||
}
|
||||
|
||||
if (average_output_) {
|
||||
ss << "average_output" << '\n';
|
||||
}
|
||||
|
||||
ss << "feature_names=" << CommonC::Join(feature_names_, " ") << '\n';
|
||||
|
||||
if (monotone_constraints_.size() != 0) {
|
||||
ss << "monotone_constraints=" << CommonC::Join(monotone_constraints_, " ")
|
||||
<< '\n';
|
||||
}
|
||||
|
||||
ss << "feature_infos=" << CommonC::Join(feature_infos_, " ") << '\n';
|
||||
|
||||
int num_used_model = static_cast<int>(models_.size());
|
||||
int total_iteration = num_used_model / num_tree_per_iteration_;
|
||||
start_iteration = std::max(start_iteration, 0);
|
||||
start_iteration = std::min(start_iteration, total_iteration);
|
||||
if (num_iteration > 0) {
|
||||
int end_iteration = start_iteration + num_iteration;
|
||||
num_used_model = std::min(end_iteration * num_tree_per_iteration_, num_used_model);
|
||||
}
|
||||
|
||||
int start_model = start_iteration * num_tree_per_iteration_;
|
||||
|
||||
std::vector<std::string> tree_strs(num_used_model - start_model);
|
||||
std::vector<size_t> tree_sizes(num_used_model - start_model);
|
||||
// output tree models
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int i = start_model; i < num_used_model; ++i) {
|
||||
const int idx = i - start_model;
|
||||
tree_strs[idx] = "Tree=" + std::to_string(idx) + '\n';
|
||||
tree_strs[idx] += models_[i]->ToString() + '\n';
|
||||
tree_sizes[idx] = tree_strs[idx].size();
|
||||
}
|
||||
|
||||
ss << "tree_sizes=" << CommonC::Join(tree_sizes, " ") << '\n';
|
||||
ss << '\n';
|
||||
|
||||
for (int i = 0; i < num_used_model - start_model; ++i) {
|
||||
ss << tree_strs[i];
|
||||
tree_strs[i].clear();
|
||||
}
|
||||
ss << "end of trees" << "\n";
|
||||
std::vector<double> feature_importances = FeatureImportance(
|
||||
num_iteration, feature_importance_type);
|
||||
// store the importance first
|
||||
std::vector<std::pair<size_t, std::string>> pairs;
|
||||
for (size_t i = 0; i < feature_importances.size(); ++i) {
|
||||
size_t feature_importances_int = static_cast<size_t>(feature_importances[i]);
|
||||
if (feature_importances_int > 0) {
|
||||
pairs.emplace_back(feature_importances_int, feature_names_[i]);
|
||||
}
|
||||
}
|
||||
// sort the importance
|
||||
std::stable_sort(pairs.begin(), pairs.end(),
|
||||
[](const std::pair<size_t, std::string>& lhs,
|
||||
const std::pair<size_t, std::string>& rhs) {
|
||||
return lhs.first > rhs.first;
|
||||
});
|
||||
ss << '\n' << "feature_importances:" << '\n';
|
||||
for (size_t i = 0; i < pairs.size(); ++i) {
|
||||
ss << pairs[i].second << "=" << std::to_string(pairs[i].first) << '\n';
|
||||
}
|
||||
if (config_ != nullptr) {
|
||||
ss << "\nparameters:" << '\n';
|
||||
ss << config_->ToString() << "\n";
|
||||
ss << "end of parameters" << '\n';
|
||||
} else if (!loaded_parameter_.empty()) {
|
||||
ss << "\nparameters:" << '\n';
|
||||
ss << loaded_parameter_ << "\n";
|
||||
ss << "end of parameters" << '\n';
|
||||
}
|
||||
if (!parser_config_str_.empty()) {
|
||||
ss << "\nparser:" << '\n';
|
||||
ss << parser_config_str_ << "\n";
|
||||
ss << "end of parser" << '\n';
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool GBDT::SaveModelToFile(int start_iteration, int num_iteration, int feature_importance_type, const char* filename) const {
|
||||
/*! \brief File to write models */
|
||||
auto writer = VirtualFileWriter::Make(filename);
|
||||
if (!writer->Init()) {
|
||||
Log::Fatal("Model file %s is not available for writes", filename);
|
||||
}
|
||||
std::string str_to_write = SaveModelToString(start_iteration, num_iteration, feature_importance_type);
|
||||
auto size = writer->Write(str_to_write.c_str(), str_to_write.size());
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
bool GBDT::LoadModelFromString(const char* buffer, size_t len) {
|
||||
// use serialized string to restore this object
|
||||
models_.clear();
|
||||
auto c_str = buffer;
|
||||
auto p = c_str;
|
||||
auto end = p + len;
|
||||
std::unordered_map<std::string, std::string> key_vals;
|
||||
while (p < end) {
|
||||
auto line_len = Common::GetLine(p);
|
||||
if (line_len > 0) {
|
||||
std::string cur_line(p, line_len);
|
||||
if (!Common::StartsWith(cur_line, "Tree=")) {
|
||||
auto strs = Common::Split(cur_line.c_str(), '=');
|
||||
if (strs.size() == 1) {
|
||||
key_vals[strs[0]] = "";
|
||||
} else if (strs.size() == 2) {
|
||||
key_vals[strs[0]] = strs[1];
|
||||
} else if (strs.size() > 2) {
|
||||
if (strs[0] == "feature_names") {
|
||||
key_vals[strs[0]] = cur_line.substr(std::strlen("feature_names="));
|
||||
} else if (strs[0] == "monotone_constraints") {
|
||||
key_vals[strs[0]] = cur_line.substr(std::strlen("monotone_constraints="));
|
||||
} else {
|
||||
// Use first 128 chars to avoid exceed the message buffer.
|
||||
Log::Fatal("Wrong line at model file: %s", cur_line.substr(0, std::min<size_t>(128, cur_line.size())).c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
p += line_len;
|
||||
p = Common::SkipNewLine(p);
|
||||
}
|
||||
|
||||
// get number of classes
|
||||
if (key_vals.count("num_class")) {
|
||||
Common::Atoi(key_vals["num_class"].c_str(), &num_class_);
|
||||
} else {
|
||||
Log::Fatal("Model file doesn't specify the number of classes");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key_vals.count("num_tree_per_iteration")) {
|
||||
Common::Atoi(key_vals["num_tree_per_iteration"].c_str(), &num_tree_per_iteration_);
|
||||
} else {
|
||||
num_tree_per_iteration_ = num_class_;
|
||||
}
|
||||
|
||||
// get index of label
|
||||
if (key_vals.count("label_index")) {
|
||||
Common::Atoi(key_vals["label_index"].c_str(), &label_idx_);
|
||||
} else {
|
||||
Log::Fatal("Model file doesn't specify the label index");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get max_feature_idx first
|
||||
if (key_vals.count("max_feature_idx")) {
|
||||
Common::Atoi(key_vals["max_feature_idx"].c_str(), &max_feature_idx_);
|
||||
} else {
|
||||
Log::Fatal("Model file doesn't specify max_feature_idx");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get average_output
|
||||
if (key_vals.count("average_output")) {
|
||||
average_output_ = true;
|
||||
}
|
||||
|
||||
// get feature names
|
||||
if (key_vals.count("feature_names")) {
|
||||
feature_names_ = Common::Split(key_vals["feature_names"].c_str(), ' ');
|
||||
if (feature_names_.size() != static_cast<size_t>(max_feature_idx_ + 1)) {
|
||||
Log::Fatal("Wrong size of feature_names");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Log::Fatal("Model file doesn't contain feature_names");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get monotone_constraints
|
||||
if (key_vals.count("monotone_constraints")) {
|
||||
monotone_constraints_ = CommonC::StringToArray<int8_t>(key_vals["monotone_constraints"].c_str(), ' ');
|
||||
if (monotone_constraints_.size() != static_cast<size_t>(max_feature_idx_ + 1)) {
|
||||
Log::Fatal("Wrong size of monotone_constraints");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (key_vals.count("feature_infos")) {
|
||||
feature_infos_ = Common::Split(key_vals["feature_infos"].c_str(), ' ');
|
||||
if (feature_infos_.size() != static_cast<size_t>(max_feature_idx_ + 1)) {
|
||||
Log::Fatal("Wrong size of feature_infos");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Log::Fatal("Model file doesn't contain feature_infos");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key_vals.count("objective")) {
|
||||
auto str = key_vals["objective"];
|
||||
loaded_objective_.reset(ObjectiveFunction::CreateObjectiveFunction(ParseObjectiveAlias(str)));
|
||||
objective_function_ = loaded_objective_.get();
|
||||
}
|
||||
|
||||
if (!key_vals.count("tree_sizes")) {
|
||||
while (p < end) {
|
||||
auto line_len = Common::GetLine(p);
|
||||
if (line_len > 0) {
|
||||
std::string cur_line(p, line_len);
|
||||
if (Common::StartsWith(cur_line, "Tree=")) {
|
||||
p += line_len;
|
||||
p = Common::SkipNewLine(p);
|
||||
size_t used_len = 0;
|
||||
models_.emplace_back(new Tree(p, &used_len));
|
||||
p += used_len;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
p = Common::SkipNewLine(p);
|
||||
}
|
||||
} else {
|
||||
std::vector<size_t> tree_sizes = CommonC::StringToArray<size_t>(key_vals["tree_sizes"].c_str(), ' ');
|
||||
std::vector<size_t> tree_boundaries(tree_sizes.size() + 1, 0);
|
||||
int num_trees = static_cast<int>(tree_sizes.size());
|
||||
for (int i = 0; i < num_trees; ++i) {
|
||||
tree_boundaries[i + 1] = tree_boundaries[i] + tree_sizes[i];
|
||||
models_.emplace_back(nullptr);
|
||||
}
|
||||
OMP_INIT_EX();
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int i = 0; i < num_trees; ++i) {
|
||||
OMP_LOOP_EX_BEGIN();
|
||||
auto cur_p = p + tree_boundaries[i];
|
||||
auto line_len = Common::GetLine(cur_p);
|
||||
std::string cur_line(cur_p, line_len);
|
||||
if (Common::StartsWith(cur_line, "Tree=")) {
|
||||
cur_p += line_len;
|
||||
cur_p = Common::SkipNewLine(cur_p);
|
||||
size_t used_len = 0;
|
||||
models_[i].reset(new Tree(cur_p, &used_len));
|
||||
} else {
|
||||
Log::Fatal("Model format error, expect a tree here. met %s", cur_line.c_str());
|
||||
}
|
||||
OMP_LOOP_EX_END();
|
||||
}
|
||||
OMP_THROW_EX();
|
||||
}
|
||||
num_iteration_for_pred_ = static_cast<int>(models_.size()) / num_tree_per_iteration_;
|
||||
num_init_iteration_ = num_iteration_for_pred_;
|
||||
iter_ = 0;
|
||||
bool is_inparameter = false, is_inparser = false;
|
||||
std::stringstream ss;
|
||||
Common::C_stringstream(ss);
|
||||
while (p < end) {
|
||||
auto line_len = Common::GetLine(p);
|
||||
if (line_len > 0) {
|
||||
std::string cur_line(p, line_len);
|
||||
if (cur_line == std::string("parameters:")) {
|
||||
is_inparameter = true;
|
||||
} else if (cur_line == std::string("end of parameters")) {
|
||||
break;
|
||||
} else if (is_inparameter) {
|
||||
ss << cur_line << "\n";
|
||||
if (Common::StartsWith(cur_line, "[linear_tree: ")) {
|
||||
int is_linear = 0;
|
||||
Common::Atoi(cur_line.substr(14, 1).c_str(), &is_linear);
|
||||
linear_tree_ = static_cast<bool>(is_linear);
|
||||
}
|
||||
}
|
||||
}
|
||||
p += line_len;
|
||||
p = Common::SkipNewLine(p);
|
||||
}
|
||||
if (!ss.str().empty()) {
|
||||
loaded_parameter_ = ss.str();
|
||||
}
|
||||
ss.clear();
|
||||
ss.str("");
|
||||
while (p < end) {
|
||||
auto line_len = Common::GetLine(p);
|
||||
if (line_len > 0) {
|
||||
std::string cur_line(p, line_len);
|
||||
if (cur_line == std::string("parser:")) {
|
||||
is_inparser = true;
|
||||
} else if (cur_line == std::string("end of parser")) {
|
||||
p += line_len;
|
||||
p = Common::SkipNewLine(p);
|
||||
break;
|
||||
} else if (is_inparser) {
|
||||
ss << cur_line << "\n";
|
||||
}
|
||||
}
|
||||
p += line_len;
|
||||
p = Common::SkipNewLine(p);
|
||||
}
|
||||
parser_config_str_ = ss.str();
|
||||
ss.clear();
|
||||
ss.str("");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<double> GBDT::FeatureImportance(int num_iteration, int importance_type) const {
|
||||
int num_used_model = static_cast<int>(models_.size());
|
||||
if (num_iteration > 0) {
|
||||
num_iteration += 0;
|
||||
num_used_model = std::min(num_iteration * num_tree_per_iteration_, num_used_model);
|
||||
}
|
||||
|
||||
std::vector<double> feature_importances(max_feature_idx_ + 1, 0.0);
|
||||
if (importance_type == 0) {
|
||||
for (int iter = 0; iter < num_used_model; ++iter) {
|
||||
for (int split_idx = 0; split_idx < models_[iter]->num_leaves() - 1; ++split_idx) {
|
||||
if (models_[iter]->split_gain(split_idx) > 0) {
|
||||
#ifdef DEBUG
|
||||
CHECK_GE(models_[iter]->split_feature(split_idx), 0);
|
||||
#endif
|
||||
feature_importances[models_[iter]->split_feature(split_idx)] += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (importance_type == 1) {
|
||||
for (int iter = 0; iter < num_used_model; ++iter) {
|
||||
for (int split_idx = 0; split_idx < models_[iter]->num_leaves() - 1; ++split_idx) {
|
||||
if (models_[iter]->split_gain(split_idx) > 0) {
|
||||
#ifdef DEBUG
|
||||
CHECK_GE(models_[iter]->split_feature(split_idx), 0);
|
||||
#endif
|
||||
feature_importances[models_[iter]->split_feature(split_idx)] += models_[iter]->split_gain(split_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log::Fatal("Unknown importance type: only support split=0 and gain=1");
|
||||
}
|
||||
return feature_importances;
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,100 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/prediction_early_stop.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "gbdt.h"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
void GBDT::PredictRaw(const double* features, double* output, const PredictionEarlyStopInstance* early_stop) const {
|
||||
int early_stop_round_counter = 0;
|
||||
// set zero
|
||||
std::memset(output, 0, sizeof(double) * num_tree_per_iteration_);
|
||||
const int end_iteration_for_pred = start_iteration_for_pred_ + num_iteration_for_pred_;
|
||||
for (int i = start_iteration_for_pred_; i < end_iteration_for_pred; ++i) {
|
||||
// predict all the trees for one iteration
|
||||
for (int k = 0; k < num_tree_per_iteration_; ++k) {
|
||||
output[k] += models_[i * num_tree_per_iteration_ + k]->Predict(features);
|
||||
}
|
||||
// check early stopping
|
||||
++early_stop_round_counter;
|
||||
if (early_stop->round_period == early_stop_round_counter) {
|
||||
if (early_stop->callback_function(output, num_tree_per_iteration_)) {
|
||||
return;
|
||||
}
|
||||
early_stop_round_counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::PredictRawByMap(const std::unordered_map<int, double>& features, double* output, const PredictionEarlyStopInstance* early_stop) const {
|
||||
int early_stop_round_counter = 0;
|
||||
// set zero
|
||||
std::memset(output, 0, sizeof(double) * num_tree_per_iteration_);
|
||||
const int end_iteration_for_pred = start_iteration_for_pred_ + num_iteration_for_pred_;
|
||||
for (int i = start_iteration_for_pred_; i < end_iteration_for_pred; ++i) {
|
||||
// predict all the trees for one iteration
|
||||
for (int k = 0; k < num_tree_per_iteration_; ++k) {
|
||||
output[k] += models_[i * num_tree_per_iteration_ + k]->PredictByMap(features);
|
||||
}
|
||||
// check early stopping
|
||||
++early_stop_round_counter;
|
||||
if (early_stop->round_period == early_stop_round_counter) {
|
||||
if (early_stop->callback_function(output, num_tree_per_iteration_)) {
|
||||
return;
|
||||
}
|
||||
early_stop_round_counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::Predict(const double* features, double* output, const PredictionEarlyStopInstance* early_stop) const {
|
||||
PredictRaw(features, output, early_stop);
|
||||
if (average_output_) {
|
||||
for (int k = 0; k < num_tree_per_iteration_; ++k) {
|
||||
output[k] /= num_iteration_for_pred_;
|
||||
}
|
||||
}
|
||||
if (objective_function_ != nullptr) {
|
||||
objective_function_->ConvertOutput(output, output);
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::PredictByMap(const std::unordered_map<int, double>& features, double* output, const PredictionEarlyStopInstance* early_stop) const {
|
||||
PredictRawByMap(features, output, early_stop);
|
||||
if (average_output_) {
|
||||
for (int k = 0; k < num_tree_per_iteration_; ++k) {
|
||||
output[k] /= num_iteration_for_pred_;
|
||||
}
|
||||
}
|
||||
if (objective_function_ != nullptr) {
|
||||
objective_function_->ConvertOutput(output, output);
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::PredictLeafIndex(const double* features, double* output) const {
|
||||
int start_tree = start_iteration_for_pred_ * num_tree_per_iteration_;
|
||||
int num_trees = num_iteration_for_pred_ * num_tree_per_iteration_;
|
||||
const auto* models_ptr = models_.data() + start_tree;
|
||||
for (int i = 0; i < num_trees; ++i) {
|
||||
output[i] = models_ptr[i]->PredictLeafIndex(features);
|
||||
}
|
||||
}
|
||||
|
||||
void GBDT::PredictLeafIndexByMap(const std::unordered_map<int, double>& features, double* output) const {
|
||||
int start_tree = start_iteration_for_pred_ * num_tree_per_iteration_;
|
||||
int num_trees = num_iteration_for_pred_ * num_tree_per_iteration_;
|
||||
const auto* models_ptr = models_.data() + start_tree;
|
||||
for (int i = 0; i < num_trees; ++i) {
|
||||
output[i] = models_ptr[i]->PredictLeafIndexByMap(features);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,173 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_GOSS_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_GOSS_HPP_
|
||||
|
||||
#include <LightGBM/utils/array_args.h>
|
||||
#include <LightGBM/sample_strategy.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class GOSSStrategy : public SampleStrategy {
|
||||
public:
|
||||
GOSSStrategy(const Config* config, const Dataset* train_data, int num_tree_per_iteration) {
|
||||
config_ = config;
|
||||
train_data_ = train_data;
|
||||
num_tree_per_iteration_ = num_tree_per_iteration;
|
||||
num_data_ = train_data->num_data();
|
||||
}
|
||||
|
||||
~GOSSStrategy() {
|
||||
}
|
||||
|
||||
void Bagging(int iter, TreeLearner* tree_learner, score_t* gradients, score_t* hessians) override {
|
||||
bag_data_cnt_ = num_data_;
|
||||
// not subsample for first iterations
|
||||
if (iter < static_cast<int>(1.0f / config_->learning_rate)) {
|
||||
return;
|
||||
}
|
||||
auto left_cnt = bagging_runner_.Run<true>(
|
||||
num_data_,
|
||||
[=](int, data_size_t cur_start, data_size_t cur_cnt, data_size_t* left,
|
||||
data_size_t*) {
|
||||
data_size_t cur_left_count = 0;
|
||||
cur_left_count = Helper(cur_start, cur_cnt, left, gradients, hessians);
|
||||
return cur_left_count;
|
||||
},
|
||||
bag_data_indices_.data());
|
||||
bag_data_cnt_ = left_cnt;
|
||||
// set bagging data to tree learner
|
||||
if (!is_use_subset_) {
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
CopyFromHostToCUDADevice<data_size_t>(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
tree_learner->SetBaggingData(nullptr, cuda_bag_data_indices_.RawData(), bag_data_cnt_);
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
tree_learner->SetBaggingData(nullptr, bag_data_indices_.data(), bag_data_cnt_);
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
} else {
|
||||
// get subset
|
||||
tmp_subset_->ReSize(bag_data_cnt_);
|
||||
tmp_subset_->CopySubrow(train_data_, bag_data_indices_.data(),
|
||||
bag_data_cnt_, false);
|
||||
#ifdef USE_CUDA
|
||||
if (config_->device_type == std::string("cuda")) {
|
||||
CopyFromHostToCUDADevice<data_size_t>(cuda_bag_data_indices_.RawData(), bag_data_indices_.data(), static_cast<size_t>(num_data_), __FILE__, __LINE__);
|
||||
tree_learner->SetBaggingData(tmp_subset_.get(), cuda_bag_data_indices_.RawData(),
|
||||
bag_data_cnt_);
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
tree_learner->SetBaggingData(tmp_subset_.get(), bag_data_indices_.data(),
|
||||
bag_data_cnt_);
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
}
|
||||
|
||||
void ResetSampleConfig(const Config* config, bool /*is_change_dataset*/) override {
|
||||
// Cannot use bagging in GOSS
|
||||
config_ = config;
|
||||
need_resize_gradients_ = false;
|
||||
if (objective_function_ == nullptr) {
|
||||
// resize gradient vectors to copy the customized gradients for goss
|
||||
need_resize_gradients_ = true;
|
||||
}
|
||||
|
||||
CHECK_LE(config_->top_rate + config_->other_rate, 1.0f);
|
||||
CHECK(config_->top_rate > 0.0f && config_->other_rate > 0.0f);
|
||||
if (config_->bagging_freq > 0 && config_->bagging_fraction != 1.0f) {
|
||||
Log::Fatal("Cannot use bagging in GOSS");
|
||||
}
|
||||
Log::Info("Using GOSS");
|
||||
balanced_bagging_ = false;
|
||||
bag_data_indices_.resize(num_data_);
|
||||
bagging_runner_.ReSize(num_data_);
|
||||
bagging_rands_.clear();
|
||||
for (int i = 0;
|
||||
i < (num_data_ + bagging_rand_block_ - 1) / bagging_rand_block_; ++i) {
|
||||
bagging_rands_.emplace_back(config_->bagging_seed + i);
|
||||
}
|
||||
is_use_subset_ = false;
|
||||
if (config_->top_rate + config_->other_rate <= 0.5) {
|
||||
auto bag_data_cnt = static_cast<data_size_t>((config_->top_rate + config_->other_rate) * num_data_);
|
||||
bag_data_cnt = std::max(1, bag_data_cnt);
|
||||
tmp_subset_.reset(new Dataset(bag_data_cnt));
|
||||
tmp_subset_->CopyFeatureMapperFrom(train_data_);
|
||||
is_use_subset_ = true;
|
||||
}
|
||||
// flag to not bagging first
|
||||
bag_data_cnt_ = num_data_;
|
||||
}
|
||||
|
||||
bool IsHessianChange() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
data_size_t Helper(data_size_t start, data_size_t cnt, data_size_t* buffer, score_t* gradients, score_t* hessians) {
|
||||
if (cnt <= 0) {
|
||||
return 0;
|
||||
}
|
||||
std::vector<score_t> tmp_gradients(cnt, 0.0f);
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
size_t idx = static_cast<size_t>(cur_tree_id) * num_data_ + start + i;
|
||||
tmp_gradients[i] += std::fabs(gradients[idx] * hessians[idx]);
|
||||
}
|
||||
}
|
||||
data_size_t top_k = static_cast<data_size_t>(cnt * config_->top_rate);
|
||||
data_size_t other_k = static_cast<data_size_t>(cnt * config_->other_rate);
|
||||
top_k = std::max(1, top_k);
|
||||
ArrayArgs<score_t>::ArgMaxAtK(&tmp_gradients, 0, static_cast<int>(tmp_gradients.size()), top_k - 1);
|
||||
score_t threshold = tmp_gradients[top_k - 1];
|
||||
|
||||
score_t multiply = static_cast<score_t>(cnt - top_k) / other_k;
|
||||
data_size_t cur_left_cnt = 0;
|
||||
data_size_t cur_right_pos = cnt;
|
||||
data_size_t big_weight_cnt = 0;
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
auto cur_idx = start + i;
|
||||
score_t grad = 0.0f;
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
size_t idx = static_cast<size_t>(cur_tree_id) * num_data_ + cur_idx;
|
||||
grad += std::fabs(gradients[idx] * hessians[idx]);
|
||||
}
|
||||
if (grad >= threshold) {
|
||||
buffer[cur_left_cnt++] = cur_idx;
|
||||
++big_weight_cnt;
|
||||
} else {
|
||||
data_size_t sampled = cur_left_cnt - big_weight_cnt;
|
||||
data_size_t rest_need = other_k - sampled;
|
||||
data_size_t rest_all = (cnt - i) - (top_k - big_weight_cnt);
|
||||
double prob = (rest_need) / static_cast<double>(rest_all);
|
||||
if (bagging_rands_[cur_idx / bagging_rand_block_].NextFloat() < prob) {
|
||||
buffer[cur_left_cnt++] = cur_idx;
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
size_t idx = static_cast<size_t>(cur_tree_id) * num_data_ + cur_idx;
|
||||
gradients[idx] *= multiply;
|
||||
hessians[idx] *= multiply;
|
||||
}
|
||||
} else {
|
||||
buffer[--cur_right_pos] = cur_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cur_left_cnt;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_GOSS_HPP_
|
||||
@@ -0,0 +1,94 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/prediction_early_stop.h>
|
||||
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
PredictionEarlyStopInstance CreateNone(const PredictionEarlyStopConfig&) {
|
||||
return PredictionEarlyStopInstance{
|
||||
[](const double*, int) {
|
||||
return false;
|
||||
},
|
||||
std::numeric_limits<int>::max() // make sure the lambda is almost never called
|
||||
};
|
||||
}
|
||||
|
||||
PredictionEarlyStopInstance CreateMulticlass(const PredictionEarlyStopConfig& config) {
|
||||
// margin_threshold will be captured by value
|
||||
const double margin_threshold = config.margin_threshold;
|
||||
|
||||
return PredictionEarlyStopInstance{
|
||||
[margin_threshold](const double* pred, int sz) {
|
||||
if (sz < 2) {
|
||||
Log::Fatal("Multiclass early stopping needs predictions to be of length two or larger");
|
||||
}
|
||||
|
||||
// copy and sort
|
||||
std::vector<double> votes(static_cast<size_t>(sz));
|
||||
for (int i = 0; i < sz; ++i) {
|
||||
votes[i] = pred[i];
|
||||
}
|
||||
std::partial_sort(votes.begin(), votes.begin() + 2, votes.end(), std::greater<double>());
|
||||
|
||||
const auto margin = votes[0] - votes[1];
|
||||
|
||||
if (margin > margin_threshold) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
config.round_period
|
||||
};
|
||||
}
|
||||
|
||||
PredictionEarlyStopInstance CreateBinary(const PredictionEarlyStopConfig& config) {
|
||||
// margin_threshold will be captured by value
|
||||
const double margin_threshold = config.margin_threshold;
|
||||
|
||||
return PredictionEarlyStopInstance{
|
||||
[margin_threshold](const double* pred, int sz) {
|
||||
if (sz != 1) {
|
||||
Log::Fatal("Binary early stopping needs predictions to be of length one");
|
||||
}
|
||||
const auto margin = 2.0 * fabs(pred[0]);
|
||||
|
||||
if (margin > margin_threshold) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
config.round_period
|
||||
};
|
||||
}
|
||||
|
||||
PredictionEarlyStopInstance CreatePredictionEarlyStopInstance(const std::string& type,
|
||||
const PredictionEarlyStopConfig& config) {
|
||||
if (type == "none") {
|
||||
return CreateNone(config);
|
||||
} else if (type == "multiclass") {
|
||||
return CreateMulticlass(config);
|
||||
} else if (type == "binary") {
|
||||
return CreateBinary(config);
|
||||
} else {
|
||||
Log::Fatal("Unknown early stopping type: %s", type.c_str());
|
||||
}
|
||||
|
||||
// Fix for compiler warnings about reaching end of control
|
||||
return CreateNone(config);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,237 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_RF_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_RF_HPP_
|
||||
|
||||
#include <LightGBM/boosting.h>
|
||||
#include <LightGBM/metric.h>
|
||||
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gbdt.h"
|
||||
#include "score_updater.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief Random Forest implementation
|
||||
*/
|
||||
class RF : public GBDT {
|
||||
public:
|
||||
RF() : GBDT() {
|
||||
average_output_ = true;
|
||||
}
|
||||
|
||||
~RF() {}
|
||||
|
||||
void Init(const Config* config, const Dataset* train_data, const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) override {
|
||||
if (config->data_sample_strategy == std::string("bagging")) {
|
||||
CHECK((config->bagging_freq > 0 && config->bagging_fraction < 1.0f && config->bagging_fraction > 0.0f) ||
|
||||
(config->feature_fraction < 1.0f && config->feature_fraction > 0.0f));
|
||||
} else {
|
||||
CHECK_EQ(config->data_sample_strategy, std::string("goss"));
|
||||
}
|
||||
GBDT::Init(config, train_data, objective_function, training_metrics);
|
||||
|
||||
if (num_init_iteration_ > 0) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
MultiplyScore(cur_tree_id, 1.0f / num_init_iteration_);
|
||||
}
|
||||
} else {
|
||||
CHECK_EQ(train_data->metadata().init_score(), nullptr);
|
||||
}
|
||||
CHECK_EQ(num_tree_per_iteration_, num_class_);
|
||||
// not shrinkage rate for the RF
|
||||
shrinkage_rate_ = 1.0f;
|
||||
// only boosting one time
|
||||
Boosting();
|
||||
if (data_sample_strategy_->is_use_subset() && data_sample_strategy_->bag_data_cnt() < num_data_) {
|
||||
tmp_grad_.resize(num_data_);
|
||||
tmp_hess_.resize(num_data_);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetConfig(const Config* config) override {
|
||||
if (config->data_sample_strategy == std::string("bagging")) {
|
||||
CHECK((config->bagging_freq > 0 && config->bagging_fraction < 1.0f && config->bagging_fraction > 0.0f) ||
|
||||
(config->feature_fraction < 1.0f && config->feature_fraction > 0.0f));
|
||||
} else {
|
||||
CHECK_EQ(config->data_sample_strategy, std::string("goss"));
|
||||
}
|
||||
GBDT::ResetConfig(config);
|
||||
// not shrinkage rate for the RF
|
||||
shrinkage_rate_ = 1.0f;
|
||||
}
|
||||
|
||||
void ResetTrainingData(const Dataset* train_data, const ObjectiveFunction* objective_function,
|
||||
const std::vector<const Metric*>& training_metrics) override {
|
||||
GBDT::ResetTrainingData(train_data, objective_function, training_metrics);
|
||||
if (iter_ + num_init_iteration_ > 0) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
train_score_updater_->MultiplyScore(1.0f / (iter_ + num_init_iteration_), cur_tree_id);
|
||||
}
|
||||
}
|
||||
CHECK_EQ(num_tree_per_iteration_, num_class_);
|
||||
// only boosting one time
|
||||
Boosting();
|
||||
if (data_sample_strategy_->is_use_subset() && data_sample_strategy_->bag_data_cnt() < num_data_) {
|
||||
tmp_grad_.resize(num_data_);
|
||||
tmp_hess_.resize(num_data_);
|
||||
}
|
||||
}
|
||||
|
||||
void Boosting() override {
|
||||
if (objective_function_ == nullptr) {
|
||||
Log::Fatal("RF mode do not support custom objective function, please use built-in objectives.");
|
||||
}
|
||||
init_scores_.resize(num_tree_per_iteration_, 0.0);
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
init_scores_[cur_tree_id] = BoostFromAverage(cur_tree_id, false);
|
||||
}
|
||||
size_t total_size = static_cast<size_t>(num_data_) * num_tree_per_iteration_;
|
||||
std::vector<double> tmp_scores(total_size, 0.0f);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int j = 0; j < num_tree_per_iteration_; ++j) {
|
||||
size_t offset = static_cast<size_t>(j)* num_data_;
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
tmp_scores[offset + i] = init_scores_[j];
|
||||
}
|
||||
}
|
||||
objective_function_->
|
||||
GetGradients(tmp_scores.data(), gradients_.data(), hessians_.data());
|
||||
}
|
||||
|
||||
bool TrainOneIter(const score_t* gradients, const score_t* hessians) override {
|
||||
// bagging logic
|
||||
data_sample_strategy_ ->Bagging(iter_, tree_learner_.get(), gradients_.data(), hessians_.data());
|
||||
const bool is_use_subset = data_sample_strategy_->is_use_subset();
|
||||
const data_size_t bag_data_cnt = data_sample_strategy_->bag_data_cnt();
|
||||
const std::vector<data_size_t, Common::AlignmentAllocator<data_size_t, kAlignedSize>>& bag_data_indices = data_sample_strategy_->bag_data_indices();
|
||||
|
||||
// GOSSStrategy->Bagging may modify value of bag_data_cnt_
|
||||
if (is_use_subset && bag_data_cnt < num_data_) {
|
||||
tmp_grad_.resize(num_data_);
|
||||
tmp_hess_.resize(num_data_);
|
||||
}
|
||||
|
||||
CHECK_EQ(gradients, nullptr);
|
||||
CHECK_EQ(hessians, nullptr);
|
||||
|
||||
gradients = gradients_.data();
|
||||
hessians = hessians_.data();
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
std::unique_ptr<Tree> new_tree(new Tree(2, false, false));
|
||||
size_t offset = static_cast<size_t>(cur_tree_id)* num_data_;
|
||||
if (class_need_train_[cur_tree_id]) {
|
||||
auto grad = gradients + offset;
|
||||
auto hess = hessians + offset;
|
||||
|
||||
if (is_use_subset && bag_data_cnt < num_data_ && !boosting_on_gpu_) {
|
||||
for (int i = 0; i < bag_data_cnt; ++i) {
|
||||
tmp_grad_[i] = grad[bag_data_indices[i]];
|
||||
tmp_hess_[i] = hess[bag_data_indices[i]];
|
||||
}
|
||||
grad = tmp_grad_.data();
|
||||
hess = tmp_hess_.data();
|
||||
}
|
||||
|
||||
new_tree.reset(tree_learner_->Train(grad, hess, false));
|
||||
}
|
||||
|
||||
if (new_tree->num_leaves() > 1) {
|
||||
double pred = init_scores_[cur_tree_id];
|
||||
auto residual_getter = [pred](const label_t* label, int i) {return static_cast<double>(label[i]) - pred; };
|
||||
tree_learner_->RenewTreeOutput(new_tree.get(), objective_function_, residual_getter,
|
||||
num_data_, bag_data_indices.data(), bag_data_cnt, train_score_updater_->score());
|
||||
if (std::fabs(init_scores_[cur_tree_id]) > kEpsilon) {
|
||||
new_tree->AddBias(init_scores_[cur_tree_id]);
|
||||
}
|
||||
// update score
|
||||
MultiplyScore(cur_tree_id, (iter_ + num_init_iteration_));
|
||||
UpdateScore(new_tree.get(), cur_tree_id);
|
||||
MultiplyScore(cur_tree_id, 1.0 / (iter_ + num_init_iteration_ + 1));
|
||||
} else {
|
||||
// only add default score one-time
|
||||
if (models_.size() < static_cast<size_t>(num_tree_per_iteration_)) {
|
||||
double output = 0.0;
|
||||
if (!class_need_train_[cur_tree_id]) {
|
||||
if (objective_function_ != nullptr) {
|
||||
output = objective_function_->BoostFromScore(cur_tree_id);
|
||||
} else {
|
||||
output = init_scores_[cur_tree_id];
|
||||
}
|
||||
}
|
||||
new_tree->AsConstantTree(output, num_data_);
|
||||
MultiplyScore(cur_tree_id, (iter_ + num_init_iteration_));
|
||||
UpdateScore(new_tree.get(), cur_tree_id);
|
||||
MultiplyScore(cur_tree_id, 1.0 / (iter_ + num_init_iteration_ + 1));
|
||||
}
|
||||
}
|
||||
// add model
|
||||
models_.push_back(std::move(new_tree));
|
||||
}
|
||||
++iter_;
|
||||
return false;
|
||||
}
|
||||
|
||||
void RollbackOneIter() override {
|
||||
if (iter_ <= 0) {
|
||||
return;
|
||||
}
|
||||
int cur_iter = iter_ + num_init_iteration_ - 1;
|
||||
// reset score
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
auto curr_tree = cur_iter * num_tree_per_iteration_ + cur_tree_id;
|
||||
models_[curr_tree]->Shrinkage(-1.0);
|
||||
MultiplyScore(cur_tree_id, (iter_ + num_init_iteration_));
|
||||
train_score_updater_->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->AddScore(models_[curr_tree].get(), cur_tree_id);
|
||||
}
|
||||
MultiplyScore(cur_tree_id, 1.0f / (iter_ + num_init_iteration_ - 1));
|
||||
}
|
||||
// remove model
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
models_.pop_back();
|
||||
}
|
||||
--iter_;
|
||||
}
|
||||
|
||||
void MultiplyScore(const int cur_tree_id, double val) {
|
||||
train_score_updater_->MultiplyScore(val, cur_tree_id);
|
||||
for (auto& score_updater : valid_score_updater_) {
|
||||
score_updater->MultiplyScore(val, cur_tree_id);
|
||||
}
|
||||
}
|
||||
|
||||
void AddValidDataset(const Dataset* valid_data,
|
||||
const std::vector<const Metric*>& valid_metrics) override {
|
||||
GBDT::AddValidDataset(valid_data, valid_metrics);
|
||||
if (iter_ + num_init_iteration_ > 0) {
|
||||
for (int cur_tree_id = 0; cur_tree_id < num_tree_per_iteration_; ++cur_tree_id) {
|
||||
valid_score_updater_.back()->MultiplyScore(1.0f / (iter_ + num_init_iteration_), cur_tree_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NeedAccuratePrediction() const override {
|
||||
// No early stopping for prediction
|
||||
return true;
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<score_t> tmp_grad_;
|
||||
std::vector<score_t> tmp_hess_;
|
||||
std::vector<double> init_scores_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_RF_HPP_
|
||||
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#include <LightGBM/sample_strategy.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "goss.hpp"
|
||||
#include "bagging.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
SampleStrategy* SampleStrategy::CreateSampleStrategy(
|
||||
const Config* config,
|
||||
const Dataset* train_data,
|
||||
const ObjectiveFunction* objective_function,
|
||||
int num_tree_per_iteration) {
|
||||
if (config->data_sample_strategy == std::string("goss")) {
|
||||
return new GOSSStrategy(config, train_data, num_tree_per_iteration);
|
||||
} else {
|
||||
return new BaggingSampleStrategy(config, train_data, objective_function, num_tree_per_iteration);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,129 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_BOOSTING_SCORE_UPDATER_HPP_
|
||||
#define LIGHTGBM_SRC_BOOSTING_SCORE_UPDATER_HPP_
|
||||
|
||||
#include <LightGBM/dataset.h>
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/tree.h>
|
||||
#include <LightGBM/tree_learner.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief Used to store and update score for data
|
||||
*/
|
||||
class ScoreUpdater {
|
||||
public:
|
||||
/*!
|
||||
* \brief Constructor, will pass a const pointer of dataset
|
||||
* \param data This class will bind with this data set
|
||||
*/
|
||||
ScoreUpdater(const Dataset* data, int num_tree_per_iteration) : data_(data) {
|
||||
num_data_ = data->num_data();
|
||||
int64_t total_size = static_cast<int64_t>(num_data_) * num_tree_per_iteration;
|
||||
score_.resize(total_size);
|
||||
// default start score is zero
|
||||
std::memset(score_.data(), 0, total_size * sizeof(double));
|
||||
has_init_score_ = false;
|
||||
const double* init_score = data->metadata().init_score();
|
||||
// if exists initial score, will start from it
|
||||
if (init_score != nullptr) {
|
||||
if ((data->metadata().num_init_score() % num_data_) != 0
|
||||
|| (data->metadata().num_init_score() / num_data_) != num_tree_per_iteration) {
|
||||
Log::Fatal("Number of class for initial score error");
|
||||
}
|
||||
has_init_score_ = true;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (total_size >= 1024)
|
||||
for (int64_t i = 0; i < total_size; ++i) {
|
||||
score_[i] = init_score[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
/*! \brief Destructor */
|
||||
virtual ~ScoreUpdater() {
|
||||
}
|
||||
|
||||
inline bool has_init_score() const { return has_init_score_; }
|
||||
|
||||
virtual inline void AddScore(double val, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024)
|
||||
for (int i = 0; i < num_data_; ++i) {
|
||||
score_[offset + i] += val;
|
||||
}
|
||||
}
|
||||
|
||||
virtual inline void MultiplyScore(double val, int cur_tree_id) {
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024)
|
||||
for (int i = 0; i < num_data_; ++i) {
|
||||
score_[offset + i] *= val;
|
||||
}
|
||||
}
|
||||
/*!
|
||||
* \brief Using tree model to get prediction number, then adding to scores for all data
|
||||
* Note: this function generally will be used on validation data too.
|
||||
* \param tree Trained tree model
|
||||
* \param cur_tree_id Current tree for multiclass training
|
||||
*/
|
||||
virtual inline void AddScore(const Tree* tree, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
tree->AddPredictionToScore(data_, num_data_, score_.data() + offset);
|
||||
}
|
||||
/*!
|
||||
* \brief Adding prediction score, only used for training data.
|
||||
* The training data is partitioned into tree leaves after training
|
||||
* Based on which We can get prediction quickly.
|
||||
* \param tree_learner
|
||||
* \param cur_tree_id Current tree for multiclass training
|
||||
*/
|
||||
virtual inline void AddScore(const TreeLearner* tree_learner, const Tree* tree, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
tree_learner->AddPredictionToScore(tree, score_.data() + offset);
|
||||
}
|
||||
/*!
|
||||
* \brief Using tree model to get prediction number, then adding to scores for parts of data
|
||||
* Used for prediction of training out-of-bag data
|
||||
* \param tree Trained tree model
|
||||
* \param data_indices Indices of data that will be processed
|
||||
* \param data_cnt Number of data that will be processed
|
||||
* \param cur_tree_id Current tree for multiclass training
|
||||
*/
|
||||
virtual inline void AddScore(const Tree* tree, const data_size_t* data_indices,
|
||||
data_size_t data_cnt, int cur_tree_id) {
|
||||
Common::FunctionTimer fun_timer("ScoreUpdater::AddScore", global_timer);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * cur_tree_id;
|
||||
tree->AddPredictionToScore(data_, data_indices, data_cnt, score_.data() + offset);
|
||||
}
|
||||
/*! \brief Pointer of score */
|
||||
virtual inline const double* score() const { return score_.data(); }
|
||||
|
||||
inline data_size_t num_data() const { return num_data_; }
|
||||
|
||||
/*! \brief Disable copy */
|
||||
ScoreUpdater& operator=(const ScoreUpdater&) = delete;
|
||||
/*! \brief Disable copy */
|
||||
ScoreUpdater(const ScoreUpdater&) = delete;
|
||||
|
||||
protected:
|
||||
/*! \brief Number of total data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of data set */
|
||||
const Dataset* data_;
|
||||
/*! \brief Scores for data set */
|
||||
std::vector<double, Common::AlignmentAllocator<double, kAlignedSize>> score_;
|
||||
bool has_init_score_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_BOOSTING_SCORE_UPDATER_HPP_
|
||||
+3080
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,451 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
* Modifications Copyright(C) 2023 Advanced Micro Devices, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_algorithms.hpp>
|
||||
#include <LightGBM/cuda/cuda_rocm_interop.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename T>
|
||||
__global__ void ShufflePrefixSumGlobalKernel(T* values, size_t len, T* block_prefix_sum_buffer) {
|
||||
__shared__ T shared_mem_buffer[WARPSIZE];
|
||||
const size_t index = static_cast<size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
T value = 0;
|
||||
if (index < len) {
|
||||
value = values[index];
|
||||
}
|
||||
const T prefix_sum_value = ShufflePrefixSum<T>(value, shared_mem_buffer);
|
||||
values[index] = prefix_sum_value;
|
||||
if (threadIdx.x == blockDim.x - 1) {
|
||||
block_prefix_sum_buffer[blockIdx.x] = prefix_sum_value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void ShufflePrefixSumGlobalReduceBlockKernel(T* block_prefix_sum_buffer, int num_blocks) {
|
||||
__shared__ T shared_mem_buffer[WARPSIZE];
|
||||
const int num_blocks_per_thread = (num_blocks + GLOBAL_PREFIX_SUM_BLOCK_SIZE - 2) / (GLOBAL_PREFIX_SUM_BLOCK_SIZE - 1);
|
||||
int thread_block_start = threadIdx.x == 0 ? 0 : (threadIdx.x - 1) * num_blocks_per_thread;
|
||||
int thread_block_end = threadIdx.x == 0 ? 0 : min(thread_block_start + num_blocks_per_thread, num_blocks);
|
||||
T base = 0;
|
||||
for (int block_index = thread_block_start; block_index < thread_block_end; ++block_index) {
|
||||
base += block_prefix_sum_buffer[block_index];
|
||||
}
|
||||
base = ShufflePrefixSum<T>(base, shared_mem_buffer);
|
||||
thread_block_start = threadIdx.x == blockDim.x - 1 ? 0 : threadIdx.x * num_blocks_per_thread;
|
||||
thread_block_end = threadIdx.x == blockDim.x - 1 ? 0 : min(thread_block_start + num_blocks_per_thread, num_blocks);
|
||||
for (int block_index = thread_block_start + 1; block_index < thread_block_end; ++block_index) {
|
||||
block_prefix_sum_buffer[block_index] += block_prefix_sum_buffer[block_index - 1];
|
||||
}
|
||||
for (int block_index = thread_block_start; block_index < thread_block_end; ++block_index) {
|
||||
block_prefix_sum_buffer[block_index] += base;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void ShufflePrefixSumGlobalAddBase(size_t len, const T* block_prefix_sum_buffer, T* values) {
|
||||
const T base = blockIdx.x == 0 ? 0 : block_prefix_sum_buffer[blockIdx.x - 1];
|
||||
const size_t index = static_cast<size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (index < len) {
|
||||
values[index] += base;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ShufflePrefixSumGlobal(T* values, size_t len, T* block_prefix_sum_buffer) {
|
||||
const int num_blocks = (static_cast<int>(len) + GLOBAL_PREFIX_SUM_BLOCK_SIZE - 1) / GLOBAL_PREFIX_SUM_BLOCK_SIZE;
|
||||
ShufflePrefixSumGlobalKernel<<<num_blocks, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(values, len, block_prefix_sum_buffer);
|
||||
ShufflePrefixSumGlobalReduceBlockKernel<<<1, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(block_prefix_sum_buffer, num_blocks);
|
||||
ShufflePrefixSumGlobalAddBase<<<num_blocks, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(len, block_prefix_sum_buffer, values);
|
||||
}
|
||||
|
||||
template void ShufflePrefixSumGlobal<uint16_t>(uint16_t* values, size_t len, uint16_t* block_prefix_sum_buffer);
|
||||
template void ShufflePrefixSumGlobal<uint32_t>(uint32_t* values, size_t len, uint32_t* block_prefix_sum_buffer);
|
||||
template void ShufflePrefixSumGlobal<uint64_t>(uint64_t* values, size_t len, uint64_t* block_prefix_sum_buffer);
|
||||
|
||||
__global__ void BitonicArgSortItemsGlobalKernel(const double* scores,
|
||||
const int num_queries,
|
||||
const data_size_t* cuda_query_boundaries,
|
||||
data_size_t* out_indices) {
|
||||
const int query_index_start = static_cast<int>(blockIdx.x) * BITONIC_SORT_QUERY_ITEM_BLOCK_SIZE;
|
||||
const int query_index_end = min(query_index_start + BITONIC_SORT_QUERY_ITEM_BLOCK_SIZE, num_queries);
|
||||
for (int query_index = query_index_start; query_index < query_index_end; ++query_index) {
|
||||
const data_size_t query_item_start = cuda_query_boundaries[query_index];
|
||||
const data_size_t query_item_end = cuda_query_boundaries[query_index + 1];
|
||||
const data_size_t num_items_in_query = query_item_end - query_item_start;
|
||||
BitonicArgSortDevice<double, data_size_t, false, BITONIC_SORT_NUM_ELEMENTS, 11>(scores + query_item_start,
|
||||
out_indices + query_item_start,
|
||||
num_items_in_query);
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
void BitonicArgSortItemsGlobal(
|
||||
const double* scores,
|
||||
const int num_queries,
|
||||
const data_size_t* cuda_query_boundaries,
|
||||
data_size_t* out_indices) {
|
||||
const int num_blocks = (num_queries + BITONIC_SORT_QUERY_ITEM_BLOCK_SIZE - 1) / BITONIC_SORT_QUERY_ITEM_BLOCK_SIZE;
|
||||
BitonicArgSortItemsGlobalKernel<<<num_blocks, BITONIC_SORT_NUM_ELEMENTS>>>(
|
||||
scores, num_queries, cuda_query_boundaries, out_indices);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void BlockReduceSum(T* block_buffer, const data_size_t num_blocks) {
|
||||
__shared__ T shared_buffer[WARPSIZE];
|
||||
T thread_sum = 0;
|
||||
for (data_size_t block_index = static_cast<data_size_t>(threadIdx.x); block_index < num_blocks; block_index += static_cast<data_size_t>(blockDim.x)) {
|
||||
thread_sum += block_buffer[block_index];
|
||||
}
|
||||
thread_sum = ShuffleReduceSum<T>(thread_sum, shared_buffer, blockDim.x);
|
||||
if (threadIdx.x == 0) {
|
||||
block_buffer[0] = thread_sum;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename REDUCE_T>
|
||||
__global__ void ShuffleReduceSumGlobalKernel(const VAL_T* values, const data_size_t num_value, REDUCE_T* block_buffer) {
|
||||
__shared__ REDUCE_T shared_buffer[WARPSIZE];
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
const REDUCE_T value = (data_index < num_value ? static_cast<REDUCE_T>(values[data_index]) : 0.0f);
|
||||
const REDUCE_T reduce_value = ShuffleReduceSum<REDUCE_T>(value, shared_buffer, blockDim.x);
|
||||
if (threadIdx.x == 0) {
|
||||
block_buffer[blockIdx.x] = reduce_value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename REDUCE_T>
|
||||
void ShuffleReduceSumGlobal(const VAL_T* values, size_t n, REDUCE_T* block_buffer) {
|
||||
const data_size_t num_value = static_cast<data_size_t>(n);
|
||||
const data_size_t num_blocks = (num_value + GLOBAL_PREFIX_SUM_BLOCK_SIZE - 1) / GLOBAL_PREFIX_SUM_BLOCK_SIZE;
|
||||
ShuffleReduceSumGlobalKernel<VAL_T, REDUCE_T><<<num_blocks, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(values, num_value, block_buffer);
|
||||
BlockReduceSum<REDUCE_T><<<1, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(block_buffer, num_blocks);
|
||||
}
|
||||
|
||||
template void ShuffleReduceSumGlobal<label_t, double>(const label_t* values, size_t n, double* block_buffer);
|
||||
template void ShuffleReduceSumGlobal<double, double>(const double* values, size_t n, double* block_buffer);
|
||||
|
||||
template <typename VAL_T, typename REDUCE_T>
|
||||
__global__ void ShuffleReduceMinGlobalKernel(const VAL_T* values, const data_size_t num_value, REDUCE_T* block_buffer) {
|
||||
__shared__ REDUCE_T shared_buffer[WARPSIZE];
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
const REDUCE_T value = (data_index < num_value ? static_cast<REDUCE_T>(values[data_index]) : 0.0f);
|
||||
const REDUCE_T reduce_value = ShuffleReduceMin<REDUCE_T>(value, shared_buffer, blockDim.x);
|
||||
if (threadIdx.x == 0) {
|
||||
block_buffer[blockIdx.x] = reduce_value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void ShuffleBlockReduceMin(T* block_buffer, const data_size_t num_blocks) {
|
||||
__shared__ T shared_buffer[WARPSIZE];
|
||||
T thread_min = 0;
|
||||
for (data_size_t block_index = static_cast<data_size_t>(threadIdx.x); block_index < num_blocks; block_index += static_cast<data_size_t>(blockDim.x)) {
|
||||
const T value = block_buffer[block_index];
|
||||
if (value < thread_min) {
|
||||
thread_min = value;
|
||||
}
|
||||
}
|
||||
thread_min = ShuffleReduceMin<T>(thread_min, shared_buffer, blockDim.x);
|
||||
if (threadIdx.x == 0) {
|
||||
block_buffer[0] = thread_min;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename REDUCE_T>
|
||||
void ShuffleReduceMinGlobal(const VAL_T* values, size_t n, REDUCE_T* block_buffer) {
|
||||
const data_size_t num_value = static_cast<data_size_t>(n);
|
||||
const data_size_t num_blocks = (num_value + GLOBAL_PREFIX_SUM_BLOCK_SIZE - 1) / GLOBAL_PREFIX_SUM_BLOCK_SIZE;
|
||||
ShuffleReduceMinGlobalKernel<VAL_T, REDUCE_T><<<num_blocks, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(values, num_value, block_buffer);
|
||||
ShuffleBlockReduceMin<REDUCE_T><<<1, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(block_buffer, num_blocks);
|
||||
}
|
||||
|
||||
template void ShuffleReduceMinGlobal<label_t, double>(const label_t* values, size_t n, double* block_buffer);
|
||||
|
||||
template <typename VAL_T, typename REDUCE_T>
|
||||
__global__ void ShuffleReduceDotProdGlobalKernel(const VAL_T* values1, const VAL_T* values2, const data_size_t num_value, REDUCE_T* block_buffer) {
|
||||
__shared__ REDUCE_T shared_buffer[WARPSIZE];
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
const REDUCE_T value1 = (data_index < num_value ? static_cast<REDUCE_T>(values1[data_index]) : 0.0f);
|
||||
const REDUCE_T value2 = (data_index < num_value ? static_cast<REDUCE_T>(values2[data_index]) : 0.0f);
|
||||
const REDUCE_T reduce_value = ShuffleReduceSum<REDUCE_T>(value1 * value2, shared_buffer, blockDim.x);
|
||||
if (threadIdx.x == 0) {
|
||||
block_buffer[blockIdx.x] = reduce_value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename REDUCE_T>
|
||||
void ShuffleReduceDotProdGlobal(const VAL_T* values1, const VAL_T* values2, size_t n, REDUCE_T* block_buffer) {
|
||||
const data_size_t num_value = static_cast<data_size_t>(n);
|
||||
const data_size_t num_blocks = (num_value + GLOBAL_PREFIX_SUM_BLOCK_SIZE - 1) / GLOBAL_PREFIX_SUM_BLOCK_SIZE;
|
||||
ShuffleReduceDotProdGlobalKernel<VAL_T, REDUCE_T><<<num_blocks, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(values1, values2, num_value, block_buffer);
|
||||
BlockReduceSum<REDUCE_T><<<1, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(block_buffer, num_blocks);
|
||||
}
|
||||
|
||||
template void ShuffleReduceDotProdGlobal<label_t, double>(const label_t* values1, const label_t* values2, size_t n, double* block_buffer);
|
||||
|
||||
template <typename INDEX_T, typename VAL_T, typename REDUCE_T>
|
||||
__global__ void GlobalInclusiveArgPrefixSumKernel(
|
||||
const INDEX_T* sorted_indices, const VAL_T* in_values, REDUCE_T* out_values, REDUCE_T* block_buffer, data_size_t num_data) {
|
||||
__shared__ REDUCE_T shared_buffer[WARPSIZE];
|
||||
const data_size_t data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
REDUCE_T value = static_cast<REDUCE_T>(data_index < num_data ? in_values[sorted_indices[data_index]] : 0);
|
||||
__syncthreads();
|
||||
value = ShufflePrefixSum<REDUCE_T>(value, shared_buffer);
|
||||
if (data_index < num_data) {
|
||||
out_values[data_index] = value;
|
||||
}
|
||||
if (threadIdx.x == blockDim.x - 1) {
|
||||
block_buffer[blockIdx.x + 1] = value;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void GlobalInclusivePrefixSumReduceBlockKernel(T* block_buffer, data_size_t num_blocks) {
|
||||
__shared__ T shared_buffer[WARPSIZE];
|
||||
T thread_sum = 0;
|
||||
const data_size_t num_blocks_per_thread = (num_blocks + static_cast<data_size_t>(blockDim.x)) / static_cast<data_size_t>(blockDim.x);
|
||||
const data_size_t thread_start_block_index = static_cast<data_size_t>(threadIdx.x) * num_blocks_per_thread;
|
||||
const data_size_t thread_end_block_index = min(thread_start_block_index + num_blocks_per_thread, num_blocks + 1);
|
||||
for (data_size_t block_index = thread_start_block_index; block_index < thread_end_block_index; ++block_index) {
|
||||
thread_sum += block_buffer[block_index];
|
||||
}
|
||||
ShufflePrefixSumExclusive<T>(thread_sum, shared_buffer);
|
||||
for (data_size_t block_index = thread_start_block_index; block_index < thread_end_block_index; ++block_index) {
|
||||
block_buffer[block_index] += thread_sum;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void GlobalInclusivePrefixSumAddBlockBaseKernel(const T* block_buffer, T* values, data_size_t num_data) {
|
||||
const T block_sum_base = block_buffer[blockIdx.x];
|
||||
const data_size_t data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (data_index < num_data) {
|
||||
values[data_index] += block_sum_base;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename REDUCE_T, typename INDEX_T>
|
||||
void GlobalInclusiveArgPrefixSum(const INDEX_T* sorted_indices, const VAL_T* in_values, REDUCE_T* out_values, REDUCE_T* block_buffer, size_t n) {
|
||||
const data_size_t num_data = static_cast<data_size_t>(n);
|
||||
const data_size_t num_blocks = (num_data + GLOBAL_PREFIX_SUM_BLOCK_SIZE - 1) / GLOBAL_PREFIX_SUM_BLOCK_SIZE;
|
||||
GlobalInclusiveArgPrefixSumKernel<INDEX_T, VAL_T, REDUCE_T><<<num_blocks, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(
|
||||
sorted_indices, in_values, out_values, block_buffer, num_data);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
GlobalInclusivePrefixSumReduceBlockKernel<REDUCE_T><<<1, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(
|
||||
block_buffer, num_blocks);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
GlobalInclusivePrefixSumAddBlockBaseKernel<REDUCE_T><<<num_blocks, GLOBAL_PREFIX_SUM_BLOCK_SIZE>>>(
|
||||
block_buffer, out_values, num_data);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
template void GlobalInclusiveArgPrefixSum<label_t, double, data_size_t>(const data_size_t* sorted_indices, const label_t* in_values, double* out_values, double* block_buffer, size_t n);
|
||||
|
||||
template <typename VAL_T, typename INDEX_T, bool ASCENDING>
|
||||
__global__ void BitonicArgSortGlobalKernel(const VAL_T* values, INDEX_T* indices, const int num_total_data) {
|
||||
const int thread_index = static_cast<int>(threadIdx.x);
|
||||
const int low = static_cast<int>(blockIdx.x * BITONIC_SORT_NUM_ELEMENTS);
|
||||
const bool outer_ascending = ASCENDING ? (blockIdx.x % 2 == 0) : (blockIdx.x % 2 == 1);
|
||||
const VAL_T* values_pointer = values + low;
|
||||
INDEX_T* indices_pointer = indices + low;
|
||||
const int num_data = min(BITONIC_SORT_NUM_ELEMENTS, num_total_data - low);
|
||||
__shared__ VAL_T shared_values[BITONIC_SORT_NUM_ELEMENTS];
|
||||
__shared__ INDEX_T shared_indices[BITONIC_SORT_NUM_ELEMENTS];
|
||||
if (thread_index < num_data) {
|
||||
shared_values[thread_index] = values_pointer[thread_index];
|
||||
shared_indices[thread_index] = static_cast<INDEX_T>(thread_index + blockIdx.x * blockDim.x);
|
||||
}
|
||||
__syncthreads();
|
||||
for (int depth = BITONIC_SORT_DEPTH - 1; depth >= 1; --depth) {
|
||||
const int segment_length = 1 << (BITONIC_SORT_DEPTH - depth);
|
||||
const int segment_index = thread_index / segment_length;
|
||||
const bool ascending = outer_ascending ? (segment_index % 2 == 0) : (segment_index % 2 == 1);
|
||||
const int num_total_segment = (num_data + segment_length - 1) / segment_length;
|
||||
{
|
||||
const int inner_depth = depth;
|
||||
const int inner_segment_length_half = 1 << (BITONIC_SORT_DEPTH - 1 - inner_depth);
|
||||
const int inner_segment_index_half = thread_index / inner_segment_length_half;
|
||||
const int offset = ((inner_segment_index_half >> 1) == num_total_segment - 1 && ascending == outer_ascending) ?
|
||||
(num_total_segment * segment_length - num_data) : 0;
|
||||
const int segment_start = segment_index * segment_length;
|
||||
if (inner_segment_index_half % 2 == 0) {
|
||||
if (thread_index >= offset + segment_start) {
|
||||
const int index_to_compare = thread_index + inner_segment_length_half - offset;
|
||||
const INDEX_T this_index = shared_indices[thread_index];
|
||||
const INDEX_T other_index = shared_indices[index_to_compare];
|
||||
const VAL_T this_value = shared_values[thread_index];
|
||||
const VAL_T other_value = shared_values[index_to_compare];
|
||||
if (index_to_compare < num_data && (this_value > other_value) == ascending) {
|
||||
shared_indices[thread_index] = other_index;
|
||||
shared_indices[index_to_compare] = this_index;
|
||||
shared_values[thread_index] = other_value;
|
||||
shared_values[index_to_compare] = this_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
for (int inner_depth = depth + 1; inner_depth < BITONIC_SORT_DEPTH; ++inner_depth) {
|
||||
const int inner_segment_length_half = 1 << (BITONIC_SORT_DEPTH - 1 - inner_depth);
|
||||
const int inner_segment_index_half = thread_index / inner_segment_length_half;
|
||||
if (inner_segment_index_half % 2 == 0) {
|
||||
const int index_to_compare = thread_index + inner_segment_length_half;
|
||||
const INDEX_T this_index = shared_indices[thread_index];
|
||||
const INDEX_T other_index = shared_indices[index_to_compare];
|
||||
const VAL_T this_value = shared_values[thread_index];
|
||||
const VAL_T other_value = shared_values[index_to_compare];
|
||||
if (index_to_compare < num_data && (this_value > other_value) == ascending) {
|
||||
shared_indices[thread_index] = other_index;
|
||||
shared_indices[index_to_compare] = this_index;
|
||||
shared_values[thread_index] = other_value;
|
||||
shared_values[index_to_compare] = this_value;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
if (thread_index < num_data) {
|
||||
indices_pointer[thread_index] = shared_indices[thread_index];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename INDEX_T, bool ASCENDING>
|
||||
__global__ void BitonicArgSortMergeKernel(const VAL_T* values, INDEX_T* indices, const int segment_length, const int len) {
|
||||
const int thread_index = static_cast<int>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
const int segment_index = thread_index / segment_length;
|
||||
const bool ascending = ASCENDING ? (segment_index % 2 == 0) : (segment_index % 2 == 1);
|
||||
__shared__ VAL_T shared_values[BITONIC_SORT_NUM_ELEMENTS];
|
||||
__shared__ INDEX_T shared_indices[BITONIC_SORT_NUM_ELEMENTS];
|
||||
const int offset = static_cast<int>(blockIdx.x * blockDim.x);
|
||||
const int local_len = min(BITONIC_SORT_NUM_ELEMENTS, len - offset);
|
||||
if (thread_index < len) {
|
||||
const INDEX_T index = indices[thread_index];
|
||||
shared_values[threadIdx.x] = values[index];
|
||||
shared_indices[threadIdx.x] = index;
|
||||
}
|
||||
__syncthreads();
|
||||
int half_segment_length = BITONIC_SORT_NUM_ELEMENTS / 2;
|
||||
while (half_segment_length >= 1) {
|
||||
const int half_segment_index = static_cast<int>(threadIdx.x) / half_segment_length;
|
||||
if (half_segment_index % 2 == 0) {
|
||||
const int index_to_compare = static_cast<int>(threadIdx.x) + half_segment_length;
|
||||
const INDEX_T this_index = shared_indices[threadIdx.x];
|
||||
const INDEX_T other_index = shared_indices[index_to_compare];
|
||||
const VAL_T this_value = shared_values[threadIdx.x];
|
||||
const VAL_T other_value = shared_values[index_to_compare];
|
||||
if (index_to_compare < local_len && ((this_value > other_value) == ascending)) {
|
||||
shared_indices[threadIdx.x] = other_index;
|
||||
shared_indices[index_to_compare] = this_index;
|
||||
shared_values[threadIdx.x] = other_value;
|
||||
shared_values[index_to_compare] = this_value;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
half_segment_length >>= 1;
|
||||
}
|
||||
if (thread_index < len) {
|
||||
indices[thread_index] = shared_indices[threadIdx.x];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename INDEX_T, bool ASCENDING, bool BEGIN>
|
||||
__global__ void BitonicArgCompareKernel(const VAL_T* values, INDEX_T* indices, const int half_segment_length, const int outer_segment_length, const int len) {
|
||||
const int thread_index = static_cast<int>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
const int segment_index = thread_index / outer_segment_length;
|
||||
const int half_segment_index = thread_index / half_segment_length;
|
||||
const bool ascending = ASCENDING ? (segment_index % 2 == 0) : (segment_index % 2 == 1);
|
||||
if (half_segment_index % 2 == 0) {
|
||||
const int num_total_segment = (len + outer_segment_length - 1) / outer_segment_length;
|
||||
if (BEGIN && (half_segment_index >> 1) == num_total_segment - 1 && ascending == ASCENDING) {
|
||||
const int offset = num_total_segment * outer_segment_length - len;
|
||||
const int segment_start = segment_index * outer_segment_length;
|
||||
if (thread_index >= offset + segment_start) {
|
||||
const int index_to_compare = thread_index + half_segment_length - offset;
|
||||
if (index_to_compare < len) {
|
||||
const INDEX_T this_index = indices[thread_index];
|
||||
const INDEX_T other_index = indices[index_to_compare];
|
||||
if ((values[this_index] > values[other_index]) == ascending) {
|
||||
indices[thread_index] = other_index;
|
||||
indices[index_to_compare] = this_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const int index_to_compare = thread_index + half_segment_length;
|
||||
if (index_to_compare < len) {
|
||||
const INDEX_T this_index = indices[thread_index];
|
||||
const INDEX_T other_index = indices[index_to_compare];
|
||||
if ((values[this_index] > values[other_index]) == ascending) {
|
||||
indices[thread_index] = other_index;
|
||||
indices[index_to_compare] = this_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, typename INDEX_T, bool ASCENDING>
|
||||
void BitonicArgSortGlobalHelper(const VAL_T* values, INDEX_T* indices, const size_t len) {
|
||||
int max_depth = 1;
|
||||
int len_to_shift = static_cast<int>(len) - 1;
|
||||
while (len_to_shift > 0) {
|
||||
++max_depth;
|
||||
len_to_shift >>= 1;
|
||||
}
|
||||
const int num_blocks = (static_cast<int>(len) + BITONIC_SORT_NUM_ELEMENTS - 1) / BITONIC_SORT_NUM_ELEMENTS;
|
||||
BitonicArgSortGlobalKernel<VAL_T, INDEX_T, ASCENDING><<<num_blocks, BITONIC_SORT_NUM_ELEMENTS>>>(values, indices, static_cast<int>(len));
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
for (int depth = max_depth - 11; depth >= 1; --depth) {
|
||||
const int segment_length = (1 << (max_depth - depth));
|
||||
int half_segment_length = (segment_length >> 1);
|
||||
{
|
||||
BitonicArgCompareKernel<VAL_T, INDEX_T, ASCENDING, true><<<num_blocks, BITONIC_SORT_NUM_ELEMENTS>>>(
|
||||
values, indices, half_segment_length, segment_length, static_cast<int>(len));
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
half_segment_length >>= 1;
|
||||
}
|
||||
for (int inner_depth = depth + 1; inner_depth <= max_depth - 11; ++inner_depth) {
|
||||
BitonicArgCompareKernel<VAL_T, INDEX_T, ASCENDING, false><<<num_blocks, BITONIC_SORT_NUM_ELEMENTS>>>(
|
||||
values, indices, half_segment_length, segment_length, static_cast<int>(len));
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
half_segment_length >>= 1;
|
||||
}
|
||||
BitonicArgSortMergeKernel<VAL_T, INDEX_T, ASCENDING><<<num_blocks, BITONIC_SORT_NUM_ELEMENTS>>>(
|
||||
values, indices, segment_length, static_cast<int>(len));
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void BitonicArgSortGlobal<double, data_size_t, false>(const double* values, data_size_t* indices, const size_t len) {
|
||||
BitonicArgSortGlobalHelper<double, data_size_t, false>(values, indices, len);
|
||||
}
|
||||
|
||||
template <>
|
||||
void BitonicArgSortGlobal<double, data_size_t, true>(const double* values, data_size_t* indices, const size_t len) {
|
||||
BitonicArgSortGlobalHelper<double, data_size_t, true>(values, indices, len);
|
||||
}
|
||||
|
||||
template <>
|
||||
void BitonicArgSortGlobal<label_t, data_size_t, false>(const label_t* values, data_size_t* indices, const size_t len) {
|
||||
BitonicArgSortGlobalHelper<label_t, data_size_t, false>(values, indices, len);
|
||||
}
|
||||
|
||||
template <>
|
||||
void BitonicArgSortGlobal<data_size_t, int, true>(const data_size_t* values, int* indices, const size_t len) {
|
||||
BitonicArgSortGlobalHelper<data_size_t, int, true>(values, indices, len);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,61 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_rocm_interop.h>
|
||||
#include <LightGBM/cuda/cuda_utils.hu>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
void SynchronizeCUDADevice(const char* file, const int line) {
|
||||
gpuAssert(cudaDeviceSynchronize(), file, line);
|
||||
}
|
||||
|
||||
void SynchronizeCUDAStream(cudaStream_t cuda_stream, const char* file, const int line) {
|
||||
gpuAssert(cudaStreamSynchronize(cuda_stream), file, line);
|
||||
}
|
||||
|
||||
void PrintLastCUDAError() {
|
||||
const char* error_name = cudaGetErrorName(cudaGetLastError());
|
||||
Log::Fatal(error_name);
|
||||
}
|
||||
|
||||
void SetCUDADevice(int gpu_device_id, const char* file, int line) {
|
||||
int cur_gpu_device_id = 0;
|
||||
CUDASUCCESS_OR_FATAL_OUTER(cudaGetDevice(&cur_gpu_device_id));
|
||||
if (cur_gpu_device_id != gpu_device_id) {
|
||||
CUDASUCCESS_OR_FATAL_OUTER(cudaSetDevice(gpu_device_id));
|
||||
}
|
||||
}
|
||||
|
||||
int GetCUDADevice(const char* file, int line) {
|
||||
int cur_gpu_device_id = 0;
|
||||
CUDASUCCESS_OR_FATAL_OUTER(cudaGetDevice(&cur_gpu_device_id));
|
||||
return cur_gpu_device_id;
|
||||
}
|
||||
|
||||
cudaStream_t CUDAStreamCreate() {
|
||||
cudaStream_t cuda_stream;
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_stream));
|
||||
return cuda_stream;
|
||||
}
|
||||
|
||||
void CUDAStreamDestroy(cudaStream_t cuda_stream) {
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamDestroy(cuda_stream));
|
||||
}
|
||||
|
||||
void NCCLGroupStart() {
|
||||
NCCLCHECK(ncclGroupStart());
|
||||
}
|
||||
|
||||
void NCCLGroupEnd() {
|
||||
NCCLCHECK(ncclGroupEnd());
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
+1077
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,519 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/config.h>
|
||||
|
||||
#include <LightGBM/cuda/vector_cudahost.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
#include <LightGBM/utils/random.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
void Config::KV2Map(std::unordered_map<std::string, std::vector<std::string>>* params, const char* kv) {
|
||||
std::vector<std::string> tmp_strs = Common::Split(kv, '=');
|
||||
if (tmp_strs.size() == 2 || tmp_strs.size() == 1) {
|
||||
std::string key = Common::RemoveQuotationSymbol(Common::Trim(tmp_strs[0]));
|
||||
std::string value = "";
|
||||
if (tmp_strs.size() == 2) {
|
||||
value = Common::RemoveQuotationSymbol(Common::Trim(tmp_strs[1]));
|
||||
}
|
||||
if (key.size() > 0) {
|
||||
params->operator[](key).emplace_back(value);
|
||||
}
|
||||
} else {
|
||||
Log::Warning("Unknown parameter %s", kv);
|
||||
}
|
||||
}
|
||||
|
||||
void GetFirstValueAsInt(const std::unordered_map<std::string, std::vector<std::string>>& params, std::string key, int* out) {
|
||||
const auto pair = params.find(key);
|
||||
if (pair != params.end()) {
|
||||
auto candidate = pair->second[0].c_str();
|
||||
if (!Common::AtoiAndCheck(candidate, out)) {
|
||||
Log::Fatal("Parameter %s should be of type int, got \"%s\"", key.c_str(), candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Config::SetVerbosity(const std::unordered_map<std::string, std::vector<std::string>>& params) {
|
||||
int verbosity = 1;
|
||||
|
||||
// if "verbosity" was found in params, prefer that to any other aliases
|
||||
const auto verbosity_iter = params.find("verbosity");
|
||||
if (verbosity_iter != params.end()) {
|
||||
GetFirstValueAsInt(params, "verbosity", &verbosity);
|
||||
} else {
|
||||
// if "verbose" was found in params and "verbosity" was not, use that value
|
||||
const auto verbose_iter = params.find("verbose");
|
||||
if (verbose_iter != params.end()) {
|
||||
GetFirstValueAsInt(params, "verbose", &verbosity);
|
||||
} else {
|
||||
// if "verbosity" and "verbose" were both missing from params, don't modify LightGBM's log level
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise, update LightGBM's log level based on the passed-in value
|
||||
if (verbosity < 0) {
|
||||
LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Fatal);
|
||||
} else if (verbosity == 0) {
|
||||
LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Warning);
|
||||
} else if (verbosity == 1) {
|
||||
LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Info);
|
||||
} else {
|
||||
LightGBM::Log::ResetLogLevel(LightGBM::LogLevel::Debug);
|
||||
}
|
||||
}
|
||||
|
||||
void Config::KeepFirstValues(const std::unordered_map<std::string, std::vector<std::string>>& params, std::unordered_map<std::string, std::string>* out) {
|
||||
for (auto pair = params.begin(); pair != params.end(); ++pair) {
|
||||
auto name = pair->first.c_str();
|
||||
auto values = pair->second;
|
||||
out->emplace(name, values[0]);
|
||||
for (size_t i = 1; i < pair->second.size(); ++i) {
|
||||
Log::Warning("%s is set=%s, %s=%s will be ignored. Current value: %s=%s",
|
||||
name, values[0].c_str(),
|
||||
name, values[i].c_str(),
|
||||
name, values[0].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> Config::Str2Map(const char* parameters) {
|
||||
std::unordered_map<std::string, std::vector<std::string>> all_params;
|
||||
std::unordered_map<std::string, std::string> params;
|
||||
auto args = Common::Split(parameters, " \t\n\r");
|
||||
for (auto arg : args) {
|
||||
KV2Map(&all_params, Common::Trim(arg).c_str());
|
||||
}
|
||||
SetVerbosity(all_params);
|
||||
KeepFirstValues(all_params, ¶ms);
|
||||
ParameterAlias::KeyAliasTransform(¶ms);
|
||||
return params;
|
||||
}
|
||||
|
||||
void GetBoostingType(const std::unordered_map<std::string, std::string>& params, std::string* boosting) {
|
||||
std::string value;
|
||||
if (Config::GetString(params, "boosting", &value)) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
if (value == std::string("gbdt") || value == std::string("gbrt")) {
|
||||
*boosting = "gbdt";
|
||||
} else if (value == std::string("dart")) {
|
||||
*boosting = "dart";
|
||||
} else if (value == std::string("goss")) {
|
||||
*boosting = "goss";
|
||||
} else if (value == std::string("rf") || value == std::string("random_forest")) {
|
||||
*boosting = "rf";
|
||||
} else {
|
||||
Log::Fatal("Unknown boosting type %s", value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetDataSampleStrategy(const std::unordered_map<std::string, std::string>& params, std::string* strategy) {
|
||||
std::string value;
|
||||
if (Config::GetString(params, "data_sample_strategy", &value)) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
if (value == std::string("goss")) {
|
||||
*strategy = "goss";
|
||||
} else if (value == std::string("bagging")) {
|
||||
*strategy = "bagging";
|
||||
} else {
|
||||
Log::Fatal("Unknown sample strategy %s", value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseMetrics(const std::string& value, std::vector<std::string>* out_metric) {
|
||||
std::unordered_set<std::string> metric_sets;
|
||||
out_metric->clear();
|
||||
std::vector<std::string> metrics = Common::Split(value.c_str(), ',');
|
||||
for (auto& met : metrics) {
|
||||
auto type = ParseMetricAlias(met);
|
||||
if (metric_sets.count(type) <= 0) {
|
||||
out_metric->push_back(type);
|
||||
metric_sets.insert(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetObjectiveType(const std::unordered_map<std::string, std::string>& params, std::string* objective) {
|
||||
std::string value;
|
||||
if (Config::GetString(params, "objective", &value)) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
*objective = ParseObjectiveAlias(value);
|
||||
}
|
||||
}
|
||||
|
||||
void GetMetricType(const std::unordered_map<std::string, std::string>& params, const std::string& objective, std::vector<std::string>* metric) {
|
||||
std::string value;
|
||||
if (Config::GetString(params, "metric", &value)) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
ParseMetrics(value, metric);
|
||||
}
|
||||
// add names of objective function if not providing metric
|
||||
if (metric->empty() && value.size() == 0) {
|
||||
ParseMetrics(objective, metric);
|
||||
}
|
||||
}
|
||||
|
||||
void GetTaskType(const std::unordered_map<std::string, std::string>& params, TaskType* task) {
|
||||
std::string value;
|
||||
if (Config::GetString(params, "task", &value)) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
if (value == std::string("train") || value == std::string("training")) {
|
||||
*task = TaskType::kTrain;
|
||||
} else if (value == std::string("predict") || value == std::string("prediction")
|
||||
|| value == std::string("test")) {
|
||||
*task = TaskType::kPredict;
|
||||
} else if (value == std::string("convert_model")) {
|
||||
*task = TaskType::kConvertModel;
|
||||
} else if (value == std::string("refit") || value == std::string("refit_tree")) {
|
||||
*task = TaskType::KRefitTree;
|
||||
} else if (value == std::string("save_binary")) {
|
||||
*task = TaskType::kSaveBinary;
|
||||
} else {
|
||||
Log::Fatal("Unknown task type %s", value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetDeviceType(const std::unordered_map<std::string, std::string>& params, std::string* device_type) {
|
||||
std::string value;
|
||||
if (Config::GetString(params, "device_type", &value)) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
if (value == std::string("cpu")) {
|
||||
*device_type = "cpu";
|
||||
} else if (value == std::string("gpu")) {
|
||||
*device_type = "gpu";
|
||||
} else if (value == std::string("cuda")) {
|
||||
*device_type = "cuda";
|
||||
} else {
|
||||
Log::Fatal("Unknown device type %s", value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetTreeLearnerType(const std::unordered_map<std::string, std::string>& params, std::string* tree_learner) {
|
||||
std::string value;
|
||||
if (Config::GetString(params, "tree_learner", &value)) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c){ return std::tolower(c); });
|
||||
if (value == std::string("serial")) {
|
||||
*tree_learner = "serial";
|
||||
} else if (value == std::string("feature") || value == std::string("feature_parallel")) {
|
||||
*tree_learner = "feature";
|
||||
} else if (value == std::string("data") || value == std::string("data_parallel")) {
|
||||
*tree_learner = "data";
|
||||
} else if (value == std::string("voting") || value == std::string("voting_parallel")) {
|
||||
*tree_learner = "voting";
|
||||
} else {
|
||||
Log::Fatal("Unknown tree learner type %s", value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Config::GetAucMuWeights() {
|
||||
if (auc_mu_weights.empty()) {
|
||||
// equal weights for all classes
|
||||
auc_mu_weights_matrix = std::vector<std::vector<double>> (num_class, std::vector<double>(num_class, 1));
|
||||
for (size_t i = 0; i < static_cast<size_t>(num_class); ++i) {
|
||||
auc_mu_weights_matrix[i][i] = 0;
|
||||
}
|
||||
} else {
|
||||
auc_mu_weights_matrix = std::vector<std::vector<double>> (num_class, std::vector<double>(num_class, 0));
|
||||
if (auc_mu_weights.size() != static_cast<size_t>(num_class * num_class)) {
|
||||
Log::Fatal("auc_mu_weights must have %d elements, but found %zu", num_class * num_class, auc_mu_weights.size());
|
||||
}
|
||||
for (size_t i = 0; i < static_cast<size_t>(num_class); ++i) {
|
||||
for (size_t j = 0; j < static_cast<size_t>(num_class); ++j) {
|
||||
if (i == j) {
|
||||
auc_mu_weights_matrix[i][j] = 0;
|
||||
if (std::fabs(auc_mu_weights[i * num_class + j]) > kZeroThreshold) {
|
||||
Log::Info("AUC-mu matrix must have zeros on diagonal. Overwriting value in position %zu of auc_mu_weights with 0.", i * num_class + j);
|
||||
}
|
||||
} else {
|
||||
if (std::fabs(auc_mu_weights[i * num_class + j]) < kZeroThreshold) {
|
||||
Log::Fatal("AUC-mu matrix must have non-zero values for non-diagonal entries. Found zero value in position %zu of auc_mu_weights.", i * num_class + j);
|
||||
}
|
||||
auc_mu_weights_matrix[i][j] = auc_mu_weights[i * num_class + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Config::GetInteractionConstraints() {
|
||||
if (interaction_constraints == "") {
|
||||
interaction_constraints_vector = std::vector<std::vector<int>>();
|
||||
} else {
|
||||
interaction_constraints_vector = Common::StringToArrayofArrays<int>(interaction_constraints, '[', ']', ',');
|
||||
}
|
||||
}
|
||||
|
||||
void Config::Set(const std::unordered_map<std::string, std::string>& params) {
|
||||
// generate seeds by seed.
|
||||
if (GetInt(params, "seed", &seed)) {
|
||||
Random rand(seed);
|
||||
int int_max = std::numeric_limits<int16_t>::max();
|
||||
data_random_seed = static_cast<int>(rand.NextShort(0, int_max));
|
||||
bagging_seed = static_cast<int>(rand.NextShort(0, int_max));
|
||||
drop_seed = static_cast<int>(rand.NextShort(0, int_max));
|
||||
feature_fraction_seed = static_cast<int>(rand.NextShort(0, int_max));
|
||||
objective_seed = static_cast<int>(rand.NextShort(0, int_max));
|
||||
extra_seed = static_cast<int>(rand.NextShort(0, int_max));
|
||||
}
|
||||
|
||||
GetTaskType(params, &task);
|
||||
GetBoostingType(params, &boosting);
|
||||
GetDataSampleStrategy(params, &data_sample_strategy);
|
||||
GetObjectiveType(params, &objective);
|
||||
GetMetricType(params, objective, &metric);
|
||||
GetDeviceType(params, &device_type);
|
||||
if (device_type == std::string("cuda")) {
|
||||
LGBM_config_::current_device = lgbm_device_cuda;
|
||||
}
|
||||
GetTreeLearnerType(params, &tree_learner);
|
||||
|
||||
GetMembersFromString(params);
|
||||
|
||||
GetAucMuWeights();
|
||||
|
||||
GetInteractionConstraints();
|
||||
|
||||
// sort eval_at
|
||||
std::sort(eval_at.begin(), eval_at.end());
|
||||
|
||||
std::vector<std::string> new_valid;
|
||||
for (size_t i = 0; i < valid.size(); ++i) {
|
||||
if (valid[i] != data) {
|
||||
// Only push the non-training data
|
||||
new_valid.push_back(valid[i]);
|
||||
} else {
|
||||
is_provide_training_metric = true;
|
||||
}
|
||||
}
|
||||
valid = new_valid;
|
||||
|
||||
if ((task == TaskType::kSaveBinary) && !save_binary) {
|
||||
Log::Info("save_binary parameter set to true because task is save_binary");
|
||||
save_binary = true;
|
||||
}
|
||||
|
||||
// check for conflicts
|
||||
CheckParamConflict(params);
|
||||
}
|
||||
|
||||
bool CheckMultiClassObjective(const std::string& objective) {
|
||||
return (objective == std::string("multiclass") || objective == std::string("multiclassova"));
|
||||
}
|
||||
|
||||
void Config::CheckParamConflict(const std::unordered_map<std::string, std::string>& params) {
|
||||
// check if objective, metric, and num_class match
|
||||
int num_class_check = num_class;
|
||||
bool objective_type_multiclass = CheckMultiClassObjective(objective) || (objective == std::string("custom") && num_class_check > 1);
|
||||
|
||||
if (objective_type_multiclass) {
|
||||
if (num_class_check <= 1) {
|
||||
Log::Fatal("Number of classes should be specified and greater than 1 for multiclass training");
|
||||
}
|
||||
} else {
|
||||
if (task == TaskType::kTrain && num_class_check != 1) {
|
||||
Log::Fatal("Number of classes must be 1 for non-multiclass training");
|
||||
}
|
||||
}
|
||||
for (std::string metric_type : metric) {
|
||||
bool metric_type_multiclass = (CheckMultiClassObjective(metric_type)
|
||||
|| metric_type == std::string("multi_logloss")
|
||||
|| metric_type == std::string("multi_error")
|
||||
|| metric_type == std::string("auc_mu")
|
||||
|| (metric_type == std::string("custom") && num_class_check > 1));
|
||||
if ((objective_type_multiclass && !metric_type_multiclass)
|
||||
|| (!objective_type_multiclass && metric_type_multiclass)) {
|
||||
Log::Fatal("Multiclass objective and metrics don't match");
|
||||
}
|
||||
}
|
||||
|
||||
if (num_machines > 1) {
|
||||
is_parallel = true;
|
||||
} else {
|
||||
is_parallel = false;
|
||||
tree_learner = "serial";
|
||||
}
|
||||
|
||||
bool is_single_tree_learner = tree_learner == std::string("serial");
|
||||
|
||||
if (is_single_tree_learner) {
|
||||
is_parallel = false;
|
||||
num_machines = 1;
|
||||
}
|
||||
|
||||
if (is_single_tree_learner || tree_learner == std::string("feature")) {
|
||||
is_data_based_parallel = false;
|
||||
} else if (tree_learner == std::string("data")
|
||||
|| tree_learner == std::string("voting")) {
|
||||
is_data_based_parallel = true;
|
||||
if (histogram_pool_size >= 0
|
||||
&& tree_learner == std::string("data")) {
|
||||
Log::Warning("Histogram LRU queue was enabled (histogram_pool_size=%f).\n"
|
||||
"Will disable this to reduce communication costs",
|
||||
histogram_pool_size);
|
||||
// Change pool size to -1 (no limit) when using data parallel to reduce communication costs
|
||||
histogram_pool_size = -1;
|
||||
}
|
||||
}
|
||||
if (is_data_based_parallel) {
|
||||
if (!forcedsplits_filename.empty()) {
|
||||
Log::Fatal("Don't support forcedsplits in %s tree learner",
|
||||
tree_learner.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// max_depth defaults to -1, so max_depth>0 implies "you explicitly overrode the default"
|
||||
//
|
||||
// Changing max_depth while leaving num_leaves at its default (31) can lead to 2 undesirable situations:
|
||||
//
|
||||
// * (0 <= max_depth <= 4) it's not possible to produce a tree with 31 leaves
|
||||
// - this block reduces num_leaves to 2^max_depth
|
||||
// * (max_depth > 4) 31 leaves is less than a full depth-wise tree, which might lead to underfitting
|
||||
// - this block warns about that
|
||||
// ref: https://github.com/lightgbm-org/LightGBM/issues/2898#issuecomment-1002860601
|
||||
if (max_depth > 0 && (params.count("num_leaves") == 0 || params.at("num_leaves").empty())) {
|
||||
double full_num_leaves = std::pow(2, max_depth);
|
||||
if (full_num_leaves > num_leaves) {
|
||||
Log::Warning("Provided parameters constrain tree depth (max_depth=%d) without explicitly setting 'num_leaves'. "
|
||||
"This can lead to underfitting. To resolve this warning, pass 'num_leaves' (<=%.0f) in params. "
|
||||
"Alternatively, pass (max_depth=-1) and just use 'num_leaves' to constrain model complexity.",
|
||||
max_depth,
|
||||
full_num_leaves);
|
||||
}
|
||||
|
||||
if (full_num_leaves < num_leaves) {
|
||||
// Fits in an int, and is more restrictive than the current num_leaves
|
||||
num_leaves = static_cast<int>(full_num_leaves);
|
||||
}
|
||||
}
|
||||
if (device_type == std::string("gpu")) {
|
||||
// force col-wise for gpu version
|
||||
force_col_wise = true;
|
||||
force_row_wise = false;
|
||||
if (deterministic) {
|
||||
Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic.");
|
||||
}
|
||||
if (use_quantized_grad) {
|
||||
Log::Warning("Quantized training is not supported by GPU tree learner. Switch to full precision training.");
|
||||
use_quantized_grad = false;
|
||||
}
|
||||
} else if (device_type == std::string("cuda")) {
|
||||
// force row-wise for cuda version
|
||||
force_col_wise = false;
|
||||
force_row_wise = true;
|
||||
if (deterministic) {
|
||||
Log::Warning("Although \"deterministic\" is set, the results ran by GPU may be non-deterministic.");
|
||||
}
|
||||
}
|
||||
// linear tree learner must be serial type and run on CPU device
|
||||
if (linear_tree) {
|
||||
if (device_type != std::string("cpu") && device_type != std::string("gpu")) {
|
||||
device_type = "cpu";
|
||||
Log::Warning("Linear tree learner only works with CPU and GPU. Falling back to CPU now.");
|
||||
}
|
||||
if (tree_learner != std::string("serial")) {
|
||||
tree_learner = "serial";
|
||||
Log::Warning("Linear tree learner must be serial.");
|
||||
}
|
||||
if (zero_as_missing) {
|
||||
Log::Fatal("zero_as_missing must be false when fitting linear trees.");
|
||||
}
|
||||
if (objective == std::string("regression_l1")) {
|
||||
Log::Fatal("Cannot use regression_l1 objective when fitting linear trees.");
|
||||
}
|
||||
}
|
||||
// min_data_in_leaf must be at least 2 if path smoothing is active. This is because when the split is calculated
|
||||
// the count is calculated using the proportion of hessian in the leaf which is rounded up to nearest int, so it can
|
||||
// be 1 when there is actually no data in the leaf. In rare cases this can cause a bug because with path smoothing the
|
||||
// calculated split gain can be positive even with zero gradient and hessian.
|
||||
if (path_smooth > kEpsilon && min_data_in_leaf < 2) {
|
||||
min_data_in_leaf = 2;
|
||||
Log::Warning("min_data_in_leaf has been increased to 2 because this is required when path smoothing is active.");
|
||||
}
|
||||
if (is_parallel && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
|
||||
// In distributed mode, local node doesn't have histograms on all features, cannot perform "intermediate" monotone constraints.
|
||||
Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints in distributed learning, auto set to \"basic\" method.");
|
||||
monotone_constraints_method = "basic";
|
||||
}
|
||||
if (feature_fraction_bynode != 1.0 && (monotone_constraints_method == std::string("intermediate") || monotone_constraints_method == std::string("advanced"))) {
|
||||
// "intermediate" monotone constraints need to recompute splits. If the features are sampled when computing the
|
||||
// split initially, then the sampling needs to be recorded or done once again, which is currently not supported
|
||||
Log::Warning("Cannot use \"intermediate\" or \"advanced\" monotone constraints with feature fraction different from 1, auto set monotone constraints to \"basic\" method.");
|
||||
monotone_constraints_method = "basic";
|
||||
}
|
||||
if (max_depth > 0 && monotone_penalty >= max_depth) {
|
||||
Log::Warning("Monotone penalty greater than tree depth. Monotone features won't be used.");
|
||||
}
|
||||
if (min_data_in_leaf <= 0 && min_sum_hessian_in_leaf <= kEpsilon) {
|
||||
Log::Warning(
|
||||
"Cannot set both min_data_in_leaf and min_sum_hessian_in_leaf to 0. "
|
||||
"Will set min_data_in_leaf to 1.");
|
||||
min_data_in_leaf = 1;
|
||||
}
|
||||
if (boosting == std::string("goss")) {
|
||||
boosting = std::string("gbdt");
|
||||
data_sample_strategy = std::string("goss");
|
||||
Log::Warning("Found boosting=goss. For backwards compatibility reasons, LightGBM interprets this as boosting=gbdt, data_sample_strategy=goss."
|
||||
"To suppress this warning, set data_sample_strategy=goss instead.");
|
||||
}
|
||||
|
||||
if (bagging_by_query && data_sample_strategy != std::string("bagging")) {
|
||||
Log::Warning("bagging_by_query=true is only compatible with data_sample_strategy=bagging. Setting bagging_by_query=false.");
|
||||
bagging_by_query = false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Config::ToString() const {
|
||||
std::stringstream str_buf;
|
||||
str_buf << "[boosting: " << boosting << "]\n";
|
||||
str_buf << "[objective: " << objective << "]\n";
|
||||
str_buf << "[metric: " << Common::Join(metric, ",") << "]\n";
|
||||
str_buf << "[tree_learner: " << tree_learner << "]\n";
|
||||
str_buf << "[device_type: " << device_type << "]\n";
|
||||
str_buf << SaveMembersToString();
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
const std::string Config::DumpAliases() {
|
||||
auto map = Config::parameter2aliases();
|
||||
for (auto& pair : map) {
|
||||
std::sort(pair.second.begin(), pair.second.end(), SortAlias);
|
||||
}
|
||||
std::stringstream str_buf;
|
||||
str_buf << "{\n";
|
||||
bool first = true;
|
||||
for (const auto& pair : map) {
|
||||
if (first) {
|
||||
str_buf << " \"";
|
||||
first = false;
|
||||
} else {
|
||||
str_buf << " , \"";
|
||||
}
|
||||
str_buf << pair.first << "\": [";
|
||||
if (pair.second.size() > 0) {
|
||||
str_buf << "\"" << CommonC::Join(pair.second, "\", \"") << "\"";
|
||||
}
|
||||
str_buf << "]\n";
|
||||
}
|
||||
str_buf << "}\n";
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,227 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_column_data.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDAColumnData::CUDAColumnData(const data_size_t num_data, const int gpu_device_id) {
|
||||
num_threads_ = OMP_NUM_THREADS();
|
||||
num_data_ = num_data;
|
||||
gpu_device_id_ = gpu_device_id >= 0 ? gpu_device_id : 0;
|
||||
SetCUDADevice(gpu_device_id_, __FILE__, __LINE__);
|
||||
data_by_column_.clear();
|
||||
}
|
||||
|
||||
CUDAColumnData::~CUDAColumnData() {}
|
||||
|
||||
template <bool IS_SPARSE, bool IS_4BIT, typename BIN_TYPE>
|
||||
void CUDAColumnData::InitOneColumnData(const void* in_column_data, BinIterator* bin_iterator, CUDAVector<uint8_t>* out_column_data_pointer) {
|
||||
CUDAVector<BIN_TYPE> cuda_column_data;
|
||||
if (!IS_SPARSE) {
|
||||
if (IS_4BIT) {
|
||||
std::vector<BIN_TYPE> expanded_column_data(num_data_, 0);
|
||||
const BIN_TYPE* in_column_data_reintrepreted = reinterpret_cast<const BIN_TYPE*>(in_column_data);
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
expanded_column_data[i] = static_cast<BIN_TYPE>((in_column_data_reintrepreted[i >> 1] >> ((i & 1) << 2)) & 0xf);
|
||||
}
|
||||
cuda_column_data.InitFromHostVector(expanded_column_data);
|
||||
} else {
|
||||
cuda_column_data.InitFromHostMemory(reinterpret_cast<const BIN_TYPE*>(in_column_data), static_cast<size_t>(num_data_));
|
||||
}
|
||||
} else {
|
||||
// need to iterate bin iterator
|
||||
std::vector<BIN_TYPE> expanded_column_data(num_data_, 0);
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
expanded_column_data[i] = static_cast<BIN_TYPE>(bin_iterator->RawGet(i));
|
||||
}
|
||||
cuda_column_data.InitFromHostVector(expanded_column_data);
|
||||
}
|
||||
out_column_data_pointer->MoveFrom(cuda_column_data, sizeof(BIN_TYPE) * cuda_column_data.Size());
|
||||
}
|
||||
|
||||
void CUDAColumnData::Init(const int num_columns,
|
||||
const std::vector<const void*>& column_data,
|
||||
const std::vector<BinIterator*>& column_bin_iterator,
|
||||
const std::vector<uint8_t>& column_bit_type,
|
||||
const std::vector<uint32_t>& feature_max_bin,
|
||||
const std::vector<uint32_t>& feature_min_bin,
|
||||
const std::vector<uint32_t>& feature_offset,
|
||||
const std::vector<uint32_t>& feature_most_freq_bin,
|
||||
const std::vector<uint32_t>& feature_default_bin,
|
||||
const std::vector<uint8_t>& feature_missing_is_zero,
|
||||
const std::vector<uint8_t>& feature_missing_is_na,
|
||||
const std::vector<uint8_t>& feature_mfb_is_zero,
|
||||
const std::vector<uint8_t>& feature_mfb_is_na,
|
||||
const std::vector<int>& feature_to_column) {
|
||||
num_columns_ = num_columns;
|
||||
column_bit_type_ = column_bit_type;
|
||||
feature_max_bin_ = feature_max_bin;
|
||||
feature_min_bin_ = feature_min_bin;
|
||||
feature_offset_ = feature_offset;
|
||||
feature_most_freq_bin_ = feature_most_freq_bin;
|
||||
feature_default_bin_ = feature_default_bin;
|
||||
feature_missing_is_zero_ = feature_missing_is_zero;
|
||||
feature_missing_is_na_ = feature_missing_is_na;
|
||||
feature_mfb_is_zero_ = feature_mfb_is_zero;
|
||||
feature_mfb_is_na_ = feature_mfb_is_na;
|
||||
for (int column_index = 0; column_index < num_columns_; ++column_index) {
|
||||
data_by_column_.emplace_back(new CUDAVector<uint8_t>());
|
||||
}
|
||||
OMP_INIT_EX();
|
||||
#pragma omp parallel num_threads(num_threads_)
|
||||
{
|
||||
SetCUDADevice(gpu_device_id_, __FILE__, __LINE__);
|
||||
#pragma omp for schedule(static)
|
||||
for (int column_index = 0; column_index < num_columns_; ++column_index) {
|
||||
OMP_LOOP_EX_BEGIN();
|
||||
const int8_t bit_type = column_bit_type[column_index];
|
||||
if (column_data[column_index] != nullptr) {
|
||||
// is dense column
|
||||
if (bit_type == 4) {
|
||||
column_bit_type_[column_index] = 8;
|
||||
InitOneColumnData<false, true, uint8_t>(column_data[column_index], nullptr, data_by_column_[column_index].get());
|
||||
} else if (bit_type == 8) {
|
||||
InitOneColumnData<false, false, uint8_t>(column_data[column_index], nullptr, data_by_column_[column_index].get());
|
||||
} else if (bit_type == 16) {
|
||||
InitOneColumnData<false, false, uint16_t>(column_data[column_index], nullptr, data_by_column_[column_index].get());
|
||||
} else if (bit_type == 32) {
|
||||
InitOneColumnData<false, false, uint32_t>(column_data[column_index], nullptr, data_by_column_[column_index].get());
|
||||
} else {
|
||||
Log::Fatal("Unknown column bit type %d", bit_type);
|
||||
}
|
||||
} else {
|
||||
// is sparse column
|
||||
if (bit_type == 8) {
|
||||
InitOneColumnData<true, false, uint8_t>(nullptr, column_bin_iterator[column_index], data_by_column_[column_index].get());
|
||||
} else if (bit_type == 16) {
|
||||
InitOneColumnData<true, false, uint16_t>(nullptr, column_bin_iterator[column_index], data_by_column_[column_index].get());
|
||||
} else if (bit_type == 32) {
|
||||
InitOneColumnData<true, false, uint32_t>(nullptr, column_bin_iterator[column_index], data_by_column_[column_index].get());
|
||||
} else {
|
||||
Log::Fatal("Unknown column bit type %d", bit_type);
|
||||
}
|
||||
}
|
||||
OMP_LOOP_EX_END();
|
||||
}
|
||||
}
|
||||
OMP_THROW_EX();
|
||||
feature_to_column_ = feature_to_column;
|
||||
cuda_data_by_column_.InitFromHostVector(GetDataByColumnPointers(data_by_column_));
|
||||
InitColumnMetaInfo();
|
||||
}
|
||||
|
||||
void CUDAColumnData::CopySubrow(
|
||||
const CUDAColumnData* full_set,
|
||||
const data_size_t* used_indices,
|
||||
const data_size_t num_used_indices) {
|
||||
num_threads_ = full_set->num_threads_;
|
||||
num_columns_ = full_set->num_columns_;
|
||||
column_bit_type_ = full_set->column_bit_type_;
|
||||
feature_min_bin_ = full_set->feature_min_bin_;
|
||||
feature_max_bin_ = full_set->feature_max_bin_;
|
||||
feature_offset_ = full_set->feature_offset_;
|
||||
feature_most_freq_bin_ = full_set->feature_most_freq_bin_;
|
||||
feature_default_bin_ = full_set->feature_default_bin_;
|
||||
feature_missing_is_zero_ = full_set->feature_missing_is_zero_;
|
||||
feature_missing_is_na_ = full_set->feature_missing_is_na_;
|
||||
feature_mfb_is_zero_ = full_set->feature_mfb_is_zero_;
|
||||
feature_mfb_is_na_ = full_set->feature_mfb_is_na_;
|
||||
feature_to_column_ = full_set->feature_to_column_;
|
||||
if (cuda_used_indices_.Size() == 0) {
|
||||
// initialize the subset cuda column data
|
||||
const size_t num_used_indices_size = static_cast<size_t>(num_used_indices);
|
||||
cuda_used_indices_.Resize(num_used_indices_size);
|
||||
for (int column_index = 0; column_index < num_columns_; ++column_index) {
|
||||
data_by_column_.emplace_back(new CUDAVector<uint8_t>());
|
||||
}
|
||||
OMP_INIT_EX();
|
||||
#pragma omp parallel num_threads(num_threads_)
|
||||
{
|
||||
SetCUDADevice(gpu_device_id_, __FILE__, __LINE__);
|
||||
#pragma omp for schedule(static)
|
||||
for (int column_index = 0; column_index < num_columns_; ++column_index) {
|
||||
OMP_LOOP_EX_BEGIN();
|
||||
const uint8_t bit_type = column_bit_type_[column_index];
|
||||
if (bit_type == 8) {
|
||||
CUDAVector<uint8_t> column_data;
|
||||
column_data.Resize(num_used_indices_size);
|
||||
data_by_column_[column_index]->MoveFrom(column_data, sizeof(uint8_t) * column_data.Size());
|
||||
} else if (bit_type == 16) {
|
||||
CUDAVector<uint16_t> column_data;
|
||||
column_data.Resize(num_used_indices_size);
|
||||
data_by_column_[column_index]->MoveFrom(column_data, sizeof(uint16_t) * column_data.Size());
|
||||
} else if (bit_type == 32) {
|
||||
CUDAVector<uint32_t> column_data;
|
||||
column_data.Resize(num_used_indices_size);
|
||||
data_by_column_[column_index]->MoveFrom(column_data, sizeof(uint32_t) * column_data.Size());
|
||||
}
|
||||
OMP_LOOP_EX_END();
|
||||
}
|
||||
}
|
||||
OMP_THROW_EX();
|
||||
cuda_data_by_column_.InitFromHostVector(GetDataByColumnPointers(data_by_column_));
|
||||
InitColumnMetaInfo();
|
||||
cur_subset_buffer_size_ = num_used_indices;
|
||||
} else {
|
||||
if (num_used_indices > cur_subset_buffer_size_) {
|
||||
ResizeWhenCopySubrow(num_used_indices);
|
||||
cur_subset_buffer_size_ = num_used_indices;
|
||||
}
|
||||
}
|
||||
cuda_used_indices_.InitFromHostMemory(used_indices, static_cast<size_t>(num_used_indices));
|
||||
num_used_indices_ = num_used_indices;
|
||||
LaunchCopySubrowKernel(full_set->cuda_data_by_column());
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDAColumnData::ResizeWhenCopySubrow(const data_size_t num_used_indices) {
|
||||
const size_t num_used_indices_size = static_cast<size_t>(num_used_indices);
|
||||
cuda_used_indices_.Resize(num_used_indices_size);
|
||||
OMP_INIT_EX();
|
||||
#pragma omp parallel num_threads(num_threads_)
|
||||
{
|
||||
SetCUDADevice(gpu_device_id_, __FILE__, __LINE__);
|
||||
#pragma omp for schedule(static)
|
||||
for (int column_index = 0; column_index < num_columns_; ++column_index) {
|
||||
OMP_LOOP_EX_BEGIN();
|
||||
const uint8_t bit_type = column_bit_type_[column_index];
|
||||
if (bit_type == 8) {
|
||||
data_by_column_[column_index]->Resize(sizeof(uint8_t) * num_used_indices_size);
|
||||
} else if (bit_type == 16) {
|
||||
data_by_column_[column_index]->Resize(sizeof(uint16_t) * num_used_indices_size);
|
||||
} else if (bit_type == 32) {
|
||||
data_by_column_[column_index]->Resize(sizeof(uint32_t) * num_used_indices_size);
|
||||
}
|
||||
OMP_LOOP_EX_END();
|
||||
}
|
||||
}
|
||||
OMP_THROW_EX();
|
||||
cuda_data_by_column_.InitFromHostVector(GetDataByColumnPointers(data_by_column_));
|
||||
}
|
||||
|
||||
void CUDAColumnData::InitColumnMetaInfo() {
|
||||
cuda_column_bit_type_.InitFromHostVector(column_bit_type_);
|
||||
cuda_feature_max_bin_.InitFromHostVector(feature_max_bin_);
|
||||
cuda_feature_min_bin_.InitFromHostVector(feature_min_bin_);
|
||||
cuda_feature_offset_.InitFromHostVector(feature_offset_);
|
||||
cuda_feature_most_freq_bin_.InitFromHostVector(feature_most_freq_bin_);
|
||||
cuda_feature_default_bin_.InitFromHostVector(feature_default_bin_);
|
||||
cuda_feature_missing_is_zero_.InitFromHostVector(feature_missing_is_zero_);
|
||||
cuda_feature_missing_is_na_.InitFromHostVector(feature_missing_is_na_);
|
||||
cuda_feature_mfb_is_zero_.InitFromHostVector(feature_mfb_is_zero_);
|
||||
cuda_feature_mfb_is_na_.InitFromHostVector(feature_mfb_is_na_);
|
||||
cuda_feature_to_column_.InitFromHostVector(feature_to_column_);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,62 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_column_data.hpp>
|
||||
|
||||
#define COPY_SUBROW_BLOCK_SIZE_COLUMN_DATA (1024)
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
__global__ void CopySubrowKernel_ColumnData(
|
||||
uint8_t* const* in_cuda_data_by_column,
|
||||
const uint8_t* cuda_column_bit_type,
|
||||
const data_size_t* cuda_used_indices,
|
||||
const data_size_t num_used_indices,
|
||||
const int num_column,
|
||||
uint8_t** out_cuda_data_by_column) {
|
||||
const data_size_t local_data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (local_data_index < num_used_indices) {
|
||||
for (int column_index = 0; column_index < num_column; ++column_index) {
|
||||
const uint8_t* in_column_data = in_cuda_data_by_column[column_index];
|
||||
uint8_t* out_column_data = out_cuda_data_by_column[column_index];
|
||||
const uint8_t bit_type = cuda_column_bit_type[column_index];
|
||||
if (bit_type == 8) {
|
||||
const uint8_t* true_in_column_data = reinterpret_cast<const uint8_t*>(in_column_data);
|
||||
uint8_t* true_out_column_data = reinterpret_cast<uint8_t*>(out_column_data);
|
||||
const data_size_t global_data_index = cuda_used_indices[local_data_index];
|
||||
true_out_column_data[local_data_index] = true_in_column_data[global_data_index];
|
||||
} else if (bit_type == 16) {
|
||||
const uint16_t* true_in_column_data = reinterpret_cast<const uint16_t*>(in_column_data);
|
||||
uint16_t* true_out_column_data = reinterpret_cast<uint16_t*>(out_column_data);
|
||||
const data_size_t global_data_index = cuda_used_indices[local_data_index];
|
||||
true_out_column_data[local_data_index] = true_in_column_data[global_data_index];
|
||||
} else if (bit_type == 32) {
|
||||
const uint32_t* true_in_column_data = reinterpret_cast<const uint32_t*>(in_column_data);
|
||||
uint32_t* true_out_column_data = reinterpret_cast<uint32_t*>(out_column_data);
|
||||
const data_size_t global_data_index = cuda_used_indices[local_data_index];
|
||||
true_out_column_data[local_data_index] = true_in_column_data[global_data_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAColumnData::LaunchCopySubrowKernel(uint8_t* const* in_cuda_data_by_column) {
|
||||
const int num_blocks = (num_used_indices_ + COPY_SUBROW_BLOCK_SIZE_COLUMN_DATA - 1) / COPY_SUBROW_BLOCK_SIZE_COLUMN_DATA;
|
||||
CopySubrowKernel_ColumnData<<<num_blocks, COPY_SUBROW_BLOCK_SIZE_COLUMN_DATA>>>(
|
||||
in_cuda_data_by_column,
|
||||
cuda_column_bit_type_.RawData(),
|
||||
cuda_used_indices_.RawData(),
|
||||
num_used_indices_,
|
||||
num_columns_,
|
||||
cuda_data_by_column_.RawData());
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_metadata.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDAMetadata::CUDAMetadata(const int gpu_device_id) {
|
||||
if (gpu_device_id >= 0) {
|
||||
SetCUDADevice(gpu_device_id, __FILE__, __LINE__);
|
||||
} else {
|
||||
SetCUDADevice(0, __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
CUDAMetadata::~CUDAMetadata() {}
|
||||
|
||||
void CUDAMetadata::Init(const std::vector<label_t>& label,
|
||||
const std::vector<label_t>& weight,
|
||||
const std::vector<data_size_t>& query_boundaries,
|
||||
const std::vector<label_t>& query_weights,
|
||||
const std::vector<double>& init_score) {
|
||||
if (label.size() == 0) {
|
||||
cuda_label_.Clear();
|
||||
} else {
|
||||
cuda_label_.InitFromHostVector(label);
|
||||
}
|
||||
if (weight.size() == 0) {
|
||||
cuda_weights_.Clear();
|
||||
} else {
|
||||
cuda_weights_.InitFromHostVector(weight);
|
||||
}
|
||||
if (query_boundaries.size() == 0) {
|
||||
cuda_query_boundaries_.Clear();
|
||||
} else {
|
||||
cuda_query_boundaries_.InitFromHostVector(query_boundaries);
|
||||
}
|
||||
if (query_weights.size() == 0) {
|
||||
cuda_query_weights_.Clear();
|
||||
} else {
|
||||
cuda_query_weights_.InitFromHostVector(query_weights);
|
||||
}
|
||||
if (init_score.size() == 0) {
|
||||
cuda_init_score_.Clear();
|
||||
} else {
|
||||
cuda_init_score_.InitFromHostVector(init_score);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDAMetadata::SetLabel(const label_t* label, data_size_t len) {
|
||||
cuda_label_.InitFromHostMemory(label, static_cast<size_t>(len));
|
||||
}
|
||||
|
||||
void CUDAMetadata::SetWeights(const label_t* weights, data_size_t len) {
|
||||
cuda_weights_.InitFromHostMemory(weights, static_cast<size_t>(len));
|
||||
}
|
||||
|
||||
void CUDAMetadata::SetQuery(const data_size_t* query_boundaries, const label_t* query_weights, data_size_t num_queries) {
|
||||
cuda_query_boundaries_.InitFromHostMemory(query_boundaries, static_cast<size_t>(num_queries) + 1);
|
||||
if (query_weights != nullptr) {
|
||||
cuda_query_weights_.InitFromHostMemory(query_weights, static_cast<size_t>(num_queries));
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAMetadata::SetInitScore(const double* init_score, data_size_t len) {
|
||||
cuda_init_score_.InitFromHostMemory(init_score, len);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,438 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_row_data.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDARowData::CUDARowData(const Dataset* train_data,
|
||||
const TrainingShareStates* train_share_state,
|
||||
const int gpu_device_id,
|
||||
const bool gpu_use_dp):
|
||||
gpu_device_id_(gpu_device_id),
|
||||
gpu_use_dp_(gpu_use_dp) {
|
||||
num_threads_ = OMP_NUM_THREADS();
|
||||
num_data_ = train_data->num_data();
|
||||
const auto& feature_hist_offsets = train_share_state->feature_hist_offsets();
|
||||
if (gpu_use_dp_) {
|
||||
shared_hist_size_ = DP_SHARED_HIST_SIZE;
|
||||
} else {
|
||||
shared_hist_size_ = SP_SHARED_HIST_SIZE;
|
||||
}
|
||||
if (feature_hist_offsets.empty()) {
|
||||
num_total_bin_ = 0;
|
||||
} else {
|
||||
num_total_bin_ = static_cast<int>(feature_hist_offsets.back());
|
||||
}
|
||||
num_feature_group_ = train_data->num_feature_groups();
|
||||
num_feature_ = train_data->num_features();
|
||||
if (gpu_device_id >= 0) {
|
||||
SetCUDADevice(gpu_device_id, __FILE__, __LINE__);
|
||||
} else {
|
||||
SetCUDADevice(0, __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
CUDARowData::~CUDARowData() {}
|
||||
|
||||
void CUDARowData::Init(const Dataset* train_data, TrainingShareStates* train_share_state) {
|
||||
if (num_feature_ == 0) {
|
||||
return;
|
||||
}
|
||||
DivideCUDAFeatureGroups(train_data, train_share_state);
|
||||
bit_type_ = 0;
|
||||
size_t total_size = 0;
|
||||
const void* host_row_ptr = nullptr;
|
||||
row_ptr_bit_type_ = 0;
|
||||
const void* host_data = train_share_state->GetRowWiseData(&bit_type_, &total_size, &is_sparse_, &host_row_ptr, &row_ptr_bit_type_);
|
||||
if (bit_type_ == 8) {
|
||||
if (!is_sparse_) {
|
||||
std::vector<uint8_t> partitioned_data;
|
||||
GetDenseDataPartitioned<uint8_t>(reinterpret_cast<const uint8_t*>(host_data), &partitioned_data);
|
||||
cuda_data_uint8_t_.InitFromHostVector(partitioned_data);
|
||||
} else {
|
||||
if (row_ptr_bit_type_ == 16) {
|
||||
InitSparseData<uint8_t, uint16_t>(
|
||||
reinterpret_cast<const uint8_t*>(host_data),
|
||||
reinterpret_cast<const uint16_t*>(host_row_ptr),
|
||||
&cuda_data_uint8_t_,
|
||||
&cuda_row_ptr_uint16_t_,
|
||||
&cuda_partition_ptr_uint16_t_);
|
||||
} else if (row_ptr_bit_type_ == 32) {
|
||||
InitSparseData<uint8_t, uint32_t>(
|
||||
reinterpret_cast<const uint8_t*>(host_data),
|
||||
reinterpret_cast<const uint32_t*>(host_row_ptr),
|
||||
&cuda_data_uint8_t_,
|
||||
&cuda_row_ptr_uint32_t_,
|
||||
&cuda_partition_ptr_uint32_t_);
|
||||
} else if (row_ptr_bit_type_ == 64) {
|
||||
InitSparseData<uint8_t, uint64_t>(
|
||||
reinterpret_cast<const uint8_t*>(host_data),
|
||||
reinterpret_cast<const uint64_t*>(host_row_ptr),
|
||||
&cuda_data_uint8_t_,
|
||||
&cuda_row_ptr_uint64_t_,
|
||||
&cuda_partition_ptr_uint64_t_);
|
||||
} else {
|
||||
Log::Fatal("Unknown data ptr bit type %d", row_ptr_bit_type_);
|
||||
}
|
||||
}
|
||||
} else if (bit_type_ == 16) {
|
||||
if (!is_sparse_) {
|
||||
std::vector<uint16_t> partitioned_data;
|
||||
GetDenseDataPartitioned<uint16_t>(reinterpret_cast<const uint16_t*>(host_data), &partitioned_data);
|
||||
cuda_data_uint16_t_.InitFromHostVector(partitioned_data);
|
||||
} else {
|
||||
if (row_ptr_bit_type_ == 16) {
|
||||
InitSparseData<uint16_t, uint16_t>(
|
||||
reinterpret_cast<const uint16_t*>(host_data),
|
||||
reinterpret_cast<const uint16_t*>(host_row_ptr),
|
||||
&cuda_data_uint16_t_,
|
||||
&cuda_row_ptr_uint16_t_,
|
||||
&cuda_partition_ptr_uint16_t_);
|
||||
} else if (row_ptr_bit_type_ == 32) {
|
||||
InitSparseData<uint16_t, uint32_t>(
|
||||
reinterpret_cast<const uint16_t*>(host_data),
|
||||
reinterpret_cast<const uint32_t*>(host_row_ptr),
|
||||
&cuda_data_uint16_t_,
|
||||
&cuda_row_ptr_uint32_t_,
|
||||
&cuda_partition_ptr_uint32_t_);
|
||||
} else if (row_ptr_bit_type_ == 64) {
|
||||
InitSparseData<uint16_t, uint64_t>(
|
||||
reinterpret_cast<const uint16_t*>(host_data),
|
||||
reinterpret_cast<const uint64_t*>(host_row_ptr),
|
||||
&cuda_data_uint16_t_,
|
||||
&cuda_row_ptr_uint64_t_,
|
||||
&cuda_partition_ptr_uint64_t_);
|
||||
} else {
|
||||
Log::Fatal("Unknown data ptr bit type %d", row_ptr_bit_type_);
|
||||
}
|
||||
}
|
||||
} else if (bit_type_ == 32) {
|
||||
if (!is_sparse_) {
|
||||
std::vector<uint32_t> partitioned_data;
|
||||
GetDenseDataPartitioned<uint32_t>(reinterpret_cast<const uint32_t*>(host_data), &partitioned_data);
|
||||
cuda_data_uint32_t_.InitFromHostVector(partitioned_data);
|
||||
} else {
|
||||
if (row_ptr_bit_type_ == 16) {
|
||||
InitSparseData<uint32_t, uint16_t>(
|
||||
reinterpret_cast<const uint32_t*>(host_data),
|
||||
reinterpret_cast<const uint16_t*>(host_row_ptr),
|
||||
&cuda_data_uint32_t_,
|
||||
&cuda_row_ptr_uint16_t_,
|
||||
&cuda_partition_ptr_uint16_t_);
|
||||
} else if (row_ptr_bit_type_ == 32) {
|
||||
InitSparseData<uint32_t, uint32_t>(
|
||||
reinterpret_cast<const uint32_t*>(host_data),
|
||||
reinterpret_cast<const uint32_t*>(host_row_ptr),
|
||||
&cuda_data_uint32_t_,
|
||||
&cuda_row_ptr_uint32_t_,
|
||||
&cuda_partition_ptr_uint32_t_);
|
||||
} else if (row_ptr_bit_type_ == 64) {
|
||||
InitSparseData<uint32_t, uint64_t>(
|
||||
reinterpret_cast<const uint32_t*>(host_data),
|
||||
reinterpret_cast<const uint64_t*>(host_row_ptr),
|
||||
&cuda_data_uint32_t_,
|
||||
&cuda_row_ptr_uint64_t_,
|
||||
&cuda_partition_ptr_uint64_t_);
|
||||
} else {
|
||||
Log::Fatal("Unknown data ptr bit type %d", row_ptr_bit_type_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log::Fatal("Unknown bit type = %d", bit_type_);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDARowData::DivideCUDAFeatureGroups(const Dataset* train_data, TrainingShareStates* share_state) {
|
||||
const uint32_t max_num_bin_per_partition = shared_hist_size_ / 2;
|
||||
const std::vector<uint32_t>& column_hist_offsets = share_state->column_hist_offsets();
|
||||
std::vector<int> feature_group_num_feature_offsets;
|
||||
int offsets = 0;
|
||||
int prev_group_index = -1;
|
||||
for (int feature_index = 0; feature_index < num_feature_; ++feature_index) {
|
||||
const int feature_group_index = train_data->Feature2Group(feature_index);
|
||||
if (prev_group_index == -1 || feature_group_index != prev_group_index) {
|
||||
feature_group_num_feature_offsets.emplace_back(offsets);
|
||||
prev_group_index = feature_group_index;
|
||||
}
|
||||
++offsets;
|
||||
}
|
||||
CHECK_EQ(offsets, num_feature_);
|
||||
feature_group_num_feature_offsets.emplace_back(offsets);
|
||||
|
||||
uint32_t start_hist_offset = 0;
|
||||
feature_partition_column_index_offsets_.clear();
|
||||
column_hist_offsets_.clear();
|
||||
partition_hist_offsets_.clear();
|
||||
feature_partition_column_index_offsets_.emplace_back(0);
|
||||
partition_hist_offsets_.emplace_back(0);
|
||||
const int num_feature_groups = train_data->num_feature_groups();
|
||||
int column_index = 0;
|
||||
num_feature_partitions_ = 0;
|
||||
large_bin_partitions_.clear();
|
||||
small_bin_partitions_.clear();
|
||||
for (int feature_group_index = 0; feature_group_index < num_feature_groups; ++feature_group_index) {
|
||||
if (!train_data->IsMultiGroup(feature_group_index)) {
|
||||
const uint32_t column_feature_hist_start = column_hist_offsets[column_index];
|
||||
const uint32_t column_feature_hist_end = column_hist_offsets[column_index + 1];
|
||||
const uint32_t num_bin_in_dense_group = column_feature_hist_end - column_feature_hist_start;
|
||||
|
||||
// if one column has too many bins, use a separate partition for that column
|
||||
if (num_bin_in_dense_group > max_num_bin_per_partition) {
|
||||
feature_partition_column_index_offsets_.emplace_back(column_index + 1);
|
||||
start_hist_offset = column_feature_hist_end;
|
||||
partition_hist_offsets_.emplace_back(start_hist_offset);
|
||||
large_bin_partitions_.emplace_back(num_feature_partitions_);
|
||||
++num_feature_partitions_;
|
||||
column_hist_offsets_.emplace_back(0);
|
||||
++column_index;
|
||||
continue;
|
||||
}
|
||||
|
||||
// try if adding this column exceed the maximum number per partition
|
||||
const uint32_t cur_hist_num_bin = column_feature_hist_end - start_hist_offset;
|
||||
if (cur_hist_num_bin > max_num_bin_per_partition) {
|
||||
feature_partition_column_index_offsets_.emplace_back(column_index);
|
||||
start_hist_offset = column_feature_hist_start;
|
||||
partition_hist_offsets_.emplace_back(start_hist_offset);
|
||||
small_bin_partitions_.emplace_back(num_feature_partitions_);
|
||||
++num_feature_partitions_;
|
||||
}
|
||||
column_hist_offsets_.emplace_back(column_hist_offsets[column_index] - start_hist_offset);
|
||||
if (feature_group_index == num_feature_groups - 1) {
|
||||
feature_partition_column_index_offsets_.emplace_back(column_index + 1);
|
||||
partition_hist_offsets_.emplace_back(column_hist_offsets.back());
|
||||
small_bin_partitions_.emplace_back(num_feature_partitions_);
|
||||
++num_feature_partitions_;
|
||||
}
|
||||
++column_index;
|
||||
} else {
|
||||
const int group_feature_index_start = feature_group_num_feature_offsets[feature_group_index];
|
||||
const int num_feature_in_group = feature_group_num_feature_offsets[feature_group_index + 1] - group_feature_index_start;
|
||||
for (int sub_feature_index = 0; sub_feature_index < num_feature_in_group; ++sub_feature_index) {
|
||||
const int feature_index = group_feature_index_start + sub_feature_index;
|
||||
const uint32_t column_feature_hist_start = column_hist_offsets[column_index];
|
||||
const uint32_t column_feature_hist_end = column_hist_offsets[column_index + 1];
|
||||
const uint32_t num_bin_in_dense_group = column_feature_hist_end - column_feature_hist_start;
|
||||
|
||||
// if one column has too many bins, use a separate partition for that column
|
||||
if (num_bin_in_dense_group > max_num_bin_per_partition) {
|
||||
feature_partition_column_index_offsets_.emplace_back(column_index + 1);
|
||||
start_hist_offset = column_feature_hist_end;
|
||||
partition_hist_offsets_.emplace_back(start_hist_offset);
|
||||
large_bin_partitions_.emplace_back(num_feature_partitions_);
|
||||
++num_feature_partitions_;
|
||||
column_hist_offsets_.emplace_back(0);
|
||||
++column_index;
|
||||
continue;
|
||||
}
|
||||
|
||||
// try if adding this column exceed the maximum number per partition
|
||||
const uint32_t cur_hist_num_bin = column_feature_hist_end - start_hist_offset;
|
||||
if (cur_hist_num_bin > max_num_bin_per_partition) {
|
||||
feature_partition_column_index_offsets_.emplace_back(column_index);
|
||||
start_hist_offset = column_feature_hist_start;
|
||||
partition_hist_offsets_.emplace_back(start_hist_offset);
|
||||
small_bin_partitions_.emplace_back(num_feature_partitions_);
|
||||
++num_feature_partitions_;
|
||||
}
|
||||
column_hist_offsets_.emplace_back(column_hist_offsets[column_index] - start_hist_offset);
|
||||
if (feature_group_index == num_feature_groups - 1 && sub_feature_index == num_feature_in_group - 1) {
|
||||
CHECK_EQ(feature_index, num_feature_ - 1);
|
||||
feature_partition_column_index_offsets_.emplace_back(column_index + 1);
|
||||
partition_hist_offsets_.emplace_back(column_hist_offsets.back());
|
||||
small_bin_partitions_.emplace_back(num_feature_partitions_);
|
||||
++num_feature_partitions_;
|
||||
}
|
||||
++column_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
column_hist_offsets_.emplace_back(column_hist_offsets.back() - start_hist_offset);
|
||||
max_num_column_per_partition_ = 0;
|
||||
for (size_t i = 0; i < feature_partition_column_index_offsets_.size() - 1; ++i) {
|
||||
const int num_column = feature_partition_column_index_offsets_[i + 1] - feature_partition_column_index_offsets_[i];
|
||||
if (num_column > max_num_column_per_partition_) {
|
||||
max_num_column_per_partition_ = num_column;
|
||||
}
|
||||
}
|
||||
|
||||
cuda_feature_partition_column_index_offsets_.InitFromHostVector(feature_partition_column_index_offsets_);
|
||||
cuda_column_hist_offsets_.InitFromHostVector(column_hist_offsets_);
|
||||
cuda_partition_hist_offsets_.InitFromHostVector(partition_hist_offsets_);
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE>
|
||||
void CUDARowData::GetDenseDataPartitioned(const BIN_TYPE* row_wise_data, std::vector<BIN_TYPE>* partitioned_data) {
|
||||
const int num_total_columns = feature_partition_column_index_offsets_.back();
|
||||
partitioned_data->resize(static_cast<size_t>(num_total_columns) * static_cast<size_t>(num_data_), 0);
|
||||
BIN_TYPE* out_data = partitioned_data->data();
|
||||
Threading::For<data_size_t>(0, num_data_, 512,
|
||||
[this, num_total_columns, row_wise_data, out_data] (int /*thread_index*/, data_size_t start, data_size_t end) {
|
||||
for (size_t i = 0; i < feature_partition_column_index_offsets_.size() - 1; ++i) {
|
||||
const int num_prev_columns = static_cast<int>(feature_partition_column_index_offsets_[i]);
|
||||
const size_t offset = static_cast<size_t>(num_data_) * static_cast<size_t>(num_prev_columns);
|
||||
const int partition_column_start = feature_partition_column_index_offsets_[i];
|
||||
const int partition_column_end = feature_partition_column_index_offsets_[i + 1];
|
||||
const int num_columns_in_cur_partition = partition_column_end - partition_column_start;
|
||||
for (data_size_t data_index = start; data_index < end; ++data_index) {
|
||||
const size_t data_offset = offset + static_cast<size_t>(data_index) * num_columns_in_cur_partition;
|
||||
const size_t read_data_offset = static_cast<size_t>(data_index) * num_total_columns;
|
||||
for (int column_index = 0; column_index < num_columns_in_cur_partition; ++column_index) {
|
||||
const size_t true_column_index = read_data_offset + column_index + partition_column_start;
|
||||
const BIN_TYPE bin = row_wise_data[true_column_index];
|
||||
out_data[data_offset + column_index] = bin;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, typename DATA_PTR_TYPE>
|
||||
void CUDARowData::GetSparseDataPartitioned(
|
||||
const BIN_TYPE* row_wise_data,
|
||||
const DATA_PTR_TYPE* row_ptr,
|
||||
std::vector<std::vector<BIN_TYPE>>* partitioned_data,
|
||||
std::vector<std::vector<DATA_PTR_TYPE>>* partitioned_row_ptr,
|
||||
std::vector<DATA_PTR_TYPE>* partition_ptr) {
|
||||
const int num_partitions = static_cast<int>(feature_partition_column_index_offsets_.size()) - 1;
|
||||
partitioned_data->resize(num_partitions);
|
||||
partitioned_row_ptr->resize(num_partitions);
|
||||
std::vector<int> thread_max_elements_per_row(num_threads_, 0);
|
||||
Threading::For<int>(0, num_partitions, 1,
|
||||
[partitioned_data, partitioned_row_ptr, row_ptr, row_wise_data, &thread_max_elements_per_row, this] (int thread_index, int start, int end) {
|
||||
for (int partition_index = start; partition_index < end; ++partition_index) {
|
||||
std::vector<BIN_TYPE>& data_for_this_partition = partitioned_data->at(partition_index);
|
||||
std::vector<DATA_PTR_TYPE>& row_ptr_for_this_partition = partitioned_row_ptr->at(partition_index);
|
||||
const int partition_hist_start = partition_hist_offsets_[partition_index];
|
||||
const int partition_hist_end = partition_hist_offsets_[partition_index + 1];
|
||||
DATA_PTR_TYPE offset = 0;
|
||||
row_ptr_for_this_partition.clear();
|
||||
data_for_this_partition.clear();
|
||||
row_ptr_for_this_partition.emplace_back(offset);
|
||||
for (data_size_t data_index = 0; data_index < num_data_; ++data_index) {
|
||||
const DATA_PTR_TYPE row_start = row_ptr[data_index];
|
||||
const DATA_PTR_TYPE row_end = row_ptr[data_index + 1];
|
||||
const BIN_TYPE* row_data_start = row_wise_data + row_start;
|
||||
const BIN_TYPE* row_data_end = row_wise_data + row_end;
|
||||
const size_t partition_start_in_row = std::lower_bound(row_data_start, row_data_end, partition_hist_start) - row_data_start;
|
||||
const size_t partition_end_in_row = std::lower_bound(row_data_start, row_data_end, partition_hist_end) - row_data_start;
|
||||
for (size_t pos = partition_start_in_row; pos < partition_end_in_row; ++pos) {
|
||||
const BIN_TYPE bin = row_data_start[pos];
|
||||
CHECK_GE(bin, static_cast<BIN_TYPE>(partition_hist_start));
|
||||
data_for_this_partition.emplace_back(bin - partition_hist_start);
|
||||
}
|
||||
CHECK_GE(partition_end_in_row, partition_start_in_row);
|
||||
const data_size_t num_elements_in_row = partition_end_in_row - partition_start_in_row;
|
||||
offset += static_cast<DATA_PTR_TYPE>(num_elements_in_row);
|
||||
row_ptr_for_this_partition.emplace_back(offset);
|
||||
if (num_elements_in_row > thread_max_elements_per_row[thread_index]) {
|
||||
thread_max_elements_per_row[thread_index] = num_elements_in_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
partition_ptr->clear();
|
||||
DATA_PTR_TYPE offset = 0;
|
||||
partition_ptr->emplace_back(offset);
|
||||
for (size_t i = 0; i < partitioned_row_ptr->size(); ++i) {
|
||||
offset += partitioned_row_ptr->at(i).back();
|
||||
partition_ptr->emplace_back(offset);
|
||||
}
|
||||
max_num_column_per_partition_ = 0;
|
||||
for (int thread_index = 0; thread_index < num_threads_; ++thread_index) {
|
||||
if (thread_max_elements_per_row[thread_index] > max_num_column_per_partition_) {
|
||||
max_num_column_per_partition_ = thread_max_elements_per_row[thread_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, typename ROW_PTR_TYPE>
|
||||
void CUDARowData::InitSparseData(const BIN_TYPE* host_data,
|
||||
const ROW_PTR_TYPE* host_row_ptr,
|
||||
CUDAVector<BIN_TYPE>* cuda_data,
|
||||
CUDAVector<ROW_PTR_TYPE>* cuda_row_ptr,
|
||||
CUDAVector<ROW_PTR_TYPE>* cuda_partition_ptr) {
|
||||
std::vector<std::vector<BIN_TYPE>> partitioned_data;
|
||||
std::vector<std::vector<ROW_PTR_TYPE>> partitioned_data_ptr;
|
||||
std::vector<ROW_PTR_TYPE> partition_ptr;
|
||||
GetSparseDataPartitioned<BIN_TYPE, ROW_PTR_TYPE>(host_data, host_row_ptr, &partitioned_data, &partitioned_data_ptr, &partition_ptr);
|
||||
cuda_partition_ptr->InitFromHostVector(partition_ptr);
|
||||
cuda_data->Resize(partition_ptr.back());
|
||||
cuda_row_ptr->Resize((num_data_ + 1) * partitioned_data_ptr.size());
|
||||
for (size_t i = 0; i < partitioned_data.size(); ++i) {
|
||||
const std::vector<ROW_PTR_TYPE>& data_ptr_for_this_partition = partitioned_data_ptr[i];
|
||||
const std::vector<BIN_TYPE>& data_for_this_partition = partitioned_data[i];
|
||||
CopyFromHostToCUDADevice<BIN_TYPE>(cuda_data->RawData() + partition_ptr[i], data_for_this_partition.data(), data_for_this_partition.size(), __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<ROW_PTR_TYPE>(cuda_row_ptr->RawData() + i * (num_data_ + 1), data_ptr_for_this_partition.data(), data_ptr_for_this_partition.size(), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE>
|
||||
const BIN_TYPE* CUDARowData::GetBin() const {
|
||||
if (bit_type_ == 8) {
|
||||
return reinterpret_cast<const BIN_TYPE*>(cuda_data_uint8_t_.RawData());
|
||||
} else if (bit_type_ == 16) {
|
||||
return reinterpret_cast<const BIN_TYPE*>(cuda_data_uint16_t_.RawData());
|
||||
} else if (bit_type_ == 32) {
|
||||
return reinterpret_cast<const BIN_TYPE*>(cuda_data_uint32_t_.RawData());
|
||||
} else {
|
||||
Log::Fatal("Unknown bit_type %d for GetBin.", bit_type_);
|
||||
}
|
||||
}
|
||||
|
||||
template const uint8_t* CUDARowData::GetBin<uint8_t>() const;
|
||||
|
||||
template const uint16_t* CUDARowData::GetBin<uint16_t>() const;
|
||||
|
||||
template const uint32_t* CUDARowData::GetBin<uint32_t>() const;
|
||||
|
||||
template <typename PTR_TYPE>
|
||||
const PTR_TYPE* CUDARowData::GetRowPtr() const {
|
||||
if (row_ptr_bit_type_ == 16) {
|
||||
return reinterpret_cast<const PTR_TYPE*>(cuda_row_ptr_uint16_t_.RawData());
|
||||
} else if (row_ptr_bit_type_ == 32) {
|
||||
return reinterpret_cast<const PTR_TYPE*>(cuda_row_ptr_uint32_t_.RawData());
|
||||
} else if (row_ptr_bit_type_ == 64) {
|
||||
return reinterpret_cast<const PTR_TYPE*>(cuda_row_ptr_uint64_t_.RawData());
|
||||
} else {
|
||||
Log::Fatal("Unknown row_ptr_bit_type = %d for GetRowPtr.", row_ptr_bit_type_);
|
||||
}
|
||||
}
|
||||
|
||||
template const uint16_t* CUDARowData::GetRowPtr<uint16_t>() const;
|
||||
|
||||
template const uint32_t* CUDARowData::GetRowPtr<uint32_t>() const;
|
||||
|
||||
template const uint64_t* CUDARowData::GetRowPtr<uint64_t>() const;
|
||||
|
||||
template <typename PTR_TYPE>
|
||||
const PTR_TYPE* CUDARowData::GetPartitionPtr() const {
|
||||
if (row_ptr_bit_type_ == 16) {
|
||||
return reinterpret_cast<const PTR_TYPE*>(cuda_partition_ptr_uint16_t_.RawData());
|
||||
} else if (row_ptr_bit_type_ == 32) {
|
||||
return reinterpret_cast<const PTR_TYPE*>(cuda_partition_ptr_uint32_t_.RawData());
|
||||
} else if (row_ptr_bit_type_ == 64) {
|
||||
return reinterpret_cast<const PTR_TYPE*>(cuda_partition_ptr_uint64_t_.RawData());
|
||||
} else {
|
||||
Log::Fatal("Unknown row_ptr_bit_type = %d for GetPartitionPtr.", row_ptr_bit_type_);
|
||||
}
|
||||
}
|
||||
|
||||
template const uint16_t* CUDARowData::GetPartitionPtr<uint16_t>() const;
|
||||
|
||||
template const uint32_t* CUDARowData::GetPartitionPtr<uint32_t>() const;
|
||||
|
||||
template const uint64_t* CUDARowData::GetPartitionPtr<uint64_t>() const;
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,214 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_tree.hpp>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDATree::CUDATree(int max_leaves, bool track_branch_features, bool is_linear,
|
||||
const int gpu_device_id, const bool has_categorical_feature):
|
||||
Tree(max_leaves, track_branch_features, is_linear),
|
||||
num_threads_per_block_add_prediction_to_score_(1024) {
|
||||
is_cuda_tree_ = true;
|
||||
if (gpu_device_id >= 0) {
|
||||
SetCUDADevice(gpu_device_id, __FILE__, __LINE__);
|
||||
} else {
|
||||
SetCUDADevice(0, __FILE__, __LINE__);
|
||||
}
|
||||
if (has_categorical_feature) {
|
||||
cuda_cat_boundaries_.Resize(max_leaves);
|
||||
cuda_cat_boundaries_inner_.Resize(max_leaves);
|
||||
}
|
||||
InitCUDAMemory();
|
||||
}
|
||||
|
||||
CUDATree::CUDATree(const Tree* host_tree):
|
||||
Tree(*host_tree),
|
||||
num_threads_per_block_add_prediction_to_score_(1024) {
|
||||
is_cuda_tree_ = true;
|
||||
InitCUDA();
|
||||
}
|
||||
|
||||
CUDATree::~CUDATree() {
|
||||
gpuAssert(cudaStreamDestroy(cuda_stream_), __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDATree::InitCUDAMemory() {
|
||||
cuda_left_child_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_right_child_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_split_feature_inner_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_split_feature_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_leaf_depth_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_leaf_parent_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_threshold_in_bin_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_threshold_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_decision_type_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_leaf_value_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_internal_weight_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_internal_value_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_leaf_weight_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_leaf_count_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_internal_count_.Resize(static_cast<size_t>(max_leaves_));
|
||||
cuda_split_gain_.Resize(static_cast<size_t>(max_leaves_));
|
||||
SetCUDAMemory<double>(cuda_leaf_value_.RawData(), 0.0f, 1, __FILE__, __LINE__);
|
||||
SetCUDAMemory<double>(cuda_leaf_weight_.RawData(), 0.0f, 1, __FILE__, __LINE__);
|
||||
SetCUDAMemory<int>(cuda_leaf_parent_.RawData(), -1, 1, __FILE__, __LINE__);
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_stream_));
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDATree::InitCUDA() {
|
||||
cuda_left_child_.InitFromHostVector(left_child_);
|
||||
cuda_right_child_.InitFromHostVector(right_child_);
|
||||
cuda_split_feature_inner_.InitFromHostVector(split_feature_inner_);
|
||||
cuda_split_feature_.InitFromHostVector(split_feature_);
|
||||
cuda_threshold_in_bin_.InitFromHostVector(threshold_in_bin_);
|
||||
cuda_threshold_.InitFromHostVector(threshold_);
|
||||
cuda_leaf_depth_.InitFromHostVector(leaf_depth_);
|
||||
cuda_decision_type_.InitFromHostVector(decision_type_);
|
||||
cuda_internal_weight_.InitFromHostVector(internal_weight_);
|
||||
cuda_internal_value_.InitFromHostVector(internal_value_);
|
||||
cuda_internal_count_.InitFromHostVector(internal_count_);
|
||||
cuda_leaf_count_.InitFromHostVector(leaf_count_);
|
||||
cuda_split_gain_.InitFromHostVector(split_gain_);
|
||||
cuda_leaf_value_.InitFromHostVector(leaf_value_);
|
||||
cuda_leaf_weight_.InitFromHostVector(leaf_weight_);
|
||||
cuda_leaf_parent_.InitFromHostVector(leaf_parent_);
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_stream_));
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
int CUDATree::Split(const int leaf_index,
|
||||
const int real_feature_index,
|
||||
const double real_threshold,
|
||||
const MissingType missing_type,
|
||||
const CUDASplitInfo* cuda_split_info) {
|
||||
LaunchSplitKernel(leaf_index, real_feature_index, real_threshold, missing_type, cuda_split_info);
|
||||
RecordBranchFeatures(leaf_index, num_leaves_, real_feature_index);
|
||||
++num_leaves_;
|
||||
return num_leaves_ - 1;
|
||||
}
|
||||
|
||||
int CUDATree::SplitCategorical(const int leaf_index,
|
||||
const int real_feature_index,
|
||||
const MissingType missing_type,
|
||||
const CUDASplitInfo* cuda_split_info,
|
||||
uint32_t* cuda_bitset,
|
||||
size_t cuda_bitset_len,
|
||||
uint32_t* cuda_bitset_inner,
|
||||
size_t cuda_bitset_inner_len) {
|
||||
LaunchSplitCategoricalKernel(leaf_index, real_feature_index,
|
||||
missing_type, cuda_split_info,
|
||||
cuda_bitset_len, cuda_bitset_inner_len);
|
||||
cuda_bitset_.PushBack(cuda_bitset, cuda_bitset_len);
|
||||
cuda_bitset_inner_.PushBack(cuda_bitset_inner, cuda_bitset_inner_len);
|
||||
++num_leaves_;
|
||||
++num_cat_;
|
||||
RecordBranchFeatures(leaf_index, num_leaves_, real_feature_index);
|
||||
return num_leaves_ - 1;
|
||||
}
|
||||
|
||||
void CUDATree::RecordBranchFeatures(const int left_leaf_index,
|
||||
const int right_leaf_index,
|
||||
const int real_feature_index) {
|
||||
if (track_branch_features_) {
|
||||
branch_features_[right_leaf_index] = branch_features_[left_leaf_index];
|
||||
branch_features_[right_leaf_index].push_back(real_feature_index);
|
||||
branch_features_[left_leaf_index].push_back(real_feature_index);
|
||||
}
|
||||
}
|
||||
|
||||
void CUDATree::AddPredictionToScore(const Dataset* data,
|
||||
data_size_t num_data,
|
||||
double* score) const {
|
||||
LaunchAddPredictionToScoreKernel(data, nullptr, num_data, score);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDATree::AddPredictionToScore(const Dataset* data,
|
||||
const data_size_t* used_data_indices,
|
||||
data_size_t num_data, double* score) const {
|
||||
LaunchAddPredictionToScoreKernel(data, used_data_indices, num_data, score);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
inline void CUDATree::Shrinkage(double rate) {
|
||||
Tree::Shrinkage(rate);
|
||||
LaunchShrinkageKernel(rate);
|
||||
}
|
||||
|
||||
inline void CUDATree::AddBias(double val) {
|
||||
Tree::AddBias(val);
|
||||
LaunchAddBiasKernel(val);
|
||||
}
|
||||
|
||||
void CUDATree::ToHost() {
|
||||
left_child_.resize(max_leaves_ - 1);
|
||||
right_child_.resize(max_leaves_ - 1);
|
||||
split_feature_inner_.resize(max_leaves_ - 1);
|
||||
split_feature_.resize(max_leaves_ - 1);
|
||||
threshold_in_bin_.resize(max_leaves_ - 1);
|
||||
threshold_.resize(max_leaves_ - 1);
|
||||
decision_type_.resize(max_leaves_ - 1, 0);
|
||||
split_gain_.resize(max_leaves_ - 1);
|
||||
leaf_parent_.resize(max_leaves_);
|
||||
leaf_value_.resize(max_leaves_);
|
||||
leaf_weight_.resize(max_leaves_);
|
||||
leaf_count_.resize(max_leaves_);
|
||||
internal_value_.resize(max_leaves_ - 1);
|
||||
internal_weight_.resize(max_leaves_ - 1);
|
||||
internal_count_.resize(max_leaves_ - 1);
|
||||
leaf_depth_.resize(max_leaves_);
|
||||
|
||||
const size_t num_leaves_size = static_cast<size_t>(num_leaves_);
|
||||
CopyFromCUDADeviceToHost<int>(left_child_.data(), cuda_left_child_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<int>(right_child_.data(), cuda_right_child_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<int>(split_feature_inner_.data(), cuda_split_feature_inner_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<int>(split_feature_.data(), cuda_split_feature_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<uint32_t>(threshold_in_bin_.data(), cuda_threshold_in_bin_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<double>(threshold_.data(), cuda_threshold_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<int8_t>(decision_type_.data(), cuda_decision_type_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<float>(split_gain_.data(), cuda_split_gain_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<int>(leaf_parent_.data(), cuda_leaf_parent_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<double>(leaf_value_.data(), cuda_leaf_value_.RawData(), num_leaves_size, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<double>(leaf_weight_.data(), cuda_leaf_weight_.RawData(), num_leaves_size, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<data_size_t>(leaf_count_.data(), cuda_leaf_count_.RawData(), num_leaves_size, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<double>(internal_value_.data(), cuda_internal_value_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<double>(internal_weight_.data(), cuda_internal_weight_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<data_size_t>(internal_count_.data(), cuda_internal_count_.RawData(), num_leaves_size - 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<int>(leaf_depth_.data(), cuda_leaf_depth_.RawData(), num_leaves_size, __FILE__, __LINE__);
|
||||
|
||||
if (num_cat_ > 0) {
|
||||
cuda_cat_boundaries_inner_.Resize(num_cat_ + 1);
|
||||
cuda_cat_boundaries_.Resize(num_cat_ + 1);
|
||||
cat_boundaries_ = cuda_cat_boundaries_.ToHost();
|
||||
cat_boundaries_inner_ = cuda_cat_boundaries_inner_.ToHost();
|
||||
cat_threshold_ = cuda_bitset_.ToHost();
|
||||
cat_threshold_inner_ = cuda_bitset_inner_.ToHost();
|
||||
}
|
||||
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDATree::SyncLeafOutputFromHostToCUDA() {
|
||||
CopyFromHostToCUDADevice<double>(cuda_leaf_value_.RawData(), leaf_value_.data(), leaf_value_.size(), __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDATree::SyncLeafOutputFromCUDAToHost() {
|
||||
CopyFromCUDADeviceToHost<double>(leaf_value_.data(), cuda_leaf_value_.RawData(), leaf_value_.size(), __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDATree::AsConstantTree(double val, int count) {
|
||||
Tree::AsConstantTree(val, count);
|
||||
CopyFromHostToCUDADevice<double>(cuda_leaf_value_.RawData(), &val, 1, __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<int>(cuda_leaf_count_.RawData(), &count, 1, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,460 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_tree.hpp>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
__device__ void SetDecisionTypeCUDA(int8_t* decision_type, bool input, int8_t mask) {
|
||||
if (input) {
|
||||
(*decision_type) |= mask;
|
||||
} else {
|
||||
(*decision_type) &= (127 - mask);
|
||||
}
|
||||
}
|
||||
|
||||
__device__ void SetMissingTypeCUDA(int8_t* decision_type, int8_t input) {
|
||||
(*decision_type) &= 3;
|
||||
(*decision_type) |= (input << 2);
|
||||
}
|
||||
|
||||
__device__ bool GetDecisionTypeCUDA(int8_t decision_type, int8_t mask) {
|
||||
return (decision_type & mask) > 0;
|
||||
}
|
||||
|
||||
__device__ int8_t GetMissingTypeCUDA(int8_t decision_type) {
|
||||
return (decision_type >> 2) & 3;
|
||||
}
|
||||
|
||||
__device__ bool IsZeroCUDA(double fval) {
|
||||
return (fval >= -kZeroThreshold && fval <= kZeroThreshold);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
__device__ bool FindInBitsetCUDA(const uint32_t* bits, int n, T pos) {
|
||||
int i1 = pos / 32;
|
||||
if (i1 >= n) {
|
||||
return false;
|
||||
}
|
||||
int i2 = pos % 32;
|
||||
return (bits[i1] >> i2) & 1;
|
||||
}
|
||||
|
||||
__global__ void SplitKernel( // split information
|
||||
const int leaf_index,
|
||||
const int real_feature_index,
|
||||
const double real_threshold,
|
||||
const MissingType missing_type,
|
||||
const CUDASplitInfo* cuda_split_info,
|
||||
// tree structure
|
||||
const int num_leaves,
|
||||
int* leaf_parent,
|
||||
int* leaf_depth,
|
||||
int* left_child,
|
||||
int* right_child,
|
||||
int* split_feature_inner,
|
||||
int* split_feature,
|
||||
float* split_gain,
|
||||
double* internal_weight,
|
||||
double* internal_value,
|
||||
data_size_t* internal_count,
|
||||
double* leaf_weight,
|
||||
double* leaf_value,
|
||||
data_size_t* leaf_count,
|
||||
int8_t* decision_type,
|
||||
uint32_t* threshold_in_bin,
|
||||
double* threshold) {
|
||||
const int new_node_index = num_leaves - 1;
|
||||
const int thread_index = static_cast<int>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
const int parent_index = leaf_parent[leaf_index];
|
||||
if (thread_index == 0) {
|
||||
if (parent_index >= 0) {
|
||||
// if cur node is left child
|
||||
if (left_child[parent_index] == ~leaf_index) {
|
||||
left_child[parent_index] = new_node_index;
|
||||
} else {
|
||||
right_child[parent_index] = new_node_index;
|
||||
}
|
||||
}
|
||||
left_child[new_node_index] = ~leaf_index;
|
||||
right_child[new_node_index] = ~num_leaves;
|
||||
leaf_parent[leaf_index] = new_node_index;
|
||||
leaf_parent[num_leaves] = new_node_index;
|
||||
} else if (thread_index == 1) {
|
||||
// add new node
|
||||
split_feature_inner[new_node_index] = cuda_split_info->inner_feature_index;
|
||||
} else if (thread_index == 2) {
|
||||
split_feature[new_node_index] = real_feature_index;
|
||||
} else if (thread_index == 3) {
|
||||
split_gain[new_node_index] = static_cast<float>(cuda_split_info->gain);
|
||||
} else if (thread_index == 4) {
|
||||
// save current leaf value to internal node before change
|
||||
internal_weight[new_node_index] = cuda_split_info->left_sum_hessians + cuda_split_info->right_sum_hessians;
|
||||
leaf_weight[leaf_index] = cuda_split_info->left_sum_hessians;
|
||||
} else if (thread_index == 5) {
|
||||
internal_value[new_node_index] = leaf_value[leaf_index];
|
||||
leaf_value[leaf_index] = isnan(cuda_split_info->left_value) ? 0.0f : cuda_split_info->left_value;
|
||||
} else if (thread_index == 6) {
|
||||
internal_count[new_node_index] = cuda_split_info->left_count + cuda_split_info->right_count;
|
||||
} else if (thread_index == 7) {
|
||||
leaf_count[leaf_index] = cuda_split_info->left_count;
|
||||
} else if (thread_index == 8) {
|
||||
leaf_value[num_leaves] = isnan(cuda_split_info->right_value) ? 0.0f : cuda_split_info->right_value;
|
||||
} else if (thread_index == 9) {
|
||||
leaf_weight[num_leaves] = cuda_split_info->right_sum_hessians;
|
||||
} else if (thread_index == 10) {
|
||||
leaf_count[num_leaves] = cuda_split_info->right_count;
|
||||
} else if (thread_index == 11) {
|
||||
// update leaf depth
|
||||
leaf_depth[num_leaves] = leaf_depth[leaf_index] + 1;
|
||||
leaf_depth[leaf_index]++;
|
||||
} else if (thread_index == 12) {
|
||||
decision_type[new_node_index] = 0;
|
||||
SetDecisionTypeCUDA(&decision_type[new_node_index], false, kCategoricalMask);
|
||||
SetDecisionTypeCUDA(&decision_type[new_node_index], cuda_split_info->default_left, kDefaultLeftMask);
|
||||
SetMissingTypeCUDA(&decision_type[new_node_index], static_cast<int8_t>(missing_type));
|
||||
} else if (thread_index == 13) {
|
||||
threshold_in_bin[new_node_index] = cuda_split_info->threshold;
|
||||
} else if (thread_index == 14) {
|
||||
threshold[new_node_index] = real_threshold;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDATree::LaunchSplitKernel(const int leaf_index,
|
||||
const int real_feature_index,
|
||||
const double real_threshold,
|
||||
const MissingType missing_type,
|
||||
const CUDASplitInfo* cuda_split_info) {
|
||||
SplitKernel<<<3, 5, 0, cuda_stream_>>>(
|
||||
// split information
|
||||
leaf_index,
|
||||
real_feature_index,
|
||||
real_threshold,
|
||||
missing_type,
|
||||
cuda_split_info,
|
||||
// tree structure
|
||||
num_leaves_,
|
||||
cuda_leaf_parent_.RawData(),
|
||||
cuda_leaf_depth_.RawData(),
|
||||
cuda_left_child_.RawData(),
|
||||
cuda_right_child_.RawData(),
|
||||
cuda_split_feature_inner_.RawData(),
|
||||
cuda_split_feature_.RawData(),
|
||||
cuda_split_gain_.RawData(),
|
||||
cuda_internal_weight_.RawData(),
|
||||
cuda_internal_value_.RawData(),
|
||||
cuda_internal_count_.RawData(),
|
||||
cuda_leaf_weight_.RawData(),
|
||||
cuda_leaf_value_.RawData(),
|
||||
cuda_leaf_count_.RawData(),
|
||||
cuda_decision_type_.RawData(),
|
||||
cuda_threshold_in_bin_.RawData(),
|
||||
cuda_threshold_.RawData());
|
||||
}
|
||||
|
||||
__global__ void SplitCategoricalKernel( // split information
|
||||
const int leaf_index,
|
||||
const int real_feature_index,
|
||||
const MissingType missing_type,
|
||||
const CUDASplitInfo* cuda_split_info,
|
||||
// tree structure
|
||||
const int num_leaves,
|
||||
int* leaf_parent,
|
||||
int* leaf_depth,
|
||||
int* left_child,
|
||||
int* right_child,
|
||||
int* split_feature_inner,
|
||||
int* split_feature,
|
||||
float* split_gain,
|
||||
double* internal_weight,
|
||||
double* internal_value,
|
||||
data_size_t* internal_count,
|
||||
double* leaf_weight,
|
||||
double* leaf_value,
|
||||
data_size_t* leaf_count,
|
||||
int8_t* decision_type,
|
||||
uint32_t* threshold_in_bin,
|
||||
double* threshold,
|
||||
size_t cuda_bitset_len,
|
||||
size_t cuda_bitset_inner_len,
|
||||
int num_cat,
|
||||
int* cuda_cat_boundaries,
|
||||
int* cuda_cat_boundaries_inner) {
|
||||
const int new_node_index = num_leaves - 1;
|
||||
const int thread_index = static_cast<int>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
const int parent_index = leaf_parent[leaf_index];
|
||||
if (thread_index == 0) {
|
||||
if (parent_index >= 0) {
|
||||
// if cur node is left child
|
||||
if (left_child[parent_index] == ~leaf_index) {
|
||||
left_child[parent_index] = new_node_index;
|
||||
} else {
|
||||
right_child[parent_index] = new_node_index;
|
||||
}
|
||||
}
|
||||
left_child[new_node_index] = ~leaf_index;
|
||||
right_child[new_node_index] = ~num_leaves;
|
||||
leaf_parent[leaf_index] = new_node_index;
|
||||
leaf_parent[num_leaves] = new_node_index;
|
||||
} else if (thread_index == 1) {
|
||||
// add new node
|
||||
split_feature_inner[new_node_index] = cuda_split_info->inner_feature_index;
|
||||
} else if (thread_index == 2) {
|
||||
split_feature[new_node_index] = real_feature_index;
|
||||
} else if (thread_index == 3) {
|
||||
split_gain[new_node_index] = static_cast<float>(cuda_split_info->gain);
|
||||
} else if (thread_index == 4) {
|
||||
// save current leaf value to internal node before change
|
||||
internal_weight[new_node_index] = cuda_split_info->left_sum_hessians + cuda_split_info->right_sum_hessians;
|
||||
leaf_weight[leaf_index] = cuda_split_info->left_sum_hessians;
|
||||
} else if (thread_index == 5) {
|
||||
internal_value[new_node_index] = leaf_value[leaf_index];
|
||||
leaf_value[leaf_index] = isnan(cuda_split_info->left_value) ? 0.0f : cuda_split_info->left_value;
|
||||
} else if (thread_index == 6) {
|
||||
internal_count[new_node_index] = cuda_split_info->left_count + cuda_split_info->right_count;
|
||||
} else if (thread_index == 7) {
|
||||
leaf_count[leaf_index] = cuda_split_info->left_count;
|
||||
} else if (thread_index == 8) {
|
||||
leaf_value[num_leaves] = isnan(cuda_split_info->right_value) ? 0.0f : cuda_split_info->right_value;
|
||||
} else if (thread_index == 9) {
|
||||
leaf_weight[num_leaves] = cuda_split_info->right_sum_hessians;
|
||||
} else if (thread_index == 10) {
|
||||
leaf_count[num_leaves] = cuda_split_info->right_count;
|
||||
} else if (thread_index == 11) {
|
||||
// update leaf depth
|
||||
leaf_depth[num_leaves] = leaf_depth[leaf_index] + 1;
|
||||
leaf_depth[leaf_index]++;
|
||||
} else if (thread_index == 12) {
|
||||
decision_type[new_node_index] = 0;
|
||||
SetDecisionTypeCUDA(&decision_type[new_node_index], true, kCategoricalMask);
|
||||
SetMissingTypeCUDA(&decision_type[new_node_index], static_cast<int8_t>(missing_type));
|
||||
} else if (thread_index == 13) {
|
||||
threshold_in_bin[new_node_index] = num_cat;
|
||||
} else if (thread_index == 14) {
|
||||
threshold[new_node_index] = num_cat;
|
||||
} else if (thread_index == 15) {
|
||||
if (num_cat == 0) {
|
||||
cuda_cat_boundaries[num_cat] = 0;
|
||||
}
|
||||
cuda_cat_boundaries[num_cat + 1] = cuda_cat_boundaries[num_cat] + cuda_bitset_len;
|
||||
} else if (thread_index == 16) {
|
||||
if (num_cat == 0) {
|
||||
cuda_cat_boundaries_inner[num_cat] = 0;
|
||||
}
|
||||
cuda_cat_boundaries_inner[num_cat + 1] = cuda_cat_boundaries_inner[num_cat] + cuda_bitset_inner_len;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDATree::LaunchSplitCategoricalKernel(const int leaf_index,
|
||||
const int real_feature_index,
|
||||
const MissingType missing_type,
|
||||
const CUDASplitInfo* cuda_split_info,
|
||||
size_t cuda_bitset_len,
|
||||
size_t cuda_bitset_inner_len) {
|
||||
SplitCategoricalKernel<<<3, 6, 0, cuda_stream_>>>(
|
||||
// split information
|
||||
leaf_index,
|
||||
real_feature_index,
|
||||
missing_type,
|
||||
cuda_split_info,
|
||||
// tree structure
|
||||
num_leaves_,
|
||||
cuda_leaf_parent_.RawData(),
|
||||
cuda_leaf_depth_.RawData(),
|
||||
cuda_left_child_.RawData(),
|
||||
cuda_right_child_.RawData(),
|
||||
cuda_split_feature_inner_.RawData(),
|
||||
cuda_split_feature_.RawData(),
|
||||
cuda_split_gain_.RawData(),
|
||||
cuda_internal_weight_.RawData(),
|
||||
cuda_internal_value_.RawData(),
|
||||
cuda_internal_count_.RawData(),
|
||||
cuda_leaf_weight_.RawData(),
|
||||
cuda_leaf_value_.RawData(),
|
||||
cuda_leaf_count_.RawData(),
|
||||
cuda_decision_type_.RawData(),
|
||||
cuda_threshold_in_bin_.RawData(),
|
||||
cuda_threshold_.RawData(),
|
||||
cuda_bitset_len,
|
||||
cuda_bitset_inner_len,
|
||||
num_cat_,
|
||||
cuda_cat_boundaries_.RawData(),
|
||||
cuda_cat_boundaries_inner_.RawData());
|
||||
}
|
||||
|
||||
__global__ void ShrinkageKernel(const double rate, double* cuda_leaf_value, const int num_leaves) {
|
||||
const int leaf_index = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (leaf_index < num_leaves) {
|
||||
cuda_leaf_value[leaf_index] *= rate;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDATree::LaunchShrinkageKernel(const double rate) {
|
||||
const int num_threads_per_block = 1024;
|
||||
const int num_blocks = (num_leaves_ + num_threads_per_block - 1) / num_threads_per_block;
|
||||
ShrinkageKernel<<<num_blocks, num_threads_per_block>>>(rate, cuda_leaf_value_.RawData(), num_leaves_);
|
||||
}
|
||||
|
||||
__global__ void AddBiasKernel(const double val, double* cuda_leaf_value, const int num_leaves) {
|
||||
const int leaf_index = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (leaf_index < num_leaves) {
|
||||
cuda_leaf_value[leaf_index] += val;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDATree::LaunchAddBiasKernel(const double val) {
|
||||
const int num_threads_per_block = 1024;
|
||||
const int num_blocks = (num_leaves_ + num_threads_per_block - 1) / num_threads_per_block;
|
||||
AddBiasKernel<<<num_blocks, num_threads_per_block>>>(val, cuda_leaf_value_.RawData(), num_leaves_);
|
||||
}
|
||||
|
||||
template <bool USE_INDICES>
|
||||
__global__ void AddPredictionToScoreKernel(
|
||||
// dataset information
|
||||
const data_size_t num_data,
|
||||
uint8_t* const* cuda_data_by_column,
|
||||
const uint8_t* cuda_column_bit_type,
|
||||
const uint32_t* cuda_feature_min_bin,
|
||||
const uint32_t* cuda_feature_max_bin,
|
||||
const uint32_t* cuda_feature_offset,
|
||||
const uint32_t* cuda_feature_default_bin,
|
||||
const uint32_t* cuda_feature_most_freq_bin,
|
||||
const int* cuda_feature_to_column,
|
||||
const data_size_t* cuda_used_indices,
|
||||
// tree information
|
||||
const uint32_t* cuda_threshold_in_bin,
|
||||
const int8_t* cuda_decision_type,
|
||||
const int* cuda_split_feature_inner,
|
||||
const int* cuda_left_child,
|
||||
const int* cuda_right_child,
|
||||
const double* cuda_leaf_value,
|
||||
const uint32_t* cuda_bitset_inner,
|
||||
const int* cuda_cat_boundaries_inner,
|
||||
// output
|
||||
double* score) {
|
||||
const data_size_t inner_data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (inner_data_index < num_data) {
|
||||
const data_size_t data_index = USE_INDICES ? cuda_used_indices[inner_data_index] : inner_data_index;
|
||||
int node = 0;
|
||||
while (node >= 0) {
|
||||
const int split_feature_inner = cuda_split_feature_inner[node];
|
||||
const int column = cuda_feature_to_column[split_feature_inner];
|
||||
const uint32_t default_bin = cuda_feature_default_bin[split_feature_inner];
|
||||
const uint32_t most_freq_bin = cuda_feature_most_freq_bin[split_feature_inner];
|
||||
const uint32_t max_bin = cuda_feature_max_bin[split_feature_inner];
|
||||
const uint32_t min_bin = cuda_feature_min_bin[split_feature_inner];
|
||||
const uint32_t offset = cuda_feature_offset[split_feature_inner];
|
||||
const uint8_t column_bit_type = cuda_column_bit_type[column];
|
||||
uint32_t bin = 0;
|
||||
if (column_bit_type == 8) {
|
||||
bin = static_cast<uint32_t>((reinterpret_cast<const uint8_t*>(cuda_data_by_column[column]))[data_index]);
|
||||
} else if (column_bit_type == 16) {
|
||||
bin = static_cast<uint32_t>((reinterpret_cast<const uint16_t*>(cuda_data_by_column[column]))[data_index]);
|
||||
} else if (column_bit_type == 32) {
|
||||
bin = static_cast<uint32_t>((reinterpret_cast<const uint32_t*>(cuda_data_by_column[column]))[data_index]);
|
||||
}
|
||||
if (bin >= min_bin && bin <= max_bin) {
|
||||
bin = bin - min_bin + offset;
|
||||
} else {
|
||||
bin = most_freq_bin;
|
||||
}
|
||||
const int8_t decision_type = cuda_decision_type[node];
|
||||
if (GetDecisionTypeCUDA(decision_type, kCategoricalMask)) {
|
||||
int cat_idx = static_cast<int>(cuda_threshold_in_bin[node]);
|
||||
if (FindInBitsetCUDA(cuda_bitset_inner + cuda_cat_boundaries_inner[cat_idx],
|
||||
cuda_cat_boundaries_inner[cat_idx + 1] - cuda_cat_boundaries_inner[cat_idx], bin)) {
|
||||
node = cuda_left_child[node];
|
||||
} else {
|
||||
node = cuda_right_child[node];
|
||||
}
|
||||
} else {
|
||||
const uint32_t threshold_in_bin = cuda_threshold_in_bin[node];
|
||||
const int8_t missing_type = GetMissingTypeCUDA(decision_type);
|
||||
const bool default_left = ((decision_type & kDefaultLeftMask) > 0);
|
||||
if ((missing_type == 1 && bin == default_bin) || (missing_type == 2 && bin == max_bin)) {
|
||||
if (default_left) {
|
||||
node = cuda_left_child[node];
|
||||
} else {
|
||||
node = cuda_right_child[node];
|
||||
}
|
||||
} else {
|
||||
if (bin <= threshold_in_bin) {
|
||||
node = cuda_left_child[node];
|
||||
} else {
|
||||
node = cuda_right_child[node];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
score[data_index] += cuda_leaf_value[~node];
|
||||
}
|
||||
}
|
||||
|
||||
void CUDATree::LaunchAddPredictionToScoreKernel(
|
||||
const Dataset* data,
|
||||
const data_size_t* used_data_indices,
|
||||
data_size_t num_data,
|
||||
double* score) const {
|
||||
const CUDAColumnData* cuda_column_data = data->cuda_column_data();
|
||||
const int num_blocks = (num_data + num_threads_per_block_add_prediction_to_score_ - 1) / num_threads_per_block_add_prediction_to_score_;
|
||||
if (used_data_indices == nullptr) {
|
||||
AddPredictionToScoreKernel<false><<<num_blocks, num_threads_per_block_add_prediction_to_score_>>>(
|
||||
// dataset information
|
||||
num_data,
|
||||
cuda_column_data->cuda_data_by_column(),
|
||||
cuda_column_data->cuda_column_bit_type(),
|
||||
cuda_column_data->cuda_feature_min_bin(),
|
||||
cuda_column_data->cuda_feature_max_bin(),
|
||||
cuda_column_data->cuda_feature_offset(),
|
||||
cuda_column_data->cuda_feature_default_bin(),
|
||||
cuda_column_data->cuda_feature_most_freq_bin(),
|
||||
cuda_column_data->cuda_feature_to_column(),
|
||||
nullptr,
|
||||
// tree information
|
||||
cuda_threshold_in_bin_.RawData(),
|
||||
cuda_decision_type_.RawData(),
|
||||
cuda_split_feature_inner_.RawData(),
|
||||
cuda_left_child_.RawData(),
|
||||
cuda_right_child_.RawData(),
|
||||
cuda_leaf_value_.RawData(),
|
||||
cuda_bitset_inner_.RawDataReadOnly(),
|
||||
cuda_cat_boundaries_inner_.RawDataReadOnly(),
|
||||
// output
|
||||
score);
|
||||
} else {
|
||||
AddPredictionToScoreKernel<true><<<num_blocks, num_threads_per_block_add_prediction_to_score_>>>(
|
||||
// dataset information
|
||||
num_data,
|
||||
cuda_column_data->cuda_data_by_column(),
|
||||
cuda_column_data->cuda_column_bit_type(),
|
||||
cuda_column_data->cuda_feature_min_bin(),
|
||||
cuda_column_data->cuda_feature_max_bin(),
|
||||
cuda_column_data->cuda_feature_offset(),
|
||||
cuda_column_data->cuda_feature_default_bin(),
|
||||
cuda_column_data->cuda_feature_most_freq_bin(),
|
||||
cuda_column_data->cuda_feature_to_column(),
|
||||
used_data_indices,
|
||||
// tree information
|
||||
cuda_threshold_in_bin_.RawData(),
|
||||
cuda_decision_type_.RawData(),
|
||||
cuda_split_feature_inner_.RawData(),
|
||||
cuda_left_child_.RawData(),
|
||||
cuda_right_child_.RawData(),
|
||||
cuda_leaf_value_.RawData(),
|
||||
cuda_bitset_inner_.RawDataReadOnly(),
|
||||
cuda_cat_boundaries_inner_.RawDataReadOnly(),
|
||||
// output
|
||||
score);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
+1955
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,650 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_IO_DENSE_BIN_HPP_
|
||||
#define LIGHTGBM_SRC_IO_DENSE_BIN_HPP_
|
||||
|
||||
#include <LightGBM/bin.h>
|
||||
#include <LightGBM/cuda/vector_cudahost.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename VAL_T, bool IS_4BIT>
|
||||
class DenseBin;
|
||||
|
||||
template <typename VAL_T, bool IS_4BIT>
|
||||
class DenseBinIterator : public BinIterator {
|
||||
public:
|
||||
explicit DenseBinIterator(const DenseBin<VAL_T, IS_4BIT>* bin_data,
|
||||
uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin)
|
||||
: bin_data_(bin_data),
|
||||
min_bin_(static_cast<VAL_T>(min_bin)),
|
||||
max_bin_(static_cast<VAL_T>(max_bin)),
|
||||
most_freq_bin_(static_cast<VAL_T>(most_freq_bin)) {
|
||||
if (most_freq_bin_ == 0) {
|
||||
offset_ = 1;
|
||||
} else {
|
||||
offset_ = 0;
|
||||
}
|
||||
}
|
||||
inline uint32_t RawGet(data_size_t idx) override;
|
||||
inline uint32_t Get(data_size_t idx) override;
|
||||
inline void Reset(data_size_t) override {}
|
||||
|
||||
private:
|
||||
const DenseBin<VAL_T, IS_4BIT>* bin_data_;
|
||||
VAL_T min_bin_;
|
||||
VAL_T max_bin_;
|
||||
VAL_T most_freq_bin_;
|
||||
uint8_t offset_;
|
||||
};
|
||||
/*!
|
||||
* \brief Used to store bins for dense feature
|
||||
* Use template to reduce memory cost
|
||||
*/
|
||||
template <typename VAL_T, bool IS_4BIT>
|
||||
class DenseBin : public Bin {
|
||||
public:
|
||||
friend DenseBinIterator<VAL_T, IS_4BIT>;
|
||||
explicit DenseBin(data_size_t num_data)
|
||||
: num_data_(num_data) {
|
||||
if (IS_4BIT) {
|
||||
CHECK_EQ(sizeof(VAL_T), 1);
|
||||
data_.resize((num_data_ + 1) / 2, static_cast<uint8_t>(0));
|
||||
buf_.resize((num_data_ + 1) / 2, static_cast<uint8_t>(0));
|
||||
} else {
|
||||
data_.resize(num_data_, static_cast<VAL_T>(0));
|
||||
}
|
||||
}
|
||||
|
||||
~DenseBin() {}
|
||||
|
||||
void Push(int, data_size_t idx, uint32_t value) override {
|
||||
if (IS_4BIT) {
|
||||
const int i1 = idx >> 1;
|
||||
const int i2 = (idx & 1) << 2;
|
||||
const uint8_t val = static_cast<uint8_t>(value) << i2;
|
||||
if (i2 == 0) {
|
||||
data_[i1] = val;
|
||||
} else {
|
||||
buf_[i1] = val;
|
||||
}
|
||||
} else {
|
||||
data_[idx] = static_cast<VAL_T>(value);
|
||||
}
|
||||
}
|
||||
|
||||
void ReSize(data_size_t num_data) override {
|
||||
if (num_data_ != num_data) {
|
||||
num_data_ = num_data;
|
||||
if (IS_4BIT) {
|
||||
data_.resize((num_data_ + 1) / 2, static_cast<VAL_T>(0));
|
||||
} else {
|
||||
data_.resize(num_data_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinIterator* GetIterator(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin) const override;
|
||||
|
||||
template <bool USE_INDICES, bool USE_PREFETCH, bool USE_HESSIAN>
|
||||
void ConstructHistogramInner(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* ordered_hessians,
|
||||
hist_t* out) const {
|
||||
data_size_t i = start;
|
||||
hist_t* grad = out;
|
||||
hist_t* hess = out + 1;
|
||||
hist_cnt_t* cnt = reinterpret_cast<hist_cnt_t*>(hess);
|
||||
if (USE_PREFETCH) {
|
||||
const data_size_t pf_offset = 64 / sizeof(VAL_T);
|
||||
const data_size_t pf_end = end - pf_offset;
|
||||
for (; i < pf_end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto pf_idx =
|
||||
USE_INDICES ? data_indices[i + pf_offset] : i + pf_offset;
|
||||
if (IS_4BIT) {
|
||||
PREFETCH_T0(data_.data() + (pf_idx >> 1));
|
||||
} else {
|
||||
PREFETCH_T0(data_.data() + pf_idx);
|
||||
}
|
||||
const auto ti = static_cast<uint32_t>(data(idx)) << 1;
|
||||
if (USE_HESSIAN) {
|
||||
grad[ti] += ordered_gradients[i];
|
||||
hess[ti] += ordered_hessians[i];
|
||||
} else {
|
||||
grad[ti] += ordered_gradients[i];
|
||||
++cnt[ti];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto ti = static_cast<uint32_t>(data(idx)) << 1;
|
||||
if (USE_HESSIAN) {
|
||||
grad[ti] += ordered_gradients[i];
|
||||
hess[ti] += ordered_hessians[i];
|
||||
} else {
|
||||
grad[ti] += ordered_gradients[i];
|
||||
++cnt[ti];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogram(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* ordered_hessians,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<true, true, true>(
|
||||
data_indices, start, end, ordered_gradients, ordered_hessians, out);
|
||||
}
|
||||
|
||||
void ConstructHistogram(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* ordered_hessians,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<false, false, true>(
|
||||
nullptr, start, end, ordered_gradients, ordered_hessians, out);
|
||||
}
|
||||
|
||||
void ConstructHistogram(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<true, true, false>(data_indices, start, end,
|
||||
ordered_gradients, nullptr, out);
|
||||
}
|
||||
|
||||
void ConstructHistogram(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<false, false, false>(
|
||||
nullptr, start, end, ordered_gradients, nullptr, out);
|
||||
}
|
||||
|
||||
|
||||
template <bool USE_INDICES, bool USE_PREFETCH, bool USE_HESSIAN, typename PACKED_HIST_T, int HIST_BITS>
|
||||
void ConstructHistogramIntInner(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const {
|
||||
data_size_t i = start;
|
||||
PACKED_HIST_T* out_ptr = reinterpret_cast<PACKED_HIST_T*>(out);
|
||||
const int16_t* gradients_ptr = reinterpret_cast<const int16_t*>(ordered_gradients);
|
||||
const VAL_T* data_ptr_base = data_.data();
|
||||
if (USE_PREFETCH) {
|
||||
const data_size_t pf_offset = 64 / sizeof(VAL_T);
|
||||
const data_size_t pf_end = end - pf_offset;
|
||||
for (; i < pf_end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto pf_idx =
|
||||
USE_INDICES ? data_indices[i + pf_offset] : i + pf_offset;
|
||||
if (IS_4BIT) {
|
||||
PREFETCH_T0(data_ptr_base + (pf_idx >> 1));
|
||||
} else {
|
||||
PREFETCH_T0(data_ptr_base + pf_idx);
|
||||
}
|
||||
const auto ti = static_cast<uint32_t>(data(idx));
|
||||
const int16_t gradient_16 = gradients_ptr[i];
|
||||
if (USE_HESSIAN) {
|
||||
const PACKED_HIST_T gradient_packed = HIST_BITS == 8 ? gradient_16 :
|
||||
(static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) | (gradient_16 & 0xff);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
} else {
|
||||
const PACKED_HIST_T gradient_packed = HIST_BITS == 8 ? gradient_16 :
|
||||
(static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) | (1);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto ti = static_cast<uint32_t>(data(idx));
|
||||
const int16_t gradient_16 = gradients_ptr[i];
|
||||
if (USE_HESSIAN) {
|
||||
const PACKED_HIST_T gradient_packed = HIST_BITS == 8 ? gradient_16 :
|
||||
(static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) | (gradient_16 & 0xff);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
} else {
|
||||
const PACKED_HIST_T gradient_packed = HIST_BITS == 8 ? gradient_16 :
|
||||
(static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) | (1);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int16_t, 8>(
|
||||
data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, true, int16_t, 8>(
|
||||
nullptr, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int16_t, 8>(
|
||||
data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int16_t, 8>(
|
||||
nullptr, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int32_t, 16>(
|
||||
data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, true, int32_t, 16>(
|
||||
nullptr, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int32_t, 16>(
|
||||
data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int32_t, 16>(
|
||||
nullptr, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int64_t, 32>(
|
||||
data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, true, int64_t, 32>(
|
||||
nullptr, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int64_t, 32>(
|
||||
data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int64_t, 32>(
|
||||
nullptr, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
template <bool MISS_IS_ZERO, bool MISS_IS_NA, bool MFB_IS_ZERO,
|
||||
bool MFB_IS_NA, bool USE_MIN_BIN>
|
||||
data_size_t SplitInner(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t default_bin, uint32_t most_freq_bin,
|
||||
bool default_left, uint32_t threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const {
|
||||
auto th = static_cast<VAL_T>(threshold + min_bin);
|
||||
auto t_zero_bin = static_cast<VAL_T>(min_bin + default_bin);
|
||||
if (most_freq_bin == 0) {
|
||||
--th;
|
||||
--t_zero_bin;
|
||||
}
|
||||
const auto minb = static_cast<VAL_T>(min_bin);
|
||||
const auto maxb = static_cast<VAL_T>(max_bin);
|
||||
data_size_t lte_count = 0;
|
||||
data_size_t gt_count = 0;
|
||||
data_size_t* default_indices = gt_indices;
|
||||
data_size_t* default_count = >_count;
|
||||
data_size_t* missing_default_indices = gt_indices;
|
||||
data_size_t* missing_default_count = >_count;
|
||||
if (most_freq_bin <= threshold) {
|
||||
default_indices = lte_indices;
|
||||
default_count = <e_count;
|
||||
}
|
||||
if (MISS_IS_ZERO || MISS_IS_NA) {
|
||||
if (default_left) {
|
||||
missing_default_indices = lte_indices;
|
||||
missing_default_count = <e_count;
|
||||
}
|
||||
}
|
||||
if (min_bin < max_bin) {
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
const data_size_t idx = data_indices[i];
|
||||
const auto bin = data(idx);
|
||||
if ((MISS_IS_ZERO && !MFB_IS_ZERO && bin == t_zero_bin) ||
|
||||
(MISS_IS_NA && !MFB_IS_NA && bin == maxb)) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else if ((USE_MIN_BIN && (bin < minb || bin > maxb)) ||
|
||||
(!USE_MIN_BIN && bin == 0)) {
|
||||
if ((MISS_IS_NA && MFB_IS_NA) || (MISS_IS_ZERO && MFB_IS_ZERO)) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
}
|
||||
} else if (bin > th) {
|
||||
gt_indices[gt_count++] = idx;
|
||||
} else {
|
||||
lte_indices[lte_count++] = idx;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data_size_t* max_bin_indices = gt_indices;
|
||||
data_size_t* max_bin_count = >_count;
|
||||
if (maxb <= th) {
|
||||
max_bin_indices = lte_indices;
|
||||
max_bin_count = <e_count;
|
||||
}
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
const data_size_t idx = data_indices[i];
|
||||
const auto bin = data(idx);
|
||||
if (MISS_IS_ZERO && !MFB_IS_ZERO && bin == t_zero_bin) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else if (bin != maxb) {
|
||||
if ((MISS_IS_NA && MFB_IS_NA) || (MISS_IS_ZERO && MFB_IS_ZERO)) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
}
|
||||
} else {
|
||||
if (MISS_IS_NA && !MFB_IS_NA) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else {
|
||||
max_bin_indices[(*max_bin_count)++] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lte_count;
|
||||
}
|
||||
|
||||
data_size_t Split(uint32_t min_bin, uint32_t max_bin, uint32_t default_bin,
|
||||
uint32_t most_freq_bin, MissingType missing_type,
|
||||
bool default_left, uint32_t threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
#define ARGUMENTS \
|
||||
min_bin, max_bin, default_bin, most_freq_bin, default_left, threshold, \
|
||||
data_indices, cnt, lte_indices, gt_indices
|
||||
if (missing_type == MissingType::None) {
|
||||
return SplitInner<false, false, false, false, true>(ARGUMENTS);
|
||||
} else if (missing_type == MissingType::Zero) {
|
||||
if (default_bin == most_freq_bin) {
|
||||
return SplitInner<true, false, true, false, true>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<true, false, false, false, true>(ARGUMENTS);
|
||||
}
|
||||
} else {
|
||||
if (max_bin == most_freq_bin + min_bin && most_freq_bin > 0) {
|
||||
return SplitInner<false, true, false, true, true>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<false, true, false, false, true>(ARGUMENTS);
|
||||
}
|
||||
}
|
||||
#undef ARGUMENTS
|
||||
}
|
||||
|
||||
data_size_t Split(uint32_t max_bin, uint32_t default_bin,
|
||||
uint32_t most_freq_bin, MissingType missing_type,
|
||||
bool default_left, uint32_t threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
#define ARGUMENTS \
|
||||
1, max_bin, default_bin, most_freq_bin, default_left, threshold, \
|
||||
data_indices, cnt, lte_indices, gt_indices
|
||||
if (missing_type == MissingType::None) {
|
||||
return SplitInner<false, false, false, false, false>(ARGUMENTS);
|
||||
} else if (missing_type == MissingType::Zero) {
|
||||
if (default_bin == most_freq_bin) {
|
||||
return SplitInner<true, false, true, false, false>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<true, false, false, false, false>(ARGUMENTS);
|
||||
}
|
||||
} else {
|
||||
if (max_bin == most_freq_bin + 1 && most_freq_bin > 0) {
|
||||
return SplitInner<false, true, false, true, false>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<false, true, false, false, false>(ARGUMENTS);
|
||||
}
|
||||
}
|
||||
#undef ARGUMENTS
|
||||
}
|
||||
|
||||
template <bool USE_MIN_BIN>
|
||||
data_size_t SplitCategoricalInner(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin,
|
||||
const uint32_t* threshold,
|
||||
int num_threshold,
|
||||
const data_size_t* data_indices,
|
||||
data_size_t cnt, data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const {
|
||||
data_size_t lte_count = 0;
|
||||
data_size_t gt_count = 0;
|
||||
data_size_t* default_indices = gt_indices;
|
||||
data_size_t* default_count = >_count;
|
||||
int8_t offset = most_freq_bin == 0 ? 1 : 0;
|
||||
if (most_freq_bin > 0 &&
|
||||
Common::FindInBitset(threshold, num_threshold, most_freq_bin)) {
|
||||
default_indices = lte_indices;
|
||||
default_count = <e_count;
|
||||
}
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
const data_size_t idx = data_indices[i];
|
||||
const uint32_t bin = data(idx);
|
||||
if (USE_MIN_BIN && (bin < min_bin || bin > max_bin)) {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
} else if (!USE_MIN_BIN && bin == 0) {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
} else if (Common::FindInBitset(threshold, num_threshold,
|
||||
bin - min_bin + offset)) {
|
||||
lte_indices[lte_count++] = idx;
|
||||
} else {
|
||||
gt_indices[gt_count++] = idx;
|
||||
}
|
||||
}
|
||||
return lte_count;
|
||||
}
|
||||
|
||||
data_size_t SplitCategorical(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin,
|
||||
const uint32_t* threshold, int num_threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
return SplitCategoricalInner<true>(min_bin, max_bin, most_freq_bin,
|
||||
threshold, num_threshold, data_indices,
|
||||
cnt, lte_indices, gt_indices);
|
||||
}
|
||||
|
||||
data_size_t SplitCategorical(uint32_t max_bin, uint32_t most_freq_bin,
|
||||
const uint32_t* threshold, int num_threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
return SplitCategoricalInner<false>(1, max_bin, most_freq_bin, threshold,
|
||||
num_threshold, data_indices, cnt,
|
||||
lte_indices, gt_indices);
|
||||
}
|
||||
|
||||
data_size_t num_data() const override { return num_data_; }
|
||||
|
||||
void* get_data() override { return data_.data(); }
|
||||
|
||||
void FinishLoad() override {
|
||||
if (IS_4BIT) {
|
||||
if (buf_.empty()) {
|
||||
return;
|
||||
}
|
||||
int len = (num_data_ + 1) / 2;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
data_[i] |= buf_[i];
|
||||
}
|
||||
buf_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void LoadFromMemory(
|
||||
const void* memory,
|
||||
const std::vector<data_size_t>& local_used_indices) override {
|
||||
const VAL_T* mem_data = reinterpret_cast<const VAL_T*>(memory);
|
||||
if (!local_used_indices.empty()) {
|
||||
if (IS_4BIT) {
|
||||
const data_size_t rest = num_data_ & 1;
|
||||
for (int i = 0; i < num_data_ - rest; i += 2) {
|
||||
// get old bins
|
||||
data_size_t idx = local_used_indices[i];
|
||||
const auto bin1 = static_cast<uint8_t>(
|
||||
(mem_data[idx >> 1] >> ((idx & 1) << 2)) & 0xf);
|
||||
idx = local_used_indices[i + 1];
|
||||
const auto bin2 = static_cast<uint8_t>(
|
||||
(mem_data[idx >> 1] >> ((idx & 1) << 2)) & 0xf);
|
||||
// add
|
||||
const int i1 = i >> 1;
|
||||
data_[i1] = (bin1 | (bin2 << 4));
|
||||
}
|
||||
if (rest) {
|
||||
data_size_t idx = local_used_indices[num_data_ - 1];
|
||||
data_[num_data_ >> 1] =
|
||||
(mem_data[idx >> 1] >> ((idx & 1) << 2)) & 0xf;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < num_data_; ++i) {
|
||||
data_[i] = mem_data[local_used_indices[i]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < data_.size(); ++i) {
|
||||
data_[i] = mem_data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline VAL_T data(data_size_t idx) const {
|
||||
if (IS_4BIT) {
|
||||
return (data_[idx >> 1] >> ((idx & 1) << 2)) & 0xf;
|
||||
} else {
|
||||
return data_[idx];
|
||||
}
|
||||
}
|
||||
|
||||
void CopySubrow(const Bin* full_bin, const data_size_t* used_indices,
|
||||
data_size_t num_used_indices) override {
|
||||
auto other_bin = dynamic_cast<const DenseBin<VAL_T, IS_4BIT>*>(full_bin);
|
||||
if (IS_4BIT) {
|
||||
const data_size_t rest = num_used_indices & 1;
|
||||
for (int i = 0; i < num_used_indices - rest; i += 2) {
|
||||
data_size_t idx = used_indices[i];
|
||||
const auto bin1 = static_cast<uint8_t>(
|
||||
(other_bin->data_[idx >> 1] >> ((idx & 1) << 2)) & 0xf);
|
||||
idx = used_indices[i + 1];
|
||||
const auto bin2 = static_cast<uint8_t>(
|
||||
(other_bin->data_[idx >> 1] >> ((idx & 1) << 2)) & 0xf);
|
||||
const int i1 = i >> 1;
|
||||
data_[i1] = (bin1 | (bin2 << 4));
|
||||
}
|
||||
if (rest) {
|
||||
data_size_t idx = used_indices[num_used_indices - 1];
|
||||
data_[num_used_indices >> 1] =
|
||||
(other_bin->data_[idx >> 1] >> ((idx & 1) << 2)) & 0xf;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < num_used_indices; ++i) {
|
||||
data_[i] = other_bin->data_[used_indices[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SaveBinaryToFile(BinaryWriter* writer) const override {
|
||||
writer->AlignedWrite(data_.data(), sizeof(VAL_T) * data_.size());
|
||||
}
|
||||
|
||||
size_t SizesInByte() const override {
|
||||
return VirtualFileWriter::AlignedSize(sizeof(VAL_T) * data_.size());
|
||||
}
|
||||
|
||||
DenseBin<VAL_T, IS_4BIT>* Clone() override;
|
||||
|
||||
const void* GetColWiseData(uint8_t* bit_type, bool* is_sparse, std::vector<BinIterator*>* bin_iterator, const int num_threads) const override;
|
||||
|
||||
const void* GetColWiseData(uint8_t* bit_type, bool* is_sparse, BinIterator** bin_iterator) const override;
|
||||
|
||||
private:
|
||||
data_size_t num_data_;
|
||||
#ifdef USE_CUDA
|
||||
std::vector<VAL_T, CHAllocator<VAL_T>> data_;
|
||||
#else
|
||||
std::vector<VAL_T, Common::AlignmentAllocator<VAL_T, kAlignedSize>> data_;
|
||||
#endif
|
||||
std::vector<uint8_t> buf_;
|
||||
|
||||
DenseBin(const DenseBin<VAL_T, IS_4BIT>& other)
|
||||
: num_data_(other.num_data_), data_(other.data_) {}
|
||||
};
|
||||
|
||||
template <typename VAL_T, bool IS_4BIT>
|
||||
DenseBin<VAL_T, IS_4BIT>* DenseBin<VAL_T, IS_4BIT>::Clone() {
|
||||
return new DenseBin<VAL_T, IS_4BIT>(*this);
|
||||
}
|
||||
|
||||
template <typename VAL_T, bool IS_4BIT>
|
||||
uint32_t DenseBinIterator<VAL_T, IS_4BIT>::Get(data_size_t idx) {
|
||||
auto ret = bin_data_->data(idx);
|
||||
if (ret >= min_bin_ && ret <= max_bin_) {
|
||||
return ret - min_bin_ + offset_;
|
||||
} else {
|
||||
return most_freq_bin_;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T, bool IS_4BIT>
|
||||
inline uint32_t DenseBinIterator<VAL_T, IS_4BIT>::RawGet(data_size_t idx) {
|
||||
return bin_data_->data(idx);
|
||||
}
|
||||
|
||||
template <typename VAL_T, bool IS_4BIT>
|
||||
BinIterator* DenseBin<VAL_T, IS_4BIT>::GetIterator(
|
||||
uint32_t min_bin, uint32_t max_bin, uint32_t most_freq_bin) const {
|
||||
return new DenseBinIterator<VAL_T, IS_4BIT>(this, min_bin, max_bin,
|
||||
most_freq_bin);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_IO_DENSE_BIN_HPP_
|
||||
@@ -0,0 +1,80 @@
|
||||
/*!
|
||||
* Copyright (c) 2018-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2018-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#include <LightGBM/utils/file_io.h>
|
||||
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
struct LocalFile : VirtualFileReader, VirtualFileWriter {
|
||||
LocalFile(const std::string& filename, const std::string& mode)
|
||||
: filename_(filename), mode_(mode) {}
|
||||
virtual ~LocalFile() {
|
||||
if (file_ != NULL) {
|
||||
fclose(file_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Init() {
|
||||
if (file_ == NULL) {
|
||||
#if _MSC_VER
|
||||
fopen_s(&file_, filename_.c_str(), mode_.c_str());
|
||||
#else
|
||||
file_ = fopen(filename_.c_str(), mode_.c_str());
|
||||
#endif
|
||||
}
|
||||
return file_ != NULL;
|
||||
}
|
||||
|
||||
bool Exists() const {
|
||||
LocalFile file(filename_, "rb");
|
||||
return file.Init();
|
||||
}
|
||||
|
||||
size_t Read(void* buffer, size_t bytes) const {
|
||||
return fread(buffer, 1, bytes, file_);
|
||||
}
|
||||
|
||||
size_t Write(const void* buffer, size_t bytes) {
|
||||
size_t bytes_written = fwrite(buffer, 1, bytes, file_);
|
||||
if (bytes_written != bytes) {
|
||||
Log::Fatal(
|
||||
"Cannot write binary data to %s, wrote %zu of %zu bytes",
|
||||
filename_.c_str(), bytes_written, bytes);
|
||||
}
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
private:
|
||||
FILE* file_ = NULL;
|
||||
const std::string filename_;
|
||||
const std::string mode_;
|
||||
};
|
||||
|
||||
std::unique_ptr<VirtualFileReader> VirtualFileReader::Make(
|
||||
const std::string& filename) {
|
||||
return std::unique_ptr<VirtualFileReader>(new LocalFile(filename, "rb"));
|
||||
}
|
||||
|
||||
std::unique_ptr<VirtualFileWriter> VirtualFileWriter::Make(
|
||||
const std::string& filename) {
|
||||
return std::unique_ptr<VirtualFileWriter>(new LocalFile(filename, "wb"));
|
||||
}
|
||||
|
||||
bool VirtualFileWriter::Exists(const std::string& filename) {
|
||||
LocalFile file(filename, "rb");
|
||||
return file.Exists();
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,785 @@
|
||||
/* Copyright (c) 2013 Dropbox, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#include <LightGBM/utils/json11.h>
|
||||
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace json11_internal_lightgbm {
|
||||
|
||||
static const int max_depth = 200;
|
||||
|
||||
using std::initializer_list;
|
||||
using std::make_shared;
|
||||
using std::map;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
using LightGBM::Log;
|
||||
|
||||
/* Helper for representing null - just a do-nothing struct, plus comparison
|
||||
* operators so the helpers in JsonValue work. We can't use nullptr_t because
|
||||
* it may not be orderable.
|
||||
*/
|
||||
struct NullStruct {
|
||||
bool operator==(NullStruct) const { return true; }
|
||||
bool operator<(NullStruct) const { return false; }
|
||||
};
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Serialization
|
||||
*/
|
||||
|
||||
static void dump(NullStruct, string *out) { *out += "null"; }
|
||||
|
||||
static void dump(double value, string *out) {
|
||||
if (std::isfinite(value)) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof buf, "%.17g", value);
|
||||
*out += buf;
|
||||
} else {
|
||||
*out += "null";
|
||||
}
|
||||
}
|
||||
|
||||
static void dump(int value, string *out) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof buf, "%d", value);
|
||||
*out += buf;
|
||||
}
|
||||
|
||||
static void dump(bool value, string *out) { *out += value ? "true" : "false"; }
|
||||
|
||||
static void dump(const string &value, string *out) {
|
||||
*out += '"';
|
||||
for (size_t i = 0; i < value.length(); i++) {
|
||||
const char ch = value[i];
|
||||
if (ch == '\\') {
|
||||
*out += "\\\\";
|
||||
} else if (ch == '"') {
|
||||
*out += "\\\"";
|
||||
} else if (ch == '\b') {
|
||||
*out += "\\b";
|
||||
} else if (ch == '\f') {
|
||||
*out += "\\f";
|
||||
} else if (ch == '\n') {
|
||||
*out += "\\n";
|
||||
} else if (ch == '\r') {
|
||||
*out += "\\r";
|
||||
} else if (ch == '\t') {
|
||||
*out += "\\t";
|
||||
} else if (static_cast<uint8_t>(ch) <= 0x1f) {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof buf, "\\u%04x", ch);
|
||||
*out += buf;
|
||||
} else if (static_cast<uint8_t>(ch) == 0xe2 &&
|
||||
static_cast<uint8_t>(value[i + 1]) == 0x80 &&
|
||||
static_cast<uint8_t>(value[i + 2]) == 0xa8) {
|
||||
*out += "\\u2028";
|
||||
i += 2;
|
||||
} else if (static_cast<uint8_t>(ch) == 0xe2 &&
|
||||
static_cast<uint8_t>(value[i + 1]) == 0x80 &&
|
||||
static_cast<uint8_t>(value[i + 2]) == 0xa9) {
|
||||
*out += "\\u2029";
|
||||
i += 2;
|
||||
} else {
|
||||
*out += ch;
|
||||
}
|
||||
}
|
||||
*out += '"';
|
||||
}
|
||||
|
||||
static void dump(const Json::array &values, string *out) {
|
||||
bool first = true;
|
||||
*out += "[";
|
||||
for (const auto &value : values) {
|
||||
if (!first) *out += ", ";
|
||||
value.dump(out);
|
||||
first = false;
|
||||
}
|
||||
*out += "]";
|
||||
}
|
||||
|
||||
static void dump(const Json::object &values, string *out) {
|
||||
bool first = true;
|
||||
*out += "{";
|
||||
for (const auto &kv : values) {
|
||||
if (!first) *out += ", ";
|
||||
dump(kv.first, out);
|
||||
*out += ": ";
|
||||
kv.second.dump(out);
|
||||
first = false;
|
||||
}
|
||||
*out += "}";
|
||||
}
|
||||
|
||||
void Json::dump(string *out) const { m_ptr->dump(out); }
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Value wrappers
|
||||
*/
|
||||
|
||||
template <Json::Type tag, typename T>
|
||||
class Value : public JsonValue {
|
||||
protected:
|
||||
// Constructors
|
||||
explicit Value(const T &value) : m_value(value) {}
|
||||
explicit Value(T &&value) : m_value(std::move(value)) {}
|
||||
|
||||
// Get type tag
|
||||
Json::Type type() const override { return tag; }
|
||||
|
||||
// Comparisons
|
||||
bool equals(const JsonValue *other) const override {
|
||||
return m_value == static_cast<const Value<tag, T> *>(other)->m_value;
|
||||
}
|
||||
bool less(const JsonValue *other) const override {
|
||||
return m_value < (static_cast<const Value<tag, T> *>(other)->m_value);
|
||||
}
|
||||
|
||||
const T m_value;
|
||||
void dump(string *out) const override { json11_internal_lightgbm::dump(m_value, out); }
|
||||
};
|
||||
|
||||
class JsonDouble final : public Value<Json::NUMBER, double> {
|
||||
double number_value() const override { return m_value; }
|
||||
int int_value() const override { return static_cast<int>(m_value); }
|
||||
bool equals(const JsonValue *other) const override {
|
||||
return m_value == other->number_value();
|
||||
}
|
||||
bool less(const JsonValue *other) const override {
|
||||
return m_value < other->number_value();
|
||||
}
|
||||
|
||||
public:
|
||||
explicit JsonDouble(double value) : Value(value) {}
|
||||
};
|
||||
|
||||
class JsonInt final : public Value<Json::NUMBER, int> {
|
||||
double number_value() const override { return m_value; }
|
||||
int int_value() const override { return m_value; }
|
||||
bool equals(const JsonValue *other) const override {
|
||||
return m_value == other->number_value();
|
||||
}
|
||||
bool less(const JsonValue *other) const override {
|
||||
return m_value < other->number_value();
|
||||
}
|
||||
|
||||
public:
|
||||
explicit JsonInt(int value) : Value(value) {}
|
||||
};
|
||||
|
||||
class JsonBoolean final : public Value<Json::BOOL, bool> {
|
||||
bool bool_value() const override { return m_value; }
|
||||
|
||||
public:
|
||||
explicit JsonBoolean(bool value) : Value(value) {}
|
||||
};
|
||||
|
||||
class JsonString final : public Value<Json::STRING, string> {
|
||||
const string &string_value() const override { return m_value; }
|
||||
|
||||
public:
|
||||
explicit JsonString(const string &value) : Value(value) {}
|
||||
explicit JsonString(string &&value) : Value(std::move(value)) {}
|
||||
};
|
||||
|
||||
class JsonArray final : public Value<Json::ARRAY, Json::array> {
|
||||
const Json::array &array_items() const override { return m_value; }
|
||||
const Json &operator[](size_t i) const override;
|
||||
|
||||
public:
|
||||
explicit JsonArray(const Json::array &value) : Value(value) {}
|
||||
explicit JsonArray(Json::array &&value) : Value(std::move(value)) {}
|
||||
};
|
||||
|
||||
class JsonObject final : public Value<Json::OBJECT, Json::object> {
|
||||
const Json::object &object_items() const override { return m_value; }
|
||||
const Json &operator[](const string &key) const override;
|
||||
|
||||
public:
|
||||
explicit JsonObject(const Json::object &value) : Value(value) {}
|
||||
explicit JsonObject(Json::object &&value) : Value(std::move(value)) {}
|
||||
};
|
||||
|
||||
class JsonNull final : public Value<Json::NUL, NullStruct> {
|
||||
public:
|
||||
JsonNull() : Value({}) {}
|
||||
};
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Static globals - static-init-safe
|
||||
*/
|
||||
struct Statics {
|
||||
const std::shared_ptr<JsonValue> null = make_shared<JsonNull>();
|
||||
const std::shared_ptr<JsonValue> t = make_shared<JsonBoolean>(true);
|
||||
const std::shared_ptr<JsonValue> f = make_shared<JsonBoolean>(false);
|
||||
const string empty_string;
|
||||
const vector<Json> empty_vector;
|
||||
const map<string, Json> empty_map;
|
||||
Statics() {}
|
||||
};
|
||||
|
||||
static const Statics &statics() {
|
||||
static const Statics s{};
|
||||
return s;
|
||||
}
|
||||
|
||||
static const Json &static_null() {
|
||||
// This has to be separate, not in Statics, because Json() accesses
|
||||
// statics().null.
|
||||
static const Json json_null;
|
||||
return json_null;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Constructors
|
||||
*/
|
||||
|
||||
Json::Json() noexcept : m_ptr(statics().null) {}
|
||||
Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {}
|
||||
Json::Json(double value) : m_ptr(make_shared<JsonDouble>(value)) {}
|
||||
Json::Json(int value) : m_ptr(make_shared<JsonInt>(value)) {}
|
||||
Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {}
|
||||
Json::Json(const string &value) : m_ptr(make_shared<JsonString>(value)) {}
|
||||
Json::Json(string &&value) : m_ptr(make_shared<JsonString>(std::move(value))) {}
|
||||
Json::Json(const char *value) : m_ptr(make_shared<JsonString>(value)) {}
|
||||
Json::Json(const Json::array &values) : m_ptr(make_shared<JsonArray>(values)) {}
|
||||
Json::Json(Json::array &&values)
|
||||
: m_ptr(make_shared<JsonArray>(std::move(values))) {}
|
||||
Json::Json(const Json::object &values)
|
||||
: m_ptr(make_shared<JsonObject>(values)) {}
|
||||
Json::Json(Json::object &&values)
|
||||
: m_ptr(make_shared<JsonObject>(std::move(values))) {}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Accessors
|
||||
*/
|
||||
|
||||
Json::Type Json::type() const { return m_ptr->type(); }
|
||||
double Json::number_value() const { return m_ptr->number_value(); }
|
||||
int Json::int_value() const { return m_ptr->int_value(); }
|
||||
bool Json::bool_value() const { return m_ptr->bool_value(); }
|
||||
const string &Json::string_value() const { return m_ptr->string_value(); }
|
||||
const vector<Json> &Json::array_items() const { return m_ptr->array_items(); }
|
||||
const map<string, Json> &Json::object_items() const {
|
||||
return m_ptr->object_items();
|
||||
}
|
||||
const Json &Json::operator[](size_t i) const { return (*m_ptr)[i]; }
|
||||
const Json &Json::operator[](const string &key) const { return (*m_ptr)[key]; }
|
||||
|
||||
double JsonValue::number_value() const { return 0; }
|
||||
int JsonValue::int_value() const { return 0; }
|
||||
bool JsonValue::bool_value() const { return false; }
|
||||
const string &JsonValue::string_value() const { return statics().empty_string; }
|
||||
const vector<Json> &JsonValue::array_items() const {
|
||||
return statics().empty_vector;
|
||||
}
|
||||
const map<string, Json> &JsonValue::object_items() const {
|
||||
return statics().empty_map;
|
||||
}
|
||||
const Json &JsonValue::operator[](size_t) const { return static_null(); }
|
||||
const Json &JsonValue::operator[](const string &) const {
|
||||
return static_null();
|
||||
}
|
||||
|
||||
const Json &JsonObject::operator[](const string &key) const {
|
||||
auto iter = m_value.find(key);
|
||||
return (iter == m_value.end()) ? static_null() : iter->second;
|
||||
}
|
||||
const Json &JsonArray::operator[](size_t i) const {
|
||||
if (i >= m_value.size())
|
||||
return static_null();
|
||||
else
|
||||
return m_value[i];
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Comparison
|
||||
*/
|
||||
|
||||
bool Json::operator==(const Json &other) const {
|
||||
if (m_ptr == other.m_ptr) return true;
|
||||
if (m_ptr->type() != other.m_ptr->type()) return false;
|
||||
|
||||
return m_ptr->equals(other.m_ptr.get());
|
||||
}
|
||||
|
||||
bool Json::operator<(const Json &other) const {
|
||||
if (m_ptr == other.m_ptr) return false;
|
||||
if (m_ptr->type() != other.m_ptr->type())
|
||||
return m_ptr->type() < other.m_ptr->type();
|
||||
|
||||
return m_ptr->less(other.m_ptr.get());
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Parsing
|
||||
*/
|
||||
|
||||
/* esc(c)
|
||||
*
|
||||
* Format char c suitable for printing in an error message.
|
||||
*/
|
||||
static inline string esc(char c) {
|
||||
char buf[12];
|
||||
if (static_cast<uint8_t>(c) >= 0x20 && static_cast<uint8_t>(c) <= 0x7f) {
|
||||
snprintf(buf, sizeof buf, "'%c' (%d)", c, c);
|
||||
} else {
|
||||
snprintf(buf, sizeof buf, "(%d)", c);
|
||||
}
|
||||
return string(buf);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline bool in_range(T x, T lower, T upper) {
|
||||
return (x >= lower && x <= upper);
|
||||
}
|
||||
|
||||
namespace {
|
||||
/* JsonParser
|
||||
*
|
||||
* Object that tracks all state of an in-progress parse.
|
||||
*/
|
||||
struct JsonParser final {
|
||||
/* State
|
||||
*/
|
||||
const char *str;
|
||||
const size_t str_len;
|
||||
size_t i;
|
||||
string *err;
|
||||
bool failed;
|
||||
const JsonParse strategy;
|
||||
|
||||
/* fail(msg, err_ret = Json())
|
||||
*
|
||||
* Mark this parse as failed.
|
||||
*/
|
||||
Json fail(string &&msg) { return fail(std::move(msg), Json()); }
|
||||
|
||||
template <typename T>
|
||||
T fail(string &&msg, const T err_ret) {
|
||||
if (!failed) *err = std::move(msg);
|
||||
failed = true;
|
||||
return err_ret;
|
||||
}
|
||||
|
||||
/* consume_whitespace()
|
||||
*
|
||||
* Advance until the current character is non-whitespace.
|
||||
*/
|
||||
void consume_whitespace() {
|
||||
while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')
|
||||
i++;
|
||||
}
|
||||
|
||||
/* consume_comment()
|
||||
*
|
||||
* Advance comments (c-style inline and multiline).
|
||||
*/
|
||||
bool consume_comment() {
|
||||
bool comment_found = false;
|
||||
if (str[i] == '/') {
|
||||
i++;
|
||||
if (i == str_len)
|
||||
return fail("Unexpected end of input after start of comment", false);
|
||||
if (str[i] == '/') { // inline comment
|
||||
i++;
|
||||
// advance until next line, or end of input
|
||||
while (i < str_len && str[i] != '\n') {
|
||||
i++;
|
||||
}
|
||||
comment_found = true;
|
||||
} else if (str[i] == '*') { // multiline comment
|
||||
i++;
|
||||
if (i > str_len - 2)
|
||||
return fail("Unexpected end of input inside multi-line comment",
|
||||
false);
|
||||
// advance until closing tokens
|
||||
while (!(str[i] == '*' && str[i + 1] == '/')) {
|
||||
i++;
|
||||
if (i > str_len - 2)
|
||||
return fail("Unexpected end of input inside multi-line comment",
|
||||
false);
|
||||
}
|
||||
i += 2;
|
||||
comment_found = true;
|
||||
} else {
|
||||
return fail("Malformed comment", false);
|
||||
}
|
||||
}
|
||||
return comment_found;
|
||||
}
|
||||
|
||||
/* consume_garbage()
|
||||
*
|
||||
* Advance until the current character is non-whitespace and non-comment.
|
||||
*/
|
||||
void consume_garbage() {
|
||||
consume_whitespace();
|
||||
if (strategy == JsonParse::COMMENTS) {
|
||||
bool comment_found = false;
|
||||
do {
|
||||
comment_found = consume_comment();
|
||||
if (failed) return;
|
||||
consume_whitespace();
|
||||
} while (comment_found);
|
||||
}
|
||||
}
|
||||
|
||||
/* get_next_token()
|
||||
*
|
||||
* Return the next non-whitespace character. If the end of the input is
|
||||
* reached, flag an error and return 0.
|
||||
*/
|
||||
char get_next_token() {
|
||||
consume_garbage();
|
||||
if (failed) return char{0};
|
||||
if (i == str_len) return fail("Unexpected end of input", char{0});
|
||||
|
||||
return str[i++];
|
||||
}
|
||||
|
||||
/* encode_utf8(pt, out)
|
||||
*
|
||||
* Encode pt as UTF-8 and add it to out.
|
||||
*/
|
||||
void encode_utf8(int64_t pt, string* out) {
|
||||
if (pt < 0) return;
|
||||
|
||||
if (pt < 0x80) {
|
||||
*out += static_cast<char>(pt);
|
||||
} else if (pt < 0x800) {
|
||||
*out += static_cast<char>((pt >> 6) | 0xC0);
|
||||
*out += static_cast<char>((pt & 0x3F) | 0x80);
|
||||
} else if (pt < 0x10000) {
|
||||
*out += static_cast<char>((pt >> 12) | 0xE0);
|
||||
*out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
|
||||
*out += static_cast<char>((pt & 0x3F) | 0x80);
|
||||
} else {
|
||||
*out += static_cast<char>((pt >> 18) | 0xF0);
|
||||
*out += static_cast<char>(((pt >> 12) & 0x3F) | 0x80);
|
||||
*out += static_cast<char>(((pt >> 6) & 0x3F) | 0x80);
|
||||
*out += static_cast<char>((pt & 0x3F) | 0x80);
|
||||
}
|
||||
}
|
||||
|
||||
/* parse_string()
|
||||
*
|
||||
* Parse a string, starting at the current position.
|
||||
*/
|
||||
string parse_string() {
|
||||
string out;
|
||||
int64_t last_escaped_codepoint = -1;
|
||||
while (true) {
|
||||
if (i == str_len) return fail("Unexpected end of input in string", "");
|
||||
|
||||
char ch = str[i++];
|
||||
|
||||
if (ch == '"') {
|
||||
encode_utf8(last_escaped_codepoint, &out);
|
||||
return out;
|
||||
}
|
||||
|
||||
if (in_range<int64_t>(ch, 0, 0x1f))
|
||||
return fail("Unescaped " + esc(ch) + " in string", "");
|
||||
|
||||
// The usual case: non-escaped characters
|
||||
if (ch != '\\') {
|
||||
encode_utf8(last_escaped_codepoint, &out);
|
||||
last_escaped_codepoint = -1;
|
||||
out += ch;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle escapes
|
||||
if (i == str_len) return fail("Unexpected end of input in string", "");
|
||||
|
||||
ch = str[i++];
|
||||
|
||||
if (ch == 'u') {
|
||||
// Extract 4-byte escape sequence
|
||||
string esc = string(str + i, 4);
|
||||
// Explicitly check length of the substring. The following loop
|
||||
// relies on std::string returning the terminating NUL when
|
||||
// accessing str[length]. Checking here reduces brittleness.
|
||||
if (esc.length() < 4) {
|
||||
return fail("Bad \\u escape: " + esc, "");
|
||||
}
|
||||
for (size_t j = 0; j < 4; j++) {
|
||||
if (!in_range(esc[j], 'a', 'f') && !in_range(esc[j], 'A', 'F') &&
|
||||
!in_range(esc[j], '0', '9'))
|
||||
return fail("Bad \\u escape: " + esc, "");
|
||||
}
|
||||
|
||||
int64_t codepoint =
|
||||
static_cast<int64_t>(strtol(esc.data(), nullptr, 16));
|
||||
|
||||
// JSON specifies that characters outside the BMP shall be encoded as a
|
||||
// pair of 4-hex-digit \u escapes encoding their surrogate pair
|
||||
// components. Check whether we're in the middle of such a beast: the
|
||||
// previous codepoint was an escaped lead (high) surrogate, and this is
|
||||
// a trail (low) surrogate.
|
||||
if (in_range<int64_t>(last_escaped_codepoint, 0xD800, 0xDBFF) &&
|
||||
in_range<int64_t>(codepoint, 0xDC00, 0xDFFF)) {
|
||||
// Reassemble the two surrogate pairs into one astral-plane character,
|
||||
// per the UTF-16 algorithm.
|
||||
encode_utf8((((last_escaped_codepoint - 0xD800) << 10) |
|
||||
(codepoint - 0xDC00)) +
|
||||
0x10000,
|
||||
&out);
|
||||
last_escaped_codepoint = -1;
|
||||
} else {
|
||||
encode_utf8(last_escaped_codepoint, &out);
|
||||
last_escaped_codepoint = codepoint;
|
||||
}
|
||||
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
encode_utf8(last_escaped_codepoint, &out);
|
||||
last_escaped_codepoint = -1;
|
||||
|
||||
if (ch == 'b') {
|
||||
out += '\b';
|
||||
} else if (ch == 'f') {
|
||||
out += '\f';
|
||||
} else if (ch == 'n') {
|
||||
out += '\n';
|
||||
} else if (ch == 'r') {
|
||||
out += '\r';
|
||||
} else if (ch == 't') {
|
||||
out += '\t';
|
||||
} else if (ch == '"' || ch == '\\' || ch == '/') {
|
||||
out += ch;
|
||||
} else {
|
||||
return fail("Invalid escape character " + esc(ch), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* parse_number()
|
||||
*
|
||||
* Parse a double.
|
||||
*/
|
||||
Json parse_number() {
|
||||
size_t start_pos = i;
|
||||
|
||||
if (str[i] == '-') i++;
|
||||
|
||||
// Integer part
|
||||
if (str[i] == '0') {
|
||||
i++;
|
||||
if (in_range(str[i], '0', '9'))
|
||||
return fail("Leading 0s not permitted in numbers");
|
||||
} else if (in_range(str[i], '1', '9')) {
|
||||
i++;
|
||||
while (in_range(str[i], '0', '9')) i++;
|
||||
} else {
|
||||
return fail("Invalid " + esc(str[i]) + " in number");
|
||||
}
|
||||
|
||||
if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' &&
|
||||
(i - start_pos) <=
|
||||
static_cast<size_t>(std::numeric_limits<int>::digits10)) {
|
||||
return Json(std::atoi(str + start_pos));
|
||||
}
|
||||
|
||||
// Decimal part
|
||||
if (str[i] == '.') {
|
||||
i++;
|
||||
if (!in_range(str[i], '0', '9'))
|
||||
return fail("At least one digit required in fractional part");
|
||||
|
||||
while (in_range(str[i], '0', '9')) i++;
|
||||
}
|
||||
|
||||
// Exponent part
|
||||
if (str[i] == 'e' || str[i] == 'E') {
|
||||
i++;
|
||||
|
||||
if (str[i] == '+' || str[i] == '-') i++;
|
||||
|
||||
if (!in_range(str[i], '0', '9'))
|
||||
return fail("At least one digit required in exponent");
|
||||
|
||||
while (in_range(str[i], '0', '9')) i++;
|
||||
}
|
||||
|
||||
return Json(std::strtod(str + start_pos, nullptr));
|
||||
}
|
||||
|
||||
/* expect(str, res)
|
||||
*
|
||||
* Expect that 'str' starts at the character that was just read. If it does,
|
||||
* advance the input and return res. If not, flag an error.
|
||||
*/
|
||||
Json expect(const string &expected, Json res) {
|
||||
CHECK_NE(i, 0)
|
||||
i--;
|
||||
auto substr = string(str + i, expected.length());
|
||||
if (substr == expected) {
|
||||
i += expected.length();
|
||||
return res;
|
||||
} else {
|
||||
return fail("Parse error: expected " + expected + ", got " + substr);
|
||||
}
|
||||
}
|
||||
|
||||
/* parse_json()
|
||||
*
|
||||
* Parse a JSON object.
|
||||
*/
|
||||
Json parse_json(int depth) {
|
||||
if (depth > max_depth) {
|
||||
return fail("Exceeded maximum nesting depth");
|
||||
}
|
||||
|
||||
char ch = get_next_token();
|
||||
if (failed) return Json();
|
||||
|
||||
if (ch == '-' || (ch >= '0' && ch <= '9')) {
|
||||
i--;
|
||||
return parse_number();
|
||||
}
|
||||
|
||||
if (ch == 't') return expect("true", Json(true));
|
||||
|
||||
if (ch == 'f') return expect("false", Json(false));
|
||||
|
||||
if (ch == 'n') return expect("null", Json());
|
||||
|
||||
if (ch == '"') return Json(parse_string());
|
||||
|
||||
if (ch == '{') {
|
||||
map<string, Json> data;
|
||||
ch = get_next_token();
|
||||
if (ch == '}') return Json(data);
|
||||
|
||||
while (1) {
|
||||
if (ch != '"') return fail("Expected '\"' in object, got " + esc(ch));
|
||||
|
||||
string key = parse_string();
|
||||
if (failed) return Json();
|
||||
|
||||
ch = get_next_token();
|
||||
if (ch != ':') return fail("Expected ':' in object, got " + esc(ch));
|
||||
|
||||
data[std::move(key)] = parse_json(depth + 1);
|
||||
if (failed) return Json();
|
||||
|
||||
ch = get_next_token();
|
||||
if (ch == '}') break;
|
||||
if (ch != ',') return fail("Expected ',' in object, got " + esc(ch));
|
||||
|
||||
ch = get_next_token();
|
||||
}
|
||||
return Json(data);
|
||||
}
|
||||
|
||||
if (ch == '[') {
|
||||
vector<Json> data;
|
||||
ch = get_next_token();
|
||||
if (ch == ']') return Json(data);
|
||||
|
||||
while (1) {
|
||||
i--;
|
||||
data.push_back(parse_json(depth + 1));
|
||||
if (failed) return Json();
|
||||
|
||||
ch = get_next_token();
|
||||
if (ch == ']') break;
|
||||
if (ch != ',') return fail("Expected ',' in list, got " + esc(ch));
|
||||
|
||||
ch = get_next_token();
|
||||
(void)ch;
|
||||
}
|
||||
return Json(data);
|
||||
}
|
||||
|
||||
return fail("Expected value, got " + esc(ch));
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Json Json::parse(const string &in, string *err, JsonParse strategy) {
|
||||
JsonParser parser{in.c_str(), in.size(), 0, err, false, strategy};
|
||||
Json result = parser.parse_json(0);
|
||||
|
||||
// Check for any trailing garbage
|
||||
parser.consume_garbage();
|
||||
if (parser.failed) return Json();
|
||||
if (parser.i != in.size())
|
||||
return parser.fail("Unexpected trailing " + esc(in[parser.i]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Documented in json11.hpp
|
||||
vector<Json> Json::parse_multi(const string &in,
|
||||
std::string::size_type *parser_stop_pos,
|
||||
string *err, JsonParse strategy) {
|
||||
JsonParser parser{in.c_str(), in.size(), 0, err, false, strategy};
|
||||
*parser_stop_pos = 0;
|
||||
vector<Json> json_vec;
|
||||
while (parser.i != in.size() && !parser.failed) {
|
||||
json_vec.push_back(parser.parse_json(0));
|
||||
if (parser.failed) break;
|
||||
|
||||
// Check for another object
|
||||
parser.consume_garbage();
|
||||
if (parser.failed) break;
|
||||
*parser_stop_pos = parser.i;
|
||||
}
|
||||
return json_vec;
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * *
|
||||
* Shape-checking
|
||||
*/
|
||||
|
||||
bool Json::has_shape(const shape &types, string *err) const {
|
||||
if (!is_object()) {
|
||||
*err = "Expected JSON object, got " + dump();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto &item : types) {
|
||||
if ((*this)[item.first].type() != item.second) {
|
||||
*err = "Bad type for " + item.first + " in " + dump();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace json11_internal_lightgbm
|
||||
@@ -0,0 +1,946 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/dataset.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#ifndef LGB_R_BUILD
|
||||
#include "../arrow/array.hpp"
|
||||
#endif // LGB_R_BUILD
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
Metadata::Metadata() {
|
||||
num_weights_ = 0;
|
||||
num_init_score_ = 0;
|
||||
num_data_ = 0;
|
||||
num_queries_ = 0;
|
||||
num_positions_ = 0;
|
||||
weight_load_from_file_ = false;
|
||||
position_load_from_file_ = false;
|
||||
query_load_from_file_ = false;
|
||||
init_score_load_from_file_ = false;
|
||||
#ifdef USE_CUDA
|
||||
cuda_metadata_ = nullptr;
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
void Metadata::Init(const char* data_filename) {
|
||||
data_filename_ = data_filename;
|
||||
// for lambdarank, it needs query data for partition data in distributed learning
|
||||
LoadQueryBoundaries();
|
||||
LoadWeights();
|
||||
LoadPositions();
|
||||
CalculateQueryWeights();
|
||||
LoadInitialScore(data_filename_);
|
||||
}
|
||||
|
||||
Metadata::~Metadata() {
|
||||
}
|
||||
|
||||
void Metadata::Init(data_size_t num_data, int weight_idx, int query_idx) {
|
||||
num_data_ = num_data;
|
||||
label_ = std::vector<label_t>(num_data_);
|
||||
if (weight_idx >= 0) {
|
||||
if (!weights_.empty()) {
|
||||
Log::Info("Using weights in data file, ignoring the additional weights file");
|
||||
weights_.clear();
|
||||
}
|
||||
weights_ = std::vector<label_t>(num_data_, 0.0f);
|
||||
num_weights_ = num_data_;
|
||||
weight_load_from_file_ = false;
|
||||
}
|
||||
if (query_idx >= 0) {
|
||||
if (!query_boundaries_.empty()) {
|
||||
Log::Info("Using query id in data file, ignoring the additional query file");
|
||||
query_boundaries_.clear();
|
||||
}
|
||||
if (!query_weights_.empty()) {
|
||||
query_weights_.clear();
|
||||
}
|
||||
queries_ = std::vector<data_size_t>(num_data_, 0);
|
||||
query_load_from_file_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::InitByReference(data_size_t num_data, const Metadata* reference) {
|
||||
int has_weights = reference->num_weights_ > 0;
|
||||
int has_init_scores = reference->num_init_score_ > 0;
|
||||
int has_queries = reference->num_queries_ > 0;
|
||||
int nclasses = reference->num_init_score_classes();
|
||||
Init(num_data, has_weights, has_init_scores, has_queries, nclasses);
|
||||
}
|
||||
|
||||
void Metadata::Init(data_size_t num_data, int32_t has_weights, int32_t has_init_scores, int32_t has_queries, int32_t nclasses) {
|
||||
num_data_ = num_data;
|
||||
label_ = std::vector<label_t>(num_data_);
|
||||
if (has_weights) {
|
||||
if (!weights_.empty()) {
|
||||
Log::Fatal("Calling Init() on Metadata weights that have already been initialized");
|
||||
}
|
||||
weights_.resize(num_data_, 0.0f);
|
||||
num_weights_ = num_data_;
|
||||
weight_load_from_file_ = false;
|
||||
}
|
||||
if (has_init_scores) {
|
||||
if (!init_score_.empty()) {
|
||||
Log::Fatal("Calling Init() on Metadata initial scores that have already been initialized");
|
||||
}
|
||||
num_init_score_ = static_cast<int64_t>(num_data) * nclasses;
|
||||
init_score_.resize(num_init_score_, 0);
|
||||
}
|
||||
if (has_queries) {
|
||||
if (!query_weights_.empty()) {
|
||||
Log::Fatal("Calling Init() on Metadata queries that have already been initialized");
|
||||
}
|
||||
queries_.resize(num_data_, 0);
|
||||
query_load_from_file_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::Init(const Metadata& fullset, const data_size_t* used_indices, data_size_t num_used_indices) {
|
||||
num_data_ = num_used_indices;
|
||||
|
||||
label_.resize(num_used_indices);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_used_indices >= 1024)
|
||||
for (data_size_t i = 0; i < num_used_indices; ++i) {
|
||||
label_[i] = fullset.label_[used_indices[i]];
|
||||
}
|
||||
|
||||
if (!fullset.weights_.empty()) {
|
||||
weights_ = std::vector<label_t>(num_used_indices);
|
||||
num_weights_ = num_used_indices;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_used_indices >= 1024)
|
||||
for (data_size_t i = 0; i < num_used_indices; ++i) {
|
||||
weights_[i] = fullset.weights_[used_indices[i]];
|
||||
}
|
||||
} else {
|
||||
num_weights_ = 0;
|
||||
}
|
||||
|
||||
if (!fullset.init_score_.empty()) {
|
||||
int num_class = static_cast<int>(fullset.num_init_score_ / fullset.num_data_);
|
||||
init_score_ = std::vector<double>(static_cast<size_t>(num_used_indices) * num_class);
|
||||
num_init_score_ = static_cast<int64_t>(num_used_indices) * num_class;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int k = 0; k < num_class; ++k) {
|
||||
const size_t offset_dest = static_cast<size_t>(k) * num_data_;
|
||||
const size_t offset_src = static_cast<size_t>(k) * fullset.num_data_;
|
||||
for (data_size_t i = 0; i < num_used_indices; ++i) {
|
||||
init_score_[offset_dest + i] = fullset.init_score_[offset_src + used_indices[i]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
num_init_score_ = 0;
|
||||
}
|
||||
|
||||
if (!fullset.query_boundaries_.empty()) {
|
||||
std::vector<data_size_t> used_query;
|
||||
data_size_t data_idx = 0;
|
||||
for (data_size_t qid = 0; qid < num_queries_ && data_idx < num_used_indices; ++qid) {
|
||||
data_size_t start = fullset.query_boundaries_[qid];
|
||||
data_size_t end = fullset.query_boundaries_[qid + 1];
|
||||
data_size_t len = end - start;
|
||||
if (used_indices[data_idx] > start) {
|
||||
continue;
|
||||
} else if (used_indices[data_idx] == start) {
|
||||
if (num_used_indices >= data_idx + len && used_indices[data_idx + len - 1] == end - 1) {
|
||||
used_query.push_back(qid);
|
||||
data_idx += len;
|
||||
} else {
|
||||
Log::Fatal("Data partition error, data didn't match queries");
|
||||
}
|
||||
} else {
|
||||
Log::Fatal("Data partition error, data didn't match queries");
|
||||
}
|
||||
}
|
||||
query_boundaries_ = std::vector<data_size_t>(used_query.size() + 1);
|
||||
num_queries_ = static_cast<data_size_t>(used_query.size());
|
||||
query_boundaries_[0] = 0;
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
data_size_t qid = used_query[i];
|
||||
data_size_t len = fullset.query_boundaries_[qid + 1] - fullset.query_boundaries_[qid];
|
||||
query_boundaries_[i + 1] = query_boundaries_[i] + len;
|
||||
}
|
||||
} else {
|
||||
num_queries_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::PartitionLabel(const std::vector<data_size_t>& used_indices) {
|
||||
if (used_indices.empty()) {
|
||||
return;
|
||||
}
|
||||
auto old_label = label_;
|
||||
num_data_ = static_cast<data_size_t>(used_indices.size());
|
||||
label_ = std::vector<label_t>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
label_[i] = old_label[used_indices[i]];
|
||||
}
|
||||
old_label.clear();
|
||||
}
|
||||
|
||||
void Metadata::CalculateQueryBoundaries() {
|
||||
if (!queries_.empty()) {
|
||||
// need convert query_id to boundaries
|
||||
std::vector<data_size_t> tmp_buffer;
|
||||
data_size_t last_qid = -1;
|
||||
data_size_t cur_cnt = 0;
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
if (last_qid != queries_[i]) {
|
||||
if (cur_cnt > 0) {
|
||||
tmp_buffer.push_back(cur_cnt);
|
||||
}
|
||||
cur_cnt = 0;
|
||||
last_qid = queries_[i];
|
||||
}
|
||||
++cur_cnt;
|
||||
}
|
||||
tmp_buffer.push_back(cur_cnt);
|
||||
query_boundaries_ = std::vector<data_size_t>(tmp_buffer.size() + 1);
|
||||
num_queries_ = static_cast<data_size_t>(tmp_buffer.size());
|
||||
query_boundaries_[0] = 0;
|
||||
for (size_t i = 0; i < tmp_buffer.size(); ++i) {
|
||||
query_boundaries_[i + 1] = query_boundaries_[i] + tmp_buffer[i];
|
||||
}
|
||||
CalculateQueryWeights();
|
||||
queries_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::CheckOrPartition(data_size_t num_all_data, const std::vector<data_size_t>& used_data_indices) {
|
||||
if (used_data_indices.empty()) {
|
||||
CalculateQueryBoundaries();
|
||||
// check weights
|
||||
if (!weights_.empty() && num_weights_ != num_data_) {
|
||||
weights_.clear();
|
||||
num_weights_ = 0;
|
||||
Log::Fatal("Weights size doesn't match data size");
|
||||
}
|
||||
|
||||
// check positions
|
||||
if (!positions_.empty() && num_positions_ != num_data_) {
|
||||
Log::Fatal("Positions size (%i) doesn't match data size (%i)", num_positions_, num_data_);
|
||||
positions_.clear();
|
||||
num_positions_ = 0;
|
||||
}
|
||||
|
||||
// check query boundaries
|
||||
if (!query_boundaries_.empty() && query_boundaries_[num_queries_] != num_data_) {
|
||||
query_boundaries_.clear();
|
||||
num_queries_ = 0;
|
||||
Log::Fatal("Query size doesn't match data size");
|
||||
}
|
||||
|
||||
// contain initial score file
|
||||
if (!init_score_.empty() && (num_init_score_ % num_data_) != 0) {
|
||||
init_score_.clear();
|
||||
num_init_score_ = 0;
|
||||
Log::Fatal("Initial score size doesn't match data size");
|
||||
}
|
||||
} else {
|
||||
if (!queries_.empty()) {
|
||||
Log::Fatal("Cannot used query_id for distributed training");
|
||||
}
|
||||
data_size_t num_used_data = static_cast<data_size_t>(used_data_indices.size());
|
||||
// check weights
|
||||
if (weight_load_from_file_) {
|
||||
if (weights_.size() > 0 && num_weights_ != num_all_data) {
|
||||
weights_.clear();
|
||||
num_weights_ = 0;
|
||||
Log::Fatal("Weights size doesn't match data size");
|
||||
}
|
||||
// get local weights
|
||||
if (!weights_.empty()) {
|
||||
auto old_weights = weights_;
|
||||
num_weights_ = num_data_;
|
||||
weights_ = std::vector<label_t>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512)
|
||||
for (int i = 0; i < static_cast<int>(used_data_indices.size()); ++i) {
|
||||
weights_[i] = old_weights[used_data_indices[i]];
|
||||
}
|
||||
old_weights.clear();
|
||||
}
|
||||
}
|
||||
// check positions
|
||||
if (position_load_from_file_) {
|
||||
if (positions_.size() > 0 && num_positions_ != num_all_data) {
|
||||
positions_.clear();
|
||||
num_positions_ = 0;
|
||||
Log::Fatal("Positions size (%i) doesn't match data size (%i)", num_positions_, num_data_);
|
||||
}
|
||||
// get local positions
|
||||
if (!positions_.empty()) {
|
||||
auto old_positions = positions_;
|
||||
num_positions_ = num_data_;
|
||||
positions_ = std::vector<data_size_t>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512)
|
||||
for (int i = 0; i < static_cast<int>(used_data_indices.size()); ++i) {
|
||||
positions_[i] = old_positions[used_data_indices[i]];
|
||||
}
|
||||
old_positions.clear();
|
||||
}
|
||||
}
|
||||
if (query_load_from_file_) {
|
||||
// check query boundaries
|
||||
if (!query_boundaries_.empty() && query_boundaries_[num_queries_] != num_all_data) {
|
||||
query_boundaries_.clear();
|
||||
num_queries_ = 0;
|
||||
Log::Fatal("Query size doesn't match data size");
|
||||
}
|
||||
// get local query boundaries
|
||||
if (!query_boundaries_.empty()) {
|
||||
std::vector<data_size_t> used_query;
|
||||
data_size_t data_idx = 0;
|
||||
for (data_size_t qid = 0; qid < num_queries_ && data_idx < num_used_data; ++qid) {
|
||||
data_size_t start = query_boundaries_[qid];
|
||||
data_size_t end = query_boundaries_[qid + 1];
|
||||
data_size_t len = end - start;
|
||||
if (used_data_indices[data_idx] > start) {
|
||||
continue;
|
||||
} else if (used_data_indices[data_idx] == start) {
|
||||
if (num_used_data >= data_idx + len && used_data_indices[data_idx + len - 1] == end - 1) {
|
||||
used_query.push_back(qid);
|
||||
data_idx += len;
|
||||
} else {
|
||||
Log::Fatal("Data partition error, data didn't match queries");
|
||||
}
|
||||
} else {
|
||||
Log::Fatal("Data partition error, data didn't match queries");
|
||||
}
|
||||
}
|
||||
auto old_query_boundaries = query_boundaries_;
|
||||
query_boundaries_ = std::vector<data_size_t>(used_query.size() + 1);
|
||||
num_queries_ = static_cast<data_size_t>(used_query.size());
|
||||
query_boundaries_[0] = 0;
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
data_size_t qid = used_query[i];
|
||||
data_size_t len = old_query_boundaries[qid + 1] - old_query_boundaries[qid];
|
||||
query_boundaries_[i + 1] = query_boundaries_[i] + len;
|
||||
}
|
||||
old_query_boundaries.clear();
|
||||
}
|
||||
}
|
||||
if (init_score_load_from_file_) {
|
||||
// contain initial score file
|
||||
if (!init_score_.empty() && (num_init_score_ % num_all_data) != 0) {
|
||||
init_score_.clear();
|
||||
num_init_score_ = 0;
|
||||
Log::Fatal("Initial score size doesn't match data size");
|
||||
}
|
||||
|
||||
// get local initial scores
|
||||
if (!init_score_.empty()) {
|
||||
auto old_scores = init_score_;
|
||||
int num_class = static_cast<int>(num_init_score_ / num_all_data);
|
||||
num_init_score_ = static_cast<int64_t>(num_data_) * num_class;
|
||||
init_score_ = std::vector<double>(num_init_score_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (int k = 0; k < num_class; ++k) {
|
||||
const size_t offset_dest = static_cast<size_t>(k) * num_data_;
|
||||
const size_t offset_src = static_cast<size_t>(k) * num_all_data;
|
||||
for (size_t i = 0; i < used_data_indices.size(); ++i) {
|
||||
init_score_[offset_dest + i] = old_scores[offset_src + used_data_indices[i]];
|
||||
}
|
||||
}
|
||||
old_scores.clear();
|
||||
}
|
||||
}
|
||||
// re-calculate query weight
|
||||
CalculateQueryWeights();
|
||||
}
|
||||
if (num_queries_ > 0) {
|
||||
Log::Debug("Number of queries in %s: %i. Average number of rows per query: %f.",
|
||||
data_filename_.c_str(), static_cast<int>(num_queries_), static_cast<double>(num_data_) / num_queries_);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
void Metadata::SetInitScoresFromIterator(It first, It last) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// Clear init scores on empty input
|
||||
if (last - first == 0) {
|
||||
init_score_.clear();
|
||||
num_init_score_ = 0;
|
||||
return;
|
||||
}
|
||||
if (((last - first) % num_data_) != 0) {
|
||||
Log::Fatal("Initial score size doesn't match data size");
|
||||
}
|
||||
if (init_score_.empty()) {
|
||||
init_score_.resize(last - first);
|
||||
}
|
||||
num_init_score_ = last - first;
|
||||
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_init_score_ >= 1024)
|
||||
for (int64_t i = 0; i < num_init_score_; ++i) {
|
||||
init_score_[i] = Common::AvoidInf(first[i]);
|
||||
}
|
||||
init_score_load_from_file_ = false;
|
||||
|
||||
#ifdef USE_CUDA
|
||||
if (cuda_metadata_ != nullptr) {
|
||||
cuda_metadata_->SetInitScore(init_score_.data(), init_score_.size());
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
void Metadata::SetInitScore(const double* init_score, data_size_t len) {
|
||||
SetInitScoresFromIterator(init_score, init_score + len);
|
||||
}
|
||||
|
||||
#ifndef LGB_R_BUILD
|
||||
ArrowChunkedArray::View InitScoreView(const ArrowChunkedArray& chunked_array) {
|
||||
auto view = chunked_array.view();
|
||||
// For multiclass classification, the init scores are provided in multiple columns. In this
|
||||
// case, we must concatenate all fields of the chunked array.
|
||||
if (chunked_array.is_struct()) {
|
||||
std::vector<ArrowChunkedArray::View> concat_views;
|
||||
concat_views.reserve(chunked_array.get_num_fields());
|
||||
for (int64_t i = 0; i < chunked_array.get_num_fields(); ++i) {
|
||||
concat_views.push_back(view.field(i));
|
||||
}
|
||||
view = ArrowChunkedArray::View(concat_views);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
void Metadata::SetInitScore(struct ArrowArrayStream* stream) {
|
||||
ArrowChunkedArray chunked_array(stream);
|
||||
auto view = InitScoreView(chunked_array);
|
||||
view.visit<double>([&](auto&& visitor) {
|
||||
SetInitScoresFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
|
||||
void Metadata::SetInitScore(int64_t n_chunks, struct ArrowArray* chunks,
|
||||
struct ArrowSchema* schema) {
|
||||
ArrowChunkedArray chunked_array(n_chunks, chunks, schema);
|
||||
auto view = InitScoreView(chunked_array);
|
||||
view.visit<double>([&](auto&& visitor) {
|
||||
SetInitScoresFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
#endif // LGB_R_BUILD
|
||||
|
||||
void Metadata::InsertInitScores(const double* init_scores, data_size_t start_index, data_size_t len, data_size_t source_size) {
|
||||
if (num_init_score_ <= 0) {
|
||||
Log::Fatal("Inserting initial score data into dataset with no initial scores");
|
||||
}
|
||||
if (start_index + len > num_data_) {
|
||||
// Note that len here is row count, not num_init_score, so we compare against num_data
|
||||
Log::Fatal("Inserted initial score data is too large for dataset");
|
||||
}
|
||||
if (init_score_.empty()) {
|
||||
init_score_.resize(num_init_score_);
|
||||
}
|
||||
|
||||
int nclasses = num_init_score_classes();
|
||||
|
||||
for (int32_t col = 0; col < nclasses; ++col) {
|
||||
int32_t dest_offset = num_data_ * col + start_index;
|
||||
// We need to use source_size here, because len might not equal size (due to a partially loaded dataset)
|
||||
int32_t source_offset = source_size * col;
|
||||
memcpy(init_score_.data() + dest_offset, init_scores + source_offset, sizeof(double) * len);
|
||||
}
|
||||
init_score_load_from_file_ = false;
|
||||
// CUDA is handled after all insertions are complete
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
void Metadata::SetLabelsFromIterator(It first, It last) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (num_data_ != last - first) {
|
||||
Log::Fatal("Length of labels differs from the length of #data");
|
||||
}
|
||||
if (label_.empty()) {
|
||||
label_.resize(num_data_);
|
||||
}
|
||||
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_data_ >= 1024)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
label_[i] = Common::AvoidInf(first[i]);
|
||||
}
|
||||
|
||||
#ifdef USE_CUDA
|
||||
if (cuda_metadata_ != nullptr) {
|
||||
cuda_metadata_->SetLabel(label_.data(), label_.size());
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
void Metadata::SetLabel(const label_t* label, data_size_t len) {
|
||||
if (label == nullptr) {
|
||||
Log::Fatal("label cannot be nullptr");
|
||||
}
|
||||
SetLabelsFromIterator(label, label + len);
|
||||
}
|
||||
|
||||
#ifndef LGB_R_BUILD
|
||||
void Metadata::SetLabel(struct ArrowArrayStream* stream) {
|
||||
ArrowChunkedArray chunked_array(stream);
|
||||
chunked_array.view().visit<label_t>([&](auto&& visitor) {
|
||||
SetLabelsFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
|
||||
void Metadata::SetLabel(int64_t n_chunks, struct ArrowArray* chunks,
|
||||
struct ArrowSchema* schema) {
|
||||
ArrowChunkedArray chunked_array(n_chunks, chunks, schema);
|
||||
chunked_array.view().visit<label_t>([&](auto&& visitor) {
|
||||
SetLabelsFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
#endif // LGB_R_BUILD
|
||||
|
||||
void Metadata::InsertLabels(const label_t* labels, data_size_t start_index, data_size_t len) {
|
||||
if (labels == nullptr) {
|
||||
Log::Fatal("label cannot be nullptr");
|
||||
}
|
||||
if (start_index + len > num_data_) {
|
||||
Log::Fatal("Inserted label data is too large for dataset");
|
||||
}
|
||||
if (label_.empty()) {
|
||||
label_.resize(num_data_);
|
||||
}
|
||||
|
||||
memcpy(label_.data() + start_index, labels, sizeof(label_t) * len);
|
||||
|
||||
// CUDA is handled after all insertions are complete
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
void Metadata::SetWeightsFromIterator(It first, It last) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// Clear weights on empty input
|
||||
if (last - first == 0) {
|
||||
weights_.clear();
|
||||
num_weights_ = 0;
|
||||
return;
|
||||
}
|
||||
if (num_data_ != last - first) {
|
||||
Log::Fatal("Length of weights differs from the length of #data");
|
||||
}
|
||||
if (weights_.empty()) {
|
||||
weights_.resize(num_data_);
|
||||
}
|
||||
num_weights_ = num_data_;
|
||||
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_weights_ >= 1024)
|
||||
for (data_size_t i = 0; i < num_weights_; ++i) {
|
||||
weights_[i] = Common::AvoidInf(first[i]);
|
||||
}
|
||||
CalculateQueryWeights();
|
||||
weight_load_from_file_ = false;
|
||||
|
||||
#ifdef USE_CUDA
|
||||
if (cuda_metadata_ != nullptr) {
|
||||
cuda_metadata_->SetWeights(weights_.data(), weights_.size());
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
void Metadata::SetWeights(const label_t* weights, data_size_t len) {
|
||||
SetWeightsFromIterator(weights, weights + len);
|
||||
}
|
||||
|
||||
#ifndef LGB_R_BUILD
|
||||
void Metadata::SetWeights(struct ArrowArrayStream* stream) {
|
||||
ArrowChunkedArray chunked_array(stream);
|
||||
chunked_array.view().visit<label_t>([&](auto&& visitor) {
|
||||
SetWeightsFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
|
||||
void Metadata::SetWeights(int64_t n_chunks, struct ArrowArray* chunks,
|
||||
struct ArrowSchema* schema) {
|
||||
ArrowChunkedArray chunked_array(n_chunks, chunks, schema);
|
||||
chunked_array.view().visit<label_t>([&](auto&& visitor) {
|
||||
SetWeightsFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
#endif // LGB_R_BUILD
|
||||
|
||||
void Metadata::InsertWeights(const label_t* weights, data_size_t start_index, data_size_t len) {
|
||||
if (!weights) {
|
||||
Log::Fatal("Passed null weights");
|
||||
}
|
||||
if (num_weights_ <= 0) {
|
||||
Log::Fatal("Inserting weight data into dataset with no weights");
|
||||
}
|
||||
if (start_index + len > num_weights_) {
|
||||
Log::Fatal("Inserted weight data is too large for dataset");
|
||||
}
|
||||
if (weights_.empty()) {
|
||||
weights_.resize(num_weights_);
|
||||
}
|
||||
|
||||
memcpy(weights_.data() + start_index, weights, sizeof(label_t) * len);
|
||||
|
||||
weight_load_from_file_ = false;
|
||||
// CUDA is handled after all insertions are complete
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
void Metadata::SetQueriesFromIterator(It first, It last) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// Clear query boundaries on empty input
|
||||
if (last - first == 0) {
|
||||
query_boundaries_.clear();
|
||||
num_queries_ = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
data_size_t sum = 0;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum)
|
||||
for (data_size_t i = 0; i < static_cast<data_size_t>(last - first); ++i) {
|
||||
sum += first[i];
|
||||
}
|
||||
if (num_data_ != sum) {
|
||||
Log::Fatal("Sum of query counts (%i) differs from the length of #data (%i)", num_data_, sum);
|
||||
}
|
||||
num_queries_ = last - first;
|
||||
|
||||
query_boundaries_.resize(num_queries_ + 1);
|
||||
query_boundaries_[0] = 0;
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
query_boundaries_[i + 1] = query_boundaries_[i] + first[i];
|
||||
}
|
||||
CalculateQueryWeights();
|
||||
query_load_from_file_ = false;
|
||||
|
||||
#ifdef USE_CUDA
|
||||
if (cuda_metadata_ != nullptr) {
|
||||
if (query_weights_.size() > 0) {
|
||||
CHECK_EQ(query_weights_.size(), static_cast<size_t>(num_queries_));
|
||||
cuda_metadata_->SetQuery(query_boundaries_.data(), query_weights_.data(), num_queries_);
|
||||
} else {
|
||||
cuda_metadata_->SetQuery(query_boundaries_.data(), nullptr, num_queries_);
|
||||
}
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
void Metadata::SetQuery(const data_size_t* query, data_size_t len) {
|
||||
SetQueriesFromIterator(query, query + len);
|
||||
}
|
||||
|
||||
#ifndef LGB_R_BUILD
|
||||
void Metadata::SetQuery(struct ArrowArrayStream* stream) {
|
||||
ArrowChunkedArray chunked_array(stream);
|
||||
chunked_array.view().visit<data_size_t>([&](auto&& visitor) {
|
||||
SetQueriesFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
|
||||
void Metadata::SetQuery(int64_t n_chunks, struct ArrowArray* chunks,
|
||||
struct ArrowSchema* schema) {
|
||||
ArrowChunkedArray chunked_array(n_chunks, chunks, schema);
|
||||
chunked_array.view().visit<data_size_t>([&](auto&& visitor) {
|
||||
SetQueriesFromIterator(visitor.begin(), visitor.end());
|
||||
});
|
||||
}
|
||||
#endif // LGB_R_BUILD
|
||||
|
||||
void Metadata::SetPosition(const data_size_t* positions, data_size_t len) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// save to nullptr
|
||||
if (positions == nullptr || len == 0) {
|
||||
positions_.clear();
|
||||
num_positions_ = 0;
|
||||
return;
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
Log::Fatal("Positions in learning to rank is not supported in CUDA version yet.");
|
||||
#endif // USE_CUDA
|
||||
if (num_data_ != len) {
|
||||
Log::Fatal("Positions size (%i) doesn't match data size (%i)", len, num_data_);
|
||||
}
|
||||
if (positions_.empty()) {
|
||||
positions_.resize(num_data_);
|
||||
} else {
|
||||
Log::Warning("Overwriting positions in dataset.");
|
||||
}
|
||||
num_positions_ = num_data_;
|
||||
|
||||
position_load_from_file_ = false;
|
||||
|
||||
position_ids_.clear();
|
||||
std::unordered_map<data_size_t, int> map_id2pos;
|
||||
for (data_size_t i = 0; i < num_positions_; ++i) {
|
||||
if (map_id2pos.count(positions[i]) == 0) {
|
||||
int pos = static_cast<int>(map_id2pos.size());
|
||||
map_id2pos[positions[i]] = pos;
|
||||
position_ids_.push_back(std::to_string(positions[i]));
|
||||
}
|
||||
}
|
||||
|
||||
Log::Debug("number of unique positions found = %ld", position_ids_.size());
|
||||
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (num_positions_ >= 1024)
|
||||
for (data_size_t i = 0; i < num_positions_; ++i) {
|
||||
positions_[i] = map_id2pos.at(positions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::InsertQueries(const data_size_t* queries, data_size_t start_index, data_size_t len) {
|
||||
if (!queries) {
|
||||
Log::Fatal("Passed null queries");
|
||||
}
|
||||
if (queries_.size() <= 0) {
|
||||
Log::Fatal("Inserting query data into dataset with no queries");
|
||||
}
|
||||
if (static_cast<size_t>(start_index + len) > queries_.size()) {
|
||||
Log::Fatal("Inserted query data is too large for dataset");
|
||||
}
|
||||
|
||||
memcpy(queries_.data() + start_index, queries, sizeof(data_size_t) * len);
|
||||
|
||||
query_load_from_file_ = false;
|
||||
// CUDA is handled after all insertions are complete
|
||||
}
|
||||
|
||||
void Metadata::LoadWeights() {
|
||||
num_weights_ = 0;
|
||||
std::string weight_filename(data_filename_);
|
||||
// default weight file name
|
||||
weight_filename.append(".weight");
|
||||
TextReader<size_t> reader(weight_filename.c_str(), false);
|
||||
reader.ReadAllLines();
|
||||
if (reader.Lines().empty()) {
|
||||
return;
|
||||
}
|
||||
Log::Info("Loading weights...");
|
||||
num_weights_ = static_cast<data_size_t>(reader.Lines().size());
|
||||
weights_ = std::vector<label_t>(num_weights_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_weights_; ++i) {
|
||||
double tmp_weight = 0.0f;
|
||||
Common::Atof(reader.Lines()[i].c_str(), &tmp_weight);
|
||||
weights_[i] = Common::AvoidInf(static_cast<label_t>(tmp_weight));
|
||||
}
|
||||
weight_load_from_file_ = true;
|
||||
}
|
||||
|
||||
void Metadata::LoadPositions() {
|
||||
num_positions_ = 0;
|
||||
std::string position_filename(data_filename_);
|
||||
// default position file name
|
||||
position_filename.append(".position");
|
||||
TextReader<size_t> reader(position_filename.c_str(), false);
|
||||
reader.ReadAllLines();
|
||||
if (reader.Lines().empty()) {
|
||||
return;
|
||||
}
|
||||
Log::Info("Loading positions from %s ...", position_filename.c_str());
|
||||
num_positions_ = static_cast<data_size_t>(reader.Lines().size());
|
||||
positions_ = std::vector<data_size_t>(num_positions_);
|
||||
position_ids_ = std::vector<std::string>();
|
||||
std::unordered_map<std::string, data_size_t> map_id2pos;
|
||||
for (data_size_t i = 0; i < num_positions_; ++i) {
|
||||
std::string& line = reader.Lines()[i];
|
||||
if (map_id2pos.count(line) == 0) {
|
||||
map_id2pos[line] = static_cast<data_size_t>(position_ids_.size());
|
||||
position_ids_.push_back(line);
|
||||
}
|
||||
positions_[i] = map_id2pos.at(line);
|
||||
}
|
||||
position_load_from_file_ = true;
|
||||
}
|
||||
|
||||
void Metadata::LoadInitialScore(const std::string& data_filename) {
|
||||
num_init_score_ = 0;
|
||||
std::string init_score_filename(data_filename);
|
||||
init_score_filename = std::string(data_filename);
|
||||
// default init_score file name
|
||||
init_score_filename.append(".init");
|
||||
TextReader<size_t> reader(init_score_filename.c_str(), false);
|
||||
reader.ReadAllLines();
|
||||
if (reader.Lines().empty()) {
|
||||
return;
|
||||
}
|
||||
Log::Info("Loading initial scores...");
|
||||
|
||||
// use first line to count number class
|
||||
int num_class = static_cast<int>(Common::Split(reader.Lines()[0].c_str(), '\t').size());
|
||||
data_size_t num_line = static_cast<data_size_t>(reader.Lines().size());
|
||||
num_init_score_ = static_cast<int64_t>(num_line) * num_class;
|
||||
|
||||
init_score_ = std::vector<double>(num_init_score_);
|
||||
if (num_class == 1) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_line; ++i) {
|
||||
double tmp = 0.0f;
|
||||
Common::Atof(reader.Lines()[i].c_str(), &tmp);
|
||||
init_score_[i] = Common::AvoidInf(static_cast<double>(tmp));
|
||||
}
|
||||
} else {
|
||||
std::vector<std::string> oneline_init_score;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_line; ++i) {
|
||||
double tmp = 0.0f;
|
||||
oneline_init_score = Common::Split(reader.Lines()[i].c_str(), '\t');
|
||||
if (static_cast<int>(oneline_init_score.size()) != num_class) {
|
||||
Log::Fatal("Invalid initial score file. Redundant or insufficient columns");
|
||||
}
|
||||
for (int k = 0; k < num_class; ++k) {
|
||||
Common::Atof(oneline_init_score[k].c_str(), &tmp);
|
||||
init_score_[static_cast<size_t>(k) * num_line + i] = Common::AvoidInf(static_cast<double>(tmp));
|
||||
}
|
||||
}
|
||||
}
|
||||
init_score_load_from_file_ = true;
|
||||
}
|
||||
|
||||
void Metadata::LoadQueryBoundaries() {
|
||||
num_queries_ = 0;
|
||||
std::string query_filename(data_filename_);
|
||||
// default query file name
|
||||
query_filename.append(".query");
|
||||
TextReader<size_t> reader(query_filename.c_str(), false);
|
||||
reader.ReadAllLines();
|
||||
if (reader.Lines().empty()) {
|
||||
return;
|
||||
}
|
||||
Log::Info("Calculating query boundaries...");
|
||||
query_boundaries_ = std::vector<data_size_t>(reader.Lines().size() + 1);
|
||||
num_queries_ = static_cast<data_size_t>(reader.Lines().size());
|
||||
query_boundaries_[0] = 0;
|
||||
for (size_t i = 0; i < reader.Lines().size(); ++i) {
|
||||
int tmp_cnt;
|
||||
Common::Atoi(reader.Lines()[i].c_str(), &tmp_cnt);
|
||||
query_boundaries_[i + 1] = query_boundaries_[i] + static_cast<data_size_t>(tmp_cnt);
|
||||
}
|
||||
query_load_from_file_ = true;
|
||||
}
|
||||
|
||||
void Metadata::CalculateQueryWeights() {
|
||||
if (weights_.size() == 0 || query_boundaries_.size() == 0) {
|
||||
return;
|
||||
}
|
||||
query_weights_.clear();
|
||||
Log::Info("Calculating query weights...");
|
||||
query_weights_ = std::vector<label_t>(num_queries_);
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
query_weights_[i] = 0.0f;
|
||||
for (data_size_t j = query_boundaries_[i]; j < query_boundaries_[i + 1]; ++j) {
|
||||
query_weights_[i] += weights_[j];
|
||||
}
|
||||
query_weights_[i] /= (query_boundaries_[i + 1] - query_boundaries_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::InsertAt(data_size_t start_index,
|
||||
data_size_t count,
|
||||
const float* labels,
|
||||
const float* weights,
|
||||
const double* init_scores,
|
||||
const int32_t* queries) {
|
||||
if (num_data_ < count + start_index) {
|
||||
Log::Fatal("Length of metadata is too long to append #data");
|
||||
}
|
||||
InsertLabels(labels, start_index, count);
|
||||
if (weights) {
|
||||
InsertWeights(weights, start_index, count);
|
||||
}
|
||||
if (init_scores) {
|
||||
InsertInitScores(init_scores, start_index, count, count);
|
||||
}
|
||||
if (queries) {
|
||||
InsertQueries(queries, start_index, count);
|
||||
}
|
||||
}
|
||||
|
||||
void Metadata::FinishLoad() {
|
||||
CalculateQueryBoundaries();
|
||||
}
|
||||
|
||||
#ifdef USE_CUDA
|
||||
void Metadata::CreateCUDAMetadata(const int gpu_device_id) {
|
||||
cuda_metadata_.reset(new CUDAMetadata(gpu_device_id));
|
||||
cuda_metadata_->Init(label_, weights_, query_boundaries_, query_weights_, init_score_);
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
|
||||
void Metadata::LoadFromMemory(const void* memory) {
|
||||
const char* mem_ptr = reinterpret_cast<const char*>(memory);
|
||||
|
||||
num_data_ = *(reinterpret_cast<const data_size_t*>(mem_ptr));
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(num_data_));
|
||||
num_weights_ = *(reinterpret_cast<const data_size_t*>(mem_ptr));
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(num_weights_));
|
||||
num_queries_ = *(reinterpret_cast<const data_size_t*>(mem_ptr));
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(num_queries_));
|
||||
|
||||
if (!label_.empty()) {
|
||||
label_.clear();
|
||||
}
|
||||
label_ = std::vector<label_t>(num_data_);
|
||||
std::memcpy(label_.data(), mem_ptr, sizeof(label_t) * num_data_);
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(label_t) * num_data_);
|
||||
|
||||
if (num_weights_ > 0) {
|
||||
if (!weights_.empty()) {
|
||||
weights_.clear();
|
||||
}
|
||||
weights_ = std::vector<label_t>(num_weights_);
|
||||
std::memcpy(weights_.data(), mem_ptr, sizeof(label_t) * num_weights_);
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(label_t) * num_weights_);
|
||||
weight_load_from_file_ = true;
|
||||
}
|
||||
if (num_queries_ > 0) {
|
||||
if (!query_boundaries_.empty()) {
|
||||
query_boundaries_.clear();
|
||||
}
|
||||
query_boundaries_ = std::vector<data_size_t>(num_queries_ + 1);
|
||||
std::memcpy(query_boundaries_.data(), mem_ptr, sizeof(data_size_t) * (num_queries_ + 1));
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(data_size_t) *
|
||||
(num_queries_ + 1));
|
||||
query_load_from_file_ = true;
|
||||
}
|
||||
CalculateQueryWeights();
|
||||
}
|
||||
|
||||
void Metadata::SaveBinaryToFile(BinaryWriter* writer) const {
|
||||
writer->AlignedWrite(&num_data_, sizeof(num_data_));
|
||||
writer->AlignedWrite(&num_weights_, sizeof(num_weights_));
|
||||
writer->AlignedWrite(&num_queries_, sizeof(num_queries_));
|
||||
writer->AlignedWrite(label_.data(), sizeof(label_t) * num_data_);
|
||||
if (!weights_.empty()) {
|
||||
writer->AlignedWrite(weights_.data(), sizeof(label_t) * num_weights_);
|
||||
}
|
||||
if (!query_boundaries_.empty()) {
|
||||
writer->AlignedWrite(query_boundaries_.data(),
|
||||
sizeof(data_size_t) * (num_queries_ + 1));
|
||||
}
|
||||
if (num_init_score_ > 0) {
|
||||
Log::Warning("Please note that `init_score` is not saved in binary file.\n"
|
||||
"If you need it, please set it again after loading Dataset.");
|
||||
}
|
||||
}
|
||||
|
||||
size_t Metadata::SizesInByte() const {
|
||||
size_t size = VirtualFileWriter::AlignedSize(sizeof(num_data_)) +
|
||||
VirtualFileWriter::AlignedSize(sizeof(num_weights_)) +
|
||||
VirtualFileWriter::AlignedSize(sizeof(num_queries_));
|
||||
size += VirtualFileWriter::AlignedSize(sizeof(label_t) * num_data_);
|
||||
if (!weights_.empty()) {
|
||||
size += VirtualFileWriter::AlignedSize(sizeof(label_t) * num_weights_);
|
||||
}
|
||||
if (!query_boundaries_.empty()) {
|
||||
size += VirtualFileWriter::AlignedSize(sizeof(data_size_t) *
|
||||
(num_queries_ + 1));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,360 @@
|
||||
/*!
|
||||
* Copyright (c) 2020-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2020-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_IO_MULTI_VAL_DENSE_BIN_HPP_
|
||||
#define LIGHTGBM_SRC_IO_MULTI_VAL_DENSE_BIN_HPP_
|
||||
|
||||
#include <LightGBM/bin.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
#include <LightGBM/utils/threading.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename VAL_T>
|
||||
class MultiValDenseBin : public MultiValBin {
|
||||
public:
|
||||
explicit MultiValDenseBin(data_size_t num_data, int num_bin, int num_feature,
|
||||
const std::vector<uint32_t>& offsets)
|
||||
: num_data_(num_data), num_bin_(num_bin), num_feature_(num_feature),
|
||||
offsets_(offsets) {
|
||||
data_.resize(static_cast<size_t>(num_data_) * num_feature_, static_cast<VAL_T>(0));
|
||||
}
|
||||
|
||||
~MultiValDenseBin() {
|
||||
}
|
||||
|
||||
data_size_t num_data() const override {
|
||||
return num_data_;
|
||||
}
|
||||
|
||||
int num_bin() const override {
|
||||
return num_bin_;
|
||||
}
|
||||
|
||||
double num_element_per_row() const override { return num_feature_; }
|
||||
|
||||
const std::vector<uint32_t>& offsets() const override { return offsets_; }
|
||||
|
||||
void PushOneRow(int , data_size_t idx, const std::vector<uint32_t>& values) override {
|
||||
auto start = RowPtr(idx);
|
||||
for (auto i = 0; i < num_feature_; ++i) {
|
||||
data_[start + i] = static_cast<VAL_T>(values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void FinishLoad() override {
|
||||
}
|
||||
|
||||
bool IsSparse() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<bool USE_INDICES, bool USE_PREFETCH, bool ORDERED>
|
||||
void ConstructHistogramInner(const data_size_t* data_indices, data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* hessians, hist_t* out) const {
|
||||
data_size_t i = start;
|
||||
hist_t* grad = out;
|
||||
hist_t* hess = out + 1;
|
||||
|
||||
if (USE_PREFETCH) {
|
||||
const data_size_t pf_offset = 32 / sizeof(VAL_T);
|
||||
const data_size_t pf_end = end - pf_offset;
|
||||
|
||||
for (; i < pf_end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto pf_idx = USE_INDICES ? data_indices[i + pf_offset] : i + pf_offset;
|
||||
if (!ORDERED) {
|
||||
PREFETCH_T0(gradients + pf_idx);
|
||||
PREFETCH_T0(hessians + pf_idx);
|
||||
}
|
||||
PREFETCH_T0(data_.data() + RowPtr(pf_idx));
|
||||
const auto j_start = RowPtr(idx);
|
||||
const VAL_T* data_ptr = data_.data() + j_start;
|
||||
const score_t gradient = ORDERED ? gradients[i] : gradients[idx];
|
||||
const score_t hessian = ORDERED ? hessians[i] : hessians[idx];
|
||||
for (int j = 0; j < num_feature_; ++j) {
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[j]);
|
||||
const auto ti = (bin + offsets_[j]) << 1;
|
||||
grad[ti] += gradient;
|
||||
hess[ti] += hessian;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto j_start = RowPtr(idx);
|
||||
const VAL_T* data_ptr = data_.data() + j_start;
|
||||
const score_t gradient = ORDERED ? gradients[i] : gradients[idx];
|
||||
const score_t hessian = ORDERED ? hessians[i] : hessians[idx];
|
||||
for (int j = 0; j < num_feature_; ++j) {
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[j]);
|
||||
const auto ti = (bin + offsets_[j]) << 1;
|
||||
grad[ti] += gradient;
|
||||
hess[ti] += hessian;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogram(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* hessians, hist_t* out) const override {
|
||||
ConstructHistogramInner<true, true, false>(data_indices, start, end,
|
||||
gradients, hessians, out);
|
||||
}
|
||||
|
||||
void ConstructHistogram(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* hessians,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<false, false, false>(
|
||||
nullptr, start, end, gradients, hessians, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrdered(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* hessians,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<true, true, true>(data_indices, start, end,
|
||||
gradients, hessians, out);
|
||||
}
|
||||
|
||||
template<bool USE_INDICES, bool USE_PREFETCH, bool ORDERED, typename PACKED_HIST_T, int HIST_BITS>
|
||||
void ConstructHistogramIntInner(const data_size_t* data_indices, data_size_t start, data_size_t end,
|
||||
const score_t* gradients_and_hessians, hist_t* out) const {
|
||||
data_size_t i = start;
|
||||
const VAL_T* data_ptr_base = data_.data();
|
||||
const int16_t* gradients_and_hessians_ptr = reinterpret_cast<const int16_t*>(gradients_and_hessians);
|
||||
PACKED_HIST_T* out_ptr = reinterpret_cast<PACKED_HIST_T*>(out);
|
||||
|
||||
if (USE_PREFETCH) {
|
||||
const data_size_t pf_offset = 32 / sizeof(VAL_T);
|
||||
const data_size_t pf_end = end - pf_offset;
|
||||
|
||||
for (; i < pf_end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto pf_idx = USE_INDICES ? data_indices[i + pf_offset] : i + pf_offset;
|
||||
if (!ORDERED) {
|
||||
PREFETCH_T0(gradients_and_hessians_ptr + pf_idx);
|
||||
}
|
||||
PREFETCH_T0(data_ptr_base + RowPtr(pf_idx));
|
||||
const auto j_start = RowPtr(idx);
|
||||
const VAL_T* data_ptr = data_ptr_base + j_start;
|
||||
const int16_t gradient_16 = gradients_and_hessians_ptr[idx];
|
||||
const PACKED_HIST_T gradient_packed = (HIST_BITS == 8) ? gradient_16 :
|
||||
((static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) |
|
||||
static_cast<PACKED_HIST_T>(gradient_16 & 0xff));
|
||||
for (int j = 0; j < num_feature_; ++j) {
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[j]);
|
||||
const auto ti = (bin + offsets_[j]);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto j_start = RowPtr(idx);
|
||||
const VAL_T* data_ptr = data_ptr_base + j_start;
|
||||
const int16_t gradient_16 = gradients_and_hessians_ptr[idx];
|
||||
const PACKED_HIST_T gradient_packed = (HIST_BITS == 8) ? gradient_16 :
|
||||
((static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) |
|
||||
static_cast<PACKED_HIST_T>(gradient_16 & 0xff));
|
||||
for (int j = 0; j < num_feature_; ++j) {
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[j]);
|
||||
const auto ti = (bin + offsets_[j]);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* /*hessians*/, hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int64_t, 32>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int64_t, 32>(
|
||||
nullptr, start, end, gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrderedInt32(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int64_t, 32>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* /*hessians*/, hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int32_t, 16>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int32_t, 16>(
|
||||
nullptr, start, end, gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrderedInt16(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int32_t, 16>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* /*hessians*/, hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int16_t, 8>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int16_t, 8>(
|
||||
nullptr, start, end, gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrderedInt8(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int16_t, 8>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
MultiValBin* CreateLike(data_size_t num_data, int num_bin, int num_feature, double,
|
||||
const std::vector<uint32_t>& offsets) const override {
|
||||
return new MultiValDenseBin<VAL_T>(num_data, num_bin, num_feature, offsets);
|
||||
}
|
||||
|
||||
void ReSize(data_size_t num_data, int num_bin, int num_feature,
|
||||
double, const std::vector<uint32_t>& offsets) override {
|
||||
num_data_ = num_data;
|
||||
num_bin_ = num_bin;
|
||||
num_feature_ = num_feature;
|
||||
offsets_ = offsets;
|
||||
size_t new_size = static_cast<size_t>(num_feature_) * num_data_;
|
||||
if (data_.size() < new_size) {
|
||||
data_.resize(new_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool SUBROW, bool SUBCOL>
|
||||
void CopyInner(const MultiValBin* full_bin, const data_size_t* used_indices,
|
||||
data_size_t num_used_indices,
|
||||
const std::vector<int>& used_feature_index) {
|
||||
const auto other_bin =
|
||||
reinterpret_cast<const MultiValDenseBin<VAL_T>*>(full_bin);
|
||||
if (SUBROW) {
|
||||
CHECK_EQ(num_data_, num_used_indices);
|
||||
}
|
||||
int n_block = 1;
|
||||
data_size_t block_size = num_data_;
|
||||
Threading::BlockInfo<data_size_t>(num_data_, 1024, &n_block,
|
||||
&block_size);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1)
|
||||
for (int tid = 0; tid < n_block; ++tid) {
|
||||
data_size_t start = tid * block_size;
|
||||
data_size_t end = std::min(num_data_, start + block_size);
|
||||
for (data_size_t i = start; i < end; ++i) {
|
||||
const auto j_start = RowPtr(i);
|
||||
const auto other_j_start =
|
||||
SUBROW ? other_bin->RowPtr(used_indices[i]) : other_bin->RowPtr(i);
|
||||
for (int j = 0; j < num_feature_; ++j) {
|
||||
if (SUBCOL) {
|
||||
if (other_bin->data_[other_j_start + used_feature_index[j]] > 0) {
|
||||
data_[j_start + j] = static_cast<VAL_T>(
|
||||
other_bin->data_[other_j_start + used_feature_index[j]]);
|
||||
} else {
|
||||
data_[j_start + j] = 0;
|
||||
}
|
||||
} else {
|
||||
data_[j_start + j] =
|
||||
static_cast<VAL_T>(other_bin->data_[other_j_start + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CopySubrow(const MultiValBin* full_bin, const data_size_t* used_indices,
|
||||
data_size_t num_used_indices) override {
|
||||
CopyInner<true, false>(full_bin, used_indices, num_used_indices,
|
||||
std::vector<int>());
|
||||
}
|
||||
|
||||
void CopySubcol(const MultiValBin* full_bin,
|
||||
const std::vector<int>& used_feature_index,
|
||||
const std::vector<uint32_t>&,
|
||||
const std::vector<uint32_t>&,
|
||||
const std::vector<uint32_t>&) override {
|
||||
CopyInner<false, true>(full_bin, nullptr, num_data_, used_feature_index);
|
||||
}
|
||||
|
||||
void CopySubrowAndSubcol(const MultiValBin* full_bin,
|
||||
const data_size_t* used_indices,
|
||||
data_size_t num_used_indices,
|
||||
const std::vector<int>& used_feature_index,
|
||||
const std::vector<uint32_t>&,
|
||||
const std::vector<uint32_t>&,
|
||||
const std::vector<uint32_t>&) override {
|
||||
CopyInner<true, true>(full_bin, used_indices, num_used_indices,
|
||||
used_feature_index);
|
||||
}
|
||||
|
||||
inline size_t RowPtr(data_size_t idx) const {
|
||||
return static_cast<size_t>(idx) * num_feature_;
|
||||
}
|
||||
|
||||
MultiValDenseBin<VAL_T>* Clone() override;
|
||||
|
||||
#ifdef USE_CUDA
|
||||
const void* GetRowWiseData(uint8_t* bit_type,
|
||||
size_t* total_size,
|
||||
bool* is_sparse,
|
||||
const void** out_data_ptr,
|
||||
uint8_t* data_ptr_bit_type) const override;
|
||||
#endif // USE_CUDA
|
||||
|
||||
private:
|
||||
data_size_t num_data_;
|
||||
int num_bin_;
|
||||
int num_feature_;
|
||||
std::vector<uint32_t> offsets_;
|
||||
std::vector<VAL_T, Common::AlignmentAllocator<VAL_T, 32>> data_;
|
||||
|
||||
MultiValDenseBin(const MultiValDenseBin<VAL_T>& other)
|
||||
: num_data_(other.num_data_), num_bin_(other.num_bin_), num_feature_(other.num_feature_),
|
||||
offsets_(other.offsets_), data_(other.data_) {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename VAL_T>
|
||||
MultiValDenseBin<VAL_T>* MultiValDenseBin<VAL_T>::Clone() {
|
||||
return new MultiValDenseBin<VAL_T>(*this);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_IO_MULTI_VAL_DENSE_BIN_HPP_
|
||||
@@ -0,0 +1,449 @@
|
||||
/*!
|
||||
* Copyright (c) 2020-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2020-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_IO_MULTI_VAL_SPARSE_BIN_HPP_
|
||||
#define LIGHTGBM_SRC_IO_MULTI_VAL_SPARSE_BIN_HPP_
|
||||
|
||||
#include <LightGBM/bin.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
#include <LightGBM/utils/threading.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename INDEX_T, typename VAL_T>
|
||||
class MultiValSparseBin : public MultiValBin {
|
||||
public:
|
||||
explicit MultiValSparseBin(data_size_t num_data, int num_bin,
|
||||
double estimate_element_per_row)
|
||||
: num_data_(num_data),
|
||||
num_bin_(num_bin),
|
||||
estimate_element_per_row_(estimate_element_per_row) {
|
||||
row_ptr_.resize(num_data_ + 1, 0);
|
||||
INDEX_T estimate_num_data = static_cast<INDEX_T>(estimate_element_per_row_ * 1.1 * num_data_);
|
||||
int num_threads = OMP_NUM_THREADS();
|
||||
if (num_threads > 1) {
|
||||
t_data_.resize(num_threads - 1);
|
||||
for (size_t i = 0; i < t_data_.size(); ++i) {
|
||||
t_data_[i].resize(estimate_num_data / num_threads);
|
||||
}
|
||||
}
|
||||
t_size_.resize(num_threads, 0);
|
||||
data_.resize(estimate_num_data / num_threads);
|
||||
}
|
||||
|
||||
~MultiValSparseBin() {}
|
||||
|
||||
data_size_t num_data() const override { return num_data_; }
|
||||
|
||||
int num_bin() const override { return num_bin_; }
|
||||
|
||||
double num_element_per_row() const override {
|
||||
return estimate_element_per_row_;
|
||||
}
|
||||
|
||||
const std::vector<uint32_t>& offsets() const override { return offsets_; }
|
||||
|
||||
void PushOneRow(int tid, data_size_t idx,
|
||||
const std::vector<uint32_t>& values) override {
|
||||
const int pre_alloc_size = 50;
|
||||
row_ptr_[idx + 1] = static_cast<INDEX_T>(values.size());
|
||||
if (tid == 0) {
|
||||
if (t_size_[tid] + row_ptr_[idx + 1] >
|
||||
static_cast<INDEX_T>(data_.size())) {
|
||||
data_.resize(t_size_[tid] + row_ptr_[idx + 1] * pre_alloc_size);
|
||||
}
|
||||
for (auto val : values) {
|
||||
data_[t_size_[tid]++] = static_cast<VAL_T>(val);
|
||||
}
|
||||
} else {
|
||||
if (t_size_[tid] + row_ptr_[idx + 1] >
|
||||
static_cast<INDEX_T>(t_data_[tid - 1].size())) {
|
||||
t_data_[tid - 1].resize(t_size_[tid] +
|
||||
row_ptr_[idx + 1] * pre_alloc_size);
|
||||
}
|
||||
for (auto val : values) {
|
||||
t_data_[tid - 1][t_size_[tid]++] = static_cast<VAL_T>(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MergeData(const INDEX_T* sizes) {
|
||||
Common::FunctionTimer fun_time("MultiValSparseBin::MergeData", global_timer);
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
row_ptr_[i + 1] += row_ptr_[i];
|
||||
}
|
||||
if (t_data_.size() > 0) {
|
||||
std::vector<INDEX_T> offsets(1 + t_data_.size());
|
||||
offsets[0] = sizes[0];
|
||||
for (size_t tid = 0; tid < t_data_.size() - 1; ++tid) {
|
||||
offsets[tid + 1] = offsets[tid] + sizes[tid + 1];
|
||||
}
|
||||
data_.resize(row_ptr_[num_data_]);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1)
|
||||
for (int tid = 0; tid < static_cast<int>(t_data_.size()); ++tid) {
|
||||
std::copy_n(t_data_[tid].data(), sizes[tid + 1],
|
||||
data_.data() + offsets[tid]);
|
||||
}
|
||||
} else {
|
||||
data_.resize(row_ptr_[num_data_]);
|
||||
}
|
||||
}
|
||||
|
||||
void FinishLoad() override {
|
||||
MergeData(t_size_.data());
|
||||
t_size_.clear();
|
||||
row_ptr_.shrink_to_fit();
|
||||
data_.shrink_to_fit();
|
||||
t_data_.clear();
|
||||
t_data_.shrink_to_fit();
|
||||
// update estimate_element_per_row_ by all data
|
||||
estimate_element_per_row_ =
|
||||
static_cast<double>(row_ptr_[num_data_]) / num_data_;
|
||||
}
|
||||
|
||||
bool IsSparse() override { return true; }
|
||||
|
||||
template <bool USE_INDICES, bool USE_PREFETCH, bool ORDERED>
|
||||
void ConstructHistogramInner(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* hessians, hist_t* out) const {
|
||||
data_size_t i = start;
|
||||
hist_t* grad = out;
|
||||
hist_t* hess = out + 1;
|
||||
const VAL_T* data_ptr = data_.data();
|
||||
if (USE_PREFETCH) {
|
||||
const data_size_t pf_offset = 32 / sizeof(VAL_T);
|
||||
const data_size_t pf_end = end - pf_offset;
|
||||
|
||||
for (; i < pf_end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto pf_idx =
|
||||
USE_INDICES ? data_indices[i + pf_offset] : i + pf_offset;
|
||||
if (!ORDERED) {
|
||||
PREFETCH_T0(gradients + pf_idx);
|
||||
PREFETCH_T0(hessians + pf_idx);
|
||||
}
|
||||
PREFETCH_T0(row_ptr_.data() + pf_idx);
|
||||
PREFETCH_T0(data_ptr + row_ptr_[pf_idx]);
|
||||
const auto j_start = RowPtr(idx);
|
||||
const auto j_end = RowPtr(idx + 1);
|
||||
const score_t gradient = ORDERED ? gradients[i] : gradients[idx];
|
||||
const score_t hessian = ORDERED ? hessians[i] : hessians[idx];
|
||||
for (auto j = j_start; j < j_end; ++j) {
|
||||
const auto ti = static_cast<uint32_t>(data_ptr[j]) << 1;
|
||||
grad[ti] += gradient;
|
||||
hess[ti] += hessian;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto j_start = RowPtr(idx);
|
||||
const auto j_end = RowPtr(idx + 1);
|
||||
const score_t gradient = ORDERED ? gradients[i] : gradients[idx];
|
||||
const score_t hessian = ORDERED ? hessians[i] : hessians[idx];
|
||||
for (auto j = j_start; j < j_end; ++j) {
|
||||
const auto ti = static_cast<uint32_t>(data_ptr[j]) << 1;
|
||||
grad[ti] += gradient;
|
||||
hess[ti] += hessian;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogram(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* hessians, hist_t* out) const override {
|
||||
ConstructHistogramInner<true, true, false>(data_indices, start, end,
|
||||
gradients, hessians, out);
|
||||
}
|
||||
|
||||
void ConstructHistogram(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* hessians,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<false, false, false>(
|
||||
nullptr, start, end, gradients, hessians, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrdered(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* hessians,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramInner<true, true, true>(data_indices, start, end,
|
||||
gradients, hessians, out);
|
||||
}
|
||||
|
||||
template <bool USE_INDICES, bool USE_PREFETCH, bool ORDERED, typename PACKED_HIST_T, int HIST_BITS>
|
||||
void ConstructHistogramIntInner(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients_and_hessians, hist_t* out) const {
|
||||
data_size_t i = start;
|
||||
PACKED_HIST_T* out_ptr = reinterpret_cast<PACKED_HIST_T*>(out);
|
||||
const int16_t* gradients_and_hessians_ptr = reinterpret_cast<const int16_t*>(gradients_and_hessians);
|
||||
const VAL_T* data_ptr = data_.data();
|
||||
const INDEX_T* row_ptr_base = row_ptr_.data();
|
||||
if (USE_PREFETCH) {
|
||||
const data_size_t pf_offset = 32 / sizeof(VAL_T);
|
||||
const data_size_t pf_end = end - pf_offset;
|
||||
|
||||
for (; i < pf_end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto pf_idx =
|
||||
USE_INDICES ? data_indices[i + pf_offset] : i + pf_offset;
|
||||
if (!ORDERED) {
|
||||
PREFETCH_T0(gradients_and_hessians_ptr + pf_idx);
|
||||
}
|
||||
PREFETCH_T0(row_ptr_base + pf_idx);
|
||||
PREFETCH_T0(data_ptr + row_ptr_[pf_idx]);
|
||||
const auto j_start = RowPtr(idx);
|
||||
const auto j_end = RowPtr(idx + 1);
|
||||
const int16_t gradient_16 = ORDERED ? gradients_and_hessians_ptr[i] : gradients_and_hessians_ptr[idx];
|
||||
const PACKED_HIST_T gradient_packed = (HIST_BITS == 8) ? gradient_16 :
|
||||
((static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) |
|
||||
static_cast<PACKED_HIST_T>(gradient_16 & 0xff));
|
||||
for (auto j = j_start; j < j_end; ++j) {
|
||||
const auto ti = static_cast<uint32_t>(data_ptr[j]);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (; i < end; ++i) {
|
||||
const auto idx = USE_INDICES ? data_indices[i] : i;
|
||||
const auto j_start = RowPtr(idx);
|
||||
const auto j_end = RowPtr(idx + 1);
|
||||
const int16_t gradient_16 = ORDERED ? gradients_and_hessians_ptr[i] : gradients_and_hessians_ptr[idx];
|
||||
const PACKED_HIST_T gradient_packed = (HIST_BITS == 8) ? gradient_16 :
|
||||
((static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) |
|
||||
static_cast<PACKED_HIST_T>(gradient_16 & 0xff));
|
||||
for (auto j = j_start; j < j_end; ++j) {
|
||||
const auto ti = static_cast<uint32_t>(data_ptr[j]);
|
||||
out_ptr[ti] += gradient_packed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* /*hessians*/, hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int64_t, 32>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int64_t, 32>(
|
||||
nullptr, start, end, gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrderedInt32(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int64_t, 32>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* /*hessians*/, hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int32_t, 16>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int32_t, 16>(
|
||||
nullptr, start, end, gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrderedInt16(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int32_t, 16>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* gradients,
|
||||
const score_t* /*hessians*/, hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, false, int16_t, 8>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(data_size_t start, data_size_t end,
|
||||
const score_t* gradients, const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<false, false, false, int16_t, 8>(
|
||||
nullptr, start, end, gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramOrderedInt8(const data_size_t* data_indices,
|
||||
data_size_t start, data_size_t end,
|
||||
const score_t* gradients,
|
||||
const score_t* /*hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructHistogramIntInner<true, true, true, int16_t, 8>(data_indices, start, end,
|
||||
gradients, out);
|
||||
}
|
||||
|
||||
MultiValBin* CreateLike(data_size_t num_data, int num_bin, int,
|
||||
double estimate_element_per_row,
|
||||
const std::vector<uint32_t>& /*offsets*/) const override {
|
||||
return new MultiValSparseBin<INDEX_T, VAL_T>(num_data, num_bin,
|
||||
estimate_element_per_row);
|
||||
}
|
||||
|
||||
void ReSize(data_size_t num_data, int num_bin, int,
|
||||
double estimate_element_per_row, const std::vector<uint32_t>& /*offsets*/) override {
|
||||
num_data_ = num_data;
|
||||
num_bin_ = num_bin;
|
||||
estimate_element_per_row_ = estimate_element_per_row;
|
||||
INDEX_T estimate_num_data =
|
||||
static_cast<INDEX_T>(estimate_element_per_row_ * 1.1 * num_data_);
|
||||
size_t npart = 1 + t_data_.size();
|
||||
INDEX_T avg_num_data = static_cast<INDEX_T>(estimate_num_data / npart);
|
||||
if (static_cast<INDEX_T>(data_.size()) < avg_num_data) {
|
||||
data_.resize(avg_num_data, 0);
|
||||
}
|
||||
for (size_t i = 0; i < t_data_.size(); ++i) {
|
||||
if (static_cast<INDEX_T>(t_data_[i].size()) < avg_num_data) {
|
||||
t_data_[i].resize(avg_num_data, 0);
|
||||
}
|
||||
}
|
||||
if (num_data_ + 1 > static_cast<data_size_t>(row_ptr_.size())) {
|
||||
row_ptr_.resize(num_data_ + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool SUBROW, bool SUBCOL>
|
||||
void CopyInner(const MultiValBin* full_bin, const data_size_t* used_indices,
|
||||
data_size_t num_used_indices,
|
||||
const std::vector<uint32_t>& lower,
|
||||
const std::vector<uint32_t>& upper,
|
||||
const std::vector<uint32_t>& delta) {
|
||||
const auto other =
|
||||
reinterpret_cast<const MultiValSparseBin<INDEX_T, VAL_T>*>(full_bin);
|
||||
if (SUBROW) {
|
||||
CHECK_EQ(num_data_, num_used_indices);
|
||||
}
|
||||
int n_block = 1;
|
||||
data_size_t block_size = num_data_;
|
||||
Threading::BlockInfo<data_size_t>(static_cast<int>(t_data_.size() + 1),
|
||||
num_data_, 1024, &n_block, &block_size);
|
||||
std::vector<INDEX_T> sizes(t_data_.size() + 1, 0);
|
||||
const int pre_alloc_size = 50;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 1)
|
||||
for (int tid = 0; tid < n_block; ++tid) {
|
||||
data_size_t start = tid * block_size;
|
||||
data_size_t end = std::min(num_data_, start + block_size);
|
||||
auto& buf = (tid == 0) ? data_ : t_data_[tid - 1];
|
||||
INDEX_T size = 0;
|
||||
for (data_size_t i = start; i < end; ++i) {
|
||||
const auto j_start =
|
||||
SUBROW ? other->RowPtr(used_indices[i]) : other->RowPtr(i);
|
||||
const auto j_end =
|
||||
SUBROW ? other->RowPtr(used_indices[i] + 1) : other->RowPtr(i + 1);
|
||||
if (size + (j_end - j_start) > static_cast<INDEX_T>(buf.size())) {
|
||||
buf.resize(size + (j_end - j_start) * pre_alloc_size);
|
||||
}
|
||||
int k = 0;
|
||||
const auto pre_size = size;
|
||||
for (auto j = j_start; j < j_end; ++j) {
|
||||
const auto val = other->data_[j];
|
||||
if (SUBCOL) {
|
||||
while (val >= upper[k]) {
|
||||
++k;
|
||||
}
|
||||
if (val >= lower[k]) {
|
||||
buf[size++] = static_cast<VAL_T>(val - delta[k]);
|
||||
}
|
||||
} else {
|
||||
buf[size++] = val;
|
||||
}
|
||||
}
|
||||
row_ptr_[i + 1] = size - pre_size;
|
||||
}
|
||||
sizes[tid] = size;
|
||||
}
|
||||
MergeData(sizes.data());
|
||||
}
|
||||
|
||||
void CopySubrow(const MultiValBin* full_bin, const data_size_t* used_indices,
|
||||
data_size_t num_used_indices) override {
|
||||
CopyInner<true, false>(full_bin, used_indices, num_used_indices,
|
||||
std::vector<uint32_t>(), std::vector<uint32_t>(),
|
||||
std::vector<uint32_t>());
|
||||
}
|
||||
|
||||
void CopySubcol(const MultiValBin* full_bin, const std::vector<int>&,
|
||||
const std::vector<uint32_t>& lower,
|
||||
const std::vector<uint32_t>& upper,
|
||||
const std::vector<uint32_t>& delta) override {
|
||||
CopyInner<false, true>(full_bin, nullptr, num_data_, lower, upper, delta);
|
||||
}
|
||||
|
||||
void CopySubrowAndSubcol(const MultiValBin* full_bin,
|
||||
const data_size_t* used_indices,
|
||||
data_size_t num_used_indices,
|
||||
const std::vector<int>&,
|
||||
const std::vector<uint32_t>& lower,
|
||||
const std::vector<uint32_t>& upper,
|
||||
const std::vector<uint32_t>& delta) override {
|
||||
CopyInner<true, true>(full_bin, used_indices, num_used_indices, lower,
|
||||
upper, delta);
|
||||
}
|
||||
|
||||
inline INDEX_T RowPtr(data_size_t idx) const { return row_ptr_[idx]; }
|
||||
|
||||
MultiValSparseBin<INDEX_T, VAL_T>* Clone() override;
|
||||
|
||||
|
||||
#ifdef USE_CUDA
|
||||
const void* GetRowWiseData(uint8_t* bit_type,
|
||||
size_t* total_size,
|
||||
bool* is_sparse,
|
||||
const void** out_data_ptr,
|
||||
uint8_t* data_ptr_bit_type) const override;
|
||||
#endif // USE_CUDA
|
||||
|
||||
private:
|
||||
data_size_t num_data_;
|
||||
int num_bin_;
|
||||
double estimate_element_per_row_;
|
||||
std::vector<VAL_T, Common::AlignmentAllocator<VAL_T, 32>> data_;
|
||||
std::vector<INDEX_T, Common::AlignmentAllocator<INDEX_T, 32>>
|
||||
row_ptr_;
|
||||
std::vector<std::vector<VAL_T, Common::AlignmentAllocator<VAL_T, 32>>>
|
||||
t_data_;
|
||||
std::vector<INDEX_T> t_size_;
|
||||
std::vector<uint32_t> offsets_;
|
||||
|
||||
MultiValSparseBin(const MultiValSparseBin<INDEX_T, VAL_T>& other)
|
||||
: num_data_(other.num_data_),
|
||||
num_bin_(other.num_bin_),
|
||||
estimate_element_per_row_(other.estimate_element_per_row_),
|
||||
data_(other.data_),
|
||||
row_ptr_(other.row_ptr_) {}
|
||||
};
|
||||
|
||||
template <typename INDEX_T, typename VAL_T>
|
||||
MultiValSparseBin<INDEX_T, VAL_T>* MultiValSparseBin<INDEX_T, VAL_T>::Clone() {
|
||||
return new MultiValSparseBin<INDEX_T, VAL_T>(*this);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_IO_MULTI_VAL_SPARSE_BIN_HPP_
|
||||
@@ -0,0 +1,319 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include "parser.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
void GetStatistic(const char* str, int* comma_cnt, int* tab_cnt, int* colon_cnt) {
|
||||
*comma_cnt = 0;
|
||||
*tab_cnt = 0;
|
||||
*colon_cnt = 0;
|
||||
for (int i = 0; str[i] != '\0'; ++i) {
|
||||
if (str[i] == ',') {
|
||||
++(*comma_cnt);
|
||||
} else if (str[i] == '\t') {
|
||||
++(*tab_cnt);
|
||||
} else if (str[i] == ':') {
|
||||
++(*colon_cnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GetLabelIdxForLibsvm(const std::string& str, int num_features, int label_idx) {
|
||||
if (num_features <= 0) {
|
||||
return label_idx;
|
||||
}
|
||||
auto str2 = Common::Trim(str);
|
||||
auto pos_space = str2.find_first_of(" \f\n\r\t\v");
|
||||
auto pos_colon = str2.find_first_of(":");
|
||||
if (pos_space == std::string::npos || pos_space < pos_colon) {
|
||||
return label_idx;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int GetLabelIdxForTSV(const std::string& str, int num_features, int label_idx) {
|
||||
if (num_features <= 0) {
|
||||
return label_idx;
|
||||
}
|
||||
auto str2 = Common::Trim(str);
|
||||
auto tokens = Common::Split(str2.c_str(), '\t');
|
||||
if (static_cast<int>(tokens.size()) == num_features) {
|
||||
return -1;
|
||||
} else {
|
||||
return label_idx;
|
||||
}
|
||||
}
|
||||
|
||||
int GetLabelIdxForCSV(const std::string& str, int num_features, int label_idx) {
|
||||
if (num_features <= 0) {
|
||||
return label_idx;
|
||||
}
|
||||
auto str2 = Common::Trim(str);
|
||||
auto tokens = Common::Split(str2.c_str(), ',');
|
||||
if (static_cast<int>(tokens.size()) == num_features) {
|
||||
return -1;
|
||||
} else {
|
||||
return label_idx;
|
||||
}
|
||||
}
|
||||
|
||||
enum DataType {
|
||||
INVALID,
|
||||
CSV,
|
||||
TSV,
|
||||
LIBSVM
|
||||
};
|
||||
|
||||
void GetLine(std::stringstream* ss, std::string* line, const VirtualFileReader* reader, std::vector<char>* buffer, size_t buffer_size) {
|
||||
std::getline(*ss, *line);
|
||||
while (ss->eof()) {
|
||||
size_t read_len = reader->Read(buffer->data(), buffer_size);
|
||||
if (read_len <= 0) {
|
||||
break;
|
||||
}
|
||||
ss->clear();
|
||||
ss->str(std::string(buffer->data(), read_len));
|
||||
std::string tmp;
|
||||
std::getline(*ss, tmp);
|
||||
*line += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> ReadKLineFromFile(const char* filename, bool header, int k) {
|
||||
auto reader = VirtualFileReader::Make(filename);
|
||||
if (!reader->Init()) {
|
||||
Log::Fatal("Data file %s doesn't exist.", filename);
|
||||
}
|
||||
std::vector<std::string> ret;
|
||||
std::string cur_line;
|
||||
const size_t buffer_size = 1024 * 1024;
|
||||
auto buffer = std::vector<char>(buffer_size);
|
||||
size_t read_len = reader->Read(buffer.data(), buffer_size);
|
||||
if (read_len <= 0) {
|
||||
Log::Fatal("Data file %s couldn't be read.", filename);
|
||||
}
|
||||
std::string read_str = std::string(buffer.data(), read_len);
|
||||
std::stringstream tmp_file(read_str);
|
||||
if (header) {
|
||||
if (!tmp_file.eof()) {
|
||||
GetLine(&tmp_file, &cur_line, reader.get(), &buffer, buffer_size);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < k; ++i) {
|
||||
if (!tmp_file.eof()) {
|
||||
GetLine(&tmp_file, &cur_line, reader.get(), &buffer, buffer_size);
|
||||
cur_line = Common::Trim(cur_line);
|
||||
if (!cur_line.empty()) {
|
||||
ret.push_back(cur_line);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret.empty()) {
|
||||
Log::Fatal("Data file %s should have at least one line.", filename);
|
||||
} else if (ret.size() == 1) {
|
||||
Log::Warning("Data file %s only has one line.", filename);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int GetNumColFromLIBSVMFile(const char* filename, bool header) {
|
||||
auto reader = VirtualFileReader::Make(filename);
|
||||
if (!reader->Init()) {
|
||||
Log::Fatal("Data file %s doesn't exist.", filename);
|
||||
}
|
||||
std::vector<std::string> ret;
|
||||
std::string cur_line;
|
||||
const size_t buffer_size = 1024 * 1024;
|
||||
auto buffer = std::vector<char>(buffer_size);
|
||||
size_t read_len = reader->Read(buffer.data(), buffer_size);
|
||||
if (read_len <= 0) {
|
||||
Log::Fatal("Data file %s couldn't be read.", filename);
|
||||
}
|
||||
std::string read_str = std::string(buffer.data(), read_len);
|
||||
std::stringstream tmp_file(read_str);
|
||||
if (header) {
|
||||
if (!tmp_file.eof()) {
|
||||
GetLine(&tmp_file, &cur_line, reader.get(), &buffer, buffer_size);
|
||||
}
|
||||
}
|
||||
int max_col_idx = 0;
|
||||
int max_line_idx = 0;
|
||||
const int stop_round = 1 << 7;
|
||||
const int max_line = 1 << 13;
|
||||
for (int i = 0; i < max_line; ++i) {
|
||||
if (!tmp_file.eof()) {
|
||||
GetLine(&tmp_file, &cur_line, reader.get(), &buffer, buffer_size);
|
||||
cur_line = Common::Trim(cur_line);
|
||||
auto colon_pos = cur_line.find_last_of(":");
|
||||
auto space_pos = cur_line.find_last_of(" \f\t\v");
|
||||
auto sub_str = cur_line.substr(space_pos + 1, space_pos - colon_pos - 1);
|
||||
int cur_idx = 0;
|
||||
Common::Atoi(sub_str.c_str(), &cur_idx);
|
||||
if (cur_idx > max_col_idx) {
|
||||
max_col_idx = cur_idx;
|
||||
max_line_idx = i;
|
||||
}
|
||||
if (i - max_line_idx >= stop_round) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
CHECK_GT(max_col_idx, 0);
|
||||
return max_col_idx;
|
||||
}
|
||||
|
||||
DataType GetDataType(const char* filename, bool header,
|
||||
const std::vector<std::string>& lines, int* num_col) {
|
||||
DataType type = DataType::INVALID;
|
||||
if (lines.empty()) {
|
||||
return type;
|
||||
}
|
||||
int comma_cnt = 0;
|
||||
int tab_cnt = 0;
|
||||
int colon_cnt = 0;
|
||||
GetStatistic(lines[0].c_str(), &comma_cnt, &tab_cnt, &colon_cnt);
|
||||
size_t num_lines = lines.size();
|
||||
if (num_lines == 1) {
|
||||
if (colon_cnt > 0) {
|
||||
type = DataType::LIBSVM;
|
||||
} else if (tab_cnt > 0) {
|
||||
type = DataType::TSV;
|
||||
} else if (comma_cnt > 0) {
|
||||
type = DataType::CSV;
|
||||
}
|
||||
} else {
|
||||
int comma_cnt2 = 0;
|
||||
int tab_cnt2 = 0;
|
||||
int colon_cnt2 = 0;
|
||||
GetStatistic(lines[1].c_str(), &comma_cnt2, &tab_cnt2, &colon_cnt2);
|
||||
if (colon_cnt > 0 || colon_cnt2 > 0) {
|
||||
type = DataType::LIBSVM;
|
||||
} else if (tab_cnt == tab_cnt2 && tab_cnt > 0) {
|
||||
type = DataType::TSV;
|
||||
} else if (comma_cnt == comma_cnt2 && comma_cnt > 0) {
|
||||
type = DataType::CSV;
|
||||
}
|
||||
if (type == DataType::TSV || type == DataType::CSV) {
|
||||
// valid the type
|
||||
for (size_t i = 2; i < num_lines; ++i) {
|
||||
GetStatistic(lines[i].c_str(), &comma_cnt2, &tab_cnt2, &colon_cnt2);
|
||||
if (type == DataType::TSV && tab_cnt2 != tab_cnt) {
|
||||
type = DataType::INVALID;
|
||||
break;
|
||||
} else if (type == DataType::CSV && comma_cnt != comma_cnt2) {
|
||||
type = DataType::INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == DataType::LIBSVM) {
|
||||
int max_col_idx = GetNumColFromLIBSVMFile(filename, header);
|
||||
*num_col = max_col_idx + 1;
|
||||
} else if (type == DataType::CSV) {
|
||||
*num_col = comma_cnt + 1;
|
||||
} else if (type == DataType::TSV) {
|
||||
*num_col = tab_cnt + 1;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
// parser factory implementation.
|
||||
ParserFactory& ParserFactory::getInstance() {
|
||||
static ParserFactory factory;
|
||||
return factory;
|
||||
}
|
||||
|
||||
void ParserFactory::Register(std::string class_name, std::function<Parser*(std::string)> m_objc) {
|
||||
if (m_objc) {
|
||||
object_map_.insert(
|
||||
std::map<std::string, std::function<Parser*(std::string)>>::value_type(class_name, m_objc));
|
||||
}
|
||||
}
|
||||
|
||||
Parser* ParserFactory::getObject(std::string class_name, std::string config_str) {
|
||||
std::map<std::string, std::function<Parser*(std::string)>>::const_iterator iter =
|
||||
object_map_.find(class_name);
|
||||
if (iter != object_map_.end()) {
|
||||
return iter->second(config_str);
|
||||
} else {
|
||||
Log::Fatal("Cannot find parser class '%s', please register first or check config format.", class_name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Parser* Parser::CreateParser(const char* filename, bool header, int num_features, int label_idx, bool precise_float_parser) {
|
||||
const int n_read_line = 32;
|
||||
auto lines = ReadKLineFromFile(filename, header, n_read_line);
|
||||
int num_col = 0;
|
||||
DataType type = GetDataType(filename, header, lines, &num_col);
|
||||
if (type == DataType::INVALID) {
|
||||
Log::Fatal("Unknown format of training data. Only CSV, TSV, and LibSVM (zero-based) formatted text files are supported.");
|
||||
}
|
||||
std::unique_ptr<Parser> ret;
|
||||
int output_label_index = -1;
|
||||
AtofFunc atof = precise_float_parser ? Common::AtofPrecise : Common::Atof;
|
||||
if (type == DataType::LIBSVM) {
|
||||
output_label_index = GetLabelIdxForLibsvm(lines[0], num_features, label_idx);
|
||||
ret.reset(new LibSVMParser(output_label_index, num_col, atof));
|
||||
} else if (type == DataType::TSV) {
|
||||
output_label_index = GetLabelIdxForTSV(lines[0], num_features, label_idx);
|
||||
ret.reset(new TSVParser(output_label_index, num_col, atof));
|
||||
} else if (type == DataType::CSV) {
|
||||
output_label_index = GetLabelIdxForCSV(lines[0], num_features, label_idx);
|
||||
ret.reset(new CSVParser(output_label_index, num_col, atof));
|
||||
}
|
||||
|
||||
if (output_label_index < 0 && label_idx >= 0) {
|
||||
Log::Info("Data file %s doesn't contain a label column.", filename);
|
||||
}
|
||||
return ret.release();
|
||||
}
|
||||
|
||||
Parser* Parser::CreateParser(const char* filename, bool header, int num_features, int label_idx, bool precise_float_parser, std::string parser_config_str) {
|
||||
// customized parser add-on.
|
||||
if (!parser_config_str.empty()) {
|
||||
std::unique_ptr<Parser> ret;
|
||||
std::string class_name = Common::GetFromParserConfig(parser_config_str, "className");
|
||||
Log::Info("Custom parser class name: %s", class_name.c_str());
|
||||
Parser* p = ParserFactory::getInstance().getObject(class_name, parser_config_str);
|
||||
ret.reset(p);
|
||||
return ret.release();
|
||||
}
|
||||
return CreateParser(filename, header, num_features, label_idx, precise_float_parser);
|
||||
}
|
||||
|
||||
std::string Parser::GenerateParserConfigStr(const char* filename, const char* parser_config_filename, bool header, int label_idx) {
|
||||
TextReader<data_size_t> parser_config_reader(parser_config_filename, false);
|
||||
parser_config_reader.ReadAllLines();
|
||||
std::string parser_config_str = parser_config_reader.JoinedLines();
|
||||
if (!parser_config_str.empty()) {
|
||||
// save header to parser config in case needed.
|
||||
if (header && Common::GetFromParserConfig(parser_config_str, "header").empty()) {
|
||||
TextReader<data_size_t> text_reader(filename, header);
|
||||
parser_config_str = Common::SaveToParserConfig(parser_config_str, "header", text_reader.first_line());
|
||||
}
|
||||
// save label id to parser config in case needed.
|
||||
if (Common::GetFromParserConfig(parser_config_str, "labelId").empty()) {
|
||||
parser_config_str = Common::SaveToParserConfig(parser_config_str, "labelId", std::to_string(label_idx));
|
||||
}
|
||||
}
|
||||
return parser_config_str;
|
||||
}
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,136 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_IO_PARSER_HPP_
|
||||
#define LIGHTGBM_SRC_IO_PARSER_HPP_
|
||||
|
||||
#include <LightGBM/dataset.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class CSVParser: public Parser {
|
||||
public:
|
||||
explicit CSVParser(int label_idx, int total_columns, AtofFunc atof)
|
||||
:label_idx_(label_idx), total_columns_(total_columns), atof_(atof) {
|
||||
}
|
||||
inline void ParseOneLine(const char* str,
|
||||
std::vector<std::pair<int, double>>* out_features, double* out_label) const override {
|
||||
int idx = 0;
|
||||
double val = 0.0f;
|
||||
int offset = 0;
|
||||
*out_label = 0.0f;
|
||||
while (*str != '\0') {
|
||||
str = atof_(str, &val);
|
||||
if (idx == label_idx_) {
|
||||
*out_label = val;
|
||||
offset = -1;
|
||||
} else if (std::fabs(val) > kZeroThreshold || std::isnan(val)) {
|
||||
out_features->emplace_back(idx + offset, val);
|
||||
}
|
||||
++idx;
|
||||
if (*str == ',') {
|
||||
++str;
|
||||
} else if (*str != '\0') {
|
||||
Log::Fatal("Input format error when parsing as CSV");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline int NumFeatures() const override {
|
||||
return total_columns_ - (label_idx_ >= 0);
|
||||
}
|
||||
|
||||
private:
|
||||
int label_idx_ = 0;
|
||||
int total_columns_ = -1;
|
||||
AtofFunc atof_;
|
||||
};
|
||||
|
||||
class TSVParser: public Parser {
|
||||
public:
|
||||
explicit TSVParser(int label_idx, int total_columns, AtofFunc atof)
|
||||
:label_idx_(label_idx), total_columns_(total_columns), atof_(atof) {
|
||||
}
|
||||
inline void ParseOneLine(const char* str,
|
||||
std::vector<std::pair<int, double>>* out_features, double* out_label) const override {
|
||||
int idx = 0;
|
||||
double val = 0.0f;
|
||||
int offset = 0;
|
||||
while (*str != '\0') {
|
||||
str = atof_(str, &val);
|
||||
if (idx == label_idx_) {
|
||||
*out_label = val;
|
||||
offset = -1;
|
||||
} else if (std::fabs(val) > kZeroThreshold || std::isnan(val)) {
|
||||
out_features->emplace_back(idx + offset, val);
|
||||
}
|
||||
++idx;
|
||||
if (*str == '\t') {
|
||||
++str;
|
||||
} else if (*str != '\0') {
|
||||
Log::Fatal("Input format error when parsing as TSV");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline int NumFeatures() const override {
|
||||
return total_columns_ - (label_idx_ >= 0);
|
||||
}
|
||||
|
||||
private:
|
||||
int label_idx_ = 0;
|
||||
int total_columns_ = -1;
|
||||
AtofFunc atof_;
|
||||
};
|
||||
|
||||
class LibSVMParser: public Parser {
|
||||
public:
|
||||
explicit LibSVMParser(int label_idx, int total_columns, AtofFunc atof)
|
||||
:label_idx_(label_idx), total_columns_(total_columns), atof_(atof) {
|
||||
if (label_idx > 0) {
|
||||
Log::Fatal("Label should be the first column in a LibSVM file");
|
||||
}
|
||||
}
|
||||
inline void ParseOneLine(const char* str,
|
||||
std::vector<std::pair<int, double>>* out_features, double* out_label) const override {
|
||||
int idx = 0;
|
||||
double val = 0.0f;
|
||||
if (label_idx_ == 0) {
|
||||
str = atof_(str, &val);
|
||||
*out_label = val;
|
||||
str = Common::SkipSpaceAndTab(str);
|
||||
}
|
||||
while (*str != '\0') {
|
||||
str = Common::Atoi(str, &idx);
|
||||
str = Common::SkipSpaceAndTab(str);
|
||||
if (*str == ':') {
|
||||
++str;
|
||||
str = Common::Atof(str, &val);
|
||||
out_features->emplace_back(idx, val);
|
||||
} else {
|
||||
Log::Fatal("Input format error when parsing as LibSVM");
|
||||
}
|
||||
str = Common::SkipSpaceAndTab(str);
|
||||
}
|
||||
}
|
||||
|
||||
inline int NumFeatures() const override {
|
||||
return total_columns_;
|
||||
}
|
||||
|
||||
private:
|
||||
int label_idx_ = 0;
|
||||
int total_columns_ = -1;
|
||||
AtofFunc atof_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_IO_PARSER_HPP_
|
||||
@@ -0,0 +1,858 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_IO_SPARSE_BIN_HPP_
|
||||
#define LIGHTGBM_SRC_IO_SPARSE_BIN_HPP_
|
||||
|
||||
#include <LightGBM/bin.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename VAL_T>
|
||||
class SparseBin;
|
||||
|
||||
const size_t kNumFastIndex = 64;
|
||||
|
||||
template <typename VAL_T>
|
||||
class SparseBinIterator : public BinIterator {
|
||||
public:
|
||||
SparseBinIterator(const SparseBin<VAL_T>* bin_data, uint32_t min_bin,
|
||||
uint32_t max_bin, uint32_t most_freq_bin)
|
||||
: bin_data_(bin_data),
|
||||
min_bin_(static_cast<VAL_T>(min_bin)),
|
||||
max_bin_(static_cast<VAL_T>(max_bin)),
|
||||
most_freq_bin_(static_cast<VAL_T>(most_freq_bin)) {
|
||||
if (most_freq_bin_ == 0) {
|
||||
offset_ = 1;
|
||||
} else {
|
||||
offset_ = 0;
|
||||
}
|
||||
Reset(0);
|
||||
}
|
||||
SparseBinIterator(const SparseBin<VAL_T>* bin_data, data_size_t start_idx)
|
||||
: bin_data_(bin_data) {
|
||||
Reset(start_idx);
|
||||
}
|
||||
|
||||
inline uint32_t RawGet(data_size_t idx) override;
|
||||
inline VAL_T InnerRawGet(data_size_t idx);
|
||||
|
||||
inline uint32_t Get(data_size_t idx) override {
|
||||
VAL_T ret = InnerRawGet(idx);
|
||||
if (ret >= min_bin_ && ret <= max_bin_) {
|
||||
return ret - min_bin_ + offset_;
|
||||
} else {
|
||||
return most_freq_bin_;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Reset(data_size_t idx) override;
|
||||
|
||||
private:
|
||||
const SparseBin<VAL_T>* bin_data_;
|
||||
data_size_t cur_pos_;
|
||||
data_size_t i_delta_;
|
||||
VAL_T min_bin_;
|
||||
VAL_T max_bin_;
|
||||
VAL_T most_freq_bin_;
|
||||
uint8_t offset_;
|
||||
};
|
||||
|
||||
template <typename VAL_T>
|
||||
class SparseBin : public Bin {
|
||||
public:
|
||||
friend class SparseBinIterator<VAL_T>;
|
||||
|
||||
explicit SparseBin(data_size_t num_data) : num_data_(num_data) {
|
||||
int num_threads = OMP_NUM_THREADS();
|
||||
push_buffers_.resize(num_threads);
|
||||
}
|
||||
|
||||
~SparseBin() {}
|
||||
|
||||
void InitStreaming(uint32_t num_thread, int32_t omp_max_threads) override {
|
||||
// Each external thread needs its own set of OpenMP push buffers,
|
||||
// so allocate num_thread times the maximum number of OMP threads per external thread
|
||||
push_buffers_.resize(omp_max_threads * num_thread);
|
||||
};
|
||||
|
||||
void ReSize(data_size_t num_data) override { num_data_ = num_data; }
|
||||
|
||||
void Push(int tid, data_size_t idx, uint32_t value) override {
|
||||
auto cur_bin = static_cast<VAL_T>(value);
|
||||
if (cur_bin != 0) {
|
||||
push_buffers_[tid].emplace_back(idx, cur_bin);
|
||||
}
|
||||
}
|
||||
|
||||
BinIterator* GetIterator(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin) const override;
|
||||
|
||||
#define ACC_GH(hist, i, g, h) \
|
||||
const auto ti = static_cast<int>(i) << 1; \
|
||||
hist[ti] += g; \
|
||||
hist[ti + 1] += h;
|
||||
|
||||
void ConstructHistogram(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* ordered_hessians,
|
||||
hist_t* out) const override {
|
||||
data_size_t i_delta, cur_pos;
|
||||
InitIndex(data_indices[start], &i_delta, &cur_pos);
|
||||
data_size_t i = start;
|
||||
for (;;) {
|
||||
if (cur_pos < data_indices[i]) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
} else if (cur_pos > data_indices[i]) {
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const VAL_T bin = vals_[i_delta];
|
||||
ACC_GH(out, bin, ordered_gradients[i], ordered_hessians[i]);
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogram(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* ordered_hessians,
|
||||
hist_t* out) const override {
|
||||
data_size_t i_delta, cur_pos;
|
||||
InitIndex(start, &i_delta, &cur_pos);
|
||||
while (cur_pos < start && i_delta < num_vals_) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
while (cur_pos < end && i_delta < num_vals_) {
|
||||
const VAL_T bin = vals_[i_delta];
|
||||
ACC_GH(out, bin, ordered_gradients[cur_pos], ordered_hessians[cur_pos]);
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogram(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
data_size_t i_delta, cur_pos;
|
||||
InitIndex(data_indices[start], &i_delta, &cur_pos);
|
||||
data_size_t i = start;
|
||||
hist_t* grad = out;
|
||||
hist_cnt_t* cnt = reinterpret_cast<hist_cnt_t*>(out + 1);
|
||||
for (;;) {
|
||||
if (cur_pos < data_indices[i]) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
} else if (cur_pos > data_indices[i]) {
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint32_t ti = static_cast<uint32_t>(vals_[i_delta]) << 1;
|
||||
grad[ti] += ordered_gradients[i];
|
||||
++cnt[ti];
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogram(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
data_size_t i_delta, cur_pos;
|
||||
InitIndex(start, &i_delta, &cur_pos);
|
||||
hist_t* grad = out;
|
||||
hist_cnt_t* cnt = reinterpret_cast<hist_cnt_t*>(out + 1);
|
||||
while (cur_pos < start && i_delta < num_vals_) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
while (cur_pos < end && i_delta < num_vals_) {
|
||||
const uint32_t ti = static_cast<uint32_t>(vals_[i_delta]) << 1;
|
||||
grad[ti] += ordered_gradients[cur_pos];
|
||||
++cnt[ti];
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
}
|
||||
#undef ACC_GH
|
||||
|
||||
template <bool USE_HESSIAN, typename PACKED_HIST_T, typename GRAD_HIST_T, typename HESS_HIST_T, int HIST_BITS>
|
||||
void ConstructIntHistogramInner(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients_and_hessians,
|
||||
hist_t* out) const {
|
||||
data_size_t i_delta, cur_pos;
|
||||
InitIndex(start, &i_delta, &cur_pos);
|
||||
if (USE_HESSIAN) {
|
||||
PACKED_HIST_T* out_ptr = reinterpret_cast<PACKED_HIST_T*>(out);
|
||||
const int16_t* gradients_and_hessians_ptr = reinterpret_cast<const int16_t*>(ordered_gradients_and_hessians);
|
||||
while (cur_pos < start && i_delta < num_vals_) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
while (cur_pos < end && i_delta < num_vals_) {
|
||||
const VAL_T bin = vals_[i_delta];
|
||||
const int16_t gradient_16 = gradients_and_hessians_ptr[cur_pos];
|
||||
const PACKED_HIST_T gradient_64 = (static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) | (gradient_16 & 0xff);
|
||||
out_ptr[bin] += gradient_64;
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
} else {
|
||||
GRAD_HIST_T* grad = reinterpret_cast<GRAD_HIST_T*>(out);
|
||||
HESS_HIST_T* cnt = reinterpret_cast<HESS_HIST_T*>(out) + 1;
|
||||
const int8_t* gradients_and_hessians_ptr = reinterpret_cast<const int8_t*>(ordered_gradients_and_hessians);
|
||||
while (cur_pos < start && i_delta < num_vals_) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
while (cur_pos < end && i_delta < num_vals_) {
|
||||
const uint32_t ti = static_cast<uint32_t>(vals_[i_delta]) << 1;
|
||||
grad[ti] += gradients_and_hessians_ptr[cur_pos];
|
||||
++cnt[ti];
|
||||
cur_pos += deltas_[++i_delta];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_HESSIAN, typename PACKED_HIST_T, typename GRAD_HIST_T, typename HESS_HIST_T, int HIST_BITS>
|
||||
void ConstructIntHistogramInner(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients_and_hessians,
|
||||
hist_t* out) const {
|
||||
data_size_t i_delta, cur_pos;
|
||||
InitIndex(data_indices[start], &i_delta, &cur_pos);
|
||||
data_size_t i = start;
|
||||
if (USE_HESSIAN) {
|
||||
PACKED_HIST_T* out_ptr = reinterpret_cast<PACKED_HIST_T*>(out);
|
||||
const int16_t* gradients_and_hessians_ptr = reinterpret_cast<const int16_t*>(ordered_gradients_and_hessians);
|
||||
for (;;) {
|
||||
if (cur_pos < data_indices[i]) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
} else if (cur_pos > data_indices[i]) {
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const VAL_T bin = vals_[i_delta];
|
||||
const int16_t gradient_16 = gradients_and_hessians_ptr[i];
|
||||
const PACKED_HIST_T gradient_packed = (HIST_BITS == 8) ? gradient_16 :
|
||||
(static_cast<PACKED_HIST_T>(static_cast<int8_t>(gradient_16 >> 8)) << HIST_BITS) | (gradient_16 & 0xff);
|
||||
out_ptr[bin] += gradient_packed;
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
GRAD_HIST_T* grad = reinterpret_cast<GRAD_HIST_T*>(out);
|
||||
HESS_HIST_T* cnt = reinterpret_cast<HESS_HIST_T*>(out) + 1;
|
||||
const int8_t* gradients_and_hessians_ptr = reinterpret_cast<const int8_t*>(ordered_gradients_and_hessians);
|
||||
for (;;) {
|
||||
if (cur_pos < data_indices[i]) {
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
} else if (cur_pos > data_indices[i]) {
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const uint32_t ti = static_cast<uint32_t>(vals_[i_delta]) << 1;
|
||||
grad[ti] += gradients_and_hessians_ptr[i << 1];
|
||||
++cnt[ti];
|
||||
if (++i >= end) {
|
||||
break;
|
||||
}
|
||||
cur_pos += deltas_[++i_delta];
|
||||
if (i_delta >= num_vals_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<true, int64_t, int32_t, uint32_t, 32>(data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<true, int64_t, int32_t, uint32_t, 32>(start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<false, int64_t, int32_t, uint32_t, 32>(data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt32(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<false, int64_t, int32_t, uint32_t, 32>(start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<true, int32_t, int16_t, uint16_t, 16>(data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<true, int32_t, int16_t, uint16_t, 16>(start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<false, int32_t, int16_t, uint16_t, 16>(data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt16(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<false, int32_t, int16_t, uint16_t, 16>(start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<true, int16_t, uint8_t, uint8_t, 8>(data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
const score_t* /*ordered_hessians*/,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<true, int16_t, uint8_t, uint8_t, 8>(start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(const data_size_t* data_indices, data_size_t start,
|
||||
data_size_t end, const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<false, int16_t, uint8_t, uint8_t, 8>(data_indices, start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
void ConstructHistogramInt8(data_size_t start, data_size_t end,
|
||||
const score_t* ordered_gradients,
|
||||
hist_t* out) const override {
|
||||
ConstructIntHistogramInner<false, int16_t, uint8_t, uint8_t, 8>(start, end, ordered_gradients, out);
|
||||
}
|
||||
|
||||
inline void NextNonzeroFast(data_size_t* i_delta,
|
||||
data_size_t* cur_pos) const {
|
||||
*cur_pos += deltas_[++(*i_delta)];
|
||||
if (*i_delta >= num_vals_) {
|
||||
*cur_pos = num_data_;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool NextNonzero(data_size_t* i_delta, data_size_t* cur_pos) const {
|
||||
*cur_pos += deltas_[++(*i_delta)];
|
||||
if (*i_delta < num_vals_) {
|
||||
return true;
|
||||
} else {
|
||||
*cur_pos = num_data_;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool MISS_IS_ZERO, bool MISS_IS_NA, bool MFB_IS_ZERO,
|
||||
bool MFB_IS_NA, bool USE_MIN_BIN>
|
||||
data_size_t SplitInner(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t default_bin, uint32_t most_freq_bin,
|
||||
bool default_left, uint32_t threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const {
|
||||
auto th = static_cast<VAL_T>(threshold + min_bin);
|
||||
auto t_zero_bin = static_cast<VAL_T>(min_bin + default_bin);
|
||||
if (most_freq_bin == 0) {
|
||||
--th;
|
||||
--t_zero_bin;
|
||||
}
|
||||
const auto minb = static_cast<VAL_T>(min_bin);
|
||||
const auto maxb = static_cast<VAL_T>(max_bin);
|
||||
data_size_t lte_count = 0;
|
||||
data_size_t gt_count = 0;
|
||||
data_size_t* default_indices = gt_indices;
|
||||
data_size_t* default_count = >_count;
|
||||
data_size_t* missing_default_indices = gt_indices;
|
||||
data_size_t* missing_default_count = >_count;
|
||||
if (most_freq_bin <= threshold) {
|
||||
default_indices = lte_indices;
|
||||
default_count = <e_count;
|
||||
}
|
||||
if (MISS_IS_ZERO || MISS_IS_NA) {
|
||||
if (default_left) {
|
||||
missing_default_indices = lte_indices;
|
||||
missing_default_count = <e_count;
|
||||
}
|
||||
}
|
||||
SparseBinIterator<VAL_T> iterator(this, data_indices[0]);
|
||||
if (min_bin < max_bin) {
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
const data_size_t idx = data_indices[i];
|
||||
const auto bin = iterator.InnerRawGet(idx);
|
||||
if ((MISS_IS_ZERO && !MFB_IS_ZERO && bin == t_zero_bin) ||
|
||||
(MISS_IS_NA && !MFB_IS_NA && bin == maxb)) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else if ((USE_MIN_BIN && (bin < minb || bin > maxb)) ||
|
||||
(!USE_MIN_BIN && bin == 0)) {
|
||||
if ((MISS_IS_NA && MFB_IS_NA) || (MISS_IS_ZERO && MFB_IS_ZERO)) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
}
|
||||
} else if (bin > th) {
|
||||
gt_indices[gt_count++] = idx;
|
||||
} else {
|
||||
lte_indices[lte_count++] = idx;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data_size_t* max_bin_indices = gt_indices;
|
||||
data_size_t* max_bin_count = >_count;
|
||||
if (maxb <= th) {
|
||||
max_bin_indices = lte_indices;
|
||||
max_bin_count = <e_count;
|
||||
}
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
const data_size_t idx = data_indices[i];
|
||||
const auto bin = iterator.InnerRawGet(idx);
|
||||
if (MISS_IS_ZERO && !MFB_IS_ZERO && bin == t_zero_bin) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else if (bin != maxb) {
|
||||
if ((MISS_IS_NA && MFB_IS_NA) || (MISS_IS_ZERO && MFB_IS_ZERO)) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
}
|
||||
} else {
|
||||
if (MISS_IS_NA && !MFB_IS_NA) {
|
||||
missing_default_indices[(*missing_default_count)++] = idx;
|
||||
} else {
|
||||
max_bin_indices[(*max_bin_count)++] = idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lte_count;
|
||||
}
|
||||
|
||||
data_size_t Split(uint32_t min_bin, uint32_t max_bin, uint32_t default_bin,
|
||||
uint32_t most_freq_bin, MissingType missing_type,
|
||||
bool default_left, uint32_t threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
#define ARGUMENTS \
|
||||
min_bin, max_bin, default_bin, most_freq_bin, default_left, threshold, \
|
||||
data_indices, cnt, lte_indices, gt_indices
|
||||
if (missing_type == MissingType::None) {
|
||||
return SplitInner<false, false, false, false, true>(ARGUMENTS);
|
||||
} else if (missing_type == MissingType::Zero) {
|
||||
if (default_bin == most_freq_bin) {
|
||||
return SplitInner<true, false, true, false, true>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<true, false, false, false, true>(ARGUMENTS);
|
||||
}
|
||||
} else {
|
||||
if (max_bin == most_freq_bin + min_bin && most_freq_bin > 0) {
|
||||
return SplitInner<false, true, false, true, true>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<false, true, false, false, true>(ARGUMENTS);
|
||||
}
|
||||
}
|
||||
#undef ARGUMENTS
|
||||
}
|
||||
|
||||
data_size_t Split(uint32_t max_bin, uint32_t default_bin,
|
||||
uint32_t most_freq_bin, MissingType missing_type,
|
||||
bool default_left, uint32_t threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
#define ARGUMENTS \
|
||||
1, max_bin, default_bin, most_freq_bin, default_left, threshold, \
|
||||
data_indices, cnt, lte_indices, gt_indices
|
||||
if (missing_type == MissingType::None) {
|
||||
return SplitInner<false, false, false, false, false>(ARGUMENTS);
|
||||
} else if (missing_type == MissingType::Zero) {
|
||||
if (default_bin == most_freq_bin) {
|
||||
return SplitInner<true, false, true, false, false>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<true, false, false, false, false>(ARGUMENTS);
|
||||
}
|
||||
} else {
|
||||
if (max_bin == most_freq_bin + 1 && most_freq_bin > 0) {
|
||||
return SplitInner<false, true, false, true, false>(ARGUMENTS);
|
||||
} else {
|
||||
return SplitInner<false, true, false, false, false>(ARGUMENTS);
|
||||
}
|
||||
}
|
||||
#undef ARGUMENTS
|
||||
}
|
||||
template <bool USE_MIN_BIN>
|
||||
data_size_t SplitCategoricalInner(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin,
|
||||
const uint32_t* threshold,
|
||||
int num_threshold,
|
||||
const data_size_t* data_indices,
|
||||
data_size_t cnt, data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const {
|
||||
data_size_t lte_count = 0;
|
||||
data_size_t gt_count = 0;
|
||||
data_size_t* default_indices = gt_indices;
|
||||
data_size_t* default_count = >_count;
|
||||
SparseBinIterator<VAL_T> iterator(this, data_indices[0]);
|
||||
int8_t offset = most_freq_bin == 0 ? 1 : 0;
|
||||
if (most_freq_bin > 0 && Common::FindInBitset(threshold, num_threshold, most_freq_bin)) {
|
||||
default_indices = lte_indices;
|
||||
default_count = <e_count;
|
||||
}
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
const data_size_t idx = data_indices[i];
|
||||
const uint32_t bin = iterator.RawGet(idx);
|
||||
if (USE_MIN_BIN && (bin < min_bin || bin > max_bin)) {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
} else if (!USE_MIN_BIN && bin == 0) {
|
||||
default_indices[(*default_count)++] = idx;
|
||||
} else if (Common::FindInBitset(threshold, num_threshold,
|
||||
bin - min_bin + offset)) {
|
||||
lte_indices[lte_count++] = idx;
|
||||
} else {
|
||||
gt_indices[gt_count++] = idx;
|
||||
}
|
||||
}
|
||||
return lte_count;
|
||||
}
|
||||
|
||||
data_size_t SplitCategorical(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin,
|
||||
const uint32_t* threshold, int num_threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
return SplitCategoricalInner<true>(min_bin, max_bin, most_freq_bin,
|
||||
threshold, num_threshold, data_indices,
|
||||
cnt, lte_indices, gt_indices);
|
||||
}
|
||||
|
||||
data_size_t SplitCategorical(uint32_t max_bin, uint32_t most_freq_bin,
|
||||
const uint32_t* threshold, int num_threshold,
|
||||
const data_size_t* data_indices, data_size_t cnt,
|
||||
data_size_t* lte_indices,
|
||||
data_size_t* gt_indices) const override {
|
||||
return SplitCategoricalInner<false>(1, max_bin, most_freq_bin, threshold,
|
||||
num_threshold, data_indices, cnt,
|
||||
lte_indices, gt_indices);
|
||||
}
|
||||
|
||||
data_size_t num_data() const override { return num_data_; }
|
||||
|
||||
void* get_data() override { return nullptr; }
|
||||
|
||||
void FinishLoad() override {
|
||||
// get total non zero size
|
||||
size_t pair_cnt = 0;
|
||||
for (size_t i = 0; i < push_buffers_.size(); ++i) {
|
||||
pair_cnt += push_buffers_[i].size();
|
||||
}
|
||||
std::vector<std::pair<data_size_t, VAL_T>>& idx_val_pairs =
|
||||
push_buffers_[0];
|
||||
idx_val_pairs.reserve(pair_cnt);
|
||||
|
||||
for (size_t i = 1; i < push_buffers_.size(); ++i) {
|
||||
idx_val_pairs.insert(idx_val_pairs.end(), push_buffers_[i].begin(),
|
||||
push_buffers_[i].end());
|
||||
push_buffers_[i].clear();
|
||||
push_buffers_[i].shrink_to_fit();
|
||||
}
|
||||
// sort by data index
|
||||
std::sort(idx_val_pairs.begin(), idx_val_pairs.end(),
|
||||
[](const std::pair<data_size_t, VAL_T>& a,
|
||||
const std::pair<data_size_t, VAL_T>& b) {
|
||||
return a.first < b.first;
|
||||
});
|
||||
// load delta array
|
||||
LoadFromPair(idx_val_pairs);
|
||||
}
|
||||
|
||||
void LoadFromPair(
|
||||
const std::vector<std::pair<data_size_t, VAL_T>>& idx_val_pairs) {
|
||||
deltas_.clear();
|
||||
vals_.clear();
|
||||
deltas_.reserve(idx_val_pairs.size());
|
||||
vals_.reserve(idx_val_pairs.size());
|
||||
// transform to delta array
|
||||
data_size_t last_idx = 0;
|
||||
for (size_t i = 0; i < idx_val_pairs.size(); ++i) {
|
||||
const data_size_t cur_idx = idx_val_pairs[i].first;
|
||||
const VAL_T bin = idx_val_pairs[i].second;
|
||||
data_size_t cur_delta = cur_idx - last_idx;
|
||||
// disallow the multi-val in one row
|
||||
if (i > 0 && cur_delta == 0) {
|
||||
continue;
|
||||
}
|
||||
while (cur_delta >= 256) {
|
||||
deltas_.push_back(255);
|
||||
vals_.push_back(0);
|
||||
cur_delta -= 255;
|
||||
}
|
||||
deltas_.push_back(static_cast<uint8_t>(cur_delta));
|
||||
vals_.push_back(bin);
|
||||
last_idx = cur_idx;
|
||||
}
|
||||
// avoid out of range
|
||||
deltas_.push_back(0);
|
||||
num_vals_ = static_cast<data_size_t>(vals_.size());
|
||||
|
||||
// reduce memory cost
|
||||
deltas_.shrink_to_fit();
|
||||
vals_.shrink_to_fit();
|
||||
|
||||
// generate fast index
|
||||
GetFastIndex();
|
||||
}
|
||||
|
||||
void GetFastIndex() {
|
||||
fast_index_.clear();
|
||||
// get shift cnt
|
||||
data_size_t mod_size = (num_data_ + kNumFastIndex - 1) / kNumFastIndex;
|
||||
data_size_t pow2_mod_size = 1;
|
||||
fast_index_shift_ = 0;
|
||||
while (pow2_mod_size < mod_size) {
|
||||
pow2_mod_size <<= 1;
|
||||
++fast_index_shift_;
|
||||
}
|
||||
// build fast index
|
||||
data_size_t i_delta = -1;
|
||||
data_size_t cur_pos = 0;
|
||||
data_size_t next_threshold = 0;
|
||||
while (NextNonzero(&i_delta, &cur_pos)) {
|
||||
while (next_threshold <= cur_pos) {
|
||||
fast_index_.emplace_back(i_delta, cur_pos);
|
||||
next_threshold += pow2_mod_size;
|
||||
}
|
||||
}
|
||||
// avoid out of range
|
||||
while (next_threshold < num_data_) {
|
||||
fast_index_.emplace_back(num_vals_ - 1, cur_pos);
|
||||
next_threshold += pow2_mod_size;
|
||||
}
|
||||
fast_index_.shrink_to_fit();
|
||||
}
|
||||
|
||||
void SaveBinaryToFile(BinaryWriter* writer) const override {
|
||||
writer->AlignedWrite(&num_vals_, sizeof(num_vals_));
|
||||
writer->AlignedWrite(deltas_.data(), sizeof(uint8_t) * (num_vals_ + 1));
|
||||
writer->AlignedWrite(vals_.data(), sizeof(VAL_T) * num_vals_);
|
||||
}
|
||||
|
||||
size_t SizesInByte() const override {
|
||||
return VirtualFileWriter::AlignedSize(sizeof(num_vals_)) +
|
||||
VirtualFileWriter::AlignedSize(sizeof(uint8_t) * (num_vals_ + 1)) +
|
||||
VirtualFileWriter::AlignedSize(sizeof(VAL_T) * num_vals_);
|
||||
}
|
||||
|
||||
void LoadFromMemory(
|
||||
const void* memory,
|
||||
const std::vector<data_size_t>& local_used_indices) override {
|
||||
const char* mem_ptr = reinterpret_cast<const char*>(memory);
|
||||
data_size_t tmp_num_vals = *(reinterpret_cast<const data_size_t*>(mem_ptr));
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(tmp_num_vals));
|
||||
const uint8_t* tmp_delta = reinterpret_cast<const uint8_t*>(mem_ptr);
|
||||
mem_ptr += VirtualFileWriter::AlignedSize(sizeof(uint8_t) * (tmp_num_vals + 1));
|
||||
const VAL_T* tmp_vals = reinterpret_cast<const VAL_T*>(mem_ptr);
|
||||
|
||||
deltas_.clear();
|
||||
vals_.clear();
|
||||
num_vals_ = tmp_num_vals;
|
||||
for (data_size_t i = 0; i < num_vals_; ++i) {
|
||||
deltas_.push_back(tmp_delta[i]);
|
||||
vals_.push_back(tmp_vals[i]);
|
||||
}
|
||||
deltas_.push_back(0);
|
||||
// reduce memory cost
|
||||
deltas_.shrink_to_fit();
|
||||
vals_.shrink_to_fit();
|
||||
|
||||
if (local_used_indices.empty()) {
|
||||
// generate fast index
|
||||
GetFastIndex();
|
||||
} else {
|
||||
std::vector<std::pair<data_size_t, VAL_T>> tmp_pair;
|
||||
data_size_t cur_pos = 0;
|
||||
data_size_t j = -1;
|
||||
for (data_size_t i = 0;
|
||||
i < static_cast<data_size_t>(local_used_indices.size()); ++i) {
|
||||
const data_size_t idx = local_used_indices[i];
|
||||
while (cur_pos < idx && j < num_vals_) {
|
||||
NextNonzero(&j, &cur_pos);
|
||||
}
|
||||
if (cur_pos == idx && j < num_vals_ && vals_[j] > 0) {
|
||||
// new row index is i
|
||||
tmp_pair.emplace_back(i, vals_[j]);
|
||||
}
|
||||
}
|
||||
LoadFromPair(tmp_pair);
|
||||
}
|
||||
}
|
||||
|
||||
void CopySubrow(const Bin* full_bin, const data_size_t* used_indices,
|
||||
data_size_t num_used_indices) override {
|
||||
auto other_bin = dynamic_cast<const SparseBin<VAL_T>*>(full_bin);
|
||||
deltas_.clear();
|
||||
vals_.clear();
|
||||
data_size_t start = 0;
|
||||
if (num_used_indices > 0) {
|
||||
start = used_indices[0];
|
||||
}
|
||||
SparseBinIterator<VAL_T> iterator(other_bin, start);
|
||||
// transform to delta array
|
||||
data_size_t last_idx = 0;
|
||||
for (data_size_t i = 0; i < num_used_indices; ++i) {
|
||||
auto bin = iterator.InnerRawGet(used_indices[i]);
|
||||
if (bin > 0) {
|
||||
data_size_t cur_delta = i - last_idx;
|
||||
while (cur_delta >= 256) {
|
||||
deltas_.push_back(255);
|
||||
vals_.push_back(0);
|
||||
cur_delta -= 255;
|
||||
}
|
||||
deltas_.push_back(static_cast<uint8_t>(cur_delta));
|
||||
vals_.push_back(bin);
|
||||
last_idx = i;
|
||||
}
|
||||
}
|
||||
// avoid out of range
|
||||
deltas_.push_back(0);
|
||||
num_vals_ = static_cast<data_size_t>(vals_.size());
|
||||
|
||||
// reduce memory cost
|
||||
deltas_.shrink_to_fit();
|
||||
vals_.shrink_to_fit();
|
||||
|
||||
// generate fast index
|
||||
GetFastIndex();
|
||||
}
|
||||
|
||||
SparseBin<VAL_T>* Clone() override;
|
||||
|
||||
SparseBin(const SparseBin<VAL_T>& other)
|
||||
: num_data_(other.num_data_),
|
||||
deltas_(other.deltas_),
|
||||
vals_(other.vals_),
|
||||
num_vals_(other.num_vals_),
|
||||
push_buffers_(other.push_buffers_),
|
||||
fast_index_(other.fast_index_),
|
||||
fast_index_shift_(other.fast_index_shift_) {}
|
||||
|
||||
void InitIndex(data_size_t start_idx, data_size_t* i_delta,
|
||||
data_size_t* cur_pos) const {
|
||||
auto idx = start_idx >> fast_index_shift_;
|
||||
if (static_cast<size_t>(idx) < fast_index_.size()) {
|
||||
const auto fast_pair = fast_index_[start_idx >> fast_index_shift_];
|
||||
*i_delta = fast_pair.first;
|
||||
*cur_pos = fast_pair.second;
|
||||
} else {
|
||||
*i_delta = -1;
|
||||
*cur_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const void* GetColWiseData(uint8_t* bit_type, bool* is_sparse, std::vector<BinIterator*>* bin_iterator, const int num_threads) const override;
|
||||
|
||||
const void* GetColWiseData(uint8_t* bit_type, bool* is_sparse, BinIterator** bin_iterator) const override;
|
||||
|
||||
private:
|
||||
data_size_t num_data_;
|
||||
std::vector<uint8_t, Common::AlignmentAllocator<uint8_t, kAlignedSize>>
|
||||
deltas_;
|
||||
std::vector<VAL_T, Common::AlignmentAllocator<VAL_T, kAlignedSize>> vals_;
|
||||
data_size_t num_vals_;
|
||||
std::vector<std::vector<std::pair<data_size_t, VAL_T>>> push_buffers_;
|
||||
std::vector<std::pair<data_size_t, data_size_t>> fast_index_;
|
||||
data_size_t fast_index_shift_;
|
||||
};
|
||||
|
||||
template <typename VAL_T>
|
||||
SparseBin<VAL_T>* SparseBin<VAL_T>::Clone() {
|
||||
return new SparseBin(*this);
|
||||
}
|
||||
|
||||
template <typename VAL_T>
|
||||
inline uint32_t SparseBinIterator<VAL_T>::RawGet(data_size_t idx) {
|
||||
return InnerRawGet(idx);
|
||||
}
|
||||
|
||||
template <typename VAL_T>
|
||||
inline VAL_T SparseBinIterator<VAL_T>::InnerRawGet(data_size_t idx) {
|
||||
while (cur_pos_ < idx) {
|
||||
bin_data_->NextNonzeroFast(&i_delta_, &cur_pos_);
|
||||
}
|
||||
if (cur_pos_ == idx) {
|
||||
return bin_data_->vals_[i_delta_];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename VAL_T>
|
||||
inline void SparseBinIterator<VAL_T>::Reset(data_size_t start_idx) {
|
||||
bin_data_->InitIndex(start_idx, &i_delta_, &cur_pos_);
|
||||
}
|
||||
|
||||
template <typename VAL_T>
|
||||
BinIterator* SparseBin<VAL_T>::GetIterator(uint32_t min_bin, uint32_t max_bin,
|
||||
uint32_t most_freq_bin) const {
|
||||
return new SparseBinIterator<VAL_T>(this, min_bin, max_bin, most_freq_bin);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_IO_SPARSE_BIN_HPP_
|
||||
@@ -0,0 +1,540 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#include <LightGBM/train_share_states.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
MultiValBinWrapper::MultiValBinWrapper(MultiValBin* bin, data_size_t num_data,
|
||||
const std::vector<int>& feature_groups_contained, const int num_grad_quant_bins):
|
||||
feature_groups_contained_(feature_groups_contained) {
|
||||
num_threads_ = OMP_NUM_THREADS();
|
||||
num_data_ = num_data;
|
||||
multi_val_bin_.reset(bin);
|
||||
if (bin == nullptr) {
|
||||
return;
|
||||
}
|
||||
num_bin_ = bin->num_bin();
|
||||
num_bin_aligned_ = (num_bin_ + kAlignedSize - 1) / kAlignedSize * kAlignedSize;
|
||||
num_grad_quant_bins_ = num_grad_quant_bins;
|
||||
}
|
||||
|
||||
void MultiValBinWrapper::InitTrain(const std::vector<int>& group_feature_start,
|
||||
const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
|
||||
const std::vector<int8_t>& is_feature_used,
|
||||
const data_size_t* bagging_use_indices,
|
||||
data_size_t bagging_indices_cnt) {
|
||||
is_use_subcol_ = false;
|
||||
if (multi_val_bin_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
CopyMultiValBinSubset(group_feature_start, feature_groups,
|
||||
is_feature_used, bagging_use_indices, bagging_indices_cnt);
|
||||
const auto cur_multi_val_bin = (is_use_subcol_ || is_use_subrow_)
|
||||
? multi_val_bin_subset_.get()
|
||||
: multi_val_bin_.get();
|
||||
if (cur_multi_val_bin != nullptr) {
|
||||
num_bin_ = cur_multi_val_bin->num_bin();
|
||||
num_bin_aligned_ = (num_bin_ + kAlignedSize - 1) / kAlignedSize * kAlignedSize;
|
||||
auto num_element_per_row = cur_multi_val_bin->num_element_per_row();
|
||||
min_block_size_ = std::min<int>(static_cast<int>(0.3f * num_bin_ /
|
||||
(num_element_per_row + kZeroThreshold)) + 1, 1024);
|
||||
min_block_size_ = std::max<int>(min_block_size_, 32);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_QUANT_GRAD, int HIST_BITS, int INNER_HIST_BITS>
|
||||
void MultiValBinWrapper::HistMove(const std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf) {
|
||||
if (!is_use_subcol_ && INNER_HIST_BITS != 8) {
|
||||
return;
|
||||
}
|
||||
if (USE_QUANT_GRAD) {
|
||||
if (HIST_BITS == 32) {
|
||||
const int64_t* src = reinterpret_cast<const int64_t*>(hist_buf.data()) + hist_buf.size() / 2 -
|
||||
static_cast<size_t>(num_bin_aligned_);
|
||||
#pragma omp parallel for schedule(static) num_threads(num_threads_)
|
||||
for (int i = 0; i < static_cast<int>(hist_move_src_.size()); ++i) {
|
||||
std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2,
|
||||
reinterpret_cast<int64_t*>(origin_hist_data_) + hist_move_dest_[i] / 2);
|
||||
}
|
||||
} else if (HIST_BITS == 16) {
|
||||
if (is_use_subcol_) {
|
||||
const int32_t* src = reinterpret_cast<const int32_t*>(hist_buf.data()) + hist_buf.size() / 2 -
|
||||
static_cast<size_t>(num_bin_aligned_);
|
||||
#pragma omp parallel for schedule(static) num_threads(num_threads_)
|
||||
for (int i = 0; i < static_cast<int>(hist_move_src_.size()); ++i) {
|
||||
std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2,
|
||||
reinterpret_cast<int32_t*>(origin_hist_data_) + hist_move_dest_[i] / 2);
|
||||
}
|
||||
} else {
|
||||
CHECK_EQ(INNER_HIST_BITS, 8);
|
||||
const int32_t* src = reinterpret_cast<const int32_t*>(hist_buf.data()) + hist_buf.size() / 2;
|
||||
int32_t* orig_ptr = reinterpret_cast<int32_t*>(origin_hist_data_);
|
||||
#pragma omp parallel for schedule(static) num_threads(num_threads_)
|
||||
for (int i = 0; i < num_bin_; ++i) {
|
||||
orig_ptr[i] = src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const hist_t* src = hist_buf.data() + hist_buf.size() -
|
||||
2 * static_cast<size_t>(num_bin_aligned_);
|
||||
#pragma omp parallel for schedule(static) num_threads(num_threads_)
|
||||
for (int i = 0; i < static_cast<int>(hist_move_src_.size()); ++i) {
|
||||
std::copy_n(src + hist_move_src_[i], hist_move_size_[i],
|
||||
origin_hist_data_ + hist_move_dest_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template void MultiValBinWrapper::HistMove<false, 0, 0>(const std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMove<false, 0, 8>(const std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMove<true, 16, 8>(const std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMove<true, 16, 16>(const std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMove<true, 32, 8>(const std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMove<true, 32, 32>(const std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
|
||||
|
||||
template <bool USE_QUANT_GRAD, int HIST_BITS, int INNER_HIST_BITS>
|
||||
void MultiValBinWrapper::HistMerge(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf) {
|
||||
int n_bin_block = 1;
|
||||
int bin_block_size = num_bin_;
|
||||
Threading::BlockInfo<data_size_t>(num_threads_, num_bin_, 512, &n_bin_block,
|
||||
&bin_block_size);
|
||||
if (USE_QUANT_GRAD) {
|
||||
if (HIST_BITS == 32) {
|
||||
int64_t* dst = reinterpret_cast<int64_t*>(origin_hist_data_);
|
||||
if (is_use_subcol_) {
|
||||
dst = reinterpret_cast<int64_t*>(hist_buf->data()) + hist_buf->size() / 2 - static_cast<size_t>(num_bin_aligned_);
|
||||
}
|
||||
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
|
||||
for (int t = 0; t < n_bin_block; ++t) {
|
||||
const int start = t * bin_block_size;
|
||||
const int end = std::min(start + bin_block_size, num_bin_);
|
||||
for (int tid = 1; tid < n_data_block_; ++tid) {
|
||||
auto src_ptr = reinterpret_cast<const int64_t*>(hist_buf->data()) + static_cast<size_t>(num_bin_aligned_) * (tid - 1);
|
||||
for (int i = start; i < end; ++i) {
|
||||
dst[i] += src_ptr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (HIST_BITS == 16 && INNER_HIST_BITS == 16) {
|
||||
int32_t* dst = reinterpret_cast<int32_t*>(origin_hist_data_);
|
||||
if (is_use_subcol_) {
|
||||
dst = reinterpret_cast<int32_t*>(hist_buf->data()) + hist_buf->size() / 2 - static_cast<size_t>(num_bin_aligned_);
|
||||
}
|
||||
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
|
||||
for (int t = 0; t < n_bin_block; ++t) {
|
||||
const int start = t * bin_block_size;
|
||||
const int end = std::min(start + bin_block_size, num_bin_);
|
||||
for (int tid = 1; tid < n_data_block_; ++tid) {
|
||||
auto src_ptr = reinterpret_cast<const int32_t*>(hist_buf->data()) + static_cast<size_t>(num_bin_aligned_) * (tid - 1);
|
||||
for (int i = start; i < end; ++i) {
|
||||
dst[i] += src_ptr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (HIST_BITS == 16 && INNER_HIST_BITS == 8) {
|
||||
int32_t* dst = reinterpret_cast<int32_t*>(hist_buf->data()) + hist_buf->size() / 2;
|
||||
std::memset(reinterpret_cast<void*>(dst), 0, num_bin_ * kInt16HistBufferEntrySize);
|
||||
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
|
||||
for (int t = 0; t < n_bin_block; ++t) {
|
||||
const int start = t * bin_block_size;
|
||||
const int end = std::min(start + bin_block_size, num_bin_);
|
||||
for (int tid = 0; tid < n_data_block_; ++tid) {
|
||||
auto src_ptr = reinterpret_cast<const int16_t*>(hist_buf->data()) + static_cast<size_t>(num_bin_aligned_) * tid;
|
||||
for (int i = start; i < end; ++i) {
|
||||
const int16_t packed_hist = src_ptr[i];
|
||||
const int32_t packed_hist_int32 = (static_cast<int32_t>(static_cast<int8_t>(packed_hist >> 8)) << 16) | static_cast<int32_t>(packed_hist & 0x00ff);
|
||||
dst[i] += packed_hist_int32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hist_t* dst = origin_hist_data_;
|
||||
if (is_use_subcol_) {
|
||||
dst = hist_buf->data() + hist_buf->size() - 2 * static_cast<size_t>(num_bin_aligned_);
|
||||
}
|
||||
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
|
||||
for (int t = 0; t < n_bin_block; ++t) {
|
||||
const int start = t * bin_block_size;
|
||||
const int end = std::min(start + bin_block_size, num_bin_);
|
||||
for (int tid = 1; tid < n_data_block_; ++tid) {
|
||||
auto src_ptr = hist_buf->data() + static_cast<size_t>(num_bin_aligned_) * 2 * (tid - 1);
|
||||
for (int i = start * 2; i < end * 2; ++i) {
|
||||
dst[i] += src_ptr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template void MultiValBinWrapper::HistMerge<false, 0, 0>(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMerge<false, 0, 8>(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMerge<true, 16, 8>(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMerge<true, 16, 16>(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMerge<true, 32, 8>(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
|
||||
|
||||
template void MultiValBinWrapper::HistMerge<true, 32, 32>(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
|
||||
|
||||
void MultiValBinWrapper::ResizeHistBuf(std::vector<hist_t,
|
||||
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf,
|
||||
MultiValBin* sub_multi_val_bin,
|
||||
hist_t* origin_hist_data) {
|
||||
num_bin_ = sub_multi_val_bin->num_bin();
|
||||
num_bin_aligned_ = (num_bin_ + kAlignedSize - 1) / kAlignedSize * kAlignedSize;
|
||||
origin_hist_data_ = origin_hist_data;
|
||||
size_t new_buf_size = static_cast<size_t>(n_data_block_) * static_cast<size_t>(num_bin_aligned_) * 2;
|
||||
if (hist_buf->size() < new_buf_size) {
|
||||
hist_buf->resize(new_buf_size);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiValBinWrapper::CopyMultiValBinSubset(
|
||||
const std::vector<int>& group_feature_start,
|
||||
const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
|
||||
const std::vector<int8_t>& is_feature_used,
|
||||
const data_size_t* bagging_use_indices,
|
||||
data_size_t bagging_indices_cnt) {
|
||||
double sum_used_dense_ratio = 0.0;
|
||||
double sum_dense_ratio = 0.0;
|
||||
int num_used = 0;
|
||||
int total = 0;
|
||||
std::vector<int> used_feature_index;
|
||||
for (int i : feature_groups_contained_) {
|
||||
int f_start = group_feature_start[i];
|
||||
if (feature_groups[i]->is_multi_val_) {
|
||||
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
|
||||
const auto dense_rate =
|
||||
1.0 - feature_groups[i]->bin_mappers_[j]->sparse_rate();
|
||||
if (is_feature_used[f_start + j]) {
|
||||
++num_used;
|
||||
used_feature_index.push_back(total);
|
||||
sum_used_dense_ratio += dense_rate;
|
||||
}
|
||||
sum_dense_ratio += dense_rate;
|
||||
++total;
|
||||
}
|
||||
} else {
|
||||
bool is_group_used = false;
|
||||
double dense_rate = 0;
|
||||
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
|
||||
if (is_feature_used[f_start + j]) {
|
||||
is_group_used = true;
|
||||
}
|
||||
dense_rate += 1.0 - feature_groups[i]->bin_mappers_[j]->sparse_rate();
|
||||
}
|
||||
if (is_group_used) {
|
||||
++num_used;
|
||||
used_feature_index.push_back(total);
|
||||
sum_used_dense_ratio += dense_rate;
|
||||
}
|
||||
sum_dense_ratio += dense_rate;
|
||||
++total;
|
||||
}
|
||||
}
|
||||
const double k_subfeature_threshold = 0.6;
|
||||
if (sum_used_dense_ratio >= sum_dense_ratio * k_subfeature_threshold) {
|
||||
// only need to copy subset
|
||||
if (is_use_subrow_ && !is_subrow_copied_) {
|
||||
if (multi_val_bin_subset_ == nullptr) {
|
||||
multi_val_bin_subset_.reset(multi_val_bin_->CreateLike(
|
||||
bagging_indices_cnt, multi_val_bin_->num_bin(), total,
|
||||
multi_val_bin_->num_element_per_row(), multi_val_bin_->offsets()));
|
||||
} else {
|
||||
multi_val_bin_subset_->ReSize(
|
||||
bagging_indices_cnt, multi_val_bin_->num_bin(), total,
|
||||
multi_val_bin_->num_element_per_row(), multi_val_bin_->offsets());
|
||||
}
|
||||
multi_val_bin_subset_->CopySubrow(
|
||||
multi_val_bin_.get(), bagging_use_indices,
|
||||
bagging_indices_cnt);
|
||||
// avoid to copy subset many times
|
||||
is_subrow_copied_ = true;
|
||||
}
|
||||
} else {
|
||||
is_use_subcol_ = true;
|
||||
std::vector<uint32_t> upper_bound;
|
||||
std::vector<uint32_t> lower_bound;
|
||||
std::vector<uint32_t> delta;
|
||||
std::vector<uint32_t> offsets;
|
||||
hist_move_src_.clear();
|
||||
hist_move_dest_.clear();
|
||||
hist_move_size_.clear();
|
||||
|
||||
const int offset = multi_val_bin_->IsSparse() ? 1 : 0;
|
||||
int num_total_bin = offset;
|
||||
int new_num_total_bin = offset;
|
||||
offsets.push_back(static_cast<uint32_t>(new_num_total_bin));
|
||||
for (int i : feature_groups_contained_) {
|
||||
int f_start = group_feature_start[i];
|
||||
if (feature_groups[i]->is_multi_val_) {
|
||||
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
|
||||
const auto& bin_mapper = feature_groups[i]->bin_mappers_[j];
|
||||
if (i == 0 && j == 0 && bin_mapper->GetMostFreqBin() > 0) {
|
||||
num_total_bin = 1;
|
||||
}
|
||||
int cur_num_bin = bin_mapper->num_bin();
|
||||
if (bin_mapper->GetMostFreqBin() == 0) {
|
||||
cur_num_bin -= offset;
|
||||
}
|
||||
num_total_bin += cur_num_bin;
|
||||
if (is_feature_used[f_start + j]) {
|
||||
new_num_total_bin += cur_num_bin;
|
||||
offsets.push_back(static_cast<uint32_t>(new_num_total_bin));
|
||||
lower_bound.push_back(num_total_bin - cur_num_bin);
|
||||
upper_bound.push_back(num_total_bin);
|
||||
|
||||
hist_move_src_.push_back(
|
||||
(new_num_total_bin - cur_num_bin) * 2);
|
||||
hist_move_dest_.push_back((num_total_bin - cur_num_bin) *
|
||||
2);
|
||||
hist_move_size_.push_back(cur_num_bin * 2);
|
||||
delta.push_back(num_total_bin - new_num_total_bin);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bool is_group_used = false;
|
||||
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
|
||||
if (is_feature_used[f_start + j]) {
|
||||
is_group_used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int cur_num_bin = feature_groups[i]->bin_offsets_.back() - offset;
|
||||
num_total_bin += cur_num_bin;
|
||||
if (is_group_used) {
|
||||
new_num_total_bin += cur_num_bin;
|
||||
offsets.push_back(static_cast<uint32_t>(new_num_total_bin));
|
||||
lower_bound.push_back(num_total_bin - cur_num_bin);
|
||||
upper_bound.push_back(num_total_bin);
|
||||
|
||||
hist_move_src_.push_back(
|
||||
(new_num_total_bin - cur_num_bin) * 2);
|
||||
hist_move_dest_.push_back((num_total_bin - cur_num_bin) *
|
||||
2);
|
||||
hist_move_size_.push_back(cur_num_bin * 2);
|
||||
delta.push_back(num_total_bin - new_num_total_bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
// avoid out of range
|
||||
lower_bound.push_back(num_total_bin);
|
||||
upper_bound.push_back(num_total_bin);
|
||||
data_size_t num_data = is_use_subrow_ ? bagging_indices_cnt : num_data_;
|
||||
if (multi_val_bin_subset_ == nullptr) {
|
||||
multi_val_bin_subset_.reset(multi_val_bin_->CreateLike(
|
||||
num_data, new_num_total_bin, num_used, sum_used_dense_ratio, offsets));
|
||||
} else {
|
||||
multi_val_bin_subset_->ReSize(num_data, new_num_total_bin,
|
||||
num_used, sum_used_dense_ratio, offsets);
|
||||
}
|
||||
if (is_use_subrow_) {
|
||||
multi_val_bin_subset_->CopySubrowAndSubcol(
|
||||
multi_val_bin_.get(), bagging_use_indices,
|
||||
bagging_indices_cnt, used_feature_index, lower_bound,
|
||||
upper_bound, delta);
|
||||
// may need to recopy subset
|
||||
is_subrow_copied_ = false;
|
||||
} else {
|
||||
multi_val_bin_subset_->CopySubcol(
|
||||
multi_val_bin_.get(), used_feature_index, lower_bound, upper_bound, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrainingShareStates::CalcBinOffsets(const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
|
||||
std::vector<uint32_t>* offsets, bool in_is_col_wise) {
|
||||
offsets->clear();
|
||||
feature_hist_offsets_.clear();
|
||||
if (in_is_col_wise) {
|
||||
uint32_t cur_num_bin = 0;
|
||||
uint32_t hist_cur_num_bin = 0;
|
||||
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
|
||||
const std::unique_ptr<FeatureGroup>& feature_group = feature_groups[group];
|
||||
if (feature_group->is_multi_val_) {
|
||||
if (feature_group->is_dense_multi_val_) {
|
||||
for (int i = 0; i < feature_group->num_feature_; ++i) {
|
||||
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
|
||||
if (group == 0 && i == 0 && bin_mapper->GetMostFreqBin() > 0) {
|
||||
cur_num_bin += 1;
|
||||
hist_cur_num_bin += 1;
|
||||
}
|
||||
offsets->push_back(cur_num_bin);
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin);
|
||||
int num_bin = bin_mapper->num_bin();
|
||||
hist_cur_num_bin += num_bin;
|
||||
if (bin_mapper->GetMostFreqBin() == 0) {
|
||||
feature_hist_offsets_.back() += 1;
|
||||
}
|
||||
cur_num_bin += num_bin;
|
||||
}
|
||||
offsets->push_back(cur_num_bin);
|
||||
CHECK(cur_num_bin == feature_group->bin_offsets_.back());
|
||||
} else {
|
||||
cur_num_bin += 1;
|
||||
hist_cur_num_bin += 1;
|
||||
for (int i = 0; i < feature_group->num_feature_; ++i) {
|
||||
offsets->push_back(cur_num_bin);
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin);
|
||||
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
|
||||
int num_bin = bin_mapper->num_bin();
|
||||
if (bin_mapper->GetMostFreqBin() == 0) {
|
||||
num_bin -= 1;
|
||||
}
|
||||
hist_cur_num_bin += num_bin;
|
||||
cur_num_bin += num_bin;
|
||||
}
|
||||
offsets->push_back(cur_num_bin);
|
||||
CHECK(cur_num_bin == feature_group->bin_offsets_.back());
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < feature_group->num_feature_; ++i) {
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin + feature_group->bin_offsets_[i]);
|
||||
}
|
||||
hist_cur_num_bin += feature_group->bin_offsets_.back();
|
||||
}
|
||||
}
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin);
|
||||
num_hist_total_bin_ = static_cast<int>(feature_hist_offsets_.back());
|
||||
} else {
|
||||
double sum_dense_ratio = 0.0f;
|
||||
int ncol = 0;
|
||||
for (int gid = 0; gid < static_cast<int>(feature_groups.size()); ++gid) {
|
||||
if (feature_groups[gid]->is_multi_val_) {
|
||||
ncol += feature_groups[gid]->num_feature_;
|
||||
} else {
|
||||
++ncol;
|
||||
}
|
||||
for (int fid = 0; fid < feature_groups[gid]->num_feature_; ++fid) {
|
||||
const auto& bin_mapper = feature_groups[gid]->bin_mappers_[fid];
|
||||
sum_dense_ratio += 1.0f - bin_mapper->sparse_rate();
|
||||
}
|
||||
}
|
||||
sum_dense_ratio /= ncol;
|
||||
const bool is_sparse_row_wise = (1.0f - sum_dense_ratio) >=
|
||||
MultiValBin::multi_val_bin_sparse_threshold ? 1 : 0;
|
||||
if (is_sparse_row_wise) {
|
||||
int cur_num_bin = 1;
|
||||
uint32_t hist_cur_num_bin = 1;
|
||||
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
|
||||
const std::unique_ptr<FeatureGroup>& feature_group = feature_groups[group];
|
||||
if (feature_group->is_multi_val_) {
|
||||
for (int i = 0; i < feature_group->num_feature_; ++i) {
|
||||
offsets->push_back(cur_num_bin);
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin);
|
||||
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
|
||||
int num_bin = bin_mapper->num_bin();
|
||||
if (bin_mapper->GetMostFreqBin() == 0) {
|
||||
num_bin -= 1;
|
||||
}
|
||||
cur_num_bin += num_bin;
|
||||
hist_cur_num_bin += num_bin;
|
||||
}
|
||||
} else {
|
||||
offsets->push_back(cur_num_bin);
|
||||
cur_num_bin += feature_group->bin_offsets_.back() - 1;
|
||||
for (int i = 0; i < feature_group->num_feature_; ++i) {
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin + feature_group->bin_offsets_[i] - 1);
|
||||
}
|
||||
hist_cur_num_bin += feature_group->bin_offsets_.back() - 1;
|
||||
}
|
||||
}
|
||||
offsets->push_back(cur_num_bin);
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin);
|
||||
} else {
|
||||
int cur_num_bin = 0;
|
||||
uint32_t hist_cur_num_bin = 0;
|
||||
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
|
||||
const std::unique_ptr<FeatureGroup>& feature_group = feature_groups[group];
|
||||
if (feature_group->is_multi_val_) {
|
||||
for (int i = 0; i < feature_group->num_feature_; ++i) {
|
||||
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
|
||||
if (group == 0 && i == 0 && bin_mapper->GetMostFreqBin() > 0) {
|
||||
cur_num_bin += 1;
|
||||
hist_cur_num_bin += 1;
|
||||
}
|
||||
offsets->push_back(cur_num_bin);
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin);
|
||||
int num_bin = bin_mapper->num_bin();
|
||||
cur_num_bin += num_bin;
|
||||
hist_cur_num_bin += num_bin;
|
||||
if (bin_mapper->GetMostFreqBin() == 0) {
|
||||
feature_hist_offsets_.back() += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
offsets->push_back(cur_num_bin);
|
||||
cur_num_bin += feature_group->bin_offsets_.back();
|
||||
for (int i = 0; i < feature_group->num_feature_; ++i) {
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin + feature_group->bin_offsets_[i]);
|
||||
}
|
||||
hist_cur_num_bin += feature_group->bin_offsets_.back();
|
||||
}
|
||||
}
|
||||
offsets->push_back(cur_num_bin);
|
||||
feature_hist_offsets_.push_back(hist_cur_num_bin);
|
||||
}
|
||||
num_hist_total_bin_ = static_cast<int>(feature_hist_offsets_.back());
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
column_hist_offsets_ = *offsets;
|
||||
#endif // USE_CUDA
|
||||
}
|
||||
|
||||
void TrainingShareStates::SetMultiValBin(MultiValBin* bin, data_size_t num_data,
|
||||
const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
|
||||
bool dense_only, bool sparse_only, const int num_grad_quant_bins) {
|
||||
num_threads = OMP_NUM_THREADS();
|
||||
if (bin == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::vector<int> feature_groups_contained;
|
||||
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
|
||||
const auto& feature_group = feature_groups[group];
|
||||
if (feature_group->is_multi_val_) {
|
||||
if (!dense_only) {
|
||||
feature_groups_contained.push_back(group);
|
||||
}
|
||||
} else if (!sparse_only) {
|
||||
feature_groups_contained.push_back(group);
|
||||
}
|
||||
}
|
||||
num_total_bin_ += bin->num_bin();
|
||||
num_elements_per_row_ += bin->num_element_per_row();
|
||||
multi_val_bin_wrapper_.reset(new MultiValBinWrapper(
|
||||
bin, num_data, feature_groups_contained, num_grad_quant_bins));
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
+1060
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/application.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#ifdef USE_MPI
|
||||
#include "network/linkers.h"
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
bool success = false;
|
||||
try {
|
||||
LightGBM::Application app(argc, argv);
|
||||
app.Run();
|
||||
|
||||
#ifdef USE_MPI
|
||||
LightGBM::Linkers::MpiFinalizeIfIsParallel();
|
||||
#endif
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
std::cerr << "Met Exceptions:" << std::endl;
|
||||
std::cerr << ex.what() << std::endl;
|
||||
}
|
||||
catch (const std::string& ex) {
|
||||
std::cerr << "Met Exceptions:" << std::endl;
|
||||
std::cerr << ex << std::endl;
|
||||
}
|
||||
catch (...) {
|
||||
std::cerr << "Unknown Exceptions" << std::endl;
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
#ifdef USE_MPI
|
||||
LightGBM::Linkers::MpiAbortIfIsParallel();
|
||||
#endif
|
||||
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_METRIC_BINARY_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_BINARY_METRIC_HPP_
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
/*!
|
||||
* \brief Metric for binary classification task.
|
||||
* Use static class "PointWiseLossCalculator" to calculate loss point-wise
|
||||
*/
|
||||
template<typename PointWiseLossCalculator>
|
||||
class BinaryMetric: public Metric {
|
||||
public:
|
||||
explicit BinaryMetric(const Config&) {
|
||||
}
|
||||
|
||||
virtual ~BinaryMetric() {
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back(PointWiseLossCalculator::Name());
|
||||
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
|
||||
// get weights
|
||||
weights_ = metadata.weights();
|
||||
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
sum_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
sum_weights_ += weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
|
||||
double sum_loss = 0.0f;
|
||||
if (objective == nullptr) {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i]);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i]) * weights_[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double prob = 0;
|
||||
objective->ConvertOutput(&score[i], &prob);
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], prob);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double prob = 0;
|
||||
objective->ConvertOutput(&score[i], &prob);
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], prob) * weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
double loss = sum_loss / sum_weights_;
|
||||
return std::vector<double>(1, loss);
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer of weighs */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Name of test set */
|
||||
std::vector<std::string> name_;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Log loss metric for binary classification task.
|
||||
*/
|
||||
class BinaryLoglossMetric: public BinaryMetric<BinaryLoglossMetric> {
|
||||
public:
|
||||
explicit BinaryLoglossMetric(const Config& config) :BinaryMetric<BinaryLoglossMetric>(config) {}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double prob) {
|
||||
if (label <= 0) {
|
||||
if (1.0f - prob > kEpsilon) {
|
||||
return -std::log(1.0f - prob);
|
||||
}
|
||||
} else {
|
||||
if (prob > kEpsilon) {
|
||||
return -std::log(prob);
|
||||
}
|
||||
}
|
||||
return -std::log(kEpsilon);
|
||||
}
|
||||
|
||||
inline static const char* Name() {
|
||||
return "binary_logloss";
|
||||
}
|
||||
};
|
||||
/*!
|
||||
* \brief Error rate metric for binary classification task.
|
||||
*/
|
||||
class BinaryErrorMetric: public BinaryMetric<BinaryErrorMetric> {
|
||||
public:
|
||||
explicit BinaryErrorMetric(const Config& config) :BinaryMetric<BinaryErrorMetric>(config) {}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double prob) {
|
||||
if (prob <= 0.5f) {
|
||||
return label > 0;
|
||||
} else {
|
||||
return label <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline static const char* Name() {
|
||||
return "binary_error";
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Auc Metric for binary classification task.
|
||||
*/
|
||||
class AUCMetric: public Metric {
|
||||
public:
|
||||
explicit AUCMetric(const Config&) {
|
||||
}
|
||||
|
||||
virtual ~AUCMetric() {
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back("auc");
|
||||
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
// get weights
|
||||
weights_ = metadata.weights();
|
||||
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
sum_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
sum_weights_ += weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
|
||||
// get indices sorted by score, descent order
|
||||
std::vector<data_size_t> sorted_idx;
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sorted_idx.emplace_back(i);
|
||||
}
|
||||
Common::ParallelSort(sorted_idx.begin(), sorted_idx.end(), [score](data_size_t a, data_size_t b) {return score[a] > score[b]; });
|
||||
// temp sum of positive label
|
||||
double cur_pos = 0.0f;
|
||||
// total sum of positive label
|
||||
double sum_pos = 0.0f;
|
||||
// accumulate of AUC
|
||||
double accum = 0.0f;
|
||||
// temp sum of negative label
|
||||
double cur_neg = 0.0f;
|
||||
double threshold = score[sorted_idx[0]];
|
||||
if (weights_ == nullptr) { // no weights
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const label_t cur_label = label_[sorted_idx[i]];
|
||||
const double cur_score = score[sorted_idx[i]];
|
||||
// new threshold
|
||||
if (cur_score != threshold) {
|
||||
threshold = cur_score;
|
||||
// accumulate
|
||||
accum += cur_neg*(cur_pos * 0.5f + sum_pos);
|
||||
sum_pos += cur_pos;
|
||||
// reset
|
||||
cur_neg = cur_pos = 0.0f;
|
||||
}
|
||||
cur_neg += (cur_label <= 0);
|
||||
cur_pos += (cur_label > 0);
|
||||
}
|
||||
} else { // has weights
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const label_t cur_label = label_[sorted_idx[i]];
|
||||
const double cur_score = score[sorted_idx[i]];
|
||||
const label_t cur_weight = weights_[sorted_idx[i]];
|
||||
// new threshold
|
||||
if (cur_score != threshold) {
|
||||
threshold = cur_score;
|
||||
// accumulate
|
||||
accum += cur_neg*(cur_pos * 0.5f + sum_pos);
|
||||
sum_pos += cur_pos;
|
||||
// reset
|
||||
cur_neg = cur_pos = 0.0f;
|
||||
}
|
||||
cur_neg += (cur_label <= 0)*cur_weight;
|
||||
cur_pos += (cur_label > 0)*cur_weight;
|
||||
}
|
||||
}
|
||||
accum += cur_neg*(cur_pos * 0.5f + sum_pos);
|
||||
sum_pos += cur_pos;
|
||||
double auc = 1.0f;
|
||||
if (sum_pos > 0.0f && sum_pos != sum_weights_) {
|
||||
auc = accum / (sum_pos *(sum_weights_ - sum_pos));
|
||||
}
|
||||
return std::vector<double>(1, auc);
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer of weighs */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Name of test set */
|
||||
std::vector<std::string> name_;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Average Precision Metric for binary classification task.
|
||||
*/
|
||||
class AveragePrecisionMetric: public Metric {
|
||||
public:
|
||||
explicit AveragePrecisionMetric(const Config&) {
|
||||
}
|
||||
|
||||
virtual ~AveragePrecisionMetric() {
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back("average_precision");
|
||||
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
// get weights
|
||||
weights_ = metadata.weights();
|
||||
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
sum_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
sum_weights_ += weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
|
||||
// get indices sorted by score, descending order
|
||||
std::vector<data_size_t> sorted_idx;
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sorted_idx.emplace_back(i);
|
||||
}
|
||||
Common::ParallelSort(sorted_idx.begin(), sorted_idx.end(), [score](data_size_t a, data_size_t b) {return score[a] > score[b]; });
|
||||
// temp sum of positive label
|
||||
double cur_actual_pos = 0.0f;
|
||||
// total sum of positive label
|
||||
double sum_actual_pos = 0.0f;
|
||||
// total sum of predicted positive
|
||||
double sum_pred_pos = 0.0f;
|
||||
// accumulated precision
|
||||
double accum_prec = 1.0f;
|
||||
// accumulated pr-auc
|
||||
double accum = 0.0f;
|
||||
// temp sum of negative label
|
||||
double cur_neg = 0.0f;
|
||||
double threshold = score[sorted_idx[0]];
|
||||
if (weights_ == nullptr) { // no weights
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const label_t cur_label = label_[sorted_idx[i]];
|
||||
const double cur_score = score[sorted_idx[i]];
|
||||
// new threshold
|
||||
if (cur_score != threshold) {
|
||||
threshold = cur_score;
|
||||
// accumulate
|
||||
sum_actual_pos += cur_actual_pos;
|
||||
sum_pred_pos += cur_actual_pos + cur_neg;
|
||||
accum_prec = sum_actual_pos / sum_pred_pos;
|
||||
accum += cur_actual_pos * accum_prec;
|
||||
// reset
|
||||
cur_neg = cur_actual_pos = 0.0f;
|
||||
}
|
||||
cur_neg += (cur_label <= 0);
|
||||
cur_actual_pos += (cur_label > 0);
|
||||
}
|
||||
} else { // has weights
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const label_t cur_label = label_[sorted_idx[i]];
|
||||
const double cur_score = score[sorted_idx[i]];
|
||||
const label_t cur_weight = weights_[sorted_idx[i]];
|
||||
// new threshold
|
||||
if (cur_score != threshold) {
|
||||
threshold = cur_score;
|
||||
// accumulate
|
||||
sum_actual_pos += cur_actual_pos;
|
||||
sum_pred_pos += cur_actual_pos + cur_neg;
|
||||
accum_prec = sum_actual_pos / sum_pred_pos;
|
||||
accum += cur_actual_pos * accum_prec;
|
||||
// reset
|
||||
cur_neg = cur_actual_pos = 0.0f;
|
||||
}
|
||||
cur_neg += (cur_label <= 0) * cur_weight;
|
||||
cur_actual_pos += (cur_label > 0) * cur_weight;
|
||||
}
|
||||
}
|
||||
sum_actual_pos += cur_actual_pos;
|
||||
sum_pred_pos += cur_actual_pos + cur_neg;
|
||||
accum_prec = sum_actual_pos / sum_pred_pos;
|
||||
accum += cur_actual_pos * accum_prec;
|
||||
double ap = 1.0f;
|
||||
if (sum_actual_pos > 0.0f && sum_actual_pos != sum_weights_) {
|
||||
ap = accum / sum_actual_pos;
|
||||
}
|
||||
return std::vector<double>(1, ap);
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer of weighs */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Name of test set */
|
||||
std::vector<std::string> name_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_METRIC_BINARY_METRIC_HPP_
|
||||
@@ -0,0 +1,35 @@
|
||||
/*!
|
||||
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_binary_metric.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_METRIC, typename CUDA_METRIC>
|
||||
std::vector<double> CUDABinaryMetricInterface<HOST_METRIC, CUDA_METRIC>::Eval(const double* score, const ObjectiveFunction* objective) const {
|
||||
const double* score_convert = score;
|
||||
if (objective != nullptr && objective->NeedConvertOutputCUDA()) {
|
||||
this->score_convert_buffer_.Resize(static_cast<size_t>(this->num_data_) * static_cast<size_t>(this->num_class_));
|
||||
score_convert = objective->ConvertOutputCUDA(this->num_data_, score, this->score_convert_buffer_.RawData());
|
||||
}
|
||||
double sum_loss = 0.0, sum_weight = 0.0;
|
||||
this->LaunchEvalKernel(score_convert, &sum_loss, &sum_weight);
|
||||
const double eval_score = sum_loss / sum_weight;
|
||||
return std::vector<double>{eval_score};
|
||||
}
|
||||
|
||||
CUDABinaryLoglossMetric::CUDABinaryLoglossMetric(const Config& config):CUDABinaryMetricInterface<BinaryLoglossMetric, CUDABinaryLoglossMetric>(config) {}
|
||||
|
||||
CUDABinaryErrorMetric::CUDABinaryErrorMetric(const Config& config):CUDABinaryMetricInterface<BinaryErrorMetric, CUDABinaryErrorMetric>(config) {}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,73 @@
|
||||
/*!
|
||||
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_METRIC_CUDA_CUDA_BINARY_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_CUDA_CUDA_BINARY_METRIC_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_metric.hpp>
|
||||
#include <LightGBM/cuda/cuda_utils.hu>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_regression_metric.hpp"
|
||||
#include "../binary_metric.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_METRIC, typename CUDA_METRIC>
|
||||
class CUDABinaryMetricInterface: public CUDAPointwiseMetricInterface<HOST_METRIC, CUDA_METRIC> {
|
||||
public:
|
||||
explicit CUDABinaryMetricInterface(const Config& config): CUDAPointwiseMetricInterface<HOST_METRIC, CUDA_METRIC>(config) {}
|
||||
|
||||
virtual ~CUDABinaryMetricInterface() {}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override;
|
||||
};
|
||||
|
||||
class CUDABinaryLoglossMetric: public CUDABinaryMetricInterface<BinaryLoglossMetric, CUDABinaryLoglossMetric> {
|
||||
public:
|
||||
explicit CUDABinaryLoglossMetric(const Config& config);
|
||||
|
||||
virtual ~CUDABinaryLoglossMetric() {}
|
||||
|
||||
__device__ static double MetricOnPointCUDA(label_t label, double score, const double /*param*/) {
|
||||
// score should have been converted to probability
|
||||
if (label <= 0) {
|
||||
if (1.0f - score > kEpsilon) {
|
||||
return -log(1.0f - score);
|
||||
}
|
||||
} else {
|
||||
if (score > kEpsilon) {
|
||||
return -log(score);
|
||||
}
|
||||
}
|
||||
return -log(kEpsilon);
|
||||
}
|
||||
};
|
||||
|
||||
class CUDABinaryErrorMetric: public CUDABinaryMetricInterface<BinaryErrorMetric, CUDABinaryErrorMetric> {
|
||||
public:
|
||||
explicit CUDABinaryErrorMetric(const Config& config);
|
||||
|
||||
virtual ~CUDABinaryErrorMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, const double /*param*/) {
|
||||
if (score <= 0.5f) {
|
||||
return label > 0;
|
||||
} else {
|
||||
return label <= 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
|
||||
#endif // LIGHTGBM_SRC_METRIC_CUDA_CUDA_BINARY_METRIC_HPP_
|
||||
@@ -0,0 +1,52 @@
|
||||
/*!
|
||||
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_binary_metric.hpp"
|
||||
#include "cuda_pointwise_metric.hpp"
|
||||
#include "cuda_regression_metric.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_METRIC, typename CUDA_METRIC>
|
||||
void CUDAPointwiseMetricInterface<HOST_METRIC, CUDA_METRIC>::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDAMetricInterface<HOST_METRIC>::Init(metadata, num_data);
|
||||
const int max_num_reduce_blocks = (this->num_data_ + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD;
|
||||
if (this->cuda_weights_ == nullptr) {
|
||||
reduce_block_buffer_.Resize(max_num_reduce_blocks);
|
||||
} else {
|
||||
reduce_block_buffer_.Resize(max_num_reduce_blocks * 2);
|
||||
}
|
||||
const int max_num_reduce_blocks_inner = (max_num_reduce_blocks + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD;
|
||||
if (this->cuda_weights_ == nullptr) {
|
||||
reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner);
|
||||
} else {
|
||||
reduce_block_buffer_inner_.Resize(max_num_reduce_blocks_inner * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Regression metrics
|
||||
template void CUDAPointwiseMetricInterface<RMSEMetric, CUDARMSEMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<L2Metric, CUDAL2Metric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<QuantileMetric, CUDAQuantileMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<L1Metric, CUDAL1Metric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<HuberLossMetric, CUDAHuberLossMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<FairLossMetric, CUDAFairLossMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<PoissonMetric, CUDAPoissonMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<MAPEMetric, CUDAMAPEMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<GammaMetric, CUDAGammaMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<GammaDevianceMetric, CUDAGammaDevianceMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<TweedieMetric, CUDATweedieMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
|
||||
// Binary metrics
|
||||
template void CUDAPointwiseMetricInterface<BinaryLoglossMetric, CUDABinaryLoglossMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDAPointwiseMetricInterface<BinaryErrorMetric, CUDABinaryErrorMetric>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,85 @@
|
||||
/*!
|
||||
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
* Modifications Copyright(C) 2023 Advanced Micro Devices, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_algorithms.hpp>
|
||||
#include <LightGBM/cuda/cuda_rocm_interop.h>
|
||||
|
||||
#include "cuda_binary_metric.hpp"
|
||||
#include "cuda_pointwise_metric.hpp"
|
||||
#include "cuda_regression_metric.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename CUDA_METRIC, bool USE_WEIGHTS>
|
||||
__global__ void EvalKernel(const data_size_t num_data, const label_t* labels, const label_t* weights,
|
||||
const double* scores, double* reduce_block_buffer, const double param) {
|
||||
__shared__ double shared_mem_buffer[WARPSIZE];
|
||||
const data_size_t index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
double point_metric = 0.0;
|
||||
if (index < num_data) {
|
||||
point_metric = USE_WEIGHTS ?
|
||||
CUDA_METRIC::MetricOnPointCUDA(labels[index], scores[index], param) * weights[index] :
|
||||
CUDA_METRIC::MetricOnPointCUDA(labels[index], scores[index], param);
|
||||
}
|
||||
const double block_sum_point_metric = ShuffleReduceSum<double>(point_metric, shared_mem_buffer, NUM_DATA_PER_EVAL_THREAD);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_block_buffer[blockIdx.x] = block_sum_point_metric;
|
||||
}
|
||||
if (USE_WEIGHTS) {
|
||||
double weight = 0.0;
|
||||
if (index < num_data) {
|
||||
weight = static_cast<double>(weights[index]);
|
||||
const double block_sum_weight = ShuffleReduceSum<double>(weight, shared_mem_buffer, NUM_DATA_PER_EVAL_THREAD);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_block_buffer[blockIdx.x + gridDim.x] = block_sum_weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename HOST_METRIC, typename CUDA_METRIC>
|
||||
void CUDAPointwiseMetricInterface<HOST_METRIC, CUDA_METRIC>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const {
|
||||
const int num_blocks = (this->num_data_ + NUM_DATA_PER_EVAL_THREAD - 1) / NUM_DATA_PER_EVAL_THREAD;
|
||||
if (this->cuda_weights_ != nullptr) {
|
||||
EvalKernel<CUDA_METRIC, true><<<num_blocks, NUM_DATA_PER_EVAL_THREAD>>>(
|
||||
this->num_data_, this->cuda_labels_, this->cuda_weights_, score, reduce_block_buffer_.RawData(), GetParamFromConfig());
|
||||
} else {
|
||||
EvalKernel<CUDA_METRIC, false><<<num_blocks, NUM_DATA_PER_EVAL_THREAD>>>(
|
||||
this->num_data_, this->cuda_labels_, this->cuda_weights_, score, reduce_block_buffer_.RawData(), GetParamFromConfig());
|
||||
}
|
||||
ShuffleReduceSumGlobal<double, double>(reduce_block_buffer_.RawData(), num_blocks, reduce_block_buffer_inner_.RawData());
|
||||
CopyFromCUDADeviceToHost<double>(sum_loss, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__);
|
||||
*sum_weight = static_cast<double>(this->num_data_);
|
||||
if (this->cuda_weights_ != nullptr) {
|
||||
ShuffleReduceSumGlobal<double, double>(reduce_block_buffer_.RawData() + num_blocks, num_blocks, reduce_block_buffer_inner_.RawData());
|
||||
CopyFromCUDADeviceToHost<double>(sum_weight, reduce_block_buffer_inner_.RawData(), 1, __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
// Regression metrics
|
||||
template void CUDAPointwiseMetricInterface<RMSEMetric, CUDARMSEMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<L2Metric, CUDAL2Metric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<QuantileMetric, CUDAQuantileMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<L1Metric, CUDAL1Metric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<HuberLossMetric, CUDAHuberLossMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<FairLossMetric, CUDAFairLossMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<PoissonMetric, CUDAPoissonMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<MAPEMetric, CUDAMAPEMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<GammaMetric, CUDAGammaMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<GammaDevianceMetric, CUDAGammaDevianceMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<TweedieMetric, CUDATweedieMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
|
||||
// Binary metrics
|
||||
template void CUDAPointwiseMetricInterface<BinaryLoglossMetric, CUDABinaryLoglossMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
template void CUDAPointwiseMetricInterface<BinaryErrorMetric, CUDABinaryErrorMetric>::LaunchEvalKernel(const double* score, double* sum_loss, double* sum_weight) const;
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,46 @@
|
||||
/*!
|
||||
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_METRIC_CUDA_CUDA_POINTWISE_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_CUDA_CUDA_POINTWISE_METRIC_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_metric.hpp>
|
||||
#include <LightGBM/cuda/cuda_utils.hu>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define NUM_DATA_PER_EVAL_THREAD (1024)
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_METRIC, typename CUDA_METRIC>
|
||||
class CUDAPointwiseMetricInterface: public CUDAMetricInterface<HOST_METRIC> {
|
||||
public:
|
||||
explicit CUDAPointwiseMetricInterface(const Config& config): CUDAMetricInterface<HOST_METRIC>(config), num_class_(config.num_class) {}
|
||||
|
||||
virtual ~CUDAPointwiseMetricInterface() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
protected:
|
||||
void LaunchEvalKernel(const double* score_convert, double* sum_loss, double* sum_weight) const;
|
||||
|
||||
virtual double GetParamFromConfig() const { return 0.0; }
|
||||
|
||||
mutable CUDAVector<double> score_convert_buffer_;
|
||||
CUDAVector<double> reduce_block_buffer_;
|
||||
CUDAVector<double> reduce_block_buffer_inner_;
|
||||
const int num_class_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
|
||||
#endif // LIGHTGBM_SRC_METRIC_CUDA_CUDA_POINTWISE_METRIC_HPP_
|
||||
@@ -0,0 +1,53 @@
|
||||
/*!
|
||||
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_regression_metric.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_METRIC, typename CUDA_METRIC>
|
||||
std::vector<double> CUDARegressionMetricInterface<HOST_METRIC, CUDA_METRIC>::Eval(const double* score, const ObjectiveFunction* objective) const {
|
||||
const double* score_convert = score;
|
||||
if (objective != nullptr && objective->NeedConvertOutputCUDA()) {
|
||||
this->score_convert_buffer_.Resize(static_cast<size_t>(this->num_data_) * static_cast<size_t>(this->num_class_));
|
||||
score_convert = objective->ConvertOutputCUDA(this->num_data_, score, this->score_convert_buffer_.RawData());
|
||||
}
|
||||
double sum_loss = 0.0, sum_weight = 0.0;
|
||||
this->LaunchEvalKernel(score_convert, &sum_loss, &sum_weight);
|
||||
const double eval_score = this->AverageLoss(sum_loss, sum_weight);
|
||||
return std::vector<double>{eval_score};
|
||||
}
|
||||
|
||||
CUDARMSEMetric::CUDARMSEMetric(const Config& config): CUDARegressionMetricInterface<RMSEMetric, CUDARMSEMetric>(config) {}
|
||||
|
||||
CUDAL2Metric::CUDAL2Metric(const Config& config): CUDARegressionMetricInterface<L2Metric, CUDAL2Metric>(config) {}
|
||||
|
||||
CUDAQuantileMetric::CUDAQuantileMetric(const Config& config): CUDARegressionMetricInterface<QuantileMetric, CUDAQuantileMetric>(config), alpha_(config.alpha) {}
|
||||
|
||||
CUDAL1Metric::CUDAL1Metric(const Config& config): CUDARegressionMetricInterface<L1Metric, CUDAL1Metric>(config) {}
|
||||
|
||||
CUDAHuberLossMetric::CUDAHuberLossMetric(const Config& config): CUDARegressionMetricInterface<HuberLossMetric, CUDAHuberLossMetric>(config), alpha_(config.alpha) {}
|
||||
|
||||
CUDAFairLossMetric::CUDAFairLossMetric(const Config& config): CUDARegressionMetricInterface<FairLossMetric, CUDAFairLossMetric>(config) , fair_c_(config.fair_c) {}
|
||||
|
||||
CUDAPoissonMetric::CUDAPoissonMetric(const Config& config): CUDARegressionMetricInterface<PoissonMetric, CUDAPoissonMetric>(config) {}
|
||||
|
||||
CUDAMAPEMetric::CUDAMAPEMetric(const Config& config): CUDARegressionMetricInterface<MAPEMetric, CUDAMAPEMetric>(config) {}
|
||||
|
||||
CUDAGammaMetric::CUDAGammaMetric(const Config& config): CUDARegressionMetricInterface<GammaMetric, CUDAGammaMetric>(config) {}
|
||||
|
||||
CUDAGammaDevianceMetric::CUDAGammaDevianceMetric(const Config& config): CUDARegressionMetricInterface<GammaDevianceMetric, CUDAGammaDevianceMetric>(config) {}
|
||||
|
||||
CUDATweedieMetric::CUDATweedieMetric(const Config& config): CUDARegressionMetricInterface<TweedieMetric, CUDATweedieMetric>(config) , tweedie_variance_power_(config.tweedie_variance_power) {}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,216 @@
|
||||
/*!
|
||||
* Copyright (c) 2022-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2022-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_METRIC_CUDA_CUDA_REGRESSION_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_CUDA_CUDA_REGRESSION_METRIC_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_metric.hpp>
|
||||
#include <LightGBM/cuda/cuda_utils.hu>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_pointwise_metric.hpp"
|
||||
#include "../regression_metric.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_METRIC, typename CUDA_METRIC>
|
||||
class CUDARegressionMetricInterface: public CUDAPointwiseMetricInterface<HOST_METRIC, CUDA_METRIC> {
|
||||
public:
|
||||
explicit CUDARegressionMetricInterface(const Config& config):
|
||||
CUDAPointwiseMetricInterface<HOST_METRIC, CUDA_METRIC>(config) {}
|
||||
|
||||
virtual ~CUDARegressionMetricInterface() {}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override;
|
||||
};
|
||||
|
||||
class CUDARMSEMetric: public CUDARegressionMetricInterface<RMSEMetric, CUDARMSEMetric> {
|
||||
public:
|
||||
explicit CUDARMSEMetric(const Config& config);
|
||||
|
||||
virtual ~CUDARMSEMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double /*alpha*/) {
|
||||
return (score - label) * (score - label);
|
||||
}
|
||||
};
|
||||
|
||||
class CUDAL2Metric : public CUDARegressionMetricInterface<L2Metric, CUDAL2Metric> {
|
||||
public:
|
||||
explicit CUDAL2Metric(const Config& config);
|
||||
|
||||
virtual ~CUDAL2Metric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double /*alpha*/) {
|
||||
return (score - label) * (score - label);
|
||||
}
|
||||
};
|
||||
|
||||
class CUDAQuantileMetric : public CUDARegressionMetricInterface<QuantileMetric, CUDAQuantileMetric> {
|
||||
public:
|
||||
explicit CUDAQuantileMetric(const Config& config);
|
||||
|
||||
virtual ~CUDAQuantileMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double alpha) {
|
||||
double delta = label - score;
|
||||
if (delta < 0) {
|
||||
return (alpha - 1.0f) * delta;
|
||||
} else {
|
||||
return alpha * delta;
|
||||
}
|
||||
}
|
||||
|
||||
double GetParamFromConfig() const override {
|
||||
return alpha_;
|
||||
}
|
||||
|
||||
private:
|
||||
const double alpha_;
|
||||
};
|
||||
|
||||
class CUDAL1Metric : public CUDARegressionMetricInterface<L1Metric, CUDAL1Metric> {
|
||||
public:
|
||||
explicit CUDAL1Metric(const Config& config);
|
||||
|
||||
virtual ~CUDAL1Metric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double /*alpha*/) {
|
||||
return std::fabs(score - label);
|
||||
}
|
||||
};
|
||||
|
||||
class CUDAHuberLossMetric : public CUDARegressionMetricInterface<HuberLossMetric, CUDAHuberLossMetric> {
|
||||
public:
|
||||
explicit CUDAHuberLossMetric(const Config& config);
|
||||
|
||||
virtual ~CUDAHuberLossMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double alpha) {
|
||||
const double diff = score - label;
|
||||
if (std::abs(diff) <= alpha) {
|
||||
return 0.5f * diff * diff;
|
||||
} else {
|
||||
return alpha * (std::abs(diff) - 0.5f * alpha);
|
||||
}
|
||||
}
|
||||
|
||||
double GetParamFromConfig() const override {
|
||||
return alpha_;
|
||||
}
|
||||
private:
|
||||
const double alpha_;
|
||||
};
|
||||
|
||||
class CUDAFairLossMetric : public CUDARegressionMetricInterface<FairLossMetric, CUDAFairLossMetric> {
|
||||
public:
|
||||
explicit CUDAFairLossMetric(const Config& config);
|
||||
|
||||
virtual ~CUDAFairLossMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double fair_c) {
|
||||
const double x = std::fabs(score - label);
|
||||
const double c = fair_c;
|
||||
return c * x - c * c * std::log1p(x / c);
|
||||
}
|
||||
|
||||
double GetParamFromConfig() const override {
|
||||
return fair_c_;
|
||||
}
|
||||
|
||||
private:
|
||||
const double fair_c_;
|
||||
};
|
||||
|
||||
class CUDAPoissonMetric : public CUDARegressionMetricInterface<PoissonMetric, CUDAPoissonMetric> {
|
||||
public:
|
||||
explicit CUDAPoissonMetric(const Config& config);
|
||||
|
||||
virtual ~CUDAPoissonMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double /*alpha*/) {
|
||||
const double eps = 1e-10f;
|
||||
if (score < eps) {
|
||||
score = eps;
|
||||
}
|
||||
return score - label * std::log(score);
|
||||
}
|
||||
};
|
||||
|
||||
class CUDAMAPEMetric : public CUDARegressionMetricInterface<MAPEMetric, CUDAMAPEMetric> {
|
||||
public:
|
||||
explicit CUDAMAPEMetric(const Config& config);
|
||||
|
||||
virtual ~CUDAMAPEMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double /*alpha*/) {
|
||||
return std::fabs((label - score)) / fmax(1.0f, std::fabs(label));
|
||||
}
|
||||
};
|
||||
|
||||
class CUDAGammaMetric : public CUDARegressionMetricInterface<GammaMetric, CUDAGammaMetric> {
|
||||
public:
|
||||
explicit CUDAGammaMetric(const Config& config);
|
||||
|
||||
virtual ~CUDAGammaMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double /*alpha*/) {
|
||||
const double psi = 1.0;
|
||||
const double theta = -1.0 / score;
|
||||
const double a = psi;
|
||||
const double b = -SafeLog(-theta);
|
||||
const double c = 1. / psi * SafeLog(label / psi) - SafeLog(label) - 0; // 0 = std::lgamma(1.0 / psi) = std::lgamma(1.0);
|
||||
return -((label * theta - b) / a + c);
|
||||
}
|
||||
};
|
||||
|
||||
class CUDAGammaDevianceMetric : public CUDARegressionMetricInterface<GammaDevianceMetric, CUDAGammaDevianceMetric> {
|
||||
public:
|
||||
explicit CUDAGammaDevianceMetric(const Config& config);
|
||||
|
||||
virtual ~CUDAGammaDevianceMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double /*alpha*/) {
|
||||
const double epsilon = 1.0e-9;
|
||||
const double tmp = label / (score + epsilon);
|
||||
return tmp - SafeLog(tmp) - 1;
|
||||
}
|
||||
};
|
||||
|
||||
class CUDATweedieMetric : public CUDARegressionMetricInterface<TweedieMetric, CUDATweedieMetric> {
|
||||
public:
|
||||
explicit CUDATweedieMetric(const Config& config);
|
||||
|
||||
virtual ~CUDATweedieMetric() {}
|
||||
|
||||
__device__ inline static double MetricOnPointCUDA(label_t label, double score, double tweedie_variance_power) {
|
||||
const double rho = tweedie_variance_power;
|
||||
const double eps = 1e-10f;
|
||||
if (score < eps) {
|
||||
score = eps;
|
||||
}
|
||||
const double a = label * std::exp((1 - rho) * std::log(score)) / (1 - rho);
|
||||
const double b = std::exp((2 - rho) * std::log(score)) / (2 - rho);
|
||||
return -a + b;
|
||||
}
|
||||
|
||||
double GetParamFromConfig() const override {
|
||||
return tweedie_variance_power_;
|
||||
}
|
||||
|
||||
private:
|
||||
const double tweedie_variance_power_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
|
||||
#endif // LIGHTGBM_SRC_METRIC_CUDA_CUDA_REGRESSION_METRIC_HPP_
|
||||
@@ -0,0 +1,174 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
/*! \brief Declaration for some static members */
|
||||
std::vector<double> DCGCalculator::label_gain_;
|
||||
std::vector<double> DCGCalculator::discount_;
|
||||
const data_size_t DCGCalculator::kMaxPosition = 10000;
|
||||
|
||||
|
||||
void DCGCalculator::DefaultEvalAt(std::vector<int>* eval_at) {
|
||||
auto& ref_eval_at = *eval_at;
|
||||
if (ref_eval_at.empty()) {
|
||||
for (int i = 1; i <= 5; ++i) {
|
||||
ref_eval_at.push_back(i);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < eval_at->size(); ++i) {
|
||||
CHECK_GT(ref_eval_at[i], 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCGCalculator::DefaultLabelGain(std::vector<double>* label_gain) {
|
||||
if (!label_gain->empty()) {
|
||||
return;
|
||||
}
|
||||
// label_gain = 2^i - 1, may overflow, so we use 31 here
|
||||
const int max_label = 31;
|
||||
label_gain->push_back(0.0f);
|
||||
for (int i = 1; i < max_label; ++i) {
|
||||
label_gain->push_back(static_cast<double>((1 << i) - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void DCGCalculator::Init(const std::vector<double>& input_label_gain) {
|
||||
label_gain_.resize(input_label_gain.size());
|
||||
for (size_t i = 0; i < input_label_gain.size(); ++i) {
|
||||
label_gain_[i] = static_cast<double>(input_label_gain[i]);
|
||||
}
|
||||
discount_.resize(kMaxPosition);
|
||||
for (data_size_t i = 0; i < kMaxPosition; ++i) {
|
||||
discount_[i] = 1.0 / std::log2(2.0 + i);
|
||||
}
|
||||
}
|
||||
|
||||
double DCGCalculator::CalMaxDCGAtK(data_size_t k, const label_t* label, data_size_t num_data) {
|
||||
double ret = 0.0f;
|
||||
// counts for all labels
|
||||
std::vector<data_size_t> label_cnt(label_gain_.size(), 0);
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
++label_cnt[static_cast<int>(label[i])];
|
||||
}
|
||||
int top_label = static_cast<int>(label_gain_.size()) - 1;
|
||||
|
||||
if (k > num_data) {
|
||||
k = num_data;
|
||||
}
|
||||
// start from top label, and accumulate DCG
|
||||
for (data_size_t j = 0; j < k; ++j) {
|
||||
while (top_label > 0 && label_cnt[top_label] <= 0) {
|
||||
top_label -= 1;
|
||||
}
|
||||
if (top_label < 0) {
|
||||
break;
|
||||
}
|
||||
ret += discount_[j] * label_gain_[top_label];
|
||||
label_cnt[top_label] -= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DCGCalculator::CalMaxDCG(const std::vector<data_size_t>& ks,
|
||||
const label_t* label,
|
||||
data_size_t num_data,
|
||||
std::vector<double>* out) {
|
||||
std::vector<data_size_t> label_cnt(label_gain_.size(), 0);
|
||||
// counts for all labels
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
++label_cnt[static_cast<int>(label[i])];
|
||||
}
|
||||
double cur_result = 0.0f;
|
||||
data_size_t cur_left = 0;
|
||||
int top_label = static_cast<int>(label_gain_.size()) - 1;
|
||||
// calculate k Max DCG by one pass
|
||||
for (size_t i = 0; i < ks.size(); ++i) {
|
||||
data_size_t cur_k = ks[i];
|
||||
if (cur_k > num_data) {
|
||||
cur_k = num_data;
|
||||
}
|
||||
for (data_size_t j = cur_left; j < cur_k; ++j) {
|
||||
while (top_label > 0 && label_cnt[top_label] <= 0) {
|
||||
top_label -= 1;
|
||||
}
|
||||
if (top_label < 0) {
|
||||
break;
|
||||
}
|
||||
cur_result += discount_[j] * label_gain_[top_label];
|
||||
label_cnt[top_label] -= 1;
|
||||
}
|
||||
(*out)[i] = cur_result;
|
||||
cur_left = cur_k;
|
||||
}
|
||||
}
|
||||
|
||||
void DCGCalculator::CalDCG(const std::vector<data_size_t>& ks, const label_t* label,
|
||||
const double * score, data_size_t num_data, std::vector<double>* out) {
|
||||
// get sorted indices by score
|
||||
std::vector<data_size_t> sorted_idx(num_data);
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
sorted_idx[i] = i;
|
||||
}
|
||||
std::stable_sort(sorted_idx.begin(), sorted_idx.end(),
|
||||
[score](data_size_t a, data_size_t b) {return score[a] > score[b]; });
|
||||
|
||||
double cur_result = 0.0f;
|
||||
data_size_t cur_left = 0;
|
||||
// calculate multi dcg by one pass
|
||||
for (size_t i = 0; i < ks.size(); ++i) {
|
||||
data_size_t cur_k = ks[i];
|
||||
if (cur_k > num_data) {
|
||||
cur_k = num_data;
|
||||
}
|
||||
for (data_size_t j = cur_left; j < cur_k; ++j) {
|
||||
data_size_t idx = sorted_idx[j];
|
||||
cur_result += label_gain_[static_cast<int>(label[idx])] * discount_[j];
|
||||
}
|
||||
(*out)[i] = cur_result;
|
||||
cur_left = cur_k;
|
||||
}
|
||||
}
|
||||
|
||||
void DCGCalculator::CheckMetadata(const Metadata& metadata, data_size_t num_queries) {
|
||||
const data_size_t* query_boundaries = metadata.query_boundaries();
|
||||
if (num_queries > 0 && query_boundaries != nullptr) {
|
||||
for (data_size_t i = 0; i < num_queries; i++) {
|
||||
data_size_t num_rows = query_boundaries[i + 1] - query_boundaries[i];
|
||||
if (num_rows > kMaxPosition) {
|
||||
Log::Fatal("Number of rows %i exceeds upper limit of %i for a query", static_cast<int>(num_rows), static_cast<int>(kMaxPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DCGCalculator::CheckLabel(const label_t* label, data_size_t num_data) {
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
label_t delta = std::fabs(label[i] - static_cast<int>(label[i]));
|
||||
if (delta > kEpsilon) {
|
||||
Log::Fatal("label should be int type (met %f) for ranking task,\n"
|
||||
"for the gain of label, please set the label_gain parameter", label[i]);
|
||||
}
|
||||
|
||||
if (label[i] < 0) {
|
||||
Log::Fatal("Label should be non-negative (met %f) for ranking task", label[i]);
|
||||
}
|
||||
|
||||
if (static_cast<size_t>(label[i]) >= label_gain_.size()) {
|
||||
Log::Fatal("Label %zu is not less than the number of label mappings (%zu)", static_cast<size_t>(label[i]), label_gain_.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,169 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_METRIC_MAP_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_MAP_METRIC_HPP_
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class MapMetric:public Metric {
|
||||
public:
|
||||
explicit MapMetric(const Config& config) {
|
||||
// get eval position
|
||||
eval_at_ = config.eval_at;
|
||||
DCGCalculator::DefaultEvalAt(&eval_at_);
|
||||
}
|
||||
|
||||
~MapMetric() {
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
for (auto k : eval_at_) {
|
||||
name_.emplace_back(std::string("map@") + std::to_string(k));
|
||||
}
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
// get query boundaries
|
||||
query_boundaries_ = metadata.query_boundaries();
|
||||
if (query_boundaries_ == nullptr) {
|
||||
Log::Fatal("For MAP metric, there should be query information");
|
||||
}
|
||||
num_queries_ = metadata.num_queries();
|
||||
Log::Info("Total groups: %d, total data: %d", num_queries_, num_data_);
|
||||
// get query weights
|
||||
query_weights_ = metadata.query_weights();
|
||||
if (query_weights_ == nullptr) {
|
||||
sum_query_weights_ = static_cast<double>(num_queries_);
|
||||
} else {
|
||||
sum_query_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
sum_query_weights_ += query_weights_[i];
|
||||
}
|
||||
}
|
||||
|
||||
npos_per_query_.resize(num_queries_, 0);
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
for (data_size_t j = query_boundaries_[i]; j < query_boundaries_[i + 1]; ++j) {
|
||||
if (label_[j] > 0.5f) {
|
||||
++npos_per_query_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void CalMapAtK(std::vector<int> ks, data_size_t npos, const label_t* label,
|
||||
const double* score, data_size_t num_data, std::vector<double>* out) const {
|
||||
// get sorted indices by score
|
||||
std::vector<data_size_t> sorted_idx;
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
sorted_idx.emplace_back(i);
|
||||
}
|
||||
std::stable_sort(sorted_idx.begin(), sorted_idx.end(),
|
||||
[score](data_size_t a, data_size_t b) {return score[a] > score[b]; });
|
||||
|
||||
int num_hit = 0;
|
||||
double sum_ap = 0.0f;
|
||||
data_size_t cur_left = 0;
|
||||
for (size_t i = 0; i < ks.size(); ++i) {
|
||||
data_size_t cur_k = static_cast<data_size_t>(ks[i]);
|
||||
if (cur_k > num_data) {
|
||||
cur_k = num_data;
|
||||
}
|
||||
for (data_size_t j = cur_left; j < cur_k; ++j) {
|
||||
data_size_t idx = sorted_idx[j];
|
||||
if (label[idx] > 0.5f) {
|
||||
++num_hit;
|
||||
sum_ap += static_cast<double>(num_hit) / (j + 1.0f);
|
||||
}
|
||||
}
|
||||
if (npos > 0) {
|
||||
(*out)[i] = sum_ap / std::min(npos, cur_k);
|
||||
} else {
|
||||
(*out)[i] = 1.0f;
|
||||
}
|
||||
cur_left = cur_k;
|
||||
}
|
||||
}
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
|
||||
// some buffers for multi-threading sum up
|
||||
int num_threads = OMP_NUM_THREADS();
|
||||
std::vector<std::vector<double>> result_buffer_;
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
result_buffer_.emplace_back(eval_at_.size(), 0.0f);
|
||||
}
|
||||
std::vector<double> tmp_map(eval_at_.size(), 0.0f);
|
||||
if (query_weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) firstprivate(tmp_map)
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
const int tid = omp_get_thread_num();
|
||||
CalMapAtK(eval_at_, npos_per_query_[i], label_ + query_boundaries_[i],
|
||||
score + query_boundaries_[i], query_boundaries_[i + 1] - query_boundaries_[i], &tmp_map);
|
||||
for (size_t j = 0; j < eval_at_.size(); ++j) {
|
||||
result_buffer_[tid][j] += tmp_map[j];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided) firstprivate(tmp_map)
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
const int tid = omp_get_thread_num();
|
||||
CalMapAtK(eval_at_, npos_per_query_[i], label_ + query_boundaries_[i],
|
||||
score + query_boundaries_[i], query_boundaries_[i + 1] - query_boundaries_[i], &tmp_map);
|
||||
for (size_t j = 0; j < eval_at_.size(); ++j) {
|
||||
result_buffer_[tid][j] += tmp_map[j] * query_weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get final average MAP
|
||||
std::vector<double> result(eval_at_.size(), 0.0f);
|
||||
for (size_t j = 0; j < result.size(); ++j) {
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
result[j] += result_buffer_[i][j];
|
||||
}
|
||||
result[j] /= sum_query_weights_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Query boundaries information */
|
||||
const data_size_t* query_boundaries_;
|
||||
/*! \brief Number of queries */
|
||||
data_size_t num_queries_;
|
||||
/*! \brief Weights of queries */
|
||||
const label_t* query_weights_;
|
||||
/*! \brief Sum weights of queries */
|
||||
double sum_query_weights_;
|
||||
/*! \brief Evaluate position of Nmap */
|
||||
std::vector<data_size_t> eval_at_;
|
||||
std::vector<std::string> name_;
|
||||
std::vector<data_size_t> npos_per_query_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_METRIC_MAP_METRIC_HPP_
|
||||
@@ -0,0 +1,142 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/metric.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "binary_metric.hpp"
|
||||
#include "map_metric.hpp"
|
||||
#include "multiclass_metric.hpp"
|
||||
#include "rank_metric.hpp"
|
||||
#include "regression_metric.hpp"
|
||||
#include "xentropy_metric.hpp"
|
||||
|
||||
#include "cuda/cuda_binary_metric.hpp"
|
||||
#include "cuda/cuda_regression_metric.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
Metric* Metric::CreateMetric(const std::string& type, const Config& config) {
|
||||
#ifdef USE_CUDA
|
||||
if (config.device_type == std::string("cuda") && config.boosting == std::string("gbdt")) {
|
||||
if (type == std::string("l2")) {
|
||||
return new CUDAL2Metric(config);
|
||||
} else if (type == std::string("rmse")) {
|
||||
return new CUDARMSEMetric(config);
|
||||
} else if (type == std::string("l1")) {
|
||||
return new CUDAL1Metric(config);
|
||||
} else if (type == std::string("quantile")) {
|
||||
return new CUDAQuantileMetric(config);
|
||||
} else if (type == std::string("huber")) {
|
||||
return new CUDAHuberLossMetric(config);
|
||||
} else if (type == std::string("fair")) {
|
||||
return new CUDAFairLossMetric(config);
|
||||
} else if (type == std::string("poisson")) {
|
||||
return new CUDAPoissonMetric(config);
|
||||
} else if (type == std::string("binary_logloss")) {
|
||||
return new CUDABinaryLoglossMetric(config);
|
||||
} else if (type == std::string("binary_error")) {
|
||||
return new CUDABinaryErrorMetric(config);
|
||||
} else if (type == std::string("auc")) {
|
||||
Log::Warning("Metric auc is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new AUCMetric(config);
|
||||
} else if (type == std::string("average_precision")) {
|
||||
Log::Warning("Metric average_precision is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new AveragePrecisionMetric(config);
|
||||
} else if (type == std::string("auc_mu")) {
|
||||
Log::Warning("Metric auc_mu is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new AucMuMetric(config);
|
||||
} else if (type == std::string("ndcg")) {
|
||||
Log::Warning("Metric ndcg is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new NDCGMetric(config);
|
||||
} else if (type == std::string("map")) {
|
||||
Log::Warning("Metric map is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new MapMetric(config);
|
||||
} else if (type == std::string("multi_logloss")) {
|
||||
Log::Warning("Metric multi_logloss is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new MultiSoftmaxLoglossMetric(config);
|
||||
} else if (type == std::string("multi_error")) {
|
||||
Log::Warning("Metric multi_error is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new MultiErrorMetric(config);
|
||||
} else if (type == std::string("cross_entropy")) {
|
||||
Log::Warning("Metric cross_entropy is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new CrossEntropyMetric(config);
|
||||
} else if (type == std::string("cross_entropy_lambda")) {
|
||||
Log::Warning("Metric cross_entropy_lambda is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new CrossEntropyLambdaMetric(config);
|
||||
} else if (type == std::string("kullback_leibler")) {
|
||||
Log::Warning("Metric kullback_leibler is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new KullbackLeiblerDivergence(config);
|
||||
} else if (type == std::string("mape")) {
|
||||
return new CUDAMAPEMetric(config);
|
||||
} else if (type == std::string("gamma")) {
|
||||
return new CUDAGammaMetric(config);
|
||||
} else if (type == std::string("gamma_deviance")) {
|
||||
return new CUDAGammaDevianceMetric(config);
|
||||
} else if (type == std::string("tweedie")) {
|
||||
return new CUDATweedieMetric(config);
|
||||
} else if (type == std::string("r2")) {
|
||||
Log::Warning("Metric r2 is not implemented in cuda version. Fall back to evaluation on CPU.");
|
||||
return new R2Metric(config);
|
||||
}
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
if (type == std::string("l2")) {
|
||||
return new L2Metric(config);
|
||||
} else if (type == std::string("rmse")) {
|
||||
return new RMSEMetric(config);
|
||||
} else if (type == std::string("l1")) {
|
||||
return new L1Metric(config);
|
||||
} else if (type == std::string("quantile")) {
|
||||
return new QuantileMetric(config);
|
||||
} else if (type == std::string("huber")) {
|
||||
return new HuberLossMetric(config);
|
||||
} else if (type == std::string("fair")) {
|
||||
return new FairLossMetric(config);
|
||||
} else if (type == std::string("poisson")) {
|
||||
return new PoissonMetric(config);
|
||||
} else if (type == std::string("binary_logloss")) {
|
||||
return new BinaryLoglossMetric(config);
|
||||
} else if (type == std::string("binary_error")) {
|
||||
return new BinaryErrorMetric(config);
|
||||
} else if (type == std::string("auc")) {
|
||||
return new AUCMetric(config);
|
||||
} else if (type == std::string("average_precision")) {
|
||||
return new AveragePrecisionMetric(config);
|
||||
} else if (type == std::string("auc_mu")) {
|
||||
return new AucMuMetric(config);
|
||||
} else if (type == std::string("ndcg")) {
|
||||
return new NDCGMetric(config);
|
||||
} else if (type == std::string("map")) {
|
||||
return new MapMetric(config);
|
||||
} else if (type == std::string("multi_logloss")) {
|
||||
return new MultiSoftmaxLoglossMetric(config);
|
||||
} else if (type == std::string("multi_error")) {
|
||||
return new MultiErrorMetric(config);
|
||||
} else if (type == std::string("cross_entropy")) {
|
||||
return new CrossEntropyMetric(config);
|
||||
} else if (type == std::string("cross_entropy_lambda")) {
|
||||
return new CrossEntropyLambdaMetric(config);
|
||||
} else if (type == std::string("kullback_leibler")) {
|
||||
return new KullbackLeiblerDivergence(config);
|
||||
} else if (type == std::string("mape")) {
|
||||
return new MAPEMetric(config);
|
||||
} else if (type == std::string("gamma")) {
|
||||
return new GammaMetric(config);
|
||||
} else if (type == std::string("gamma_deviance")) {
|
||||
return new GammaDevianceMetric(config);
|
||||
} else if (type == std::string("tweedie")) {
|
||||
return new TweedieMetric(config);
|
||||
} else if (type == std::string("r2")) {
|
||||
return new R2Metric(config);
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,369 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_METRIC_MULTICLASS_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_MULTICLASS_METRIC_HPP_
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief Metric for multiclass task.
|
||||
* Use static class "PointWiseLossCalculator" to calculate loss point-wise
|
||||
*/
|
||||
template<typename PointWiseLossCalculator>
|
||||
class MulticlassMetric: public Metric {
|
||||
public:
|
||||
explicit MulticlassMetric(const Config& config) :config_(config) {
|
||||
num_class_ = config.num_class;
|
||||
}
|
||||
|
||||
virtual ~MulticlassMetric() {
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back(PointWiseLossCalculator::Name(config_));
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
// get weights
|
||||
weights_ = metadata.weights();
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
sum_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_weights_ += weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
|
||||
double sum_loss = 0.0;
|
||||
int num_tree_per_iteration = num_class_;
|
||||
int num_pred_per_row = num_class_;
|
||||
if (objective != nullptr) {
|
||||
num_tree_per_iteration = objective->NumModelPerIteration();
|
||||
num_pred_per_row = objective->NumPredictOneRow();
|
||||
}
|
||||
if (objective != nullptr) {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
std::vector<double> raw_score(num_tree_per_iteration);
|
||||
for (int k = 0; k < num_tree_per_iteration; ++k) {
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
raw_score[k] = static_cast<double>(score[idx]);
|
||||
}
|
||||
std::vector<double> rec(num_pred_per_row);
|
||||
objective->ConvertOutput(raw_score.data(), rec.data());
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
std::vector<double> raw_score(num_tree_per_iteration);
|
||||
for (int k = 0; k < num_tree_per_iteration; ++k) {
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
raw_score[k] = static_cast<double>(score[idx]);
|
||||
}
|
||||
std::vector<double> rec(num_pred_per_row);
|
||||
objective->ConvertOutput(raw_score.data(), rec.data());
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_) * weights_[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
std::vector<double> rec(num_tree_per_iteration);
|
||||
for (int k = 0; k < num_tree_per_iteration; ++k) {
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
rec[k] = static_cast<double>(score[idx]);
|
||||
}
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
std::vector<double> rec(num_tree_per_iteration);
|
||||
for (int k = 0; k < num_tree_per_iteration; ++k) {
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
rec[k] = static_cast<double>(score[idx]);
|
||||
}
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], &rec, config_) * weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
double loss = sum_loss / sum_weights_;
|
||||
return std::vector<double>(1, loss);
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer of weighs */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Name of this test set */
|
||||
std::vector<std::string> name_;
|
||||
int num_class_;
|
||||
/*! \brief config parameters*/
|
||||
Config config_;
|
||||
};
|
||||
|
||||
/*! \brief top-k error for multiclass task; if k=1 (default) this is the usual multi-error */
|
||||
class MultiErrorMetric: public MulticlassMetric<MultiErrorMetric> {
|
||||
public:
|
||||
explicit MultiErrorMetric(const Config& config) :MulticlassMetric<MultiErrorMetric>(config) {}
|
||||
|
||||
inline static double LossOnPoint(label_t label, std::vector<double>* score, const Config& config) {
|
||||
size_t k = static_cast<size_t>(label);
|
||||
auto& ref_score = *score;
|
||||
int num_larger = 0;
|
||||
for (size_t i = 0; i < score->size(); ++i) {
|
||||
if (ref_score[i] >= ref_score[k]) ++num_larger;
|
||||
if (num_larger > config.multi_error_top_k) return 1.0f;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
inline static const std::string Name(const Config& config) {
|
||||
if (config.multi_error_top_k == 1) {
|
||||
return "multi_error";
|
||||
} else {
|
||||
return "multi_error@" + std::to_string(config.multi_error_top_k);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief Logloss for multiclass task */
|
||||
class MultiSoftmaxLoglossMetric: public MulticlassMetric<MultiSoftmaxLoglossMetric> {
|
||||
public:
|
||||
explicit MultiSoftmaxLoglossMetric(const Config& config) :MulticlassMetric<MultiSoftmaxLoglossMetric>(config) {}
|
||||
|
||||
inline static double LossOnPoint(label_t label, std::vector<double>* score, const Config&) {
|
||||
size_t k = static_cast<size_t>(label);
|
||||
auto& ref_score = *score;
|
||||
if (ref_score[k] > kEpsilon) {
|
||||
return static_cast<double>(-std::log(ref_score[k]));
|
||||
} else {
|
||||
return -std::log(kEpsilon);
|
||||
}
|
||||
}
|
||||
|
||||
inline static const std::string Name(const Config&) {
|
||||
return "multi_logloss";
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief AUC mu for multiclass task*/
|
||||
class AucMuMetric : public Metric {
|
||||
public:
|
||||
explicit AucMuMetric(const Config& config) : config_(config) {
|
||||
num_class_ = config.num_class;
|
||||
class_weights_ = config.auc_mu_weights_matrix;
|
||||
}
|
||||
|
||||
virtual ~AucMuMetric() {}
|
||||
|
||||
const std::vector<std::string>& GetName() const override { return name_; }
|
||||
|
||||
double factor_to_bigger_better() const override { return 1.0f; }
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back("auc_mu");
|
||||
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
|
||||
// get weights
|
||||
weights_ = metadata.weights();
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
sum_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_weights_ += weights_[i];
|
||||
}
|
||||
}
|
||||
|
||||
// sort the data indices by true class
|
||||
sorted_data_idx_ = std::vector<data_size_t>(num_data_, 0);
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sorted_data_idx_[i] = i;
|
||||
}
|
||||
Common::ParallelSort(sorted_data_idx_.begin(), sorted_data_idx_.end(),
|
||||
[this](data_size_t a, data_size_t b) { return label_[a] < label_[b]; });
|
||||
|
||||
// get size of each class
|
||||
class_sizes_ = std::vector<data_size_t>(num_class_, 0);
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
data_size_t curr_label = static_cast<data_size_t>(label_[i]);
|
||||
++class_sizes_[curr_label];
|
||||
}
|
||||
|
||||
// get total weight of data in each class
|
||||
class_data_weights_ = std::vector<double>(num_class_, 0);
|
||||
if (weights_ != nullptr) {
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
data_size_t curr_label = static_cast<data_size_t>(label_[i]);
|
||||
class_data_weights_[curr_label] += weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
|
||||
// the notation follows that used in the paper introducing the auc-mu metric:
|
||||
// https://proceedings.mlr.press/v97/kleiman19a.html
|
||||
|
||||
auto S = std::vector<std::vector<double>>(num_class_, std::vector<double>(num_class_, 0));
|
||||
int i_start = 0;
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
int j_start = i_start + class_sizes_[i];
|
||||
for (int j = i + 1; j < num_class_; ++j) {
|
||||
std::vector<double> curr_v;
|
||||
for (int k = 0; k < num_class_; ++k) {
|
||||
curr_v.emplace_back(class_weights_[i][k] - class_weights_[j][k]);
|
||||
}
|
||||
double t1 = curr_v[i] - curr_v[j];
|
||||
// extract the data indices belonging to class i or j
|
||||
std::vector<data_size_t> class_i_j_indices;
|
||||
class_i_j_indices.assign(sorted_data_idx_.begin() + i_start, sorted_data_idx_.begin() + i_start + class_sizes_[i]);
|
||||
class_i_j_indices.insert(class_i_j_indices.end(),
|
||||
sorted_data_idx_.begin() + j_start, sorted_data_idx_.begin() + j_start + class_sizes_[j]);
|
||||
// sort according to distance from separating hyperplane
|
||||
std::vector<std::pair<data_size_t, double>> dist;
|
||||
for (data_size_t k = 0; static_cast<size_t>(k) < class_i_j_indices.size(); ++k) {
|
||||
data_size_t a = class_i_j_indices[k];
|
||||
double v_a = 0;
|
||||
for (int m = 0; m < num_class_; ++m) {
|
||||
v_a += curr_v[m] * score[num_data_ * m + a];
|
||||
}
|
||||
dist.push_back(std::pair<data_size_t, double>(a, t1 * v_a));
|
||||
}
|
||||
Common::ParallelSort(dist.begin(), dist.end(),
|
||||
[this](std::pair<data_size_t, double> a, std::pair<data_size_t, double> b) {
|
||||
// if scores are equal, put j class first
|
||||
if (std::fabs(a.second - b.second) < kEpsilon) {
|
||||
return label_[a.first] > label_[b.first];
|
||||
} else if (a.second < b.second) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// calculate AUC
|
||||
double num_j = 0;
|
||||
double last_j_dist = 0;
|
||||
double num_current_j = 0;
|
||||
if (weights_ == nullptr) {
|
||||
for (size_t k = 0; k < dist.size(); ++k) {
|
||||
data_size_t a = dist[k].first;
|
||||
double curr_dist = dist[k].second;
|
||||
if (label_[a] == i) {
|
||||
if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
|
||||
S[i][j] += num_j - 0.5 * num_current_j; // members of class j with same distance as a contribute 0.5
|
||||
} else {
|
||||
S[i][j] += num_j;
|
||||
}
|
||||
} else {
|
||||
++num_j;
|
||||
if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
|
||||
++num_current_j;
|
||||
} else {
|
||||
last_j_dist = dist[k].second;
|
||||
num_current_j = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t k = 0; k < dist.size(); ++k) {
|
||||
data_size_t a = dist[k].first;
|
||||
double curr_dist = dist[k].second;
|
||||
double curr_weight = weights_[a];
|
||||
if (label_[a] == i) {
|
||||
if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
|
||||
S[i][j] += curr_weight * (num_j - 0.5 * num_current_j); // members of class j with same distance as a contribute 0.5
|
||||
} else {
|
||||
S[i][j] += curr_weight * num_j;
|
||||
}
|
||||
} else {
|
||||
num_j += curr_weight;
|
||||
if (std::fabs(curr_dist - last_j_dist) < kEpsilon) {
|
||||
num_current_j += curr_weight;
|
||||
} else {
|
||||
last_j_dist = dist[k].second;
|
||||
num_current_j = curr_weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
j_start += class_sizes_[j];
|
||||
}
|
||||
i_start += class_sizes_[i];
|
||||
}
|
||||
double ans = 0;
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
for (int j = i + 1; j < num_class_; ++j) {
|
||||
if (weights_ == nullptr) {
|
||||
ans += (S[i][j] / class_sizes_[i]) / class_sizes_[j];
|
||||
} else {
|
||||
ans += (S[i][j] / class_data_weights_[i]) / class_data_weights_[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
ans = (2.0 * ans / num_class_) / (num_class_ - 1);
|
||||
return std::vector<double>(1, ans);
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data*/
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer to label*/
|
||||
const label_t* label_;
|
||||
/*! \brief Name of this metric*/
|
||||
std::vector<std::string> name_;
|
||||
/*! \brief Number of classes*/
|
||||
int num_class_;
|
||||
/*! \brief Class auc-mu weights*/
|
||||
std::vector<std::vector<double>> class_weights_;
|
||||
/*! \brief Data weights */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum of data weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Sum of data weights in each class*/
|
||||
std::vector<double> class_data_weights_;
|
||||
/*! \brief Number of data in each class*/
|
||||
std::vector<data_size_t> class_sizes_;
|
||||
/*! \brief config parameters*/
|
||||
Config config_;
|
||||
/*! \brief index to data, sorted by true class*/
|
||||
std::vector<data_size_t> sorted_data_idx_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_METRIC_MULTICLASS_METRIC_HPP_
|
||||
@@ -0,0 +1,170 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_METRIC_RANK_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_RANK_METRIC_HPP_
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class NDCGMetric:public Metric {
|
||||
public:
|
||||
explicit NDCGMetric(const Config& config) {
|
||||
// get eval position
|
||||
eval_at_ = config.eval_at;
|
||||
auto label_gain = config.label_gain;
|
||||
DCGCalculator::DefaultEvalAt(&eval_at_);
|
||||
DCGCalculator::DefaultLabelGain(&label_gain);
|
||||
// initialize DCG calculator
|
||||
DCGCalculator::Init(label_gain);
|
||||
}
|
||||
|
||||
~NDCGMetric() {
|
||||
}
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
for (auto k : eval_at_) {
|
||||
name_.emplace_back(std::string("ndcg@") + std::to_string(k));
|
||||
}
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
num_queries_ = metadata.num_queries();
|
||||
DCGCalculator::CheckMetadata(metadata, num_queries_);
|
||||
DCGCalculator::CheckLabel(label_, num_data_);
|
||||
// get query boundaries
|
||||
query_boundaries_ = metadata.query_boundaries();
|
||||
if (query_boundaries_ == nullptr) {
|
||||
Log::Fatal("The NDCG metric requires query information");
|
||||
}
|
||||
// get query weights
|
||||
query_weights_ = metadata.query_weights();
|
||||
if (query_weights_ == nullptr) {
|
||||
sum_query_weights_ = static_cast<double>(num_queries_);
|
||||
} else {
|
||||
sum_query_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
sum_query_weights_ += query_weights_[i];
|
||||
}
|
||||
}
|
||||
inverse_max_dcgs_.resize(num_queries_);
|
||||
// cache the inverse max DCG for all queries, used to calculate NDCG
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
inverse_max_dcgs_[i].resize(eval_at_.size(), 0.0f);
|
||||
DCGCalculator::CalMaxDCG(eval_at_, label_ + query_boundaries_[i],
|
||||
query_boundaries_[i + 1] - query_boundaries_[i],
|
||||
&inverse_max_dcgs_[i]);
|
||||
for (size_t j = 0; j < inverse_max_dcgs_[i].size(); ++j) {
|
||||
if (inverse_max_dcgs_[i][j] > 0.0f) {
|
||||
inverse_max_dcgs_[i][j] = 1.0f / inverse_max_dcgs_[i][j];
|
||||
} else {
|
||||
// marking negative for all negative queries.
|
||||
// if one meet this query, it's ndcg will be set as -1.
|
||||
inverse_max_dcgs_[i][j] = -1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction*) const override {
|
||||
int num_threads = OMP_NUM_THREADS();
|
||||
// some buffers for multi-threading sum up
|
||||
std::vector<std::vector<double>> result_buffer_;
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
result_buffer_.emplace_back(eval_at_.size(), 0.0f);
|
||||
}
|
||||
std::vector<double> tmp_dcg(eval_at_.size(), 0.0f);
|
||||
if (query_weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(tmp_dcg)
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
const int tid = omp_get_thread_num();
|
||||
// if all doc in this query are all negative, let its NDCG=1
|
||||
if (inverse_max_dcgs_[i][0] <= 0.0f) {
|
||||
for (size_t j = 0; j < eval_at_.size(); ++j) {
|
||||
result_buffer_[tid][j] += 1.0f;
|
||||
}
|
||||
} else {
|
||||
// calculate DCG
|
||||
DCGCalculator::CalDCG(eval_at_, label_ + query_boundaries_[i],
|
||||
score + query_boundaries_[i],
|
||||
query_boundaries_[i + 1] - query_boundaries_[i], &tmp_dcg);
|
||||
// calculate NDCG
|
||||
for (size_t j = 0; j < eval_at_.size(); ++j) {
|
||||
result_buffer_[tid][j] += tmp_dcg[j] * inverse_max_dcgs_[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) firstprivate(tmp_dcg)
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
const int tid = omp_get_thread_num();
|
||||
// if all doc in this query are all negative, let its NDCG=1
|
||||
if (inverse_max_dcgs_[i][0] <= 0.0f) {
|
||||
for (size_t j = 0; j < eval_at_.size(); ++j) {
|
||||
result_buffer_[tid][j] += 1.0f;
|
||||
}
|
||||
} else {
|
||||
// calculate DCG
|
||||
DCGCalculator::CalDCG(eval_at_, label_ + query_boundaries_[i],
|
||||
score + query_boundaries_[i],
|
||||
query_boundaries_[i + 1] - query_boundaries_[i], &tmp_dcg);
|
||||
// calculate NDCG
|
||||
for (size_t j = 0; j < eval_at_.size(); ++j) {
|
||||
result_buffer_[tid][j] += tmp_dcg[j] * inverse_max_dcgs_[i][j] * query_weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get final average NDCG
|
||||
std::vector<double> result(eval_at_.size(), 0.0f);
|
||||
for (size_t j = 0; j < result.size(); ++j) {
|
||||
for (int i = 0; i < num_threads; ++i) {
|
||||
result[j] += result_buffer_[i][j];
|
||||
}
|
||||
result[j] /= sum_query_weights_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Name of test set */
|
||||
std::vector<std::string> name_;
|
||||
/*! \brief Query boundaries information */
|
||||
const data_size_t* query_boundaries_;
|
||||
/*! \brief Number of queries */
|
||||
data_size_t num_queries_;
|
||||
/*! \brief Weights of queries */
|
||||
const label_t* query_weights_;
|
||||
/*! \brief Sum weights of queries */
|
||||
double sum_query_weights_;
|
||||
/*! \brief Evaluate position of NDCG */
|
||||
std::vector<data_size_t> eval_at_;
|
||||
/*! \brief Cache the inverse max dcg for all queries */
|
||||
std::vector<std::vector<double>> inverse_max_dcgs_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_METRIC_RANK_METRIC_HPP_
|
||||
@@ -0,0 +1,433 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_METRIC_REGRESSION_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_REGRESSION_METRIC_HPP_
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief Metric for regression task.
|
||||
* Use static class "PointWiseLossCalculator" to calculate loss point-wise
|
||||
*/
|
||||
template<typename PointWiseLossCalculator>
|
||||
class RegressionMetric: public Metric {
|
||||
public:
|
||||
explicit RegressionMetric(const Config& config) :config_(config) {
|
||||
}
|
||||
|
||||
virtual ~RegressionMetric() {
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back(PointWiseLossCalculator::Name());
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
// get weights
|
||||
weights_ = metadata.weights();
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
sum_weights_ = 0.0f;
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_weights_ += weights_[i];
|
||||
}
|
||||
}
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
PointWiseLossCalculator::CheckLabel(label_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
|
||||
double sum_loss = 0.0f;
|
||||
if (objective == nullptr) {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i], config_);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// add loss
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], score[i], config_) * weights_[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// add loss
|
||||
double t = 0;
|
||||
objective->ConvertOutput(&score[i], &t);
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], t, config_);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// add loss
|
||||
double t = 0;
|
||||
objective->ConvertOutput(&score[i], &t);
|
||||
sum_loss += PointWiseLossCalculator::LossOnPoint(label_[i], t, config_) * weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
double loss = PointWiseLossCalculator::AverageLoss(sum_loss, sum_weights_);
|
||||
return std::vector<double>(1, loss);
|
||||
}
|
||||
|
||||
inline static double AverageLoss(double sum_loss, double sum_weights) {
|
||||
return sum_loss / sum_weights;
|
||||
}
|
||||
|
||||
inline static void CheckLabel(label_t) {
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer of weighs */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Name of this test set */
|
||||
Config config_;
|
||||
std::vector<std::string> name_;
|
||||
};
|
||||
|
||||
/*! \brief RMSE loss for regression task */
|
||||
class RMSEMetric: public RegressionMetric<RMSEMetric> {
|
||||
public:
|
||||
explicit RMSEMetric(const Config& config) :RegressionMetric<RMSEMetric>(config) {}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config&) {
|
||||
return (score - label)*(score - label);
|
||||
}
|
||||
|
||||
inline static double AverageLoss(double sum_loss, double sum_weights) {
|
||||
// need sqrt the result for RMSE loss
|
||||
return std::sqrt(sum_loss / sum_weights);
|
||||
}
|
||||
|
||||
inline static const char* Name() {
|
||||
return "rmse";
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief L2 loss for regression task */
|
||||
class L2Metric: public RegressionMetric<L2Metric> {
|
||||
public:
|
||||
explicit L2Metric(const Config& config) :RegressionMetric<L2Metric>(config) {}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config&) {
|
||||
return (score - label)*(score - label);
|
||||
}
|
||||
|
||||
inline static const char* Name() {
|
||||
return "l2";
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief Quantile loss for regression task */
|
||||
class QuantileMetric : public RegressionMetric<QuantileMetric> {
|
||||
public:
|
||||
explicit QuantileMetric(const Config& config) :RegressionMetric<QuantileMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config& config) {
|
||||
double delta = label - score;
|
||||
if (delta < 0) {
|
||||
return (config.alpha - 1.0f) * delta;
|
||||
} else {
|
||||
return config.alpha * delta;
|
||||
}
|
||||
}
|
||||
|
||||
inline static const char* Name() {
|
||||
return "quantile";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! \brief L1 loss for regression task */
|
||||
class L1Metric: public RegressionMetric<L1Metric> {
|
||||
public:
|
||||
explicit L1Metric(const Config& config) :RegressionMetric<L1Metric>(config) {}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config&) {
|
||||
return std::fabs(score - label);
|
||||
}
|
||||
inline static const char* Name() {
|
||||
return "l1";
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief Huber loss for regression task */
|
||||
class HuberLossMetric: public RegressionMetric<HuberLossMetric> {
|
||||
public:
|
||||
explicit HuberLossMetric(const Config& config) :RegressionMetric<HuberLossMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config& config) {
|
||||
const double diff = score - label;
|
||||
if (std::abs(diff) <= config.alpha) {
|
||||
return 0.5f * diff * diff;
|
||||
} else {
|
||||
return config.alpha * (std::abs(diff) - 0.5f * config.alpha);
|
||||
}
|
||||
}
|
||||
|
||||
inline static const char* Name() {
|
||||
return "huber";
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief Fair loss for regression task */
|
||||
// http://research.microsoft.com/en-us/um/people/zhang/INRIA/Publis/Tutorial-Estim/node24.html
|
||||
class FairLossMetric: public RegressionMetric<FairLossMetric> {
|
||||
public:
|
||||
explicit FairLossMetric(const Config& config) :RegressionMetric<FairLossMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config& config) {
|
||||
const double x = std::fabs(score - label);
|
||||
const double c = config.fair_c;
|
||||
return c * x - c * c * std::log1p(x / c);
|
||||
}
|
||||
|
||||
inline static const char* Name() {
|
||||
return "fair";
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief Poisson regression loss for regression task */
|
||||
class PoissonMetric: public RegressionMetric<PoissonMetric> {
|
||||
public:
|
||||
explicit PoissonMetric(const Config& config) :RegressionMetric<PoissonMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config&) {
|
||||
const double eps = 1e-10f;
|
||||
if (score < eps) {
|
||||
score = eps;
|
||||
}
|
||||
return score - label * std::log(score);
|
||||
}
|
||||
inline static const char* Name() {
|
||||
return "poisson";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*! \brief MAPE regression loss for regression task */
|
||||
class MAPEMetric : public RegressionMetric<MAPEMetric> {
|
||||
public:
|
||||
explicit MAPEMetric(const Config& config) :RegressionMetric<MAPEMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config&) {
|
||||
return std::fabs((label - score)) / std::max(1.0f, std::fabs(label));
|
||||
}
|
||||
inline static const char* Name() {
|
||||
return "mape";
|
||||
}
|
||||
};
|
||||
|
||||
class GammaMetric : public RegressionMetric<GammaMetric> {
|
||||
public:
|
||||
explicit GammaMetric(const Config& config) :RegressionMetric<GammaMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config&) {
|
||||
const double psi = 1.0;
|
||||
const double theta = -1.0 / score;
|
||||
const double a = psi;
|
||||
const double b = -Common::SafeLog(-theta);
|
||||
const double c = 1. / psi * Common::SafeLog(label / psi) - Common::SafeLog(label) - 0; // 0 = std::lgamma(1.0 / psi) = std::lgamma(1.0);
|
||||
return -((label * theta - b) / a + c);
|
||||
}
|
||||
inline static const char* Name() {
|
||||
return "gamma";
|
||||
}
|
||||
|
||||
inline static void CheckLabel(label_t label) {
|
||||
CHECK_GT(label, 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class GammaDevianceMetric : public RegressionMetric<GammaDevianceMetric> {
|
||||
public:
|
||||
explicit GammaDevianceMetric(const Config& config) :RegressionMetric<GammaDevianceMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config&) {
|
||||
const double epsilon = 1.0e-9;
|
||||
const double tmp = label / (score + epsilon);
|
||||
return tmp - Common::SafeLog(tmp) - 1;
|
||||
}
|
||||
inline static const char* Name() {
|
||||
return "gamma_deviance";
|
||||
}
|
||||
inline static double AverageLoss(double sum_loss, double) {
|
||||
return sum_loss * 2;
|
||||
}
|
||||
inline static void CheckLabel(label_t label) {
|
||||
CHECK_GT(label, 0);
|
||||
}
|
||||
};
|
||||
|
||||
class TweedieMetric : public RegressionMetric<TweedieMetric> {
|
||||
public:
|
||||
explicit TweedieMetric(const Config& config) :RegressionMetric<TweedieMetric>(config) {
|
||||
}
|
||||
|
||||
inline static double LossOnPoint(label_t label, double score, const Config& config) {
|
||||
const double rho = config.tweedie_variance_power;
|
||||
const double eps = 1e-10f;
|
||||
if (score < eps) {
|
||||
score = eps;
|
||||
}
|
||||
const double a = label * std::exp((1 - rho) * std::log(score)) / (1 - rho);
|
||||
const double b = std::exp((2 - rho) * std::log(score)) / (2 - rho);
|
||||
return -a + b;
|
||||
}
|
||||
inline static const char* Name() {
|
||||
return "tweedie";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class R2Metric: public Metric {
|
||||
public:
|
||||
explicit R2Metric(const Config& config) :config_(config) {}
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back("r2");
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
|
||||
double sum_label = 0.0f;
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_label)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_label += label_[i];
|
||||
}
|
||||
} else {
|
||||
double local_sum_weights = 0.0f;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:local_sum_weights, sum_label)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
local_sum_weights += weights_[i];
|
||||
sum_label += label_[i] * weights_[i];
|
||||
}
|
||||
sum_weights_ = local_sum_weights;
|
||||
}
|
||||
label_mean_ = sum_label / sum_weights_;
|
||||
|
||||
total_sum_squares_ = 0.0f;
|
||||
double local_total_sum_squares = 0.0f;
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:local_total_sum_squares)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double diff = label_[i] - label_mean_;
|
||||
local_total_sum_squares += diff * diff;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:local_total_sum_squares)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double diff = label_[i] - label_mean_;
|
||||
local_total_sum_squares += diff * diff * weights_[i];
|
||||
}
|
||||
}
|
||||
total_sum_squares_ = local_total_sum_squares;
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
|
||||
double residual_sum_squares = 0.0f;
|
||||
if (objective == nullptr) {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:residual_sum_squares)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double diff = label_[i] - score[i];
|
||||
residual_sum_squares += diff * diff;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:residual_sum_squares)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double diff = label_[i] - score[i];
|
||||
residual_sum_squares += diff * diff * weights_[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:residual_sum_squares)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double t = 0;
|
||||
objective->ConvertOutput(&score[i], &t);
|
||||
double diff = label_[i] - t;
|
||||
residual_sum_squares += diff * diff;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:residual_sum_squares)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double t = 0;
|
||||
objective->ConvertOutput(&score[i], &t);
|
||||
double diff = label_[i] - t;
|
||||
residual_sum_squares += diff * diff * weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double r2 = 1.0 - (residual_sum_squares / total_sum_squares_);
|
||||
if (std::fabs(total_sum_squares_) < kZeroThreshold) {
|
||||
return std::vector<double>(1, std::fabs(residual_sum_squares) < kZeroThreshold ? 1.0 : 0.0);
|
||||
}
|
||||
return std::vector<double>(1, r2);
|
||||
}
|
||||
|
||||
protected:
|
||||
data_size_t num_data_;
|
||||
const label_t* label_;
|
||||
const label_t* weights_;
|
||||
double sum_weights_;
|
||||
Config config_;
|
||||
std::vector<std::string> name_;
|
||||
|
||||
// Custom members for R2 calculation
|
||||
double label_mean_;
|
||||
double total_sum_squares_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_METRIC_REGRESSION_METRIC_HPP_
|
||||
@@ -0,0 +1,359 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_METRIC_XENTROPY_METRIC_HPP_
|
||||
#define LIGHTGBM_SRC_METRIC_XENTROPY_METRIC_HPP_
|
||||
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* Implements three related metrics:
|
||||
*
|
||||
* (1) standard cross-entropy that can be used for continuous labels in [0, 1]
|
||||
* (2) "intensity-weighted" cross-entropy, also for continuous labels in [0, 1]
|
||||
* (3) Kullback-Leibler divergence, also for continuous labels in [0, 1]
|
||||
*
|
||||
* (3) adds an offset term to (1); the entropy of the label
|
||||
*
|
||||
* See xentropy_objective.hpp for further details.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
// label should be in interval [0, 1];
|
||||
// prob should be in interval (0, 1); prob is clipped if needed
|
||||
inline static double XentLoss(label_t label, double prob) {
|
||||
const double log_arg_epsilon = 1.0e-12;
|
||||
double a = label;
|
||||
if (prob > log_arg_epsilon) {
|
||||
a *= std::log(prob);
|
||||
} else {
|
||||
a *= std::log(log_arg_epsilon);
|
||||
}
|
||||
double b = 1.0f - label;
|
||||
if (1.0f - prob > log_arg_epsilon) {
|
||||
b *= std::log(1.0f - prob);
|
||||
} else {
|
||||
b *= std::log(log_arg_epsilon);
|
||||
}
|
||||
return - (a + b);
|
||||
}
|
||||
|
||||
// hhat >(=) 0 assumed; and weight > 0 required; but not checked here
|
||||
inline static double XentLambdaLoss(label_t label, label_t weight, double hhat) {
|
||||
return XentLoss(label, 1.0f - std::exp(-weight * hhat));
|
||||
}
|
||||
|
||||
// Computes the (negative) entropy for label p; p should be in interval [0, 1];
|
||||
// This is used to presum the KL-divergence offset term (to be _added_ to the cross-entropy loss).
|
||||
// NOTE: x*log(x) = 0 for x=0,1; so only add when in (0, 1); avoid log(0)*0
|
||||
inline static double YentLoss(double p) {
|
||||
double hp = 0.0;
|
||||
if (p > 0) hp += p * std::log(p);
|
||||
double q = 1.0f - p;
|
||||
if (q > 0) hp += q * std::log(q);
|
||||
return hp;
|
||||
}
|
||||
|
||||
//
|
||||
// CrossEntropyMetric : "xentropy" : (optional) weights are used linearly
|
||||
//
|
||||
class CrossEntropyMetric : public Metric {
|
||||
public:
|
||||
explicit CrossEntropyMetric(const Config&) {}
|
||||
virtual ~CrossEntropyMetric() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back("cross_entropy");
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
|
||||
CHECK_NOTNULL(label_);
|
||||
|
||||
// ensure that labels are in interval [0, 1], interval ends included
|
||||
Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName()[0].c_str());
|
||||
Log::Info("[%s:%s]: (metric) labels passed interval [0, 1] check", GetName()[0].c_str(), __func__);
|
||||
|
||||
// check that weights are non-negative and sum is positive
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
label_t minw;
|
||||
Common::ObtainMinMaxSum(weights_, num_data_, &minw, static_cast<label_t*>(nullptr), &sum_weights_);
|
||||
if (minw < 0.0f) {
|
||||
Log::Fatal("[%s:%s]: (metric) weights not allowed to be negative", GetName()[0].c_str(), __func__);
|
||||
}
|
||||
}
|
||||
|
||||
// check weight sum (may fail to be zero)
|
||||
if (sum_weights_ <= 0.0f) {
|
||||
Log::Fatal("[%s:%s]: sum-of-weights = %f is non-positive", __func__, GetName()[0].c_str(), sum_weights_);
|
||||
}
|
||||
Log::Info("[%s:%s]: sum-of-weights = %f", GetName()[0].c_str(), __func__, sum_weights_);
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
|
||||
double sum_loss = 0.0f;
|
||||
if (objective == nullptr) {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double p = 0;
|
||||
objective->ConvertOutput(&score[i], &p);
|
||||
sum_loss += XentLoss(label_[i], p);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double p = 0;
|
||||
objective->ConvertOutput(&score[i], &p);
|
||||
sum_loss += XentLoss(label_[i], p) * weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
double loss = sum_loss / sum_weights_;
|
||||
return std::vector<double>(1, loss);
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return -1.0f; // negative means smaller loss is better, positive means larger loss is better
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data points */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer to label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer to weights */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum of weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Name of this metric */
|
||||
std::vector<std::string> name_;
|
||||
};
|
||||
|
||||
//
|
||||
// CrossEntropyLambdaMetric : "xentlambda" : (optional) weights have a different meaning than for "xentropy"
|
||||
// ATTENTION: Supposed to be used when the objective also is "xentlambda"
|
||||
//
|
||||
class CrossEntropyLambdaMetric : public Metric {
|
||||
public:
|
||||
explicit CrossEntropyLambdaMetric(const Config&) {}
|
||||
virtual ~CrossEntropyLambdaMetric() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back("cross_entropy_lambda");
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
|
||||
CHECK_NOTNULL(label_);
|
||||
Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName()[0].c_str());
|
||||
Log::Info("[%s:%s]: (metric) labels passed interval [0, 1] check", GetName()[0].c_str(), __func__);
|
||||
|
||||
// check all weights are strictly positive; throw error if not
|
||||
if (weights_ != nullptr) {
|
||||
label_t minw;
|
||||
Common::ObtainMinMaxSum(weights_, num_data_, &minw, static_cast<label_t*>(nullptr), static_cast<label_t*>(nullptr));
|
||||
if (minw <= 0.0f) {
|
||||
Log::Fatal("[%s:%s]: (metric) all weights must be positive", GetName()[0].c_str(), __func__);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
|
||||
double sum_loss = 0.0f;
|
||||
if (objective == nullptr) {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double hhat = std::log1p(std::exp(score[i])); // auto-convert
|
||||
sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double hhat = std::log1p(std::exp(score[i])); // auto-convert
|
||||
sum_loss += XentLambdaLoss(label_[i], weights_[i], hhat);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double hhat = 0;
|
||||
objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda"
|
||||
sum_loss += XentLambdaLoss(label_[i], 1.0f, hhat);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double hhat = 0;
|
||||
objective->ConvertOutput(&score[i], &hhat); // NOTE: this only works if objective = "xentlambda"
|
||||
sum_loss += XentLambdaLoss(label_[i], weights_[i], hhat);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::vector<double>(1, sum_loss / static_cast<double>(num_data_));
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data points */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer to label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer to weights */
|
||||
const label_t* weights_;
|
||||
/*! \brief Name of this metric */
|
||||
std::vector<std::string> name_;
|
||||
};
|
||||
|
||||
//
|
||||
// KullbackLeiblerDivergence : "kldiv" : (optional) weights are used linearly
|
||||
//
|
||||
class KullbackLeiblerDivergence : public Metric {
|
||||
public:
|
||||
explicit KullbackLeiblerDivergence(const Config&) {}
|
||||
virtual ~KullbackLeiblerDivergence() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
name_.emplace_back("kullback_leibler");
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
|
||||
CHECK_NOTNULL(label_);
|
||||
Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName()[0].c_str());
|
||||
Log::Info("[%s:%s]: (metric) labels passed interval [0, 1] check", GetName()[0].c_str(), __func__);
|
||||
|
||||
if (weights_ == nullptr) {
|
||||
sum_weights_ = static_cast<double>(num_data_);
|
||||
} else {
|
||||
label_t minw;
|
||||
Common::ObtainMinMaxSum(weights_, num_data_, &minw, static_cast<label_t*>(nullptr), &sum_weights_);
|
||||
if (minw < 0.0f) {
|
||||
Log::Fatal("[%s:%s]: (metric) at least one weight is negative", GetName()[0].c_str(), __func__);
|
||||
}
|
||||
}
|
||||
|
||||
// check weight sum
|
||||
if (sum_weights_ <= 0.0f) {
|
||||
Log::Fatal("[%s:%s]: sum-of-weights = %f is non-positive", GetName()[0].c_str(), __func__, sum_weights_);
|
||||
}
|
||||
|
||||
Log::Info("[%s:%s]: sum-of-weights = %f", GetName()[0].c_str(), __func__, sum_weights_);
|
||||
|
||||
// evaluate offset term
|
||||
presum_label_entropy_ = 0.0f;
|
||||
if (weights_ == nullptr) {
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
presum_label_entropy_ += YentLoss(label_[i]);
|
||||
}
|
||||
} else {
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
presum_label_entropy_ += YentLoss(label_[i]) * weights_[i];
|
||||
}
|
||||
}
|
||||
presum_label_entropy_ /= sum_weights_;
|
||||
|
||||
// communicate the value of the offset term to be added
|
||||
Log::Info("%s offset term = %f", GetName()[0].c_str(), presum_label_entropy_);
|
||||
}
|
||||
|
||||
std::vector<double> Eval(const double* score, const ObjectiveFunction* objective) const override {
|
||||
double sum_loss = 0.0f;
|
||||
if (objective == nullptr) {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_loss += XentLoss(label_[i], score[i]); // NOTE: does not work unless score is a probability
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
sum_loss += XentLoss(label_[i], score[i]) * weights_[i]; // NOTE: does not work unless score is a probability
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double p = 0;
|
||||
objective->ConvertOutput(&score[i], &p);
|
||||
sum_loss += XentLoss(label_[i], p);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:sum_loss)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double p = 0;
|
||||
objective->ConvertOutput(&score[i], &p);
|
||||
sum_loss += XentLoss(label_[i], p) * weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
double loss = presum_label_entropy_ + sum_loss / sum_weights_;
|
||||
return std::vector<double>(1, loss);
|
||||
}
|
||||
|
||||
const std::vector<std::string>& GetName() const override {
|
||||
return name_;
|
||||
}
|
||||
|
||||
double factor_to_bigger_better() const override {
|
||||
return -1.0f;
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data points */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer to label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer to weights */
|
||||
const label_t* weights_;
|
||||
/*! \brief Sum of weights */
|
||||
double sum_weights_;
|
||||
/*! \brief Offset term to cross-entropy; precomputed during init */
|
||||
double presum_label_entropy_;
|
||||
/*! \brief Name of this metric */
|
||||
std::vector<std::string> name_;
|
||||
};
|
||||
|
||||
} // end namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_METRIC_XENTROPY_METRIC_HPP_
|
||||
@@ -0,0 +1,182 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/network.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
|
||||
BruckMap::BruckMap() {
|
||||
k = 0;
|
||||
}
|
||||
|
||||
BruckMap::BruckMap(int n) {
|
||||
k = n;
|
||||
// default set to -1
|
||||
for (int i = 0; i < n; ++i) {
|
||||
in_ranks.push_back(-1);
|
||||
out_ranks.push_back(-1);
|
||||
}
|
||||
}
|
||||
|
||||
BruckMap BruckMap::Construct(int rank, int num_machines) {
|
||||
// distance at k-th communication, distance[k] = 2^k
|
||||
std::vector<int> distance;
|
||||
int k = 0;
|
||||
for (k = 0; (1 << k) < num_machines; ++k) {
|
||||
distance.push_back(1 << k);
|
||||
}
|
||||
BruckMap bruckMap(k);
|
||||
for (int j = 0; j < k; ++j) {
|
||||
// set incoming rank at k-th communication
|
||||
const int in_rank = (rank + distance[j]) % num_machines;
|
||||
bruckMap.in_ranks[j] = in_rank;
|
||||
// set outgoing rank at k-th communication
|
||||
const int out_rank = (rank - distance[j] + num_machines) % num_machines;
|
||||
bruckMap.out_ranks[j] = out_rank;
|
||||
}
|
||||
return bruckMap;
|
||||
}
|
||||
|
||||
RecursiveHalvingMap::RecursiveHalvingMap() {
|
||||
k = 0;
|
||||
}
|
||||
|
||||
RecursiveHalvingMap::RecursiveHalvingMap(int in_k, RecursiveHalvingNodeType _type, bool _is_power_of_2) {
|
||||
type = _type;
|
||||
k = in_k;
|
||||
is_power_of_2 = _is_power_of_2;
|
||||
if (type != RecursiveHalvingNodeType::Other) {
|
||||
for (int i = 0; i < k; ++i) {
|
||||
// default set as -1
|
||||
ranks.push_back(-1);
|
||||
send_block_start.push_back(-1);
|
||||
send_block_len.push_back(-1);
|
||||
recv_block_start.push_back(-1);
|
||||
recv_block_len.push_back(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecursiveHalvingMap RecursiveHalvingMap::Construct(int rank, int num_machines) {
|
||||
// construct all recursive halving map for all machines
|
||||
int k = 0;
|
||||
while ((1 << k) <= num_machines) {
|
||||
++k;
|
||||
}
|
||||
// let 1 << k <= num_machines
|
||||
--k;
|
||||
// distance of each communication
|
||||
std::vector<int> distance;
|
||||
for (int i = 0; i < k; ++i) {
|
||||
distance.push_back(1 << (k - 1 - i));
|
||||
}
|
||||
|
||||
if ((1 << k) == num_machines) {
|
||||
RecursiveHalvingMap rec_map(k, RecursiveHalvingNodeType::Normal, true);
|
||||
// if num_machines = 2^k, don't need to group machines
|
||||
for (int i = 0; i < k; ++i) {
|
||||
// communication direction, %2 == 0 is positive
|
||||
const int dir = ((rank / distance[i]) % 2 == 0) ? 1 : -1;
|
||||
// neighbor at k-th communication
|
||||
const int next_node_idx = rank + dir * distance[i];
|
||||
rec_map.ranks[i] = next_node_idx;
|
||||
// receive data block at k-th communication
|
||||
const int recv_block_start = rank / distance[i];
|
||||
rec_map.recv_block_start[i] = recv_block_start * distance[i];
|
||||
rec_map.recv_block_len[i] = distance[i];
|
||||
// send data block at k-th communication
|
||||
const int send_block_start = next_node_idx / distance[i];
|
||||
rec_map.send_block_start[i] = send_block_start * distance[i];
|
||||
rec_map.send_block_len[i] = distance[i];
|
||||
}
|
||||
return rec_map;
|
||||
} else {
|
||||
// if num_machines != 2^k, need to group machines
|
||||
|
||||
int lower_power_of_2 = 1 << k;
|
||||
|
||||
int rest = num_machines - lower_power_of_2;
|
||||
|
||||
std::vector<RecursiveHalvingNodeType> node_type(num_machines);
|
||||
for (int i = 0; i < num_machines; ++i) {
|
||||
node_type[i] = RecursiveHalvingNodeType::Normal;
|
||||
}
|
||||
// group, two machine in one group, total "rest" groups will have 2 machines.
|
||||
for (int i = 0; i < rest; ++i) {
|
||||
int right = num_machines - i * 2 - 1;
|
||||
int left = num_machines - i * 2 - 2;
|
||||
// let left machine as group leader
|
||||
node_type[left] = RecursiveHalvingNodeType::GroupLeader;
|
||||
node_type[right] = RecursiveHalvingNodeType::Other;
|
||||
}
|
||||
int group_cnt = 0;
|
||||
// cache block information for groups, group with 2 machines will have double block size
|
||||
std::vector<int> group_block_start(lower_power_of_2);
|
||||
std::vector<int> group_block_len(lower_power_of_2, 0);
|
||||
// convert from group to node leader
|
||||
std::vector<int> group_to_node(lower_power_of_2);
|
||||
// convert from node to group
|
||||
std::vector<int> node_to_group(num_machines);
|
||||
|
||||
for (int i = 0; i < num_machines; ++i) {
|
||||
// meet new group
|
||||
if (node_type[i] == RecursiveHalvingNodeType::Normal || node_type[i] == RecursiveHalvingNodeType::GroupLeader) {
|
||||
group_to_node[group_cnt++] = i;
|
||||
}
|
||||
node_to_group[i] = group_cnt - 1;
|
||||
// add block len for this group
|
||||
group_block_len[group_cnt - 1]++;
|
||||
}
|
||||
// calculate the group block start
|
||||
group_block_start[0] = 0;
|
||||
for (int i = 1; i < lower_power_of_2; ++i) {
|
||||
group_block_start[i] = group_block_start[i - 1] + group_block_len[i - 1];
|
||||
}
|
||||
|
||||
RecursiveHalvingMap rec_map(k, node_type[rank], false);
|
||||
if (node_type[rank] == RecursiveHalvingNodeType::Other) {
|
||||
rec_map.neighbor = rank - 1;
|
||||
// not need to construct
|
||||
return rec_map;
|
||||
}
|
||||
if (node_type[rank] == RecursiveHalvingNodeType::GroupLeader) {
|
||||
rec_map.neighbor = rank + 1;
|
||||
}
|
||||
const int cur_group_idx = node_to_group[rank];
|
||||
for (int i = 0; i < k; ++i) {
|
||||
const int dir = ((cur_group_idx / distance[i]) % 2 == 0) ? 1 : -1;
|
||||
const int next_node_idx = group_to_node[(cur_group_idx + dir * distance[i])];
|
||||
rec_map.ranks[i] = next_node_idx;
|
||||
// get receive block information
|
||||
const int recv_block_start = cur_group_idx / distance[i];
|
||||
rec_map.recv_block_start[i] = group_block_start[static_cast<size_t>(recv_block_start) * distance[i]];
|
||||
int recv_block_len = 0;
|
||||
// accumulate block len
|
||||
for (int j = 0; j < distance[i]; ++j) {
|
||||
recv_block_len += group_block_len[recv_block_start * distance[i] + j];
|
||||
}
|
||||
rec_map.recv_block_len[i] = recv_block_len;
|
||||
// get send block information
|
||||
const int send_block_start = (cur_group_idx + dir * distance[i]) / distance[i];
|
||||
rec_map.send_block_start[i] = group_block_start[static_cast<size_t>(send_block_start) * distance[i]];
|
||||
int send_block_len = 0;
|
||||
// accumulate block len
|
||||
for (int j = 0; j < distance[i]; ++j) {
|
||||
send_block_len += group_block_len[send_block_start * distance[i] + j];
|
||||
}
|
||||
rec_map.send_block_len[i] = send_block_len;
|
||||
}
|
||||
return rec_map;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,329 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_NETWORK_LINKERS_H_
|
||||
#define LIGHTGBM_SRC_NETWORK_LINKERS_H_
|
||||
|
||||
#include <LightGBM/config.h>
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/network.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_SOCKET
|
||||
#include "socket_wrapper.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef USE_MPI
|
||||
#include <mpi.h>
|
||||
#define MPI_SAFE_CALL(mpi_return) CHECK((mpi_return) == MPI_SUCCESS)
|
||||
#endif
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
/*!
|
||||
* \brief A network basic communication wrapper.
|
||||
* Will wrap low level communication methods, e.g. mpi, socket and so on.
|
||||
* This class will wrap all linkers to other machines if needs
|
||||
*/
|
||||
class Linkers {
|
||||
public:
|
||||
Linkers() {
|
||||
is_init_ = false;
|
||||
}
|
||||
/*!
|
||||
* \brief Constructor
|
||||
* \param config Config of network settings
|
||||
*/
|
||||
explicit Linkers(Config config);
|
||||
/*!
|
||||
* \brief Destructor
|
||||
*/
|
||||
~Linkers();
|
||||
/*!
|
||||
* \brief Recv data, blocking
|
||||
* \param rank Which rank will send data to local machine
|
||||
* \param data Pointer of receive data
|
||||
* \param len Recv size, will block until receive len size of data
|
||||
*/
|
||||
inline void Recv(int rank, char* data, int len) const;
|
||||
|
||||
inline void Recv(int rank, char* data, int64_t len) const;
|
||||
|
||||
/*!
|
||||
* \brief Send data, blocking
|
||||
* \param rank Which rank local machine will send to
|
||||
* \param data Pointer of send data
|
||||
* \param len Send size
|
||||
*/
|
||||
inline void Send(int rank, char* data, int len) const;
|
||||
|
||||
inline void Send(int rank, char* data, int64_t len) const;
|
||||
/*!
|
||||
* \brief Send and Recv at same time, blocking
|
||||
* \param send_rank
|
||||
* \param send_data
|
||||
* \param send_len
|
||||
* \param recv_rank
|
||||
* \param recv_data
|
||||
* \param recv_len
|
||||
*/
|
||||
inline void SendRecv(int send_rank, char* send_data, int send_len,
|
||||
int recv_rank, char* recv_data, int recv_len);
|
||||
|
||||
inline void SendRecv(int send_rank, char* send_data, int64_t send_len,
|
||||
int recv_rank, char* recv_data, int64_t recv_len);
|
||||
/*!
|
||||
* \brief Get rank of local machine
|
||||
*/
|
||||
inline int rank();
|
||||
/*!
|
||||
* \brief Get total number of machines
|
||||
*/
|
||||
inline int num_machines();
|
||||
/*!
|
||||
* \brief Get Bruck map of this network
|
||||
*/
|
||||
inline const BruckMap& bruck_map();
|
||||
/*!
|
||||
* \brief Get Recursive Halving map of this network
|
||||
*/
|
||||
inline const RecursiveHalvingMap& recursive_halving_map();
|
||||
|
||||
#ifdef USE_SOCKET
|
||||
/*!
|
||||
* \brief Bind local listen to port
|
||||
* \param port Local listen port
|
||||
*/
|
||||
void TryBind(int port);
|
||||
/*!
|
||||
* \brief Set socket to rank
|
||||
* \param rank
|
||||
* \param socket
|
||||
*/
|
||||
void SetLinker(int rank, const TcpSocket& socket);
|
||||
/*!
|
||||
* \brief Thread for listening
|
||||
* \param incoming_cnt Number of incoming machines
|
||||
*/
|
||||
void ListenThread(int incoming_cnt);
|
||||
/*!
|
||||
* \brief Construct network topo
|
||||
*/
|
||||
void Construct();
|
||||
/*!
|
||||
* \brief Parser machines information from file
|
||||
* \param machines
|
||||
* \param filename
|
||||
*/
|
||||
void ParseMachineList(const std::string& machines, const std::string& filename);
|
||||
/*!
|
||||
* \brief Check one linker is connected or not
|
||||
* \param rank
|
||||
* \return True if linker is connected
|
||||
*/
|
||||
bool CheckLinker(int rank);
|
||||
/*!
|
||||
* \brief Print connected linkers
|
||||
*/
|
||||
void PrintLinkers();
|
||||
|
||||
#endif // USE_SOCKET
|
||||
|
||||
#ifdef USE_MPI
|
||||
|
||||
/*!
|
||||
* \brief Check if MPI has been initialized
|
||||
*/
|
||||
static bool IsMpiInitialized();
|
||||
|
||||
/*!
|
||||
* \brief Finalize the MPI session if it was initialized
|
||||
*/
|
||||
static void MpiFinalizeIfIsParallel();
|
||||
|
||||
/*!
|
||||
* \brief Abort the MPI session if it was initialized (called in case there was a error that needs abrupt ending)
|
||||
*/
|
||||
static void MpiAbortIfIsParallel();
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
/*! \brief Rank of local machine */
|
||||
int rank_;
|
||||
/*! \brief Total number machines */
|
||||
int num_machines_;
|
||||
/*! \brief Bruck map */
|
||||
BruckMap bruck_map_;
|
||||
/*! \brief Recursive Halving map */
|
||||
RecursiveHalvingMap recursive_halving_map_;
|
||||
|
||||
std::chrono::duration<double, std::milli> network_time_;
|
||||
|
||||
bool is_init_;
|
||||
|
||||
#ifdef USE_SOCKET
|
||||
/*! \brief use to store client ips */
|
||||
std::vector<std::string> client_ips_;
|
||||
/*! \brief use to store client ports */
|
||||
std::vector<int> client_ports_;
|
||||
/*! \brief time out for sockets, in minutes */
|
||||
int socket_timeout_;
|
||||
/*! \brief Local listen ports */
|
||||
int local_listen_port_;
|
||||
/*! \brief Linkers */
|
||||
std::vector<std::unique_ptr<TcpSocket>> linkers_;
|
||||
/*! \brief Local socket listener */
|
||||
std::unique_ptr<TcpSocket> listener_;
|
||||
#endif // USE_SOCKET
|
||||
};
|
||||
|
||||
|
||||
inline int Linkers::rank() {
|
||||
return rank_;
|
||||
}
|
||||
|
||||
inline int Linkers::num_machines() {
|
||||
return num_machines_;
|
||||
}
|
||||
|
||||
inline const BruckMap& Linkers::bruck_map() {
|
||||
return bruck_map_;
|
||||
}
|
||||
|
||||
inline const RecursiveHalvingMap& Linkers::recursive_halving_map() {
|
||||
return recursive_halving_map_;
|
||||
}
|
||||
|
||||
inline void Linkers::Recv(int rank, char* data, int64_t len) const {
|
||||
int64_t used = 0;
|
||||
do {
|
||||
int cur_size = static_cast<int>(std::min<int64_t>(len - used, INT32_MAX));
|
||||
Recv(rank, data + used, cur_size);
|
||||
used += cur_size;
|
||||
} while (used < len);
|
||||
}
|
||||
|
||||
inline void Linkers::Send(int rank, char* data, int64_t len) const {
|
||||
int64_t used = 0;
|
||||
do {
|
||||
int cur_size = static_cast<int>(std::min<int64_t>(len - used, INT32_MAX));
|
||||
Send(rank, data + used, cur_size);
|
||||
used += cur_size;
|
||||
} while (used < len);
|
||||
}
|
||||
|
||||
inline void Linkers::SendRecv(int send_rank, char* send_data, int64_t send_len,
|
||||
int recv_rank, char* recv_data, int64_t recv_len) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
std::thread send_worker(
|
||||
[this, send_rank, send_data, send_len]() {
|
||||
Send(send_rank, send_data, send_len);
|
||||
});
|
||||
Recv(recv_rank, recv_data, recv_len);
|
||||
send_worker.join();
|
||||
// wait for send complete
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
// output used time on each iteration
|
||||
network_time_ += std::chrono::duration<double, std::milli>(end_time - start_time);
|
||||
}
|
||||
|
||||
#ifdef USE_SOCKET
|
||||
|
||||
inline void Linkers::Recv(int rank, char* data, int len) const {
|
||||
int recv_cnt = 0;
|
||||
while (recv_cnt < len) {
|
||||
recv_cnt += linkers_[rank]->Recv(data + recv_cnt,
|
||||
// len - recv_cnt
|
||||
std::min(len - recv_cnt, SocketConfig::kMaxReceiveSize));
|
||||
}
|
||||
}
|
||||
|
||||
inline void Linkers::Send(int rank, char* data, int len) const {
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
int send_cnt = 0;
|
||||
while (send_cnt < len) {
|
||||
send_cnt += linkers_[rank]->Send(data + send_cnt, len - send_cnt);
|
||||
}
|
||||
}
|
||||
|
||||
inline void Linkers::SendRecv(int send_rank, char* send_data, int send_len,
|
||||
int recv_rank, char* recv_data, int recv_len) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
if (send_len < SocketConfig::kSocketBufferSize) {
|
||||
// if buffer is enough, send will non-blocking
|
||||
Send(send_rank, send_data, send_len);
|
||||
Recv(recv_rank, recv_data, recv_len);
|
||||
} else {
|
||||
// if buffer is not enough, use another thread to send, since send will be blocking
|
||||
std::thread send_worker(
|
||||
[this, send_rank, send_data, send_len]() {
|
||||
Send(send_rank, send_data, send_len);
|
||||
});
|
||||
Recv(recv_rank, recv_data, recv_len);
|
||||
send_worker.join();
|
||||
}
|
||||
// wait for send complete
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
// output used time on each iteration
|
||||
network_time_ += std::chrono::duration<double, std::milli>(end_time - start_time);
|
||||
}
|
||||
|
||||
#endif // USE_SOCKET
|
||||
|
||||
#ifdef USE_MPI
|
||||
|
||||
inline void Linkers::Recv(int rank, char* data, int len) const {
|
||||
MPI_Status status;
|
||||
int read_cnt = 0;
|
||||
while (read_cnt < len) {
|
||||
MPI_SAFE_CALL(MPI_Recv(data + read_cnt, len - read_cnt, MPI_BYTE, rank, MPI_ANY_TAG, MPI_COMM_WORLD, &status));
|
||||
int cur_cnt;
|
||||
MPI_SAFE_CALL(MPI_Get_count(&status, MPI_BYTE, &cur_cnt));
|
||||
read_cnt += cur_cnt;
|
||||
}
|
||||
}
|
||||
|
||||
inline void Linkers::Send(int rank, char* data, int len) const {
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
MPI_Status status;
|
||||
MPI_Request send_request;
|
||||
MPI_SAFE_CALL(MPI_Isend(data, len, MPI_BYTE, rank, 0, MPI_COMM_WORLD, &send_request));
|
||||
MPI_SAFE_CALL(MPI_Wait(&send_request, &status));
|
||||
}
|
||||
|
||||
inline void Linkers::SendRecv(int send_rank, char* send_data, int send_len,
|
||||
int recv_rank, char* recv_data, int recv_len) {
|
||||
MPI_Request send_request;
|
||||
// send first, non-blocking
|
||||
MPI_SAFE_CALL(MPI_Isend(send_data, send_len, MPI_BYTE, send_rank, 0, MPI_COMM_WORLD, &send_request));
|
||||
// then receive, blocking
|
||||
MPI_Status status;
|
||||
int read_cnt = 0;
|
||||
while (read_cnt < recv_len) {
|
||||
MPI_SAFE_CALL(MPI_Recv(recv_data + read_cnt, recv_len - read_cnt, MPI_BYTE, recv_rank, 0, MPI_COMM_WORLD, &status));
|
||||
int cur_cnt;
|
||||
MPI_SAFE_CALL(MPI_Get_count(&status, MPI_BYTE, &cur_cnt));
|
||||
read_cnt += cur_cnt;
|
||||
}
|
||||
// wait for send complete
|
||||
MPI_SAFE_CALL(MPI_Wait(&send_request, &status));
|
||||
}
|
||||
|
||||
#endif // USE_MPI
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_NETWORK_LINKERS_H_
|
||||
@@ -0,0 +1,64 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifdef USE_MPI
|
||||
|
||||
#include "linkers.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
Linkers::Linkers(Config) {
|
||||
is_init_ = false;
|
||||
int argc = 0;
|
||||
char**argv = nullptr;
|
||||
int flag = 0;
|
||||
MPI_SAFE_CALL(MPI_Initialized(&flag)); // test if MPI has been initialized
|
||||
if (!flag) { // if MPI not started, start it
|
||||
MPI_SAFE_CALL(MPI_Init_thread(&argc, &argv, MPI_THREAD_SERIALIZED, &flag));
|
||||
}
|
||||
MPI_SAFE_CALL(MPI_Comm_size(MPI_COMM_WORLD, &num_machines_));
|
||||
MPI_SAFE_CALL(MPI_Comm_rank(MPI_COMM_WORLD, &rank_));
|
||||
// wait for all client start up
|
||||
MPI_SAFE_CALL(MPI_Barrier(MPI_COMM_WORLD));
|
||||
bruck_map_ = BruckMap::Construct(rank_, num_machines_);
|
||||
recursive_halving_map_ = RecursiveHalvingMap::Construct(rank_, num_machines_);
|
||||
is_init_ = true;
|
||||
}
|
||||
|
||||
Linkers::~Linkers() {
|
||||
// Don't call MPI_Finalize() here: If the destructor was called because only this node had an exception, calling MPI_Finalize() will cause all nodes to hang.
|
||||
// Instead we will handle finalize/abort for MPI in main().
|
||||
}
|
||||
|
||||
bool Linkers::IsMpiInitialized() {
|
||||
int is_mpi_init;
|
||||
MPI_SAFE_CALL(MPI_Initialized(&is_mpi_init));
|
||||
return is_mpi_init;
|
||||
}
|
||||
|
||||
void Linkers::MpiFinalizeIfIsParallel() {
|
||||
if (IsMpiInitialized()) {
|
||||
Log::Debug("Finalizing MPI session.");
|
||||
MPI_SAFE_CALL(MPI_Finalize());
|
||||
}
|
||||
}
|
||||
|
||||
void Linkers::MpiAbortIfIsParallel() {
|
||||
try {
|
||||
if (IsMpiInitialized()) {
|
||||
std::cerr << "Aborting MPI communication." << std::endl << std::flush;
|
||||
MPI_SAFE_CALL(MPI_Abort(MPI_COMM_WORLD, -1));;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
std::cerr << "Exception was raised before aborting MPI. Aborting process..." << std::endl << std::flush;
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // USE_MPI
|
||||
@@ -0,0 +1,241 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifdef USE_SOCKET
|
||||
|
||||
#include <LightGBM/config.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/text_reader.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "linkers.h"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
Linkers::Linkers(Config config) {
|
||||
is_init_ = false;
|
||||
// start up socket
|
||||
TcpSocket::Startup();
|
||||
network_time_ = std::chrono::duration<double, std::milli>(0);
|
||||
num_machines_ = config.num_machines;
|
||||
local_listen_port_ = config.local_listen_port;
|
||||
socket_timeout_ = config.time_out;
|
||||
rank_ = -1;
|
||||
// parse clients from file
|
||||
ParseMachineList(config.machines, config.machine_list_filename);
|
||||
|
||||
if (rank_ == -1) {
|
||||
// get ip list of local machine
|
||||
std::unordered_set<std::string> local_ip_list = TcpSocket::GetLocalIpList();
|
||||
// get local rank
|
||||
for (size_t i = 0; i < client_ips_.size(); ++i) {
|
||||
if (local_ip_list.count(client_ips_[i]) > 0 && client_ports_[i] == local_listen_port_) {
|
||||
rank_ = static_cast<int>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rank_ == -1) {
|
||||
Log::Fatal("Machine list file doesn't contain the local machine");
|
||||
}
|
||||
// construct listener
|
||||
listener_ = std::unique_ptr<TcpSocket>(new TcpSocket());
|
||||
TryBind(local_listen_port_);
|
||||
|
||||
for (int i = 0; i < num_machines_; ++i) {
|
||||
linkers_.push_back(nullptr);
|
||||
}
|
||||
|
||||
// construct communication topo
|
||||
bruck_map_ = BruckMap::Construct(rank_, num_machines_);
|
||||
recursive_halving_map_ = RecursiveHalvingMap::Construct(rank_, num_machines_);
|
||||
|
||||
// construct linkers
|
||||
Construct();
|
||||
// free listener
|
||||
listener_->Close();
|
||||
is_init_ = true;
|
||||
}
|
||||
|
||||
Linkers::~Linkers() {
|
||||
if (is_init_) {
|
||||
for (size_t i = 0; i < linkers_.size(); ++i) {
|
||||
if (linkers_[i] != nullptr) {
|
||||
linkers_[i]->Close();
|
||||
}
|
||||
}
|
||||
TcpSocket::Finalize();
|
||||
Log::Info("Finished linking network in %f seconds", network_time_ * 1e-3);
|
||||
}
|
||||
}
|
||||
|
||||
void Linkers::ParseMachineList(const std::string& machines, const std::string& filename) {
|
||||
std::vector<std::string> lines;
|
||||
if (machines.empty()) {
|
||||
TextReader<size_t> machine_list_reader(filename.c_str(), false);
|
||||
machine_list_reader.ReadAllLines();
|
||||
if (machine_list_reader.Lines().empty()) {
|
||||
Log::Fatal("Machine list file %s doesn't exist", filename.c_str());
|
||||
}
|
||||
lines = machine_list_reader.Lines();
|
||||
} else {
|
||||
lines = Common::Split(machines.c_str(), ',');
|
||||
}
|
||||
for (auto& line : lines) {
|
||||
line = Common::Trim(line);
|
||||
if (line.find("rank=") != std::string::npos) {
|
||||
std::vector<std::string> str_after_split = Common::Split(line.c_str(), '=');
|
||||
Common::Atoi(str_after_split[1].c_str(), &rank_);
|
||||
continue;
|
||||
}
|
||||
std::vector<std::string> str_after_split = Common::Split(line.c_str(), ' ');
|
||||
if (str_after_split.size() != 2) {
|
||||
str_after_split = Common::Split(line.c_str(), ':');
|
||||
if (str_after_split.size() != 2) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (client_ips_.size() >= static_cast<size_t>(num_machines_)) {
|
||||
Log::Warning("machine_list size is larger than the parameter num_machines, ignoring redundant entries");
|
||||
break;
|
||||
}
|
||||
str_after_split[0] = Common::Trim(str_after_split[0]);
|
||||
str_after_split[1] = Common::Trim(str_after_split[1]);
|
||||
client_ips_.push_back(str_after_split[0]);
|
||||
client_ports_.push_back(atoi(str_after_split[1].c_str()));
|
||||
}
|
||||
if (client_ips_.empty()) {
|
||||
Log::Fatal("Cannot find any ip and port.\n"
|
||||
"Please check machine_list_filename or machines parameter");
|
||||
}
|
||||
if (client_ips_.size() != static_cast<size_t>(num_machines_)) {
|
||||
Log::Warning("World size is larger than the machine_list size, change world size to %zu", client_ips_.size());
|
||||
num_machines_ = static_cast<int>(client_ips_.size());
|
||||
}
|
||||
}
|
||||
|
||||
void Linkers::TryBind(int port) {
|
||||
Log::Info("Trying to bind port %d...", port);
|
||||
if (listener_->Bind(port)) {
|
||||
Log::Info("Binding port %d succeeded", port);
|
||||
} else {
|
||||
Log::Fatal("Binding port %d failed", port);
|
||||
}
|
||||
}
|
||||
|
||||
void Linkers::SetLinker(int rank, const TcpSocket& socket) {
|
||||
linkers_[rank].reset(new TcpSocket(socket));
|
||||
// set timeout
|
||||
linkers_[rank]->SetTimeout(socket_timeout_ * 1000 * 60);
|
||||
}
|
||||
|
||||
void Linkers::ListenThread(int incoming_cnt) {
|
||||
Log::Info("Listening...");
|
||||
char buffer[100];
|
||||
int connected_cnt = 0;
|
||||
while (connected_cnt < incoming_cnt) {
|
||||
// accept incoming socket
|
||||
TcpSocket handler = listener_->Accept();
|
||||
if (handler.IsClosed()) {
|
||||
continue;
|
||||
}
|
||||
// receive rank
|
||||
int read_cnt = 0;
|
||||
int size_of_int = static_cast<int>(sizeof(int));
|
||||
while (read_cnt < size_of_int) {
|
||||
int cur_read_cnt = handler.Recv(buffer + read_cnt, size_of_int - read_cnt);
|
||||
read_cnt += cur_read_cnt;
|
||||
}
|
||||
int* ptr_in_rank = reinterpret_cast<int*>(buffer);
|
||||
int in_rank = *ptr_in_rank;
|
||||
if (in_rank < 0 || in_rank >= num_machines_) {
|
||||
Log::Fatal("Invalid rank %d found during initialization of linkers. The world size is %d.", in_rank, num_machines_);
|
||||
}
|
||||
// add new socket
|
||||
SetLinker(in_rank, handler);
|
||||
++connected_cnt;
|
||||
}
|
||||
}
|
||||
|
||||
void Linkers::Construct() {
|
||||
// save ranks that need to connect with
|
||||
std::unordered_map<int, int> need_connect;
|
||||
for (int i = 0; i < num_machines_; ++i) {
|
||||
if (i != rank_) {
|
||||
need_connect[i] = 1;
|
||||
}
|
||||
}
|
||||
int incoming_cnt = 0;
|
||||
for (auto it = need_connect.begin(); it != need_connect.end(); ++it) {
|
||||
int machine_rank = it->first;
|
||||
if (machine_rank < rank_) {
|
||||
++incoming_cnt;
|
||||
}
|
||||
}
|
||||
|
||||
// start listener
|
||||
listener_->SetTimeout(socket_timeout_ * 1000 * 60);
|
||||
listener_->Listen(incoming_cnt);
|
||||
std::thread listen_thread(&Linkers::ListenThread, this, incoming_cnt);
|
||||
const int connect_fail_constant_factor = 20;
|
||||
const int connect_fail_retries_scale_factor = static_cast<int>(num_machines_ / connect_fail_constant_factor);
|
||||
const int connect_fail_retry_cnt = std::max(connect_fail_constant_factor, connect_fail_retries_scale_factor);
|
||||
const int connect_fail_retry_first_delay_interval = 200; // 0.2 s
|
||||
const float connect_fail_retry_delay_factor = 1.3f;
|
||||
// start connect
|
||||
for (auto it = need_connect.begin(); it != need_connect.end(); ++it) {
|
||||
int out_rank = it->first;
|
||||
// let smaller rank connect to larger rank
|
||||
if (out_rank > rank_) {
|
||||
int connect_fail_delay_time = connect_fail_retry_first_delay_interval;
|
||||
for (int i = 0; i < connect_fail_retry_cnt; ++i) {
|
||||
TcpSocket cur_socket;
|
||||
if (cur_socket.Connect(client_ips_[out_rank].c_str(), client_ports_[out_rank])) {
|
||||
// send local rank
|
||||
cur_socket.Send(reinterpret_cast<const char*>(&rank_), sizeof(rank_));
|
||||
SetLinker(out_rank, cur_socket);
|
||||
break;
|
||||
} else {
|
||||
Log::Warning("Connecting to rank %d failed, waiting for %d milliseconds", out_rank, connect_fail_delay_time);
|
||||
cur_socket.Close();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(connect_fail_delay_time));
|
||||
connect_fail_delay_time = static_cast<int>(connect_fail_delay_time * connect_fail_retry_delay_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// wait for listener
|
||||
listen_thread.join();
|
||||
// print connected linkers
|
||||
PrintLinkers();
|
||||
}
|
||||
|
||||
bool Linkers::CheckLinker(int rank) {
|
||||
if (linkers_[rank] == nullptr || linkers_[rank]->IsClosed()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Linkers::PrintLinkers() {
|
||||
for (int i = 0; i < num_machines_; ++i) {
|
||||
if (CheckLinker(i)) {
|
||||
Log::Info("Connected to rank %d", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_SOCKET
|
||||
@@ -0,0 +1,332 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#include <LightGBM/network.h>
|
||||
|
||||
#include <LightGBM/utils/common.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "linkers.h"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
// static member definition
|
||||
THREAD_LOCAL int Network::num_machines_ = 1;
|
||||
THREAD_LOCAL int Network::rank_ = 0;
|
||||
THREAD_LOCAL std::unique_ptr<Linkers> Network::linkers_;
|
||||
THREAD_LOCAL BruckMap Network::bruck_map_;
|
||||
THREAD_LOCAL RecursiveHalvingMap Network::recursive_halving_map_;
|
||||
THREAD_LOCAL std::vector<comm_size_t> Network::block_start_;
|
||||
THREAD_LOCAL std::vector<comm_size_t> Network::block_len_;
|
||||
THREAD_LOCAL comm_size_t Network::buffer_size_ = 0;
|
||||
THREAD_LOCAL std::vector<char> Network::buffer_;
|
||||
THREAD_LOCAL ReduceScatterFunction Network::reduce_scatter_ext_fun_ = nullptr;
|
||||
THREAD_LOCAL AllgatherFunction Network::allgather_ext_fun_ = nullptr;
|
||||
|
||||
|
||||
void Network::Init(Config config) {
|
||||
if (config.num_machines > 1) {
|
||||
linkers_.reset(new Linkers(config));
|
||||
rank_ = linkers_->rank();
|
||||
num_machines_ = linkers_->num_machines();
|
||||
bruck_map_ = linkers_->bruck_map();
|
||||
recursive_halving_map_ = linkers_->recursive_halving_map();
|
||||
block_start_ = std::vector<comm_size_t>(num_machines_);
|
||||
block_len_ = std::vector<comm_size_t>(num_machines_);
|
||||
buffer_size_ = 1024 * 1024;
|
||||
buffer_.resize(buffer_size_);
|
||||
Log::Info("Local rank: %d, total number of machines: %d", rank_, num_machines_);
|
||||
}
|
||||
}
|
||||
|
||||
void Network::Init(int num_machines, int rank,
|
||||
ReduceScatterFunction reduce_scatter_ext_fun, AllgatherFunction allgather_ext_fun) {
|
||||
if (num_machines > 1) {
|
||||
rank_ = rank;
|
||||
num_machines_ = num_machines;
|
||||
block_start_ = std::vector<comm_size_t>(num_machines_);
|
||||
block_len_ = std::vector<comm_size_t>(num_machines_);
|
||||
buffer_size_ = 1024 * 1024;
|
||||
buffer_.resize(buffer_size_);
|
||||
reduce_scatter_ext_fun_ = reduce_scatter_ext_fun;
|
||||
allgather_ext_fun_ = allgather_ext_fun;
|
||||
Log::Info("Local rank: %d, total number of machines: %d", rank_, num_machines_);
|
||||
}
|
||||
}
|
||||
|
||||
void Network::Dispose() {
|
||||
num_machines_ = 1;
|
||||
rank_ = 0;
|
||||
linkers_.reset(new Linkers());
|
||||
reduce_scatter_ext_fun_ = nullptr;
|
||||
allgather_ext_fun_ = nullptr;
|
||||
}
|
||||
|
||||
void Network::Allreduce(char* input, comm_size_t input_size, int type_size, char* output, const ReduceFunction& reducer) {
|
||||
if (num_machines_ <= 1) {
|
||||
Log::Fatal("Please initialize the network interface first");
|
||||
}
|
||||
comm_size_t count = input_size / type_size;
|
||||
// if small package or small count , do it by all gather.(reduce the communication times.)
|
||||
if (count < num_machines_ || input_size < 4096) {
|
||||
AllreduceByAllGather(input, input_size, type_size, output, reducer);
|
||||
return;
|
||||
}
|
||||
// assign the blocks to every rank.
|
||||
comm_size_t step = (count + num_machines_ - 1) / num_machines_;
|
||||
if (step < 1) {
|
||||
step = 1;
|
||||
}
|
||||
block_start_[0] = 0;
|
||||
for (int i = 0; i < num_machines_ - 1; ++i) {
|
||||
block_len_[i] = std::min<comm_size_t>(step * type_size, input_size - block_start_[i]);
|
||||
block_start_[i + 1] = block_start_[i] + block_len_[i];
|
||||
}
|
||||
block_len_[num_machines_ - 1] = input_size - block_start_[num_machines_ - 1];
|
||||
// do reduce scatter
|
||||
ReduceScatter(input, input_size, type_size, block_start_.data(), block_len_.data(), output, input_size, reducer);
|
||||
// do all gather
|
||||
Allgather(output, block_start_.data(), block_len_.data(), output, input_size);
|
||||
}
|
||||
|
||||
void Network::AllreduceByAllGather(char* input, comm_size_t input_size, int type_size, char* output, const ReduceFunction& reducer) {
|
||||
if (num_machines_ <= 1) {
|
||||
Log::Fatal("Please initialize the network interface first");
|
||||
}
|
||||
// assign blocks
|
||||
comm_size_t all_size = input_size * num_machines_;
|
||||
block_start_[0] = 0;
|
||||
block_len_[0] = input_size;
|
||||
for (int i = 1; i < num_machines_; ++i) {
|
||||
block_start_[i] = block_start_[i - 1] + block_len_[i - 1];
|
||||
block_len_[i] = input_size;
|
||||
}
|
||||
// need use buffer here, since size of "output" is smaller than size after all gather
|
||||
if (input_size*num_machines_ > buffer_size_) {
|
||||
buffer_size_ = input_size*num_machines_;
|
||||
buffer_.resize(buffer_size_);
|
||||
}
|
||||
|
||||
Allgather(input, block_start_.data(), block_len_.data(), buffer_.data(), all_size);
|
||||
for (int i = 1; i < num_machines_; ++i) {
|
||||
reducer(buffer_.data() + block_start_[i], buffer_.data() + block_start_[0], type_size, input_size);
|
||||
}
|
||||
// copy back
|
||||
std::memcpy(output, buffer_.data(), input_size);
|
||||
}
|
||||
|
||||
void Network::Allgather(char* input, comm_size_t send_size, char* output) {
|
||||
if (num_machines_ <= 1) {
|
||||
Log::Fatal("Please initialize the network interface first");
|
||||
return;
|
||||
}
|
||||
// assign blocks
|
||||
block_start_[0] = 0;
|
||||
block_len_[0] = send_size;
|
||||
for (int i = 1; i < num_machines_; ++i) {
|
||||
block_start_[i] = block_start_[i - 1] + block_len_[i - 1];
|
||||
block_len_[i] = send_size;
|
||||
}
|
||||
// start all gather
|
||||
Allgather(input, block_start_.data(), block_len_.data(), output, send_size * num_machines_);
|
||||
}
|
||||
|
||||
void Network::Allgather(char* input, const comm_size_t* block_start, const comm_size_t* block_len, char* output, comm_size_t all_size) {
|
||||
if (num_machines_ <= 1) {
|
||||
Log::Fatal("Please initialize the network interface first");
|
||||
}
|
||||
if (allgather_ext_fun_ != nullptr) {
|
||||
return allgather_ext_fun_(input, block_len[rank_], block_start, block_len, num_machines_, output, all_size);
|
||||
}
|
||||
const comm_size_t kRingThreshold = 10 * 1024 * 1024; // 10MB
|
||||
const int kRingNodeThreshold = 64;
|
||||
if (all_size > kRingThreshold && num_machines_ < kRingNodeThreshold) {
|
||||
// when num_machines is small and data is large
|
||||
AllgatherRing(input, block_start, block_len, output, all_size);
|
||||
} else if (recursive_halving_map_.is_power_of_2) {
|
||||
AllgatherRecursiveDoubling(input, block_start, block_len, output, all_size);
|
||||
} else {
|
||||
AllgatherBruck(input, block_start, block_len, output, all_size);
|
||||
}
|
||||
}
|
||||
|
||||
void Network::AllgatherBruck(char* input, const comm_size_t* block_start, const comm_size_t* block_len, char* output, comm_size_t all_size) {
|
||||
comm_size_t write_pos = 0;
|
||||
// use output as receive buffer
|
||||
std::memcpy(output, input, block_len[rank_]);
|
||||
write_pos += block_len[rank_];
|
||||
int accumulated_block = 1;
|
||||
for (int i = 0; i < bruck_map_.k; ++i) {
|
||||
// get current local block size
|
||||
int cur_block_size = std::min(1 << i, num_machines_ - accumulated_block);
|
||||
// get out rank
|
||||
int out_rank = bruck_map_.out_ranks[i];
|
||||
// get in rank
|
||||
int in_rank = bruck_map_.in_ranks[i];
|
||||
// get send information
|
||||
comm_size_t need_send_len = 0;
|
||||
// get recv information
|
||||
comm_size_t need_recv_len = 0;
|
||||
for (int j = 0; j < cur_block_size; ++j) {
|
||||
need_send_len += block_len[(rank_ + j) % num_machines_];
|
||||
need_recv_len += block_len[(rank_ + accumulated_block + j) % num_machines_];
|
||||
}
|
||||
// send and recv at same time
|
||||
linkers_->SendRecv(out_rank, output, need_send_len, in_rank, output + write_pos, need_recv_len);
|
||||
write_pos += need_recv_len;
|
||||
accumulated_block += cur_block_size;
|
||||
}
|
||||
// rotate in-place
|
||||
std::reverse<char*>(output, output + all_size);
|
||||
std::reverse<char*>(output, output + block_start[rank_]);
|
||||
std::reverse<char*>(output + block_start[rank_], output + all_size);
|
||||
}
|
||||
|
||||
void Network::AllgatherRecursiveDoubling(char* input, const comm_size_t* block_start, const comm_size_t* block_len, char* output, comm_size_t) {
|
||||
// use output as receive buffer
|
||||
std::memcpy(output + block_start[rank_], input, block_len[rank_]);
|
||||
for (int i = 0; i < bruck_map_.k; ++i) {
|
||||
// get current local block size
|
||||
int cur_step = 1 << i;
|
||||
const int vgroup = rank_ / cur_step;
|
||||
const int vrank = vgroup * cur_step;
|
||||
int target = rank_ + cur_step;
|
||||
int target_vrank = (vgroup + 1) * cur_step;
|
||||
if (vgroup & 1) {
|
||||
target = rank_ - cur_step;
|
||||
target_vrank = (vgroup - 1) * cur_step;
|
||||
}
|
||||
// get send information
|
||||
comm_size_t need_send_len = 0;
|
||||
// get recv information
|
||||
comm_size_t need_recv_len = 0;
|
||||
for (int j = 0; j < cur_step; ++j) {
|
||||
need_send_len += block_len[(vrank + j)];
|
||||
need_recv_len += block_len[(target_vrank + j)];
|
||||
}
|
||||
// send and recv at same time
|
||||
linkers_->SendRecv(target, output + block_start[vrank], need_send_len,
|
||||
target, output + block_start[target_vrank], need_recv_len);
|
||||
}
|
||||
}
|
||||
|
||||
void Network::AllgatherRing(char* input, const comm_size_t* block_start, const comm_size_t* block_len, char* output, comm_size_t) {
|
||||
// use output as receive buffer
|
||||
std::memcpy(output + block_start[rank_], input, block_len[rank_]);
|
||||
int out_rank = (rank_ + 1) % num_machines_;
|
||||
int in_rank = (rank_ - 1 + num_machines_) % num_machines_;
|
||||
int out_block = rank_;
|
||||
int in_block = in_rank;
|
||||
for (int i = 1; i < num_machines_; ++i) {
|
||||
// send and recv at same time
|
||||
linkers_->SendRecv(out_rank, output + block_start[out_block], block_len[out_block],
|
||||
in_rank, output + block_start[in_block], block_len[in_block]);
|
||||
out_block = (out_block - 1 + num_machines_) % num_machines_;
|
||||
in_block = (in_block - 1 + num_machines_) % num_machines_;
|
||||
}
|
||||
}
|
||||
|
||||
void Network::ReduceScatter(char* input, comm_size_t input_size, int type_size,
|
||||
const comm_size_t* block_start, const comm_size_t* block_len, char* output,
|
||||
comm_size_t output_size, const ReduceFunction& reducer) {
|
||||
if (num_machines_ <= 1) {
|
||||
Log::Fatal("Please initialize the network interface first");
|
||||
}
|
||||
if (reduce_scatter_ext_fun_ != nullptr) {
|
||||
return reduce_scatter_ext_fun_(input, input_size, type_size, block_start, block_len, num_machines_, output, output_size, reducer);
|
||||
}
|
||||
const comm_size_t kRingThreshold = 10 * 1024 * 1024; // 10MB
|
||||
if (recursive_halving_map_.is_power_of_2 || input_size < kRingThreshold) {
|
||||
ReduceScatterRecursiveHalving(input, input_size, type_size, block_start, block_len, output, output_size, reducer);
|
||||
} else {
|
||||
ReduceScatterRing(input, input_size, type_size, block_start, block_len, output, output_size, reducer);
|
||||
}
|
||||
}
|
||||
|
||||
void Network::ReduceScatterRecursiveHalving(char* input, comm_size_t input_size, int type_size,
|
||||
const comm_size_t* block_start, const comm_size_t* block_len, char* output,
|
||||
comm_size_t, const ReduceFunction& reducer) {
|
||||
if (!recursive_halving_map_.is_power_of_2) {
|
||||
if (recursive_halving_map_.type == RecursiveHalvingNodeType::Other) {
|
||||
// send local data to neighbor first
|
||||
linkers_->Send(recursive_halving_map_.neighbor, input, input_size);
|
||||
} else if (recursive_halving_map_.type == RecursiveHalvingNodeType::GroupLeader) {
|
||||
// receive neighbor data first
|
||||
int need_recv_cnt = input_size;
|
||||
linkers_->Recv(recursive_halving_map_.neighbor, output, need_recv_cnt);
|
||||
// reduce
|
||||
reducer(output, input, type_size, input_size);
|
||||
}
|
||||
}
|
||||
if (recursive_halving_map_.type != RecursiveHalvingNodeType::Other) {
|
||||
for (int i = 0; i < recursive_halving_map_.k; ++i) {
|
||||
// get target
|
||||
int target = recursive_halving_map_.ranks[i];
|
||||
comm_size_t send_block_start = recursive_halving_map_.send_block_start[i];
|
||||
comm_size_t recv_block_start = recursive_halving_map_.recv_block_start[i];
|
||||
// get send information
|
||||
comm_size_t send_size = 0;
|
||||
for (int j = 0; j < recursive_halving_map_.send_block_len[i]; ++j) {
|
||||
send_size += block_len[send_block_start + j];
|
||||
}
|
||||
// get recv information
|
||||
comm_size_t need_recv_cnt = 0;
|
||||
for (int j = 0; j < recursive_halving_map_.recv_block_len[i]; ++j) {
|
||||
need_recv_cnt += block_len[recv_block_start + j];
|
||||
}
|
||||
// send and recv at same time
|
||||
linkers_->SendRecv(target, input + block_start[send_block_start], send_size, target, output, need_recv_cnt);
|
||||
// reduce
|
||||
reducer(output, input + block_start[recv_block_start], type_size, need_recv_cnt);
|
||||
}
|
||||
}
|
||||
if (!recursive_halving_map_.is_power_of_2) {
|
||||
if (recursive_halving_map_.type == RecursiveHalvingNodeType::GroupLeader) {
|
||||
// send result to neighbor
|
||||
linkers_->Send(recursive_halving_map_.neighbor,
|
||||
input + block_start[recursive_halving_map_.neighbor],
|
||||
block_len[recursive_halving_map_.neighbor]);
|
||||
} else if (recursive_halving_map_.type == RecursiveHalvingNodeType::Other) {
|
||||
// receive result from neighbor
|
||||
int need_recv_cnt = block_len[rank_];
|
||||
linkers_->Recv(recursive_halving_map_.neighbor, output, need_recv_cnt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// copy result
|
||||
std::memcpy(output, input + block_start[rank_], block_len[rank_]);
|
||||
}
|
||||
|
||||
void Network::ReduceScatterRing(char* input, comm_size_t, int type_size,
|
||||
const comm_size_t* block_start, const comm_size_t* block_len, char* output,
|
||||
comm_size_t, const ReduceFunction& reducer) {
|
||||
const int out_rank = (rank_ + 1) % num_machines_;
|
||||
const int in_rank = (rank_ - 1 + num_machines_) % num_machines_;
|
||||
int out_block = in_rank;
|
||||
int in_block = (in_rank - 1 + num_machines_) % num_machines_;
|
||||
for (int i = 1; i < num_machines_; ++i) {
|
||||
linkers_->SendRecv(out_rank, input + block_start[out_block], block_len[out_block],
|
||||
in_rank, output, block_len[in_block]);
|
||||
reducer(output, input + block_start[in_block], type_size, block_len[in_block]);
|
||||
out_block = (out_block - 1 + num_machines_) % num_machines_;
|
||||
in_block = (in_block - 1 + num_machines_) % num_machines_;
|
||||
}
|
||||
std::memcpy(output, input + block_start[rank_], block_len[rank_]);
|
||||
}
|
||||
|
||||
int Network::rank() {
|
||||
return rank_;
|
||||
}
|
||||
|
||||
int Network::num_machines() {
|
||||
return num_machines_;
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,333 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_NETWORK_SOCKET_WRAPPER_HPP_
|
||||
#define LIGHTGBM_SRC_NETWORK_SOCKET_WRAPPER_HPP_
|
||||
#ifdef USE_SOCKET
|
||||
|
||||
#include <LightGBM/utils/log.h>
|
||||
|
||||
#include <string>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <unordered_set>
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
#pragma comment(lib, "IPHLPAPI.lib")
|
||||
#endif
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
typedef int SOCKET;
|
||||
const int INVALID_SOCKET = -1;
|
||||
#define SOCKET_ERROR -1
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
|
||||
#define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
|
||||
|
||||
// existence of inet_pton is checked in CMakeLists.txt and configure.win, then stored in WIN_HAS_INET_PTON
|
||||
#ifndef WIN_HAS_INET_PTON
|
||||
inline int inet_pton(int af, const char *src, void *dst) {
|
||||
struct sockaddr_storage ss;
|
||||
int size = sizeof(ss);
|
||||
char src_copy[INET6_ADDRSTRLEN + 1];
|
||||
|
||||
ZeroMemory(&ss, sizeof(ss));
|
||||
/* stupid non-const API */
|
||||
strncpy(src_copy, src, INET6_ADDRSTRLEN + 1);
|
||||
src_copy[INET6_ADDRSTRLEN] = 0;
|
||||
|
||||
if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) {
|
||||
switch (af) {
|
||||
case AF_INET:
|
||||
*(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr;
|
||||
return 1;
|
||||
case AF_INET6:
|
||||
*(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace SocketConfig {
|
||||
const int kSocketBufferSize = 100 * 1000;
|
||||
const int kMaxReceiveSize = 100 * 1000;
|
||||
const int kNoDelay = 1;
|
||||
}
|
||||
|
||||
class TcpSocket {
|
||||
public:
|
||||
TcpSocket() {
|
||||
sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (sockfd_ == INVALID_SOCKET) {
|
||||
Log::Fatal("Socket construction error");
|
||||
return;
|
||||
}
|
||||
ConfigSocket();
|
||||
}
|
||||
|
||||
explicit TcpSocket(SOCKET socket) {
|
||||
sockfd_ = socket;
|
||||
if (sockfd_ == INVALID_SOCKET) {
|
||||
Log::Fatal("Passed socket error");
|
||||
return;
|
||||
}
|
||||
ConfigSocket();
|
||||
}
|
||||
|
||||
TcpSocket(const TcpSocket &object) {
|
||||
sockfd_ = object.sockfd_;
|
||||
ConfigSocket();
|
||||
}
|
||||
~TcpSocket() {
|
||||
}
|
||||
inline void SetTimeout(int timeout_ms) {
|
||||
#if defined(_WIN32)
|
||||
DWORD timeout = static_cast<DWORD>(timeout_ms);
|
||||
setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout));
|
||||
#else
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
#endif
|
||||
}
|
||||
inline void ConfigSocket() {
|
||||
if (sockfd_ == INVALID_SOCKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (setsockopt(sockfd_, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char*>(&SocketConfig::kSocketBufferSize), sizeof(SocketConfig::kSocketBufferSize)) != 0) {
|
||||
Log::Warning("Set SO_RCVBUF failed, please increase your net.core.rmem_max to 100k at least");
|
||||
}
|
||||
|
||||
if (setsockopt(sockfd_, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&SocketConfig::kSocketBufferSize), sizeof(SocketConfig::kSocketBufferSize)) != 0) {
|
||||
Log::Warning("Set SO_SNDBUF failed, please increase your net.core.wmem_max to 100k at least");
|
||||
}
|
||||
if (setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char*>(&SocketConfig::kNoDelay), sizeof(SocketConfig::kNoDelay)) != 0) {
|
||||
Log::Warning("Set TCP_NODELAY failed");
|
||||
}
|
||||
}
|
||||
|
||||
inline static void Startup() {
|
||||
#if defined(_WIN32)
|
||||
WSADATA wsa_data;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) == -1) {
|
||||
Log::Fatal("Socket error: WSAStartup error");
|
||||
}
|
||||
if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2) {
|
||||
WSACleanup();
|
||||
Log::Fatal("Socket error: Winsock.dll version error");
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
inline static void Finalize() {
|
||||
#if defined(_WIN32)
|
||||
WSACleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline static int GetLastError() {
|
||||
#if defined(_WIN32)
|
||||
return WSAGetLastError();
|
||||
#else
|
||||
return errno;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if defined(_WIN32)
|
||||
inline static std::unordered_set<std::string> GetLocalIpList() {
|
||||
std::unordered_set<std::string> ip_list;
|
||||
char buffer[512];
|
||||
// get hostName
|
||||
if (gethostname(buffer, sizeof(buffer)) == SOCKET_ERROR) {
|
||||
Log::Fatal("Error code %d, when getting local host name", WSAGetLastError());
|
||||
}
|
||||
// push local ip
|
||||
PIP_ADAPTER_INFO pAdapterInfo;
|
||||
PIP_ADAPTER_INFO pAdapter = NULL;
|
||||
DWORD dwRetVal = 0;
|
||||
ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
|
||||
pAdapterInfo = reinterpret_cast<IP_ADAPTER_INFO *>(MALLOC(sizeof(IP_ADAPTER_INFO)));
|
||||
if (pAdapterInfo == NULL) {
|
||||
Log::Fatal("GetAdaptersinfo error: allocating memory");
|
||||
}
|
||||
// Make an initial call to GetAdaptersInfo to get
|
||||
// the necessary size into the ulOutBufLen variable
|
||||
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
|
||||
FREE(pAdapterInfo);
|
||||
pAdapterInfo = reinterpret_cast<IP_ADAPTER_INFO *>(MALLOC(ulOutBufLen));
|
||||
if (pAdapterInfo == NULL) {
|
||||
Log::Fatal("GetAdaptersinfo error: allocating memory");
|
||||
}
|
||||
}
|
||||
if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) == NO_ERROR) {
|
||||
pAdapter = pAdapterInfo;
|
||||
while (pAdapter) {
|
||||
ip_list.insert(pAdapter->IpAddressList.IpAddress.String);
|
||||
pAdapter = pAdapter->Next;
|
||||
}
|
||||
} else {
|
||||
Log::Fatal("GetAdaptersinfo error: code %d", dwRetVal);
|
||||
}
|
||||
if (pAdapterInfo)
|
||||
FREE(pAdapterInfo);
|
||||
return ip_list;
|
||||
}
|
||||
#else
|
||||
inline static std::unordered_set<std::string> GetLocalIpList() {
|
||||
std::unordered_set<std::string> ip_list;
|
||||
struct ifaddrs * ifAddrStruct = NULL;
|
||||
struct ifaddrs * ifa = NULL;
|
||||
void * tmpAddrPtr = NULL;
|
||||
|
||||
getifaddrs(&ifAddrStruct);
|
||||
|
||||
for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr) {
|
||||
continue;
|
||||
}
|
||||
if (ifa->ifa_addr->sa_family == AF_INET) {
|
||||
// NOLINTNEXTLINE
|
||||
tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
|
||||
char addressBuffer[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
|
||||
ip_list.insert(std::string(addressBuffer));
|
||||
}
|
||||
}
|
||||
if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
|
||||
return ip_list;
|
||||
}
|
||||
#endif
|
||||
inline static sockaddr_in GetAddress(const char* url, int port) {
|
||||
sockaddr_in addr = sockaddr_in();
|
||||
std::memset(&addr, 0, sizeof(sockaddr_in));
|
||||
inet_pton(AF_INET, url, &addr.sin_addr);
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<u_short>(port));
|
||||
return addr;
|
||||
}
|
||||
|
||||
inline bool Bind(int port) {
|
||||
sockaddr_in local_addr = GetAddress("0.0.0.0", port);
|
||||
if (bind(sockfd_, reinterpret_cast<const sockaddr*>(&local_addr), sizeof(sockaddr_in)) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool Connect(const char *url, int port) {
|
||||
sockaddr_in server_addr = GetAddress(url, port);
|
||||
if (connect(sockfd_, reinterpret_cast<const sockaddr*>(&server_addr), sizeof(sockaddr_in)) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void Listen(int backlog = 128) {
|
||||
listen(sockfd_, backlog);
|
||||
}
|
||||
|
||||
inline TcpSocket Accept() {
|
||||
SOCKET newfd = accept(sockfd_, NULL, NULL);
|
||||
if (newfd == INVALID_SOCKET) {
|
||||
int err_code = GetLastError();
|
||||
#if defined(_WIN32)
|
||||
Log::Fatal("Socket accept error (code: %d)", err_code);
|
||||
#else
|
||||
Log::Fatal("Socket accept error, %s (code: %d)", std::strerror(err_code), err_code);
|
||||
#endif
|
||||
}
|
||||
return TcpSocket(newfd);
|
||||
}
|
||||
|
||||
inline int Send(const char *buf_, int len, int flag = 0) {
|
||||
int cur_cnt = send(sockfd_, buf_, len, flag);
|
||||
if (cur_cnt == SOCKET_ERROR) {
|
||||
int err_code = GetLastError();
|
||||
#if defined(_WIN32)
|
||||
Log::Fatal("Socket send error (code: %d)", err_code);
|
||||
#else
|
||||
Log::Fatal("Socket send error, %s (code: %d)", std::strerror(err_code), err_code);
|
||||
#endif
|
||||
}
|
||||
return cur_cnt;
|
||||
}
|
||||
|
||||
inline int Recv(char *buf_, int len, int flags = 0) {
|
||||
int cur_cnt = recv(sockfd_, buf_ , len , flags);
|
||||
if (cur_cnt == SOCKET_ERROR) {
|
||||
int err_code = GetLastError();
|
||||
#if defined(_WIN32)
|
||||
Log::Fatal("Socket recv error (code: %d)", err_code);
|
||||
#else
|
||||
Log::Fatal("Socket recv error, %s (code: %d)", std::strerror(err_code), err_code);
|
||||
#endif
|
||||
}
|
||||
return cur_cnt;
|
||||
}
|
||||
|
||||
inline bool IsClosed() {
|
||||
return sockfd_ == INVALID_SOCKET;
|
||||
}
|
||||
|
||||
inline void Close() {
|
||||
if (!IsClosed()) {
|
||||
#if defined(_WIN32)
|
||||
closesocket(sockfd_);
|
||||
#else
|
||||
close(sockfd_);
|
||||
#endif
|
||||
sockfd_ = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SOCKET sockfd_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // USE_SOCKET
|
||||
#endif // LIGHTGBM_SRC_NETWORK_SOCKET_WRAPPER_HPP_
|
||||
@@ -0,0 +1,217 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_BINARY_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_BINARY_OBJECTIVE_HPP_
|
||||
|
||||
#include <LightGBM/network.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief Objective function for binary classification
|
||||
*/
|
||||
class BinaryLogloss: public ObjectiveFunction {
|
||||
public:
|
||||
explicit BinaryLogloss(const Config& config,
|
||||
std::function<bool(label_t)> is_pos = nullptr)
|
||||
: deterministic_(config.deterministic) {
|
||||
sigmoid_ = static_cast<double>(config.sigmoid);
|
||||
if (sigmoid_ <= 0.0) {
|
||||
Log::Fatal("Sigmoid parameter %f should be greater than zero", sigmoid_);
|
||||
}
|
||||
is_unbalance_ = config.is_unbalance;
|
||||
scale_pos_weight_ = static_cast<double>(config.scale_pos_weight);
|
||||
if (is_unbalance_ && std::fabs(scale_pos_weight_ - 1.0f) > 1e-6) {
|
||||
Log::Fatal("Cannot set is_unbalance and scale_pos_weight at the same time");
|
||||
}
|
||||
is_pos_ = is_pos;
|
||||
if (is_pos_ == nullptr) {
|
||||
is_pos_ = [](label_t label) { return label > 0; };
|
||||
}
|
||||
}
|
||||
|
||||
explicit BinaryLogloss(const std::vector<std::string>& strs)
|
||||
: deterministic_(false) {
|
||||
sigmoid_ = -1;
|
||||
for (auto str : strs) {
|
||||
auto tokens = Common::Split(str.c_str(), ':');
|
||||
if (tokens.size() == 2) {
|
||||
if (tokens[0] == std::string("sigmoid")) {
|
||||
Common::Atof(tokens[1].c_str(), &sigmoid_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sigmoid_ <= 0.0) {
|
||||
Log::Fatal("Sigmoid parameter %f should be greater than zero", sigmoid_);
|
||||
}
|
||||
}
|
||||
|
||||
~BinaryLogloss() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
data_size_t cnt_positive = 0;
|
||||
data_size_t cnt_negative = 0;
|
||||
// count for positive and negative samples
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:cnt_positive, cnt_negative)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
if (is_pos_(label_[i])) {
|
||||
++cnt_positive;
|
||||
} else {
|
||||
++cnt_negative;
|
||||
}
|
||||
}
|
||||
num_pos_data_ = cnt_positive;
|
||||
if (Network::num_machines() > 1) {
|
||||
cnt_positive = Network::GlobalSyncUpBySum(cnt_positive);
|
||||
cnt_negative = Network::GlobalSyncUpBySum(cnt_negative);
|
||||
}
|
||||
need_train_ = true;
|
||||
if (cnt_negative == 0 || cnt_positive == 0) {
|
||||
Log::Warning("Contains only one class");
|
||||
// not need to boost.
|
||||
need_train_ = false;
|
||||
}
|
||||
Log::Info("Number of positive: %d, number of negative: %d", cnt_positive, cnt_negative);
|
||||
// use -1 for negative class, and 1 for positive class
|
||||
label_val_[0] = -1;
|
||||
label_val_[1] = 1;
|
||||
// weight for label
|
||||
label_weights_[0] = 1.0f;
|
||||
label_weights_[1] = 1.0f;
|
||||
// if using unbalance, change the labels weight
|
||||
if (is_unbalance_ && cnt_positive > 0 && cnt_negative > 0) {
|
||||
if (cnt_positive > cnt_negative) {
|
||||
label_weights_[1] = 1.0f;
|
||||
label_weights_[0] = static_cast<double>(cnt_positive) / cnt_negative;
|
||||
} else {
|
||||
label_weights_[1] = static_cast<double>(cnt_negative) / cnt_positive;
|
||||
label_weights_[0] = 1.0f;
|
||||
}
|
||||
}
|
||||
label_weights_[1] *= scale_pos_weight_;
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
|
||||
if (!need_train_) {
|
||||
return;
|
||||
}
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// get label and label weights
|
||||
const int is_pos = is_pos_(label_[i]);
|
||||
const int label = label_val_[is_pos];
|
||||
const double label_weight = label_weights_[is_pos];
|
||||
// calculate gradients and hessians
|
||||
const double response = -label * sigmoid_ / (1.0f + std::exp(label * sigmoid_ * score[i]));
|
||||
const double abs_response = fabs(response);
|
||||
gradients[i] = static_cast<score_t>(response * label_weight);
|
||||
hessians[i] = static_cast<score_t>(abs_response * (sigmoid_ - abs_response) * label_weight);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
// get label and label weights
|
||||
const int is_pos = is_pos_(label_[i]);
|
||||
const int label = label_val_[is_pos];
|
||||
const double label_weight = label_weights_[is_pos];
|
||||
// calculate gradients and hessians
|
||||
const double response = -label * sigmoid_ / (1.0f + std::exp(label * sigmoid_ * score[i]));
|
||||
const double abs_response = fabs(response);
|
||||
gradients[i] = static_cast<score_t>(response * label_weight * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>(abs_response * (sigmoid_ - abs_response) * label_weight * weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// implement custom average to boost from (if enabled among options)
|
||||
double BoostFromScore(int) const override {
|
||||
double suml = 0.0f;
|
||||
double sumw = 0.0f;
|
||||
if (weights_ != nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += is_pos_(label_[i]) * weights_[i];
|
||||
sumw += weights_[i];
|
||||
}
|
||||
} else {
|
||||
sumw = static_cast<double>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += is_pos_(label_[i]);
|
||||
}
|
||||
}
|
||||
if (Network::num_machines() > 1) {
|
||||
suml = Network::GlobalSyncUpBySum(suml);
|
||||
sumw = Network::GlobalSyncUpBySum(sumw);
|
||||
}
|
||||
double pavg = suml / sumw;
|
||||
pavg = std::min(pavg, 1.0 - kEpsilon);
|
||||
pavg = std::max<double>(pavg, kEpsilon);
|
||||
double initscore = std::log(pavg / (1.0f - pavg)) / sigmoid_;
|
||||
Log::Info("[%s:%s]: pavg=%f -> initscore=%f", GetName(), __func__, pavg, initscore);
|
||||
return initscore;
|
||||
}
|
||||
|
||||
bool ClassNeedTrain(int /*class_id*/) const override {
|
||||
return need_train_;
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "binary";
|
||||
}
|
||||
|
||||
void ConvertOutput(const double* input, double* output) const override {
|
||||
output[0] = 1.0f / (1.0f + std::exp(-sigmoid_ * input[0]));
|
||||
}
|
||||
|
||||
std::string ToString() const override {
|
||||
std::stringstream str_buf;
|
||||
str_buf << GetName() << " ";
|
||||
str_buf << "sigmoid:" << sigmoid_;
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
bool SkipEmptyClass() const override { return true; }
|
||||
|
||||
bool NeedAccuratePrediction() const override { return false; }
|
||||
|
||||
data_size_t NumPositiveData() const override { return num_pos_data_; }
|
||||
|
||||
protected:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Number of positive samples */
|
||||
data_size_t num_pos_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief True if using unbalance training */
|
||||
bool is_unbalance_;
|
||||
/*! \brief Sigmoid parameter */
|
||||
double sigmoid_;
|
||||
/*! \brief Values for positive and negative labels */
|
||||
int label_val_[2];
|
||||
/*! \brief Weights for positive and negative labels */
|
||||
double label_weights_[2];
|
||||
/*! \brief Weights for data */
|
||||
const label_t* weights_;
|
||||
double scale_pos_weight_;
|
||||
std::function<bool(label_t)> is_pos_;
|
||||
bool need_train_;
|
||||
const bool deterministic_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_BINARY_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,58 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_binary_objective.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDABinaryLogloss::CUDABinaryLogloss(const Config& config):
|
||||
CUDAObjectiveInterface<BinaryLogloss>(config), ova_class_id_(-1) {
|
||||
cuda_label_ = nullptr;
|
||||
cuda_weights_ = nullptr;
|
||||
}
|
||||
|
||||
CUDABinaryLogloss::CUDABinaryLogloss(const Config& config, const int ova_class_id):
|
||||
CUDAObjectiveInterface<BinaryLogloss>(config), ova_class_id_(ova_class_id) {
|
||||
is_pos_ = [ova_class_id](label_t label) { return static_cast<int>(label) == ova_class_id; };
|
||||
}
|
||||
|
||||
CUDABinaryLogloss::CUDABinaryLogloss(const std::vector<std::string>& strs): CUDAObjectiveInterface<BinaryLogloss>(strs) {}
|
||||
|
||||
CUDABinaryLogloss::~CUDABinaryLogloss() {}
|
||||
|
||||
void CUDABinaryLogloss::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDAObjectiveInterface<BinaryLogloss>::Init(metadata, num_data);
|
||||
if (ova_class_id_ == -1) {
|
||||
cuda_label_ = metadata.cuda_metadata()->cuda_label();
|
||||
cuda_ova_label_.Clear();
|
||||
} else {
|
||||
cuda_ova_label_.Resize(static_cast<size_t>(num_data));
|
||||
CopyFromHostToCUDADevice<label_t>(cuda_ova_label_.RawData(), metadata.cuda_metadata()->cuda_label(), static_cast<size_t>(num_data), __FILE__, __LINE__);
|
||||
LaunchResetOVACUDALabelKernel();
|
||||
cuda_label_ = cuda_ova_label_.RawData();
|
||||
}
|
||||
cuda_weights_ = metadata.cuda_metadata()->cuda_weights();
|
||||
cuda_boost_from_score_.Resize(1);
|
||||
SetCUDAMemory<double>(cuda_boost_from_score_.RawData(), 0, 1, __FILE__, __LINE__);
|
||||
cuda_sum_weights_.Resize(1);
|
||||
SetCUDAMemory<double>(cuda_sum_weights_.RawData(), 0, 1, __FILE__, __LINE__);
|
||||
if (label_weights_[0] != 1.0f || label_weights_[1] != 1.0f) {
|
||||
cuda_label_weights_.Resize(2);
|
||||
CopyFromHostToCUDADevice<double>(cuda_label_weights_.RawData(), label_weights_, 2, __FILE__, __LINE__);
|
||||
} else {
|
||||
cuda_label_weights_.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,225 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
* Modifications Copyright(C) 2023 Advanced Micro Devices, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_binary_objective.hpp"
|
||||
|
||||
#include <LightGBM/cuda/cuda_rocm_interop.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void BoostFromScoreKernel_1_BinaryLogloss(const label_t* cuda_labels, const data_size_t num_data, double* out_cuda_sum_labels,
|
||||
double* out_cuda_sum_weights, const label_t* cuda_weights) {
|
||||
__shared__ double shared_buffer[WARPSIZE];
|
||||
const uint32_t mask = 0xffffffff;
|
||||
const uint32_t warpLane = threadIdx.x % warpSize;
|
||||
const uint32_t warpID = threadIdx.x / warpSize;
|
||||
const uint32_t num_warp = blockDim.x / warpSize;
|
||||
const data_size_t index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
double label_value = 0.0;
|
||||
double weight_value = 0.0;
|
||||
if (index < num_data) {
|
||||
if (USE_WEIGHT) {
|
||||
const label_t cuda_label = cuda_labels[index];
|
||||
const double sample_weight = cuda_weights[index];
|
||||
const label_t label = cuda_label > 0 ? 1 : 0;
|
||||
label_value = label * sample_weight;
|
||||
weight_value = sample_weight;
|
||||
} else {
|
||||
const label_t cuda_label = cuda_labels[index];
|
||||
label_value = cuda_label > 0 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
for (uint32_t offset = warpSize / 2; offset >= 1; offset >>= 1) {
|
||||
label_value += __shfl_down_sync(mask, label_value, offset);
|
||||
}
|
||||
if (warpLane == 0) {
|
||||
shared_buffer[warpID] = label_value;
|
||||
}
|
||||
__syncthreads();
|
||||
if (warpID == 0) {
|
||||
label_value = (warpLane < num_warp ? shared_buffer[warpLane] : 0);
|
||||
for (uint32_t offset = warpSize / 2; offset >= 1; offset >>= 1) {
|
||||
label_value += __shfl_down_sync(mask, label_value, offset);
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
if (USE_WEIGHT) {
|
||||
for (uint32_t offset = warpSize / 2; offset >= 1; offset >>= 1) {
|
||||
weight_value += __shfl_down_sync(mask, weight_value, offset);
|
||||
}
|
||||
if (warpLane == 0) {
|
||||
shared_buffer[warpID] = weight_value;
|
||||
}
|
||||
__syncthreads();
|
||||
if (warpID == 0) {
|
||||
weight_value = (warpLane < num_warp ? shared_buffer[warpLane] : 0);
|
||||
for (uint32_t offset = warpSize / 2; offset >= 1; offset >>= 1) {
|
||||
weight_value += __shfl_down_sync(mask, weight_value, offset);
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
if (threadIdx.x == 0) {
|
||||
atomicAdd_system(out_cuda_sum_labels, label_value);
|
||||
if (USE_WEIGHT) {
|
||||
atomicAdd_system(out_cuda_sum_weights, weight_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void BoostFromScoreKernel_2_BinaryLogloss(double* out_cuda_sum_labels, double* out_cuda_sum_weights,
|
||||
const data_size_t num_data, const double sigmoid) {
|
||||
const double suml = *out_cuda_sum_labels;
|
||||
const double sumw = USE_WEIGHT ? *out_cuda_sum_weights : static_cast<double>(num_data);
|
||||
double pavg = suml / sumw;
|
||||
pavg = min(pavg, 1.0 - kEpsilon);
|
||||
pavg = max(pavg, kEpsilon);
|
||||
const double init_score = log(pavg / (1.0f - pavg)) / sigmoid;
|
||||
*out_cuda_sum_weights = pavg;
|
||||
*out_cuda_sum_labels = init_score;
|
||||
}
|
||||
|
||||
double CUDABinaryLogloss::LaunchCalcInitScoreKernel(const int /*class_id*/) const {
|
||||
const int num_blocks = (num_data_ + CALC_INIT_SCORE_BLOCK_SIZE_BINARY - 1) / CALC_INIT_SCORE_BLOCK_SIZE_BINARY;
|
||||
SetCUDAMemory<double>(cuda_boost_from_score_.RawData(), 0, 1, __FILE__, __LINE__);
|
||||
if (cuda_weights_ == nullptr) {
|
||||
BoostFromScoreKernel_1_BinaryLogloss<false><<<num_blocks, CALC_INIT_SCORE_BLOCK_SIZE_BINARY>>>
|
||||
(cuda_label_, num_data_, cuda_boost_from_score_.RawData(), cuda_sum_weights_.RawData(), cuda_weights_);
|
||||
} else {
|
||||
BoostFromScoreKernel_1_BinaryLogloss<true><<<num_blocks, CALC_INIT_SCORE_BLOCK_SIZE_BINARY>>>
|
||||
(cuda_label_, num_data_, cuda_boost_from_score_.RawData(), cuda_sum_weights_.RawData(), cuda_weights_);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
if (cuda_weights_ == nullptr) {
|
||||
if (nccl_communicator_ == nullptr) {
|
||||
BoostFromScoreKernel_2_BinaryLogloss<false><<<1, 1>>>(cuda_boost_from_score_.RawData(), cuda_sum_weights_.RawData(), num_data_, sigmoid_);
|
||||
} else {
|
||||
NCCLAllReduce<double>(cuda_boost_from_score_.RawData(), cuda_boost_from_score_.RawData(), 1, ncclFloat64, ncclSum, nccl_communicator_);
|
||||
const data_size_t global_num_data = NCCLAllReduce<data_size_t>(num_data_, ncclInt32, ncclSum, nccl_communicator_);
|
||||
BoostFromScoreKernel_2_BinaryLogloss<false><<<1, 1>>>(cuda_boost_from_score_.RawData(), cuda_sum_weights_.RawData(), global_num_data, sigmoid_);
|
||||
}
|
||||
} else {
|
||||
if (nccl_communicator_ == nullptr) {
|
||||
BoostFromScoreKernel_2_BinaryLogloss<true><<<1, 1>>>(cuda_boost_from_score_.RawData(), cuda_sum_weights_.RawData(), num_data_, sigmoid_);
|
||||
} else {
|
||||
NCCLAllReduce<double>(cuda_boost_from_score_.RawData(), cuda_boost_from_score_.RawData(), 1, ncclFloat64, ncclSum, nccl_communicator_);
|
||||
NCCLAllReduce<double>(cuda_sum_weights_.RawData(), cuda_sum_weights_.RawData(), 1, ncclFloat64, ncclSum, nccl_communicator_);
|
||||
BoostFromScoreKernel_2_BinaryLogloss<true><<<1, 1>>>(cuda_boost_from_score_.RawData(), cuda_sum_weights_.RawData(), num_data_, sigmoid_);
|
||||
}
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
double boost_from_score = 0.0f;
|
||||
CopyFromCUDADeviceToHost<double>(&boost_from_score, cuda_boost_from_score_.RawData(), 1, __FILE__, __LINE__);
|
||||
double pavg = 0.0f;
|
||||
CopyFromCUDADeviceToHost<double>(&pavg, cuda_sum_weights_.RawData(), 1, __FILE__, __LINE__);
|
||||
// for some test cases in test_utilities.py which check the log output
|
||||
Log::Info("[%s:%s]: pavg=%f -> initscore=%f", GetName(), "BoostFromScore", pavg, boost_from_score);
|
||||
return boost_from_score;
|
||||
}
|
||||
|
||||
template <bool USE_LABEL_WEIGHT, bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_BinaryLogloss(const double* cuda_scores, const label_t* cuda_labels,
|
||||
const double* cuda_label_weights, const label_t* cuda_weights,
|
||||
const double sigmoid, const data_size_t num_data,
|
||||
score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockDim.x * blockIdx.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
const label_t cuda_label = static_cast<int>(cuda_labels[data_index]);
|
||||
const int label = cuda_label > 0 ? 1 : -1;
|
||||
const double response = -label * sigmoid / (1.0f + exp(label * sigmoid * cuda_scores[data_index]));
|
||||
const double abs_response = fabs(response);
|
||||
if (!USE_WEIGHT) {
|
||||
if (USE_LABEL_WEIGHT) {
|
||||
const double label_weight = cuda_label_weights[label];
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(response * label_weight);
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(abs_response * (sigmoid - abs_response) * label_weight);
|
||||
} else {
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(response);
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(abs_response * (sigmoid - abs_response));
|
||||
}
|
||||
} else {
|
||||
const double sample_weight = cuda_weights[data_index];
|
||||
if (USE_LABEL_WEIGHT) {
|
||||
const double label_weight = cuda_label_weights[label];
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(response * label_weight * sample_weight);
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(abs_response * (sigmoid - abs_response) * label_weight * sample_weight);
|
||||
} else {
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(response * sample_weight);
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(abs_response * (sigmoid - abs_response) * sample_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define GetGradientsKernel_BinaryLogloss_ARGS \
|
||||
scores, \
|
||||
cuda_label_, \
|
||||
cuda_label_weights_.RawData(), \
|
||||
cuda_weights_, \
|
||||
sigmoid_, \
|
||||
num_data_, \
|
||||
gradients, \
|
||||
hessians
|
||||
|
||||
void CUDABinaryLogloss::LaunchGetGradientsKernel(const double* scores, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_BINARY - 1) / GET_GRADIENTS_BLOCK_SIZE_BINARY;
|
||||
if (cuda_label_weights_.Size() == 0) {
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_BinaryLogloss<false, false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_BINARY>>>(GetGradientsKernel_BinaryLogloss_ARGS);
|
||||
} else {
|
||||
GetGradientsKernel_BinaryLogloss<false, true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_BINARY>>>(GetGradientsKernel_BinaryLogloss_ARGS);
|
||||
}
|
||||
} else {
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_BinaryLogloss<true, false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_BINARY>>>(GetGradientsKernel_BinaryLogloss_ARGS);
|
||||
} else {
|
||||
GetGradientsKernel_BinaryLogloss<true, true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_BINARY>>>(GetGradientsKernel_BinaryLogloss_ARGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef GetGradientsKernel_BinaryLogloss_ARGS
|
||||
|
||||
__global__ void ConvertOutputCUDAKernel_BinaryLogloss(const double sigmoid, const data_size_t num_data, const double* input, double* output) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
output[data_index] = 1.0f / (1.0f + exp(-sigmoid * input[data_index]));
|
||||
}
|
||||
}
|
||||
|
||||
const double* CUDABinaryLogloss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const {
|
||||
const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_BINARY - 1) / GET_GRADIENTS_BLOCK_SIZE_BINARY;
|
||||
ConvertOutputCUDAKernel_BinaryLogloss<<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_BINARY>>>(sigmoid_, num_data, input, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
__global__ void ResetOVACUDALabelKernel(
|
||||
const int ova_class_id,
|
||||
const data_size_t num_data,
|
||||
label_t* cuda_label) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (data_index < num_data) {
|
||||
const int int_label = static_cast<int>(cuda_label[data_index]);
|
||||
cuda_label[data_index] = (int_label == ova_class_id ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void CUDABinaryLogloss::LaunchResetOVACUDALabelKernel() const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_BINARY - 1) / GET_GRADIENTS_BLOCK_SIZE_BINARY;
|
||||
ResetOVACUDALabelKernel<<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_BINARY>>>(ova_class_id_, num_data_, cuda_ova_label_.RawData());
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,64 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_BINARY_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_BINARY_OBJECTIVE_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#define GET_GRADIENTS_BLOCK_SIZE_BINARY (1024)
|
||||
#define CALC_INIT_SCORE_BLOCK_SIZE_BINARY (1024)
|
||||
|
||||
#include <LightGBM/cuda/cuda_objective_function.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../binary_objective.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class CUDABinaryLogloss : public CUDAObjectiveInterface<BinaryLogloss> {
|
||||
public:
|
||||
explicit CUDABinaryLogloss(const Config& config);
|
||||
|
||||
explicit CUDABinaryLogloss(const Config& config, const int ova_class_id);
|
||||
|
||||
explicit CUDABinaryLogloss(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDABinaryLogloss();
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
bool NeedConvertOutputCUDA() const override { return true; }
|
||||
|
||||
private:
|
||||
void LaunchGetGradientsKernel(const double* scores, score_t* gradients, score_t* hessians) const override;
|
||||
|
||||
double LaunchCalcInitScoreKernel(const int class_id) const override;
|
||||
|
||||
const double* LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const override;
|
||||
|
||||
void LaunchResetOVACUDALabelKernel() const;
|
||||
|
||||
// CUDA memory, held by other objects
|
||||
const label_t* cuda_label_;
|
||||
CUDAVector<label_t> cuda_ova_label_;
|
||||
const label_t* cuda_weights_;
|
||||
|
||||
// CUDA memory, held by this object
|
||||
CUDAVector<double> cuda_boost_from_score_;
|
||||
CUDAVector<double> cuda_sum_weights_;
|
||||
CUDAVector<double> cuda_label_weights_;
|
||||
const int ova_class_id_ = -1;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_BINARY_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,63 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_multiclass_objective.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDAMulticlassSoftmax::CUDAMulticlassSoftmax(const Config& config): CUDAObjectiveInterface<MulticlassSoftmax>(config) {}
|
||||
|
||||
CUDAMulticlassSoftmax::CUDAMulticlassSoftmax(const std::vector<std::string>& strs): CUDAObjectiveInterface<MulticlassSoftmax>(strs) {}
|
||||
|
||||
CUDAMulticlassSoftmax::~CUDAMulticlassSoftmax() {}
|
||||
|
||||
void CUDAMulticlassSoftmax::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDAObjectiveInterface<MulticlassSoftmax>::Init(metadata, num_data);
|
||||
cuda_softmax_buffer_.Resize(static_cast<size_t>(num_data) * static_cast<size_t>(num_class_));
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
CUDAMulticlassOVA::CUDAMulticlassOVA(const Config& config): CUDAObjectiveInterface<MulticlassOVA>(config) {
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
cuda_binary_loss_.emplace_back(new CUDABinaryLogloss(config, i));
|
||||
}
|
||||
}
|
||||
|
||||
CUDAMulticlassOVA::CUDAMulticlassOVA(const std::vector<std::string>& strs): CUDAObjectiveInterface<MulticlassOVA>(strs) {}
|
||||
|
||||
CUDAMulticlassOVA::~CUDAMulticlassOVA() {}
|
||||
|
||||
void CUDAMulticlassOVA::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
MulticlassOVA::Init(metadata, num_data);
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
cuda_binary_loss_[i]->Init(metadata, num_data);
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAMulticlassOVA::GetGradients(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
int64_t offset = static_cast<int64_t>(num_data_) * i;
|
||||
cuda_binary_loss_[i]->GetGradients(score + offset, gradients + offset, hessians + offset);
|
||||
}
|
||||
}
|
||||
|
||||
const double* CUDAMulticlassOVA::ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const {
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
cuda_binary_loss_[i]->ConvertOutputCUDA(num_data, input + i * num_data, output + i * num_data);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,109 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "cuda_multiclass_objective.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
__device__ void SoftmaxCUDA(double* softmax_buffer, int len) {
|
||||
double wmax = softmax_buffer[0];
|
||||
for (int i = 1; i < len; ++i) {
|
||||
wmax = max(softmax_buffer[i], wmax);
|
||||
}
|
||||
double wsum = 0.0f;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
softmax_buffer[i] = exp(softmax_buffer[i] - wmax);
|
||||
wsum += softmax_buffer[i];
|
||||
}
|
||||
for (int i = 0; i < len; ++i) {
|
||||
softmax_buffer[i] /= static_cast<double>(wsum);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_MulticlassSoftmax(
|
||||
const double* cuda_scores, const label_t* cuda_labels, const label_t* cuda_weights,
|
||||
const double factor, const int num_class, const data_size_t num_data,
|
||||
double* cuda_softmax_buffer, score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (data_index < num_data) {
|
||||
const data_size_t offset = data_index * num_class;
|
||||
double* softmax_result = cuda_softmax_buffer + offset;
|
||||
for (int k = 0; k < num_class; ++k) {
|
||||
softmax_result[k] = cuda_scores[k * num_data + data_index];
|
||||
}
|
||||
SoftmaxCUDA(softmax_result, num_class);
|
||||
if (!USE_WEIGHT) {
|
||||
for (int k = 0; k < num_class; ++k) {
|
||||
const double p = softmax_result[k];
|
||||
size_t idx = static_cast<size_t>(num_data) * k + data_index;
|
||||
if (static_cast<int>(cuda_labels[data_index]) == k) {
|
||||
cuda_out_gradients[idx] = static_cast<score_t>(p - 1.0f);
|
||||
} else {
|
||||
cuda_out_gradients[idx] = static_cast<score_t>(p);
|
||||
}
|
||||
cuda_out_hessians[idx] = static_cast<score_t>(factor * p * (1.0f - p));
|
||||
}
|
||||
} else {
|
||||
for (int k = 0; k < num_class; ++k) {
|
||||
const double p = softmax_result[k];
|
||||
const double weight = cuda_weights[data_index];
|
||||
size_t idx = static_cast<size_t>(num_data) * k + data_index;
|
||||
if (static_cast<int>(cuda_labels[data_index]) == k) {
|
||||
cuda_out_gradients[idx] = static_cast<score_t>((p - 1.0f) * weight);
|
||||
} else {
|
||||
cuda_out_gradients[idx] = static_cast<score_t>(p * weight);
|
||||
}
|
||||
cuda_out_hessians[idx] = static_cast<score_t>((factor * p * (1.0f - p)) * weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAMulticlassSoftmax::LaunchGetGradientsKernel(const double* scores, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_MULTICLASS - 1) / GET_GRADIENTS_BLOCK_SIZE_MULTICLASS;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_MulticlassSoftmax<false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_MULTICLASS>>>(
|
||||
scores, cuda_labels_, cuda_weights_, factor_, num_class_, num_data_,
|
||||
cuda_softmax_buffer_.RawData(), gradients, hessians);
|
||||
} else {
|
||||
GetGradientsKernel_MulticlassSoftmax<true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_MULTICLASS>>>(
|
||||
scores, cuda_labels_, cuda_weights_, factor_, num_class_, num_data_,
|
||||
cuda_softmax_buffer_.RawData(), gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void ConvertOutputCUDAKernel_MulticlassSoftmax(
|
||||
const int num_class, const data_size_t num_data, const double* input, double* cuda_softmax_buffer, double* output) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
if (data_index < num_data) {
|
||||
const data_size_t offset = data_index * num_class;
|
||||
double* cuda_softmax_buffer_ptr = cuda_softmax_buffer + offset;
|
||||
for (int class_index = 0; class_index < num_class; ++class_index) {
|
||||
cuda_softmax_buffer_ptr[class_index] = input[class_index * num_data + data_index];
|
||||
}
|
||||
SoftmaxCUDA(cuda_softmax_buffer_ptr, num_class);
|
||||
for (int class_index = 0; class_index < num_class; ++class_index) {
|
||||
output[class_index * num_data + data_index] = cuda_softmax_buffer_ptr[class_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const double* CUDAMulticlassSoftmax::LaunchConvertOutputCUDAKernel(
|
||||
const data_size_t num_data, const double* input, double* output) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_MULTICLASS - 1) / GET_GRADIENTS_BLOCK_SIZE_MULTICLASS;
|
||||
ConvertOutputCUDAKernel_MulticlassSoftmax<<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_MULTICLASS>>>(
|
||||
num_class_, num_data, input, cuda_softmax_buffer_.RawData(), output);
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_MULTICLASS_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_MULTICLASS_OBJECTIVE_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_objective_function.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_binary_objective.hpp"
|
||||
|
||||
#include "../multiclass_objective.hpp"
|
||||
|
||||
#define GET_GRADIENTS_BLOCK_SIZE_MULTICLASS (1024)
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class CUDAMulticlassSoftmax: public CUDAObjectiveInterface<MulticlassSoftmax> {
|
||||
public:
|
||||
explicit CUDAMulticlassSoftmax(const Config& config);
|
||||
|
||||
explicit CUDAMulticlassSoftmax(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDAMulticlassSoftmax();
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
private:
|
||||
void LaunchGetGradientsKernel(const double* scores, score_t* gradients, score_t* hessians) const;
|
||||
|
||||
const double* LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const;
|
||||
|
||||
// CUDA memory, held by this object
|
||||
CUDAVector<double> cuda_softmax_buffer_;
|
||||
};
|
||||
|
||||
|
||||
class CUDAMulticlassOVA: public CUDAObjectiveInterface<MulticlassOVA> {
|
||||
public:
|
||||
explicit CUDAMulticlassOVA(const Config& config);
|
||||
|
||||
explicit CUDAMulticlassOVA(const std::vector<std::string>& strs);
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
|
||||
const double* ConvertOutputCUDA(const data_size_t num_data, const double* input, double* output) const override;
|
||||
|
||||
double BoostFromScore(int class_id) const override {
|
||||
return cuda_binary_loss_[class_id]->BoostFromScore(0);
|
||||
}
|
||||
|
||||
bool ClassNeedTrain(int class_id) const override {
|
||||
return cuda_binary_loss_[class_id]->ClassNeedTrain(0);
|
||||
}
|
||||
|
||||
~CUDAMulticlassOVA();
|
||||
|
||||
bool IsCUDAObjective() const override { return true; }
|
||||
|
||||
private:
|
||||
void LaunchGetGradientsKernel(const double* /*scores*/, score_t* /*gradients*/, score_t* /*hessians*/) const {}
|
||||
|
||||
std::vector<std::unique_ptr<CUDABinaryLogloss>> cuda_binary_loss_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_MULTICLASS_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,68 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_rank_objective.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDALambdarankNDCG::CUDALambdarankNDCG(const Config& config): CUDALambdaRankObjectiveInterface<LambdarankNDCG>(config) {}
|
||||
|
||||
CUDALambdarankNDCG::CUDALambdarankNDCG(const std::vector<std::string>& strs): CUDALambdaRankObjectiveInterface<LambdarankNDCG>(strs) {}
|
||||
|
||||
CUDALambdarankNDCG::~CUDALambdarankNDCG() {}
|
||||
|
||||
void CUDALambdarankNDCG::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDALambdaRankObjectiveInterface<LambdarankNDCG>::Init(metadata, num_data);
|
||||
cuda_inverse_max_dcgs_.Resize(this->inverse_max_dcgs_.size());
|
||||
CopyFromHostToCUDADevice(cuda_inverse_max_dcgs_.RawData(), this->inverse_max_dcgs_.data(), this->inverse_max_dcgs_.size(), __FILE__, __LINE__);
|
||||
cuda_label_gain_.Resize(this->label_gain_.size());
|
||||
CopyFromHostToCUDADevice(cuda_label_gain_.RawData(), this->label_gain_.data(), this->label_gain_.size(), __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
CUDARankXENDCG::CUDARankXENDCG(const Config& config): CUDALambdaRankObjectiveInterface<RankXENDCG>(config) {}
|
||||
|
||||
CUDARankXENDCG::CUDARankXENDCG(const std::vector<std::string>& strs): CUDALambdaRankObjectiveInterface<RankXENDCG>(strs) {}
|
||||
|
||||
CUDARankXENDCG::~CUDARankXENDCG() {}
|
||||
|
||||
void CUDARankXENDCG::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDALambdaRankObjectiveInterface<RankXENDCG>::Init(metadata, num_data);
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
rands_.emplace_back(seed_ + i);
|
||||
}
|
||||
item_rands_.resize(num_data, 0.0f);
|
||||
cuda_item_rands_.Resize(static_cast<size_t>(num_data));
|
||||
if (max_items_in_query_aligned_ >= 2048) {
|
||||
cuda_params_buffer_.Resize(static_cast<size_t>(num_data_));
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARankXENDCG::GenerateItemRands() const {
|
||||
const int num_threads = OMP_NUM_THREADS();
|
||||
OMP_INIT_EX();
|
||||
#pragma omp parallel for schedule(static) num_threads(num_threads)
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
OMP_LOOP_EX_BEGIN();
|
||||
const data_size_t start = query_boundaries_[i];
|
||||
const data_size_t end = query_boundaries_[i + 1];
|
||||
for (data_size_t j = start; j < end; ++j) {
|
||||
item_rands_[j] = rands_[i].NextFloat();
|
||||
}
|
||||
OMP_LOOP_EX_END();
|
||||
}
|
||||
OMP_THROW_EX();
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,666 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
* Modifications Copyright(C) 2023 Advanced Micro Devices, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_rank_objective.hpp"
|
||||
|
||||
#include <LightGBM/cuda/cuda_algorithms.hpp>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <bool MAX_ITEM_GREATER_THAN_1024, data_size_t NUM_RANK_LABEL>
|
||||
__global__ void GetGradientsKernel_LambdarankNDCG(const double* cuda_scores, const label_t* cuda_labels, const data_size_t num_data,
|
||||
const data_size_t num_queries, const data_size_t* cuda_query_boundaries, const double* cuda_inverse_max_dcgs,
|
||||
const bool norm, const double sigmoid, const int truncation_level, const double* cuda_label_gain, const data_size_t num_rank_label,
|
||||
score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
__shared__ score_t shared_scores[MAX_ITEM_GREATER_THAN_1024 ? 2048 : 1024];
|
||||
__shared__ uint16_t shared_indices[MAX_ITEM_GREATER_THAN_1024 ? 2048 : 1024];
|
||||
__shared__ score_t shared_lambdas[MAX_ITEM_GREATER_THAN_1024 ? 2048 : 1024];
|
||||
__shared__ score_t shared_hessians[MAX_ITEM_GREATER_THAN_1024 ? 2048 : 1024];
|
||||
__shared__ double shared_label_gain[NUM_RANK_LABEL > 1024 ? 1 : NUM_RANK_LABEL];
|
||||
const double* label_gain_ptr = nullptr;
|
||||
if (NUM_RANK_LABEL <= 1024) {
|
||||
for (uint32_t i = threadIdx.x; i < num_rank_label; i += blockDim.x) {
|
||||
shared_label_gain[i] = cuda_label_gain[i];
|
||||
}
|
||||
__syncthreads();
|
||||
label_gain_ptr = shared_label_gain;
|
||||
} else {
|
||||
label_gain_ptr = cuda_label_gain;
|
||||
}
|
||||
const data_size_t query_index_start = static_cast<data_size_t>(blockIdx.x) * NUM_QUERY_PER_BLOCK;
|
||||
const data_size_t query_index_end = min(query_index_start + NUM_QUERY_PER_BLOCK, num_queries);
|
||||
for (data_size_t query_index = query_index_start; query_index < query_index_end; ++query_index) {
|
||||
const double inverse_max_dcg = cuda_inverse_max_dcgs[query_index];
|
||||
const data_size_t query_start = cuda_query_boundaries[query_index];
|
||||
const data_size_t query_end = cuda_query_boundaries[query_index + 1];
|
||||
const data_size_t query_item_count = query_end - query_start;
|
||||
const double* cuda_scores_pointer = cuda_scores + query_start;
|
||||
score_t* cuda_out_gradients_pointer = cuda_out_gradients + query_start;
|
||||
score_t* cuda_out_hessians_pointer = cuda_out_hessians + query_start;
|
||||
const label_t* cuda_label_pointer = cuda_labels + query_start;
|
||||
if (threadIdx.x < query_item_count) {
|
||||
shared_scores[threadIdx.x] = cuda_scores_pointer[threadIdx.x];
|
||||
shared_indices[threadIdx.x] = static_cast<uint16_t>(threadIdx.x);
|
||||
shared_lambdas[threadIdx.x] = 0.0f;
|
||||
shared_hessians[threadIdx.x] = 0.0f;
|
||||
} else {
|
||||
shared_scores[threadIdx.x] = kMinScore;
|
||||
shared_indices[threadIdx.x] = static_cast<uint16_t>(threadIdx.x);
|
||||
}
|
||||
if (MAX_ITEM_GREATER_THAN_1024) {
|
||||
if (query_item_count > 1024) {
|
||||
const unsigned int threadIdx_x_plus_1024 = threadIdx.x + 1024;
|
||||
if (threadIdx_x_plus_1024 < query_item_count) {
|
||||
shared_scores[threadIdx_x_plus_1024] = cuda_scores_pointer[threadIdx_x_plus_1024];
|
||||
shared_indices[threadIdx_x_plus_1024] = static_cast<uint16_t>(threadIdx_x_plus_1024);
|
||||
shared_lambdas[threadIdx_x_plus_1024] = 0.0f;
|
||||
shared_hessians[threadIdx_x_plus_1024] = 0.0f;
|
||||
} else {
|
||||
shared_scores[threadIdx_x_plus_1024] = kMinScore;
|
||||
shared_indices[threadIdx_x_plus_1024] = static_cast<uint16_t>(threadIdx_x_plus_1024);
|
||||
}
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
if (MAX_ITEM_GREATER_THAN_1024) {
|
||||
if (query_item_count > 1024) {
|
||||
BitonicArgSort_2048<score_t, uint16_t, false>(shared_scores, shared_indices);
|
||||
} else {
|
||||
BitonicArgSort_1024<score_t, uint16_t, false>(shared_scores, shared_indices, static_cast<uint16_t>(query_item_count));
|
||||
}
|
||||
} else {
|
||||
BitonicArgSort_1024<score_t, uint16_t, false>(shared_scores, shared_indices, static_cast<uint16_t>(query_item_count));
|
||||
}
|
||||
__syncthreads();
|
||||
// get best and worst score
|
||||
const double best_score = shared_scores[shared_indices[0]];
|
||||
data_size_t worst_idx = query_item_count - 1;
|
||||
if (worst_idx > 0 && shared_scores[shared_indices[worst_idx]] == kMinScore) {
|
||||
worst_idx -= 1;
|
||||
}
|
||||
const double worst_score = shared_scores[shared_indices[worst_idx]];
|
||||
__shared__ double sum_lambdas;
|
||||
if (threadIdx.x == 0) {
|
||||
sum_lambdas = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
// start accumulate lambdas by pairs that contain at least one document above truncation level
|
||||
const data_size_t num_items_i = min(query_item_count - 1, truncation_level);
|
||||
const data_size_t num_j_per_i = query_item_count - 1;
|
||||
const data_size_t s = num_j_per_i - num_items_i + 1;
|
||||
const data_size_t num_pairs = (num_j_per_i + s) * num_items_i / 2;
|
||||
double thread_sum_lambdas = 0.0f;
|
||||
for (data_size_t pair_index = static_cast<data_size_t>(threadIdx.x); pair_index < num_pairs; pair_index += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double square = 2 * static_cast<double>(pair_index) + s * s - s;
|
||||
const double sqrt_result = floor(sqrt(square));
|
||||
const data_size_t row_index = static_cast<data_size_t>(floor(sqrt(square - sqrt_result)) + 1 - s);
|
||||
const data_size_t i = num_items_i - 1 - row_index;
|
||||
const data_size_t j = num_j_per_i - (pair_index - (2 * s + row_index - 1) * row_index / 2);
|
||||
if (cuda_label_pointer[shared_indices[i]] != cuda_label_pointer[shared_indices[j]] && shared_scores[shared_indices[j]] != kMinScore) {
|
||||
data_size_t high_rank, low_rank;
|
||||
if (cuda_label_pointer[shared_indices[i]] > cuda_label_pointer[shared_indices[j]]) {
|
||||
high_rank = i;
|
||||
low_rank = j;
|
||||
} else {
|
||||
high_rank = j;
|
||||
low_rank = i;
|
||||
}
|
||||
const data_size_t high = shared_indices[high_rank];
|
||||
const int high_label = static_cast<int>(cuda_label_pointer[high]);
|
||||
const double high_score = shared_scores[high];
|
||||
const double high_label_gain = label_gain_ptr[high_label];
|
||||
const double high_discount = log2(2.0f + high_rank);
|
||||
const data_size_t low = shared_indices[low_rank];
|
||||
const int low_label = static_cast<int>(cuda_label_pointer[low]);
|
||||
const double low_score = shared_scores[low];
|
||||
const double low_label_gain = label_gain_ptr[low_label];
|
||||
const double low_discount = log2(2.0f + low_rank);
|
||||
|
||||
const double delta_score = high_score - low_score;
|
||||
|
||||
// get dcg gap
|
||||
const double dcg_gap = high_label_gain - low_label_gain;
|
||||
// get discount of this pair
|
||||
const double paired_discount = fabs(high_discount - low_discount);
|
||||
// get delta NDCG
|
||||
double delta_pair_NDCG = dcg_gap * paired_discount * inverse_max_dcg;
|
||||
// regular the delta_pair_NDCG by score distance
|
||||
if (norm && best_score != worst_score) {
|
||||
delta_pair_NDCG /= (0.01f + fabs(delta_score));
|
||||
}
|
||||
// calculate lambda for this pair
|
||||
double p_lambda = 1.0f / (1.0f + exp(sigmoid * delta_score));
|
||||
double p_hessian = p_lambda * (1.0f - p_lambda);
|
||||
// update
|
||||
p_lambda *= -sigmoid * delta_pair_NDCG;
|
||||
p_hessian *= sigmoid * sigmoid * delta_pair_NDCG;
|
||||
atomicAdd_block(shared_lambdas + low, -static_cast<score_t>(p_lambda));
|
||||
atomicAdd_block(shared_hessians + low, static_cast<score_t>(p_hessian));
|
||||
atomicAdd_block(shared_lambdas + high, static_cast<score_t>(p_lambda));
|
||||
atomicAdd_block(shared_hessians + high, static_cast<score_t>(p_hessian));
|
||||
// lambda is negative, so use minus to accumulate
|
||||
thread_sum_lambdas -= 2 * p_lambda;
|
||||
}
|
||||
}
|
||||
atomicAdd_block(&sum_lambdas, thread_sum_lambdas);
|
||||
__syncthreads();
|
||||
if (norm && sum_lambdas > 0) {
|
||||
const double norm_factor = log2(1 + sum_lambdas) / sum_lambdas;
|
||||
if (threadIdx.x < static_cast<unsigned int>(query_item_count)) {
|
||||
cuda_out_gradients_pointer[threadIdx.x] = static_cast<score_t>(shared_lambdas[threadIdx.x] * norm_factor);
|
||||
cuda_out_hessians_pointer[threadIdx.x] = static_cast<score_t>(shared_hessians[threadIdx.x] * norm_factor);
|
||||
}
|
||||
if (MAX_ITEM_GREATER_THAN_1024) {
|
||||
if (query_item_count > 1024) {
|
||||
const unsigned int threadIdx_x_plus_1024 = threadIdx.x + 1024;
|
||||
if (threadIdx_x_plus_1024 < static_cast<unsigned int>(query_item_count)) {
|
||||
cuda_out_gradients_pointer[threadIdx_x_plus_1024] = static_cast<score_t>(shared_lambdas[threadIdx_x_plus_1024] * norm_factor);
|
||||
cuda_out_hessians_pointer[threadIdx_x_plus_1024] = static_cast<score_t>(shared_hessians[threadIdx_x_plus_1024] * norm_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (threadIdx.x < static_cast<unsigned int>(query_item_count)) {
|
||||
cuda_out_gradients_pointer[threadIdx.x] = static_cast<score_t>(shared_lambdas[threadIdx.x]);
|
||||
cuda_out_hessians_pointer[threadIdx.x] = static_cast<score_t>(shared_hessians[threadIdx.x]);
|
||||
}
|
||||
if (MAX_ITEM_GREATER_THAN_1024) {
|
||||
if (query_item_count > 1024) {
|
||||
const unsigned int threadIdx_x_plus_1024 = threadIdx.x + 1024;
|
||||
if (threadIdx_x_plus_1024 < static_cast<unsigned int>(query_item_count)) {
|
||||
cuda_out_gradients_pointer[threadIdx_x_plus_1024] = static_cast<score_t>(shared_lambdas[threadIdx_x_plus_1024]);
|
||||
cuda_out_hessians_pointer[threadIdx_x_plus_1024] = static_cast<score_t>(shared_hessians[threadIdx_x_plus_1024]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
template <data_size_t NUM_RANK_LABEL>
|
||||
__global__ void GetGradientsKernel_LambdarankNDCG_Sorted(
|
||||
const double* cuda_scores, const int* cuda_item_indices_buffer, const label_t* cuda_labels, const data_size_t num_data,
|
||||
const data_size_t num_queries, const data_size_t* cuda_query_boundaries, const double* cuda_inverse_max_dcgs,
|
||||
const bool norm, const double sigmoid, const int truncation_level, const double* cuda_label_gain, const data_size_t num_rank_label,
|
||||
score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
__shared__ double shared_label_gain[NUM_RANK_LABEL > 1024 ? 1 : NUM_RANK_LABEL];
|
||||
const double* label_gain_ptr = nullptr;
|
||||
if (NUM_RANK_LABEL <= 1024) {
|
||||
for (uint32_t i = threadIdx.x; i < static_cast<uint32_t>(num_rank_label); i += blockDim.x) {
|
||||
shared_label_gain[i] = cuda_label_gain[i];
|
||||
}
|
||||
__syncthreads();
|
||||
label_gain_ptr = shared_label_gain;
|
||||
} else {
|
||||
label_gain_ptr = cuda_label_gain;
|
||||
}
|
||||
const data_size_t query_index_start = static_cast<data_size_t>(blockIdx.x) * NUM_QUERY_PER_BLOCK;
|
||||
const data_size_t query_index_end = min(query_index_start + NUM_QUERY_PER_BLOCK, num_queries);
|
||||
for (data_size_t query_index = query_index_start; query_index < query_index_end; ++query_index) {
|
||||
const double inverse_max_dcg = cuda_inverse_max_dcgs[query_index];
|
||||
const data_size_t query_start = cuda_query_boundaries[query_index];
|
||||
const data_size_t query_end = cuda_query_boundaries[query_index + 1];
|
||||
const data_size_t query_item_count = query_end - query_start;
|
||||
const double* cuda_scores_pointer = cuda_scores + query_start;
|
||||
const int* cuda_item_indices_buffer_pointer = cuda_item_indices_buffer + query_start;
|
||||
score_t* cuda_out_gradients_pointer = cuda_out_gradients + query_start;
|
||||
score_t* cuda_out_hessians_pointer = cuda_out_hessians + query_start;
|
||||
const label_t* cuda_label_pointer = cuda_labels + query_start;
|
||||
// get best and worst score
|
||||
const double best_score = cuda_scores_pointer[cuda_item_indices_buffer_pointer[0]];
|
||||
data_size_t worst_idx = query_item_count - 1;
|
||||
if (worst_idx > 0 && cuda_scores_pointer[cuda_item_indices_buffer_pointer[worst_idx]] == kMinScore) {
|
||||
worst_idx -= 1;
|
||||
}
|
||||
const double worst_score = cuda_scores_pointer[cuda_item_indices_buffer_pointer[worst_idx]];
|
||||
__shared__ double sum_lambdas;
|
||||
if (threadIdx.x == 0) {
|
||||
sum_lambdas = 0.0f;
|
||||
}
|
||||
for (int item_index = static_cast<int>(threadIdx.x); item_index < query_item_count; item_index += static_cast<int>(blockDim.x)) {
|
||||
cuda_out_gradients_pointer[item_index] = 0.0f;
|
||||
cuda_out_hessians_pointer[item_index] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
// start accumulate lambdas by pairs that contain at least one document above truncation level
|
||||
const data_size_t num_items_i = min(query_item_count - 1, truncation_level);
|
||||
const data_size_t num_j_per_i = query_item_count - 1;
|
||||
const data_size_t s = num_j_per_i - num_items_i + 1;
|
||||
const data_size_t num_pairs = (num_j_per_i + s) * num_items_i / 2;
|
||||
double thread_sum_lambdas = 0.0f;
|
||||
for (data_size_t pair_index = static_cast<data_size_t>(threadIdx.x); pair_index < num_pairs; pair_index += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double square = 2 * static_cast<double>(pair_index) + s * s - s;
|
||||
const double sqrt_result = floor(sqrt(square));
|
||||
const data_size_t row_index = static_cast<data_size_t>(floor(sqrt(square - sqrt_result)) + 1 - s);
|
||||
const data_size_t i = num_items_i - 1 - row_index;
|
||||
const data_size_t j = num_j_per_i - (pair_index - (2 * s + row_index - 1) * row_index / 2);
|
||||
if (j > i) {
|
||||
// skip pairs with the same labels
|
||||
if (cuda_label_pointer[cuda_item_indices_buffer_pointer[i]] != cuda_label_pointer[cuda_item_indices_buffer_pointer[j]] && cuda_scores_pointer[cuda_item_indices_buffer_pointer[j]] != kMinScore) {
|
||||
data_size_t high_rank, low_rank;
|
||||
if (cuda_label_pointer[cuda_item_indices_buffer_pointer[i]] > cuda_label_pointer[cuda_item_indices_buffer_pointer[j]]) {
|
||||
high_rank = i;
|
||||
low_rank = j;
|
||||
} else {
|
||||
high_rank = j;
|
||||
low_rank = i;
|
||||
}
|
||||
const data_size_t high = cuda_item_indices_buffer_pointer[high_rank];
|
||||
const int high_label = static_cast<int>(cuda_label_pointer[high]);
|
||||
const double high_score = cuda_scores_pointer[high];
|
||||
const double high_label_gain = label_gain_ptr[high_label];
|
||||
const double high_discount = log2(2.0f + high_rank);
|
||||
const data_size_t low = cuda_item_indices_buffer_pointer[low_rank];
|
||||
const int low_label = static_cast<int>(cuda_label_pointer[low]);
|
||||
const double low_score = cuda_scores_pointer[low];
|
||||
const double low_label_gain = label_gain_ptr[low_label];
|
||||
const double low_discount = log2(2.0f + low_rank);
|
||||
|
||||
const double delta_score = high_score - low_score;
|
||||
|
||||
// get dcg gap
|
||||
const double dcg_gap = high_label_gain - low_label_gain;
|
||||
// get discount of this pair
|
||||
const double paired_discount = fabs(high_discount - low_discount);
|
||||
// get delta NDCG
|
||||
double delta_pair_NDCG = dcg_gap * paired_discount * inverse_max_dcg;
|
||||
// regular the delta_pair_NDCG by score distance
|
||||
if (norm && best_score != worst_score) {
|
||||
delta_pair_NDCG /= (0.01f + fabs(delta_score));
|
||||
}
|
||||
// calculate lambda for this pair
|
||||
double p_lambda = 1.0f / (1.0f + exp(sigmoid * delta_score));
|
||||
double p_hessian = p_lambda * (1.0f - p_lambda);
|
||||
// update
|
||||
p_lambda *= -sigmoid * delta_pair_NDCG;
|
||||
p_hessian *= sigmoid * sigmoid * delta_pair_NDCG;
|
||||
atomicAdd_block(cuda_out_gradients_pointer + low, -static_cast<score_t>(p_lambda));
|
||||
atomicAdd_block(cuda_out_hessians_pointer + low, static_cast<score_t>(p_hessian));
|
||||
atomicAdd_block(cuda_out_gradients_pointer + high, static_cast<score_t>(p_lambda));
|
||||
atomicAdd_block(cuda_out_hessians_pointer + high, static_cast<score_t>(p_hessian));
|
||||
// lambda is negative, so use minus to accumulate
|
||||
thread_sum_lambdas -= 2 * p_lambda;
|
||||
}
|
||||
}
|
||||
}
|
||||
atomicAdd_block(&sum_lambdas, thread_sum_lambdas);
|
||||
__syncthreads();
|
||||
if (norm && sum_lambdas > 0) {
|
||||
const double norm_factor = log2(1 + sum_lambdas) / sum_lambdas;
|
||||
for (int item_index = static_cast<int>(threadIdx.x); item_index < query_item_count; item_index += static_cast<int>(blockDim.x)) {
|
||||
cuda_out_gradients_pointer[item_index] *= norm_factor;
|
||||
cuda_out_hessians_pointer[item_index] *= norm_factor;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
void CUDALambdarankNDCG::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_queries_ + NUM_QUERY_PER_BLOCK - 1) / NUM_QUERY_PER_BLOCK;
|
||||
const data_size_t num_rank_label = static_cast<int>(label_gain_.size());
|
||||
const int device_index = GetCUDADevice(__FILE__, __LINE__);
|
||||
cudaDeviceProp device_prop;
|
||||
CUDASUCCESS_OR_FATAL(cudaGetDeviceProperties(&device_prop, device_index));
|
||||
|
||||
#define GetGradientsKernel_LambdarankNDCG_ARGS \
|
||||
score, cuda_labels_, num_data_, \
|
||||
num_queries_, cuda_query_boundaries_, cuda_inverse_max_dcgs_.RawData(), \
|
||||
norm_, sigmoid_, truncation_level_, cuda_label_gain_.RawData(), num_rank_label, \
|
||||
gradients, hessians
|
||||
|
||||
#define GetGradientsKernel_LambdarankNDCG_Sorted_ARGS \
|
||||
score, cuda_item_indices_buffer_.RawData(), cuda_labels_, num_data_, \
|
||||
num_queries_, cuda_query_boundaries_, cuda_inverse_max_dcgs_.RawData(), \
|
||||
norm_, sigmoid_, truncation_level_, cuda_label_gain_.RawData(), num_rank_label, \
|
||||
gradients, hessians
|
||||
|
||||
if (max_items_in_query_aligned_ <= 1024) {
|
||||
if (num_rank_label <= 32 && device_prop.warpSize == 32) {
|
||||
GetGradientsKernel_LambdarankNDCG<false, 32><<<num_blocks, max_items_in_query_aligned_>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 64) {
|
||||
GetGradientsKernel_LambdarankNDCG<false, 64><<<num_blocks, max_items_in_query_aligned_>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 128) {
|
||||
GetGradientsKernel_LambdarankNDCG<false, 128><<<num_blocks, max_items_in_query_aligned_>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 256) {
|
||||
GetGradientsKernel_LambdarankNDCG<false, 256><<<num_blocks, max_items_in_query_aligned_>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 512) {
|
||||
GetGradientsKernel_LambdarankNDCG<false, 512><<<num_blocks, max_items_in_query_aligned_>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 1024) {
|
||||
GetGradientsKernel_LambdarankNDCG<false, 1024><<<num_blocks, max_items_in_query_aligned_>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else {
|
||||
GetGradientsKernel_LambdarankNDCG<false, 2048><<<num_blocks, max_items_in_query_aligned_>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
}
|
||||
} else if (max_items_in_query_aligned_ <= 2048) {
|
||||
if (num_rank_label <= 32 && device_prop.warpSize == 32) {
|
||||
GetGradientsKernel_LambdarankNDCG<true, 32><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 64) {
|
||||
GetGradientsKernel_LambdarankNDCG<true, 64><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 128) {
|
||||
GetGradientsKernel_LambdarankNDCG<true, 128><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 256) {
|
||||
GetGradientsKernel_LambdarankNDCG<true, 256><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 512) {
|
||||
GetGradientsKernel_LambdarankNDCG<true, 512><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else if (num_rank_label <= 1024) {
|
||||
GetGradientsKernel_LambdarankNDCG<true, 1024><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
} else {
|
||||
GetGradientsKernel_LambdarankNDCG<true, 2048><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_ARGS);
|
||||
}
|
||||
} else {
|
||||
BitonicArgSortItemsGlobal(score, num_queries_, cuda_query_boundaries_, cuda_item_indices_buffer_.RawData());
|
||||
if (num_rank_label <= 32 && device_prop.warpSize == 32) {
|
||||
GetGradientsKernel_LambdarankNDCG_Sorted<32><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_Sorted_ARGS);
|
||||
} else if (num_rank_label <= 64) {
|
||||
GetGradientsKernel_LambdarankNDCG_Sorted<64><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_Sorted_ARGS);
|
||||
} else if (num_rank_label <= 128) {
|
||||
GetGradientsKernel_LambdarankNDCG_Sorted<128><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_Sorted_ARGS);
|
||||
} else if (num_rank_label <= 256) {
|
||||
GetGradientsKernel_LambdarankNDCG_Sorted<256><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_Sorted_ARGS);
|
||||
} else if (num_rank_label <= 512) {
|
||||
GetGradientsKernel_LambdarankNDCG_Sorted<512><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_Sorted_ARGS);
|
||||
} else if (num_rank_label <= 1024) {
|
||||
GetGradientsKernel_LambdarankNDCG_Sorted<1024><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_Sorted_ARGS);
|
||||
} else {
|
||||
GetGradientsKernel_LambdarankNDCG_Sorted<2048><<<num_blocks, 1024>>>(GetGradientsKernel_LambdarankNDCG_Sorted_ARGS);
|
||||
}
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
|
||||
#undef GetGradientsKernel_LambdarankNDCG_ARGS
|
||||
#undef GetGradientsKernel_LambdarankNDCG_Sorted_ARGS
|
||||
}
|
||||
|
||||
|
||||
__device__ __forceinline__ double CUDAPhi(const label_t l, double g) {
|
||||
return pow(2.0f, static_cast<double>(l)) - g;
|
||||
}
|
||||
|
||||
template <size_t SHARED_MEMORY_SIZE>
|
||||
__global__ void GetGradientsKernel_RankXENDCG_SharedMemory(
|
||||
const double* cuda_scores,
|
||||
const label_t* cuda_labels,
|
||||
const double* cuda_item_rands,
|
||||
const data_size_t num_data,
|
||||
const data_size_t num_queries,
|
||||
const data_size_t* cuda_query_boundaries,
|
||||
score_t* cuda_out_gradients,
|
||||
score_t* cuda_out_hessians) {
|
||||
const data_size_t query_index_start = static_cast<data_size_t>(blockIdx.x) * NUM_QUERY_PER_BLOCK;
|
||||
const data_size_t query_index_end = min(query_index_start + NUM_QUERY_PER_BLOCK, num_queries);
|
||||
for (data_size_t query_index = query_index_start; query_index < query_index_end; ++query_index) {
|
||||
const data_size_t item_index_start = cuda_query_boundaries[query_index];
|
||||
const data_size_t item_index_end = cuda_query_boundaries[query_index + 1];
|
||||
const data_size_t query_item_count = item_index_end - item_index_start;
|
||||
score_t* cuda_out_gradients_pointer = cuda_out_gradients + item_index_start;
|
||||
score_t* cuda_out_hessians_pointer = cuda_out_hessians + item_index_start;
|
||||
const label_t* cuda_labels_pointer = cuda_labels + item_index_start;
|
||||
const double* cuda_scores_pointer = cuda_scores + item_index_start;
|
||||
const double* cuda_item_rands_pointer = cuda_item_rands + item_index_start;
|
||||
const data_size_t block_reduce_size = query_item_count >= 1024 ? 1024 : query_item_count;
|
||||
__shared__ double shared_rho[SHARED_MEMORY_SIZE];
|
||||
// assert that warpSize == 32
|
||||
__shared__ double shared_buffer[1024 / WARPSIZE];
|
||||
__shared__ double shared_params[SHARED_MEMORY_SIZE];
|
||||
__shared__ score_t shared_lambdas[SHARED_MEMORY_SIZE];
|
||||
__shared__ double reduce_result;
|
||||
if (query_item_count <= 1) {
|
||||
for (data_size_t i = 0; i <= query_item_count; ++i) {
|
||||
cuda_out_gradients_pointer[i] = 0.0f;
|
||||
cuda_out_hessians_pointer[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
} else {
|
||||
// compute softmax
|
||||
double thread_reduce_result = kMinScore;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double rho = cuda_scores_pointer[i];
|
||||
shared_rho[i] = rho;
|
||||
if (rho > thread_reduce_result) {
|
||||
thread_reduce_result = rho;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
thread_reduce_result = ShuffleReduceMax<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double exp_value = exp(shared_rho[i] - reduce_result);
|
||||
shared_rho[i] = exp_value;
|
||||
thread_reduce_result += exp_value;
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
shared_rho[i] /= reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
// compute params
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double param_value = CUDAPhi(cuda_labels_pointer[i], cuda_item_rands_pointer[i]);
|
||||
shared_params[i] = param_value;
|
||||
thread_reduce_result += param_value;
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
reduce_result = 1.0f / max(kEpsilon, reduce_result);
|
||||
}
|
||||
__syncthreads();
|
||||
const double inv_denominator = reduce_result;
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double term = -shared_params[i] * inv_denominator + shared_rho[i];
|
||||
shared_lambdas[i] = static_cast<score_t>(term);
|
||||
shared_params[i] = term / (1.0f - shared_rho[i]);
|
||||
thread_reduce_result += shared_params[i];
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
const double sum_l1 = reduce_result;
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double term = shared_rho[i] * (sum_l1 - shared_params[i]);
|
||||
shared_lambdas[i] += static_cast<score_t>(term);
|
||||
shared_params[i] = term / (1.0f - shared_rho[i]);
|
||||
thread_reduce_result += shared_params[i];
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
const double sum_l2 = reduce_result;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
shared_lambdas[i] += static_cast<score_t>(shared_rho[i] * (sum_l2 - shared_params[i]));
|
||||
cuda_out_hessians_pointer[i] = static_cast<score_t>(shared_rho[i] * (1.0f - shared_rho[i]));
|
||||
}
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
cuda_out_gradients_pointer[i] = shared_lambdas[i];
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void GetGradientsKernel_RankXENDCG_GlobalMemory(
|
||||
const double* cuda_scores,
|
||||
const label_t* cuda_labels,
|
||||
const double* cuda_item_rands,
|
||||
const data_size_t num_data,
|
||||
const data_size_t num_queries,
|
||||
const data_size_t* cuda_query_boundaries,
|
||||
double* cuda_params_buffer,
|
||||
score_t* cuda_out_gradients,
|
||||
score_t* cuda_out_hessians) {
|
||||
const data_size_t query_index_start = static_cast<data_size_t>(blockIdx.x) * NUM_QUERY_PER_BLOCK;
|
||||
const data_size_t query_index_end = min(query_index_start + NUM_QUERY_PER_BLOCK, num_queries);
|
||||
for (data_size_t query_index = query_index_start; query_index < query_index_end; ++query_index) {
|
||||
const data_size_t item_index_start = cuda_query_boundaries[query_index];
|
||||
const data_size_t item_index_end = cuda_query_boundaries[query_index + 1];
|
||||
const data_size_t query_item_count = item_index_end - item_index_start;
|
||||
score_t* cuda_out_gradients_pointer = cuda_out_gradients + item_index_start;
|
||||
score_t* cuda_out_hessians_pointer = cuda_out_hessians + item_index_start;
|
||||
const label_t* cuda_labels_pointer = cuda_labels + item_index_start;
|
||||
const double* cuda_scores_pointer = cuda_scores + item_index_start;
|
||||
const double* cuda_item_rands_pointer = cuda_item_rands + item_index_start;
|
||||
double* cuda_params_buffer_pointer = cuda_params_buffer + item_index_start;
|
||||
const data_size_t block_reduce_size = query_item_count > 1024 ? 1024 : query_item_count;
|
||||
// assert that warpSize == 32, so we use buffer size 1024 / 32 = 32
|
||||
__shared__ double shared_buffer[1024 / WARPSIZE];
|
||||
__shared__ double reduce_result;
|
||||
if (query_item_count <= 1) {
|
||||
for (data_size_t i = 0; i <= query_item_count; ++i) {
|
||||
cuda_out_gradients_pointer[i] = 0.0f;
|
||||
cuda_out_hessians_pointer[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
} else {
|
||||
// compute softmax
|
||||
double thread_reduce_result = kMinScore;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double rho = cuda_scores_pointer[i];
|
||||
if (rho > thread_reduce_result) {
|
||||
thread_reduce_result = rho;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
thread_reduce_result = ShuffleReduceMax<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double exp_value = exp(cuda_scores_pointer[i] - reduce_result);
|
||||
cuda_out_hessians_pointer[i] = exp_value;
|
||||
thread_reduce_result += exp_value;
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
// store probability into hessians
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
cuda_out_hessians_pointer[i] /= reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
// compute params
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double param_value = CUDAPhi(cuda_labels_pointer[i], cuda_item_rands_pointer[i]);
|
||||
cuda_params_buffer_pointer[i] = param_value;
|
||||
thread_reduce_result += param_value;
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
reduce_result = 1.0f / max(kEpsilon, reduce_result);
|
||||
}
|
||||
__syncthreads();
|
||||
const double inv_denominator = reduce_result;
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double term = -cuda_params_buffer_pointer[i] * inv_denominator + cuda_out_hessians_pointer[i];
|
||||
cuda_out_gradients_pointer[i] = static_cast<score_t>(term);
|
||||
const double param = term / (1.0f - cuda_out_hessians_pointer[i]);
|
||||
cuda_params_buffer_pointer[i] = param;
|
||||
thread_reduce_result += param;
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
const double sum_l1 = reduce_result;
|
||||
thread_reduce_result = 0.0f;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double term = cuda_out_hessians_pointer[i] * (sum_l1 - cuda_params_buffer_pointer[i]);
|
||||
cuda_out_gradients_pointer[i] += static_cast<score_t>(term);
|
||||
const double param = term / (1.0f - cuda_out_hessians_pointer[i]);
|
||||
cuda_params_buffer_pointer[i] = param;
|
||||
thread_reduce_result += param;
|
||||
}
|
||||
thread_reduce_result = ShuffleReduceSum<double>(thread_reduce_result, shared_buffer, block_reduce_size);
|
||||
if (threadIdx.x == 0) {
|
||||
reduce_result = thread_reduce_result;
|
||||
}
|
||||
__syncthreads();
|
||||
const double sum_l2 = reduce_result;
|
||||
for (data_size_t i = static_cast<data_size_t>(threadIdx.x); i < query_item_count; i += static_cast<data_size_t>(blockDim.x)) {
|
||||
const double prob = cuda_out_hessians_pointer[i];
|
||||
cuda_out_gradients_pointer[i] += static_cast<score_t>(prob * (sum_l2 - cuda_params_buffer_pointer[i]));
|
||||
cuda_out_hessians_pointer[i] = static_cast<score_t>(prob * (1.0f - prob));
|
||||
}
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARankXENDCG::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
GenerateItemRands();
|
||||
CopyFromHostToCUDADevice<double>(cuda_item_rands_.RawData(), item_rands_.data(), item_rands_.size(), __FILE__, __LINE__);
|
||||
|
||||
const int num_blocks = (num_queries_ + NUM_QUERY_PER_BLOCK - 1) / NUM_QUERY_PER_BLOCK;
|
||||
if (max_items_in_query_aligned_ <= 1024) {
|
||||
GetGradientsKernel_RankXENDCG_SharedMemory<1024><<<num_blocks, max_items_in_query_aligned_>>>(
|
||||
score,
|
||||
cuda_labels_,
|
||||
cuda_item_rands_.RawData(),
|
||||
num_data_,
|
||||
num_queries_,
|
||||
cuda_query_boundaries_,
|
||||
gradients,
|
||||
hessians);
|
||||
} else if (max_items_in_query_aligned_ <= 2 * 1024) {
|
||||
GetGradientsKernel_RankXENDCG_SharedMemory<2 * 1024><<<num_blocks, 1024>>>(
|
||||
score,
|
||||
cuda_labels_,
|
||||
cuda_item_rands_.RawData(),
|
||||
num_data_,
|
||||
num_queries_,
|
||||
cuda_query_boundaries_,
|
||||
gradients,
|
||||
hessians);
|
||||
} else {
|
||||
GetGradientsKernel_RankXENDCG_GlobalMemory<<<num_blocks, 1024>>>(
|
||||
score,
|
||||
cuda_labels_,
|
||||
cuda_item_rands_.RawData(),
|
||||
num_data_,
|
||||
num_queries_,
|
||||
cuda_query_boundaries_,
|
||||
cuda_params_buffer_.RawData(),
|
||||
gradients,
|
||||
hessians);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,123 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_RANK_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_RANK_OBJECTIVE_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#define NUM_QUERY_PER_BLOCK (10)
|
||||
|
||||
#include <LightGBM/cuda/cuda_objective_function.hpp>
|
||||
#include <LightGBM/utils/threading.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../rank_objective.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_OBJECTIVE>
|
||||
class CUDALambdaRankObjectiveInterface : public CUDAObjectiveInterface<HOST_OBJECTIVE> {
|
||||
public:
|
||||
explicit CUDALambdaRankObjectiveInterface(const Config& config): CUDAObjectiveInterface<HOST_OBJECTIVE>(config) {}
|
||||
|
||||
explicit CUDALambdaRankObjectiveInterface(const std::vector<std::string>& strs): CUDAObjectiveInterface<HOST_OBJECTIVE>(strs) {}
|
||||
|
||||
~CUDALambdaRankObjectiveInterface() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
CUDAObjectiveInterface<HOST_OBJECTIVE>::Init(metadata, num_data);
|
||||
|
||||
const int num_threads = OMP_NUM_THREADS();
|
||||
std::vector<uint16_t> thread_max_num_items_in_query(num_threads);
|
||||
Threading::For<data_size_t>(0, this->num_queries_, 1,
|
||||
[this, &thread_max_num_items_in_query] (int thread_index, data_size_t start, data_size_t end) {
|
||||
for (data_size_t query_index = start; query_index < end; ++query_index) {
|
||||
const data_size_t query_item_count = this->query_boundaries_[query_index + 1] - this->query_boundaries_[query_index];
|
||||
if (query_item_count > thread_max_num_items_in_query[thread_index]) {
|
||||
thread_max_num_items_in_query[thread_index] = query_item_count;
|
||||
}
|
||||
}
|
||||
});
|
||||
data_size_t max_items_in_query = 0;
|
||||
for (int thread_index = 0; thread_index < num_threads; ++thread_index) {
|
||||
if (thread_max_num_items_in_query[thread_index] > max_items_in_query) {
|
||||
max_items_in_query = thread_max_num_items_in_query[thread_index];
|
||||
}
|
||||
}
|
||||
max_items_in_query_aligned_ = 1;
|
||||
--max_items_in_query;
|
||||
while (max_items_in_query > 0) {
|
||||
max_items_in_query >>= 1;
|
||||
max_items_in_query_aligned_ <<= 1;
|
||||
}
|
||||
if (max_items_in_query_aligned_ > 2048) {
|
||||
cuda_item_indices_buffer_.Resize(static_cast<size_t>(metadata.query_boundaries()[metadata.num_queries()]));
|
||||
}
|
||||
this->cuda_labels_ = metadata.cuda_metadata()->cuda_label();
|
||||
cuda_query_boundaries_ = metadata.cuda_metadata()->cuda_query_boundaries();
|
||||
}
|
||||
|
||||
protected:
|
||||
// CUDA memory, held by this object
|
||||
CUDAVector<int> cuda_item_indices_buffer_;
|
||||
|
||||
// CUDA memory, held by other objects
|
||||
const data_size_t* cuda_query_boundaries_;
|
||||
|
||||
// Host memory
|
||||
int max_items_in_query_aligned_;
|
||||
};
|
||||
|
||||
|
||||
class CUDALambdarankNDCG: public CUDALambdaRankObjectiveInterface<LambdarankNDCG> {
|
||||
public:
|
||||
explicit CUDALambdarankNDCG(const Config& config);
|
||||
|
||||
explicit CUDALambdarankNDCG(const std::vector<std::string>& strs);
|
||||
|
||||
void Init(const Metadata& mdtadata, data_size_t num_data) override;
|
||||
|
||||
~CUDALambdarankNDCG();
|
||||
|
||||
private:
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
|
||||
// CUDA memory, held by this object
|
||||
CUDAVector<double> cuda_inverse_max_dcgs_;
|
||||
CUDAVector<double> cuda_label_gain_;
|
||||
};
|
||||
|
||||
|
||||
class CUDARankXENDCG : public CUDALambdaRankObjectiveInterface<RankXENDCG> {
|
||||
public:
|
||||
explicit CUDARankXENDCG(const Config& config);
|
||||
|
||||
explicit CUDARankXENDCG(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDARankXENDCG();
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
protected:
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const;
|
||||
|
||||
void GenerateItemRands() const;
|
||||
|
||||
mutable std::vector<double> item_rands_;
|
||||
CUDAVector<double> cuda_item_rands_;
|
||||
CUDAVector<double> cuda_params_buffer_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_RANK_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,110 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_regression_objective.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDARegressionL2loss::CUDARegressionL2loss(const Config& config):
|
||||
CUDARegressionObjectiveInterface<RegressionL2loss>(config) {}
|
||||
|
||||
CUDARegressionL2loss::CUDARegressionL2loss(const std::vector<std::string>& strs):
|
||||
CUDARegressionObjectiveInterface<RegressionL2loss>(strs) {}
|
||||
|
||||
CUDARegressionL2loss::~CUDARegressionL2loss() {}
|
||||
|
||||
void CUDARegressionL2loss::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDARegressionObjectiveInterface<RegressionL2loss>::Init(metadata, num_data);
|
||||
}
|
||||
|
||||
CUDARegressionL1loss::CUDARegressionL1loss(const Config& config):
|
||||
CUDARegressionObjectiveInterface<RegressionL1loss>(config) {}
|
||||
|
||||
CUDARegressionL1loss::CUDARegressionL1loss(const std::vector<std::string>& strs):
|
||||
CUDARegressionObjectiveInterface<RegressionL1loss>(strs) {}
|
||||
|
||||
CUDARegressionL1loss::~CUDARegressionL1loss() {}
|
||||
|
||||
void CUDARegressionL1loss::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDARegressionObjectiveInterface<RegressionL1loss>::Init(metadata, num_data);
|
||||
cuda_data_indices_buffer_.Resize(static_cast<size_t>(num_data));
|
||||
cuda_percentile_result_.Resize(1);
|
||||
if (cuda_weights_ != nullptr) {
|
||||
const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION + 1;
|
||||
cuda_weights_prefix_sum_.Resize(static_cast<size_t>(num_data));
|
||||
cuda_weights_prefix_sum_buffer_.Resize(static_cast<size_t>(num_blocks));
|
||||
cuda_weight_by_leaf_buffer_.Resize(static_cast<size_t>(num_data));
|
||||
}
|
||||
cuda_residual_buffer_.Resize(static_cast<size_t>(num_data));
|
||||
}
|
||||
|
||||
|
||||
CUDARegressionHuberLoss::CUDARegressionHuberLoss(const Config& config):
|
||||
CUDARegressionObjectiveInterface<RegressionHuberLoss>(config) {}
|
||||
|
||||
CUDARegressionHuberLoss::CUDARegressionHuberLoss(const std::vector<std::string>& strs):
|
||||
CUDARegressionObjectiveInterface<RegressionHuberLoss>(strs) {}
|
||||
|
||||
CUDARegressionHuberLoss::~CUDARegressionHuberLoss() {}
|
||||
|
||||
|
||||
CUDARegressionFairLoss::CUDARegressionFairLoss(const Config& config):
|
||||
CUDARegressionObjectiveInterface<RegressionFairLoss>(config) {}
|
||||
|
||||
CUDARegressionFairLoss::CUDARegressionFairLoss(const std::vector<std::string>& strs):
|
||||
CUDARegressionObjectiveInterface<RegressionFairLoss>(strs) {}
|
||||
|
||||
CUDARegressionFairLoss::~CUDARegressionFairLoss() {}
|
||||
|
||||
|
||||
CUDARegressionPoissonLoss::CUDARegressionPoissonLoss(const Config& config):
|
||||
CUDARegressionObjectiveInterface<RegressionPoissonLoss>(config) {}
|
||||
|
||||
CUDARegressionPoissonLoss::CUDARegressionPoissonLoss(const std::vector<std::string>& strs):
|
||||
CUDARegressionObjectiveInterface<RegressionPoissonLoss>(strs) {}
|
||||
|
||||
CUDARegressionPoissonLoss::~CUDARegressionPoissonLoss() {}
|
||||
|
||||
void CUDARegressionPoissonLoss::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDARegressionObjectiveInterface<RegressionPoissonLoss>::Init(metadata, num_data);
|
||||
LaunchCheckLabelKernel();
|
||||
}
|
||||
|
||||
double CUDARegressionPoissonLoss::LaunchCalcInitScoreKernel(const int class_id) const {
|
||||
return Common::SafeLog(CUDARegressionObjectiveInterface<RegressionPoissonLoss>::LaunchCalcInitScoreKernel(class_id));
|
||||
}
|
||||
|
||||
|
||||
CUDARegressionQuantileloss::CUDARegressionQuantileloss(const Config& config):
|
||||
CUDARegressionObjectiveInterface<RegressionQuantileloss>(config) {}
|
||||
|
||||
CUDARegressionQuantileloss::CUDARegressionQuantileloss(const std::vector<std::string>& strs):
|
||||
CUDARegressionObjectiveInterface<RegressionQuantileloss>(strs) {}
|
||||
|
||||
CUDARegressionQuantileloss::~CUDARegressionQuantileloss() {}
|
||||
|
||||
void CUDARegressionQuantileloss::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDARegressionObjectiveInterface<RegressionQuantileloss>::Init(metadata, num_data);
|
||||
cuda_data_indices_buffer_.Resize(static_cast<size_t>(num_data));
|
||||
cuda_percentile_result_.Resize(1);
|
||||
if (cuda_weights_ != nullptr) {
|
||||
const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION + 1;
|
||||
cuda_weights_prefix_sum_.Resize(static_cast<size_t>(num_data));
|
||||
cuda_weights_prefix_sum_buffer_.Resize(static_cast<size_t>(num_blocks));
|
||||
cuda_weight_by_leaf_buffer_.Resize(static_cast<size_t>(num_data));
|
||||
}
|
||||
cuda_residual_buffer_.Resize(static_cast<size_t>(num_data));
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,482 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_regression_objective.hpp"
|
||||
#include <LightGBM/cuda/cuda_algorithms.hpp>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_OBJECTIVE>
|
||||
void CUDARegressionObjectiveInterface<HOST_OBJECTIVE>::Init(const Metadata& metadata, data_size_t num_data) {
|
||||
CUDAObjectiveInterface<HOST_OBJECTIVE>::Init(metadata, num_data);
|
||||
const data_size_t num_get_gradients_blocks = (this->num_data_ + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
cuda_block_buffer_.Resize(static_cast<size_t>(num_get_gradients_blocks));
|
||||
if (this->sqrt_) {
|
||||
cuda_trans_label_.Resize(this->trans_label_.size());
|
||||
CopyFromHostToCUDADevice<label_t>(cuda_trans_label_.RawData(), this->trans_label_.data(), this->trans_label_.size(), __FILE__, __LINE__);
|
||||
this->cuda_labels_ = cuda_trans_label_.RawData();
|
||||
}
|
||||
}
|
||||
|
||||
template void CUDARegressionObjectiveInterface<RegressionL2loss>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDARegressionObjectiveInterface<RegressionL1loss>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDARegressionObjectiveInterface<RegressionHuberLoss>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDARegressionObjectiveInterface<RegressionFairLoss>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDARegressionObjectiveInterface<RegressionPoissonLoss>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
template void CUDARegressionObjectiveInterface<RegressionQuantileloss>::Init(const Metadata& metadata, data_size_t num_data);
|
||||
|
||||
template <typename HOST_OBJECTIVE>
|
||||
double CUDARegressionObjectiveInterface<HOST_OBJECTIVE>::LaunchCalcInitScoreKernel(const int /*class_id*/) const {
|
||||
double label_sum = 0.0f, weight_sum = 0.0f;
|
||||
if (this->cuda_weights_ == nullptr) {
|
||||
ShuffleReduceSumGlobal<label_t, double>(this->cuda_labels_,
|
||||
static_cast<size_t>(this->num_data_), cuda_block_buffer_.RawData());
|
||||
CopyFromCUDADeviceToHost<double>(&label_sum, cuda_block_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
weight_sum = static_cast<double>(this->num_data_);
|
||||
} else {
|
||||
ShuffleReduceDotProdGlobal<label_t, double>(this->cuda_labels_,
|
||||
this->cuda_weights_, static_cast<size_t>(this->num_data_), cuda_block_buffer_.RawData());
|
||||
CopyFromCUDADeviceToHost<double>(&label_sum, cuda_block_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
ShuffleReduceSumGlobal<label_t, double>(this->cuda_weights_,
|
||||
static_cast<size_t>(this->num_data_), cuda_block_buffer_.RawData());
|
||||
CopyFromCUDADeviceToHost<double>(&weight_sum, cuda_block_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
}
|
||||
return label_sum / weight_sum;
|
||||
}
|
||||
|
||||
template double CUDARegressionObjectiveInterface<RegressionL2loss>::LaunchCalcInitScoreKernel(const int class_id) const;
|
||||
template double CUDARegressionObjectiveInterface<RegressionL1loss>::LaunchCalcInitScoreKernel(const int class_id) const;
|
||||
template double CUDARegressionObjectiveInterface<RegressionHuberLoss>::LaunchCalcInitScoreKernel(const int class_id) const;
|
||||
template double CUDARegressionObjectiveInterface<RegressionFairLoss>::LaunchCalcInitScoreKernel(const int class_id) const;
|
||||
template double CUDARegressionObjectiveInterface<RegressionPoissonLoss>::LaunchCalcInitScoreKernel(const int class_id) const;
|
||||
template double CUDARegressionObjectiveInterface<RegressionQuantileloss>::LaunchCalcInitScoreKernel(const int class_id) const;
|
||||
|
||||
__global__ void ConvertOutputCUDAKernel_Regression(const bool sqrt, const data_size_t num_data, const double* input, double* output) {
|
||||
const int data_index = static_cast<data_size_t>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
if (sqrt) {
|
||||
const double sign = input[data_index] >= 0.0f ? 1 : -1;
|
||||
output[data_index] = sign * input[data_index] * input[data_index];
|
||||
} else {
|
||||
output[data_index] = input[data_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const double* CUDARegressionL2loss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const {
|
||||
const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
if (sqrt_) {
|
||||
ConvertOutputCUDAKernel_Regression<<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(sqrt_, num_data, input, output);
|
||||
return output;
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_RegressionL2(const double* cuda_scores, const label_t* cuda_labels, const label_t* cuda_weights, const data_size_t num_data,
|
||||
score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockDim.x * blockIdx.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
if (!USE_WEIGHT) {
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(cuda_scores[data_index] - cuda_labels[data_index]);
|
||||
cuda_out_hessians[data_index] = 1.0f;
|
||||
} else {
|
||||
const score_t weight = static_cast<score_t>(cuda_weights[data_index]);
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(cuda_scores[data_index] - cuda_labels[data_index]) * weight;
|
||||
cuda_out_hessians[data_index] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionL2loss::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_RegressionL2<false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, nullptr, num_data_, gradients, hessians);
|
||||
} else {
|
||||
GetGradientsKernel_RegressionL2<true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, cuda_weights_, num_data_, gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double CUDARegressionL1loss::LaunchCalcInitScoreKernel(const int /*class_id*/) const {
|
||||
const double alpha = 0.5f;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
PercentileGlobal<label_t, data_size_t, label_t, double, false, false>(
|
||||
cuda_labels_, nullptr, cuda_data_indices_buffer_.RawData(), nullptr, nullptr, alpha, num_data_, cuda_percentile_result_.RawData());
|
||||
} else {
|
||||
PercentileGlobal<label_t, data_size_t, label_t, double, false, true>(
|
||||
cuda_labels_, cuda_weights_, cuda_data_indices_buffer_.RawData(), cuda_weights_prefix_sum_.RawData(),
|
||||
cuda_weights_prefix_sum_buffer_.RawData(), alpha, num_data_, cuda_percentile_result_.RawData());
|
||||
}
|
||||
label_t percentile_result = 0.0f;
|
||||
CopyFromCUDADeviceToHost<label_t>(&percentile_result, cuda_percentile_result_.RawData(), 1, __FILE__, __LINE__);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
return static_cast<label_t>(percentile_result);
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_RegressionL1(const double* cuda_scores, const label_t* cuda_labels, const label_t* cuda_weights, const data_size_t num_data,
|
||||
score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockDim.x * blockIdx.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
if (!USE_WEIGHT) {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>((diff > 0.0f) - (diff < 0.0f));
|
||||
cuda_out_hessians[data_index] = 1.0f;
|
||||
} else {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
const score_t weight = static_cast<score_t>(cuda_weights[data_index]);
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>((diff > 0.0f) - (diff < 0.0f)) * weight;
|
||||
cuda_out_hessians[data_index] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionL1loss::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_RegressionL1<false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, nullptr, num_data_, gradients, hessians);
|
||||
} else {
|
||||
GetGradientsKernel_RegressionL1<true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, cuda_weights_, num_data_, gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void RenewTreeOutputCUDAKernel_RegressionL1(
|
||||
const double* score,
|
||||
const label_t* label,
|
||||
const label_t* weight,
|
||||
double* residual_buffer,
|
||||
label_t* weight_by_leaf,
|
||||
double* weight_prefix_sum_buffer,
|
||||
const data_size_t* data_indices_in_leaf,
|
||||
const data_size_t* num_data_in_leaf,
|
||||
const data_size_t* data_start_in_leaf,
|
||||
data_size_t* data_indices_buffer,
|
||||
double* leaf_value) {
|
||||
const int leaf_index = static_cast<int>(blockIdx.x);
|
||||
const data_size_t data_start = data_start_in_leaf[leaf_index];
|
||||
const data_size_t num_data = num_data_in_leaf[leaf_index];
|
||||
data_size_t* data_indices_buffer_pointer = data_indices_buffer + data_start;
|
||||
const label_t* weight_by_leaf_pointer = weight_by_leaf + data_start;
|
||||
double* weight_prefix_sum_buffer_pointer = weight_prefix_sum_buffer + data_start;
|
||||
const double* residual_buffer_pointer = residual_buffer + data_start;
|
||||
const double alpha = 0.5f;
|
||||
for (data_size_t inner_data_index = data_start + static_cast<data_size_t>(threadIdx.x);
|
||||
inner_data_index < data_start + num_data; inner_data_index += static_cast<data_size_t>(blockDim.x)) {
|
||||
const data_size_t data_index = data_indices_in_leaf[inner_data_index];
|
||||
const label_t data_label = label[data_index];
|
||||
const double data_score = score[data_index];
|
||||
residual_buffer[inner_data_index] = static_cast<double>(data_label) - data_score;
|
||||
if (USE_WEIGHT) {
|
||||
weight_by_leaf[inner_data_index] = weight[data_index];
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
const double renew_leaf_value = PercentileDevice<double, data_size_t, label_t, double, false, USE_WEIGHT>(
|
||||
residual_buffer_pointer, weight_by_leaf_pointer, data_indices_buffer_pointer,
|
||||
weight_prefix_sum_buffer_pointer, alpha, num_data);
|
||||
if (threadIdx.x == 0) {
|
||||
leaf_value[leaf_index] = renew_leaf_value;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionL1loss::LaunchRenewTreeOutputCUDAKernel(
|
||||
const double* score,
|
||||
const data_size_t* data_indices_in_leaf,
|
||||
const data_size_t* num_data_in_leaf,
|
||||
const data_size_t* data_start_in_leaf,
|
||||
const int num_leaves,
|
||||
double* leaf_value) const {
|
||||
if (cuda_weights_ == nullptr) {
|
||||
RenewTreeOutputCUDAKernel_RegressionL1<false><<<num_leaves, GET_GRADIENTS_BLOCK_SIZE_REGRESSION / 2>>>(
|
||||
score,
|
||||
cuda_labels_,
|
||||
cuda_weights_,
|
||||
cuda_residual_buffer_.RawData(),
|
||||
cuda_weight_by_leaf_buffer_.RawData(),
|
||||
cuda_weights_prefix_sum_.RawData(),
|
||||
data_indices_in_leaf,
|
||||
num_data_in_leaf,
|
||||
data_start_in_leaf,
|
||||
cuda_data_indices_buffer_.RawData(),
|
||||
leaf_value);
|
||||
} else {
|
||||
RenewTreeOutputCUDAKernel_RegressionL1<true><<<num_leaves, GET_GRADIENTS_BLOCK_SIZE_REGRESSION / 4>>>(
|
||||
score,
|
||||
cuda_labels_,
|
||||
cuda_weights_,
|
||||
cuda_residual_buffer_.RawData(),
|
||||
cuda_weight_by_leaf_buffer_.RawData(),
|
||||
cuda_weights_prefix_sum_.RawData(),
|
||||
data_indices_in_leaf,
|
||||
num_data_in_leaf,
|
||||
data_start_in_leaf,
|
||||
cuda_data_indices_buffer_.RawData(),
|
||||
leaf_value);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_Huber(const double* cuda_scores, const label_t* cuda_labels, const label_t* cuda_weights, const data_size_t num_data,
|
||||
const double alpha, score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockDim.x * blockIdx.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
if (!USE_WEIGHT) {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
if (fabs(diff) <= alpha) {
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(diff);
|
||||
} else {
|
||||
const score_t sign = static_cast<score_t>((diff > 0.0f) - (diff < 0.0f));
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(sign * alpha);
|
||||
}
|
||||
cuda_out_hessians[data_index] = 1.0f;
|
||||
} else {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
const score_t weight = static_cast<score_t>(cuda_weights[data_index]);
|
||||
if (fabs(diff) <= alpha) {
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(diff) * weight;
|
||||
} else {
|
||||
const score_t sign = static_cast<score_t>((diff > 0.0f) - (diff < 0.0f));
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(sign * alpha) * weight;
|
||||
}
|
||||
cuda_out_hessians[data_index] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionHuberLoss::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_Huber<false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, nullptr, num_data_, alpha_, gradients, hessians);
|
||||
} else {
|
||||
GetGradientsKernel_Huber<true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, cuda_weights_, num_data_, alpha_, gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_Fair(const double* cuda_scores, const label_t* cuda_labels, const label_t* cuda_weights, const data_size_t num_data,
|
||||
const double c, score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockDim.x * blockIdx.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
if (!USE_WEIGHT) {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(c * diff / (fabs(diff) + c));
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(c * c / ((fabs(diff) + c) * (fabs(diff) + c)));
|
||||
} else {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
const score_t weight = static_cast<score_t>(cuda_weights[data_index]);
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(c * diff / (fabs(diff) + c) * weight);
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(c * c / ((fabs(diff) + c) * (fabs(diff) + c)) * weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionFairLoss::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_Fair<false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, nullptr, num_data_, c_, gradients, hessians);
|
||||
} else {
|
||||
GetGradientsKernel_Fair<true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, cuda_weights_, num_data_, c_, gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionPoissonLoss::LaunchCheckLabelKernel() const {
|
||||
ShuffleReduceSumGlobal<label_t, double>(cuda_labels_, static_cast<size_t>(num_data_), cuda_block_buffer_.RawData());
|
||||
double label_sum = 0.0f;
|
||||
CopyFromCUDADeviceToHost<double>(&label_sum, cuda_block_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
|
||||
ShuffleReduceMinGlobal<label_t, double>(cuda_labels_, static_cast<size_t>(num_data_), cuda_block_buffer_.RawData());
|
||||
double label_min = 0.0f;
|
||||
CopyFromCUDADeviceToHost<double>(&label_min, cuda_block_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
|
||||
if (label_min < 0.0f) {
|
||||
Log::Fatal("[%s]: at least one target label is negative", GetName());
|
||||
}
|
||||
if (label_sum == 0.0f) {
|
||||
Log::Fatal("[%s]: sum of labels is zero", GetName());
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_Poisson(const double* cuda_scores, const label_t* cuda_labels, const label_t* cuda_weights, const data_size_t num_data,
|
||||
const double max_delta_step, score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockDim.x * blockIdx.x + threadIdx.x);
|
||||
const double exp_max_delta_step = std::exp(max_delta_step);
|
||||
if (data_index < num_data) {
|
||||
if (!USE_WEIGHT) {
|
||||
const double exp_score = exp(cuda_scores[data_index]);
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>(exp_score - cuda_labels[data_index]);
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(exp_score * exp_max_delta_step);
|
||||
} else {
|
||||
const double exp_score = exp(cuda_scores[data_index]);
|
||||
const score_t weight = static_cast<score_t>(cuda_weights[data_index]);
|
||||
cuda_out_gradients[data_index] = static_cast<score_t>((exp_score - cuda_labels[data_index]) * weight);
|
||||
cuda_out_hessians[data_index] = static_cast<score_t>(exp_score * exp_max_delta_step * weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionPoissonLoss::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_Poisson<false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(
|
||||
score, cuda_labels_, nullptr, num_data_, max_delta_step_, gradients, hessians);
|
||||
} else {
|
||||
GetGradientsKernel_Poisson<true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(
|
||||
score, cuda_labels_, cuda_weights_, num_data_, max_delta_step_, gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void ConvertOutputCUDAKernel_Regression_Poisson(const data_size_t num_data, const double* input, double* output) {
|
||||
const int data_index = static_cast<data_size_t>(blockIdx.x * blockDim.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
output[data_index] = exp(input[data_index]);
|
||||
}
|
||||
}
|
||||
|
||||
const double* CUDARegressionPoissonLoss::LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const {
|
||||
const int num_blocks = (num_data + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
ConvertOutputCUDAKernel_Regression_Poisson<<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(num_data, input, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
double CUDARegressionQuantileloss::LaunchCalcInitScoreKernel(const int /*class_id*/) const {
|
||||
if (cuda_weights_ == nullptr) {
|
||||
PercentileGlobal<label_t, data_size_t, label_t, double, false, false>(
|
||||
cuda_labels_, nullptr, cuda_data_indices_buffer_.RawData(), nullptr, nullptr, alpha_, num_data_, cuda_percentile_result_.RawData());
|
||||
} else {
|
||||
PercentileGlobal<label_t, data_size_t, label_t, double, false, true>(
|
||||
cuda_labels_, cuda_weights_, cuda_data_indices_buffer_.RawData(), cuda_weights_prefix_sum_.RawData(),
|
||||
cuda_weights_prefix_sum_buffer_.RawData(), alpha_, num_data_, cuda_percentile_result_.RawData());
|
||||
}
|
||||
label_t percentile_result = 0.0f;
|
||||
CopyFromCUDADeviceToHost<label_t>(&percentile_result, cuda_percentile_result_.RawData(), 1, __FILE__, __LINE__);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
return static_cast<label_t>(percentile_result);
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void RenewTreeOutputCUDAKernel_RegressionQuantile(
|
||||
const double* score,
|
||||
const label_t* label,
|
||||
const label_t* weight,
|
||||
double* residual_buffer,
|
||||
label_t* weight_by_leaf,
|
||||
double* weight_prefix_sum_buffer,
|
||||
const data_size_t* data_indices_in_leaf,
|
||||
const data_size_t* num_data_in_leaf,
|
||||
const data_size_t* data_start_in_leaf,
|
||||
data_size_t* data_indices_buffer,
|
||||
double* leaf_value,
|
||||
const double alpha) {
|
||||
const int leaf_index = static_cast<int>(blockIdx.x);
|
||||
const data_size_t data_start = data_start_in_leaf[leaf_index];
|
||||
const data_size_t num_data = num_data_in_leaf[leaf_index];
|
||||
data_size_t* data_indices_buffer_pointer = data_indices_buffer + data_start;
|
||||
const label_t* weight_by_leaf_pointer = weight_by_leaf + data_start;
|
||||
double* weight_prefix_sum_buffer_pointer = weight_prefix_sum_buffer + data_start;
|
||||
const double* residual_buffer_pointer = residual_buffer + data_start;
|
||||
for (data_size_t inner_data_index = data_start + static_cast<data_size_t>(threadIdx.x); inner_data_index < data_start + num_data; inner_data_index += static_cast<data_size_t>(blockDim.x)) {
|
||||
const data_size_t data_index = data_indices_in_leaf[inner_data_index];
|
||||
const label_t data_label = label[data_index];
|
||||
const double data_score = score[data_index];
|
||||
residual_buffer[inner_data_index] = static_cast<double>(data_label) - data_score;
|
||||
if (USE_WEIGHT) {
|
||||
weight_by_leaf[inner_data_index] = weight[data_index];
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
const double renew_leaf_value = PercentileDevice<double, data_size_t, label_t, double, false, USE_WEIGHT>(
|
||||
residual_buffer_pointer, weight_by_leaf_pointer, data_indices_buffer_pointer,
|
||||
weight_prefix_sum_buffer_pointer, alpha, num_data);
|
||||
if (threadIdx.x == 0) {
|
||||
leaf_value[leaf_index] = renew_leaf_value;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionQuantileloss::LaunchRenewTreeOutputCUDAKernel(
|
||||
const double* score, const data_size_t* data_indices_in_leaf, const data_size_t* num_data_in_leaf,
|
||||
const data_size_t* data_start_in_leaf, const int num_leaves, double* leaf_value) const {
|
||||
if (cuda_weights_ == nullptr) {
|
||||
RenewTreeOutputCUDAKernel_RegressionQuantile<false><<<num_leaves, GET_GRADIENTS_BLOCK_SIZE_REGRESSION / 2>>>(
|
||||
score,
|
||||
cuda_labels_,
|
||||
cuda_weights_,
|
||||
cuda_residual_buffer_.RawData(),
|
||||
cuda_weight_by_leaf_buffer_.RawData(),
|
||||
cuda_weights_prefix_sum_.RawData(),
|
||||
data_indices_in_leaf,
|
||||
num_data_in_leaf,
|
||||
data_start_in_leaf,
|
||||
cuda_data_indices_buffer_.RawData(),
|
||||
leaf_value,
|
||||
alpha_);
|
||||
} else {
|
||||
RenewTreeOutputCUDAKernel_RegressionQuantile<true><<<num_leaves, GET_GRADIENTS_BLOCK_SIZE_REGRESSION / 4>>>(
|
||||
score,
|
||||
cuda_labels_,
|
||||
cuda_weights_,
|
||||
cuda_residual_buffer_.RawData(),
|
||||
cuda_weight_by_leaf_buffer_.RawData(),
|
||||
cuda_weights_prefix_sum_.RawData(),
|
||||
data_indices_in_leaf,
|
||||
num_data_in_leaf,
|
||||
data_start_in_leaf,
|
||||
cuda_data_indices_buffer_.RawData(),
|
||||
leaf_value,
|
||||
alpha_);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
template <bool USE_WEIGHT>
|
||||
__global__ void GetGradientsKernel_RegressionQuantile(const double* cuda_scores, const label_t* cuda_labels,
|
||||
const label_t* cuda_weights, const data_size_t num_data, const double alpha,
|
||||
score_t* cuda_out_gradients, score_t* cuda_out_hessians) {
|
||||
const data_size_t data_index = static_cast<data_size_t>(blockDim.x * blockIdx.x + threadIdx.x);
|
||||
if (data_index < num_data) {
|
||||
if (!USE_WEIGHT) {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
if (diff >= 0.0f) {
|
||||
cuda_out_gradients[data_index] = (1.0f - alpha);
|
||||
} else {
|
||||
cuda_out_gradients[data_index] = -alpha;
|
||||
}
|
||||
cuda_out_hessians[data_index] = 1.0f;
|
||||
} else {
|
||||
const double diff = cuda_scores[data_index] - static_cast<double>(cuda_labels[data_index]);
|
||||
const score_t weight = static_cast<score_t>(cuda_weights[data_index]);
|
||||
if (diff >= 0.0f) {
|
||||
cuda_out_gradients[data_index] = (1.0f - alpha) * weight;
|
||||
} else {
|
||||
cuda_out_gradients[data_index] = -alpha * weight;
|
||||
}
|
||||
cuda_out_hessians[data_index] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDARegressionQuantileloss::LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const {
|
||||
const int num_blocks = (num_data_ + GET_GRADIENTS_BLOCK_SIZE_REGRESSION - 1) / GET_GRADIENTS_BLOCK_SIZE_REGRESSION;
|
||||
if (cuda_weights_ == nullptr) {
|
||||
GetGradientsKernel_RegressionQuantile<false><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, nullptr, num_data_, alpha_, gradients, hessians);
|
||||
} else {
|
||||
GetGradientsKernel_RegressionQuantile<true><<<num_blocks, GET_GRADIENTS_BLOCK_SIZE_REGRESSION>>>(score, cuda_labels_, cuda_weights_, num_data_, alpha_, gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,168 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_REGRESSION_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_REGRESSION_OBJECTIVE_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#define GET_GRADIENTS_BLOCK_SIZE_REGRESSION (1024)
|
||||
|
||||
#include <LightGBM/cuda/cuda_objective_function.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../regression_objective.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename HOST_OBJECTIVE>
|
||||
class CUDARegressionObjectiveInterface: public CUDAObjectiveInterface<HOST_OBJECTIVE> {
|
||||
public:
|
||||
explicit CUDARegressionObjectiveInterface(const Config& config): CUDAObjectiveInterface<HOST_OBJECTIVE>(config) {}
|
||||
|
||||
explicit CUDARegressionObjectiveInterface(const std::vector<std::string>& strs): CUDAObjectiveInterface<HOST_OBJECTIVE>(strs) {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
protected:
|
||||
double LaunchCalcInitScoreKernel(const int class_id) const override;
|
||||
|
||||
CUDAVector<double> cuda_block_buffer_;
|
||||
CUDAVector<label_t> cuda_trans_label_;
|
||||
};
|
||||
|
||||
class CUDARegressionL2loss : public CUDARegressionObjectiveInterface<RegressionL2loss> {
|
||||
public:
|
||||
explicit CUDARegressionL2loss(const Config& config);
|
||||
|
||||
explicit CUDARegressionL2loss(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDARegressionL2loss();
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
protected:
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
|
||||
const double* LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const override;
|
||||
|
||||
bool NeedConvertOutputCUDA() const override { return sqrt_; }
|
||||
};
|
||||
|
||||
|
||||
class CUDARegressionL1loss : public CUDARegressionObjectiveInterface<RegressionL1loss> {
|
||||
public:
|
||||
explicit CUDARegressionL1loss(const Config& config);
|
||||
|
||||
explicit CUDARegressionL1loss(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDARegressionL1loss();
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
protected:
|
||||
CUDAVector<data_size_t> cuda_data_indices_buffer_;
|
||||
CUDAVector<double> cuda_weights_prefix_sum_;
|
||||
CUDAVector<double> cuda_weights_prefix_sum_buffer_;
|
||||
CUDAVector<double> cuda_residual_buffer_;
|
||||
CUDAVector<label_t> cuda_weight_by_leaf_buffer_;
|
||||
CUDAVector<label_t> cuda_percentile_result_;
|
||||
|
||||
double LaunchCalcInitScoreKernel(const int class_id) const override;
|
||||
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
|
||||
void LaunchRenewTreeOutputCUDAKernel(
|
||||
const double* score, const data_size_t* data_indices_in_leaf, const data_size_t* num_data_in_leaf,
|
||||
const data_size_t* data_start_in_leaf, const int num_leaves, double* leaf_value) const override;
|
||||
};
|
||||
|
||||
|
||||
class CUDARegressionHuberLoss : public CUDARegressionObjectiveInterface<RegressionHuberLoss> {
|
||||
public:
|
||||
explicit CUDARegressionHuberLoss(const Config& config);
|
||||
|
||||
explicit CUDARegressionHuberLoss(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDARegressionHuberLoss();
|
||||
|
||||
private:
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
};
|
||||
|
||||
|
||||
// http://research.microsoft.com/en-us/um/people/zhang/INRIA/Publis/Tutorial-Estim/node24.html
|
||||
class CUDARegressionFairLoss : public CUDARegressionObjectiveInterface<RegressionFairLoss> {
|
||||
public:
|
||||
explicit CUDARegressionFairLoss(const Config& config);
|
||||
|
||||
explicit CUDARegressionFairLoss(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDARegressionFairLoss();
|
||||
|
||||
private:
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
};
|
||||
|
||||
|
||||
class CUDARegressionPoissonLoss : public CUDARegressionObjectiveInterface<RegressionPoissonLoss> {
|
||||
public:
|
||||
explicit CUDARegressionPoissonLoss(const Config& config);
|
||||
|
||||
explicit CUDARegressionPoissonLoss(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDARegressionPoissonLoss();
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
private:
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
|
||||
const double* LaunchConvertOutputCUDAKernel(const data_size_t num_data, const double* input, double* output) const override;
|
||||
|
||||
bool NeedConvertOutputCUDA() const override { return true; }
|
||||
|
||||
double LaunchCalcInitScoreKernel(const int class_id) const override;
|
||||
|
||||
void LaunchCheckLabelKernel() const;
|
||||
};
|
||||
|
||||
|
||||
class CUDARegressionQuantileloss : public CUDARegressionObjectiveInterface<RegressionQuantileloss> {
|
||||
public:
|
||||
explicit CUDARegressionQuantileloss(const Config& config);
|
||||
|
||||
explicit CUDARegressionQuantileloss(const std::vector<std::string>& strs);
|
||||
|
||||
~CUDARegressionQuantileloss();
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override;
|
||||
|
||||
protected:
|
||||
void LaunchGetGradientsKernel(const double* score, score_t* gradients, score_t* hessians) const override;
|
||||
|
||||
double LaunchCalcInitScoreKernel(const int class_id) const override;
|
||||
|
||||
void LaunchRenewTreeOutputCUDAKernel(
|
||||
const double* score, const data_size_t* data_indices_in_leaf, const data_size_t* num_data_in_leaf,
|
||||
const data_size_t* data_start_in_leaf, const int num_leaves, double* leaf_value) const override;
|
||||
|
||||
CUDAVector<data_size_t> cuda_data_indices_buffer_;
|
||||
CUDAVector<double> cuda_weights_prefix_sum_;
|
||||
CUDAVector<double> cuda_weights_prefix_sum_buffer_;
|
||||
CUDAVector<double> cuda_residual_buffer_;
|
||||
CUDAVector<label_t> cuda_weight_by_leaf_buffer_;
|
||||
CUDAVector<label_t> cuda_percentile_result_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_CUDA_CUDA_REGRESSION_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,280 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_MULTICLASS_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_MULTICLASS_OBJECTIVE_HPP_
|
||||
|
||||
#include <LightGBM/network.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "binary_objective.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief Objective function for multiclass classification, use softmax as objective functions
|
||||
*/
|
||||
class MulticlassSoftmax: public ObjectiveFunction {
|
||||
public:
|
||||
explicit MulticlassSoftmax(const Config& config) {
|
||||
num_class_ = config.num_class;
|
||||
// This factor is to rescale the redundant form of K-classification, to the non-redundant form.
|
||||
// In the traditional settings of K-classification, there is one redundant class, whose output is set to 0 (like the class 0 in binary classification).
|
||||
// This is from the Friedman GBDT paper.
|
||||
factor_ = static_cast<double>(num_class_) / (num_class_ - 1.0f);
|
||||
}
|
||||
|
||||
explicit MulticlassSoftmax(const std::vector<std::string>& strs) {
|
||||
num_class_ = -1;
|
||||
for (auto str : strs) {
|
||||
auto tokens = Common::Split(str.c_str(), ':');
|
||||
if (tokens.size() == 2) {
|
||||
if (tokens[0] == std::string("num_class")) {
|
||||
Common::Atoi(tokens[1].c_str(), &num_class_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_class_ < 0) {
|
||||
Log::Fatal("Objective should contain num_class field");
|
||||
}
|
||||
factor_ = static_cast<double>(num_class_) / (num_class_ - 1.0f);
|
||||
}
|
||||
|
||||
~MulticlassSoftmax() {
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
label_int_.resize(num_data_);
|
||||
class_init_probs_.resize(num_class_, 0.0);
|
||||
double sum_weight = 0.0;
|
||||
for (int i = 0; i < num_data_; ++i) {
|
||||
label_int_[i] = static_cast<int>(label_[i]);
|
||||
if (label_int_[i] < 0 || label_int_[i] >= num_class_) {
|
||||
Log::Fatal("Label must be in [0, %d), but found %d in label", num_class_, label_int_[i]);
|
||||
}
|
||||
if (weights_ == nullptr) {
|
||||
class_init_probs_[label_int_[i]] += 1.0;
|
||||
} else {
|
||||
class_init_probs_[label_int_[i]] += weights_[i];
|
||||
sum_weight += weights_[i];
|
||||
}
|
||||
}
|
||||
if (weights_ == nullptr) {
|
||||
sum_weight = num_data_;
|
||||
}
|
||||
if (Network::num_machines() > 1) {
|
||||
sum_weight = Network::GlobalSyncUpBySum(sum_weight);
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
class_init_probs_[i] = Network::GlobalSyncUpBySum(class_init_probs_[i]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
class_init_probs_[i] /= sum_weight;
|
||||
}
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
std::vector<double> rec;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(rec)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
rec.resize(num_class_);
|
||||
for (int k = 0; k < num_class_; ++k) {
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
rec[k] = static_cast<double>(score[idx]);
|
||||
}
|
||||
Common::Softmax(&rec);
|
||||
for (int k = 0; k < num_class_; ++k) {
|
||||
auto p = rec[k];
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
if (label_int_[i] == k) {
|
||||
gradients[idx] = static_cast<score_t>(p - 1.0f);
|
||||
} else {
|
||||
gradients[idx] = static_cast<score_t>(p);
|
||||
}
|
||||
hessians[idx] = static_cast<score_t>(factor_ * p * (1.0f - p));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::vector<double> rec;
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) private(rec)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
rec.resize(num_class_);
|
||||
for (int k = 0; k < num_class_; ++k) {
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
rec[k] = static_cast<double>(score[idx]);
|
||||
}
|
||||
Common::Softmax(&rec);
|
||||
for (int k = 0; k < num_class_; ++k) {
|
||||
auto p = rec[k];
|
||||
size_t idx = static_cast<size_t>(num_data_) * k + i;
|
||||
if (label_int_[i] == k) {
|
||||
gradients[idx] = static_cast<score_t>((p - 1.0f) * weights_[i]);
|
||||
} else {
|
||||
gradients[idx] = static_cast<score_t>(p * weights_[i]);
|
||||
}
|
||||
hessians[idx] = static_cast<score_t>((factor_ * p * (1.0f - p))* weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertOutput(const double* input, double* output) const override {
|
||||
Common::Softmax(input, output, num_class_);
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "multiclass";
|
||||
}
|
||||
|
||||
std::string ToString() const override {
|
||||
std::stringstream str_buf;
|
||||
str_buf << GetName() << " ";
|
||||
str_buf << "num_class:" << num_class_;
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
bool SkipEmptyClass() const override { return true; }
|
||||
|
||||
int NumModelPerIteration() const override { return num_class_; }
|
||||
|
||||
int NumPredictOneRow() const override { return num_class_; }
|
||||
|
||||
bool NeedAccuratePrediction() const override { return false; }
|
||||
|
||||
double BoostFromScore(int class_id) const override {
|
||||
return std::log(std::max<double>(kEpsilon, class_init_probs_[class_id]));
|
||||
}
|
||||
|
||||
bool ClassNeedTrain(int class_id) const override {
|
||||
if (std::fabs(class_init_probs_[class_id]) <= kEpsilon
|
||||
|| std::fabs(class_init_probs_[class_id]) >= 1.0 - kEpsilon) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
double factor_;
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Number of classes */
|
||||
int num_class_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Corresponding integers of label_ */
|
||||
std::vector<int> label_int_;
|
||||
/*! \brief Weights for data */
|
||||
const label_t* weights_;
|
||||
std::vector<double> class_init_probs_;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Objective function for multiclass classification, use one-vs-all binary objective function
|
||||
*/
|
||||
class MulticlassOVA: public ObjectiveFunction {
|
||||
public:
|
||||
explicit MulticlassOVA(const Config& config) {
|
||||
num_class_ = config.num_class;
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
binary_loss_.emplace_back(
|
||||
new BinaryLogloss(config, [i](label_t label) { return static_cast<int>(label) == i; }));
|
||||
}
|
||||
sigmoid_ = config.sigmoid;
|
||||
}
|
||||
|
||||
explicit MulticlassOVA(const std::vector<std::string>& strs) {
|
||||
num_class_ = -1;
|
||||
sigmoid_ = -1;
|
||||
for (auto str : strs) {
|
||||
auto tokens = Common::Split(str.c_str(), ':');
|
||||
if (tokens.size() == 2) {
|
||||
if (tokens[0] == std::string("num_class")) {
|
||||
Common::Atoi(tokens[1].c_str(), &num_class_);
|
||||
} else if (tokens[0] == std::string("sigmoid")) {
|
||||
Common::Atof(tokens[1].c_str(), &sigmoid_);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_class_ < 0) {
|
||||
Log::Fatal("Objective should contain num_class field");
|
||||
}
|
||||
if (sigmoid_ <= 0.0) {
|
||||
Log::Fatal("Sigmoid parameter %f should be greater than zero", sigmoid_);
|
||||
}
|
||||
}
|
||||
|
||||
~MulticlassOVA() {
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
num_data_ = num_data;
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
binary_loss_[i]->Init(metadata, num_data);
|
||||
}
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
int64_t offset = static_cast<int64_t>(num_data_) * i;
|
||||
binary_loss_[i]->GetGradients(score + offset, gradients + offset, hessians + offset);
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "multiclassova";
|
||||
}
|
||||
|
||||
void ConvertOutput(const double* input, double* output) const override {
|
||||
for (int i = 0; i < num_class_; ++i) {
|
||||
output[i] = 1.0f / (1.0f + std::exp(-sigmoid_ * input[i]));
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString() const override {
|
||||
std::stringstream str_buf;
|
||||
str_buf << GetName() << " ";
|
||||
str_buf << "num_class:" << num_class_ << " ";
|
||||
str_buf << "sigmoid:" << sigmoid_;
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
bool SkipEmptyClass() const override { return true; }
|
||||
|
||||
int NumModelPerIteration() const override { return num_class_; }
|
||||
|
||||
int NumPredictOneRow() const override { return num_class_; }
|
||||
|
||||
bool NeedAccuratePrediction() const override { return false; }
|
||||
|
||||
double BoostFromScore(int class_id) const override {
|
||||
return binary_loss_[class_id]->BoostFromScore(0);
|
||||
}
|
||||
|
||||
bool ClassNeedTrain(int class_id) const override {
|
||||
return binary_loss_[class_id]->ClassNeedTrain(0);
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Number of classes */
|
||||
int num_class_;
|
||||
std::vector<std::unique_ptr<BinaryLogloss>> binary_loss_;
|
||||
double sigmoid_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_MULTICLASS_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,164 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
|
||||
#include <LightGBM/objective_function.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "binary_objective.hpp"
|
||||
#include "multiclass_objective.hpp"
|
||||
#include "rank_objective.hpp"
|
||||
#include "regression_objective.hpp"
|
||||
#include "xentropy_objective.hpp"
|
||||
|
||||
#include <LightGBM/cuda/cuda_objective_function.hpp>
|
||||
|
||||
#include "cuda/cuda_binary_objective.hpp"
|
||||
#include "cuda/cuda_multiclass_objective.hpp"
|
||||
#include "cuda/cuda_rank_objective.hpp"
|
||||
#include "cuda/cuda_regression_objective.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
#ifdef USE_CUDA
|
||||
ObjectiveFunction* ObjectiveFunction::CreateObjectiveFunctionCUDA(const std::string& type, const Config& config) {
|
||||
if (type == std::string("regression")) {
|
||||
return new CUDARegressionL2loss(config);
|
||||
} else if (type == std::string("regression_l1")) {
|
||||
return new CUDARegressionL1loss(config);
|
||||
} else if (type == std::string("quantile")) {
|
||||
return new CUDARegressionQuantileloss(config);
|
||||
} else if (type == std::string("huber")) {
|
||||
return new CUDARegressionHuberLoss(config);
|
||||
} else if (type == std::string("fair")) {
|
||||
return new CUDARegressionFairLoss(config);
|
||||
} else if (type == std::string("poisson")) {
|
||||
return new CUDARegressionPoissonLoss(config);
|
||||
} else if (type == std::string("binary")) {
|
||||
return new CUDABinaryLogloss(config);
|
||||
} else if (type == std::string("lambdarank")) {
|
||||
return new CUDALambdarankNDCG(config);
|
||||
} else if (type == std::string("rank_xendcg")) {
|
||||
return new CUDARankXENDCG(config);
|
||||
} else if (type == std::string("multiclass")) {
|
||||
return new CUDAMulticlassSoftmax(config);
|
||||
} else if (type == std::string("multiclassova")) {
|
||||
return new CUDAMulticlassOVA(config);
|
||||
} else if (type == std::string("cross_entropy")) {
|
||||
Log::Warning("Objective cross_entropy is not implemented in cuda version. Fall back to boosting on CPU.");
|
||||
return new CrossEntropy(config);
|
||||
} else if (type == std::string("cross_entropy_lambda")) {
|
||||
Log::Warning("Objective cross_entropy_lambda is not implemented in cuda version. Fall back to boosting on CPU.");
|
||||
return new CrossEntropyLambda(config);
|
||||
} else if (type == std::string("mape")) {
|
||||
Log::Warning("Objective mape is not implemented in cuda version. Fall back to boosting on CPU.");
|
||||
return new RegressionMAPELOSS(config);
|
||||
} else if (type == std::string("gamma")) {
|
||||
Log::Warning("Objective gamma is not implemented in cuda version. Fall back to boosting on CPU.");
|
||||
return new RegressionGammaLoss(config);
|
||||
} else if (type == std::string("tweedie")) {
|
||||
Log::Warning("Objective tweedie is not implemented in cuda version. Fall back to boosting on CPU.");
|
||||
return new RegressionTweedieLoss(config);
|
||||
} else if (type == std::string("custom")) {
|
||||
Log::Warning("Using customized objective with cuda. This requires copying gradients from CPU to GPU, which can be slow.");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
|
||||
ObjectiveFunction* ObjectiveFunction::CreateObjectiveFunction(const std::string& type, const Config& config) {
|
||||
#ifdef USE_CUDA
|
||||
if (config.device_type == std::string("cuda") &&
|
||||
config.data_sample_strategy != std::string("goss") &&
|
||||
config.boosting != std::string("rf")) {
|
||||
return CreateObjectiveFunctionCUDA(type, config);
|
||||
} else {
|
||||
#endif // USE_CUDA
|
||||
if (type == std::string("regression")) {
|
||||
return new RegressionL2loss(config);
|
||||
} else if (type == std::string("regression_l1")) {
|
||||
return new RegressionL1loss(config);
|
||||
} else if (type == std::string("quantile")) {
|
||||
return new RegressionQuantileloss(config);
|
||||
} else if (type == std::string("huber")) {
|
||||
return new RegressionHuberLoss(config);
|
||||
} else if (type == std::string("fair")) {
|
||||
return new RegressionFairLoss(config);
|
||||
} else if (type == std::string("poisson")) {
|
||||
return new RegressionPoissonLoss(config);
|
||||
} else if (type == std::string("binary")) {
|
||||
return new BinaryLogloss(config);
|
||||
} else if (type == std::string("lambdarank")) {
|
||||
return new LambdarankNDCG(config);
|
||||
} else if (type == std::string("rank_xendcg")) {
|
||||
return new RankXENDCG(config);
|
||||
} else if (type == std::string("multiclass")) {
|
||||
return new MulticlassSoftmax(config);
|
||||
} else if (type == std::string("multiclassova")) {
|
||||
return new MulticlassOVA(config);
|
||||
} else if (type == std::string("cross_entropy")) {
|
||||
return new CrossEntropy(config);
|
||||
} else if (type == std::string("cross_entropy_lambda")) {
|
||||
return new CrossEntropyLambda(config);
|
||||
} else if (type == std::string("mape")) {
|
||||
return new RegressionMAPELOSS(config);
|
||||
} else if (type == std::string("gamma")) {
|
||||
return new RegressionGammaLoss(config);
|
||||
} else if (type == std::string("tweedie")) {
|
||||
return new RegressionTweedieLoss(config);
|
||||
} else if (type == std::string("custom")) {
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef USE_CUDA
|
||||
}
|
||||
#endif // USE_CUDA
|
||||
Log::Fatal("Unknown objective type name: %s", type.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ObjectiveFunction* ObjectiveFunction::CreateObjectiveFunction(const std::string& str) {
|
||||
auto strs = Common::Split(str.c_str(), ' ');
|
||||
auto type = strs[0];
|
||||
if (type == std::string("regression")) {
|
||||
return new RegressionL2loss(strs);
|
||||
} else if (type == std::string("regression_l1")) {
|
||||
return new RegressionL1loss(strs);
|
||||
} else if (type == std::string("quantile")) {
|
||||
return new RegressionQuantileloss(strs);
|
||||
} else if (type == std::string("huber")) {
|
||||
return new RegressionHuberLoss(strs);
|
||||
} else if (type == std::string("fair")) {
|
||||
return new RegressionFairLoss(strs);
|
||||
} else if (type == std::string("poisson")) {
|
||||
return new RegressionPoissonLoss(strs);
|
||||
} else if (type == std::string("binary")) {
|
||||
return new BinaryLogloss(strs);
|
||||
} else if (type == std::string("lambdarank")) {
|
||||
return new LambdarankNDCG(strs);
|
||||
} else if (type == std::string("rank_xendcg")) {
|
||||
return new RankXENDCG(strs);
|
||||
} else if (type == std::string("multiclass")) {
|
||||
return new MulticlassSoftmax(strs);
|
||||
} else if (type == std::string("multiclassova")) {
|
||||
return new MulticlassOVA(strs);
|
||||
} else if (type == std::string("cross_entropy")) {
|
||||
return new CrossEntropy(strs);
|
||||
} else if (type == std::string("cross_entropy_lambda")) {
|
||||
return new CrossEntropyLambda(strs);
|
||||
} else if (type == std::string("mape")) {
|
||||
return new RegressionMAPELOSS(strs);
|
||||
} else if (type == std::string("gamma")) {
|
||||
return new RegressionGammaLoss(strs);
|
||||
} else if (type == std::string("tweedie")) {
|
||||
return new RegressionTweedieLoss(strs);
|
||||
} else if (type == std::string("custom")) {
|
||||
return nullptr;
|
||||
}
|
||||
Log::Fatal("Unknown objective type name: %s", type.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
@@ -0,0 +1,466 @@
|
||||
/*!
|
||||
* Copyright (c) 2020-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2020-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_RANK_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_RANK_OBJECTIVE_HPP_
|
||||
|
||||
#include <LightGBM/metric.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
/*!
|
||||
* \brief Objective function for Ranking
|
||||
*/
|
||||
class RankingObjective : public ObjectiveFunction {
|
||||
public:
|
||||
explicit RankingObjective(const Config& config)
|
||||
: seed_(config.objective_seed) {
|
||||
learning_rate_ = config.learning_rate;
|
||||
position_bias_regularization_ = config.lambdarank_position_bias_regularization;
|
||||
}
|
||||
|
||||
explicit RankingObjective(const std::vector<std::string>&) : seed_(0) {}
|
||||
|
||||
~RankingObjective() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
num_data_ = num_data;
|
||||
// get label
|
||||
label_ = metadata.label();
|
||||
// get weights
|
||||
weights_ = metadata.weights();
|
||||
// get positions
|
||||
positions_ = metadata.positions();
|
||||
// get position ids
|
||||
position_ids_ = metadata.position_ids();
|
||||
// get number of different position ids
|
||||
num_position_ids_ = static_cast<data_size_t>(metadata.num_position_ids());
|
||||
// get boundaries
|
||||
query_boundaries_ = metadata.query_boundaries();
|
||||
if (query_boundaries_ == nullptr) {
|
||||
Log::Fatal("Ranking tasks require query information");
|
||||
}
|
||||
num_queries_ = metadata.num_queries();
|
||||
// initialize position bias vectors
|
||||
pos_biases_.resize(num_position_ids_, 0.0);
|
||||
}
|
||||
|
||||
void GetGradientsWithSampledQueries(const double* score, const data_size_t num_sampled_queries, const data_size_t* sampled_query_indices,
|
||||
score_t* gradients, score_t* hessians) const override {
|
||||
const data_size_t num_queries = (sampled_query_indices == nullptr ? num_queries_ : num_sampled_queries);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(guided)
|
||||
for (data_size_t i = 0; i < num_queries; ++i) {
|
||||
const data_size_t query_index = (sampled_query_indices == nullptr ? i : sampled_query_indices[i]);
|
||||
const data_size_t start = query_boundaries_[query_index];
|
||||
const data_size_t cnt = query_boundaries_[query_index + 1] - query_boundaries_[query_index];
|
||||
std::vector<double> score_adjusted;
|
||||
if (num_position_ids_ > 0) {
|
||||
for (data_size_t j = 0; j < cnt; ++j) {
|
||||
score_adjusted.push_back(score[start + j] + pos_biases_[positions_[start + j]]);
|
||||
}
|
||||
}
|
||||
GetGradientsForOneQuery(query_index, cnt, label_ + start, num_position_ids_ > 0 ? score_adjusted.data() : score + start,
|
||||
gradients + start, hessians + start);
|
||||
if (weights_ != nullptr) {
|
||||
for (data_size_t j = 0; j < cnt; ++j) {
|
||||
gradients[start + j] =
|
||||
static_cast<score_t>(gradients[start + j] * weights_[start + j]);
|
||||
hessians[start + j] =
|
||||
static_cast<score_t>(hessians[start + j] * weights_[start + j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_position_ids_ > 0) {
|
||||
UpdatePositionBiasFactors(gradients, hessians);
|
||||
}
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
|
||||
GetGradientsWithSampledQueries(score, num_queries_, nullptr, gradients, hessians);
|
||||
}
|
||||
|
||||
virtual void GetGradientsForOneQuery(data_size_t query_id, data_size_t cnt,
|
||||
const label_t* label,
|
||||
const double* score, score_t* lambdas,
|
||||
score_t* hessians) const = 0;
|
||||
|
||||
virtual void UpdatePositionBiasFactors(const score_t* /*lambdas*/, const score_t* /*hessians*/) const {}
|
||||
|
||||
const char* GetName() const override = 0;
|
||||
|
||||
std::string ToString() const override {
|
||||
std::stringstream str_buf;
|
||||
str_buf << GetName();
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
bool NeedAccuratePrediction() const override { return false; }
|
||||
|
||||
protected:
|
||||
int seed_;
|
||||
data_size_t num_queries_;
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer of weights */
|
||||
const label_t* weights_;
|
||||
/*! \brief Pointer of positions */
|
||||
const data_size_t* positions_;
|
||||
/*! \brief Pointer of position IDs */
|
||||
const std::string* position_ids_;
|
||||
/*! \brief Pointer of label */
|
||||
data_size_t num_position_ids_;
|
||||
/*! \brief Query boundaries */
|
||||
const data_size_t* query_boundaries_;
|
||||
/*! \brief Position bias factors */
|
||||
mutable std::vector<label_t> pos_biases_;
|
||||
/*! \brief Learning rate to update position bias factors */
|
||||
double learning_rate_;
|
||||
/*! \brief Position bias regularization */
|
||||
double position_bias_regularization_;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Objective function for LambdaRank with NDCG
|
||||
*/
|
||||
class LambdarankNDCG : public RankingObjective {
|
||||
public:
|
||||
explicit LambdarankNDCG(const Config& config)
|
||||
: RankingObjective(config),
|
||||
sigmoid_(config.sigmoid),
|
||||
norm_(config.lambdarank_norm),
|
||||
truncation_level_(config.lambdarank_truncation_level) {
|
||||
label_gain_ = config.label_gain;
|
||||
// initialize DCG calculator
|
||||
DCGCalculator::DefaultLabelGain(&label_gain_);
|
||||
DCGCalculator::Init(label_gain_);
|
||||
sigmoid_table_.clear();
|
||||
inverse_max_dcgs_.clear();
|
||||
if (sigmoid_ <= 0.0) {
|
||||
Log::Fatal("Sigmoid param %f should be greater than zero", sigmoid_);
|
||||
}
|
||||
}
|
||||
|
||||
explicit LambdarankNDCG(const std::vector<std::string>& strs)
|
||||
: RankingObjective(strs) {}
|
||||
|
||||
~LambdarankNDCG() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
RankingObjective::Init(metadata, num_data);
|
||||
DCGCalculator::CheckMetadata(metadata, num_queries_);
|
||||
DCGCalculator::CheckLabel(label_, num_data_);
|
||||
inverse_max_dcgs_.resize(num_queries_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
inverse_max_dcgs_[i] = DCGCalculator::CalMaxDCGAtK(
|
||||
truncation_level_, label_ + query_boundaries_[i],
|
||||
query_boundaries_[i + 1] - query_boundaries_[i]);
|
||||
|
||||
if (inverse_max_dcgs_[i] > 0.0) {
|
||||
inverse_max_dcgs_[i] = 1.0f / inverse_max_dcgs_[i];
|
||||
}
|
||||
}
|
||||
// construct Sigmoid table to speed up Sigmoid transform
|
||||
ConstructSigmoidTable();
|
||||
}
|
||||
|
||||
inline void GetGradientsForOneQuery(data_size_t query_id, data_size_t cnt,
|
||||
const label_t* label, const double* score,
|
||||
score_t* lambdas,
|
||||
score_t* hessians) const override {
|
||||
// get max DCG on current query
|
||||
const double inverse_max_dcg = inverse_max_dcgs_[query_id];
|
||||
// initialize with zero
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
lambdas[i] = 0.0f;
|
||||
hessians[i] = 0.0f;
|
||||
}
|
||||
// get sorted indices for scores
|
||||
std::vector<data_size_t> sorted_idx(cnt);
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
sorted_idx[i] = i;
|
||||
}
|
||||
std::stable_sort(
|
||||
sorted_idx.begin(), sorted_idx.end(),
|
||||
[score](data_size_t a, data_size_t b) { return score[a] > score[b]; });
|
||||
// get best and worst score
|
||||
const double best_score = score[sorted_idx[0]];
|
||||
data_size_t worst_idx = cnt - 1;
|
||||
if (worst_idx > 0 && score[sorted_idx[worst_idx]] == kMinScore) {
|
||||
worst_idx -= 1;
|
||||
}
|
||||
const double worst_score = score[sorted_idx[worst_idx]];
|
||||
double sum_lambdas = 0.0;
|
||||
// start accumulate lambdas by pairs that contain at least one document above truncation level
|
||||
for (data_size_t i = 0; i < cnt - 1 && i < truncation_level_; ++i) {
|
||||
if (score[sorted_idx[i]] == kMinScore) {
|
||||
continue;
|
||||
}
|
||||
for (data_size_t j = i + 1; j < cnt; ++j) {
|
||||
if (score[sorted_idx[j]] == kMinScore) {
|
||||
continue;
|
||||
}
|
||||
// skip pairs with the same labels
|
||||
if (label[sorted_idx[i]] == label[sorted_idx[j]]) {
|
||||
continue;
|
||||
}
|
||||
data_size_t high_rank, low_rank;
|
||||
if (label[sorted_idx[i]] > label[sorted_idx[j]]) {
|
||||
high_rank = i;
|
||||
low_rank = j;
|
||||
} else {
|
||||
high_rank = j;
|
||||
low_rank = i;
|
||||
}
|
||||
const data_size_t high = sorted_idx[high_rank];
|
||||
const int high_label = static_cast<int>(label[high]);
|
||||
const double high_score = score[high];
|
||||
const double high_label_gain = label_gain_[high_label];
|
||||
const double high_discount = DCGCalculator::GetDiscount(high_rank);
|
||||
const data_size_t low = sorted_idx[low_rank];
|
||||
const int low_label = static_cast<int>(label[low]);
|
||||
const double low_score = score[low];
|
||||
const double low_label_gain = label_gain_[low_label];
|
||||
const double low_discount = DCGCalculator::GetDiscount(low_rank);
|
||||
|
||||
const double delta_score = high_score - low_score;
|
||||
|
||||
// get dcg gap
|
||||
const double dcg_gap = high_label_gain - low_label_gain;
|
||||
// get discount of this pair
|
||||
const double paired_discount = fabs(high_discount - low_discount);
|
||||
// get delta NDCG
|
||||
double delta_pair_NDCG = dcg_gap * paired_discount * inverse_max_dcg;
|
||||
// regular the delta_pair_NDCG by score distance
|
||||
if (norm_ && best_score != worst_score) {
|
||||
delta_pair_NDCG /= (0.01f + fabs(delta_score));
|
||||
}
|
||||
// calculate lambda for this pair
|
||||
double p_lambda = GetSigmoid(delta_score);
|
||||
double p_hessian = p_lambda * (1.0f - p_lambda);
|
||||
// update
|
||||
p_lambda *= -sigmoid_ * delta_pair_NDCG;
|
||||
p_hessian *= sigmoid_ * sigmoid_ * delta_pair_NDCG;
|
||||
lambdas[low] -= static_cast<score_t>(p_lambda);
|
||||
hessians[low] += static_cast<score_t>(p_hessian);
|
||||
lambdas[high] += static_cast<score_t>(p_lambda);
|
||||
hessians[high] += static_cast<score_t>(p_hessian);
|
||||
// lambda is negative, so use minus to accumulate
|
||||
sum_lambdas -= 2 * p_lambda;
|
||||
}
|
||||
}
|
||||
if (norm_ && sum_lambdas > 0) {
|
||||
double norm_factor = std::log2(1 + sum_lambdas) / sum_lambdas;
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
lambdas[i] = static_cast<score_t>(lambdas[i] * norm_factor);
|
||||
hessians[i] = static_cast<score_t>(hessians[i] * norm_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline double GetSigmoid(double score) const {
|
||||
if (score <= min_sigmoid_input_) {
|
||||
// too small, use lower bound
|
||||
return sigmoid_table_[0];
|
||||
} else if (score >= max_sigmoid_input_) {
|
||||
// too large, use upper bound
|
||||
return sigmoid_table_[_sigmoid_bins - 1];
|
||||
} else {
|
||||
return sigmoid_table_[static_cast<size_t>((score - min_sigmoid_input_) *
|
||||
sigmoid_table_idx_factor_)];
|
||||
}
|
||||
}
|
||||
|
||||
void ConstructSigmoidTable() {
|
||||
// get boundary
|
||||
min_sigmoid_input_ = min_sigmoid_input_ / sigmoid_ / 2;
|
||||
max_sigmoid_input_ = -min_sigmoid_input_;
|
||||
sigmoid_table_.resize(_sigmoid_bins);
|
||||
// get score to bin factor
|
||||
sigmoid_table_idx_factor_ =
|
||||
_sigmoid_bins / (max_sigmoid_input_ - min_sigmoid_input_);
|
||||
// cache
|
||||
for (size_t i = 0; i < _sigmoid_bins; ++i) {
|
||||
const double score = i / sigmoid_table_idx_factor_ + min_sigmoid_input_;
|
||||
sigmoid_table_[i] = 1.0f / (1.0f + std::exp(score * sigmoid_));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePositionBiasFactors(const score_t* lambdas, const score_t* hessians) const override {
|
||||
/// get number of threads
|
||||
int num_threads = OMP_NUM_THREADS();
|
||||
// create per-thread buffers for first and second derivatives of utility w.r.t. position bias factors
|
||||
std::vector<double> bias_first_derivatives(num_position_ids_ * num_threads, 0.0);
|
||||
std::vector<double> bias_second_derivatives(num_position_ids_ * num_threads, 0.0);
|
||||
std::vector<int> instance_counts(num_position_ids_ * num_threads, 0);
|
||||
#pragma omp parallel for schedule(guided) num_threads(num_threads)
|
||||
for (data_size_t i = 0; i < num_data_; i++) {
|
||||
// get thread ID
|
||||
const int tid = omp_get_thread_num();
|
||||
size_t offset = static_cast<size_t>(positions_[i] + tid * num_position_ids_);
|
||||
// accumulate first derivatives of utility w.r.t. position bias factors, for each position
|
||||
bias_first_derivatives[offset] -= lambdas[i];
|
||||
// accumulate second derivatives of utility w.r.t. position bias factors, for each position
|
||||
bias_second_derivatives[offset] -= hessians[i];
|
||||
instance_counts[offset]++;
|
||||
}
|
||||
#pragma omp parallel for schedule(guided) num_threads(num_threads)
|
||||
for (data_size_t i = 0; i < num_position_ids_; i++) {
|
||||
double bias_first_derivative = 0.0;
|
||||
double bias_second_derivative = 0.0;
|
||||
int instance_count = 0;
|
||||
// aggregate derivatives from per-thread buffers
|
||||
for (int tid = 0; tid < num_threads; tid++) {
|
||||
size_t offset = static_cast<size_t>(i + tid * num_position_ids_);
|
||||
bias_first_derivative += bias_first_derivatives[offset];
|
||||
bias_second_derivative += bias_second_derivatives[offset];
|
||||
instance_count += instance_counts[offset];
|
||||
}
|
||||
// L2 regularization on position bias factors
|
||||
bias_first_derivative -= pos_biases_[i] * position_bias_regularization_ * instance_count;
|
||||
bias_second_derivative -= position_bias_regularization_ * instance_count;
|
||||
// do Newton-Raphson step to update position bias factors
|
||||
pos_biases_[i] += learning_rate_ * bias_first_derivative / (std::abs(bias_second_derivative) + 0.001);
|
||||
}
|
||||
LogDebugPositionBiasFactors();
|
||||
}
|
||||
|
||||
const char* GetName() const override { return "lambdarank"; }
|
||||
|
||||
protected:
|
||||
void LogDebugPositionBiasFactors() const {
|
||||
std::stringstream message_stream;
|
||||
message_stream << std::setw(15) << "position"
|
||||
<< std::setw(15) << "bias_factor"
|
||||
<< std::endl;
|
||||
Log::Debug(message_stream.str().c_str());
|
||||
message_stream.str("");
|
||||
for (int i = 0; i < num_position_ids_; ++i) {
|
||||
message_stream << std::setw(15) << position_ids_[i]
|
||||
<< std::setw(15) << pos_biases_[i];
|
||||
Log::Debug(message_stream.str().c_str());
|
||||
message_stream.str("");
|
||||
}
|
||||
}
|
||||
/*! \brief Sigmoid param */
|
||||
double sigmoid_;
|
||||
/*! \brief Normalize the lambdas or not */
|
||||
bool norm_;
|
||||
/*! \brief Truncation position for max DCG */
|
||||
int truncation_level_;
|
||||
/*! \brief Cache inverse max DCG, speed up calculation */
|
||||
std::vector<double> inverse_max_dcgs_;
|
||||
/*! \brief Cache result for sigmoid transform to speed up */
|
||||
std::vector<double> sigmoid_table_;
|
||||
/*! \brief Gains for labels */
|
||||
std::vector<double> label_gain_;
|
||||
/*! \brief Number of bins in simoid table */
|
||||
size_t _sigmoid_bins = 1024 * 1024;
|
||||
/*! \brief Minimal input of sigmoid table */
|
||||
double min_sigmoid_input_ = -50;
|
||||
/*! \brief Maximal input of Sigmoid table */
|
||||
double max_sigmoid_input_ = 50;
|
||||
/*! \brief Factor that covert score to bin in Sigmoid table */
|
||||
double sigmoid_table_idx_factor_;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Implementation of the learning-to-rank objective function, XE_NDCG
|
||||
* [arxiv.org/abs/1911.09798].
|
||||
*/
|
||||
class RankXENDCG : public RankingObjective {
|
||||
public:
|
||||
explicit RankXENDCG(const Config& config) : RankingObjective(config) {}
|
||||
|
||||
explicit RankXENDCG(const std::vector<std::string>& strs)
|
||||
: RankingObjective(strs) {}
|
||||
|
||||
~RankXENDCG() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
RankingObjective::Init(metadata, num_data);
|
||||
for (data_size_t i = 0; i < num_queries_; ++i) {
|
||||
rands_.emplace_back(seed_ + i);
|
||||
}
|
||||
}
|
||||
|
||||
inline void GetGradientsForOneQuery(data_size_t query_id, data_size_t cnt,
|
||||
const label_t* label, const double* score,
|
||||
score_t* lambdas,
|
||||
score_t* hessians) const override {
|
||||
// Skip groups with too few items.
|
||||
if (cnt <= 1) {
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
lambdas[i] = 0.0f;
|
||||
hessians[i] = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Turn scores into a probability distribution using Softmax.
|
||||
std::vector<double> rho(cnt, 0.0);
|
||||
Common::Softmax(score, rho.data(), cnt);
|
||||
|
||||
// An auxiliary buffer of parameters used to form the ground-truth
|
||||
// distribution and compute the loss.
|
||||
std::vector<double> params(cnt);
|
||||
|
||||
double inv_denominator = 0;
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
params[i] = Phi(label[i], rands_[query_id].NextFloat());
|
||||
inv_denominator += params[i];
|
||||
}
|
||||
// sum_labels will always be positive number
|
||||
inv_denominator = 1. / std::max<double>(kEpsilon, inv_denominator);
|
||||
|
||||
// Approximate gradients and inverse Hessian.
|
||||
// First order terms.
|
||||
double sum_l1 = 0.0;
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
double term = -params[i] * inv_denominator + rho[i];
|
||||
lambdas[i] = static_cast<score_t>(term);
|
||||
// Params will now store terms needed to compute second-order terms.
|
||||
params[i] = term / (1. - rho[i]);
|
||||
sum_l1 += params[i];
|
||||
}
|
||||
// Second order terms.
|
||||
double sum_l2 = 0.0;
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
double term = rho[i] * (sum_l1 - params[i]);
|
||||
lambdas[i] += static_cast<score_t>(term);
|
||||
// Params will now store terms needed to compute third-order terms.
|
||||
params[i] = term / (1. - rho[i]);
|
||||
sum_l2 += params[i];
|
||||
}
|
||||
for (data_size_t i = 0; i < cnt; ++i) {
|
||||
lambdas[i] += static_cast<score_t>(rho[i] * (sum_l2 - params[i]));
|
||||
hessians[i] = static_cast<score_t>(rho[i] * (1.0 - rho[i]));
|
||||
}
|
||||
}
|
||||
|
||||
double Phi(const label_t l, double g) const {
|
||||
return Common::Pow(2, static_cast<int>(l)) - g;
|
||||
}
|
||||
|
||||
const char* GetName() const override { return "rank_xendcg"; }
|
||||
|
||||
protected:
|
||||
mutable std::vector<Random> rands_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_RANK_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,764 @@
|
||||
/*!
|
||||
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_REGRESSION_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_REGRESSION_OBJECTIVE_HPP_
|
||||
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/utils/array_args.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
#define PercentileFun(T, data_reader, cnt_data, alpha) \
|
||||
{ \
|
||||
if (cnt_data <= 1) { \
|
||||
return data_reader(0); \
|
||||
} \
|
||||
std::vector<T> ref_data(cnt_data); \
|
||||
for (data_size_t i = 0; i < cnt_data; ++i) { \
|
||||
ref_data[i] = data_reader(i); \
|
||||
} \
|
||||
const double float_pos = static_cast<double>(cnt_data - 1) * (1.0 - alpha); \
|
||||
const data_size_t pos = static_cast<data_size_t>(float_pos) + 1; \
|
||||
if (pos < 1) { \
|
||||
return ref_data[ArrayArgs<T>::ArgMax(ref_data)]; \
|
||||
} else if (pos >= cnt_data) { \
|
||||
return ref_data[ArrayArgs<T>::ArgMin(ref_data)]; \
|
||||
} else { \
|
||||
const double bias = float_pos - (pos - 1); \
|
||||
if (pos > cnt_data / 2) { \
|
||||
ArrayArgs<T>::ArgMaxAtK(&ref_data, 0, cnt_data, pos - 1); \
|
||||
T v1 = ref_data[pos - 1]; \
|
||||
T v2 = ref_data[pos + ArrayArgs<T>::ArgMax(ref_data.data() + pos, \
|
||||
cnt_data - pos)]; \
|
||||
return static_cast<T>(v1 - (v1 - v2) * bias); \
|
||||
} else { \
|
||||
ArrayArgs<T>::ArgMaxAtK(&ref_data, 0, cnt_data, pos); \
|
||||
T v2 = ref_data[pos]; \
|
||||
T v1 = ref_data[ArrayArgs<T>::ArgMin(ref_data.data(), pos)]; \
|
||||
return static_cast<T>(v1 - (v1 - v2) * bias); \
|
||||
} \
|
||||
} \
|
||||
}\
|
||||
|
||||
#define WeightedPercentileFun(T, data_reader, weight_reader, cnt_data, alpha) \
|
||||
{ \
|
||||
if (cnt_data <= 1) { \
|
||||
return data_reader(0); \
|
||||
} \
|
||||
std::vector<data_size_t> sorted_idx(cnt_data); \
|
||||
for (data_size_t i = 0; i < cnt_data; ++i) { \
|
||||
sorted_idx[i] = i; \
|
||||
} \
|
||||
std::stable_sort(sorted_idx.begin(), sorted_idx.end(), \
|
||||
[&](data_size_t a, data_size_t b) { \
|
||||
return data_reader(a) < data_reader(b); \
|
||||
}); \
|
||||
std::vector<double> weighted_cdf(cnt_data); \
|
||||
weighted_cdf[0] = weight_reader(sorted_idx[0]); \
|
||||
for (data_size_t i = 1; i < cnt_data; ++i) { \
|
||||
weighted_cdf[i] = weighted_cdf[i - 1] + weight_reader(sorted_idx[i]); \
|
||||
} \
|
||||
double threshold = weighted_cdf[cnt_data - 1] * alpha; \
|
||||
size_t pos = std::upper_bound(weighted_cdf.begin(), weighted_cdf.end(), \
|
||||
threshold) - \
|
||||
weighted_cdf.begin(); \
|
||||
pos = std::min(pos, static_cast<size_t>(cnt_data - 1)); \
|
||||
if (pos == 0 || pos == static_cast<size_t>(cnt_data - 1)) { \
|
||||
return data_reader(sorted_idx[pos]); \
|
||||
} \
|
||||
CHECK_GE(threshold, weighted_cdf[pos - 1]); \
|
||||
CHECK_LT(threshold, weighted_cdf[pos]); \
|
||||
T v1 = data_reader(sorted_idx[pos - 1]); \
|
||||
T v2 = data_reader(sorted_idx[pos]); \
|
||||
if (weighted_cdf[pos] - weighted_cdf[pos - 1] >= 1.0) { \
|
||||
return static_cast<T>((threshold - weighted_cdf[pos - 1]) / \
|
||||
(weighted_cdf[pos] - weighted_cdf[pos - 1]) * \
|
||||
(v2 - v1) + \
|
||||
v1); \
|
||||
} else { \
|
||||
return static_cast<T>(v1); \
|
||||
} \
|
||||
}\
|
||||
|
||||
/*!
|
||||
* \brief Objective function for regression
|
||||
*/
|
||||
class RegressionL2loss: public ObjectiveFunction {
|
||||
public:
|
||||
explicit RegressionL2loss(const Config& config)
|
||||
: deterministic_(config.deterministic) {
|
||||
sqrt_ = config.reg_sqrt;
|
||||
}
|
||||
|
||||
explicit RegressionL2loss(const std::vector<std::string>& strs)
|
||||
: deterministic_(false) {
|
||||
sqrt_ = false;
|
||||
for (auto str : strs) {
|
||||
if (str == std::string("sqrt")) {
|
||||
sqrt_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~RegressionL2loss() {
|
||||
}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
if (sqrt_) {
|
||||
trans_label_.resize(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data; ++i) {
|
||||
trans_label_[i] = Common::Sign(label_[i]) * std::sqrt(std::fabs(label_[i]));
|
||||
}
|
||||
label_ = trans_label_.data();
|
||||
}
|
||||
weights_ = metadata.weights();
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
gradients[i] = static_cast<score_t>(score[i] - label_[i]);
|
||||
hessians[i] = 1.0f;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
gradients[i] = static_cast<score_t>(static_cast<score_t>((score[i] - label_[i])) * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>(weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "regression";
|
||||
}
|
||||
|
||||
void ConvertOutput(const double* input, double* output) const override {
|
||||
if (sqrt_) {
|
||||
output[0] = Common::Sign(input[0]) * input[0] * input[0];
|
||||
} else {
|
||||
output[0] = input[0];
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString() const override {
|
||||
std::stringstream str_buf;
|
||||
str_buf << GetName();
|
||||
if (sqrt_) {
|
||||
str_buf << " sqrt";
|
||||
}
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
bool IsConstantHessian() const override {
|
||||
if (weights_ == nullptr) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
double BoostFromScore(int) const override {
|
||||
double suml = 0.0f;
|
||||
double sumw = 0.0f;
|
||||
if (weights_ != nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += static_cast<double>(label_[i]) * weights_[i];
|
||||
sumw += weights_[i];
|
||||
}
|
||||
} else {
|
||||
sumw = static_cast<double>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += label_[i];
|
||||
}
|
||||
}
|
||||
return suml / sumw;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool sqrt_;
|
||||
/*! \brief Number of data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer of label */
|
||||
const label_t* label_;
|
||||
/*! \brief Pointer of weights */
|
||||
const label_t* weights_;
|
||||
std::vector<label_t> trans_label_;
|
||||
const bool deterministic_;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief L1 regression loss
|
||||
*/
|
||||
class RegressionL1loss: public RegressionL2loss {
|
||||
public:
|
||||
explicit RegressionL1loss(const Config& config): RegressionL2loss(config) {
|
||||
}
|
||||
|
||||
explicit RegressionL1loss(const std::vector<std::string>& strs): RegressionL2loss(strs) {
|
||||
}
|
||||
|
||||
~RegressionL1loss() {}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double diff = score[i] - label_[i];
|
||||
gradients[i] = static_cast<score_t>(Common::Sign(diff));
|
||||
hessians[i] = 1.0f;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double diff = score[i] - label_[i];
|
||||
gradients[i] = static_cast<score_t>(Common::Sign(diff) * weights_[i]);
|
||||
hessians[i] = weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double BoostFromScore(int) const override {
|
||||
const double alpha = 0.5;
|
||||
if (weights_ != nullptr) {
|
||||
#define data_reader(i) (label_[i])
|
||||
#define weight_reader(i) (weights_[i])
|
||||
WeightedPercentileFun(label_t, data_reader, weight_reader, num_data_, alpha);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
} else {
|
||||
#define data_reader(i) (label_[i])
|
||||
PercentileFun(label_t, data_reader, num_data_, alpha);
|
||||
#undef data_reader
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRenewTreeOutput() const override { return true; }
|
||||
|
||||
double RenewTreeOutput(double, std::function<double(const label_t*, int)> residual_getter,
|
||||
const data_size_t* index_mapper,
|
||||
const data_size_t* bagging_mapper,
|
||||
data_size_t num_data_in_leaf) const override {
|
||||
const double alpha = 0.5;
|
||||
if (weights_ == nullptr) {
|
||||
if (bagging_mapper == nullptr) {
|
||||
#define data_reader(i) (residual_getter(label_, index_mapper[i]))
|
||||
PercentileFun(double, data_reader, num_data_in_leaf, alpha);
|
||||
#undef data_reader
|
||||
} else {
|
||||
#define data_reader(i) (residual_getter(label_, bagging_mapper[index_mapper[i]]))
|
||||
PercentileFun(double, data_reader, num_data_in_leaf, alpha);
|
||||
#undef data_reader
|
||||
}
|
||||
} else {
|
||||
if (bagging_mapper == nullptr) {
|
||||
#define data_reader(i) (residual_getter(label_, index_mapper[i]))
|
||||
#define weight_reader(i) (weights_[index_mapper[i]])
|
||||
WeightedPercentileFun(double, data_reader, weight_reader, num_data_in_leaf, alpha);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
} else {
|
||||
#define data_reader(i) (residual_getter(label_, bagging_mapper[index_mapper[i]]))
|
||||
#define weight_reader(i) (weights_[bagging_mapper[index_mapper[i]]])
|
||||
WeightedPercentileFun(double, data_reader, weight_reader, num_data_in_leaf, alpha);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "regression_l1";
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Huber regression loss
|
||||
*/
|
||||
class RegressionHuberLoss: public RegressionL2loss {
|
||||
public:
|
||||
explicit RegressionHuberLoss(const Config& config): RegressionL2loss(config) {
|
||||
alpha_ = static_cast<double>(config.alpha);
|
||||
if (sqrt_) {
|
||||
Log::Warning("Cannot use sqrt transform in %s Regression, will auto disable it", GetName());
|
||||
sqrt_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
explicit RegressionHuberLoss(const std::vector<std::string>& strs): RegressionL2loss(strs) {
|
||||
if (sqrt_) {
|
||||
Log::Warning("Cannot use sqrt transform in %s Regression, will auto disable it", GetName());
|
||||
sqrt_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
~RegressionHuberLoss() {
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double diff = score[i] - label_[i];
|
||||
if (std::abs(diff) <= alpha_) {
|
||||
gradients[i] = static_cast<score_t>(diff);
|
||||
} else {
|
||||
gradients[i] = static_cast<score_t>(Common::Sign(diff) * alpha_);
|
||||
}
|
||||
hessians[i] = 1.0f;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double diff = score[i] - label_[i];
|
||||
if (std::abs(diff) <= alpha_) {
|
||||
gradients[i] = static_cast<score_t>(diff * weights_[i]);
|
||||
} else {
|
||||
gradients[i] = static_cast<score_t>(Common::Sign(diff) * static_cast<score_t>(weights_[i]) * alpha_);
|
||||
}
|
||||
hessians[i] = static_cast<score_t>(weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "huber";
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! \brief delta for Huber loss */
|
||||
double alpha_;
|
||||
};
|
||||
|
||||
|
||||
// http://research.microsoft.com/en-us/um/people/zhang/INRIA/Publis/Tutorial-Estim/node24.html
|
||||
class RegressionFairLoss: public RegressionL2loss {
|
||||
public:
|
||||
explicit RegressionFairLoss(const Config& config): RegressionL2loss(config) {
|
||||
c_ = static_cast<double>(config.fair_c);
|
||||
}
|
||||
|
||||
explicit RegressionFairLoss(const std::vector<std::string>& strs): RegressionL2loss(strs) {
|
||||
}
|
||||
|
||||
~RegressionFairLoss() {}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double x = score[i] - label_[i];
|
||||
gradients[i] = static_cast<score_t>(c_ * x / (std::fabs(x) + c_));
|
||||
hessians[i] = static_cast<score_t>(c_ * c_ / ((std::fabs(x) + c_) * (std::fabs(x) + c_)));
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double x = score[i] - label_[i];
|
||||
gradients[i] = static_cast<score_t>(c_ * x / (std::fabs(x) + c_) * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>(c_ * c_ / ((std::fabs(x) + c_) * (std::fabs(x) + c_)) * weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "fair";
|
||||
}
|
||||
|
||||
bool IsConstantHessian() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! \brief c for Fair loss */
|
||||
double c_;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Objective function for Poisson regression
|
||||
*/
|
||||
class RegressionPoissonLoss: public RegressionL2loss {
|
||||
public:
|
||||
explicit RegressionPoissonLoss(const Config& config): RegressionL2loss(config) {
|
||||
max_delta_step_ = static_cast<double>(config.poisson_max_delta_step);
|
||||
if (sqrt_) {
|
||||
Log::Warning("Cannot use sqrt transform in %s Regression, will auto disable it", GetName());
|
||||
sqrt_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
explicit RegressionPoissonLoss(const std::vector<std::string>& strs): RegressionL2loss(strs) {
|
||||
}
|
||||
|
||||
~RegressionPoissonLoss() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
if (sqrt_) {
|
||||
Log::Warning("Cannot use sqrt transform in %s Regression, will auto disable it", GetName());
|
||||
sqrt_ = false;
|
||||
}
|
||||
RegressionL2loss::Init(metadata, num_data);
|
||||
// Safety check of labels
|
||||
label_t miny;
|
||||
double sumy;
|
||||
Common::ObtainMinMaxSum(label_, num_data_, &miny, static_cast<label_t*>(nullptr), &sumy);
|
||||
if (miny < 0.0f) {
|
||||
Log::Fatal("[%s]: at least one target label is negative", GetName());
|
||||
}
|
||||
if (sumy == 0.0f) {
|
||||
Log::Fatal("[%s]: sum of labels is zero", GetName());
|
||||
}
|
||||
}
|
||||
|
||||
/* Parametrize with unbounded internal score "f"; then
|
||||
* loss = exp(f) - label * f
|
||||
* grad = exp(f) - label
|
||||
* hess = exp(f)
|
||||
*
|
||||
* And the output is exp(f); so the associated metric get s=exp(f)
|
||||
* so that its loss = s - label * log(s); a little awkward maybe.
|
||||
*
|
||||
*/
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
double exp_max_delta_step_ = std::exp(max_delta_step_);
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double exp_score = std::exp(score[i]);
|
||||
gradients[i] = static_cast<score_t>(exp_score - label_[i]);
|
||||
hessians[i] = static_cast<score_t>(exp_score * exp_max_delta_step_);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double exp_score = std::exp(score[i]);
|
||||
gradients[i] = static_cast<score_t>((exp_score - label_[i]) * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>(exp_score * exp_max_delta_step_ * weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertOutput(const double* input, double* output) const override {
|
||||
output[0] = std::exp(input[0]);
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "poisson";
|
||||
}
|
||||
|
||||
double BoostFromScore(int) const override {
|
||||
return Common::SafeLog(RegressionL2loss::BoostFromScore(0));
|
||||
}
|
||||
|
||||
bool IsConstantHessian() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! \brief used to safeguard optimization */
|
||||
double max_delta_step_;
|
||||
};
|
||||
|
||||
class RegressionQuantileloss : public RegressionL2loss {
|
||||
public:
|
||||
explicit RegressionQuantileloss(const Config& config): RegressionL2loss(config) {
|
||||
alpha_ = static_cast<score_t>(config.alpha);
|
||||
CHECK(alpha_ > 0 && alpha_ < 1);
|
||||
}
|
||||
|
||||
explicit RegressionQuantileloss(const std::vector<std::string>& strs): RegressionL2loss(strs) {
|
||||
}
|
||||
|
||||
~RegressionQuantileloss() {}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
score_t delta = static_cast<score_t>(score[i] - label_[i]);
|
||||
if (delta >= 0) {
|
||||
gradients[i] = (1.0f - alpha_);
|
||||
} else {
|
||||
gradients[i] = -alpha_;
|
||||
}
|
||||
hessians[i] = 1.0f;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
score_t delta = static_cast<score_t>(score[i] - label_[i]);
|
||||
if (delta >= 0) {
|
||||
gradients[i] = static_cast<score_t>((1.0f - alpha_) * weights_[i]);
|
||||
} else {
|
||||
gradients[i] = static_cast<score_t>(-alpha_ * weights_[i]);
|
||||
}
|
||||
hessians[i] = static_cast<score_t>(weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "quantile";
|
||||
}
|
||||
|
||||
double BoostFromScore(int) const override {
|
||||
if (weights_ != nullptr) {
|
||||
#define data_reader(i) (label_[i])
|
||||
#define weight_reader(i) (weights_[i])
|
||||
WeightedPercentileFun(label_t, data_reader, weight_reader, num_data_, alpha_);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
} else {
|
||||
#define data_reader(i) (label_[i])
|
||||
PercentileFun(label_t, data_reader, num_data_, alpha_);
|
||||
#undef data_reader
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRenewTreeOutput() const override { return true; }
|
||||
|
||||
double RenewTreeOutput(double, std::function<double(const label_t*, int)> residual_getter,
|
||||
const data_size_t* index_mapper,
|
||||
const data_size_t* bagging_mapper,
|
||||
data_size_t num_data_in_leaf) const override {
|
||||
if (weights_ == nullptr) {
|
||||
if (bagging_mapper == nullptr) {
|
||||
#define data_reader(i) (residual_getter(label_, index_mapper[i]))
|
||||
PercentileFun(double, data_reader, num_data_in_leaf, alpha_);
|
||||
#undef data_reader
|
||||
} else {
|
||||
#define data_reader(i) (residual_getter(label_, bagging_mapper[index_mapper[i]]))
|
||||
PercentileFun(double, data_reader, num_data_in_leaf, alpha_);
|
||||
#undef data_reader
|
||||
}
|
||||
} else {
|
||||
if (bagging_mapper == nullptr) {
|
||||
#define data_reader(i) (residual_getter(label_, index_mapper[i]))
|
||||
#define weight_reader(i) (weights_[index_mapper[i]])
|
||||
WeightedPercentileFun(double, data_reader, weight_reader, num_data_in_leaf, alpha_);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
} else {
|
||||
#define data_reader(i) (residual_getter(label_, bagging_mapper[index_mapper[i]]))
|
||||
#define weight_reader(i) (weights_[bagging_mapper[index_mapper[i]]])
|
||||
WeightedPercentileFun(double, data_reader, weight_reader, num_data_in_leaf, alpha_);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
score_t alpha_;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief MAPE Regression Loss
|
||||
*/
|
||||
class RegressionMAPELOSS : public RegressionL1loss {
|
||||
public:
|
||||
explicit RegressionMAPELOSS(const Config& config) : RegressionL1loss(config) {
|
||||
}
|
||||
|
||||
explicit RegressionMAPELOSS(const std::vector<std::string>& strs) : RegressionL1loss(strs) {
|
||||
}
|
||||
|
||||
~RegressionMAPELOSS() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
RegressionL2loss::Init(metadata, num_data);
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
if (std::fabs(label_[i]) < 1) {
|
||||
Log::Warning(
|
||||
"Some label values are < 1 in absolute value. MAPE is unstable with such values, "
|
||||
"so LightGBM rounds them to 1.0 when calculating MAPE.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
label_weight_.resize(num_data);
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
label_weight_[i] = 1.0f / std::max(1.0f, std::fabs(label_[i]));
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
label_weight_[i] = 1.0f / std::max(1.0f, std::fabs(label_[i])) * weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double diff = score[i] - label_[i];
|
||||
gradients[i] = static_cast<score_t>(Common::Sign(diff) * label_weight_[i]);
|
||||
hessians[i] = 1.0f;
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double diff = score[i] - label_[i];
|
||||
gradients[i] = static_cast<score_t>(Common::Sign(diff) * label_weight_[i]);
|
||||
hessians[i] = weights_[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double BoostFromScore(int) const override {
|
||||
const double alpha = 0.5;
|
||||
#define data_reader(i) (label_[i])
|
||||
#define weight_reader(i) (label_weight_[i])
|
||||
WeightedPercentileFun(label_t, data_reader, weight_reader, num_data_, alpha);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
}
|
||||
|
||||
bool IsRenewTreeOutput() const override { return true; }
|
||||
|
||||
double RenewTreeOutput(double, std::function<double(const label_t*, int)> residual_getter,
|
||||
const data_size_t* index_mapper,
|
||||
const data_size_t* bagging_mapper,
|
||||
data_size_t num_data_in_leaf) const override {
|
||||
const double alpha = 0.5;
|
||||
if (bagging_mapper == nullptr) {
|
||||
#define data_reader(i) (residual_getter(label_, index_mapper[i]))
|
||||
#define weight_reader(i) (label_weight_[index_mapper[i]])
|
||||
WeightedPercentileFun(double, data_reader, weight_reader, num_data_in_leaf, alpha);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
} else {
|
||||
#define data_reader(i) (residual_getter(label_, bagging_mapper[index_mapper[i]]))
|
||||
#define weight_reader(i) (label_weight_[bagging_mapper[index_mapper[i]]])
|
||||
WeightedPercentileFun(double, data_reader, weight_reader, num_data_in_leaf, alpha);
|
||||
#undef data_reader
|
||||
#undef weight_reader
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "mape";
|
||||
}
|
||||
|
||||
bool IsConstantHessian() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<label_t> label_weight_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*!
|
||||
* \brief Objective function for Gamma regression
|
||||
*/
|
||||
class RegressionGammaLoss : public RegressionPoissonLoss {
|
||||
public:
|
||||
explicit RegressionGammaLoss(const Config& config) : RegressionPoissonLoss(config) {
|
||||
}
|
||||
|
||||
explicit RegressionGammaLoss(const std::vector<std::string>& strs) : RegressionPoissonLoss(strs) {
|
||||
}
|
||||
|
||||
~RegressionGammaLoss() {}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double exp_score = std::exp(-score[i]);
|
||||
gradients[i] = static_cast<score_t>(1.0 - label_[i] * exp_score);
|
||||
hessians[i] = static_cast<score_t>(label_[i] * exp_score);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double exp_score = std::exp(-score[i]);
|
||||
gradients[i] = static_cast<score_t>((1.0 - label_[i] * exp_score) * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>(label_[i] * exp_score * weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "gamma";
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Objective function for Tweedie regression
|
||||
*/
|
||||
class RegressionTweedieLoss: public RegressionPoissonLoss {
|
||||
public:
|
||||
explicit RegressionTweedieLoss(const Config& config) : RegressionPoissonLoss(config) {
|
||||
rho_ = config.tweedie_variance_power;
|
||||
}
|
||||
|
||||
explicit RegressionTweedieLoss(const std::vector<std::string>& strs) : RegressionPoissonLoss(strs) {
|
||||
}
|
||||
|
||||
~RegressionTweedieLoss() {}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients,
|
||||
score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double exp_1_score = std::exp((1 - rho_) * score[i]);
|
||||
double exp_2_score = std::exp((2 - rho_) * score[i]);
|
||||
gradients[i] = static_cast<score_t>(-label_[i] * exp_1_score + exp_2_score);
|
||||
hessians[i] = static_cast<score_t>(-label_[i] * (1 - rho_) * exp_1_score +
|
||||
(2 - rho_) * exp_2_score);
|
||||
}
|
||||
} else {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
double exp_1_score = std::exp((1 - rho_) * score[i]);
|
||||
double exp_2_score = std::exp((2 - rho_) * score[i]);
|
||||
gradients[i] = static_cast<score_t>((-label_[i] * exp_1_score + exp_2_score) * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>((-label_[i] * (1 - rho_) * exp_1_score +
|
||||
(2 - rho_) * exp_2_score) * weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "tweedie";
|
||||
}
|
||||
|
||||
private:
|
||||
double rho_;
|
||||
};
|
||||
|
||||
#undef PercentileFun
|
||||
#undef WeightedPercentileFun
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_REGRESSION_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,317 @@
|
||||
/*!
|
||||
* Copyright (c) 2017-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2017-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_OBJECTIVE_XENTROPY_OBJECTIVE_HPP_
|
||||
#define LIGHTGBM_SRC_OBJECTIVE_XENTROPY_OBJECTIVE_HPP_
|
||||
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/objective_function.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* Implements gradients and Hessians for the following point losses.
|
||||
* Target y is anything in interval [0, 1].
|
||||
*
|
||||
* (1) CrossEntropy; "xentropy";
|
||||
*
|
||||
* loss(y, p, w) = { -(1-y)*log(1-p)-y*log(p) }*w,
|
||||
* with probability p = 1/(1+exp(-f)), where f is being boosted
|
||||
*
|
||||
* ConvertToOutput: f -> p
|
||||
*
|
||||
* (2) CrossEntropyLambda; "xentlambda"
|
||||
*
|
||||
* loss(y, p, w) = -(1-y)*log(1-p)-y*log(p),
|
||||
* with p = 1-exp(-lambda*w), lambda = log(1+exp(f)), f being boosted, and w > 0
|
||||
*
|
||||
* ConvertToOutput: f -> lambda
|
||||
*
|
||||
* (1) and (2) are the same if w=1; but outputs still differ.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace LightGBM {
|
||||
/*!
|
||||
* \brief Objective function for cross-entropy (with optional linear weights)
|
||||
*/
|
||||
class CrossEntropy: public ObjectiveFunction {
|
||||
public:
|
||||
explicit CrossEntropy(const Config& config)
|
||||
: deterministic_(config.deterministic) {}
|
||||
|
||||
explicit CrossEntropy(const std::vector<std::string>&)
|
||||
: deterministic_(false) {
|
||||
}
|
||||
|
||||
~CrossEntropy() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
|
||||
CHECK_NOTNULL(label_);
|
||||
Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName());
|
||||
Log::Info("[%s:%s]: (objective) labels passed interval [0, 1] check", GetName(), __func__);
|
||||
|
||||
if (weights_ != nullptr) {
|
||||
label_t minw;
|
||||
double sumw;
|
||||
Common::ObtainMinMaxSum(weights_, num_data_, &minw, static_cast<label_t*>(nullptr), &sumw);
|
||||
if (minw < 0.0f) {
|
||||
Log::Fatal("[%s]: at least one weight is negative", GetName());
|
||||
}
|
||||
if (sumw == 0.0f) {
|
||||
Log::Fatal("[%s]: sum of weights is zero", GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
|
||||
// z = expit(score) = 1 / (1 + exp(-score))
|
||||
// gradient = z - label = expit(score) - label
|
||||
// Numerically more stable, see http://fa.bianp.net/blog/2019/evaluate_logistic/
|
||||
// if score < 0:
|
||||
// exp_tmp = exp(score)
|
||||
// return ((1 - label) * exp_tmp - label) / (1 + exp_tmp)
|
||||
// else:
|
||||
// exp_tmp = exp(-score)
|
||||
// return ((1 - label) - label * exp_tmp) / (1 + exp_tmp)
|
||||
// Note that optimal speed would be achieved, at the cost of precision, by
|
||||
// return expit(score) - y_true
|
||||
// i.e. no "if else" and an own inline implementation of expit.
|
||||
// The case distinction score < 0 in the stable implementation does not
|
||||
// provide significant better precision apart from protecting overflow of exp(..).
|
||||
// The branch (if else), however, can incur runtime costs of up to 30%.
|
||||
// Instead, we help branch prediction by almost always ending in the first if clause
|
||||
// and making the second branch (else) a bit simpler. This has the exact same
|
||||
// precision but is faster than the stable implementation.
|
||||
// As branching criteria, we use the same cutoff as in log1pexp, see link above.
|
||||
// Note that the maximal value to get gradient = -1 with label = 1 is -37.439198610162731
|
||||
// (based on mpmath), and scipy.special.logit(np.finfo(float).eps) ~ -36.04365.
|
||||
if (weights_ == nullptr) {
|
||||
// compute pointwise gradients and Hessians with implied unit weights
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
if (score[i] > -37.0) {
|
||||
const double exp_tmp = std::exp(-score[i]);
|
||||
gradients[i] = static_cast<score_t>(((1.0f - label_[i]) - label_[i] * exp_tmp) / (1.0f + exp_tmp));
|
||||
hessians[i] = static_cast<score_t>(exp_tmp / ((1 + exp_tmp) * (1 + exp_tmp)));
|
||||
} else {
|
||||
const double exp_tmp = std::exp(score[i]);
|
||||
gradients[i] = static_cast<score_t>(exp_tmp - label_[i]);
|
||||
hessians[i] = static_cast<score_t>(exp_tmp);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// compute pointwise gradients and Hessians with given weights
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
if (score[i] > -37.0) {
|
||||
const double exp_tmp = std::exp(-score[i]);
|
||||
gradients[i] = static_cast<score_t>(((1.0f - label_[i]) - label_[i] * exp_tmp) / (1.0f + exp_tmp) * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>(exp_tmp / ((1 + exp_tmp) * (1 + exp_tmp)) * weights_[i]);
|
||||
} else {
|
||||
const double exp_tmp = std::exp(score[i]);
|
||||
gradients[i] = static_cast<score_t>((exp_tmp - label_[i]) * weights_[i]);
|
||||
hessians[i] = static_cast<score_t>(exp_tmp * weights_[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "cross_entropy";
|
||||
}
|
||||
|
||||
// convert score to a probability
|
||||
void ConvertOutput(const double* input, double* output) const override {
|
||||
output[0] = 1.0f / (1.0f + std::exp(-input[0]));
|
||||
}
|
||||
|
||||
std::string ToString() const override {
|
||||
std::stringstream str_buf;
|
||||
str_buf << GetName();
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
// implement custom average to boost from (if enabled among options)
|
||||
double BoostFromScore(int) const override {
|
||||
double suml = 0.0f;
|
||||
double sumw = 0.0f;
|
||||
if (weights_ != nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_)
|
||||
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += static_cast<double>(label_[i]) * weights_[i];
|
||||
sumw += weights_[i];
|
||||
}
|
||||
} else {
|
||||
sumw = static_cast<double>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_)
|
||||
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += label_[i];
|
||||
}
|
||||
}
|
||||
double pavg = suml / sumw;
|
||||
pavg = std::min(pavg, 1.0 - kEpsilon);
|
||||
pavg = std::max<double>(pavg, kEpsilon);
|
||||
double initscore = std::log(pavg / (1.0f - pavg));
|
||||
Log::Info("[%s:%s]: pavg = %f -> initscore = %f", GetName(), __func__, pavg, initscore);
|
||||
return initscore;
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data points */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer for label */
|
||||
const label_t* label_;
|
||||
/*! \brief Weights for data */
|
||||
const label_t* weights_;
|
||||
const bool deterministic_;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Objective function for alternative parameterization of cross-entropy (see top of file for explanation)
|
||||
*/
|
||||
class CrossEntropyLambda: public ObjectiveFunction {
|
||||
public:
|
||||
explicit CrossEntropyLambda(const Config& config)
|
||||
: deterministic_(config.deterministic) {
|
||||
min_weight_ = max_weight_ = 0.0f;
|
||||
}
|
||||
|
||||
explicit CrossEntropyLambda(const std::vector<std::string>&)
|
||||
: deterministic_(false) {}
|
||||
|
||||
~CrossEntropyLambda() {}
|
||||
|
||||
void Init(const Metadata& metadata, data_size_t num_data) override {
|
||||
num_data_ = num_data;
|
||||
label_ = metadata.label();
|
||||
weights_ = metadata.weights();
|
||||
|
||||
CHECK_NOTNULL(label_);
|
||||
Common::CheckElementsIntervalClosed<label_t>(label_, 0.0f, 1.0f, num_data_, GetName());
|
||||
Log::Info("[%s:%s]: (objective) labels passed interval [0, 1] check", GetName(), __func__);
|
||||
|
||||
if (weights_ != nullptr) {
|
||||
Common::ObtainMinMaxSum(weights_, num_data_, &min_weight_, &max_weight_, static_cast<label_t*>(nullptr));
|
||||
if (min_weight_ <= 0.0f) {
|
||||
Log::Fatal("[%s]: at least one weight is non-positive", GetName());
|
||||
}
|
||||
|
||||
// Issue an info statement about this ratio
|
||||
double weight_ratio = max_weight_ / min_weight_;
|
||||
Log::Info("[%s:%s]: min, max weights = %f, %f; ratio = %f",
|
||||
GetName(), __func__,
|
||||
min_weight_, max_weight_,
|
||||
weight_ratio);
|
||||
} else {
|
||||
// all weights are implied to be unity; no need to do anything
|
||||
}
|
||||
}
|
||||
|
||||
void GetGradients(const double* score, score_t* gradients, score_t* hessians) const override {
|
||||
if (weights_ == nullptr) {
|
||||
// compute pointwise gradients and Hessians with implied unit weights; exactly equivalent to CrossEntropy with unit weights
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double z = 1.0f / (1.0f + std::exp(-score[i]));
|
||||
gradients[i] = static_cast<score_t>(z - label_[i]);
|
||||
hessians[i] = static_cast<score_t>(z * (1.0f - z));
|
||||
}
|
||||
} else {
|
||||
// compute pointwise gradients and Hessians with given weights
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static)
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
const double w = weights_[i];
|
||||
const double y = label_[i];
|
||||
const double epf = std::exp(score[i]);
|
||||
const double hhat = std::log1p(epf);
|
||||
const double z = 1.0f - std::exp(-w*hhat);
|
||||
const double enf = 1.0f / epf; // = std::exp(-score[i]);
|
||||
gradients[i] = static_cast<score_t>((1.0f - y / z) * w / (1.0f + enf));
|
||||
const double c = 1.0f / (1.0f - z);
|
||||
double d = 1.0f + epf;
|
||||
const double a = w * epf / (d * d);
|
||||
d = c - 1.0f;
|
||||
const double b = (c / (d * d) ) * (1.0f + w * epf - c);
|
||||
hessians[i] = static_cast<score_t>(a * (1.0f + y * b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetName() const override {
|
||||
return "cross_entropy_lambda";
|
||||
}
|
||||
|
||||
//
|
||||
// ATTENTION: the function output is the "normalized exponential parameter" lambda > 0, not the probability
|
||||
//
|
||||
// If this code would read: output[0] = 1.0f / (1.0f + std::exp(-input[0]));
|
||||
// The output would still not be the probability unless the weights are unity.
|
||||
//
|
||||
// Let z = 1 / (1 + exp(-f)), then prob(z) = 1-(1-z)^w, where w is the weight for the specific point.
|
||||
//
|
||||
|
||||
void ConvertOutput(const double* input, double* output) const override {
|
||||
output[0] = std::log1p(std::exp(input[0]));
|
||||
}
|
||||
|
||||
std::string ToString() const override {
|
||||
std::stringstream str_buf;
|
||||
str_buf << GetName();
|
||||
return str_buf.str();
|
||||
}
|
||||
|
||||
double BoostFromScore(int) const override {
|
||||
double suml = 0.0f;
|
||||
double sumw = 0.0f;
|
||||
if (weights_ != nullptr) {
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml, sumw) if (!deterministic_)
|
||||
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += static_cast<double>(label_[i]) * weights_[i];
|
||||
sumw += weights_[i];
|
||||
}
|
||||
} else {
|
||||
sumw = static_cast<double>(num_data_);
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static) reduction(+:suml) if (!deterministic_)
|
||||
|
||||
for (data_size_t i = 0; i < num_data_; ++i) {
|
||||
suml += label_[i];
|
||||
}
|
||||
}
|
||||
double havg = suml / sumw;
|
||||
double initscore = std::log(std::expm1(havg));
|
||||
Log::Info("[%s:%s]: havg = %f -> initscore = %f", GetName(), __func__, havg, initscore);
|
||||
return initscore;
|
||||
}
|
||||
|
||||
private:
|
||||
/*! \brief Number of data points */
|
||||
data_size_t num_data_;
|
||||
/*! \brief Pointer for label */
|
||||
const label_t* label_;
|
||||
/*! \brief Weights for data */
|
||||
const label_t* weights_;
|
||||
/*! \brief Minimum weight found during init */
|
||||
label_t min_weight_;
|
||||
/*! \brief Maximum weight found during init */
|
||||
label_t max_weight_;
|
||||
const bool deterministic_;
|
||||
};
|
||||
|
||||
} // end namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_OBJECTIVE_XENTROPY_OBJECTIVE_HPP_
|
||||
@@ -0,0 +1,208 @@
|
||||
/*!
|
||||
* Copyright (c) 2020-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2020-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_TREELEARNER_COL_SAMPLER_HPP_
|
||||
#define LIGHTGBM_SRC_TREELEARNER_COL_SAMPLER_HPP_
|
||||
|
||||
#include <LightGBM/dataset.h>
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/openmp_wrapper.h>
|
||||
#include <LightGBM/utils/random.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
class ColSampler {
|
||||
public:
|
||||
explicit ColSampler(const Config* config)
|
||||
: fraction_bytree_(config->feature_fraction),
|
||||
fraction_bynode_(config->feature_fraction_bynode),
|
||||
seed_(config->feature_fraction_seed),
|
||||
random_(config->feature_fraction_seed) {
|
||||
for (auto constraint : config->interaction_constraints_vector) {
|
||||
std::unordered_set<int> constraint_set(constraint.begin(), constraint.end());
|
||||
interaction_constraints_.push_back(constraint_set);
|
||||
}
|
||||
}
|
||||
|
||||
static int GetCnt(size_t total_cnt, double fraction) {
|
||||
const int min = std::min(1, static_cast<int>(total_cnt));
|
||||
int used_feature_cnt = static_cast<int>(Common::RoundInt(total_cnt * fraction));
|
||||
return std::max(used_feature_cnt, min);
|
||||
}
|
||||
|
||||
void SetTrainingData(const Dataset* train_data) {
|
||||
train_data_ = train_data;
|
||||
is_feature_used_.resize(train_data_->num_features(), 1);
|
||||
valid_feature_indices_ = train_data->ValidFeatureIndices();
|
||||
if (fraction_bytree_ >= 1.0f) {
|
||||
need_reset_bytree_ = false;
|
||||
used_cnt_bytree_ = static_cast<int>(valid_feature_indices_.size());
|
||||
} else {
|
||||
need_reset_bytree_ = true;
|
||||
used_cnt_bytree_ =
|
||||
GetCnt(valid_feature_indices_.size(), fraction_bytree_);
|
||||
}
|
||||
ResetByTree();
|
||||
}
|
||||
|
||||
void SetConfig(const Config* config) {
|
||||
fraction_bytree_ = config->feature_fraction;
|
||||
fraction_bynode_ = config->feature_fraction_bynode;
|
||||
is_feature_used_.resize(train_data_->num_features(), 1);
|
||||
// seed is changed
|
||||
if (seed_ != config->feature_fraction_seed) {
|
||||
seed_ = config->feature_fraction_seed;
|
||||
random_ = Random(seed_);
|
||||
}
|
||||
if (fraction_bytree_ >= 1.0f) {
|
||||
need_reset_bytree_ = false;
|
||||
used_cnt_bytree_ = static_cast<int>(valid_feature_indices_.size());
|
||||
} else {
|
||||
need_reset_bytree_ = true;
|
||||
used_cnt_bytree_ =
|
||||
GetCnt(valid_feature_indices_.size(), fraction_bytree_);
|
||||
}
|
||||
ResetByTree();
|
||||
}
|
||||
|
||||
void ResetByTree() {
|
||||
if (need_reset_bytree_) {
|
||||
std::memset(is_feature_used_.data(), 0,
|
||||
sizeof(int8_t) * is_feature_used_.size());
|
||||
used_feature_indices_ = random_.Sample(
|
||||
static_cast<int>(valid_feature_indices_.size()), used_cnt_bytree_);
|
||||
int omp_loop_size = static_cast<int>(used_feature_indices_.size());
|
||||
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024)
|
||||
for (int i = 0; i < omp_loop_size; ++i) {
|
||||
int used_feature = valid_feature_indices_[used_feature_indices_[i]];
|
||||
int inner_feature_index = train_data_->InnerFeatureIndex(used_feature);
|
||||
is_feature_used_[inner_feature_index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int8_t> GetByNode(const Tree* tree, int leaf) {
|
||||
// get interaction constraints for current branch
|
||||
std::unordered_set<int> allowed_features;
|
||||
if (!interaction_constraints_.empty()) {
|
||||
std::vector<int> branch_features = tree->branch_features(leaf);
|
||||
allowed_features.insert(branch_features.begin(), branch_features.end());
|
||||
for (auto constraint : interaction_constraints_) {
|
||||
int num_feat_found = 0;
|
||||
if (branch_features.size() == 0) {
|
||||
allowed_features.insert(constraint.begin(), constraint.end());
|
||||
}
|
||||
for (int feat : branch_features) {
|
||||
if (constraint.count(feat) == 0) {
|
||||
break;
|
||||
}
|
||||
++num_feat_found;
|
||||
if (num_feat_found == static_cast<int>(branch_features.size())) {
|
||||
allowed_features.insert(constraint.begin(), constraint.end());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int8_t> ret(train_data_->num_features(), 0);
|
||||
if (fraction_bynode_ >= 1.0f) {
|
||||
if (interaction_constraints_.empty()) {
|
||||
return std::vector<int8_t>(train_data_->num_features(), 1);
|
||||
} else {
|
||||
for (int feat : allowed_features) {
|
||||
int inner_feat = train_data_->InnerFeatureIndex(feat);
|
||||
if (inner_feat >= 0) {
|
||||
ret[inner_feat] = 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (need_reset_bytree_) {
|
||||
auto used_feature_cnt = GetCnt(used_feature_indices_.size(), fraction_bynode_);
|
||||
std::vector<int>* allowed_used_feature_indices;
|
||||
std::vector<int> filtered_feature_indices;
|
||||
if (interaction_constraints_.empty()) {
|
||||
allowed_used_feature_indices = &used_feature_indices_;
|
||||
} else {
|
||||
for (int feat_ind : used_feature_indices_) {
|
||||
if (allowed_features.count(valid_feature_indices_[feat_ind]) == 1) {
|
||||
filtered_feature_indices.push_back(feat_ind);
|
||||
}
|
||||
}
|
||||
used_feature_cnt = std::min(used_feature_cnt, static_cast<int>(filtered_feature_indices.size()));
|
||||
allowed_used_feature_indices = &filtered_feature_indices;
|
||||
}
|
||||
auto sampled_indices = random_.Sample(
|
||||
static_cast<int>((*allowed_used_feature_indices).size()), used_feature_cnt);
|
||||
int omp_loop_size = static_cast<int>(sampled_indices.size());
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024)
|
||||
for (int i = 0; i < omp_loop_size; ++i) {
|
||||
int used_feature =
|
||||
valid_feature_indices_[(*allowed_used_feature_indices)[sampled_indices[i]]];
|
||||
int inner_feature_index = train_data_->InnerFeatureIndex(used_feature);
|
||||
ret[inner_feature_index] = 1;
|
||||
}
|
||||
} else {
|
||||
auto used_feature_cnt =
|
||||
GetCnt(valid_feature_indices_.size(), fraction_bynode_);
|
||||
std::vector<int>* allowed_valid_feature_indices;
|
||||
std::vector<int> filtered_feature_indices;
|
||||
if (interaction_constraints_.empty()) {
|
||||
allowed_valid_feature_indices = &valid_feature_indices_;
|
||||
} else {
|
||||
for (int feat : valid_feature_indices_) {
|
||||
if (allowed_features.count(feat) == 1) {
|
||||
filtered_feature_indices.push_back(feat);
|
||||
}
|
||||
}
|
||||
allowed_valid_feature_indices = &filtered_feature_indices;
|
||||
used_feature_cnt = std::min(used_feature_cnt, static_cast<int>(filtered_feature_indices.size()));
|
||||
}
|
||||
auto sampled_indices = random_.Sample(
|
||||
static_cast<int>((*allowed_valid_feature_indices).size()), used_feature_cnt);
|
||||
int omp_loop_size = static_cast<int>(sampled_indices.size());
|
||||
#pragma omp parallel for num_threads(OMP_NUM_THREADS()) schedule(static, 512) if (omp_loop_size >= 1024)
|
||||
for (int i = 0; i < omp_loop_size; ++i) {
|
||||
int used_feature = (*allowed_valid_feature_indices)[sampled_indices[i]];
|
||||
int inner_feature_index = train_data_->InnerFeatureIndex(used_feature);
|
||||
ret[inner_feature_index] = 1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<int8_t>& is_feature_used_bytree() const {
|
||||
return is_feature_used_;
|
||||
}
|
||||
|
||||
void SetIsFeatureUsedByTree(int fid, bool val) {
|
||||
is_feature_used_[fid] = val;
|
||||
}
|
||||
|
||||
private:
|
||||
const Dataset* train_data_;
|
||||
double fraction_bytree_;
|
||||
double fraction_bynode_;
|
||||
bool need_reset_bytree_;
|
||||
int used_cnt_bytree_;
|
||||
int seed_;
|
||||
Random random_;
|
||||
std::vector<int8_t> is_feature_used_;
|
||||
std::vector<int> used_feature_indices_;
|
||||
std::vector<int> valid_feature_indices_;
|
||||
/*! \brief interaction constraints index in original (raw data) features */
|
||||
std::vector<std::unordered_set<int>> interaction_constraints_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
#endif // LIGHTGBM_SRC_TREELEARNER_COL_SAMPLER_HPP_
|
||||
@@ -0,0 +1,175 @@
|
||||
/*!
|
||||
* Copyright (c) 2019-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2019-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_TREELEARNER_COST_EFFECTIVE_GRADIENT_BOOSTING_HPP_
|
||||
#define LIGHTGBM_SRC_TREELEARNER_COST_EFFECTIVE_GRADIENT_BOOSTING_HPP_
|
||||
|
||||
#include <LightGBM/config.h>
|
||||
#include <LightGBM/dataset.h>
|
||||
#include <LightGBM/utils/common.h>
|
||||
#include <LightGBM/utils/log.h>
|
||||
#include <LightGBM/utils/threading.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "data_partition.hpp"
|
||||
#include "serial_tree_learner.h"
|
||||
#include "split_info.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class CostEfficientGradientBoosting {
|
||||
public:
|
||||
explicit CostEfficientGradientBoosting(const SerialTreeLearner* tree_learner)
|
||||
: init_(false), tree_learner_(tree_learner) {}
|
||||
static bool IsEnable(const Config* config) {
|
||||
if (config->cegb_tradeoff >= 1.0f && config->cegb_penalty_split <= 0.0f &&
|
||||
config->cegb_penalty_feature_coupled.empty() &&
|
||||
config->cegb_penalty_feature_lazy.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Init() {
|
||||
auto train_data = tree_learner_->train_data_;
|
||||
if (!init_) {
|
||||
splits_per_leaf_.resize(
|
||||
static_cast<size_t>(tree_learner_->config_->num_leaves) *
|
||||
train_data->num_features());
|
||||
is_feature_used_in_split_.clear();
|
||||
is_feature_used_in_split_.resize(train_data->num_features());
|
||||
}
|
||||
|
||||
if (!tree_learner_->config_->cegb_penalty_feature_coupled.empty() &&
|
||||
tree_learner_->config_->cegb_penalty_feature_coupled.size() !=
|
||||
static_cast<size_t>(train_data->num_total_features())) {
|
||||
Log::Fatal(
|
||||
"cegb_penalty_feature_coupled should be the same size as feature "
|
||||
"number.");
|
||||
}
|
||||
if (!tree_learner_->config_->cegb_penalty_feature_lazy.empty()) {
|
||||
if (tree_learner_->config_->cegb_penalty_feature_lazy.size() !=
|
||||
static_cast<size_t>(train_data->num_total_features())) {
|
||||
Log::Fatal(
|
||||
"cegb_penalty_feature_lazy should be the same size as feature "
|
||||
"number.");
|
||||
}
|
||||
if (!init_) {
|
||||
feature_used_in_data_ = Common::EmptyBitset(train_data->num_features() *
|
||||
tree_learner_->num_data_);
|
||||
}
|
||||
}
|
||||
init_ = true;
|
||||
}
|
||||
|
||||
void BeforeTrain() {
|
||||
// clear the splits in splits_per_leaf_
|
||||
Threading::For<size_t>(0, splits_per_leaf_.size(), 1024,
|
||||
[this] (int /*thread_index*/, size_t start, size_t end) {
|
||||
for (size_t i = start; i < end; ++i) {
|
||||
splits_per_leaf_[i].Reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
double DeltaGain(int feature_index, int real_fidx, int leaf_index,
|
||||
int num_data_in_leaf, SplitInfo split_info) {
|
||||
auto config = tree_learner_->config_;
|
||||
double delta =
|
||||
config->cegb_tradeoff * config->cegb_penalty_split * num_data_in_leaf;
|
||||
if (!config->cegb_penalty_feature_coupled.empty() &&
|
||||
!is_feature_used_in_split_[feature_index]) {
|
||||
delta += config->cegb_tradeoff *
|
||||
config->cegb_penalty_feature_coupled[real_fidx];
|
||||
}
|
||||
if (!config->cegb_penalty_feature_lazy.empty()) {
|
||||
delta += config->cegb_tradeoff *
|
||||
CalculateOndemandCosts(feature_index, real_fidx, leaf_index);
|
||||
}
|
||||
splits_per_leaf_[static_cast<size_t>(leaf_index) *
|
||||
tree_learner_->train_data_->num_features() +
|
||||
feature_index] = split_info;
|
||||
return delta;
|
||||
}
|
||||
|
||||
void UpdateLeafBestSplits(Tree* tree, int best_leaf,
|
||||
const SplitInfo* best_split_info,
|
||||
std::vector<SplitInfo>* best_split_per_leaf) {
|
||||
auto config = tree_learner_->config_;
|
||||
auto train_data = tree_learner_->train_data_;
|
||||
const int inner_feature_index =
|
||||
train_data->InnerFeatureIndex(best_split_info->feature);
|
||||
auto& ref_best_split_per_leaf = *best_split_per_leaf;
|
||||
if (!config->cegb_penalty_feature_coupled.empty() &&
|
||||
!is_feature_used_in_split_[inner_feature_index]) {
|
||||
is_feature_used_in_split_[inner_feature_index] = true;
|
||||
for (int i = 0; i < tree->num_leaves(); ++i) {
|
||||
if (i == best_leaf) continue;
|
||||
auto split = &splits_per_leaf_[static_cast<size_t>(i) *
|
||||
train_data->num_features() +
|
||||
inner_feature_index];
|
||||
split->gain +=
|
||||
config->cegb_tradeoff *
|
||||
config->cegb_penalty_feature_coupled[best_split_info->feature];
|
||||
// Avoid to update the leaf that cannot split
|
||||
if (ref_best_split_per_leaf[i].gain > kMinScore &&
|
||||
*split > ref_best_split_per_leaf[i]) {
|
||||
ref_best_split_per_leaf[i] = *split;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!config->cegb_penalty_feature_lazy.empty()) {
|
||||
data_size_t cnt_leaf_data = 0;
|
||||
auto tmp_idx = tree_learner_->data_partition_->GetIndexOnLeaf(
|
||||
best_leaf, &cnt_leaf_data);
|
||||
for (data_size_t i_input = 0; i_input < cnt_leaf_data; ++i_input) {
|
||||
int real_idx = tmp_idx[i_input];
|
||||
Common::InsertBitset(
|
||||
&feature_used_in_data_,
|
||||
train_data->num_data() * inner_feature_index + real_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
double CalculateOndemandCosts(int feature_index, int real_fidx,
|
||||
int leaf_index) const {
|
||||
if (tree_learner_->config_->cegb_penalty_feature_lazy.empty()) {
|
||||
return 0.0f;
|
||||
}
|
||||
auto train_data = tree_learner_->train_data_;
|
||||
double penalty =
|
||||
tree_learner_->config_->cegb_penalty_feature_lazy[real_fidx];
|
||||
|
||||
double total = 0.0f;
|
||||
data_size_t cnt_leaf_data = 0;
|
||||
auto tmp_idx = tree_learner_->data_partition_->GetIndexOnLeaf(
|
||||
leaf_index, &cnt_leaf_data);
|
||||
|
||||
for (data_size_t i_input = 0; i_input < cnt_leaf_data; ++i_input) {
|
||||
int real_idx = tmp_idx[i_input];
|
||||
if (Common::FindInBitset(
|
||||
feature_used_in_data_.data(),
|
||||
train_data->num_data() * train_data->num_features(),
|
||||
train_data->num_data() * feature_index + real_idx)) {
|
||||
continue;
|
||||
}
|
||||
total += penalty;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
bool init_;
|
||||
const SerialTreeLearner* tree_learner_;
|
||||
std::vector<SplitInfo> splits_per_leaf_;
|
||||
std::vector<bool> is_feature_used_in_split_;
|
||||
std::vector<uint32_t> feature_used_in_data_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // LIGHTGBM_SRC_TREELEARNER_COST_EFFECTIVE_GRADIENT_BOOSTING_HPP_
|
||||
@@ -0,0 +1,380 @@
|
||||
/*!
|
||||
* Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_best_split_finder.hpp"
|
||||
#include "cuda_leaf_splits.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDABestSplitFinder::CUDABestSplitFinder(
|
||||
const hist_t* cuda_hist,
|
||||
const Dataset* train_data,
|
||||
const std::vector<uint32_t>& feature_hist_offsets,
|
||||
const bool select_features_by_node,
|
||||
const Config* config):
|
||||
num_features_(train_data->num_features()),
|
||||
num_leaves_(config->num_leaves),
|
||||
feature_hist_offsets_(feature_hist_offsets),
|
||||
lambda_l1_(config->lambda_l1),
|
||||
lambda_l2_(config->lambda_l2),
|
||||
min_data_in_leaf_(config->min_data_in_leaf),
|
||||
min_sum_hessian_in_leaf_(config->min_sum_hessian_in_leaf),
|
||||
min_gain_to_split_(config->min_gain_to_split),
|
||||
cat_smooth_(config->cat_smooth),
|
||||
cat_l2_(config->cat_l2),
|
||||
max_cat_threshold_(config->max_cat_threshold),
|
||||
min_data_per_group_(config->min_data_per_group),
|
||||
max_cat_to_onehot_(config->max_cat_to_onehot),
|
||||
extra_trees_(config->extra_trees),
|
||||
extra_seed_(config->extra_seed),
|
||||
use_smoothing_(config->path_smooth > 0),
|
||||
path_smooth_(config->path_smooth),
|
||||
num_total_bin_(feature_hist_offsets.empty() ? 0 : static_cast<int>(feature_hist_offsets.back())),
|
||||
select_features_by_node_(select_features_by_node),
|
||||
cuda_hist_(cuda_hist) {
|
||||
InitFeatureMetaInfo(train_data);
|
||||
if (has_categorical_feature_ && config->use_quantized_grad) {
|
||||
Log::Fatal("Quantized training on GPU with categorical features is not supported yet.");
|
||||
}
|
||||
}
|
||||
|
||||
CUDABestSplitFinder::~CUDABestSplitFinder() {
|
||||
gpuAssert(cudaStreamDestroy(cuda_streams_[0]), __FILE__, __LINE__);
|
||||
gpuAssert(cudaStreamDestroy(cuda_streams_[1]), __FILE__, __LINE__);
|
||||
cuda_streams_.clear();
|
||||
cuda_streams_.shrink_to_fit();
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::InitFeatureMetaInfo(const Dataset* train_data) {
|
||||
feature_missing_type_.resize(num_features_);
|
||||
feature_mfb_offsets_.resize(num_features_);
|
||||
feature_default_bins_.resize(num_features_);
|
||||
feature_num_bins_.resize(num_features_);
|
||||
max_num_bin_in_feature_ = 0;
|
||||
has_categorical_feature_ = false;
|
||||
max_num_categorical_bin_ = 0;
|
||||
is_categorical_.resize(train_data->num_features(), 0);
|
||||
for (int inner_feature_index = 0; inner_feature_index < num_features_; ++inner_feature_index) {
|
||||
const BinMapper* bin_mapper = train_data->FeatureBinMapper(inner_feature_index);
|
||||
if (bin_mapper->bin_type() == BinType::CategoricalBin) {
|
||||
has_categorical_feature_ = true;
|
||||
is_categorical_[inner_feature_index] = 1;
|
||||
if (bin_mapper->num_bin() > max_num_categorical_bin_) {
|
||||
max_num_categorical_bin_ = bin_mapper->num_bin();
|
||||
}
|
||||
}
|
||||
const MissingType missing_type = bin_mapper->missing_type();
|
||||
feature_missing_type_[inner_feature_index] = missing_type;
|
||||
feature_mfb_offsets_[inner_feature_index] = static_cast<int8_t>(bin_mapper->GetMostFreqBin() == 0);
|
||||
feature_default_bins_[inner_feature_index] = bin_mapper->GetDefaultBin();
|
||||
feature_num_bins_[inner_feature_index] = static_cast<uint32_t>(bin_mapper->num_bin());
|
||||
const int num_bin_hist = bin_mapper->num_bin() - feature_mfb_offsets_[inner_feature_index];
|
||||
if (num_bin_hist > max_num_bin_in_feature_) {
|
||||
max_num_bin_in_feature_ = num_bin_hist;
|
||||
}
|
||||
}
|
||||
if (max_num_bin_in_feature_ > NUM_THREADS_PER_BLOCK_BEST_SPLIT_FINDER) {
|
||||
use_global_memory_ = true;
|
||||
} else {
|
||||
use_global_memory_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::Init() {
|
||||
InitCUDAFeatureMetaInfo();
|
||||
cuda_streams_.resize(2);
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_streams_[0]));
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_streams_[1]));
|
||||
cuda_best_split_info_buffer_.Resize(8);
|
||||
if (use_global_memory_) {
|
||||
cuda_feature_hist_grad_buffer_.Resize(static_cast<size_t>(num_total_bin_));
|
||||
cuda_feature_hist_hess_buffer_.Resize(static_cast<size_t>(num_total_bin_));
|
||||
if (has_categorical_feature_) {
|
||||
cuda_feature_hist_stat_buffer_.Resize(static_cast<size_t>(num_total_bin_));
|
||||
cuda_feature_hist_index_buffer_.Resize(static_cast<size_t>(num_total_bin_));
|
||||
}
|
||||
}
|
||||
|
||||
if (select_features_by_node_) {
|
||||
is_feature_used_by_smaller_node_.Resize(num_features_);
|
||||
is_feature_used_by_larger_node_.Resize(num_features_);
|
||||
}
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::InitCUDAFeatureMetaInfo() {
|
||||
cuda_is_feature_used_bytree_.Resize(static_cast<size_t>(num_features_));
|
||||
|
||||
// initialize split find task information (a split find task is one pass through the histogram of a feature)
|
||||
num_tasks_ = 0;
|
||||
for (int inner_feature_index = 0; inner_feature_index < num_features_; ++inner_feature_index) {
|
||||
const uint32_t num_bin = feature_num_bins_[inner_feature_index];
|
||||
const MissingType missing_type = feature_missing_type_[inner_feature_index];
|
||||
if (num_bin > 2 && missing_type != MissingType::None && !is_categorical_[inner_feature_index]) {
|
||||
num_tasks_ += 2;
|
||||
} else {
|
||||
++num_tasks_;
|
||||
}
|
||||
}
|
||||
split_find_tasks_.resize(num_tasks_);
|
||||
split_find_tasks_.shrink_to_fit();
|
||||
int cur_task_index = 0;
|
||||
for (int inner_feature_index = 0; inner_feature_index < num_features_; ++inner_feature_index) {
|
||||
const uint32_t num_bin = feature_num_bins_[inner_feature_index];
|
||||
const MissingType missing_type = feature_missing_type_[inner_feature_index];
|
||||
if (num_bin > 2 && missing_type != MissingType::None && !is_categorical_[inner_feature_index]) {
|
||||
if (missing_type == MissingType::Zero) {
|
||||
SplitFindTask* new_task = &split_find_tasks_[cur_task_index];
|
||||
new_task->reverse = false;
|
||||
new_task->skip_default_bin = true;
|
||||
new_task->na_as_missing = false;
|
||||
new_task->inner_feature_index = inner_feature_index;
|
||||
new_task->assume_out_default_left = false;
|
||||
new_task->is_categorical = false;
|
||||
uint32_t num_bin = feature_num_bins_[inner_feature_index];
|
||||
new_task->is_one_hot = false;
|
||||
new_task->hist_offset = feature_hist_offsets_[inner_feature_index];
|
||||
new_task->mfb_offset = feature_mfb_offsets_[inner_feature_index];
|
||||
new_task->default_bin = feature_default_bins_[inner_feature_index];
|
||||
new_task->num_bin = num_bin;
|
||||
++cur_task_index;
|
||||
|
||||
new_task = &split_find_tasks_[cur_task_index];
|
||||
new_task->reverse = true;
|
||||
new_task->skip_default_bin = true;
|
||||
new_task->na_as_missing = false;
|
||||
new_task->inner_feature_index = inner_feature_index;
|
||||
new_task->assume_out_default_left = true;
|
||||
new_task->is_categorical = false;
|
||||
num_bin = feature_num_bins_[inner_feature_index];
|
||||
new_task->is_one_hot = false;
|
||||
new_task->hist_offset = feature_hist_offsets_[inner_feature_index];
|
||||
new_task->default_bin = feature_default_bins_[inner_feature_index];
|
||||
new_task->mfb_offset = feature_mfb_offsets_[inner_feature_index];
|
||||
new_task->num_bin = num_bin;
|
||||
++cur_task_index;
|
||||
} else {
|
||||
SplitFindTask* new_task = &split_find_tasks_[cur_task_index];
|
||||
new_task->reverse = false;
|
||||
new_task->skip_default_bin = false;
|
||||
new_task->na_as_missing = true;
|
||||
new_task->inner_feature_index = inner_feature_index;
|
||||
new_task->assume_out_default_left = false;
|
||||
new_task->is_categorical = false;
|
||||
uint32_t num_bin = feature_num_bins_[inner_feature_index];
|
||||
new_task->is_one_hot = false;
|
||||
new_task->hist_offset = feature_hist_offsets_[inner_feature_index];
|
||||
new_task->mfb_offset = feature_mfb_offsets_[inner_feature_index];
|
||||
new_task->default_bin = feature_default_bins_[inner_feature_index];
|
||||
new_task->num_bin = num_bin;
|
||||
++cur_task_index;
|
||||
|
||||
new_task = &split_find_tasks_[cur_task_index];
|
||||
new_task->reverse = true;
|
||||
new_task->skip_default_bin = false;
|
||||
new_task->na_as_missing = true;
|
||||
new_task->inner_feature_index = inner_feature_index;
|
||||
new_task->assume_out_default_left = true;
|
||||
new_task->is_categorical = false;
|
||||
num_bin = feature_num_bins_[inner_feature_index];
|
||||
new_task->is_one_hot = false;
|
||||
new_task->hist_offset = feature_hist_offsets_[inner_feature_index];
|
||||
new_task->mfb_offset = feature_mfb_offsets_[inner_feature_index];
|
||||
new_task->default_bin = feature_default_bins_[inner_feature_index];
|
||||
new_task->num_bin = num_bin;
|
||||
++cur_task_index;
|
||||
}
|
||||
} else {
|
||||
SplitFindTask& new_task = split_find_tasks_[cur_task_index];
|
||||
const uint32_t num_bin = feature_num_bins_[inner_feature_index];
|
||||
if (is_categorical_[inner_feature_index]) {
|
||||
new_task.reverse = false;
|
||||
new_task.is_categorical = true;
|
||||
new_task.is_one_hot = (static_cast<int>(num_bin) <= max_cat_to_onehot_);
|
||||
} else {
|
||||
new_task.reverse = true;
|
||||
new_task.is_categorical = false;
|
||||
new_task.is_one_hot = false;
|
||||
}
|
||||
new_task.skip_default_bin = false;
|
||||
new_task.na_as_missing = false;
|
||||
new_task.inner_feature_index = inner_feature_index;
|
||||
if (missing_type != MissingType::NaN && !is_categorical_[inner_feature_index]) {
|
||||
new_task.assume_out_default_left = true;
|
||||
} else {
|
||||
new_task.assume_out_default_left = false;
|
||||
}
|
||||
new_task.hist_offset = feature_hist_offsets_[inner_feature_index];
|
||||
new_task.mfb_offset = feature_mfb_offsets_[inner_feature_index];
|
||||
new_task.default_bin = feature_default_bins_[inner_feature_index];
|
||||
new_task.num_bin = num_bin;
|
||||
++cur_task_index;
|
||||
}
|
||||
}
|
||||
CHECK_EQ(cur_task_index, static_cast<int>(split_find_tasks_.size()));
|
||||
|
||||
if (extra_trees_) {
|
||||
cuda_randoms_.Resize(num_tasks_ * 2);
|
||||
LaunchInitCUDARandomKernel();
|
||||
}
|
||||
|
||||
const int num_task_blocks = (num_tasks_ + NUM_TASKS_PER_SYNC_BLOCK - 1) / NUM_TASKS_PER_SYNC_BLOCK;
|
||||
const size_t cuda_best_leaf_split_info_buffer_size = static_cast<size_t>(num_task_blocks) * static_cast<size_t>(num_leaves_);
|
||||
|
||||
cuda_leaf_best_split_info_.Resize(cuda_best_leaf_split_info_buffer_size);
|
||||
|
||||
cuda_split_find_tasks_.Resize(num_tasks_);
|
||||
CopyFromHostToCUDADevice<SplitFindTask>(cuda_split_find_tasks_.RawData(),
|
||||
split_find_tasks_.data(),
|
||||
split_find_tasks_.size(),
|
||||
__FILE__,
|
||||
__LINE__);
|
||||
|
||||
const size_t output_buffer_size = 2 * static_cast<size_t>(num_tasks_);
|
||||
cuda_best_split_info_.Resize(output_buffer_size);
|
||||
|
||||
max_num_categories_in_split_ = std::min(max_cat_threshold_, max_num_categorical_bin_ / 2);
|
||||
cuda_cat_threshold_feature_.Resize(max_num_categories_in_split_ * output_buffer_size);
|
||||
cuda_cat_threshold_real_feature_.Resize(max_num_categories_in_split_ * output_buffer_size);
|
||||
cuda_cat_threshold_leaf_.Resize(max_num_categories_in_split_ * cuda_best_leaf_split_info_buffer_size);
|
||||
cuda_cat_threshold_real_leaf_.Resize(max_num_categories_in_split_ * cuda_best_leaf_split_info_buffer_size);
|
||||
AllocateCatVectors(cuda_leaf_best_split_info_.RawData(), cuda_cat_threshold_leaf_.RawData(), cuda_cat_threshold_real_leaf_.RawData(), cuda_best_leaf_split_info_buffer_size);
|
||||
AllocateCatVectors(cuda_best_split_info_.RawData(), cuda_cat_threshold_feature_.RawData(), cuda_cat_threshold_real_feature_.RawData(), output_buffer_size);
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::ResetTrainingData(
|
||||
const hist_t* cuda_hist,
|
||||
const Dataset* train_data,
|
||||
const std::vector<uint32_t>& feature_hist_offsets) {
|
||||
cuda_hist_ = cuda_hist;
|
||||
num_features_ = train_data->num_features();
|
||||
feature_hist_offsets_ = feature_hist_offsets;
|
||||
InitFeatureMetaInfo(train_data);
|
||||
cuda_is_feature_used_bytree_.Clear();
|
||||
cuda_best_split_info_.Clear();
|
||||
InitCUDAFeatureMetaInfo();
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::ResetConfig(const Config* config, const hist_t* cuda_hist) {
|
||||
num_leaves_ = config->num_leaves;
|
||||
lambda_l1_ = config->lambda_l1;
|
||||
lambda_l2_ = config->lambda_l2;
|
||||
min_data_in_leaf_ = config->min_data_in_leaf;
|
||||
min_sum_hessian_in_leaf_ = config->min_sum_hessian_in_leaf;
|
||||
min_gain_to_split_ = config->min_gain_to_split;
|
||||
cat_smooth_ = config->cat_smooth;
|
||||
cat_l2_ = config->cat_l2;
|
||||
max_cat_threshold_ = config->max_cat_threshold;
|
||||
min_data_per_group_ = config->min_data_per_group;
|
||||
max_cat_to_onehot_ = config->max_cat_to_onehot;
|
||||
extra_trees_ = config->extra_trees;
|
||||
extra_seed_ = config->extra_seed;
|
||||
use_smoothing_ = (config->path_smooth > 0.0f);
|
||||
path_smooth_ = config->path_smooth;
|
||||
cuda_hist_ = cuda_hist;
|
||||
|
||||
const int num_task_blocks = (num_tasks_ + NUM_TASKS_PER_SYNC_BLOCK - 1) / NUM_TASKS_PER_SYNC_BLOCK;
|
||||
size_t cuda_best_leaf_split_info_buffer_size = static_cast<size_t>(num_task_blocks) * static_cast<size_t>(num_leaves_);
|
||||
cuda_leaf_best_split_info_.Resize(cuda_best_leaf_split_info_buffer_size);
|
||||
max_num_categories_in_split_ = std::min(max_cat_threshold_, max_num_categorical_bin_ / 2);
|
||||
size_t total_cat_threshold_size = max_num_categories_in_split_ * cuda_best_leaf_split_info_buffer_size;
|
||||
cuda_cat_threshold_leaf_.Resize(total_cat_threshold_size);
|
||||
cuda_cat_threshold_real_leaf_.Resize(total_cat_threshold_size);
|
||||
AllocateCatVectors(cuda_leaf_best_split_info_.RawData(), cuda_cat_threshold_leaf_.RawData(), cuda_cat_threshold_real_leaf_.RawData(), cuda_best_leaf_split_info_buffer_size);
|
||||
|
||||
cuda_best_leaf_split_info_buffer_size = 2 * static_cast<size_t>(num_tasks_);
|
||||
total_cat_threshold_size = max_num_categories_in_split_ * cuda_best_leaf_split_info_buffer_size;
|
||||
cuda_cat_threshold_feature_.Resize(total_cat_threshold_size);
|
||||
cuda_cat_threshold_real_feature_.Resize(total_cat_threshold_size);
|
||||
AllocateCatVectors(cuda_best_split_info_.RawData(), cuda_cat_threshold_feature_.RawData(), cuda_cat_threshold_real_feature_.RawData(), cuda_best_leaf_split_info_buffer_size);
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::BeforeTrain(const std::vector<int8_t>& is_feature_used_bytree) {
|
||||
CopyFromHostToCUDADevice<int8_t>(cuda_is_feature_used_bytree_.RawData(),
|
||||
is_feature_used_bytree.data(),
|
||||
is_feature_used_bytree.size(), __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::FindBestSplitsForLeaf(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* larger_leaf_splits,
|
||||
const int smaller_leaf_index,
|
||||
const int larger_leaf_index,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const data_size_t num_data_in_larger_leaf,
|
||||
const double sum_hessians_in_smaller_leaf,
|
||||
const double sum_hessians_in_larger_leaf,
|
||||
const score_t* grad_scale,
|
||||
const score_t* hess_scale,
|
||||
const uint8_t smaller_num_bits_in_histogram_bins,
|
||||
const uint8_t larger_num_bits_in_histogram_bins) {
|
||||
const bool is_smaller_leaf_valid = (num_data_in_smaller_leaf > min_data_in_leaf_ &&
|
||||
sum_hessians_in_smaller_leaf > min_sum_hessian_in_leaf_);
|
||||
const bool is_larger_leaf_valid = (num_data_in_larger_leaf > min_data_in_leaf_ &&
|
||||
sum_hessians_in_larger_leaf > min_sum_hessian_in_leaf_ && larger_leaf_index >= 0);
|
||||
if (grad_scale != nullptr && hess_scale != nullptr) {
|
||||
LaunchFindBestSplitsDiscretizedForLeafKernel(smaller_leaf_splits, larger_leaf_splits,
|
||||
smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid,
|
||||
grad_scale, hess_scale, smaller_num_bits_in_histogram_bins, larger_num_bits_in_histogram_bins, num_data_in_smaller_leaf, num_data_in_larger_leaf);
|
||||
} else {
|
||||
LaunchFindBestSplitsForLeafKernel(smaller_leaf_splits, larger_leaf_splits,
|
||||
smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid, num_data_in_smaller_leaf, num_data_in_larger_leaf);
|
||||
}
|
||||
global_timer.Start("CUDABestSplitFinder::LaunchSyncBestSplitForLeafKernel");
|
||||
LaunchSyncBestSplitForLeafKernel(smaller_leaf_index, larger_leaf_index, is_smaller_leaf_valid, is_larger_leaf_valid);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
global_timer.Stop("CUDABestSplitFinder::LaunchSyncBestSplitForLeafKernel");
|
||||
}
|
||||
|
||||
const CUDASplitInfo* CUDABestSplitFinder::FindBestFromAllSplits(
|
||||
const int cur_num_leaves,
|
||||
const int smaller_leaf_index,
|
||||
const int larger_leaf_index,
|
||||
int* smaller_leaf_best_split_feature,
|
||||
uint32_t* smaller_leaf_best_split_threshold,
|
||||
uint8_t* smaller_leaf_best_split_default_left,
|
||||
int* larger_leaf_best_split_feature,
|
||||
uint32_t* larger_leaf_best_split_threshold,
|
||||
uint8_t* larger_leaf_best_split_default_left,
|
||||
int* best_leaf_index,
|
||||
int* num_cat_threshold) {
|
||||
LaunchFindBestFromAllSplitsKernel(
|
||||
cur_num_leaves,
|
||||
smaller_leaf_index,
|
||||
larger_leaf_index,
|
||||
smaller_leaf_best_split_feature,
|
||||
smaller_leaf_best_split_threshold,
|
||||
smaller_leaf_best_split_default_left,
|
||||
larger_leaf_best_split_feature,
|
||||
larger_leaf_best_split_threshold,
|
||||
larger_leaf_best_split_default_left,
|
||||
best_leaf_index,
|
||||
num_cat_threshold);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
return cuda_leaf_best_split_info_.RawData() + (*best_leaf_index);
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::AllocateCatVectors(CUDASplitInfo* cuda_split_infos, uint32_t* cat_threshold_vec, int* cat_threshold_real_vec, size_t len) {
|
||||
LaunchAllocateCatVectorsKernel(cuda_split_infos, cat_threshold_vec, cat_threshold_real_vec, len);
|
||||
}
|
||||
|
||||
void CUDABestSplitFinder::SetUsedFeatureByNode(const std::vector<int8_t>& is_feature_used_by_smaller_node,
|
||||
const std::vector<int8_t>& is_feature_used_by_larger_node) {
|
||||
if (select_features_by_node_) {
|
||||
CopyFromHostToCUDADevice<int8_t>(is_feature_used_by_smaller_node_.RawData(),
|
||||
is_feature_used_by_smaller_node.data(), is_feature_used_by_smaller_node.size(), __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<int8_t>(is_feature_used_by_larger_node_.RawData(),
|
||||
is_feature_used_by_larger_node.data(), is_feature_used_by_larger_node.size(), __FILE__, __LINE__);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,249 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_BEST_SPLIT_FINDER_HPP_
|
||||
#define LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_BEST_SPLIT_FINDER_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/bin.h>
|
||||
#include <LightGBM/dataset.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <LightGBM/cuda/cuda_random.hpp>
|
||||
#include <LightGBM/cuda/cuda_split_info.hpp>
|
||||
|
||||
#include "cuda_leaf_splits.hpp"
|
||||
|
||||
#define NUM_THREADS_PER_BLOCK_BEST_SPLIT_FINDER (256)
|
||||
#define NUM_THREADS_FIND_BEST_LEAF (256)
|
||||
#define NUM_TASKS_PER_SYNC_BLOCK (1024)
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
struct SplitFindTask {
|
||||
int inner_feature_index;
|
||||
bool reverse;
|
||||
bool skip_default_bin;
|
||||
bool na_as_missing;
|
||||
bool assume_out_default_left;
|
||||
bool is_categorical;
|
||||
bool is_one_hot;
|
||||
uint32_t hist_offset;
|
||||
uint8_t mfb_offset;
|
||||
uint32_t num_bin;
|
||||
uint32_t default_bin;
|
||||
int rand_threshold;
|
||||
};
|
||||
|
||||
class CUDABestSplitFinder {
|
||||
public:
|
||||
CUDABestSplitFinder(
|
||||
const hist_t* cuda_hist,
|
||||
const Dataset* train_data,
|
||||
const std::vector<uint32_t>& feature_hist_offsets,
|
||||
const bool select_features_by_node,
|
||||
const Config* config);
|
||||
|
||||
~CUDABestSplitFinder();
|
||||
|
||||
void InitFeatureMetaInfo(const Dataset* train_data);
|
||||
|
||||
void Init();
|
||||
|
||||
void InitCUDAFeatureMetaInfo();
|
||||
|
||||
void BeforeTrain(const std::vector<int8_t>& is_feature_used_bytree);
|
||||
|
||||
void FindBestSplitsForLeaf(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* larger_leaf_splits,
|
||||
const int smaller_leaf_index,
|
||||
const int larger_leaf_index,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const data_size_t num_data_in_larger_leaf,
|
||||
const double sum_hessians_in_smaller_leaf,
|
||||
const double sum_hessians_in_larger_leaf,
|
||||
const score_t* grad_scale,
|
||||
const score_t* hess_scale,
|
||||
const uint8_t smaller_num_bits_in_histogram_bins,
|
||||
const uint8_t larger_num_bits_in_histogram_bins);
|
||||
|
||||
const CUDASplitInfo* FindBestFromAllSplits(
|
||||
const int cur_num_leaves,
|
||||
const int smaller_leaf_index,
|
||||
const int larger_leaf_index,
|
||||
int* smaller_leaf_best_split_feature,
|
||||
uint32_t* smaller_leaf_best_split_threshold,
|
||||
uint8_t* smaller_leaf_best_split_default_left,
|
||||
int* larger_leaf_best_split_feature,
|
||||
uint32_t* larger_leaf_best_split_threshold,
|
||||
uint8_t* larger_leaf_best_split_default_left,
|
||||
int* best_leaf_index,
|
||||
int* num_cat_threshold);
|
||||
|
||||
void ResetTrainingData(
|
||||
const hist_t* cuda_hist,
|
||||
const Dataset* train_data,
|
||||
const std::vector<uint32_t>& feature_hist_offsets);
|
||||
|
||||
void ResetConfig(const Config* config, const hist_t* cuda_hist);
|
||||
|
||||
void SetUsedFeatureByNode(const std::vector<int8_t>& is_feature_used_by_smaller_node,
|
||||
const std::vector<int8_t>& is_feature_used_by_larger_node);
|
||||
|
||||
private:
|
||||
#define LaunchFindBestSplitsForLeafKernel_PARAMS \
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits, \
|
||||
const CUDALeafSplitsStruct* larger_leaf_splits, \
|
||||
const int smaller_leaf_index, \
|
||||
const int larger_leaf_index, \
|
||||
const bool is_smaller_leaf_valid, \
|
||||
const bool is_larger_leaf_valid, \
|
||||
const data_size_t global_num_data_in_smaller_leaf, \
|
||||
const data_size_t global_num_data_in_larger_leaf
|
||||
|
||||
void LaunchFindBestSplitsForLeafKernel(LaunchFindBestSplitsForLeafKernel_PARAMS);
|
||||
|
||||
template <bool USE_RAND>
|
||||
void LaunchFindBestSplitsForLeafKernelInner0(LaunchFindBestSplitsForLeafKernel_PARAMS);
|
||||
|
||||
template <bool USE_RAND, bool USE_L1>
|
||||
void LaunchFindBestSplitsForLeafKernelInner1(LaunchFindBestSplitsForLeafKernel_PARAMS);
|
||||
|
||||
template <bool USE_RAND, bool USE_L1, bool USE_SMOOTHING>
|
||||
void LaunchFindBestSplitsForLeafKernelInner2(LaunchFindBestSplitsForLeafKernel_PARAMS);
|
||||
|
||||
#undef LaunchFindBestSplitsForLeafKernel_PARAMS
|
||||
|
||||
#define LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS \
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits, \
|
||||
const CUDALeafSplitsStruct* larger_leaf_splits, \
|
||||
const int smaller_leaf_index, \
|
||||
const int larger_leaf_index, \
|
||||
const bool is_smaller_leaf_valid, \
|
||||
const bool is_larger_leaf_valid, \
|
||||
const score_t* grad_scale, \
|
||||
const score_t* hess_scale, \
|
||||
const uint8_t smaller_num_bits_in_histogram_bins, \
|
||||
const uint8_t larger_num_bits_in_histogram_bins, \
|
||||
const data_size_t global_num_data_in_smaller_leaf, \
|
||||
const data_size_t global_num_data_in_larger_leaf
|
||||
|
||||
void LaunchFindBestSplitsDiscretizedForLeafKernel(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS);
|
||||
|
||||
template <bool USE_RAND>
|
||||
void LaunchFindBestSplitsDiscretizedForLeafKernelInner0(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS);
|
||||
|
||||
template <bool USE_RAND, bool USE_L1>
|
||||
void LaunchFindBestSplitsDiscretizedForLeafKernelInner1(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS);
|
||||
|
||||
template <bool USE_RAND, bool USE_L1, bool USE_SMOOTHING>
|
||||
void LaunchFindBestSplitsDiscretizedForLeafKernelInner2(LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS);
|
||||
|
||||
#undef LaunchFindBestSplitsDiscretizedForLeafKernel_PARAMS
|
||||
|
||||
void LaunchSyncBestSplitForLeafKernel(
|
||||
const int host_smaller_leaf_index,
|
||||
const int host_larger_leaf_index,
|
||||
const bool is_smaller_leaf_valid,
|
||||
const bool is_larger_leaf_valid);
|
||||
|
||||
void LaunchFindBestFromAllSplitsKernel(
|
||||
const int cur_num_leaves,
|
||||
const int smaller_leaf_index,
|
||||
const int larger_leaf_index,
|
||||
int* smaller_leaf_best_split_feature,
|
||||
uint32_t* smaller_leaf_best_split_threshold,
|
||||
uint8_t* smaller_leaf_best_split_default_left,
|
||||
int* larger_leaf_best_split_feature,
|
||||
uint32_t* larger_leaf_best_split_threshold,
|
||||
uint8_t* larger_leaf_best_split_default_left,
|
||||
int* best_leaf_index,
|
||||
data_size_t* num_cat_threshold);
|
||||
|
||||
void AllocateCatVectors(CUDASplitInfo* cuda_split_infos, uint32_t* cat_threshold_vec, int* cat_threshold_real_vec, size_t len);
|
||||
|
||||
void LaunchAllocateCatVectorsKernel(CUDASplitInfo* cuda_split_infos, uint32_t* cat_threshold_vec, int* cat_threshold_real_vec, size_t len);
|
||||
|
||||
void LaunchInitCUDARandomKernel();
|
||||
|
||||
// Host memory
|
||||
int num_features_;
|
||||
int num_leaves_;
|
||||
int max_num_bin_in_feature_;
|
||||
std::vector<uint32_t> feature_hist_offsets_;
|
||||
std::vector<uint8_t> feature_mfb_offsets_;
|
||||
std::vector<uint32_t> feature_default_bins_;
|
||||
std::vector<uint32_t> feature_num_bins_;
|
||||
std::vector<MissingType> feature_missing_type_;
|
||||
double lambda_l1_;
|
||||
double lambda_l2_;
|
||||
data_size_t min_data_in_leaf_;
|
||||
double min_sum_hessian_in_leaf_;
|
||||
double min_gain_to_split_;
|
||||
double cat_smooth_;
|
||||
double cat_l2_;
|
||||
int max_cat_threshold_;
|
||||
int min_data_per_group_;
|
||||
int max_cat_to_onehot_;
|
||||
bool extra_trees_;
|
||||
int extra_seed_;
|
||||
bool use_smoothing_;
|
||||
double path_smooth_;
|
||||
std::vector<cudaStream_t> cuda_streams_;
|
||||
// for best split find tasks
|
||||
std::vector<SplitFindTask> split_find_tasks_;
|
||||
int num_tasks_;
|
||||
// use global memory
|
||||
bool use_global_memory_;
|
||||
// number of total bins in the dataset
|
||||
const int num_total_bin_;
|
||||
// has categorical feature
|
||||
bool has_categorical_feature_;
|
||||
// maximum number of bins of categorical features
|
||||
int max_num_categorical_bin_;
|
||||
// marks whether a feature is categorical
|
||||
std::vector<int8_t> is_categorical_;
|
||||
// whether need to select features by node
|
||||
bool select_features_by_node_;
|
||||
|
||||
// CUDA memory, held by this object
|
||||
// for per leaf best split information
|
||||
CUDAVector<CUDASplitInfo> cuda_leaf_best_split_info_;
|
||||
// for best split information when finding best split
|
||||
CUDAVector<CUDASplitInfo> cuda_best_split_info_;
|
||||
// best split information buffer, to be copied to host
|
||||
CUDAVector<int> cuda_best_split_info_buffer_;
|
||||
// find best split task information
|
||||
CUDAVector<SplitFindTask> cuda_split_find_tasks_;
|
||||
CUDAVector<int8_t> cuda_is_feature_used_bytree_;
|
||||
// used when finding best split with global memory
|
||||
CUDAVector<hist_t> cuda_feature_hist_grad_buffer_;
|
||||
CUDAVector<hist_t> cuda_feature_hist_hess_buffer_;
|
||||
CUDAVector<hist_t> cuda_feature_hist_stat_buffer_;
|
||||
CUDAVector<data_size_t> cuda_feature_hist_index_buffer_;
|
||||
CUDAVector<uint32_t> cuda_cat_threshold_leaf_;
|
||||
CUDAVector<int> cuda_cat_threshold_real_leaf_;
|
||||
CUDAVector<uint32_t> cuda_cat_threshold_feature_;
|
||||
CUDAVector<int> cuda_cat_threshold_real_feature_;
|
||||
int max_num_categories_in_split_;
|
||||
// used for extremely randomized trees
|
||||
CUDAVector<CUDARandom> cuda_randoms_;
|
||||
// features used by node
|
||||
CUDAVector<int8_t> is_feature_used_by_smaller_node_;
|
||||
CUDAVector<int8_t> is_feature_used_by_larger_node_;
|
||||
|
||||
// CUDA memory, held by other object
|
||||
const hist_t* cuda_hist_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_BEST_SPLIT_FINDER_HPP_
|
||||
@@ -0,0 +1,352 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_data_partition.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDADataPartition::CUDADataPartition(
|
||||
const Dataset* train_data,
|
||||
const int num_total_bin,
|
||||
const int num_leaves,
|
||||
const int num_threads,
|
||||
const bool use_quantized_grad,
|
||||
hist_t* cuda_hist):
|
||||
|
||||
num_data_(train_data->num_data()),
|
||||
num_features_(train_data->num_features()),
|
||||
num_total_bin_(num_total_bin),
|
||||
num_leaves_(num_leaves),
|
||||
num_threads_(num_threads),
|
||||
use_quantized_grad_(use_quantized_grad),
|
||||
cuda_hist_(cuda_hist) {
|
||||
CalcBlockDim(num_data_);
|
||||
max_num_split_indices_blocks_ = grid_dim_;
|
||||
cur_num_leaves_ = 1;
|
||||
cuda_column_data_ = train_data->cuda_column_data();
|
||||
|
||||
is_categorical_feature_.resize(train_data->num_features(), false);
|
||||
is_single_feature_in_column_.resize(train_data->num_features(), false);
|
||||
for (int feature_index = 0; feature_index < train_data->num_features(); ++feature_index) {
|
||||
if (train_data->FeatureBinMapper(feature_index)->bin_type() == BinType::CategoricalBin) {
|
||||
is_categorical_feature_[feature_index] = true;
|
||||
}
|
||||
const int feature_group_index = train_data->Feature2Group(feature_index);
|
||||
if (!train_data->IsMultiGroup(feature_group_index)) {
|
||||
if ((feature_index == 0 || train_data->Feature2Group(feature_index - 1) != feature_group_index) &&
|
||||
(feature_index == train_data->num_features() - 1 || train_data->Feature2Group(feature_index + 1) != feature_group_index)) {
|
||||
is_single_feature_in_column_[feature_index] = true;
|
||||
}
|
||||
} else {
|
||||
is_single_feature_in_column_[feature_index] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CUDADataPartition::~CUDADataPartition() {
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamDestroy(cuda_streams_[0]));
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamDestroy(cuda_streams_[1]));
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamDestroy(cuda_streams_[2]));
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamDestroy(cuda_streams_[3]));
|
||||
cuda_streams_.clear();
|
||||
cuda_streams_.shrink_to_fit();
|
||||
}
|
||||
|
||||
void CUDADataPartition::Init() {
|
||||
// allocate CUDA memory
|
||||
cuda_data_indices_.Resize(static_cast<size_t>(num_data_));
|
||||
cuda_leaf_data_start_.Resize(static_cast<size_t>(num_leaves_));
|
||||
cuda_leaf_data_end_.Resize(static_cast<size_t>(num_leaves_));
|
||||
cuda_leaf_num_data_.Resize(static_cast<size_t>(num_leaves_));
|
||||
// leave some space for alignment
|
||||
cuda_block_to_left_offset_.Resize(static_cast<size_t>(num_data_));
|
||||
cuda_data_index_to_leaf_index_.Resize(static_cast<size_t>(num_data_));
|
||||
cuda_block_data_to_left_offset_.Resize(static_cast<size_t>(max_num_split_indices_blocks_) + 1);
|
||||
cuda_block_data_to_right_offset_.Resize(static_cast<size_t>(max_num_split_indices_blocks_) + 1);
|
||||
SetCUDAMemory<data_size_t>(cuda_block_data_to_left_offset_.RawData(), 0, static_cast<size_t>(max_num_split_indices_blocks_) + 1, __FILE__, __LINE__);
|
||||
SetCUDAMemory<data_size_t>(cuda_block_data_to_right_offset_.RawData(), 0, static_cast<size_t>(max_num_split_indices_blocks_) + 1, __FILE__, __LINE__);
|
||||
cuda_out_data_indices_in_leaf_.Resize(static_cast<size_t>(num_data_));
|
||||
cuda_hist_pool_.Resize(static_cast<size_t>(num_leaves_));
|
||||
CopyFromHostToCUDADevice<hist_t*>(cuda_hist_pool_.RawData(), &cuda_hist_, 1, __FILE__, __LINE__);
|
||||
|
||||
cuda_split_info_buffer_.Resize(18);
|
||||
|
||||
cuda_leaf_output_.Resize(static_cast<size_t>(num_leaves_));
|
||||
|
||||
cuda_streams_.resize(4);
|
||||
gpuAssert(cudaStreamCreate(&cuda_streams_[0]), __FILE__, __LINE__);
|
||||
gpuAssert(cudaStreamCreate(&cuda_streams_[1]), __FILE__, __LINE__);
|
||||
gpuAssert(cudaStreamCreate(&cuda_streams_[2]), __FILE__, __LINE__);
|
||||
gpuAssert(cudaStreamCreate(&cuda_streams_[3]), __FILE__, __LINE__);
|
||||
|
||||
cuda_num_data_.InitFromHostVector(std::vector<data_size_t>{num_data_});
|
||||
use_bagging_ = false;
|
||||
used_indices_ = nullptr;
|
||||
}
|
||||
|
||||
void CUDADataPartition::BeforeTrain() {
|
||||
if (!use_bagging_) {
|
||||
LaunchFillDataIndicesBeforeTrain();
|
||||
}
|
||||
SetCUDAMemory<data_size_t>(cuda_leaf_num_data_.RawData(), 0, static_cast<size_t>(num_leaves_), __FILE__, __LINE__);
|
||||
SetCUDAMemory<data_size_t>(cuda_leaf_data_start_.RawData(), 0, static_cast<size_t>(num_leaves_), __FILE__, __LINE__);
|
||||
SetCUDAMemory<data_size_t>(cuda_leaf_data_end_.RawData(), 0, static_cast<size_t>(num_leaves_), __FILE__, __LINE__);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
if (!use_bagging_) {
|
||||
CopyFromCUDADeviceToCUDADevice<data_size_t>(cuda_leaf_num_data_.RawData(), cuda_num_data_.RawData(), 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToCUDADevice<data_size_t>(cuda_leaf_data_end_.RawData(), cuda_num_data_.RawData(), 1, __FILE__, __LINE__);
|
||||
} else {
|
||||
CopyFromHostToCUDADevice<data_size_t>(cuda_leaf_num_data_.RawData(), &num_used_indices_, 1, __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<data_size_t>(cuda_leaf_data_end_.RawData(), &num_used_indices_, 1, __FILE__, __LINE__);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<hist_t*>(cuda_hist_pool_.RawData(), &cuda_hist_, 1, __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDADataPartition::Split(
|
||||
// input best split info
|
||||
const CUDASplitInfo* best_split_info,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index,
|
||||
const int leaf_best_split_feature,
|
||||
const uint32_t leaf_best_split_threshold,
|
||||
const uint32_t* categorical_bitset,
|
||||
const int categorical_bitset_len,
|
||||
const uint8_t leaf_best_split_default_left,
|
||||
const data_size_t num_data_in_leaf,
|
||||
const data_size_t leaf_data_start,
|
||||
// for leaf information update
|
||||
CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
CUDALeafSplitsStruct* larger_leaf_splits,
|
||||
// gather information for CPU, used for launching kernels
|
||||
data_size_t* left_leaf_num_data,
|
||||
data_size_t* right_leaf_num_data,
|
||||
data_size_t* left_leaf_start,
|
||||
data_size_t* right_leaf_start,
|
||||
double* left_leaf_sum_of_hessians,
|
||||
double* right_leaf_sum_of_hessians,
|
||||
double* left_leaf_sum_of_gradients,
|
||||
double* right_leaf_sum_of_gradients,
|
||||
data_size_t* global_left_leaf_num_data,
|
||||
data_size_t* global_right_leaf_num_data) {
|
||||
CalcBlockDim(num_data_in_leaf);
|
||||
global_timer.Start("GenDataToLeftBitVector");
|
||||
GenDataToLeftBitVector(num_data_in_leaf,
|
||||
leaf_best_split_feature,
|
||||
leaf_best_split_threshold,
|
||||
categorical_bitset,
|
||||
categorical_bitset_len,
|
||||
leaf_best_split_default_left,
|
||||
leaf_data_start,
|
||||
left_leaf_index,
|
||||
right_leaf_index);
|
||||
global_timer.Stop("GenDataToLeftBitVector");
|
||||
global_timer.Start("SplitInner");
|
||||
|
||||
SplitInner(num_data_in_leaf,
|
||||
best_split_info,
|
||||
left_leaf_index,
|
||||
right_leaf_index,
|
||||
smaller_leaf_splits,
|
||||
larger_leaf_splits,
|
||||
left_leaf_num_data,
|
||||
right_leaf_num_data,
|
||||
left_leaf_start,
|
||||
right_leaf_start,
|
||||
left_leaf_sum_of_hessians,
|
||||
right_leaf_sum_of_hessians,
|
||||
left_leaf_sum_of_gradients,
|
||||
right_leaf_sum_of_gradients,
|
||||
global_left_leaf_num_data,
|
||||
global_right_leaf_num_data);
|
||||
global_timer.Stop("SplitInner");
|
||||
}
|
||||
|
||||
void CUDADataPartition::GenDataToLeftBitVector(
|
||||
const data_size_t num_data_in_leaf,
|
||||
const int split_feature_index,
|
||||
const uint32_t split_threshold,
|
||||
const uint32_t* categorical_bitset,
|
||||
const int categorical_bitset_len,
|
||||
const uint8_t split_default_left,
|
||||
const data_size_t leaf_data_start,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index) {
|
||||
if (is_categorical_feature_[split_feature_index]) {
|
||||
LaunchGenDataToLeftBitVectorCategoricalKernel(
|
||||
num_data_in_leaf,
|
||||
split_feature_index,
|
||||
categorical_bitset,
|
||||
categorical_bitset_len,
|
||||
split_default_left,
|
||||
leaf_data_start,
|
||||
left_leaf_index,
|
||||
right_leaf_index);
|
||||
} else {
|
||||
LaunchGenDataToLeftBitVectorKernel(
|
||||
num_data_in_leaf,
|
||||
split_feature_index,
|
||||
split_threshold,
|
||||
split_default_left,
|
||||
leaf_data_start,
|
||||
left_leaf_index,
|
||||
right_leaf_index);
|
||||
}
|
||||
}
|
||||
|
||||
void CUDADataPartition::SplitInner(
|
||||
const data_size_t num_data_in_leaf,
|
||||
const CUDASplitInfo* best_split_info,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index,
|
||||
// for leaf splits information update
|
||||
CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
CUDALeafSplitsStruct* larger_leaf_splits,
|
||||
data_size_t* left_leaf_num_data,
|
||||
data_size_t* right_leaf_num_data,
|
||||
data_size_t* left_leaf_start,
|
||||
data_size_t* right_leaf_start,
|
||||
double* left_leaf_sum_of_hessians,
|
||||
double* right_leaf_sum_of_hessians,
|
||||
double* left_leaf_sum_of_gradients,
|
||||
double* right_leaf_sum_of_gradients,
|
||||
data_size_t* global_left_leaf_num_data,
|
||||
data_size_t* global_right_leaf_num_data) {
|
||||
LaunchSplitInnerKernel(
|
||||
num_data_in_leaf,
|
||||
best_split_info,
|
||||
left_leaf_index,
|
||||
right_leaf_index,
|
||||
smaller_leaf_splits,
|
||||
larger_leaf_splits,
|
||||
left_leaf_num_data,
|
||||
right_leaf_num_data,
|
||||
left_leaf_start,
|
||||
right_leaf_start,
|
||||
left_leaf_sum_of_hessians,
|
||||
right_leaf_sum_of_hessians,
|
||||
left_leaf_sum_of_gradients,
|
||||
right_leaf_sum_of_gradients,
|
||||
global_left_leaf_num_data,
|
||||
global_right_leaf_num_data);
|
||||
++cur_num_leaves_;
|
||||
}
|
||||
|
||||
void CUDADataPartition::UpdateTrainScore(const Tree* tree, double* scores) {
|
||||
const CUDATree* cuda_tree = nullptr;
|
||||
std::unique_ptr<CUDATree> cuda_tree_ptr;
|
||||
if (tree->is_cuda_tree()) {
|
||||
cuda_tree = reinterpret_cast<const CUDATree*>(tree);
|
||||
} else {
|
||||
cuda_tree_ptr.reset(new CUDATree(tree));
|
||||
cuda_tree = cuda_tree_ptr.get();
|
||||
}
|
||||
if (use_bagging_) {
|
||||
// we need restore the order of indices in cuda_data_indices_
|
||||
CopyFromCUDADeviceToCUDADevice<data_size_t>(cuda_data_indices_.RawData(), used_indices_, static_cast<size_t>(num_used_indices_), __FILE__, __LINE__);
|
||||
}
|
||||
LaunchAddPredictionToScoreKernel(cuda_tree->cuda_leaf_value(), scores);
|
||||
}
|
||||
|
||||
void CUDADataPartition::CalcBlockDim(const data_size_t num_data_in_leaf) {
|
||||
const int min_num_blocks = num_data_in_leaf <= 100 ? 1 : 80;
|
||||
const int num_blocks = std::max(min_num_blocks, (num_data_in_leaf + SPLIT_INDICES_BLOCK_SIZE_DATA_PARTITION - 1) / SPLIT_INDICES_BLOCK_SIZE_DATA_PARTITION);
|
||||
int split_indices_block_size_data_partition = (num_data_in_leaf + num_blocks - 1) / num_blocks - 1;
|
||||
CHECK_GT(split_indices_block_size_data_partition, 0);
|
||||
int split_indices_block_size_data_partition_aligned = 1;
|
||||
while (split_indices_block_size_data_partition > 0) {
|
||||
split_indices_block_size_data_partition_aligned <<= 1;
|
||||
split_indices_block_size_data_partition >>= 1;
|
||||
}
|
||||
const int num_blocks_final = (num_data_in_leaf + split_indices_block_size_data_partition_aligned - 1) / split_indices_block_size_data_partition_aligned;
|
||||
grid_dim_ = num_blocks_final;
|
||||
block_dim_ = split_indices_block_size_data_partition_aligned;
|
||||
}
|
||||
|
||||
void CUDADataPartition::SetUsedDataIndices(const data_size_t* used_indices, const data_size_t num_used_indices) {
|
||||
use_bagging_ = true;
|
||||
num_used_indices_ = num_used_indices;
|
||||
used_indices_ = used_indices;
|
||||
CopyFromCUDADeviceToCUDADevice<data_size_t>(cuda_data_indices_.RawData(), used_indices, static_cast<size_t>(num_used_indices), __FILE__, __LINE__);
|
||||
LaunchFillDataIndexToLeafIndex();
|
||||
}
|
||||
|
||||
void CUDADataPartition::ResetTrainingData(const Dataset* train_data, const int num_total_bin, hist_t* cuda_hist) {
|
||||
const data_size_t old_num_data = num_data_;
|
||||
num_data_ = train_data->num_data();
|
||||
num_features_ = train_data->num_features();
|
||||
num_total_bin_ = num_total_bin;
|
||||
cuda_column_data_ = train_data->cuda_column_data();
|
||||
cuda_hist_ = cuda_hist;
|
||||
CopyFromHostToCUDADevice<hist_t*>(cuda_hist_pool_.RawData(), &cuda_hist_, 1, __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<int>(cuda_num_data_.RawData(), &num_data_, 1, __FILE__, __LINE__);
|
||||
if (num_data_ > old_num_data) {
|
||||
CalcBlockDim(num_data_);
|
||||
const int old_max_num_split_indices_blocks = max_num_split_indices_blocks_;
|
||||
max_num_split_indices_blocks_ = grid_dim_;
|
||||
if (max_num_split_indices_blocks_ > old_max_num_split_indices_blocks) {
|
||||
cuda_block_data_to_left_offset_.Resize(static_cast<size_t>(max_num_split_indices_blocks_) + 1);
|
||||
cuda_block_data_to_right_offset_.Resize(static_cast<size_t>(max_num_split_indices_blocks_) + 1);
|
||||
SetCUDAMemory<data_size_t>(cuda_block_data_to_left_offset_.RawData(), 0, static_cast<size_t>(max_num_split_indices_blocks_) + 1, __FILE__, __LINE__);
|
||||
SetCUDAMemory<data_size_t>(cuda_block_data_to_right_offset_.RawData(), 0, static_cast<size_t>(max_num_split_indices_blocks_) + 1, __FILE__, __LINE__);
|
||||
}
|
||||
cuda_data_indices_.Resize(static_cast<size_t>(num_data_));
|
||||
cuda_block_to_left_offset_.Resize(static_cast<size_t>(num_data_));
|
||||
cuda_data_index_to_leaf_index_.Resize(static_cast<size_t>(num_data_));
|
||||
cuda_out_data_indices_in_leaf_.Resize(static_cast<size_t>(num_data_));
|
||||
}
|
||||
used_indices_ = nullptr;
|
||||
use_bagging_ = false;
|
||||
num_used_indices_ = 0;
|
||||
cur_num_leaves_ = 1;
|
||||
}
|
||||
|
||||
void CUDADataPartition::ResetConfig(const Config* config, hist_t* cuda_hist) {
|
||||
num_threads_ = OMP_NUM_THREADS();
|
||||
num_leaves_ = config->num_leaves;
|
||||
cuda_hist_ = cuda_hist;
|
||||
cuda_leaf_data_start_.Resize(static_cast<size_t>(num_leaves_));
|
||||
cuda_leaf_data_end_.Resize(static_cast<size_t>(num_leaves_));
|
||||
cuda_leaf_num_data_.Resize(static_cast<size_t>(num_leaves_));
|
||||
cuda_hist_pool_.Resize(static_cast<size_t>(num_leaves_));
|
||||
cuda_leaf_output_.Resize(static_cast<size_t>(num_leaves_));
|
||||
}
|
||||
|
||||
void CUDADataPartition::SetBaggingSubset(const Dataset* subset) {
|
||||
num_used_indices_ = subset->num_data();
|
||||
used_indices_ = nullptr;
|
||||
use_bagging_ = true;
|
||||
cuda_column_data_ = subset->cuda_column_data();
|
||||
}
|
||||
|
||||
void CUDADataPartition::ResetByLeafPred(const std::vector<int>& leaf_pred, int num_leaves) {
|
||||
if (leaf_pred.size() != static_cast<size_t>(num_data_)) {
|
||||
cuda_data_index_to_leaf_index_.Clear();
|
||||
cuda_data_index_to_leaf_index_.InitFromHostVector(leaf_pred);
|
||||
num_data_ = static_cast<data_size_t>(leaf_pred.size());
|
||||
} else {
|
||||
CopyFromHostToCUDADevice<int>(cuda_data_index_to_leaf_index_.RawData(), leaf_pred.data(), leaf_pred.size(), __FILE__, __LINE__);
|
||||
}
|
||||
num_leaves_ = num_leaves;
|
||||
cur_num_leaves_ = num_leaves;
|
||||
}
|
||||
|
||||
void CUDADataPartition::ReduceLeafGradStat(
|
||||
const score_t* gradients, const score_t* hessians,
|
||||
CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const {
|
||||
LaunchReduceLeafGradStat(gradients, hessians, tree, leaf_grad_stat_buffer, leaf_hess_state_buffer);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,406 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_DATA_PARTITION_HPP_
|
||||
#define LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_DATA_PARTITION_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/bin.h>
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/tree.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <LightGBM/cuda/cuda_column_data.hpp>
|
||||
#include <LightGBM/cuda/cuda_split_info.hpp>
|
||||
#include <LightGBM/cuda/cuda_tree.hpp>
|
||||
|
||||
#include "cuda_leaf_splits.hpp"
|
||||
|
||||
#define FILL_INDICES_BLOCK_SIZE_DATA_PARTITION (1024)
|
||||
#define SPLIT_INDICES_BLOCK_SIZE_DATA_PARTITION (1024)
|
||||
#define AGGREGATE_BLOCK_SIZE_DATA_PARTITION (1024)
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class CUDADataPartition: public NCCLInfo {
|
||||
public:
|
||||
CUDADataPartition(
|
||||
const Dataset* train_data,
|
||||
const int num_total_bin,
|
||||
const int num_leaves,
|
||||
const int num_threads,
|
||||
const bool use_quantized_grad,
|
||||
hist_t* cuda_hist);
|
||||
|
||||
~CUDADataPartition();
|
||||
|
||||
void Init();
|
||||
|
||||
void BeforeTrain();
|
||||
|
||||
void Split(
|
||||
// input best split info
|
||||
const CUDASplitInfo* best_split_info,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index,
|
||||
const int leaf_best_split_feature,
|
||||
const uint32_t leaf_best_split_threshold,
|
||||
const uint32_t* categorical_bitset,
|
||||
const int categorical_bitset_len,
|
||||
const uint8_t leaf_best_split_default_left,
|
||||
const data_size_t num_data_in_leaf,
|
||||
const data_size_t leaf_data_start,
|
||||
// for leaf information update
|
||||
CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
CUDALeafSplitsStruct* larger_leaf_splits,
|
||||
// gather information for CPU, used for launching kernels
|
||||
data_size_t* left_leaf_num_data,
|
||||
data_size_t* right_leaf_num_data,
|
||||
data_size_t* left_leaf_start,
|
||||
data_size_t* right_leaf_start,
|
||||
double* left_leaf_sum_of_hessians,
|
||||
double* right_leaf_sum_of_hessians,
|
||||
double* left_leaf_sum_of_gradients,
|
||||
double* right_leaf_sum_of_gradients,
|
||||
data_size_t* global_left_leaf_num_data,
|
||||
data_size_t* global_right_leaf_num_data);
|
||||
|
||||
void UpdateTrainScore(const Tree* tree, double* cuda_scores);
|
||||
|
||||
void SetUsedDataIndices(const data_size_t* used_indices, const data_size_t num_used_indices);
|
||||
|
||||
void SetBaggingSubset(const Dataset* subset);
|
||||
|
||||
void ResetTrainingData(const Dataset* train_data, const int num_total_bin, hist_t* cuda_hist);
|
||||
|
||||
void ResetConfig(const Config* config, hist_t* cuda_hist);
|
||||
|
||||
void ResetByLeafPred(const std::vector<int>& leaf_pred, int num_leaves);
|
||||
|
||||
void ReduceLeafGradStat(
|
||||
const score_t* gradients, const score_t* hessians,
|
||||
CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const;
|
||||
|
||||
data_size_t root_num_data() const {
|
||||
if (use_bagging_) {
|
||||
return num_used_indices_;
|
||||
} else {
|
||||
return num_data_;
|
||||
}
|
||||
}
|
||||
|
||||
const data_size_t* cuda_data_indices() const { return cuda_data_indices_.RawData(); }
|
||||
|
||||
const data_size_t* cuda_leaf_num_data() const { return cuda_leaf_num_data_.RawData(); }
|
||||
|
||||
const data_size_t* cuda_leaf_data_start() const { return cuda_leaf_data_start_.RawData(); }
|
||||
|
||||
const int* cuda_data_index_to_leaf_index() const { return cuda_data_index_to_leaf_index_.RawData(); }
|
||||
|
||||
bool use_bagging() const { return use_bagging_; }
|
||||
|
||||
private:
|
||||
void CalcBlockDim(const data_size_t num_data_in_leaf);
|
||||
|
||||
void GenDataToLeftBitVector(
|
||||
const data_size_t num_data_in_leaf,
|
||||
const int split_feature_index,
|
||||
const uint32_t split_threshold,
|
||||
const uint32_t* categorical_bitset,
|
||||
const int categorical_bitset_len,
|
||||
const uint8_t split_default_left,
|
||||
const data_size_t leaf_data_start,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index);
|
||||
|
||||
void SplitInner(
|
||||
// input best split info
|
||||
const data_size_t num_data_in_leaf,
|
||||
const CUDASplitInfo* best_split_info,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index,
|
||||
// for leaf splits information update
|
||||
CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
CUDALeafSplitsStruct* larger_leaf_splits,
|
||||
// gather information for CPU, used for launching kernels
|
||||
data_size_t* left_leaf_num_data,
|
||||
data_size_t* right_leaf_num_data,
|
||||
data_size_t* left_leaf_start,
|
||||
data_size_t* right_leaf_start,
|
||||
double* left_leaf_sum_of_hessians,
|
||||
double* right_leaf_sum_of_hessians,
|
||||
double* left_leaf_sum_of_gradients,
|
||||
double* right_leaf_sum_of_gradients,
|
||||
data_size_t* global_left_leaf_num_data,
|
||||
data_size_t* global_right_leaf_num_data);
|
||||
|
||||
// kernel launch functions
|
||||
void LaunchFillDataIndicesBeforeTrain();
|
||||
|
||||
void LaunchSplitInnerKernel(
|
||||
// input best split info
|
||||
const data_size_t num_data_in_leaf,
|
||||
const CUDASplitInfo* best_split_info,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index,
|
||||
// for leaf splits information update
|
||||
CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
CUDALeafSplitsStruct* larger_leaf_splits,
|
||||
// gather information for CPU, used for launching kernels
|
||||
data_size_t* left_leaf_num_data,
|
||||
data_size_t* right_leaf_num_data,
|
||||
data_size_t* left_leaf_start,
|
||||
data_size_t* right_leaf_start,
|
||||
double* left_leaf_sum_of_hessians,
|
||||
double* right_leaf_sum_of_hessians,
|
||||
double* left_leaf_sum_of_gradients,
|
||||
double* right_leaf_sum_of_gradients,
|
||||
data_size_t* global_left_leaf_num_data,
|
||||
data_size_t* global_right_leaf_num_data);
|
||||
|
||||
void LaunchGenDataToLeftBitVectorKernel(
|
||||
const data_size_t num_data_in_leaf,
|
||||
const int split_feature_index,
|
||||
const uint32_t split_threshold,
|
||||
const uint8_t split_default_left,
|
||||
const data_size_t leaf_data_start,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index);
|
||||
|
||||
void LaunchGenDataToLeftBitVectorCategoricalKernel(
|
||||
const data_size_t num_data_in_leaf,
|
||||
const int split_feature_index,
|
||||
const uint32_t* bitset,
|
||||
const int bitset_len,
|
||||
const uint8_t split_default_left,
|
||||
const data_size_t leaf_data_start,
|
||||
const int left_leaf_index,
|
||||
const int right_leaf_index);
|
||||
|
||||
#define GenDataToLeftBitVectorKernel_PARAMS \
|
||||
const BIN_TYPE* column_data, \
|
||||
const data_size_t num_data_in_leaf, \
|
||||
const data_size_t* data_indices_in_leaf, \
|
||||
const uint32_t th, \
|
||||
const uint32_t t_zero_bin, \
|
||||
const uint32_t max_bin, \
|
||||
const uint32_t min_bin, \
|
||||
const uint8_t split_default_to_left, \
|
||||
const uint8_t split_missing_default_to_left
|
||||
|
||||
template <typename BIN_TYPE>
|
||||
void LaunchGenDataToLeftBitVectorKernelInner(
|
||||
GenDataToLeftBitVectorKernel_PARAMS,
|
||||
const bool missing_is_zero,
|
||||
const bool missing_is_na,
|
||||
const bool mfb_is_zero,
|
||||
const bool mfb_is_na,
|
||||
const bool max_bin_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, typename BIN_TYPE>
|
||||
void LaunchGenDataToLeftBitVectorKernelInner0(
|
||||
GenDataToLeftBitVectorKernel_PARAMS,
|
||||
const bool missing_is_na,
|
||||
const bool mfb_is_zero,
|
||||
const bool mfb_is_na,
|
||||
const bool max_bin_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, typename BIN_TYPE>
|
||||
void LaunchGenDataToLeftBitVectorKernelInner1(
|
||||
GenDataToLeftBitVectorKernel_PARAMS,
|
||||
const bool mfb_is_zero,
|
||||
const bool mfb_is_na,
|
||||
const bool max_bin_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, bool MFB_IS_ZERO, typename BIN_TYPE>
|
||||
void LaunchGenDataToLeftBitVectorKernelInner2(
|
||||
GenDataToLeftBitVectorKernel_PARAMS,
|
||||
const bool mfb_is_na,
|
||||
const bool max_bin_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, bool MFB_IS_ZERO, bool MFB_IS_NA, typename BIN_TYPE>
|
||||
void LaunchGenDataToLeftBitVectorKernelInner3(
|
||||
GenDataToLeftBitVectorKernel_PARAMS,
|
||||
const bool max_bin_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, bool MFB_IS_ZERO, bool MFB_IS_NA, bool MAX_TO_LEFT, typename BIN_TYPE>
|
||||
void LaunchGenDataToLeftBitVectorKernelInner4(
|
||||
GenDataToLeftBitVectorKernel_PARAMS,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
#undef GenDataToLeftBitVectorKernel_PARAMS
|
||||
|
||||
#define UpdateDataIndexToLeafIndexKernel_PARAMS \
|
||||
const BIN_TYPE* column_data, \
|
||||
const data_size_t num_data_in_leaf, \
|
||||
const data_size_t* data_indices_in_leaf, \
|
||||
const uint32_t th, \
|
||||
const uint32_t t_zero_bin, \
|
||||
const uint32_t max_bin_ref, \
|
||||
const uint32_t min_bin_ref, \
|
||||
const int left_leaf_index, \
|
||||
const int right_leaf_index, \
|
||||
const int default_leaf_index, \
|
||||
const int missing_default_leaf_index
|
||||
|
||||
template <typename BIN_TYPE>
|
||||
void LaunchUpdateDataIndexToLeafIndexKernel(
|
||||
UpdateDataIndexToLeafIndexKernel_PARAMS,
|
||||
const bool missing_is_zero,
|
||||
const bool missing_is_na,
|
||||
const bool mfb_is_zero,
|
||||
const bool mfb_is_na,
|
||||
const bool max_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, typename BIN_TYPE>
|
||||
void LaunchUpdateDataIndexToLeafIndexKernel_Inner0(
|
||||
UpdateDataIndexToLeafIndexKernel_PARAMS,
|
||||
const bool missing_is_na,
|
||||
const bool mfb_is_zero,
|
||||
const bool mfb_is_na,
|
||||
const bool max_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, typename BIN_TYPE>
|
||||
void LaunchUpdateDataIndexToLeafIndexKernel_Inner1(
|
||||
UpdateDataIndexToLeafIndexKernel_PARAMS,
|
||||
const bool mfb_is_zero,
|
||||
const bool mfb_is_na,
|
||||
const bool max_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, bool MFB_IS_ZERO, typename BIN_TYPE>
|
||||
void LaunchUpdateDataIndexToLeafIndexKernel_Inner2(
|
||||
UpdateDataIndexToLeafIndexKernel_PARAMS,
|
||||
const bool mfb_is_na,
|
||||
const bool max_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, bool MFB_IS_ZERO, bool MFB_IS_NA, typename BIN_TYPE>
|
||||
void LaunchUpdateDataIndexToLeafIndexKernel_Inner3(
|
||||
UpdateDataIndexToLeafIndexKernel_PARAMS,
|
||||
const bool max_to_left,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
template <bool MIN_IS_MAX, bool MISSING_IS_ZERO, bool MISSING_IS_NA, bool MFB_IS_ZERO, bool MFB_IS_NA, bool MAX_TO_LEFT, typename BIN_TYPE>
|
||||
void LaunchUpdateDataIndexToLeafIndexKernel_Inner4(
|
||||
UpdateDataIndexToLeafIndexKernel_PARAMS,
|
||||
const bool is_single_feature_in_column);
|
||||
|
||||
#undef UpdateDataIndexToLeafIndexKernel_PARAMS
|
||||
|
||||
void LaunchAddPredictionToScoreKernel(const double* leaf_value, double* cuda_scores);
|
||||
|
||||
void LaunchFillDataIndexToLeafIndex();
|
||||
|
||||
void LaunchReduceLeafGradStat(
|
||||
const score_t* gradients, const score_t* hessians,
|
||||
CUDATree* tree, double* leaf_grad_stat_buffer, double* leaf_hess_state_buffer) const;
|
||||
|
||||
// Host memory
|
||||
|
||||
// dataset information
|
||||
/*! \brief number of training data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief number of features in training data */
|
||||
int num_features_;
|
||||
/*! \brief number of total bins in training data */
|
||||
int num_total_bin_;
|
||||
/*! \brief bin data stored by column */
|
||||
const CUDAColumnData* cuda_column_data_;
|
||||
/*! \brief grid dimension when splitting one leaf */
|
||||
int grid_dim_;
|
||||
/*! \brief block dimension when splitting one leaf */
|
||||
int block_dim_;
|
||||
/*! \brief data indices used in this iteration */
|
||||
const data_size_t* used_indices_;
|
||||
/*! \brief marks whether a feature is a categorical feature */
|
||||
std::vector<bool> is_categorical_feature_;
|
||||
/*! \brief marks whether a feature is the only feature in its group */
|
||||
std::vector<bool> is_single_feature_in_column_;
|
||||
|
||||
// config information
|
||||
/*! \brief maximum number of leaves in a tree */
|
||||
int num_leaves_;
|
||||
/*! \brief number of threads */
|
||||
int num_threads_;
|
||||
/*! \brief whether to use quantized gradients */
|
||||
bool use_quantized_grad_;
|
||||
|
||||
// per iteration information
|
||||
/*! \brief whether bagging is used in this iteration */
|
||||
bool use_bagging_;
|
||||
/*! \brief number of used data indices in this iteration */
|
||||
data_size_t num_used_indices_;
|
||||
|
||||
// tree structure information
|
||||
/*! \brief current number of leaves in tree */
|
||||
int cur_num_leaves_;
|
||||
|
||||
// split algorithm related
|
||||
/*! \brief maximum number of blocks to aggregate after finding bit vector by blocks */
|
||||
int max_num_split_indices_blocks_;
|
||||
|
||||
// CUDA streams
|
||||
/*! \brief cuda streams used for asynchronizing kernel computing and memory copy */
|
||||
std::vector<cudaStream_t> cuda_streams_;
|
||||
|
||||
|
||||
// CUDA memory, held by this object
|
||||
|
||||
// tree structure information
|
||||
/*! \brief data indices by leaf */
|
||||
CUDAVector<data_size_t> cuda_data_indices_;
|
||||
/*! \brief start position of each leaf in cuda_data_indices_ */
|
||||
CUDAVector<data_size_t> cuda_leaf_data_start_;
|
||||
/*! \brief end position of each leaf in cuda_data_indices_ */
|
||||
CUDAVector<data_size_t> cuda_leaf_data_end_;
|
||||
/*! \brief number of data in each leaf */
|
||||
CUDAVector<data_size_t> cuda_leaf_num_data_;
|
||||
/*! \brief records the histogram of each leaf */
|
||||
CUDAVector<hist_t*> cuda_hist_pool_;
|
||||
/*! \brief records the value of each leaf */
|
||||
CUDAVector<double> cuda_leaf_output_;
|
||||
|
||||
// split data algorithm related
|
||||
CUDAVector<uint16_t> cuda_block_to_left_offset_;
|
||||
/*! \brief maps data index to leaf index, for adding scores to training data set */
|
||||
CUDAVector<int> cuda_data_index_to_leaf_index_;
|
||||
/*! \brief prefix sum of number of data going to left in all blocks */
|
||||
CUDAVector<data_size_t> cuda_block_data_to_left_offset_;
|
||||
/*! \brief prefix sum of number of data going to right in all blocks */
|
||||
CUDAVector<data_size_t> cuda_block_data_to_right_offset_;
|
||||
/*! \brief buffer for splitting data indices, will be copied back to cuda_data_indices_ after split */
|
||||
CUDAVector<data_size_t> cuda_out_data_indices_in_leaf_;
|
||||
|
||||
// split tree structure algorithm related
|
||||
/*! \brief buffer to store split information, prepared to be copied to cpu */
|
||||
CUDAVector<int> cuda_split_info_buffer_;
|
||||
|
||||
// dataset information
|
||||
/*! \brief number of data in training set, for initialization of cuda_leaf_num_data_ and cuda_leaf_data_end_ */
|
||||
CUDAVector<data_size_t> cuda_num_data_;
|
||||
|
||||
|
||||
// CUDA memory, held by other object
|
||||
|
||||
// dataset information
|
||||
/*! \brief beginning of histograms, for initialization of cuda_hist_pool_ */
|
||||
hist_t* cuda_hist_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_DATA_PARTITION_HPP_
|
||||
@@ -0,0 +1,185 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <LightGBM/cuda/cuda_algorithms.hpp>
|
||||
|
||||
#include "cuda_gradient_discretizer.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
__global__ void ReduceMinMaxKernel(
|
||||
const data_size_t num_data,
|
||||
const score_t* input_gradients,
|
||||
const score_t* input_hessians,
|
||||
score_t* grad_min_block_buffer,
|
||||
score_t* grad_max_block_buffer,
|
||||
score_t* hess_min_block_buffer,
|
||||
score_t* hess_max_block_buffer) {
|
||||
__shared__ score_t shared_mem_buffer[WARPSIZE];
|
||||
const data_size_t index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
score_t grad_max_val = kMinScore;
|
||||
score_t grad_min_val = kMaxScore;
|
||||
score_t hess_max_val = kMinScore;
|
||||
score_t hess_min_val = kMaxScore;
|
||||
if (index < num_data) {
|
||||
grad_max_val = input_gradients[index];
|
||||
grad_min_val = input_gradients[index];
|
||||
hess_max_val = input_hessians[index];
|
||||
hess_min_val = input_hessians[index];
|
||||
}
|
||||
grad_min_val = ShuffleReduceMin<score_t>(grad_min_val, shared_mem_buffer, blockDim.x);
|
||||
__syncthreads();
|
||||
grad_max_val = ShuffleReduceMax<score_t>(grad_max_val, shared_mem_buffer, blockDim.x);
|
||||
__syncthreads();
|
||||
hess_min_val = ShuffleReduceMin<score_t>(hess_min_val, shared_mem_buffer, blockDim.x);
|
||||
__syncthreads();
|
||||
hess_max_val = ShuffleReduceMax<score_t>(hess_max_val, shared_mem_buffer, blockDim.x);
|
||||
if (threadIdx.x == 0) {
|
||||
grad_min_block_buffer[blockIdx.x] = grad_min_val;
|
||||
grad_max_block_buffer[blockIdx.x] = grad_max_val;
|
||||
hess_min_block_buffer[blockIdx.x] = hess_min_val;
|
||||
hess_max_block_buffer[blockIdx.x] = hess_max_val;
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void ReduceBlockMinMaxKernel(
|
||||
const int num_blocks,
|
||||
const int grad_discretize_bins,
|
||||
score_t* grad_min_block_buffer,
|
||||
score_t* grad_max_block_buffer,
|
||||
score_t* hess_min_block_buffer,
|
||||
score_t* hess_max_block_buffer) {
|
||||
__shared__ score_t shared_mem_buffer[WARPSIZE];
|
||||
score_t grad_max_val = kMinScore;
|
||||
score_t grad_min_val = kMaxScore;
|
||||
score_t hess_max_val = kMinScore;
|
||||
score_t hess_min_val = kMaxScore;
|
||||
for (int block_index = static_cast<int>(threadIdx.x); block_index < num_blocks; block_index += static_cast<int>(blockDim.x)) {
|
||||
grad_min_val = min(grad_min_val, grad_min_block_buffer[block_index]);
|
||||
grad_max_val = max(grad_max_val, grad_max_block_buffer[block_index]);
|
||||
hess_min_val = min(hess_min_val, hess_min_block_buffer[block_index]);
|
||||
hess_max_val = max(hess_max_val, hess_max_block_buffer[block_index]);
|
||||
}
|
||||
grad_min_val = ShuffleReduceMin<score_t>(grad_min_val, shared_mem_buffer, blockDim.x);
|
||||
__syncthreads();
|
||||
grad_max_val = ShuffleReduceMax<score_t>(grad_max_val, shared_mem_buffer, blockDim.x);
|
||||
__syncthreads();
|
||||
hess_max_val = ShuffleReduceMax<score_t>(hess_max_val, shared_mem_buffer, blockDim.x);
|
||||
__syncthreads();
|
||||
hess_max_val = ShuffleReduceMax<score_t>(hess_max_val, shared_mem_buffer, blockDim.x);
|
||||
if (threadIdx.x == 0) {
|
||||
const score_t grad_abs_max = max(fabs(grad_min_val), fabs(grad_max_val));
|
||||
const score_t hess_abs_max = max(fabs(hess_min_val), fabs(hess_max_val));
|
||||
grad_min_block_buffer[0] = 1.0f / (grad_abs_max / (grad_discretize_bins / 2));
|
||||
grad_max_block_buffer[0] = (grad_abs_max / (grad_discretize_bins / 2));
|
||||
hess_min_block_buffer[0] = 1.0f / (hess_abs_max / (grad_discretize_bins));
|
||||
hess_max_block_buffer[0] = (hess_abs_max / (grad_discretize_bins));
|
||||
}
|
||||
}
|
||||
|
||||
template <bool STOCHASTIC_ROUNDING>
|
||||
__global__ void DiscretizeGradientsKernel(
|
||||
const data_size_t num_data,
|
||||
const score_t* input_gradients,
|
||||
const score_t* input_hessians,
|
||||
const score_t* grad_scale_ptr,
|
||||
const score_t* hess_scale_ptr,
|
||||
const int iter,
|
||||
const int* random_values_use_start,
|
||||
const score_t* gradient_random_values,
|
||||
const score_t* hessian_random_values,
|
||||
const int grad_discretize_bins,
|
||||
int8_t* output_gradients_and_hessians) {
|
||||
const int start = random_values_use_start[iter];
|
||||
const data_size_t index = static_cast<data_size_t>(threadIdx.x + blockIdx.x * blockDim.x);
|
||||
const score_t grad_scale = *grad_scale_ptr;
|
||||
const score_t hess_scale = *hess_scale_ptr;
|
||||
int16_t* output_gradients_and_hessians_ptr = reinterpret_cast<int16_t*>(output_gradients_and_hessians);
|
||||
if (index < num_data) {
|
||||
if (STOCHASTIC_ROUNDING) {
|
||||
const data_size_t index_offset = (index + start) % num_data;
|
||||
const score_t gradient = input_gradients[index];
|
||||
const score_t hessian = input_hessians[index];
|
||||
const score_t gradient_random_value = gradient_random_values[index_offset];
|
||||
const score_t hessian_random_value = hessian_random_values[index_offset];
|
||||
output_gradients_and_hessians_ptr[2 * index + 1] = gradient > 0.0f ?
|
||||
static_cast<int16_t>(gradient * grad_scale + gradient_random_value) :
|
||||
static_cast<int16_t>(gradient * grad_scale - gradient_random_value);
|
||||
output_gradients_and_hessians_ptr[2 * index] = static_cast<int16_t>(hessian * hess_scale + hessian_random_value);
|
||||
} else {
|
||||
const score_t gradient = input_gradients[index];
|
||||
const score_t hessian = input_hessians[index];
|
||||
output_gradients_and_hessians_ptr[2 * index + 1] = gradient > 0.0f ?
|
||||
static_cast<int16_t>(gradient * grad_scale + 0.5) :
|
||||
static_cast<int16_t>(gradient * grad_scale - 0.5);
|
||||
output_gradients_and_hessians_ptr[2 * index] = static_cast<int16_t>(hessian * hess_scale + 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAGradientDiscretizer::DiscretizeGradients(
|
||||
const data_size_t num_data,
|
||||
const score_t* input_gradients,
|
||||
const score_t* input_hessians) {
|
||||
ReduceMinMaxKernel<<<num_reduce_blocks_, CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE>>>(
|
||||
num_data, input_gradients, input_hessians,
|
||||
grad_min_block_buffer_.RawData(),
|
||||
grad_max_block_buffer_.RawData(),
|
||||
hess_min_block_buffer_.RawData(),
|
||||
hess_max_block_buffer_.RawData());
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
ReduceBlockMinMaxKernel<<<1, CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE>>>(
|
||||
num_reduce_blocks_,
|
||||
num_grad_quant_bins_,
|
||||
grad_min_block_buffer_.RawData(),
|
||||
grad_max_block_buffer_.RawData(),
|
||||
hess_min_block_buffer_.RawData(),
|
||||
hess_max_block_buffer_.RawData());
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
|
||||
if (nccl_communicator_ != nullptr) {
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
cudaStream_t cuda_stream = CUDAStreamCreate();
|
||||
NCCLGroupStart();
|
||||
NCCLAllReduce<score_t>(grad_min_block_buffer_.RawDataReadOnly(), grad_min_block_buffer_.RawData(), 1, ncclFloat32, ncclMin, nccl_communicator_, cuda_stream);
|
||||
NCCLAllReduce<score_t>(hess_min_block_buffer_.RawDataReadOnly(), hess_min_block_buffer_.RawData(), 1, ncclFloat32, ncclMin, nccl_communicator_, cuda_stream);
|
||||
NCCLAllReduce<score_t>(grad_max_block_buffer_.RawDataReadOnly(), grad_max_block_buffer_.RawData(), 1, ncclFloat32, ncclMax, nccl_communicator_, cuda_stream);
|
||||
NCCLAllReduce<score_t>(hess_max_block_buffer_.RawDataReadOnly(), hess_max_block_buffer_.RawData(), 1, ncclFloat32, ncclMax, nccl_communicator_, cuda_stream);
|
||||
NCCLGroupEnd();
|
||||
SynchronizeCUDAStream(cuda_stream, __FILE__, __LINE__);
|
||||
CUDAStreamDestroy(cuda_stream);
|
||||
}
|
||||
|
||||
#define DiscretizeGradientsKernel_ARGS \
|
||||
num_data, \
|
||||
input_gradients, \
|
||||
input_hessians, \
|
||||
grad_min_block_buffer_.RawData(), \
|
||||
hess_min_block_buffer_.RawData(), \
|
||||
iter_, \
|
||||
random_values_use_start_.RawData(), \
|
||||
gradient_random_values_.RawData(), \
|
||||
hessian_random_values_.RawData(), \
|
||||
num_grad_quant_bins_, \
|
||||
discretized_gradients_and_hessians_.RawData()
|
||||
|
||||
if (stochastic_rounding_) {
|
||||
DiscretizeGradientsKernel<true><<<num_reduce_blocks_, CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE>>>(DiscretizeGradientsKernel_ARGS);
|
||||
} else {
|
||||
DiscretizeGradientsKernel<false><<<num_reduce_blocks_, CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE>>>(DiscretizeGradientsKernel_ARGS);
|
||||
}
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
++iter_;
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,121 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifndef LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_
|
||||
#define LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/bin.h>
|
||||
#include <LightGBM/meta.h>
|
||||
#include <LightGBM/cuda/cuda_utils.hu>
|
||||
#include <LightGBM/utils/threading.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_leaf_splits.hpp"
|
||||
#include "../gradient_discretizer.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
#define CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE (1024)
|
||||
|
||||
class CUDAGradientDiscretizer: public GradientDiscretizer, public NCCLInfo {
|
||||
public:
|
||||
CUDAGradientDiscretizer(int num_grad_quant_bins, int num_trees, int random_seed, bool is_constant_hessian, bool stochastic_roudning):
|
||||
GradientDiscretizer(num_grad_quant_bins, num_trees, random_seed, is_constant_hessian, stochastic_roudning) {
|
||||
}
|
||||
|
||||
~CUDAGradientDiscretizer() {}
|
||||
|
||||
void DiscretizeGradients(
|
||||
const data_size_t num_data,
|
||||
const score_t* input_gradients,
|
||||
const score_t* input_hessians) override;
|
||||
|
||||
const int8_t* discretized_gradients_and_hessians() const override { return discretized_gradients_and_hessians_.RawData(); }
|
||||
|
||||
double grad_scale() const override {
|
||||
Log::Fatal("grad_scale() of CUDAGradientDiscretizer should not be called.");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double hess_scale() const override {
|
||||
Log::Fatal("hess_scale() of CUDAGradientDiscretizer should not be called.");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
const score_t* grad_scale_ptr() const { return grad_max_block_buffer_.RawData(); }
|
||||
|
||||
const score_t* hess_scale_ptr() const { return hess_max_block_buffer_.RawData(); }
|
||||
|
||||
void Init(const data_size_t num_data, const int num_leaves,
|
||||
const int num_features, const Dataset* train_data) override {
|
||||
GradientDiscretizer::Init(num_data, num_leaves, num_features, train_data);
|
||||
discretized_gradients_and_hessians_.Resize(num_data * 2);
|
||||
num_reduce_blocks_ = (num_data + CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE - 1) / CUDA_GRADIENT_DISCRETIZER_BLOCK_SIZE;
|
||||
grad_min_block_buffer_.Resize(num_reduce_blocks_);
|
||||
grad_max_block_buffer_.Resize(num_reduce_blocks_);
|
||||
hess_min_block_buffer_.Resize(num_reduce_blocks_);
|
||||
hess_max_block_buffer_.Resize(num_reduce_blocks_);
|
||||
random_values_use_start_.Resize(num_trees_);
|
||||
gradient_random_values_.Resize(num_data);
|
||||
hessian_random_values_.Resize(num_data);
|
||||
|
||||
std::vector<score_t> gradient_random_values(num_data, 0.0f);
|
||||
std::vector<score_t> hessian_random_values(num_data, 0.0f);
|
||||
std::vector<int> random_values_use_start(num_trees_, 0);
|
||||
|
||||
const int num_threads = OMP_NUM_THREADS();
|
||||
|
||||
std::mt19937 random_values_use_start_eng = std::mt19937(random_seed_);
|
||||
std::uniform_int_distribution<data_size_t> random_values_use_start_dist = std::uniform_int_distribution<data_size_t>(0, num_data);
|
||||
for (int tree_index = 0; tree_index < num_trees_; ++tree_index) {
|
||||
random_values_use_start[tree_index] = random_values_use_start_dist(random_values_use_start_eng);
|
||||
}
|
||||
|
||||
int num_blocks = 0;
|
||||
data_size_t block_size = 0;
|
||||
Threading::BlockInfo<data_size_t>(num_data, 512, &num_blocks, &block_size);
|
||||
#pragma omp parallel for schedule(static, 1) num_threads(num_threads)
|
||||
for (int thread_id = 0; thread_id < num_blocks; ++thread_id) {
|
||||
const data_size_t start = thread_id * block_size;
|
||||
const data_size_t end = std::min(start + block_size, num_data);
|
||||
std::mt19937 gradient_random_values_eng(random_seed_ + thread_id);
|
||||
std::uniform_real_distribution<double> gradient_random_values_dist(0.0f, 1.0f);
|
||||
std::mt19937 hessian_random_values_eng(random_seed_ + thread_id + num_threads);
|
||||
std::uniform_real_distribution<double> hessian_random_values_dist(0.0f, 1.0f);
|
||||
for (data_size_t i = start; i < end; ++i) {
|
||||
gradient_random_values[i] = gradient_random_values_dist(gradient_random_values_eng);
|
||||
hessian_random_values[i] = hessian_random_values_dist(hessian_random_values_eng);
|
||||
}
|
||||
}
|
||||
|
||||
CopyFromHostToCUDADevice<score_t>(gradient_random_values_.RawData(), gradient_random_values.data(), gradient_random_values.size(), __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<score_t>(hessian_random_values_.RawData(), hessian_random_values.data(), hessian_random_values.size(), __FILE__, __LINE__);
|
||||
CopyFromHostToCUDADevice<int>(random_values_use_start_.RawData(), random_values_use_start.data(), random_values_use_start.size(), __FILE__, __LINE__);
|
||||
iter_ = 0;
|
||||
}
|
||||
|
||||
protected:
|
||||
mutable CUDAVector<int8_t> discretized_gradients_and_hessians_;
|
||||
mutable CUDAVector<score_t> grad_min_block_buffer_;
|
||||
mutable CUDAVector<score_t> grad_max_block_buffer_;
|
||||
mutable CUDAVector<score_t> hess_min_block_buffer_;
|
||||
mutable CUDAVector<score_t> hess_max_block_buffer_;
|
||||
CUDAVector<int> random_values_use_start_;
|
||||
CUDAVector<score_t> gradient_random_values_;
|
||||
CUDAVector<score_t> hessian_random_values_;
|
||||
int num_reduce_blocks_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_GRADIENT_DISCRETIZER_HPP_
|
||||
@@ -0,0 +1,192 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_histogram_constructor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDAHistogramConstructor::CUDAHistogramConstructor(
|
||||
const Dataset* train_data,
|
||||
const int num_leaves,
|
||||
const int num_threads,
|
||||
const std::vector<uint32_t>& feature_hist_offsets,
|
||||
const int min_data_in_leaf,
|
||||
const double min_sum_hessian_in_leaf,
|
||||
const int gpu_device_id,
|
||||
const bool gpu_use_dp,
|
||||
const bool use_quantized_grad,
|
||||
const int num_grad_quant_bins):
|
||||
num_data_(train_data->num_data()),
|
||||
num_features_(train_data->num_features()),
|
||||
num_leaves_(num_leaves),
|
||||
num_threads_(num_threads),
|
||||
min_data_in_leaf_(min_data_in_leaf),
|
||||
min_sum_hessian_in_leaf_(min_sum_hessian_in_leaf),
|
||||
gpu_device_id_(gpu_device_id),
|
||||
gpu_use_dp_(gpu_use_dp),
|
||||
use_quantized_grad_(use_quantized_grad),
|
||||
num_grad_quant_bins_(num_grad_quant_bins) {
|
||||
InitFeatureMetaInfo(train_data, feature_hist_offsets);
|
||||
cuda_row_data_.reset(nullptr);
|
||||
}
|
||||
|
||||
CUDAHistogramConstructor::~CUDAHistogramConstructor() {
|
||||
gpuAssert(cudaStreamDestroy(cuda_stream_), __FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::InitFeatureMetaInfo(const Dataset* train_data, const std::vector<uint32_t>& feature_hist_offsets) {
|
||||
need_fix_histogram_features_.clear();
|
||||
need_fix_histogram_features_num_bin_aligend_.clear();
|
||||
feature_num_bins_.clear();
|
||||
feature_most_freq_bins_.clear();
|
||||
for (int feature_index = 0; feature_index < train_data->num_features(); ++feature_index) {
|
||||
const BinMapper* bin_mapper = train_data->FeatureBinMapper(feature_index);
|
||||
const uint32_t most_freq_bin = bin_mapper->GetMostFreqBin();
|
||||
if (most_freq_bin != 0) {
|
||||
need_fix_histogram_features_.emplace_back(feature_index);
|
||||
uint32_t num_bin_ref = static_cast<uint32_t>(bin_mapper->num_bin()) - 1;
|
||||
uint32_t num_bin_aligned = 1;
|
||||
while (num_bin_ref > 0) {
|
||||
num_bin_aligned <<= 1;
|
||||
num_bin_ref >>= 1;
|
||||
}
|
||||
need_fix_histogram_features_num_bin_aligend_.emplace_back(num_bin_aligned);
|
||||
}
|
||||
feature_num_bins_.emplace_back(static_cast<uint32_t>(bin_mapper->num_bin()));
|
||||
feature_most_freq_bins_.emplace_back(most_freq_bin);
|
||||
}
|
||||
feature_hist_offsets_.clear();
|
||||
for (size_t i = 0; i < feature_hist_offsets.size(); ++i) {
|
||||
feature_hist_offsets_.emplace_back(feature_hist_offsets[i]);
|
||||
}
|
||||
if (feature_hist_offsets.empty()) {
|
||||
num_total_bin_ = 0;
|
||||
} else {
|
||||
num_total_bin_ = static_cast<int>(feature_hist_offsets.back());
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::BeforeTrain(const score_t* gradients, const score_t* hessians) {
|
||||
cuda_gradients_ = gradients;
|
||||
cuda_hessians_ = hessians;
|
||||
cuda_hist_.SetValue(0);
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::Init(const Dataset* train_data, TrainingShareStates* share_state) {
|
||||
cuda_hist_.Resize(static_cast<size_t>(num_total_bin_ * 2 * num_leaves_));
|
||||
cuda_hist_.SetValue(0);
|
||||
|
||||
cuda_feature_num_bins_.InitFromHostVector(feature_num_bins_);
|
||||
cuda_feature_hist_offsets_.InitFromHostVector(feature_hist_offsets_);
|
||||
cuda_feature_most_freq_bins_.InitFromHostVector(feature_most_freq_bins_);
|
||||
|
||||
cuda_row_data_.reset(new CUDARowData(train_data, share_state, gpu_device_id_, gpu_use_dp_));
|
||||
cuda_row_data_->Init(train_data, share_state);
|
||||
|
||||
CUDASUCCESS_OR_FATAL(cudaStreamCreate(&cuda_stream_));
|
||||
|
||||
cuda_need_fix_histogram_features_.InitFromHostVector(need_fix_histogram_features_);
|
||||
cuda_need_fix_histogram_features_num_bin_aligned_.InitFromHostVector(need_fix_histogram_features_num_bin_aligend_);
|
||||
|
||||
if (cuda_row_data_->NumLargeBinPartition() > 0) {
|
||||
int grid_dim_x = 0, grid_dim_y = 0, block_dim_x = 0, block_dim_y = 0;
|
||||
CalcConstructHistogramKernelDim(&grid_dim_x, &grid_dim_y, &block_dim_x, &block_dim_y, num_data_);
|
||||
const size_t buffer_size = static_cast<size_t>(grid_dim_y) * static_cast<size_t>(num_total_bin_);
|
||||
if (!use_quantized_grad_) {
|
||||
if (gpu_use_dp_) {
|
||||
// need to double the size of histogram buffer in global memory when using double precision in histogram construction
|
||||
cuda_hist_buffer_.Resize(buffer_size * 4);
|
||||
} else {
|
||||
cuda_hist_buffer_.Resize(buffer_size * 2);
|
||||
}
|
||||
} else {
|
||||
// use only half the size of histogram buffer in global memory when quantized training since each gradient and hessian takes only 2 bytes
|
||||
cuda_hist_buffer_.Resize(buffer_size);
|
||||
}
|
||||
}
|
||||
hist_buffer_for_num_bit_change_.Resize(num_total_bin_ * 2);
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::ConstructHistogramForLeaf(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* /*cuda_larger_leaf_splits*/,
|
||||
const data_size_t global_num_data_in_smaller_leaf,
|
||||
const data_size_t global_num_data_in_larger_leaf,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const data_size_t /*num_data_in_larger_leaf*/,
|
||||
const double sum_hessians_in_smaller_leaf,
|
||||
const double sum_hessians_in_larger_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins) {
|
||||
if ((global_num_data_in_smaller_leaf <= min_data_in_leaf_ || sum_hessians_in_smaller_leaf <= min_sum_hessian_in_leaf_) &&
|
||||
(global_num_data_in_larger_leaf <= min_data_in_leaf_ || sum_hessians_in_larger_leaf <= min_sum_hessian_in_leaf_)) {
|
||||
return;
|
||||
}
|
||||
LaunchConstructHistogramKernel(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::SubtractHistogramForLeaf(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits,
|
||||
const bool use_quantized_grad,
|
||||
const uint8_t parent_num_bits_in_histogram_bins,
|
||||
const uint8_t smaller_num_bits_in_histogram_bins,
|
||||
const uint8_t larger_num_bits_in_histogram_bins) {
|
||||
global_timer.Start("CUDAHistogramConstructor::ConstructHistogramForLeaf::LaunchSubtractHistogramKernel");
|
||||
LaunchSubtractHistogramKernel(cuda_smaller_leaf_splits, cuda_larger_leaf_splits, use_quantized_grad,
|
||||
parent_num_bits_in_histogram_bins, smaller_num_bits_in_histogram_bins, larger_num_bits_in_histogram_bins);
|
||||
global_timer.Stop("CUDAHistogramConstructor::ConstructHistogramForLeaf::LaunchSubtractHistogramKernel");
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::CalcConstructHistogramKernelDim(
|
||||
int* grid_dim_x,
|
||||
int* grid_dim_y,
|
||||
int* block_dim_x,
|
||||
int* block_dim_y,
|
||||
const data_size_t num_data_in_smaller_leaf) {
|
||||
*block_dim_x = cuda_row_data_->max_num_column_per_partition();
|
||||
*block_dim_y = NUM_THREADS_PER_BLOCK / cuda_row_data_->max_num_column_per_partition();
|
||||
*grid_dim_x = cuda_row_data_->num_feature_partitions();
|
||||
*grid_dim_y = std::max(min_grid_dim_y_,
|
||||
((num_data_in_smaller_leaf + NUM_DATA_PER_THREAD - 1) / NUM_DATA_PER_THREAD + (*block_dim_y) - 1) / (*block_dim_y));
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::ResetTrainingData(const Dataset* train_data, TrainingShareStates* share_states) {
|
||||
num_data_ = train_data->num_data();
|
||||
num_features_ = train_data->num_features();
|
||||
InitFeatureMetaInfo(train_data, share_states->feature_hist_offsets());
|
||||
|
||||
cuda_hist_.Resize(static_cast<size_t>(num_total_bin_ * 2 * num_leaves_));
|
||||
cuda_hist_.SetValue(0);
|
||||
cuda_feature_num_bins_.InitFromHostVector(feature_num_bins_);
|
||||
cuda_feature_hist_offsets_.InitFromHostVector(feature_hist_offsets_);
|
||||
cuda_feature_most_freq_bins_.InitFromHostVector(feature_most_freq_bins_);
|
||||
|
||||
cuda_row_data_.reset(new CUDARowData(train_data, share_states, gpu_device_id_, gpu_use_dp_));
|
||||
cuda_row_data_->Init(train_data, share_states);
|
||||
|
||||
cuda_need_fix_histogram_features_.InitFromHostVector(need_fix_histogram_features_);
|
||||
cuda_need_fix_histogram_features_num_bin_aligned_.InitFromHostVector(need_fix_histogram_features_num_bin_aligend_);
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::ResetConfig(const Config* config) {
|
||||
num_threads_ = OMP_NUM_THREADS();
|
||||
num_leaves_ = config->num_leaves;
|
||||
min_data_in_leaf_ = config->min_data_in_leaf;
|
||||
min_sum_hessian_in_leaf_ = config->min_sum_hessian_in_leaf;
|
||||
cuda_hist_.Resize(static_cast<size_t>(num_total_bin_ * 2 * num_leaves_));
|
||||
cuda_hist_.SetValue(0);
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,963 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
* Modifications Copyright(C) 2023 Advanced Micro Devices, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_histogram_constructor.hpp"
|
||||
|
||||
#include <LightGBM/cuda/cuda_algorithms.hpp>
|
||||
#include <LightGBM/cuda/cuda_rocm_interop.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
template <typename BIN_TYPE, typename HIST_TYPE, size_t SHARED_HIST_SIZE>
|
||||
__global__ void CUDAConstructHistogramDenseKernel(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const score_t* cuda_gradients,
|
||||
const score_t* cuda_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const uint32_t* column_hist_offsets,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const int* feature_partition_column_index_offsets,
|
||||
const data_size_t num_data) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
__shared__ HIST_TYPE shared_hist[SHARED_HIST_SIZE];
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x];
|
||||
const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1];
|
||||
const BIN_TYPE* data_ptr = data + static_cast<size_t>(partition_column_start) * num_data;
|
||||
const int num_columns_in_partition = partition_column_end - partition_column_start;
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1;
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (static_cast<size_t>(blockIdx_y) * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const int column_index = static_cast<int>(threadIdx.x) + partition_column_start;
|
||||
if (threadIdx.x < static_cast<unsigned int>(num_columns_in_partition)) {
|
||||
HIST_TYPE* shared_hist_ptr = shared_hist + (column_hist_offsets[column_index] << 1);
|
||||
for (data_size_t inner_data_index = static_cast<data_size_t>(threadIdx.y); inner_data_index < block_num_data; inner_data_index += blockDim.y) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const score_t grad = cuda_gradients[data_index];
|
||||
const score_t hess = cuda_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[static_cast<size_t>(data_index) * num_columns_in_partition + threadIdx.x]);
|
||||
const uint32_t pos = bin << 1;
|
||||
HIST_TYPE* pos_ptr = shared_hist_ptr + pos;
|
||||
atomicAdd_block(pos_ptr, grad);
|
||||
atomicAdd_block(pos_ptr + 1, hess);
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
hist_t* feature_histogram_ptr = smaller_leaf_splits->hist_in_leaf + (partition_hist_start << 1);
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
atomicAdd_system(feature_histogram_ptr + i, shared_hist[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, typename DATA_PTR_TYPE, typename HIST_TYPE, size_t SHARED_HIST_SIZE>
|
||||
__global__ void CUDAConstructHistogramSparseKernel(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const score_t* cuda_gradients,
|
||||
const score_t* cuda_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const DATA_PTR_TYPE* row_ptr,
|
||||
const DATA_PTR_TYPE* partition_ptr,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const data_size_t num_data) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
__shared__ HIST_TYPE shared_hist[SHARED_HIST_SIZE];
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const DATA_PTR_TYPE* block_row_ptr = row_ptr + static_cast<size_t>(blockIdx.x) * (num_data + 1);
|
||||
const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x];
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1;
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int threadIdx_y = threadIdx.y;
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y;
|
||||
const data_size_t remainder = block_num_data % blockDim.y;
|
||||
const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast<data_size_t>(threadIdx_y >= remainder);
|
||||
data_size_t inner_data_index = static_cast<data_size_t>(threadIdx_y);
|
||||
for (data_size_t i = 0; i < num_iteration_this; ++i) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const DATA_PTR_TYPE row_start = block_row_ptr[data_index];
|
||||
const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1];
|
||||
const DATA_PTR_TYPE row_size = row_end - row_start;
|
||||
if (threadIdx.x < row_size) {
|
||||
const score_t grad = cuda_gradients[data_index];
|
||||
const score_t hess = cuda_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[row_start + threadIdx.x]);
|
||||
const uint32_t pos = bin << 1;
|
||||
HIST_TYPE* pos_ptr = shared_hist + pos;
|
||||
atomicAdd_block(pos_ptr, grad);
|
||||
atomicAdd_block(pos_ptr + 1, hess);
|
||||
}
|
||||
inner_data_index += blockDim.y;
|
||||
}
|
||||
__syncthreads();
|
||||
hist_t* feature_histogram_ptr = smaller_leaf_splits->hist_in_leaf + (partition_hist_start << 1);
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
atomicAdd_system(feature_histogram_ptr + i, shared_hist[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, typename HIST_TYPE>
|
||||
__global__ void CUDAConstructHistogramDenseKernel_GlobalMemory(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const score_t* cuda_gradients,
|
||||
const score_t* cuda_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const uint32_t* column_hist_offsets,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const int* feature_partition_column_index_offsets,
|
||||
const data_size_t num_data,
|
||||
HIST_TYPE* global_hist_buffer) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x];
|
||||
const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1];
|
||||
const BIN_TYPE* data_ptr = data + static_cast<size_t>(partition_column_start) * num_data;
|
||||
const int num_columns_in_partition = partition_column_end - partition_column_start;
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1;
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
const int num_total_bin = column_hist_offsets_full[gridDim.x];
|
||||
HIST_TYPE* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int threadIdx_y = threadIdx.y;
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (static_cast<size_t>(blockIdx_y) * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y;
|
||||
const data_size_t remainder = block_num_data % blockDim.y;
|
||||
const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast<data_size_t>(threadIdx_y >= remainder);
|
||||
data_size_t inner_data_index = static_cast<data_size_t>(threadIdx_y);
|
||||
const int column_index = static_cast<int>(threadIdx.x) + partition_column_start;
|
||||
if (threadIdx.x < static_cast<unsigned int>(num_columns_in_partition)) {
|
||||
HIST_TYPE* shared_hist_ptr = shared_hist + (column_hist_offsets[column_index] << 1);
|
||||
for (data_size_t i = 0; i < num_iteration_this; ++i) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const score_t grad = cuda_gradients[data_index];
|
||||
const score_t hess = cuda_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[static_cast<size_t>(data_index) * num_columns_in_partition + threadIdx.x]);
|
||||
const uint32_t pos = bin << 1;
|
||||
HIST_TYPE* pos_ptr = shared_hist_ptr + pos;
|
||||
atomicAdd_block(pos_ptr, grad);
|
||||
atomicAdd_block(pos_ptr + 1, hess);
|
||||
inner_data_index += blockDim.y;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
hist_t* feature_histogram_ptr = smaller_leaf_splits->hist_in_leaf + (partition_hist_start << 1);
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
atomicAdd_system(feature_histogram_ptr + i, shared_hist[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, typename HIST_TYPE, typename DATA_PTR_TYPE>
|
||||
__global__ void CUDAConstructHistogramSparseKernel_GlobalMemory(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const score_t* cuda_gradients,
|
||||
const score_t* cuda_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const DATA_PTR_TYPE* row_ptr,
|
||||
const DATA_PTR_TYPE* partition_ptr,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const data_size_t num_data,
|
||||
HIST_TYPE* global_hist_buffer) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const DATA_PTR_TYPE* block_row_ptr = row_ptr + static_cast<size_t>(blockIdx.x) * (num_data + 1);
|
||||
const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x];
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start) << 1;
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
const int num_total_bin = column_hist_offsets_full[gridDim.x];
|
||||
HIST_TYPE* shared_hist = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start) * 2;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int threadIdx_y = threadIdx.y;
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y;
|
||||
const data_size_t remainder = block_num_data % blockDim.y;
|
||||
const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast<data_size_t>(threadIdx_y >= remainder);
|
||||
data_size_t inner_data_index = static_cast<data_size_t>(threadIdx_y);
|
||||
for (data_size_t i = 0; i < num_iteration_this; ++i) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const DATA_PTR_TYPE row_start = block_row_ptr[data_index];
|
||||
const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1];
|
||||
const DATA_PTR_TYPE row_size = row_end - row_start;
|
||||
if (threadIdx.x < row_size) {
|
||||
const score_t grad = cuda_gradients[data_index];
|
||||
const score_t hess = cuda_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[row_start + threadIdx.x]);
|
||||
const uint32_t pos = bin << 1;
|
||||
HIST_TYPE* pos_ptr = shared_hist + pos;
|
||||
atomicAdd_block(pos_ptr, grad);
|
||||
atomicAdd_block(pos_ptr + 1, hess);
|
||||
}
|
||||
inner_data_index += blockDim.y;
|
||||
}
|
||||
__syncthreads();
|
||||
hist_t* feature_histogram_ptr = smaller_leaf_splits->hist_in_leaf + (partition_hist_start << 1);
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
atomicAdd_system(feature_histogram_ptr + i, shared_hist[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, int SHARED_HIST_SIZE, bool USE_16BIT_HIST>
|
||||
__global__ void CUDAConstructDiscretizedHistogramDenseKernel(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const int32_t* cuda_gradients_and_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const uint32_t* column_hist_offsets,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const int* feature_partition_column_index_offsets,
|
||||
const data_size_t num_data) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
__shared__ int16_t shared_hist[SHARED_HIST_SIZE];
|
||||
int32_t* shared_hist_packed = reinterpret_cast<int32_t*>(shared_hist);
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x];
|
||||
const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1];
|
||||
const BIN_TYPE* data_ptr = data + partition_column_start * num_data;
|
||||
const int num_columns_in_partition = partition_column_end - partition_column_start;
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start);
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist_packed[i] = 0;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int threadIdx_y = threadIdx.y;
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y;
|
||||
const data_size_t remainder = block_num_data % blockDim.y;
|
||||
const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast<data_size_t>(threadIdx_y >= remainder);
|
||||
data_size_t inner_data_index = static_cast<data_size_t>(threadIdx_y);
|
||||
const int column_index = static_cast<int>(threadIdx.x) + partition_column_start;
|
||||
if (threadIdx.x < static_cast<unsigned int>(num_columns_in_partition)) {
|
||||
int32_t* shared_hist_ptr = shared_hist_packed + (column_hist_offsets[column_index]);
|
||||
for (data_size_t i = 0; i < num_iteration_this; ++i) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[data_index * num_columns_in_partition + threadIdx.x]);
|
||||
int32_t* pos_ptr = shared_hist_ptr + bin;
|
||||
atomicAdd_block(pos_ptr, grad_and_hess);
|
||||
inner_data_index += blockDim.y;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
if (USE_16BIT_HIST) {
|
||||
int32_t* feature_histogram_ptr = reinterpret_cast<int32_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess);
|
||||
}
|
||||
} else {
|
||||
atomic_add_long_t* feature_histogram_ptr = reinterpret_cast<atomic_add_long_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
const int64_t packed_grad_hess_int64 = (static_cast<int64_t>(static_cast<int16_t>(packed_grad_hess >> 16)) << 32) | (static_cast<int64_t>(packed_grad_hess & 0x0000ffff));
|
||||
atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, typename DATA_PTR_TYPE, int SHARED_HIST_SIZE, bool USE_16BIT_HIST>
|
||||
__global__ void CUDAConstructDiscretizedHistogramSparseKernel(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const int32_t* cuda_gradients_and_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const DATA_PTR_TYPE* row_ptr,
|
||||
const DATA_PTR_TYPE* partition_ptr,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const data_size_t num_data) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
__shared__ int16_t shared_hist[SHARED_HIST_SIZE];
|
||||
int32_t* shared_hist_packed = reinterpret_cast<int32_t*>(shared_hist);
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const DATA_PTR_TYPE* block_row_ptr = row_ptr + blockIdx.x * (num_data + 1);
|
||||
const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x];
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start);
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist_packed[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int threadIdx_y = threadIdx.y;
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y;
|
||||
const data_size_t remainder = block_num_data % blockDim.y;
|
||||
const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast<data_size_t>(threadIdx_y >= remainder);
|
||||
data_size_t inner_data_index = static_cast<data_size_t>(threadIdx_y);
|
||||
for (data_size_t i = 0; i < num_iteration_this; ++i) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const DATA_PTR_TYPE row_start = block_row_ptr[data_index];
|
||||
const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1];
|
||||
const DATA_PTR_TYPE row_size = row_end - row_start;
|
||||
if (threadIdx.x < row_size) {
|
||||
const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[row_start + threadIdx.x]);
|
||||
int32_t* pos_ptr = shared_hist_packed + bin;
|
||||
atomicAdd_block(pos_ptr, grad_and_hess);
|
||||
}
|
||||
inner_data_index += blockDim.y;
|
||||
}
|
||||
__syncthreads();
|
||||
if (USE_16BIT_HIST) {
|
||||
int32_t* feature_histogram_ptr = reinterpret_cast<int32_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess);
|
||||
}
|
||||
} else {
|
||||
atomic_add_long_t* feature_histogram_ptr = reinterpret_cast<atomic_add_long_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
const int64_t packed_grad_hess_int64 = (static_cast<int64_t>(static_cast<int16_t>(packed_grad_hess >> 16)) << 32) | (static_cast<int64_t>(packed_grad_hess & 0x0000ffff));
|
||||
atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, int SHARED_HIST_SIZE, bool USE_16BIT_HIST>
|
||||
__global__ void CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const int32_t* cuda_gradients_and_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const uint32_t* column_hist_offsets,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const int* feature_partition_column_index_offsets,
|
||||
const data_size_t num_data,
|
||||
int32_t* global_hist_buffer) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const int partition_column_start = feature_partition_column_index_offsets[blockIdx.x];
|
||||
const int partition_column_end = feature_partition_column_index_offsets[blockIdx.x + 1];
|
||||
const BIN_TYPE* data_ptr = data + partition_column_start * num_data;
|
||||
const int num_columns_in_partition = partition_column_end - partition_column_start;
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start);
|
||||
const int num_total_bin = column_hist_offsets_full[gridDim.x];
|
||||
int32_t* shared_hist_packed = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start);
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist_packed[i] = 0;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int threadIdx_y = threadIdx.y;
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y;
|
||||
const data_size_t remainder = block_num_data % blockDim.y;
|
||||
const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast<data_size_t>(threadIdx_y >= remainder);
|
||||
data_size_t inner_data_index = static_cast<data_size_t>(threadIdx_y);
|
||||
const int column_index = static_cast<int>(threadIdx.x) + partition_column_start;
|
||||
if (threadIdx.x < static_cast<unsigned int>(num_columns_in_partition)) {
|
||||
int32_t* shared_hist_ptr = shared_hist_packed + (column_hist_offsets[column_index]);
|
||||
for (data_size_t i = 0; i < num_iteration_this; ++i) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[data_index * num_columns_in_partition + threadIdx.x]);
|
||||
int32_t* pos_ptr = shared_hist_ptr + bin;
|
||||
atomicAdd_block(pos_ptr, grad_and_hess);
|
||||
inner_data_index += blockDim.y;
|
||||
}
|
||||
}
|
||||
__syncthreads();
|
||||
if (USE_16BIT_HIST) {
|
||||
int32_t* feature_histogram_ptr = reinterpret_cast<int32_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess);
|
||||
}
|
||||
} else {
|
||||
atomic_add_long_t* feature_histogram_ptr = reinterpret_cast<atomic_add_long_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
const int64_t packed_grad_hess_int64 = (static_cast<int64_t>(static_cast<int16_t>(packed_grad_hess >> 16)) << 32) | (static_cast<int64_t>(packed_grad_hess & 0x0000ffff));
|
||||
atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BIN_TYPE, typename DATA_PTR_TYPE, int SHARED_HIST_SIZE, bool USE_16BIT_HIST>
|
||||
__global__ void CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory(
|
||||
const CUDALeafSplitsStruct* smaller_leaf_splits,
|
||||
const int32_t* cuda_gradients_and_hessians,
|
||||
const BIN_TYPE* data,
|
||||
const DATA_PTR_TYPE* row_ptr,
|
||||
const DATA_PTR_TYPE* partition_ptr,
|
||||
const uint32_t* column_hist_offsets_full,
|
||||
const data_size_t num_data,
|
||||
int32_t* global_hist_buffer) {
|
||||
const int dim_y = static_cast<int>(gridDim.y * blockDim.y);
|
||||
const data_size_t num_data_in_smaller_leaf = smaller_leaf_splits->num_data_in_leaf;
|
||||
const data_size_t num_data_per_thread = (num_data_in_smaller_leaf + dim_y - 1) / dim_y;
|
||||
const data_size_t* data_indices_ref = smaller_leaf_splits->data_indices_in_leaf;
|
||||
const int num_total_bin = column_hist_offsets_full[gridDim.x];
|
||||
const unsigned int num_threads_per_block = blockDim.x * blockDim.y;
|
||||
const DATA_PTR_TYPE* block_row_ptr = row_ptr + blockIdx.x * (num_data + 1);
|
||||
const BIN_TYPE* data_ptr = data + partition_ptr[blockIdx.x];
|
||||
const uint32_t partition_hist_start = column_hist_offsets_full[blockIdx.x];
|
||||
const uint32_t partition_hist_end = column_hist_offsets_full[blockIdx.x + 1];
|
||||
const uint32_t num_items_in_partition = (partition_hist_end - partition_hist_start);
|
||||
const unsigned int thread_idx = threadIdx.x + threadIdx.y * blockDim.x;
|
||||
int32_t* shared_hist_packed = global_hist_buffer + (blockIdx.y * num_total_bin + partition_hist_start);
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
shared_hist_packed[i] = 0.0f;
|
||||
}
|
||||
__syncthreads();
|
||||
const unsigned int threadIdx_y = threadIdx.y;
|
||||
const unsigned int blockIdx_y = blockIdx.y;
|
||||
const data_size_t block_start = (blockIdx_y * blockDim.y) * num_data_per_thread;
|
||||
const data_size_t* data_indices_ref_this_block = data_indices_ref + block_start;
|
||||
data_size_t block_num_data = max(0, min(num_data_in_smaller_leaf - block_start, num_data_per_thread * static_cast<data_size_t>(blockDim.y)));
|
||||
const data_size_t num_iteration_total = (block_num_data + blockDim.y - 1) / blockDim.y;
|
||||
const data_size_t remainder = block_num_data % blockDim.y;
|
||||
const data_size_t num_iteration_this = remainder == 0 ? num_iteration_total : num_iteration_total - static_cast<data_size_t>(threadIdx_y >= remainder);
|
||||
data_size_t inner_data_index = static_cast<data_size_t>(threadIdx_y);
|
||||
for (data_size_t i = 0; i < num_iteration_this; ++i) {
|
||||
const data_size_t data_index = data_indices_ref_this_block[inner_data_index];
|
||||
const DATA_PTR_TYPE row_start = block_row_ptr[data_index];
|
||||
const DATA_PTR_TYPE row_end = block_row_ptr[data_index + 1];
|
||||
const DATA_PTR_TYPE row_size = row_end - row_start;
|
||||
if (threadIdx.x < row_size) {
|
||||
const int32_t grad_and_hess = cuda_gradients_and_hessians[data_index];
|
||||
const uint32_t bin = static_cast<uint32_t>(data_ptr[row_start + threadIdx.x]);
|
||||
int32_t* pos_ptr = shared_hist_packed + bin;
|
||||
atomicAdd_block(pos_ptr, grad_and_hess);
|
||||
}
|
||||
inner_data_index += blockDim.y;
|
||||
}
|
||||
__syncthreads();
|
||||
if (USE_16BIT_HIST) {
|
||||
int32_t* feature_histogram_ptr = reinterpret_cast<int32_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
atomicAdd_system(feature_histogram_ptr + i, packed_grad_hess);
|
||||
}
|
||||
} else {
|
||||
atomic_add_long_t* feature_histogram_ptr = reinterpret_cast<atomic_add_long_t*>(smaller_leaf_splits->hist_in_leaf) + partition_hist_start;
|
||||
for (unsigned int i = thread_idx; i < num_items_in_partition; i += num_threads_per_block) {
|
||||
const int32_t packed_grad_hess = shared_hist_packed[i];
|
||||
const int64_t packed_grad_hess_int64 = (static_cast<int64_t>(static_cast<int16_t>(packed_grad_hess >> 16)) << 32) | (static_cast<int64_t>(packed_grad_hess & 0x0000ffff));
|
||||
atomicAdd_system(feature_histogram_ptr + i, (atomic_add_long_t)(packed_grad_hess_int64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::LaunchConstructHistogramKernel(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins) {
|
||||
if (cuda_row_data_->shared_hist_size() == DP_SHARED_HIST_SIZE && gpu_use_dp_) {
|
||||
LaunchConstructHistogramKernelInner<double, DP_SHARED_HIST_SIZE>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else if (cuda_row_data_->shared_hist_size() == SP_SHARED_HIST_SIZE && !gpu_use_dp_) {
|
||||
LaunchConstructHistogramKernelInner<float, SP_SHARED_HIST_SIZE>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else {
|
||||
Log::Fatal("Unknown shared histogram size %d", cuda_row_data_->shared_hist_size());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE>
|
||||
void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins) {
|
||||
if (cuda_row_data_->bit_type() == 8) {
|
||||
LaunchConstructHistogramKernelInner0<HIST_TYPE, SHARED_HIST_SIZE, uint8_t>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else if (cuda_row_data_->bit_type() == 16) {
|
||||
LaunchConstructHistogramKernelInner0<HIST_TYPE, SHARED_HIST_SIZE, uint16_t>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else if (cuda_row_data_->bit_type() == 32) {
|
||||
LaunchConstructHistogramKernelInner0<HIST_TYPE, SHARED_HIST_SIZE, uint32_t>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else {
|
||||
Log::Fatal("Unknown bit_type = %d", cuda_row_data_->bit_type());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE, typename BIN_TYPE>
|
||||
void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner0(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins) {
|
||||
if (cuda_row_data_->row_ptr_bit_type() == 16) {
|
||||
LaunchConstructHistogramKernelInner1<HIST_TYPE, SHARED_HIST_SIZE, BIN_TYPE, uint16_t>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else if (cuda_row_data_->row_ptr_bit_type() == 32) {
|
||||
LaunchConstructHistogramKernelInner1<HIST_TYPE, SHARED_HIST_SIZE, BIN_TYPE, uint32_t>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else if (cuda_row_data_->row_ptr_bit_type() == 64) {
|
||||
LaunchConstructHistogramKernelInner1<HIST_TYPE, SHARED_HIST_SIZE, BIN_TYPE, uint64_t>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else {
|
||||
if (!cuda_row_data_->is_sparse()) {
|
||||
LaunchConstructHistogramKernelInner1<HIST_TYPE, SHARED_HIST_SIZE, BIN_TYPE, uint16_t>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else {
|
||||
Log::Fatal("Unknown row_ptr_bit_type = %d", cuda_row_data_->row_ptr_bit_type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE, typename BIN_TYPE, typename PTR_TYPE>
|
||||
void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner1(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins) {
|
||||
if (cuda_row_data_->NumLargeBinPartition() == 0) {
|
||||
LaunchConstructHistogramKernelInner2<HIST_TYPE, SHARED_HIST_SIZE, BIN_TYPE, PTR_TYPE, false>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
} else {
|
||||
LaunchConstructHistogramKernelInner2<HIST_TYPE, SHARED_HIST_SIZE, BIN_TYPE, PTR_TYPE, true>(cuda_smaller_leaf_splits, num_data_in_smaller_leaf, num_bits_in_histogram_bins);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE, typename BIN_TYPE, typename PTR_TYPE, bool USE_GLOBAL_MEM_BUFFER>
|
||||
void CUDAHistogramConstructor::LaunchConstructHistogramKernelInner2(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins) {
|
||||
int grid_dim_x = 0;
|
||||
int grid_dim_y = 0;
|
||||
int block_dim_x = 0;
|
||||
int block_dim_y = 0;
|
||||
CalcConstructHistogramKernelDim(&grid_dim_x, &grid_dim_y, &block_dim_x, &block_dim_y, num_data_in_smaller_leaf);
|
||||
dim3 grid_dim(grid_dim_x, grid_dim_y);
|
||||
dim3 block_dim(block_dim_x, block_dim_y);
|
||||
if (use_quantized_grad_) {
|
||||
if (USE_GLOBAL_MEM_BUFFER) {
|
||||
if (cuda_row_data_->is_sparse()) {
|
||||
if (num_bits_in_histogram_bins <= 16) {
|
||||
CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory<BIN_TYPE, PTR_TYPE, SHARED_HIST_SIZE, true><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->GetRowPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->GetPartitionPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
num_data_,
|
||||
reinterpret_cast<int32_t*>(cuda_hist_buffer_.RawData()));
|
||||
} else {
|
||||
CUDAConstructDiscretizedHistogramSparseKernel_GlobalMemory<BIN_TYPE, PTR_TYPE, SHARED_HIST_SIZE, false><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->GetRowPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->GetPartitionPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
num_data_,
|
||||
reinterpret_cast<int32_t*>(cuda_hist_buffer_.RawData()));
|
||||
}
|
||||
} else {
|
||||
if (num_bits_in_histogram_bins <= 16) {
|
||||
CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory<BIN_TYPE, SHARED_HIST_SIZE, true><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->cuda_column_hist_offsets(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
cuda_row_data_->cuda_feature_partition_column_index_offsets(),
|
||||
num_data_,
|
||||
reinterpret_cast<int32_t*>(cuda_hist_buffer_.RawData()));
|
||||
} else {
|
||||
CUDAConstructDiscretizedHistogramDenseKernel_GlobalMemory<BIN_TYPE, SHARED_HIST_SIZE, false><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->cuda_column_hist_offsets(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
cuda_row_data_->cuda_feature_partition_column_index_offsets(),
|
||||
num_data_,
|
||||
reinterpret_cast<int32_t*>(cuda_hist_buffer_.RawData()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (cuda_row_data_->is_sparse()) {
|
||||
if (num_bits_in_histogram_bins <= 16) {
|
||||
CUDAConstructDiscretizedHistogramSparseKernel<BIN_TYPE, PTR_TYPE, SHARED_HIST_SIZE, true><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->GetRowPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->GetPartitionPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
num_data_);
|
||||
} else {
|
||||
CUDAConstructDiscretizedHistogramSparseKernel<BIN_TYPE, PTR_TYPE, SHARED_HIST_SIZE, false><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->GetRowPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->GetPartitionPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
num_data_);
|
||||
}
|
||||
} else {
|
||||
if (num_bits_in_histogram_bins <= 16) {
|
||||
CUDAConstructDiscretizedHistogramDenseKernel<BIN_TYPE, SHARED_HIST_SIZE, true><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->cuda_column_hist_offsets(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
cuda_row_data_->cuda_feature_partition_column_index_offsets(),
|
||||
num_data_);
|
||||
} else {
|
||||
CUDAConstructDiscretizedHistogramDenseKernel<BIN_TYPE, SHARED_HIST_SIZE, false><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
reinterpret_cast<const int32_t*>(cuda_gradients_),
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->cuda_column_hist_offsets(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
cuda_row_data_->cuda_feature_partition_column_index_offsets(),
|
||||
num_data_);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!USE_GLOBAL_MEM_BUFFER) {
|
||||
if (cuda_row_data_->is_sparse()) {
|
||||
CUDAConstructHistogramSparseKernel<BIN_TYPE, PTR_TYPE, HIST_TYPE, SHARED_HIST_SIZE><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_gradients_, cuda_hessians_,
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->GetRowPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->GetPartitionPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
num_data_);
|
||||
} else {
|
||||
CUDAConstructHistogramDenseKernel<BIN_TYPE, HIST_TYPE, SHARED_HIST_SIZE><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_gradients_, cuda_hessians_,
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->cuda_column_hist_offsets(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
cuda_row_data_->cuda_feature_partition_column_index_offsets(),
|
||||
num_data_);
|
||||
}
|
||||
} else {
|
||||
if (cuda_row_data_->is_sparse()) {
|
||||
CUDAConstructHistogramSparseKernel_GlobalMemory<BIN_TYPE, HIST_TYPE, PTR_TYPE><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_gradients_, cuda_hessians_,
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->GetRowPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->GetPartitionPtr<PTR_TYPE>(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
num_data_,
|
||||
reinterpret_cast<HIST_TYPE*>(cuda_hist_buffer_.RawData()));
|
||||
} else {
|
||||
CUDAConstructHistogramDenseKernel_GlobalMemory<BIN_TYPE, HIST_TYPE><<<grid_dim, block_dim, 0, cuda_stream_>>>(
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_gradients_, cuda_hessians_,
|
||||
cuda_row_data_->GetBin<BIN_TYPE>(),
|
||||
cuda_row_data_->cuda_column_hist_offsets(),
|
||||
cuda_row_data_->cuda_partition_hist_offsets(),
|
||||
cuda_row_data_->cuda_feature_partition_column_index_offsets(),
|
||||
num_data_,
|
||||
reinterpret_cast<HIST_TYPE*>(cuda_hist_buffer_.RawData()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void SubtractHistogramKernel(
|
||||
const int num_total_bin,
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits) {
|
||||
const unsigned int global_thread_index = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
const int cuda_larger_leaf_index = cuda_larger_leaf_splits->leaf_index;
|
||||
if (cuda_larger_leaf_index >= 0) {
|
||||
const hist_t* smaller_leaf_hist = cuda_smaller_leaf_splits->hist_in_leaf;
|
||||
hist_t* larger_leaf_hist = cuda_larger_leaf_splits->hist_in_leaf;
|
||||
if (global_thread_index < 2 * num_total_bin) {
|
||||
larger_leaf_hist[global_thread_index] -= smaller_leaf_hist[global_thread_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void FixHistogramKernel(
|
||||
const uint32_t* cuda_feature_num_bins,
|
||||
const uint32_t* cuda_feature_hist_offsets,
|
||||
const uint32_t* cuda_feature_most_freq_bins,
|
||||
const int* cuda_need_fix_histogram_features,
|
||||
const uint32_t* cuda_need_fix_histogram_features_num_bin_aligned,
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits) {
|
||||
__shared__ hist_t shared_mem_buffer[WARPSIZE];
|
||||
const unsigned int blockIdx_x = blockIdx.x;
|
||||
const int feature_index = cuda_need_fix_histogram_features[blockIdx_x];
|
||||
const uint32_t num_bin_aligned = cuda_need_fix_histogram_features_num_bin_aligned[blockIdx_x];
|
||||
const uint32_t feature_hist_offset = cuda_feature_hist_offsets[feature_index];
|
||||
const uint32_t most_freq_bin = cuda_feature_most_freq_bins[feature_index];
|
||||
const double leaf_sum_gradients = cuda_smaller_leaf_splits->sum_of_gradients;
|
||||
const double leaf_sum_hessians = cuda_smaller_leaf_splits->sum_of_hessians;
|
||||
hist_t* feature_hist = cuda_smaller_leaf_splits->hist_in_leaf + feature_hist_offset * 2;
|
||||
const unsigned int threadIdx_x = threadIdx.x;
|
||||
const uint32_t num_bin = cuda_feature_num_bins[feature_index];
|
||||
const uint32_t hist_pos = threadIdx_x << 1;
|
||||
const hist_t bin_gradient = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[hist_pos] : 0.0f;
|
||||
const hist_t bin_hessian = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[hist_pos + 1] : 0.0f;
|
||||
const hist_t sum_gradient = ShuffleReduceSum<hist_t>(bin_gradient, shared_mem_buffer, num_bin_aligned);
|
||||
const hist_t sum_hessian = ShuffleReduceSum<hist_t>(bin_hessian, shared_mem_buffer, num_bin_aligned);
|
||||
if (threadIdx_x == 0) {
|
||||
feature_hist[most_freq_bin << 1] = leaf_sum_gradients - sum_gradient;
|
||||
feature_hist[(most_freq_bin << 1) + 1] = leaf_sum_hessians - sum_hessian;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool SMALLER_USE_16BIT_HIST, bool LARGER_USE_16BIT_HIST, bool PARENT_USE_16BIT_HIST>
|
||||
__global__ void SubtractHistogramDiscretizedKernel(
|
||||
const int num_total_bin,
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits,
|
||||
hist_t* num_bit_change_buffer) {
|
||||
const unsigned int global_thread_index = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
const int cuda_larger_leaf_index_ref = cuda_larger_leaf_splits->leaf_index;
|
||||
if (cuda_larger_leaf_index_ref >= 0) {
|
||||
if (PARENT_USE_16BIT_HIST) {
|
||||
const int32_t* smaller_leaf_hist = reinterpret_cast<const int32_t*>(cuda_smaller_leaf_splits->hist_in_leaf);
|
||||
int32_t* larger_leaf_hist = reinterpret_cast<int32_t*>(cuda_larger_leaf_splits->hist_in_leaf);
|
||||
if (global_thread_index < num_total_bin) {
|
||||
larger_leaf_hist[global_thread_index] -= smaller_leaf_hist[global_thread_index];
|
||||
}
|
||||
} else if (LARGER_USE_16BIT_HIST) {
|
||||
int32_t* buffer = reinterpret_cast<int32_t*>(num_bit_change_buffer);
|
||||
const int32_t* smaller_leaf_hist = reinterpret_cast<const int32_t*>(cuda_smaller_leaf_splits->hist_in_leaf);
|
||||
int64_t* larger_leaf_hist = reinterpret_cast<int64_t*>(cuda_larger_leaf_splits->hist_in_leaf);
|
||||
if (global_thread_index < num_total_bin) {
|
||||
const int64_t parent_hist_item = larger_leaf_hist[global_thread_index];
|
||||
const int32_t smaller_hist_item = smaller_leaf_hist[global_thread_index];
|
||||
const int64_t smaller_hist_item_int64 = (static_cast<int64_t>(static_cast<int16_t>(smaller_hist_item >> 16)) << 32) |
|
||||
static_cast<int64_t>(smaller_hist_item & 0x0000ffff);
|
||||
const int64_t larger_hist_item = parent_hist_item - smaller_hist_item_int64;
|
||||
buffer[global_thread_index] = static_cast<int32_t>(static_cast<int16_t>(larger_hist_item >> 32) << 16) |
|
||||
static_cast<int32_t>(larger_hist_item & 0x000000000000ffff);
|
||||
}
|
||||
} else if (SMALLER_USE_16BIT_HIST) {
|
||||
const int32_t* smaller_leaf_hist = reinterpret_cast<const int32_t*>(cuda_smaller_leaf_splits->hist_in_leaf);
|
||||
int64_t* larger_leaf_hist = reinterpret_cast<int64_t*>(cuda_larger_leaf_splits->hist_in_leaf);
|
||||
if (global_thread_index < num_total_bin) {
|
||||
const int64_t parent_hist_item = larger_leaf_hist[global_thread_index];
|
||||
const int32_t smaller_hist_item = smaller_leaf_hist[global_thread_index];
|
||||
const int64_t smaller_hist_item_int64 = (static_cast<int64_t>(static_cast<int16_t>(smaller_hist_item >> 16)) << 32) |
|
||||
static_cast<int64_t>(smaller_hist_item & 0x0000ffff);
|
||||
const int64_t larger_hist_item = parent_hist_item - smaller_hist_item_int64;
|
||||
larger_leaf_hist[global_thread_index] = larger_hist_item;
|
||||
}
|
||||
} else {
|
||||
const int64_t* smaller_leaf_hist = reinterpret_cast<const int64_t*>(cuda_smaller_leaf_splits->hist_in_leaf);
|
||||
int64_t* larger_leaf_hist = reinterpret_cast<int64_t*>(cuda_larger_leaf_splits->hist_in_leaf);
|
||||
if (global_thread_index < num_total_bin) {
|
||||
larger_leaf_hist[global_thread_index] -= smaller_leaf_hist[global_thread_index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void CopyChangedNumBitHistogram(
|
||||
const int num_total_bin,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits,
|
||||
hist_t* num_bit_change_buffer) {
|
||||
int32_t* hist_dst = reinterpret_cast<int32_t*>(cuda_larger_leaf_splits->hist_in_leaf);
|
||||
const int32_t* hist_src = reinterpret_cast<const int32_t*>(num_bit_change_buffer);
|
||||
const unsigned int global_thread_index = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
if (global_thread_index < static_cast<unsigned int>(num_total_bin)) {
|
||||
hist_dst[global_thread_index] = hist_src[global_thread_index];
|
||||
}
|
||||
}
|
||||
|
||||
template <bool USE_16BIT_HIST>
|
||||
__global__ void FixHistogramDiscretizedKernel(
|
||||
const uint32_t* cuda_feature_num_bins,
|
||||
const uint32_t* cuda_feature_hist_offsets,
|
||||
const uint32_t* cuda_feature_most_freq_bins,
|
||||
const int* cuda_need_fix_histogram_features,
|
||||
const uint32_t* cuda_need_fix_histogram_features_num_bin_aligned,
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits) {
|
||||
__shared__ int64_t shared_mem_buffer[WARPSIZE];
|
||||
const unsigned int blockIdx_x = blockIdx.x;
|
||||
const int feature_index = cuda_need_fix_histogram_features[blockIdx_x];
|
||||
const uint32_t num_bin_aligned = cuda_need_fix_histogram_features_num_bin_aligned[blockIdx_x];
|
||||
const uint32_t feature_hist_offset = cuda_feature_hist_offsets[feature_index];
|
||||
const uint32_t most_freq_bin = cuda_feature_most_freq_bins[feature_index];
|
||||
if (USE_16BIT_HIST) {
|
||||
const int64_t leaf_sum_gradients_hessians_int64 = cuda_smaller_leaf_splits->sum_of_gradients_hessians;
|
||||
const int32_t leaf_sum_gradients_hessians =
|
||||
(static_cast<int32_t>(leaf_sum_gradients_hessians_int64 >> 32) << 16) | static_cast<int32_t>(leaf_sum_gradients_hessians_int64 & 0x000000000000ffff);
|
||||
int32_t* feature_hist = reinterpret_cast<int32_t*>(cuda_smaller_leaf_splits->hist_in_leaf) + feature_hist_offset;
|
||||
const unsigned int threadIdx_x = threadIdx.x;
|
||||
const uint32_t num_bin = cuda_feature_num_bins[feature_index];
|
||||
const int32_t bin_gradient_hessian = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[threadIdx_x] : 0;
|
||||
const int32_t sum_gradient_hessian = ShuffleReduceSum<int32_t>(
|
||||
bin_gradient_hessian,
|
||||
reinterpret_cast<int32_t*>(shared_mem_buffer),
|
||||
num_bin_aligned);
|
||||
if (threadIdx_x == 0) {
|
||||
feature_hist[most_freq_bin] = leaf_sum_gradients_hessians - sum_gradient_hessian;
|
||||
}
|
||||
} else {
|
||||
const int64_t leaf_sum_gradients_hessians = cuda_smaller_leaf_splits->sum_of_gradients_hessians;
|
||||
int64_t* feature_hist = reinterpret_cast<int64_t*>(cuda_smaller_leaf_splits->hist_in_leaf) + feature_hist_offset;
|
||||
const unsigned int threadIdx_x = threadIdx.x;
|
||||
const uint32_t num_bin = cuda_feature_num_bins[feature_index];
|
||||
const int64_t bin_gradient_hessian = (threadIdx_x < num_bin && threadIdx_x != most_freq_bin) ? feature_hist[threadIdx_x] : 0;
|
||||
const int64_t sum_gradient_hessian = ShuffleReduceSum<int64_t>(bin_gradient_hessian, shared_mem_buffer, num_bin_aligned);
|
||||
if (threadIdx_x == 0) {
|
||||
feature_hist[most_freq_bin] = leaf_sum_gradients_hessians - sum_gradient_hessian;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CUDAHistogramConstructor::LaunchSubtractHistogramKernel(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits,
|
||||
const bool use_discretized_grad,
|
||||
const uint8_t parent_num_bits_in_histogram_bins,
|
||||
const uint8_t smaller_num_bits_in_histogram_bins,
|
||||
const uint8_t larger_num_bits_in_histogram_bins) {
|
||||
if (!use_discretized_grad) {
|
||||
const int num_subtract_threads = 2 * num_total_bin_;
|
||||
const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE;
|
||||
global_timer.Start("CUDAHistogramConstructor::FixHistogramKernel");
|
||||
if (need_fix_histogram_features_.size() > 0) {
|
||||
FixHistogramKernel<<<need_fix_histogram_features_.size(), FIX_HISTOGRAM_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
cuda_feature_num_bins_.RawData(),
|
||||
cuda_feature_hist_offsets_.RawData(),
|
||||
cuda_feature_most_freq_bins_.RawData(),
|
||||
cuda_need_fix_histogram_features_.RawData(),
|
||||
cuda_need_fix_histogram_features_num_bin_aligned_.RawData(),
|
||||
cuda_smaller_leaf_splits);
|
||||
}
|
||||
global_timer.Stop("CUDAHistogramConstructor::FixHistogramKernel");
|
||||
global_timer.Start("CUDAHistogramConstructor::SubtractHistogramKernel");
|
||||
SubtractHistogramKernel<<<num_subtract_blocks, SUBTRACT_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
num_total_bin_,
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_larger_leaf_splits);
|
||||
global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramKernel");
|
||||
} else {
|
||||
const int num_subtract_threads = num_total_bin_;
|
||||
const int num_subtract_blocks = (num_subtract_threads + SUBTRACT_BLOCK_SIZE - 1) / SUBTRACT_BLOCK_SIZE;
|
||||
global_timer.Start("CUDAHistogramConstructor::FixHistogramDiscretizedKernel");
|
||||
if (need_fix_histogram_features_.size() > 0) {
|
||||
if (smaller_num_bits_in_histogram_bins <= 16) {
|
||||
FixHistogramDiscretizedKernel<true><<<need_fix_histogram_features_.size(), FIX_HISTOGRAM_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
cuda_feature_num_bins_.RawData(),
|
||||
cuda_feature_hist_offsets_.RawData(),
|
||||
cuda_feature_most_freq_bins_.RawData(),
|
||||
cuda_need_fix_histogram_features_.RawData(),
|
||||
cuda_need_fix_histogram_features_num_bin_aligned_.RawData(),
|
||||
cuda_smaller_leaf_splits);
|
||||
} else {
|
||||
FixHistogramDiscretizedKernel<false><<<need_fix_histogram_features_.size(), FIX_HISTOGRAM_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
cuda_feature_num_bins_.RawData(),
|
||||
cuda_feature_hist_offsets_.RawData(),
|
||||
cuda_feature_most_freq_bins_.RawData(),
|
||||
cuda_need_fix_histogram_features_.RawData(),
|
||||
cuda_need_fix_histogram_features_num_bin_aligned_.RawData(),
|
||||
cuda_smaller_leaf_splits);
|
||||
}
|
||||
}
|
||||
global_timer.Stop("CUDAHistogramConstructor::FixHistogramDiscretizedKernel");
|
||||
global_timer.Start("CUDAHistogramConstructor::SubtractHistogramDiscretizedKernel");
|
||||
if (parent_num_bits_in_histogram_bins <= 16) {
|
||||
CHECK_LE(smaller_num_bits_in_histogram_bins, 16);
|
||||
CHECK_LE(larger_num_bits_in_histogram_bins, 16);
|
||||
SubtractHistogramDiscretizedKernel<true, true, true><<<num_subtract_blocks, SUBTRACT_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
num_total_bin_,
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_larger_leaf_splits,
|
||||
hist_buffer_for_num_bit_change_.RawData());
|
||||
} else if (larger_num_bits_in_histogram_bins <= 16) {
|
||||
CHECK_LE(smaller_num_bits_in_histogram_bins, 16);
|
||||
SubtractHistogramDiscretizedKernel<true, true, false><<<num_subtract_blocks, SUBTRACT_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
num_total_bin_,
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_larger_leaf_splits,
|
||||
hist_buffer_for_num_bit_change_.RawData());
|
||||
CopyChangedNumBitHistogram<<<num_subtract_blocks, SUBTRACT_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
num_total_bin_,
|
||||
cuda_larger_leaf_splits,
|
||||
hist_buffer_for_num_bit_change_.RawData());
|
||||
} else if (smaller_num_bits_in_histogram_bins <= 16) {
|
||||
SubtractHistogramDiscretizedKernel<true, false, false><<<num_subtract_blocks, SUBTRACT_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
num_total_bin_,
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_larger_leaf_splits,
|
||||
hist_buffer_for_num_bit_change_.RawData());
|
||||
} else {
|
||||
SubtractHistogramDiscretizedKernel<false, false, false><<<num_subtract_blocks, SUBTRACT_BLOCK_SIZE, 0, cuda_stream_>>>(
|
||||
num_total_bin_,
|
||||
cuda_smaller_leaf_splits,
|
||||
cuda_larger_leaf_splits,
|
||||
hist_buffer_for_num_bit_change_.RawData());
|
||||
}
|
||||
global_timer.Stop("CUDAHistogramConstructor::SubtractHistogramDiscretizedKernel");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
@@ -0,0 +1,199 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
#ifndef LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_HISTOGRAM_CONSTRUCTOR_HPP_
|
||||
#define LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_HISTOGRAM_CONSTRUCTOR_HPP_
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include <LightGBM/cuda/cuda_row_data.hpp>
|
||||
#include <LightGBM/cuda/cuda_utils.hu>
|
||||
#include <LightGBM/feature_group.h>
|
||||
#include <LightGBM/tree.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cuda_leaf_splits.hpp"
|
||||
|
||||
#define NUM_DATA_PER_THREAD (400)
|
||||
#define NUM_THREADS_PER_BLOCK (504)
|
||||
#define NUM_FEATURE_PER_THREAD_GROUP (28)
|
||||
#define SUBTRACT_BLOCK_SIZE (1024)
|
||||
#define FIX_HISTOGRAM_SHARED_MEM_SIZE (1024)
|
||||
#define FIX_HISTOGRAM_BLOCK_SIZE (512)
|
||||
#define USED_HISTOGRAM_BUFFER_NUM (8)
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
class CUDAHistogramConstructor {
|
||||
public:
|
||||
CUDAHistogramConstructor(
|
||||
const Dataset* train_data,
|
||||
const int num_leaves,
|
||||
const int num_threads,
|
||||
const std::vector<uint32_t>& feature_hist_offsets,
|
||||
const int min_data_in_leaf,
|
||||
const double min_sum_hessian_in_leaf,
|
||||
const int gpu_device_id,
|
||||
const bool gpu_use_dp,
|
||||
const bool use_discretized_grad,
|
||||
const int grad_discretized_bins);
|
||||
|
||||
~CUDAHistogramConstructor();
|
||||
|
||||
void Init(const Dataset* train_data, TrainingShareStates* share_state);
|
||||
|
||||
void ConstructHistogramForLeaf(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits,
|
||||
const data_size_t global_num_data_in_smaller_leaf,
|
||||
const data_size_t global_num_data_in_larger_leaf,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const data_size_t num_data_in_larger_leaf,
|
||||
const double sum_hessians_in_smaller_leaf,
|
||||
const double sum_hessians_in_larger_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins);
|
||||
|
||||
void SubtractHistogramForLeaf(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits,
|
||||
const bool use_discretized_grad,
|
||||
const uint8_t parent_num_bits_in_histogram_bins,
|
||||
const uint8_t smaller_num_bits_in_histogram_bins,
|
||||
const uint8_t larger_num_bits_in_histogram_bins);
|
||||
|
||||
void ResetTrainingData(const Dataset* train_data, TrainingShareStates* share_states);
|
||||
|
||||
void ResetConfig(const Config* config);
|
||||
|
||||
void BeforeTrain(const score_t* gradients, const score_t* hessians);
|
||||
|
||||
const hist_t* cuda_hist() const { return cuda_hist_.RawData(); }
|
||||
|
||||
hist_t* cuda_hist_pointer() { return cuda_hist_.RawData(); }
|
||||
|
||||
private:
|
||||
void InitFeatureMetaInfo(const Dataset* train_data, const std::vector<uint32_t>& feature_hist_offsets);
|
||||
|
||||
void CalcConstructHistogramKernelDim(
|
||||
int* grid_dim_x,
|
||||
int* grid_dim_y,
|
||||
int* block_dim_x,
|
||||
int* block_dim_y,
|
||||
const data_size_t num_data_in_smaller_leaf);
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE>
|
||||
void LaunchConstructHistogramKernelInner(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins);
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE, typename BIN_TYPE>
|
||||
void LaunchConstructHistogramKernelInner0(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins);
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE, typename BIN_TYPE, typename PTR_TYPE>
|
||||
void LaunchConstructHistogramKernelInner1(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins);
|
||||
|
||||
template <typename HIST_TYPE, size_t SHARED_HIST_SIZE, typename BIN_TYPE, typename PTR_TYPE, bool USE_GLOBAL_MEM_BUFFER>
|
||||
void LaunchConstructHistogramKernelInner2(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins);
|
||||
|
||||
void LaunchConstructHistogramKernel(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const data_size_t num_data_in_smaller_leaf,
|
||||
const uint8_t num_bits_in_histogram_bins);
|
||||
|
||||
void LaunchSubtractHistogramKernel(
|
||||
const CUDALeafSplitsStruct* cuda_smaller_leaf_splits,
|
||||
const CUDALeafSplitsStruct* cuda_larger_leaf_splits,
|
||||
const bool use_discretized_grad,
|
||||
const uint8_t parent_num_bits_in_histogram_bins,
|
||||
const uint8_t smaller_num_bits_in_histogram_bins,
|
||||
const uint8_t larger_num_bits_in_histogram_bins);
|
||||
|
||||
// Host memory
|
||||
|
||||
/*! \brief size of training data */
|
||||
data_size_t num_data_;
|
||||
/*! \brief number of features in training data */
|
||||
int num_features_;
|
||||
/*! \brief maximum number of leaves */
|
||||
int num_leaves_;
|
||||
/*! \brief number of threads */
|
||||
int num_threads_;
|
||||
/*! \brief total number of bins in histogram */
|
||||
int num_total_bin_;
|
||||
/*! \brief number of bins per feature */
|
||||
std::vector<uint32_t> feature_num_bins_;
|
||||
/*! \brief offsets in histogram of all features */
|
||||
std::vector<uint32_t> feature_hist_offsets_;
|
||||
/*! \brief most frequent bins in each feature */
|
||||
std::vector<uint32_t> feature_most_freq_bins_;
|
||||
/*! \brief minimum number of data allowed per leaf */
|
||||
int min_data_in_leaf_;
|
||||
/*! \brief minimum sum value of hessians allowed per leaf */
|
||||
double min_sum_hessian_in_leaf_;
|
||||
/*! \brief cuda stream for histogram construction */
|
||||
cudaStream_t cuda_stream_;
|
||||
/*! \brief indices of feature whose histograms need to be fixed */
|
||||
std::vector<int> need_fix_histogram_features_;
|
||||
/*! \brief aligned number of bins of the features whose histograms need to be fixed */
|
||||
std::vector<uint32_t> need_fix_histogram_features_num_bin_aligend_;
|
||||
/*! \brief minimum number of blocks allowed in the y dimension */
|
||||
const int min_grid_dim_y_ = 160;
|
||||
|
||||
|
||||
// CUDA memory, held by this object
|
||||
|
||||
/*! \brief CUDA row wise data */
|
||||
std::unique_ptr<CUDARowData> cuda_row_data_;
|
||||
/*! \brief number of bins per feature */
|
||||
CUDAVector<uint32_t> cuda_feature_num_bins_;
|
||||
/*! \brief offsets in histogram of all features */
|
||||
CUDAVector<uint32_t> cuda_feature_hist_offsets_;
|
||||
/*! \brief most frequent bins in each feature */
|
||||
CUDAVector<uint32_t> cuda_feature_most_freq_bins_;
|
||||
/*! \brief CUDA histograms */
|
||||
CUDAVector<hist_t> cuda_hist_;
|
||||
/*! \brief CUDA histograms buffer for each block */
|
||||
CUDAVector<float> cuda_hist_buffer_;
|
||||
/*! \brief indices of feature whose histograms need to be fixed */
|
||||
CUDAVector<int> cuda_need_fix_histogram_features_;
|
||||
/*! \brief aligned number of bins of the features whose histograms need to be fixed */
|
||||
CUDAVector<uint32_t> cuda_need_fix_histogram_features_num_bin_aligned_;
|
||||
/*! \brief histogram buffer used in histogram subtraction with different number of bits for histogram bins */
|
||||
CUDAVector<hist_t> hist_buffer_for_num_bit_change_;
|
||||
|
||||
// CUDA memory, held by other object
|
||||
|
||||
/*! \brief gradients on CUDA */
|
||||
const score_t* cuda_gradients_;
|
||||
/*! \brief hessians on CUDA */
|
||||
const score_t* cuda_hessians_;
|
||||
|
||||
/*! \brief GPU device index */
|
||||
const int gpu_device_id_;
|
||||
/*! \brief use double precision histogram per block */
|
||||
const bool gpu_use_dp_;
|
||||
/*! \brief whether to use quantized gradients */
|
||||
const bool use_quantized_grad_;
|
||||
/*! \brief the number of bins to quantized gradients */
|
||||
const int num_grad_quant_bins_;
|
||||
};
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
#endif // LIGHTGBM_SRC_TREELEARNER_CUDA_CUDA_HISTOGRAM_CONSTRUCTOR_HPP_
|
||||
@@ -0,0 +1,79 @@
|
||||
/*!
|
||||
* Copyright (c) 2021-2026 Microsoft Corporation. All rights reserved.
|
||||
* Copyright (c) 2021-2026 The LightGBM developers. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
#ifdef USE_CUDA
|
||||
|
||||
#include "cuda_leaf_splits.hpp"
|
||||
|
||||
namespace LightGBM {
|
||||
|
||||
CUDALeafSplits::CUDALeafSplits(const data_size_t num_data):
|
||||
num_data_(num_data) {}
|
||||
|
||||
CUDALeafSplits::~CUDALeafSplits() {}
|
||||
|
||||
void CUDALeafSplits::Init(const bool use_quantized_grad) {
|
||||
num_blocks_init_from_gradients_ = (num_data_ + NUM_THREADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THREADS_PER_BLOCK_LEAF_SPLITS;
|
||||
|
||||
// allocate more memory for sum reduction in CUDA
|
||||
// only the first element records the final sum
|
||||
cuda_sum_of_gradients_buffer_.Resize(static_cast<size_t>(num_blocks_init_from_gradients_));
|
||||
cuda_sum_of_hessians_buffer_.Resize(static_cast<size_t>(num_blocks_init_from_gradients_));
|
||||
if (use_quantized_grad) {
|
||||
cuda_sum_of_gradients_hessians_buffer_.Resize(static_cast<size_t>(num_blocks_init_from_gradients_));
|
||||
}
|
||||
|
||||
cuda_struct_.Resize(1);
|
||||
}
|
||||
|
||||
void CUDALeafSplits::InitValues() {
|
||||
LaunchInitValuesEmptyKernel();
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDALeafSplits::InitValues(
|
||||
const double lambda_l1, const double lambda_l2,
|
||||
const score_t* cuda_gradients, const score_t* cuda_hessians,
|
||||
const data_size_t* cuda_bagging_data_indices, const data_size_t* cuda_data_indices_in_leaf,
|
||||
const data_size_t num_used_indices, hist_t* cuda_hist_in_leaf,
|
||||
double* root_sum_gradients, double* root_sum_hessians) {
|
||||
cuda_gradients_ = cuda_gradients;
|
||||
cuda_hessians_ = cuda_hessians;
|
||||
cuda_sum_of_gradients_buffer_.SetValue(0);
|
||||
cuda_sum_of_hessians_buffer_.SetValue(0);
|
||||
LaunchInitValuesKernel(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf);
|
||||
CopyFromCUDADeviceToHost<double>(root_sum_gradients, cuda_sum_of_gradients_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<double>(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDALeafSplits::InitValues(
|
||||
const double lambda_l1, const double lambda_l2,
|
||||
const int16_t* cuda_gradients_and_hessians,
|
||||
const data_size_t* cuda_bagging_data_indices,
|
||||
const data_size_t* cuda_data_indices_in_leaf, const data_size_t num_used_indices,
|
||||
hist_t* cuda_hist_in_leaf, double* root_sum_gradients, double* root_sum_hessians,
|
||||
const score_t* grad_scale, const score_t* hess_scale) {
|
||||
cuda_gradients_ = reinterpret_cast<const score_t*>(cuda_gradients_and_hessians);
|
||||
cuda_hessians_ = nullptr;
|
||||
LaunchInitValuesKernel(lambda_l1, lambda_l2, cuda_bagging_data_indices, cuda_data_indices_in_leaf, num_used_indices, cuda_hist_in_leaf, grad_scale, hess_scale);
|
||||
CopyFromCUDADeviceToHost<double>(root_sum_gradients, cuda_sum_of_gradients_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
CopyFromCUDADeviceToHost<double>(root_sum_hessians, cuda_sum_of_hessians_buffer_.RawData(), 1, __FILE__, __LINE__);
|
||||
SynchronizeCUDADevice(__FILE__, __LINE__);
|
||||
}
|
||||
|
||||
void CUDALeafSplits::Resize(const data_size_t num_data) {
|
||||
num_data_ = num_data;
|
||||
num_blocks_init_from_gradients_ = (num_data + NUM_THREADS_PER_BLOCK_LEAF_SPLITS - 1) / NUM_THREADS_PER_BLOCK_LEAF_SPLITS;
|
||||
cuda_sum_of_gradients_buffer_.Resize(static_cast<size_t>(num_blocks_init_from_gradients_));
|
||||
cuda_sum_of_hessians_buffer_.Resize(static_cast<size_t>(num_blocks_init_from_gradients_));
|
||||
cuda_sum_of_gradients_hessians_buffer_.Resize(static_cast<size_t>(num_blocks_init_from_gradients_));
|
||||
}
|
||||
|
||||
} // namespace LightGBM
|
||||
|
||||
#endif // USE_CUDA
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user