Files
lightgbm-org--lightgbm/src/io/train_share_states.cpp
T
2026-07-13 13:27:18 +08:00

541 lines
22 KiB
C++

/*!
* Copyright (c) 2016-2026 Microsoft Corporation. All rights reserved.
* Copyright (c) 2016-2026 The LightGBM developers. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for
* license information.
*/
#include <LightGBM/train_share_states.h>
#include <algorithm>
#include <memory>
#include <vector>
namespace LightGBM {
MultiValBinWrapper::MultiValBinWrapper(MultiValBin* bin, data_size_t num_data,
const std::vector<int>& feature_groups_contained, const int num_grad_quant_bins):
feature_groups_contained_(feature_groups_contained) {
num_threads_ = OMP_NUM_THREADS();
num_data_ = num_data;
multi_val_bin_.reset(bin);
if (bin == nullptr) {
return;
}
num_bin_ = bin->num_bin();
num_bin_aligned_ = (num_bin_ + kAlignedSize - 1) / kAlignedSize * kAlignedSize;
num_grad_quant_bins_ = num_grad_quant_bins;
}
void MultiValBinWrapper::InitTrain(const std::vector<int>& group_feature_start,
const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
const std::vector<int8_t>& is_feature_used,
const data_size_t* bagging_use_indices,
data_size_t bagging_indices_cnt) {
is_use_subcol_ = false;
if (multi_val_bin_ == nullptr) {
return;
}
CopyMultiValBinSubset(group_feature_start, feature_groups,
is_feature_used, bagging_use_indices, bagging_indices_cnt);
const auto cur_multi_val_bin = (is_use_subcol_ || is_use_subrow_)
? multi_val_bin_subset_.get()
: multi_val_bin_.get();
if (cur_multi_val_bin != nullptr) {
num_bin_ = cur_multi_val_bin->num_bin();
num_bin_aligned_ = (num_bin_ + kAlignedSize - 1) / kAlignedSize * kAlignedSize;
auto num_element_per_row = cur_multi_val_bin->num_element_per_row();
min_block_size_ = std::min<int>(static_cast<int>(0.3f * num_bin_ /
(num_element_per_row + kZeroThreshold)) + 1, 1024);
min_block_size_ = std::max<int>(min_block_size_, 32);
}
}
template <bool USE_QUANT_GRAD, int HIST_BITS, int INNER_HIST_BITS>
void MultiValBinWrapper::HistMove(const std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf) {
if (!is_use_subcol_ && INNER_HIST_BITS != 8) {
return;
}
if (USE_QUANT_GRAD) {
if (HIST_BITS == 32) {
const int64_t* src = reinterpret_cast<const int64_t*>(hist_buf.data()) + hist_buf.size() / 2 -
static_cast<size_t>(num_bin_aligned_);
#pragma omp parallel for schedule(static) num_threads(num_threads_)
for (int i = 0; i < static_cast<int>(hist_move_src_.size()); ++i) {
std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2,
reinterpret_cast<int64_t*>(origin_hist_data_) + hist_move_dest_[i] / 2);
}
} else if (HIST_BITS == 16) {
if (is_use_subcol_) {
const int32_t* src = reinterpret_cast<const int32_t*>(hist_buf.data()) + hist_buf.size() / 2 -
static_cast<size_t>(num_bin_aligned_);
#pragma omp parallel for schedule(static) num_threads(num_threads_)
for (int i = 0; i < static_cast<int>(hist_move_src_.size()); ++i) {
std::copy_n(src + hist_move_src_[i] / 2, hist_move_size_[i] / 2,
reinterpret_cast<int32_t*>(origin_hist_data_) + hist_move_dest_[i] / 2);
}
} else {
CHECK_EQ(INNER_HIST_BITS, 8);
const int32_t* src = reinterpret_cast<const int32_t*>(hist_buf.data()) + hist_buf.size() / 2;
int32_t* orig_ptr = reinterpret_cast<int32_t*>(origin_hist_data_);
#pragma omp parallel for schedule(static) num_threads(num_threads_)
for (int i = 0; i < num_bin_; ++i) {
orig_ptr[i] = src[i];
}
}
}
} else {
const hist_t* src = hist_buf.data() + hist_buf.size() -
2 * static_cast<size_t>(num_bin_aligned_);
#pragma omp parallel for schedule(static) num_threads(num_threads_)
for (int i = 0; i < static_cast<int>(hist_move_src_.size()); ++i) {
std::copy_n(src + hist_move_src_[i], hist_move_size_[i],
origin_hist_data_ + hist_move_dest_[i]);
}
}
}
template void MultiValBinWrapper::HistMove<false, 0, 0>(const std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
template void MultiValBinWrapper::HistMove<false, 0, 8>(const std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
template void MultiValBinWrapper::HistMove<true, 16, 8>(const std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
template void MultiValBinWrapper::HistMove<true, 16, 16>(const std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
template void MultiValBinWrapper::HistMove<true, 32, 8>(const std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
template void MultiValBinWrapper::HistMove<true, 32, 32>(const std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>& hist_buf);
template <bool USE_QUANT_GRAD, int HIST_BITS, int INNER_HIST_BITS>
void MultiValBinWrapper::HistMerge(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf) {
int n_bin_block = 1;
int bin_block_size = num_bin_;
Threading::BlockInfo<data_size_t>(num_threads_, num_bin_, 512, &n_bin_block,
&bin_block_size);
if (USE_QUANT_GRAD) {
if (HIST_BITS == 32) {
int64_t* dst = reinterpret_cast<int64_t*>(origin_hist_data_);
if (is_use_subcol_) {
dst = reinterpret_cast<int64_t*>(hist_buf->data()) + hist_buf->size() / 2 - static_cast<size_t>(num_bin_aligned_);
}
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
for (int t = 0; t < n_bin_block; ++t) {
const int start = t * bin_block_size;
const int end = std::min(start + bin_block_size, num_bin_);
for (int tid = 1; tid < n_data_block_; ++tid) {
auto src_ptr = reinterpret_cast<const int64_t*>(hist_buf->data()) + static_cast<size_t>(num_bin_aligned_) * (tid - 1);
for (int i = start; i < end; ++i) {
dst[i] += src_ptr[i];
}
}
}
} else if (HIST_BITS == 16 && INNER_HIST_BITS == 16) {
int32_t* dst = reinterpret_cast<int32_t*>(origin_hist_data_);
if (is_use_subcol_) {
dst = reinterpret_cast<int32_t*>(hist_buf->data()) + hist_buf->size() / 2 - static_cast<size_t>(num_bin_aligned_);
}
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
for (int t = 0; t < n_bin_block; ++t) {
const int start = t * bin_block_size;
const int end = std::min(start + bin_block_size, num_bin_);
for (int tid = 1; tid < n_data_block_; ++tid) {
auto src_ptr = reinterpret_cast<const int32_t*>(hist_buf->data()) + static_cast<size_t>(num_bin_aligned_) * (tid - 1);
for (int i = start; i < end; ++i) {
dst[i] += src_ptr[i];
}
}
}
} else if (HIST_BITS == 16 && INNER_HIST_BITS == 8) {
int32_t* dst = reinterpret_cast<int32_t*>(hist_buf->data()) + hist_buf->size() / 2;
std::memset(reinterpret_cast<void*>(dst), 0, num_bin_ * kInt16HistBufferEntrySize);
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
for (int t = 0; t < n_bin_block; ++t) {
const int start = t * bin_block_size;
const int end = std::min(start + bin_block_size, num_bin_);
for (int tid = 0; tid < n_data_block_; ++tid) {
auto src_ptr = reinterpret_cast<const int16_t*>(hist_buf->data()) + static_cast<size_t>(num_bin_aligned_) * tid;
for (int i = start; i < end; ++i) {
const int16_t packed_hist = src_ptr[i];
const int32_t packed_hist_int32 = (static_cast<int32_t>(static_cast<int8_t>(packed_hist >> 8)) << 16) | static_cast<int32_t>(packed_hist & 0x00ff);
dst[i] += packed_hist_int32;
}
}
}
}
} else {
hist_t* dst = origin_hist_data_;
if (is_use_subcol_) {
dst = hist_buf->data() + hist_buf->size() - 2 * static_cast<size_t>(num_bin_aligned_);
}
#pragma omp parallel for schedule(static, 1) num_threads(num_threads_)
for (int t = 0; t < n_bin_block; ++t) {
const int start = t * bin_block_size;
const int end = std::min(start + bin_block_size, num_bin_);
for (int tid = 1; tid < n_data_block_; ++tid) {
auto src_ptr = hist_buf->data() + static_cast<size_t>(num_bin_aligned_) * 2 * (tid - 1);
for (int i = start * 2; i < end * 2; ++i) {
dst[i] += src_ptr[i];
}
}
}
}
}
template void MultiValBinWrapper::HistMerge<false, 0, 0>(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
template void MultiValBinWrapper::HistMerge<false, 0, 8>(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
template void MultiValBinWrapper::HistMerge<true, 16, 8>(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
template void MultiValBinWrapper::HistMerge<true, 16, 16>(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
template void MultiValBinWrapper::HistMerge<true, 32, 8>(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
template void MultiValBinWrapper::HistMerge<true, 32, 32>(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf);
void MultiValBinWrapper::ResizeHistBuf(std::vector<hist_t,
Common::AlignmentAllocator<hist_t, kAlignedSize>>* hist_buf,
MultiValBin* sub_multi_val_bin,
hist_t* origin_hist_data) {
num_bin_ = sub_multi_val_bin->num_bin();
num_bin_aligned_ = (num_bin_ + kAlignedSize - 1) / kAlignedSize * kAlignedSize;
origin_hist_data_ = origin_hist_data;
size_t new_buf_size = static_cast<size_t>(n_data_block_) * static_cast<size_t>(num_bin_aligned_) * 2;
if (hist_buf->size() < new_buf_size) {
hist_buf->resize(new_buf_size);
}
}
void MultiValBinWrapper::CopyMultiValBinSubset(
const std::vector<int>& group_feature_start,
const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
const std::vector<int8_t>& is_feature_used,
const data_size_t* bagging_use_indices,
data_size_t bagging_indices_cnt) {
double sum_used_dense_ratio = 0.0;
double sum_dense_ratio = 0.0;
int num_used = 0;
int total = 0;
std::vector<int> used_feature_index;
for (int i : feature_groups_contained_) {
int f_start = group_feature_start[i];
if (feature_groups[i]->is_multi_val_) {
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
const auto dense_rate =
1.0 - feature_groups[i]->bin_mappers_[j]->sparse_rate();
if (is_feature_used[f_start + j]) {
++num_used;
used_feature_index.push_back(total);
sum_used_dense_ratio += dense_rate;
}
sum_dense_ratio += dense_rate;
++total;
}
} else {
bool is_group_used = false;
double dense_rate = 0;
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
if (is_feature_used[f_start + j]) {
is_group_used = true;
}
dense_rate += 1.0 - feature_groups[i]->bin_mappers_[j]->sparse_rate();
}
if (is_group_used) {
++num_used;
used_feature_index.push_back(total);
sum_used_dense_ratio += dense_rate;
}
sum_dense_ratio += dense_rate;
++total;
}
}
const double k_subfeature_threshold = 0.6;
if (sum_used_dense_ratio >= sum_dense_ratio * k_subfeature_threshold) {
// only need to copy subset
if (is_use_subrow_ && !is_subrow_copied_) {
if (multi_val_bin_subset_ == nullptr) {
multi_val_bin_subset_.reset(multi_val_bin_->CreateLike(
bagging_indices_cnt, multi_val_bin_->num_bin(), total,
multi_val_bin_->num_element_per_row(), multi_val_bin_->offsets()));
} else {
multi_val_bin_subset_->ReSize(
bagging_indices_cnt, multi_val_bin_->num_bin(), total,
multi_val_bin_->num_element_per_row(), multi_val_bin_->offsets());
}
multi_val_bin_subset_->CopySubrow(
multi_val_bin_.get(), bagging_use_indices,
bagging_indices_cnt);
// avoid to copy subset many times
is_subrow_copied_ = true;
}
} else {
is_use_subcol_ = true;
std::vector<uint32_t> upper_bound;
std::vector<uint32_t> lower_bound;
std::vector<uint32_t> delta;
std::vector<uint32_t> offsets;
hist_move_src_.clear();
hist_move_dest_.clear();
hist_move_size_.clear();
const int offset = multi_val_bin_->IsSparse() ? 1 : 0;
int num_total_bin = offset;
int new_num_total_bin = offset;
offsets.push_back(static_cast<uint32_t>(new_num_total_bin));
for (int i : feature_groups_contained_) {
int f_start = group_feature_start[i];
if (feature_groups[i]->is_multi_val_) {
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
const auto& bin_mapper = feature_groups[i]->bin_mappers_[j];
if (i == 0 && j == 0 && bin_mapper->GetMostFreqBin() > 0) {
num_total_bin = 1;
}
int cur_num_bin = bin_mapper->num_bin();
if (bin_mapper->GetMostFreqBin() == 0) {
cur_num_bin -= offset;
}
num_total_bin += cur_num_bin;
if (is_feature_used[f_start + j]) {
new_num_total_bin += cur_num_bin;
offsets.push_back(static_cast<uint32_t>(new_num_total_bin));
lower_bound.push_back(num_total_bin - cur_num_bin);
upper_bound.push_back(num_total_bin);
hist_move_src_.push_back(
(new_num_total_bin - cur_num_bin) * 2);
hist_move_dest_.push_back((num_total_bin - cur_num_bin) *
2);
hist_move_size_.push_back(cur_num_bin * 2);
delta.push_back(num_total_bin - new_num_total_bin);
}
}
} else {
bool is_group_used = false;
for (int j = 0; j < feature_groups[i]->num_feature_; ++j) {
if (is_feature_used[f_start + j]) {
is_group_used = true;
break;
}
}
int cur_num_bin = feature_groups[i]->bin_offsets_.back() - offset;
num_total_bin += cur_num_bin;
if (is_group_used) {
new_num_total_bin += cur_num_bin;
offsets.push_back(static_cast<uint32_t>(new_num_total_bin));
lower_bound.push_back(num_total_bin - cur_num_bin);
upper_bound.push_back(num_total_bin);
hist_move_src_.push_back(
(new_num_total_bin - cur_num_bin) * 2);
hist_move_dest_.push_back((num_total_bin - cur_num_bin) *
2);
hist_move_size_.push_back(cur_num_bin * 2);
delta.push_back(num_total_bin - new_num_total_bin);
}
}
}
// avoid out of range
lower_bound.push_back(num_total_bin);
upper_bound.push_back(num_total_bin);
data_size_t num_data = is_use_subrow_ ? bagging_indices_cnt : num_data_;
if (multi_val_bin_subset_ == nullptr) {
multi_val_bin_subset_.reset(multi_val_bin_->CreateLike(
num_data, new_num_total_bin, num_used, sum_used_dense_ratio, offsets));
} else {
multi_val_bin_subset_->ReSize(num_data, new_num_total_bin,
num_used, sum_used_dense_ratio, offsets);
}
if (is_use_subrow_) {
multi_val_bin_subset_->CopySubrowAndSubcol(
multi_val_bin_.get(), bagging_use_indices,
bagging_indices_cnt, used_feature_index, lower_bound,
upper_bound, delta);
// may need to recopy subset
is_subrow_copied_ = false;
} else {
multi_val_bin_subset_->CopySubcol(
multi_val_bin_.get(), used_feature_index, lower_bound, upper_bound, delta);
}
}
}
void TrainingShareStates::CalcBinOffsets(const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
std::vector<uint32_t>* offsets, bool in_is_col_wise) {
offsets->clear();
feature_hist_offsets_.clear();
if (in_is_col_wise) {
uint32_t cur_num_bin = 0;
uint32_t hist_cur_num_bin = 0;
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
const std::unique_ptr<FeatureGroup>& feature_group = feature_groups[group];
if (feature_group->is_multi_val_) {
if (feature_group->is_dense_multi_val_) {
for (int i = 0; i < feature_group->num_feature_; ++i) {
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
if (group == 0 && i == 0 && bin_mapper->GetMostFreqBin() > 0) {
cur_num_bin += 1;
hist_cur_num_bin += 1;
}
offsets->push_back(cur_num_bin);
feature_hist_offsets_.push_back(hist_cur_num_bin);
int num_bin = bin_mapper->num_bin();
hist_cur_num_bin += num_bin;
if (bin_mapper->GetMostFreqBin() == 0) {
feature_hist_offsets_.back() += 1;
}
cur_num_bin += num_bin;
}
offsets->push_back(cur_num_bin);
CHECK(cur_num_bin == feature_group->bin_offsets_.back());
} else {
cur_num_bin += 1;
hist_cur_num_bin += 1;
for (int i = 0; i < feature_group->num_feature_; ++i) {
offsets->push_back(cur_num_bin);
feature_hist_offsets_.push_back(hist_cur_num_bin);
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
int num_bin = bin_mapper->num_bin();
if (bin_mapper->GetMostFreqBin() == 0) {
num_bin -= 1;
}
hist_cur_num_bin += num_bin;
cur_num_bin += num_bin;
}
offsets->push_back(cur_num_bin);
CHECK(cur_num_bin == feature_group->bin_offsets_.back());
}
} else {
for (int i = 0; i < feature_group->num_feature_; ++i) {
feature_hist_offsets_.push_back(hist_cur_num_bin + feature_group->bin_offsets_[i]);
}
hist_cur_num_bin += feature_group->bin_offsets_.back();
}
}
feature_hist_offsets_.push_back(hist_cur_num_bin);
num_hist_total_bin_ = static_cast<int>(feature_hist_offsets_.back());
} else {
double sum_dense_ratio = 0.0f;
int ncol = 0;
for (int gid = 0; gid < static_cast<int>(feature_groups.size()); ++gid) {
if (feature_groups[gid]->is_multi_val_) {
ncol += feature_groups[gid]->num_feature_;
} else {
++ncol;
}
for (int fid = 0; fid < feature_groups[gid]->num_feature_; ++fid) {
const auto& bin_mapper = feature_groups[gid]->bin_mappers_[fid];
sum_dense_ratio += 1.0f - bin_mapper->sparse_rate();
}
}
sum_dense_ratio /= ncol;
const bool is_sparse_row_wise = (1.0f - sum_dense_ratio) >=
MultiValBin::multi_val_bin_sparse_threshold ? 1 : 0;
if (is_sparse_row_wise) {
int cur_num_bin = 1;
uint32_t hist_cur_num_bin = 1;
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
const std::unique_ptr<FeatureGroup>& feature_group = feature_groups[group];
if (feature_group->is_multi_val_) {
for (int i = 0; i < feature_group->num_feature_; ++i) {
offsets->push_back(cur_num_bin);
feature_hist_offsets_.push_back(hist_cur_num_bin);
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
int num_bin = bin_mapper->num_bin();
if (bin_mapper->GetMostFreqBin() == 0) {
num_bin -= 1;
}
cur_num_bin += num_bin;
hist_cur_num_bin += num_bin;
}
} else {
offsets->push_back(cur_num_bin);
cur_num_bin += feature_group->bin_offsets_.back() - 1;
for (int i = 0; i < feature_group->num_feature_; ++i) {
feature_hist_offsets_.push_back(hist_cur_num_bin + feature_group->bin_offsets_[i] - 1);
}
hist_cur_num_bin += feature_group->bin_offsets_.back() - 1;
}
}
offsets->push_back(cur_num_bin);
feature_hist_offsets_.push_back(hist_cur_num_bin);
} else {
int cur_num_bin = 0;
uint32_t hist_cur_num_bin = 0;
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
const std::unique_ptr<FeatureGroup>& feature_group = feature_groups[group];
if (feature_group->is_multi_val_) {
for (int i = 0; i < feature_group->num_feature_; ++i) {
const std::unique_ptr<BinMapper>& bin_mapper = feature_group->bin_mappers_[i];
if (group == 0 && i == 0 && bin_mapper->GetMostFreqBin() > 0) {
cur_num_bin += 1;
hist_cur_num_bin += 1;
}
offsets->push_back(cur_num_bin);
feature_hist_offsets_.push_back(hist_cur_num_bin);
int num_bin = bin_mapper->num_bin();
cur_num_bin += num_bin;
hist_cur_num_bin += num_bin;
if (bin_mapper->GetMostFreqBin() == 0) {
feature_hist_offsets_.back() += 1;
}
}
} else {
offsets->push_back(cur_num_bin);
cur_num_bin += feature_group->bin_offsets_.back();
for (int i = 0; i < feature_group->num_feature_; ++i) {
feature_hist_offsets_.push_back(hist_cur_num_bin + feature_group->bin_offsets_[i]);
}
hist_cur_num_bin += feature_group->bin_offsets_.back();
}
}
offsets->push_back(cur_num_bin);
feature_hist_offsets_.push_back(hist_cur_num_bin);
}
num_hist_total_bin_ = static_cast<int>(feature_hist_offsets_.back());
}
#ifdef USE_CUDA
column_hist_offsets_ = *offsets;
#endif // USE_CUDA
}
void TrainingShareStates::SetMultiValBin(MultiValBin* bin, data_size_t num_data,
const std::vector<std::unique_ptr<FeatureGroup>>& feature_groups,
bool dense_only, bool sparse_only, const int num_grad_quant_bins) {
num_threads = OMP_NUM_THREADS();
if (bin == nullptr) {
return;
}
std::vector<int> feature_groups_contained;
for (int group = 0; group < static_cast<int>(feature_groups.size()); ++group) {
const auto& feature_group = feature_groups[group];
if (feature_group->is_multi_val_) {
if (!dense_only) {
feature_groups_contained.push_back(group);
}
} else if (!sparse_only) {
feature_groups_contained.push_back(group);
}
}
num_total_bin_ += bin->num_bin();
num_elements_per_row_ += bin->num_element_per_row();
multi_val_bin_wrapper_.reset(new MultiValBinWrapper(
bin, num_data, feature_groups_contained, num_grad_quant_bins));
}
} // namespace LightGBM