Fix column split race condition. (#10572)

This commit is contained in:
Jiaming Yuan
2024-07-12 01:07:12 +08:00
committed by GitHub
parent 1ca4bfd20e
commit 6c403187ec
5 changed files with 166 additions and 132 deletions

View File

@@ -6,6 +6,7 @@
#include "../../../src/tree/common_row_partitioner.h"
#include "../collective/test_worker.h" // for TestDistributedGlobal
#include "../helpers.h"
#include "test_column_split.h" // for TestColumnSplit
#include "test_partitioner.h"
namespace xgboost::tree {
@@ -154,4 +155,26 @@ TEST(Approx, PartitionerColSplit) {
mid_partitioner);
});
}
namespace {
class TestApproxColSplit : public ::testing::TestWithParam<std::tuple<bool, float>> {
public:
void Run() {
auto [categorical, sparsity] = GetParam();
TestColumnSplit(1u, categorical, "grow_histmaker", sparsity);
}
};
} // namespace
TEST_P(TestApproxColSplit, Basic) { this->Run(); }
INSTANTIATE_TEST_SUITE_P(ColumnSplit, TestApproxColSplit, ::testing::ValuesIn([]() {
std::vector<std::tuple<bool, float>> params;
for (auto categorical : {true, false}) {
for (auto sparsity : {0.0f, 0.6f}) {
params.emplace_back(categorical, sparsity);
}
}
return params;
}()));
} // namespace xgboost::tree

View File

@@ -0,0 +1,79 @@
/**
* Copyright 2023-2024, XGBoost Contributors
*/
#pragma once
#include <xgboost/data.h> // for FeatureType, DMatrix
#include <xgboost/tree_model.h> // for RegTree
#include <xgboost/tree_updater.h> // for TreeUpdater
#include <cstddef> // for size_t
#include <memory> // for shared_ptr
#include <vector> // for vector
#include "../../../src/tree/param.h" // for TrainParam
#include "../collective/test_worker.h" // for TestDistributedGlobal
#include "../helpers.h" // for RandomDataGenerator
namespace xgboost::tree {
inline std::shared_ptr<DMatrix> GenerateCatDMatrix(std::size_t rows, std::size_t cols,
float sparsity, bool categorical) {
if (categorical) {
std::vector<FeatureType> ft(cols);
for (size_t i = 0; i < ft.size(); ++i) {
ft[i] = (i % 3 == 0) ? FeatureType::kNumerical : FeatureType::kCategorical;
}
return RandomDataGenerator(rows, cols, 0.6f).Seed(3).Type(ft).MaxCategory(17).GenerateDMatrix();
} else {
return RandomDataGenerator{rows, cols, 0.6f}.Seed(3).GenerateDMatrix();
}
}
inline void TestColumnSplit(bst_target_t n_targets, bool categorical, std::string name,
float sparsity) {
auto constexpr kRows = 32;
auto constexpr kCols = 16;
RegTree expected_tree{n_targets, static_cast<bst_feature_t>(kCols)};
ObjInfo task{ObjInfo::kRegression};
Context ctx;
{
auto p_dmat = GenerateCatDMatrix(kRows, kCols, sparsity, categorical);
auto gpair = GenerateRandomGradients(&ctx, kRows, n_targets);
std::unique_ptr<TreeUpdater> updater{TreeUpdater::Create(name, &ctx, &task)};
std::vector<HostDeviceVector<bst_node_t>> position(1);
TrainParam param;
param.Init(Args{});
updater->Configure(Args{});
updater->Update(&param, &gpair, p_dmat.get(), position, {&expected_tree});
}
auto verify = [&] {
Context ctx;
auto p_dmat = GenerateCatDMatrix(kRows, kCols, sparsity, categorical);
auto gpair = GenerateRandomGradients(&ctx, kRows, n_targets);
ObjInfo task{ObjInfo::kRegression};
std::unique_ptr<TreeUpdater> updater{TreeUpdater::Create(name, &ctx, &task)};
std::vector<HostDeviceVector<bst_node_t>> position(1);
std::unique_ptr<DMatrix> sliced{
p_dmat->SliceCol(collective::GetWorldSize(), collective::GetRank())};
RegTree tree{n_targets, static_cast<bst_feature_t>(kCols)};
TrainParam param;
param.Init(Args{});
updater->Configure(Args{});
updater->Update(&param, &gpair, sliced.get(), position, {&tree});
Json json{Object{}};
tree.SaveModel(&json);
Json expected_json{Object{}};
expected_tree.SaveModel(&expected_json);
ASSERT_EQ(json, expected_json);
};
auto constexpr kWorldSize = 2;
collective::TestDistributedGlobal(kWorldSize, [&] { verify(); });
}
} // namespace xgboost::tree

