Skip to content

Clean up host quantile helpers#12074

Merged
RAMitchell merged 27 commits into
dmlc:masterfrom
RAMitchell:quantile-container-cleanup
Mar 12, 2026
Merged

Clean up host quantile helpers#12074
RAMitchell merged 27 commits into
dmlc:masterfrom
RAMitchell:quantile-container-cleanup

Conversation

@RAMitchell

Copy link
Copy Markdown
Member

Summary

  • simplify the host-side quantile sketch container structure
  • remove non-sketch helper leakage from quantile.h
  • keep the hist/quantile tests aligned with the new local helper boundaries

Testing

  • ./build/testxgboost --gtest_brief=1 --gtest_filter='HistUtil.:Quantile.:GHistIndexPageRawFormat.*'

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the CPU quantile sketching implementation by consolidating the sorted/unsorted sketch containers into a single HostSketchContainer, and updates unit tests accordingly (including moving some previously-header-only helpers into test code).

Changes:

  • Remove SortedSketchContainer / SketchContainerImpl split and use HostSketchContainer for both row-page and sorted column-page sketching.
  • Move group-weight “unroll” helper implementation out of the public header into the .cc, and add a test-only equivalent helper.
  • Update/clean up affected tests (including removing the SearchGroupIndFromRow test after the method removal).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/cpp/common/test_quantile.cc Updates distributed quantile tests to always use HostSketchContainer and template-dispatch PushRowPage vs PushColPage.
tests/cpp/common/test_hist_util.h Adds a test helper for unrolling group weights and switches validation to use it.
tests/cpp/common/test_hist_util.cc Removes test coverage for SearchGroupIndFromRow after its removal.
src/common/quantile.h Renames/reshapes the sketch container API to HostSketchContainer and removes the sorted-container class.
src/common/quantile.cc Moves/implements sketch container logic under HostSketchContainer, adds local UnrollGroupWeights, and refactors cut/category helpers.
src/common/hist_util.cc Updates sorted sketching path to instantiate HostSketchContainer instead of SortedSketchContainer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/common/quantile.cc Outdated
Comment on lines +62 to +67
size_t cur_group = 0;
for (bst_idx_t i = 0; i < n_samples; ++i) {
results[i] = group_weights[cur_group];
if (i == group_ptr[cur_group + 1]) {
cur_group++;
}

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

UnrollGroupWeights assigns group_weights[cur_group] to row i before advancing cur_group when i reaches the next group boundary (group_ptr[cur_group + 1]). Since group_ptr_ is a prefix-sum with half-open intervals [group_ptr[g], group_ptr[g+1]), this is off by one and will apply the previous group's weight to the first row of each subsequent group. Adjust the boundary update logic (and the similar loop in MergeWeights) so the group index is advanced before emitting the weight for rows at i == group_ptr[cur_group + 1].

Copilot uses AI. Check for mistakes.
Comment thread tests/cpp/common/test_hist_util.h Outdated
Comment on lines +41 to +44
out[i] = group_weights[cur_group];
if (i == group_ptr[cur_group + 1]) {
cur_group++;
}

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

The group-boundary handling in UnrollGroupWeightsForTest is off by one: it writes the current group's weight for row i and only then increments cur_group when i == group_ptr[cur_group + 1]. With the usual [group_ptr[g], group_ptr[g+1]) convention, the row at group_ptr[g+1] belongs to the next group, so this will mis-assign weights. Update the loop to advance the group index before assigning the weight for rows at the boundary.

Suggested change
out[i] = group_weights[cur_group];
if (i == group_ptr[cur_group + 1]) {
cur_group++;
}
if (i >= group_ptr[cur_group + 1]) {
++cur_group;
}
out[i] = group_weights[cur_group];

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This bug has existed since about 2022 and meant that in the ranking case with weights the quantiles were probably wrong. I fixed and tested it here.

Comment thread src/common/quantile.h
Comment thread tests/cpp/common/test_hist_util.h
@RAMitchell RAMitchell marked this pull request as ready for review March 12, 2026 10:24
@RAMitchell RAMitchell requested a review from Copilot March 12, 2026 10:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/common/quantile.cc
Comment on lines +35 to +41
ParallelFor(sketches_.size(), n_threads_, Sched::Auto(), [&](auto i) {
auto n_bins = std::min(static_cast<bst_idx_t>(max_bins_), columns_size_[i]);
n_bins = std::max(n_bins, static_cast<decltype(n_bins)>(1));
auto eps = 1.0 / (static_cast<float>(n_bins) * WQSketch::kFactor);
if (!IsCat(this->feature_types_, i)) {
sketches_[i] = WQSketch{columns_size_[i], eps};
}

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

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

HostSketchContainer now initializes WQuantileSketch with eps = 1 / (n_bins * kFactor) for all use cases. Previously the sorted-column path used SortedSketchContainer with a much larger eps (e.g. 2.0 / max_bins), which kept WQuantileSketch::limit_size_ relatively small.

With the smaller eps, limit_size_ can become ~O(max_bins * kFactor * log(maxn)) for large columns, and WQuantileSketch::PushSummary() will Reserve(limit_size_ * 2) even when PushColPage() uses PushSorted() (where the incoming summary is already pruned to ~max_bins). On wide datasets this can significantly increase per-feature memory and CPU for the sorted sketch path.

Consider restoring a separate initialization policy for the sorted-column sketching case (e.g. pass a flag into HostSketchContainer or reintroduce a lightweight sorted-only container) so that the sorted path uses a larger eps / smaller limit_size_ consistent with the previous behavior, while keeping the current initialization for the row-wise path.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is intentional. I don't see a reason to have less accurate quantiles for the sorted version. The increase is only a small constant factor.

Comment thread tests/cpp/common/test_hist_util.cc Outdated
info.weights_.HostVector() = {1.0f, 5.0f, 9.0f};

std::vector<float> expected{1.0f, 1.0f, 5.0f, 9.0f, 9.0f, 9.0f};
ASSERT_EQ(UnrollGroupWeightsForTest(info), expected);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why checking a test utility function instead of the real one?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sorry that was an artefact from the refactor

@RAMitchell RAMitchell merged commit b20ce6d into dmlc:master Mar 12, 2026
78 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants