Skip to content
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

Merged
merged 5 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions plugin/sycl/tree/param.h
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 {
Copy link
Member

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?

Copy link
Contributor Author

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.

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 {
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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_
210 changes: 210 additions & 0 deletions plugin/sycl/tree/split_evaluator.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; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the one in common/math.h ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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_
Loading
Loading