Initial GPU support for the approx tree method. (#9414)
This commit is contained in:
@@ -13,10 +13,7 @@
|
||||
#include "../../../src/common/common.h"
|
||||
#include "../../../src/data/ellpack_page.cuh" // for EllpackPageImpl
|
||||
#include "../../../src/data/ellpack_page.h" // for EllpackPage
|
||||
#include "../../../src/data/sparse_page_source.h"
|
||||
#include "../../../src/tree/constraints.cuh"
|
||||
#include "../../../src/tree/param.h" // for TrainParam
|
||||
#include "../../../src/tree/updater_gpu_common.cuh"
|
||||
#include "../../../src/tree/updater_gpu_hist.cu"
|
||||
#include "../filesystem.h" // dmlc::TemporaryDirectory
|
||||
#include "../helpers.h"
|
||||
@@ -94,8 +91,9 @@ void TestBuildHist(bool use_shared_memory_histograms) {
|
||||
auto page = BuildEllpackPage(kNRows, kNCols);
|
||||
BatchParam batch_param{};
|
||||
Context ctx{MakeCUDACtx(0)};
|
||||
GPUHistMakerDevice<GradientSumT> maker(&ctx, /*is_external_memory=*/false, {}, kNRows, param,
|
||||
kNCols, kNCols, batch_param);
|
||||
auto cs = std::make_shared<common::ColumnSampler>(0);
|
||||
GPUHistMakerDevice maker(&ctx, /*is_external_memory=*/false, {}, kNRows, param, cs, kNCols,
|
||||
batch_param);
|
||||
xgboost::SimpleLCG gen;
|
||||
xgboost::SimpleRealUniformDistribution<bst_float> dist(0.0f, 1.0f);
|
||||
HostDeviceVector<GradientPair> gpair(kNRows);
|
||||
|
||||
@@ -24,15 +24,11 @@ class TestPredictionCache : public ::testing::Test {
|
||||
Xy_ = RandomDataGenerator{n_samples_, n_features, 0}.Targets(n_targets).GenerateDMatrix(true);
|
||||
}
|
||||
|
||||
void RunLearnerTest(std::string updater_name, float subsample, std::string const& grow_policy,
|
||||
std::string const& strategy) {
|
||||
void RunLearnerTest(Context const* ctx, std::string updater_name, float subsample,
|
||||
std::string const& grow_policy, std::string const& strategy) {
|
||||
std::unique_ptr<Learner> learner{Learner::Create({Xy_})};
|
||||
if (updater_name == "grow_gpu_hist") {
|
||||
// gpu_id setup
|
||||
learner->SetParam("tree_method", "gpu_hist");
|
||||
} else {
|
||||
learner->SetParam("updater", updater_name);
|
||||
}
|
||||
learner->SetParam("device", ctx->DeviceName());
|
||||
learner->SetParam("updater", updater_name);
|
||||
learner->SetParam("multi_strategy", strategy);
|
||||
learner->SetParam("grow_policy", grow_policy);
|
||||
learner->SetParam("subsample", std::to_string(subsample));
|
||||
@@ -65,20 +61,14 @@ class TestPredictionCache : public ::testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
void RunTest(std::string const& updater_name, std::string const& strategy) {
|
||||
void RunTest(Context* ctx, std::string const& updater_name, std::string const& strategy) {
|
||||
{
|
||||
Context ctx;
|
||||
ctx.InitAllowUnknown(Args{{"nthread", "8"}});
|
||||
if (updater_name == "grow_gpu_hist") {
|
||||
ctx = ctx.MakeCUDA(0);
|
||||
} else {
|
||||
ctx = ctx.MakeCPU();
|
||||
}
|
||||
ctx->InitAllowUnknown(Args{{"nthread", "8"}});
|
||||
|
||||
ObjInfo task{ObjInfo::kRegression};
|
||||
std::unique_ptr<TreeUpdater> updater{TreeUpdater::Create(updater_name, &ctx, &task)};
|
||||
std::unique_ptr<TreeUpdater> updater{TreeUpdater::Create(updater_name, ctx, &task)};
|
||||
RegTree tree;
|
||||
std::vector<RegTree *> trees{&tree};
|
||||
std::vector<RegTree*> trees{&tree};
|
||||
auto gpair = GenerateRandomGradients(n_samples_);
|
||||
tree::TrainParam param;
|
||||
param.UpdateAllowUnknown(Args{{"max_bin", "64"}});
|
||||
@@ -86,33 +76,46 @@ class TestPredictionCache : public ::testing::Test {
|
||||
std::vector<HostDeviceVector<bst_node_t>> position(1);
|
||||
updater->Update(¶m, &gpair, Xy_.get(), position, trees);
|
||||
HostDeviceVector<float> out_prediction_cached;
|
||||
out_prediction_cached.SetDevice(ctx.gpu_id);
|
||||
out_prediction_cached.SetDevice(ctx->Device());
|
||||
out_prediction_cached.Resize(n_samples_);
|
||||
auto cache =
|
||||
linalg::MakeTensorView(&ctx, &out_prediction_cached, out_prediction_cached.Size(), 1);
|
||||
linalg::MakeTensorView(ctx, &out_prediction_cached, out_prediction_cached.Size(), 1);
|
||||
ASSERT_TRUE(updater->UpdatePredictionCache(Xy_.get(), cache));
|
||||
}
|
||||
|
||||
for (auto policy : {"depthwise", "lossguide"}) {
|
||||
for (auto subsample : {1.0f, 0.4f}) {
|
||||
this->RunLearnerTest(updater_name, subsample, policy, strategy);
|
||||
this->RunLearnerTest(updater_name, subsample, policy, strategy);
|
||||
this->RunLearnerTest(ctx, updater_name, subsample, policy, strategy);
|
||||
this->RunLearnerTest(ctx, updater_name, subsample, policy, strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TestPredictionCache, Approx) { this->RunTest("grow_histmaker", "one_output_per_tree"); }
|
||||
TEST_F(TestPredictionCache, Approx) {
|
||||
Context ctx;
|
||||
this->RunTest(&ctx, "grow_histmaker", "one_output_per_tree");
|
||||
}
|
||||
|
||||
TEST_F(TestPredictionCache, Hist) {
|
||||
this->RunTest("grow_quantile_histmaker", "one_output_per_tree");
|
||||
Context ctx;
|
||||
this->RunTest(&ctx, "grow_quantile_histmaker", "one_output_per_tree");
|
||||
}
|
||||
|
||||
TEST_F(TestPredictionCache, HistMulti) {
|
||||
this->RunTest("grow_quantile_histmaker", "multi_output_tree");
|
||||
Context ctx;
|
||||
this->RunTest(&ctx, "grow_quantile_histmaker", "multi_output_tree");
|
||||
}
|
||||
|
||||
#if defined(XGBOOST_USE_CUDA)
|
||||
TEST_F(TestPredictionCache, GpuHist) { this->RunTest("grow_gpu_hist", "one_output_per_tree"); }
|
||||
TEST_F(TestPredictionCache, GpuHist) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
this->RunTest(&ctx, "grow_gpu_hist", "one_output_per_tree");
|
||||
}
|
||||
|
||||
TEST_F(TestPredictionCache, GpuApprox) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
this->RunTest(&ctx, "grow_gpu_approx", "one_output_per_tree");
|
||||
}
|
||||
#endif // defined(XGBOOST_USE_CUDA)
|
||||
} // namespace xgboost
|
||||
|
||||
@@ -62,8 +62,10 @@ class RegenTest : public ::testing::Test {
|
||||
auto constexpr Iter() const { return 4; }
|
||||
|
||||
template <typename Page>
|
||||
size_t TestTreeMethod(std::string tree_method, std::string obj, bool reset = true) const {
|
||||
size_t TestTreeMethod(Context const* ctx, std::string tree_method, std::string obj,
|
||||
bool reset = true) const {
|
||||
auto learner = std::unique_ptr<Learner>{Learner::Create({p_fmat_})};
|
||||
learner->SetParam("device", ctx->DeviceName());
|
||||
learner->SetParam("tree_method", tree_method);
|
||||
learner->SetParam("objective", obj);
|
||||
learner->Configure();
|
||||
@@ -87,40 +89,71 @@ class RegenTest : public ::testing::Test {
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(RegenTest, Approx) {
|
||||
auto n = this->TestTreeMethod<GHistIndexMatrix>("approx", "reg:squarederror");
|
||||
Context ctx;
|
||||
auto n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "approx", "reg:squarederror");
|
||||
ASSERT_EQ(n, 1);
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>("approx", "reg:logistic");
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "approx", "reg:logistic");
|
||||
ASSERT_EQ(n, this->Iter());
|
||||
}
|
||||
|
||||
TEST_F(RegenTest, Hist) {
|
||||
auto n = this->TestTreeMethod<GHistIndexMatrix>("hist", "reg:squarederror");
|
||||
Context ctx;
|
||||
auto n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "hist", "reg:squarederror");
|
||||
ASSERT_EQ(n, 1);
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>("hist", "reg:logistic");
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "hist", "reg:logistic");
|
||||
ASSERT_EQ(n, 1);
|
||||
}
|
||||
|
||||
TEST_F(RegenTest, Mixed) {
|
||||
auto n = this->TestTreeMethod<GHistIndexMatrix>("hist", "reg:squarederror", false);
|
||||
Context ctx;
|
||||
auto n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "hist", "reg:squarederror", false);
|
||||
ASSERT_EQ(n, 1);
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>("approx", "reg:logistic", true);
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "approx", "reg:logistic", true);
|
||||
ASSERT_EQ(n, this->Iter() + 1);
|
||||
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>("approx", "reg:logistic", false);
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "approx", "reg:logistic", false);
|
||||
ASSERT_EQ(n, this->Iter());
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>("hist", "reg:squarederror", true);
|
||||
n = this->TestTreeMethod<GHistIndexMatrix>(&ctx, "hist", "reg:squarederror", true);
|
||||
ASSERT_EQ(n, this->Iter() + 1);
|
||||
}
|
||||
|
||||
#if defined(XGBOOST_USE_CUDA)
|
||||
TEST_F(RegenTest, GpuHist) {
|
||||
auto n = this->TestTreeMethod<EllpackPage>("gpu_hist", "reg:squarederror");
|
||||
TEST_F(RegenTest, GpuApprox) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
auto n = this->TestTreeMethod<EllpackPage>(&ctx, "approx", "reg:squarederror", true);
|
||||
ASSERT_EQ(n, 1);
|
||||
n = this->TestTreeMethod<EllpackPage>("gpu_hist", "reg:logistic", false);
|
||||
n = this->TestTreeMethod<EllpackPage>(&ctx, "approx", "reg:logistic", false);
|
||||
ASSERT_EQ(n, this->Iter());
|
||||
|
||||
n = this->TestTreeMethod<EllpackPage>(&ctx, "approx", "reg:logistic", true);
|
||||
ASSERT_EQ(n, this->Iter() * 2);
|
||||
}
|
||||
|
||||
TEST_F(RegenTest, GpuHist) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
auto n = this->TestTreeMethod<EllpackPage>(&ctx, "hist", "reg:squarederror", true);
|
||||
ASSERT_EQ(n, 1);
|
||||
n = this->TestTreeMethod<EllpackPage>(&ctx, "hist", "reg:logistic", false);
|
||||
ASSERT_EQ(n, 1);
|
||||
|
||||
n = this->TestTreeMethod<EllpackPage>("hist", "reg:logistic");
|
||||
ASSERT_EQ(n, 2);
|
||||
{
|
||||
Context ctx;
|
||||
n = this->TestTreeMethod<EllpackPage>(&ctx, "hist", "reg:logistic");
|
||||
ASSERT_EQ(n, 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RegenTest, GpuMixed) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
auto n = this->TestTreeMethod<EllpackPage>(&ctx, "hist", "reg:squarederror", false);
|
||||
ASSERT_EQ(n, 1);
|
||||
n = this->TestTreeMethod<EllpackPage>(&ctx, "approx", "reg:logistic", true);
|
||||
ASSERT_EQ(n, this->Iter() + 1);
|
||||
|
||||
n = this->TestTreeMethod<EllpackPage>(&ctx, "approx", "reg:logistic", false);
|
||||
ASSERT_EQ(n, this->Iter());
|
||||
n = this->TestTreeMethod<EllpackPage>(&ctx, "hist", "reg:squarederror", true);
|
||||
ASSERT_EQ(n, this->Iter() + 1);
|
||||
}
|
||||
#endif // defined(XGBOOST_USE_CUDA)
|
||||
} // namespace xgboost
|
||||
|
||||
@@ -20,10 +20,11 @@ class TestGrowPolicy : public ::testing::Test {
|
||||
true);
|
||||
}
|
||||
|
||||
std::unique_ptr<Learner> TrainOneIter(std::string tree_method, std::string policy,
|
||||
int32_t max_leaves, int32_t max_depth) {
|
||||
std::unique_ptr<Learner> TrainOneIter(Context const* ctx, std::string tree_method,
|
||||
std::string policy, int32_t max_leaves, int32_t max_depth) {
|
||||
std::unique_ptr<Learner> learner{Learner::Create({this->Xy_})};
|
||||
learner->SetParam("tree_method", tree_method);
|
||||
learner->SetParam("device", ctx->DeviceName());
|
||||
if (max_leaves >= 0) {
|
||||
learner->SetParam("max_leaves", std::to_string(max_leaves));
|
||||
}
|
||||
@@ -63,7 +64,7 @@ class TestGrowPolicy : public ::testing::Test {
|
||||
|
||||
if (max_leaves == 0 && max_depth == 0) {
|
||||
// unconstrainted
|
||||
if (tree_method != "gpu_hist") {
|
||||
if (ctx->IsCPU()) {
|
||||
// GPU pre-allocates for all nodes.
|
||||
learner->UpdateOneIter(0, Xy_);
|
||||
}
|
||||
@@ -86,23 +87,23 @@ class TestGrowPolicy : public ::testing::Test {
|
||||
return learner;
|
||||
}
|
||||
|
||||
void TestCombination(std::string tree_method) {
|
||||
void TestCombination(Context const* ctx, std::string tree_method) {
|
||||
for (auto policy : {"depthwise", "lossguide"}) {
|
||||
// -1 means default
|
||||
for (auto leaves : {-1, 0, 3}) {
|
||||
for (auto depth : {-1, 0, 3}) {
|
||||
this->TrainOneIter(tree_method, policy, leaves, depth);
|
||||
this->TrainOneIter(ctx, tree_method, policy, leaves, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestTreeGrowPolicy(std::string tree_method, std::string policy) {
|
||||
void TestTreeGrowPolicy(Context const* ctx, std::string tree_method, std::string policy) {
|
||||
{
|
||||
/**
|
||||
* max_leaves
|
||||
*/
|
||||
auto learner = this->TrainOneIter(tree_method, policy, 16, -1);
|
||||
auto learner = this->TrainOneIter(ctx, tree_method, policy, 16, -1);
|
||||
Json model{Object{}};
|
||||
learner->SaveModel(&model);
|
||||
|
||||
@@ -115,7 +116,7 @@ class TestGrowPolicy : public ::testing::Test {
|
||||
/**
|
||||
* max_depth
|
||||
*/
|
||||
auto learner = this->TrainOneIter(tree_method, policy, -1, 3);
|
||||
auto learner = this->TrainOneIter(ctx, tree_method, policy, -1, 3);
|
||||
Json model{Object{}};
|
||||
learner->SaveModel(&model);
|
||||
|
||||
@@ -133,25 +134,36 @@ class TestGrowPolicy : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(TestGrowPolicy, Approx) {
|
||||
this->TestTreeGrowPolicy("approx", "depthwise");
|
||||
this->TestTreeGrowPolicy("approx", "lossguide");
|
||||
Context ctx;
|
||||
this->TestTreeGrowPolicy(&ctx, "approx", "depthwise");
|
||||
this->TestTreeGrowPolicy(&ctx, "approx", "lossguide");
|
||||
|
||||
this->TestCombination("approx");
|
||||
this->TestCombination(&ctx, "approx");
|
||||
}
|
||||
|
||||
TEST_F(TestGrowPolicy, Hist) {
|
||||
this->TestTreeGrowPolicy("hist", "depthwise");
|
||||
this->TestTreeGrowPolicy("hist", "lossguide");
|
||||
Context ctx;
|
||||
this->TestTreeGrowPolicy(&ctx, "hist", "depthwise");
|
||||
this->TestTreeGrowPolicy(&ctx, "hist", "lossguide");
|
||||
|
||||
this->TestCombination("hist");
|
||||
this->TestCombination(&ctx, "hist");
|
||||
}
|
||||
|
||||
#if defined(XGBOOST_USE_CUDA)
|
||||
TEST_F(TestGrowPolicy, GpuHist) {
|
||||
this->TestTreeGrowPolicy("gpu_hist", "depthwise");
|
||||
this->TestTreeGrowPolicy("gpu_hist", "lossguide");
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
this->TestTreeGrowPolicy(&ctx, "hist", "depthwise");
|
||||
this->TestTreeGrowPolicy(&ctx, "hist", "lossguide");
|
||||
|
||||
this->TestCombination("gpu_hist");
|
||||
this->TestCombination(&ctx, "hist");
|
||||
}
|
||||
|
||||
TEST_F(TestGrowPolicy, GpuApprox) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
this->TestTreeGrowPolicy(&ctx, "approx", "depthwise");
|
||||
this->TestTreeGrowPolicy(&ctx, "approx", "lossguide");
|
||||
|
||||
this->TestCombination(&ctx, "approx");
|
||||
}
|
||||
#endif // defined(XGBOOST_USE_CUDA)
|
||||
} // namespace xgboost
|
||||
|
||||
@@ -135,7 +135,7 @@ class TestMinSplitLoss : public ::testing::Test {
|
||||
gpair_ = GenerateRandomGradients(kRows);
|
||||
}
|
||||
|
||||
std::int32_t Update(std::string updater, float gamma) {
|
||||
std::int32_t Update(Context const* ctx, std::string updater, float gamma) {
|
||||
Args args{{"max_depth", "1"},
|
||||
{"max_leaves", "0"},
|
||||
|
||||
@@ -154,8 +154,7 @@ class TestMinSplitLoss : public ::testing::Test {
|
||||
param.UpdateAllowUnknown(args);
|
||||
ObjInfo task{ObjInfo::kRegression};
|
||||
|
||||
Context ctx{MakeCUDACtx(updater == "grow_gpu_hist" ? 0 : Context::kCpuId)};
|
||||
auto up = std::unique_ptr<TreeUpdater>{TreeUpdater::Create(updater, &ctx, &task)};
|
||||
auto up = std::unique_ptr<TreeUpdater>{TreeUpdater::Create(updater, ctx, &task)};
|
||||
up->Configure({});
|
||||
|
||||
RegTree tree;
|
||||
@@ -167,16 +166,16 @@ class TestMinSplitLoss : public ::testing::Test {
|
||||
}
|
||||
|
||||
public:
|
||||
void RunTest(std::string updater) {
|
||||
void RunTest(Context const* ctx, std::string updater) {
|
||||
{
|
||||
int32_t n_nodes = Update(updater, 0.01);
|
||||
int32_t n_nodes = Update(ctx, updater, 0.01);
|
||||
// This is not strictly verified, meaning the numeber `2` is whatever GPU_Hist retured
|
||||
// when writing this test, and only used for testing larger gamma (below) does prevent
|
||||
// building tree.
|
||||
ASSERT_EQ(n_nodes, 2);
|
||||
}
|
||||
{
|
||||
int32_t n_nodes = Update(updater, 100.0);
|
||||
int32_t n_nodes = Update(ctx, updater, 100.0);
|
||||
// No new nodes with gamma == 100.
|
||||
ASSERT_EQ(n_nodes, static_cast<decltype(n_nodes)>(0));
|
||||
}
|
||||
@@ -185,10 +184,25 @@ class TestMinSplitLoss : public ::testing::Test {
|
||||
|
||||
/* Exact tree method requires a pruner as an additional updater, so not tested here. */
|
||||
|
||||
TEST_F(TestMinSplitLoss, Approx) { this->RunTest("grow_histmaker"); }
|
||||
TEST_F(TestMinSplitLoss, Approx) {
|
||||
Context ctx;
|
||||
this->RunTest(&ctx, "grow_histmaker");
|
||||
}
|
||||
|
||||
TEST_F(TestMinSplitLoss, Hist) {
|
||||
Context ctx;
|
||||
this->RunTest(&ctx, "grow_quantile_histmaker");
|
||||
}
|
||||
|
||||
TEST_F(TestMinSplitLoss, Hist) { this->RunTest("grow_quantile_histmaker"); }
|
||||
#if defined(XGBOOST_USE_CUDA)
|
||||
TEST_F(TestMinSplitLoss, GpuHist) { this->RunTest("grow_gpu_hist"); }
|
||||
TEST_F(TestMinSplitLoss, GpuHist) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
this->RunTest(&ctx, "grow_gpu_hist");
|
||||
}
|
||||
|
||||
TEST_F(TestMinSplitLoss, GpuApprox) {
|
||||
auto ctx = MakeCUDACtx(0);
|
||||
this->RunTest(&ctx, "grow_gpu_approx");
|
||||
}
|
||||
#endif // defined(XGBOOST_USE_CUDA)
|
||||
} // namespace xgboost
|
||||
|
||||
@@ -7,11 +7,18 @@ from hypothesis import assume, given, note, settings, strategies
|
||||
|
||||
import xgboost as xgb
|
||||
from xgboost import testing as tm
|
||||
from xgboost.testing.params import cat_parameter_strategy, hist_parameter_strategy
|
||||
from xgboost.testing.params import (
|
||||
cat_parameter_strategy,
|
||||
exact_parameter_strategy,
|
||||
hist_parameter_strategy,
|
||||
)
|
||||
from xgboost.testing.updater import (
|
||||
check_categorical_missing,
|
||||
check_categorical_ohe,
|
||||
check_get_quantile_cut,
|
||||
check_init_estimation,
|
||||
check_quantile_loss,
|
||||
train_result,
|
||||
)
|
||||
|
||||
sys.path.append("tests/python")
|
||||
@@ -20,22 +27,6 @@ import test_updaters as test_up
|
||||
pytestmark = tm.timeout(30)
|
||||
|
||||
|
||||
def train_result(param, dmat: xgb.DMatrix, num_rounds: int) -> dict:
|
||||
result: xgb.callback.TrainingCallback.EvalsLog = {}
|
||||
booster = xgb.train(
|
||||
param,
|
||||
dmat,
|
||||
num_rounds,
|
||||
[(dmat, "train")],
|
||||
verbose_eval=False,
|
||||
evals_result=result,
|
||||
)
|
||||
assert booster.num_features() == dmat.num_col()
|
||||
assert booster.num_boosted_rounds() == num_rounds
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class TestGPUUpdatersMulti:
|
||||
@given(
|
||||
hist_parameter_strategy, strategies.integers(1, 20), tm.multi_dataset_strategy
|
||||
@@ -53,14 +44,45 @@ class TestGPUUpdaters:
|
||||
cputest = test_up.TestTreeMethod()
|
||||
|
||||
@given(
|
||||
hist_parameter_strategy, strategies.integers(1, 20), tm.make_dataset_strategy()
|
||||
exact_parameter_strategy,
|
||||
hist_parameter_strategy,
|
||||
strategies.integers(1, 20),
|
||||
tm.make_dataset_strategy(),
|
||||
)
|
||||
@settings(deadline=None, max_examples=50, print_blob=True)
|
||||
def test_gpu_hist(self, param, num_rounds, dataset):
|
||||
param["tree_method"] = "gpu_hist"
|
||||
def test_gpu_hist(
|
||||
self,
|
||||
param: Dict[str, Any],
|
||||
hist_param: Dict[str, Any],
|
||||
num_rounds: int,
|
||||
dataset: tm.TestDataset,
|
||||
) -> None:
|
||||
param.update({"tree_method": "hist", "device": "cuda"})
|
||||
param.update(hist_param)
|
||||
param = dataset.set_params(param)
|
||||
result = train_result(param, dataset.get_dmat(), num_rounds)
|
||||
note(result)
|
||||
note(str(result))
|
||||
assert tm.non_increasing(result["train"][dataset.metric])
|
||||
|
||||
@given(
|
||||
exact_parameter_strategy,
|
||||
hist_parameter_strategy,
|
||||
strategies.integers(1, 20),
|
||||
tm.make_dataset_strategy(),
|
||||
)
|
||||
@settings(deadline=None, print_blob=True)
|
||||
def test_gpu_approx(
|
||||
self,
|
||||
param: Dict[str, Any],
|
||||
hist_param: Dict[str, Any],
|
||||
num_rounds: int,
|
||||
dataset: tm.TestDataset,
|
||||
) -> None:
|
||||
param.update({"tree_method": "approx", "device": "cuda"})
|
||||
param.update(hist_param)
|
||||
param = dataset.set_params(param)
|
||||
result = train_result(param, dataset.get_dmat(), num_rounds)
|
||||
note(str(result))
|
||||
assert tm.non_increasing(result["train"][dataset.metric])
|
||||
|
||||
@given(tm.sparse_datasets_strategy)
|
||||
@@ -69,23 +91,27 @@ class TestGPUUpdaters:
|
||||
param = {"tree_method": "hist", "max_bin": 64}
|
||||
hist_result = train_result(param, dataset.get_dmat(), 16)
|
||||
note(hist_result)
|
||||
assert tm.non_increasing(hist_result['train'][dataset.metric])
|
||||
assert tm.non_increasing(hist_result["train"][dataset.metric])
|
||||
|
||||
param = {"tree_method": "gpu_hist", "max_bin": 64}
|
||||
gpu_hist_result = train_result(param, dataset.get_dmat(), 16)
|
||||
note(gpu_hist_result)
|
||||
assert tm.non_increasing(gpu_hist_result['train'][dataset.metric])
|
||||
assert tm.non_increasing(gpu_hist_result["train"][dataset.metric])
|
||||
|
||||
np.testing.assert_allclose(
|
||||
hist_result["train"]["rmse"], gpu_hist_result["train"]["rmse"], rtol=1e-2
|
||||
)
|
||||
|
||||
@given(strategies.integers(10, 400), strategies.integers(3, 8),
|
||||
strategies.integers(1, 2), strategies.integers(4, 7))
|
||||
@given(
|
||||
strategies.integers(10, 400),
|
||||
strategies.integers(3, 8),
|
||||
strategies.integers(1, 2),
|
||||
strategies.integers(4, 7),
|
||||
)
|
||||
@settings(deadline=None, max_examples=20, print_blob=True)
|
||||
@pytest.mark.skipif(**tm.no_pandas())
|
||||
def test_categorical_ohe(self, rows, cols, rounds, cats):
|
||||
self.cputest.run_categorical_ohe(rows, cols, rounds, cats, "gpu_hist")
|
||||
check_categorical_ohe(rows, cols, rounds, cats, "cuda", "hist")
|
||||
|
||||
@given(
|
||||
tm.categorical_dataset_strategy,
|
||||
@@ -95,7 +121,7 @@ class TestGPUUpdaters:
|
||||
)
|
||||
@settings(deadline=None, max_examples=20, print_blob=True)
|
||||
@pytest.mark.skipif(**tm.no_pandas())
|
||||
def test_categorical(
|
||||
def test_categorical_hist(
|
||||
self,
|
||||
dataset: tm.TestDataset,
|
||||
hist_parameters: Dict[str, Any],
|
||||
@@ -103,7 +129,30 @@ class TestGPUUpdaters:
|
||||
n_rounds: int,
|
||||
) -> None:
|
||||
cat_parameters.update(hist_parameters)
|
||||
cat_parameters["tree_method"] = "gpu_hist"
|
||||
cat_parameters["tree_method"] = "hist"
|
||||
cat_parameters["device"] = "cuda"
|
||||
|
||||
results = train_result(cat_parameters, dataset.get_dmat(), n_rounds)
|
||||
tm.non_increasing(results["train"]["rmse"])
|
||||
|
||||
@given(
|
||||
tm.categorical_dataset_strategy,
|
||||
hist_parameter_strategy,
|
||||
cat_parameter_strategy,
|
||||
strategies.integers(4, 32),
|
||||
)
|
||||
@settings(deadline=None, max_examples=20, print_blob=True)
|
||||
@pytest.mark.skipif(**tm.no_pandas())
|
||||
def test_categorical_approx(
|
||||
self,
|
||||
dataset: tm.TestDataset,
|
||||
hist_parameters: Dict[str, Any],
|
||||
cat_parameters: Dict[str, Any],
|
||||
n_rounds: int,
|
||||
) -> None:
|
||||
cat_parameters.update(hist_parameters)
|
||||
cat_parameters["tree_method"] = "approx"
|
||||
cat_parameters["device"] = "cuda"
|
||||
|
||||
results = train_result(cat_parameters, dataset.get_dmat(), n_rounds)
|
||||
tm.non_increasing(results["train"]["rmse"])
|
||||
@@ -129,24 +178,25 @@ class TestGPUUpdaters:
|
||||
@given(
|
||||
strategies.integers(10, 400),
|
||||
strategies.integers(3, 8),
|
||||
strategies.integers(4, 7)
|
||||
strategies.integers(4, 7),
|
||||
)
|
||||
@settings(deadline=None, max_examples=20, print_blob=True)
|
||||
@pytest.mark.skipif(**tm.no_pandas())
|
||||
def test_categorical_missing(self, rows, cols, cats):
|
||||
self.cputest.run_categorical_missing(rows, cols, cats, "gpu_hist")
|
||||
check_categorical_missing(rows, cols, cats, "cuda", "approx")
|
||||
check_categorical_missing(rows, cols, cats, "cuda", "hist")
|
||||
|
||||
@pytest.mark.skipif(**tm.no_pandas())
|
||||
def test_max_cat(self) -> None:
|
||||
self.cputest.run_max_cat("gpu_hist")
|
||||
|
||||
def test_categorical_32_cat(self):
|
||||
'''32 hits the bound of integer bitset, so special test'''
|
||||
"""32 hits the bound of integer bitset, so special test"""
|
||||
rows = 1000
|
||||
cols = 10
|
||||
cats = 32
|
||||
rounds = 4
|
||||
self.cputest.run_categorical_ohe(rows, cols, rounds, cats, "gpu_hist")
|
||||
check_categorical_ohe(rows, cols, rounds, cats, "cuda", "hist")
|
||||
|
||||
@pytest.mark.skipif(**tm.no_cupy())
|
||||
def test_invalid_category(self):
|
||||
@@ -164,15 +214,15 @@ class TestGPUUpdaters:
|
||||
) -> None:
|
||||
# We cannot handle empty dataset yet
|
||||
assume(len(dataset.y) > 0)
|
||||
param['tree_method'] = 'gpu_hist'
|
||||
param["tree_method"] = "gpu_hist"
|
||||
param = dataset.set_params(param)
|
||||
result = train_result(
|
||||
param,
|
||||
dataset.get_device_dmat(max_bin=param.get("max_bin", None)),
|
||||
num_rounds
|
||||
num_rounds,
|
||||
)
|
||||
note(result)
|
||||
assert tm.non_increasing(result['train'][dataset.metric], tolerance=1e-3)
|
||||
assert tm.non_increasing(result["train"][dataset.metric], tolerance=1e-3)
|
||||
|
||||
@given(
|
||||
hist_parameter_strategy,
|
||||
@@ -185,12 +235,12 @@ class TestGPUUpdaters:
|
||||
return
|
||||
# We cannot handle empty dataset yet
|
||||
assume(len(dataset.y) > 0)
|
||||
param['tree_method'] = 'gpu_hist'
|
||||
param["tree_method"] = "gpu_hist"
|
||||
param = dataset.set_params(param)
|
||||
m = dataset.get_external_dmat()
|
||||
external_result = train_result(param, m, num_rounds)
|
||||
del m
|
||||
assert tm.non_increasing(external_result['train'][dataset.metric])
|
||||
assert tm.non_increasing(external_result["train"][dataset.metric])
|
||||
|
||||
def test_empty_dmatrix_prediction(self):
|
||||
# FIXME(trivialfis): This should be done with all updaters
|
||||
@@ -207,7 +257,7 @@ class TestGPUUpdaters:
|
||||
dtrain,
|
||||
verbose_eval=True,
|
||||
num_boost_round=6,
|
||||
evals=[(dtrain, 'Train')]
|
||||
evals=[(dtrain, "Train")],
|
||||
)
|
||||
|
||||
kRows = 100
|
||||
@@ -222,10 +272,10 @@ class TestGPUUpdaters:
|
||||
@given(tm.make_dataset_strategy(), strategies.integers(0, 10))
|
||||
@settings(deadline=None, max_examples=10, print_blob=True)
|
||||
def test_specified_gpu_id_gpu_update(self, dataset, gpu_id):
|
||||
param = {'tree_method': 'gpu_hist', 'gpu_id': gpu_id}
|
||||
param = {"tree_method": "gpu_hist", "gpu_id": gpu_id}
|
||||
param = dataset.set_params(param)
|
||||
result = train_result(param, dataset.get_dmat(), 10)
|
||||
assert tm.non_increasing(result['train'][dataset.metric])
|
||||
assert tm.non_increasing(result["train"][dataset.metric])
|
||||
|
||||
@pytest.mark.skipif(**tm.no_sklearn())
|
||||
@pytest.mark.parametrize("weighted", [True, False])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import json
|
||||
from string import ascii_lowercase
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any, Dict
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
@@ -15,30 +15,15 @@ from xgboost.testing.params import (
|
||||
hist_parameter_strategy,
|
||||
)
|
||||
from xgboost.testing.updater import (
|
||||
check_categorical_missing,
|
||||
check_categorical_ohe,
|
||||
check_get_quantile_cut,
|
||||
check_init_estimation,
|
||||
check_quantile_loss,
|
||||
train_result,
|
||||
)
|
||||
|
||||
|
||||
def train_result(param, dmat, num_rounds):
|
||||
result = {}
|
||||
booster = xgb.train(
|
||||
param,
|
||||
dmat,
|
||||
num_rounds,
|
||||
evals=[(dmat, "train")],
|
||||
verbose_eval=False,
|
||||
evals_result=result,
|
||||
)
|
||||
assert booster.num_features() == dmat.num_col()
|
||||
assert booster.num_boosted_rounds() == num_rounds
|
||||
assert booster.feature_names == dmat.feature_names
|
||||
assert booster.feature_types == dmat.feature_types
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class TestTreeMethodMulti:
|
||||
@given(
|
||||
exact_parameter_strategy, strategies.integers(1, 20), tm.multi_dataset_strategy
|
||||
@@ -281,115 +266,6 @@ class TestTreeMethod:
|
||||
def test_max_cat(self, tree_method) -> None:
|
||||
self.run_max_cat(tree_method)
|
||||
|
||||
def run_categorical_missing(
|
||||
self, rows: int, cols: int, cats: int, tree_method: str
|
||||
) -> None:
|
||||
parameters: Dict[str, Any] = {"tree_method": tree_method}
|
||||
cat, label = tm.make_categorical(
|
||||
rows, n_features=cols, n_categories=cats, onehot=False, sparsity=0.5
|
||||
)
|
||||
Xy = xgb.DMatrix(cat, label, enable_categorical=True)
|
||||
|
||||
def run(max_cat_to_onehot: int):
|
||||
# Test with onehot splits
|
||||
parameters["max_cat_to_onehot"] = max_cat_to_onehot
|
||||
|
||||
evals_result: Dict[str, Dict] = {}
|
||||
booster = xgb.train(
|
||||
parameters,
|
||||
Xy,
|
||||
num_boost_round=16,
|
||||
evals=[(Xy, "Train")],
|
||||
evals_result=evals_result
|
||||
)
|
||||
assert tm.non_increasing(evals_result["Train"]["rmse"])
|
||||
y_predt = booster.predict(Xy)
|
||||
|
||||
rmse = tm.root_mean_square(label, y_predt)
|
||||
np.testing.assert_allclose(
|
||||
rmse, evals_result["Train"]["rmse"][-1], rtol=2e-5
|
||||
)
|
||||
|
||||
# Test with OHE split
|
||||
run(self.USE_ONEHOT)
|
||||
|
||||
# Test with partition-based split
|
||||
run(self.USE_PART)
|
||||
|
||||
def run_categorical_ohe(
|
||||
self, rows: int, cols: int, rounds: int, cats: int, tree_method: str
|
||||
) -> None:
|
||||
onehot, label = tm.make_categorical(rows, cols, cats, True)
|
||||
cat, _ = tm.make_categorical(rows, cols, cats, False)
|
||||
|
||||
by_etl_results: Dict[str, Dict[str, List[float]]] = {}
|
||||
by_builtin_results: Dict[str, Dict[str, List[float]]] = {}
|
||||
|
||||
parameters: Dict[str, Any] = {
|
||||
"tree_method": tree_method,
|
||||
# Use one-hot exclusively
|
||||
"max_cat_to_onehot": self.USE_ONEHOT
|
||||
}
|
||||
|
||||
m = xgb.DMatrix(onehot, label, enable_categorical=False)
|
||||
xgb.train(
|
||||
parameters,
|
||||
m,
|
||||
num_boost_round=rounds,
|
||||
evals=[(m, "Train")],
|
||||
evals_result=by_etl_results,
|
||||
)
|
||||
|
||||
m = xgb.DMatrix(cat, label, enable_categorical=True)
|
||||
xgb.train(
|
||||
parameters,
|
||||
m,
|
||||
num_boost_round=rounds,
|
||||
evals=[(m, "Train")],
|
||||
evals_result=by_builtin_results,
|
||||
)
|
||||
|
||||
# There are guidelines on how to specify tolerance based on considering output
|
||||
# as random variables. But in here the tree construction is extremely sensitive
|
||||
# to floating point errors. An 1e-5 error in a histogram bin can lead to an
|
||||
# entirely different tree. So even though the test is quite lenient, hypothesis
|
||||
# can still pick up falsifying examples from time to time.
|
||||
np.testing.assert_allclose(
|
||||
np.array(by_etl_results["Train"]["rmse"]),
|
||||
np.array(by_builtin_results["Train"]["rmse"]),
|
||||
rtol=1e-3,
|
||||
)
|
||||
assert tm.non_increasing(by_builtin_results["Train"]["rmse"])
|
||||
|
||||
by_grouping: Dict[str, Dict[str, List[float]]] = {}
|
||||
# switch to partition-based splits
|
||||
parameters["max_cat_to_onehot"] = self.USE_PART
|
||||
parameters["reg_lambda"] = 0
|
||||
m = xgb.DMatrix(cat, label, enable_categorical=True)
|
||||
xgb.train(
|
||||
parameters,
|
||||
m,
|
||||
num_boost_round=rounds,
|
||||
evals=[(m, "Train")],
|
||||
evals_result=by_grouping,
|
||||
)
|
||||
rmse_oh = by_builtin_results["Train"]["rmse"]
|
||||
rmse_group = by_grouping["Train"]["rmse"]
|
||||
# always better or equal to onehot when there's no regularization.
|
||||
for a, b in zip(rmse_oh, rmse_group):
|
||||
assert a >= b
|
||||
|
||||
parameters["reg_lambda"] = 1.0
|
||||
by_grouping = {}
|
||||
xgb.train(
|
||||
parameters,
|
||||
m,
|
||||
num_boost_round=32,
|
||||
evals=[(m, "Train")],
|
||||
evals_result=by_grouping,
|
||||
)
|
||||
assert tm.non_increasing(by_grouping["Train"]["rmse"]), by_grouping
|
||||
|
||||
@given(strategies.integers(10, 400), strategies.integers(3, 8),
|
||||
strategies.integers(1, 2), strategies.integers(4, 7))
|
||||
@settings(deadline=None, print_blob=True)
|
||||
@@ -397,8 +273,8 @@ class TestTreeMethod:
|
||||
def test_categorical_ohe(
|
||||
self, rows: int, cols: int, rounds: int, cats: int
|
||||
) -> None:
|
||||
self.run_categorical_ohe(rows, cols, rounds, cats, "approx")
|
||||
self.run_categorical_ohe(rows, cols, rounds, cats, "hist")
|
||||
check_categorical_ohe(rows, cols, rounds, cats, "cpu", "approx")
|
||||
check_categorical_ohe(rows, cols, rounds, cats, "cpu", "hist")
|
||||
|
||||
@given(
|
||||
tm.categorical_dataset_strategy,
|
||||
@@ -454,8 +330,8 @@ class TestTreeMethod:
|
||||
@settings(deadline=None, print_blob=True)
|
||||
@pytest.mark.skipif(**tm.no_pandas())
|
||||
def test_categorical_missing(self, rows, cols, cats):
|
||||
self.run_categorical_missing(rows, cols, cats, "approx")
|
||||
self.run_categorical_missing(rows, cols, cats, "hist")
|
||||
check_categorical_missing(rows, cols, cats, "cpu", "approx")
|
||||
check_categorical_missing(rows, cols, cats, "cpu", "hist")
|
||||
|
||||
def run_adaptive(self, tree_method, weighted) -> None:
|
||||
rng = np.random.RandomState(1994)
|
||||
|
||||
@@ -154,7 +154,6 @@ def run_gpu_hist(
|
||||
DMatrixT: Type,
|
||||
client: Client,
|
||||
) -> None:
|
||||
params["tree_method"] = "hist"
|
||||
params["device"] = "cuda"
|
||||
params = dataset.set_params(params)
|
||||
# It doesn't make sense to distribute a completely
|
||||
@@ -275,8 +274,31 @@ class TestDistributedGPU:
|
||||
dmatrix_type: type,
|
||||
local_cuda_client: Client,
|
||||
) -> None:
|
||||
params["tree_method"] = "hist"
|
||||
run_gpu_hist(params, num_rounds, dataset, dmatrix_type, local_cuda_client)
|
||||
|
||||
@given(
|
||||
params=hist_parameter_strategy,
|
||||
num_rounds=strategies.integers(1, 20),
|
||||
dataset=tm.make_dataset_strategy(),
|
||||
)
|
||||
@settings(
|
||||
deadline=duration(seconds=120),
|
||||
max_examples=20,
|
||||
suppress_health_check=suppress,
|
||||
print_blob=True,
|
||||
)
|
||||
@pytest.mark.skipif(**tm.no_cupy())
|
||||
def test_gpu_approx(
|
||||
self,
|
||||
params: Dict,
|
||||
num_rounds: int,
|
||||
dataset: tm.TestDataset,
|
||||
local_cuda_client: Client,
|
||||
) -> None:
|
||||
params["tree_method"] = "approx"
|
||||
run_gpu_hist(params, num_rounds, dataset, dxgb.DaskDMatrix, local_cuda_client)
|
||||
|
||||
def test_empty_quantile_dmatrix(self, local_cuda_client: Client) -> None:
|
||||
client = local_cuda_client
|
||||
X, y = make_categorical(client, 1, 30, 13)
|
||||
|
||||
Reference in New Issue
Block a user