-
-
Notifications
You must be signed in to change notification settings - Fork 8.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SYCL. Add functional for split evaluation #10119
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
/*! | ||
* Copyright 2014-2024 by Contributors | ||
*/ | ||
#ifndef PLUGIN_SYCL_TREE_PARAM_H_ | ||
#define PLUGIN_SYCL_TREE_PARAM_H_ | ||
|
||
|
||
#include <cmath> | ||
#include <cstring> | ||
#include <limits> | ||
#include <string> | ||
#include <vector> | ||
|
||
|
||
#include "xgboost/parameter.h" | ||
#include "xgboost/data.h" | ||
#pragma GCC diagnostic push | ||
#pragma GCC diagnostic ignored "-Wtautological-constant-compare" | ||
#include "../src/tree/param.h" | ||
#pragma GCC diagnostic pop | ||
|
||
#include <CL/sycl.hpp> | ||
|
||
namespace xgboost { | ||
namespace sycl { | ||
namespace tree { | ||
|
||
|
||
/*! \brief Wrapper for necessary training parameters for regression tree to access on device */ | ||
struct TrainParam { | ||
float min_child_weight; | ||
float reg_lambda; | ||
float reg_alpha; | ||
float max_delta_step; | ||
|
||
|
||
TrainParam() {} | ||
|
||
|
||
explicit TrainParam(const xgboost::tree::TrainParam& param) { | ||
reg_lambda = param.reg_lambda; | ||
reg_alpha = param.reg_alpha; | ||
min_child_weight = param.min_child_weight; | ||
max_delta_step = param.max_delta_step; | ||
} | ||
}; | ||
|
||
/*! \brief core statistics used for tree construction */ | ||
template<typename GradType> | ||
struct GradStats { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we reuse the existing one? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
/*! \brief sum gradient statistics */ | ||
GradType sum_grad { 0 }; | ||
/*! \brief sum hessian statistics */ | ||
GradType sum_hess { 0 }; | ||
|
||
public: | ||
GradType GetGrad() const { return sum_grad; } | ||
GradType GetHess() const { return sum_hess; } | ||
|
||
GradStats<GradType>& operator+= (const GradStats<GradType>& rhs) { | ||
sum_grad += rhs.sum_grad; | ||
sum_hess += rhs.sum_hess; | ||
|
||
return *this; | ||
} | ||
|
||
GradStats<GradType>& operator-= (const GradStats<GradType>& rhs) { | ||
sum_grad -= rhs.sum_grad; | ||
sum_hess -= rhs.sum_hess; | ||
|
||
return *this; | ||
} | ||
|
||
friend GradStats<GradType> operator+ (GradStats<GradType> lhs, | ||
const GradStats<GradType> rhs) { | ||
lhs += rhs; | ||
return lhs; | ||
} | ||
|
||
friend GradStats<GradType> operator- (GradStats<GradType> lhs, | ||
const GradStats<GradType> rhs) { | ||
lhs -= rhs; | ||
return lhs; | ||
} | ||
|
||
|
||
friend std::ostream& operator<<(std::ostream& os, GradStats s) { | ||
os << s.GetGrad() << "/" << s.GetHess(); | ||
return os; | ||
} | ||
|
||
GradStats() {} | ||
|
||
template <typename GpairT> | ||
explicit GradStats(const GpairT &sum) | ||
: sum_grad(sum.GetGrad()), sum_hess(sum.GetHess()) {} | ||
explicit GradStats(const GradType grad, const GradType hess) | ||
: sum_grad(grad), sum_hess(hess) {} | ||
}; | ||
|
||
} // namespace tree | ||
} // namespace sycl | ||
} // namespace xgboost | ||
#endif // PLUGIN_SYCL_TREE_PARAM_H_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
/*! | ||
* Copyright 2018-2024 by Contributors | ||
*/ | ||
|
||
#ifndef PLUGIN_SYCL_TREE_SPLIT_EVALUATOR_H_ | ||
#define PLUGIN_SYCL_TREE_SPLIT_EVALUATOR_H_ | ||
|
||
#include <dmlc/registry.h> | ||
#include <xgboost/base.h> | ||
#include <utility> | ||
#include <vector> | ||
#include <limits> | ||
|
||
#include "param.h" | ||
#include "../data.h" | ||
|
||
#include "xgboost/tree_model.h" | ||
#include "xgboost/host_device_vector.h" | ||
#include "xgboost/context.h" | ||
#include "../../src/common/transform.h" | ||
#include "../../src/common/math.h" | ||
#include "../../src/tree/param.h" | ||
|
||
#include <CL/sycl.hpp> | ||
|
||
namespace xgboost { | ||
namespace sycl { | ||
namespace tree { | ||
|
||
/*! \brief SYCL implementation of TreeEvaluator, with USM memory for temporary buffer to access on device. | ||
* It also contains own implementation of SplitEvaluator for device compilation, because some of the | ||
functions from the original SplitEvaluator are currently not supported | ||
*/ | ||
|
||
template<typename GradType> | ||
class TreeEvaluator { | ||
// hist and exact use parent id to calculate constraints. | ||
static constexpr bst_node_t kRootParentId = | ||
(-1 & static_cast<bst_node_t>((1U << 31) - 1)); | ||
|
||
USMVector<GradType> lower_bounds_; | ||
USMVector<GradType> upper_bounds_; | ||
USMVector<int> monotone_; | ||
TrainParam param_; | ||
::sycl::queue qu_; | ||
bool has_constraint_; | ||
|
||
public: | ||
void Reset(::sycl::queue qu, xgboost::tree::TrainParam const& p, bst_feature_t n_features) { | ||
qu_ = qu; | ||
|
||
has_constraint_ = false; | ||
for (const auto& constraint : p.monotone_constraints) { | ||
if (constraint != 0) { | ||
has_constraint_ = true; | ||
break; | ||
} | ||
} | ||
|
||
if (has_constraint_) { | ||
monotone_.Resize(&qu_, n_features, 0); | ||
qu_.memcpy(monotone_.Data(), p.monotone_constraints.data(), | ||
sizeof(int) * p.monotone_constraints.size()); | ||
qu_.wait(); | ||
|
||
lower_bounds_.Resize(&qu_, p.MaxNodes(), std::numeric_limits<GradType>::lowest()); | ||
upper_bounds_.Resize(&qu_, p.MaxNodes(), std::numeric_limits<GradType>::max()); | ||
} | ||
param_ = TrainParam(p); | ||
} | ||
|
||
bool HasConstraint() const { | ||
return has_constraint_; | ||
} | ||
|
||
TreeEvaluator(::sycl::queue qu, xgboost::tree::TrainParam const& p, bst_feature_t n_features) { | ||
Reset(qu, p, n_features); | ||
} | ||
|
||
struct SplitEvaluator { | ||
const int* constraints; | ||
const GradType* lower; | ||
const GradType* upper; | ||
bool has_constraint; | ||
TrainParam param; | ||
|
||
GradType CalcSplitGain(bst_node_t nidx, | ||
bst_feature_t fidx, | ||
const GradStats<GradType>& left, | ||
const GradStats<GradType>& right) const { | ||
const GradType negative_infinity = -std::numeric_limits<GradType>::infinity(); | ||
GradType wleft = this->CalcWeight(nidx, left); | ||
GradType wright = this->CalcWeight(nidx, right); | ||
|
||
GradType gain = this->CalcGainGivenWeight(nidx, left, wleft) + | ||
this->CalcGainGivenWeight(nidx, right, wright); | ||
if (!has_constraint) { | ||
return gain; | ||
} | ||
|
||
int constraint = constraints[fidx]; | ||
if (constraint == 0) { | ||
return gain; | ||
} else if (constraint > 0) { | ||
return wleft <= wright ? gain : negative_infinity; | ||
} else { | ||
return wleft >= wright ? gain : negative_infinity; | ||
} | ||
} | ||
|
||
inline static GradType ThresholdL1(GradType w, float alpha) { | ||
if (w > + alpha) { | ||
return w - alpha; | ||
} | ||
if (w < - alpha) { | ||
return w + alpha; | ||
} | ||
return 0.0; | ||
} | ||
|
||
inline GradType CalcWeight(GradType sum_grad, GradType sum_hess) const { | ||
if (sum_hess < param.min_child_weight || sum_hess <= 0.0) { | ||
return 0.0; | ||
} | ||
GradType dw = -this->ThresholdL1(sum_grad, param.reg_alpha) / (sum_hess + param.reg_lambda); | ||
if (param.max_delta_step != 0.0f && std::abs(dw) > param.max_delta_step) { | ||
dw = ::sycl::copysign((GradType)param.max_delta_step, dw); | ||
} | ||
return dw; | ||
} | ||
|
||
inline GradType CalcWeight(bst_node_t nodeid, const GradStats<GradType>& stats) const { | ||
GradType w = this->CalcWeight(stats.GetGrad(), stats.GetHess()); | ||
if (!has_constraint) { | ||
return w; | ||
} | ||
|
||
if (nodeid == kRootParentId) { | ||
return w; | ||
} else if (w < lower[nodeid]) { | ||
return lower[nodeid]; | ||
} else if (w > upper[nodeid]) { | ||
return upper[nodeid]; | ||
} else { | ||
return w; | ||
} | ||
} | ||
|
||
inline GradType Sqr(GradType a) const { return a * a; } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use the one in common/math.h ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
inline GradType CalcGainGivenWeight(GradType sum_grad, GradType sum_hess, GradType w) const { | ||
return -(2.0f * sum_grad * w + (sum_hess + param.reg_lambda) * this->Sqr(w)); | ||
} | ||
|
||
inline GradType CalcGainGivenWeight(bst_node_t nid, const GradStats<GradType>& stats, | ||
GradType w) const { | ||
if (stats.GetHess() <= 0) { | ||
return .0f; | ||
} | ||
// Avoiding tree::CalcGainGivenWeight can significantly reduce avg floating point error. | ||
if (param.max_delta_step == 0.0f && has_constraint == false) { | ||
return this->Sqr(this->ThresholdL1(stats.sum_grad, param.reg_alpha)) / | ||
(stats.sum_hess + param.reg_lambda); | ||
} | ||
return this->CalcGainGivenWeight(stats.sum_grad, stats.sum_hess, w); | ||
} | ||
|
||
GradType CalcGain(bst_node_t nid, const GradStats<GradType>& stats) const { | ||
return this->CalcGainGivenWeight(nid, stats, this->CalcWeight(nid, stats)); | ||
} | ||
}; | ||
|
||
public: | ||
/* Get a view to the evaluator that can be passed down to device. */ | ||
auto GetEvaluator() const { | ||
return SplitEvaluator{monotone_.DataConst(), | ||
lower_bounds_.DataConst(), | ||
upper_bounds_.DataConst(), | ||
has_constraint_, | ||
param_}; | ||
} | ||
|
||
void AddSplit(bst_node_t nodeid, bst_node_t leftid, bst_node_t rightid, | ||
bst_feature_t f, GradType left_weight, GradType right_weight) { | ||
if (!has_constraint_) { | ||
return; | ||
} | ||
|
||
lower_bounds_[leftid] = lower_bounds_[nodeid]; | ||
upper_bounds_[leftid] = upper_bounds_[nodeid]; | ||
|
||
lower_bounds_[rightid] = lower_bounds_[nodeid]; | ||
upper_bounds_[rightid] = upper_bounds_[nodeid]; | ||
int32_t c = monotone_[f]; | ||
GradType mid = (left_weight + right_weight) / 2; | ||
|
||
if (c < 0) { | ||
lower_bounds_[leftid] = mid; | ||
upper_bounds_[rightid] = mid; | ||
} else if (c > 0) { | ||
upper_bounds_[leftid] = mid; | ||
lower_bounds_[rightid] = mid; | ||
} | ||
} | ||
}; | ||
} // namespace tree | ||
} // namespace sycl | ||
} // namespace xgboost | ||
|
||
#endif // PLUGIN_SYCL_TREE_SPLIT_EVALUATOR_H_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we reuse the existing one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, no. The original one has fields of type std::vector, that are not copyable to sycl-device.