Medial Code Documentation
Loading...
Searching...
No Matches
partition_builder.h
1
7#ifndef XGBOOST_COMMON_PARTITION_BUILDER_H_
8#define XGBOOST_COMMON_PARTITION_BUILDER_H_
9
10#include <xgboost/data.h>
11
12#include <algorithm>
13#include <cstddef> // for size_t
14#include <limits>
15#include <memory>
16#include <utility>
17#include <vector>
18
19#include "../tree/hist/expand_entry.h"
20#include "categorical.h"
21#include "column_matrix.h"
22#include "xgboost/context.h"
23#include "xgboost/tree_model.h"
24
25namespace xgboost::common {
26// The builder is required for samples partition to left and rights children for set of nodes
27// Responsible for:
28// 1) Effective memory allocation for intermediate results for multi-thread work
29// 2) Merging partial results produced by threads into original row set (row_set_collection_)
30// BlockSize is template to enable memory alignment easily with C++11 'alignas()' feature
31template<size_t BlockSize>
33 using BitVector = RBitField8;
34
35 public:
36 template<typename Func>
37 void Init(const size_t n_tasks, size_t n_nodes, Func funcNTask) {
38 left_right_nodes_sizes_.resize(n_nodes);
39 blocks_offsets_.resize(n_nodes+1);
40
41 blocks_offsets_[0] = 0;
42 for (size_t i = 1; i < n_nodes+1; ++i) {
43 blocks_offsets_[i] = blocks_offsets_[i-1] + funcNTask(i-1);
44 }
45
46 if (n_tasks > max_n_tasks_) {
47 mem_blocks_.resize(n_tasks);
48 max_n_tasks_ = n_tasks;
49 }
50 }
51
52 // split row indexes (rid_span) to 2 parts (left_part, right_part) depending
53 // on comparison of indexes values (idx_span) and split point (split_cond)
54 // Handle dense columns
55 // Analog of std::stable_partition, but in no-inplace manner
56 template <bool default_left, bool any_missing, typename ColumnType, typename Predicate>
57 inline std::pair<size_t, size_t> PartitionKernel(ColumnType* p_column,
59 common::Span<size_t> left_part,
60 common::Span<size_t> right_part,
61 size_t base_rowid, Predicate&& pred) {
62 auto& column = *p_column;
63 size_t* p_left_part = left_part.data();
64 size_t* p_right_part = right_part.data();
65 size_t nleft_elems = 0;
66 size_t nright_elems = 0;
67
68 auto p_row_indices = row_indices.data();
69 auto n_samples = row_indices.size();
70
71 for (size_t i = 0; i < n_samples; ++i) {
72 auto rid = p_row_indices[i];
73 const int32_t bin_id = column[rid - base_rowid];
74 if (any_missing && bin_id == ColumnType::kMissingId) {
75 if (default_left) {
76 p_left_part[nleft_elems++] = rid;
77 } else {
78 p_right_part[nright_elems++] = rid;
79 }
80 } else {
81 if (pred(rid, bin_id)) {
82 p_left_part[nleft_elems++] = rid;
83 } else {
84 p_right_part[nright_elems++] = rid;
85 }
86 }
87 }
88
89 return {nleft_elems, nright_elems};
90 }
91
92 template <typename Pred>
93 inline std::pair<size_t, size_t> PartitionRangeKernel(common::Span<const size_t> ridx,
94 common::Span<size_t> left_part,
95 common::Span<size_t> right_part,
96 Pred pred) {
97 size_t* p_left_part = left_part.data();
98 size_t* p_right_part = right_part.data();
99 size_t nleft_elems = 0;
100 size_t nright_elems = 0;
101 for (auto row_id : ridx) {
102 if (pred(row_id)) {
103 p_left_part[nleft_elems++] = row_id;
104 } else {
105 p_right_part[nright_elems++] = row_id;
106 }
107 }
108 return {nleft_elems, nright_elems};
109 }
110
111 template <typename BinIdxType, bool any_missing, bool any_cat, typename ExpandEntry>
112 void Partition(const size_t node_in_set, std::vector<ExpandEntry> const& nodes,
113 const common::Range1d range, const bst_bin_t split_cond,
114 GHistIndexMatrix const& gmat, const common::ColumnMatrix& column_matrix,
115 const RegTree& tree, const size_t* rid) {
116 common::Span<const size_t> rid_span(rid + range.begin(), rid + range.end());
117 common::Span<size_t> left = GetLeftBuffer(node_in_set, range.begin(), range.end());
118 common::Span<size_t> right = GetRightBuffer(node_in_set, range.begin(), range.end());
119 std::size_t nid = nodes[node_in_set].nid;
120 bst_feature_t fid = tree.SplitIndex(nid);
121 bool default_left = tree.DefaultLeft(nid);
122 bool is_cat = tree.GetSplitTypes()[nid] == FeatureType::kCategorical;
123 auto node_cats = tree.NodeCats(nid);
124 auto const& cut_values = gmat.cut.Values();
125
126 auto pred_hist = [&](auto ridx, auto bin_id) {
127 if (any_cat && is_cat) {
128 auto gidx = gmat.GetGindex(ridx, fid);
129 bool go_left = default_left;
130 if (gidx > -1) {
131 go_left = Decision(node_cats, cut_values[gidx]);
132 }
133 return go_left;
134 } else {
135 return bin_id <= split_cond;
136 }
137 };
138
139 auto pred_approx = [&](auto ridx) {
140 auto gidx = gmat.GetGindex(ridx, fid);
141 bool go_left = default_left;
142 if (gidx > -1) {
143 if (is_cat) {
144 go_left = Decision(node_cats, cut_values[gidx]);
145 } else {
146 go_left = cut_values[gidx] <= nodes[node_in_set].split.split_value;
147 }
148 }
149 return go_left;
150 };
151
152 std::pair<size_t, size_t> child_nodes_sizes;
153 if (!column_matrix.IsInitialized()) {
154 child_nodes_sizes = PartitionRangeKernel(rid_span, left, right, pred_approx);
155 } else {
156 if (column_matrix.GetColumnType(fid) == xgboost::common::kDenseColumn) {
157 auto column = column_matrix.DenseColumn<BinIdxType, any_missing>(fid);
158 if (default_left) {
159 child_nodes_sizes = PartitionKernel<true, any_missing>(&column, rid_span, left, right,
160 gmat.base_rowid, pred_hist);
161 } else {
162 child_nodes_sizes = PartitionKernel<false, any_missing>(&column, rid_span, left, right,
163 gmat.base_rowid, pred_hist);
164 }
165 } else {
166 CHECK_EQ(any_missing, true);
167 auto column =
168 column_matrix.SparseColumn<BinIdxType>(fid, rid_span.front() - gmat.base_rowid);
169 if (default_left) {
170 child_nodes_sizes = PartitionKernel<true, any_missing>(&column, rid_span, left, right,
171 gmat.base_rowid, pred_hist);
172 } else {
173 child_nodes_sizes = PartitionKernel<false, any_missing>(&column, rid_span, left, right,
174 gmat.base_rowid, pred_hist);
175 }
176 }
177 }
178
179 const size_t n_left = child_nodes_sizes.first;
180 const size_t n_right = child_nodes_sizes.second;
181
182 SetNLeftElems(node_in_set, range.begin(), n_left);
183 SetNRightElems(node_in_set, range.begin(), n_right);
184 }
185
186 template <bool any_missing, typename ColumnType, typename Predicate>
187 void MaskKernel(ColumnType* p_column, common::Span<const size_t> row_indices, size_t base_rowid,
188 BitVector* decision_bits, BitVector* missing_bits, Predicate&& pred) {
189 auto& column = *p_column;
190 for (auto const row_id : row_indices) {
191 auto const bin_id = column[row_id - base_rowid];
192 if (any_missing && bin_id == ColumnType::kMissingId) {
193 missing_bits->Set(row_id - base_rowid);
194 } else if (pred(row_id, bin_id)) {
195 decision_bits->Set(row_id - base_rowid);
196 }
197 }
198 }
199
205 template <typename BinIdxType, bool any_missing, bool any_cat, typename ExpandEntry>
206 void MaskRows(const size_t node_in_set, std::vector<ExpandEntry> const& nodes,
207 const common::Range1d range, bst_bin_t split_cond, GHistIndexMatrix const& gmat,
208 const common::ColumnMatrix& column_matrix, const RegTree& tree, const size_t* rid,
209 BitVector* decision_bits, BitVector* missing_bits) {
210 common::Span<const size_t> rid_span(rid + range.begin(), rid + range.end());
211 std::size_t nid = nodes[node_in_set].nid;
212 bst_feature_t fid = tree.SplitIndex(nid);
213 bool is_cat = tree.GetSplitTypes()[nid] == FeatureType::kCategorical;
214 auto node_cats = tree.NodeCats(nid);
215 auto const& cut_values = gmat.cut.Values();
216
217 if (!column_matrix.IsInitialized()) {
218 for (auto row_id : rid_span) {
219 auto gidx = gmat.GetGindex(row_id, fid);
220 if (gidx > -1) {
221 bool go_left;
222 if (is_cat) {
223 go_left = Decision(node_cats, cut_values[gidx]);
224 } else {
225 go_left = cut_values[gidx] <= nodes[node_in_set].split.split_value;
226 }
227 if (go_left) {
228 decision_bits->Set(row_id - gmat.base_rowid);
229 }
230 } else {
231 missing_bits->Set(row_id - gmat.base_rowid);
232 }
233 }
234 } else {
235 auto pred_hist = [&](auto ridx, auto bin_id) {
236 if (any_cat && is_cat) {
237 auto gidx = gmat.GetGindex(ridx, fid);
238 CHECK_GT(gidx, -1);
239 return Decision(node_cats, cut_values[gidx]);
240 } else {
241 return bin_id <= split_cond;
242 }
243 };
244
245 if (column_matrix.GetColumnType(fid) == xgboost::common::kDenseColumn) {
246 auto column = column_matrix.DenseColumn<BinIdxType, any_missing>(fid);
247 MaskKernel<any_missing>(&column, rid_span, gmat.base_rowid, decision_bits, missing_bits,
248 pred_hist);
249 } else {
250 CHECK_EQ(any_missing, true);
251 auto column =
252 column_matrix.SparseColumn<BinIdxType>(fid, rid_span.front() - gmat.base_rowid);
253 MaskKernel<any_missing>(&column, rid_span, gmat.base_rowid, decision_bits, missing_bits,
254 pred_hist);
255 }
256 }
257 }
258
263 template <typename ExpandEntry>
264 void PartitionByMask(const size_t node_in_set, std::vector<ExpandEntry> const& nodes,
265 const common::Range1d range, GHistIndexMatrix const& gmat,
266 const RegTree& tree, const size_t* rid, BitVector const& decision_bits,
267 BitVector const& missing_bits) {
268 common::Span<const size_t> rid_span(rid + range.begin(), rid + range.end());
269 common::Span<size_t> left = GetLeftBuffer(node_in_set, range.begin(), range.end());
270 common::Span<size_t> right = GetRightBuffer(node_in_set, range.begin(), range.end());
271 std::size_t nid = nodes[node_in_set].nid;
272 bool default_left = tree.DefaultLeft(nid);
273
274 auto pred = [&](auto ridx) {
275 bool go_left = default_left;
276 bool is_missing = missing_bits.Check(ridx - gmat.base_rowid);
277 if (!is_missing) {
278 go_left = decision_bits.Check(ridx - gmat.base_rowid);
279 }
280 return go_left;
281 };
282
283 std::pair<size_t, size_t> child_nodes_sizes;
284 child_nodes_sizes = PartitionRangeKernel(rid_span, left, right, pred);
285
286 const size_t n_left = child_nodes_sizes.first;
287 const size_t n_right = child_nodes_sizes.second;
288
289 SetNLeftElems(node_in_set, range.begin(), n_left);
290 SetNRightElems(node_in_set, range.begin(), n_right);
291 }
292
293 // allocate thread local memory, should be called for each specific task
294 void AllocateForTask(size_t id) {
295 if (mem_blocks_[id].get() == nullptr) {
296 BlockInfo* local_block_ptr = new BlockInfo;
297 CHECK_NE(local_block_ptr, (BlockInfo*)nullptr);
298 mem_blocks_[id].reset(local_block_ptr);
299 }
300 }
301
302 common::Span<size_t> GetLeftBuffer(int nid, size_t begin, size_t end) {
303 const size_t task_idx = GetTaskIdx(nid, begin);
304 return { mem_blocks_.at(task_idx)->Left(), end - begin };
305 }
306
307 common::Span<size_t> GetRightBuffer(int nid, size_t begin, size_t end) {
308 const size_t task_idx = GetTaskIdx(nid, begin);
309 return { mem_blocks_.at(task_idx)->Right(), end - begin };
310 }
311
312 void SetNLeftElems(int nid, size_t begin, size_t n_left) {
313 size_t task_idx = GetTaskIdx(nid, begin);
314 mem_blocks_.at(task_idx)->n_left = n_left;
315 }
316
317 void SetNRightElems(int nid, size_t begin, size_t n_right) {
318 size_t task_idx = GetTaskIdx(nid, begin);
319 mem_blocks_.at(task_idx)->n_right = n_right;
320 }
321
322
323 [[nodiscard]] std::size_t GetNLeftElems(int nid) const {
324 return left_right_nodes_sizes_[nid].first;
325 }
326
327 [[nodiscard]] std::size_t GetNRightElems(int nid) const {
328 return left_right_nodes_sizes_[nid].second;
329 }
330
331 // Each thread has partial results for some set of tree-nodes
332 // The function decides order of merging partial results into final row set
333 void CalculateRowOffsets() {
334 for (size_t i = 0; i < blocks_offsets_.size()-1; ++i) {
335 size_t n_left = 0;
336 for (size_t j = blocks_offsets_[i]; j < blocks_offsets_[i+1]; ++j) {
337 mem_blocks_[j]->n_offset_left = n_left;
338 n_left += mem_blocks_[j]->n_left;
339 }
340 size_t n_right = 0;
341 for (size_t j = blocks_offsets_[i]; j < blocks_offsets_[i + 1]; ++j) {
342 mem_blocks_[j]->n_offset_right = n_left + n_right;
343 n_right += mem_blocks_[j]->n_right;
344 }
345 left_right_nodes_sizes_[i] = {n_left, n_right};
346 }
347 }
348
349 void MergeToArray(int nid, size_t begin, size_t* rows_indexes) {
350 size_t task_idx = GetTaskIdx(nid, begin);
351
352 size_t* left_result = rows_indexes + mem_blocks_[task_idx]->n_offset_left;
353 size_t* right_result = rows_indexes + mem_blocks_[task_idx]->n_offset_right;
354
355 const size_t* left = mem_blocks_[task_idx]->Left();
356 const size_t* right = mem_blocks_[task_idx]->Right();
357
358 std::copy_n(left, mem_blocks_[task_idx]->n_left, left_result);
359 std::copy_n(right, mem_blocks_[task_idx]->n_right, right_result);
360 }
361
362 size_t GetTaskIdx(int nid, size_t begin) {
363 return blocks_offsets_[nid] + begin / BlockSize;
364 }
365
366 // Copy row partitions into global cache for reuse in objective
367 template <typename Sampledp>
368 void LeafPartition(Context const* ctx, RegTree const& tree, RowSetCollection const& row_set,
369 std::vector<bst_node_t>* p_position, Sampledp sampledp) const {
370 auto& h_pos = *p_position;
371 h_pos.resize(row_set.Data()->size(), std::numeric_limits<bst_node_t>::max());
372
373 auto p_begin = row_set.Data()->data();
374 ParallelFor(row_set.Size(), ctx->Threads(), [&](size_t i) {
375 auto const& node = row_set[i];
376 if (node.node_id < 0) {
377 return;
378 }
379 CHECK(tree.IsLeaf(node.node_id));
380 if (node.begin) { // guard for empty node.
381 size_t ptr_offset = node.end - p_begin;
382 CHECK_LE(ptr_offset, row_set.Data()->size()) << node.node_id;
383 for (auto idx = node.begin; idx != node.end; ++idx) {
384 h_pos[*idx] = sampledp(*idx) ? ~node.node_id : node.node_id;
385 }
386 }
387 });
388 }
389
390 protected:
391 struct BlockInfo{
392 size_t n_left;
393 size_t n_right;
394
395 size_t n_offset_left;
396 size_t n_offset_right;
397
398 size_t* Left() {
399 return &left_data_[0];
400 }
401
402 size_t* Right() {
403 return &right_data_[0];
404 }
405 private:
406 size_t left_data_[BlockSize];
407 size_t right_data_[BlockSize];
408 };
409 std::vector<std::pair<size_t, size_t>> left_right_nodes_sizes_;
410 std::vector<size_t> blocks_offsets_;
411 std::vector<std::shared_ptr<BlockInfo>> mem_blocks_;
412 size_t max_n_tasks_ = 0;
413};
414} // namespace xgboost::common
415#endif // XGBOOST_COMMON_PARTITION_BUILDER_H_
Copyright 2020-2023, XGBoost Contributors.
preprocessed global index matrix, in CSR format.
Definition gradient_index.h:38
bst_row_t base_rowid
base row index for current page (used by external memory)
Definition gradient_index.h:152
common::HistogramCuts cut
The corresponding cuts.
Definition gradient_index.h:148
define regression tree to be the most common tree model.
Definition tree_model.h:158
common::Span< uint32_t const > NodeCats(bst_node_t nidx) const
Get the bit storage for categories.
Definition tree_model.h:639
std::vector< FeatureType > const & GetSplitTypes() const
Get split types for all nodes.
Definition tree_model.h:630
Column major matrix for gradient index on CPU.
Definition column_matrix.h:148
Definition partition_builder.h:32
void PartitionByMask(const size_t node_in_set, std::vector< ExpandEntry > const &nodes, const common::Range1d range, GHistIndexMatrix const &gmat, const RegTree &tree, const size_t *rid, BitVector const &decision_bits, BitVector const &missing_bits)
Once we've aggregated the decision and missing bits from all the workers, we can then use them to par...
Definition partition_builder.h:264
void MaskRows(const size_t node_in_set, std::vector< ExpandEntry > const &nodes, const common::Range1d range, bst_bin_t split_cond, GHistIndexMatrix const &gmat, const common::ColumnMatrix &column_matrix, const RegTree &tree, const size_t *rid, BitVector *decision_bits, BitVector *missing_bits)
When data is split by column, we don't have all the features locally on the current worker,...
Definition partition_builder.h:206
Definition threading_utils.h:39
span class implementation, based on ISO++20 span<T>. The interface should be the same.
Definition span.h:424
Copyright 2017-2023, XGBoost Contributors.
Copyright 2014-2023, XGBoost Contributors.
Copyright 2015-2023 by XGBoost Contributors.
Copyright 2017-2023, XGBoost Contributors.
Definition span.h:77
ColumnType
column type
Definition column_matrix.h:34
XGBOOST_DEVICE bool Decision(common::Span< CatBitField::value_type const > cats, float cat)
Whether should it traverse to left branch of a tree.
Definition categorical.h:55
uint32_t bst_feature_t
Type for data column (feature) index.
Definition base.h:101
auto get(U &json) -> decltype(detail::GetImpl(*Cast< T >(&json.GetValue())))&
Get Json value.
Definition json.h:585
int32_t bst_bin_t
Type for histogram bin index.
Definition base.h:103
std::int32_t Threads() const
Returns the automatically chosen number of threads based on the nthread parameter and the system sett...
Definition context.cc:203
Definition partition_builder.h:391
Copyright 2014-2023 by Contributors.