Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify the hist tree method for different devices. #9363

Merged
merged 9 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions R-package/src/Makevars.in
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ OBJECTS= \
$(PKGROOT)/src/common/charconv.o \
$(PKGROOT)/src/common/column_matrix.o \
$(PKGROOT)/src/common/common.o \
$(PKGROOT)/src/common/error_msg.o \
$(PKGROOT)/src/common/hist_util.o \
$(PKGROOT)/src/common/host_device_vector.o \
$(PKGROOT)/src/common/io.o \
Expand Down
1 change: 1 addition & 0 deletions R-package/src/Makevars.win
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ OBJECTS= \
$(PKGROOT)/src/common/charconv.o \
$(PKGROOT)/src/common/column_matrix.o \
$(PKGROOT)/src/common/common.o \
$(PKGROOT)/src/common/error_msg.o \
$(PKGROOT)/src/common/hist_util.o \
$(PKGROOT)/src/common/host_device_vector.o \
$(PKGROOT)/src/common/io.o \
Expand Down
36 changes: 36 additions & 0 deletions src/common/error_msg.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright 2023 by XGBoost contributors
*/
#include "error_msg.h"

#include "xgboost/logging.h"

namespace xgboost::error {
void WarnDeprecatedGPUHist() {
bool static thread_local logged{false};
if (logged) {
return;
}
auto msg =
"The tree method `gpu_hist` is deprecated since 2.0.0. To use GPU training, set the `device` "
R"(parameter to CUDA instead.

E.g. tree_method = "hist", device = "CUDA"

)";
LOG(WARNING) << msg;
logged = true;
Comment on lines +14 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the device parameter exist already? Then why do the C++ tests use gpu_id ?

Copy link
Member Author

@trivialfis trivialfis Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet, it's in this PR: #9362 . It's difficult to split up the changes without referencing each other.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Let me approve this PR for now.

}

void WarnManualUpdater() {
bool static thread_local logged{false};
if (logged) {
return;
}
LOG(WARNING)
<< "You have manually specified the `updater` parameter. The `tree_method` parameter "
"will be ignored. Incorrect sequence of updaters will produce undefined "
"behavior. For common uses, we recommend using `tree_method` parameter instead.";
logged = true;
}
} // namespace xgboost::error
5 changes: 4 additions & 1 deletion src/common/error_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ inline void WarnOldSerialization() {
if (logged) {
return;
}

LOG(WARNING) << OldSerialization();
logged = true;
}

void WarnDeprecatedGPUHist();

void WarnManualUpdater();
} // namespace xgboost::error
#endif // XGBOOST_COMMON_ERROR_MSG_H_
182 changes: 71 additions & 111 deletions src/gbm/gbtree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#include <dmlc/omp.h>
#include <dmlc/parameter.h>

