initial merge
This commit is contained in:
@@ -1,22 +1,26 @@
|
||||
/*!
|
||||
* Copyright 2021-2022 XGBoost contributors
|
||||
/**
|
||||
* Copyright 2021-2023 XGBoost contributors
|
||||
* \file common_row_partitioner.h
|
||||
* \brief Common partitioner logic for hist and approx methods.
|
||||
*/
|
||||
#ifndef XGBOOST_TREE_COMMON_ROW_PARTITIONER_H_
|
||||
#define XGBOOST_TREE_COMMON_ROW_PARTITIONER_H_
|
||||
|
||||
#include <algorithm> // std::all_of
|
||||
#include <cinttypes> // std::uint32_t
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <vector>
|
||||
|
||||
#include "../collective/communicator-inl.h"
|
||||
#include "../common/linalg_op.h" // cbegin
|
||||
#include "../common/numeric.h" // Iota
|
||||
#include "../common/partition_builder.h"
|
||||
#include "hist/expand_entry.h" // CPUExpandEntry
|
||||
#include "xgboost/base.h"
|
||||
#include "xgboost/context.h" // Context
|
||||
#include "xgboost/linalg.h" // TensorView
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
namespace xgboost::tree {
|
||||
|
||||
static constexpr size_t kPartitionBlockSize = 2048;
|
||||
|
||||
@@ -34,9 +38,10 @@ class ColumnSplitHelper {
|
||||
missing_bits_ = BitVector(common::Span<BitVector::value_type>(missing_storage_));
|
||||
}
|
||||
|
||||
template <typename ExpandEntry>
|
||||
void Partition(common::BlockedSpace2d const& space, std::int32_t n_threads,
|
||||
GHistIndexMatrix const& gmat, common::ColumnMatrix const& column_matrix,
|
||||
std::vector<CPUExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
std::vector<ExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
// When data is split by column, we don't have all the feature values in the local worker, so
|
||||
// we first collect all the decisions and whether the feature is missing into bit vectors.
|
||||
std::fill(decision_storage_.begin(), decision_storage_.end(), 0);
|
||||
@@ -97,41 +102,47 @@ class CommonRowPartitioner {
|
||||
}
|
||||
}
|
||||
|
||||
void FindSplitConditions(const std::vector<CPUExpandEntry>& nodes, const RegTree& tree,
|
||||
template <typename ExpandEntry>
|
||||
void FindSplitConditions(const std::vector<ExpandEntry>& nodes, const RegTree& tree,
|
||||
const GHistIndexMatrix& gmat, std::vector<int32_t>* split_conditions) {
|
||||
for (size_t i = 0; i < nodes.size(); ++i) {
|
||||
const int32_t nid = nodes[i].nid;
|
||||
const bst_uint fid = tree[nid].SplitIndex();
|
||||
const bst_float split_pt = tree[nid].SplitCond();
|
||||
const uint32_t lower_bound = gmat.cut.Ptrs()[fid];
|
||||
const uint32_t upper_bound = gmat.cut.Ptrs()[fid + 1];
|
||||
auto const& ptrs = gmat.cut.Ptrs();
|
||||
auto const& vals = gmat.cut.Values();
|
||||
|
||||
for (std::size_t i = 0; i < nodes.size(); ++i) {
|
||||
bst_node_t const nidx = nodes[i].nid;
|
||||
bst_feature_t const fidx = tree.SplitIndex(nidx);
|
||||
float const split_pt = tree.SplitCond(nidx);
|
||||
std::uint32_t const lower_bound = ptrs[fidx];
|
||||
std::uint32_t const upper_bound = ptrs[fidx + 1];
|
||||
bst_bin_t split_cond = -1;
|
||||
// convert floating-point split_pt into corresponding bin_id
|
||||
// split_cond = -1 indicates that split_pt is less than all known cut points
|
||||
CHECK_LT(upper_bound, static_cast<uint32_t>(std::numeric_limits<int32_t>::max()));
|
||||
for (auto bound = lower_bound; bound < upper_bound; ++bound) {
|
||||
if (split_pt == gmat.cut.Values()[bound]) {
|
||||
split_cond = static_cast<int32_t>(bound);
|
||||
if (split_pt == vals[bound]) {
|
||||
split_cond = static_cast<bst_bin_t>(bound);
|
||||
}
|
||||
}
|
||||
(*split_conditions).at(i) = split_cond;
|
||||
(*split_conditions)[i] = split_cond;
|
||||
}
|
||||
}
|
||||
|
||||
void AddSplitsToRowSet(const std::vector<CPUExpandEntry>& nodes, RegTree const* p_tree) {
|
||||
template <typename ExpandEntry>
|
||||
void AddSplitsToRowSet(const std::vector<ExpandEntry>& nodes, RegTree const* p_tree) {
|
||||
const size_t n_nodes = nodes.size();
|
||||
for (unsigned int i = 0; i < n_nodes; ++i) {
|
||||
const int32_t nid = nodes[i].nid;
|
||||
const int32_t nidx = nodes[i].nid;
|
||||
const size_t n_left = partition_builder_.GetNLeftElems(i);
|
||||
const size_t n_right = partition_builder_.GetNRightElems(i);
|
||||
CHECK_EQ((*p_tree)[nid].LeftChild() + 1, (*p_tree)[nid].RightChild());
|
||||
row_set_collection_.AddSplit(nid, (*p_tree)[nid].LeftChild(), (*p_tree)[nid].RightChild(),
|
||||
n_left, n_right);
|
||||
CHECK_EQ(p_tree->LeftChild(nidx) + 1, p_tree->RightChild(nidx));
|
||||
row_set_collection_.AddSplit(nidx, p_tree->LeftChild(nidx), p_tree->RightChild(nidx), n_left,
|
||||
n_right);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ExpandEntry>
|
||||
void UpdatePosition(Context const* ctx, GHistIndexMatrix const& gmat,
|
||||
std::vector<CPUExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
std::vector<ExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
auto const& column_matrix = gmat.Transpose();
|
||||
if (column_matrix.IsInitialized()) {
|
||||
if (gmat.cut.HasCategorical()) {
|
||||
@@ -149,10 +160,10 @@ class CommonRowPartitioner {
|
||||
}
|
||||
}
|
||||
|
||||
template <bool any_cat>
|
||||
template <bool any_cat, typename ExpandEntry>
|
||||
void UpdatePosition(Context const* ctx, GHistIndexMatrix const& gmat,
|
||||
const common::ColumnMatrix& column_matrix,
|
||||
std::vector<CPUExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
std::vector<ExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
if (column_matrix.AnyMissing()) {
|
||||
this->template UpdatePosition<true, any_cat>(ctx, gmat, column_matrix, nodes, p_tree);
|
||||
} else {
|
||||
@@ -160,33 +171,21 @@ class CommonRowPartitioner {
|
||||
}
|
||||
}
|
||||
|
||||
template <bool any_missing, bool any_cat>
|
||||
template <bool any_missing, bool any_cat, typename ExpandEntry>
|
||||
void UpdatePosition(Context const* ctx, GHistIndexMatrix const& gmat,
|
||||
const common::ColumnMatrix& column_matrix,
|
||||
std::vector<CPUExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
switch (column_matrix.GetTypeSize()) {
|
||||
case common::kUint8BinsTypeSize:
|
||||
this->template UpdatePosition<uint8_t, any_missing, any_cat>(ctx, gmat, column_matrix,
|
||||
nodes, p_tree);
|
||||
break;
|
||||
case common::kUint16BinsTypeSize:
|
||||
this->template UpdatePosition<uint16_t, any_missing, any_cat>(ctx, gmat, column_matrix,
|
||||
nodes, p_tree);
|
||||
break;
|
||||
case common::kUint32BinsTypeSize:
|
||||
this->template UpdatePosition<uint32_t, any_missing, any_cat>(ctx, gmat, column_matrix,
|
||||
nodes, p_tree);
|
||||
break;
|
||||
default:
|
||||
// no default behavior
|
||||
CHECK(false) << column_matrix.GetTypeSize();
|
||||
}
|
||||
std::vector<ExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
common::DispatchBinType(column_matrix.GetTypeSize(), [&](auto t) {
|
||||
using T = decltype(t);
|
||||
this->template UpdatePosition<T, any_missing, any_cat>(ctx, gmat, column_matrix, nodes,
|
||||
p_tree);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename BinIdxType, bool any_missing, bool any_cat>
|
||||
template <typename BinIdxType, bool any_missing, bool any_cat, typename ExpandEntry>
|
||||
void UpdatePosition(Context const* ctx, GHistIndexMatrix const& gmat,
|
||||
const common::ColumnMatrix& column_matrix,
|
||||
std::vector<CPUExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
std::vector<ExpandEntry> const& nodes, RegTree const* p_tree) {
|
||||
// 1. Find split condition for each split
|
||||
size_t n_nodes = nodes.size();
|
||||
|
||||
@@ -248,9 +247,9 @@ class CommonRowPartitioner {
|
||||
AddSplitsToRowSet(nodes, p_tree);
|
||||
}
|
||||
|
||||
auto const& Partitions() const { return row_set_collection_; }
|
||||
[[nodiscard]] auto const& Partitions() const { return row_set_collection_; }
|
||||
|
||||
size_t Size() const {
|
||||
[[nodiscard]] std::size_t Size() const {
|
||||
return std::distance(row_set_collection_.begin(), row_set_collection_.end());
|
||||
}
|
||||
|
||||
@@ -263,12 +262,29 @@ class CommonRowPartitioner {
|
||||
[&](size_t idx) -> bool { return hess[idx] - .0f == .0f; });
|
||||
}
|
||||
|
||||
void LeafPartition(Context const* ctx, RegTree const& tree,
|
||||
linalg::TensorView<GradientPair const, 2> gpair,
|
||||
std::vector<bst_node_t>* p_out_position) const {
|
||||
if (gpair.Shape(1) > 1) {
|
||||
partition_builder_.LeafPartition(
|
||||
ctx, tree, this->Partitions(), p_out_position, [&](std::size_t idx) -> bool {
|
||||
auto sample = gpair.Slice(idx, linalg::All());
|
||||
return std::all_of(linalg::cbegin(sample), linalg::cend(sample),
|
||||
[](GradientPair const& g) { return g.GetHess() - .0f == .0f; });
|
||||
});
|
||||
} else {
|
||||
auto s = gpair.Slice(linalg::All(), 0);
|
||||
partition_builder_.LeafPartition(
|
||||
ctx, tree, this->Partitions(), p_out_position,
|
||||
[&](std::size_t idx) -> bool { return s(idx).GetHess() - .0f == .0f; });
|
||||
}
|
||||
}
|
||||
void LeafPartition(Context const* ctx, RegTree const& tree,
|
||||
common::Span<GradientPair const> gpair,
|
||||
std::vector<bst_node_t>* p_out_position) const {
|
||||
partition_builder_.LeafPartition(
|
||||
ctx, tree, this->Partitions(), p_out_position,
|
||||
[&](size_t idx) -> bool { return gpair[idx].GetHess() - .0f == .0f; });
|
||||
[&](std::size_t idx) -> bool { return gpair[idx].GetHess() - .0f == .0f; });
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -278,6 +294,5 @@ class CommonRowPartitioner {
|
||||
ColumnSplitHelper column_split_helper_;
|
||||
};
|
||||
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
} // namespace xgboost::tree
|
||||
#endif // XGBOOST_TREE_COMMON_ROW_PARTITIONER_H_
|
||||
|
||||
@@ -1,111 +1,111 @@
|
||||
/*!
|
||||
* Copyright 2021 by XGBoost Contributors
|
||||
*/
|
||||
#ifndef XGBOOST_TREE_DRIVER_H_
|
||||
#define XGBOOST_TREE_DRIVER_H_
|
||||
#include <xgboost/span.h>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include "./param.h"
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
|
||||
template <typename ExpandEntryT>
|
||||
inline bool DepthWise(const ExpandEntryT& lhs, const ExpandEntryT& rhs) {
|
||||
return lhs.GetNodeId() > rhs.GetNodeId(); // favor small depth
|
||||
}
|
||||
|
||||
template <typename ExpandEntryT>
|
||||
inline bool LossGuide(const ExpandEntryT& lhs, const ExpandEntryT& rhs) {
|
||||
if (lhs.GetLossChange() == rhs.GetLossChange()) {
|
||||
return lhs.GetNodeId() > rhs.GetNodeId(); // favor small timestamp
|
||||
} else {
|
||||
return lhs.GetLossChange() < rhs.GetLossChange(); // favor large loss_chg
|
||||
}
|
||||
}
|
||||
|
||||
// Drives execution of tree building on device
|
||||
template <typename ExpandEntryT>
|
||||
class Driver {
|
||||
using ExpandQueue =
|
||||
std::priority_queue<ExpandEntryT, std::vector<ExpandEntryT>,
|
||||
std::function<bool(ExpandEntryT, ExpandEntryT)>>;
|
||||
|
||||
public:
|
||||
explicit Driver(TrainParam param, std::size_t max_node_batch_size = 256)
|
||||
: param_(param),
|
||||
max_node_batch_size_(max_node_batch_size),
|
||||
queue_(param.grow_policy == TrainParam::kDepthWise ? DepthWise<ExpandEntryT>
|
||||
: LossGuide<ExpandEntryT>) {}
|
||||
template <typename EntryIterT>
|
||||
void Push(EntryIterT begin, EntryIterT end) {
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
const ExpandEntryT& e = *it;
|
||||
if (e.split.loss_chg > kRtEps) {
|
||||
queue_.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
void Push(const std::vector<ExpandEntryT> &entries) {
|
||||
this->Push(entries.begin(), entries.end());
|
||||
}
|
||||
void Push(ExpandEntryT const& e) { queue_.push(e); }
|
||||
|
||||
bool IsEmpty() {
|
||||
return queue_.empty();
|
||||
}
|
||||
|
||||
// Can a child of this entry still be expanded?
|
||||
// can be used to avoid extra work
|
||||
bool IsChildValid(ExpandEntryT const& parent_entry) {
|
||||
if (param_.max_depth > 0 && parent_entry.depth + 1 >= param_.max_depth) return false;
|
||||
if (param_.max_leaves > 0 && num_leaves_ >= param_.max_leaves) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the set of nodes to be expanded
|
||||
// This set has no dependencies between entries so they may be expanded in
|
||||
// parallel or asynchronously
|
||||
std::vector<ExpandEntryT> Pop() {
|
||||
if (queue_.empty()) return {};
|
||||
// Return a single entry for loss guided mode
|
||||
if (param_.grow_policy == TrainParam::kLossGuide) {
|
||||
ExpandEntryT e = queue_.top();
|
||||
queue_.pop();
|
||||
|
||||
if (e.IsValid(param_, num_leaves_)) {
|
||||
num_leaves_++;
|
||||
return {e};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// Return nodes on same level for depth wise
|
||||
std::vector<ExpandEntryT> result;
|
||||
ExpandEntryT e = queue_.top();
|
||||
int level = e.depth;
|
||||
while (e.depth == level && !queue_.empty() && result.size() < max_node_batch_size_) {
|
||||
queue_.pop();
|
||||
if (e.IsValid(param_, num_leaves_)) {
|
||||
num_leaves_++;
|
||||
result.emplace_back(e);
|
||||
}
|
||||
|
||||
if (!queue_.empty()) {
|
||||
e = queue_.top();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
TrainParam param_;
|
||||
bst_node_t num_leaves_ = 1;
|
||||
std::size_t max_node_batch_size_;
|
||||
ExpandQueue queue_;
|
||||
};
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
|
||||
#endif // XGBOOST_TREE_DRIVER_H_
|
||||
/*!
|
||||
* Copyright 2021 by XGBoost Contributors
|
||||
*/
|
||||
#ifndef XGBOOST_TREE_DRIVER_H_
|
||||
#define XGBOOST_TREE_DRIVER_H_
|
||||
#include <xgboost/span.h>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include "./param.h"
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
|
||||
template <typename ExpandEntryT>
|
||||
inline bool DepthWise(const ExpandEntryT& lhs, const ExpandEntryT& rhs) {
|
||||
return lhs.GetNodeId() > rhs.GetNodeId(); // favor small depth
|
||||
}
|
||||
|
||||
template <typename ExpandEntryT>
|
||||
inline bool LossGuide(const ExpandEntryT& lhs, const ExpandEntryT& rhs) {
|
||||
if (lhs.GetLossChange() == rhs.GetLossChange()) {
|
||||
return lhs.GetNodeId() > rhs.GetNodeId(); // favor small timestamp
|
||||
} else {
|
||||
return lhs.GetLossChange() < rhs.GetLossChange(); // favor large loss_chg
|
||||
}
|
||||
}
|
||||
|
||||
// Drives execution of tree building on device
|
||||
template <typename ExpandEntryT>
|
||||
class Driver {
|
||||
using ExpandQueue =
|
||||
std::priority_queue<ExpandEntryT, std::vector<ExpandEntryT>,
|
||||
std::function<bool(ExpandEntryT, ExpandEntryT)>>;
|
||||
|
||||
public:
|
||||
explicit Driver(TrainParam param, std::size_t max_node_batch_size = 256)
|
||||
: param_(param),
|
||||
max_node_batch_size_(max_node_batch_size),
|
||||
queue_(param.grow_policy == TrainParam::kDepthWise ? DepthWise<ExpandEntryT>
|
||||
: LossGuide<ExpandEntryT>) {}
|
||||
template <typename EntryIterT>
|
||||
void Push(EntryIterT begin, EntryIterT end) {
|
||||
for (auto it = begin; it != end; ++it) {
|
||||
const ExpandEntryT& e = *it;
|
||||
if (e.split.loss_chg > kRtEps) {
|
||||
queue_.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
void Push(const std::vector<ExpandEntryT> &entries) {
|
||||
this->Push(entries.begin(), entries.end());
|
||||
}
|
||||
void Push(ExpandEntryT const& e) { queue_.push(e); }
|
||||
|
||||
bool IsEmpty() {
|
||||
return queue_.empty();
|
||||
}
|
||||
|
||||
// Can a child of this entry still be expanded?
|
||||
// can be used to avoid extra work
|
||||
bool IsChildValid(ExpandEntryT const& parent_entry) {
|
||||
if (param_.max_depth > 0 && parent_entry.depth + 1 >= param_.max_depth) return false;
|
||||
if (param_.max_leaves > 0 && num_leaves_ >= param_.max_leaves) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the set of nodes to be expanded
|
||||
// This set has no dependencies between entries so they may be expanded in
|
||||
// parallel or asynchronously
|
||||
std::vector<ExpandEntryT> Pop() {
|
||||
if (queue_.empty()) return {};
|
||||
// Return a single entry for loss guided mode
|
||||
if (param_.grow_policy == TrainParam::kLossGuide) {
|
||||
ExpandEntryT e = queue_.top();
|
||||
queue_.pop();
|
||||
|
||||
if (e.IsValid(param_, num_leaves_)) {
|
||||
num_leaves_++;
|
||||
return {e};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// Return nodes on same level for depth wise
|
||||
std::vector<ExpandEntryT> result;
|
||||
ExpandEntryT e = queue_.top();
|
||||
int level = e.depth;
|
||||
while (e.depth == level && !queue_.empty() && result.size() < max_node_batch_size_) {
|
||||
queue_.pop();
|
||||
if (e.IsValid(param_, num_leaves_)) {
|
||||
num_leaves_++;
|
||||
result.emplace_back(e);
|
||||
}
|
||||
|
||||
if (!queue_.empty()) {
|
||||
e = queue_.top();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
TrainParam param_;
|
||||
bst_node_t num_leaves_ = 1;
|
||||
std::size_t max_node_batch_size_;
|
||||
ExpandQueue queue_;
|
||||
};
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
|
||||
#endif // XGBOOST_TREE_DRIVER_H_
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
namespace cpu_impl {
|
||||
void FitStump(Context const* ctx, linalg::TensorView<GradientPair const, 2> gpair,
|
||||
void FitStump(Context const* ctx, MetaInfo const& info,
|
||||
linalg::TensorView<GradientPair const, 2> gpair,
|
||||
linalg::VectorView<float> out) {
|
||||
auto n_targets = out.Size();
|
||||
CHECK_EQ(n_targets, gpair.Shape(1));
|
||||
@@ -43,8 +44,12 @@ void FitStump(Context const* ctx, linalg::TensorView<GradientPair const, 2> gpai
|
||||
}
|
||||
}
|
||||
CHECK(h_sum.CContiguous());
|
||||
collective::Allreduce<collective::Operation::kSum>(
|
||||
reinterpret_cast<double*>(h_sum.Values().data()), h_sum.Size() * 2);
|
||||
|
||||
// In vertical federated learning, only worker 0 needs to call this, no need to do an allreduce.
|
||||
if (!collective::IsFederated() || info.data_split_mode != DataSplitMode::kCol) {
|
||||
collective::Allreduce<collective::Operation::kSum>(
|
||||
reinterpret_cast<double*>(h_sum.Values().data()), h_sum.Size() * 2);
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < h_sum.Size(); ++i) {
|
||||
out(i) = static_cast<float>(CalcUnregularizedWeight(h_sum(i).GetGrad(), h_sum(i).GetHess()));
|
||||
@@ -64,7 +69,7 @@ inline void FitStump(Context const*, linalg::TensorView<GradientPair const, 2>,
|
||||
#endif // !defined(XGBOOST_USE_CUDA) && !defined(XGBOOST_USE_HIP)
|
||||
} // namespace cuda_impl
|
||||
|
||||
void FitStump(Context const* ctx, HostDeviceVector<GradientPair> const& gpair,
|
||||
void FitStump(Context const* ctx, MetaInfo const& info, HostDeviceVector<GradientPair> const& gpair,
|
||||
bst_target_t n_targets, linalg::Vector<float>* out) {
|
||||
out->SetDevice(ctx->gpu_id);
|
||||
out->Reshape(n_targets);
|
||||
@@ -72,7 +77,7 @@ void FitStump(Context const* ctx, HostDeviceVector<GradientPair> const& gpair,
|
||||
|
||||
gpair.SetDevice(ctx->gpu_id);
|
||||
auto gpair_t = linalg::MakeTensorView(ctx, &gpair, n_samples, n_targets);
|
||||
ctx->IsCPU() ? cpu_impl::FitStump(ctx, gpair_t, out->HostView())
|
||||
ctx->IsCPU() ? cpu_impl::FitStump(ctx, info, gpair_t, out->HostView())
|
||||
: cuda_impl::FitStump(ctx, gpair_t, out->View(ctx->gpu_id));
|
||||
}
|
||||
} // namespace tree
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "../common/common.h" // AssertGPUSupport
|
||||
#include "xgboost/base.h" // GradientPair
|
||||
#include "xgboost/context.h" // Context
|
||||
#include "xgboost/data.h" // MetaInfo
|
||||
#include "xgboost/host_device_vector.h" // HostDeviceVector
|
||||
#include "xgboost/linalg.h" // TensorView
|
||||
|
||||
@@ -30,7 +31,7 @@ XGBOOST_DEVICE inline double CalcUnregularizedWeight(T sum_grad, T sum_hess) {
|
||||
/**
|
||||
* @brief Fit a tree stump as an estimation of base_score.
|
||||
*/
|
||||
void FitStump(Context const* ctx, HostDeviceVector<GradientPair> const& gpair,
|
||||
void FitStump(Context const* ctx, MetaInfo const& info, HostDeviceVector<GradientPair> const& gpair,
|
||||
bst_target_t n_targets, linalg::Vector<float>* out);
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
|
||||
@@ -4,22 +4,25 @@
|
||||
#ifndef XGBOOST_TREE_HIST_EVALUATE_SPLITS_H_
|
||||
#define XGBOOST_TREE_HIST_EVALUATE_SPLITS_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // for size_t
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <algorithm> // for copy
|
||||
#include <cstddef> // for size_t
|
||||
#include <limits> // for numeric_limits
|
||||
#include <memory> // for shared_ptr
|
||||
#include <numeric> // for accumulate
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
#include "../../common/categorical.h"
|
||||
#include "../../common/hist_util.h"
|
||||
#include "../../common/random.h"
|
||||
#include "../../data/gradient_index.h"
|
||||
#include "../constraints.h"
|
||||
#include "../param.h" // for TrainParam
|
||||
#include "../split_evaluator.h"
|
||||
#include "xgboost/context.h"
|
||||
#include "../../common/categorical.h" // for CatBitField
|
||||
#include "../../common/hist_util.h" // for GHistRow, HistogramCuts
|
||||
#include "../../common/linalg_op.h" // for cbegin, cend, begin
|
||||
#include "../../common/random.h" // for ColumnSampler
|
||||
#include "../constraints.h" // for FeatureInteractionConstraintHost
|
||||
#include "../param.h" // for TrainParam
|
||||
#include "../split_evaluator.h" // for TreeEvaluator
|
||||
#include "expand_entry.h" // for MultiExpandEntry
|
||||
#include "xgboost/base.h" // for bst_node_t, bst_target_t, bst_feature_t
|
||||
#include "xgboost/context.h" // for COntext
|
||||
#include "xgboost/linalg.h" // for Constants, Vector
|
||||
|
||||
namespace xgboost::tree {
|
||||
template <typename ExpandEntry>
|
||||
@@ -410,8 +413,6 @@ class HistEvaluator {
|
||||
tree[candidate.nid].SplitIndex(), left_weight,
|
||||
right_weight);
|
||||
|
||||
auto max_node = std::max(left_child, tree[candidate.nid].RightChild());
|
||||
max_node = std::max(candidate.nid, max_node);
|
||||
snode_.resize(tree.GetNodes().size());
|
||||
snode_.at(left_child).stats = candidate.split.left_sum;
|
||||
snode_.at(left_child).root_gain =
|
||||
@@ -456,6 +457,216 @@ class HistEvaluator {
|
||||
}
|
||||
};
|
||||
|
||||
class HistMultiEvaluator {
|
||||
std::vector<double> gain_;
|
||||
linalg::Matrix<GradientPairPrecise> stats_;
|
||||
TrainParam const *param_;
|
||||
FeatureInteractionConstraintHost interaction_constraints_;
|
||||
std::shared_ptr<common::ColumnSampler> column_sampler_;
|
||||
Context const *ctx_;
|
||||
|
||||
private:
|
||||
static double MultiCalcSplitGain(TrainParam const ¶m,
|
||||
linalg::VectorView<GradientPairPrecise const> left_sum,
|
||||
linalg::VectorView<GradientPairPrecise const> right_sum,
|
||||
linalg::VectorView<float> left_weight,
|
||||
linalg::VectorView<float> right_weight) {
|
||||
CalcWeight(param, left_sum, left_weight);
|
||||
CalcWeight(param, right_sum, right_weight);
|
||||
|
||||
auto left_gain = CalcGainGivenWeight(param, left_sum, left_weight);
|
||||
auto right_gain = CalcGainGivenWeight(param, right_sum, right_weight);
|
||||
return left_gain + right_gain;
|
||||
}
|
||||
|
||||
template <bst_bin_t d_step>
|
||||
bool EnumerateSplit(common::HistogramCuts const &cut, bst_feature_t fidx,
|
||||
common::Span<common::GHistRow const> hist,
|
||||
linalg::VectorView<GradientPairPrecise const> parent_sum, double parent_gain,
|
||||
SplitEntryContainer<std::vector<GradientPairPrecise>> *p_best) const {
|
||||
auto const &cut_ptr = cut.Ptrs();
|
||||
auto const &cut_val = cut.Values();
|
||||
auto const &min_val = cut.MinValues();
|
||||
|
||||
auto sum = linalg::Empty<GradientPairPrecise>(ctx_, 2, hist.size());
|
||||
auto left_sum = sum.Slice(0, linalg::All());
|
||||
auto right_sum = sum.Slice(1, linalg::All());
|
||||
|
||||
bst_bin_t ibegin, iend;
|
||||
if (d_step > 0) {
|
||||
ibegin = static_cast<bst_bin_t>(cut_ptr[fidx]);
|
||||
iend = static_cast<bst_bin_t>(cut_ptr[fidx + 1]);
|
||||
} else {
|
||||
ibegin = static_cast<bst_bin_t>(cut_ptr[fidx + 1]) - 1;
|
||||
iend = static_cast<bst_bin_t>(cut_ptr[fidx]) - 1;
|
||||
}
|
||||
const auto imin = static_cast<bst_bin_t>(cut_ptr[fidx]);
|
||||
|
||||
auto n_targets = hist.size();
|
||||
auto weight = linalg::Empty<float>(ctx_, 2, n_targets);
|
||||
auto left_weight = weight.Slice(0, linalg::All());
|
||||
auto right_weight = weight.Slice(1, linalg::All());
|
||||
|
||||
for (bst_bin_t i = ibegin; i != iend; i += d_step) {
|
||||
for (bst_target_t t = 0; t < n_targets; ++t) {
|
||||
auto t_hist = hist[t];
|
||||
auto t_p = parent_sum(t);
|
||||
left_sum(t) += t_hist[i];
|
||||
right_sum(t) = t_p - left_sum(t);
|
||||
}
|
||||
|
||||
if (d_step > 0) {
|
||||
auto split_pt = cut_val[i];
|
||||
auto loss_chg =
|
||||
MultiCalcSplitGain(*param_, right_sum, left_sum, right_weight, left_weight) -
|
||||
parent_gain;
|
||||
p_best->Update(loss_chg, fidx, split_pt, d_step == -1, false, left_sum, right_sum);
|
||||
} else {
|
||||
float split_pt;
|
||||
if (i == imin) {
|
||||
split_pt = min_val[fidx];
|
||||
} else {
|
||||
split_pt = cut_val[i - 1];
|
||||
}
|
||||
auto loss_chg =
|
||||
MultiCalcSplitGain(*param_, right_sum, left_sum, left_weight, right_weight) -
|
||||
parent_gain;
|
||||
p_best->Update(loss_chg, fidx, split_pt, d_step == -1, false, right_sum, left_sum);
|
||||
}
|
||||
}
|
||||
// return true if there's missing. Doesn't handle floating-point error well.
|
||||
if (d_step == +1) {
|
||||
return !std::equal(linalg::cbegin(left_sum), linalg::cend(left_sum),
|
||||
linalg::cbegin(parent_sum));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
void EvaluateSplits(RegTree const &tree, common::Span<const common::HistCollection *> hist,
|
||||
common::HistogramCuts const &cut, std::vector<MultiExpandEntry> *p_entries) {
|
||||
auto &entries = *p_entries;
|
||||
std::vector<std::shared_ptr<HostDeviceVector<bst_feature_t>>> features(entries.size());
|
||||
|
||||
for (std::size_t nidx_in_set = 0; nidx_in_set < entries.size(); ++nidx_in_set) {
|
||||
auto nidx = entries[nidx_in_set].nid;
|
||||
features[nidx_in_set] = column_sampler_->GetFeatureSet(tree.GetDepth(nidx));
|
||||
}
|
||||
CHECK(!features.empty());
|
||||
|
||||
std::int32_t n_threads = ctx_->Threads();
|
||||
std::size_t const grain_size = std::max<std::size_t>(1, features.front()->Size() / n_threads);
|
||||
common::BlockedSpace2d space(
|
||||
entries.size(), [&](std::size_t nidx_in_set) { return features[nidx_in_set]->Size(); },
|
||||
grain_size);
|
||||
|
||||
std::vector<MultiExpandEntry> tloc_candidates(n_threads * entries.size());
|
||||
for (std::size_t i = 0; i < entries.size(); ++i) {
|
||||
for (std::int32_t j = 0; j < n_threads; ++j) {
|
||||
tloc_candidates[i * n_threads + j] = entries[i];
|
||||
}
|
||||
}
|
||||
common::ParallelFor2d(space, n_threads, [&](std::size_t nidx_in_set, common::Range1d r) {
|
||||
auto tidx = omp_get_thread_num();
|
||||
auto entry = &tloc_candidates[n_threads * nidx_in_set + tidx];
|
||||
auto best = &entry->split;
|
||||
auto parent_sum = stats_.Slice(entry->nid, linalg::All());
|
||||
std::vector<common::GHistRow> node_hist;
|
||||
for (auto t_hist : hist) {
|
||||
node_hist.push_back((*t_hist)[entry->nid]);
|
||||
}
|
||||
auto features_set = features[nidx_in_set]->ConstHostSpan();
|
||||
|
||||
for (auto fidx_in_set = r.begin(); fidx_in_set < r.end(); fidx_in_set++) {
|
||||
auto fidx = features_set[fidx_in_set];
|
||||
if (!interaction_constraints_.Query(entry->nid, fidx)) {
|
||||
continue;
|
||||
}
|
||||
auto parent_gain = gain_[entry->nid];
|
||||
bool missing =
|
||||
this->EnumerateSplit<+1>(cut, fidx, node_hist, parent_sum, parent_gain, best);
|
||||
if (missing) {
|
||||
this->EnumerateSplit<-1>(cut, fidx, node_hist, parent_sum, parent_gain, best);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (std::size_t nidx_in_set = 0; nidx_in_set < entries.size(); ++nidx_in_set) {
|
||||
for (auto tidx = 0; tidx < n_threads; ++tidx) {
|
||||
entries[nidx_in_set].split.Update(tloc_candidates[n_threads * nidx_in_set + tidx].split);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linalg::Vector<float> InitRoot(linalg::VectorView<GradientPairPrecise const> root_sum) {
|
||||
auto n_targets = root_sum.Size();
|
||||
stats_ = linalg::Constant(ctx_, GradientPairPrecise{}, 1, n_targets);
|
||||
gain_.resize(1);
|
||||
|
||||
linalg::Vector<float> weight({n_targets}, ctx_->gpu_id);
|
||||
CalcWeight(*param_, root_sum, weight.HostView());
|
||||
auto root_gain = CalcGainGivenWeight(*param_, root_sum, weight.HostView());
|
||||
gain_.front() = root_gain;
|
||||
|
||||
auto h_stats = stats_.HostView();
|
||||
std::copy(linalg::cbegin(root_sum), linalg::cend(root_sum), linalg::begin(h_stats));
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
void ApplyTreeSplit(MultiExpandEntry const &candidate, RegTree *p_tree) {
|
||||
auto n_targets = p_tree->NumTargets();
|
||||
auto parent_sum = stats_.Slice(candidate.nid, linalg::All());
|
||||
|
||||
auto weight = linalg::Empty<float>(ctx_, 3, n_targets);
|
||||
auto base_weight = weight.Slice(0, linalg::All());
|
||||
CalcWeight(*param_, parent_sum, base_weight);
|
||||
|
||||
auto left_weight = weight.Slice(1, linalg::All());
|
||||
auto left_sum =
|
||||
linalg::MakeVec(candidate.split.left_sum.data(), candidate.split.left_sum.size());
|
||||
CalcWeight(*param_, left_sum, param_->learning_rate, left_weight);
|
||||
|
||||
auto right_weight = weight.Slice(2, linalg::All());
|
||||
auto right_sum =
|
||||
linalg::MakeVec(candidate.split.right_sum.data(), candidate.split.right_sum.size());
|
||||
CalcWeight(*param_, right_sum, param_->learning_rate, right_weight);
|
||||
|
||||
p_tree->ExpandNode(candidate.nid, candidate.split.SplitIndex(), candidate.split.split_value,
|
||||
candidate.split.DefaultLeft(), base_weight, left_weight, right_weight);
|
||||
CHECK(p_tree->IsMultiTarget());
|
||||
auto left_child = p_tree->LeftChild(candidate.nid);
|
||||
CHECK_GT(left_child, candidate.nid);
|
||||
auto right_child = p_tree->RightChild(candidate.nid);
|
||||
CHECK_GT(right_child, candidate.nid);
|
||||
|
||||
std::size_t n_nodes = p_tree->Size();
|
||||
gain_.resize(n_nodes);
|
||||
gain_[left_child] = CalcGainGivenWeight(*param_, left_sum, left_weight);
|
||||
gain_[right_child] = CalcGainGivenWeight(*param_, right_sum, right_weight);
|
||||
|
||||
if (n_nodes >= stats_.Shape(0)) {
|
||||
stats_.Reshape(n_nodes * 2, stats_.Shape(1));
|
||||
}
|
||||
CHECK_EQ(stats_.Shape(1), n_targets);
|
||||
auto left_sum_stat = stats_.Slice(left_child, linalg::All());
|
||||
std::copy(candidate.split.left_sum.cbegin(), candidate.split.left_sum.cend(),
|
||||
linalg::begin(left_sum_stat));
|
||||
auto right_sum_stat = stats_.Slice(right_child, linalg::All());
|
||||
std::copy(candidate.split.right_sum.cbegin(), candidate.split.right_sum.cend(),
|
||||
linalg::begin(right_sum_stat));
|
||||
}
|
||||
|
||||
explicit HistMultiEvaluator(Context const *ctx, MetaInfo const &info, TrainParam const *param,
|
||||
std::shared_ptr<common::ColumnSampler> sampler)
|
||||
: param_{param}, column_sampler_{std::move(sampler)}, ctx_{ctx} {
|
||||
interaction_constraints_.Configure(*param, info.num_col_);
|
||||
column_sampler_->Init(ctx, info.num_col_, info.feature_weights.HostVector(),
|
||||
param_->colsample_bynode, param_->colsample_bylevel,
|
||||
param_->colsample_bytree);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief CPU implementation of update prediction cache, which calculates the leaf value
|
||||
* for the last tree and accumulates it to prediction vector.
|
||||
|
||||
@@ -1,29 +1,51 @@
|
||||
/*!
|
||||
* Copyright 2021 XGBoost contributors
|
||||
/**
|
||||
* Copyright 2021-2023 XGBoost contributors
|
||||
*/
|
||||
#ifndef XGBOOST_TREE_HIST_EXPAND_ENTRY_H_
|
||||
#define XGBOOST_TREE_HIST_EXPAND_ENTRY_H_
|
||||
|
||||
#include <utility>
|
||||
#include "../param.h"
|
||||
#include <algorithm> // for all_of
|
||||
#include <ostream> // for ostream
|
||||
#include <utility> // for move
|
||||
#include <vector> // for vector
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
#include "../param.h" // for SplitEntry, SplitEntryContainer, TrainParam
|
||||
#include "xgboost/base.h" // for GradientPairPrecise, bst_node_t
|
||||
|
||||
struct CPUExpandEntry {
|
||||
int nid;
|
||||
int depth;
|
||||
SplitEntry split;
|
||||
CPUExpandEntry() = default;
|
||||
XGBOOST_DEVICE
|
||||
CPUExpandEntry(int nid, int depth, SplitEntry split)
|
||||
: nid(nid), depth(depth), split(std::move(split)) {}
|
||||
CPUExpandEntry(int nid, int depth, float loss_chg)
|
||||
: nid(nid), depth(depth) {
|
||||
split.loss_chg = loss_chg;
|
||||
namespace xgboost::tree {
|
||||
/**
|
||||
* \brief Structure for storing tree split candidate.
|
||||
*/
|
||||
template <typename Impl>
|
||||
struct ExpandEntryImpl {
|
||||
bst_node_t nid;
|
||||
bst_node_t depth;
|
||||
|
||||
[[nodiscard]] float GetLossChange() const {
|
||||
return static_cast<Impl const*>(this)->split.loss_chg;
|
||||
}
|
||||
[[nodiscard]] bst_node_t GetNodeId() const { return nid; }
|
||||
|
||||
static bool ChildIsValid(TrainParam const& param, bst_node_t depth, bst_node_t num_leaves) {
|
||||
if (param.max_depth > 0 && depth >= param.max_depth) return false;
|
||||
if (param.max_leaves > 0 && num_leaves >= param.max_leaves) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsValid(const TrainParam& param, int num_leaves) const {
|
||||
[[nodiscard]] bool IsValid(TrainParam const& param, bst_node_t num_leaves) const {
|
||||
return static_cast<Impl const*>(this)->IsValidImpl(param, num_leaves);
|
||||
}
|
||||
};
|
||||
|
||||
struct CPUExpandEntry : public ExpandEntryImpl<CPUExpandEntry> {
|
||||
SplitEntry split;
|
||||
|
||||
CPUExpandEntry() = default;
|
||||
CPUExpandEntry(bst_node_t nidx, bst_node_t depth, SplitEntry split)
|
||||
: ExpandEntryImpl{nidx, depth}, split(std::move(split)) {}
|
||||
CPUExpandEntry(bst_node_t nidx, bst_node_t depth) : ExpandEntryImpl{nidx, depth} {}
|
||||
|
||||
[[nodiscard]] bool IsValidImpl(TrainParam const& param, bst_node_t num_leaves) const {
|
||||
if (split.loss_chg <= kRtEps) return false;
|
||||
if (split.left_sum.GetHess() == 0 || split.right_sum.GetHess() == 0) {
|
||||
return false;
|
||||
@@ -40,16 +62,7 @@ struct CPUExpandEntry {
|
||||
return true;
|
||||
}
|
||||
|
||||
float GetLossChange() const { return split.loss_chg; }
|
||||
bst_node_t GetNodeId() const { return nid; }
|
||||
|
||||
static bool ChildIsValid(const TrainParam& param, int depth, int num_leaves) {
|
||||
if (param.max_depth > 0 && depth >= param.max_depth) return false;
|
||||
if (param.max_leaves > 0 && num_leaves >= param.max_leaves) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const CPUExpandEntry& e) {
|
||||
friend std::ostream& operator<<(std::ostream& os, CPUExpandEntry const& e) {
|
||||
os << "ExpandEntry:\n";
|
||||
os << "nidx: " << e.nid << "\n";
|
||||
os << "depth: " << e.depth << "\n";
|
||||
@@ -58,6 +71,54 @@ struct CPUExpandEntry {
|
||||
return os;
|
||||
}
|
||||
};
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
|
||||
struct MultiExpandEntry : public ExpandEntryImpl<MultiExpandEntry> {
|
||||
SplitEntryContainer<std::vector<GradientPairPrecise>> split;
|
||||
|
||||
MultiExpandEntry() = default;
|
||||
MultiExpandEntry(bst_node_t nidx, bst_node_t depth) : ExpandEntryImpl{nidx, depth} {}
|
||||
|
||||
[[nodiscard]] bool IsValidImpl(TrainParam const& param, bst_node_t num_leaves) const {
|
||||
if (split.loss_chg <= kRtEps) return false;
|
||||
auto is_zero = [](auto const& sum) {
|
||||
return std::all_of(sum.cbegin(), sum.cend(),
|
||||
[&](auto const& g) { return g.GetHess() - .0 == .0; });
|
||||
};
|
||||
if (is_zero(split.left_sum) || is_zero(split.right_sum)) {
|
||||
return false;
|
||||
}
|
||||
if (split.loss_chg < param.min_split_loss) {
|
||||
return false;
|
||||
}
|
||||
if (param.max_depth > 0 && depth == param.max_depth) {
|
||||
return false;
|
||||
}
|
||||
if (param.max_leaves > 0 && num_leaves == param.max_leaves) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, MultiExpandEntry const& e) {
|
||||
os << "ExpandEntry: \n";
|
||||
os << "nidx: " << e.nid << "\n";
|
||||
os << "depth: " << e.depth << "\n";
|
||||
os << "loss: " << e.split.loss_chg << "\n";
|
||||
os << "split cond:" << e.split.split_value << "\n";
|
||||
os << "split ind:" << e.split.SplitIndex() << "\n";
|
||||
os << "left_sum: [";
|
||||
for (auto v : e.split.left_sum) {
|
||||
os << v << ", ";
|
||||
}
|
||||
os << "]\n";
|
||||
|
||||
os << "right_sum: [";
|
||||
for (auto v : e.split.right_sum) {
|
||||
os << v << ", ";
|
||||
}
|
||||
os << "]\n";
|
||||
return os;
|
||||
}
|
||||
};
|
||||
} // namespace xgboost::tree
|
||||
#endif // XGBOOST_TREE_HIST_EXPAND_ENTRY_H_
|
||||
|
||||
@@ -306,9 +306,9 @@ class HistogramBuilder {
|
||||
|
||||
// Construct a work space for building histogram. Eventually we should move this
|
||||
// function into histogram builder once hist tree method supports external memory.
|
||||
template <typename Partitioner>
|
||||
template <typename Partitioner, typename ExpandEntry = CPUExpandEntry>
|
||||
common::BlockedSpace2d ConstructHistSpace(Partitioner const &partitioners,
|
||||
std::vector<CPUExpandEntry> const &nodes_to_build) {
|
||||
std::vector<ExpandEntry> const &nodes_to_build) {
|
||||
std::vector<size_t> partition_size(nodes_to_build.size(), 0);
|
||||
for (auto const &partition : partitioners) {
|
||||
size_t k = 0;
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xgboost/parameter.h"
|
||||
#include "xgboost/data.h"
|
||||
#include "../common/categorical.h"
|
||||
#include "../common/linalg_op.h"
|
||||
#include "../common/math.h"
|
||||
#include "xgboost/data.h"
|
||||
#include "xgboost/linalg.h"
|
||||
#include "xgboost/parameter.h"
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
@@ -197,12 +199,11 @@ struct TrainParam : public XGBoostParameter<TrainParam> {
|
||||
}
|
||||
|
||||
/*! \brief given the loss change, whether we need to invoke pruning */
|
||||
bool NeedPrune(double loss_chg, int depth) const {
|
||||
return loss_chg < this->min_split_loss ||
|
||||
(this->max_depth != 0 && depth > this->max_depth);
|
||||
[[nodiscard]] bool NeedPrune(double loss_chg, int depth) const {
|
||||
return loss_chg < this->min_split_loss || (this->max_depth != 0 && depth > this->max_depth);
|
||||
}
|
||||
|
||||
bst_node_t MaxNodes() const {
|
||||
[[nodiscard]] bst_node_t MaxNodes() const {
|
||||
if (this->max_depth == 0 && this->max_leaves == 0) {
|
||||
LOG(FATAL) << "Max leaves and max depth cannot both be unconstrained.";
|
||||
}
|
||||
@@ -292,6 +293,34 @@ XGBOOST_DEVICE inline float CalcWeight(const TrainingParams &p, GpairT sum_grad)
|
||||
return CalcWeight(p, sum_grad.GetGrad(), sum_grad.GetHess());
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief multi-target weight, calculated with learning rate.
|
||||
*/
|
||||
inline void CalcWeight(TrainParam const &p, linalg::VectorView<GradientPairPrecise const> grad_sum,
|
||||
float eta, linalg::VectorView<float> out_w) {
|
||||
for (bst_target_t i = 0; i < out_w.Size(); ++i) {
|
||||
out_w(i) = CalcWeight(p, grad_sum(i).GetGrad(), grad_sum(i).GetHess()) * eta;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief multi-target weight
|
||||
*/
|
||||
inline void CalcWeight(TrainParam const &p, linalg::VectorView<GradientPairPrecise const> grad_sum,
|
||||
linalg::VectorView<float> out_w) {
|
||||
return CalcWeight(p, grad_sum, 1.0f, out_w);
|
||||
}
|
||||
|
||||
inline double CalcGainGivenWeight(TrainParam const &p,
|
||||
linalg::VectorView<GradientPairPrecise const> sum_grad,
|
||||
linalg::VectorView<float const> weight) {
|
||||
double gain{0};
|
||||
for (bst_target_t i = 0; i < weight.Size(); ++i) {
|
||||
gain += -weight(i) * ThresholdL1(sum_grad(i).GetGrad(), p.reg_alpha);
|
||||
}
|
||||
return gain;
|
||||
}
|
||||
|
||||
/*! \brief core statistics used for tree construction */
|
||||
struct XGBOOST_ALIGNAS(16) GradStats {
|
||||
using GradType = double;
|
||||
@@ -301,8 +330,8 @@ struct XGBOOST_ALIGNAS(16) GradStats {
|
||||
GradType sum_hess { 0 };
|
||||
|
||||
public:
|
||||
XGBOOST_DEVICE GradType GetGrad() const { return sum_grad; }
|
||||
XGBOOST_DEVICE GradType GetHess() const { return sum_hess; }
|
||||
[[nodiscard]] XGBOOST_DEVICE GradType GetGrad() const { return sum_grad; }
|
||||
[[nodiscard]] XGBOOST_DEVICE GradType GetHess() const { return sum_hess; }
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, GradStats s) {
|
||||
os << s.GetGrad() << "/" << s.GetHess();
|
||||
@@ -340,7 +369,7 @@ struct XGBOOST_ALIGNAS(16) GradStats {
|
||||
sum_hess = a.sum_hess - b.sum_hess;
|
||||
}
|
||||
/*! \return whether the statistics is not used yet */
|
||||
inline bool Empty() const { return sum_hess == 0.0; }
|
||||
[[nodiscard]] bool Empty() const { return sum_hess == 0.0; }
|
||||
/*! \brief add statistics to the data */
|
||||
inline void Add(GradType grad, GradType hess) {
|
||||
sum_grad += grad;
|
||||
@@ -348,6 +377,19 @@ struct XGBOOST_ALIGNAS(16) GradStats {
|
||||
}
|
||||
};
|
||||
|
||||
// Helper functions for copying gradient statistic, one for vector leaf, another for normal scalar.
|
||||
template <typename T, typename U>
|
||||
std::vector<T> &CopyStats(linalg::VectorView<U> const &src, std::vector<T> *dst) { // NOLINT
|
||||
dst->resize(src.Size());
|
||||
std::copy(linalg::cbegin(src), linalg::cend(src), dst->begin());
|
||||
return *dst;
|
||||
}
|
||||
|
||||
inline GradStats &CopyStats(GradStats const &src, GradStats *dst) { // NOLINT
|
||||
*dst = src;
|
||||
return *dst;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief statistics that is helpful to store
|
||||
* and represent a split solution for the tree
|
||||
@@ -378,9 +420,9 @@ struct SplitEntryContainer {
|
||||
return os;
|
||||
}
|
||||
/*!\return feature index to split on */
|
||||
bst_feature_t SplitIndex() const { return sindex & ((1U << 31) - 1U); }
|
||||
[[nodiscard]] bst_feature_t SplitIndex() const { return sindex & ((1U << 31) - 1U); }
|
||||
/*!\return whether missing value goes to left branch */
|
||||
bool DefaultLeft() const { return (sindex >> 31) != 0; }
|
||||
[[nodiscard]] bool DefaultLeft() const { return (sindex >> 31) != 0; }
|
||||
/*!
|
||||
* \brief decides whether we can replace current entry with the given statistics
|
||||
*
|
||||
@@ -391,10 +433,10 @@ struct SplitEntryContainer {
|
||||
* \param new_loss_chg the loss reduction get through the split
|
||||
* \param split_index the feature index where the split is on
|
||||
*/
|
||||
bool NeedReplace(bst_float new_loss_chg, unsigned split_index) const {
|
||||
[[nodiscard]] bool NeedReplace(bst_float new_loss_chg, unsigned split_index) const {
|
||||
if (std::isinf(new_loss_chg)) { // in some cases new_loss_chg can be NaN or Inf,
|
||||
// for example when lambda = 0 & min_child_weight = 0
|
||||
// skip value in this case
|
||||
// for example when lambda = 0 & min_child_weight = 0
|
||||
// skip value in this case
|
||||
return false;
|
||||
} else if (this->SplitIndex() <= split_index) {
|
||||
return new_loss_chg > this->loss_chg;
|
||||
@@ -429,9 +471,10 @@ struct SplitEntryContainer {
|
||||
* \param default_left whether the missing value goes to left
|
||||
* \return whether the proposed split is better and can replace current split
|
||||
*/
|
||||
bool Update(bst_float new_loss_chg, unsigned split_index,
|
||||
bst_float new_split_value, bool default_left, bool is_cat,
|
||||
const GradientT &left_sum, const GradientT &right_sum) {
|
||||
template <typename GradientSumT>
|
||||
bool Update(bst_float new_loss_chg, unsigned split_index, bst_float new_split_value,
|
||||
bool default_left, bool is_cat, GradientSumT const &left_sum,
|
||||
GradientSumT const &right_sum) {
|
||||
if (this->NeedReplace(new_loss_chg, split_index)) {
|
||||
this->loss_chg = new_loss_chg;
|
||||
if (default_left) {
|
||||
@@ -440,8 +483,8 @@ struct SplitEntryContainer {
|
||||
this->sindex = split_index;
|
||||
this->split_value = new_split_value;
|
||||
this->is_cat = is_cat;
|
||||
this->left_sum = left_sum;
|
||||
this->right_sum = right_sum;
|
||||
CopyStats(left_sum, &this->left_sum);
|
||||
CopyStats(right_sum, &this->right_sum);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -815,9 +815,9 @@ void RegTree::ExpandNode(bst_node_t nidx, bst_feature_t split_index, float split
|
||||
linalg::VectorView<float const> left_weight,
|
||||
linalg::VectorView<float const> right_weight) {
|
||||
CHECK(IsMultiTarget());
|
||||
CHECK_LT(split_index, this->param.num_feature);
|
||||
CHECK_LT(split_index, this->param_.num_feature);
|
||||
CHECK(this->p_mt_tree_);
|
||||
CHECK_GT(param.size_leaf_vector, 1);
|
||||
CHECK_GT(param_.size_leaf_vector, 1);
|
||||
|
||||
this->p_mt_tree_->Expand(nidx, split_index, split_cond, default_left, base_weight, left_weight,
|
||||
right_weight);
|
||||
@@ -826,7 +826,7 @@ void RegTree::ExpandNode(bst_node_t nidx, bst_feature_t split_index, float split
|
||||
split_categories_segments_.resize(this->Size());
|
||||
this->split_types_.at(nidx) = FeatureType::kNumerical;
|
||||
|
||||
this->param.num_nodes = this->p_mt_tree_->Size();
|
||||
this->param_.num_nodes = this->p_mt_tree_->Size();
|
||||
}
|
||||
|
||||
void RegTree::ExpandCategorical(bst_node_t nid, bst_feature_t split_index,
|
||||
@@ -850,13 +850,13 @@ void RegTree::ExpandCategorical(bst_node_t nid, bst_feature_t split_index,
|
||||
}
|
||||
|
||||
void RegTree::Load(dmlc::Stream* fi) {
|
||||
CHECK_EQ(fi->Read(¶m, sizeof(TreeParam)), sizeof(TreeParam));
|
||||
CHECK_EQ(fi->Read(¶m_, sizeof(TreeParam)), sizeof(TreeParam));
|
||||
if (!DMLC_IO_NO_ENDIAN_SWAP) {
|
||||
param = param.ByteSwap();
|
||||
param_ = param_.ByteSwap();
|
||||
}
|
||||
nodes_.resize(param.num_nodes);
|
||||
stats_.resize(param.num_nodes);
|
||||
CHECK_NE(param.num_nodes, 0);
|
||||
nodes_.resize(param_.num_nodes);
|
||||
stats_.resize(param_.num_nodes);
|
||||
CHECK_NE(param_.num_nodes, 0);
|
||||
CHECK_EQ(fi->Read(dmlc::BeginPtr(nodes_), sizeof(Node) * nodes_.size()),
|
||||
sizeof(Node) * nodes_.size());
|
||||
if (!DMLC_IO_NO_ENDIAN_SWAP) {
|
||||
@@ -873,29 +873,31 @@ void RegTree::Load(dmlc::Stream* fi) {
|
||||
}
|
||||
// chg deleted nodes
|
||||
deleted_nodes_.resize(0);
|
||||
for (int i = 1; i < param.num_nodes; ++i) {
|
||||
for (int i = 1; i < param_.num_nodes; ++i) {
|
||||
if (nodes_[i].IsDeleted()) {
|
||||
deleted_nodes_.push_back(i);
|
||||
}
|
||||
}
|
||||
CHECK_EQ(static_cast<int>(deleted_nodes_.size()), param.num_deleted);
|
||||
CHECK_EQ(static_cast<int>(deleted_nodes_.size()), param_.num_deleted);
|
||||
|
||||
split_types_.resize(param.num_nodes, FeatureType::kNumerical);
|
||||
split_categories_segments_.resize(param.num_nodes);
|
||||
split_types_.resize(param_.num_nodes, FeatureType::kNumerical);
|
||||
split_categories_segments_.resize(param_.num_nodes);
|
||||
}
|
||||
|
||||
void RegTree::Save(dmlc::Stream* fo) const {
|
||||
CHECK_EQ(param.num_nodes, static_cast<int>(nodes_.size()));
|
||||
CHECK_EQ(param.num_nodes, static_cast<int>(stats_.size()));
|
||||
CHECK_EQ(param.deprecated_num_roots, 1);
|
||||
CHECK_NE(param.num_nodes, 0);
|
||||
CHECK_EQ(param_.num_nodes, static_cast<int>(nodes_.size()));
|
||||
CHECK_EQ(param_.num_nodes, static_cast<int>(stats_.size()));
|
||||
CHECK_EQ(param_.deprecated_num_roots, 1);
|
||||
CHECK_NE(param_.num_nodes, 0);
|
||||
CHECK(!IsMultiTarget())
|
||||
<< "Please use JSON/UBJSON for saving models with multi-target trees.";
|
||||
CHECK(!HasCategoricalSplit())
|
||||
<< "Please use JSON/UBJSON for saving models with categorical splits.";
|
||||
|
||||
if (DMLC_IO_NO_ENDIAN_SWAP) {
|
||||
fo->Write(¶m, sizeof(TreeParam));
|
||||
fo->Write(¶m_, sizeof(TreeParam));
|
||||
} else {
|
||||
TreeParam x = param.ByteSwap();
|
||||
TreeParam x = param_.ByteSwap();
|
||||
fo->Write(&x, sizeof(x));
|
||||
}
|
||||
|
||||
@@ -1081,7 +1083,7 @@ void RegTree::LoadModel(Json const& in) {
|
||||
bool typed = IsA<I32Array>(in[tf::kParent]);
|
||||
auto const& in_obj = get<Object const>(in);
|
||||
// basic properties
|
||||
FromJson(in["tree_param"], ¶m);
|
||||
FromJson(in["tree_param"], ¶m_);
|
||||
// categorical splits
|
||||
bool has_cat = in_obj.find("split_type") != in_obj.cend();
|
||||
if (has_cat) {
|
||||
@@ -1092,55 +1094,55 @@ void RegTree::LoadModel(Json const& in) {
|
||||
}
|
||||
}
|
||||
// multi-target
|
||||
if (param.size_leaf_vector > 1) {
|
||||
this->p_mt_tree_.reset(new MultiTargetTree{¶m});
|
||||
if (param_.size_leaf_vector > 1) {
|
||||
this->p_mt_tree_.reset(new MultiTargetTree{¶m_});
|
||||
this->GetMultiTargetTree()->LoadModel(in);
|
||||
return;
|
||||
}
|
||||
|
||||
bool feature_is_64 = IsA<I64Array>(in["split_indices"]);
|
||||
if (typed && feature_is_64) {
|
||||
LoadModelImpl<true, true>(in, param, &stats_, &nodes_);
|
||||
LoadModelImpl<true, true>(in, param_, &stats_, &nodes_);
|
||||
} else if (typed && !feature_is_64) {
|
||||
LoadModelImpl<true, false>(in, param, &stats_, &nodes_);
|
||||
LoadModelImpl<true, false>(in, param_, &stats_, &nodes_);
|
||||
} else if (!typed && feature_is_64) {
|
||||
LoadModelImpl<false, true>(in, param, &stats_, &nodes_);
|
||||
LoadModelImpl<false, true>(in, param_, &stats_, &nodes_);
|
||||
} else {
|
||||
LoadModelImpl<false, false>(in, param, &stats_, &nodes_);
|
||||
LoadModelImpl<false, false>(in, param_, &stats_, &nodes_);
|
||||
}
|
||||
|
||||
if (!has_cat) {
|
||||
this->split_categories_segments_.resize(this->param.num_nodes);
|
||||
this->split_types_.resize(this->param.num_nodes);
|
||||
this->split_categories_segments_.resize(this->param_.num_nodes);
|
||||
this->split_types_.resize(this->param_.num_nodes);
|
||||
std::fill(split_types_.begin(), split_types_.end(), FeatureType::kNumerical);
|
||||
}
|
||||
|
||||
deleted_nodes_.clear();
|
||||
for (bst_node_t i = 1; i < param.num_nodes; ++i) {
|
||||
for (bst_node_t i = 1; i < param_.num_nodes; ++i) {
|
||||
if (nodes_[i].IsDeleted()) {
|
||||
deleted_nodes_.push_back(i);
|
||||
}
|
||||
}
|
||||
// easier access to [] operator
|
||||
auto& self = *this;
|
||||
for (auto nid = 1; nid < param.num_nodes; ++nid) {
|
||||
for (auto nid = 1; nid < param_.num_nodes; ++nid) {
|
||||
auto parent = self[nid].Parent();
|
||||
CHECK_NE(parent, RegTree::kInvalidNodeId);
|
||||
self[nid].SetParent(self[nid].Parent(), self[parent].LeftChild() == nid);
|
||||
}
|
||||
CHECK_EQ(static_cast<bst_node_t>(deleted_nodes_.size()), param.num_deleted);
|
||||
CHECK_EQ(this->split_categories_segments_.size(), param.num_nodes);
|
||||
CHECK_EQ(static_cast<bst_node_t>(deleted_nodes_.size()), param_.num_deleted);
|
||||
CHECK_EQ(this->split_categories_segments_.size(), param_.num_nodes);
|
||||
}
|
||||
|
||||
void RegTree::SaveModel(Json* p_out) const {
|
||||
auto& out = *p_out;
|
||||
// basic properties
|
||||
out["tree_param"] = ToJson(param);
|
||||
out["tree_param"] = ToJson(param_);
|
||||
// categorical splits
|
||||
this->SaveCategoricalSplit(p_out);
|
||||
// multi-target
|
||||
if (this->IsMultiTarget()) {
|
||||
CHECK_GT(param.size_leaf_vector, 1);
|
||||
CHECK_GT(param_.size_leaf_vector, 1);
|
||||
this->GetMultiTargetTree()->SaveModel(p_out);
|
||||
return;
|
||||
}
|
||||
@@ -1150,11 +1152,11 @@ void RegTree::SaveModel(Json* p_out) const {
|
||||
* pruner, and this pruner can be used inside another updater so leaf are not necessary
|
||||
* at the end of node array.
|
||||
*/
|
||||
CHECK_EQ(param.num_nodes, static_cast<int>(nodes_.size()));
|
||||
CHECK_EQ(param.num_nodes, static_cast<int>(stats_.size()));
|
||||
CHECK_EQ(param_.num_nodes, static_cast<int>(nodes_.size()));
|
||||
CHECK_EQ(param_.num_nodes, static_cast<int>(stats_.size()));
|
||||
|
||||
CHECK_EQ(get<String>(out["tree_param"]["num_nodes"]), std::to_string(param.num_nodes));
|
||||
auto n_nodes = param.num_nodes;
|
||||
CHECK_EQ(get<String>(out["tree_param"]["num_nodes"]), std::to_string(param_.num_nodes));
|
||||
auto n_nodes = param_.num_nodes;
|
||||
|
||||
// stats
|
||||
F32Array loss_changes(n_nodes);
|
||||
@@ -1168,7 +1170,7 @@ void RegTree::SaveModel(Json* p_out) const {
|
||||
|
||||
F32Array conds(n_nodes);
|
||||
U8Array default_left(n_nodes);
|
||||
CHECK_EQ(this->split_types_.size(), param.num_nodes);
|
||||
CHECK_EQ(this->split_types_.size(), param_.num_nodes);
|
||||
|
||||
namespace tf = tree_field;
|
||||
|
||||
@@ -1189,7 +1191,7 @@ void RegTree::SaveModel(Json* p_out) const {
|
||||
default_left.Set(i, static_cast<uint8_t>(!!n.DefaultLeft()));
|
||||
}
|
||||
};
|
||||
if (this->param.num_feature > static_cast<bst_feature_t>(std::numeric_limits<int32_t>::max())) {
|
||||
if (this->param_.num_feature > static_cast<bst_feature_t>(std::numeric_limits<int32_t>::max())) {
|
||||
I64Array indices_64(n_nodes);
|
||||
save_tree(&indices_64);
|
||||
out[tf::kSplitIdx] = std::move(indices_64);
|
||||
|
||||
@@ -226,8 +226,8 @@ class GloablApproxBuilder {
|
||||
for (auto const &candidate : valid_candidates) {
|
||||
int left_child_nidx = tree[candidate.nid].LeftChild();
|
||||
int right_child_nidx = tree[candidate.nid].RightChild();
|
||||
CPUExpandEntry l_best{left_child_nidx, tree.GetDepth(left_child_nidx), {}};
|
||||
CPUExpandEntry r_best{right_child_nidx, tree.GetDepth(right_child_nidx), {}};
|
||||
CPUExpandEntry l_best{left_child_nidx, tree.GetDepth(left_child_nidx)};
|
||||
CPUExpandEntry r_best{right_child_nidx, tree.GetDepth(right_child_nidx)};
|
||||
best_splits.push_back(l_best);
|
||||
best_splits.push_back(r_best);
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ class ColMaker: public TreeUpdater {
|
||||
(*p_tree)[nid].SetLeaf(snode_[nid].weight * param_.learning_rate);
|
||||
}
|
||||
// remember auxiliary statistics in the tree node
|
||||
for (int nid = 0; nid < p_tree->param.num_nodes; ++nid) {
|
||||
for (int nid = 0; nid < p_tree->NumNodes(); ++nid) {
|
||||
p_tree->Stat(nid).loss_chg = snode_[nid].best.loss_chg;
|
||||
p_tree->Stat(nid).base_weight = snode_[nid].weight;
|
||||
p_tree->Stat(nid).sum_hess = static_cast<float>(snode_[nid].stats.sum_hess);
|
||||
@@ -255,9 +255,9 @@ class ColMaker: public TreeUpdater {
|
||||
{
|
||||
// setup statistics space for each tree node
|
||||
for (auto& i : stemp_) {
|
||||
i.resize(tree.param.num_nodes, ThreadEntry());
|
||||
i.resize(tree.NumNodes(), ThreadEntry());
|
||||
}
|
||||
snode_.resize(tree.param.num_nodes, NodeEntry());
|
||||
snode_.resize(tree.NumNodes(), NodeEntry());
|
||||
}
|
||||
const MetaInfo& info = fmat.Info();
|
||||
// setup position
|
||||
|
||||
@@ -72,7 +72,7 @@ class TreePruner : public TreeUpdater {
|
||||
void DoPrune(TrainParam const* param, RegTree* p_tree) {
|
||||
auto& tree = *p_tree;
|
||||
bst_node_t npruned = 0;
|
||||
for (int nid = 0; nid < tree.param.num_nodes; ++nid) {
|
||||
for (int nid = 0; nid < tree.NumNodes(); ++nid) {
|
||||
if (tree[nid].IsLeaf() && !tree[nid].IsDeleted()) {
|
||||
npruned = this->TryPruneLeaf(param, p_tree, nid, tree.GetDepth(nid), npruned);
|
||||
}
|
||||
|
||||
@@ -4,263 +4,368 @@
|
||||
* \brief use quantized feature values to construct a tree
|
||||
* \author Philip Cho, Tianqi Checn, Egor Smirnov
|
||||
*/
|
||||
#include "./updater_quantile_hist.h"
|
||||
#include <algorithm> // for max, copy, transform
|
||||
#include <cstddef> // for size_t
|
||||
#include <cstdint> // for uint32_t, int32_t
|
||||
#include <memory> // for unique_ptr, allocator, make_unique, shared_ptr
|
||||
#include <numeric> // for accumulate
|
||||
#include <ostream> // for basic_ostream, char_traits, operator<<
|
||||
#include <utility> // for move, swap
|
||||
#include <vector> // for vector
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../collective/communicator-inl.h" // for Allreduce, IsDistributed
|
||||
#include "../collective/communicator.h" // for Operation
|
||||
#include "../common/hist_util.h" // for HistogramCuts, HistCollection
|
||||
#include "../common/linalg_op.h" // for begin, cbegin, cend
|
||||
#include "../common/random.h" // for ColumnSampler
|
||||
#include "../common/threading_utils.h" // for ParallelFor
|
||||
#include "../common/timer.h" // for Monitor
|
||||
#include "../common/transform_iterator.h" // for IndexTransformIter, MakeIndexTransformIter
|
||||
#include "../data/gradient_index.h" // for GHistIndexMatrix
|
||||
#include "common_row_partitioner.h" // for CommonRowPartitioner
|
||||
#include "dmlc/omp.h" // for omp_get_thread_num
|
||||
#include "dmlc/registry.h" // for DMLC_REGISTRY_FILE_TAG
|
||||
#include "driver.h" // for Driver
|
||||
#include "hist/evaluate_splits.h" // for HistEvaluator, HistMultiEvaluator, UpdatePre...
|
||||
#include "hist/expand_entry.h" // for MultiExpandEntry, CPUExpandEntry
|
||||
#include "hist/histogram.h" // for HistogramBuilder, ConstructHistSpace
|
||||
#include "hist/sampler.h" // for SampleGradient
|
||||
#include "param.h" // for TrainParam, SplitEntryContainer, GradStats
|
||||
#include "xgboost/base.h" // for GradientPairInternal, GradientPair, bst_targ...
|
||||
#include "xgboost/context.h" // for Context
|
||||
#include "xgboost/data.h" // for BatchIterator, BatchSet, DMatrix, MetaInfo
|
||||
#include "xgboost/host_device_vector.h" // for HostDeviceVector
|
||||
#include "xgboost/linalg.h" // for All, MatrixView, TensorView, Matrix, Empty
|
||||
#include "xgboost/logging.h" // for LogCheck_EQ, CHECK_EQ, CHECK, LogCheck_GE
|
||||
#include "xgboost/span.h" // for Span, operator!=, SpanIterator
|
||||
#include "xgboost/string_view.h" // for operator<<
|
||||
#include "xgboost/task.h" // for ObjInfo
|
||||
#include "xgboost/tree_model.h" // for RegTree, MTNotImplemented, RTreeNodeStat
|
||||
#include "xgboost/tree_updater.h" // for TreeUpdater, TreeUpdaterReg, XGBOOST_REGISTE...
|
||||
|
||||
#include "common_row_partitioner.h"
|
||||
#include "constraints.h"
|
||||
#include "hist/evaluate_splits.h"
|
||||
#include "hist/histogram.h"
|
||||
#include "hist/sampler.h"
|
||||
#include "param.h"
|
||||
#include "xgboost/linalg.h"
|
||||
#include "xgboost/logging.h"
|
||||
#include "xgboost/tree_updater.h"
|
||||
|
||||
namespace xgboost {
|
||||
namespace tree {
|
||||
namespace xgboost::tree {
|
||||
|
||||
DMLC_REGISTRY_FILE_TAG(updater_quantile_hist);
|
||||
|
||||
void QuantileHistMaker::Update(TrainParam const *param, HostDeviceVector<GradientPair> *gpair,
|
||||
DMatrix *dmat,
|
||||
common::Span<HostDeviceVector<bst_node_t>> out_position,
|
||||
const std::vector<RegTree *> &trees) {
|
||||
// build tree
|
||||
const size_t n_trees = trees.size();
|
||||
if (!pimpl_) {
|
||||
pimpl_.reset(new Builder(n_trees, param, dmat, *task_, ctx_));
|
||||
}
|
||||
BatchParam HistBatch(TrainParam const *param) { return {param->max_bin, param->sparse_threshold}; }
|
||||
|
||||
size_t t_idx{0};
|
||||
for (auto p_tree : trees) {
|
||||
auto &t_row_position = out_position[t_idx];
|
||||
this->pimpl_->UpdateTree(gpair, dmat, p_tree, &t_row_position);
|
||||
++t_idx;
|
||||
}
|
||||
}
|
||||
|
||||
bool QuantileHistMaker::UpdatePredictionCache(const DMatrix *data,
|
||||
linalg::VectorView<float> out_preds) {
|
||||
if (pimpl_) {
|
||||
return pimpl_->UpdatePredictionCache(data, out_preds);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CPUExpandEntry QuantileHistMaker::Builder::InitRoot(
|
||||
DMatrix *p_fmat, RegTree *p_tree, const std::vector<GradientPair> &gpair_h) {
|
||||
CPUExpandEntry node(RegTree::kRoot, p_tree->GetDepth(0), 0.0f);
|
||||
|
||||
size_t page_id = 0;
|
||||
auto space = ConstructHistSpace(partitioner_, {node});
|
||||
for (auto const &gidx : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
std::vector<CPUExpandEntry> nodes_to_build{node};
|
||||
std::vector<CPUExpandEntry> nodes_to_sub;
|
||||
this->histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
||||
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
||||
nodes_to_sub, gpair_h);
|
||||
++page_id;
|
||||
}
|
||||
|
||||
{
|
||||
GradientPairPrecise grad_stat;
|
||||
if (p_fmat->IsDense()) {
|
||||
/**
|
||||
* Specialized code for dense data: For dense data (with no missing value), the sum
|
||||
* of gradient histogram is equal to snode[nid]
|
||||
*/
|
||||
auto const &gmat = *(p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_)).begin());
|
||||
std::vector<uint32_t> const &row_ptr = gmat.cut.Ptrs();
|
||||
CHECK_GE(row_ptr.size(), 2);
|
||||
uint32_t const ibegin = row_ptr[0];
|
||||
uint32_t const iend = row_ptr[1];
|
||||
auto hist = this->histogram_builder_->Histogram()[RegTree::kRoot];
|
||||
auto begin = hist.data();
|
||||
for (uint32_t i = ibegin; i < iend; ++i) {
|
||||
GradientPairPrecise const &et = begin[i];
|
||||
grad_stat.Add(et.GetGrad(), et.GetHess());
|
||||
}
|
||||
} else {
|
||||
for (auto const &grad : gpair_h) {
|
||||
grad_stat.Add(grad.GetGrad(), grad.GetHess());
|
||||
}
|
||||
collective::Allreduce<collective::Operation::kSum>(reinterpret_cast<double *>(&grad_stat), 2);
|
||||
}
|
||||
|
||||
auto weight = evaluator_->InitRoot(GradStats{grad_stat});
|
||||
p_tree->Stat(RegTree::kRoot).sum_hess = grad_stat.GetHess();
|
||||
p_tree->Stat(RegTree::kRoot).base_weight = weight;
|
||||
(*p_tree)[RegTree::kRoot].SetLeaf(param_->learning_rate * weight);
|
||||
|
||||
std::vector<CPUExpandEntry> entries{node};
|
||||
monitor_->Start("EvaluateSplits");
|
||||
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
evaluator_->EvaluateSplits(histogram_builder_->Histogram(), gmat.cut, ft, *p_tree, &entries);
|
||||
break;
|
||||
}
|
||||
monitor_->Stop("EvaluateSplits");
|
||||
node = entries.front();
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void QuantileHistMaker::Builder::BuildHistogram(DMatrix *p_fmat, RegTree *p_tree,
|
||||
std::vector<CPUExpandEntry> const &valid_candidates,
|
||||
std::vector<GradientPair> const &gpair) {
|
||||
std::vector<CPUExpandEntry> nodes_to_build(valid_candidates.size());
|
||||
std::vector<CPUExpandEntry> nodes_to_sub(valid_candidates.size());
|
||||
|
||||
size_t n_idx = 0;
|
||||
for (auto const &c : valid_candidates) {
|
||||
auto left_nidx = (*p_tree)[c.nid].LeftChild();
|
||||
auto right_nidx = (*p_tree)[c.nid].RightChild();
|
||||
auto fewer_right = c.split.right_sum.GetHess() < c.split.left_sum.GetHess();
|
||||
|
||||
auto build_nidx = left_nidx;
|
||||
auto subtract_nidx = right_nidx;
|
||||
if (fewer_right) {
|
||||
std::swap(build_nidx, subtract_nidx);
|
||||
}
|
||||
nodes_to_build[n_idx] = CPUExpandEntry{build_nidx, p_tree->GetDepth(build_nidx), {}};
|
||||
nodes_to_sub[n_idx] = CPUExpandEntry{subtract_nidx, p_tree->GetDepth(subtract_nidx), {}};
|
||||
n_idx++;
|
||||
}
|
||||
|
||||
size_t page_id{0};
|
||||
auto space = ConstructHistSpace(partitioner_, nodes_to_build);
|
||||
for (auto const &gidx : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
||||
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
||||
nodes_to_sub, gpair);
|
||||
++page_id;
|
||||
}
|
||||
}
|
||||
|
||||
void QuantileHistMaker::Builder::LeafPartition(RegTree const &tree,
|
||||
common::Span<GradientPair const> gpair,
|
||||
std::vector<bst_node_t> *p_out_position) {
|
||||
template <typename ExpandEntry, typename Updater>
|
||||
void UpdateTree(common::Monitor *monitor_, linalg::MatrixView<GradientPair const> gpair,
|
||||
Updater *updater, DMatrix *p_fmat, TrainParam const *param,
|
||||
HostDeviceVector<bst_node_t> *p_out_position, RegTree *p_tree) {
|
||||
monitor_->Start(__func__);
|
||||
if (!task_.UpdateTreeLeaf()) {
|
||||
return;
|
||||
}
|
||||
for (auto const &part : partitioner_) {
|
||||
part.LeafPartition(ctx_, tree, gpair, p_out_position);
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
updater->InitData(p_fmat, p_tree);
|
||||
|
||||
void QuantileHistMaker::Builder::ExpandTree(DMatrix *p_fmat, RegTree *p_tree,
|
||||
const std::vector<GradientPair> &gpair_h,
|
||||
HostDeviceVector<bst_node_t> *p_out_position) {
|
||||
monitor_->Start(__func__);
|
||||
|
||||
Driver<CPUExpandEntry> driver(*param_);
|
||||
driver.Push(this->InitRoot(p_fmat, p_tree, gpair_h));
|
||||
Driver<ExpandEntry> driver{*param};
|
||||
auto const &tree = *p_tree;
|
||||
driver.Push(updater->InitRoot(p_fmat, gpair, p_tree));
|
||||
auto expand_set = driver.Pop();
|
||||
|
||||
/**
|
||||
* Note for update position
|
||||
* Root:
|
||||
* Not applied: No need to update position as initialization has got all the rows ordered.
|
||||
* Applied: Update position is run on applied nodes so the rows are partitioned.
|
||||
* Non-root:
|
||||
* Not applied: That node is root of the subtree, same rule as root.
|
||||
* Applied: Ditto
|
||||
*/
|
||||
while (!expand_set.empty()) {
|
||||
// candidates that can be further splited.
|
||||
std::vector<CPUExpandEntry> valid_candidates;
|
||||
std::vector<ExpandEntry> valid_candidates;
|
||||
// candidaates that can be applied.
|
||||
std::vector<CPUExpandEntry> applied;
|
||||
int32_t depth = expand_set.front().depth + 1;
|
||||
for (auto const& candidate : expand_set) {
|
||||
evaluator_->ApplyTreeSplit(candidate, p_tree);
|
||||
std::vector<ExpandEntry> applied;
|
||||
for (auto const &candidate : expand_set) {
|
||||
updater->ApplyTreeSplit(candidate, p_tree);
|
||||
CHECK_GT(p_tree->LeftChild(candidate.nid), candidate.nid);
|
||||
applied.push_back(candidate);
|
||||
if (driver.IsChildValid(candidate)) {
|
||||
valid_candidates.emplace_back(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
monitor_->Start("UpdatePosition");
|
||||
size_t page_id{0};
|
||||
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
partitioner_.at(page_id).UpdatePosition(ctx_, page, applied, p_tree);
|
||||
++page_id;
|
||||
}
|
||||
monitor_->Stop("UpdatePosition");
|
||||
updater->UpdatePosition(p_fmat, p_tree, applied);
|
||||
|
||||
std::vector<CPUExpandEntry> best_splits;
|
||||
std::vector<ExpandEntry> best_splits;
|
||||
if (!valid_candidates.empty()) {
|
||||
this->BuildHistogram(p_fmat, p_tree, valid_candidates, gpair_h);
|
||||
updater->BuildHistogram(p_fmat, p_tree, valid_candidates, gpair);
|
||||
for (auto const &candidate : valid_candidates) {
|
||||
int left_child_nidx = tree[candidate.nid].LeftChild();
|
||||
int right_child_nidx = tree[candidate.nid].RightChild();
|
||||
CPUExpandEntry l_best{left_child_nidx, depth, 0.0};
|
||||
CPUExpandEntry r_best{right_child_nidx, depth, 0.0};
|
||||
auto left_child_nidx = tree.LeftChild(candidate.nid);
|
||||
auto right_child_nidx = tree.RightChild(candidate.nid);
|
||||
ExpandEntry l_best{left_child_nidx, tree.GetDepth(left_child_nidx)};
|
||||
ExpandEntry r_best{right_child_nidx, tree.GetDepth(right_child_nidx)};
|
||||
best_splits.push_back(l_best);
|
||||
best_splits.push_back(r_best);
|
||||
}
|
||||
auto const &histograms = histogram_builder_->Histogram();
|
||||
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
evaluator_->EvaluateSplits(histograms, gmat.cut, ft, *p_tree, &best_splits);
|
||||
break;
|
||||
}
|
||||
updater->EvaluateSplits(p_fmat, p_tree, &best_splits);
|
||||
}
|
||||
driver.Push(best_splits.begin(), best_splits.end());
|
||||
expand_set = driver.Pop();
|
||||
}
|
||||
|
||||
auto &h_out_position = p_out_position->HostVector();
|
||||
this->LeafPartition(tree, gpair_h, &h_out_position);
|
||||
updater->LeafPartition(tree, gpair, &h_out_position);
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
void QuantileHistMaker::Builder::UpdateTree(HostDeviceVector<GradientPair> *gpair, DMatrix *p_fmat,
|
||||
RegTree *p_tree,
|
||||
HostDeviceVector<bst_node_t> *p_out_position) {
|
||||
monitor_->Start(__func__);
|
||||
/**
|
||||
* \brief Updater for building multi-target trees. The implementation simply iterates over
|
||||
* each target.
|
||||
*/
|
||||
class MultiTargetHistBuilder {
|
||||
private:
|
||||
common::Monitor *monitor_{nullptr};
|
||||
TrainParam const *param_{nullptr};
|
||||
std::shared_ptr<common::ColumnSampler> col_sampler_;
|
||||
std::unique_ptr<HistMultiEvaluator> evaluator_;
|
||||
// Histogram builder for each target.
|
||||
std::vector<HistogramBuilder<MultiExpandEntry>> histogram_builder_;
|
||||
Context const *ctx_{nullptr};
|
||||
// Partitioner for each data batch.
|
||||
std::vector<CommonRowPartitioner> partitioner_;
|
||||
// Pointer to last updated tree, used for update prediction cache.
|
||||
RegTree const *p_last_tree_{nullptr};
|
||||
|
||||
std::vector<GradientPair> *gpair_ptr = &(gpair->HostVector());
|
||||
// in case 'num_parallel_trees != 1' no posibility to change initial gpair
|
||||
if (GetNumberOfTrees() != 1) {
|
||||
gpair_local_.resize(gpair_ptr->size());
|
||||
gpair_local_ = *gpair_ptr;
|
||||
gpair_ptr = &gpair_local_;
|
||||
ObjInfo const *task_{nullptr};
|
||||
|
||||
public:
|
||||
void UpdatePosition(DMatrix *p_fmat, RegTree const *p_tree,
|
||||
std::vector<MultiExpandEntry> const &applied) {
|
||||
monitor_->Start(__func__);
|
||||
std::size_t page_id{0};
|
||||
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(this->param_))) {
|
||||
this->partitioner_.at(page_id).UpdatePosition(this->ctx_, page, applied, p_tree);
|
||||
page_id++;
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
this->InitData(p_fmat, *p_tree, gpair_ptr);
|
||||
|
||||
ExpandTree(p_fmat, p_tree, *gpair_ptr, p_out_position);
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
bool QuantileHistMaker::Builder::UpdatePredictionCache(DMatrix const *data,
|
||||
linalg::VectorView<float> out_preds) const {
|
||||
// p_last_fmat_ is a valid pointer as long as UpdatePredictionCache() is called in
|
||||
// conjunction with Update().
|
||||
if (!p_last_fmat_ || !p_last_tree_ || data != p_last_fmat_) {
|
||||
return false;
|
||||
void ApplyTreeSplit(MultiExpandEntry const &candidate, RegTree *p_tree) {
|
||||
this->evaluator_->ApplyTreeSplit(candidate, p_tree);
|
||||
}
|
||||
monitor_->Start(__func__);
|
||||
CHECK_EQ(out_preds.Size(), data->Info().num_row_);
|
||||
UpdatePredictionCacheImpl(ctx_, p_last_tree_, partitioner_, out_preds);
|
||||
monitor_->Stop(__func__);
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t QuantileHistMaker::Builder::GetNumberOfTrees() { return n_trees_; }
|
||||
void InitData(DMatrix *p_fmat, RegTree const *p_tree) {
|
||||
monitor_->Start(__func__);
|
||||
|
||||
void QuantileHistMaker::Builder::InitData(DMatrix *fmat, const RegTree &tree,
|
||||
std::vector<GradientPair> *gpair) {
|
||||
monitor_->Start(__func__);
|
||||
const auto& info = fmat->Info();
|
||||
std::size_t page_id = 0;
|
||||
bst_bin_t n_total_bins = 0;
|
||||
partitioner_.clear();
|
||||
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
if (n_total_bins == 0) {
|
||||
n_total_bins = page.cut.TotalBins();
|
||||
} else {
|
||||
CHECK_EQ(n_total_bins, page.cut.TotalBins());
|
||||
}
|
||||
partitioner_.emplace_back(ctx_, page.Size(), page.base_rowid, p_fmat->IsColumnSplit());
|
||||
page_id++;
|
||||
}
|
||||
|
||||
{
|
||||
size_t page_id{0};
|
||||
int32_t n_total_bins{0};
|
||||
bst_target_t n_targets = p_tree->NumTargets();
|
||||
histogram_builder_.clear();
|
||||
for (std::size_t i = 0; i < n_targets; ++i) {
|
||||
histogram_builder_.emplace_back();
|
||||
histogram_builder_.back().Reset(n_total_bins, HistBatch(param_), ctx_->Threads(), page_id,
|
||||
collective::IsDistributed(), p_fmat->IsColumnSplit());
|
||||
}
|
||||
|
||||
evaluator_ = std::make_unique<HistMultiEvaluator>(ctx_, p_fmat->Info(), param_, col_sampler_);
|
||||
p_last_tree_ = p_tree;
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
MultiExpandEntry InitRoot(DMatrix *p_fmat, linalg::MatrixView<GradientPair const> gpair,
|
||||
RegTree *p_tree) {
|
||||
monitor_->Start(__func__);
|
||||
MultiExpandEntry best;
|
||||
best.nid = RegTree::kRoot;
|
||||
best.depth = 0;
|
||||
|
||||
auto n_targets = p_tree->NumTargets();
|
||||
linalg::Matrix<GradientPairPrecise> root_sum_tloc =
|
||||
linalg::Empty<GradientPairPrecise>(ctx_, ctx_->Threads(), n_targets);
|
||||
CHECK_EQ(root_sum_tloc.Shape(1), gpair.Shape(1));
|
||||
auto h_root_sum_tloc = root_sum_tloc.HostView();
|
||||
common::ParallelFor(gpair.Shape(0), ctx_->Threads(), [&](auto i) {
|
||||
for (bst_target_t t{0}; t < n_targets; ++t) {
|
||||
h_root_sum_tloc(omp_get_thread_num(), t) += GradientPairPrecise{gpair(i, t)};
|
||||
}
|
||||
});
|
||||
// Aggregate to the first row.
|
||||
auto root_sum = h_root_sum_tloc.Slice(0, linalg::All());
|
||||
for (std::int32_t tidx{1}; tidx < ctx_->Threads(); ++tidx) {
|
||||
for (bst_target_t t{0}; t < n_targets; ++t) {
|
||||
root_sum(t) += h_root_sum_tloc(tidx, t);
|
||||
}
|
||||
}
|
||||
CHECK(root_sum.CContiguous());
|
||||
collective::Allreduce<collective::Operation::kSum>(
|
||||
reinterpret_cast<double *>(root_sum.Values().data()), root_sum.Size() * 2);
|
||||
|
||||
std::vector<MultiExpandEntry> nodes{best};
|
||||
std::size_t i = 0;
|
||||
auto space = ConstructHistSpace(partitioner_, nodes);
|
||||
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
for (bst_target_t t{0}; t < n_targets; ++t) {
|
||||
auto t_gpair = gpair.Slice(linalg::All(), t);
|
||||
histogram_builder_[t].BuildHist(i, space, page, p_tree, partitioner_.at(i).Partitions(),
|
||||
nodes, {}, t_gpair.Values());
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
auto weight = evaluator_->InitRoot(root_sum);
|
||||
auto weight_t = weight.HostView();
|
||||
std::transform(linalg::cbegin(weight_t), linalg::cend(weight_t), linalg::begin(weight_t),
|
||||
[&](float w) { return w * param_->learning_rate; });
|
||||
|
||||
p_tree->SetLeaf(RegTree::kRoot, weight_t);
|
||||
std::vector<common::HistCollection const *> hists;
|
||||
for (bst_target_t t{0}; t < p_tree->NumTargets(); ++t) {
|
||||
hists.push_back(&histogram_builder_[t].Histogram());
|
||||
}
|
||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
evaluator_->EvaluateSplits(*p_tree, hists, gmat.cut, &nodes);
|
||||
break;
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
|
||||
return nodes.front();
|
||||
}
|
||||
|
||||
void BuildHistogram(DMatrix *p_fmat, RegTree const *p_tree,
|
||||
std::vector<MultiExpandEntry> const &valid_candidates,
|
||||
linalg::MatrixView<GradientPair const> gpair) {
|
||||
monitor_->Start(__func__);
|
||||
std::vector<MultiExpandEntry> nodes_to_build;
|
||||
std::vector<MultiExpandEntry> nodes_to_sub;
|
||||
|
||||
for (auto const &c : valid_candidates) {
|
||||
auto left_nidx = p_tree->LeftChild(c.nid);
|
||||
auto right_nidx = p_tree->RightChild(c.nid);
|
||||
|
||||
auto build_nidx = left_nidx;
|
||||
auto subtract_nidx = right_nidx;
|
||||
auto lit =
|
||||
common::MakeIndexTransformIter([&](auto i) { return c.split.left_sum[i].GetHess(); });
|
||||
auto left_sum = std::accumulate(lit, lit + c.split.left_sum.size(), .0);
|
||||
auto rit =
|
||||
common::MakeIndexTransformIter([&](auto i) { return c.split.right_sum[i].GetHess(); });
|
||||
auto right_sum = std::accumulate(rit, rit + c.split.right_sum.size(), .0);
|
||||
auto fewer_right = right_sum < left_sum;
|
||||
if (fewer_right) {
|
||||
std::swap(build_nidx, subtract_nidx);
|
||||
}
|
||||
nodes_to_build.emplace_back(build_nidx, p_tree->GetDepth(build_nidx));
|
||||
nodes_to_sub.emplace_back(subtract_nidx, p_tree->GetDepth(subtract_nidx));
|
||||
}
|
||||
|
||||
std::size_t i = 0;
|
||||
auto space = ConstructHistSpace(partitioner_, nodes_to_build);
|
||||
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
for (std::size_t t = 0; t < p_tree->NumTargets(); ++t) {
|
||||
auto t_gpair = gpair.Slice(linalg::All(), t);
|
||||
// Make sure the gradient matrix is f-order.
|
||||
CHECK(t_gpair.Contiguous());
|
||||
histogram_builder_[t].BuildHist(i, space, page, p_tree, partitioner_.at(i).Partitions(),
|
||||
nodes_to_build, nodes_to_sub, t_gpair.Values());
|
||||
}
|
||||
i++;
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
void EvaluateSplits(DMatrix *p_fmat, RegTree const *p_tree,
|
||||
std::vector<MultiExpandEntry> *best_splits) {
|
||||
monitor_->Start(__func__);
|
||||
std::vector<common::HistCollection const *> hists;
|
||||
for (bst_target_t t{0}; t < p_tree->NumTargets(); ++t) {
|
||||
hists.push_back(&histogram_builder_[t].Histogram());
|
||||
}
|
||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
evaluator_->EvaluateSplits(*p_tree, hists, gmat.cut, best_splits);
|
||||
break;
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
void LeafPartition(RegTree const &tree, linalg::MatrixView<GradientPair const> gpair,
|
||||
std::vector<bst_node_t> *p_out_position) {
|
||||
monitor_->Start(__func__);
|
||||
if (!task_->UpdateTreeLeaf()) {
|
||||
return;
|
||||
}
|
||||
for (auto const &part : partitioner_) {
|
||||
part.LeafPartition(ctx_, tree, gpair, p_out_position);
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit MultiTargetHistBuilder(Context const *ctx, MetaInfo const &info, TrainParam const *param,
|
||||
std::shared_ptr<common::ColumnSampler> column_sampler,
|
||||
ObjInfo const *task, common::Monitor *monitor)
|
||||
: monitor_{monitor},
|
||||
param_{param},
|
||||
col_sampler_{std::move(column_sampler)},
|
||||
evaluator_{std::make_unique<HistMultiEvaluator>(ctx, info, param, col_sampler_)},
|
||||
ctx_{ctx},
|
||||
task_{task} {
|
||||
monitor_->Init(__func__);
|
||||
}
|
||||
};
|
||||
|
||||
class HistBuilder {
|
||||
private:
|
||||
common::Monitor *monitor_;
|
||||
TrainParam const *param_;
|
||||
std::shared_ptr<common::ColumnSampler> col_sampler_;
|
||||
std::unique_ptr<HistEvaluator<CPUExpandEntry>> evaluator_;
|
||||
std::vector<CommonRowPartitioner> partitioner_;
|
||||
|
||||
// back pointers to tree and data matrix
|
||||
const RegTree *p_last_tree_{nullptr};
|
||||
DMatrix const *const p_last_fmat_{nullptr};
|
||||
|
||||
std::unique_ptr<HistogramBuilder<CPUExpandEntry>> histogram_builder_;
|
||||
ObjInfo const *task_{nullptr};
|
||||
// Context for number of threads
|
||||
Context const *ctx_{nullptr};
|
||||
|
||||
public:
|
||||
explicit HistBuilder(Context const *ctx, std::shared_ptr<common::ColumnSampler> column_sampler,
|
||||
TrainParam const *param, DMatrix const *fmat, ObjInfo const *task,
|
||||
common::Monitor *monitor)
|
||||
: monitor_{monitor},
|
||||
param_{param},
|
||||
col_sampler_{std::move(column_sampler)},
|
||||
evaluator_{std::make_unique<HistEvaluator<CPUExpandEntry>>(ctx, param, fmat->Info(),
|
||||
col_sampler_)},
|
||||
p_last_fmat_(fmat),
|
||||
histogram_builder_{new HistogramBuilder<CPUExpandEntry>},
|
||||
task_{task},
|
||||
ctx_{ctx} {
|
||||
monitor_->Init(__func__);
|
||||
}
|
||||
|
||||
bool UpdatePredictionCache(DMatrix const *data, linalg::VectorView<float> out_preds) const {
|
||||
// p_last_fmat_ is a valid pointer as long as UpdatePredictionCache() is called in
|
||||
// conjunction with Update().
|
||||
if (!p_last_fmat_ || !p_last_tree_ || data != p_last_fmat_) {
|
||||
return false;
|
||||
}
|
||||
monitor_->Start(__func__);
|
||||
CHECK_EQ(out_preds.Size(), data->Info().num_row_);
|
||||
UpdatePredictionCacheImpl(ctx_, p_last_tree_, partitioner_, out_preds);
|
||||
monitor_->Stop(__func__);
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
// initialize temp data structure
|
||||
void InitData(DMatrix *fmat, RegTree const *p_tree) {
|
||||
monitor_->Start(__func__);
|
||||
std::size_t page_id{0};
|
||||
bst_bin_t n_total_bins{0};
|
||||
partitioner_.clear();
|
||||
for (auto const &page : fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
if (n_total_bins == 0) {
|
||||
@@ -273,22 +378,227 @@ void QuantileHistMaker::Builder::InitData(DMatrix *fmat, const RegTree &tree,
|
||||
}
|
||||
histogram_builder_->Reset(n_total_bins, HistBatch(param_), ctx_->Threads(), page_id,
|
||||
collective::IsDistributed(), fmat->IsColumnSplit());
|
||||
|
||||
auto m_gpair = linalg::MakeTensorView(ctx_, *gpair, gpair->size(), static_cast<std::size_t>(1));
|
||||
SampleGradient(ctx_, *param_, m_gpair);
|
||||
evaluator_ = std::make_unique<HistEvaluator<CPUExpandEntry>>(ctx_, this->param_, fmat->Info(),
|
||||
col_sampler_);
|
||||
p_last_tree_ = p_tree;
|
||||
}
|
||||
|
||||
// store a pointer to the tree
|
||||
p_last_tree_ = &tree;
|
||||
evaluator_.reset(new HistEvaluator<CPUExpandEntry>{ctx_, param_, info, column_sampler_});
|
||||
void EvaluateSplits(DMatrix *p_fmat, RegTree const *p_tree,
|
||||
std::vector<CPUExpandEntry> *best_splits) {
|
||||
monitor_->Start(__func__);
|
||||
auto const &histograms = histogram_builder_->Histogram();
|
||||
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
evaluator_->EvaluateSplits(histograms, gmat.cut, ft, *p_tree, best_splits);
|
||||
break;
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
void ApplyTreeSplit(CPUExpandEntry const &candidate, RegTree *p_tree) {
|
||||
this->evaluator_->ApplyTreeSplit(candidate, p_tree);
|
||||
}
|
||||
|
||||
CPUExpandEntry InitRoot(DMatrix *p_fmat, linalg::MatrixView<GradientPair const> gpair,
|
||||
RegTree *p_tree) {
|
||||
CPUExpandEntry node(RegTree::kRoot, p_tree->GetDepth(0));
|
||||
|
||||
std::size_t page_id = 0;
|
||||
auto space = ConstructHistSpace(partitioner_, {node});
|
||||
for (auto const &gidx : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
std::vector<CPUExpandEntry> nodes_to_build{node};
|
||||
std::vector<CPUExpandEntry> nodes_to_sub;
|
||||
this->histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
||||
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
||||
nodes_to_sub, gpair.Slice(linalg::All(), 0).Values());
|
||||
++page_id;
|
||||
}
|
||||
|
||||
{
|
||||
GradientPairPrecise grad_stat;
|
||||
if (p_fmat->IsDense()) {
|
||||
/**
|
||||
* Specialized code for dense data: For dense data (with no missing value), the sum
|
||||
* of gradient histogram is equal to snode[nid]
|
||||
*/
|
||||
auto const &gmat = *(p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_)).begin());
|
||||
std::vector<std::uint32_t> const &row_ptr = gmat.cut.Ptrs();
|
||||
CHECK_GE(row_ptr.size(), 2);
|
||||
std::uint32_t const ibegin = row_ptr[0];
|
||||
std::uint32_t const iend = row_ptr[1];
|
||||
auto hist = this->histogram_builder_->Histogram()[RegTree::kRoot];
|
||||
auto begin = hist.data();
|
||||
for (std::uint32_t i = ibegin; i < iend; ++i) {
|
||||
GradientPairPrecise const &et = begin[i];
|
||||
grad_stat.Add(et.GetGrad(), et.GetHess());
|
||||
}
|
||||
} else {
|
||||
auto gpair_h = gpair.Slice(linalg::All(), 0).Values();
|
||||
for (auto const &grad : gpair_h) {
|
||||
grad_stat.Add(grad.GetGrad(), grad.GetHess());
|
||||
}
|
||||
collective::Allreduce<collective::Operation::kSum>(reinterpret_cast<double *>(&grad_stat),
|
||||
2);
|
||||
}
|
||||
|
||||
auto weight = evaluator_->InitRoot(GradStats{grad_stat});
|
||||
p_tree->Stat(RegTree::kRoot).sum_hess = grad_stat.GetHess();
|
||||
p_tree->Stat(RegTree::kRoot).base_weight = weight;
|
||||
(*p_tree)[RegTree::kRoot].SetLeaf(param_->learning_rate * weight);
|
||||
|
||||
std::vector<CPUExpandEntry> entries{node};
|
||||
monitor_->Start("EvaluateSplits");
|
||||
auto ft = p_fmat->Info().feature_types.ConstHostSpan();
|
||||
for (auto const &gmat : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
evaluator_->EvaluateSplits(histogram_builder_->Histogram(), gmat.cut, ft, *p_tree,
|
||||
&entries);
|
||||
break;
|
||||
}
|
||||
monitor_->Stop("EvaluateSplits");
|
||||
node = entries.front();
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void BuildHistogram(DMatrix *p_fmat, RegTree *p_tree,
|
||||
std::vector<CPUExpandEntry> const &valid_candidates,
|
||||
linalg::MatrixView<GradientPair const> gpair) {
|
||||
std::vector<CPUExpandEntry> nodes_to_build(valid_candidates.size());
|
||||
std::vector<CPUExpandEntry> nodes_to_sub(valid_candidates.size());
|
||||
|
||||
std::size_t n_idx = 0;
|
||||
for (auto const &c : valid_candidates) {
|
||||
auto left_nidx = (*p_tree)[c.nid].LeftChild();
|
||||
auto right_nidx = (*p_tree)[c.nid].RightChild();
|
||||
auto fewer_right = c.split.right_sum.GetHess() < c.split.left_sum.GetHess();
|
||||
|
||||
auto build_nidx = left_nidx;
|
||||
auto subtract_nidx = right_nidx;
|
||||
if (fewer_right) {
|
||||
std::swap(build_nidx, subtract_nidx);
|
||||
}
|
||||
nodes_to_build[n_idx] = CPUExpandEntry{build_nidx, p_tree->GetDepth(build_nidx), {}};
|
||||
nodes_to_sub[n_idx] = CPUExpandEntry{subtract_nidx, p_tree->GetDepth(subtract_nidx), {}};
|
||||
n_idx++;
|
||||
}
|
||||
|
||||
std::size_t page_id{0};
|
||||
auto space = ConstructHistSpace(partitioner_, nodes_to_build);
|
||||
for (auto const &gidx : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(param_))) {
|
||||
histogram_builder_->BuildHist(page_id, space, gidx, p_tree,
|
||||
partitioner_.at(page_id).Partitions(), nodes_to_build,
|
||||
nodes_to_sub, gpair.Values());
|
||||
++page_id;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdatePosition(DMatrix *p_fmat, RegTree const *p_tree,
|
||||
std::vector<CPUExpandEntry> const &applied) {
|
||||
monitor_->Start(__func__);
|
||||
std::size_t page_id{0};
|
||||
for (auto const &page : p_fmat->GetBatches<GHistIndexMatrix>(HistBatch(this->param_))) {
|
||||
this->partitioner_.at(page_id).UpdatePosition(this->ctx_, page, applied, p_tree);
|
||||
page_id++;
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
|
||||
void LeafPartition(RegTree const &tree, linalg::MatrixView<GradientPair const> gpair,
|
||||
std::vector<bst_node_t> *p_out_position) {
|
||||
monitor_->Start(__func__);
|
||||
if (!task_->UpdateTreeLeaf()) {
|
||||
return;
|
||||
}
|
||||
for (auto const &part : partitioner_) {
|
||||
part.LeafPartition(ctx_, tree, gpair, p_out_position);
|
||||
}
|
||||
monitor_->Stop(__func__);
|
||||
}
|
||||
};
|
||||
|
||||
/*! \brief construct a tree using quantized feature values */
|
||||
class QuantileHistMaker : public TreeUpdater {
|
||||
std::unique_ptr<HistBuilder> p_impl_{nullptr};
|
||||
std::unique_ptr<MultiTargetHistBuilder> p_mtimpl_{nullptr};
|
||||
std::shared_ptr<common::ColumnSampler> column_sampler_ =
|
||||
std::make_shared<common::ColumnSampler>();
|
||||
common::Monitor monitor_;
|
||||
ObjInfo const *task_{nullptr};
|
||||
|
||||
public:
|
||||
explicit QuantileHistMaker(Context const *ctx, ObjInfo const *task)
|
||||
: TreeUpdater{ctx}, task_{task} {}
|
||||
void Configure(const Args &) override {}
|
||||
|
||||
void LoadConfig(Json const &) override {}
|
||||
void SaveConfig(Json *) const override {}
|
||||
|
||||
[[nodiscard]] char const *Name() const override { return "grow_quantile_histmaker"; }
|
||||
|
||||
void Update(TrainParam const *param, HostDeviceVector<GradientPair> *gpair, DMatrix *p_fmat,
|
||||
common::Span<HostDeviceVector<bst_node_t>> out_position,
|
||||
const std::vector<RegTree *> &trees) override {
|
||||
if (trees.front()->IsMultiTarget()) {
|
||||
CHECK(param->monotone_constraints.empty()) << "monotone constraint" << MTNotImplemented();
|
||||
if (!p_mtimpl_) {
|
||||
this->p_mtimpl_ = std::make_unique<MultiTargetHistBuilder>(
|
||||
ctx_, p_fmat->Info(), param, column_sampler_, task_, &monitor_);
|
||||
}
|
||||
} else {
|
||||
if (!p_impl_) {
|
||||
p_impl_ =
|
||||
std::make_unique<HistBuilder>(ctx_, column_sampler_, param, p_fmat, task_, &monitor_);
|
||||
}
|
||||
}
|
||||
|
||||
bst_target_t n_targets = trees.front()->NumTargets();
|
||||
auto h_gpair =
|
||||
linalg::MakeTensorView(ctx_, gpair->HostSpan(), p_fmat->Info().num_row_, n_targets);
|
||||
|
||||
linalg::Matrix<GradientPair> sample_out;
|
||||
auto h_sample_out = h_gpair;
|
||||
auto need_copy = [&] { return trees.size() > 1 || n_targets > 1; };
|
||||
if (need_copy()) {
|
||||
// allocate buffer
|
||||
sample_out = decltype(sample_out){h_gpair.Shape(), ctx_->gpu_id, linalg::Order::kF};
|
||||
h_sample_out = sample_out.HostView();
|
||||
}
|
||||
|
||||
for (auto tree_it = trees.begin(); tree_it != trees.end(); ++tree_it) {
|
||||
if (need_copy()) {
|
||||
// Copy gradient into buffer for sampling. This converts C-order to F-order.
|
||||
std::copy(linalg::cbegin(h_gpair), linalg::cend(h_gpair), linalg::begin(h_sample_out));
|
||||
}
|
||||
SampleGradient(ctx_, *param, h_sample_out);
|
||||
auto *h_out_position = &out_position[tree_it - trees.begin()];
|
||||
if ((*tree_it)->IsMultiTarget()) {
|
||||
UpdateTree<MultiExpandEntry>(&monitor_, h_sample_out, p_mtimpl_.get(), p_fmat, param,
|
||||
h_out_position, *tree_it);
|
||||
} else {
|
||||
UpdateTree<CPUExpandEntry>(&monitor_, h_sample_out, p_impl_.get(), p_fmat, param,
|
||||
h_out_position, *tree_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdatePredictionCache(const DMatrix *data, linalg::VectorView<float> out_preds) override {
|
||||
if (p_impl_) {
|
||||
return p_impl_->UpdatePredictionCache(data, out_preds);
|
||||
} else if (p_mtimpl_) {
|
||||
// Not yet supported.
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasNodePosition() const override { return true; }
|
||||
};
|
||||
|
||||
XGBOOST_REGISTER_TREE_UPDATER(QuantileHistMaker, "grow_quantile_histmaker")
|
||||
.describe("Grow tree using quantized histogram.")
|
||||
.set_body([](Context const *ctx, ObjInfo const *task) {
|
||||
return new QuantileHistMaker(ctx, task);
|
||||
return new QuantileHistMaker{ctx, task};
|
||||
});
|
||||
} // namespace tree
|
||||
} // namespace xgboost
|
||||
} // namespace xgboost::tree
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*!
|
||||
* Copyright 2017-2022 by XGBoost Contributors
|
||||
* \file updater_quantile_hist.h
|
||||
* \brief use quantized feature values to construct a tree
|
||||
* \author Philip Cho, Tianqi Chen, Egor Smirnov
|
||||
*/
|
||||
#ifndef XGBOOST_TREE_UPDATER_QUANTILE_HIST_H_
|
||||
#define XGBOOST_TREE_UPDATER_QUANTILE_HIST_H_
|
||||
|
||||
#include <xgboost/tree_updater.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xgboost/base.h"
|
||||
#include "xgboost/data.h"
|
||||
#include "xgboost/json.h"
|
||||
|
||||
#include "hist/evaluate_splits.h"
|
||||
#include "hist/histogram.h"
|
||||
#include "hist/expand_entry.h"
|
||||
|
||||
#include "common_row_partitioner.h"
|
||||
#include "constraints.h"
|
||||
#include "./param.h"
|
||||
#include "./driver.h"
|
||||
#include "../common/random.h"
|
||||
#include "../common/timer.h"
|
||||
#include "../common/hist_util.h"
|
||||
#include "../common/row_set.h"
|
||||
#include "../common/partition_builder.h"
|
||||
#include "../common/column_matrix.h"
|
||||
|
||||
namespace xgboost::tree {
|
||||
inline BatchParam HistBatch(TrainParam const* param) {
|
||||
return {param->max_bin, param->sparse_threshold};
|
||||
}
|
||||
|
||||
/*! \brief construct a tree using quantized feature values */
|
||||
class QuantileHistMaker: public TreeUpdater {
|
||||
public:
|
||||
explicit QuantileHistMaker(Context const* ctx, ObjInfo const* task)
|
||||
: TreeUpdater(ctx), task_{task} {}
|
||||
void Configure(const Args&) override {}
|
||||
|
||||
void Update(TrainParam const* param, HostDeviceVector<GradientPair>* gpair, DMatrix* dmat,
|
||||
common::Span<HostDeviceVector<bst_node_t>> out_position,
|
||||
const std::vector<RegTree*>& trees) override;
|
||||
|
||||
bool UpdatePredictionCache(const DMatrix *data,
|
||||
linalg::VectorView<float> out_preds) override;
|
||||
|
||||
void LoadConfig(Json const&) override {}
|
||||
void SaveConfig(Json*) const override {}
|
||||
|
||||
[[nodiscard]] char const* Name() const override { return "grow_quantile_histmaker"; }
|
||||
[[nodiscard]] bool HasNodePosition() const override { return true; }
|
||||
|
||||
protected:
|
||||
// actual builder that runs the algorithm
|
||||
struct Builder {
|
||||
public:
|
||||
// constructor
|
||||
explicit Builder(const size_t n_trees, TrainParam const* param, DMatrix const* fmat,
|
||||
ObjInfo task, Context const* ctx)
|
||||
: n_trees_(n_trees),
|
||||
param_(param),
|
||||
p_last_fmat_(fmat),
|
||||
histogram_builder_{new HistogramBuilder<CPUExpandEntry>},
|
||||
task_{task},
|
||||
ctx_{ctx},
|
||||
monitor_{std::make_unique<common::Monitor>()} {
|
||||
monitor_->Init("Quantile::Builder");
|
||||
}
|
||||
// update one tree, growing
|
||||
void UpdateTree(HostDeviceVector<GradientPair>* gpair, DMatrix* p_fmat, RegTree* p_tree,
|
||||
HostDeviceVector<bst_node_t>* p_out_position);
|
||||
|
||||
bool UpdatePredictionCache(DMatrix const* data, linalg::VectorView<float> out_preds) const;
|
||||
|
||||
private:
|
||||
// initialize temp data structure
|
||||
void InitData(DMatrix* fmat, const RegTree& tree, std::vector<GradientPair>* gpair);
|
||||
|
||||
size_t GetNumberOfTrees();
|
||||
|
||||
CPUExpandEntry InitRoot(DMatrix* p_fmat, RegTree* p_tree,
|
||||
const std::vector<GradientPair>& gpair_h);
|
||||
|
||||
void BuildHistogram(DMatrix* p_fmat, RegTree* p_tree,
|
||||
std::vector<CPUExpandEntry> const& valid_candidates,
|
||||
std::vector<GradientPair> const& gpair);
|
||||
|
||||
void LeafPartition(RegTree const& tree, common::Span<GradientPair const> gpair,
|
||||
std::vector<bst_node_t>* p_out_position);
|
||||
|
||||
void ExpandTree(DMatrix* p_fmat, RegTree* p_tree, const std::vector<GradientPair>& gpair_h,
|
||||
HostDeviceVector<bst_node_t>* p_out_position);
|
||||
|
||||
private:
|
||||
const size_t n_trees_;
|
||||
TrainParam const* param_;
|
||||
std::shared_ptr<common::ColumnSampler> column_sampler_{
|
||||
std::make_shared<common::ColumnSampler>()};
|
||||
|
||||
std::vector<GradientPair> gpair_local_;
|
||||
|
||||
std::unique_ptr<HistEvaluator<CPUExpandEntry>> evaluator_;
|
||||
std::vector<CommonRowPartitioner> partitioner_;
|
||||
|
||||
// back pointers to tree and data matrix
|
||||
const RegTree* p_last_tree_{nullptr};
|
||||
DMatrix const* const p_last_fmat_;
|
||||
|
||||
std::unique_ptr<HistogramBuilder<CPUExpandEntry>> histogram_builder_;
|
||||
ObjInfo task_;
|
||||
// Context for number of threads
|
||||
Context const* ctx_;
|
||||
|
||||
std::unique_ptr<common::Monitor> monitor_;
|
||||
};
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Builder> pimpl_;
|
||||
ObjInfo const* task_;
|
||||
};
|
||||
} // namespace xgboost::tree
|
||||
|
||||
#endif // XGBOOST_TREE_UPDATER_QUANTILE_HIST_H_
|
||||
@@ -50,11 +50,11 @@ class TreeRefresher : public TreeUpdater {
|
||||
int tid = omp_get_thread_num();
|
||||
int num_nodes = 0;
|
||||
for (auto tree : trees) {
|
||||
num_nodes += tree->param.num_nodes;
|
||||
num_nodes += tree->NumNodes();
|
||||
}
|
||||
stemp[tid].resize(num_nodes, GradStats());
|
||||
std::fill(stemp[tid].begin(), stemp[tid].end(), GradStats());
|
||||
fvec_temp[tid].Init(trees[0]->param.num_feature);
|
||||
fvec_temp[tid].Init(trees[0]->NumFeatures());
|
||||
});
|
||||
}
|
||||
exc.Rethrow();
|
||||
@@ -77,7 +77,7 @@ class TreeRefresher : public TreeUpdater {
|
||||
for (auto tree : trees) {
|
||||
AddStats(*tree, feats, gpair_h, info, ridx,
|
||||
dmlc::BeginPtr(stemp[tid]) + offset);
|
||||
offset += tree->param.num_nodes;
|
||||
offset += tree->NumNodes();
|
||||
}
|
||||
feats.Drop(inst);
|
||||
});
|
||||
@@ -96,7 +96,7 @@ class TreeRefresher : public TreeUpdater {
|
||||
int offset = 0;
|
||||
for (auto tree : trees) {
|
||||
this->Refresh(param, dmlc::BeginPtr(stemp[0]) + offset, 0, tree);
|
||||
offset += tree->param.num_nodes;
|
||||
offset += tree->NumNodes();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user