Patch to improve multithreaded performance scaling (#2493)

* Patch to improve multithreaded performance scaling

Change parallel strategy for histogram construction.
Instead of partitioning data rows among multiple threads, partition feature
columns instead. Useful heuristics for assigning partitions have been adopted
from LightGBM project.

* Add missing header to satisfy MSVC

* Restore max_bin and related parameters to TrainParam

* Fix lint error

* inline functions do not require static keyword

* Feature grouping algorithm accepting FastHistParam

Feature grouping algorithm accepts many parameters (3+), and it gets annoying to
pass them one by one. Instead, simply pass the reference to FastHistParam. The
definition of FastHistParam has been moved to a separate header file to
accomodate this change.
This commit is contained in:
Philip Cho 2017-07-07 08:25:07 -07:00 committed by Tianqi Chen
parent 6bfc472bec
commit ba820847f9
6 changed files with 466 additions and 52 deletions

View File

@ -29,6 +29,9 @@ switch (dtype) { \
#include <limits>
#include <vector>
#include "hist_util.h"
#include "../tree/fast_hist_param.h"
using xgboost::tree::FastHistParam;
namespace xgboost {
namespace common {
@ -68,8 +71,9 @@ class ColumnMatrix {
}
// construct column matrix from GHistIndexMatrix
inline void Init(const GHistIndexMatrix& gmat, DataType dtype) {
this->dtype = dtype;
inline void Init(const GHistIndexMatrix& gmat,
const FastHistParam& param) {
this->dtype = static_cast<DataType>(param.colmat_dtype);
/* if dtype is smaller than uint32_t, multiple bin_id's will be stored in each
slot of internal buffer. */
packing_factor_ = sizeof(uint32_t) / static_cast<size_t>(this->dtype);
@ -93,7 +97,8 @@ class ColumnMatrix {
gmat.GetFeatureCounts(&feature_counts_[0]);
// classify features
for (uint32_t fid = 0; fid < nfeature; ++fid) {
if (static_cast<double>(feature_counts_[fid]) < 0.5*nrow) {
if (static_cast<double>(feature_counts_[fid])
< param.sparse_threshold * nrow) {
type_[fid] = kSparseColumn;
} else {
type_[fid] = kDenseColumn;

View File

@ -5,10 +5,12 @@
* \author Philip Cho, Tianqi Chen
*/
#include <dmlc/omp.h>
#include <numeric>
#include <vector>
#include "./sync.h"
#include "./hist_util.h"
#include "./random.h"
#include "./column_matrix.h"
#include "./hist_util.h"
#include "./quantile.h"
namespace xgboost {
@ -154,6 +156,246 @@ void GHistIndexMatrix::Init(DMatrix* p_fmat) {
}
}
template <typename T>
static unsigned GetConflictCount(const std::vector<bool>& mark,
const Column<T>& column,
unsigned max_cnt) {
unsigned ret = 0;
if (column.type == xgboost::common::kDenseColumn) {
for (size_t i = 0; i < column.len; ++i) {
if (column.index[i] != std::numeric_limits<T>::max() && mark[i]) {
++ret;
if (ret > max_cnt) {
return max_cnt + 1;
}
}
}
} else {
for (size_t i = 0; i < column.len; ++i) {
if (mark[column.row_ind[i]]) {
++ret;
if (ret > max_cnt) {
return max_cnt + 1;
}
}
}
}
return ret;
}
template <typename T>
inline void
MarkUsed(std::vector<bool>* p_mark, const Column<T>& column) {
std::vector<bool>& mark = *p_mark;
if (column.type == xgboost::common::kDenseColumn) {
for (size_t i = 0; i < column.len; ++i) {
if (column.index[i] != std::numeric_limits<T>::max()) {
mark[i] = true;
}
}
} else {
for (size_t i = 0; i < column.len; ++i) {
mark[column.row_ind[i]] = true;
}
}
}
template <typename T>
inline std::vector<std::vector<unsigned>>
FindGroups_(const std::vector<unsigned>& feature_list,
const std::vector<bst_uint>& feature_nnz,
const ColumnMatrix& colmat,
unsigned nrow,
const FastHistParam& param) {
/* Goal: Bundle features together that has little or no "overlap", i.e.
only a few data points should have nonzero values for
member features.
Note that one-hot encoded features will be grouped together. */
std::vector<std::vector<unsigned>> groups;
std::vector<std::vector<bool>> conflict_marks;
std::vector<unsigned> group_nnz;
std::vector<unsigned> group_conflict_cnt;
const unsigned max_conflict_cnt
= static_cast<unsigned>(param.max_conflict_rate * nrow);
for (auto fid : feature_list) {
const Column<T>& column = colmat.GetColumn<T>(fid);
const size_t cur_fid_nnz = feature_nnz[fid];
bool need_new_group = true;
// randomly choose some of existing groups as candidates
std::vector<unsigned> search_groups;
for (size_t gid = 0; gid < groups.size(); ++gid) {
if (group_nnz[gid] + cur_fid_nnz <= nrow + max_conflict_cnt) {
search_groups.push_back(gid);
}
}
std::shuffle(search_groups.begin(), search_groups.end(), common::GlobalRandom());
if (param.max_search_group > 0 && search_groups.size() > param.max_search_group) {
search_groups.resize(param.max_search_group);
}
// examine each candidate group: is it okay to insert fid?
for (auto gid : search_groups) {
const unsigned rest_max_cnt = max_conflict_cnt - group_conflict_cnt[gid];
const unsigned cnt = GetConflictCount(conflict_marks[gid], column, rest_max_cnt);
if (cnt <= rest_max_cnt) {
need_new_group = false;
groups[gid].push_back(fid);
group_conflict_cnt[gid] += cnt;
group_nnz[gid] += cur_fid_nnz - cnt;
MarkUsed(&conflict_marks[gid], column);
break;
}
}
// create new group if necessary
if (need_new_group) {
groups.emplace_back();
groups.back().push_back(fid);
group_conflict_cnt.push_back(0);
conflict_marks.emplace_back(nrow, false);
MarkUsed(&conflict_marks.back(), column);
group_nnz.emplace_back(cur_fid_nnz);
}
}
return groups;
}
inline std::vector<std::vector<unsigned>>
FindGroups(const std::vector<unsigned>& feature_list,
const std::vector<bst_uint>& feature_nnz,
const ColumnMatrix& colmat,
unsigned nrow,
const FastHistParam& param) {
XGBOOST_TYPE_SWITCH(colmat.dtype, {
return FindGroups_<DType>(feature_list, feature_nnz, colmat, nrow, param);
});
return std::vector<std::vector<unsigned>>(); // to avoid warning message
}
inline std::vector<std::vector<unsigned>>
FastFeatureGrouping(const GHistIndexMatrix& gmat,
const ColumnMatrix& colmat,
const FastHistParam& param) {
const size_t nrow = gmat.row_ptr.size() - 1;
const size_t nfeature = gmat.cut->row_ptr.size() - 1;
std::vector<unsigned> feature_list(nfeature);
std::iota(feature_list.begin(), feature_list.end(), 0);
// sort features by nonzero counts, descending order
std::vector<bst_uint> feature_nnz(nfeature);
std::vector<unsigned> features_by_nnz(feature_list);
gmat.GetFeatureCounts(&feature_nnz[0]);
std::sort(features_by_nnz.begin(), features_by_nnz.end(),
[&feature_nnz](int a, int b) {
return feature_nnz[a] > feature_nnz[b];
});
auto groups_alt1 = FindGroups(feature_list, feature_nnz, colmat, nrow, param);
auto groups_alt2 = FindGroups(features_by_nnz, feature_nnz, colmat, nrow, param);
auto& groups = (groups_alt1.size() > groups_alt2.size()) ? groups_alt2 : groups_alt1;
// take apart small, sparse groups, as it won't help speed
{
std::vector<std::vector<unsigned>> ret;
for (const auto& group : groups) {
if (group.size() <= 1 || group.size() >= 5) {
ret.push_back(group); // keep singleton groups and large (5+) groups
} else {
unsigned nnz = 0;
for (auto fid : group) {
nnz += feature_nnz[fid];
}
double nnz_rate = static_cast<double>(nnz) / nrow;
// take apart small sparse group, due it will not gain on speed
if (nnz_rate <= param.sparse_threshold) {
for (auto fid : group) {
ret.emplace_back();
ret.back().push_back(fid);
}
} else {
ret.push_back(group);
}
}
}
groups = std::move(ret);
}
// shuffle groups
std::shuffle(groups.begin(), groups.end(), common::GlobalRandom());
return groups;
}
void GHistIndexBlockMatrix::Init(const GHistIndexMatrix& gmat,
const ColumnMatrix& colmat,
const FastHistParam& param) {
cut = gmat.cut;
const size_t nrow = gmat.row_ptr.size() - 1;
const size_t nbins = gmat.cut->row_ptr.back();
/* step 1: form feature groups */
auto groups = FastFeatureGrouping(gmat, colmat, param);
const size_t nblock = groups.size();
/* step 2: build a new CSR matrix for each feature group */
std::vector<unsigned> bin2block(nbins); // lookup table [bin id] => [block id]
for (size_t group_id = 0; group_id < nblock; ++group_id) {
for (auto& fid : groups[group_id]) {
const unsigned bin_begin = gmat.cut->row_ptr[fid];
const unsigned bin_end = gmat.cut->row_ptr[fid + 1];
for (unsigned bin_id = bin_begin; bin_id < bin_end; ++bin_id) {
bin2block[bin_id] = group_id;
}
}
}
std::vector<std::vector<unsigned>> index_temp(nblock);
std::vector<std::vector<unsigned>> row_ptr_temp(nblock);
for (size_t block_id = 0; block_id < nblock; ++block_id) {
row_ptr_temp[block_id].push_back(0);
}
for (size_t rid = 0; rid < nrow; ++rid) {
const size_t ibegin = static_cast<size_t>(gmat.row_ptr[rid]);
const size_t iend = static_cast<size_t>(gmat.row_ptr[rid + 1]);
for (size_t j = ibegin; j < iend; ++j) {
const size_t bin_id = gmat.index[j];
const size_t block_id = bin2block[bin_id];
index_temp[block_id].push_back(bin_id);
}
for (size_t block_id = 0; block_id < nblock; ++block_id) {
row_ptr_temp[block_id].push_back(index_temp[block_id].size());
}
}
/* step 3: concatenate CSR matrices into one (index, row_ptr) pair */
std::vector<size_t> index_blk_ptr;
std::vector<size_t> row_ptr_blk_ptr;
index_blk_ptr.push_back(0);
row_ptr_blk_ptr.push_back(0);
for (size_t block_id = 0; block_id < nblock; ++block_id) {
index.insert(index.end(), index_temp[block_id].begin(), index_temp[block_id].end());
row_ptr.insert(row_ptr.end(), row_ptr_temp[block_id].begin(), row_ptr_temp[block_id].end());
index_blk_ptr.push_back(index.size());
row_ptr_blk_ptr.push_back(row_ptr.size());
}
// save shortcut for each block
for (size_t block_id = 0; block_id < nblock; ++block_id) {
Block blk;
blk.index_begin = &index[index_blk_ptr[block_id]];
blk.row_ptr_begin = &row_ptr[row_ptr_blk_ptr[block_id]];
blk.index_end = &index[index_blk_ptr[block_id + 1]];
blk.row_ptr_end = &row_ptr[row_ptr_blk_ptr[block_id + 1]];
blocks.push_back(blk);
}
}
void GHistBuilder::BuildHist(const std::vector<bst_gpair>& gpair,
const RowSetCollection::Elem row_indices,
const GHistIndexMatrix& gmat,
@ -161,33 +403,12 @@ void GHistBuilder::BuildHist(const std::vector<bst_gpair>& gpair,
GHistRow hist) {
data_.resize(nbins_ * nthread_, GHistEntry());
std::fill(data_.begin(), data_.end(), GHistEntry());
stat_buf_.resize(row_indices.size());
const int K = 8; // loop unrolling factor
const bst_omp_uint nthread = static_cast<bst_omp_uint>(this->nthread_);
const bst_omp_uint nrows = row_indices.end - row_indices.begin;
const bst_omp_uint rest = nrows % K;
#pragma omp parallel for num_threads(nthread) schedule(static)
for (bst_omp_uint i = 0; i < nrows - rest; i += K) {
bst_uint rid[K];
bst_gpair stat[K];
for (int k = 0; k < K; ++k) {
rid[k] = row_indices.begin[i + k];
}
for (int k = 0; k < K; ++k) {
stat[k] = gpair[rid[k]];
}
for (int k = 0; k < K; ++k) {
stat_buf_[i + k] = stat[k];
}
}
for (bst_omp_uint i = nrows - rest; i < nrows; ++i) {
const bst_uint rid = row_indices.begin[i];
const bst_gpair stat = gpair[rid];
stat_buf_[i] = stat;
}
#pragma omp parallel for num_threads(nthread) schedule(guided)
for (bst_omp_uint i = 0; i < nrows - rest; i += K) {
const bst_omp_uint tid = omp_get_thread_num();
@ -204,7 +425,7 @@ void GHistBuilder::BuildHist(const std::vector<bst_gpair>& gpair,
iend[k] = static_cast<size_t>(gmat.row_ptr[rid[k] + 1]);
}
for (int k = 0; k < K; ++k) {
stat[k] = stat_buf_[i + k];
stat[k] = gpair[rid[k]];
}
for (int k = 0; k < K; ++k) {
for (size_t j = ibegin[k]; j < iend[k]; ++j) {
@ -217,7 +438,7 @@ void GHistBuilder::BuildHist(const std::vector<bst_gpair>& gpair,
const bst_uint rid = row_indices.begin[i];
const size_t ibegin = static_cast<size_t>(gmat.row_ptr[rid]);
const size_t iend = static_cast<size_t>(gmat.row_ptr[rid + 1]);
const bst_gpair stat = stat_buf_[i];
const bst_gpair stat = gpair[rid];
for (size_t j = ibegin; j < iend; ++j) {
const size_t bin = gmat.index[j];
data_[bin].Add(stat);
@ -234,10 +455,60 @@ void GHistBuilder::BuildHist(const std::vector<bst_gpair>& gpair,
}
}
void GHistBuilder::BuildBlockHist(const std::vector<bst_gpair>& gpair,
const RowSetCollection::Elem row_indices,
const GHistIndexBlockMatrix& gmatb,
const std::vector<bst_uint>& feat_set,
GHistRow hist) {
const int K = 8; // loop unrolling factor
const bst_omp_uint nthread = static_cast<bst_omp_uint>(this->nthread_);
const bst_omp_uint nblock = gmatb.GetNumBlock();
const bst_omp_uint nrows = row_indices.end - row_indices.begin;
const bst_omp_uint rest = nrows % K;
#pragma omp parallel for num_threads(nthread) schedule(guided)
for (bst_omp_uint bid = 0; bid < nblock; ++bid) {
auto gmat = gmatb[bid];
for (bst_omp_uint i = 0; i < nrows - rest; i += K) {
bst_uint rid[K];
size_t ibegin[K];
size_t iend[K];
bst_gpair stat[K];
for (int k = 0; k < K; ++k) {
rid[k] = row_indices.begin[i + k];
}
for (int k = 0; k < K; ++k) {
ibegin[k] = static_cast<size_t>(gmat.row_ptr[rid[k]]);
iend[k] = static_cast<size_t>(gmat.row_ptr[rid[k] + 1]);
}
for (int k = 0; k < K; ++k) {
stat[k] = gpair[rid[k]];
}
for (int k = 0; k < K; ++k) {
for (size_t j = ibegin[k]; j < iend[k]; ++j) {
const size_t bin = gmat.index[j];
hist.begin[bin].Add(stat[k]);
}
}
}
for (bst_omp_uint i = nrows - rest; i < nrows; ++i) {
const bst_uint rid = row_indices.begin[i];
const size_t ibegin = static_cast<size_t>(gmat.row_ptr[rid]);
const size_t iend = static_cast<size_t>(gmat.row_ptr[rid + 1]);
const bst_gpair stat = gpair[rid];
for (size_t j = ibegin; j < iend; ++j) {
const size_t bin = gmat.index[j];
hist.begin[bin].Add(stat);
}
}
}
}
void GHistBuilder::SubtractionTrick(GHistRow self, GHistRow sibling, GHistRow parent) {
const bst_omp_uint nthread = static_cast<bst_omp_uint>(this->nthread_);
const bst_omp_uint nbins = static_cast<bst_omp_uint>(nbins_);
const int K = 8;
const int K = 8; // loop unrolling factor
const bst_omp_uint rest = nbins % K;
#pragma omp parallel for num_threads(nthread) schedule(static)
for (bst_omp_uint bin_id = 0; bin_id < nbins - rest; bin_id += K) {

View File

@ -11,6 +11,9 @@
#include <limits>
#include <vector>
#include "row_set.h"
#include "../tree/fast_hist_param.h"
using xgboost::tree::FastHistParam;
namespace xgboost {
namespace common {
@ -24,6 +27,10 @@ struct GHistEntry {
GHistEntry() : sum_grad(0), sum_hess(0) {}
inline void Clear() {
sum_grad = sum_hess = 0;
}
/*! \brief add a bst_gpair to the sum */
inline void Add(const bst_gpair& e) {
sum_grad += e.grad;
@ -125,6 +132,48 @@ struct GHistIndexMatrix {
std::vector<unsigned> hit_count_tloc_;
};
struct GHistIndexBlock {
const unsigned* row_ptr;
const unsigned* index;
inline GHistIndexBlock(const unsigned* row_ptr, const unsigned* index)
: row_ptr(row_ptr), index(index) {}
// get i-th row
inline GHistIndexRow operator[](bst_uint i) const {
return GHistIndexRow(&index[0] + row_ptr[i], row_ptr[i + 1] - row_ptr[i]);
}
};
class ColumnMatrix;
class GHistIndexBlockMatrix {
public:
void Init(const GHistIndexMatrix& gmat,
const ColumnMatrix& colmat,
const FastHistParam& param);
inline GHistIndexBlock operator[](bst_uint i) const {
return GHistIndexBlock(blocks[i].row_ptr_begin, blocks[i].index_begin);
}
inline unsigned GetNumBlock() const {
return blocks.size();
}
private:
std::vector<unsigned> row_ptr;
std::vector<unsigned> index;
const HistCutMatrix* cut;
struct Block {
const unsigned* row_ptr_begin;
const unsigned* row_ptr_end;
const unsigned* index_begin;
const unsigned* index_end;
};
std::vector<Block> blocks;
};
/*!
* \brief histogram of graident statistics for a single node.
* Consists of multiple GHistEntry's, each entry showing total graident statistics
@ -206,6 +255,12 @@ class GHistBuilder {
const GHistIndexMatrix& gmat,
const std::vector<bst_uint>& feat_set,
GHistRow hist);
// same, with feature grouping
void BuildBlockHist(const std::vector<bst_gpair>& gpair,
const RowSetCollection::Elem row_indices,
const GHistIndexBlockMatrix& gmatb,
const std::vector<bst_uint>& feat_set,
GHistRow hist);
// construct a histogram via subtraction trick
void SubtractionTrick(GHistRow self, GHistRow sibling, GHistRow parent);
@ -215,7 +270,6 @@ class GHistBuilder {
/*! \brief number of all bins over all features */
size_t nbins_;
std::vector<GHistEntry> data_;
std::vector<bst_gpair> stat_buf_;
};

View File

@ -0,0 +1,64 @@
/*!
* Copyright 2017 by Contributors
* \file updater_fast_hist.h
* \brief parameters for histogram-based training
* \author Philip Cho, Tianqi Chen
*/
#ifndef XGBOOST_TREE_FAST_HIST_PARAM_H_
#define XGBOOST_TREE_FAST_HIST_PARAM_H_
namespace xgboost {
namespace tree {
/*! \brief training parameters for histogram-based training */
struct FastHistParam : public dmlc::Parameter<FastHistParam> {
// integral data type to be used with columnar data storage
enum class DataType { uint8 = 1, uint16 = 2, uint32 = 4 };
int colmat_dtype;
// percentage threshold for treating a feature as sparse
// e.g. 0.2 indicates a feature with fewer than 20% nonzeros is considered sparse
double sparse_threshold;
// use feature grouping? (default yes)
int enable_feature_grouping;
// when grouping features, how many "conflicts" to allow.
// conflict is when an instance has nonzero values for two or more features
// default is 0, meaning features should be strictly complementary
double max_conflict_rate;
// when grouping features, how much effort to expend to prevent singleton groups
// we'll try to insert each feature into existing groups before creating a new group
// for that feature; to save time, only up to (max_search_group) of existing groups
// will be considered. If set to zero, ALL existing groups will be examined
unsigned max_search_group;
// declare the parameters
DMLC_DECLARE_PARAMETER(FastHistParam) {
DMLC_DECLARE_FIELD(colmat_dtype)
.set_default(static_cast<int>(DataType::uint32))
.add_enum("uint8", static_cast<int>(DataType::uint8))
.add_enum("uint16", static_cast<int>(DataType::uint16))
.add_enum("uint32", static_cast<int>(DataType::uint32))
.describe("Integral data type to be used with columnar data storage."
"May carry marginal performance implications. Reserved for "
"advanced use");
DMLC_DECLARE_FIELD(sparse_threshold).set_range(0, 1.0).set_default(0.2)
.describe("percentage threshold for treating a feature as sparse");
DMLC_DECLARE_FIELD(enable_feature_grouping).set_lower_bound(0).set_default(1)
.describe("if >0, enable feature grouping to ameliorate work imbalance "
"among worker threads");
DMLC_DECLARE_FIELD(max_conflict_rate).set_range(0, 1.0).set_default(0)
.describe("when grouping features, how many \"conflicts\" to allow."
"conflict is when an instance has nonzero values for two or more features."
"default is 0, meaning features should be strictly complementary.");
DMLC_DECLARE_FIELD(max_search_group).set_lower_bound(0).set_default(100)
.describe("when grouping features, how much effort to expend to prevent "
"singleton groups. We'll try to insert each feature into existing "
"groups before creating a new group for that feature; to save time, "
"only up to (max_search_group) of existing groups will be "
"considered. If set to zero, ALL existing groups will be examined.");
}
};
} // namespace tree
} // namespace xgboost
#endif // XGBOOST_TREE_FAST_HIST_PARAM_H_

View File

@ -30,8 +30,6 @@ struct TrainParam : public dmlc::Parameter<TrainParam> {
int max_leaves;
// if using histogram based algorithm, maximum number of bins per feature
int max_bin;
enum class DataType { uint8 = 1, uint16 = 2, uint32 = 4 };
int colmat_dtype;
// growing policy
enum TreeGrowPolicy { kDepthWise = 0, kLossGuide = 1 };
int grow_policy;
@ -111,14 +109,6 @@ struct TrainParam : public dmlc::Parameter<TrainParam> {
"Tree growing policy. 0: favor splitting at nodes closest to the node, "
"i.e. grow depth-wise. 1: favor splitting at nodes with highest loss "
"change. (cf. LightGBM)");
DMLC_DECLARE_FIELD(colmat_dtype)
.set_default(static_cast<int>(DataType::uint32))
.add_enum("uint8", static_cast<int>(DataType::uint8))
.add_enum("uint16", static_cast<int>(DataType::uint16))
.add_enum("uint32", static_cast<int>(DataType::uint32))
.describe("Integral data type to be used with columnar data storage."
"May carry marginal performance implications. Reserved for "
"advanced use");
DMLC_DECLARE_FIELD(min_child_weight)
.set_lower_bound(0.0f)
.set_default(1.0f)

View File

@ -13,6 +13,7 @@
#include <iomanip>
#include <numeric>
#include "./param.h"
#include "./fast_hist_param.h"
#include "../common/random.h"
#include "../common/bitmap.h"
#include "../common/sync.h"
@ -25,6 +26,7 @@ namespace tree {
using xgboost::common::HistCutMatrix;
using xgboost::common::GHistIndexMatrix;
using xgboost::common::GHistIndexBlockMatrix;
using xgboost::common::GHistIndexRow;
using xgboost::common::GHistEntry;
using xgboost::common::HistCollection;
@ -36,6 +38,8 @@ using xgboost::common::Column;
DMLC_REGISTRY_FILE_TAG(updater_fast_hist);
DMLC_REGISTER_PARAMETER(FastHistParam);
/*! \brief construct a tree using quantized feature values */
template<typename TStats, typename TConstraint>
class FastHistMaker: public TreeUpdater {
@ -47,6 +51,7 @@ class FastHistMaker: public TreeUpdater {
}
pruner_->Init(args);
param.InitAllowUnknown(args);
fhparam.InitAllowUnknown(args);
is_gmat_initialized_ = false;
}
@ -59,7 +64,10 @@ class FastHistMaker: public TreeUpdater {
hmat_.Init(dmat, param.max_bin);
gmat_.cut = &hmat_;
gmat_.Init(dmat);
column_matrix_.Init(gmat_, static_cast<xgboost::common::DataType>(param.colmat_dtype));
column_matrix_.Init(gmat_, fhparam);
if (fhparam.enable_feature_grouping > 0) {
gmatb_.Init(gmat_, column_matrix_, fhparam);
}
is_gmat_initialized_ = true;
if (param.debug_verbose > 0) {
LOG(INFO) << "Generating gmat: " << dmlc::GetTime() - tstart << " sec";
@ -71,10 +79,10 @@ class FastHistMaker: public TreeUpdater {
TConstraint::Init(&param, dmat->info().num_col);
// build tree
if (!builder_) {
builder_.reset(new Builder(param, std::move(pruner_)));
builder_.reset(new Builder(param, fhparam, std::move(pruner_)));
}
for (size_t i = 0; i < trees.size(); ++i) {
builder_->Update(gmat_, column_matrix_, gpair, dmat, trees[i]);
builder_->Update(gmat_, gmatb_, column_matrix_, gpair, dmat, trees[i]);
}
param.learning_rate = lr;
}
@ -91,9 +99,13 @@ class FastHistMaker: public TreeUpdater {
protected:
// training parameter
TrainParam param;
FastHistParam fhparam;
// data sketch
HistCutMatrix hmat_;
// quantized data matrix
GHistIndexMatrix gmat_;
// (optional) data matrix with feature grouping
GHistIndexBlockMatrix gmatb_;
// column accessor
ColumnMatrix column_matrix_;
bool is_gmat_initialized_;
@ -136,11 +148,13 @@ class FastHistMaker: public TreeUpdater {
public:
// constructor
explicit Builder(const TrainParam& param,
const FastHistParam& fhparam,
std::unique_ptr<TreeUpdater> pruner)
: param(param), pruner_(std::move(pruner)),
: param(param), fhparam(fhparam), pruner_(std::move(pruner)),
p_last_tree_(nullptr), p_last_fmat_(nullptr) {}
// update one tree, growing
virtual void Update(const GHistIndexMatrix& gmat,
const GHistIndexBlockMatrix& gmatb,
const ColumnMatrix& column_matrix,
const std::vector<bst_gpair>& gpair,
DMatrix* p_fmat,
@ -168,7 +182,7 @@ class FastHistMaker: public TreeUpdater {
for (int nid = 0; nid < p_tree->param.num_roots; ++nid) {
tstart = dmlc::GetTime();
hist_.AddHistRow(nid);
builder_.BuildHist(gpair, row_set_collection_[nid], gmat, feat_set, hist_[nid]);
BuildHist(gpair, row_set_collection_[nid], gmat, gmatb, feat_set, hist_[nid]);
time_build_hist += dmlc::GetTime() - tstart;
tstart = dmlc::GetTime();
@ -203,13 +217,11 @@ class FastHistMaker: public TreeUpdater {
hist_.AddHistRow(cleft);
hist_.AddHistRow(cright);
if (row_set_collection_[cleft].size() < row_set_collection_[cright].size()) {
builder_.BuildHist(gpair, row_set_collection_[cleft], gmat, feat_set,
hist_[cleft]);
builder_.SubtractionTrick(hist_[cright], hist_[cleft], hist_[nid]);
BuildHist(gpair, row_set_collection_[cleft], gmat, gmatb, feat_set, hist_[cleft]);
SubtractionTrick(hist_[cright], hist_[cleft], hist_[nid]);
} else {
builder_.BuildHist(gpair, row_set_collection_[cright], gmat, feat_set,
hist_[cright]);
builder_.SubtractionTrick(hist_[cleft], hist_[cright], hist_[nid]);
BuildHist(gpair, row_set_collection_[cright], gmat, gmatb, feat_set, hist_[cright]);
SubtractionTrick(hist_[cleft], hist_[cright], hist_[nid]);
}
time_build_hist += dmlc::GetTime() - tstart;
@ -280,6 +292,23 @@ class FastHistMaker: public TreeUpdater {
}
}
inline void BuildHist(const std::vector<bst_gpair>& gpair,
const RowSetCollection::Elem row_indices,
const GHistIndexMatrix& gmat,
const GHistIndexBlockMatrix& gmatb,
const std::vector<bst_uint>& feat_set,
GHistRow hist) {
if (fhparam.enable_feature_grouping > 0) {
hist_builder_.BuildBlockHist(gpair, row_indices, gmatb, feat_set, hist);
} else {
hist_builder_.BuildHist(gpair, row_indices, gmat, feat_set, hist);
}
}
inline void SubtractionTrick(GHistRow self, GHistRow sibling, GHistRow parent) {
hist_builder_.SubtractionTrick(self, sibling, parent);
}
inline bool UpdatePredictionCache(const DMatrix* data,
std::vector<bst_float>* p_out_preds) {
std::vector<bst_float>& out_preds = *p_out_preds;
@ -351,7 +380,7 @@ class FastHistMaker: public TreeUpdater {
{
this->nthread = omp_get_num_threads();
}
builder_.Init(this->nthread, nbins);
hist_builder_.Init(this->nthread, nbins);
CHECK_EQ(info.root_index.size(), 0U);
std::vector<bst_uint>& row_indices = row_set_collection_.row_indices_;
@ -885,6 +914,7 @@ class FastHistMaker: public TreeUpdater {
// --data fields--
const TrainParam& param;
const FastHistParam& fhparam;
// number of omp thread used during training
int nthread;
// Per feature: shuffle index of each feature index
@ -904,7 +934,7 @@ class FastHistMaker: public TreeUpdater {
/*! \brief local prediction cache; maps node id to leaf value */
std::vector<float> leaf_value_cache_;
GHistBuilder builder_;
GHistBuilder hist_builder_;
std::unique_ptr<TreeUpdater> pruner_;
// back pointers to tree and data matrix