#include <algorithm>
#include <algorithm> // for equal
#include <cinttypes> // for uint32_t
#include <limits>
#include <memory>
Expand Down Expand Up @@ -40,8 +40,53 @@
namespace xgboost::gbm {
DMLC_REGISTRY_FILE_TAG(gbtree);

namespace {
/** @brief Map the `tree_method` parameter to the `updater` parameter. */
std::string MapTreeMethodToUpdaters(Context const* ctx_, TreeMethod tree_method) {
// Choose updaters according to tree_method parameters
switch (tree_method) {
case TreeMethod::kAuto: // Use hist as default in 2.0
case TreeMethod::kHist: {
return ctx_->DispatchDevice([] { return "grow_quantile_histmaker"; },
[] {
common::AssertGPUSupport();
return "grow_gpu_hist";
});
}
case TreeMethod::kApprox:
CHECK(ctx_->IsCPU()) << "The `approx` tree method is not supported on GPU.";
return "grow_histmaker";
case TreeMethod::kExact:
CHECK(ctx_->IsCPU()) << "The `exact` tree method is not supported on GPU.";
return "grow_colmaker,prune";
case TreeMethod::kGPUHist: {
common::AssertGPUSupport();
error::WarnDeprecatedGPUHist();
return "grow_gpu_hist";
}
default:
auto tm = static_cast<std::underlying_type_t<TreeMethod>>(tree_method);
LOG(FATAL) << "Unknown tree_method: `" << tm << "`.";
}

LOG(FATAL) << "unreachable";
return "";
}

bool UpdatersMatched(std::vector<std::string> updater_seq,
std::vector<std::unique_ptr<TreeUpdater>> const& updaters) {
if (updater_seq.size() != updaters.size()) {
return false;
}

return std::equal(updater_seq.cbegin(), updater_seq.cend(), updaters.cbegin(),
[](std::string const& name, std::unique_ptr<TreeUpdater> const& up) {
return name == up->Name();
});
}
} // namespace

void GBTree::Configure(Args const& cfg) {
std::string updater_seq = tparam_.updater_seq;
tparam_.UpdateAllowUnknown(cfg);
tree_param_.UpdateAllowUnknown(cfg);

Expand All @@ -54,8 +99,7 @@ void GBTree::Configure(Args const& cfg) {

// configure predictors
if (!cpu_predictor_) {
cpu_predictor_ = std::unique_ptr<Predictor>(
Predictor::Create("cpu_predictor", this->ctx_));
cpu_predictor_ = std::unique_ptr<Predictor>(Predictor::Create("cpu_predictor", this->ctx_));
}
cpu_predictor_->Configure(cfg);
#if defined(XGBOOST_USE_CUDA)
Expand All @@ -70,74 +114,43 @@ void GBTree::Configure(Args const& cfg) {

#if defined(XGBOOST_USE_ONEAPI)
if (!oneapi_predictor_) {
oneapi_predictor_ = std::unique_ptr<Predictor>(
Predictor::Create("oneapi_predictor", this->ctx_));
oneapi_predictor_ =
std::unique_ptr<Predictor>(Predictor::Create("oneapi_predictor", this->ctx_));
}
oneapi_predictor_->Configure(cfg);
#endif // defined(XGBOOST_USE_ONEAPI)

monitor_.Init("GBTree");

specified_updater_ = std::any_of(
cfg.cbegin(), cfg.cend(),
[](std::pair<std::string, std::string> const& arg) { return arg.first == "updater"; });

if (specified_updater_ && !showed_updater_warning_) {
LOG(WARNING) << "DANGER AHEAD: You have manually specified `updater` "
"parameter. The `tree_method` parameter will be ignored. "
"Incorrect sequence of updaters will produce undefined "
"behavior. For common uses, we recommend using "
"`tree_method` parameter instead.";
// Don't drive users to silent XGBOost.
showed_updater_warning_ = true;
// `updater` parameter was manually specified
specified_updater_ =
std::any_of(cfg.cbegin(), cfg.cend(), [](auto const& arg) { return arg.first == "updater"; });
if (specified_updater_) {
error::WarnManualUpdater();
}

if (model_.learner_model_param->IsVectorLeaf()) {
CHECK(tparam_.tree_method == TreeMethod::kHist || tparam_.tree_method == TreeMethod::kAuto)
<< "Only the hist tree method is supported for building multi-target trees with vector "
"leaf.";
}

LOG(DEBUG) << "Using tree method: " << static_cast<int>(tparam_.tree_method);
this->ConfigureUpdaters();

if (updater_seq != tparam_.updater_seq) {
if (!specified_updater_) {
this->tparam_.updater_seq = MapTreeMethodToUpdaters(ctx_, tparam_.tree_method);
}

auto up_names = common::Split(tparam_.updater_seq, ',');
if (!UpdatersMatched(up_names, updaters_)) {
updaters_.clear();
this->InitUpdater(cfg);
} else {
for (auto& up : updaters_) {
up->Configure(cfg);
for (auto const& name : up_names) {
std::unique_ptr<TreeUpdater> up(
TreeUpdater::Create(name.c_str(), ctx_, &model_.learner_model_param->task));
updaters_.push_back(std::move(up));
}
}

configured_ = true;
}

void GBTree::ConfigureUpdaters() {
if (specified_updater_) {
return;
}
// `updater` parameter was manually specified
/* Choose updaters according to tree_method parameters */
switch (tparam_.tree_method) {
case TreeMethod::kAuto: // Use hist as default in 2.0
case TreeMethod::kHist: {
tparam_.updater_seq = "grow_quantile_histmaker";
break;
}
case TreeMethod::kApprox:
tparam_.updater_seq = "grow_histmaker";
break;
case TreeMethod::kExact:
tparam_.updater_seq = "grow_colmaker,prune";
break;
case TreeMethod::kGPUHist: {
common::AssertGPUSupport();
tparam_.updater_seq = "grow_gpu_hist";
break;
}
default:
LOG(FATAL) << "Unknown tree_method (" << static_cast<int>(tparam_.tree_method)
<< ") detected";
for (auto& up : updaters_) {
up->Configure(cfg);
}
}

Expand Down Expand Up @@ -195,14 +208,8 @@ void GBTree::DoBoost(DMatrix* p_fmat, HostDeviceVector<GradientPair>* in_gpair,
bst_target_t const n_groups = model_.learner_model_param->OutputLength();
monitor_.Start("BoostNewTrees");

// Weird case that tree method is cpu-based but gpu_id is set. Ideally we should let
// `gpu_id` be the single source of determining what algorithms to run, but that will
// break a lots of existing code.
auto device = tparam_.tree_method != TreeMethod::kGPUHist ? Context::kCpuId : ctx_->gpu_id;
auto out = linalg::MakeTensorView(
device,
device == Context::kCpuId ? predt->predictions.HostSpan() : predt->predictions.DeviceSpan(),
p_fmat->Info().num_row_, model_.learner_model_param->OutputLength());
auto out = linalg::MakeTensorView(ctx_, &predt->predictions, p_fmat->Info().num_row_,
model_.learner_model_param->OutputLength());
CHECK_NE(n_groups, 0);

if (!p_fmat->SingleColBlock() && obj->Task().UpdateTreeLeaf()) {
Expand Down Expand Up @@ -261,47 +268,6 @@ void GBTree::DoBoost(DMatrix* p_fmat, HostDeviceVector<GradientPair>* in_gpair,
this->CommitModel(std::move(new_trees));
}

void GBTree::InitUpdater(Args const& cfg) {
std::string tval = tparam_.updater_seq;
std::vector<std::string> ups = common::Split(tval, ',');

if (updaters_.size() != 0) {
// Assert we have a valid set of updaters.
CHECK_EQ(ups.size(), updaters_.size());
for (auto const& up : updaters_) {
bool contains = std::any_of(ups.cbegin(), ups.cend(),
[&up](std::string const& name) {
return name == up->Name();
});
if (!contains) {
std::stringstream ss;
ss << "Internal Error: " << " mismatched updater sequence.\n";
ss << "Specified updaters: ";
std::for_each(ups.cbegin(), ups.cend(),
[&ss](std::string const& name){
ss << name << " ";
});
ss << "\n" << "Actual updaters: ";
std::for_each(updaters_.cbegin(), updaters_.cend(),
[&ss](std::unique_ptr<TreeUpdater> const& updater){
ss << updater->Name() << " ";
});
LOG(FATAL) << ss.str();
}
}
// Do not push new updater in.
return;
}

// create new updaters
for (const std::string& pstr : ups) {
std::unique_ptr<TreeUpdater> up(
TreeUpdater::Create(pstr.c_str(), ctx_, &model_.learner_model_param->task));
up->Configure(cfg);
updaters_.push_back(std::move(up));
}
}

void GBTree::BoostNewTrees(HostDeviceVector<GradientPair>* gpair, DMatrix* p_fmat, int bst_group,
std::vector<HostDeviceVector<bst_node_t>>* out_position,
TreesOneGroup* ret) {
Expand All @@ -310,6 +276,7 @@ void GBTree::BoostNewTrees(HostDeviceVector<GradientPair>* gpair, DMatrix* p_fma
// create the trees
for (int i = 0; i < model_.param.num_parallel_tree; ++i) {
if (tparam_.process_type == TreeProcessType::kDefault) {
CHECK(!updaters_.empty());
CHECK(!updaters_.front()->CanModifyTree())
<< "Updater: `" << updaters_.front()->Name() << "` "
<< "can not be used to create new trees. "
Expand Down Expand Up @@ -465,7 +432,6 @@ void GBTree::SaveModel(Json* p_out) const {

void GBTree::Slice(bst_layer_t begin, bst_layer_t end, bst_layer_t step, GradientBooster* out,
bool* out_of_bound) const {
CHECK(configured_);
CHECK(out);

auto p_gbtree = dynamic_cast<GBTree*>(out);
Expand Down Expand Up @@ -517,7 +483,6 @@ void GBTree::Slice(bst_layer_t begin, bst_layer_t end, bst_layer_t step, Gradien

void GBTree::PredictBatchImpl(DMatrix* p_fmat, PredictionCacheEntry* out_preds, bool is_training,
bst_layer_t layer_begin, bst_layer_t layer_end) const {
CHECK(configured_);
if (layer_end == 0) {
layer_end = this->BoostedRounds();
}
Expand Down Expand Up @@ -577,7 +542,6 @@ void GBTree::PredictBatch(DMatrix* p_fmat, PredictionCacheEntry* out_preds, bool
void GBTree::InplacePredict(std::shared_ptr<DMatrix> p_m, float missing,
PredictionCacheEntry* out_preds, bst_layer_t layer_begin,
bst_layer_t layer_end) const {
CHECK(configured_);
auto [tree_begin, tree_end] = detail::LayerToTree(model_, layer_begin, layer_end);
CHECK_LE(tree_end, model_.trees.size()) << "Invalid number of trees.";
if (p_m->Ctx()->Device() != this->ctx_->Device()) {
Expand Down Expand Up @@ -606,8 +570,6 @@ void GBTree::InplacePredict(std::shared_ptr<DMatrix> p_m, float missing,

[[nodiscard]] std::unique_ptr<Predictor> const& GBTree::GetPredictor(
bool is_training, HostDeviceVector<float> const* out_pred, DMatrix* f_dmat) const {
CHECK(configured_);

// Data comes from SparsePageDMatrix. Since we are loading data in pages, no need to
// prevent data copy.
if (f_dmat && !f_dmat->SingleColBlock()) {
Expand Down Expand Up @@ -914,7 +876,6 @@ class Dart : public GBTree {
void PredictContribution(DMatrix* p_fmat, HostDeviceVector<bst_float>* out_contribs,
bst_layer_t layer_begin, bst_layer_t layer_end,
bool approximate) override {
CHECK(configured_);
auto [tree_begin, tree_end] = detail::LayerToTree(model_, layer_begin, layer_end);
cpu_predictor_->PredictContribution(p_fmat, out_contribs, model_, tree_end, &weight_drop_,
approximate);
Expand All @@ -923,7 +884,6 @@ class Dart : public GBTree {
void PredictInteractionContributions(DMatrix* p_fmat, HostDeviceVector<float>* out_contribs,
bst_layer_t layer_begin, bst_layer_t layer_end,
bool approximate) override {
CHECK(configured_);
auto [tree_begin, tree_end] = detail::LayerToTree(model_, layer_begin, layer_end);
cpu_predictor_->PredictInteractionContributions(p_fmat, out_contribs, model_, tree_end,
&weight_drop_, approximate);
Expand Down
Loading
Loading