View File

@@ -1,32 +1,19 @@
/**
* Copyright 2019-2023 by XGBoost Contributors
* Copyright 2019-2024, XGBoost Contributors
*/
#include <gtest/gtest.h>
#include <xgboost/tree_model.h>
#include <xgboost/tree_updater.h>
#include "../../../src/tree/param.h" // for TrainParam
#include "../collective/test_worker.h" // for TestDistributedGlobal
#include "../../../src/tree/param.h" // for TrainParam
#include "../helpers.h"
#include "test_column_split.h" // for GenerateCatDMatrix
namespace xgboost::tree {
std::shared_ptr<DMatrix> GenerateDMatrix(std::size_t rows, std::size_t cols,
bool categorical = false) {
if (categorical) {
std::vector<FeatureType> ft(cols);
for (size_t i = 0; i < ft.size(); ++i) {
ft[i] = (i % 3 == 0) ? FeatureType::kNumerical : FeatureType::kCategorical;
}
return RandomDataGenerator(rows, cols, 0.6f).Seed(3).Type(ft).MaxCategory(17).GenerateDMatrix();
} else {
return RandomDataGenerator{rows, cols, 0.6f}.Seed(3).GenerateDMatrix();
}
}
TEST(GrowHistMaker, InteractionConstraint) {
auto constexpr kRows = 32;
auto constexpr kCols = 16;
auto p_dmat = GenerateDMatrix(kRows, kCols);
auto p_dmat = GenerateCatDMatrix(kRows, kCols, 0.0, false);
Context ctx;
linalg::Matrix<GradientPair> gpair({kRows}, ctx.Device());
@@ -69,62 +56,4 @@ TEST(GrowHistMaker, InteractionConstraint) {
ASSERT_NE(tree[tree[0].RightChild()].SplitIndex(), 0);
}
}
namespace {
void VerifyColumnSplit(int32_t rows, bst_feature_t cols, bool categorical,
RegTree const& expected_tree) {
Context ctx;
auto p_dmat = GenerateDMatrix(rows, cols, categorical);
linalg::Matrix<GradientPair> gpair({rows}, ctx.Device());
gpair.Data()->Copy(GenerateRandomGradients(rows));
ObjInfo task{ObjInfo::kRegression};
std::unique_ptr<TreeUpdater> updater{TreeUpdater::Create("grow_histmaker", &ctx, &task)};
std::vector<HostDeviceVector<bst_node_t>> position(1);
std::unique_ptr<DMatrix> sliced{
p_dmat->SliceCol(collective::GetWorldSize(), collective::GetRank())};
RegTree tree{1u, cols};
TrainParam param;
param.Init(Args{});
updater->Configure(Args{});
updater->Update(&param, &gpair, sliced.get(), position, {&tree});
Json json{Object{}};
tree.SaveModel(&json);
Json expected_json{Object{}};
expected_tree.SaveModel(&expected_json);
ASSERT_EQ(json, expected_json);
}
void TestColumnSplit(bool categorical) {
auto constexpr kRows = 32;
auto constexpr kCols = 16;
RegTree expected_tree{1u, kCols};
ObjInfo task{ObjInfo::kRegression};
{
Context ctx;
auto p_dmat = GenerateDMatrix(kRows, kCols, categorical);
linalg::Matrix<GradientPair> gpair({kRows}, ctx.Device());
gpair.Data()->Copy(GenerateRandomGradients(kRows));
std::unique_ptr<TreeUpdater> updater{TreeUpdater::Create("grow_histmaker", &ctx, &task)};
std::vector<HostDeviceVector<bst_node_t>> position(1);
TrainParam param;
param.Init(Args{});
updater->Configure(Args{});
updater->Update(&param, &gpair, p_dmat.get(), position, {&expected_tree});
}
auto constexpr kWorldSize = 2;
collective::TestDistributedGlobal(
kWorldSize, [&] { VerifyColumnSplit(kRows, kCols, categorical, expected_tree); });
}
} // anonymous namespace
TEST(GrowHistMaker, ColumnSplitNumerical) { TestColumnSplit(false); }
TEST(GrowHistMaker, ColumnSplitCategorical) { TestColumnSplit(true); }
} // namespace xgboost::tree

