Extract histogram builder from CPU Hist. (#7152)
* Extract the CPU histogram builder. * Fix tests. * Reduce number of histograms being built.
This commit is contained in:
@@ -151,188 +151,6 @@ class QuantileHistMock : public QuantileHistMaker {
|
||||
omp_set_num_threads(nthreads);
|
||||
}
|
||||
|
||||
void TestAddHistRows(const GHistIndexMatrix& gmat,
|
||||
std::vector<GradientPair>* gpair,
|
||||
DMatrix* p_fmat,
|
||||
RegTree* tree) {
|
||||
RealImpl::InitData(gmat, *p_fmat, *tree, gpair);
|
||||
|
||||
int starting_index = std::numeric_limits<int>::max();
|
||||
int sync_count = 0;
|
||||
this->nodes_for_explicit_hist_build_.clear();
|
||||
this->nodes_for_subtraction_trick_.clear();
|
||||
|
||||
tree->ExpandNode(0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0);
|
||||
tree->ExpandNode((*tree)[0].LeftChild(), 0, 0, false, 0, 0, 0, 0, 0, 0, 0);
|
||||
tree->ExpandNode((*tree)[0].RightChild(), 0, 0, false, 0, 0, 0, 0, 0, 0, 0);
|
||||
this->nodes_for_explicit_hist_build_.emplace_back(3, tree->GetDepth(3), 0.0f);
|
||||
this->nodes_for_explicit_hist_build_.emplace_back(4, tree->GetDepth(4), 0.0f);
|
||||
this->nodes_for_subtraction_trick_.emplace_back(5, tree->GetDepth(5), 0.0f);
|
||||
this->nodes_for_subtraction_trick_.emplace_back(6, tree->GetDepth(6), 0.0f);
|
||||
|
||||
this->hist_rows_adder_->AddHistRows(this, &starting_index, &sync_count, tree);
|
||||
ASSERT_EQ(sync_count, 2);
|
||||
ASSERT_EQ(starting_index, 3);
|
||||
|
||||
for (const CPUExpandEntry& node : this->nodes_for_explicit_hist_build_) {
|
||||
ASSERT_EQ(this->hist_.RowExists(node.nid), true);
|
||||
}
|
||||
for (const CPUExpandEntry& node : this->nodes_for_subtraction_trick_) {
|
||||
ASSERT_EQ(this->hist_.RowExists(node.nid), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TestSyncHistograms(const GHistIndexMatrix& gmat,
|
||||
std::vector<GradientPair>* gpair,
|
||||
DMatrix* p_fmat,
|
||||
RegTree* tree) {
|
||||
// init
|
||||
RealImpl::InitData(gmat, *p_fmat, *tree, gpair);
|
||||
|
||||
int starting_index = std::numeric_limits<int>::max();
|
||||
int sync_count = 0;
|
||||
this->nodes_for_explicit_hist_build_.clear();
|
||||
this->nodes_for_subtraction_trick_.clear();
|
||||
// level 0
|
||||
this->nodes_for_explicit_hist_build_.emplace_back(0, tree->GetDepth(0), 0.0f);
|
||||
this->hist_rows_adder_->AddHistRows(this, &starting_index, &sync_count, tree);
|
||||
tree->ExpandNode(0, 0, 0, false, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
this->nodes_for_explicit_hist_build_.clear();
|
||||
this->nodes_for_subtraction_trick_.clear();
|
||||
// level 1
|
||||
this->nodes_for_explicit_hist_build_.emplace_back((*tree)[0].LeftChild(),
|
||||
tree->GetDepth(1), 0.0f);
|
||||
this->nodes_for_subtraction_trick_.emplace_back((*tree)[0].RightChild(),
|
||||
tree->GetDepth(2), 0.0f);
|
||||
this->hist_rows_adder_->AddHistRows(this, &starting_index, &sync_count, tree);
|
||||
tree->ExpandNode((*tree)[0].LeftChild(), 0, 0, false, 0, 0, 0, 0, 0, 0, 0);
|
||||
tree->ExpandNode((*tree)[0].RightChild(), 0, 0, false, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
this->nodes_for_explicit_hist_build_.clear();
|
||||
this->nodes_for_subtraction_trick_.clear();
|
||||
// level 2
|
||||
this->nodes_for_explicit_hist_build_.emplace_back(3, tree->GetDepth(3), 0.0f);
|
||||
this->nodes_for_subtraction_trick_.emplace_back(4, tree->GetDepth(4), 0.0f);
|
||||
this->nodes_for_explicit_hist_build_.emplace_back(5, tree->GetDepth(5), 0.0f);
|
||||
this->nodes_for_subtraction_trick_.emplace_back(6, tree->GetDepth(6), 0.0f);
|
||||
this->hist_rows_adder_->AddHistRows(this, &starting_index, &sync_count, tree);
|
||||
|
||||
const size_t n_nodes = this->nodes_for_explicit_hist_build_.size();
|
||||
ASSERT_EQ(n_nodes, 2ul);
|
||||
this->row_set_collection_.AddSplit(0, (*tree)[0].LeftChild(),
|
||||
(*tree)[0].RightChild(), 4, 4);
|
||||
this->row_set_collection_.AddSplit(1, (*tree)[1].LeftChild(),
|
||||
(*tree)[1].RightChild(), 2, 2);
|
||||
this->row_set_collection_.AddSplit(2, (*tree)[2].LeftChild(),
|
||||
(*tree)[2].RightChild(), 2, 2);
|
||||
|
||||
common::BlockedSpace2d space(n_nodes, [&](size_t node) {
|
||||
const int32_t nid = this->nodes_for_explicit_hist_build_[node].nid;
|
||||
return this->row_set_collection_[nid].Size();
|
||||
}, 256);
|
||||
|
||||
std::vector<GHistRowT> target_hists(n_nodes);
|
||||
for (size_t i = 0; i < this->nodes_for_explicit_hist_build_.size(); ++i) {
|
||||
const int32_t nid = this->nodes_for_explicit_hist_build_[i].nid;
|
||||
target_hists[i] = this->hist_[nid];
|
||||
}
|
||||
|
||||
const size_t nbins = this->hist_builder_.GetNumBins();
|
||||
// set values to specific nodes hist
|
||||
std::vector<size_t> n_ids = {1, 2};
|
||||
for (size_t i : n_ids) {
|
||||
auto this_hist = this->hist_[i];
|
||||
GradientSumT* p_hist = reinterpret_cast<GradientSumT*>(this_hist.data());
|
||||
for (size_t bin_id = 0; bin_id < 2*nbins; ++bin_id) {
|
||||
p_hist[bin_id] = 2*bin_id;
|
||||
}
|
||||
}
|
||||
n_ids[0] = 3;
|
||||
n_ids[1] = 5;
|
||||
for (size_t i : n_ids) {
|
||||
auto this_hist = this->hist_[i];
|
||||
GradientSumT* p_hist = reinterpret_cast<GradientSumT*>(this_hist.data());
|
||||
for (size_t bin_id = 0; bin_id < 2*nbins; ++bin_id) {
|
||||
p_hist[bin_id] = bin_id;
|
||||
}
|
||||
}
|
||||
|
||||
this->hist_buffer_.Reset(1, n_nodes, space, target_hists);
|
||||
// sync hist
|
||||
this->hist_synchronizer_->SyncHistograms(this, starting_index, sync_count, tree);
|
||||
|
||||
auto check_hist = [] (const GHistRowT parent, const GHistRowT left,
|
||||
const GHistRowT right, size_t begin, size_t end) {
|
||||
const GradientSumT* p_parent = reinterpret_cast<const GradientSumT*>(parent.data());
|
||||
const GradientSumT* p_left = reinterpret_cast<const GradientSumT*>(left.data());
|
||||
const GradientSumT* p_right = reinterpret_cast<const GradientSumT*>(right.data());
|
||||
for (size_t i = 2 * begin; i < 2 * end; ++i) {
|
||||
ASSERT_EQ(p_parent[i], p_left[i] + p_right[i]);
|
||||
}
|
||||
};
|
||||
size_t node_id = 0;
|
||||
for (const CPUExpandEntry& node : this->nodes_for_explicit_hist_build_) {
|
||||
auto this_hist = this->hist_[node.nid];
|
||||
const size_t parent_id = (*tree)[node.nid].Parent();
|
||||
const size_t subtraction_node_id = this->nodes_for_subtraction_trick_[node_id].nid;
|
||||
auto parent_hist = this->hist_[parent_id];
|
||||
auto sibling_hist = this->hist_[subtraction_node_id];
|
||||
|
||||
check_hist(parent_hist, this_hist, sibling_hist, 0, nbins);
|
||||
++node_id;
|
||||
}
|
||||
node_id = 0;
|
||||
for (const CPUExpandEntry& node : this->nodes_for_subtraction_trick_) {
|
||||
auto this_hist = this->hist_[node.nid];
|
||||
const size_t parent_id = (*tree)[node.nid].Parent();
|
||||
const size_t subtraction_node_id = this->nodes_for_explicit_hist_build_[node_id].nid;
|
||||
auto parent_hist = this->hist_[parent_id];
|
||||
auto sibling_hist = this->hist_[subtraction_node_id];
|
||||
|
||||
check_hist(parent_hist, this_hist, sibling_hist, 0, nbins);
|
||||
++node_id;
|
||||
}
|
||||
}
|
||||
|
||||
void TestBuildHist(int nid,
|
||||
const GHistIndexMatrix& gmat,
|
||||
const DMatrix& fmat,
|
||||
const RegTree& tree) {
|
||||
std::vector<GradientPair> gpair =
|
||||
{ {0.23f, 0.24f}, {0.24f, 0.25f}, {0.26f, 0.27f}, {0.27f, 0.28f},
|
||||
{0.27f, 0.29f}, {0.37f, 0.39f}, {0.47f, 0.49f}, {0.57f, 0.59f} };
|
||||
RealImpl::InitData(gmat, fmat, tree, &gpair);
|
||||
this->hist_.AddHistRow(nid);
|
||||
this->hist_.AllocateAllData();
|
||||
this->hist_builder_.template BuildHist<true>(gpair, this->row_set_collection_[nid],
|
||||
gmat, this->hist_[nid]);
|
||||
|
||||
// Check if number of histogram bins is correct
|
||||
ASSERT_EQ(this->hist_[nid].size(), gmat.cut.Ptrs().back());
|
||||
std::vector<GradientPairPrecise> histogram_expected(this->hist_[nid].size());
|
||||
|
||||
// Compute the correct histogram (histogram_expected)
|
||||
const size_t num_row = fmat.Info().num_row_;
|
||||
CHECK_EQ(gpair.size(), num_row);
|
||||
for (size_t rid = 0; rid < num_row; ++rid) {
|
||||
const size_t ibegin = gmat.row_ptr[rid];
|
||||
const size_t iend = gmat.row_ptr[rid + 1];
|
||||
for (size_t i = ibegin; i < iend; ++i) {
|
||||
const size_t bin_id = gmat.index[i];
|
||||
histogram_expected[bin_id] += GradientPairPrecise(gpair[rid]);
|
||||
}
|
||||
}
|
||||
|
||||
// Now validate the computed histogram returned by BuildHist
|
||||
for (size_t i = 0; i < this->hist_[nid].size(); ++i) {
|
||||
GradientPairPrecise sol = histogram_expected[i];
|
||||
ASSERT_NEAR(sol.GetGrad(), this->hist_[nid][i].GetGrad(), kEps);
|
||||
ASSERT_NEAR(sol.GetHess(), this->hist_[nid][i].GetHess(), kEps);
|
||||
}
|
||||
}
|
||||
|
||||
void TestApplySplit(const RegTree& tree) {
|
||||
std::vector<GradientPair> row_gpairs =
|
||||
{ {1.23f, 0.24f}, {0.24f, 0.25f}, {0.26f, 0.27f}, {2.27f, 0.28f},
|
||||
@@ -350,9 +168,6 @@ class QuantileHistMock : public QuantileHistMaker {
|
||||
// treat everything as dense, as this is what we intend to test here
|
||||
cm.Init(gmat, 0.0);
|
||||
RealImpl::InitData(gmat, *dmat, tree, &row_gpairs);
|
||||
this->hist_.AddHistRow(0);
|
||||
this->hist_.AllocateAllData();
|
||||
|
||||
const size_t num_row = dmat->Info().num_row_;
|
||||
// split by feature 0
|
||||
const size_t bin_id_min = gmat.cut.Ptrs()[0];
|
||||
@@ -424,26 +239,12 @@ class QuantileHistMock : public QuantileHistMaker {
|
||||
param_,
|
||||
std::move(pruner_),
|
||||
dmat_.get()));
|
||||
if (batch) {
|
||||
float_builder_->SetHistSynchronizer(new BatchHistSynchronizer<float>());
|
||||
float_builder_->SetHistRowsAdder(new BatchHistRowsAdder<float>());
|
||||
} else {
|
||||
float_builder_->SetHistSynchronizer(new DistributedHistSynchronizer<float>());
|
||||
float_builder_->SetHistRowsAdder(new DistributedHistRowsAdder<float>());
|
||||
}
|
||||
} else {
|
||||
double_builder_.reset(
|
||||
new BuilderMock<double>(
|
||||
param_,
|
||||
std::move(pruner_),
|
||||
dmat_.get()));
|
||||
if (batch) {
|
||||
double_builder_->SetHistSynchronizer(new BatchHistSynchronizer<double>());
|
||||
double_builder_->SetHistRowsAdder(new BatchHistRowsAdder<double>());
|
||||
} else {
|
||||
double_builder_->SetHistSynchronizer(new DistributedHistSynchronizer<double>());
|
||||
double_builder_->SetHistRowsAdder(new DistributedHistRowsAdder<double>());
|
||||
}
|
||||
}
|
||||
}
|
||||
~QuantileHistMock() override = default;
|
||||
@@ -484,52 +285,6 @@ class QuantileHistMock : public QuantileHistMaker {
|
||||
}
|
||||
}
|
||||
|
||||
void TestAddHistRows() {
|
||||
size_t constexpr kMaxBins = 4;
|
||||
GHistIndexMatrix gmat(dmat_.get(), kMaxBins);
|
||||
|
||||
RegTree tree = RegTree();
|
||||
tree.param.UpdateAllowUnknown(cfg_);
|
||||
std::vector<GradientPair> gpair =
|
||||
{ {0.23f, 0.24f}, {0.23f, 0.24f}, {0.23f, 0.24f}, {0.23f, 0.24f},
|
||||
{0.27f, 0.29f}, {0.27f, 0.29f}, {0.27f, 0.29f}, {0.27f, 0.29f} };
|
||||
if (double_builder_) {
|
||||
double_builder_->TestAddHistRows(gmat, &gpair, dmat_.get(), &tree);
|
||||
} else {
|
||||
float_builder_->TestAddHistRows(gmat, &gpair, dmat_.get(), &tree);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSyncHistograms() {
|
||||
size_t constexpr kMaxBins = 4;
|
||||
GHistIndexMatrix gmat(dmat_.get(), kMaxBins);
|
||||
|
||||
RegTree tree = RegTree();
|
||||
tree.param.UpdateAllowUnknown(cfg_);
|
||||
std::vector<GradientPair> gpair =
|
||||
{ {0.23f, 0.24f}, {0.23f, 0.24f}, {0.23f, 0.24f}, {0.23f, 0.24f},
|
||||
{0.27f, 0.29f}, {0.27f, 0.29f}, {0.27f, 0.29f}, {0.27f, 0.29f} };
|
||||
if (double_builder_) {
|
||||
double_builder_->TestSyncHistograms(gmat, &gpair, dmat_.get(), &tree);
|
||||
} else {
|
||||
float_builder_->TestSyncHistograms(gmat, &gpair, dmat_.get(), &tree);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TestBuildHist() {
|
||||
RegTree tree = RegTree();
|
||||
tree.param.UpdateAllowUnknown(cfg_);
|
||||
|
||||
size_t constexpr kMaxBins = 4;
|
||||
GHistIndexMatrix gmat(dmat_.get(), kMaxBins);
|
||||
if (double_builder_) {
|
||||
double_builder_->TestBuildHist(0, gmat, *dmat_, tree);
|
||||
} else {
|
||||
float_builder_->TestBuildHist(0, gmat, *dmat_, tree);
|
||||
}
|
||||
}
|
||||
|
||||
void TestApplySplit() {
|
||||
RegTree tree = RegTree();
|
||||
tree.param.UpdateAllowUnknown(cfg_);
|
||||
@@ -563,57 +318,6 @@ TEST(QuantileHist, InitDataSampling) {
|
||||
maker_float.TestInitDataSampling();
|
||||
}
|
||||
|
||||
TEST(QuantileHist, AddHistRows) {
|
||||
std::vector<std::pair<std::string, std::string>> cfg
|
||||
{{"num_feature", std::to_string(QuantileHistMock::GetNumColumns())}};
|
||||
QuantileHistMock maker(cfg);
|
||||
maker.TestAddHistRows();
|
||||
const bool single_precision_histogram = true;
|
||||
QuantileHistMock maker_float(cfg, single_precision_histogram);
|
||||
maker_float.TestAddHistRows();
|
||||
}
|
||||
|
||||
TEST(QuantileHist, SyncHistograms) {
|
||||
std::vector<std::pair<std::string, std::string>> cfg
|
||||
{{"num_feature", std::to_string(QuantileHistMock::GetNumColumns())}};
|
||||
QuantileHistMock maker(cfg);
|
||||
maker.TestSyncHistograms();
|
||||
const bool single_precision_histogram = true;
|
||||
QuantileHistMock maker_float(cfg, single_precision_histogram);
|
||||
maker_float.TestSyncHistograms();
|
||||
}
|
||||
|
||||
TEST(QuantileHist, DistributedAddHistRows) {
|
||||
std::vector<std::pair<std::string, std::string>> cfg
|
||||
{{"num_feature", std::to_string(QuantileHistMock::GetNumColumns())}};
|
||||
QuantileHistMock maker(cfg, false);
|
||||
maker.TestAddHistRows();
|
||||
const bool single_precision_histogram = true;
|
||||
QuantileHistMock maker_float(cfg, single_precision_histogram);
|
||||
maker_float.TestAddHistRows();
|
||||
}
|
||||
|
||||
TEST(QuantileHist, DistributedSyncHistograms) {
|
||||
std::vector<std::pair<std::string, std::string>> cfg
|
||||
{{"num_feature", std::to_string(QuantileHistMock::GetNumColumns())}};
|
||||
QuantileHistMock maker(cfg, false);
|
||||
maker.TestSyncHistograms();
|
||||
const bool single_precision_histogram = true;
|
||||
QuantileHistMock maker_float(cfg, single_precision_histogram);
|
||||
maker_float.TestSyncHistograms();
|
||||
}
|
||||
|
||||
TEST(QuantileHist, BuildHist) {
|
||||
// Don't enable feature grouping
|
||||
std::vector<std::pair<std::string, std::string>> cfg
|
||||
{{"num_feature", std::to_string(QuantileHistMock::GetNumColumns())}};
|
||||
QuantileHistMock maker(cfg);
|
||||
maker.TestBuildHist();
|
||||
const bool single_precision_histogram = true;
|
||||
QuantileHistMock maker_float(cfg, single_precision_histogram);
|
||||
maker_float.TestBuildHist();
|
||||
}
|
||||
|
||||
TEST(QuantileHist, ApplySplit) {
|
||||
std::vector<std::pair<std::string, std::string>> cfg
|
||||
{{"num_feature", std::to_string(QuantileHistMock::GetNumColumns())},
|
||||
|
||||
Reference in New Issue
Block a user