View File

@@ -11,9 +11,9 @@
#include "../../../src/tree/common_row_partitioner.h"
#include "../../../src/tree/hist/expand_entry.h" // for MultiExpandEntry, CPUExpandEntry
#include "../../../src/tree/param.h"
#include "../collective/test_worker.h" // for TestDistributedGlobal
#include "../helpers.h"
#include "test_column_split.h" // for TestColumnSplit
#include "test_partitioner.h"
#include "xgboost/data.h"
@@ -208,57 +208,26 @@ TEST(QuantileHist, PartitionerColSplit) { TestColumnSplitPartitioner<CPUExpandEn
TEST(QuantileHist, MultiPartitionerColSplit) { TestColumnSplitPartitioner<MultiExpandEntry>(3); }
namespace {
void VerifyColumnSplit(Context const* ctx, bst_idx_t rows, bst_feature_t cols, bst_target_t n_targets,
RegTree const& expected_tree) {
auto Xy = RandomDataGenerator{rows, cols, 0}.GenerateDMatrix(true);
linalg::Matrix<GradientPair> gpair = GenerateRandomGradients(ctx, rows, n_targets);
ObjInfo task{ObjInfo::kRegression};
std::unique_ptr<TreeUpdater> updater{TreeUpdater::Create("grow_quantile_histmaker", ctx, &task)};
std::vector<HostDeviceVector<bst_node_t>> position(1);
std::unique_ptr<DMatrix> sliced{Xy->SliceCol(collective::GetWorldSize(), collective::GetRank())};
RegTree tree{n_targets, cols};
TrainParam param;
param.Init(Args{});
updater->Configure(Args{});
updater->Update(&param, &gpair, sliced.get(), position, {&tree});
Json json{Object{}};
tree.SaveModel(&json);
Json expected_json{Object{}};
expected_tree.SaveModel(&expected_json);
ASSERT_EQ(json, expected_json);
}
void TestColumnSplit(bst_target_t n_targets) {
auto constexpr kRows = 32;
auto constexpr kCols = 16;
RegTree expected_tree{n_targets, kCols};
ObjInfo task{ObjInfo::kRegression};
Context ctx;
{
auto Xy = RandomDataGenerator{kRows, kCols, 0}.GenerateDMatrix(true);
auto gpair = GenerateRandomGradients(&ctx, kRows, n_targets);
std::unique_ptr<TreeUpdater> updater{
TreeUpdater::Create("grow_quantile_histmaker", &ctx, &task)};
std::vector<HostDeviceVector<bst_node_t>> position(1);
TrainParam param;
param.Init(Args{});
updater->Configure(Args{});
updater->Update(&param, &gpair, Xy.get(), position, {&expected_tree});
class TestHistColSplit : public ::testing::TestWithParam<std::tuple<bst_target_t, bool, float>> {
public:
void Run() {
auto [n_targets, categorical, sparsity] = GetParam();
TestColumnSplit(n_targets, categorical, "grow_quantile_histmaker", sparsity);
}
auto constexpr kWorldSize = 2;
collective::TestDistributedGlobal(kWorldSize, [&] {
VerifyColumnSplit(&ctx, kRows, kCols, n_targets, std::cref(expected_tree));
});
}
};
} // anonymous namespace
TEST(QuantileHist, ColumnSplit) { TestColumnSplit(1); }
TEST_P(TestHistColSplit, Basic) { this->Run(); }
TEST(QuantileHist, ColumnSplitMultiTarget) { TestColumnSplit(3); }
INSTANTIATE_TEST_SUITE_P(ColumnSplit, TestHistColSplit, ::testing::ValuesIn([]() {
std::vector<std::tuple<bst_target_t, bool, float>> params;
for (auto categorical : {true, false}) {
for (auto sparsity : {0.0f, 0.6f}) {
for (bst_target_t n_targets : {1u, 3u}) {
params.emplace_back(n_targets, categorical, sparsity);
}
}
}
return params;
}()));
} // namespace xgboost::tree